diff --git a/.changeset/tough-boats-shop.md b/.changeset/tough-boats-shop.md new file mode 100644 index 0000000000..45a16fc965 --- /dev/null +++ b/.changeset/tough-boats-shop.md @@ -0,0 +1,5 @@ +--- +"chainlink": minor +--- + +start of next release diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ea3f1419e8..94c0655bad 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -11,8 +11,9 @@ # Services /core/services/directrequest @smartcontractkit/keepers -/core/services/feeds @smartcontractkit/FMS +/core/services/feeds @smartcontractkit/op-core @eutopian @yevshev /core/services/synchronization/telem @smartcontractkit/realtime +/core/capabilities/ccip @smartcontractkit/ccip-offchain # To be deprecated in Chainlink V3 /core/services/fluxmonitorv2 @smartcontractkit/foundations @@ -23,9 +24,9 @@ /core/services/pg @smartcontractkit/foundations @samsondav /core/services/pipeline @smartcontractkit/foundations @smartcontractkit/bix-framework /core/services/telemetry @smartcontractkit/realtime -/core/services/relay/evm/mercury @smartcontractkit/mercury-team +/core/services/relay/evm/mercury @smartcontractkit/data-streams-engineers /core/services/webhook @smartcontractkit/foundations @smartcontractkit/bix-framework -/core/services/llo @smartcontractkit/mercury-team +/core/services/llo @smartcontractkit/data-streams-engineers # VRF-related services /core/services/vrf @smartcontractkit/vrf-team @@ -67,17 +68,16 @@ core/scripts/gateway @smartcontractkit/functions /contracts/src/v0.8/functions @smartcontractkit/functions /contracts/**/*functions* @smartcontractkit/functions -/contracts/**/*llo-feeds* @smartcontrackit/mercury-team +/contracts/**/*llo-feeds* @smartcontractkit/data-streams-engineers /contracts/**/*vrf* @smartcontractkit/vrf-team /contracts/**/*l2ep* @smartcontractkit/bix-ship -# TODO: replace with a team tag when ready -/contracts/**/*keystone* @archseer @bolekk @patrick-dowell +/contracts/**/*keystone* @smartcontractkit/keystone /contracts/src/v0.8/automation @smartcontractkit/keepers /contracts/src/v0.8/functions @smartcontractkit/functions # TODO: interfaces folder, folder should be removed and files moved to the correct folders /contracts/src/v0.8/l2ep @chris-de-leon-cll -/contracts/src/v0.8/llo-feeds @smartcontractkit/mercury-team +/contracts/src/v0.8/llo-feeds @smartcontractkit/data-streams-engineers # TODO: mocks folder, folder should be removed and files moved to the correct folders /contracts/src/v0.8/operatorforwarder @smartcontractkit/data-feeds-engineers /contracts/src/v0.8/shared @RensR @matYang @RayXpub @elatoskinas @@ -93,11 +93,18 @@ core/scripts/gateway @smartcontractkit/functions # Tests -/integration-tests/**/*ccip* @smartcontractkit/test-tooling-team @jasonmci @smartcontractkit/ccip +/integration-tests/ @smartcontractkit/test-tooling-team +/integration-tests/ccip-tests @smartcontractkit/ccip-offchain /integration-tests/**/*keeper* @smartcontractkit/keepers /integration-tests/**/*automation* @smartcontractkit/keepers /integration-tests/**/*lm_* @smartcontractkit/liquidity-manager +# Deployment tooling +# Initially the common structures owned by CCIP +/integration-tests/deployment @smartcontractkit/ccip +/integration-tests/deployment/ccip @smartcontractkit/ccip +# TODO: As more products add their deployment logic here, add the team as an owner + # CI/CD /.github/** @smartcontractkit/releng @smartcontractkit/test-tooling-team @jasonmci @smartcontractkit/ccip /.github/workflows/integration-tests.yml @smartcontractkit/test-tooling-team @jasonmci diff --git a/.github/E2E_TESTS_ON_GITHUB_CI.md b/.github/E2E_TESTS_ON_GITHUB_CI.md new file mode 100644 index 0000000000..02144eff64 --- /dev/null +++ b/.github/E2E_TESTS_ON_GITHUB_CI.md @@ -0,0 +1,58 @@ +# E2E Tests on GitHub CI + +E2E tests are executed on GitHub CI using the [E2E Tests Reusable Workflow](#about-the-reusable-workflow) or dedicated workflows. + +## Automatic workflows + +These workflows are designed to run automatically at crucial stages of the software development process, such as on every commit in a PR, nightly or before release. + +### PR E2E Tests + +Run on every commit in a PR to ensure changes do not introduce regressions. + +[Link](https://github.com/smartcontractkit/chainlink/blob/develop/.github/workflows/integration-tests.yml) + +### Nightly E2E Tests + +Conducted nightly to catch issues that may develop over time or with accumulated changes. + +[Link](https://github.com/smartcontractkit/chainlink/blob/develop/.github/workflows/run-nightly-e2e-tests.yml) + +### Release E2E Tests + +This section contains automatic workflows triggering E2E tests at release. + +#### Client Compatibility Tests + +[Link](https://github.com/smartcontractkit/chainlink/actions/workflows/client-compatibility-tests.yml) + +## On-Demand Workflows + +Triggered manually by QA for specific testing needs. + +**Examples:** + +- [On-Demand Automation Tests](https://github.com/smartcontractkit/chainlink/actions/workflows/automation-ondemand-tests.yml) +- [CCIP Chaos Tests](https://github.com/smartcontractkit/chainlink/actions/workflows/ccip-chaos-tests.yml) +- [OCR Soak Tests](https://github.com/smartcontractkit/chainlink/actions/workflows/on-demand-ocr-soak-test.yml) +- [VRFv2Plus Smoke Tests](https://github.com/smartcontractkit/chainlink/actions/workflows/on-demand-vrfv2plus-smoke-tests.yml) + +## Test Configs + +E2E tests utilize TOML files to define their parameters. Each test is equipped with a default TOML config, which can be overridden by specifying an alternative TOML config. This allows for running tests with varied parameters, such as on a non-default blockchain network. For tests executed on GitHub CI, both the default configs and any override configs must reside within the git repository. The `test_config_override_path` workflow input is used to provide a path to an override config. + +Config overrides should be stored in `testconfig/*/overrides/*.toml`. Placing files here will not trigger a rebuild of the test runner image. + +**Important Note:** The use of `base64Config` input is deprecated in favor of `test_config_override_path`. For more details, refer to [the decision log](https://smartcontract-it.atlassian.net/wiki/spaces/TT/pages/927596563/Storing+All+Test+Configs+In+Git). + +To learn more about test configs see [CTF Test Config](https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/lib/config/README.md). + +## Test Secrets + +For security reasons, test secrets and sensitive information are not stored directly within the test config TOML files. Instead, these secrets are securely injected into tests using environment variables. For a detailed explanation on managing test secrets, refer to our [Test Secrets documentation](https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/lib/config/README.md#test-secrets). + +If you need to run a GitHub workflow using custom secrets, please refer to the [guide on running GitHub workflows with your test secrets](https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/lib/config/README.md#run-github-workflow-with-your-test-secrets). + +## About the E2E Test Reusable Workflow + +For information on the E2E Test Reusable Workflow, visit the documentation in the [smartcontractkit/.github repository](https://github.com/smartcontractkit/.github/blob/main/.github/workflows/README.md). diff --git a/.github/actions/build-chainlink-image/action.yml b/.github/actions/build-chainlink-image/action.yml index cfbf0acfff..7381d887a1 100644 --- a/.github/actions/build-chainlink-image/action.yml +++ b/.github/actions/build-chainlink-image/action.yml @@ -37,7 +37,7 @@ runs: AWS_ROLE_TO_ASSUME: ${{ inputs.AWS_ROLE_TO_ASSUME }} - name: Build Image if: steps.check-image.outputs.exists != 'true' - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/build-image@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 + uses: smartcontractkit/.github/actions/ctf-build-image@1a26fe378d7ebdc34ab1fe31ec4a6d1c376199f8 # ctf-build-image@0.0.0 with: cl_repo: smartcontractkit/ccip cl_ref: ${{ inputs.git_commit_sha }} @@ -51,4 +51,4 @@ runs: shell: sh run: | echo "### Chainlink node image tag used for this test run :link:" >>$GITHUB_STEP_SUMMARY - echo "\`${GITHUB_SHA}\`" >>$GITHUB_STEP_SUMMARY + echo "\`${{inputs.git_commit_sha}}\`" >>$GITHUB_STEP_SUMMARY diff --git a/.github/actions/build-sign-publish-chainlink/action.yml b/.github/actions/build-sign-publish-chainlink/action.yml index 67ca3068ef..302f436390 100644 --- a/.github/actions/build-sign-publish-chainlink/action.yml +++ b/.github/actions/build-sign-publish-chainlink/action.yml @@ -51,23 +51,11 @@ inputs: description: When set to the string boolean value of "true", the resulting build image will be signed default: "false" required: false - cosign-private-key: - description: The private key to be used with cosign to sign the image - required: false - cosign-public-key: - description: The public key to be used with cosign for verification - required: false - cosign-password: - description: The password to decrypt the cosign private key needed to sign the image - required: false - sign-method: - description: Build image will be signed using keypair or keyless methods - default: "keypair" - required: true verify-signature: description: When set to the string boolean value of "true", the resulting build image signature will be verified default: "false" required: false + outputs: docker-image-tag: description: The docker image tag that was built and pushed @@ -84,6 +72,8 @@ runs: # See https://docs.github.com/en/actions/learn-github-actions/workflow-commands-for-github-actions#multiline-strings run: | SHARED_IMAGES=${{ inputs.ecr-hostname }}/${{ inputs.ecr-image-name }} + OIDC_ISSUER=https://token.actions.githubusercontent.com + OIDC_IDENTITY=https://github.com/smartcontractkit/chainlink/.github/workflows/build-publish.yml@${{ github.ref }} SHARED_TAG_LIST=$(cat << EOF type=ref,event=branch,suffix=${{ inputs.ecr-tag-suffix }} @@ -101,6 +91,9 @@ runs: echo "$SHARED_IMAGES" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV + echo "oidc-issuer=${OIDC_ISSUER}" >> $GITHUB_ENV + echo "oidc-identity=${OIDC_IDENTITY}" >> $GITHUB_ENV + echo "shared-tag-list<> $GITHUB_ENV echo "$SHARED_TAG_LIST" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV @@ -171,7 +164,9 @@ runs: run: | IMAGES_NAME_RAW=${{ fromJSON(steps.buildpush-root.outputs.metadata)['image.name'] }} IMAGE_NAME=$(echo "$IMAGES_NAME_RAW" | cut -d"," -f1) + IMAGE_DIGEST=${{ fromJSON(steps.buildpush-root.outputs.metadata)['containerimage.digest'] }} echo "root_image_name=${IMAGE_NAME}" >> $GITHUB_ENV + echo "root_image_digest=${IMAGE_DIGEST}" >> $GITHUB_ENV - name: Generate docker metadata for non-root image id: meta-nonroot @@ -217,6 +212,7 @@ runs: IMAGE_NAME=$(echo "$IMAGES_NAME_RAW" | cut -d"," -f1) IMAGE_TAG=$(echo "$IMAGES_NAME_RAW" | cut -d":" -f2) echo "nonroot_image_name=${IMAGE_NAME}" >> $GITHUB_ENV + echo "nonroot_image_digest=${IMAGE_DIGEST}" >> $GITHUB_ENV echo '### Docker Image' >> $GITHUB_STEP_SUMMARY echo "Image Name: ${IMAGE_NAME}" >> $GITHUB_STEP_SUMMARY echo "Image Digest: ${IMAGE_DIGEST}" >> $GITHUB_STEP_SUMMARY @@ -239,74 +235,36 @@ runs: - if: inputs.sign-images == 'true' name: Install cosign - uses: sigstore/cosign-installer@e1523de7571e31dbe865fd2e80c5c7c23ae71eb4 # v3.4.0 + uses: sigstore/cosign-installer@4959ce089c160fddf62f7b42464195ba1a56d382 # v3.6.0 with: - cosign-release: "v1.6.0" + cosign-release: "v2.4.0" - - if: inputs.sign-images == 'true' && inputs.sign-method == 'keypair' - name: Sign the published root Docker image using keypair method - shell: sh - env: - COSIGN_PASSWORD: "${{ inputs.cosign-password }}" - run: | - echo "${{ inputs.cosign-private-key }}" > cosign.key - cosign sign --key cosign.key "${{ env.root_image_name }}" - rm -f cosign.key - - - if: inputs.verify-signature == 'true' && inputs.sign-method == 'keypair' - name: Verify the signature of the published root Docker image using keypair - shell: sh - run: | - echo "${{ inputs.cosign-public-key }}" > cosign.key - cosign verify --key cosign.key "${{ env.root_image_name }}" - rm -f cosign.key - - - if: inputs.sign-images == 'true' && inputs.sign-method == 'keyless' + # This automatically signs the image with the correct OIDC provider from Github + - if: inputs.sign-images == 'true' name: Sign the published root Docker image using keyless method shell: sh - env: - COSIGN_EXPERIMENTAL: 1 run: | - cosign sign "${{ env.root_image_name }}" + cosign sign "${{ env.root_image_name }}" --yes - - if: inputs.verify-signature == 'true' && inputs.sign-method == 'keyless' + - if: inputs.verify-signature == 'true' name: Verify the signature of the published root Docker image using keyless shell: sh - env: - COSIGN_EXPERIMENTAL: 1 - run: | - cosign verify "${{ env.root_image_name }}" - - - if: inputs.sign-images == 'true' && inputs.sign-method == 'keypair' - name: Sign the published non-root Docker image using keypair method - shell: sh - env: - COSIGN_PASSWORD: "${{ inputs.cosign-password }}" - run: | - echo "${{ inputs.cosign-private-key }}" > cosign.key - cosign sign --key cosign.key "${{ env.nonroot_image_name }}" - rm -f cosign.key - - - if: inputs.verify-signature == 'true' && inputs.sign-method == 'keypair' - name: Verify the signature of the published non-root Docker image using keypair - shell: sh run: | - echo "${{ inputs.cosign-public-key }}" > cosign.key - cosign verify --key cosign.key "${{ env.nonroot_image_name }}" - rm -f cosign.key + cosign verify "${{ env.root_image_name }}" \ + --certificate-oidc-issuer ${{ env.oidc-issuer }} \ + --certificate-identity "${{ env.oidc-identity }}" - - if: inputs.sign-images == 'true' && inputs.sign-method == 'keyless' + # This automatically signs the image with the correct OIDC provider from Github + - if: inputs.sign-images == 'true' name: Sign the published non-root Docker image using keyless method shell: sh - env: - COSIGN_EXPERIMENTAL: 1 run: | - cosign sign "${{ env.nonroot_image_name }}" + cosign sign "${{ env.nonroot_image_name }}" --yes - - if: inputs.verify-signature == 'true' && inputs.sign-method == 'keyless' + - if: inputs.verify-signature == 'true' name: Verify the signature of the published non-root Docker image using keyless shell: sh - env: - COSIGN_EXPERIMENTAL: 1 run: | - cosign verify "${{ env.nonroot_image_name }}" + cosign verify "${{ env.nonroot_image_name }}" \ + --certificate-oidc-issuer ${{ env.oidc-issuer }} \ + --certificate-identity "${{ env.oidc-identity }}" diff --git a/.github/actions/build-test-image/action.yml b/.github/actions/build-test-image/action.yml deleted file mode 100644 index b719e296f8..0000000000 --- a/.github/actions/build-test-image/action.yml +++ /dev/null @@ -1,123 +0,0 @@ -name: Build Test Image -description: A composite action that allows building and publishing the test remote runner image - -inputs: - repository: - description: The docker repository for the image - default: chainlink-ccip-tests - required: false - tag: - description: The tag to use by default and to use for checking image existance - default: ${{ github.sha }} - required: false - other_tags: - description: Other tags to push if needed - required: false - suites: - description: The test suites to build into the image - default: chaos migration reorg smoke soak benchmark load ccip-tests/load ccip-tests/smoke ccip-tests/chaos - required: false - QA_AWS_ROLE_TO_ASSUME: - description: The AWS role to assume as the CD user, if any. Used in configuring the docker/login-action - required: true - QA_AWS_REGION: - description: The AWS region the ECR repository is located in, should only be needed for public ECR repositories, used in configuring docker/login-action - required: true - QA_AWS_ACCOUNT_NUMBER: - description: The AWS region the ECR repository is located in, should only be needed for public ECR repositories, used in configuring docker/login-action - required: true - -runs: - using: composite - steps: - - # Base Test Image Logic - - name: Get CTF Version - id: version - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/mod-version@fc3e0df622521019f50d772726d6bf8dc919dd38 # v2.3.19 - with: - go-project-path: ./integration-tests - module-name: github.com/smartcontractkit/chainlink-testing-framework/lib - enforce-semantic-tag: false - - name: Get CTF sha - if: steps.version.outputs.is_semantic == 'false' - id: short_sha - env: - VERSION: ${{ steps.version.outputs.version }} - shell: bash - run: | - short_sha="${VERSION##*-}" - echo "short sha is: ${short_sha}" - echo "short_sha=${short_sha}" >> "$GITHUB_OUTPUT" - - name: Checkout chainlink-testing-framework - if: steps.version.outputs.is_semantic == 'false' - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - repository: smartcontractkit/chainlink-testing-framework - ref: main - fetch-depth: 0 - path: ctf - - name: Get long sha - if: steps.version.outputs.is_semantic == 'false' - id: long_sha - env: - SHORT_SHA: ${{ steps.short_sha.outputs.short_sha }} - shell: bash - run: | - cd ctf - long_sha=$(git rev-parse ${SHORT_SHA}) - echo "sha is: ${long_sha}" - echo "long_sha=${long_sha}" >> "$GITHUB_OUTPUT" - - name: Check if test base image exists - if: steps.version.outputs.is_semantic == 'false' - id: check-base-image - uses: smartcontractkit/chainlink-github-actions/docker/image-exists@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - repository: test-base-image - tag: ${{ steps.long_sha.outputs.long_sha }} - AWS_REGION: ${{ inputs.QA_AWS_REGION }} - AWS_ROLE_TO_ASSUME: ${{ inputs.QA_AWS_ROLE_TO_ASSUME }} - - name: Build Base Image - if: steps.version.outputs.is_semantic == 'false' && steps.check-base-image.outputs.exists == 'false' - uses: smartcontractkit/chainlink-github-actions/docker/build-push@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - env: - BASE_IMAGE_NAME: ${{ inputs.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ inputs.QA_AWS_REGION }}.amazonaws.com/test-base-image:${{ steps.long_sha.outputs.long_sha }} - with: - tags: ${{ env.BASE_IMAGE_NAME }} - file: ctf/k8s/Dockerfile.base - AWS_REGION: ${{ inputs.QA_AWS_REGION }} - AWS_ROLE_TO_ASSUME: ${{ inputs.QA_AWS_ROLE_TO_ASSUME }} - # End Base Image Logic - - # Test Runner Logic - - name: Check if image exists - id: check-image - uses: smartcontractkit/chainlink-github-actions/docker/image-exists@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - repository: ${{ inputs.repository }} - tag: ${{ inputs.tag }} - AWS_REGION: ${{ inputs.QA_AWS_REGION }} - AWS_ROLE_TO_ASSUME: ${{ inputs.QA_AWS_ROLE_TO_ASSUME }} - - name: Build and Publish Test Runner - if: steps.check-image.outputs.exists == 'false' - uses: smartcontractkit/chainlink-github-actions/docker/build-push@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - tags: | - ${{ inputs.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ inputs.QA_AWS_REGION }}.amazonaws.com/${{ inputs.repository }}:${{ inputs.tag }} - ${{ inputs.other_tags }} - file: ./integration-tests/test.Dockerfile - build-args: | - BASE_IMAGE=${{ inputs.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ inputs.QA_AWS_REGION }}.amazonaws.com/test-base-image - IMAGE_VERSION=${{ steps.long_sha.outputs.long_sha || steps.version.outputs.version }} - SUITES="${{ inputs.suites }}" - AWS_REGION: ${{ inputs.QA_AWS_REGION }} - AWS_ROLE_TO_ASSUME: ${{ inputs.QA_AWS_ROLE_TO_ASSUME }} - - name: Print Image Built - shell: sh - env: - INPUTS_REPOSITORY: ${{ inputs.repository }} - INPUTS_TAG: ${{ inputs.tag }} - run: | - echo "### ${INPUTS_REPOSITORY} image tag for this test run :ship:" >>$GITHUB_STEP_SUMMARY - echo "\`${INPUTS_TAG}\`" >>$GITHUB_STEP_SUMMARY - # End Test Runner Logic diff --git a/.github/actions/golangci-lint/action.yml b/.github/actions/golangci-lint/action.yml index f3697ed719..3ada575877 100644 --- a/.github/actions/golangci-lint/action.yml +++ b/.github/actions/golangci-lint/action.yml @@ -80,4 +80,4 @@ runs: hostname: ${{ inputs.gc-host }} org-id: ${{ inputs.gc-org-id }} this-job-name: ${{ inputs.name }} - continue-on-error: true + continue-on-error: true \ No newline at end of file diff --git a/.github/actions/goreleaser-build-sign-publish/README.md b/.github/actions/goreleaser-build-sign-publish/README.md index d6bf7e6fd4..189578391f 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 4159674e76..59fbc5f76a 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 @@ -14,7 +14,7 @@ inputs: required: false cosign-version: description: The cosign version - default: v2.2.2 + default: v2.4.0 required: false macos-sdk-dir: description: The macos sdk directory @@ -29,10 +29,13 @@ inputs: description: The docker registry default: localhost:5001 required: false - # snapshot inputs - enable-goreleaser-snapshot: - description: Enable goreleaser build / release snapshot - default: "false" + docker-image-name: + description: The docker image name + default: chainlink + required: false + docker-image-tag: + description: The docker image tag + default: develop required: false # goreleaser inputs goreleaser-exec: @@ -43,27 +46,22 @@ inputs: description: "The goreleaser configuration yaml" default: ".goreleaser.yaml" required: false - # signing inputs - enable-cosign: - description: Enable signing of docker images + enable-goreleaser-snapshot: + description: Enable goreleaser build / release snapshot default: "false" required: false - cosign-private-key: - description: The private key to be used with cosign to sign the image + enable-goreleaser-split: + description: Enable goreleaser split and merge builds + default: "false" required: false - cosign-public-key: - description: The public key to be used with cosign for verification + goreleaser-split-arch: + description: The architecture to split the goreleaser build required: false - cosign-password: - description: The password to decrypt the cosign private key needed to sign the image + # signing inputs + enable-cosign: + description: Enable signing of docker images + default: "false" required: false -outputs: - goreleaser-metadata: - description: "Build result metadata" - value: ${{ steps.goreleaser.outputs.metadata }} - goreleaser-artifacts: - description: "Build result artifacts" - value: ${{ steps.goreleaser.outputs.artifacts }} runs: using: composite steps: @@ -76,7 +74,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 @@ -89,7 +87,7 @@ runs: version: ${{ inputs.zig-version }} - name: Setup cosign if: inputs.enable-cosign == 'true' - uses: sigstore/cosign-installer@e1523de7571e31dbe865fd2e80c5c7c23ae71eb4 # v3.4.0 + uses: sigstore/cosign-installer@4959ce089c160fddf62f7b42464195ba1a56d382 # v3.6.0 with: cosign-release: ${{ inputs.cosign-version }} - name: Login to docker registry @@ -97,19 +95,27 @@ runs: uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 with: registry: ${{ inputs.docker-registry }} - - name: Goreleaser release - id: goreleaser + - name: Set goreleaser split env + if: inputs.enable-goreleaser-split == 'true' + shell: bash + run: | + echo "GOOS=linux" | tee -a $GITHUB_ENV + echo "GOARCH=${{ inputs.goreleaser-split-arch }}" | tee -a $GITHUB_ENV + - name: Install syft + uses: anchore/sbom-action/download-syft@61119d458adab75f756bc0b9e4bde25725f86a7a # v0.17.2 + - name: Run goreleaser release shell: bash env: - ENABLE_COSIGN: ${{ inputs.enable-cosign }} ENABLE_GORELEASER_SNAPSHOT: ${{ inputs.enable-goreleaser-snapshot }} + ENABLE_GORELEASER_SPLIT: ${{ inputs.enable-goreleaser-split }} ENABLE_DOCKER_PUBLISH: ${{ inputs.enable-docker-publish }} IMAGE_PREFIX: ${{ inputs.docker-registry }} + IMAGE_NAME: ${{ inputs.docker-image-name }} + IMAGE_TAG: ${{ inputs.docker-image-tag }} GORELEASER_EXEC: ${{ inputs.goreleaser-exec }} GORELEASER_CONFIG: ${{ inputs.goreleaser-config }} - COSIGN_PASSWORD: ${{ inputs.cosign-password }} - COSIGN_PUBLIC_KEY: ${{ inputs.cosign-public-key }} - COSIGN_PRIVATE_KEY: ${{ inputs.cosign-private-key }} + GORELEASER_KEY: ${{ inputs.goreleaser-key }} + GITHUB_TOKEN: ${{ github.token }} MACOS_SDK_DIR: ${{ inputs.macos-sdk-dir }} run: | # https://github.com/orgs/community/discussions/24950 diff --git a/.github/actions/goreleaser-build-sign-publish/action_utils b/.github/actions/goreleaser-build-sign-publish/action_utils index 4aac78d6fc..51c7c90aa1 100755 --- a/.github/actions/goreleaser-build-sign-publish/action_utils +++ b/.github/actions/goreleaser-build-sign-publish/action_utils @@ -2,10 +2,9 @@ set -x set -euo pipefail -ENABLE_COSIGN=${ENABLE_COSIGN:-false} ENABLE_GORELEASER_SNAPSHOT=${ENABLE_GORELEASER_SNAPSHOT:-false} +ENABLE_GORELEASER_SPLIT=${ENABLE_GORELEASER_SPLIT:-false} ENABLE_DOCKER_PUBLISH=${ENABLE_DOCKER_PUBLISH:-false} -COSIGN_PASSWORD=${COSIGN_PASSWORD:-""} GORELEASER_EXEC=${GORELEASER_EXEC:-goreleaser} GORELEASER_CONFIG=${GORELEASER_CONFIG:-.goreleaser.yaml} IMAGE_PREFIX=${IMAGE_PREFIX:-"localhost:5001"} @@ -27,8 +26,12 @@ _publish_snapshot_manifests() { local docker_manifest_extra_args=$DOCKER_MANIFEST_EXTRA_ARGS local full_sha=$(git rev-parse HEAD) local images=$(docker images --filter "label=org.opencontainers.image.revision=$full_sha" --format "{{.Repository}}:{{.Tag}}" | sort) - local arches=(amd64 arm64) local raw_manifest_lists="" + if [[ $ENABLE_GORELEASER_SPLIT == "true" ]]; then + local arches=(${GOARCH:-""}) + else + local arches=(amd64 arm64) + fi for image in $images; do for arch in "${arches[@]}"; do image=${image%"-$arch"} @@ -51,25 +54,28 @@ _publish_snapshot_manifests() { # wrapper function to invoke goreleaser release goreleaser_release() { - if [[ $ENABLE_COSIGN == "true" ]]; then - echo "$COSIGN_PUBLIC_KEY" > cosign.pub - echo "$COSIGN_PRIVATE_KEY" > cosign.key + goreleaser_flags=() + + # set goreleaser flags + if [[ $ENABLE_GORELEASER_SNAPSHOT == "true" ]]; then + goreleaser_flags+=("--snapshot") + goreleaser_flags+=("--clean") + fi + if [[ $ENABLE_GORELEASER_SPLIT == "true" ]]; then + goreleaser_flags+=("--split") fi + flags=$(printf "%s " "${goreleaser_flags[@]}") + flags=$(echo "$flags" | sed 's/ *$//') + if [[ -n $MACOS_SDK_DIR ]]; then MACOS_SDK_DIR=$(echo "$(cd "$(dirname "$MACOS_SDK_DIR")" || exit; pwd)/$(basename "$MACOS_SDK_DIR")") fi - if [[ $ENABLE_GORELEASER_SNAPSHOT == "true" ]]; then - $GORELEASER_EXEC release --snapshot --clean --config "$GORELEASER_CONFIG" "$@" - if [[ $ENABLE_DOCKER_PUBLISH == "true" ]]; then + + $GORELEASER_EXEC release ${flags} --config "$GORELEASER_CONFIG" "$@" + + if [[ $ENABLE_DOCKER_PUBLISH == "true" ]] && [[ $ENABLE_GORELEASER_SNAPSHOT == "true" ]]; then _publish_snapshot_images _publish_snapshot_manifests - fi - else - $GORELEASER_EXEC release --clean --config "$GORELEASER_CONFIG" "$@" - fi - if [[ $ENABLE_COSIGN == "true" ]]; then - rm -rf cosign.pub - rm -rf cosign.key fi } diff --git a/.github/actions/setup-create-base64-config-live-testnets/action.yml b/.github/actions/setup-create-base64-config-live-testnets/action.yml deleted file mode 100644 index 64fc134b46..0000000000 --- a/.github/actions/setup-create-base64-config-live-testnets/action.yml +++ /dev/null @@ -1,144 +0,0 @@ -name: Create Base64 Config -description: A composite action that creates a base64-encoded config to be used by integration tests - -inputs: - runId: - description: The run id - testLogCollect: - description: Whether to always collect logs, even for passing tests - default: "false" - chainlinkImage: - description: The chainlink image to use - default: "public.ecr.aws/chainlink/chainlink" - chainlinkVersion: - description: The git commit sha to use for the image tag - chainlinkPostgresVersion: - description: The postgres version to use with the chainlink node - default: "15.6" - pyroscopeServer: - description: URL of Pyroscope server - pyroscopeEnvironment: - description: Name of Pyroscope environment - pyroscopeKey: - description: Pyroscope server key - lokiEndpoint: - description: Loki push endpoint - lokiTenantId: - description: Loki tenant id - lokiBasicAuth: - description: Loki basic auth - logstreamLogTargets: - description: Where to send logs (e.g. file, loki) - grafanaUrl: - description: Grafana URL - grafanaDashboardUrl: - description: Grafana dashboard URL - grafanaBearerToken: - description: Grafana bearer token - network: - description: Network to run tests on - httpEndpoints: - description: HTTP endpoints to use for network - wsEndpoints: - description: WS endpoints to use for network - fundingKeys: - description: Funding keys to use for network - -runs: - using: composite - steps: - - name: Prepare Base64 TOML override - shell: bash - id: base64-config-override - env: - RUN_ID: ${{ inputs.runId }} - PYROSCOPE_SERVER: ${{ inputs.pyroscopeServer }} - PYROSCOPE_ENVIRONMENT: ${{ inputs.pyroscopeEnvironment }} - PYROSCOPE_KEY: ${{ inputs.pyroscopeKey }} - CHAINLINK_IMAGE: ${{ inputs.chainlinkImage }} - CHAINLINK_VERSION: ${{ inputs.chainlinkVersion }} - CHAINLINK_POSTGRES_VERSION: ${{ inputs.chainlinkPostgresVersion }} - LOKI_ENDPOINT: ${{ inputs.lokiEndpoint }} - LOKI_TENANT_ID: ${{ inputs.lokiTenantId }} - LOKI_BASIC_AUTH: ${{ inputs.lokiBasicAuth }} - LOGSTREAM_LOG_TARGETS: ${{ inputs.logstreamLogTargets }} - GRAFANA_URL: ${{ inputs.grafanaUrl }} - GRAFANA_DASHBOARD_URL: ${{ inputs.grafanaDashboardUrl }} - GRAFANA_BEARER_TOKEN: ${{ inputs.grafanaBearerToken }} - NETWORK: ${{ inputs.network }} - HTTP_ENDPOINTS: ${{ inputs.httpEndpoints }} - WS_ENDPOINTS: ${{ inputs.wsEndpoints }} - FUNDING_KEYS: ${{ inputs.fundingKeys }} - run: | - convert_to_toml_array() { - local IFS=',' - local input_array=($1) - local toml_array_format="[" - - for element in "${input_array[@]}"; do - toml_array_format+="\"$element\"," - done - - toml_array_format="${toml_array_format%,}]" - echo "$toml_array_format" - } - - if [ -n "$PYROSCOPE_SERVER" ]; then - pyroscope_enabled=true - else - pyroscope_enabled=false - fi - - grafana_bearer_token="" - if [ -n "$GRAFANA_BEARER_TOKEN" ]; then - grafana_bearer_token="bearer_token_secret=\"$GRAFANA_BEARER_TOKEN\"" - fi - - cat << EOF > config.toml - [Common] - chainlink_node_funding=0.5 - - [ChainlinkImage] - image="$CHAINLINK_IMAGE" - version="$CHAINLINK_VERSION" - postgres_version="$CHAINLINK_POSTGRES_VERSION" - - [Pyroscope] - enabled=$pyroscope_enabled - server_url="$PYROSCOPE_SERVER" - environment="$PYROSCOPE_ENVIRONMENT" - key_secret="$PYROSCOPE_KEY" - - [Logging] - run_id="$RUN_ID" - - [Logging.LogStream] - log_targets=$(convert_to_toml_array "$LOGSTREAM_LOG_TARGETS") - - [Logging.Loki] - tenant_id="$LOKI_TENANT_ID" - endpoint="$LOKI_URL" - basic_auth_secret="$LOKI_BASIC_AUTH" - - [Logging.Grafana] - base_url="$GRAFANA_URL" - dashboard_url="$GRAFANA_DASHBOARD_URL" - $grafana_bearer_token - - [Network] - selected_networks=["$NETWORK"] - - [Network.RpcHttpUrls] - "$NETWORK" = $(convert_to_toml_array "$HTTP_ENDPOINTS") - - [Network.RpcWsUrls] - "$NETWORK" = $(convert_to_toml_array "$WS_ENDPOINTS") - - [Network.WalletKeys] - "$NETWORK" = $(convert_to_toml_array "$FUNDING_KEYS") - EOF - - BASE64_CONFIG_OVERRIDE=$(cat config.toml | base64 -w 0) - echo ::add-mask::$BASE64_CONFIG_OVERRIDE - echo "BASE64_CONFIG_OVERRIDE=$BASE64_CONFIG_OVERRIDE" >> $GITHUB_ENV - touch .root_dir diff --git a/.github/actions/setup-create-base64-upgrade-config/action.yml b/.github/actions/setup-create-base64-upgrade-config/action.yml deleted file mode 100644 index c2d0bc19f3..0000000000 --- a/.github/actions/setup-create-base64-upgrade-config/action.yml +++ /dev/null @@ -1,125 +0,0 @@ -name: Create Base64 Upgrade Config -description: A composite action that creates a base64-encoded config to be used by Chainlink version upgrade tests - -inputs: - selectedNetworks: - description: The networks to run tests against - chainlinkImage: - description: The chainlink image to upgrade from - default: "public.ecr.aws/chainlink/chainlink" - chainlinkVersion: - description: The git commit sha to use for the image tag - chainlinkPostgresVersion: - description: The postgres version to use with the chainlink node - default: "15.6" - upgradeImage: - description: The chainlink image to upgrade to - default: "public.ecr.aws/chainlink/chainlink" - upgradeVersion: - description: The git commit sha to use for the image tag - runId: - description: The run id - testLogCollect: - description: Whether to always collect logs, even for passing tests - default: "false" - lokiEndpoint: - description: Loki push endpoint - lokiTenantId: - description: Loki tenant id - lokiBasicAuth: - description: Loki basic auth - logstreamLogTargets: - description: Where to send logs (e.g. file, loki) - grafanaUrl: - description: Grafana URL - grafanaDashboardUrl: - description: Grafana dashboard URL - grafanaBearerToken: - description: Grafana bearer token - -runs: - using: composite - steps: - - name: Prepare Base64 TOML override - shell: bash - id: base64-config-override - env: - SELECTED_NETWORKS: ${{ inputs.selectedNetworks }} - CHAINLINK_IMAGE: ${{ inputs.chainlinkImage }} - CHAINLINK_VERSION: ${{ inputs.chainlinkVersion }} - CHAINLINK_POSTGRES_VERSION: ${{ inputs.chainlinkPostgresVersion }} - UPGRADE_IMAGE: ${{ inputs.upgradeImage }} - UPGRADE_VERSION: ${{ inputs.upgradeVersion }} - RUN_ID: ${{ inputs.runId }} - TEST_LOG_COLLECT: ${{ inputs.testLogCollect }} - LOKI_ENDPOINT: ${{ inputs.lokiEndpoint }} - LOKI_TENANT_ID: ${{ inputs.lokiTenantId }} - LOKI_BASIC_AUTH: ${{ inputs.lokiBasicAuth }} - LOGSTREAM_LOG_TARGETS: ${{ inputs.logstreamLogTargets }} - GRAFANA_URL: ${{ inputs.grafanaUrl }} - GRAFANA_DASHBOARD_URL: ${{ inputs.grafanaDashboardUrl }} - GRAFANA_BEARER_TOKEN: ${{ inputs.grafanaBearerToken }} - run: | - function convert_to_toml_array() { - local IFS=',' - local input_array=($1) - local toml_array_format="[" - - for element in "${input_array[@]}"; do - toml_array_format+="\"$element\"," - done - - toml_array_format="${toml_array_format%,}]" - echo "$toml_array_format" - } - - selected_networks=$(convert_to_toml_array "$SELECTED_NETWORKS") - - if [ -n "$TEST_LOG_COLLECT" ]; then - test_log_collect=true - else - test_log_collect=false - fi - - log_targets=$(convert_to_toml_array "$LOGSTREAM_LOG_TARGETS") - - grafana_bearer_token="" - if [ -n "$GRAFANA_BEARER_TOKEN" ]; then - grafana_bearer_token="bearer_token_secret=\"$GRAFANA_BEARER_TOKEN\"" - fi - - cat << EOF > config.toml - [Network] - selected_networks=$selected_networks - - [ChainlinkImage] - image="$CHAINLINK_IMAGE" - version="$CHAINLINK_VERSION" - postgres_version="$CHAINLINK_POSTGRES_VERSION" - - [ChainlinkUpgradeImage] - image="$UPGRADE_IMAGE" - version="$UPGRADE_VERSION" - postgres_version="$CHAINLINK_POSTGRES_VERSION" - - [Logging] - test_log_collect=$test_log_collect - run_id="$RUN_ID" - - [Logging.LogStream] - log_targets=$log_targets - - [Logging.Loki] - tenant_id="$LOKI_TENANT_ID" - endpoint="$LOKI_ENDPOINT" - basic_auth_secret="$LOKI_BASIC_AUTH" - - [Logging.Grafana] - base_url="$GRAFANA_URL" - dashboard_url="$GRAFANA_DASHBOARD_URL" - $grafana_bearer_token - EOF - - BASE64_CONFIG_OVERRIDE=$(cat config.toml | base64 -w 0) - echo ::add-mask::$BASE64_CONFIG_OVERRIDE - echo "BASE64_CONFIG_OVERRIDE=$BASE64_CONFIG_OVERRIDE" >> $GITHUB_ENV diff --git a/.github/actions/setup-merge-base64-config/action.yml b/.github/actions/setup-merge-base64-config/action.yml deleted file mode 100644 index 79dc875831..0000000000 --- a/.github/actions/setup-merge-base64-config/action.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: Merge Base64 Config -description: A composite action that merges user-provided Base64-encoded config with repository's secrets - -inputs: - base64Config: - description: Base64-encoded config to decode - -runs: - using: composite - steps: - - name: Install dasel - shell: bash - run: | - if ! which dasel > /dev/null; then - curl -L -o dasel "https://github.com/TomWright/dasel/releases/download/v2.6.0/dasel_linux_amd64" && chmod +x dasel && sudo mv dasel /usr/local/bin/ - else - echo "Dasel is already installed." - fi - - name: Add masks and export base64 config - shell: bash - run: | - BASE64_CONFIG_OVERRIDE=$(jq -r '.inputs.base64Config' $GITHUB_EVENT_PATH) - echo ::add-mask::$BASE64_CONFIG_OVERRIDE - echo "BASE64_CONFIG_OVERRIDE=$BASE64_CONFIG_OVERRIDE" >> $GITHUB_ENV - - decoded_toml=$(echo $BASE64_CONFIG_OVERRIDE | base64 -d) - CHAINLINK_IMAGE=$(echo "$decoded_toml" | { dasel -r toml 'ChainlinkImage.image' 2>/dev/null || echo ''; }) - echo ::add-mask::$CHAINLINK_IMAGE - CHAINLINK_VERSION=$(echo "$decoded_toml" | { dasel -r toml 'ChainlinkImage.version' 2>/dev/null || echo ''; }) - NETWORKS=$(echo "$decoded_toml" | awk -F'=' '/^[[:space:]]*selected_networks[[:space:]]*=/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2); print $2}' 2>/dev/null) - - if [ -n "$CHAINLINK_IMAGE" ]; then - echo "CHAINLINK_IMAGE=$CHAINLINK_IMAGE" >> $GITHUB_ENV - else - echo "No Chainlink Image found in base64-ed config" - fi - if [ -n "$CHAINLINK_VERSION" ]; then - echo "CHAINLINK_VERSION=$CHAINLINK_VERSION" >> $GITHUB_ENV - else - echo "No Chainlink Version found in base64-ed config" - fi - if [ -n "$NETWORKS" ]; then - echo "NETWORKS=$NETWORKS" >> $GITHUB_ENV - fi - - grafana_bearer_token="" - if [ -n "$GRAFANA_BEARER_TOKEN" ]; then - grafana_bearer_token="bearer_token_secret=\"$GRAFANA_BEARER_TOKEN\"" - fi - - # use Loki config from GH secrets and merge it with base64 input - cat << EOF > config.toml - [Logging.Loki] - tenant_id="$LOKI_TENANT_ID" - endpoint="$LOKI_URL" - basic_auth_secret="$LOKI_BASIC_AUTH" - # legacy, you only need this to access the cloud version - # bearer_token_secret="bearer_token" - - [Logging.Grafana] - base_url="$GRAFANA_URL" - dashboard_url="$GRAFANA_DASHBOARD_URL" - $grafana_bearer_token - EOF - - echo "$decoded_toml" >> final_config.toml - cat config.toml >> final_config.toml - BASE64_CONFIG_OVERRIDE=$(cat final_config.toml | base64 -w 0) - echo ::add-mask::$BASE64_CONFIG_OVERRIDE - echo "BASE64_CONFIG_OVERRIDE=$BASE64_CONFIG_OVERRIDE" >> $GITHUB_ENV \ No newline at end of file diff --git a/.github/actions/setup-parse-base64-config/action.yml b/.github/actions/setup-parse-base64-config/action.yml deleted file mode 100644 index 72e8982e6d..0000000000 --- a/.github/actions/setup-parse-base64-config/action.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Parse Base64 Config -description: A composite action that extracts the chainlink image, version and network from a base64-encoded config - -inputs: - base64Config: - description: Base64-encoded config to decode - -runs: - using: composite - steps: - - name: Install dasel - shell: bash - run: | - if ! which dasel > /dev/null; then - curl -L -o dasel "https://github.com/TomWright/dasel/releases/download/v2.6.0/dasel_linux_amd64" && chmod +x dasel && sudo mv dasel /usr/local/bin/ - else - echo "Dasel is already installed." - fi - - name: Add masks and export base64 config - shell: bash - run: | - decoded_toml=$(echo $BASE64_CONFIG_OVERRIDE | base64 -d) - CHAINLINK_IMAGE=$(echo "$decoded_toml" | { dasel -r toml 'ChainlinkImage.image' 2>/dev/null || echo ''; }) - echo ::add-mask::$CHAINLINK_IMAGE - CHAINLINK_VERSION=$(echo "$decoded_toml" | { dasel -r toml 'ChainlinkImage.version' 2>/dev/null || echo ''; }) - NETWORKS=$(echo "$decoded_toml" | awk -F'=' '/^[[:space:]]*selected_networks[[:space:]]*=/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2); print $2}' 2>/dev/null) - ETH2_EL_CLIENT=$(echo "$decoded_toml" | awk -F'=' '/^[[:space:]]*execution_layer[[:space:]]*=/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2); print $2}' 2>/dev/null) - - if [ -n "$CHAINLINK_IMAGE" ]; then - echo "CHAINLINK_IMAGE=$CHAINLINK_IMAGE" >> $GITHUB_ENV - else - echo "No Chainlink Image found in base64-ed config" - fi - if [ -n "$CHAINLINK_VERSION" ]; then - echo "CHAINLINK_VERSION=$CHAINLINK_VERSION" >> $GITHUB_ENV - else - echo "No Chainlink Version found in base64-ed config. Exiting" - fi - if [ -n "$NETWORKS" ]; then - echo "NETWORKS=$NETWORKS" >> $GITHUB_ENV - fi - if [ -n "$ETH2_EL_CLIENT" ]; then - echo "ETH2_EL_CLIENT=$ETH2_EL_CLIENT" >> $GITHUB_ENV - fi \ No newline at end of file diff --git a/.github/e2e-tests.yml b/.github/e2e-tests.yml index 0d92d1900d..02ab3ee101 100644 --- a/.github/e2e-tests.yml +++ b/.github/e2e-tests.yml @@ -3,763 +3,846 @@ # Each entry in this file includes the following: # - The GitHub runner (runs_on field) that will execute tests. # - The tests that will be run by the runner. -# - The workflows (e.g., Run PR E2E Tests, Run Nightly E2E Tests) that should trigger these tests. +# - The triggers (e.g., Run PR E2E Tests, Nightly E2E Tests) that should trigger these tests. # runner-test-matrix: # START: OCR tests # Example of 1 runner for all tests in integration-tests/smoke/ocr_test.go - - id: integration-tests/smoke/ocr_test.go:* + - id: smoke/ocr_test.go:* path: integration-tests/smoke/ocr_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/ && go test smoke/ocr_test.go -timeout 30m -count=1 -test.parallel=2 -json pyroscope_env: ci-smoke-ocr-evm-simulated - # Example of 2 separate runners for the same test file but different tests. Can be used if tests if are too heavy to run on the same runner - - id: integration-tests/smoke/ocr2_test.go:^TestOCRv2Request$ - path: integration-tests/smoke/ocr2_test.go - test_env_type: docker - runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests - test_cmd: cd integration-tests/ && go test smoke/ocr2_test.go -test.run ^TestOCRv2Request$ -test.parallel=1 -timeout 30m -count=1 -json - pyroscope_env: ci-smoke-ocr2-evm-simulated-nightly - - - id: integration-tests/smoke/ocr2_test.go:^TestOCRv2Basic$ - path: integration-tests/smoke/ocr2_test.go - test_env_type: docker + - id: soak/ocr_test.go:TestOCRv1Soak + path: integration-tests/soak/ocr_test.go + test_env_type: k8s-remote-runner runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests - test_cmd: cd integration-tests/ && go test smoke/ocr2_test.go -test.run ^TestOCRv2Basic$ -test.parallel=1 -timeout 30m -count=1 -json - pyroscope_env: ci-smoke-ocr2-evm-simulated-nightly + test_cmd: cd integration-tests/ && go test soak/ocr_test.go -v -test.run ^TestOCRv1Soak$ -test.parallel=1 -timeout 900h -count=1 -json + test_cmd_opts: 2>&1 | tee /tmp/gotest.log | gotestloghelper -ci -singlepackage -hidepassingtests=false + test_secrets_required: true + test_env_vars: + TEST_SUITE: soak - # Example of a configuration for running a single soak test in Kubernetes Remote Runner - - id: integration-tests/soak/ocr_test.go:^TestOCRv1Soak$ + - id: soak/ocr_test.go:TestOCRv2Soak path: integration-tests/soak/ocr_test.go test_env_type: k8s-remote-runner runs_on: ubuntu-latest - test_cmd: cd integration-tests/ && go test soak/ocr_test.go -v -test.run ^TestOCRv1Soak$ -test.parallel=1 -timeout 30m -count=1 -json - test_inputs: - test_suite: soak + test_cmd: cd integration-tests/ && go test soak/ocr_test.go -v -test.run ^TestOCRv2Soak$ -test.parallel=1 -timeout 900h -count=1 -json + test_cmd_opts: 2>&1 | tee /tmp/gotest.log | gotestloghelper -ci -singlepackage -hidepassingtests=false + test_secrets_required: true + test_env_vars: + TEST_SUITE: soak - - id: integration-tests/soak/ocr_test.go:^TestOCRv2Soak$ + - id: soak/ocr_test.go:TestOCRv2Soak_WemixTestnet path: integration-tests/soak/ocr_test.go test_env_type: k8s-remote-runner runs_on: ubuntu-latest - test_cmd: cd integration-tests/ && go test soak/ocr_test.go -v -test.run ^TestOCRv2Soak$ -test.parallel=1 -timeout 30m -count=1 -json - test_config_override_required: true + test_cmd: cd integration-tests/ && go test soak/ocr_test.go -v -test.run ^TestOCRv2Soak$ -test.parallel=1 -timeout 900h -count=1 -json + test_config_override_path: integration-tests/testconfig/ocr2/overrides/wemix_testnet.toml test_secrets_required: true - test_inputs: - test_suite: soak + test_env_vars: + TEST_SUITE: soak - - id: integration-tests/soak/ocr_test.go:^TestForwarderOCRv1Soak$ + - id: soak/ocr_test.go:TestForwarderOCRv1Soak path: integration-tests/soak/ocr_test.go test_env_type: k8s-remote-runner runs_on: ubuntu-latest - test_cmd: cd integration-tests/ && go test soak/ocr_test.go -v -test.run ^TestForwarderOCRv1Soak$ -test.parallel=1 -timeout 30m -count=1 -json - test_config_override_required: true + test_cmd: cd integration-tests/ && go test soak/ocr_test.go -v -test.run ^TestForwarderOCRv1Soak$ -test.parallel=1 -timeout 900h -count=1 -json test_secrets_required: true - test_inputs: - test_suite: soak + test_env_vars: + TEST_SUITE: soak - - id: integration-tests/soak/ocr_test.go:^TestForwarderOCRv2Soak$ + - id: soak/ocr_test.go:TestForwarderOCRv2Soak path: integration-tests/soak/ocr_test.go test_env_type: k8s-remote-runner runs_on: ubuntu-latest - test_cmd: cd integration-tests/ && go test soak/ocr_test.go -v -test.run ^TestForwarderOCRv2Soak$ -test.parallel=1 -timeout 30m -count=1 -json - test_config_override_required: true + test_cmd: cd integration-tests/ && go test soak/ocr_test.go -v -test.run ^TestForwarderOCRv2Soak$ -test.parallel=1 -timeout 900h -count=1 -json test_secrets_required: true - test_inputs: - test_suite: soak + test_env_vars: + TEST_SUITE: soak - - id: integration-tests/soak/ocr_test.go:^TestOCRSoak_GethReorgBelowFinality_FinalityTagDisabled$ + - id: soak/ocr_test.go:TestOCRSoak_GethReorgBelowFinality_FinalityTagDisabled path: integration-tests/soak/ocr_test.go test_env_type: k8s-remote-runner runs_on: ubuntu-latest - test_cmd: cd integration-tests/ && go test soak/ocr_test.go -v -test.run ^TestOCRSoak_GethReorgBelowFinality_FinalityTagDisabled$ -test.parallel=1 -timeout 30m -count=1 -json - test_config_override_required: true + test_cmd: cd integration-tests/ && go test soak/ocr_test.go -v -test.run TestOCRSoak_GethReorgBelowFinality_FinalityTagDisabled -test.parallel=1 -timeout 900h -count=1 -json test_secrets_required: true - test_inputs: - test_suite: soak + test_env_vars: + TEST_SUITE: soak - - id: integration-tests/soak/ocr_test.go:^TestOCRSoak_GethReorgBelowFinality_FinalityTagEnabled$ + - id: soak/ocr_test.go:TestOCRSoak_GethReorgBelowFinality_FinalityTagEnabled path: integration-tests/soak/ocr_test.go test_env_type: k8s-remote-runner runs_on: ubuntu-latest - test_cmd: cd integration-tests/ && go test soak/ocr_test.go -v -test.run ^TestOCRSoak_GethReorgBelowFinality_FinalityTagEnabled$ -test.parallel=1 -timeout 30m -count=1 -json - test_config_override_required: true + test_cmd: cd integration-tests/ && go test soak/ocr_test.go -v -test.run ^TestOCRSoak_GethReorgBelowFinality_FinalityTagEnabled$ -test.parallel=1 -timeout 900h -count=1 -json test_secrets_required: true - test_inputs: - test_suite: soak + test_env_vars: + TEST_SUITE: soak - - id: integration-tests/soak/ocr_test.go:^TestOCRSoak_GasSpike$ + - id: soak/ocr_test.go:TestOCRSoak_GasSpike path: integration-tests/soak/ocr_test.go test_env_type: k8s-remote-runner runs_on: ubuntu-latest - test_cmd: cd integration-tests/ && go test soak/ocr_test.go -v -test.run ^TestOCRSoak_GasSpike$ -test.parallel=1 -timeout 30m -count=1 -json - test_config_override_required: true + test_cmd: cd integration-tests/ && go test soak/ocr_test.go -v -test.run ^TestOCRSoak_GasSpike$ -test.parallel=1 -timeout 900h -count=1 -json test_secrets_required: true - test_inputs: - test_suite: soak + test_env_vars: + TEST_SUITE: soak - - id: integration-tests/soak/ocr_test.go:^TestOCRSoak_ChangeBlockGasLimit$ + - id: soak/ocr_test.go:TestOCRSoak_ChangeBlockGasLimit path: integration-tests/soak/ocr_test.go test_env_type: k8s-remote-runner runs_on: ubuntu-latest - test_cmd: cd integration-tests/ && go test soak/ocr_test.go -v -test.run ^TestOCRSoak_ChangeBlockGasLimit$ -test.parallel=1 -timeout 30m -count=1 -json - test_config_override_required: true + test_cmd: cd integration-tests/ && go test soak/ocr_test.go -v -test.run ^TestOCRSoak_ChangeBlockGasLimit$ -test.parallel=1 -timeout 900h -count=1 -json test_secrets_required: true - test_inputs: - test_suite: soak + test_env_vars: + TEST_SUITE: soak - - id: integration-tests/soak/ocr_test.go:^TestOCRSoak_RPCDownForAllCLNodes$ + - id: soak/ocr_test.go:TestOCRSoak_RPCDownForAllCLNodes path: integration-tests/soak/ocr_test.go test_env_type: k8s-remote-runner runs_on: ubuntu-latest - test_cmd: cd integration-tests/ && go test soak/ocr_test.go -v -test.run ^TestOCRSoak_RPCDownForAllCLNodes$ -test.parallel=1 -timeout 30m -count=1 -json - test_config_override_required: true + test_cmd: cd integration-tests/ && go test soak/ocr_test.go -v -test.run ^TestOCRSoak_RPCDownForAllCLNodes$ -test.parallel=1 -timeout 900h -count=1 -json test_secrets_required: true - test_inputs: - test_suite: soak + test_env_vars: + TEST_SUITE: soak - - id: integration-tests/soak/ocr_test.go:^TestOCRSoak_RPCDownForHalfCLNodes$ + - id: soak/ocr_test.go:TestOCRSoak_RPCDownForHalfCLNodes path: integration-tests/soak/ocr_test.go test_env_type: k8s-remote-runner runs_on: ubuntu-latest - test_cmd: cd integration-tests/ && go test soak/ocr_test.go -v -test.run ^TestOCRSoak_RPCDownForHalfCLNodes$ -test.parallel=1 -timeout 30m -count=1 -json - test_config_override_required: true + test_cmd: cd integration-tests/ && go test soak/ocr_test.go -v -test.run ^TestOCRSoak_RPCDownForHalfCLNodes$ -test.parallel=1 -timeout 900h -count=1 -json test_secrets_required: true - test_inputs: - test_suite: soak + test_env_vars: + TEST_SUITE: soak - - id: integration-tests/smoke/forwarder_ocr_test.go:* + - id: smoke/forwarder_ocr_test.go:* path: integration-tests/smoke/forwarder_ocr_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/ && go test smoke/forwarder_ocr_test.go -timeout 30m -count=1 -test.parallel=2 -json pyroscope_env: ci-smoke-forwarder-ocr-evm-simulated - - id: integration-tests/smoke/forwarders_ocr2_test.go:* + - id: smoke/forwarders_ocr2_test.go:* path: integration-tests/smoke/forwarders_ocr2_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/ && go test smoke/forwarders_ocr2_test.go -timeout 30m -count=1 -test.parallel=2 -json pyroscope_env: ci-smoke-forwarder-ocr-evm-simulated - - id: integration-tests/smoke/ocr2_test.go:* + - id: smoke/ocr2_test.go:* path: integration-tests/smoke/ocr2_test.go test_env_type: docker runs_on: ubuntu22.04-16cores-64GB - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/ && go test smoke/ocr2_test.go -timeout 30m -count=1 -test.parallel=6 -json pyroscope_env: ci-smoke-ocr2-evm-simulated - - - id: integration-tests/smoke/ocr2_test.go:*-plugins + test_env_vars: + E2E_TEST_CHAINLINK_VERSION: '{{ env.DEFAULT_CHAINLINK_PLUGINS_VERSION }}' # This is the chainlink version that has the plugins + + - id: smoke/ocr2_test.go:*-plugins path: integration-tests/smoke/ocr2_test.go test_env_type: docker runs_on: ubuntu22.04-16cores-64GB - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests 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_inputs: - # chainlink_version: '{{ env.GITHUB_SHA_PLUGINS }}' # This is the chainlink version that has the plugins - chainlink_version: develop-plugins + test_env_vars: + E2E_TEST_CHAINLINK_VERSION: '{{ env.DEFAULT_CHAINLINK_PLUGINS_VERSION }}' # This is the chainlink version that has the plugins + ENABLE_OTEL_TRACES: true + + - id: chaos/ocr_chaos_test.go + path: integration-tests/chaos/ocr_chaos_test.go + test_env_type: k8s-remote-runner + runs_on: ubuntu-latest + triggers: + - Automation On Demand Tests + - E2E Chaos Tests + test_cmd: cd integration-tests/chaos && DETACH_RUNNER=false go test -test.run "^TestOCRChaos$" -v -test.parallel=10 -timeout 60m -count=1 -json + test_env_vars: + TEST_SUITE: chaos # END: OCR tests # START: Automation tests - - id: integration-tests/smoke/automation_test.go:^TestAutomationBasic/registry_2_0|TestAutomationBasic/registry_2_1_conditional|TestAutomationBasic/registry_2_1_logtrigger$ + - id: smoke/automation_test.go:^TestAutomationBasic/registry_2_0|TestAutomationBasic/registry_2_1_conditional|TestAutomationBasic/registry_2_1_logtrigger$ path: integration-tests/smoke/automation_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run "^TestAutomationBasic/registry_2_0|TestAutomationBasic/registry_2_1_conditional|TestAutomationBasic/registry_2_1_logtrigger$" -test.parallel=3 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-automation-evm-simulated - - id: integration-tests/smoke/automation_test.go:^TestAutomationBasic/registry_2_1_with_mercury_v02|TestAutomationBasic/registry_2_1_with_mercury_v03|TestAutomationBasic/registry_2_1_with_logtrigger_and_mercury_v02$ + - id: smoke/automation_test.go:^TestAutomationBasic/registry_2_1_with_mercury_v02|TestAutomationBasic/registry_2_1_with_mercury_v03|TestAutomationBasic/registry_2_1_with_logtrigger_and_mercury_v02$ path: integration-tests/smoke/automation_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run "^TestAutomationBasic/registry_2_1_with_mercury_v02|TestAutomationBasic/registry_2_1_with_mercury_v03|TestAutomationBasic/registry_2_1_with_logtrigger_and_mercury_v02$" -test.parallel=3 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-automation-evm-simulated - - id: integration-tests/smoke/automation_test.go:^TestAutomationBasic/registry_2_2_conditional|TestAutomationBasic/registry_2_2_logtrigger|TestAutomationBasic/registry_2_2_with_mercury_v02$ + - id: smoke/automation_test.go:^TestAutomationBasic/registry_2_2_conditional|TestAutomationBasic/registry_2_2_logtrigger|TestAutomationBasic/registry_2_2_with_mercury_v02$ path: integration-tests/smoke/automation_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run "^TestAutomationBasic/registry_2_2_conditional|TestAutomationBasic/registry_2_2_logtrigger|TestAutomationBasic/registry_2_2_with_mercury_v02$" -test.parallel=3 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-automation-evm-simulated - - id: integration-tests/smoke/automation_test.go:^TestAutomationBasic/registry_2_2_with_mercury_v03|TestAutomationBasic/registry_2_2_with_logtrigger_and_mercury_v02$ + - id: smoke/automation_test.go:^TestAutomationBasic/registry_2_2_with_mercury_v03|TestAutomationBasic/registry_2_2_with_logtrigger_and_mercury_v02$ path: integration-tests/smoke/automation_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run "^TestAutomationBasic/registry_2_2_with_mercury_v03|TestAutomationBasic/registry_2_2_with_logtrigger_and_mercury_v02$" -test.parallel=2 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-automation-evm-simulated - - id: integration-tests/smoke/automation_test.go:^TestAutomationBasic/registry_2_3_conditional_native|TestAutomationBasic/registry_2_3_conditional_link$ + - id: smoke/automation_test.go:^TestAutomationBasic/registry_2_3_conditional_native|TestAutomationBasic/registry_2_3_conditional_link$ path: integration-tests/smoke/automation_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run 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 + triggers: + - PR E2E Core Tests + - Merge Queue 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=2 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-automation-evm-simulated - - id: integration-tests/smoke/automation_test.go:^TestAutomationBasic/registry_2_3_logtrigger_native|TestAutomationBasic/registry_2_3_logtrigger_link$ + - id: smoke/automation_test.go:^TestAutomationBasic/registry_2_3_logtrigger_native|TestAutomationBasic/registry_2_3_logtrigger_link$ path: integration-tests/smoke/automation_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run "^TestAutomationBasic/registry_2_3_logtrigger_native|TestAutomationBasic/registry_2_3_logtrigger_link$" -test.parallel=2 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-automation-evm-simulated - - id: integration-tests/smoke/automation_test.go:^TestAutomationBasic/registry_2_3_with_mercury_v03_link|TestAutomationBasic/registry_2_3_with_logtrigger_and_mercury_v02_link$ + - id: smoke/automation_test.go:^TestAutomationBasic/registry_2_3_with_mercury_v03_link|TestAutomationBasic/registry_2_3_with_logtrigger_and_mercury_v02_link$ path: integration-tests/smoke/automation_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run "^TestAutomationBasic/registry_2_3_with_mercury_v03_link|TestAutomationBasic/registry_2_3_with_logtrigger_and_mercury_v02_link$" -test.parallel=2 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-automation-evm-simulated - - id: integration-tests/smoke/automation_test.go:^TestSetUpkeepTriggerConfig$ + - id: smoke/automation_test.go:^TestSetUpkeepTriggerConfig$ path: integration-tests/smoke/automation_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestSetUpkeepTriggerConfig$ -test.parallel=2 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-automation-evm-simulated - - id: integration-tests/smoke/automation_test.go:^TestAutomationAddFunds$ + - id: smoke/automation_test.go:^TestAutomationAddFunds$ path: integration-tests/smoke/automation_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestAutomationAddFunds$ -test.parallel=3 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-automation-evm-simulated - - id: integration-tests/smoke/automation_test.go:^TestAutomationPauseUnPause$ + - id: smoke/automation_test.go:^TestAutomationPauseUnPause$ path: integration-tests/smoke/automation_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestAutomationPauseUnPause$ -test.parallel=3 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-automation-evm-simulated - - id: integration-tests/smoke/automation_test.go:^TestAutomationRegisterUpkeep$ + - id: smoke/automation_test.go:^TestAutomationRegisterUpkeep$ path: integration-tests/smoke/automation_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestAutomationRegisterUpkeep$ -test.parallel=3 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-automation-evm-simulated - - id: integration-tests/smoke/automation_test.go:^TestAutomationPauseRegistry$ + - id: smoke/automation_test.go:^TestAutomationPauseRegistry$ path: integration-tests/smoke/automation_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestAutomationPauseRegistry$ -test.parallel=3 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-automation-evm-simulated - - id: integration-tests/smoke/automation_test.go:^TestAutomationKeeperNodesDown$ + - id: smoke/automation_test.go:^TestAutomationKeeperNodesDown$ path: integration-tests/smoke/automation_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestAutomationKeeperNodesDown$ -test.parallel=3 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-automation-evm-simulated - - id: integration-tests/smoke/automation_test.go:^TestAutomationPerformSimulation$ + - id: smoke/automation_test.go:^TestAutomationPerformSimulation$ path: integration-tests/smoke/automation_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestAutomationPerformSimulation$ -test.parallel=3 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-automation-evm-simulated - - id: integration-tests/smoke/automation_test.go:^TestAutomationCheckPerformGasLimit$ + - id: smoke/automation_test.go:^TestAutomationCheckPerformGasLimit$ path: integration-tests/smoke/automation_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestAutomationCheckPerformGasLimit$ -test.parallel=3 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-automation-evm-simulated - - id: integration-tests/smoke/automation_test.go:^TestUpdateCheckData$ + - id: smoke/automation_test.go:^TestUpdateCheckData$ path: integration-tests/smoke/automation_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestUpdateCheckData$ -test.parallel=3 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-automation-evm-simulated - - id: integration-tests/smoke/automation_test.go:^TestSetOffchainConfigWithMaxGasPrice$ + - id: smoke/automation_test.go:^TestSetOffchainConfigWithMaxGasPrice$ path: integration-tests/smoke/automation_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestSetOffchainConfigWithMaxGasPrice$ -test.parallel=2 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-automation-evm-simulated - - id: integration-tests/smoke/keeper_test.go:^TestKeeperBasicSmoke$ + - id: smoke/keeper_test.go:^TestKeeperBasicSmoke$ path: integration-tests/smoke/keeper_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestKeeperBasicSmoke$ -test.parallel=3 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-keeper-evm-simulated - - id: integration-tests/smoke/keeper_test.go:^TestKeeperBlockCountPerTurn$ + - id: smoke/keeper_test.go:^TestKeeperBlockCountPerTurn$ path: integration-tests/smoke/keeper_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestKeeperBlockCountPerTurn$ -test.parallel=3 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-keeper-evm-simulated - - id: integration-tests/smoke/keeper_test.go:^TestKeeperSimulation$ + - id: smoke/keeper_test.go:^TestKeeperSimulation$ path: integration-tests/smoke/keeper_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestKeeperSimulation$ -test.parallel=2 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-keeper-evm-simulated - - id: integration-tests/smoke/keeper_test.go:^TestKeeperCheckPerformGasLimit$ + - id: smoke/keeper_test.go:^TestKeeperCheckPerformGasLimit$ path: integration-tests/smoke/keeper_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests - test_cmd: cd integration-tests/smoke && go test -test.run ^TestKeeperCheckPerformGasLimit$ -test.parallel=3 -timeout 30m -count=1 -json + triggers: + - Merge Queue E2E Core Tests + - Nightly E2E Tests + 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: integration-tests/smoke/keeper_test.go:^TestKeeperRegisterUpkeep$ + - id: smoke/keeper_test.go:^TestKeeperRegisterUpkeep$ path: integration-tests/smoke/keeper_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestKeeperRegisterUpkeep$ -test.parallel=3 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-keeper-evm-simulated - - id: integration-tests/smoke/keeper_test.go:^TestKeeperAddFunds$ + - id: smoke/keeper_test.go:^TestKeeperAddFunds$ path: integration-tests/smoke/keeper_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestKeeperAddFunds$ -test.parallel=3 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-keeper-evm-simulated - - id: integration-tests/smoke/keeper_test.go:^TestKeeperRemove$ + - id: smoke/keeper_test.go:^TestKeeperRemove$ path: integration-tests/smoke/keeper_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestKeeperRemove$ -test.parallel=3 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-keeper-evm-simulated - - id: integration-tests/smoke/keeper_test.go:^TestKeeperPauseRegistry$ + - id: smoke/keeper_test.go:^TestKeeperPauseRegistry$ path: integration-tests/smoke/keeper_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestKeeperPauseRegistry$ -test.parallel=2 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-keeper-evm-simulated - - id: integration-tests/smoke/keeper_test.go:^TestKeeperMigrateRegistry$ + - id: smoke/keeper_test.go:^TestKeeperMigrateRegistry$ path: integration-tests/smoke/keeper_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestKeeperMigrateRegistry$ -test.parallel=1 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-keeper-evm-simulated - - id: integration-tests/smoke/keeper_test.go:^TestKeeperNodeDown$ + - id: smoke/keeper_test.go:^TestKeeperNodeDown$ path: integration-tests/smoke/keeper_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestKeeperNodeDown$ -test.parallel=3 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-keeper-evm-simulated - - id: integration-tests/smoke/keeper_test.go:^TestKeeperPauseUnPauseUpkeep$ + - id: smoke/keeper_test.go:^TestKeeperPauseUnPauseUpkeep$ path: integration-tests/smoke/keeper_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestKeeperPauseUnPauseUpkeep$ -test.parallel=1 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-keeper-evm-simulated - - id: integration-tests/smoke/keeper_test.go:^TestKeeperUpdateCheckData$ + - id: smoke/keeper_test.go:^TestKeeperUpdateCheckData$ path: integration-tests/smoke/keeper_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestKeeperUpdateCheckData$ -test.parallel=1 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-keeper-evm-simulated - - id: integration-tests/smoke/keeper_test.go:^TestKeeperJobReplacement$ + - id: smoke/keeper_test.go:^TestKeeperJobReplacement$ path: integration-tests/smoke/keeper_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestKeeperJobReplacement$ -test.parallel=1 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-keeper-evm-simulated - - id: integration-tests/load/automationv2_1/automationv2_1_test.go:TestLogTrigger + - id: load/automationv2_1/automationv2_1_test.go:TestLogTrigger path: integration-tests/load/automationv2_1/automationv2_1_test.go runs_on: ubuntu-latest test_env_type: k8s-remote-runner test_cmd: cd integration-tests/load/automationv2_1 && go test -test.run TestLogTrigger -test.parallel=1 -timeout 60m -count=1 -json remote_runner_memory: 4Gi - test_config_override_required: true test_secrets_required: true - test_inputs: - test_suite: automationv2_1 - workflows: - - Automation Load Test + test_env_vars: + TEST_LOG_LEVEL: info + TEST_SUITE: automationv2_1 pyroscope_env: automation-load-test - - id: integration-tests/smoke/automation_upgrade_test.go:^TestAutomationNodeUpgrade/registry_2_0 + - id: smoke/automation_upgrade_test.go:^TestAutomationNodeUpgrade/registry_2_0 path: integration-tests/smoke/automation_upgrade_test.go test_env_type: docker runs_on: ubuntu22.04-8cores-32GB - workflows: - - Run Automation Product Nightly E2E Tests + triggers: + - Automation Nightly Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestAutomationNodeUpgrade/registry_2_0 -test.parallel=1 -timeout 60m -count=1 -json - test_inputs: - chainlink_image: public.ecr.aws/chainlink/chainlink - chainlink_version: latest - chainlink_upgrade_image: '{{ env.QA_CHAINLINK_IMAGE }}' - chainlink_upgrade_version: develop + test_env_vars: + E2E_TEST_CHAINLINK_IMAGE: public.ecr.aws/chainlink/chainlink + E2E_TEST_CHAINLINK_VERSION: latest + E2E_TEST_CHAINLINK_UPGRADE_IMAGE: '{{ env.QA_CHAINLINK_IMAGE }}' + E2E_TEST_CHAINLINK_UPGRADE_VERSION: '{{ env.DEFAULT_CHAINLINK_UPGRADE_VERSION }}' pyroscope_env: ci-smoke-automation-upgrade-tests - - id: integration-tests/smoke/automation_upgrade_test.go:^TestAutomationNodeUpgrade/registry_2_1 + - id: smoke/automation_upgrade_test.go:^TestAutomationNodeUpgrade/registry_2_1 path: integration-tests/smoke/automation_upgrade_test.go test_env_type: docker runs_on: ubuntu22.04-8cores-32GB - workflows: - - Run Automation Product Nightly E2E Tests + triggers: + - Automation Nightly Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestAutomationNodeUpgrade/registry_2_1 -test.parallel=5 -timeout 60m -count=1 -json - test_inputs: - chainlink_image: public.ecr.aws/chainlink/chainlink - chainlink_version: latest - chainlink_upgrade_image: '{{ env.QA_CHAINLINK_IMAGE }}' - chainlink_upgrade_version: develop + test_env_vars: + E2E_TEST_CHAINLINK_IMAGE: public.ecr.aws/chainlink/chainlink + E2E_TEST_CHAINLINK_VERSION: latest + E2E_TEST_CHAINLINK_UPGRADE_IMAGE: '{{ env.QA_CHAINLINK_IMAGE }}' + E2E_TEST_CHAINLINK_UPGRADE_VERSION: '{{ env.DEFAULT_CHAINLINK_UPGRADE_VERSION }}' pyroscope_env: ci-smoke-automation-upgrade-tests - - id: integration-tests/smoke/automation_upgrade_test.go:^TestAutomationNodeUpgrade/registry_2_2 + - id: smoke/automation_upgrade_test.go:^TestAutomationNodeUpgrade/registry_2_2 path: integration-tests/smoke/automation_upgrade_test.go test_env_type: docker runs_on: ubuntu22.04-8cores-32GB - workflows: - - Run Automation Product Nightly E2E Tests + triggers: + - Automation Nightly Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestAutomationNodeUpgrade/registry_2_2 -test.parallel=5 -timeout 60m -count=1 -json - test_inputs: - chainlink_image: public.ecr.aws/chainlink/chainlink - chainlink_version: latest - chainlink_upgrade_image: '{{ env.QA_CHAINLINK_IMAGE }}' - chainlink_upgrade_version: develop + test_env_vars: + E2E_TEST_CHAINLINK_IMAGE: public.ecr.aws/chainlink/chainlink + E2E_TEST_CHAINLINK_VERSION: latest + E2E_TEST_CHAINLINK_UPGRADE_IMAGE: '{{ env.QA_CHAINLINK_IMAGE }}' + E2E_TEST_CHAINLINK_UPGRADE_VERSION: '{{ env.DEFAULT_CHAINLINK_UPGRADE_VERSION }}' pyroscope_env: ci-smoke-automation-upgrade-tests - - id: integration-tests/reorg/automation_reorg_test.go + - id: reorg/automation_reorg_test.go^TestAutomationReorg/registry_2_0 path: integration-tests/reorg/automation_reorg_test.go runs_on: ubuntu-latest - test_env_type: k8s-remote-runner - test_inputs: - test_suite: reorg - workflows: - - Run Automation On Demand Tests (TEST WORKFLOW) - test_cmd: cd integration-tests/reorg && DETACH_RUNNER=false go test -v -test.run ^TestAutomationReorg$ -test.parallel=7 -timeout 60m -count=1 -json + test_env_type: docker + test_env_vars: + TEST_SUITE: reorg + triggers: + - Automation On Demand Tests + test_cmd: cd integration-tests/reorg && DETACH_RUNNER=false go test -v -test.run ^TestAutomationReorg/registry_2_0 -test.parallel=1 -timeout 30m -count=1 -json + pyroscope_env: ci-automation-on-demand-reorg + + - id: reorg/automation_reorg_test.go^TestAutomationReorg/registry_2_1 + path: integration-tests/reorg/automation_reorg_test.go + runs_on: ubuntu-latest + test_env_type: docker + test_env_vars: + TEST_SUITE: reorg + triggers: + - Automation On Demand Tests + test_cmd: cd integration-tests/reorg && DETACH_RUNNER=false go test -v -test.run ^TestAutomationReorg/registry_2_1 -test.parallel=2 -timeout 30m -count=1 -json + pyroscope_env: ci-automation-on-demand-reorg + + - id: reorg/automation_reorg_test.go^TestAutomationReorg/registry_2_2 + path: integration-tests/reorg/automation_reorg_test.go + runs_on: ubuntu-latest + test_env_type: docker + test_env_vars: + TEST_SUITE: reorg + triggers: + - Automation On Demand Tests + test_cmd: cd integration-tests/reorg && DETACH_RUNNER=false go test -v -test.run ^TestAutomationReorg/registry_2_2 -test.parallel=2 -timeout 30m -count=1 -json + pyroscope_env: ci-automation-on-demand-reorg + + - id: reorg/automation_reorg_test.go^TestAutomationReorg/registry_2_3 + path: integration-tests/reorg/automation_reorg_test.go + runs_on: ubuntu-latest + test_env_type: docker + test_env_vars: + TEST_SUITE: reorg + triggers: + - Automation On Demand Tests + test_cmd: cd integration-tests/reorg && DETACH_RUNNER=false go test -v -test.run ^TestAutomationReorg/registry_2_3 -test.parallel=2 -timeout 30m -count=1 -json pyroscope_env: ci-automation-on-demand-reorg - - id: integration-tests/chaos/automation_chaos_test.go + - id: chaos/automation_chaos_test.go path: integration-tests/chaos/automation_chaos_test.go test_env_type: k8s-remote-runner runs_on: ubuntu-latest - workflows: - - Run Automation On Demand Tests (TEST WORKFLOW) - test_cmd: cd integration-tests/chaos && DETACH_RUNNER=false go test -v -test.run ^TestAutomationChaos$ -test.parallel=15 -timeout 60m -count=1 -json + triggers: + - Automation On Demand Tests + - E2E Chaos Tests + test_cmd: cd integration-tests/chaos && DETACH_RUNNER=false go test -v -test.run ^TestAutomationChaos$ -test.parallel=20 -timeout 60m -count=1 -json pyroscope_env: ci-automation-on-demand-chaos - test_inputs: - test_suite: chaos + test_env_vars: + TEST_SUITE: chaos - - id: integration-tests/benchmark/keeper_test.go:^TestAutomationBenchmark$ - path: integration-tests/benchmark/keeper_test.go + - id: benchmark/automation_test.go:TestAutomationBenchmark + path: integration-tests/benchmark/automation_test.go test_env_type: k8s-remote-runner remote_runner_memory: 4Gi runs_on: ubuntu-latest - # workflows: - # - Run Nightly E2E Tests + # triggers: + # - Nightly E2E Tests test_cmd: cd integration-tests/benchmark && go test -v -test.run ^TestAutomationBenchmark$ -test.parallel=1 -timeout 30m -count=1 -json pyroscope_env: ci-benchmark-automation-nightly - test_inputs: - test_suite: benchmark + test_env_vars: + TEST_LOG_LEVEL: info + TEST_SUITE: benchmark + TEST_TYPE: benchmark + + - id: soak/automation_test.go:TestAutomationBenchmark + path: integration-tests/benchmark/automation_test.go + test_env_type: k8s-remote-runner + remote_runner_memory: 4Gi + runs_on: ubuntu-latest + # triggers: + # - Nightly E2E Tests + test_cmd: cd integration-tests/benchmark && go test -v -test.run ^TestAutomationBenchmark$ -test.parallel=1 -timeout 30m -count=1 -json + pyroscope_env: ci-benchmark-automation-nightly + test_env_vars: + TEST_LOG_LEVEL: info + TEST_SUITE: benchmark + TEST_TYPE: soak # END: Automation tests # START: VRF tests - - id: integration-tests/smoke/vrfv2_test.go:TestVRFv2Basic + - id: smoke/vrfv2_test.go:TestVRFv2Basic path: integration-tests/smoke/vrfv2_test.go runs_on: ubuntu22.04-8cores-32GB test_env_type: docker test_cmd: cd integration-tests/smoke && go test -v -test.run TestVRFv2Basic -test.parallel=1 -timeout 30m -count=1 -json - test_config_override_required: true test_secrets_required: true - workflows: + triggers: - On Demand VRFV2 Smoke Test (Ethereum clients) - - id: integration-tests/load/vrfv2plus/vrfv2plus_test.go:^TestVRFV2PlusPerformance$Smoke + - id: load/vrfv2plus/vrfv2plus_test.go:^TestVRFV2PlusPerformance$Smoke path: integration-tests/load/vrfv2plus/vrfv2plus_test.go runs_on: ubuntu22.04-8cores-32GB test_env_type: docker test_cmd: cd integration-tests/load/vrfv2plus && go test -v -test.run ^TestVRFV2PlusPerformance$ -test.parallel=1 -timeout 24h -count=1 -json test_config_override_required: true test_secrets_required: true - test_inputs: - test_type: Smoke - workflows: + test_env_vars: + TEST_TYPE: Smoke + triggers: - On Demand VRFV2 Plus Performance Test - - id: integration-tests/load/vrfv2plus/vrfv2plus_test.go:^TestVRFV2PlusBHSPerformance$Smoke + - id: load/vrfv2plus/vrfv2plus_test.go:^TestVRFV2PlusBHSPerformance$Smoke path: integration-tests/load/vrfv2plus/vrfv2plus_test.go runs_on: ubuntu22.04-8cores-32GB test_env_type: docker test_cmd: cd integration-tests/load/vrfv2plus && go test -v -test.run ^TestVRFV2PlusBHSPerformance$ -test.parallel=1 -timeout 24h -count=1 -json test_config_override_required: true test_secrets_required: true - test_inputs: - test_type: Smoke - workflows: + test_env_vars: + TEST_TYPE: Smoke + triggers: - On Demand VRFV2 Plus Performance Test - - id: integration-tests/load/vrfv2/vrfv2_test.go:^TestVRFV2Performance$Smoke + - id: load/vrfv2/vrfv2_test.go:^TestVRFV2Performance$Smoke path: integration-tests/load/vrfv2/vrfv2_test.go runs_on: ubuntu22.04-8cores-32GB test_env_type: docker test_cmd: cd integration-tests/load/vrfv2 && go test -v -test.run ^TestVRFV2Performance$ -test.parallel=1 -timeout 24h -count=1 -json test_config_override_required: true test_secrets_required: true - test_inputs: - test_type: Smoke - workflows: + test_env_vars: + TEST_TYPE: Smoke + triggers: - On Demand VRFV2 Performance Test - - id: integration-tests/load/vrfv2/vrfv2_test.go:^TestVRFV2PlusBHSPerformance$Smoke + - id: load/vrfv2/vrfv2_test.go:^TestVRFV2PlusBHSPerformance$Smoke path: integration-tests/load/vrfv2/vrfv2_test.go runs_on: ubuntu22.04-8cores-32GB test_env_type: docker test_cmd: cd integration-tests/load/vrfv2 && go test -v -test.run ^TestVRFV2PlusBHSPerformance$ -test.parallel=1 -timeout 24h -count=1 -json test_config_override_required: true test_secrets_required: true - test_inputs: - test_type: Smoke - workflows: + test_env_vars: + TEST_TYPE: Smoke + triggers: - On Demand VRFV2 Performance Test - - id: integration-tests/smoke/vrfv2plus_test.go:^TestVRFv2Plus$/^Link_Billing$ - path: integration-tests/smoke/vrfv2plus_test.go - runs_on: ubuntu22.04-8cores-32GB - test_env_type: docker - test_cmd: cd integration-tests && go test -v -test.run ^TestVRFv2Plus$/^Link_Billing$ smoke/vrfv2plus_test.go -test.parallel=1 -timeout 30m -count=1 -json - test_config_override_required: true - test_secrets_required: true - workflows: - - On Demand VRFV2Plus Smoke Test (Ethereum clients) - - - id: integration-tests/smoke/vrf_test.go:* + - id: smoke/vrf_test.go:* path: integration-tests/smoke/vrf_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/ && go test smoke/vrf_test.go -timeout 30m -count=1 -test.parallel=2 -json pyroscope_env: ci-smoke-vrf-evm-simulated - - id: integration-tests/smoke/vrfv2_test.go:* + - id: smoke/vrfv2_test.go:* path: integration-tests/smoke/vrfv2_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/ && go test smoke/vrfv2_test.go -timeout 30m -count=1 -test.parallel=6 -json pyroscope_env: ci-smoke-vrf2-evm-simulated - - id: integration-tests/smoke/vrfv2plus_test.go:* + - id: smoke/vrfv2plus_test.go:* path: integration-tests/smoke/vrfv2plus_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/ && go test smoke/vrfv2plus_test.go -timeout 30m -count=1 -test.parallel=9 -json pyroscope_env: ci-smoke-vrf2plus-evm-simulated + # VRFv2Plus tests on any live testnet (test_config_override_path required) + # Tests have to run in sequence because of a single private key used + - id: TestVRFv2Plus_LiveTestnets + path: integration-tests/smoke/vrfv2plus_test.go + runs_on: ubuntu-latest + test_env_type: docker + test_cmd: cd integration-tests/smoke && go test -v -test.run "TestVRFv2Plus$/(Link_Billing|Native_Billing|Direct_Funding)|TestVRFV2PlusWithBHS" -test.parallel=1 -timeout 2h -count=1 -json + + # VRFv2Plus release tests on Sepolia testnet + - id: TestVRFv2Plus_Release_Sepolia + path: integration-tests/smoke/vrfv2plus_test.go + runs_on: ubuntu-latest + test_env_type: docker + test_cmd: cd integration-tests/smoke && go test -v -test.run "TestVRFv2Plus$/(Link_Billing|Native_Billing|Direct_Funding)|TestVRFV2PlusWithBHS" -test.parallel=1 -timeout 2h -count=1 -json + test_config_override_path: integration-tests/testconfig/vrfv2plus/overrides/new_env/sepolia_new_env_test_config.toml + triggers: + - VRF E2E Release Tests + # END: VRF tests # START: LogPoller tests - - id: integration-tests/smoke/log_poller_test.go:^TestLogPollerFewFiltersFixedDepth$ + - id: smoke/log_poller_test.go:^TestLogPollerFewFiltersFixedDepth$ path: integration-tests/smoke/log_poller_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestLogPollerFewFiltersFixedDepth$ -test.parallel=1 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-log_poller-evm-simulated - - id: integration-tests/smoke/log_poller_test.go:^TestLogPollerFewFiltersFinalityTag$ + - id: smoke/log_poller_test.go:^TestLogPollerFewFiltersFinalityTag$ path: integration-tests/smoke/log_poller_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestLogPollerFewFiltersFinalityTag$ -test.parallel=1 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-log_poller-evm-simulated - - id: integration-tests/smoke/log_poller_test.go:^TestLogPollerWithChaosFixedDepth$ + - id: smoke/log_poller_test.go:^TestLogPollerWithChaosFixedDepth$ path: integration-tests/smoke/log_poller_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestLogPollerWithChaosFixedDepth$ -test.parallel=1 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-log_poller-evm-simulated - - id: integration-tests/smoke/log_poller_test.go:^TestLogPollerWithChaosFinalityTag$ + - id: smoke/log_poller_test.go:^TestLogPollerWithChaosFinalityTag$ path: integration-tests/smoke/log_poller_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestLogPollerWithChaosFinalityTag$ -test.parallel=1 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-log_poller-evm-simulated - - id: integration-tests/smoke/log_poller_test.go:^TestLogPollerWithChaosPostgresFinalityTag$ + - id: smoke/log_poller_test.go:^TestLogPollerWithChaosPostgresFinalityTag$ path: integration-tests/smoke/log_poller_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestLogPollerWithChaosPostgresFinalityTag$ -test.parallel=1 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-log_poller-evm-simulated - - id: integration-tests/smoke/log_poller_test.go:^TestLogPollerWithChaosPostgresFixedDepth$ + - id: smoke/log_poller_test.go:^TestLogPollerWithChaosPostgresFixedDepth$ path: integration-tests/smoke/log_poller_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestLogPollerWithChaosPostgresFixedDepth$ -test.parallel=1 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-log_poller-evm-simulated - - id: integration-tests/smoke/log_poller_test.go:^TestLogPollerReplayFixedDepth$ + - id: smoke/log_poller_test.go:^TestLogPollerReplayFixedDepth$ path: integration-tests/smoke/log_poller_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestLogPollerReplayFixedDepth$ -test.parallel=1 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-log_poller-evm-simulated - - id: integration-tests/smoke/log_poller_test.go:^TestLogPollerReplayFinalityTag$ + - id: smoke/log_poller_test.go:^TestLogPollerReplayFinalityTag$ path: integration-tests/smoke/log_poller_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/smoke && go test -test.run ^TestLogPollerReplayFinalityTag$ -test.parallel=1 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-log_poller-evm-simulated @@ -767,58 +850,303 @@ runner-test-matrix: # START: Other tests - - id: integration-tests/smoke/runlog_test.go:* + - id: smoke/runlog_test.go:* path: integration-tests/smoke/runlog_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests - test_cmd: cd integration-tests/ && go test smoke/runlog_test.go -timeout 30m -count=1 -json + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests + test_cmd: cd integration-tests/ && go test smoke/runlog_test.go -timeout 30m -test.parallel=2 -count=1 -json pyroscope_env: ci-smoke-runlog-evm-simulated - - id: integration-tests/smoke/cron_test.go:* + - id: smoke/cron_test.go:* path: integration-tests/smoke/cron_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/ && go test smoke/cron_test.go -timeout 30m -count=1 -json pyroscope_env: ci-smoke-cron-evm-simulated - - id: integration-tests/smoke/flux_test.go:* + - id: smoke/flux_test.go:* path: integration-tests/smoke/flux_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/ && go test smoke/flux_test.go -timeout 30m -count=1 -json pyroscope_env: ci-smoke-flux-evm-simulated - - id: integration-tests/smoke/reorg_above_finality_test.go:* + - id: smoke/reorg_above_finality_test.go:* path: integration-tests/smoke/reorg_above_finality_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/ && go test smoke/reorg_above_finality_test.go -timeout 30m -count=1 -json pyroscope_env: ci-smoke-reorg-above-finality-evm-simulated - - id: integration-tests/migration/upgrade_version_test.go:* + - id: migration/upgrade_version_test.go:* path: integration-tests/migration/upgrade_version_test.go test_env_type: docker runs_on: ubuntu-latest - workflows: - - Run PR E2E Tests - - Run Nightly E2E Tests + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests test_cmd: cd integration-tests/migration && go test upgrade_version_test.go -timeout 30m -count=1 -test.parallel=2 -json - test_inputs: - chainlink_image: public.ecr.aws/chainlink/chainlink - chainlink_version: '{{ env.LATEST_CHAINLINK_RELEASE_VERSION }}' - chainlink_upgrade_image: '{{ env.QA_CHAINLINK_IMAGE }}' - chainlink_upgrade_version: develop + test_env_vars: + E2E_TEST_CHAINLINK_IMAGE: public.ecr.aws/w0i8p0z9/chainlink-ccip + E2E_TEST_CHAINLINK_VERSION: '{{ env.LATEST_CHAINLINK_RELEASE_VERSION }}' + E2E_TEST_CHAINLINK_UPGRADE_IMAGE: '{{ env.QA_CHAINLINK_IMAGE }}' + E2E_TEST_CHAINLINK_UPGRADE_VERSION: '{{ env.DEFAULT_CHAINLINK_VERSION }}' + + - id: smoke/job_distributor_test.go:* + path: integration-tests/smoke/job_distributor_test.go + test_env_type: docker + runs_on: ubuntu-latest + triggers: + - PR E2E Core Tests + - Merge Queue E2E Core Tests + - Nightly E2E Tests + test_cmd: cd integration-tests/ && go test smoke/job_distributor_test.go -timeout 30m -count=1 -json + pyroscope_env: ci-smoke-jd-evm-simulated # END: Other tests + + # START: CCIP tests + + - id: ccip-smoke + path: integration-tests/ccip-tests/smoke/ccip_test.go + test_env_type: docker + runs_on: ubuntu-latest + triggers: + - PR E2E CCIP Tests + - Merge Queue E2E CCIP Tests + - Nightly E2E Tests + test_cmd: cd integration-tests/ccip-tests/smoke && go test ccip_test.go -test.run ^TestSmokeCCIPForBidirectionalLane$ -timeout 30m -count=1 -test.parallel=1 -json + test_env_vars: + E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + + - id: ccip-smoke-1.4-pools + path: integration-tests/ccip-tests/smoke/ccip_test.go + test_env_type: docker + runs_on: ubuntu-latest + triggers: + - PR E2E CCIP Tests + - Merge Queue E2E CCIP Tests + - Nightly E2E Tests + test_cmd: cd integration-tests/ccip-tests/smoke && go test ccip_test.go -test.run ^TestSmokeCCIPForBidirectionalLane$ -timeout 30m -count=1 -test.parallel=1 -json + test_env_vars: + E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + test_config_override_path: integration-tests/ccip-tests/testconfig/tomls/contract-version1.4.toml + + - id: ccip-smoke-usdc + path: integration-tests/ccip-tests/smoke/ccip_test.go + test_env_type: docker + runs_on: ubuntu-latest + triggers: + - PR E2E CCIP Tests + - Merge Queue E2E CCIP Tests + - Nightly E2E Tests + test_cmd: cd integration-tests/ccip-tests/smoke && go test ccip_test.go -test.run ^TestSmokeCCIPForBidirectionalLane$ -timeout 30m -count=1 -test.parallel=1 -json + test_env_vars: + E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + test_config_override_path: integration-tests/ccip-tests/testconfig/tomls/usdc_mock_deployment.toml + + - id: ccip-smoke-db-compatibility + path: integration-tests/ccip-tests/smoke/ccip_test.go + test_env_type: docker + runs_on: ubuntu-latest + triggers: + - PR E2E CCIP Tests + - Merge Queue E2E CCIP Tests + - Nightly E2E Tests + test_cmd: cd integration-tests/ccip-tests/smoke && go test ccip_test.go -test.run ^TestSmokeCCIPForBidirectionalLane$ -timeout 30m -count=1 -test.parallel=1 -json + test_env_vars: + E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + test_config_override_path: integration-tests/ccip-tests/testconfig/tomls/db-compatibility.toml + + - id: ccip-smoke-leader-lane + path: integration-tests/ccip-tests/smoke/ccip_test.go + test_env_type: docker + runs_on: ubuntu-latest + # Leader lane test is flakey in Core repo - Need to fix CCIP-3074 to enable it. + triggers: + # - PR E2E CCIP Tests + # - Nightly E2E Tests + test_cmd: cd integration-tests/ccip-tests/smoke && go test ccip_test.go -test.run ^TestSmokeCCIPForBidirectionalLane$ -timeout 30m -count=1 -test.parallel=1 -json + test_env_vars: + E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + test_config_override_path: integration-tests/ccip-tests/testconfig/tomls/leader-lane.toml + + - id: ccip-tests/smoke/ccip_test.go:^TestSmokeCCIPTokenPoolRateLimits$ + path: integration-tests/ccip-tests/smoke/ccip_test.go + test_env_type: docker + runs_on: ubuntu-latest + triggers: + - PR E2E CCIP Tests + - Merge Queue E2E CCIP Tests + - Nightly E2E Tests + test_cmd: cd integration-tests/ccip-tests/smoke && go test ccip_test.go -test.run ^TestSmokeCCIPTokenPoolRateLimits$ -timeout 30m -count=1 -test.parallel=1 -json + test_env_vars: + E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + + - id: ccip-tests/smoke/ccip_test.go:^TestSmokeCCIPMulticall$ + path: integration-tests/ccip-tests/smoke/ccip_test.go + test_env_type: docker + runs_on: ubuntu-latest + triggers: + - PR E2E CCIP Tests + - Merge Queue E2E CCIP Tests + - Nightly E2E Tests + test_cmd: cd integration-tests/ccip-tests/smoke && go test ccip_test.go -test.run ^TestSmokeCCIPMulticall$ -timeout 30m -count=1 -test.parallel=1 -json + test_env_vars: + E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + + - id: ccip-tests/smoke/ccip_test.go:^TestSmokeCCIPManuallyExecuteAfterExecutionFailingDueToInsufficientGas$ + path: integration-tests/ccip-tests/smoke/ccip_test.go + test_env_type: docker + runs_on: ubuntu-latest + triggers: + - PR E2E CCIP Tests + - Merge Queue E2E CCIP Tests + - Nightly E2E Tests + test_cmd: cd integration-tests/ccip-tests/smoke && go test ccip_test.go -test.run ^TestSmokeCCIPManuallyExecuteAfterExecutionFailingDueToInsufficientGas$ -timeout 30m -count=1 -test.parallel=1 -json + test_env_vars: + E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + + - id: ccip-tests/smoke/ccip_test.go:^TestSmokeCCIPOnRampLimits$ + path: integration-tests/ccip-tests/smoke/ccip_test.go + test_env_type: docker + runs_on: ubuntu-latest + triggers: + - PR E2E CCIP Tests + - Merge Queue E2E CCIP Tests + - Nightly E2E Tests + test_cmd: cd integration-tests/ccip-tests/smoke && go test ccip_test.go -test.run ^TestSmokeCCIPOnRampLimits$ -timeout 30m -count=1 -test.parallel=1 -json + test_env_vars: + E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + + - id: ccip-tests/smoke/ccip_test.go:^TestSmokeCCIPOffRampCapacityLimit$ + path: integration-tests/ccip-tests/smoke/ccip_test.go + test_env_type: docker + runs_on: ubuntu-latest + triggers: + - 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: + E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + + - id: ccip-tests/smoke/ccip_test.go:^TestSmokeCCIPOffRampAggRateLimit$ + path: integration-tests/ccip-tests/smoke/ccip_test.go + test_env_type: docker + runs_on: ubuntu-latest + triggers: + - 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: + E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + + - id: ccip-tests/smoke/ccip_test.go:^TestSmokeCCIPReorgBelowFinality$ + path: integration-tests/ccip-tests/smoke/ccip_test.go + test_env_type: docker + runs_on: ubuntu-latest + triggers: + - PR E2E CCIP Tests + - Merge Queue E2E CCIP Tests + - Nightly E2E Tests + test_cmd: cd integration-tests/ccip-tests/smoke && go test ccip_test.go -test.run ^TestSmokeCCIPReorgBelowFinality$ -timeout 30m -count=1 -test.parallel=1 -json + test_env_vars: + E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + test_config_override_path: integration-tests/ccip-tests/testconfig/tomls/ccip-reorg.toml + + - id: ccip-tests/smoke/ccip_test.go:^TestSmokeCCIPReorgAboveFinalityAtDestination$ + path: integration-tests/ccip-tests/smoke/ccip_test.go + test_env_type: docker + runs_on: ubuntu-latest + triggers: + - PR E2E CCIP Tests + - Merge Queue E2E CCIP Tests + - Nightly E2E Tests + test_cmd: cd integration-tests/ccip-tests/smoke && go test ccip_test.go -test.run ^TestSmokeCCIPReorgAboveFinalityAtDestination$ -timeout 30m -count=1 -test.parallel=1 -json + test_env_vars: + E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + test_config_override_path: integration-tests/ccip-tests/testconfig/tomls/ccip-reorg.toml + + - id: ccip-tests/smoke/ccip_test.go:^TestSmokeCCIPReorgAboveFinalityAtSource$ + path: integration-tests/ccip-tests/smoke/ccip_test.go + test_env_type: docker + runs_on: ubuntu-latest + triggers: + - PR E2E CCIP Tests + - Merge Queue E2E CCIP Tests + - Nightly E2E Tests + test_cmd: cd integration-tests/ccip-tests/smoke && go test ccip_test.go -test.run ^TestSmokeCCIPReorgAboveFinalityAtSource$ -timeout 30m -count=1 -test.parallel=1 -json + test_env_vars: + E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + test_config_override_path: integration-tests/ccip-tests/testconfig/tomls/ccip-reorg.toml + + - id: integration-tests/ccip-tests/load/ccip_test.go:TestLoadCCIPStableRPS + path: integration-tests/ccip-tests/load/ccip_test.go + test_env_type: k8s-remote-runner + runs_on: ubuntu-latest + test_cmd: cd integration-tests/ccip-tests/load && DETACH_RUNNER=false go test -test.run ^TestLoadCCIPStableRPS$ -timeout 70m -count=1 -test.parallel=1 -json + test_env_vars: + TEST_SUITE: ccip-load + E2E_TEST_GRAFANA_DASHBOARD_URL: "/d/6vjVx-1V8/ccip-long-running-tests" + triggers: + - E2E CCIP Load Tests + test_artifacts_on_failure: + - ./integration-tests/load/logs/payload_ccip.json + + # Enable when CCIP-2277 is resolved + # + # - id: integration-tests/ccip-tests/load/ccip_test.go:TestLoadCCIPStableRPSAfterARMCurseAndUncurse + # path: integration-tests/ccip-tests/load/ccip_test.go + # test_env_type: k8s-remote-runner + # runs_on: ubuntu-latest + # test_cmd: cd integration-tests/ccip-tests/load && DETACH_RUNNER=false go test -test.run $TestLoadCCIPStableRPSAfterARMCurseAndUncurse$ -timeout 70m -count=1 -test.parallel=1 -json + # test_config_override_path: integration-tests/ccip-tests/testconfig/tomls/load-with-arm-curse-uncurse.toml + # test_env_vars: + # E2E_TEST_GRAFANA_DASHBOARD_URL: "/d/6vjVx-1V8/ccip-long-running-tests" + # triggers: + # - E2E CCIP Load Tests + # test_artifacts_on_failure: + # - ./integration-tests/load/logs/payload_ccip.json + + - id: ccip-tests/chaos/ccip_test.go + path: integration-tests/ccip-tests/chaos/ccip_test.go + test_env_type: k8s-remote-runner + runs_on: ubuntu-latest + triggers: + - E2E CCIP Chaos Tests + test_cmd: cd integration-tests/ccip-tests/chaos && DETACH_RUNNER=false go test ccip_test.go -v -test.parallel=11 -timeout 60m -count=1 -json + test_env_vars: + TEST_SUITE: chaos + TEST_TRIGGERED_BY: ccip-cron-chaos-eth + TEST_LOG_LEVEL: debug + + - id: ccip-tests/load/ccip_test.go:^TestLoadCCIPStableWithPodChaosDiffCommitAndExec + path: integration-tests/ccip-tests/load/ccip_test.go + test_env_type: k8s-remote-runner + runs_on: ubuntu-latest + triggers: + # Disabled until CCIP-2555 is resolved + # - E2E CCIP Chaos Tests + test_cmd: cd integration-tests/ccip-tests/load && DETACH_RUNNER=false go test -run '^TestLoadCCIPStableWithPodChaosDiffCommitAndExec' -v -test.parallel=4 -timeout 120m -count=1 -json + test_env_vars: + TEST_SUITE: chaos + TEST_TRIGGERED_BY: ccip-cron-chaos-eth + TEST_LOG_LEVEL: debug + E2E_TEST_GRAFANA_DASHBOARD_URL: /d/6vjVx-1V8/ccip-long-running-tests + + # END: CCIP tests \ No newline at end of file diff --git a/.github/scripts/functions.sh b/.github/scripts/functions.sh deleted file mode 100644 index 53b5339226..0000000000 --- a/.github/scripts/functions.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -# Function to convert a comma-separated list into a TOML array format. -# Usage: convert_to_toml_array "elem1,elem2,elem3" -# Effect: "a,b,c" -> ["a","b","c"] -function convert_to_toml_array() { - local IFS=',' - local input_array=($1) - local toml_array_format="[" - - for element in "${input_array[@]}"; do - toml_array_format+="\"$element\"," - done - - toml_array_format="${toml_array_format%,}]" - echo "$toml_array_format" -} \ No newline at end of file diff --git a/.github/scripts/jira/package.json b/.github/scripts/jira/package.json deleted file mode 100644 index 2c57d35a64..0000000000 --- a/.github/scripts/jira/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "jira", - "version": "0.1.0", - "description": "Updates Jira issue with release information like the version and tags for a PR.", - "main": "update-jira-issue.js", - "type": "module", - "private": true, - "keywords": [], - "author": "", - "license": "MIT", - "engines": { - "node": ">=18", - "pnpm": ">=9" - }, - "dependencies": { - "@actions/core": "^1.10.1", - "node-fetch": "^2.7.0" - } -} diff --git a/.github/scripts/jira/pnpm-lock.yaml b/.github/scripts/jira/pnpm-lock.yaml deleted file mode 100644 index 09ba8c749c..0000000000 --- a/.github/scripts/jira/pnpm-lock.yaml +++ /dev/null @@ -1,93 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@actions/core': - specifier: ^1.10.1 - version: 1.10.1 - node-fetch: - specifier: ^2.7.0 - version: 2.7.0 - -packages: - - '@actions/core@1.10.1': - resolution: {integrity: sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g==} - - '@actions/http-client@2.2.1': - resolution: {integrity: sha512-KhC/cZsq7f8I4LfZSJKgCvEwfkE8o1538VoBeoGzokVLLnbFDEAdFD3UhoMklxo2un9NJVBdANOresx7vTHlHw==} - - '@fastify/busboy@2.1.1': - resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} - engines: {node: '>=14'} - - node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - - tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - - tunnel@0.0.6: - resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} - engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} - - undici@5.28.4: - resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} - engines: {node: '>=14.0'} - - uuid@8.3.2: - resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} - hasBin: true - - webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - - whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - -snapshots: - - '@actions/core@1.10.1': - dependencies: - '@actions/http-client': 2.2.1 - uuid: 8.3.2 - - '@actions/http-client@2.2.1': - dependencies: - tunnel: 0.0.6 - undici: 5.28.4 - - '@fastify/busboy@2.1.1': {} - - node-fetch@2.7.0: - dependencies: - whatwg-url: 5.0.0 - - tr46@0.0.3: {} - - tunnel@0.0.6: {} - - undici@5.28.4: - dependencies: - '@fastify/busboy': 2.1.1 - - uuid@8.3.2: {} - - webidl-conversions@3.0.1: {} - - whatwg-url@5.0.0: - dependencies: - tr46: 0.0.3 - webidl-conversions: 3.0.1 diff --git a/.github/scripts/jira/update-jira-issue.js b/.github/scripts/jira/update-jira-issue.js deleted file mode 100644 index 77d5b81bff..0000000000 --- a/.github/scripts/jira/update-jira-issue.js +++ /dev/null @@ -1,118 +0,0 @@ -#!/usr/bin/env node - -import * as core from "@actions/core"; -import fetch from "node-fetch"; - -function parseIssueNumber(prTitle, commitMessage, branchName) { - const jiraIssueRegex = /[A-Z]{2,}-\d+/; - if (!!branchName && jiraIssueRegex.test(branchName.toUpperCase())) { - return branchName.toUpperCase().match(jiraIssueRegex)[0]; - } else if ( - !!commitMessage && - jiraIssueRegex.test(commitMessage.toUpperCase()) - ) { - return commitMessage.toUpperCase().match(jiraIssueRegex)[0]; - } else if (!!prTitle && jiraIssueRegex.test(prTitle.toUpperCase())) { - return prTitle.toUpperCase().match(jiraIssueRegex)[0]; - } else { - return null; - } -} - -function getLabels(tags) { - const labelPrefix = "core-release"; - return tags.map((tag) => { - return { - add: `${labelPrefix}/${tag.substring(1)}`, - }; - }); -} - -async function updateJiraIssue( - jiraHost, - jiraUserName, - jiraApiToken, - issueNumber, - tags, - fixVersionName -) { - const token = Buffer.from(`${jiraUserName}:${jiraApiToken}`).toString( - "base64" - ); - const bodyData = { - update: { - labels: getLabels(tags), - fixVersions: [{ set: [{ name: fixVersionName }] }], - }, - }; - - fetch(`https://${jiraHost}/rest/api/3/issue/${issueNumber}`, { - method: "PUT", - headers: { - Authorization: `Basic ${token}`, - Accept: "application/json", - "Content-Type": "application/json", - }, - body: JSON.stringify(bodyData), - }) - .then((response) => { - console.log(`Response: ${JSON.stringify(response)}`); - return response.text(); - }) - .then((text) => console.log(text)) - .catch((err) => console.error(err)); -} - -async function run() { - try { - const jiraHost = process.env.JIRA_HOST; - const jiraUserName = process.env.JIRA_USERNAME; - const jiraApiToken = process.env.JIRA_API_TOKEN; - const chainlinkVersion = process.env.CHAINLINK_VERSION; - const prTitle = process.env.PR_TITLE; - const commitMessage = process.env.COMMIT_MESSAGE; - const branchName = process.env.BRANCH_NAME; - // tags are not getting used at the current moment so will always default to [] - const tags = process.env.FOUND_TAGS - ? process.env.FOUND_TAGS.split(",") - : []; - - // Check for the existence of JIRA_HOST and JIRA_USERNAME and JIRA_API_TOKEN - if (!jiraHost || !jiraUserName || !jiraApiToken) { - core.setFailed( - "Error: Missing required environment variables: JIRA_HOST and JIRA_USERNAME and JIRA_API_TOKEN." - ); - return; - } - - // Checks for the Jira issue number and exit if it can't find it - const issueNumber = parseIssueNumber(prTitle, commitMessage, branchName); - if (!issueNumber) { - core.info( - "No JIRA issue number found in: PR title, commit message, or branch name. Please include the issue ID in one of these." - ); - core.notice( - "No JIRA issue number found in: PR title, commit message, or branch name. Please include the issue ID in one of these." - ); - core.setOutput( - "jiraComment", - "> :medal_military: No JIRA issue number found - Please include it in the PR title or in a commit message." - ); - return; - } - const fixVersionName = `chainlink-v${chainlinkVersion}`; - await updateJiraIssue( - jiraHost, - jiraUserName, - jiraApiToken, - issueNumber, - tags, - fixVersionName - ); - core.setOutput("jiraComment", ""); - } catch (error) { - core.setFailed(error.message); - } -} - -run(); diff --git a/.github/workflows/automation-benchmark-tests.yml b/.github/workflows/automation-benchmark-tests.yml index c21171a83d..7d46b8e0c2 100644 --- a/.github/workflows/automation-benchmark-tests.yml +++ b/.github/workflows/automation-benchmark-tests.yml @@ -2,100 +2,53 @@ name: Automation Benchmark Test on: workflow_dispatch: inputs: - testType: - description: Type of test to run (benchmark, soak) - required: true - default: benchmark - type: string - base64Config: - description: base64-ed config - required: true - type: string + test_config_override_path: + description: Path to a test config file used to override the default test config + required: false + type: string + test_secrets_override_key: + description: Key to run tests with custom test secrets + required: false + type: string slackMemberID: description: Notifies test results (Not your @) required: true default: U02Q14G80TY type: string + testType: + description: Type of test to run (benchmark, soak) + required: true + default: benchmark + type: string + jobs: - automation_benchmark: - environment: integration - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - name: Automation Benchmark Test - runs-on: ubuntu22.04-16cores-64GB - env: - SLACK_API_KEY: ${{ secrets.QA_SLACK_API_KEY }} + run-e2e-tests-workflow: + name: Run E2E Tests + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@aad83f232743646faa35f5ac03ee3829148d37ce + with: + test_path: .github/e2e-tests.yml + test_ids: '${{ inputs.testType }}/automation_test.go:TestAutomationBenchmark' + test_config_override_path: ${{ inputs.test_config_override_path }} + SLACK_USER: ${{ inputs.slackMemberID }} SLACK_CHANNEL: C03KJ5S7KEK - CHAINLINK_ENV_USER: ${{ github.actor }} - REF_NAME: ${{ github.head_ref || github.ref_name }} - steps: - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - ref: ${{ env.REF_NAME }} - - name: Get Slack config and mask base64 config - run: | - SLACK_USER=$(jq -r '.inputs.slackMemberID' $GITHUB_EVENT_PATH) - echo ::add-mask::$SLACK_USER - echo SLACK_USER=$SLACK_USER >> $GITHUB_ENV - - BASE64_CONFIG_OVERRIDE=$(jq -r '.inputs.base64Config' $GITHUB_EVENT_PATH) - echo ::add-mask::$BASE64_CONFIG_OVERRIDE - echo "BASE64_CONFIG_OVERRIDE=$BASE64_CONFIG_OVERRIDE" >> $GITHUB_ENV - - name: Parse base64 config - uses: ./.github/actions/setup-parse-base64-config - with: - base64Config: ${{ env.BASE64_CONFIG_OVERRIDE }} - - name: Send details to Step Summary - shell: bash - run: | - echo "### chainlink image used for this test run :link:" >>$GITHUB_STEP_SUMMARY - echo "\`${{ env.CHAINLINK_IMAGE }}\`" >>$GITHUB_STEP_SUMMARY - echo "### chainlink-tests image tag for this test run :ship:" >>$GITHUB_STEP_SUMMARY - echo "\`${GITHUB_SHA}\`" >>$GITHUB_STEP_SUMMARY - echo "### Networks on which test was run" >>$GITHUB_STEP_SUMMARY - echo "\`${{ env.NETWORKS }}\`" >>$GITHUB_STEP_SUMMARY - - name: Build Test Image - uses: ./.github/actions/build-test-image - with: - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - suites: benchmark chaos reorg load - - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@2967f2287bd3f3ddbac7b476e9568993df01796e # v2.3.27 - env: - DETACH_RUNNER: true - TEST_SUITE: benchmark - TEST_ARGS: -test.timeout 720h - ENV_JOB_IMAGE: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink-tests:${{ github.sha }} - INTERNAL_DOCKER_REPO: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com - TEST_TYPE: ${{ github.event.inputs.testType }} - TEST_TEST_TYPE: ${{ github.event.inputs.testType }} - RR_MEM: 4Gi - TEST_LOG_LEVEL: info - with: - test_command_to_run: cd integration-tests && go test -timeout 30m -v -run ^TestAutomationBenchmark$ ./benchmark -count=1 - test_download_vendor_packages_command: make gomod - cl_repo: ${{ env.CHAINLINK_IMAGE }} - cl_image_tag: ${{ env.CHAINLINK_VERSION }} - token: ${{ secrets.GITHUB_TOKEN }} - should_cleanup: false - go_mod_path: ./integration-tests/go.mod - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} - - name: Collect Metrics - if: always() - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: automation-benchmark-build-test-image - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: Automation Benchmark Test - continue-on-error: true + secrets: + QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} + QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} + QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} + QA_PYROSCOPE_INSTANCE: ${{ secrets.QA_PYROSCOPE_INSTANCE }} + QA_PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} + QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} + GRAFANA_INTERNAL_TENANT_ID: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} + GRAFANA_INTERNAL_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} + GRAFANA_INTERNAL_HOST: ${{ secrets.GRAFANA_INTERNAL_HOST }} + GRAFANA_INTERNAL_URL_SHORTENER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} + LOKI_TENANT_ID: ${{ secrets.LOKI_TENANT_ID }} + LOKI_URL: ${{ secrets.LOKI_URL }} + LOKI_BASIC_AUTH: ${{ secrets.LOKI_BASIC_AUTH }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + AWS_REGION: ${{ secrets.QA_AWS_REGION }} + AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} + AWS_API_GW_HOST_GRAFANA: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} + TEST_SECRETS_OVERRIDE_BASE64: ${{ secrets[inputs.test_secrets_override_key] }} + SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} + SLACK_API_KEY: ${{ secrets.QA_SLACK_API_KEY }} diff --git a/.github/workflows/automation-load-tests.yml b/.github/workflows/automation-load-tests.yml index 187ca3b5b6..5d37e81c14 100644 --- a/.github/workflows/automation-load-tests.yml +++ b/.github/workflows/automation-load-tests.yml @@ -2,121 +2,48 @@ name: Automation Load Test on: workflow_dispatch: inputs: - base64Config: - description: base64-ed config - required: true - type: string + test_config_override_path: + description: Path to a test config file used to override the default test config + required: false + type: string + test_secrets_override_key: + description: 'Key to run tests with custom test secrets' + required: false + type: string slackMemberID: description: Notifies test results (Not your @) required: true default: U02Q14G80TY - type: string - test_secrets_override_key: - description: 'Key to run tests with custom test secrets' - required: false - type: string + type: string jobs: - automation_load: - environment: integration - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - name: Automation Load Test - runs-on: ubuntu22.04-16cores-64GB - env: - SLACK_API_KEY: ${{ secrets.QA_SLACK_API_KEY }} + run-e2e-tests-workflow: + name: Run E2E Tests + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@aad83f232743646faa35f5ac03ee3829148d37ce + with: + test_path: .github/e2e-tests.yml + test_ids: 'load/automationv2_1/automationv2_1_test.go:TestLogTrigger' + test_config_override_path: ${{ inputs.test_config_override_path }} + SLACK_USER: ${{ inputs.slackMemberID }} SLACK_CHANNEL: C03KJ5S7KEK - CHAINLINK_ENV_USER: ${{ github.actor }} - REF_NAME: ${{ github.head_ref || github.ref_name }} - steps: - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - ref: ${{ env.REF_NAME }} - - name: Get Slack config and mask base64 config - run: | - SLACK_USER=$(jq -r '.inputs.slackMemberID' $GITHUB_EVENT_PATH) - echo ::add-mask::$SLACK_USER - echo SLACK_USER=$SLACK_USER >> $GITHUB_ENV - - BASE64_CONFIG_OVERRIDE=$(jq -r '.inputs.base64Config' $GITHUB_EVENT_PATH) - echo ::add-mask::$BASE64_CONFIG_OVERRIDE - echo "BASE64_CONFIG_OVERRIDE=$BASE64_CONFIG_OVERRIDE" >> $GITHUB_ENV - - name: Merge Pyrsoscope config - env: - PYROSCOPE_SERVER: ${{ secrets.QA_PYROSCOPE_INSTANCE }} - PYROSCOPE_ENVIRONMENT: "automation-load-test" - PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} - run: | - decoded_toml=$(echo $BASE64_CONFIG_OVERRIDE | base64 -d) - - # use Pyroscope config from GH secrets and merge it with base64 input - cat << EOF > config.toml - server_url="$PYROSCOPE_SERVER" - environment="$PYROSCOPE_ENVIRONMENT" - key_secret="$PYROSCOPE_KEY" - EOF - - echo "$decoded_toml" >> final_config.toml - cat config.toml >> final_config.toml - BASE64_CONFIG_OVERRIDE=$(cat final_config.toml | base64 -w 0) - echo ::add-mask::$BASE64_CONFIG_OVERRIDE - echo "BASE64_CONFIG_OVERRIDE=$BASE64_CONFIG_OVERRIDE" >> $GITHUB_ENV - - name: Parse base64 config - uses: ./.github/actions/setup-parse-base64-config - with: - base64Config: ${{ env.BASE64_CONFIG_OVERRIDE }} - - name: Send details to Step Summary - shell: bash - run: | - echo "### chainlink image used for this test run :link:" >>$GITHUB_STEP_SUMMARY - echo "\`${{ env.CHAINLINK_IMAGE }}\`" >>$GITHUB_STEP_SUMMARY - echo "### chainlink-tests image tag for this test run :ship:" >>$GITHUB_STEP_SUMMARY - echo "\`${GITHUB_SHA}\`" >>$GITHUB_STEP_SUMMARY - echo "### Networks on which test was run" >>$GITHUB_STEP_SUMMARY - echo "\`${{ env.NETWORKS }}\`" >>$GITHUB_STEP_SUMMARY - - name: Build Test Image - uses: ./.github/actions/build-test-image - with: - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - suites: benchmark chaos reorg load - - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@2967f2287bd3f3ddbac7b476e9568993df01796e # v2.3.27 - env: - RR_CPU: 4000m - RR_MEM: 4Gi - DETACH_RUNNER: true - TEST_SUITE: automationv2_1 - TEST_ARGS: -test.timeout 720h - ENV_JOB_IMAGE: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink-tests:${{ github.sha }} - INTERNAL_DOCKER_REPO: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com - PYROSCOPE_SERVER: ${{ secrets.QA_PYROSCOPE_INSTANCE }} - PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} - with: - test_command_to_run: cd integration-tests/load && go test -timeout 1h -v -run TestLogTrigger ./automationv2_1 -count=1 - test_secrets_override_base64: ${{ secrets[inputs.test_secrets_override_key] }} - test_download_vendor_packages_command: make gomod - cl_repo: ${{ env.CHAINLINK_IMAGE }} - cl_image_tag: ${{ env.CHAINLINK_VERSION }} - token: ${{ secrets.GITHUB_TOKEN }} - should_cleanup: false - go_mod_path: ./integration-tests/go.mod - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} - - name: Collect Metrics - if: always() - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: automation-load-test - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: Automation Load Test - continue-on-error: true + secrets: + QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} + QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} + QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} + QA_PYROSCOPE_INSTANCE: ${{ secrets.QA_PYROSCOPE_INSTANCE }} + QA_PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} + QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} + GRAFANA_INTERNAL_TENANT_ID: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} + GRAFANA_INTERNAL_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} + GRAFANA_INTERNAL_HOST: ${{ secrets.GRAFANA_INTERNAL_HOST }} + GRAFANA_INTERNAL_URL_SHORTENER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} + LOKI_TENANT_ID: ${{ secrets.LOKI_TENANT_ID }} + LOKI_URL: ${{ secrets.LOKI_URL }} + LOKI_BASIC_AUTH: ${{ secrets.LOKI_BASIC_AUTH }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + AWS_REGION: ${{ secrets.QA_AWS_REGION }} + AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} + AWS_API_GW_HOST_GRAFANA: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} + TEST_SECRETS_OVERRIDE_BASE64: ${{ secrets[inputs.test_secrets_override_key] }} + SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} + SLACK_API_KEY: ${{ secrets.QA_SLACK_API_KEY }} diff --git a/.github/workflows/automation-nightly-tests.yml b/.github/workflows/automation-nightly-tests.yml index f018124624..1ff80cff3c 100644 --- a/.github/workflows/automation-nightly-tests.yml +++ b/.github/workflows/automation-nightly-tests.yml @@ -7,294 +7,35 @@ on: - "*" workflow_dispatch: -env: - CHAINLINK_IMAGE: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink - jobs: - build-chainlink: - environment: integration - permissions: - id-token: write - contents: read - name: Build Chainlink Image - runs-on: ubuntu22.04-16cores-64GB - steps: - - name: Collect Metrics - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: automation-nightly-build-chainlink - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: Build Chainlink Image - continue-on-error: true - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - ref: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} - - name: Build Chainlink Image - uses: ./.github/actions/build-chainlink-image - with: - tag_suffix: "" - dockerfile: core/chainlink.Dockerfile - git_commit_sha: ${{ github.sha }} - AWS_REGION: ${{ secrets.QA_AWS_REGION }} - AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - - automation-upgrade-tests: - environment: integration - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - needs: [build-chainlink] - env: - CHAINLINK_COMMIT_SHA: ${{ github.sha }} - CHAINLINK_ENV_USER: ${{ github.actor }} - TEST_LOG_LEVEL: info - SELECTED_NETWORKS: "SIMULATED" - strategy: - fail-fast: false - matrix: - tests: - - name: Upgrade 2.0 - id: upgrade-2-0 - suite: smoke - nodes: 1 - os: ubuntu22.04-8cores-32GB - network: SIMULATED - command: -run ^TestAutomationNodeUpgrade/registry_2_0 ./smoke - - name: Upgrade 2.1 - id: upgrade-2-1 - suite: smoke - nodes: 5 - os: ubuntu22.04-8cores-32GB - network: SIMULATED - command: -run ^TestAutomationNodeUpgrade/registry_2_1 ./smoke - - name: Upgrade 2.2 - id: upgrade-2-2 - suite: smoke - nodes: 5 - os: ubuntu22.04-8cores-32GB - network: SIMULATED - command: -run ^TestAutomationNodeUpgrade/registry_2_2 ./smoke - runs-on: ${{ matrix.tests.os }} - name: Automation ${{ matrix.tests.name }} Test - steps: - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - ref: ${{ github.head_ref || github.ref_name }} - - name: Setup GAP for Grafana - uses: smartcontractkit/.github/actions/setup-gap@d316f66b2990ea4daa479daa3de6fc92b00f863e # setup-gap@0.3.2 - with: - # aws inputs - aws-region: ${{ secrets.AWS_REGION }} - aws-role-arn: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} - api-gateway-host: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} - # other inputs - duplicate-authorization-header: "true" - - name: Prepare Base64 TOML override - uses: ./.github/actions/setup-create-base64-upgrade-config - with: - selectedNetworks: ${{ env.SELECTED_NETWORKS }} - chainlinkImage: "public.ecr.aws/chainlink/chainlink" - chainlinkVersion: "latest" - upgradeImage: ${{ env.CHAINLINK_IMAGE }} - upgradeVersion: ${{ github.sha }} - runId: ${{ github.run_id }} - testLogCollect: "true" - lokiEndpoint: https://${{ secrets.GRAFANA_INTERNAL_HOST }}/loki/api/v1/push - lokiTenantId: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - lokiBasicAuth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - logstreamLogTargets: ${{ vars.LOGSTREAM_LOG_TARGETS }} - grafanaUrl: "http://localhost:8080/primary" - grafanaDashboardUrl: "/d/ddf75041-1e39-42af-aa46-361fe4c36e9e/ci-e2e-tests-logs" - grafanaBearerToken: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} - - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@2967f2287bd3f3ddbac7b476e9568993df01796e # v2.3.27 - env: - TEST_SUITE: ${{ matrix.tests.suite }} - with: - test_command_to_run: cd ./integration-tests && go test -timeout 60m -count=1 -json -test.parallel=${{ matrix.tests.nodes }} ${{ matrix.tests.command }} 2>&1 | tee /tmp/gotest.log | gotestloghelper -ci -singlepackage -hidepassingtests=false -hidepassinglogs - test_download_vendor_packages_command: cd ./integration-tests && go mod download - cl_repo: 'public.ecr.aws/chainlink/chainlink' - cl_image_tag: 'latest' - aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - artifacts_location: ./integration-tests/${{ matrix.tests.suite }}/logs - artifacts_name: testcontainers-logs-${{ matrix.tests.name }} - publish_check_name: Automation Results ${{ matrix.tests.name }} - token: ${{ secrets.GITHUB_TOKEN }} - go_mod_path: ./integration-tests/go.mod - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} - - name: Upload test log - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 - if: failure() - with: - name: gotest-logs-${{ matrix.tests.name }} - path: /tmp/gotest.log - retention-days: 7 - continue-on-error: true - - name: Collect Metrics - if: always() - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: automation-nightly-upgrade-tests-${{ matrix.tests.id }} - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: Automation ${{ matrix.tests.name }} Test - test-results-file: '{"testType":"go","filePath":"/tmp/gotest.log"}' - continue-on-error: true - - name: Print failed test summary - if: always() - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/show-test-summary@b49a9d04744b0237908831730f8553f26d73a94b # v2.3.17 - - test-notify: - name: Start Slack Thread - if: ${{ always() && needs.*.result != 'skipped' && needs.*.result != 'cancelled' }} - environment: integration - outputs: - thread_ts: ${{ steps.slack.outputs.thread_ts }} - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - runs-on: ubuntu-latest - needs: [ automation-upgrade-tests ] - steps: - - name: Debug Result - run: echo ${{ join(needs.*.result, ',') }} - - name: Main Slack Notification - uses: slackapi/slack-github-action@6c661ce58804a1a20f6dc5fbee7f0381b469e001 # v1.25.0 - id: slack - with: - channel-id: C03KJ5S7KEK - payload: | - { - "attachments": [ - { - "color": "${{ contains(join(needs.*.result, ','), 'failure') && '#C62828' || '#2E7D32' }}", - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "Automation Nightly Tests ${{ contains(join(needs.*.result, ','), 'failure') && ':x:' || ':white_check_mark:'}}", - "emoji": true - } - }, - { - "type": "divider" - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "<${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ github.ref_name }}|${{ github.ref_name }}> | <${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}|${{ github.sha }}> | <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run>" - } - } - ] - } - ] - } - env: - SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} - - test-results: - name: Post Test Results for ${{ matrix.name }} - if: ${{ always() && needs.*.result != 'skipped' && needs.*.result != 'cancelled' }} - environment: integration - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - runs-on: ubuntu-latest - needs: test-notify - strategy: - fail-fast: false - matrix: - name: [ Upgrade 2.0, Upgrade 2.1, Upgrade 2.2 ] - steps: - - name: Get Results - id: test-results - run: | - # I feel like there's some clever, fully jq way to do this, but I ain't got the motivation to figure it out - echo "Querying test results" - - PARSED_RESULTS=$(curl \ - -H "Authorization: Bearer ${{ github.token }}" \ - 'https://api.github.com/repos/${{github.repository}}/actions/runs/${{ github.run_id }}/jobs' \ - | jq -r --arg pattern "${{ matrix.name }} Test" '.jobs[] - | select(.name | test($pattern)) as $job - | $job.steps[] - | select(.name == "Run Tests") - | { conclusion: (if .conclusion == "success" then ":white_check_mark:" else ":x:" end), product: ("*" + ($job.name | capture($pattern).product) + "*") }') - - echo "Parsed Results:" - echo $PARSED_RESULTS - - ALL_SUCCESS=true - for row in $(echo "$PARSED_RESULTS" | jq -s | jq -r '.[] | select(.conclusion != ":white_check_mark:")'); do - success=false - break - done - - echo all_success=$ALL_SUCCESS >> $GITHUB_OUTPUT - - FORMATTED_RESULTS=$(echo $PARSED_RESULTS | jq -s '[.[] - | { - conclusion: .conclusion, - product: .product - } - ] - | map("{\"type\": \"section\", \"text\": {\"type\": \"mrkdwn\", \"text\": \"\(.product): \(.conclusion)\"}}") - | join(",")') - - echo "Formatted Results:" - echo $FORMATTED_RESULTS - - # Cleans out backslashes and quotes from jq - CLEAN_RESULTS=$(echo "$FORMATTED_RESULTS" | sed 's/\\\"/"/g' | sed 's/^"//;s/"$//') - - echo "Clean Results" - echo $CLEAN_RESULTS - - echo results=$CLEAN_RESULTS >> $GITHUB_OUTPUT - - - name: Test Details - uses: slackapi/slack-github-action@6c661ce58804a1a20f6dc5fbee7f0381b469e001 # v1.25.0 - with: - channel-id: C03KJ5S7KEK - payload: | - { - "thread_ts": "${{ needs.test-notify.outputs.thread_ts }}", - "attachments": [ - { - "color": "${{ steps.test-results.outputs.all_success && '#2E7D32' || '#C62828' }}", - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "${{ matrix.name }} ${{ steps.test-results.outputs.all_success && ':white_check_mark:' || ':x: Notifying <@U02Q14G80TY>'}}", - "emoji": true - } - }, - { - "type": "divider" - }, - ${{ steps.test-results.outputs.results }} - ] - } - ] - } - env: - SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} \ No newline at end of file + run-e2e-tests-workflow: + name: Run E2E Tests + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@aad83f232743646faa35f5ac03ee3829148d37ce + with: + test_path: .github/e2e-tests.yml + test_trigger: Automation Nightly Tests + chainlink_version: ${{ github.sha }} + slack_notification_after_tests: true + slack_notification_after_tests_channel_id: "#automation-test-notifications" + slack_notification_after_tests_name: Automation Nightly E2E Tests + # slack_notification_after_tests_notify_user_id_on_failure: U0XXXXXXX + secrets: + QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} + QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} + QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} + QA_PYROSCOPE_INSTANCE: ${{ secrets.QA_PYROSCOPE_INSTANCE }} + QA_PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} + QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} + GRAFANA_INTERNAL_TENANT_ID: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} + GRAFANA_INTERNAL_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} + GRAFANA_INTERNAL_HOST: ${{ secrets.GRAFANA_INTERNAL_HOST }} + GRAFANA_INTERNAL_URL_SHORTENER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} + LOKI_TENANT_ID: ${{ secrets.LOKI_TENANT_ID }} + LOKI_URL: ${{ secrets.LOKI_URL }} + LOKI_BASIC_AUTH: ${{ secrets.LOKI_BASIC_AUTH }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + AWS_REGION: ${{ secrets.QA_AWS_REGION }} + AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} + AWS_API_GW_HOST_GRAFANA: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} + TEST_SECRETS_OVERRIDE_BASE64: ${{ secrets[inputs.test_secrets_override_key] }} + SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} diff --git a/.github/workflows/automation-ondemand-tests.yml b/.github/workflows/automation-ondemand-tests.yml index 7514743fa8..053a0e9147 100644 --- a/.github/workflows/automation-ondemand-tests.yml +++ b/.github/workflows/automation-ondemand-tests.yml @@ -1,16 +1,17 @@ -name: Automation On Demand Tests +name: Run Automation On Demand Tests + on: workflow_dispatch: inputs: chainlinkVersionUpdate: - description: Chainlink image version to upgrade to + description: Chainlink image version to upgrade to (Leave empty to build from head/ref) required: false type: string chainlinkImageUpdate: - description: Chainlink image repo to upgrade to (Leave empty to build from head/ref) + description: Chainlink image repo to upgrade to options: - - public.ecr.aws/chainlink/chainlink - QA_ECR + - public.ecr.aws/chainlink/chainlink type: choice chainlinkVersion: description: Chainlink image version to use initially for upgrade test @@ -34,285 +35,148 @@ on: type: boolean default: false required: true - -env: - ENV_JOB_IMAGE: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink-tests:${{ github.sha }} - CHAINLINK_IMAGE: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink + with_existing_remote_runner_version: + description: 'Tag of the existing remote runner version to use (Leave empty to build from head/ref)' + required: false + type: string jobs: - build-chainlink: - environment: integration - permissions: - id-token: write - contents: read - strategy: - matrix: - image: - - name: "" - dockerfile: core/chainlink.Dockerfile - tag-suffix: "" - - name: (plugins) - dockerfile: plugins/chainlink.Dockerfile - tag-suffix: -plugins - name: Build Chainlink Image ${{ matrix.image.name }} - runs-on: ubuntu22.04-16cores-64GB + # Set tests to run based on the workflow inputs + set-tests-to-run: + name: Set tests to run + runs-on: ubuntu-latest + outputs: + test_list: ${{ steps.set-tests.outputs.test_list }} + require_chainlink_image_versions_in_qa_ecr: ${{ steps.determine-chainlink-image-check.outputs.require_chainlink_image_versions_in_qa_ecr }} steps: - - name: Collect Metrics - if: inputs.chainlinkImage == '' - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: automation-on-demand-build-chainlink - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: Build Chainlink Image ${{ matrix.image.name }} - continue-on-error: true - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - ref: ${{ github.head_ref || github.ref_name }} - - name: Check if image exists - if: inputs.chainlinkImage == '' - id: check-image - uses: smartcontractkit/chainlink-github-actions/docker/image-exists@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - repository: chainlink - tag: ${{ github.sha }}${{ matrix.image.tag-suffix }} - AWS_REGION: ${{ secrets.QA_AWS_REGION }} - AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - - name: Build Image - if: steps.check-image.outputs.exists == 'false' && inputs.chainlinkImage == '' - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/build-image@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - cl_repo: smartcontractkit/chainlink - cl_ref: ${{ github.sha }} - cl_dockerfile: ${{ matrix.image.dockerfile }} - push_tag: ${{ env.CHAINLINK_IMAGE }}:${{ github.sha }}${{ matrix.image.tag-suffix }} - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - - name: Print Chainlink Image Built - if: inputs.chainlinkImage == '' - run: | - echo "### chainlink node image tag used for this test run :link:" >>$GITHUB_STEP_SUMMARY - echo "\`${GITHUB_SHA}\`" >>$GITHUB_STEP_SUMMARY - - build-test-image: - environment: integration - permissions: - id-token: write - contents: read - name: Build Test Image - runs-on: ubuntu22.04-16cores-64GB - steps: - - name: Collect Metrics - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: automation-on-demand-build-test-image - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: Build Test Image - continue-on-error: true - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - ref: ${{ github.head_ref || github.ref_name }} - - name: Build Test Image - if: inputs.enableChaos || inputs.enableReorg - uses: ./.github/actions/build-test-image - with: - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - - automation-on-demand-tests: - environment: integration - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - needs: [build-chainlink, build-test-image] - env: - CHAINLINK_COMMIT_SHA: ${{ github.sha }} - CHAINLINK_ENV_USER: ${{ github.actor }} - TEST_LOG_LEVEL: info - strategy: - fail-fast: false - matrix: - tests: - - name: chaos - id: chaos - suite: chaos - nodes: 20 - os: ubuntu-latest - enabled: ${{ inputs.enableChaos }} - pyroscope_env: ci-automation-on-demand-chaos - network: SIMULATED - command: -run ^TestAutomationChaos$ ./chaos - - name: reorg 2.0 - id: reorg-2.0 - suite: reorg - nodes: 1 - os: ubuntu-latest - enabled: ${{ inputs.enableReorg }} - pyroscope_env: ci-automation-on-demand-reorg - network: SIMULATED - command: -run ^TestAutomationReorg/registry_2_0 ./reorg - - name: reorg 2.1 - id: reorg-2.1 - suite: reorg - nodes: 2 - os: ubuntu-latest - enabled: ${{ inputs.enableReorg }} - pyroscope_env: ci-automation-on-demand-reorg - network: SIMULATED - command: -run ^TestAutomationReorg/registry_2_1 ./reorg - - name: reorg 2.2 - id: reorg-2.2 - suite: reorg - nodes: 2 - os: ubuntu-latest - enabled: ${{ inputs.enableReorg }} - pyroscope_env: ci-automation-on-demand-reorg - network: SIMULATED - command: -run ^TestAutomationReorg/registry_2_2 ./reorg - - name: reorg 2.3 - id: reorg-2.3 - suite: reorg - nodes: 2 - os: ubuntu-latest - enabled: ${{ inputs.enableReorg }} - pyroscope_env: ci-automation-on-demand-reorg - network: SIMULATED - command: -run ^TestAutomationReorg/registry_2_3 ./reorg - - name: upgrade 2.0 - id: upgrade-2.0 - type: upgrade - suite: smoke - nodes: 1 - os: ubuntu22.04-8cores-32GB - enabled: true - pyroscope_env: ci-automation-on-demand-upgrade - network: SIMULATED - command: -run ^TestAutomationNodeUpgrade/registry_2_0 ./smoke - - name: upgrade 2.1 - id: upgrade-2.1 - type: upgrade - suite: smoke - nodes: 5 - os: ubuntu22.04-8cores-32GB - enabled: true - pyroscope_env: ci-automation-on-demand-upgrade - network: SIMULATED - command: -run ^TestAutomationNodeUpgrade/registry_2_1 ./smoke - - name: upgrade 2.2 - id: upgrade-2.2 - type: upgrade - suite: smoke - nodes: 5 - os: ubuntu22.04-8cores-32GB - enabled: true - pyroscope_env: ci-automation-on-demand-upgrade - network: SIMULATED - command: -run ^TestAutomationNodeUpgrade/registry_2_2 ./smoke - runs-on: ${{ matrix.tests.os }} - name: Automation On Demand ${{ matrix.tests.name }} Test - steps: - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - ref: ${{ github.head_ref || github.ref_name }} - name: Determine build to use id: determine-build shell: bash run: | if [[ "${{ inputs.chainlinkImage }}" == "QA_ECR" ]]; then - echo "image=${{ env.CHAINLINK_IMAGE }}" >>$GITHUB_OUTPUT + echo "image='{{ env.QA_CHAINLINK_IMAGE }}'" >> $GITHUB_ENV else - echo "image=${{ inputs.chainlinkImage }}" >>$GITHUB_OUTPUT + echo "image=${{ inputs.chainlinkImage }}" >> $GITHUB_ENV fi if [[ "${{ inputs.chainlinkImageUpdate }}" == "QA_ECR" ]]; then - echo "upgrade_image=${{ env.CHAINLINK_IMAGE }}" >>$GITHUB_OUTPUT + echo "upgrade_image='{{ env.QA_CHAINLINK_IMAGE }}'" >> $GITHUB_ENV else - echo "upgrade_image=${{ inputs.chainlinkImageUpdate }}" >>$GITHUB_OUTPUT + echo "upgrade_image=${{ inputs.chainlinkImageUpdate }}" >> $GITHUB_ENV fi if [[ -z "${{ inputs.chainlinkVersion }}" ]] && [[ "${{ inputs.chainlinkImage }}" == "QA_ECR" ]]; then - echo "version=${{ github.sha }}" >>$GITHUB_OUTPUT + echo "version=${{ github.sha }}" >> $GITHUB_ENV else - echo "version=${{ inputs.chainlinkVersion }}" >>$GITHUB_OUTPUT + echo "version=${{ inputs.chainlinkVersion }}" >> $GITHUB_ENV fi if [[ -z "${{ inputs.chainlinkVersionUpdate }}" ]] && [[ "${{ inputs.chainlinkImageUpdate }}" == "QA_ECR" ]]; then - echo "upgrade_version=${{ github.sha }}" >>$GITHUB_OUTPUT + echo "upgrade_version=${{ github.sha }}" >> $GITHUB_ENV else - echo "upgrade_version=${{ inputs.chainlinkVersionUpdate }}" >>$GITHUB_OUTPUT + echo "upgrade_version=${{ inputs.chainlinkVersionUpdate }}" >> $GITHUB_ENV + fi + - name: Check if chainlink image check required + id: determine-chainlink-image-check + shell: bash + run: | + chainlink_image_versions="" + if [ "${{ github.event.inputs.chainlinkImage }}" = "QA_ECR" ]; then + chainlink_image_versions+="${{ env.version }}," + fi + if [ "${{ github.event.inputs.chainlinkImageUpdate }}" = "QA_ECR" ]; then + chainlink_image_versions+="${{ env.upgrade_version }}" fi - - name: Setup GAP for Grafana - uses: smartcontractkit/.github/actions/setup-gap@d316f66b2990ea4daa479daa3de6fc92b00f863e # setup-gap@0.3.2 - with: - # aws inputs - aws-region: ${{ secrets.AWS_REGION }} - aws-role-arn: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} - api-gateway-host: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} - # other inputs - duplicate-authorization-header: "true" - - - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@aa8eea635029ab8d95abd3c206f56dae1e22e623 # v2.3.28 - if: ${{ matrix.tests.enabled == true }} - with: - test_config_override_base64: ${{ env.BASE64_CONFIG_OVERRIDE }} - test_command_to_run: cd ./integration-tests && go test -timeout 60m -count=1 -json -test.parallel=${{ matrix.tests.nodes }} ${{ matrix.tests.command }} 2>&1 | tee /tmp/gotest.log | gotestloghelper -ci -singlepackage -hidepassingtests=false -hidepassinglogs - test_download_vendor_packages_command: cd ./integration-tests && go mod download - test_suite: ${{ matrix.tests.suite }} - test_config_chainlink_version: ${{ steps.determine-build.outputs.version }} - test_config_chainlink_upgrade_version: ${{ steps.determine-build.outputs.upgrade_version }} - test_config_selected_networks: ${{ matrix.tests.network }} - test_config_logging_run_id: ${{ github.run_id }} - test_config_logstream_log_targets: ${{ vars.LOGSTREAM_LOG_TARGETS }} - test_config_test_log_collect: "true" - cl_repo: ${{ steps.determine-build.outputs.image }} - cl_image_tag: ${{ steps.determine-build.outputs.version }} - aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - artifacts_location: ./integration-tests/${{ matrix.tests.suite }}/logs - publish_check_name: Automation On Demand Results ${{ matrix.tests.name }} - token: ${{ secrets.GITHUB_TOKEN }} - go_mod_path: ./integration-tests/go.mod - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} - DEFAULT_CHAINLINK_IMAGE: ${{ steps.determine-build.outputs.image }} - DEFAULT_CHAINLINK_UPGRADE_IMAGE: ${{ steps.determine-build.outputs.upgrade_image }} - DEFAULT_LOKI_TENANT_ID: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - DEFAULT_LOKI_ENDPOINT: https://${{ secrets.GRAFANA_INTERNAL_HOST }}/loki/api/v1/push - DEFAULT_LOKI_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - DEFAULT_GRAFANA_BASE_URL: "http://localhost:8080/primary" - DEFAULT_GRAFANA_DASHBOARD_URL: "/d/ddf75041-1e39-42af-aa46-361fe4c36e9e/ci-e2e-tests-logs" - DEFAULT_GRAFANA_BEARER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} - DEFAULT_PYROSCOPE_SERVER_URL: ${{ matrix.tests.pyroscope_env == '' && '' || !startsWith(github.ref, 'refs/tags/') && '' || secrets.QA_PYROSCOPE_INSTANCE }} # Avoid sending blank envs https://github.com/orgs/community/discussions/25725 - DEFAULT_PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} - DEFAULT_PYROSCOPE_ENVIRONMENT: ${{ matrix.tests.pyroscope_env }} - DEFAULT_PYROSCOPE_ENABLED: ${{ matrix.tests.pyroscope_env == '' || !startsWith(github.ref, 'refs/tags/') && 'false' || 'true' }} - - - name: Upload test log - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 - if: failure() - with: - name: test-log-${{ matrix.tests.name }} - path: /tmp/gotest.log - retention-days: 7 - continue-on-error: true - - name: Collect Metrics - if: always() - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: automation-on-demand-tests-${{ matrix.tests.id }} - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: Automation On Demand ${{ matrix.tests.name }} Test - test-results-file: '{"testType":"go","filePath":"/tmp/gotest.log"}' - continue-on-error: true + echo "require_chainlink_image_versions_in_qa_ecr=$chainlink_image_versions" >> $GITHUB_OUTPUT + - name: Set tests to run + id: set-tests + run: | + + # Always run upgrade tests + cat > test_list.yaml <> test_list.yaml <> test_list.yaml <> $GITHUB_OUTPUT + + call-run-e2e-tests-workflow: + name: Run E2E Tests + needs: set-tests-to-run + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@aad83f232743646faa35f5ac03ee3829148d37ce + with: + test_path: .github/e2e-tests.yml + test_list: ${{ needs.set-tests-to-run.outputs.test_list }} + require_chainlink_image_versions_in_qa_ecr: ${{ needs.set-tests-to-run.outputs.require_chainlink_image_versions_in_qa_ecr }} + with_existing_remote_runner_version: ${{ github.event.inputs.with_existing_remote_runner_version }} + test_log_upload_on_failure: true + test_log_upload_retention_days: 7 + secrets: + QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} + QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} + QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} + QA_PYROSCOPE_INSTANCE: ${{ secrets.QA_PYROSCOPE_INSTANCE }} + QA_PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} + QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} + GRAFANA_INTERNAL_TENANT_ID: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} + GRAFANA_INTERNAL_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} + GRAFANA_INTERNAL_HOST: ${{ secrets.GRAFANA_INTERNAL_HOST }} + GRAFANA_INTERNAL_URL_SHORTENER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} + LOKI_TENANT_ID: ${{ secrets.LOKI_TENANT_ID }} + LOKI_URL: ${{ secrets.LOKI_URL }} + LOKI_BASIC_AUTH: ${{ secrets.LOKI_BASIC_AUTH }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + AWS_REGION: ${{ secrets.QA_AWS_REGION }} + AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} + AWS_API_GW_HOST_GRAFANA: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} + diff --git a/.github/workflows/build-publish-develop-pr.yml b/.github/workflows/build-publish-develop-pr.yml new file mode 100644 index 0000000000..2868616ace --- /dev/null +++ b/.github/workflows/build-publish-develop-pr.yml @@ -0,0 +1,115 @@ +name: "Build and Publish Chainlink" + +on: + pull_request: + push: + branches: + - develop + - "release/**" + workflow_dispatch: + inputs: + git_ref: + description: "The git ref to check out" + required: true + build-publish: + description: "Whether to build and publish - defaults to just build" + required: false + default: "false" + +env: + GIT_REF: ${{ github.event.inputs.git_ref || github.ref }} + +jobs: + goreleaser-build-publish-chainlink: + name: "goreleaser-build-publish-${{ matrix.image-name }}" + strategy: + fail-fast: false + matrix: + include: + - image-name: chainlink + goreleaser-config: .goreleaser.develop.yaml + - image-name: ccip + goreleaser-config: .goreleaser.ccip.develop.yaml + runs-on: ubuntu-20.04 + permissions: + id-token: write + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + with: + ref: ${{ env.GIT_REF }} + + # This gets the image tag and whether to publish the image based on the event type + # PR builds: pr-- (if label 'build-publish' is present publishes the image) + # develop builds: develop- and develop (only amd64) + # release builds: release- + # manual builds: (if build-publish is true publishes the image) + - name: Get image tag + id: get-image-tag + run: | + short_sha=$(git rev-parse --short HEAD) + echo "build-publish=false" | tee -a $GITHUB_OUTPUT + if [[ ${{ github.event_name }} == 'push' ]]; then + if [[ ${{ github.ref_name }} == 'release/'* ]]; then + echo "image-tag=release-${short_sha}" | tee -a $GITHUB_OUTPUT + echo "build-publish=true" | tee -a $GITHUB_OUTPUT + else + echo "image-tag=develop" | tee -a $GITHUB_OUTPUT + echo "build-publish=true" | tee -a $GITHUB_OUTPUT + fi + elif [[ ${{ github.event_name }} == 'workflow_dispatch' ]]; then + echo "image-tag=${short_sha}" | tee -a $GITHUB_OUTPUT + echo "build-publish=${{ github.event.inputs.build-publish }}" | tee -a $GITHUB_OUTPUT + else + if [[ ${{ github.event_name }} == "pull_request" ]]; then + echo "image-tag=pr-${{ github.event.number }}-${short_sha}" | tee -a $GITHUB_OUTPUT + if [[ ${{ contains(github.event.pull_request.labels.*.name, 'build-publish') }} == "true" ]]; then + echo "build-publish=true" | tee -a $GITHUB_OUTPUT + fi + fi + fi + + - name: Configure aws credentials + if: steps.get-image-tag.outputs.build-publish == 'true' + uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 + with: + role-to-assume: ${{ secrets.AWS_OIDC_IAM_ROLE_BUILD_PUBLISH_DEVELOP_PR }} + aws-region: ${{ secrets.AWS_REGION }} + mask-aws-account-id: true + role-session-name: goreleaser-build-publish-${{ matrix.image-name }} + + - name: Build and publish images + uses: ./.github/actions/goreleaser-build-sign-publish + with: + enable-docker-publish: ${{ steps.get-image-tag.outputs.build-publish }} + docker-registry: ${{ secrets.AWS_SDLC_ECR_HOSTNAME }} + docker-image-name: ${{ matrix.image-name }} + docker-image-tag: ${{ steps.get-image-tag.outputs.image-tag }} + enable-goreleaser-snapshot: "true" + goreleaser-exec: ./tools/bin/goreleaser_wrapper + goreleaser-config: ${{ matrix.goreleaser-config }} + goreleaser-key: ${{ secrets.GORELEASER_KEY }} + zig-version: 0.11.0 + + - name: Output image name and digest + if: steps.get-image-tag.outputs.build-publish == 'true' + shell: bash + run: | + echo "### Docker Images" | tee -a "$GITHUB_STEP_SUMMARY" + jq -r '.[] | select(.type == "Docker Image") | "\(.name)"' ${artifact_path} >> output.txt + while read -r line; do + echo "$line" | tee -a "$GITHUB_STEP_SUMMARY" + done < output.txt + + - name: Collect Metrics + if: always() + id: collect-gha-metrics + uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 + with: + id: goreleaser-build-publish + org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} + basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} + hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} + this-job-name: goreleaser-build-publish-${{ matrix.image-name }} + continue-on-error: true \ No newline at end of file diff --git a/.github/workflows/build-publish-develop.yml b/.github/workflows/build-publish-develop.yml deleted file mode 100644 index 6e8e5ba3f5..0000000000 --- a/.github/workflows/build-publish-develop.yml +++ /dev/null @@ -1,68 +0,0 @@ -name: "Push develop to private ECR" - -on: - push: - branches: - - ccip-develop - workflow_dispatch: - inputs: - git_ref: - description: "Git ref (commit SHA, branch name, tag name, etc.) to checkout" - required: true -env: - GIT_REF: ${{ github.event.inputs.git_ref || github.ref }} - -jobs: - push-ccip-develop: - runs-on: ubuntu-20.04 - environment: build-develop - permissions: - id-token: write - contents: read - strategy: - matrix: - image: - - name: "" - dockerfile: core/chainlink.Dockerfile - tag-suffix: "" - - name: (plugins) - dockerfile: plugins/chainlink.Dockerfile - tag-suffix: -plugins - name: push-ccip-develop ${{ matrix.image.name }} - steps: - - name: Checkout repository - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - ref: ${{ env.GIT_REF }} - # When this is ran from manual workflow_dispatch, the github.sha may be - # different than the checked out commit sha. The core build uses this - # commit sha as build metadata, so we need to make sure it's correct. - - name: Get checked out git ref - if: github.event.inputs.git_ref - id: git-ref - run: echo "checked-out=$(git rev-parse HEAD)" | tee -a "${GITHUB_OUTPUT}" - - name: Build, sign and publish ccip image - uses: ./.github/actions/build-sign-publish-chainlink - with: - publish: true - aws-role-to-assume: ${{ secrets.AWS_OIDC_IAM_ROLE_ARN }} - aws-role-duration-seconds: ${{ secrets.AWS_ROLE_DURATION_SECONDS }} - aws-region: ${{ secrets.AWS_REGION }} - ecr-hostname: ${{ secrets.AWS_DEVELOP_ECR_HOSTNAME }} - ecr-image-name: ccip-develop - ecr-tag-suffix: ${{ matrix.image.tag-suffix }} - dockerfile: ${{ matrix.image.dockerfile }} - dockerhub_username: ${{ secrets.DOCKER_READONLY_USERNAME }} - dockerhub_password: ${{ secrets.DOCKER_READONLY_PASSWORD }} - git-commit-sha: ${{ steps.git-ref.outputs.checked-out || github.sha }} - - - name: Collect Metrics - if: always() - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: push-ccip-develop ${{ matrix.image.name }} - continue-on-error: true diff --git a/.github/workflows/build-publish-pr.yml b/.github/workflows/build-publish-pr.yml deleted file mode 100644 index fd62739376..0000000000 --- a/.github/workflows/build-publish-pr.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: "Build and Publish from PR" - -## -# This workflow builds and publishes a Docker image for Chainlink from a PR. -# It has its own special IAM role, does not sign the image, and publishes to -# a special ECR repo. -## - -on: - pull_request: - -jobs: - build-publish-untrusted: - if: ${{ ! startsWith(github.ref_name, 'release/') || (! startsWith(github.head_ref, 'release/') && ! startsWith(github.ref_name, 'chore/'))}} - runs-on: ubuntu-20.04 - environment: sdlc - permissions: - id-token: write - contents: read - env: - ECR_IMAGE_NAME: crib-ccip-untrusted - steps: - - name: Checkout repository - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - - name: Git Short SHA - shell: bash - env: - GIT_PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} - run: | - echo "GIT_SHORT_SHA=${GIT_PR_HEAD_SHA:0:7}" | tee -a "$GITHUB_ENV" - - - name: Check if image exists - id: check-image - uses: smartcontractkit/chainlink-github-actions/docker/image-exists@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - repository: ${{ env.ECR_IMAGE_NAME}} - tag: sha-${{ env.GIT_SHORT_SHA }} - AWS_REGION: ${{ secrets.AWS_REGION }} - AWS_ROLE_TO_ASSUME: ${{ secrets.AWS_OIDC_IAM_ROLE_PUBLISH_PR_ARN }} - - - name: Build and publish chainlink image - if: steps.check-image.outputs.exists == 'false' - uses: ./.github/actions/build-sign-publish-chainlink - with: - publish: true - aws-role-to-assume: ${{ secrets.AWS_OIDC_IAM_ROLE_PUBLISH_PR_ARN }} - aws-role-duration-seconds: ${{ secrets.AWS_ROLE_DURATION_SECONDS_DEFAULT }} - aws-region: ${{ secrets.AWS_REGION }} - sign-images: false - ecr-hostname: ${{ secrets.AWS_SDLC_ECR_HOSTNAME }} - ecr-image-name: ${{ env.ECR_IMAGE_NAME }} - dockerhub_username: ${{ secrets.DOCKER_READONLY_USERNAME }} - dockerhub_password: ${{ secrets.DOCKER_READONLY_PASSWORD }} - - - name: Collect Metrics - if: always() - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: build-chainlink-pr - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: build-publish-untrusted - continue-on-error: true diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml index 4a477a0e14..2902433f9d 100644 --- a/.github/workflows/build-publish.yml +++ b/.github/workflows/build-publish.yml @@ -1,4 +1,4 @@ -name: "Build Chainlink and Publish" +name: "Build, Sign and Publish Chainlink" on: # Mimics old circleci behaviour @@ -33,7 +33,8 @@ jobs: environment: build-publish permissions: id-token: write - contents: read + contents: write + attestations: write outputs: docker-image-tag: ${{ steps.build-sign-publish.outputs.docker-image-tag }} docker-image-digest: ${{ steps.build-sign-publish.outputs.docker-image-digest }} @@ -51,14 +52,18 @@ jobs: aws-region: ${{ secrets.AWS_REGION }} ecr-hostname: ${{ env.ECR_HOSTNAME }} ecr-image-name: ${{ env.ECR_IMAGE_NAME }} + dockerhub_username: ${{ secrets.DOCKERHUB_READONLY_USERNAME }} + dockerhub_password: ${{ secrets.DOCKERHUB_READONLY_PASSWORD }} sign-images: true - sign-method: "keypair" - cosign-private-key: ${{ secrets.COSIGN_PRIVATE_KEY }} - cosign-public-key: ${{ secrets.COSIGN_PUBLIC_KEY }} - cosign-password: ${{ secrets.COSIGN_PASSWORD }} - dockerhub_username: ${{ secrets.DOCKER_READONLY_USERNAME }} - dockerhub_password: ${{ secrets.DOCKER_READONLY_PASSWORD }} verify-signature: true + + - name: Attest Docker image + uses: actions/attest-build-provenance@6149ea5740be74af77f260b9db67e633f6b0a9a1 # v1.4.2 + with: + subject-digest: ${{ steps.build-sign-publish.outputs.docker-image-digest }} + subject-name: ${{ env.ECR_HOSTNAME }}/${{ env.ECR_IMAGE_NAME }} + push-to-registry: true + - name: Collect Metrics if: always() id: collect-gha-metrics @@ -71,6 +76,139 @@ jobs: this-job-name: build-sign-publish-chainlink continue-on-error: true + goreleaser-build-sign-publish-chainlink: + needs: [checks] + if: ${{ ! startsWith(github.ref_name, 'release/') }} + runs-on: ubuntu-20.04 + environment: build-publish + permissions: + id-token: write + contents: write + attestations: write + steps: + - name: Checkout repository + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + + - name: Configure aws credentials + uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 + with: + role-to-assume: ${{ secrets.AWS_OIDC_IAM_ROLE_ARN }} + role-duration-seconds: ${{ secrets.AWS_ROLE_DURATION_SECONDS }} + aws-region: ${{ secrets.AWS_REGION }} + mask-aws-account-id: true + role-session-name: goreleaser-build-sign-publish-chainlink + + - name: Set build configs + shell: bash + id: set-build-configs + run: | + if [[ ${{ github.ref_name }} =~ "-ccip" ]]; then + echo "ECR_IMAGE_NAME=chainlink/ccip" | tee -a $GITHUB_OUTPUT + echo "GORELEASER_CONFIG=.goreleaser.ccip.production.yaml" | tee -a $GITHUB_OUTPUT + else + echo "ECR_IMAGE_NAME=chainlink/chainlink" | tee -a $GITHUB_OUTPUT + echo "GORELEASER_CONFIG=.goreleaser.production.yaml" | tee -a $GITHUB_OUTPUT + fi + + - name: Build, sign, and publish image + id: goreleaser-build-sign-publish + uses: ./.github/actions/goreleaser-build-sign-publish + with: + docker-registry: ${{ env.ECR_HOSTNAME}} + docker-image-name: ${{ steps.set-build-configs.outputs.ECR_IMAGE_NAME }} + docker-image-tag: ${{ github.ref_name }} + goreleaser-exec: ./tools/bin/goreleaser_wrapper + goreleaser-config: ${{ steps.set-build-configs.outputs.GORELEASER_CONFIG }} + goreleaser-key: ${{ secrets.GORELEASER_KEY }} + zig-version: 0.11.0 + enable-cosign: true + cosign-version: "v2.4.0" + + - name: Output image name and digest + id: get-image-name-digest + shell: bash + run: | + artifact_path="dist/artifacts.json" + jq -r '.[] | select(.type == "Docker Image") | "\(.name)"' ${artifact_path} >> output.txt + + echo "### Docker Images" | tee -a "$GITHUB_STEP_SUMMARY" + while read -r line; do + echo "$line" | tee -a "$GITHUB_STEP_SUMMARY" + done < output.txt + + core_amd64_name="${{ env.ECR_HOSTNAME }}/${{ steps.set-build-configs.outputs.ECR_IMAGE_NAME }}:${{ github.ref_name }}-amd64" + plugins_amd64_name="${{ env.ECR_HOSTNAME }}/${{ steps.set-build-configs.outputs.ECR_IMAGE_NAME }}:${{ github.ref_name }}-plugins-amd64" + core_arm64_name="${{ env.ECR_HOSTNAME }}/${{ steps.set-build-configs.outputs.ECR_IMAGE_NAME }}:${{ github.ref_name }}-arm64" + plugins_arm64_name="${{ env.ECR_HOSTNAME }}/${{ steps.set-build-configs.outputs.ECR_IMAGE_NAME }}:${{ github.ref_name }}-plugins-arm64" + + echo "core_amd64_digest=$(jq -r --arg name "$core_amd64_name" '.[]|select(.type=="Published Docker Image" and .name==$name)|.extra.Digest' ${artifact_path})" | tee -a "$GITHUB_OUTPUT" "$GITHUB_STEP_SUMMARY" + echo "plugins_amd64_digest=$(jq -r --arg name "$plugins_amd64_name" '.[]|select(.type=="Published Docker Image" and .name==$name)|.extra.Digest' ${artifact_path})" | tee -a "$GITHUB_OUTPUT" "$GITHUB_STEP_SUMMARY" + echo "core_arm64_digest=$(jq -r --arg name "$core_amd64_name" '.[]|select(.type=="Published Docker Image" and .name==$name)|.extra.Digest' ${artifact_path})" | tee -a "$GITHUB_OUTPUT" "$GITHUB_STEP_SUMMARY" + echo "plugins_arm64_digest=$(jq -r --arg name "$plugins_amd64_name" '.[]|select(.type=="Published Docker Image" and .name==$name)|.extra.Digest' ${artifact_path})" | tee -a "$GITHUB_OUTPUT" "$GITHUB_STEP_SUMMARY" + + - name: Attest tarballs + uses: actions/attest-build-provenance@6149ea5740be74af77f260b9db67e633f6b0a9a1 # v1.4.2 + with: + subject-path: "dist/*.tar.gz" + + - name: Attest Docker image (core-amd64) + uses: actions/attest-build-provenance@6149ea5740be74af77f260b9db67e633f6b0a9a1 # v1.4.2 + with: + subject-digest: ${{ steps.get-image-name-digest.outputs.core_amd64_digest }} + subject-name: ${{ env.ECR_HOSTNAME }}/${{ steps.set-build-configs.outputs.ECR_IMAGE_NAME }} + push-to-registry: true + + - name: Attest Docker image (plugins-amd64) + uses: actions/attest-build-provenance@6149ea5740be74af77f260b9db67e633f6b0a9a1 # v1.4.2 + with: + subject-digest: ${{ steps.get-image-name-digest.outputs.plugins_amd64_digest }} + subject-name: ${{ env.ECR_HOSTNAME }}/${{ steps.set-build-configs.outputs.ECR_IMAGE_NAME }} + push-to-registry: true + + - name: Attest Docker image (core-arm64) + uses: actions/attest-build-provenance@6149ea5740be74af77f260b9db67e633f6b0a9a1 # v1.4.2 + with: + subject-digest: ${{ steps.get-image-name-digest.outputs.core_arm64_digest }} + subject-name: ${{ env.ECR_HOSTNAME }}/${{ steps.set-build-configs.outputs.ECR_IMAGE_NAME }} + push-to-registry: true + + - name: Attest Docker image (plugins-arm64) + uses: actions/attest-build-provenance@6149ea5740be74af77f260b9db67e633f6b0a9a1 # v1.4.2 + with: + subject-digest: ${{ steps.get-image-name-digest.outputs.plugins_arm64_digest }} + subject-name: ${{ env.ECR_HOSTNAME }}/${{ steps.set-build-configs.outputs.ECR_IMAGE_NAME }} + push-to-registry: true + + - name: Upload SBOMs + uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 + with: + name: goreleaser-sboms + path: dist/*.sbom.json + + - name: Print SBOM artifact to job summary + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + shell: bash + run: | + ARTIFACTS=$(gh api -X GET repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts) + ARTIFACT_ID=$(echo "$ARTIFACTS" | jq '.artifacts[] | select(.name=="goreleaser-sboms") | .id') + echo "Artifact ID: $ARTIFACT_ID" + echo "### SBOM Artifact" | tee -a "$GITHUB_STEP_SUMMARY" + artifact_url="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/$ARTIFACT_ID" + echo "[Artifact URL]($artifact_url)" | tee -a $GITHUB_STEP_SUMMARY + + - name: Collect Metrics + if: always() + id: collect-gha-metrics + uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 + with: + id: goreleaser-build-chainlink-publish + org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} + basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} + hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} + this-job-name: goreleaser-build-sign-publish-chainlink + continue-on-error: true + # Notify Slack channel for new git tags. slack-notify: if: github.ref_type == 'tag' @@ -97,17 +235,17 @@ jobs: ) || '' }} docker-image-name: >- - ${{ - github.ref_type == 'tag' && + ${{ + github.ref_type == 'tag' && format( - '{0}/{1}:{2}', - env.ECR_HOSTNAME, - env.ECR_IMAGE_NAME, + '{0}/{1}:{2}', + env.ECR_HOSTNAME, + env.ECR_IMAGE_NAME, needs.build-sign-publish-chainlink.outputs.docker-image-tag ) || '' }} docker-image-digest: >- - ${{ - github.ref_type == 'tag' && + ${{ + github.ref_type == 'tag' && needs.build-sign-publish-chainlink.outputs.docker-image-digest || '' }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index b82b5a8203..0000000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: "Build Chainlink" - -on: - pull_request: - -jobs: - build-chainlink: - runs-on: ubuntu-20.04 - if: ${{ ! startsWith(github.head_ref, 'release/') && ! startsWith(github.ref_name, 'chore/') }} - steps: - - name: Checkout repository - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 - id: change - with: - predicate-quantifier: every - filters: | - changelog-only: - - 'CHANGELOG.md' - - '!common/**' - - '!contracts/**' - - '!core/**' - - '!crib/**' - - '!dashboard-lib/**' - - '!fuzz/**' - - '!integration-tests/**' - - '!internal/**' - - '!operator_ui/**' - - '!plugins/**' - - '!tools/**' - - - name: Build chainlink image - if: ${{ steps.change.outputs.changelog-only == 'false' }} - uses: ./.github/actions/build-sign-publish-chainlink - with: - dockerhub_username: ${{ secrets.DOCKER_READONLY_USERNAME }} - dockerhub_password: ${{ secrets.DOCKER_READONLY_PASSWORD }} - publish: false - sign-images: false - - - name: Collect Metrics - if: always() - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: build-chainlink - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: build-chainlink - continue-on-error: true diff --git a/.github/workflows/ccip-chaos-tests.yml b/.github/workflows/ccip-chaos-tests.yml index da868886f1..6e36e14ef0 100644 --- a/.github/workflows/ccip-chaos-tests.yml +++ b/.github/workflows/ccip-chaos-tests.yml @@ -6,254 +6,40 @@ on: branches: [ ccip-develop ] workflow_dispatch: - - # Only run 1 of this workflow at a time per PR concurrency: group: chaos-ccip-tests-chainlink-${{ github.ref }} cancel-in-progress: true -env: - CL_ECR: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink - ENV_JOB_IMAGE: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink-ccip-tests:${{ github.sha }} - MOD_CACHE_VERSION: 1 - jobs: - build-chainlink: - environment: integration - permissions: - id-token: write - contents: read - name: Build Chainlink Image - runs-on: ubuntu20.04-16cores-64GB - steps: - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - name: Check if image exists - id: check-image - uses: smartcontractkit/chainlink-github-actions/docker/image-exists@b49a9d04744b0237908831730f8553f26d73a94b # v2.3.17 - with: - repository: chainlink - tag: ${{ github.sha }} - AWS_REGION: ${{ secrets.QA_AWS_REGION }} - AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - - name: Build Image - if: steps.check-image.outputs.exists == 'false' - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/build-image@b49a9d04744b0237908831730f8553f26d73a94b # v2.3.17 - env: - GH_TOKEN: ${{ github.token }} - with: - cl_repo: smartcontractkit/chainlink-ccip - cl_ref: ${{ github.sha }} - push_tag: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink:${{ github.sha }} - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - - name: Collect Metrics - if: always() - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@dea9b546553cb4ca936607c2267a09c004e4ab3f # v3.0.0 - with: - id: ccip-chaos-tests-build-chainlink-image - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: Build Chainlink Image - continue-on-error: true - - build-test-image: - environment: integration - permissions: - id-token: write - contents: read - name: Build Test Image - runs-on: ubuntu20.04-16cores-64GB - steps: - - name: Collect Metrics - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@dea9b546553cb4ca936607c2267a09c004e4ab3f # v3.0.0 - with: - id: ccip-chaos-tests-build-test-image - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: Build Test Image - continue-on-error: true - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - name: Build Test Image - uses: ./.github/actions/build-test-image - with: - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - - ccip-chaos-tests: - environment: integration - permissions: - issues: read - checks: write - pull-requests: write - id-token: write - contents: read - name: CCIP Chaos Tests - runs-on: ubuntu-latest - needs: [ build-chainlink, build-test-image ] - env: - TEST_SUITE: chaos - TEST_ARGS: -test.timeout 30m - CHAINLINK_COMMIT_SHA: ${{ github.sha }} - CHAINLINK_ENV_USER: ${{ github.actor }} - TEST_TRIGGERED_BY: ccip-cron-chaos-eth - TEST_LOG_LEVEL: debug - DATABASE_URL: postgresql://postgres:node@localhost:5432/chainlink_test?sslmode=disable - GH_TOKEN: ${{ github.token }} - steps: - - name: Collect Metrics - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@dea9b546553cb4ca936607c2267a09c004e4ab3f # v3.0.0 - with: - id: ccip-chaos-tests - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: CCIP Chaos Tests - test-results-file: '{"testType":"go","filePath":"/tmp/gotest.log"}' - continue-on-error: true - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - name: Prepare Base64 TOML override for CCIP secrets - uses: ./.github/actions/setup-create-base64-config-ccip - id: setup_create_base64_config_ccip - with: - runId: ${{ github.run_id }} - testLogCollect: ${{ vars.TEST_LOG_COLLECT }} - chainlinkVersion: ${{ github.sha }} - logstreamLogTargets: ${{ vars.LOGSTREAM_LOG_TARGETS }} - - name: Run Chaos Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@d38226be720c5ccc1ff4d3cee40608ebf264cd59 # v2.3.26 - env: - BASE64_CCIP_CONFIG_OVERRIDE: ${{ steps.setup_create_base64_config_ccip.outputs.base64_config }} - TEST_BASE64_CCIP_CONFIG_OVERRIDE: ${{ steps.setup_create_base64_config_ccip.outputs.base64_config }} - with: - test_command_to_run: cd ./integration-tests && go test -timeout 1h -count=1 -json -test.parallel 11 -run 'TestChaosCCIP' ./chaos 2>&1 | tee /tmp/gotest.log | gotestloghelper -ci - test_download_vendor_packages_command: make gomod - artifacts_location: ./integration-tests/chaos/logs - publish_check_name: CCIP Chaos Test Results - publish_report_paths: ./tests-chaos-report.xml - triggered_by: ${{ env.TEST_TRIGGERED_BY }} - token: ${{ secrets.GITHUB_TOKEN }} - go_mod_path: ./integration-tests/go.mod - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} - CGO_ENABLED: "1" - aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - cache_key_id: ccip-load-${{ env.MOD_CACHE_VERSION }} - cache_restore_only: "true" - DEFAULT_LOKI_TENANT_ID: ${{ vars.LOKI_TENANT_ID }} - DEFAULT_LOKI_ENDPOINT: ${{ secrets.LOKI_URL }} - DEFAULT_LOKI_BASIC_AUTH: ${{ secrets.LOKI_BASIC_AUTH }} - DEFAULT_CHAINLINK_IMAGE: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink - DEFAULT_GRAFANA_BASE_URL: ${{ vars.GRAFANA_URL }} - DEFAULT_GRAFANA_DASHBOARD_URL: "/d/ddf75041-1e39-42af-aa46-361fe4c36e9e/ci-e2e-tests-logs" - - ## Notify in slack if the job fails - - name: Notify Slack - if: failure() && github.event_name != 'workflow_dispatch' - uses: slackapi/slack-github-action@6c661ce58804a1a20f6dc5fbee7f0381b469e001 # v1.25.0 - env: - SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} - with: - channel-id: "#ccip-testing" - slack-message: ":x: :mild-panic-intensifies: CCIP chaos tests failed: \n${{ format('https://github.com/{0}/actions/runs/{1}', github.repository, github.run_id) }}" - ## Run Cleanup if the job succeeds - - name: cleanup - if: always() - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/cleanup@b49a9d04744b0237908831730f8553f26d73a94b # v2.3.17 - with: - triggered_by: ${{ env.TEST_TRIGGERED_BY }} - - ccip-chaos-with-load-tests: - environment: integration - permissions: - issues: read - checks: write - pull-requests: write - id-token: write - contents: read - name: CCIP Load With Chaos Tests - if: false # Disabled until CCIP-2555 is resolved - runs-on: ubuntu-latest - needs: [ build-chainlink, build-test-image ] - env: - TEST_SUITE: load - TEST_ARGS: -test.timeout 1h - CHAINLINK_COMMIT_SHA: ${{ github.sha }} - CHAINLINK_ENV_USER: ${{ github.actor }} - TEST_TRIGGERED_BY: ccip-cron-chaos-and-load-eth - TEST_LOG_LEVEL: debug - DATABASE_URL: postgresql://postgres:node@localhost:5432/chainlink_test?sslmode=disable - GH_TOKEN: ${{ github.token }} - steps: - - name: Collect Metrics - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@dea9b546553cb4ca936607c2267a09c004e4ab3f # v3.0.0 - with: - id: ccip-chaos-tests-with-load-test - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: CCIP load with chaos test - continue-on-error: true - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - name: Prepare Base64 TOML override for CCIP secrests - uses: ./.github/actions/setup-create-base64-config-ccip - id: setup_create_base64_config_ccip - with: - runId: ${{ github.run_id }} - testLogCollect: ${{ vars.TEST_LOG_COLLECT }} - chainlinkVersion: ${{ github.sha }} - logstreamLogTargets: ${{ vars.LOGSTREAM_LOG_TARGETS }} - - name: Run Load With Chaos Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@d38226be720c5ccc1ff4d3cee40608ebf264cd59 # v2.3.26 - env: - BASE64_CCIP_CONFIG_OVERRIDE: ${{ steps.setup_create_base64_config_ccip.outputs.base64_config }} - TEST_BASE64_CCIP_CONFIG_OVERRIDE: ${{ steps.setup_create_base64_config_ccip.outputs.base64_config }} - with: - test_command_to_run: cd ./integration-tests/ccip-tests && go test -timeout 2h -count=1 -json -test.parallel 4 -run '^TestLoadCCIPStableWithPodChaosDiffCommitAndExec' ./load 2>&1 | tee /tmp/gotest.log | gotestfmt - test_download_vendor_packages_command: make gomod - artifacts_location: ./integration-tests/load/logs - publish_check_name: CCIP Chaos With Load Test Results - publish_report_paths: ./tests-chaos-with-load-report.xml - triggered_by: ${{ env.TEST_TRIGGERED_BY }} - token: ${{ secrets.GITHUB_TOKEN }} - go_mod_path: ./integration-tests/go.mod - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} - CGO_ENABLED: "1" - aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - cache_key_id: ccip-load-${{ env.MOD_CACHE_VERSION }} - cache_restore_only: "true" - DEFAULT_LOKI_TENANT_ID: ${{ vars.LOKI_TENANT_ID }} - DEFAULT_LOKI_ENDPOINT: ${{ secrets.LOKI_URL }} - DEFAULT_LOKI_BASIC_AUTH: ${{ secrets.LOKI_BASIC_AUTH }} - DEFAULT_CHAINLINK_IMAGE: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink - DEFAULT_GRAFANA_BASE_URL: ${{ vars.GRAFANA_URL }} - DEFAULT_GRAFANA_DASHBOARD_URL: "/d/6vjVx-1V8/ccip-long-running-tests" - ## Notify in slack if the job fails - - name: Notify Slack - if: failure() && github.event_name != 'workflow_dispatch' - uses: slackapi/slack-github-action@6c661ce58804a1a20f6dc5fbee7f0381b469e001 # v1.25.0 - env: - SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} - with: - channel-id: "#ccip-testing" - slack-message: ":x: :mild-panic-intensifies: CCIP chaos with load tests failed: \n${{ format('https://github.com/{0}/actions/runs/{1}', github.repository, github.run_id) }}" - ## Run Cleanup if the job succeeds - - name: cleanup - if: always() - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/cleanup@b49a9d04744b0237908831730f8553f26d73a94b # v2.3.17 - with: - triggered_by: ${{ env.TEST_TRIGGERED_BY }} + run-e2e-tests-workflow: + name: Run E2E Tests + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@aad83f232743646faa35f5ac03ee3829148d37ce + with: + test_path: .github/e2e-tests.yml + chainlink_version: ${{ github.sha }} + require_chainlink_image_versions_in_qa_ecr: ${{ github.sha }} + test_trigger: E2E CCIP Chaos Tests + test_log_level: debug + slack_notification_after_tests: on_failure + slack_notification_after_tests_channel_id: '#ccip-testing' + slack_notification_after_tests_name: CCIP Chaos E2E Tests + secrets: + QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} + QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} + QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} + QA_PYROSCOPE_INSTANCE: ${{ secrets.QA_PYROSCOPE_INSTANCE }} + QA_PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} + QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} + GRAFANA_INTERNAL_TENANT_ID: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} + GRAFANA_INTERNAL_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} + GRAFANA_INTERNAL_HOST: ${{ secrets.GRAFANA_INTERNAL_HOST }} + GRAFANA_INTERNAL_URL_SHORTENER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} + LOKI_TENANT_ID: ${{ secrets.LOKI_TENANT_ID }} + LOKI_URL: ${{ secrets.LOKI_URL }} + LOKI_BASIC_AUTH: ${{ secrets.LOKI_BASIC_AUTH }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + AWS_REGION: ${{ secrets.QA_AWS_REGION }} + AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} + AWS_API_GW_HOST_GRAFANA: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} + SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} diff --git a/.github/workflows/ccip-client-compatibility-tests.yml b/.github/workflows/ccip-client-compatibility-tests.yml index 58c05f2f72..b21ef82bab 100644 --- a/.github/workflows/ccip-client-compatibility-tests.yml +++ b/.github/workflows/ccip-client-compatibility-tests.yml @@ -534,8 +534,8 @@ jobs: - name: Run Tests uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@d38226be720c5ccc1ff4d3cee40608ebf264cd59 # v2.3.26 env: - BASE64_CCIP_CONFIG_OVERRIDE: ${{ steps.setup_create_base64_config_ccip.outputs.base64_config }} - TEST_BASE64_CCIP_CONFIG_OVERRIDE: ${{ steps.setup_create_base64_config_ccip.outputs.base64_config }} + BASE64_CONFIG_OVERRIDE: ${{ steps.setup_create_base64_config_ccip.outputs.base64_config }} + TEST_BASE64_CONFIG_OVERRIDE: ${{ steps.setup_create_base64_config_ccip.outputs.base64_config }} with: test_command_to_run: cd ./integration-tests && go test -timeout 30m -count=1 -json -test.parallel=2 ${{ matrix.evm_node.run }} 2>&1 | tee /tmp/gotest.log | gotestloghelper -ci test_download_vendor_packages_command: cd ./integration-tests && go mod download diff --git a/.github/workflows/ccip-live-network-tests.yml b/.github/workflows/ccip-live-network-tests.yml index d1789fca74..68f012421f 100644 --- a/.github/workflows/ccip-live-network-tests.yml +++ b/.github/workflows/ccip-live-network-tests.yml @@ -177,14 +177,14 @@ jobs: echo ::add-mask::$SLACK_USER echo "SLACK_USER=$SLACK_USER" >> "$GITHUB_ENV" if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then - BASE64_CCIP_CONFIG_OVERRIDE=$(jq -r '.inputs.base64_test_input' $GITHUB_EVENT_PATH) - echo ::add-mask::$BASE64_CCIP_CONFIG_OVERRIDE - echo "base_64_override=$BASE64_CCIP_CONFIG_OVERRIDE" >> $GITHUB_OUTPUT + BASE64_CONFIG_OVERRIDE=$(jq -r '.inputs.base64_test_input' $GITHUB_EVENT_PATH) + echo ::add-mask::$BASE64_CONFIG_OVERRIDE + echo "base_64_override=$BASE64_CONFIG_OVERRIDE" >> $GITHUB_OUTPUT fi if [[ "${{ github.event_name }}" == "schedule" ]]; then - BASE64_CCIP_CONFIG_OVERRIDE=$(base64 -w 0 -i ./integration-tests/ccip-tests/testconfig/override/${{ matrix.config }}) - echo ::add-mask::$BASE64_CCIP_CONFIG_OVERRIDE - echo "base_64_override=$BASE64_CCIP_CONFIG_OVERRIDE" >> $GITHUB_OUTPUT + BASE64_CONFIG_OVERRIDE=$(base64 -w 0 -i ./integration-tests/ccip-tests/testconfig/override/${{ matrix.config }}) + echo ::add-mask::$BASE64_CONFIG_OVERRIDE + echo "base_64_override=$BASE64_CONFIG_OVERRIDE" >> $GITHUB_OUTPUT echo "SLACK_USER=${{ secrets.QA_SLACK_USER }}" >> $GITHUB_ENV fi - name: step summary @@ -212,8 +212,8 @@ jobs: RR_CPU: 4 DETACH_RUNNER: true TEST_TRIGGERED_BY: ccip-load-test-ci - BASE64_CCIP_CONFIG_OVERRIDE: ${{ steps.setup_create_base64_config_ccip.outputs.base64_config }},${{ steps.set_override_config.outputs.base_64_override }} - TEST_BASE64_CCIP_CONFIG_OVERRIDE: ${{ steps.setup_create_base64_config_ccip.outputs.base64_config }},${{ steps.set_override_config.outputs.base_64_override }} + BASE64_CONFIG_OVERRIDE: ${{ steps.setup_create_base64_config_ccip.outputs.base64_config }},${{ steps.set_override_config.outputs.base_64_override }} + TEST_BASE64_CONFIG_OVERRIDE: ${{ steps.setup_create_base64_config_ccip.outputs.base64_config }},${{ steps.set_override_config.outputs.base_64_override }} E2E_TEST_GRAFANA_DASHBOARD_URL: "/d/6vjVx-1V8/ccip-long-running-tests" with: test_command_to_run: cd ./integration-tests/ccip-tests && go test -v -timeout 70m -count=1 -json -run ^TestLoadCCIPStableRPS$ ./load 2>&1 | tee /tmp/gotest.log | gotestloghelper -ci -singlepackage -hidepassingtests=false @@ -338,14 +338,14 @@ jobs: echo ::add-mask::$SLACK_USER echo "SLACK_USER=$SLACK_USER" >> "$GITHUB_ENV" if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then - BASE64_CCIP_CONFIG_OVERRIDE=$(jq -r '.inputs.base64_test_input' $GITHUB_EVENT_PATH) - echo ::add-mask::$BASE64_CCIP_CONFIG_OVERRIDE - echo "base_64_override=$BASE64_CCIP_CONFIG_OVERRIDE" >> $GITHUB_OUTPUT + BASE64_CONFIG_OVERRIDE=$(jq -r '.inputs.base64_test_input' $GITHUB_EVENT_PATH) + echo ::add-mask::$BASE64_CONFIG_OVERRIDE + echo "base_64_override=$BASE64_CONFIG_OVERRIDE" >> $GITHUB_OUTPUT fi if [[ "${{ github.event_name }}" == "schedule" ]]; then - BASE64_CCIP_CONFIG_OVERRIDE=$(base64 -w 0 -i ./integration-tests/ccip-tests/testconfig/override/mainnet.toml) - echo ::add-mask::$BASE64_CCIP_CONFIG_OVERRIDE - echo "base_64_override=$BASE64_CCIP_CONFIG_OVERRIDE" >> $GITHUB_OUTPUT + BASE64_CONFIG_OVERRIDE=$(base64 -w 0 -i ./integration-tests/ccip-tests/testconfig/override/mainnet.toml) + echo ::add-mask::$BASE64_CONFIG_OVERRIDE + echo "base_64_override=$BASE64_CONFIG_OVERRIDE" >> $GITHUB_OUTPUT echo "SLACK_USER=${{ secrets.QA_SLACK_USER }}" >> $GITHUB_ENV fi - name: step summary @@ -374,8 +374,8 @@ jobs: RR_MEM: 8Gi RR_CPU: 4 TEST_TRIGGERED_BY: ccip-smoke-test-ci - BASE64_CCIP_CONFIG_OVERRIDE: ${{ steps.setup_create_base64_config_ccip.outputs.base64_config }},${{ steps.set_override_config.outputs.base_64_override }} - TEST_BASE64_CCIP_CONFIG_OVERRIDE: ${{ steps.setup_create_base64_config_ccip.outputs.base64_config }},${{ steps.set_override_config.outputs.base_64_override }} + BASE64_CONFIG_OVERRIDE: ${{ steps.setup_create_base64_config_ccip.outputs.base64_config }},${{ steps.set_override_config.outputs.base_64_override }} + TEST_BASE64_CONFIG_OVERRIDE: ${{ steps.setup_create_base64_config_ccip.outputs.base64_config }},${{ steps.set_override_config.outputs.base_64_override }} E2E_TEST_GRAFANA_DASHBOARD_URL: "/d/ddf75041-1e39-42af-aa46-361fe4c36e9e/ci-e2e-tests-logs" OVERRIDE_NETWORK_PAIRS: ${{ matrix.lanes.pairs }} OVERRIDE_PHASE_TIMEOUT: ${{ matrix.lanes.phaseTimeout }} diff --git a/.github/workflows/ccip-load-tests.yml b/.github/workflows/ccip-load-tests.yml index a9b1af2f30..1b71c044cd 100644 --- a/.github/workflows/ccip-load-tests.yml +++ b/.github/workflows/ccip-load-tests.yml @@ -7,12 +7,17 @@ on: - '*' workflow_dispatch: inputs: - base64_test_input: # base64 encoded toml for test input - description: 'Base64 encoded toml test input' + test_config_override_path: + description: Path to a test config file used to override the default test config required: false + type: string test_secrets_override_key: description: 'Key to run tests with custom test secrets' required: false + type: string + chainlink_version: + description: Chainlink image version to use. Commit sha if not provided + required: false type: string # Only run 1 of this workflow at a time per PR @@ -20,291 +25,38 @@ concurrency: group: load-ccip-tests-chainlink-${{ github.ref }} cancel-in-progress: true -env: - CHAINLINK_IMAGE: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink - CHAINLINK_VERSION: ${{ github.sha}} - INPUT_CHAINLINK_TEST_VERSION: ${{ github.sha}} - ENV_JOB_IMAGE: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink-ccip-tests:${{ github.sha }} - INTERNAL_DOCKER_REPO: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com - AWS_ECR_REPO_PUBLIC_REGISTRY: public.ecr.aws - MOD_CACHE_VERSION: 1 - E2E_TEST_CHAINLINK_IMAGE: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink - E2E_TEST_LOKI_TENANT_ID: ${{ vars.LOKI_TENANT_ID }} - E2E_TEST_LOKI_ENDPOINT: ${{ secrets.LOKI_URL }} - E2E_TEST_LOKI_BASIC_AUTH: ${{ secrets.LOKI_BASIC_AUTH }} - E2E_TEST_GRAFANA_BASE_URL: ${{ vars.GRAFANA_URL }} - # Default private key test secret loaded from Github Secret as only security team has access to it. - # this key secrets.QA_SHARED_803C_KEY has a story behind it. To know more, see CCIP-2875 and SECHD-16575 tickets. - E2E_TEST_ETHEREUM_MAINNET_WALLET_KEY: ${{ secrets.QA_SHARED_803C_KEY }} - E2E_TEST_ARBITRUM_MAINNET_WALLET_KEY: ${{ secrets.QA_SHARED_803C_KEY }} - E2E_TEST_BASE_MAINNET_WALLET_KEY: ${{ secrets.QA_SHARED_803C_KEY }} - E2E_TEST_WEMIX_MAINNET_WALLET_KEY: ${{ secrets.QA_SHARED_803C_KEY }} - E2E_TEST_AVALANCHE_MAINNET_WALLET_KEY: ${{ secrets.QA_SHARED_803C_KEY }} - E2E_TEST_ZKSYNC_MAINNET_WALLET_KEY: ${{ secrets.QA_SHARED_803C_KEY }} - E2E_TEST_MODE_MAINNET_WALLET_KEY: ${{ secrets.QA_SHARED_803C_KEY }} - E2E_TEST_METIS_ANDROMEDA_WALLET_KEY: ${{ secrets.QA_SHARED_803C_KEY }} - E2E_TEST_OPTIMISM_MAINNET_WALLET_KEY: ${{ secrets.QA_SHARED_803C_KEY }} - E2E_TEST_KROMA_MAINNET_WALLET_KEY: ${{ secrets.QA_SHARED_803C_KEY }} - E2E_TEST_GNOSIS_MAINNET_WALLET_KEY: ${{ secrets.QA_SHARED_803C_KEY }} - E2E_TEST_POLYGON_MAINNET_WALLET_KEY: ${{ secrets.QA_SHARED_803C_KEY }} - E2E_TEST_BSC_MAINNET_WALLET_KEY: ${{ secrets.QA_SHARED_803C_KEY }} - jobs: - build-chainlink: - environment: integration - permissions: - id-token: write - contents: read - name: Build Chainlink Image - runs-on: ubuntu20.04-16cores-64GB - steps: - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - name: Check if image exists - id: check-image - uses: smartcontractkit/chainlink-github-actions/docker/image-exists@b49a9d04744b0237908831730f8553f26d73a94b # v2.3.17 - with: - repository: chainlink - tag: ${{ env.CHAINLINK_VERSION }} - AWS_REGION: ${{ secrets.QA_AWS_REGION }} - AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - - name: Build Image - if: steps.check-image.outputs.exists == 'false' - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/build-image@b49a9d04744b0237908831730f8553f26d73a94b # v2.3.17 - env: - GH_TOKEN: ${{ github.token }} - with: - cl_repo: smartcontractkit/chainlink-ccip - cl_ref: ${{ env.CHAINLINK_VERSION }} - push_tag: ${{ env.CHAINLINK_IMAGE }}:${{ env.CHAINLINK_VERSION }} - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - - name: Collect Metrics - if: always() - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@dea9b546553cb4ca936607c2267a09c004e4ab3f # v3.0.0 - with: - id: ccip-load-test-build-chainlink-image - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: Build Chainlink Image - continue-on-error: true - - build-test-image: - environment: integration - permissions: - id-token: write - contents: read - name: Build Test Image - runs-on: ubuntu20.04-16cores-64GB - steps: - - name: Collect Metrics - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@dea9b546553cb4ca936607c2267a09c004e4ab3f # v3.0.0 - with: - id: ccip-load-test-build-test-image - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: Build Test Image - continue-on-error: true - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - name: Build Test Image - uses: ./.github/actions/build-test-image - with: - tag: ${{ env.INPUT_CHAINLINK_TEST_VERSION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - - ccip-load-test: - environment: integration - needs: [ build-chainlink, build-test-image ] - if: ${{ always() && !contains(needs.*.result, 'failure') }} - permissions: - issues: read - checks: write - pull-requests: write - id-token: write - contents: read - env: - CHAINLINK_ENV_USER: ${{ github.actor }} + run-e2e-tests-workflow: + name: Run E2E Tests + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@aad83f232743646faa35f5ac03ee3829148d37ce + with: + test_path: .github/e2e-tests.yml + test_trigger: E2E CCIP Load Tests + test_config_override_path: ${{ inputs.test_config_override_path }} + chainlink_version: ${{ inputs.chainlink_version || github.sha }} + slack_notification_after_tests: always + slack_notification_after_tests_channel_id: '#ccip-testing' + slack_notification_after_tests_name: CCIP E2E Load Tests + test_image_suites: ccip-load + secrets: + QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} + QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} + QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} + QA_PYROSCOPE_INSTANCE: ${{ secrets.QA_PYROSCOPE_INSTANCE }} + QA_PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} + QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} + GRAFANA_INTERNAL_TENANT_ID: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} + GRAFANA_INTERNAL_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} + GRAFANA_INTERNAL_HOST: ${{ secrets.GRAFANA_INTERNAL_HOST }} + GRAFANA_INTERNAL_URL_SHORTENER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} + LOKI_TENANT_ID: ${{ secrets.LOKI_TENANT_ID }} + LOKI_URL: ${{ secrets.LOKI_URL }} + LOKI_BASIC_AUTH: ${{ secrets.LOKI_BASIC_AUTH }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + AWS_REGION: ${{ secrets.QA_AWS_REGION }} + AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} + AWS_API_GW_HOST_GRAFANA: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} + TEST_SECRETS_OVERRIDE_BASE64: ${{ secrets[inputs.test_secrets_override_key] }} + SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} SLACK_API_KEY: ${{ secrets.QA_SLACK_API_KEY }} - SLACK_CHANNEL: ${{ secrets.QA_SLACK_CHANNEL }} - TEST_LOG_LEVEL: info - REF_NAME: ${{ github.head_ref || github.ref_name }} - strategy: - fail-fast: false - matrix: - type: - - name: stable-load - run: ^TestLoadCCIPStableRPS$ - os: ubuntu-latest - - name: load-with-arm-curse-uncurse - run: ^TestLoadCCIPStableRPSAfterARMCurseAndUncurse$ - config_path: ./integration-tests/ccip-tests/testconfig/tomls/load-with-arm-curse-uncurse.toml - os: ubuntu-latest - runs-on: ${{ matrix.type.os }} - name: CCIP ${{ matrix.type.name }} - steps: - - name: Collect Metrics - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@dea9b546553cb4ca936607c2267a09c004e4ab3f # v3.0.0 - with: - id: ccip-load-test-${{ matrix.type.name }} - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: CCIP ${{ matrix.type.name }} - test-results-file: '{"testType":"go","filePath":"/tmp/gotest.log"}' - continue-on-error: true - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - ref: ${{ env.REF_NAME }} - - name: Sets env vars - id: set_override_config - shell: bash - run: | - # if the matrix.type.config_path is set, use it as the override config - if [ -n "${{ matrix.type.config_path }}" ]; then - BASE64_CCIP_CONFIG_OVERRIDE=$(base64 -w 0 -i ${{ matrix.type.config_path }}) - echo ::add-mask::$BASE64_CCIP_CONFIG_OVERRIDE - echo "base_64_override=$BASE64_CCIP_CONFIG_OVERRIDE" >> $GITHUB_OUTPUT - fi - if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then - BASE64_CCIP_CONFIG_OVERRIDE=$(jq -r '.inputs.base64_test_input' $GITHUB_EVENT_PATH) - echo ::add-mask::$BASE64_CCIP_CONFIG_OVERRIDE - echo "base_64_override=$BASE64_CCIP_CONFIG_OVERRIDE" >> $GITHUB_OUTPUT - fi - - name: step summary - shell: bash - run: | - echo "### chainlink image used for this test run :link:" >>$GITHUB_STEP_SUMMARY - echo "\`${{ env.CHAINLINK_VERSION }}\`" >> $GITHUB_STEP_SUMMARY - echo "### chainlink-tests image tag for this test run :ship:" >>$GITHUB_STEP_SUMMARY - echo "\`${{ env.INPUT_CHAINLINK_TEST_VERSION }}\`" >> $GITHUB_STEP_SUMMARY - - name: Prepare Base64 TOML override for CCIP secrets - uses: ./.github/actions/setup-create-base64-config-ccip - id: setup_create_base64_config_ccip - with: - runId: ${{ github.run_id }} - testLogCollect: ${{ vars.TEST_LOG_COLLECT }} - chainlinkVersion: ${{ github.sha }} - logstreamLogTargets: ${{ vars.LOGSTREAM_LOG_TARGETS }} - - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@94cb11f4bd545607a2f221c6685052b3abee723d # v2.3.32 - env: - TEST_SUITE: load - TEST_ARGS: -test.timeout 900h - DATABASE_URL: postgresql://postgres:node@localhost:5432/chainlink_test?sslmode=disable - RR_MEM: 8Gi - RR_CPU: 4 - TEST_TRIGGERED_BY: ccip-load-test-ci-${{ matrix.type.name }} - BASE64_CCIP_CONFIG_OVERRIDE: ${{ steps.set_override_config.outputs.base_64_override }},${{ steps.setup_create_base64_config_ccip.outputs.base64_config }} - TEST_BASE64_CCIP_CONFIG_OVERRIDE: ${{ steps.set_override_config.outputs.base_64_override }},${{ steps.setup_create_base64_config_ccip.outputs.base64_config }} - E2E_TEST_GRAFANA_DASHBOARD_URL: "/d/6vjVx-1V8/ccip-long-running-tests" - with: - test_command_to_run: cd ./integration-tests/ccip-tests && go test -v -timeout 70m -count=1 -json -run ${{ matrix.type.run }} ./load 2>&1 | tee /tmp/gotest.log | gotestloghelper -ci - test_download_vendor_packages_command: cd ./integration-tests && go mod download - # Load default test secrets - test_secrets_defaults_base64: ${{ secrets.CCIP_DEFAULT_TEST_SECRETS }} - # Override default test secrets with custom test secrets if provided - test_secrets_override_base64: ${{ secrets[inputs.test_secrets_override_key] }} - token: ${{ secrets.GITHUB_TOKEN }} - go_mod_path: ./integration-tests/go.mod - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} - triggered_by: ${{ env.TEST_TRIGGERED_BY }} - publish_check_name: ${{ matrix.type.name }} - artifacts_location: ./integration-tests/load/logs/payload_ccip.json - aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - cache_key_id: ccip-load-${{ env.MOD_CACHE_VERSION }} - cache_restore_only: "true" - should_cleanup: "true" - - # Reporting Jobs - start-slack-thread: - name: Start Slack Thread - if: ${{ failure() && needs.ccip-load-test.result != 'skipped' && needs.ccip-load-test.result != 'cancelled' }} - environment: integration - outputs: - thread_ts: ${{ steps.slack.outputs.thread_ts }} - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - runs-on: ubuntu-latest - needs: [ccip-load-test] - steps: - - name: Debug Result - run: echo ${{ join(needs.*.result, ',') }} - - name: Main Slack Notification - uses: slackapi/slack-github-action@6c661ce58804a1a20f6dc5fbee7f0381b469e001 # v1.25.0 - id: slack - with: - channel-id: "#ccip-testing" - payload: | - { - "attachments": [ - { - "color": "${{ contains(join(needs.*.result, ','), 'failure') && '#C62828' || '#2E7D32' }}", - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "CCIP load tests results ${{ contains(join(needs.*.result, ','), 'failure') && ':x:' || ':white_check_mark:'}}", - "emoji": true - } - }, - { - "type": "divider" - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "<${{ github.server_url }}/${{ github.repository }}/${{contains(github.ref_name, 'release') && 'releases/tag' || 'tree'}}/${{ github.ref_name }}|${{ github.ref_name }}> | <${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}|${{ github.sha }}> | <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run>" - } - } - ] - } - ] - } - env: - SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} - - post-test-results-to-slack: - name: Post Test Results - if: ${{ failure() && needs.start-slack-thread.result != 'skipped' && needs.start-slack-thread.result != 'cancelled' }} - environment: integration - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - runs-on: ubuntu-latest - needs: start-slack-thread - steps: - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - ref: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} - - name: Post Test Results - uses: ./.github/actions/notify-slack-jobs-result - with: - github_token: ${{ github.token }} - github_repository: ${{ github.repository }} - workflow_run_id: ${{ github.run_id }} - github_job_name_regex: ^CCIP (.*)$ - message_title: CCIP Jobs - slack_channel_id: "#ccip-testing" - slack_bot_token: ${{ secrets.QA_SLACK_API_KEY }} - slack_thread_ts: ${{ needs.start-slack-thread.outputs.thread_ts }} - # End Reporting Jobs diff --git a/.github/workflows/ccip-offchain-upgrade-tests.yml b/.github/workflows/ccip-offchain-upgrade-tests.yml index 3fe3c79cdd..7536488eb9 100644 --- a/.github/workflows/ccip-offchain-upgrade-tests.yml +++ b/.github/workflows/ccip-offchain-upgrade-tests.yml @@ -137,7 +137,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha || github.sha }} - name: Build Test Image if: needs.changes.outputs.src == 'true' || github.event_name == 'workflow_dispatch' - uses: ./.github/actions/build-test-image + uses: smartcontractkit/.github/actions/ctf-build-test-image@a5e4f4c8fbb8e15ab2ad131552eca6ac83c4f4b3 # ctf-build-test-image@0.1.0 with: # we just want to build the load tests suites: ccip-tests/load ccip-tests/smoke @@ -247,8 +247,8 @@ jobs: if: needs.changes.outputs.src == 'true' || github.event_name == 'workflow_dispatch' uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@94cb11f4bd545607a2f221c6685052b3abee723d # v2.3.32 env: - BASE64_CCIP_CONFIG_OVERRIDE: ${{ steps.set_override_config.outputs.base_64_override }},${{ steps.setup_create_base64_config_ccip.outputs.base64_config }} - TEST_BASE64_CCIP_CONFIG_OVERRIDE: ${{ steps.set_override_config.outputs.base_64_override }},${{ steps.setup_create_base64_config_ccip.outputs.base64_config }} + BASE64_CONFIG_OVERRIDE: ${{ steps.set_override_config.outputs.base_64_override }},${{ steps.setup_create_base64_config_ccip.outputs.base64_config }} + TEST_BASE64_CONFIG_OVERRIDE: ${{ steps.set_override_config.outputs.base_64_override }},${{ steps.setup_create_base64_config_ccip.outputs.base64_config }} ENV_JOB_IMAGE: ${{ env.ENV_JOB_IMAGE_BASE }}:${{ github.sha }} TEST_SUITE: smoke TEST_ARGS: -test.timeout 30m @@ -371,8 +371,8 @@ jobs: if: needs.changes.outputs.src == 'true' || github.event_name == 'workflow_dispatch' uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@94cb11f4bd545607a2f221c6685052b3abee723d # v2.3.32 env: - BASE64_CCIP_CONFIG_OVERRIDE: ${{ steps.set_override_config.outputs.base_64_override }},${{ steps.setup_create_base64_config_ccip.outputs.base64_config }} - TEST_BASE64_CCIP_CONFIG_OVERRIDE: ${{ steps.set_override_config.outputs.base_64_override }},${{ steps.setup_create_base64_config_ccip.outputs.base64_config }} + BASE64_CONFIG_OVERRIDE: ${{ steps.set_override_config.outputs.base_64_override }},${{ steps.setup_create_base64_config_ccip.outputs.base64_config }} + TEST_BASE64_CONFIG_OVERRIDE: ${{ steps.set_override_config.outputs.base_64_override }},${{ steps.setup_create_base64_config_ccip.outputs.base64_config }} ENV_JOB_IMAGE: ${{ env.ENV_JOB_IMAGE_BASE }}:${{ github.sha }} TEST_SUITE: load TEST_ARGS: -test.timeout 1h diff --git a/.github/workflows/changeset.yml b/.github/workflows/changeset.yml index e5853d374c..06c9ea6ab7 100644 --- a/.github/workflows/changeset.yml +++ b/.github/workflows/changeset.yml @@ -50,13 +50,8 @@ jobs: - '!core/**/*.json' - '!core/chainlink.goreleaser.Dockerfile' - '!core/chainlink.Dockerfile' - contracts: - - contracts/**/*.sol - - '!contracts/**/*.t.sol' core-changeset: - added: '.changeset/**' - contracts-changeset: - - added: 'contracts/.changeset/**' - name: Check for changeset tags for core id: changeset-tags @@ -87,19 +82,34 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # we need to set the top level directory for the jira-tracing action manually + # because now we are working with two repositories and automatic detection would + # select the repository with jira-tracing and not the chainlink repository + - name: Setup git top level directory + id: find-git-top-level-dir + run: echo "top_level_dir=$(pwd)" >> $GITHUB_OUTPUT + + - name: Checkout .Github repository + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + with: + repository: smartcontractkit/.github + ref: 9aed33e5298471f20a3d630d711b96ae5538728c # jira-tracing@0.2.0 + path: ./dot_github + - name: Update Jira ticket for core id: jira if: ${{ steps.files-changed.outputs.core == 'true' || steps.files-changed.outputs.shared == 'true' }} shell: bash - working-directory: ./.github/scripts/jira + working-directory: ./dot_github run: | echo "COMMIT_MESSAGE=$(git log -1 --pretty=format:'%s')" >> $GITHUB_ENV - pnpm install && node update-jira-issue.js + pnpm install --filter jira-tracing && pnpm --filter jira-tracing issue:update env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - JIRA_HOST: ${{ secrets.JIRA_HOST }} + JIRA_HOST: ${{ vars.JIRA_HOST }} JIRA_USERNAME: ${{ secrets.JIRA_USERNAME }} JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} + GIT_TOP_LEVEL_DIR: ${{ steps.find-git-top-level-dir.outputs.top_level_dir }} CHAINLINK_VERSION: ${{ steps.chainlink-version.outputs.chainlink_version }} PR_TITLE: ${{ github.event.pull_request.title }} BRANCH_NAME: ${{ github.event.pull_request.head.ref }} @@ -120,19 +130,6 @@ jobs: mode: ${{ steps.files-changed.outputs.core-changeset == 'false' && 'upsert' || 'delete' }} create_if_not_exists: ${{ steps.files-changed.outputs.core-changeset == 'false' && 'true' || 'false' }} - - name: Make a comment - uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0 - if: ${{ steps.files-changed.outputs.contracts == 'true' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - message: | - I see you updated files related to `contracts`. Please run `pnpm changeset` in the `contracts` directory to add a changeset. - reactions: eyes - comment_tag: changeset-contracts - mode: ${{ steps.files-changed.outputs.contracts-changeset == 'false' && 'upsert' || 'delete' }} - create_if_not_exists: ${{ steps.files-changed.outputs.contracts-changeset == 'false' && 'true' || 'false' }} - - name: Check for new changeset for core if: ${{ (steps.files-changed.outputs.core == 'true' || steps.files-changed.outputs.shared == 'true') && steps.files-changed.outputs.core-changeset == 'false' }} shell: bash @@ -140,13 +137,6 @@ jobs: echo "Please run pnpm changeset to add a changeset for core and include in the text at least one tag." exit 1 - - name: Check for new changeset for contracts - if: ${{ steps.files-changed.outputs.contracts == 'true' && steps.files-changed.outputs.contracts-changeset == 'false' }} - shell: bash - run: | - echo "Please run pnpm changeset to add a changeset for contracts." - exit 1 - - name: Make a comment uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0 if: ${{ steps.files-changed.outputs.core-changeset == 'true' }} diff --git a/.github/workflows/ci-core.yml b/.github/workflows/ci-core.yml index ddb89e517b..d7284d2f48 100644 --- a/.github/workflows/ci-core.yml +++ b/.github/workflows/ci-core.yml @@ -47,7 +47,9 @@ jobs: with: filters: | changes: + - 'integration-tests/deployment/**' - '!integration-tests/**' + - 'integration-tests/deployment/**' - name: Ignore Filter On Workflow Dispatch if: ${{ github.event_name == 'workflow_dispatch' }} id: ignore-filter @@ -57,6 +59,12 @@ jobs: # We don't directly merge dependabot PRs, so let's not waste the resources if: ${{ (github.event_name == 'pull_request' || github.event_name == 'schedule') && github.actor != 'dependabot[bot]' }} name: lint + permissions: + # For golangci-lint-actions to annotate code in the PR. + checks: write + contents: read + # For golangci-lint-action's `only-new-issues` option. + pull-requests: read runs-on: ubuntu22.04-8cores-32GB needs: [filter] steps: @@ -70,6 +78,7 @@ jobs: gc-basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} gc-host: ${{ secrets.GRAFANA_INTERNAL_HOST }} gc-org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} + - name: Notify Slack if: ${{ failure() && github.event.schedule != '' }} uses: slackapi/slack-github-action@6c661ce58804a1a20f6dc5fbee7f0381b469e001 # v1.25.0 @@ -249,7 +258,7 @@ jobs: needs: [filter, core] name: Flakey Test Detection runs-on: ubuntu-latest - if: ${{ always() && github.actor != 'dependabot[bot]' }} + if: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }} env: CL_DATABASE_URL: postgresql://postgres:postgres@localhost:5432/chainlink_test?sslmode=disable permissions: @@ -257,58 +266,45 @@ jobs: contents: read steps: - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@v4.2.1 - name: Setup node - if: ${{ needs.filter.outputs.changes == 'true' }} - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + uses: actions/setup-node@v4.0.4 - name: Setup NodeJS - if: ${{ needs.filter.outputs.changes == 'true' }} uses: ./.github/actions/setup-nodejs with: prod: "true" - name: Setup Go - if: ${{ needs.filter.outputs.changes == 'true' }} uses: ./.github/actions/setup-go - name: Setup Postgres - if: ${{ needs.filter.outputs.changes == 'true' }} uses: ./.github/actions/setup-postgres - name: Touching core/web/assets/index.html - if: ${{ needs.filter.outputs.changes == 'true' }} run: mkdir -p core/web/assets && touch core/web/assets/index.html - name: Download Go vendor packages - if: ${{ needs.filter.outputs.changes == 'true' }} run: go mod download - name: Replace chainlink-evm deps - if: ${{ needs.filter.outputs.changes == 'true' && inputs.evm-ref != ''}} + if: ${{ github.event_name == 'workflow_dispatch' && inputs.evm-ref != ''}} shell: bash run: go get github.com/smartcontractkit/chainlink-integrations/evm/relayer@${{ inputs.evm-ref }} - name: Build binary - if: ${{ needs.filter.outputs.changes == 'true' }} run: go build -o chainlink.test . - name: Setup DB - if: ${{ needs.filter.outputs.changes == 'true' }} run: ./chainlink.test local db preparetest - name: Load test outputs - if: ${{ needs.filter.outputs.changes == 'true' }} - uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4 + uses: actions/download-artifact@v4.1.8 with: name: go_core_tests_logs path: ./artifacts - name: Delete go_core_tests_logs/coverage.txt - if: ${{ needs.filter.outputs.changes == 'true' }} shell: bash run: | # Need to delete coverage.txt so the disk doesn't fill up rm -f ./artifacts/go_core_tests_logs/coverage.txt - name: Build flakey test runner - if: ${{ needs.filter.outputs.changes == 'true' }} run: go build ./tools/flakeytests/cmd/runner - name: Re-run tests - if: ${{ needs.filter.outputs.changes == 'true' }} env: GRAFANA_INTERNAL_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} GRAFANA_INTERNAL_HOST: ${{ secrets.GRAFANA_INTERNAL_HOST }} - GRAFANA_INTERNAL_TENANT_ID: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} GITHUB_EVENT_PATH: ${{ github.event_path }} GITHUB_EVENT_NAME: ${{ github.event_name }} GITHUB_REPO: ${{ github.repository }} @@ -317,21 +313,21 @@ jobs: ./runner \ -grafana_auth=$GRAFANA_INTERNAL_BASIC_AUTH \ -grafana_host=$GRAFANA_INTERNAL_HOST \ - -grafana_org_id=$GRAFANA_INTERNAL_TENANT_ID \ -gh_sha=$GITHUB_SHA \ -gh_event_path=$GITHUB_EVENT_PATH \ -gh_event_name=$GITHUB_EVENT_NAME \ -gh_run_id=$GITHUB_RUN_ID \ -gh_repo=$GITHUB_REPO \ -command=./tools/bin/go_core_tests \ - `ls -R ./artifacts/go_core_tests*/output.txt` + `ls -R ./artifacts/output.txt` - name: Store logs artifacts - if: ${{ needs.filter.outputs.changes == 'true' && always() }} - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + if: ${{ always() }} + uses: actions/upload-artifact@v4.4.3 with: name: flakey_test_runner_logs path: | ./output.txt + retention-days: 7 scan: name: SonarQube Scan @@ -340,43 +336,54 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@v4.2.1 with: fetch-depth: 0 # fetches all history for all tags and branches to provide more metadata for sonar reports - name: Download all workflow run artifacts - uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4 + uses: actions/download-artifact@v4.1.8 - - name: Set SonarQube Report Paths - id: sonarqube_report_paths + - name: Check and Set SonarQube Report Paths shell: bash run: | - echo "sonarqube_tests_report_paths=$(find go_core_tests_logs -name output.txt | paste -sd "," -)" >> $GITHUB_OUTPUT - echo "sonarqube_coverage_report_paths=$(find go_core_tests_logs -name coverage.txt | paste -sd "," -)" >> $GITHUB_OUTPUT - echo "sonarqube_lint_report_paths=$(find golangci-lint-report -name golangci-lint-report.xml | paste -sd "," -)" >> $GITHUB_OUTPUT + # Check and assign paths for coverage/test reports + if [ -d "go_core_tests_logs" ]; then + sonarqube_coverage_report_paths=$(find go_core_tests_logs -name coverage.txt | paste -sd "," -) + sonarqube_tests_report_paths=$(find go_core_tests_logs -name output.txt | paste -sd "," -) + else + sonarqube_coverage_report_paths="" + sonarqube_tests_report_paths="" + fi - - name: Check SonarQube Report Paths - id: check_sonarqube_paths - run: | - ARGS="" + # Check and assign paths for lint reports + if [ -d "golangci-lint-report" ]; then + sonarqube_lint_report_paths=$(find golangci-lint-report -name golangci-lint-report.xml | paste -sd "," -) + else + sonarqube_lint_report_paths="" + fi - if [[ -z "${{ steps.sonarqube_report_paths.outputs.sonarqube_tests_report_paths }}" ]]; then + ARGS="" + if [[ -z "$sonarqube_tests_report_paths" ]]; then echo "::warning::No test report paths found, will not pass to sonarqube" else - ARGS="$ARGS -Dsonar.go.tests.reportPaths=${{ steps.sonarqube_report_paths.outputs.sonarqube_tests_report_paths }}" + echo "Found test report paths: $sonarqube_tests_report_paths" + ARGS="$ARGS -Dsonar.go.tests.reportPaths=$sonarqube_tests_report_paths" fi - if [[ -z "${{ steps.sonarqube_report_paths.outputs.sonarqube_coverage_report_paths }}" ]]; then + if [[ -z "$sonarqube_coverage_report_paths" ]]; then echo "::warning::No coverage report paths found, will not pass to sonarqube" else - ARGS="$ARGS -Dsonar.go.coverage.reportPaths=${{ steps.sonarqube_report_paths.outputs.sonarqube_coverage_report_paths }}" + echo "Found coverage report paths: $sonarqube_coverage_report_paths" + ARGS="$ARGS -Dsonar.go.coverage.reportPaths=$sonarqube_coverage_report_paths" fi - if [[ -z "${{ steps.sonarqube_report_paths.outputs.sonarqube_lint_report_paths }}" ]]; then + if [[ -z "$sonarqube_lint_report_paths" ]]; then echo "::warning::No lint report paths found, will not pass to sonarqube" else - ARGS="$ARGS -Dsonar.go.golangci-lint.reportPaths=${{ steps.sonarqube_report_paths.outputs.sonarqube_lint_report_paths }}" + echo "Found lint report paths: $sonarqube_lint_report_paths" + ARGS="$ARGS -Dsonar.go.golangci-lint.reportPaths=$sonarqube_lint_report_paths" fi + echo "Final SONARQUBE_ARGS: $ARGS" echo "SONARQUBE_ARGS=$ARGS" >> $GITHUB_ENV - name: SonarQube Scan @@ -388,19 +395,6 @@ jobs: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} SONAR_SCANNER_OPTS: "-Xms6g -Xmx8g" - - - name: Collect Metrics - if: always() - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: ci-core-sonarqube - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: SonarQube Scan - continue-on-error: true - clean: name: Clean Go Tidy & Generate if: ${{ !contains(join(github.event.pull_request.labels.*.name, ' '), 'skip-smoke-tests') && github.actor != 'dependabot[bot]' }} @@ -425,7 +419,8 @@ jobs: run: curl https://github.com/smartcontractkit/wsrpc/raw/main/cmd/protoc-gen-go-wsrpc/protoc-gen-go-wsrpc --output $HOME/go/bin/protoc-gen-go-wsrpc && chmod +x $HOME/go/bin/protoc-gen-go-wsrpc - name: Setup NodeJS uses: ./.github/actions/setup-nodejs - - run: | + - name: make generate + run: | make rm-mocked make generate - name: Ensure clean after generate diff --git a/.github/workflows/ci-scripts.yml b/.github/workflows/ci-scripts.yml index 8497262977..8ca66332f1 100644 --- a/.github/workflows/ci-scripts.yml +++ b/.github/workflows/ci-scripts.yml @@ -6,7 +6,15 @@ on: jobs: lint-scripts: + # We don't directly merge dependabot PRs, so let's not waste the resources + if: ${{ (github.event_name == 'pull_request' || github.event_name == 'schedule') && github.actor != 'dependabot[bot]' }} runs-on: ubuntu-latest + permissions: + # For golangci-lint-actions to annotate code in the PR. + checks: write + contents: read + # For golangci-lint-action's `only-new-issues` option. + pull-requests: read steps: - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - name: Golang Lint diff --git a/.github/workflows/client-compatibility-tests.yml b/.github/workflows/client-compatibility-tests.yml index 7f569168c0..2e27f49a39 100644 --- a/.github/workflows/client-compatibility-tests.yml +++ b/.github/workflows/client-compatibility-tests.yml @@ -14,10 +14,10 @@ on: required: false type: string evmImplementations: - description: comma separated list of EVM implementations to test (ignored if base64TestList is used) + description: comma separated list of EVM implementations to test (ignored if base64TestList is used); supports geth,besu,nethermind,erigon,reth required: true type: string - default: "geth,besu,nethermind,erigon" + default: "geth,besu,nethermind,erigon,reth" latestVersionsNumber: description: how many of latest images of EVM implementations to test with (ignored if base64TestList is used) required: true @@ -98,15 +98,21 @@ jobs: id: should-run run: | if [ "${{ needs.check-dependency-bump.outputs.dependency_changed }}" == "true" ]; then + echo "## Build trigger" >> $GITHUB_STEP_SUMMARY + echo "go-ethereum dependency bump" >> $GITHUB_STEP_SUMMARY echo "Will run tests, because go-ethereum dependency was bumped" echo "should_run=true" >> $GITHUB_OUTPUT elif [ "$GITHUB_EVENT_NAME" = "schedule" ]; then + echo "## Build trigger" >> $GITHUB_STEP_SUMMARY + echo "schedule" >> $GITHUB_STEP_SUMMARY echo "Will run tests, because trigger event was $GITHUB_EVENT_NAME" echo "should_run=true" >> $GITHUB_OUTPUT elif [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then echo "Will run tests, because trigger event was $GITHUB_EVENT_NAME" echo "should_run=true" >> $GITHUB_OUTPUT elif [ "$GITHUB_REF_TYPE" = "tag" ]; then + echo "## Build trigger" >> $GITHUB_STEP_SUMMARY + echo "new tag" >> $GITHUB_STEP_SUMMARY echo "Will run tests, because new tag was created" echo "should_run=true" >> $GITHUB_OUTPUT else @@ -125,6 +131,7 @@ jobs: outputs: evm_implementations: ${{ steps.select-implementations.outputs.evm_implementations }} chainlink_version: ${{ steps.select-chainlink-version.outputs.chainlink_version }} + chainlink_image_version: ${{ steps.select-chainlink-version.outputs.chainlink_image_version }} latest_image_count: ${{ steps.get-image-count.outputs.image_count }} chainlink_ref_path: ${{ steps.select-chainlink-version.outputs.cl_ref_path }} steps: @@ -137,7 +144,7 @@ jobs: id: select-implementations run: | PATH=$PATH:$(go env GOPATH)/bin - export PATH + export PATH if [ "$GITHUB_EVENT_NAME" = "schedule" ]; then echo "Checking for new releases" @@ -162,6 +169,12 @@ jobs: echo "New nethermind release found: $new_nethermind" implementations_arr+=("nethermind") fi + new_reth=$(ghlatestreleasechecker "paradigmxyz/reth" $RELEASED_DAYS_AGO) + if [ "$new_reth" != "none" ]; then + echo "New reth release found: $new_reth" + implementations_arr+=("reth") + fi + IFS=',' eth_implementations="${implementations_arr[*]}" if [ -n "$eth_implementations" ]; then @@ -179,7 +192,7 @@ jobs: fi else echo "Will test all EVM implementations" - echo "evm_implementations=geth,besu,nethermind,erigon" >> $GITHUB_OUTPUT + echo "evm_implementations=geth,besu,nethermind,erigon,reth" >> $GITHUB_OUTPUT fi - name: Select Chainlink version id: select-chainlink-version @@ -192,8 +205,8 @@ jobs: implementations_arr=() # we use 100 days since we really want the latest one, and it's highly improbable there won't be a release in last 100 days chainlink_version=$(ghlatestreleasechecker "smartcontractkit/chainlink" 100) - echo "chainlink_version=$chainlink_version" >> $GITHUB_OUTPUT - cl_ref_path="release" + chainlink_image_version=$chainlink_version + cl_ref_path="releases" elif [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then echo "Fetching Chainlink version from input" if [ -n "${{ github.event.inputs.chainlinkVersion }}" ]; then @@ -201,36 +214,43 @@ jobs: chainlink_version="${{ github.event.inputs.chainlinkVersion }}" if [[ "$chainlink_version" =~ ^[0-9a-f]{40}$ ]]; then cl_ref_path="commit" + chainlink_image_version=$chainlink_version else - cl_ref_path="release" + cl_ref_path="releases" + # strip the 'v' from the version, because we tag our Docker images without it + chainlink_image_version="${chainlink_version#v}" fi else echo "Chainlink version not provided in input. Using latest commit SHA." chainlink_version=${{ github.sha }} + chainlink_image_version=$chainlink_version cl_ref_path="commit" fi - echo "chainlink_version=$chainlink_version" >> $GITHUB_OUTPUT - echo "cl_ref_path=$cl_ref_path" >> $GITHUB_OUTPUT elif [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then echo "Fetching Chainlink version from PR's head commit" chainlink_version="${{ github.event.pull_request.head.sha }}" - echo "chainlink_version=$chainlink_version" >> $GITHUB_OUTPUT - echo "cl_ref_path=commit" >> $GITHUB_OUTPUT + chainlink_image_version=$chainlink_version + cl_ref_path="commit" elif [ "$GITHUB_EVENT_NAME" = "merge_queue" ]; then echo "Fetching Chainlink version from merge queue's head commit" chainlink_version="${{ github.event.merge_group.head_sha }}" - echo "chainlink_version=$chainlink_version" >> $GITHUB_OUTPUT - echo "cl_ref_path=commit" >> $GITHUB_OUTPUT + chainlink_image_version=$chainlink_version + cl_ref_path="commit" elif [ "$GITHUB_REF_TYPE" = "tag" ]; then echo "Fetching Chainlink version from tag" chainlink_version="${{ github.ref_name }}" - echo "chainlink_version=$chainlink_version" >> $GITHUB_OUTPUT - echo "cl_ref_path=release" >> $GITHUB_OUTPUT + # strip the 'v' from the version, because we tag our Docker images without it + chainlink_image_version="${chainlink_version#v}" + cl_ref_path="releases" else echo "Unsupported trigger event. It's probably an issue with the pipeline definition. Please reach out to the Test Tooling team." exit 1 fi echo "Will use following Chainlink version: $chainlink_version" + echo "chainlink_version=$chainlink_version" >> $GITHUB_OUTPUT + echo "Will use following Chainlink Docker image version: $chainlink_image_version" + echo "chainlink_image_version=$chainlink_image_version" >> $GITHUB_OUTPUT + echo "cl_ref_path=$cl_ref_path" >> $GITHUB_OUTPUT - name: Get image count id: get-image-count run: | @@ -273,9 +293,11 @@ jobs: expression: '^[0-9]+\.[0-9]+\.[0-9]+$' - name: tofelb/ethereum-genesis-generator expression: '^[0-9]+\.[0-9]+\.[0-9]+(\-slots\-per\-epoch)?' + - name: ghcr.io/paradigmxyz/reth + expression: '^v[0-9]+\.[0-9]+\.[0-9]+$' steps: - name: Update internal ECR if the latest Ethereum client image does not exist - uses: smartcontractkit/chainlink-testing-framework/.github/actions/update-internal-mirrors@5eea86ee4f7742b4e944561a570a6b268e712d9e # v1.30.3 + uses: smartcontractkit/chainlink-testing-framework/.github/actions/update-internal-mirrors@352cf299b529a33208146d9f7f0e0b5534fba6e7 # v1.33.0 with: aws_region: ${{ secrets.QA_AWS_REGION }} role_to_assume: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} @@ -283,6 +305,7 @@ jobs: image_name: ${{matrix.mirror.name}} expression: ${{matrix.mirror.expression}} page_size: ${{matrix.mirror.page_size}} + github_token: ${{ secrets.RETH_GH_TOKEN }} # needed only for checking GHRC.io repositories build-chainlink: if: | @@ -309,7 +332,9 @@ jobs: with: tag_suffix: "" dockerfile: core/chainlink.Dockerfile - git_commit_sha: ${{ needs.select-versions.outputs.chainlink_version }} + # for tagged releases Docker image version is different from the Chainlink version (v2.13.0 -> 2.13.0) + # for all other cases (PRs, commits, etc.) Docker image version is the same as the Chainlink version + git_commit_sha: ${{ needs.select-versions.outputs.chainlink_image_version }} check_image_exists: "true" AWS_REGION: ${{ secrets.QA_AWS_REGION }} AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} @@ -326,7 +351,7 @@ jobs: get-latest-available-images: name: Get Latest EVM Implementation's Images - if: always() && needs.should-run.outputs.should_run == 'true' && (needs.select-versions.outputs.evm_implementations != '' || github.event.inputs.base64TestList != '') + if: always() && needs.should-run.outputs.should_run == 'true' && needs.select-versions.outputs.evm_implementations != '' && github.event.inputs.base64TestList == '' environment: integration runs-on: ubuntu-latest needs: [check-ecr-images-exist, should-run, select-versions] @@ -340,6 +365,7 @@ jobs: nethermind_images: ${{ env.NETHERMIND_IMAGES }} besu_images: ${{ env.BESU_IMAGES }} erigon_images: ${{ env.ERIGON_IMAGES }} + reth_images: ${{ env.RETH_IMAGES }} steps: # Setup AWS creds - name: Configure AWS Credentials @@ -395,6 +421,12 @@ jobs: echo "Erigon latest images: $erigon_images" fi + if [[ "$ETH_IMPLEMENTATIONS" == *"reth"* ]]; then + reth_images=$(ecrimagefetcher 'ghcr.io/paradigmxyz/reth' '^v[0-9]+\.[0-9]+\.[0-9]+$' ${{ env.LATEST_IMAGE_COUNT }}) + echo "RETH_IMAGES=$reth_images" >> $GITHUB_ENV + echo "Reth latest images: $reth_images" + fi + # End Build Test Dependencies prepare-compatibility-matrix: @@ -523,6 +555,23 @@ jobs: echo "Will not test compatibility with nethermind" fi + if [[ "$ETH_IMPLEMENTATIONS" == *"reth"* ]]; then + echo "Will test compatibility with reth" + testlistgenerator -o compatibility_test_list.json -p cron -r TestCronBasic -f './smoke/cron_test.go' -e reth -d "${{ needs.get-latest-available-images.outputs.reth_images }}" -t "evm-implementation-compatibility-test" -n "ubuntu-latest" + testlistgenerator -o compatibility_test_list.json -p flux -r TestFluxBasic -f './smoke/flux_test.go' -e reth -d "${{ needs.get-latest-available-images.outputs.reth_images }}" -t "evm-implementation-compatibility-test" -n "ubuntu-latest" + testlistgenerator -o compatibility_test_list.json -p runlog -r TestRunLogBasic -f './smoke/runlog_test.go' -e reth -d "${{ needs.get-latest-available-images.outputs.reth_images }}" -t "evm-implementation-compatibility-test" -n "ubuntu-latest" + testlistgenerator -o compatibility_test_list.json -p log_poller -r TestLogPollerFewFiltersFixedDepth -f './smoke/log_poller_test.go' -e reth -d "${{ needs.get-latest-available-images.outputs.reth_images }}" -t "evm-implementation-compatibility-test" -n "ubuntu-latest" + testlistgenerator -o compatibility_test_list.json -p ocr -r TestOCRBasic -f './smoke/ocr_test.go' -e reth -d "${{ needs.get-latest-available-images.outputs.reth_images }}" -t "evm-implementation-compatibility-test" -n "ubuntu-latest" + testlistgenerator -o compatibility_test_list.json -p ocr2 -r '^TestOCRv2Basic/plugins$' -f './smoke/ocr2_test.go' -e reth -d "${{ needs.get-latest-available-images.outputs.reth_images }}" -t "evm-implementation-compatibility-test" -n "ubuntu-latest" + testlistgenerator -o compatibility_test_list.json -p automation -r 'TestAutomationBasic/registry_2_1_logtrigger' -f './smoke/automation_test.go' -e reth -d "${{ needs.get-latest-available-images.outputs.reth_images }}" -t "evm-implementation-compatibility-test" -n "ubuntu-latest" + testlistgenerator -o compatibility_test_list.json -p keeper -r 'TestKeeperBasicSmoke/registry_1_3' -f './smoke/keeper_test.go' -e reth -d "${{ needs.get-latest-available-images.outputs.reth_images }}" -t "evm-implementation-compatibility-test" -n "ubuntu-latest" + testlistgenerator -o compatibility_test_list.json -p vrf -r '^TestVRFBasic/Request_Randomness$' -f './smoke/vrf_test.go' -e reth -d "${{ needs.get-latest-available-images.outputs.reth_images }}" -t "evm-implementation-compatibility-test" -n "ubuntu-latest" + testlistgenerator -o compatibility_test_list.json -p vrfv2 -r '^TestVRFv2Basic/Request_Randomness$' -f './smoke/vrfv2_test.go' -e reth -d "${{ needs.get-latest-available-images.outputs.reth_images }}" -t "evm-implementation-compatibility-test" -n "ubuntu-latest" + testlistgenerator -o compatibility_test_list.json -p vrfv2plus -r '^TestVRFv2Plus$/^Link_Billing$' -f './smoke/vrfv2plus_test.go' -e reth -d "${{ needs.get-latest-available-images.outputs.reth_images }}" -t "evm-implementation-compatibility-test" -n "ubuntu-latest" + else + echo "Will not test compatibility with reth" + fi + jq . compatibility_test_list.json JOB_MATRIX_JSON=$(jq -c . compatibility_test_list.json) echo "JOB_MATRIX_JSON=${JOB_MATRIX_JSON}" >> $GITHUB_ENV @@ -595,23 +644,15 @@ jobs: # comment_on_pr: false # theme: 'dark' - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@aa8eea635029ab8d95abd3c206f56dae1e22e623 # v2.3.28 + uses: smartcontractkit/.github/actions/ctf-run-tests@b6e37806737eef87e8c9137ceeb23ef0bff8b1db # ctf-run-tests@0.1.0 with: test_command_to_run: cd ./integration-tests && touch .root_dir && go test -timeout 30m -count=1 -json ${{ matrix.evm_node.run }} 2>&1 | tee /tmp/gotest.log | gotestloghelper -ci -singlepackage -hidepassingtests=false -hidepassinglogs test_download_vendor_packages_command: cd ./integration-tests && go mod download - test_config_chainlink_version: ${{ needs.select-versions.outputs.chainlink_version }} - test_config_selected_networks: ${{ env.SELECTED_NETWORKS}} - test_config_logging_run_id: ${{ github.run_id }} - test_config_test_log_collect: ${{ vars.TEST_LOG_COLLECT }} - test_config_logstream_log_targets: ${{ vars.LOGSTREAM_LOG_TARGETS }} - test_config_private_ethereum_network_execution_layer: ${{ matrix.evm_node.eth_implementation || 'geth' }} - test_config_private_ethereum_network_custom_docker_image: ${{ matrix.evm_node.docker_image }} - cl_repo: ${{ env.CHAINLINK_IMAGE }} - cl_image_tag: ${{ needs.select-versions.outputs.chainlink_version }} aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} artifacts_name: ${{ env.TEST_LOG_NAME }} artifacts_location: | ./integration-tests/smoke/logs/ + ./integration-tests/smoke/db_dumps/ /tmp/gotest.log publish_check_name: ${{ matrix.evm_node.product }}-${{ matrix.evm_node.eth_implementation }} token: ${{ secrets.GITHUB_TOKEN }} @@ -624,21 +665,29 @@ jobs: should_tidy: "false" go_coverage_src_dir: /var/tmp/go-coverage go_coverage_dest_dir: ${{ github.workspace }}/.covdata - DEFAULT_CHAINLINK_IMAGE: ${{ env.CHAINLINK_IMAGE }} - DEFAULT_LOKI_TENANT_ID: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - DEFAULT_LOKI_ENDPOINT: https://${{ secrets.GRAFANA_INTERNAL_HOST }}/loki/api/v1/push - DEFAULT_LOKI_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - DEFAULT_GRAFANA_BASE_URL: "http://localhost:8080/primary" - DEFAULT_GRAFANA_DASHBOARD_URL: "/d/ddf75041-1e39-42af-aa46-361fe4c36e9e/ci-e2e-tests-logs" - DEFAULT_GRAFANA_BEARER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} - DEFAULT_PYROSCOPE_SERVER_URL: ${{ secrets.QA_PYROSCOPE_INSTANCE }} - DEFAULT_PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} - DEFAULT_PYROSCOPE_ENVIRONMENT: ci-client-compatability-${{ matrix.eth_client }}-testnet - DEFAULT_PYROSCOPE_ENABLED: "true" - - - name: Print failed test summary + env: + E2E_TEST_SELECTED_NETWORK: ${{ env.SELECTED_NETWORKS}} + E2E_TEST_CHAINLINK_IMAGE: ${{ env.CHAINLINK_IMAGE }} + E2E_TEST_CHAINLINK_VERSION: ${{ needs.select-versions.outputs.chainlink_image_version }} + E2E_TEST_LOKI_TENANT_ID: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} + E2E_TEST_LOKI_ENDPOINT: https://${{ secrets.GRAFANA_INTERNAL_HOST }}/loki/api/v1/push + E2E_TEST_LOKI_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} + E2E_TEST_GRAFANA_DASHBOARD_URL: "/d/ddf75041-1e39-42af-aa46-361fe4c36e9e/ci-e2e-tests-logs" + E2E_TEST_GRAFANA_BEARER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} + E2E_TEST_PYROSCOPE_SERVER_URL: ${{ secrets.QA_PYROSCOPE_INSTANCE }} + E2E_TEST_PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} + E2E_TEST_PYROSCOPE_ENVIRONMENT: ci-client-compatability-${{ matrix.eth_client }}-testnet + E2E_TEST_PYROSCOPE_ENABLED: "true" + E2E_TEST_LOGGING_RUN_ID: ${{ github.run_id }} + E2E_TEST_LOG_COLLECT: ${{ vars.TEST_LOG_COLLECT }} + E2E_TEST_LOG_STREAM_LOG_TARGETS: ${{ vars.LOGSTREAM_LOG_TARGETS }} + E2E_TEST_PRIVATE_ETHEREUM_EXECUTION_LAYER: ${{ matrix.evm_node.eth_implementation || 'geth' }} + E2E_TEST_PRIVATE_ETHEREUM_ETHEREUM_VERSION: auto_fill # Auto fill the version based on the docker image + E2E_TEST_PRIVATE_ETHEREUM_CUSTOM_DOCKER_IMAGE: ${{ matrix.evm_node.docker_image }} + + - name: Show Grafana url in test summary if: always() - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/show-test-summary@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 + uses: smartcontractkit/.github/actions/ctf-show-grafana-in-test-summary@b6e37806737eef87e8c9137ceeb23ef0bff8b1db # ctf-show-grafana-in-test-summary@0.1.0 start-slack-thread: name: Start Slack Thread @@ -652,7 +701,7 @@ jobs: id-token: write contents: read runs-on: ubuntu-latest - needs: [run-client-compatibility-matrix, should-run, select-versions] + needs: [run-client-compatibility-matrix, should-run, select-versions, build-chainlink, prepare-compatibility-matrix] steps: - name: Debug Result run: echo ${{ join(needs.*.result, ',') }} @@ -665,7 +714,7 @@ jobs: { "attachments": [ { - "color": "${{ contains(join(needs.*.result, ','), 'failure') && '#C62828' || '#2E7D32' }}", + "color": "${{ (contains(join(needs.*.result, ','), 'failure') || needs.build-chainlink.result == 'failure') && '#C62828' || '#2E7D32' }}", "blocks": [ { "type": "header", @@ -679,7 +728,7 @@ jobs: "type": "section", "text": { "type": "mrkdwn", - "text": "${{ contains(join(needs.*.result, ','), 'failure') && format('Some tests failed, notifying <{0}>', secrets.COMPAT_SLACK_NOTIFICATION_HANDLE) || 'All Good!' }}" + "text": "${{ needs.prepare-compatibility-matrix.result == 'failure' && 'Failed to prepare test matrix, notifying ' || needs.build-chainlink.result == 'failure' && 'Failed to build Chainlink image, notifying ' || contains(join(needs.*.result, ','), 'failure') && format('Some tests failed, notifying ', secrets.COMPAT_SLACK_NOTIFICATION_HANDLE) || 'All Good!' }}" } }, { diff --git a/.github/workflows/crib-integration-test.yml b/.github/workflows/crib-integration-test.yml index 75b2215d2f..56e025eeff 100644 --- a/.github/workflows/crib-integration-test.yml +++ b/.github/workflows/crib-integration-test.yml @@ -1,74 +1,113 @@ -# this is disabled because of GAP limitations, should be re-enabled when github-actions-controller will be installed +name: CRIB Integration Tests +on: + schedule: + - cron: "0 1 * * *" + workflow_call: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + test: + runs-on: ubuntu-latest + environment: integration + permissions: + id-token: write + contents: read + actions: read + steps: + - name: Checkout repository + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 -#name: CRIB Integration Tests -#on: -# push: -# workflow_call: -#concurrency: -# group: ${{ github.workflow }}-${{ github.ref }} -# cancel-in-progress: true -#jobs: -# test: -# runs-on: ubuntu-latest -# environment: integration -# permissions: -# id-token: write -# contents: read -# actions: read -# steps: -# - name: Checkout repository -# uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 -# -# - name: Setup Nix + GATI environment -# uses: smartcontractkit/.github/actions/setup-nix-gati@514fe346780e2eddf7ea8b9f48120c2fba120d94 -# with: -# aws-role-arn: ${{ secrets.AWS_OIDC_CHAINLINK_AUTO_PR_TOKEN_ISSUER_ROLE_ARN }} -# aws-lambda-url: ${{ secrets.AWS_CORE_TOKEN_ISSUER_LAMBDA_URL }} # see https://github.com/smartcontractkit/ infra/blob/a79bcfb48315c4411023c182e98eb80ff9e9cda6/accounts/production/us-west-2/lambda/ github-app-token-issuer-production/teams/releng/config.json#L9 -# aws-region: ${{ secrets.AWS_REGION }} -# aws-role-duration-seconds: ${{ secrets.AWS_ROLE_DURATION_SECONDS }} -# enable-magic-cache: true -# -# - name: Nix Develop Action -# uses: nicknovitski/nix-develop@v1 -# with: -# arguments: "--accept-flake-config" -# - name: setup-gap -# uses: smartcontractkit/.github/actions/setup-gap@d316f66b2990ea4daa479daa3de6fc92b00f863e # setup-gap@0.3.2 -# with: -# aws-role-arn: ${{ secrets.AWS_OIDC_CRIB_ROLE_ARN_STAGE }} -# api-gateway-host: ${{ secrets.AWS_API_GW_HOST_K8S_STAGE }} -# aws-region: ${{ secrets.AWS_REGION }} -# ecr-private-registry: ${{ secrets.AWS_ACCOUNT_ID_PROD }} -# k8s-cluster-name: ${{ secrets.AWS_K8S_CLUSTER_NAME_STAGE }} -# use-private-ecr-registry: true -# use-k8s: true -# metrics-job-name: "k8s" -# gc-basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} -# gc-host: ${{ secrets.GRAFANA_INTERNAL_HOST }} -# gc-org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} -# - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 -# name: Checkout CRIB repository -# with: -# repository: 'smartcontractkit/crib' -# ref: 'main' -# - name: Generate Short UUID -# id: uuid -# run: echo "CRIB_NAMESPACE=$(uuidgen | cut -c1-5)" >> $GITHUB_ENV -# - name: Create a new CRIB environment -# run: |- -# devspace use namespace $CRIB_NAMESPACE -# devspace deploy --profile local-dev-simulated-core-ocr1 -# - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 -# - name: Setup go -# uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 -# with: -# go-version-file: "go.mod" -# - name: Run CRIB integration test -# working-directory: integration-tests/crib -# env: -# K8S_STAGING_INGRESS_SUFFIX: ${{ secrets.K8S_STAGING_INGRESS_SUFFIX }} -# CRIB_NAMESPACE: ${{ env.CRIB_NAMESPACE }} -# CRIB_NETWORK: geth -# CRIB_NODES: 5 -# run: |- -# go test -v -run TestCRIB \ No newline at end of file + - uses: cachix/install-nix-action@ba0dd844c9180cbf77aa72a116d6fbc515d0e87b # v27 + with: + nix_path: nixpkgs=channel:nixos-unstable + + - name: setup-gap crib + uses: smartcontractkit/.github/actions/setup-gap@00b58566e0ee2761e56d9db0ea72b783fdb89b8d # setup-gap@0.4.0 + with: + aws-role-duration-seconds: 3600 # 1 hour + aws-role-arn: ${{ secrets.AWS_OIDC_CRIB_ROLE_ARN_STAGE }} + api-gateway-host: ${{ secrets.AWS_API_GW_HOST_CRIB_STAGE }} + aws-region: ${{ secrets.AWS_REGION }} + ecr-private-registry: ${{ secrets.AWS_ACCOUNT_ID_PROD }} + k8s-cluster-name: ${{ secrets.AWS_K8S_CLUSTER_NAME_STAGE }} + gap-name: crib + use-private-ecr-registry: true + use-tls: true + proxy-port: 8080 + metrics-job-name: "test" + gc-basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} + gc-host: ${{ secrets.GRAFANA_INTERNAL_HOST }} + gc-org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} + + - name: setup-gap k8s + uses: smartcontractkit/.github/actions/setup-gap@00b58566e0ee2761e56d9db0ea72b783fdb89b8d # setup-gap@0.4.0 + with: + aws-role-duration-seconds: 3600 # 1 hour + aws-role-arn: ${{ secrets.AWS_OIDC_CRIB_ROLE_ARN_STAGE }} + api-gateway-host: ${{ secrets.AWS_API_GW_HOST_K8S_STAGE }} + aws-region: ${{ secrets.AWS_REGION }} + ecr-private-registry: ${{ secrets.AWS_ACCOUNT_ID_PROD }} + k8s-cluster-name: ${{ secrets.AWS_K8S_CLUSTER_NAME_STAGE }} + gap-name: k8s + use-private-ecr-registry: true + use-k8s: true + proxy-port: 8443 + metrics-job-name: "test" + gc-basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} + gc-host: ${{ secrets.GRAFANA_INTERNAL_HOST }} + gc-org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} + + - name: Setup GitHub token using GATI + id: token + uses: smartcontractkit/.github/actions/setup-github-token@c0b38e6c40d72d01b8d2f24f92623a2538b3dedb # main + with: + aws-role-arn: ${{ secrets.AWS_OIDC_GLOBAL_READ_ONLY_TOKEN_ISSUER_ROLE_ARN }} + aws-lambda-url: ${{ secrets.AWS_INFRA_RELENG_TOKEN_ISSUER_LAMBDA_URL }} + aws-region: ${{ secrets.AWS_REGION }} + aws-role-duration-seconds: "1800" + - name: Debug workspace dir + shell: bash + run: | + echo ${{ github.workspace }} + echo $GITHUB_WORKSPACE + + - name: Deploy and validate CRIB Environment for Core + uses: smartcontractkit/.github/actions/crib-deploy-environment@4dd21a9d6e3f1383ffe8b9650b55f6e6031d3d0a # crib-deploy-environment@1.0.0 + id: deploy-crib + with: + github-token: ${{ steps.token.outputs.access-token }} + api-gateway-host: ${{ secrets.AWS_API_GW_HOST_K8S_STAGE }} + aws-region: ${{ secrets.AWS_REGION }} + aws-role-arn: ${{ secrets.AWS_OIDC_CRIB_ROLE_ARN_STAGE }} + ecr-private-registry: ${{ secrets.AWS_ACCOUNT_ID_PROD }} + ingress-base-domain: ${{ secrets.INGRESS_BASE_DOMAIN_STAGE }} + k8s-cluster-name: ${{ secrets.AWS_K8S_CLUSTER_NAME_STAGE }} + devspace-profiles: "local-dev-simulated-core-ocr1" + crib-alert-slack-webhook: ${{ secrets.CRIB_ALERT_SLACK_WEBHOOK }} + product-image: ${{ secrets.AWS_SDLC_ECR_HOSTNAME }}/chainlink + product-image-tag: develop + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + - name: Setup go + uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + with: + go-version-file: "go.mod" + - name: Run CRIB integration test + working-directory: integration-tests/crib + env: + K8S_STAGING_INGRESS_SUFFIX: ${{ secrets.K8S_STAGING_INGRESS_SUFFIX }} + CRIB_NAMESPACE: ${{ steps.deploy-crib.outputs.devspace-namespace }} + CRIB_NETWORK: geth + CRIB_NODES: 5 + GAP_URL: ${{ secrets.GAP_URL }} + SETH_LOG_LEVEL: info + # RESTY_DEBUG: true + TEST_PERSISTENCE: true + run: |- + go test -v -run TestCRIBChaos + - name: Destroy CRIB Environment + id: destroy + if: always() && steps.deploy-crib.outputs.devspace-namespace != '' + uses: smartcontractkit/.github/actions/crib-purge-environment@c0b38e6c40d72d01b8d2f24f92623a2538b3dedb # crib-purge-environment@0.1.0 + with: + namespace: ${{ steps.deploy-crib.outputs.devspace-namespace }} diff --git a/.github/workflows/gha-workflow-validation.yml b/.github/workflows/gha-workflow-validation.yml deleted file mode 100644 index a265a4eef3..0000000000 --- a/.github/workflows/gha-workflow-validation.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: GHA Workflow Validation - -on: - pull_request: - -jobs: - - validate-worfklow-changes: - name: Validate Workflow Changes - permissions: - contents: read - pull-requests: write - actions: read - runs-on: ubuntu-latest - steps: - - name: GHA Workflow Validator - uses: smartcontractkit/.github/actions/gha-workflow-validator@d316f66b2990ea4daa479daa3de6fc92b00f863e # gha-workflow-validator@0.2.0 - env: - GITHUB_TOKEN: ${{ github.token }} - - - name: Collect Metrics - if: always() - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: lint-gh-workflows - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: Validate Workflow Changes - continue-on-error: true diff --git a/.github/workflows/goreleaser-build-publish-develop.yml b/.github/workflows/goreleaser-build-publish-develop.yml deleted file mode 100644 index 835d650f18..0000000000 --- a/.github/workflows/goreleaser-build-publish-develop.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: "Build publish Chainlink develop on private ECR" - -on: - push: - branches: - - develop - -jobs: - push-chainlink-develop-goreleaser: - runs-on: - labels: ubuntu22.04-16cores-64GB - outputs: - goreleaser-metadata: ${{ steps.build-sign-publish.outputs.goreleaser-metadata }} - goreleaser-artifacts: ${{ steps.build-sign-publish.outputs.goreleaser-artifacts }} - environment: build-develop - permissions: - id-token: write - contents: read - steps: - - name: Checkout repository - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - name: Configure aws credentials - uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 - with: - role-to-assume: ${{ secrets.AWS_OIDC_IAM_ROLE_ARN }} - role-duration-seconds: ${{ secrets.AWS_ROLE_DURATION_SECONDS }} - aws-region: ${{ secrets.AWS_REGION }} - mask-aws-account-id: true - role-session-name: goreleaser-build-publish-chainlink.push-develop - - name: Build, sign, and publish image - id: build-sign-publish - uses: ./.github/actions/goreleaser-build-sign-publish - with: - enable-docker-publish: "true" - docker-registry: ${{ secrets.AWS_DEVELOP_ECR_HOSTNAME }} - enable-goreleaser-snapshot: "true" - goreleaser-exec: ./tools/bin/goreleaser_wrapper - goreleaser-config: .goreleaser.develop.yaml - goreleaser-key: ${{ secrets.GORELEASER_KEY }} - zig-version: 0.11.0 - - name: Collect Metrics - if: always() - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: goreleaser-build-publish - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: push-chainlink-develop-goreleaser - continue-on-error: true - \ No newline at end of file diff --git a/.github/workflows/integration-chaos-tests.yml b/.github/workflows/integration-chaos-tests.yml index 673614bf2c..3198320f6c 100644 --- a/.github/workflows/integration-chaos-tests.yml +++ b/.github/workflows/integration-chaos-tests.yml @@ -7,148 +7,32 @@ on: - "*" workflow_dispatch: -env: - CHAINLINK_IMAGE: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink - ENV_JOB_IMAGE: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink-tests:${{ github.sha }} - TEST_SUITE: chaos - TEST_ARGS: -test.timeout 1h - CHAINLINK_COMMIT_SHA: ${{ github.sha }} - CHAINLINK_ENV_USER: ${{ github.actor }} - TEST_LOG_LEVEL: debug - jobs: - build-chainlink: - environment: integration - permissions: - id-token: write - contents: read - name: Build Chainlink Image - runs-on: ubuntu-latest - steps: - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - name: Check if image exists - id: check-image - uses: smartcontractkit/chainlink-github-actions/docker/image-exists@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - repository: chainlink - tag: ${{ github.sha }} - AWS_REGION: ${{ secrets.QA_AWS_REGION }} - AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - - name: Build Image - if: steps.check-image.outputs.exists == 'false' - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/build-image@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - cl_repo: smartcontractkit/chainlink - cl_ref: ${{ github.sha }} - push_tag: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink:${{ github.sha }} - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - - name: Print Chainlink Image Built - id: push - run: | - echo "### chainlink node image tag used for this test run :link:" >>$GITHUB_STEP_SUMMARY - echo "\`${GITHUB_SHA}\`" >>$GITHUB_STEP_SUMMARY - - name: Collect Metrics - if: always() - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: e2e-chaos-build-chainlink - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: Build Chainlink Image - continue-on-error: true - - build-test-runner: - environment: integration - permissions: - id-token: write - contents: read - name: Build Test Runner Image - runs-on: ubuntu-latest - steps: - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - name: Build Test Image - uses: ./.github/actions/build-test-image - with: - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - - name: Collect Metrics - if: always() - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: e2e-chaos-build-test-image - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: Build Test Runner Image - continue-on-error: true - - chaos-tests: - environment: integration - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - name: EVM Pods Chaos Tests - runs-on: ubuntu-latest - needs: [build-test-runner, build-chainlink] - steps: - - name: Collect Metrics - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: e2e-chaos-tests - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: EVM Pods Chaos Tests - test-results-file: '{"testType":"go","filePath":"/tmp/gotest.log"}' - continue-on-error: true - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - name: Prepare Base64 TOML config - env: - CHAINLINK_VERSION: ${{ github.sha }} - run: | - echo ::add-mask::$CHAINLINK_IMAGE - - cat << EOF > config.toml - [Network] - selected_networks=["SIMULATED"] - - [ChainlinkImage] - image="$CHAINLINK_IMAGE" - version="$CHAINLINK_VERSION" - EOF - - BASE64_CONFIG_OVERRIDE=$(cat config.toml | base64 -w 0) - echo ::add-mask::$BASE64_CONFIG_OVERRIDE - echo "BASE64_CONFIG_OVERRIDE=$BASE64_CONFIG_OVERRIDE" >> $GITHUB_ENV - - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@2967f2287bd3f3ddbac7b476e9568993df01796e # v2.3.27 - with: - test_command_to_run: cd integration-tests && go test -timeout 1h -count=1 -json -test.parallel 11 ./chaos 2>&1 | tee /tmp/gotest.log | gotestloghelper -ci -singlepackage -hidepassingtests=false -hidepassinglogs - test_download_vendor_packages_command: cd ./integration-tests && go mod download - cl_repo: ${{ env.CHAINLINK_IMAGE }} - cl_image_tag: ${{ github.sha }} - artifacts_location: ./integration-tests/chaos/logs - publish_check_name: EVM Pods Chaos Test Results - token: ${{ secrets.GITHUB_TOKEN }} - go_mod_path: ./integration-tests/go.mod - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} - - name: Upload test log - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 - if: failure() - with: - name: Test Results Log - path: /tmp/gotest.log - retention-days: 7 + run-e2e-tests-workflow: + name: Run E2E Tests + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@aad83f232743646faa35f5ac03ee3829148d37ce + with: + test_path: .github/e2e-tests.yml + chainlink_version: ${{ github.sha }} + require_chainlink_image_versions_in_qa_ecr: ${{ github.sha }} + test_trigger: E2E Chaos Tests + test_log_level: debug + secrets: + QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} + QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} + QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} + QA_PYROSCOPE_INSTANCE: ${{ secrets.QA_PYROSCOPE_INSTANCE }} + QA_PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} + QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} + GRAFANA_INTERNAL_TENANT_ID: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} + GRAFANA_INTERNAL_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} + GRAFANA_INTERNAL_HOST: ${{ secrets.GRAFANA_INTERNAL_HOST }} + GRAFANA_INTERNAL_URL_SHORTENER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} + LOKI_TENANT_ID: ${{ secrets.LOKI_TENANT_ID }} + LOKI_URL: ${{ secrets.LOKI_URL }} + LOKI_BASIC_AUTH: ${{ secrets.LOKI_BASIC_AUTH }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + AWS_REGION: ${{ secrets.QA_AWS_REGION }} + AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} + AWS_API_GW_HOST_GRAFANA: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} + SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} diff --git a/.github/workflows/integration-staging-tests.yml b/.github/workflows/integration-staging-tests.yml deleted file mode 100644 index d092b2bca1..0000000000 --- a/.github/workflows/integration-staging-tests.yml +++ /dev/null @@ -1,132 +0,0 @@ -# NEEDS ADJUSTING TO TOML CONFIG BEFORE USING!! -name: E2E Functions staging tests - -on: -# TODO: enable when env will be stable -# schedule: -# - cron: "0 0 * * *" - workflow_dispatch: - inputs: - network: - description: Blockchain network (testnet) - type: choice - default: "MUMBAI" - options: - - "MUMBAI" - test_type: - description: Test type - type: choice - default: "mumbai_functions_soak_test_real" - options: - - "mumbai_functions_soak_test_http" - - "mumbai_functions_stress_test_http" - - "mumbai_functions_soak_test_only_secrets" - - "mumbai_functions_stress_test_only_secrets" - - "mumbai_functions_soak_test_real" - - "mumbai_functions_stress_test_real" -# TODO: disabled, need GATI access -# - "gateway_secrets_set_soak_test" -# - "gateway_secrets_list_soak_test" - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - e2e-soak-test: - environment: sdlc - runs-on: ubuntu22.04-8cores-32GB - permissions: - contents: read - id-token: write - env: - LOKI_URL: ${{ secrets.LOKI_URL }} - LOKI_TOKEN: ${{ secrets.LOKI_TOKEN }} - SELECTED_NETWORKS: ${{ inputs.network }} - SELECTED_TEST: ${{ inputs.test_type }} - MUMBAI_URLS: ${{ secrets.FUNCTIONS_STAGING_MUMBAI_URLS }} - MUMBAI_KEYS: ${{ secrets.FUNCTIONS_STAGING_MUMBAI_KEYS }} - WASP_LOG_LEVEL: info - steps: - - name: Checkout code - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - fetch-depth: 0 - - name: Prepare Base64 TOML override - env: - PYROSCOPE_SERVER: ${{ secrets.QA_PYROSCOPE_INSTANCE }} - PYROSCOPE_ENVIRONMENT: ci-smoke-${{ matrix.product }}-sepolia - PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} - run: | - convert_to_toml_array() { - local IFS=',' - local input_array=($1) - local toml_array_format="[" - - for element in "${input_array[@]}"; do - toml_array_format+="\"$element\"," - done - - toml_array_format="${toml_array_format%,}]" - echo "$toml_array_format" - } - - if [ -n "$PYROSCOPE_SERVER" ]; then - pyroscope_enabled=true - else - pyroscope_enabled=false - fi - - cat << EOF > config.toml - [Common] - chainlink_node_funding=0.5 - - [ChainlinkImage] - image="$CHAINLINK_IMAGE" - version="${{ github.sha }}" - - [Pyroscope] - enabled=$pyroscope_enabled - server_url="$PYROSCOPE_SERVER" - environment="$PYROSCOPE_ENVIRONMENT" - key_secret="$PYROSCOPE_KEY" - - [Logging] - run_id="$RUN_ID" - - [Logging.LogStream] - log_targets=$log_targets - - [Logging.Loki] - tenant_id="$LOKI_TENANT_ID" - endpoint="$LOKI_URL" - basic_auth_secret="$LOKI_BASIC_AUTH" - - [Logging.Grafana] - base_url="$GRAFANA_URL" - dashboard_url="/d/ddf75041-1e39-42af-aa46-361fe4c36e9e/ci-e2e-tests-logs" - - [Network] - selected_networks=["sepolia"] - - [Network.RpcHttpUrls] - sepolia = $(convert_to_toml_array "$SEPOLIA_HTTP_URLS") - - [Network.RpcWsUrls] - sepolia = $(convert_to_toml_array "$SEPOLIA_URLS") - - [Network.WalletKeys] - sepolia = $(convert_to_toml_array "$EVM_KEYS") - EOF - - BASE64_CONFIG_OVERRIDE=$(cat config.toml | base64 -w 0) - echo ::add-mask::$BASE64_CONFIG_OVERRIDE - echo "BASE64_CONFIG_OVERRIDE=$BASE64_CONFIG_OVERRIDE" >> $GITHUB_ENV - - name: Run E2E soak tests - run: | - cd integration-tests/load/functions - if [[ $SELECTED_TEST == mumbai_functions* ]]; then - go test -v -timeout 6h -run TestFunctionsLoad/$SELECTED_TEST - elif [[ $SELECTED_TEST == gateway* ]]; then - go test -v -timeout 6h -run TestGatewayLoad/$SELECTED_TEST - fi \ No newline at end of file diff --git a/.github/workflows/integration-tests-publish.yml b/.github/workflows/integration-tests-publish.yml index 7a842fef6f..5f4768027e 100644 --- a/.github/workflows/integration-tests-publish.yml +++ b/.github/workflows/integration-tests-publish.yml @@ -39,7 +39,7 @@ jobs: run: | echo 'release_tag="${{ format('{0}.dkr.ecr.{1}.amazonaws.com/chainlink-ccip-tests:{2}', secrets.QA_AWS_ACCOUNT_NUMBER, secrets.QA_AWS_REGION, github.ref_name) }}"' >> $GITHUB_OUTPUT - name: Build Image - uses: ./.github/actions/build-test-image + uses: smartcontractkit/.github/actions/ctf-build-test-image@a5e4f4c8fbb8e15ab2ad131552eca6ac83c4f4b3 # ctf-build-test-image@0.1.0 with: other_tags: ${{steps.tags.outputs.release_tag}} QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} @@ -53,7 +53,7 @@ jobs: SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} with: channel-id: "#team-test-tooling-internal" - slack-message: ":x: :mild-panic-intensifies: Publish Integration Test Image failed: \n${{ format('https://github.com/{0}/actions/runs/{1}', github.repository, github.run_id) }}" + slack-message: ":x: :mild-panic-intensifies: Publish Integration Test Image failed: \n${{ format('https://github.com/{0}/actions/runs/{1}', github.repository, github.run_id) }}\nRepository: Chainlink\n${{ format('Notifying ', secrets.GUARDIAN_SLACK_NOTIFICATION_HANDLE)}}" build-chainlink-image: environment: integration # Only run this build for workflow_dispatch diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index bc59136870..8b56b88b70 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -66,7 +66,7 @@ jobs: echo "should-enforce=$SHOULD_ENFORCE" >> $GITHUB_OUTPUT - name: Enforce CTF Version if: steps.condition-check.outputs.should-enforce == 'true' - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/mod-version@fc3e0df622521019f50d772726d6bf8dc919dd38 # v2.3.19 + uses: smartcontractkit/.github/actions/ctf-check-mod-version@21b0189c5fdca0318617d259634b1a91e6d80262 # ctf-check-mod-version@0.0.0 with: go-project-path: ./integration-tests module-name: github.com/smartcontractkit/chainlink-testing-framework/lib @@ -88,15 +88,21 @@ jobs: id: changes with: filters: | - changes: + 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*/**' - name: Ignore Filter On Workflow Dispatch if: ${{ github.event_name == 'workflow_dispatch' }} id: ignore-filter @@ -113,10 +119,12 @@ jobs: this-job-name: Check Paths That Require Tests To Run continue-on-error: true outputs: - src: ${{ steps.ignore-filter.outputs.changes || steps.changes.outputs.changes }} + 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 }} - build-lint-integration-tests: - name: Build and Lint ${{ matrix.project.name }} + lint-integration-tests: + name: Lint ${{ matrix.project.name }} runs-on: ubuntu22.04-8cores-32GB # We don't directly merge dependabot PRs, so let's not waste the resources if: github.actor != 'dependabot[bot]' @@ -124,13 +132,13 @@ jobs: matrix: project: - name: integration-tests - id: e2e + id: e2e-tests path: ./integration-tests - cache-id: e2e + cache_id: e2e-tests - name: load id: load path: ./integration-tests/load - cache-id: load + cache_id: load steps: - name: Collect Metrics id: collect-gha-metrics @@ -140,7 +148,7 @@ jobs: org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: Build and Lint ${{ matrix.project.name }} + this-job-name: Lint ${{ matrix.project.name }} continue-on-error: true - name: Checkout the repo uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 @@ -148,17 +156,12 @@ jobs: repository: smartcontractkit/ccip ref: ${{ inputs.cl_ref }} - name: Setup Go - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/setup-go@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 + uses: smartcontractkit/.github/actions/ctf-setup-go@b0d756c57fcdbcff187e74166562a029fdd5d1b9 # ctf-setup-go@0.0.0 with: test_download_vendor_packages_command: cd ${{ matrix.project.path }} && go mod download go_mod_path: ${{ matrix.project.path }}/go.mod - cache_key_id: core-${{ matrix.project.cache-id }}-${{ env.MOD_CACHE_VERSION }} + cache_key_id: ${{ matrix.project.cache_id }} cache_restore_only: "true" - - name: Build Go - run: | - cd ${{ matrix.project.path }} - go build ./... - go test -run=^# ./... - name: Lint Go uses: golangci/golangci-lint-action@3cfe3a4abbb849e10058ce4af15d205b6da42804 # v4.0.0 with: @@ -190,7 +193,7 @@ jobs: needs: [changes, enforce-ctf-version] steps: - name: Collect Metrics - if: needs.changes.outputs.src == '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: @@ -215,7 +218,7 @@ jobs: aws-region: ${{ secrets.AWS_REGION }} set-git-config: "true" - name: Build Chainlink Image - if: needs.changes.outputs.src == '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 }} @@ -225,488 +228,222 @@ jobs: AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} dep_evm_sha: ${{ inputs.evm-ref }} - build-test-image: - if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'schedule' || contains(join(github.event.pull_request.labels.*.name, ' '), 'build-test-image') - environment: integration + run-core-e2e-tests-for-pr: + name: Run Core E2E Tests For PR permissions: + actions: read + checks: write + pull-requests: write id-token: write contents: read - name: Build Test Image - runs-on: ubuntu22.04-16cores-64GB - needs: [changes] - steps: - - name: Collect Metrics - if: needs.changes.outputs.src == 'true' || github.event_name == 'workflow_dispatch' - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: ${{ env.COLLECTION_ID }}-build-test-image - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: Build Test Image - continue-on-error: true - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - repository: smartcontractkit/ccip - ref: ${{ inputs.cl_ref || github.event.pull_request.head.sha || github.event.merge_group.head_sha }} - - name: Build Go Test Command - id: build-go-test-command - run: | - # if the matrix.product.run is set, use it for a different command - if [ "${{ matrix.product.run }}" != "" ]; then - echo "run_command=${{ matrix.product.run }} ./smoke/${{ matrix.product.file }}_test.go" >> "$GITHUB_OUTPUT" - else - echo "run_command=./smoke/${{ matrix.product.name }}_test.go" >> "$GITHUB_OUTPUT" - fi - - name: Setup GAP for Grafana - uses: smartcontractkit/.github/actions/setup-gap@d316f66b2990ea4daa479daa3de6fc92b00f863e # setup-gap@0.3.2 - id: setup-gap - with: - # aws inputs - aws-region: ${{ secrets.AWS_REGION }} - aws-role-arn: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} - api-gateway-host: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} - # other inputs - duplicate-authorization-header: "true" + needs: [build-chainlink, changes] + if: github.event_name == 'pull_request' && ( needs.changes.outputs.core_changes == 'true' || needs.changes.outputs.github_ci_changes == 'true') + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@aad83f232743646faa35f5ac03ee3829148d37ce + with: + workflow_name: Run Core E2E Tests For PR + chainlink_version: ${{ inputs.evm-ref || github.sha }} + chainlink_upgrade_version: ${{ github.sha }} + test_path: .github/e2e-tests.yml + test_trigger: PR E2E Core Tests + upload_cl_node_coverage_artifact: true + upload_cl_node_coverage_artifact_prefix: cl_node_coverage_data_ + enable_otel_traces_for_ocr2_plugins: ${{ contains(join(github.event.pull_request.labels.*.name, ' '), 'enable tracing') }} + secrets: + QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} + QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} + QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} + QA_PYROSCOPE_INSTANCE: ${{ secrets.QA_PYROSCOPE_INSTANCE }} + QA_PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} + QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} + GRAFANA_INTERNAL_TENANT_ID: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} + GRAFANA_INTERNAL_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} + GRAFANA_INTERNAL_HOST: ${{ secrets.GRAFANA_INTERNAL_HOST }} + GRAFANA_INTERNAL_URL_SHORTENER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} + LOKI_TENANT_ID: ${{ secrets.LOKI_TENANT_ID }} + LOKI_URL: ${{ secrets.LOKI_URL }} + LOKI_BASIC_AUTH: ${{ secrets.LOKI_BASIC_AUTH }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + AWS_REGION: ${{ secrets.QA_AWS_REGION }} + AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} + AWS_API_GW_HOST_GRAFANA: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} + SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} - eth-smoke-tests-matrix: - if: ${{ !contains(join(github.event.pull_request.labels.*.name, ' '), 'skip-smoke-tests') }} - environment: integration + run-core-e2e-tests-for-merge-queue: + name: Run Core E2E Tests For Merge Queue permissions: actions: read checks: write pull-requests: write id-token: write contents: read - needs: [build-chainlink, changes, build-lint-integration-tests] - env: - SELECTED_NETWORKS: SIMULATED - CHAINLINK_COMMIT_SHA: ${{ inputs.evm-ref || github.sha }} - CHAINLINK_ENV_USER: ${{ github.actor }} - TEST_LOG_LEVEL: debug - strategy: - fail-fast: false - matrix: - product: -# LM Smoke Test is disabled since project is paused -# - name: ccip-lm-smoke -# nodes: 1 -# os: ubuntu-latest -# file: lm -# dir: ccip-tests/smoke -# run: -run ^TestLmBasic$ - - name: ccip-smoke - nodes: 1 - os: ubuntu-latest - file: ccip - dir: ccip-tests/smoke - run: -run ^TestSmokeCCIPForBidirectionalLane$ - - name: ccip-smoke-1.4-pools - nodes: 1 - os: ubuntu-latest - file: ccip - dir: ccip-tests/smoke - run: -run ^TestSmokeCCIPForBidirectionalLane$ - config_path: ./integration-tests/ccip-tests/testconfig/tomls/contract-version1.4.toml - - name: ccip-smoke-usdc - nodes: 1 - os: ubuntu-latest - file: ccip - dir: ccip-tests/smoke - run: -run ^TestSmokeCCIPForBidirectionalLane$ - config_path: ./integration-tests/ccip-tests/testconfig/tomls/usdc_mock_deployment.toml - - name: ccip-smoke-db-compatibility - nodes: 1 - os: ubuntu-latest - file: ccip - dir: ccip-tests/smoke - run: -run ^TestSmokeCCIPForBidirectionalLane$ - config_path: ./integration-tests/ccip-tests/testconfig/tomls/db-compatibility.toml - - name: ccip-smoke-rate-limit - nodes: 1 - dir: ccip-tests/smoke - os: ubuntu-latest - file: ccip - run: -run ^TestSmokeCCIPRateLimit$ - - name: ccip-smoke-rate-limit - nodes: 1 - dir: ccip-tests/smoke - os: ubuntu-latest - file: ccip - run: -run ^TestSmokeCCIPTokenPoolRateLimits$ - - name: ccip-smoke-multicall - nodes: 1 - dir: ccip-tests/smoke - os: ubuntu-latest - file: ccip - run: -run ^TestSmokeCCIPMulticall$ - - name: ccip-smoke-manual-exec - nodes: 1 - dir: ccip-tests/smoke - os: ubuntu-latest - file: ccip - run: -run ^TestSmokeCCIPManuallyExecuteAfterExecutionFailingDueToInsufficientGas$ - - name: ccip-smoke-on-ramp-limits - nodes: 1 - dir: ccip-tests/smoke - os: ubuntu-latest - file: ccip - run: -run ^TestSmokeCCIPOnRampLimits$ - - name: ccip-smoke-off-ramp-capacity - nodes: 1 - dir: ccip-tests/smoke - os: ubuntu-latest - file: ccip - run: -run ^TestSmokeCCIPOffRampCapacityLimit$ - - name: ccip-smoke-off-ramp-agg-rate-limit - nodes: 1 - dir: ccip-tests/smoke - os: ubuntu-latest - file: ccip - run: -run ^TestSmokeCCIPOffRampAggRateLimit$ - - name: ccip-smoke-leader-lane - nodes: 15 - dir: ccip-tests/smoke - os: ubuntu-latest - file: ccip - run: -run ^TestSmokeCCIPForBidirectionalLane$ - config_path: ./integration-tests/ccip-tests/testconfig/tomls/leader-lane.toml - - name: ccip-smoke-reorg-bidirectional - nodes: 1 - dir: ccip-tests/smoke - os: ubuntu-latest - file: ccip - run: -run ^TestSmokeCCIPReorgBelowFinality$ -v - config_path: ./integration-tests/ccip-tests/testconfig/tomls/ccip-reorg.toml - - name: ccip-smoke-reorg-below-finality - nodes: 1 - dir: ccip-tests/smoke - os: ubuntu-latest - file: ccip - run: -run ^TestSmokeCCIPReorgAboveFinalityAtDestination$ -v - config_path: ./integration-tests/ccip-tests/testconfig/tomls/ccip-reorg.toml - - name: ccip-smoke-reorg-above-finality - nodes: 1 - dir: ccip-tests/smoke - os: ubuntu-latest - file: ccip - run: -run ^TestSmokeCCIPReorgAboveFinalityAtSource$ -v - config_path: ./integration-tests/ccip-tests/testconfig/tomls/ccip-reorg.toml - - name: runlog - id: runlog - nodes: 2 - os: ubuntu-latest - pyroscope_env: "ci-smoke-runlog-evm-simulated" - - name: cron - id: cron - nodes: 2 - os: ubuntu-latest - pyroscope_env: "ci-smoke-cron-evm-simulated" - - name: flux - id: flux - nodes: 1 - os: ubuntu-latest - pyroscope_env: "ci-smoke-flux-evm-simulated" - - name: ocr - id: ocr - nodes: 2 - os: ubuntu-latest - file: ocr - pyroscope_env: ci-smoke-ocr-evm-simulated - - name: reorg_above_finality - id: reorg_above_finality - nodes: 1 - os: ubuntu-latest - file: reorg_above_finality - pyroscope_env: ci-smoke-reorg-above-finality-evm-simulated - - name: ocr2 - id: ocr2 - nodes: 6 - os: ubuntu22.04-16cores-64GB - file: ocr2 - pyroscope_env: ci-smoke-ocr2-evm-simulated - - name: ocr2 - id: ocr2-plugins - nodes: 6 - os: ubuntu22.04-16cores-64GB - pyroscope_env: ci-smoke-ocr2-plugins-evm-simulated - tag_suffix: "-plugins" - - name: vrf - id: vrf - nodes: 2 - os: ubuntu-latest - pyroscope_env: ci-smoke-vrf-evm-simulated - - name: vrfv2 - id: vrfv2 - nodes: 6 - os: ubuntu-latest - pyroscope_env: ci-smoke-vrf2-evm-simulated - - name: vrfv2plus - id: vrfv2plus - nodes: 9 - os: ubuntu-latest - pyroscope_env: ci-smoke-vrf2plus-evm-simulated - - name: forwarder_ocr - id: forwarder_ocr - nodes: 2 - os: ubuntu-latest - pyroscope_env: ci-smoke-forwarder-ocr-evm-simulated - - name: forwarders_ocr2 - id: forwarders_ocr2 - nodes: 2 - os: ubuntu-latest - pyroscope_env: ci-smoke-forwarder-ocr-evm-simulated - runs-on: ${{ matrix.product.os }} - name: ETH Smoke Tests ${{ matrix.product.name }}${{ matrix.product.tag_suffix }} - steps: - # Handy for debugging resource usage - # - name: Collect Workflow Telemetry - # uses: catchpoint/workflow-telemetry-action@94c3c3d9567a0205de6da68a76c428ce4e769af1 # v2.0.0 - - name: Collect Metrics - if: needs.changes.outputs.src == 'true' || github.event_name == 'workflow_dispatch' - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: ${{ env.COLLECTION_ID }}-matrix-${{ matrix.product.id }} - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: ETH Smoke Tests ${{ matrix.product.name }}${{ matrix.product.tag_suffix }} - test-results-file: '{"testType":"go","filePath":"/tmp/gotest.log"}' - continue-on-error: true - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - repository: smartcontractkit/ccip - ref: ${{ inputs.cl_ref || github.event.pull_request.head.sha || github.event.merge_group.head_sha }} - - name: Build Go Test Command - id: build-go-test-command - run: | - # if dir is provided use it, otherwise use the smoke dir - if [ "${{ matrix.product.dir }}" != "" ]; then - dir=${{ matrix.product.dir }} - else - dir=smoke - fi - # if the matrix.product.run is set, use it for a different command - if [ "${{ matrix.product.run }}" != "" ]; then - echo "run_command=${{ matrix.product.run }} ./${dir}/${{ matrix.product.file }}_test.go" >> "$GITHUB_OUTPUT" - else - echo "run_command=./${dir}/${{ matrix.product.name }}_test.go" >> "$GITHUB_OUTPUT" - fi - - name: Check for "enable tracing" label - id: check-label - run: | - label=$(jq -r '.pull_request.labels[]?.name // empty' "$GITHUB_EVENT_PATH") - - if [[ -n "$label" ]]; then - if [[ "$label" == "enable tracing" ]]; then - echo "Enable tracing label found." - echo "trace=true" >> $GITHUB_OUTPUT - else - echo "Enable tracing label not found." - echo "trace=false" >> $GITHUB_OUTPUT - fi - else - echo "No labels present or labels are null." - echo "trace=false" >> $GITHUB_OUTPUT - fi - - - name: Setup Grafana and OpenTelemetry - id: docker-setup - if: steps.check-label.outputs.trace == 'true' && matrix.product.name == 'ocr2' && matrix.product.tag_suffix == '-plugins' - run: | - # Create network - docker network create --driver bridge tracing - - # Make trace directory - cd integration-tests/smoke/ - mkdir ./traces - chmod -R 777 ./traces - - # Switch directory - cd ../../.github/tracing - - # Create a Docker volume for traces - # docker volume create otel-traces - - # Start OpenTelemetry Collector - # Note the user must be set to the same user as the runner for the trace data to be accessible - docker run -d --network=tracing --name=otel-collector \ - -v $PWD/otel-collector-ci.yaml:/etc/otel-collector.yaml \ - -v $PWD/../../integration-tests/smoke/traces:/tracing \ - --user "$(id -u):$(id -g)" \ - -p 4317:4317 otel/opentelemetry-collector:0.88.0 --config=/etc/otel-collector.yaml + needs: [build-chainlink, changes] + if: github.event_name == 'merge_group' && ( needs.changes.outputs.core_changes == 'true' || needs.changes.outputs.github_ci_changes == 'true') + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@aad83f232743646faa35f5ac03ee3829148d37ce + with: + workflow_name: Run Core E2E Tests For Merge Queue + chainlink_version: ${{ inputs.evm-ref || github.sha }} + chainlink_upgrade_version: ${{ github.sha }} + test_path: .github/e2e-tests.yml + test_trigger: Merge Queue E2E Core Tests + upload_cl_node_coverage_artifact: true + upload_cl_node_coverage_artifact_prefix: cl_node_coverage_data_ + enable_otel_traces_for_ocr2_plugins: ${{ contains(join(github.event.pull_request.labels.*.name, ' '), 'enable tracing') }} + # Notify Test Tooling team in slack when merge queue tests fail + slack_notification_after_tests: on_failure + slack_notification_after_tests_channel_id: "#team-test-tooling-internal" + slack_notification_after_tests_name: Core E2E Tests In Merge Queue + secrets: + QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} + QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} + QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} + QA_PYROSCOPE_INSTANCE: ${{ secrets.QA_PYROSCOPE_INSTANCE }} + QA_PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} + QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} + GRAFANA_INTERNAL_TENANT_ID: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} + GRAFANA_INTERNAL_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} + GRAFANA_INTERNAL_HOST: ${{ secrets.GRAFANA_INTERNAL_HOST }} + GRAFANA_INTERNAL_URL_SHORTENER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} + LOKI_TENANT_ID: ${{ secrets.LOKI_TENANT_ID }} + LOKI_URL: ${{ secrets.LOKI_URL }} + LOKI_BASIC_AUTH: ${{ secrets.LOKI_BASIC_AUTH }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + AWS_REGION: ${{ secrets.QA_AWS_REGION }} + AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} + AWS_API_GW_HOST_GRAFANA: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} + SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} + + run-ccip-e2e-tests-for-pr: + name: Run CCIP E2E Tests For PR + permissions: + actions: read + checks: write + pull-requests: write + id-token: write + contents: read + needs: [build-chainlink, changes] + if: github.event_name == 'pull_request' && (needs.changes.outputs.ccip_changes == 'true' || needs.changes.outputs.github_ci_changes == 'true') + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@aad83f232743646faa35f5ac03ee3829148d37ce + with: + workflow_name: Run CCIP E2E Tests For PR + chainlink_version: ${{ inputs.evm-ref || github.sha }} + chainlink_upgrade_version: ${{ github.sha }} + test_path: .github/e2e-tests.yml + test_trigger: PR E2E CCIP Tests + upload_cl_node_coverage_artifact: true + upload_cl_node_coverage_artifact_prefix: cl_node_coverage_data_ + enable_otel_traces_for_ocr2_plugins: ${{ contains(join(github.event.pull_request.labels.*.name, ' '), 'enable tracing') }} + secrets: + QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} + QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} + QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} + QA_PYROSCOPE_INSTANCE: ${{ secrets.QA_PYROSCOPE_INSTANCE }} + QA_PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} + QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} + GRAFANA_INTERNAL_TENANT_ID: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} + GRAFANA_INTERNAL_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} + GRAFANA_INTERNAL_HOST: ${{ secrets.GRAFANA_INTERNAL_HOST }} + GRAFANA_INTERNAL_URL_SHORTENER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} + LOKI_TENANT_ID: ${{ secrets.LOKI_TENANT_ID }} + LOKI_URL: ${{ secrets.LOKI_URL }} + LOKI_BASIC_AUTH: ${{ secrets.LOKI_BASIC_AUTH }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + AWS_REGION: ${{ secrets.QA_AWS_REGION }} + AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} + AWS_API_GW_HOST_GRAFANA: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} + SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} + + run-ccip-e2e-tests-for-merge-queue: + name: Run CCIP E2E Tests For Merge Queue + permissions: + actions: read + checks: write + pull-requests: write + id-token: write + contents: read + needs: [build-chainlink, changes] + if: github.event_name == 'merge_group' && (needs.changes.outputs.ccip_changes == 'true' || needs.changes.outputs.github_ci_changes == 'true') + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@aad83f232743646faa35f5ac03ee3829148d37ce + with: + workflow_name: Run CCIP E2E Tests For Merge Queue + chainlink_version: ${{ inputs.evm-ref || github.sha }} + chainlink_upgrade_version: ${{ github.sha }} + test_path: .github/e2e-tests.yml + test_trigger: Merge Queue E2E CCIP Tests + upload_cl_node_coverage_artifact: true + upload_cl_node_coverage_artifact_prefix: cl_node_coverage_data_ + enable_otel_traces_for_ocr2_plugins: ${{ contains(join(github.event.pull_request.labels.*.name, ' '), 'enable tracing') }} + secrets: + QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} + QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} + QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} + QA_PYROSCOPE_INSTANCE: ${{ secrets.QA_PYROSCOPE_INSTANCE }} + QA_PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} + QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} + GRAFANA_INTERNAL_TENANT_ID: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} + GRAFANA_INTERNAL_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} + GRAFANA_INTERNAL_HOST: ${{ secrets.GRAFANA_INTERNAL_HOST }} + GRAFANA_INTERNAL_URL_SHORTENER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} + LOKI_TENANT_ID: ${{ secrets.LOKI_TENANT_ID }} + LOKI_URL: ${{ secrets.LOKI_URL }} + LOKI_BASIC_AUTH: ${{ secrets.LOKI_BASIC_AUTH }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + AWS_REGION: ${{ secrets.QA_AWS_REGION }} + AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} + AWS_API_GW_HOST_GRAFANA: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} + SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} - - name: Locate Docker Volume - id: locate-volume - if: false + check-e2e-test-results: + if: always() + name: ETH Smoke Tests + runs-on: ubuntu-latest + needs: [lint-integration-tests, run-core-e2e-tests-for-pr, run-ccip-e2e-tests-for-pr, run-core-e2e-tests-for-merge-queue, run-ccip-e2e-tests-for-merge-queue] + steps: + - name: Check Core test results + id: check_core_results run: | - echo "VOLUME_PATH=$(docker volume inspect --format '{{ .Mountpoint }}' otel-traces)" >> $GITHUB_OUTPUT + results='${{ needs.run-core-e2e-tests-for-pr.outputs.test_results }}' + echo "Core test results:" + echo "$results" | jq . + + node_migration_tests_failed=$(echo $results | jq '[.[] | select(.id == "integration-tests/migration/upgrade_version_test.go:*" ) | select(.result != "success")] | length > 0') + echo "node_migration_tests_failed=$node_migration_tests_failed" >> $GITHUB_OUTPUT - - name: Show Otel-Collector Logs - if: steps.check-label.outputs.trace == 'true' && matrix.product.name == 'ocr2' && matrix.product.tag_suffix == '-plugins' - run: | - docker logs otel-collector - - name: Set Override Config - id: set_override_config + - name: Check CCIP test results + id: check_ccip_results run: | - # if the matrix.product.config_path is set, use it as the override config - if [ "${{ matrix.product.config_path }}" != "" ]; then - echo "base_64_override=$(base64 -w 0 -i ${{ matrix.product.config_path }})" >> "$GITHUB_OUTPUT" + if [[ '${{ needs.run-ccip-e2e-tests-for-pr.result }}' != 'skipped' ]]; then + results='${{ needs.run-ccip-e2e-tests-for-pr.outputs.test_results }}' + echo "CCIP test results:" + echo "$results" | jq . + else + echo "CCIP tests were skipped." fi - - name: Setup GAP for Grafana - uses: smartcontractkit/.github/actions/setup-gap@d316f66b2990ea4daa479daa3de6fc92b00f863e # setup-gap@0.3.2 - id: setup-gap - with: - # aws inputs - aws-region: ${{ secrets.AWS_REGION }} - aws-role-arn: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} - api-gateway-host: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} - # other inputs - duplicate-authorization-header: "true" - # metrics inputs - metrics-job-name: "grafana" - gc-host: ${{ secrets.GRAFANA_INTERNAL_HOST }} - gc-basic-auth: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} - gc-org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - - name: Prepare Base64 CCIP TOML secrets - uses: ./.github/actions/setup-create-base64-config-ccip - id: setup_create_base64_config_ccip - with: - runId: ${{ github.run_id }} - pyroscopeServer: ${{ matrix.product.pyroscope_env == '' && '' || !startsWith(github.ref, 'refs/tags/') && '' || secrets.QA_PYROSCOPE_INSTANCE }} # Avoid sending blank envs https://github.com/orgs/community/discussions/25725 - pyroscopeEnvironment: ${{ matrix.product.pyroscope_env }} - pyroscopeKey: ${{ secrets.QA_PYROSCOPE_KEY }} - testLogCollect: ${{ vars.TEST_LOG_COLLECT }} - selectedNetworks: SIMULATED_1,SIMULATED_2 - chainlinkVersion: ${{ inputs.evm-ref || github.sha }} - logstreamLogTargets: ${{ vars.LOGSTREAM_LOG_TARGETS }} - ## Run this step when changes that require tests to be run are made - - name: Run Tests - if: needs.changes.outputs.src == 'true' || github.event_name == 'workflow_dispatch' - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@aa8eea635029ab8d95abd3c206f56dae1e22e623 # v2.3.28 + - name: Send slack notification for failed migration tests + if: steps.check_core_results.outputs.node_migration_tests_failed == 'true' && github.event_name != 'workflow_dispatch' + uses: slackapi/slack-github-action@6c661ce58804a1a20f6dc5fbee7f0381b469e001 # v1.25.0 env: - BASE64_CCIP_CONFIG_OVERRIDE: ${{ steps.setup_create_base64_config_ccip.outputs.base64_config }},${{ steps.set_override_config.outputs.base_64_override }} - TEST_BASE64_CCIP_CONFIG_OVERRIDE: ${{ steps.setup_create_base64_config_ccip.outputs.base64_config }},${{ steps.set_override_config.outputs.base_64_override }} - DEBUG_RESTY: false - with: - test_command_to_run: cd ./integration-tests && go test -timeout 30m -count=1 -json -test.parallel=${{ matrix.product.nodes }} ${{ steps.build-go-test-command.outputs.run_command }} 2>&1 | tee /tmp/gotest.log | gotestloghelper -ci -singlepackage -hidepassingtests=false -hidepassinglogs - test_download_vendor_packages_command: cd ./integration-tests && go mod download - test_config_chainlink_version: ${{ inputs.evm-ref || github.sha }} - test_config_selected_networks: ${{ env.SELECTED_NETWORKS }} - test_config_logging_run_id: ${{ github.run_id }} - test_config_logstream_log_targets: ${{ vars.LOGSTREAM_LOG_TARGETS }} - test_config_test_log_collect: ${{ vars.TEST_LOG_COLLECT }} - cl_repo: ${{ env.CHAINLINK_IMAGE }} - cl_image_tag: ${{ inputs.evm-ref || github.sha }}${{ matrix.product.tag_suffix }} - aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - artifacts_name: ${{ matrix.product.name }}${{ matrix.product.tag_suffix }}-test-logs - artifacts_location: | - ./integration-tests/smoke/logs/ - ./integration-tests/ccip-tests/smoke/logs/* - /tmp/gotest.log - publish_check_name: ${{ matrix.product.name }} - token: ${{ secrets.GITHUB_TOKEN }} - go_mod_path: ./integration-tests/go.mod - cache_key_id: core-e2e-${{ env.MOD_CACHE_VERSION }} - cache_restore_only: "true" - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: "" - should_tidy: "false" - go_coverage_src_dir: /var/tmp/go-coverage - go_coverage_dest_dir: ${{ github.workspace }}/.covdata - DEFAULT_CHAINLINK_IMAGE: ${{ env.CHAINLINK_IMAGE }} - DEFAULT_LOKI_TENANT_ID: ${{ secrets.LOKI_TENANT_ID }} - DEFAULT_LOKI_ENDPOINT: ${{ secrets.LOKI_URL }} - DEFAULT_LOKI_BASIC_AUTH: ${{ secrets.LOKI_BASIC_AUTH }} - DEFAULT_GRAFANA_BASE_URL: "http://localhost:8080/primary" - DEFAULT_GRAFANA_DASHBOARD_URL: "/d/ddf75041-1e39-42af-aa46-361fe4c36e9e/ci-e2e-tests-logs" - DEFAULT_GRAFANA_BEARER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} - DEFAULT_PYROSCOPE_SERVER_URL: ${{ matrix.product.pyroscope_env == '' && '' || !startsWith(github.ref, 'refs/tags/') && '' || secrets.QA_PYROSCOPE_INSTANCE }} # Avoid sending blank envs https://github.com/orgs/community/discussions/25725 - DEFAULT_PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} - DEFAULT_PYROSCOPE_ENVIRONMENT: ${{ matrix.product.pyroscope_env }} - DEFAULT_PYROSCOPE_ENABLED: ${{ matrix.product.pyroscope_env == '' || !startsWith(github.ref, 'refs/tags/') && 'false' || 'true' }} - - - name: Upload Coverage Data - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 - timeout-minutes: 2 - continue-on-error: true - with: - name: cl-node-coverage-data-${{ matrix.product.name }}-${{ matrix.product.tag_suffix }} - path: .covdata - retention-days: 1 - - # Run this step when changes that do not need the test to run are made - - name: Run Setup - if: needs.changes.outputs.src == 'false' - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/setup-run-tests-environment@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 + SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} with: - test_download_vendor_packages_command: cd ./integration-tests && go mod download - go_mod_path: ./integration-tests/go.mod - cache_key_id: core-e2e-${{ env.MOD_CACHE_VERSION }} - cache_restore_only: "true" - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: "" - should_tidy: "false" - - - name: Show Otel-Collector Logs - if: steps.check-label.outputs.trace == 'true' && matrix.product.name == 'ocr2' && matrix.product.tag_suffix == '-plugins' - run: | - docker logs otel-collector + channel-id: "#team-test-tooling-internal" + slack-message: ":x: :mild-panic-intensifies: Node Migration Tests Failed: \n${{ format('https://github.com/{0}/actions/runs/{1}', github.repository, github.run_id) }}\n${{ format('Notifying ', secrets.GUARDIAN_SLACK_NOTIFICATION_HANDLE) }}" - - name: Permissions on traces - if: steps.check-label.outputs.trace == 'true' && matrix.product.name == 'ocr2' && matrix.product.tag_suffix == '-plugins' - run: | - ls -l ./integration-tests/smoke/traces - - - name: Upload Trace Data - if: steps.check-label.outputs.trace == 'true' && matrix.product.name == 'ocr2' && matrix.product.tag_suffix == '-plugins' - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 - with: - name: trace-data - path: ./integration-tests/smoke/traces/trace-data.json + - name: Fail the job if core tests in PR not successful + if: always() && needs.run-core-e2e-tests-for-pr.result == 'failure' + run: exit 1 - - name: Print failed test summary - if: always() - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/show-test-summary@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - test_directories: ./integration-tests/smoke/,./integration-tests/ccip-tests/smoke/ + - name: Fail the job if core tests in merge queue not successful + if: always() && needs.run-core-e2e-tests-for-merge-queue.result == 'failure' + run: exit 1 - ### Used to check the required checks box when the matrix completes - eth-smoke-tests: - if: always() - runs-on: ubuntu-latest - name: ETH Smoke Tests - needs: [eth-smoke-tests-matrix] - steps: - - name: Check smoke test matrix status - if: needs.eth-smoke-tests-matrix.result != 'success' - run: | - echo "${{ needs.eth-smoke-tests-matrix.result }}" - exit 1 - - name: Collect Metrics - if: always() - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: ${{ env.COLLECTION_ID }}-matrix-results - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: ETH Smoke Tests - matrix-aggregator-status: ${{ needs.eth-smoke-tests-matrix.result }} - continue-on-error: true + - name: Fail the job if lint not successful + if: always() && needs.lint-integration-tests.result == 'failure' + run: exit 1 cleanup: name: Clean up integration environment deployments if: always() - needs: [eth-smoke-tests] + needs: [run-core-e2e-tests-for-pr, run-ccip-e2e-tests-for-pr, run-core-e2e-tests-for-merge-queue, run-ccip-e2e-tests-for-merge-queue] runs-on: ubuntu-latest steps: - name: Checkout repo @@ -735,10 +472,10 @@ jobs: this-job-name: Clean up integration environment deployments continue-on-error: true - show-coverage: + show-chainlink-node-coverage: name: Show Chainlink Node Go Coverage if: always() - needs: [cleanup] + needs: [run-core-e2e-tests-for-pr, run-ccip-e2e-tests-for-pr, run-core-e2e-tests-for-merge-queue, run-ccip-e2e-tests-for-merge-queue] runs-on: ubuntu-latest steps: - name: Checkout the repo @@ -749,182 +486,8 @@ jobs: - name: Download All Artifacts uses: actions/download-artifact@9c19ed7fe5d278cd354c7dfd5d3b88589c7e2395 # v4.1.6 with: - path: cl-node-coverage-data - pattern: cl-node-coverage-data-* + path: cl_node_coverage_data + pattern: cl_node_coverage_data_* merge-multiple: true - name: Show Coverage - run: go run ./integration-tests/scripts/show_coverage.go "${{ github.workspace }}/cl-node-coverage-data/*/merged" - - # Run the setup if the matrix finishes but this time save the cache if we have a cache hit miss - # this will also only run if both of the matrix jobs pass - eth-smoke-go-mod-cache: - environment: integration - needs: [eth-smoke-tests] - runs-on: ubuntu-latest - name: ETH Smoke Tests Go Mod Cache - continue-on-error: true - steps: - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - repository: smartcontractkit/ccip - ref: ${{ inputs.cl_ref || github.event.pull_request.head.sha || github.event.merge_group.head_sha }} - - name: Run Setup - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/setup-go@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - test_download_vendor_packages_command: | - cd ./integration-tests - go mod download - # force download of test dependencies - go test -run=NonExistentTest ./smoke/... || echo "ignore expected test failure" - go_mod_path: ./integration-tests/go.mod - cache_key_id: core-e2e-${{ env.MOD_CACHE_VERSION }} - cache_restore_only: "false" - - ### Migration tests - node-migration-tests: - name: Version Migration Tests - environment: integration - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - runs-on: ubuntu-latest - needs: [build-chainlink, changes] - # Only run migration tests on new tags - if: startsWith(github.ref, 'refs/tags/') - env: - SELECTED_NETWORKS: SIMULATED,SIMULATED_1,SIMULATED_2 - CHAINLINK_COMMIT_SHA: ${{ inputs.evm-ref || github.sha }} - CHAINLINK_ENV_USER: ${{ github.actor }} - CHAINLINK_IMAGE: public.ecr.aws/w0i8p0z9/chainlink-ccip - UPGRADE_VERSION: ${{ inputs.evm-ref || github.sha }} - UPGRADE_IMAGE: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink - TEST_LOG_LEVEL: debug - TEST_SUITE: migration - steps: - - name: Collect Metrics - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: ${{ env.COLLECTION_ID }}-migration-tests - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: Version Migration Tests - test-results-file: '{"testType":"go","filePath":"/tmp/gotest.log"}' - continue-on-error: true - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - repository: smartcontractkit/ccip - ref: ${{ inputs.cl_ref || github.event.pull_request.head.sha || github.event.merge_group.head_sha }} - - name: Get Latest Version - id: get_latest_version - run: | - untrimmed_ver=$(curl --header "Authorization: token ${{ secrets.GITHUB_TOKEN }}" --request GET https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r .name) - latest_version="${untrimmed_ver:1}" - # Check if latest_version is empty - if [ -z "$latest_version" ]; then - echo "Error: The latest_version is empty. The migration tests need a verison to run." - exit 1 - fi - # CCIP uses the `-release` suffix for their versions - echo "latest_version=${latest_version}-release" >> "$GITHUB_OUTPUT" - - name: Name Versions - run: | - echo "Running migration tests from version '${{ steps.get_latest_version.outputs.latest_version }}' to: '${{ inputs.evm-ref || github.sha }}'" - - name: Prepare Base64 TOML override - uses: ./.github/actions/setup-create-base64-upgrade-config - with: - selectedNetworks: ${{ env.SELECTED_NETWORKS }} - chainlinkVersion: ${{ steps.get_latest_version.outputs.latest_version }} - upgradeVersion: ${{ env.UPGRADE_VERSION }} - runId: ${{ github.run_id }} - testLogCollect: ${{ vars.TEST_LOG_COLLECT }} - logstreamLogTargets: ${{ vars.LOGSTREAM_LOG_TARGETS }} - - name: Run Migration Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@aa8eea635029ab8d95abd3c206f56dae1e22e623 # v2.3.28 - with: - test_command_to_run: cd ./integration-tests && go test -timeout 20m -count=1 -json ./migration 2>&1 | tee /tmp/gotest.log | gotestloghelper -ci -singlepackage -hidepassingtests=false -hidepassinglogs - test_download_vendor_packages_command: cd ./integration-tests && go mod download - test_config_override_base64: ${{ env.BASE64_CONFIG_OVERRIDE }} - cl_repo: ${{ env.CHAINLINK_IMAGE }} - cl_image_tag: ${{ steps.get_latest_version.outputs.latest_version }} - aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - artifacts_name: node-migration-test-logs - artifacts_location: | - ./integration-tests/migration/logs - /tmp/gotest.log - publish_check_name: Node Migration Test Results - token: ${{ secrets.GITHUB_TOKEN }} - go_mod_path: ./integration-tests/go.mod - cache_key_id: core-e2e-${{ env.MOD_CACHE_VERSION }} - cache_restore_only: "true" - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: "" - go_coverage_src_dir: /var/tmp/go-coverage - go_coverage_dest_dir: ${{ github.workspace }}/.covdata - should_tidy: "false" - DEFAULT_CHAINLINK_IMAGE: ${{ env.CHAINLINK_IMAGE }} - DEFAULT_CHAINLINK_UPGRADE_IMAGE: ${{ env.UPGRADE_IMAGE }} - DEFAULT_LOKI_TENANT_ID: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - DEFAULT_LOKI_ENDPOINT: https://${{ secrets.GRAFANA_INTERNAL_HOST }}/loki/api/v1/push - DEFAULT_LOKI_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - DEFAULT_GRAFANA_BASE_URL: "http://localhost:8080/primary" - DEFAULT_GRAFANA_DASHBOARD_URL: "/d/ddf75041-1e39-42af-aa46-361fe4c36e9e/ci-e2e-tests-logs" - DEFAULT_GRAFANA_BEARER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} - - - name: Upload Coverage Data - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 - timeout-minutes: 2 - continue-on-error: true - with: - name: cl-node-coverage-data-migration-tests - path: .covdata - retention-days: 1 - - name: Notify Slack - if: failure() && github.event_name != 'workflow_dispatch' - uses: slackapi/slack-github-action@6c661ce58804a1a20f6dc5fbee7f0381b469e001 # v1.25.0 - env: - SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} - with: - channel-id: "C05QQ8Z342V" - slack-message: "CCIP Node Migration Tests Failed :x: \n${{ format('https://github.com/{0}/actions/runs/{1}', github.repository, github.run_id) }}" - payload: | - { - "attachments": [ - { - "color": "${{ contains(join(needs.*.result, ','), 'failure') && '#C62828' || '#2E7D32' }}", - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "CCIP Version Migration Test Results ${{ contains(join(needs.*.result, ','), 'failure') && ':x:' || ':white_check_mark:'}}", - "emoji": true - } - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "${{ contains(join(needs.*.result, ','), 'failure') && 'Some tests failed! Notifying ' || 'All Good!' }}" - } - }, - { - "type": "divider" - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "<${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ github.ref_name }}|${{ github.ref_name }}> | <${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}|${{ github.sha }}> | <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run>" - } - } - ] - } - ] - } + run: go run ./integration-tests/scripts/show_coverage.go "${{ github.workspace }}/cl_node_coverage_data/*/merged" diff --git a/.github/workflows/live-testnet-tests.yml b/.github/workflows/live-testnet-tests.yml deleted file mode 100644 index a7eaa19f7f..0000000000 --- a/.github/workflows/live-testnet-tests.yml +++ /dev/null @@ -1,1119 +0,0 @@ -# *** -# This workflow is a monstrosity of copy-paste, and that's to increase legibility in reporting and running, so the code be damned. -# I suspect this can be cleaned up significantly with some clever trickery of the GitHub actions matrices, but I am not that clever. -# We want each chain to run in parallel, but each test within the chain needs to be able to run sequentially -# (we're trying to eliminate this as a requirement, should make it a lot easier). -# Each chain can have a variety of tests to run. -# We also want reporting to be clear in the start-slack-thread and post-test-results-to-slack jobs. -# Funding address: 0xC1107e57082945E28d3202A81B1520DEA3AE6AEC -# *** - -name: Live Testnet Tests -on: - # Disable refular runs for now until we can fix some test client flakiness and improve stability - # schedule: - # - cron: "0 5 * * *" # Run every night at midnight EST - # push: - # tags: - # - "*" - workflow_dispatch: - inputs: - slack_user_id: - description: "The Slack member ID to notify" - required: true - type: string - network: - description: "The network to run tests on" - required: true - type: choice - options: - - "All" - - "Sepolia" - - "Optimism Sepolia" - - "Arbitrum Sepolia" - - "Base Sepolia" - - "Polygon Mumbai" - - "Avalanche Fuji" - - "Fantom Testnet" - - "Celo Alfajores" - - "Linea Goerli" - - "BSC Testnet" - -env: - CHAINLINK_IMAGE: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink - INTERNAL_DOCKER_REPO: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com - MOD_CACHE_VERSION: 2 - CHAINLINK_NODE_FUNDING: .5 - PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} - LOKI_TENANT_ID: ${{ vars.LOKI_TENANT_ID }} - LOKI_URL: ${{ secrets.LOKI_URL }} - LOKI_BASIC_AUTH: ${{ secrets.LOKI_BASIC_AUTH }} - LOGSTREAM_LOG_TARGETS: loki - GRAFANA_URL: ${{ vars.GRAFANA_URL }} - RUN_ID: ${{ github.run_id }} - - CHAINLINK_COMMIT_SHA: ${{ github.sha }} - CHAINLINK_ENV_USER: ${{ github.actor }} - TEST_LOG_LEVEL: debug - -jobs: - - # Build Test Dependencies - - build-chainlink: - environment: integration - permissions: - id-token: write - contents: read - name: Build Chainlink Image - runs-on: ubuntu-latest - steps: - - name: Collect Metrics - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: live-testnet-build-chainlink - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: Build Chainlink Image - continue-on-error: true - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - ref: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} - - name: Build Chainlink Image - uses: ./.github/actions/build-chainlink-image - with: - tag_suffix: "" - dockerfile: core/chainlink.Dockerfile - git_commit_sha: ${{ github.sha }} - AWS_REGION: ${{ secrets.QA_AWS_REGION }} - AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - - build-tests: - environment: integration - permissions: - id-token: write - contents: read - name: Build Tests Binary - runs-on: ubuntu-latest - steps: - - name: Collect Metrics - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: live-testnet-build-test-image - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: Build Tests Binary - continue-on-error: true - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - ref: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} - - name: Build Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/build-tests@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - test_download_vendor_packages_command: cd ./integration-tests && go mod download - token: ${{ secrets.GITHUB_TOKEN }} - go_mod_path: ./integration-tests/go.mod - go_tags: embed - cache_key_id: core-e2e-${{ env.MOD_CACHE_VERSION }} - cache_restore_only: "true" - binary_name: tests - - # End Build Test Dependencies - - # Reporting Jobs - - start-slack-thread: - name: Start Slack Thread - if: ${{ always() && needs.*.result != 'skipped' && needs.*.result != 'cancelled' }} - environment: integration - outputs: - thread_ts: ${{ steps.slack.outputs.thread_ts }} - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - runs-on: ubuntu-latest - needs: [sepolia-smoke-tests, optimism-sepolia-smoke-tests, arbitrum-sepolia-smoke-tests, base-sepolia-smoke-tests, polygon-mumbai-smoke-tests, avalanche-fuji-smoke-tests, fantom-testnet-smoke-tests, celo-alfajores-smoke-tests, linea-goerli-smoke-tests, bsc-testnet-smoke-tests] - steps: - - name: Debug Result - run: echo ${{ join(needs.*.result, ',') }} - - name: Main Slack Notification - uses: slackapi/slack-github-action@6c661ce58804a1a20f6dc5fbee7f0381b469e001 # v1.25.0 - id: slack - with: - channel-id: ${{ secrets.QA_SLACK_CHANNEL }} - payload: | - { - "attachments": [ - { - "color": "${{ contains(join(needs.*.result, ','), 'failure') && '#C62828' || '#2E7D32' }}", - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "Live Smoke Test Results ${{ contains(join(needs.*.result, ','), 'failure') && ':x:' || ':white_check_mark:'}}", - "emoji": true - } - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "Notifying <@${{ inputs.slack_user_id }}>" - } - }, - { - "type": "divider" - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "<${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ github.ref_name }}|${{ github.ref_name }}> | <${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}|${{ github.sha }}> | <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Run>\nThe funding address for all tests and networks is `0xC1107e57082945E28d3202A81B1520DEA3AE6AEC`" - } - } - ] - } - ] - } - env: - SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} - - post-test-results-to-slack: - name: Post Test Results for ${{ matrix.network }} - if: ${{ always() && needs.*.result != 'skipped' && needs.*.result != 'cancelled' }} - environment: integration - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - runs-on: ubuntu-latest - needs: start-slack-thread - strategy: - fail-fast: false - matrix: - network: [Sepolia, Optimism Sepolia, Arbitrum Sepolia, Base Sepolia, Polygon Mumbai, Avalanche Fuji, Fantom Testnet, Celo Alfajores, Linea Goerli, BSC Testnet] - steps: - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - ref: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} - - name: Post Test Results - uses: ./.github/actions/notify-slack-jobs-result - with: - github_token: ${{ github.token }} - github_repository: ${{ github.repository }} - workflow_run_id: ${{ github.run_id }} - github_job_name_regex: ^${{ matrix.network }} (.*?) Tests$ - message_title: ${{ matrix.network }} - slack_channel_id: ${{ secrets.QA_SLACK_CHANNEL }} - slack_bot_token: ${{ secrets.QA_SLACK_API_KEY }} - slack_thread_ts: ${{ needs.start-slack-thread.outputs.thread_ts }} - - # End Reporting Jobs - - sepolia-smoke-tests: - environment: integration - if: ${{ (github.event.inputs.network == 'All' || github.event.inputs.network == 'Sepolia') }} - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - needs: [build-chainlink, build-tests] - strategy: - max-parallel: 1 - fail-fast: false - matrix: - include: # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs#example-adding-configurations - - product: OCR - test: TestOCRBasic - - product: Automation Conditional - test: TestAutomationBasic/registry_2_1_conditional - - product: Automation Log Trigger - test: TestAutomationBasic/registry_2_1_logtrigger - name: Sepolia ${{ matrix.product }} Tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - fetch-depth: 0 - - name: Setup GAP for Grafana - uses: smartcontractkit/.github/actions/setup-gap@d316f66b2990ea4daa479daa3de6fc92b00f863e # setup-gap@0.3.2 - with: - # aws inputs - aws-region: ${{ secrets.AWS_REGION }} - aws-role-arn: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} - api-gateway-host: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} - # other inputs - duplicate-authorization-header: "true" - - name: Prepare Base64 TOML override - uses: ./.github/actions/setup-create-base64-config-live-testnets - with: - runId: ${{ github.run_id }} - testLogCollect: ${{ vars.TEST_LOG_COLLECT }} - chainlinkImage: ${{ env.CHAINLINK_IMAGE }} - chainlinkVersion: ${{ github.sha }} - pyroscopeServer: ${{ matrix.product.pyroscope_env == '' && '' || !startsWith(github.ref, 'refs/tags/') && '' || secrets.QA_PYROSCOPE_INSTANCE }} # Avoid sending blank envs https://github.com/orgs/community/discussions/25725 - pyroscopeEnvironment: ci-smoke-${{ matrix.product }}-sepolia - pyroscopeKey: ${{ secrets.QA_PYROSCOPE_KEY }} - lokiEndpoint: ${{ secrets.LOKI_URL }} - lokiTenantId: ${{ vars.LOKI_TENANT_ID }} - lokiBasicAuth: ${{ secrets.LOKI_BASIC_AUTH }} - logstreamLogTargets: ${{ vars.LOGSTREAM_LOG_TARGETS }} - grafanaUrl: "http://localhost:8080/primary" - grafanaDashboardUrl: "/d/ddf75041-1e39-42af-aa46-361fe4c36e9e/ci-e2e-tests-logs" - grafanaBearerToken: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} - network: "sepolia" - httpEndpoints: ${{ secrets.QA_SEPOLIA_HTTP_URLS }} - wsEndpoints: ${{ secrets.QA_SEPOLIA_URLS }} - fundingKeys: ${{ secrets.QA_EVM_KEYS }} - - name: Download Tests Binary - uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4 - with: - name: tests - - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests-binary@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - test_command_to_run: ./tests -test.timeout 30m -test.count=1 -test.parallel=1 -test.run ${{ matrix.test }} - binary_name: tests - cl_repo: ${{ env.CHAINLINK_IMAGE }} - cl_image_tag: ${{ github.sha }} - aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - dockerhub_username: ${{ secrets.DOCKERHUB_READONLY_USERNAME }} - dockerhub_password: ${{ secrets.DOCKERHUB_READONLY_PASSWORD }} - artifacts_location: ./logs - token: ${{ secrets.GITHUB_TOKEN }} - cache_key_id: core-e2e-${{ env.MOD_CACHE_VERSION }} - cache_restore_only: "true" - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} - - name: Print failed test summary - if: always() - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/show-test-summary@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - test_directory: "./" - - bsc-testnet-smoke-tests: - environment: integration - if: ${{ github.event.inputs.network == 'All' || github.event.inputs.network == 'BSC Testnet' }} - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - needs: [build-chainlink, build-tests] - strategy: - max-parallel: 1 - fail-fast: false - matrix: - include: # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs#example-adding-configurations - - product: OCR - test: TestOCRBasic - - product: Automation Conditional - test: TestAutomationBasic/registry_2_1_conditional - - product: Automation Log Trigger - test: TestAutomationBasic/registry_2_1_logtrigger - name: BSC Testnet ${{ matrix.product }} Tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - fetch-depth: 0 - - name: Setup GAP for Grafana - uses: smartcontractkit/.github/actions/setup-gap@d316f66b2990ea4daa479daa3de6fc92b00f863e # setup-gap@0.3.2 - with: - # aws inputs - aws-region: ${{ secrets.AWS_REGION }} - aws-role-arn: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} - api-gateway-host: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} - # other inputs - duplicate-authorization-header: "true" - - name: Prepare Base64 TOML override - uses: ./.github/actions/setup-create-base64-config-live-testnets - with: - runId: ${{ github.run_id }} - testLogCollect: ${{ vars.TEST_LOG_COLLECT }} - chainlinkImage: ${{ env.CHAINLINK_IMAGE }} - chainlinkVersion: ${{ github.sha }} - pyroscopeServer: ${{ matrix.product.pyroscope_env == '' && '' || !startsWith(github.ref, 'refs/tags/') && '' || secrets.QA_PYROSCOPE_INSTANCE }} # Avoid sending blank envs https://github.com/orgs/community/discussions/25725 - pyroscopeEnvironment: ci-smoke-${{ matrix.product }}-bsc-testnet - pyroscopeKey: ${{ secrets.QA_PYROSCOPE_KEY }} - lokiEndpoint: ${{ secrets.LOKI_URL }} - lokiTenantId: ${{ vars.LOKI_TENANT_ID }} - lokiBasicAuth: ${{ secrets.LOKI_BASIC_AUTH }} - logstreamLogTargets: ${{ vars.LOGSTREAM_LOG_TARGETS }} - grafanaUrl: "http://localhost:8080/primary" - grafanaDashboardUrl: "/d/ddf75041-1e39-42af-aa46-361fe4c36e9e/ci-e2e-tests-logs" - grafanaBearerToken: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} - network: "bsc_testnet" - httpEndpoints: ${{ secrets.QA_BSC_TESTNET_HTTP_URLS }} - wsEndpoints: ${{ secrets.QA_BSC_TESTNET_URLS }} - fundingKeys: ${{ secrets.QA_EVM_KEYS }} - - name: Download Tests Binary - uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4 - with: - name: tests - - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests-binary@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - test_command_to_run: ./tests -test.timeout 30m -test.count=1 -test.parallel=1 -test.run ${{ matrix.test }} - binary_name: tests - cl_repo: ${{ env.CHAINLINK_IMAGE }} - cl_image_tag: ${{ github.sha }} - aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - dockerhub_username: ${{ secrets.DOCKERHUB_READONLY_USERNAME }} - dockerhub_password: ${{ secrets.DOCKERHUB_READONLY_PASSWORD }} - artifacts_location: ./logs - token: ${{ secrets.GITHUB_TOKEN }} - cache_key_id: core-e2e-${{ env.MOD_CACHE_VERSION }} - cache_restore_only: "true" - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} - - name: Print failed test summary - if: always() - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/show-test-summary@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - test_directory: "./" - - optimism-sepolia-smoke-tests: - environment: integration - if: ${{ github.event.inputs.network == 'All' || github.event.inputs.network == 'Optimism Sepolia' }} - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - needs: [build-chainlink, build-tests] - strategy: - max-parallel: 1 - fail-fast: false - matrix: - include: # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs#example-adding-configurations - - product: OCR - test: TestOCRBasic - - product: Automation Conditional - test: TestAutomationBasic/registry_2_1_conditional - - product: Automation Log Trigger - test: TestAutomationBasic/registry_2_1_logtrigger - name: Optimism Sepolia ${{ matrix.product }} Tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - fetch-depth: 0 - - name: Setup GAP for Grafana - uses: smartcontractkit/.github/actions/setup-gap@d316f66b2990ea4daa479daa3de6fc92b00f863e # setup-gap@0.3.2 - with: - # aws inputs - aws-region: ${{ secrets.AWS_REGION }} - aws-role-arn: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} - api-gateway-host: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} - # other inputs - duplicate-authorization-header: "true" - - name: Prepare Base64 TOML override - uses: ./.github/actions/setup-create-base64-config-live-testnets - with: - runId: ${{ github.run_id }} - testLogCollect: ${{ vars.TEST_LOG_COLLECT }} - chainlinkImage: ${{ env.CHAINLINK_IMAGE }} - chainlinkVersion: ${{ github.sha }} - pyroscopeServer: ${{ matrix.product.pyroscope_env == '' && '' || !startsWith(github.ref, 'refs/tags/') && '' || secrets.QA_PYROSCOPE_INSTANCE }} # Avoid sending blank envs https://github.com/orgs/community/discussions/25725 - pyroscopeEnvironment: ci-smoke-${{ matrix.product }}-optimism-sepolia - pyroscopeKey: ${{ secrets.QA_PYROSCOPE_KEY }} - lokiEndpoint: ${{ secrets.LOKI_URL }} - lokiTenantId: ${{ vars.LOKI_TENANT_ID }} - lokiBasicAuth: ${{ secrets.LOKI_BASIC_AUTH }} - logstreamLogTargets: ${{ vars.LOGSTREAM_LOG_TARGETS }} - grafanaUrl: "http://localhost:8080/primary" - grafanaDashboardUrl: "/d/ddf75041-1e39-42af-aa46-361fe4c36e9e/ci-e2e-tests-logs" - grafanaBearerToken: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} - network: "optimism_sepolia" - httpEndpoints: ${{ secrets.QA_OPTIMISM_SEPOLIA_HTTP_URLS }} - wsEndpoints: ${{ secrets.QA_OPTIMISM_SEPOLIA_URLS }} - fundingKeys: ${{ secrets.QA_EVM_KEYS }} - - name: Download Tests Binary - uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4 - with: - name: tests - - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests-binary@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - test_command_to_run: ./tests -test.timeout 30m -test.count=1 -test.parallel=1 -test.run ${{ matrix.test }} - binary_name: tests - cl_repo: ${{ env.CHAINLINK_IMAGE }} - cl_image_tag: ${{ github.sha }} - aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - dockerhub_username: ${{ secrets.DOCKERHUB_READONLY_USERNAME }} - dockerhub_password: ${{ secrets.DOCKERHUB_READONLY_PASSWORD }} - artifacts_location: ./logs - token: ${{ secrets.GITHUB_TOKEN }} - cache_key_id: core-e2e-${{ env.MOD_CACHE_VERSION }} - cache_restore_only: "true" - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} - - name: Print failed test summary - if: always() - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/show-test-summary@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - test_directory: "./" - - arbitrum-sepolia-smoke-tests: - environment: integration - if: ${{ github.event.inputs.network == 'All' || github.event.inputs.network == 'Arbitrum Sepolia' }} - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - needs: [build-chainlink, build-tests] - strategy: - max-parallel: 1 - fail-fast: false - matrix: - include: # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs#example-adding-configurations - - product: OCR - test: TestOCRBasic - - product: Automation Conditional - test: TestAutomationBasic/registry_2_1_conditional - - product: Automation Log Trigger - test: TestAutomationBasic/registry_2_1_logtrigger - name: Arbitrum Sepolia ${{ matrix.product }} Tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - fetch-depth: 0 - - name: Setup GAP for Grafana - uses: smartcontractkit/.github/actions/setup-gap@d316f66b2990ea4daa479daa3de6fc92b00f863e # setup-gap@0.3.2 - with: - # aws inputs - aws-region: ${{ secrets.AWS_REGION }} - aws-role-arn: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} - api-gateway-host: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} - # other inputs - duplicate-authorization-header: "true" - - name: Prepare Base64 TOML override - uses: ./.github/actions/setup-create-base64-config-live-testnets - with: - runId: ${{ github.run_id }} - testLogCollect: ${{ vars.TEST_LOG_COLLECT }} - chainlinkImage: ${{ env.CHAINLINK_IMAGE }} - chainlinkVersion: ${{ github.sha }} - pyroscopeServer: ${{ matrix.product.pyroscope_env == '' && '' || !startsWith(github.ref, 'refs/tags/') && '' || secrets.QA_PYROSCOPE_INSTANCE }} # Avoid sending blank envs https://github.com/orgs/community/discussions/25725 - pyroscopeEnvironment: ci-smoke-${{ matrix.product }}-arbitrum-sepolia - pyroscopeKey: ${{ secrets.QA_PYROSCOPE_KEY }} - lokiEndpoint: ${{ secrets.LOKI_URL }} - lokiTenantId: ${{ vars.LOKI_TENANT_ID }} - lokiBasicAuth: ${{ secrets.LOKI_BASIC_AUTH }} - logstreamLogTargets: ${{ vars.LOGSTREAM_LOG_TARGETS }} - grafanaUrl: "http://localhost:8080/primary" - grafanaDashboardUrl: "/d/ddf75041-1e39-42af-aa46-361fe4c36e9e/ci-e2e-tests-logs" - grafanaBearerToken: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} - network: "arbitrum_sepolia" - httpEndpoints: ${{ secrets.QA_ARBITRUM_SEPOLIA_HTTP_URLS }} - wsEndpoints: ${{ secrets.QA_ARBITRUM_SEPOLIA_URLS }} - fundingKeys: ${{ secrets.QA_EVM_KEYS }} - - name: Download Tests Binary - uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4 - with: - name: tests - - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests-binary@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - test_command_to_run: ./tests -test.timeout 30m -test.count=1 -test.parallel=1 -test.run ${{ matrix.test }} - binary_name: tests - cl_repo: ${{ env.CHAINLINK_IMAGE }} - cl_image_tag: ${{ github.sha }} - aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - dockerhub_username: ${{ secrets.DOCKERHUB_READONLY_USERNAME }} - dockerhub_password: ${{ secrets.DOCKERHUB_READONLY_PASSWORD }} - artifacts_location: ./logs - token: ${{ secrets.GITHUB_TOKEN }} - cache_key_id: core-e2e-${{ env.MOD_CACHE_VERSION }} - cache_restore_only: "true" - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} - - name: Print failed test summary - if: always() - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/show-test-summary@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - test_directory: "./" - - base-sepolia-smoke-tests: - environment: integration - if: ${{ github.event.inputs.network == 'All' || github.event.inputs.network == 'Base Sepolia' }} - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - needs: [build-chainlink, build-tests] - strategy: - max-parallel: 1 - fail-fast: false - matrix: - include: # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs#example-adding-configurations - - product: OCR - test: TestOCRBasic - name: Base Sepolia ${{ matrix.product }} Tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - fetch-depth: 0 - - name: Setup GAP for Grafana - uses: smartcontractkit/.github/actions/setup-gap@d316f66b2990ea4daa479daa3de6fc92b00f863e # setup-gap@0.3.2 - with: - # aws inputs - aws-region: ${{ secrets.AWS_REGION }} - aws-role-arn: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} - api-gateway-host: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} - # other inputs - duplicate-authorization-header: "true" - - name: Prepare Base64 TOML override - uses: ./.github/actions/setup-create-base64-config-live-testnets - with: - runId: ${{ github.run_id }} - testLogCollect: ${{ vars.TEST_LOG_COLLECT }} - chainlinkImage: ${{ env.CHAINLINK_IMAGE }} - chainlinkVersion: ${{ github.sha }} - pyroscopeServer: ${{ matrix.product.pyroscope_env == '' && '' || !startsWith(github.ref, 'refs/tags/') && '' || secrets.QA_PYROSCOPE_INSTANCE }} # Avoid sending blank envs https://github.com/orgs/community/discussions/25725 - pyroscopeEnvironment: ci-smoke-${{ matrix.product }}-base-sepolia - pyroscopeKey: ${{ secrets.QA_PYROSCOPE_KEY }} - lokiEndpoint: ${{ secrets.LOKI_URL }} - lokiTenantId: ${{ vars.LOKI_TENANT_ID }} - lokiBasicAuth: ${{ secrets.LOKI_BASIC_AUTH }} - logstreamLogTargets: ${{ vars.LOGSTREAM_LOG_TARGETS }} - grafanaUrl: "http://localhost:8080/primary" - grafanaDashboardUrl: "/d/ddf75041-1e39-42af-aa46-361fe4c36e9e/ci-e2e-tests-logs" - grafanaBearerToken: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} - network: "base_sepolia" - httpEndpoints: ${{ secrets.QA_BASE_SEPOLIA_HTTP_URLS }} - wsEndpoints: ${{ secrets.QA_BASE_SEPOLIA_URLS }} - fundingKeys: ${{ secrets.QA_EVM_KEYS }} - - name: Download Tests Binary - uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4 - with: - name: tests - - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests-binary@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - test_command_to_run: ./tests -test.timeout 30m -test.count=1 -test.parallel=1 -test.run ${{ matrix.test }} - binary_name: tests - cl_repo: ${{ env.CHAINLINK_IMAGE }} - cl_image_tag: ${{ github.sha }} - aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - dockerhub_username: ${{ secrets.DOCKERHUB_READONLY_USERNAME }} - dockerhub_password: ${{ secrets.DOCKERHUB_READONLY_PASSWORD }} - artifacts_location: ./logs - token: ${{ secrets.GITHUB_TOKEN }} - cache_key_id: core-e2e-${{ env.MOD_CACHE_VERSION }} - cache_restore_only: "true" - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} - - name: Print failed test summary - if: always() - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/show-test-summary@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - test_directory: "./" - - polygon-mumbai-smoke-tests: - environment: integration - if: ${{ github.event.inputs.network == 'All' || github.event.inputs.network == 'Polygon Mumbai' }} - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - needs: [build-chainlink, build-tests] - strategy: - max-parallel: 1 - fail-fast: false - matrix: - include: # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs#example-adding-configurations - - product: OCR - test: TestOCRBasic - - product: Automation Conditional - test: TestAutomationBasic/registry_2_1_conditional - - product: Automation Log Trigger - test: TestAutomationBasic/registry_2_1_logtrigger - name: Polygon Mumbai ${{ matrix.product }} Tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - fetch-depth: 0 - - name: Setup GAP for Grafana - uses: smartcontractkit/.github/actions/setup-gap@d316f66b2990ea4daa479daa3de6fc92b00f863e # setup-gap@0.3.2 - with: - # aws inputs - aws-region: ${{ secrets.AWS_REGION }} - aws-role-arn: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} - api-gateway-host: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} - # other inputs - duplicate-authorization-header: "true" - - name: Prepare Base64 TOML override - uses: ./.github/actions/setup-create-base64-config-live-testnets - with: - runId: ${{ github.run_id }} - testLogCollect: ${{ vars.TEST_LOG_COLLECT }} - chainlinkImage: ${{ env.CHAINLINK_IMAGE }} - chainlinkVersion: ${{ github.sha }} - pyroscopeServer: ${{ matrix.product.pyroscope_env == '' && '' || !startsWith(github.ref, 'refs/tags/') && '' || secrets.QA_PYROSCOPE_INSTANCE }} # Avoid sending blank envs https://github.com/orgs/community/discussions/25725 - pyroscopeEnvironment: ci-smoke-${{ matrix.product }}-polygon-mumbai - pyroscopeKey: ${{ secrets.QA_PYROSCOPE_KEY }} - lokiEndpoint: ${{ secrets.LOKI_URL }} - lokiTenantId: ${{ vars.LOKI_TENANT_ID }} - lokiBasicAuth: ${{ secrets.LOKI_BASIC_AUTH }} - logstreamLogTargets: ${{ vars.LOGSTREAM_LOG_TARGETS }} - grafanaUrl: "http://localhost:8080/primary" - grafanaDashboardUrl: "/d/ddf75041-1e39-42af-aa46-361fe4c36e9e/ci-e2e-tests-logs" - grafanaBearerToken: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} - network: "polygon_mumbai" - httpEndpoints: ${{ secrets.QA_POLYGON_MUMBAI_HTTP_URLS }} - wsEndpoints: ${{ secrets.QA_POLYGON_MUMBAI_URLS }} - fundingKeys: ${{ secrets.QA_EVM_KEYS }} - - name: Download Tests Binary - uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4 - with: - name: tests - - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests-binary@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - test_command_to_run: ./tests -test.timeout 30m -test.count=1 -test.parallel=1 -test.run ${{ matrix.test }} - binary_name: tests - cl_repo: ${{ env.CHAINLINK_IMAGE }} - cl_image_tag: ${{ github.sha }} - aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - dockerhub_username: ${{ secrets.DOCKERHUB_READONLY_USERNAME }} - dockerhub_password: ${{ secrets.DOCKERHUB_READONLY_PASSWORD }} - artifacts_location: ./logs - token: ${{ secrets.GITHUB_TOKEN }} - cache_key_id: core-e2e-${{ env.MOD_CACHE_VERSION }} - cache_restore_only: "true" - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} - - name: Print failed test summary - if: always() - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/show-test-summary@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - test_directory: "./" - - avalanche-fuji-smoke-tests: - environment: integration - if: ${{ github.event.inputs.network == 'All' || github.event.inputs.network == 'Avalanche Fuji' }} - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - needs: [build-chainlink, build-tests] - strategy: - max-parallel: 1 - fail-fast: false - matrix: - include: # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs#example-adding-configurations - - product: OCR - test: TestOCRBasic - - product: Automation Conditional - test: TestAutomationBasic/registry_2_1_conditional - - product: Automation Log Trigger - test: TestAutomationBasic/registry_2_1_logtrigger - name: Avalanche Fuji ${{ matrix.product }} Tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - fetch-depth: 0 - - name: Setup GAP for Grafana - uses: smartcontractkit/.github/actions/setup-gap@d316f66b2990ea4daa479daa3de6fc92b00f863e # setup-gap@0.3.2 - with: - # aws inputs - aws-region: ${{ secrets.AWS_REGION }} - aws-role-arn: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} - api-gateway-host: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} - # other inputs - duplicate-authorization-header: "true" - - name: Prepare Base64 TOML override - uses: ./.github/actions/setup-create-base64-config-live-testnets - with: - runId: ${{ github.run_id }} - testLogCollect: ${{ vars.TEST_LOG_COLLECT }} - chainlinkImage: ${{ env.CHAINLINK_IMAGE }} - chainlinkVersion: ${{ github.sha }} - pyroscopeServer: ${{ matrix.product.pyroscope_env == '' && '' || !startsWith(github.ref, 'refs/tags/') && '' || secrets.QA_PYROSCOPE_INSTANCE }} # Avoid sending blank envs https://github.com/orgs/community/discussions/25725 - pyroscopeEnvironment: ci-smoke-${{ matrix.product }}-avalanche-fuji - pyroscopeKey: ${{ secrets.QA_PYROSCOPE_KEY }} - lokiEndpoint: ${{ secrets.LOKI_URL }} - lokiTenantId: ${{ vars.LOKI_TENANT_ID }} - lokiBasicAuth: ${{ secrets.LOKI_BASIC_AUTH }} - logstreamLogTargets: ${{ vars.LOGSTREAM_LOG_TARGETS }} - grafanaUrl: "http://localhost:8080/primary" - grafanaDashboardUrl: "/d/ddf75041-1e39-42af-aa46-361fe4c36e9e/ci-e2e-tests-logs" - grafanaBearerToken: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} - network: "avalanche_fuji" - httpEndpoints: ${{ secrets.QA_AVALANCHE_FUJI_HTTP_URLS }} - wsEndpoints: ${{ secrets.QA_AVALANCHE_FUJI_URLS }} - fundingKeys: ${{ secrets.QA_EVM_KEYS }} - - name: Download Tests Binary - uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4 - with: - name: tests - - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests-binary@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - test_command_to_run: ./tests -test.timeout 30m -test.count=1 -test.parallel=1 -test.run ${{ matrix.test }} - binary_name: tests - cl_repo: ${{ env.CHAINLINK_IMAGE }} - cl_image_tag: ${{ github.sha }} - aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - dockerhub_username: ${{ secrets.DOCKERHUB_READONLY_USERNAME }} - dockerhub_password: ${{ secrets.DOCKERHUB_READONLY_PASSWORD }} - artifacts_location: ./logs - token: ${{ secrets.GITHUB_TOKEN }} - cache_key_id: core-e2e-${{ env.MOD_CACHE_VERSION }} - cache_restore_only: "true" - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} - - name: Print failed test summary - if: always() - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/show-test-summary@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - test_directory: "./" - - fantom-testnet-smoke-tests: - environment: integration - if: ${{ github.event.inputs.network == 'All' || github.event.inputs.network == 'Fantom Testnet' }} - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - needs: [build-chainlink, build-tests] - strategy: - max-parallel: 1 - fail-fast: false - matrix: - include: # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs#example-adding-configurations - - product: OCR - test: TestOCRBasic - - product: Automation Conditional - test: TestAutomationBasic/registry_2_1_conditional - - product: Automation Log Trigger - test: TestAutomationBasic/registry_2_1_logtrigger - name: Fantom Testnet ${{ matrix.product }} Tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - fetch-depth: 0 - - name: Setup GAP for Grafana - uses: smartcontractkit/.github/actions/setup-gap@d316f66b2990ea4daa479daa3de6fc92b00f863e # setup-gap@0.3.2 - with: - # aws inputs - aws-region: ${{ secrets.AWS_REGION }} - aws-role-arn: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} - api-gateway-host: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} - # other inputs - duplicate-authorization-header: "true" - - name: Prepare Base64 TOML override - uses: ./.github/actions/setup-create-base64-config-live-testnets - with: - runId: ${{ github.run_id }} - testLogCollect: ${{ vars.TEST_LOG_COLLECT }} - chainlinkImage: ${{ env.CHAINLINK_IMAGE }} - chainlinkVersion: ${{ github.sha }} - pyroscopeServer: ${{ matrix.product.pyroscope_env == '' && '' || !startsWith(github.ref, 'refs/tags/') && '' || secrets.QA_PYROSCOPE_INSTANCE }} # Avoid sending blank envs https://github.com/orgs/community/discussions/25725 - pyroscopeEnvironment: ci-smoke-${{ matrix.product }}-fantom-testnet - pyroscopeKey: ${{ secrets.QA_PYROSCOPE_KEY }} - lokiEndpoint: ${{ secrets.LOKI_URL }} - lokiTenantId: ${{ vars.LOKI_TENANT_ID }} - lokiBasicAuth: ${{ secrets.LOKI_BASIC_AUTH }} - logstreamLogTargets: ${{ vars.LOGSTREAM_LOG_TARGETS }} - grafanaUrl: "http://localhost:8080/primary" - grafanaDashboardUrl: "/d/ddf75041-1e39-42af-aa46-361fe4c36e9e/ci-e2e-tests-logs" - grafanaBearerToken: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} - network: "fantom_testnet" - httpEndpoints: ${{ secrets.QA_FANTOM_TESTNET_HTTP_URLS }} - wsEndpoints: ${{ secrets.QA_FANTOM_TESTNET_URLS }} - fundingKeys: ${{ secrets.QA_EVM_KEYS }} - - name: Download Tests Binary - uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4 - with: - name: tests - - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests-binary@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - test_command_to_run: ./tests -test.timeout 30m -test.count=1 -test.parallel=1 -test.run ${{ matrix.test }} - binary_name: tests - cl_repo: ${{ env.CHAINLINK_IMAGE }} - cl_image_tag: ${{ github.sha }} - aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - dockerhub_username: ${{ secrets.DOCKERHUB_READONLY_USERNAME }} - dockerhub_password: ${{ secrets.DOCKERHUB_READONLY_PASSWORD }} - artifacts_location: ./logs - token: ${{ secrets.GITHUB_TOKEN }} - cache_key_id: core-e2e-${{ env.MOD_CACHE_VERSION }} - cache_restore_only: "true" - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} - - name: Print failed test summary - if: always() - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/show-test-summary@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - test_directory: "./" - - celo-alfajores-smoke-tests: - environment: integration - if: ${{ github.event.inputs.network == 'All' || github.event.inputs.network == 'Celo Alfajores' }} - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - needs: [build-chainlink, build-tests] - strategy: - max-parallel: 1 - fail-fast: false - matrix: - include: # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs#example-adding-configurations - - product: OCR - test: TestOCRBasic - name: Celo Alfajores ${{ matrix.product }} Tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - fetch-depth: 0 - - name: Setup GAP for Grafana - uses: smartcontractkit/.github/actions/setup-gap@d316f66b2990ea4daa479daa3de6fc92b00f863e # setup-gap@0.3.2 - with: - # aws inputs - aws-region: ${{ secrets.AWS_REGION }} - aws-role-arn: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} - api-gateway-host: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} - # other inputs - duplicate-authorization-header: "true" - - name: Prepare Base64 TOML override - uses: ./.github/actions/setup-create-base64-config-live-testnets - with: - runId: ${{ github.run_id }} - testLogCollect: ${{ vars.TEST_LOG_COLLECT }} - chainlinkImage: ${{ env.CHAINLINK_IMAGE }} - chainlinkVersion: ${{ github.sha }} - pyroscopeServer: ${{ matrix.product.pyroscope_env == '' && '' || !startsWith(github.ref, 'refs/tags/') && '' || secrets.QA_PYROSCOPE_INSTANCE }} # Avoid sending blank envs https://github.com/orgs/community/discussions/25725 - pyroscopeEnvironment: ci-smoke-${{ matrix.product }}-celo-alfajores - pyroscopeKey: ${{ secrets.QA_PYROSCOPE_KEY }} - lokiEndpoint: ${{ secrets.LOKI_URL }} - lokiTenantId: ${{ vars.LOKI_TENANT_ID }} - lokiBasicAuth: ${{ secrets.LOKI_BASIC_AUTH }} - logstreamLogTargets: ${{ vars.LOGSTREAM_LOG_TARGETS }} - grafanaUrl: "http://localhost:8080/primary" - grafanaDashboardUrl: "/d/ddf75041-1e39-42af-aa46-361fe4c36e9e/ci-e2e-tests-logs" - grafanaBearerToken: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} - network: "celo_alfajores" - httpEndpoints: ${{ secrets.QA_CELO_ALFAJORES_HTTP_URLS }} - wsEndpoints: ${{ secrets.QA_CELO_ALFAJORES_URLS }} - fundingKeys: ${{ secrets.QA_EVM_KEYS }} - - name: Download Tests Binary - uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4 - with: - name: tests - - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests-binary@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - test_command_to_run: ./tests -test.timeout 30m -test.count=1 -test.parallel=1 -test.run ${{ matrix.test }} - binary_name: tests - cl_repo: ${{ env.CHAINLINK_IMAGE }} - cl_image_tag: ${{ github.sha }} - aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - dockerhub_username: ${{ secrets.DOCKERHUB_READONLY_USERNAME }} - dockerhub_password: ${{ secrets.DOCKERHUB_READONLY_PASSWORD }} - artifacts_location: ./logs - token: ${{ secrets.GITHUB_TOKEN }} - cache_key_id: core-e2e-${{ env.MOD_CACHE_VERSION }} - cache_restore_only: "true" - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} - - name: Print failed test summary - if: always() - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/show-test-summary@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - test_directory: "./" - - scroll-sepolia-smoke-tests: - if: false - environment: integration - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - needs: [build-chainlink, build-tests] - strategy: - max-parallel: 1 - fail-fast: false - matrix: - include: # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs#example-adding-configurations - - product: OCR - test: TestOCRBasic - name: Scroll Sepolia ${{ matrix.product }} Tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - fetch-depth: 0 - - name: Setup GAP for Grafana - uses: smartcontractkit/.github/actions/setup-gap@d316f66b2990ea4daa479daa3de6fc92b00f863e # setup-gap@0.3.2 - with: - # aws inputs - aws-region: ${{ secrets.AWS_REGION }} - aws-role-arn: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} - api-gateway-host: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} - # other inputs - duplicate-authorization-header: "true" - - name: Prepare Base64 TOML override - uses: ./.github/actions/setup-create-base64-config-live-testnets - with: - runId: ${{ github.run_id }} - testLogCollect: ${{ vars.TEST_LOG_COLLECT }} - chainlinkImage: ${{ env.CHAINLINK_IMAGE }} - chainlinkVersion: ${{ github.sha }} - pyroscopeServer: ${{ matrix.product.pyroscope_env == '' && '' || !startsWith(github.ref, 'refs/tags/') && '' || secrets.QA_PYROSCOPE_INSTANCE }} # Avoid sending blank envs https://github.com/orgs/community/discussions/25725 - pyroscopeEnvironment: ci-smoke-${{ matrix.product }}-scroll-sepolia - pyroscopeKey: ${{ secrets.QA_PYROSCOPE_KEY }} - lokiEndpoint: ${{ secrets.LOKI_URL }} - lokiTenantId: ${{ vars.LOKI_TENANT_ID }} - lokiBasicAuth: ${{ secrets.LOKI_BASIC_AUTH }} - logstreamLogTargets: ${{ vars.LOGSTREAM_LOG_TARGETS }} - grafanaUrl: "http://localhost:8080/primary" - grafanaDashboardUrl: "/d/ddf75041-1e39-42af-aa46-361fe4c36e9e/ci-e2e-tests-logs" - grafanaBearerToken: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} - network: "scroll_sepolia" - httpEndpoints: ${{ secrets.QA_SCROLL_SEPOLIA_HTTP_URLS }} - wsEndpoints: ${{ secrets.QA_SCROLL_SEPOLIA_URLS }} - fundingKeys: ${{ secrets.QA_EVM_KEYS }} - - name: Download Tests Binary - uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4 - with: - name: tests - - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests-binary@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - test_command_to_run: ./tests -test.timeout 30m -test.count=1 -test.parallel=1 -test.run ${{ matrix.test }} - binary_name: tests - cl_repo: ${{ env.CHAINLINK_IMAGE }} - cl_image_tag: ${{ github.sha }} - aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - dockerhub_username: ${{ secrets.DOCKERHUB_READONLY_USERNAME }} - dockerhub_password: ${{ secrets.DOCKERHUB_READONLY_PASSWORD }} - artifacts_location: ./logs - token: ${{ secrets.GITHUB_TOKEN }} - cache_key_id: core-e2e-${{ env.MOD_CACHE_VERSION }} - cache_restore_only: "true" - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} - - name: Print failed test summary - if: always() - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/show-test-summary@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - test_directory: "./" - - linea-goerli-smoke-tests: - environment: integration - if: ${{ github.event.inputs.network == 'All' || github.event.inputs.network == 'Linea Goerli' }} - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - needs: [build-chainlink, build-tests] - strategy: - max-parallel: 1 - fail-fast: false - matrix: - include: # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs#example-adding-configurations - - product: OCR - test: TestOCRBasic - name: Linea Goerli ${{ matrix.product }} Tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - fetch-depth: 0 - - name: Setup GAP for Grafana - uses: smartcontractkit/.github/actions/setup-gap@d316f66b2990ea4daa479daa3de6fc92b00f863e # setup-gap@0.3.2 - with: - # aws inputs - aws-region: ${{ secrets.AWS_REGION }} - aws-role-arn: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} - api-gateway-host: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} - # other inputs - duplicate-authorization-header: "true" - - name: Prepare Base64 TOML override - uses: ./.github/actions/setup-create-base64-config-live-testnets - with: - runId: ${{ github.run_id }} - testLogCollect: ${{ vars.TEST_LOG_COLLECT }} - chainlinkImage: ${{ env.CHAINLINK_IMAGE }} - chainlinkVersion: ${{ github.sha }} - pyroscopeServer: ${{ matrix.product.pyroscope_env == '' && '' || !startsWith(github.ref, 'refs/tags/') && '' || secrets.QA_PYROSCOPE_INSTANCE }} # Avoid sending blank envs https://github.com/orgs/community/discussions/25725 - pyroscopeEnvironment: ci-smoke-${{ matrix.product }}-linea-goerli - pyroscopeKey: ${{ secrets.QA_PYROSCOPE_KEY }} - lokiEndpoint: ${{ secrets.LOKI_URL }} - lokiTenantId: ${{ vars.LOKI_TENANT_ID }} - lokiBasicAuth: ${{ secrets.LOKI_BASIC_AUTH }} - logstreamLogTargets: ${{ vars.LOGSTREAM_LOG_TARGETS }} - grafanaUrl: "http://localhost:8080/primary" - grafanaDashboardUrl: "/d/ddf75041-1e39-42af-aa46-361fe4c36e9e/ci-e2e-tests-logs" - grafanaBearerToken: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} - network: "linea_goerli" - httpEndpoints: ${{ secrets.QA_LINEA_GOERLI_HTTP_URLS }} - wsEndpoints: ${{ secrets.QA_LINEA_GOERLI_URLS }} - fundingKeys: ${{ secrets.QA_EVM_KEYS }} - - name: Download Tests Binary - uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4 - with: - name: tests - - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests-binary@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - test_command_to_run: ./tests -test.timeout 30m -test.count=1 -test.parallel=1 -test.run ${{ matrix.test }} - binary_name: tests - cl_repo: ${{ env.CHAINLINK_IMAGE }} - cl_image_tag: ${{ github.sha }} - aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - dockerhub_username: ${{ secrets.DOCKERHUB_READONLY_USERNAME }} - dockerhub_password: ${{ secrets.DOCKERHUB_READONLY_PASSWORD }} - artifacts_location: ./logs - token: ${{ secrets.GITHUB_TOKEN }} - cache_key_id: core-e2e-${{ env.MOD_CACHE_VERSION }} - cache_restore_only: "true" - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} - - name: Print failed test summary - if: always() - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/show-test-summary@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - test_directory: "./" diff --git a/.github/workflows/live-vrf-tests.yml b/.github/workflows/live-vrf-tests.yml deleted file mode 100644 index 43b0fd8dc3..0000000000 --- a/.github/workflows/live-vrf-tests.yml +++ /dev/null @@ -1,191 +0,0 @@ -# Funding address: 0xC1107e57082945E28d3202A81B1520DEA3AE6AEC -name: Generic Live Smoke Tests -on: - workflow_dispatch: - inputs: - networks: - description: "Comma-separated list of networks to run on" - required: true - default: "SEPOLIA,OPTIMISM_SEPOLIA,ARBITRUM_SEPOLIA" - test_list: - description: "Comma-separated list of tests to run" - required: true - default: "TestVRFBasic,TestVRFv2Basic,TestVRFv2Plus" - -env: - CHAINLINK_IMAGE: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink - INTERNAL_DOCKER_REPO: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com - MOD_CACHE_VERSION: 2 - CHAINLINK_NODE_FUNDING: .5 - PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} - LOKI_TENANT_ID: ${{ vars.LOKI_TENANT_ID }} - LOKI_URL: ${{ secrets.LOKI_URL }} - LOKI_BASIC_AUTH: ${{ secrets.LOKI_BASIC_AUTH }} - LOGSTREAM_LOG_TARGETS: loki - GRAFANA_URL: ${{ vars.GRAFANA_URL }} - RUN_ID: ${{ github.run_id }} - - CHAINLINK_COMMIT_SHA: ${{ github.sha }} - CHAINLINK_ENV_USER: ${{ github.actor }} - TEST_LOG_LEVEL: debug - -jobs: - # Build Test Dependencies - - build-chainlink: - environment: integration - permissions: - id-token: write - contents: read - name: Build Chainlink Image - runs-on: ubuntu-latest - steps: - - name: Collect Metrics - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: live-vrf-build-chainlink - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: Build Chainlink Image - continue-on-error: true - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - ref: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} - - name: Build Chainlink Image - uses: ./.github/actions/build-chainlink-image - with: - tag_suffix: "" - dockerfile: core/chainlink.Dockerfile - git_commit_sha: ${{ github.sha }} - GRAFANA_CLOUD_BASIC_AUTH: ${{ secrets.GRAFANA_CLOUD_BASIC_AUTH }} - GRAFANA_CLOUD_HOST: ${{ secrets.GRAFANA_CLOUD_HOST }} - AWS_REGION: ${{ secrets.QA_AWS_REGION }} - AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - - build-tests: - environment: integration - permissions: - id-token: write - contents: read - name: Build Tests Binary - runs-on: ubuntu-latest - outputs: - matrix: ${{ steps.build-matrix.outputs.matrix }} - steps: - - name: Collect Metrics - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: live-vrf-build-test-image - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: Build Tests Binary - continue-on-error: true - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - ref: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} - - name: Build Network Matrix - id: build-matrix - run: | - NETWORKS="[\"${{ github.event.inputs.networks }}\"]" - NETWORKS="${NETWORKS//,/\",\"}" - echo "matrix=${NETWORKS}" >> "$GITHUB_OUTPUT" - - name: Build Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/build-tests@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - test_download_vendor_packages_command: cd ./integration-tests && go mod download - token: ${{ secrets.GITHUB_TOKEN }} - go_mod_path: ./integration-tests/go.mod - go_tags: embed - cache_key_id: core-e2e-${{ env.MOD_CACHE_VERSION }} - cache_restore_only: "true" - binary_name: tests - - # End Build Test Dependencies - - live-smoke-tests: - environment: integration - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - needs: [build-chainlink, build-tests] - strategy: - fail-fast: false - matrix: - network: ${{fromJson(needs.build-tests.outputs.matrix)}} - name: Smoke Tests on ${{ matrix.network }} - runs-on: ubuntu-latest - steps: - - name: Build Secrets Names - id: build-secrets-names - run: | - echo "HTTP_URLS_SECRET_NAME=QA_${{ matrix.network }}_HTTP_URLS" >> $GITHUB_ENV - echo "URLS_SECRET_NAME=QA_${{ matrix.network }}_URLS" >> $GITHUB_ENV - - name: Split Test Names - id: split_list - run: | - IFS=',' read -ra ADDR <<< "${{ inputs.test_list }}" - echo "test_list=${ADDR[*]}" >> $GITHUB_ENV - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - fetch-depth: 0 - - name: Setup GAP for Grafana - uses: smartcontractkit/.github/actions/setup-gap@d316f66b2990ea4daa479daa3de6fc92b00f863e # setup-gap@0.3.2 - with: - # aws inputs - aws-region: ${{ secrets.AWS_REGION }} - aws-role-arn: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} - api-gateway-host: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} - # other inputs - duplicate-authorization-header: "true" - - name: Prepare Base64 TOML override - uses: ./.github/actions/setup-create-base64-config-live-testnets - with: - runId: ${{ github.run_id }} - testLogCollect: ${{ vars.TEST_LOG_COLLECT }} - chainlinkImage: ${{ env.CHAINLINK_IMAGE }} - chainlinkVersion: ${{ github.sha }} - lokiEndpoint: ${{ secrets.LOKI_URL }} - lokiTenantId: ${{ vars.LOKI_TENANT_ID }} - lokiBasicAuth: ${{ secrets.LOKI_BASIC_AUTH }} - logstreamLogTargets: ${{ vars.LOGSTREAM_LOG_TARGETS }} - grafanaUrl: "http://localhost:8080/primary" - grafanaDashboardUrl: "/d/ddf75041-1e39-42af-aa46-361fe4c36e9e/ci-e2e-tests-logs" - grafanaBearerToken: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} - network: ${{ matrix.network }} - httpEndpoints: ${{ secrets[env.HTTP_URLS_SECRET_NAME] }} - wsEndpoints: ${{ secrets[env.URLS_SECRET_NAME] }} - fundingKeys: ${{ secrets.QA_EVM_KEYS }} - - name: Download Tests Binary - uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4 - with: - name: tests - - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests-binary@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - test_command_to_run: ./tests -test.v -test.timeout 4h -test.count=1 -test.parallel=1 -test.run ${{ env.test_list }} - binary_name: tests - cl_repo: ${{ env.CHAINLINK_IMAGE }} - cl_image_tag: ${{ github.sha }} - aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - dockerhub_username: ${{ secrets.DOCKER_READONLY_USERNAME }} - dockerhub_password: ${{ secrets.DOCKER_READONLY_PASSWORD }} - artifacts_location: ./logs - token: ${{ secrets.GITHUB_TOKEN }} - cache_key_id: core-e2e-${{ env.MOD_CACHE_VERSION }} - cache_restore_only: "true" - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} - - name: Print failed test summary - if: always() - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/show-test-summary@75a9005952a9e905649cfb5a6971fd9429436acd # v2.3.25 - with: - test_directory: "./" diff --git a/.github/workflows/on-demand-keeper-smoke-tests.yml b/.github/workflows/on-demand-keeper-smoke-tests.yml deleted file mode 100644 index dbdeff1b2c..0000000000 --- a/.github/workflows/on-demand-keeper-smoke-tests.yml +++ /dev/null @@ -1,289 +0,0 @@ -name: On Demand Keeper Smoke Tests -run-name: On Demand Keeper Smoke Tests ${{ inputs.distinct_run_name && inputs.distinct_run_name || '' }} -on: - workflow_dispatch: - inputs: - distinct_run_name: - description: 'A unique identifier for this run, only use from other repos' - required: false - type: string - -# Only run 1 of this workflow at a time per PR -concurrency: - group: on-demand-keeper-smoke-tests-${{ github.ref }}-${{ inputs.distinct_run_name }} - cancel-in-progress: true - -env: - # for run-test variables and environment - ENV_JOB_IMAGE: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink-tests:${{ inputs.evm-ref || github.sha }} - CHAINLINK_IMAGE: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink - TEST_SUITE: smoke - TEST_ARGS: -test.timeout 12m - INTERNAL_DOCKER_REPO: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com - MOD_CACHE_VERSION: 2 - COLLECTION_ID: chainlink-e2e-tests - -jobs: - build-chainlink: - environment: integration - permissions: - id-token: write - contents: read - strategy: - matrix: - image: - - name: "" - dockerfile: core/chainlink.Dockerfile - tag-suffix: "" - name: Build Chainlink Image ${{ matrix.image.name }} - runs-on: ubuntu22.04-16cores-64GB - steps: - - name: Collect Metrics - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: ${{ env.COLLECTION_ID }}-build-chainlink - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: Build Chainlink Image ${{ matrix.image.name }} - continue-on-error: true - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - repository: smartcontractkit/chainlink - ref: ${{ inputs.cl_ref || github.event.pull_request.head.sha || github.event.merge_group.head_sha }} - - name: Build Chainlink Image - uses: ./.github/actions/build-chainlink-image - with: - tag_suffix: ${{ matrix.image.tag-suffix }} - dockerfile: ${{ matrix.image.dockerfile }} - git_commit_sha: ${{ inputs.evm-ref || github.sha }} - AWS_REGION: ${{ secrets.QA_AWS_REGION }} - AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - dep_evm_sha: ${{ inputs.evm-ref }} - - compare-tests: - runs-on: ubuntu-latest - name: Build Automation Test List - outputs: - automation-matrix: ${{ env.AUTOMATION_JOB_MATRIX_JSON }} - steps: - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - repository: smartcontractkit/chainlink - ref: ${{ inputs.cl_ref }} - - name: Compare Test Lists - run: | - cd ./integration-tests - ./scripts/compareTestList.sh ./smoke/keeper_test.go - - name: Build Test Matrix Lists - id: build-test-matrix-list - run: | - cd ./integration-tests - KEEPER_JOB_MATRIX_JSON=$(./scripts/buildTestMatrixList.sh ./smoke/keeper_test.go keeper ubuntu-latest 1) - echo "AUTOMATION_JOB_MATRIX_JSON=${KEEPER_JOB_MATRIX_JSON}" >> $GITHUB_ENV - - eth-smoke-tests-matrix-automation: - if: ${{ !contains(join(github.event.pull_request.labels.*.name, ' '), 'skip-smoke-tests') }} - environment: integration - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - needs: [build-chainlink, compare-tests] - env: - SELECTED_NETWORKS: SIMULATED,SIMULATED_1,SIMULATED_2 - CHAINLINK_COMMIT_SHA: ${{ inputs.evm-ref || github.sha }} - CHAINLINK_ENV_USER: ${{ github.actor }} - TEST_LOG_LEVEL: debug - strategy: - fail-fast: false - matrix: - product: ${{fromJson(needs.compare-tests.outputs.automation-matrix)}} - runs-on: ${{ matrix.product.os }} - name: ETH Smoke Tests ${{ matrix.product.name }} - steps: - - name: Collect Metrics - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: ${{ env.COLLECTION_ID }}-matrix-${{ matrix.product.name }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - this-job-name: ETH Smoke Tests ${{ matrix.product.name }} - test-results-file: '{"testType":"go","filePath":"/tmp/gotest.log"}' - continue-on-error: true - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - repository: smartcontractkit/chainlink - ref: ${{ inputs.cl_ref || github.event.pull_request.head.sha || github.event.merge_group.head_sha }} - - name: Build Go Test Command - id: build-go-test-command - run: | - # if the matrix.product.run is set, use it for a different command - if [ "${{ matrix.product.run }}" != "" ]; then - echo "run_command=${{ matrix.product.run }} ./smoke/${{ matrix.product.file }}_test.go" >> "$GITHUB_OUTPUT" - else - echo "run_command=./smoke/${{ matrix.product.name }}_test.go" >> "$GITHUB_OUTPUT" - fi - - ## Run this step when changes that require tests to be run are made - - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@aa8eea635029ab8d95abd3c206f56dae1e22e623 # v2.3.28 - with: - test_command_to_run: cd ./integration-tests && go test -timeout 30m -count=1 -json -test.parallel=${{ matrix.product.nodes }} ${{ steps.build-go-test-command.outputs.run_command }} 2>&1 | tee /tmp/gotest.log | gotestloghelper -ci -singlepackage -hidepassingtests=false -hidepassinglogs - test_download_vendor_packages_command: cd ./integration-tests && go mod download - test_config_chainlink_version: ${{ inputs.evm-ref || github.sha }} - test_config_selected_networks: ${{ env.SELECTED_NETWORKS }} - test_config_logging_run_id: ${{ github.run_id }} - test_config_logstream_log_targets: ${{ vars.LOGSTREAM_LOG_TARGETS }} - test_config_test_log_collect: ${{ vars.TEST_LOG_COLLECT }} - cl_repo: ${{ env.CHAINLINK_IMAGE }} - cl_image_tag: ${{ inputs.evm-ref || github.sha }} - aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - artifacts_name: ${{ matrix.product.name }}-test-logs - artifacts_location: | - ./integration-tests/smoke/logs/ - /tmp/gotest.log - publish_check_name: ${{ matrix.product.name }} - token: ${{ secrets.GITHUB_TOKEN }} - go_mod_path: ./integration-tests/go.mod - cache_key_id: core-e2e-${{ env.MOD_CACHE_VERSION }} - cache_restore_only: "true" - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: "" - should_tidy: "false" - go_coverage_src_dir: /var/tmp/go-coverage - go_coverage_dest_dir: ${{ github.workspace }}/.covdata - DEFAULT_CHAINLINK_IMAGE: ${{ env.CHAINLINK_IMAGE }} - DEFAULT_LOKI_TENANT_ID: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - DEFAULT_LOKI_ENDPOINT: https://${{ secrets.GRAFANA_INTERNAL_HOST }}/loki/api/v1/push - DEFAULT_LOKI_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - DEFAULT_GRAFANA_BASE_URL: ${{ vars.GRAFANA_URL }} - DEFAULT_GRAFANA_DASHBOARD_URL: "/d/ddf75041-1e39-42af-aa46-361fe4c36e9e/ci-e2e-tests-logs" - DEFAULT_GRAFANA_BEARER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} - DEFAULT_PYROSCOPE_SERVER_URL: ${{ matrix.product.pyroscope_env == '' && '' || !startsWith(github.ref, 'refs/tags/') && '' || secrets.QA_PYROSCOPE_INSTANCE }} # Avoid sending blank envs https://github.com/orgs/community/discussions/25725 - DEFAULT_PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} - DEFAULT_PYROSCOPE_ENVIRONMENT: ${{ matrix.product.pyroscope_env }} - DEFAULT_PYROSCOPE_ENABLED: ${{ matrix.product.pyroscope_env == '' || !startsWith(github.ref, 'refs/tags/') && 'false' || 'true' }} - - - name: Upload Coverage Data - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 - with: - name: cl-node-coverage-data-${{ matrix.product.name }} - path: .covdata - retention-days: 1 - - - name: Print failed test summary - if: always() - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/show-test-summary@5dd916d08c03cb5f9a97304f4f174820421bb946 # v2.3.11 - - ### Used to check the required checks box when the matrix completes - eth-smoke-tests: - if: always() - runs-on: ubuntu-latest - name: ETH Smoke Tests - needs: [eth-smoke-tests-matrix-automation] - steps: - - name: Check smoke test matrix status - if: needs.eth-smoke-tests-matrix-automation.result != 'success' - run: | - echo "Automation: ${{ needs.eth-smoke-tests-matrix-automation.result }}" - exit 1 - - name: Collect Metrics - if: always() - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: ${{ env.COLLECTION_ID }}-matrix-results - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: ETH Smoke Tests - matrix-aggregator-status: ${{ needs.eth-smoke-tests-matrix.result }} - continue-on-error: true - - cleanup: - name: Clean up integration environment deployments - if: always() - needs: [eth-smoke-tests] - runs-on: ubuntu-latest - steps: - - name: Checkout repo - if: ${{ github.event_name == 'pull_request' }} - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - repository: smartcontractkit/chainlink - ref: ${{ inputs.cl_ref }} - - - name: 🧼 Clean up Environment - if: ${{ github.event_name == 'pull_request' }} - uses: ./.github/actions/delete-deployments - with: - environment: integration - ref: ${{ github.head_ref }} # See https://github.com/github/docs/issues/15319#issuecomment-1476705663 - - - name: Collect Metrics - if: ${{ github.event_name == 'pull_request' }} - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: ${{ env.COLLECTION_ID }}-env-cleanup - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: Clean up integration environment deployments - continue-on-error: true - - show-coverage: - name: Show Chainlink Node Go Coverage - if: always() - needs: [cleanup] - runs-on: ubuntu-latest - steps: - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - repository: smartcontractkit/chainlink - ref: ${{ inputs.cl_ref || github.event.pull_request.head.sha || github.event.merge_group.head_sha }} - - name: Download All Artifacts - uses: actions/download-artifact@9c19ed7fe5d278cd354c7dfd5d3b88589c7e2395 # v4.1.6 - with: - path: cl-node-coverage-data - pattern: cl-node-coverage-data-* - merge-multiple: true - - name: Show Coverage - run: go run ./integration-tests/scripts/show_coverage.go "${{ github.workspace }}/cl-node-coverage-data/*/merged" - - # Run the setup if the matrix finishes but this time save the cache if we have a cache hit miss - # this will also only run if both of the matrix jobs pass - eth-smoke-go-mod-cache: - - environment: integration - needs: [eth-smoke-tests] - runs-on: ubuntu-latest - name: ETH Smoke Tests Go Mod Cache - continue-on-error: true - steps: - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - repository: smartcontractkit/chainlink - ref: ${{ inputs.cl_ref || github.event.pull_request.head.sha || github.event.merge_group.head_sha }} - - name: Run Setup - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/setup-go@5dd916d08c03cb5f9a97304f4f174820421bb946 # v2.3.11 - with: - test_download_vendor_packages_command: | - cd ./integration-tests - go mod download - # force download of test dependencies - go test -run=NonExistentTest ./smoke/... || echo "ignore expected test failure" - go_mod_path: ./integration-tests/go.mod - cache_key_id: core-e2e-${{ env.MOD_CACHE_VERSION }} - cache_restore_only: "false" diff --git a/.github/workflows/on-demand-log-poller.yml b/.github/workflows/on-demand-log-poller.yml deleted file mode 100644 index 1685c7e455..0000000000 --- a/.github/workflows/on-demand-log-poller.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: On Demand Log Poller Consistency Test -on: - workflow_dispatch: - inputs: - base64Config: - description: base64-ed config - required: true - type: string - -jobs: - test: - env: - REF_NAME: ${{ github.head_ref || github.ref_name }} - runs-on: ubuntu22.04-8cores-32GB - steps: - - name: Add masks and export base64 config - run: | - BASE64_CONFIG_OVERRIDE=$(jq -r '.inputs.base64Config' $GITHUB_EVENT_PATH) - echo ::add-mask::$BASE64_CONFIG_OVERRIDE - echo "BASE64_CONFIG_OVERRIDE=$BASE64_CONFIG_OVERRIDE" >> $GITHUB_ENV - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - ref: ${{ env.REF_NAME }} - - name: Setup Go - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 - with: - go-version-file: "integration-tests/go.mod" - cache: true - - name: Run tests - run: | - cd integration-tests - go mod download - go test -v -timeout 5h -v -count=1 -run ^TestLogPollerFewFiltersFixedDepth$ ./smoke/log_poller_test.go diff --git a/.github/workflows/on-demand-ocr-soak-test.yml b/.github/workflows/on-demand-ocr-soak-test.yml index 924b43829e..978c1eb67d 100644 --- a/.github/workflows/on-demand-ocr-soak-test.yml +++ b/.github/workflows/on-demand-ocr-soak-test.yml @@ -3,114 +3,67 @@ on: workflow_dispatch: inputs: testToRun: - description: Select a test to run + description: Select a test to run from .github/e2e-tests.yml required: true default: TestOCRSoak type: choice options: - - TestOCRv1Soak - - TestOCRv2Soak - - TestForwarderOCRv1Soak - - TestForwarderOCRv2Soak - - TestOCRSoak_GethReorgBelowFinality_FinalityTagDisabled - - TestOCRSoak_GethReorgBelowFinality_FinalityTagEnabled - - TestOCRSoak_GasSpike - - TestOCRSoak_ChangeBlockGasLimit - - TestOCRSoak_RPCDownForAllCLNodes - - TestOCRSoak_RPCDownForHalfCLNodes - base64Config: - description: base64-ed config + - soak/ocr_test.go:TestOCRv1Soak + - soak/ocr_test.go:TestOCRv2Soak + - soak/ocr_test.go:TestForwarderOCRv1Soak + - soak/ocr_test.go:TestForwarderOCRv2Soak + - soak/ocr_test.go:TestOCRSoak_GethReorgBelowFinality_FinalityTagDisabled + - soak/ocr_test.go:TestOCRSoak_GethReorgBelowFinality_FinalityTagEnabled + - soak/ocr_test.go:TestOCRSoak_GasSpike + - soak/ocr_test.go:TestOCRSoak_ChangeBlockGasLimit + - soak/ocr_test.go:TestOCRSoak_RPCDownForAllCLNodes + - soak/ocr_test.go:TestOCRSoak_RPCDownForHalfCLNodes + test_config_override_path: + description: Path to a test config file used to override the default test config + required: false + type: string + test_secrets_override_key: + description: 'Key to run tests with custom test secrets' + required: false + type: string + chainlink_version: + description: Chainlink image version to use + default: develop required: true type: string slackMemberID: description: Slack Member ID (Not your @) required: true - default: U01A2B2C3D4 - type: string - test_secrets_override_key: - description: 'Key to run tests with custom test secrets' - required: false - type: string + default: U01A2B2C3D4 jobs: - ocr_soak_test: - name: OCR Soak Test - environment: integration - runs-on: ubuntu-latest - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - env: - CHAINLINK_ENV_USER: ${{ github.actor }} + run-e2e-tests-workflow: + name: Run E2E Tests + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@aad83f232743646faa35f5ac03ee3829148d37ce + with: + test_path: .github/e2e-tests.yml + test_ids: ${{ inputs.testToRun}} + test_config_override_path: ${{ inputs.test_config_override_path }} + chainlink_version: ${{ inputs.chainlink_version }} + SLACK_USER: ${{ inputs.slackMemberID }} + secrets: + QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} + QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} + QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} + QA_PYROSCOPE_INSTANCE: ${{ secrets.QA_PYROSCOPE_INSTANCE }} + QA_PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} + QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} + GRAFANA_INTERNAL_TENANT_ID: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} + GRAFANA_INTERNAL_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} + GRAFANA_INTERNAL_HOST: ${{ secrets.GRAFANA_INTERNAL_HOST }} + GRAFANA_INTERNAL_URL_SHORTENER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} + LOKI_TENANT_ID: ${{ secrets.LOKI_TENANT_ID }} + LOKI_URL: ${{ secrets.LOKI_URL }} + LOKI_BASIC_AUTH: ${{ secrets.LOKI_BASIC_AUTH }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + AWS_REGION: ${{ secrets.QA_AWS_REGION }} + AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} + AWS_API_GW_HOST_GRAFANA: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} + TEST_SECRETS_OVERRIDE_BASE64: ${{ secrets[inputs.test_secrets_override_key] }} SLACK_API_KEY: ${{ secrets.QA_SLACK_API_KEY }} SLACK_CHANNEL: ${{ secrets.QA_SLACK_CHANNEL }} - TEST_LOG_LEVEL: debug - REF_NAME: ${{ github.head_ref || github.ref_name }} - ENV_JOB_IMAGE_BASE: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink-tests - steps: - - name: Collect Metrics - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: on-demand-ocr-soak-test - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: ${{ inputs.network }} OCR Soak Test - continue-on-error: true - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - ref: ${{ env.REF_NAME }} - - name: Get Slack config and mask base64 config - run: | - SLACK_USER=$(jq -r '.inputs.slackMemberID' $GITHUB_EVENT_PATH) - echo ::add-mask::$SLACK_USER - echo SLACK_USER=$SLACK_USER >> $GITHUB_ENV - - BASE64_CONFIG_OVERRIDE=$(jq -r '.inputs.base64Config' $GITHUB_EVENT_PATH) - echo ::add-mask::$BASE64_CONFIG_OVERRIDE - echo "BASE64_CONFIG_OVERRIDE=$BASE64_CONFIG_OVERRIDE" >> $GITHUB_ENV - - name: Parse base64 config - uses: ./.github/actions/setup-parse-base64-config - with: - base64Config: ${{ env.BASE64_CONFIG_OVERRIDE }} - - name: Setup Push Tag - shell: bash - run: | - echo "### chainlink image used for this test run :link:" >>$GITHUB_STEP_SUMMARY - echo "\`${{ env.CHAINLINK_IMAGE }}\`" >>$GITHUB_STEP_SUMMARY - echo "### chainlink-tests image tag for this test run :ship:" >>$GITHUB_STEP_SUMMARY - echo "\`${GITHUB_SHA}\`" >>$GITHUB_STEP_SUMMARY - echo "### Networks on which test was run" >>$GITHUB_STEP_SUMMARY - echo "\`${{ env.NETWORKS }}\`" >>$GITHUB_STEP_SUMMARY - - name: Build Image - uses: ./.github/actions/build-test-image - with: - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@2967f2287bd3f3ddbac7b476e9568993df01796e # v2.3.27 - env: - DETACH_RUNNER: true - TEST_SUITE: soak - TEST_ARGS: -test.timeout 900h -test.memprofile memprofile.out -test.cpuprofile profile.out - ENV_JOB_IMAGE: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink-tests:${{ github.sha }} - # We can comment these out when we have a stable soak test and aren't worried about resource consumption - TEST_UPLOAD_CPU_PROFILE: true - TEST_UPLOAD_MEM_PROFILE: true - with: - test_command_to_run: cd ./integration-tests && go test -v -count=1 -run ^${{ github.event.inputs.testToRun }}$ ./soak - test_download_vendor_packages_command: make gomod - test_secrets_override_base64: ${{ secrets[inputs.test_secrets_override_key] }} - cl_repo: ${{ env.CHAINLINK_IMAGE }} - cl_image_tag: ${{ env.CHAINLINK_VERSION }} - token: ${{ secrets.GITHUB_TOKEN }} - should_cleanup: false - go_mod_path: ./integration-tests/go.mod - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} diff --git a/.github/workflows/on-demand-vrfv2-eth2-clients-test.yml b/.github/workflows/on-demand-vrfv2-eth2-clients-test.yml deleted file mode 100644 index 5f24fa81c3..0000000000 --- a/.github/workflows/on-demand-vrfv2-eth2-clients-test.yml +++ /dev/null @@ -1,75 +0,0 @@ -name: On Demand VRFV2 Smoke Test (Ethereum clients) -on: - workflow_dispatch: - inputs: - base64Config: - description: base64-ed config - required: true - type: string - test_secrets_override_key: - description: 'Key to run tests with custom test secrets' - required: false - type: string - -jobs: - vrfv2_smoke_test: - name: VRFV2 Smoke Test with custom EL client client - environment: integration - runs-on: ubuntu22.04-8cores-32GB - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - env: - TEST_LOG_LEVEL: debug - REF_NAME: ${{ github.head_ref || github.ref_name }} - steps: - - name: Checkout code - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - fetch-depth: 0 - - name: Mask base64 config - run: | - BASE64_CONFIG_OVERRIDE=$(jq -r '.inputs.base64Config' $GITHUB_EVENT_PATH) - echo ::add-mask::$BASE64_CONFIG_OVERRIDE - echo "BASE64_CONFIG_OVERRIDE=$BASE64_CONFIG_OVERRIDE" >> $GITHUB_ENV - - name: Parse base64 config - uses: ./.github/actions/setup-parse-base64-config - with: - base64Config: ${{ env.BASE64_CONFIG_OVERRIDE }} - - name: Send details to Step Summary - shell: bash - run: | - echo "### chainlink image used for this test run :link:" >>$GITHUB_STEP_SUMMARY - echo "\`${{ env.CHAINLINK_IMAGE }}\`" >>$GITHUB_STEP_SUMMARY - echo "### chainlink-tests image tag for this test run :ship:" >>$GITHUB_STEP_SUMMARY - echo "\`${GITHUB_SHA}\`" >>$GITHUB_STEP_SUMMARY - echo "### Networks on which test was run" >>$GITHUB_STEP_SUMMARY - echo "\`${{ env.NETWORKS }}\`" >>$GITHUB_STEP_SUMMARY - echo "### Execution client used" >>$GITHUB_STEP_SUMMARY - echo "\`${{ env.ETH2_EL_CLIENT }}\`" >>$GITHUB_STEP_SUMMARY - - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@2967f2287bd3f3ddbac7b476e9568993df01796e # v2.3.27 - with: - test_command_to_run: cd ./integration-tests && go test -timeout 30m -count=1 -json -run TestVRFv2Basic ./smoke/vrfv2_test.go 2>&1 | tee /tmp/gotest.log | gotestloghelper -ci -singlepackage -hidepassingtests=false -hidepassinglogs - test_download_vendor_packages_command: cd ./integration-tests && go mod download - test_secrets_override_base64: ${{ secrets[inputs.test_secrets_override_key] }} - cl_repo: ${{ env.CHAINLINK_IMAGE }} - cl_image_tag: ${{ env.CHAINLINK_VERSION }} - aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - artifacts_name: vrf-test-logs - artifacts_location: ./integration-tests/smoke/logs/ - token: ${{ secrets.GITHUB_TOKEN }} - go_mod_path: ./integration-tests/go.mod - should_cleanup: false - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: "" - DEFAULT_CHAINLINK_IMAGE: ${{ env.CHAINLINK_IMAGE }} - DEFAULT_LOKI_TENANT_ID: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - DEFAULT_LOKI_ENDPOINT: https://${{ secrets.GRAFANA_INTERNAL_HOST }}/loki/api/v1/push - DEFAULT_LOKI_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - DEFAULT_GRAFANA_BASE_URL: "http://localhost:8080/primary" - DEFAULT_GRAFANA_DASHBOARD_URL: "/d/ddf75041-1e39-42af-aa46-361fe4c36e9e/ci-e2e-tests-logs" - DEFAULT_GRAFANA_BEARER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/on-demand-vrfv2-performance-test.yml b/.github/workflows/on-demand-vrfv2-performance-test.yml index 3d55c38458..1f1a847d82 100644 --- a/.github/workflows/on-demand-vrfv2-performance-test.yml +++ b/.github/workflows/on-demand-vrfv2-performance-test.yml @@ -2,10 +2,6 @@ name: On Demand VRFV2 Performance Test on: workflow_dispatch: inputs: - base64Config: - description: base64-ed config - required: true - type: string performanceTestType: description: Performance Test Type of test to run type: choice @@ -19,81 +15,84 @@ on: description: "Regex for tests to run" required: false default: "(TestVRFV2Performance)" + test_config_override_path: + description: Path to a test config file used to override the default test config + required: false + type: string test_secrets_override_key: description: 'Key to run tests with custom test secrets' required: false type: string - + notify_user_id_on_failure: + description: 'Enter Slack user ID to notify on test failure' + required: false + type: string + jobs: - vrfv2_performance_test: - name: VRFV2 Performance Test - environment: integration - runs-on: ubuntu22.04-8cores-32GB - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - env: - LOKI_URL: ${{ secrets.LOKI_URL }} + set-tests-to-run: + name: Set tests to run + runs-on: ubuntu-latest + outputs: + test_list: ${{ steps.set-tests.outputs.test_list }} + steps: + - name: Generate Test List JSON + id: set-tests + run: | + TEST_CMD='cd integration-tests/load && go test -v -count=1 -timeout 24h -run "${{ inputs.test_list_regex }}" ./vrfv2' + TEST_CONFIG_OVERRIDE_PATH=${{ inputs.test_config_override_path }} + TEST_TYPE=${{ inputs.performanceTestType }} + + TEST_LIST=$(jq -n -c \ + --arg test_cmd "$TEST_CMD" \ + --arg test_config_override_path "$TEST_CONFIG_OVERRIDE_PATH" \ + --arg TEST_TYPE "$TEST_TYPE" \ + '{ + "tests": [ + { + "id": "TestVRFv2Plus_Performance", + "path": "integration-tests/load/vrfv2plus/vrfv2plus_test.go", + "runs_on": "ubuntu22.04-8cores-32GB", + "test_env_type": "docker", + "test_cmd": $test_cmd, + "test_config_override_path": $test_config_override_path, + "test_env_vars": { + "TEST_TYPE": $TEST_TYPE + } + } + ] + }') + + echo "test_list=$TEST_LIST" >> $GITHUB_OUTPUT + + run-e2e-tests-workflow: + name: Run E2E Tests + needs: set-tests-to-run + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@aad83f232743646faa35f5ac03ee3829148d37ce + with: + custom_test_list_json: ${{ needs.set-tests-to-run.outputs.test_list }} + chainlink_version: ${{ inputs.chainlink_version }} + slack_notification_after_tests: always + slack_notification_after_tests_name: "VRF V2 Performance Tests with test config: ${{ inputs.test_config_override_path || 'default' }}" + slack_notification_after_tests_notify_user_id_on_failure: ${{ inputs.notify_user_id_on_failure }} + secrets: + QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} + QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} + QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} + QA_PYROSCOPE_INSTANCE: ${{ secrets.QA_PYROSCOPE_INSTANCE }} + QA_PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} + QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} + GRAFANA_INTERNAL_TENANT_ID: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} + GRAFANA_INTERNAL_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} + GRAFANA_INTERNAL_HOST: ${{ secrets.GRAFANA_INTERNAL_HOST }} + GRAFANA_INTERNAL_URL_SHORTENER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} LOKI_TENANT_ID: ${{ secrets.LOKI_TENANT_ID }} + LOKI_URL: ${{ secrets.LOKI_URL }} LOKI_BASIC_AUTH: ${{ secrets.LOKI_BASIC_AUTH }} - TEST_TYPE: ${{ inputs.performanceTestType }} - TEST_LOG_LEVEL: debug - REF_NAME: ${{ github.head_ref || github.ref_name }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + AWS_REGION: ${{ secrets.QA_AWS_REGION }} + AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} + AWS_API_GW_HOST_GRAFANA: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} + TEST_SECRETS_OVERRIDE_BASE64: ${{ secrets[inputs.test_secrets_override_key] }} + SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} SLACK_API_KEY: ${{ secrets.QA_SLACK_API_KEY }} - SLACK_CHANNEL: ${{ secrets.QA_VRF_SLACK_CHANNEL }} - GRAFANA_URL: "http://localhost:8080/primary" - GRAFANA_DASHBOARD_URL: "/d/ddf75041-1e39-42af-aa46-361fe4c36e9e/ci-e2e-tests-logs" - GRAFANA_BEARER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} - WASP_LOG_LEVEL: info - steps: - - name: Collect Metrics - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: on-demand-vrfv2-performance-test - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: ${{ inputs.network }} VRFV2 Performance Test - continue-on-error: true - - name: Checkout code - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - fetch-depth: 0 - - name: Mask base64 config - run: | - BASE64_CONFIG_OVERRIDE=$(jq -r '.inputs.base64Config' $GITHUB_EVENT_PATH) - echo ::add-mask::$BASE64_CONFIG_OVERRIDE - echo "BASE64_CONFIG_OVERRIDE=$BASE64_CONFIG_OVERRIDE" >> $GITHUB_ENV - - name: Merge and export base64 config - uses: ./.github/actions/setup-merge-base64-config - with: - base64Config: ${{ env.BASE64_CONFIG_OVERRIDE }} - - name: Send details to Step Summary - shell: bash - run: | - echo "### chainlink image used for this test run :link:" >>$GITHUB_STEP_SUMMARY - echo "\`${{ env.CHAINLINK_IMAGE }}\`" >>$GITHUB_STEP_SUMMARY - echo "### chainlink-tests image tag for this test run :ship:" >>$GITHUB_STEP_SUMMARY - echo "\`${GITHUB_SHA}\`" >>$GITHUB_STEP_SUMMARY - echo "### Networks on which test was run" >>$GITHUB_STEP_SUMMARY - echo "\`${{ env.NETWORKS }}\`" >>$GITHUB_STEP_SUMMARY - - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@2967f2287bd3f3ddbac7b476e9568993df01796e # v2.3.27 - with: - test_command_to_run: cd ./integration-tests/load && go test -v -count=1 -timeout 24h -run "${{ inputs.test_list_regex }}" ./vrfv2 - test_download_vendor_packages_command: cd ./integration-tests && go mod download - test_secrets_override_base64: ${{ secrets[inputs.test_secrets_override_key] }} - cl_repo: ${{ env.CHAINLINK_IMAGE }} - cl_image_tag: ${{ env.CHAINLINK_VERSION }} - aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - artifacts_name: vrf-test-logs - artifacts_location: ./integration-tests/load/vrfv2/logs/ - token: ${{ secrets.GITHUB_TOKEN }} - go_mod_path: ./integration-tests/go.mod - should_cleanup: false - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} + SLACK_CHANNEL: ${{ secrets.QA_VRF_SLACK_CHANNEL }} diff --git a/.github/workflows/on-demand-vrfv2-smoke-tests.yml b/.github/workflows/on-demand-vrfv2-smoke-tests.yml new file mode 100644 index 0000000000..9f77c7ab53 --- /dev/null +++ b/.github/workflows/on-demand-vrfv2-smoke-tests.yml @@ -0,0 +1,100 @@ +name: On Demand VRFV2 Smoke Tests +on: + workflow_dispatch: + inputs: + test_suite: + description: "Test Suite to run" + required: true + type: choice + default: "All Tests" + options: + - "All Tests" + - "Selected Tests" + test_list_regex: + description: "Regex for 'Selected Tests' to run" + required: false + default: "TestVRFv2Basic/(Request_Randomness|Direct_Funding)|TestVRFV2WithBHS" + test_config_override_path: + description: Path to a test config file used to override the default test config + required: false + type: string + test_secrets_override_key: + description: 'Key to run tests with custom test secrets' + required: false + type: string + chainlink_version: + description: Chainlink image version to use + default: develop + required: false + type: string + notify_user_id_on_failure: + description: 'Enter Slack user ID to notify on test failure' + required: false + type: string + +jobs: + set-tests-to-run: + name: Set tests to run + runs-on: ubuntu-latest + outputs: + test_list: ${{ steps.set-tests.outputs.test_list }} + steps: + - name: Generate Test List JSON + id: set-tests + run: | + if [[ "${{ inputs.test_suite }}" == "All Tests" ]]; then + TEST_CMD="cd integration-tests/smoke && go test vrfv2_test.go -test.parallel=1 -timeout 3h -count=1 -json -v" + else + TEST_CMD='cd integration-tests/smoke && go test -test.run "${{ inputs.test_list_regex }}" -test.parallel=1 -timeout 2h -count=1 -json -v' + fi + TEST_CONFIG_OVERRIDE_PATH=${{ inputs.test_config_override_path }} + + TEST_LIST=$(jq -n -c \ + --arg test_cmd "$TEST_CMD" \ + --arg test_config_override_path "$TEST_CONFIG_OVERRIDE_PATH" \ + '{ + "tests": [ + { + "id": "TestVRFv2_Smoke", + "path": "integration-tests/smoke/vrfv2_test.go", + "runs_on": "ubuntu-latest", + "test_env_type": "docker", + "test_cmd": $test_cmd, + "test_config_override_path": $test_config_override_path + } + ] + }') + + echo "test_list=$TEST_LIST" >> $GITHUB_OUTPUT + + run-e2e-tests-workflow: + name: Run E2E Tests + needs: set-tests-to-run + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@aad83f232743646faa35f5ac03ee3829148d37ce + with: + custom_test_list_json: ${{ needs.set-tests-to-run.outputs.test_list }} + chainlink_version: ${{ inputs.chainlink_version }} + slack_notification_after_tests: always + slack_notification_after_tests_name: "VRF V2 Smoke Tests with test config: ${{ inputs.test_config_override_path || 'default' }}" + slack_notification_after_tests_notify_user_id_on_failure: ${{ inputs.notify_user_id_on_failure }} + secrets: + QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} + QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} + QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} + QA_PYROSCOPE_INSTANCE: ${{ secrets.QA_PYROSCOPE_INSTANCE }} + QA_PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} + QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} + GRAFANA_INTERNAL_TENANT_ID: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} + GRAFANA_INTERNAL_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} + GRAFANA_INTERNAL_HOST: ${{ secrets.GRAFANA_INTERNAL_HOST }} + GRAFANA_INTERNAL_URL_SHORTENER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} + LOKI_TENANT_ID: ${{ secrets.LOKI_TENANT_ID }} + LOKI_URL: ${{ secrets.LOKI_URL }} + LOKI_BASIC_AUTH: ${{ secrets.LOKI_BASIC_AUTH }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + AWS_REGION: ${{ secrets.QA_AWS_REGION }} + AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} + AWS_API_GW_HOST_GRAFANA: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} + TEST_SECRETS_OVERRIDE_BASE64: ${{ secrets[inputs.test_secrets_override_key] }} + SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} + SLACK_NOTIFICATION_AFTER_TESTS_CHANNEL_ID: ${{ secrets.QA_VRF_SLACK_CHANNEL }} diff --git a/.github/workflows/on-demand-vrfv2plus-eth2-clients-test.yml b/.github/workflows/on-demand-vrfv2plus-eth2-clients-test.yml deleted file mode 100644 index 58ecd39763..0000000000 --- a/.github/workflows/on-demand-vrfv2plus-eth2-clients-test.yml +++ /dev/null @@ -1,75 +0,0 @@ -name: On Demand VRFV2Plus Smoke Test (Ethereum clients) -on: - workflow_dispatch: - inputs: - base64Config: - description: base64-ed config - required: true - type: string - test_secrets_override_key: - description: 'Key to run tests with custom test secrets' - required: false - type: string - -jobs: - vrfv2plus_smoke_test: - name: VRFV2Plus Smoke Test with custom EL client - environment: integration - runs-on: ubuntu22.04-8cores-32GB - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - env: - TEST_LOG_LEVEL: debug - REF_NAME: ${{ github.head_ref || github.ref_name }} - steps: - - name: Checkout code - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - fetch-depth: 0 - - name: Mask base64 config - run: | - BASE64_CONFIG_OVERRIDE=$(jq -r '.inputs.base64Config' $GITHUB_EVENT_PATH) - echo ::add-mask::$BASE64_CONFIG_OVERRIDE - echo "BASE64_CONFIG_OVERRIDE=$BASE64_CONFIG_OVERRIDE" >> $GITHUB_ENV - - name: Parse base64 config - uses: ./.github/actions/setup-parse-base64-config - with: - base64Config: ${{ env.BASE64_CONFIG_OVERRIDE }} - - name: Send details to Step Summary - shell: bash - run: | - echo "### chainlink image used for this test run :link:" >>$GITHUB_STEP_SUMMARY - echo "\`${{ env.CHAINLINK_IMAGE }}\`" >>$GITHUB_STEP_SUMMARY - echo "### chainlink-tests image tag for this test run :ship:" >>$GITHUB_STEP_SUMMARY - echo "\`${GITHUB_SHA}\`" >>$GITHUB_STEP_SUMMARY - echo "### Networks on which test was run" >>$GITHUB_STEP_SUMMARY - echo "\`${{ env.NETWORKS }}\`" >>$GITHUB_STEP_SUMMARY - echo "### Execution client used" >>$GITHUB_STEP_SUMMARY - echo "\`${{ env.ETH2_EL_CLIENT }}\`" >>$GITHUB_STEP_SUMMARY - - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@2967f2287bd3f3ddbac7b476e9568993df01796e # v2.3.27 - with: - test_command_to_run: cd ./integration-tests && go test -timeout 30m -count=1 -json -run ^TestVRFv2Plus$/^Link_Billing$ ./smoke/vrfv2plus_test.go 2>&1 | tee /tmp/gotest.log | gotestloghelper -ci -singlepackage -hidepassingtests=false -hidepassinglogs - test_download_vendor_packages_command: cd ./integration-tests && go mod download - test_secrets_override_base64: ${{ secrets[inputs.test_secrets_override_key] }} - cl_repo: ${{ env.CHAINLINK_IMAGE }} - cl_image_tag: ${{ env.CHAINLINK_VERSION }} - aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - artifacts_name: vrfplus-test-logs - artifacts_location: ./integration-tests/smoke/logs/ - token: ${{ secrets.GITHUB_TOKEN }} - go_mod_path: ./integration-tests/go.mod - should_cleanup: false - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: "" - DEFAULT_CHAINLINK_IMAGE: ${{ env.CHAINLINK_IMAGE }} - DEFAULT_LOKI_TENANT_ID: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - DEFAULT_LOKI_ENDPOINT: https://${{ secrets.GRAFANA_INTERNAL_HOST }}/loki/api/v1/push - DEFAULT_LOKI_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - DEFAULT_GRAFANA_BASE_URL: "http://localhost:8080/primary" - DEFAULT_GRAFANA_DASHBOARD_URL: "/d/ddf75041-1e39-42af-aa46-361fe4c36e9e/ci-e2e-tests-logs" - DEFAULT_GRAFANA_BEARER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/on-demand-vrfv2plus-performance-test.yml b/.github/workflows/on-demand-vrfv2plus-performance-test.yml index 658736ab03..ae42c32945 100644 --- a/.github/workflows/on-demand-vrfv2plus-performance-test.yml +++ b/.github/workflows/on-demand-vrfv2plus-performance-test.yml @@ -2,10 +2,6 @@ name: On Demand VRFV2 Plus Performance Test on: workflow_dispatch: inputs: - base64Config: - description: base64-ed config - required: true - type: string performanceTestType: description: Performance Test Type of test to run type: string @@ -14,81 +10,90 @@ on: description: "Regex for tests to run" required: false default: "(TestVRFV2PlusPerformance)" + test_config_override_path: + description: Path to a test config file used to override the default test config + required: false + type: string test_secrets_override_key: description: 'Key to run tests with custom test secrets' required: false type: string + chainlink_version: + description: Chainlink image version to use + default: develop + required: false + type: string + notify_user_id_on_failure: + description: 'Enter Slack user ID to notify on test failure' + required: false + type: string jobs: - vrfv2plus_performance_test: - name: VRFV2 Plus Performance Test - environment: integration - runs-on: ubuntu22.04-8cores-32GB - permissions: - checks: write - pull-requests: write - id-token: write - contents: read - env: - LOKI_URL: ${{ secrets.LOKI_URL }} + set-tests-to-run: + name: Set tests to run + runs-on: ubuntu-latest + outputs: + test_list: ${{ steps.set-tests.outputs.test_list }} + steps: + - name: Generate Test List JSON + id: set-tests + run: | + TEST_CMD='cd integration-tests/load && go test -v -count=1 -timeout 24h -run "${{ inputs.test_list_regex }}" ./vrfv2plus' + TEST_CONFIG_OVERRIDE_PATH=${{ inputs.test_config_override_path }} + TEST_TYPE=${{ inputs.performanceTestType }} + + TEST_LIST=$(jq -n -c \ + --arg test_cmd "$TEST_CMD" \ + --arg test_config_override_path "$TEST_CONFIG_OVERRIDE_PATH" \ + --arg TEST_TYPE "$TEST_TYPE" \ + '{ + "tests": [ + { + "id": "TestVRFv2Plus_Performance", + "path": "integration-tests/load/vrfv2plus/vrfv2plus_test.go", + "runs_on": "ubuntu-latest", + "test_env_type": "docker", + "test_cmd": $test_cmd, + "test_config_override_path": $test_config_override_path, + "test_env_vars": { + "TEST_TYPE": $TEST_TYPE + } + } + ] + }') + + echo "test_list=$TEST_LIST" >> $GITHUB_OUTPUT + + run-e2e-tests-workflow: + name: Run E2E Tests + needs: set-tests-to-run + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@aad83f232743646faa35f5ac03ee3829148d37ce + with: + custom_test_list_json: ${{ needs.set-tests-to-run.outputs.test_list }} + chainlink_version: ${{ inputs.chainlink_version }} + slack_notification_after_tests: always + slack_notification_after_tests_name: "VRF V2 Plus Performance Tests with test config: ${{ inputs.test_config_override_path || 'default' }}" + slack_notification_after_tests_notify_user_id_on_failure: ${{ inputs.notify_user_id_on_failure }} + secrets: + QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} + QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} + QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} + QA_PYROSCOPE_INSTANCE: ${{ secrets.QA_PYROSCOPE_INSTANCE }} + QA_PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} + QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} + GRAFANA_INTERNAL_TENANT_ID: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} + GRAFANA_INTERNAL_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} + GRAFANA_INTERNAL_HOST: ${{ secrets.GRAFANA_INTERNAL_HOST }} + GRAFANA_INTERNAL_URL_SHORTENER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} LOKI_TENANT_ID: ${{ secrets.LOKI_TENANT_ID }} + LOKI_URL: ${{ secrets.LOKI_URL }} LOKI_BASIC_AUTH: ${{ secrets.LOKI_BASIC_AUTH }} - TEST_TYPE: ${{ inputs.performanceTestType }} - TEST_LOG_LEVEL: debug - REF_NAME: ${{ github.head_ref || github.ref_name }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + AWS_REGION: ${{ secrets.QA_AWS_REGION }} + AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} + AWS_API_GW_HOST_GRAFANA: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} + TEST_SECRETS_OVERRIDE_BASE64: ${{ secrets[inputs.test_secrets_override_key] }} + SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} + SLACK_NOTIFICATION_AFTER_TESTS_CHANNEL_ID: ${{ secrets.QA_VRF_SLACK_CHANNEL }} SLACK_API_KEY: ${{ secrets.QA_SLACK_API_KEY }} - SLACK_CHANNEL: ${{ secrets.QA_VRF_SLACK_CHANNEL }} - GRAFANA_URL: "http://localhost:8080/primary" - GRAFANA_DASHBOARD_URL: "/d/ddf75041-1e39-42af-aa46-361fe4c36e9e/ci-e2e-tests-logs" - GRAFANA_BEARER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} - WASP_LOG_LEVEL: info - steps: - - name: Collect Metrics - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: on-demand-vrfv2-plus-performance-test - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: ${{ inputs.network }} VRFV2 Plus Performance Test - continue-on-error: true - - name: Checkout code - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - fetch-depth: 0 - - name: Mask base64 config - run: | - BASE64_CONFIG_OVERRIDE=$(jq -r '.inputs.base64Config' $GITHUB_EVENT_PATH) - echo ::add-mask::$BASE64_CONFIG_OVERRIDE - echo "BASE64_CONFIG_OVERRIDE=$BASE64_CONFIG_OVERRIDE" >> $GITHUB_ENV - - name: Merge and export base64 config - uses: ./.github/actions/setup-merge-base64-config - with: - base64Config: ${{ env.BASE64_CONFIG_OVERRIDE }} - - name: Send details to Step Summary - shell: bash - run: | - echo "### chainlink image used for this test run :link:" >>$GITHUB_STEP_SUMMARY - echo "\`${{ env.CHAINLINK_IMAGE }}\`" >>$GITHUB_STEP_SUMMARY - echo "### chainlink-tests image tag for this test run :ship:" >>$GITHUB_STEP_SUMMARY - echo "\`${GITHUB_SHA}\`" >>$GITHUB_STEP_SUMMARY - echo "### Networks on which test was run" >>$GITHUB_STEP_SUMMARY - echo "\`${{ env.NETWORKS }}\`" >>$GITHUB_STEP_SUMMARY - - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@2967f2287bd3f3ddbac7b476e9568993df01796e # v2.3.27 - with: - test_command_to_run: cd ./integration-tests/load && go test -v -count=1 -timeout 24h -run "${{ inputs.test_list_regex }}" ./vrfv2plus - test_download_vendor_packages_command: cd ./integration-tests && go mod download - test_secrets_override_base64: ${{ secrets[inputs.test_secrets_override_key] }} - cl_repo: ${{ env.CHAINLINK_IMAGE }} - cl_image_tag: ${{ env.CHAINLINK_VERSION }} - aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - artifacts_name: vrf-test-logs - artifacts_location: ./integration-tests/load/vrfv2plus/logs/ - token: ${{ secrets.GITHUB_TOKEN }} - go_mod_path: ./integration-tests/go.mod - should_cleanup: false - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} + SLACK_CHANNEL: ${{ secrets.QA_VRF_SLACK_CHANNEL }} diff --git a/.github/workflows/on-demand-vrfv2plus-smoke-tests.yml b/.github/workflows/on-demand-vrfv2plus-smoke-tests.yml new file mode 100644 index 0000000000..9cd863eb7e --- /dev/null +++ b/.github/workflows/on-demand-vrfv2plus-smoke-tests.yml @@ -0,0 +1,100 @@ +name: On Demand VRFV2 Plus Smoke Tests +on: + workflow_dispatch: + inputs: + test_suite: + description: "Test Suite to run" + required: true + type: choice + default: "All Tests" + options: + - "All Tests" + - "Selected Tests" + test_list_regex: + description: "Regex for 'Selected Tests' to run" + required: false + default: "TestVRFv2Plus$/(Link_Billing|Native_Billing|Direct_Funding)|TestVRFV2PlusWithBHS" + test_config_override_path: + description: Path to a test config file used to override the default test config + required: false + type: string + test_secrets_override_key: + description: 'Key to run tests with custom test secrets' + required: false + type: string + chainlink_version: + description: Chainlink image version to use + default: develop + required: false + type: string + notify_user_id_on_failure: + description: 'Enter Slack user ID to notify on test failure' + required: false + type: string + +jobs: + set-tests-to-run: + name: Set tests to run + runs-on: ubuntu-latest + outputs: + test_list: ${{ steps.set-tests.outputs.test_list }} + steps: + - name: Generate Test List JSON + id: set-tests + run: | + if [[ "${{ inputs.test_suite }}" == "All Tests" ]]; then + TEST_CMD="cd integration-tests/smoke && go test vrfv2plus_test.go -test.parallel=1 -timeout 3h -count=1 -json -v" + else + TEST_CMD='cd integration-tests/smoke && go test -test.run "${{ inputs.test_list_regex }}" -test.parallel=1 -timeout 2h -count=1 -json -v' + fi + TEST_CONFIG_OVERRIDE_PATH=${{ inputs.test_config_override_path }} + + TEST_LIST=$(jq -n -c \ + --arg test_cmd "$TEST_CMD" \ + --arg test_config_override_path "$TEST_CONFIG_OVERRIDE_PATH" \ + '{ + "tests": [ + { + "id": "TestVRFv2Plus_Smoke", + "path": "integration-tests/smoke/vrfv2plus_test.go", + "runs_on": "ubuntu-latest", + "test_env_type": "docker", + "test_cmd": $test_cmd, + "test_config_override_path": $test_config_override_path + } + ] + }') + + echo "test_list=$TEST_LIST" >> $GITHUB_OUTPUT + + run-e2e-tests-workflow: + name: Run E2E Tests + needs: set-tests-to-run + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@aad83f232743646faa35f5ac03ee3829148d37ce + with: + custom_test_list_json: ${{ needs.set-tests-to-run.outputs.test_list }} + chainlink_version: ${{ inputs.chainlink_version }} + slack_notification_after_tests: always + slack_notification_after_tests_name: "VRF V2 Plus Smoke Tests with test config: ${{ inputs.test_config_override_path || 'default' }}" + slack_notification_after_tests_notify_user_id_on_failure: ${{ inputs.notify_user_id_on_failure }} + secrets: + QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} + QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} + QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} + QA_PYROSCOPE_INSTANCE: ${{ secrets.QA_PYROSCOPE_INSTANCE }} + QA_PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} + QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} + GRAFANA_INTERNAL_TENANT_ID: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} + GRAFANA_INTERNAL_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} + GRAFANA_INTERNAL_HOST: ${{ secrets.GRAFANA_INTERNAL_HOST }} + GRAFANA_INTERNAL_URL_SHORTENER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} + LOKI_TENANT_ID: ${{ secrets.LOKI_TENANT_ID }} + LOKI_URL: ${{ secrets.LOKI_URL }} + LOKI_BASIC_AUTH: ${{ secrets.LOKI_BASIC_AUTH }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + AWS_REGION: ${{ secrets.QA_AWS_REGION }} + AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} + AWS_API_GW_HOST_GRAFANA: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} + TEST_SECRETS_OVERRIDE_BASE64: ${{ secrets[inputs.test_secrets_override_key] }} + SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} + SLACK_NOTIFICATION_AFTER_TESTS_CHANNEL_ID: ${{ secrets.QA_VRF_SLACK_CHANNEL }} diff --git a/.github/workflows/run-automation-ondemand-e2e-tests.yml b/.github/workflows/run-automation-ondemand-e2e-tests.yml deleted file mode 100644 index 7bf4691ecc..0000000000 --- a/.github/workflows/run-automation-ondemand-e2e-tests.yml +++ /dev/null @@ -1,163 +0,0 @@ -name: Run Automation On Demand Tests (TEST WORKFLOW) - -on: - workflow_dispatch: - inputs: - chainlinkVersionUpdate: - description: Chainlink image version to upgrade to (Leave empty to build from head/ref) - required: false - type: string - chainlinkImageUpdate: - description: Chainlink image repo to upgrade to - options: - - QA_ECR - - public.ecr.aws/chainlink/chainlink - type: choice - chainlinkVersion: - description: Chainlink image version to use initially for upgrade test - default: latest - required: true - type: string - chainlinkImage: - description: Chainlink image repo to use initially for upgrade test - required: true - options: - - public.ecr.aws/chainlink/chainlink - - QA_ECR - type: choice - enableChaos: - description: Check to enable chaos tests - type: boolean - default: false - required: true - enableReorg: - description: Check to enable reorg tests - type: boolean - default: false - required: true - with_existing_remote_runner_version: - description: 'Tag of the existing remote runner version to use (Leave empty to build from head/ref)' - required: false - type: string - -jobs: - # Set tests to run based on the workflow inputs - set-tests-to-run: - name: Set tests to run - runs-on: ubuntu-latest - outputs: - test_list: ${{ steps.set-tests.outputs.test_list }} - require_chainlink_image_versions_in_qa_ecr: ${{ steps.determine-chainlink-image-check.outputs.require_chainlink_image_versions_in_qa_ecr }} - steps: - - name: Determine build to use - id: determine-build - shell: bash - run: | - if [[ "${{ inputs.chainlinkImage }}" == "QA_ECR" ]]; then - echo "image='{{ env.QA_CHAINLINK_IMAGE }}'" >>$GITHUB_OUTPUT - else - echo "image=${{ inputs.chainlinkImage }}" >>$GITHUB_OUTPUT - fi - if [[ "${{ inputs.chainlinkImageUpdate }}" == "QA_ECR" ]]; then - echo "upgrade_image='{{ env.QA_CHAINLINK_IMAGE }}'" >>$GITHUB_OUTPUT - else - echo "upgrade_image=${{ inputs.chainlinkImageUpdate }}" >>$GITHUB_OUTPUT - fi - if [[ -z "${{ inputs.chainlinkVersion }}" ]] && [[ "${{ inputs.chainlinkImage }}" == "QA_ECR" ]]; then - echo "version=${{ github.sha }}" >>$GITHUB_OUTPUT - else - echo "version=${{ inputs.chainlinkVersion }}" >>$GITHUB_OUTPUT - fi - if [[ -z "${{ inputs.chainlinkVersionUpdate }}" ]] && [[ "${{ inputs.chainlinkImageUpdate }}" == "QA_ECR" ]]; then - echo "upgrade_version=${{ github.sha }}" >>$GITHUB_OUTPUT - else - echo "upgrade_version=${{ inputs.chainlinkVersionUpdate }}" >>$GITHUB_OUTPUT - fi - - name: Check if chainlink image check required - id: determine-chainlink-image-check - shell: bash - run: | - chainlink_image_versions="" - if [ "${{ github.event.inputs.chainlinkImage }}" = "QA_ECR" ]; then - chainlink_image_versions+="${{ steps.determine-build.outputs.version }}," - fi - if [ "${{ github.event.inputs.chainlinkImageUpdate }}" = "QA_ECR" ]; then - chainlink_image_versions+="${{ steps.determine-build.outputs.upgrade_version }}" - fi - echo "require_chainlink_image_versions_in_qa_ecr=$chainlink_image_versions" >> $GITHUB_OUTPUT - - name: Set tests to run - id: set-tests - run: | - - # Always run upgrade tests - cat > test_list.yaml <> test_list.yaml <> test_list.yaml <> $GITHUB_OUTPUT - - call-run-e2e-tests-workflow: - name: Run E2E Tests - needs: set-tests-to-run - uses: ./.github/workflows/run-e2e-tests-reusable-workflow.yml - with: - test_list: ${{ needs.set-tests-to-run.outputs.test_list }} - require_chainlink_image_versions_in_qa_ecr: ${{ needs.set-tests-to-run.outputs.require_chainlink_image_versions_in_qa_ecr }} - with_existing_remote_runner_version: ${{ github.event.inputs.with_existing_remote_runner_version }} - test_log_upload_on_failure: true - test_log_upload_retention_days: 7 - secrets: - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - QA_PYROSCOPE_INSTANCE: ${{ secrets.QA_PYROSCOPE_INSTANCE }} - QA_PYROSCOPE_KEY: ${{ secrets.QA_PYROSCOPE_KEY }} - QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} - GRAFANA_INTERNAL_TENANT_ID: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - GRAFANA_INTERNAL_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - GRAFANA_INTERNAL_HOST: ${{ secrets.GRAFANA_INTERNAL_HOST }} - GRAFANA_INTERNAL_URL_SHORTENER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - AWS_REGION: ${{ secrets.QA_AWS_REGION }} - AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} - AWS_API_GW_HOST_GRAFANA: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} - diff --git a/.github/workflows/run-e2e-tests-reusable-workflow.yml b/.github/workflows/run-e2e-tests-reusable-workflow.yml deleted file mode 100644 index 2d3f31aa3b..0000000000 --- a/.github/workflows/run-e2e-tests-reusable-workflow.yml +++ /dev/null @@ -1,739 +0,0 @@ -# This is a reusable workflow that runs E2E tests for Chainlink. -# It is not meant to be run on its own. -name: Run E2E Tests -on: - workflow_call: - inputs: - chainlink_version: - description: 'Enter Chainlink version to use for the tests. Example: "v2.10.0" or sha' - required: false - type: string - test_ids: - description: 'Run tests by test ids separated by commas. Example: "run_all_in_ocr_tests_go,run_TestOCRv2Request_in_ocr2_test_go". Check all test IDs in .github/e2e-tests.yml' - required: false - type: string - test_list: - description: 'Base64 encoded list of tests (YML objects) to run. Example in run-automation-ondemand-e2e-tests.yml' - required: false - type: string - test_workflow: - description: 'Run tests by workflow name. Example: "Run Nightly E2E Tests"' - required: false - type: string - # TODO: Uncomment once Test Config does not have any secrets. Related ticket https://smartcontract-it.atlassian.net/browse/TT-1392 - # test_config_override_base64: - # required: false - # description: The base64-encoded test config override - # type: string - enable_check_test_configurations: - description: 'Set to "true" to enable check-test-configurations job' - required: false - type: boolean - default: false - with_existing_remote_runner_version: - description: 'Use the existing remote runner version for k8s tests. Example: "d3bf5044af33e08be788a2df31c4a745cf69d787"' - required: false - type: string - require_chainlink_image_versions_in_qa_ecr: - description: 'Check Chainlink image versions to be present in QA ECR. If not, build and push the image to QA ECR. Takes comma separated list of Chainlink image versions. Example: "5733cdcda9a9fc6da6343798b119b2ae136146cd,0b7d2c497a508efa5a827714780d908b7b8eda19"' - required: false - type: string - require_chainlink_plugin_versions_in_qa_ecr: - description: 'Check Chainlink plugins versions to be present in QA ECR. If not, build and push the image to QA ECR. Takes comma separated list of Chainlink image versions. Example: "5733cdcda9a9fc6da6343798b119b2ae136146cd,0b7d2c497a508efa5a827714780d908b7b8eda19"' - required: false - type: string - slack_notification_after_tests: - description: 'Set to "true" to send a slack notification after the tests' - required: false - type: boolean - default: false - slack_notification_after_tests_channel_id: - description: 'Slack channel ID to send the notification to' - required: false - type: string - slack_notification_after_tests_name: - description: 'Name of the slack notification' - required: false - type: string - test_log_upload_on_failure: - description: 'Set to "true" to upload the test log on failure as Github artifact' - required: false - type: boolean - default: false - test_log_upload_retention_days: - description: 'Number of days to retain the test log. Default is 3 days' - required: false - type: number - default: 3 - secrets: - TEST_SECRETS_OVERRIDE_BASE64: - required: false - QA_AWS_REGION: - required: true - QA_AWS_ROLE_TO_ASSUME: - required: true - QA_AWS_ACCOUNT_NUMBER: - required: true - QA_PYROSCOPE_INSTANCE: - required: true - QA_PYROSCOPE_KEY: - required: true - QA_KUBECONFIG: - required: true - GRAFANA_INTERNAL_TENANT_ID: - required: true - GRAFANA_INTERNAL_BASIC_AUTH: - required: true - GRAFANA_INTERNAL_HOST: - required: true - GRAFANA_INTERNAL_URL_SHORTENER_TOKEN: - required: true - GH_TOKEN: - required: true - AWS_REGION: - required: true - AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN: - required: true - AWS_API_GW_HOST_GRAFANA: - required: true - SLACK_BOT_TOKEN: - required: false - -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_PLUGINS: ${{ github.sha }}-plugins - CHAINLINK_ENV_USER: ${{ github.actor }} - CHAINLINK_COMMIT_SHA: ${{ inputs.evm-ref || github.sha }} - SELECTED_NETWORKS: SIMULATED - MOD_CACHE_VERSION: 1 - TEST_LOG_LEVEL: debug - -jobs: - validate-inputs: - name: Validate workflow inputs - runs-on: ubuntu-latest - outputs: - require_chainlink_image_versions_in_qa_ecr_matrix: ${{ steps.set-required-chainlink-image-versions-matrix.outputs.versions }} - require_chainlink_plugin_versions_in_qa_ecr_matrix: ${{ steps.set-required-chainlink-plugin-versions-matrix.outputs.versions }} - steps: - - name: Check input conditions - run: | - if [[ "${{ inputs.test_ids }}" != "" && "${{ inputs.test_workflow }}" != "" ]]; then - echo "::error::Error: Both 'test_ids' and 'test_workflow' are provided. Please specify only one." - exit 1 - fi - if [[ "${{ secrets.TEST_SECRETS_OVERRIDE_BASE64 }}" != "" ]]; then - echo "Will run tests with custom test secrets" - fi - - name: Install jq - run: sudo apt-get install jq - - name: Create matrix for required Chainlink image versions - id: set-required-chainlink-image-versions-matrix - run: | - if [[ "${{ inputs.require_chainlink_image_versions_in_qa_ecr }}" != '' ]]; then - image_versions=$(echo "${{ inputs.require_chainlink_image_versions_in_qa_ecr }}" | jq -Rc 'split(",") | if . == [""] then [] else . end') - echo "versions=$image_versions" >> $GITHUB_OUTPUT - fi - - name: Create matrix for required Chainlink plugin versions - id: set-required-chainlink-plugin-versions-matrix - run: | - if [[ "${{ inputs.require_chainlink_plugin_versions_in_qa_ecr }}" != '' ]]; then - image_versions=$(echo "${{ inputs.require_chainlink_plugin_versions_in_qa_ecr }}" | jq -Rc 'split(",") | if . == [""] then [] else . end') - echo "versions=$image_versions" >> $GITHUB_OUTPUT - fi - - check-test-configurations: - name: Check test configurations - if: ${{ inputs.enable_check_test_configurations }} - needs: validate-inputs - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - name: Setup Go - uses: ./.github/actions/setup-go - - name: Run Check Tests Command - run: | - cd integration-tests/ - if ! go run citool/main.go check-tests . ../.github/e2e-tests.yml; then - echo "::error::Some E2E test configurations have to be added to .github/e2e-tests.yml. This file defines Github CI configuration for each E2E test or set of E2E tests." && exit 1 - fi - - get_latest_chainlink_release_version: - name: Get latest Chainlink release version - runs-on: ubuntu-latest - environment: integration - outputs: - latest_chainlink_release_version: ${{ steps.get_latest_version.outputs.latest_version }} - steps: - - name: Get Latest Version - id: get_latest_version - run: | - untrimmed_ver=$(curl --header "Authorization: token ${{ secrets.GH_TOKEN }}" --request GET https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r .name) - latest_version="${untrimmed_ver:1}" - echo "Latest Chainlink release version: $latest_version" - echo "latest_version=${latest_version}" >> "$GITHUB_OUTPUT" - # Check if latest_version is empty - if [ -z "$latest_version" ]; then - echo "Error: The latest_version is empty. The migration tests need a verison to run." - exit 1 - fi - - load-test-configurations: - name: Load test configurations - needs: [validate-inputs] - runs-on: ubuntu-latest - outputs: - run-docker-tests: ${{ steps.check-matrices.outputs.run-docker-tests }} - run-k8s-tests: ${{ steps.check-matrices.outputs.run-k8s-tests }} - docker-matrix: ${{ steps.set-docker-matrix.outputs.matrix }} - k8s-runner-matrix: ${{ steps.set-k8s-runner-matrix.outputs.matrix }} - steps: - - name: Checkout code - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - name: Setup Go - uses: ./.github/actions/setup-go - - name: Install jq - run: sudo apt-get install jq - - name: Generate Docker Tests Matrix - id: set-docker-matrix - run: | - cd integration-tests/citool - MATRIX_JSON=$(go run main.go filter --file ${{ github.workspace }}/.github/e2e-tests.yml --test-env-type 'docker' --test-list '${{ inputs.test_list }}' --test-ids '${{ inputs.test_ids }}' --workflow '${{ inputs.test_workflow }}') - echo "Docker tests:" - echo "$MATRIX_JSON" | jq - echo "matrix=$MATRIX_JSON" >> $GITHUB_OUTPUT - - name: Generate K8s Tests Matrix - id: set-k8s-runner-matrix - run: | - cd integration-tests/citool - MATRIX_JSON=$(go run main.go filter --file ${{ github.workspace }}/.github/e2e-tests.yml --test-env-type 'k8s-remote-runner' --test-list '${{ inputs.test_list }}' --test-ids '${{ inputs.test_ids }}' --workflow '${{ inputs.test_workflow }}') - echo "K8s tests:" - echo "$MATRIX_JSON" | jq - echo "matrix=$MATRIX_JSON" >> $GITHUB_OUTPUT - - name: Check Test Matrices - id: check-matrices - run: | - DOCKER_MATRIX_EMPTY=$(echo '${{ steps.set-docker-matrix.outputs.matrix }}' | jq '.tests == null or .tests == []') - K8S_MATRIX_EMPTY=$(echo '${{ steps.set-k8s-runner-matrix.outputs.matrix }}' | jq '.tests == null or .tests == []') - - # Check if jq commands succeeded - if [ $? -ne 0 ]; then - echo "JSON parse error occurred." - exit 1 - fi - - if [[ "$DOCKER_MATRIX_EMPTY" == "true" ]]; then - echo "run-docker-tests=false" >> $GITHUB_OUTPUT - else - echo "run-docker-tests=true" >> $GITHUB_OUTPUT - fi - if [[ "$K8S_MATRIX_EMPTY" == "true" ]]; then - echo "run-k8s-tests=false" >> $GITHUB_OUTPUT - else - echo "run-k8s-tests=true" >> $GITHUB_OUTPUT - fi - - # Check if both matrices are empty - if [[ "$DOCKER_MATRIX_EMPTY" == "true" ]] && [[ "$K8S_MATRIX_EMPTY" == "true" ]]; then - echo "No tests found for inputs: '${{ toJson(inputs) }}'. Both Docker and Kubernetes tests matrices are empty" - exit 1 - fi - shell: bash - - - name: Check if test config override is required for any test - shell: bash - run: | - # Check if the test config override is provided and skip the checks if it is non-empty - # TODO: Uncomment once Test Config does not have any secrets. Related ticket https://smartcontract-it.atlassian.net/browse/TT-1392 - # if [ -n "${{ inputs.test_config_override_base64 }}" ]; then - # echo "Test config override provided. Skipping checks for tests requiring config override." - # exit 0 - # fi - - # Parse the JSON to check for test_config_override_required in Docker matrix - DOCKER_TESTS_REQUIRING_CONFIG_OVERRIDE=$(echo '${{ steps.set-docker-matrix.outputs.matrix }}' | jq 'if .tests then .tests[] | select(has("test_config_override_required") and .test_config_override_required) | .id else empty end' -r) - # Parse the JSON to check for test_config_override_required in Kubernetes matrix - K8S_TESTS_REQUIRING_CONFIG_OVERRIDE=$(echo '${{ steps.set-k8s-runner-matrix.outputs.matrix }}' | jq 'if .tests then .tests[] | select(has("test_config_override_required") and .test_config_override_required) | .id else empty end' -r) - - # Determine if any tests require a configuration override - if [ ! -z "$DOCKER_TESTS_REQUIRING_CONFIG_OVERRIDE" ] || [ ! -z "$K8S_TESTS_REQUIRING_CONFIG_OVERRIDE" ]; then - echo "Tests in .github/e2e-tests.yml requiring test config override:" - if [ ! -z "$DOCKER_TESTS_REQUIRING_CONFIG_OVERRIDE" ]; then - echo $DOCKER_TESTS_REQUIRING_CONFIG_OVERRIDE - fi - if [ ! -z "$K8S_TESTS_REQUIRING_CONFIG_OVERRIDE" ]; then - echo $K8S_TESTS_REQUIRING_CONFIG_OVERRIDE - fi - echo "::error::Error: Some of the tests require a test config override. Please see workflow logs and set 'test_config_override_base64' to run these tests." - exit 1 - else - echo "No tests require a configuration override. Proceeding without overrides." - fi - - - name: Check if test secrets are required for any test - shell: bash - run: | - # Check if the test secret key is provided and skip the checks if it is non-empty - if [ -n "${{ secrets.TEST_SECRETS_OVERRIDE_BASE64 }}" ]; then - echo "Test secret key provided. Skipping checks for tests requiring secrets." - exit 0 - fi - - # Parse the JSON to check for test_secrets_required in Docker matrix - DOCKER_TESTS_REQUIRING_SECRETS=$(echo '${{ steps.set-docker-matrix.outputs.matrix }}' | jq 'if .tests then .tests[] | select(has("test_secrets_required") and .test_secrets_required) | .id else empty end' -r) - # Parse the JSON to check for test_secrets_required in Kubernetes matrix - K8S_TESTS_REQUIRING_SECRETS=$(echo '${{ steps.set-k8s-runner-matrix.outputs.matrix }}' | jq 'if .tests then .tests[] | select(has("test_secrets_required") and .test_secrets_required) | .id else empty end' -r) - - # Determine if any tests require secrets - if [ ! -z "$DOCKER_TESTS_REQUIRING_SECRETS" ] || [ ! -z "$K8S_TESTS_REQUIRING_SECRETS" ]; then - echo "Tests in .github/e2e-tests.yml requiring custom test secrets:" - if [ ! -z "$DOCKER_TESTS_REQUIRING_SECRETS" ]; then - echo $DOCKER_TESTS_REQUIRING_SECRETS - fi - if [ ! -z "$K8S_TESTS_REQUIRING_SECRETS" ]; then - echo $K8S_TESTS_REQUIRING_SECRETS - fi - echo "::error::Error: Some of the tests require custom test secrets to run. Please see workflow logs and set 'test_secrets_override_key' to run these tests." - exit 1 - else - echo "No tests require secrets. Proceeding without additional secret setup." - fi - - # Build Chainlink images required for the tests - require-chainlink-image-versions-in-qa-ecr: - name: Build Chainlink image - needs: [validate-inputs, load-test-configurations] - if: ${{ needs.validate-inputs.outputs.require_chainlink_image_versions_in_qa_ecr_matrix != '' }} - runs-on: ubuntu-latest - environment: integration - permissions: - id-token: write - contents: read - env: - CHAINLINK_IMAGE: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink - strategy: - matrix: - version: ${{ fromJson(needs.validate-inputs.outputs.require_chainlink_image_versions_in_qa_ecr_matrix) }} - steps: - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - - name: Build Chainlink image for ${{ matrix.version }} and push it to QA ECR - uses: ./.github/actions/build-chainlink-image - with: - dockerfile: core/chainlink.Dockerfile - git_commit_sha: ${{ matrix.version }} - tag_suffix: '' - check_image_exists: 'true' - AWS_REGION: ${{ secrets.QA_AWS_REGION }} - AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - - # Build Chainlink plugins required for the tests - require-chainlink-plugin-versions-in-qa-ecr: - name: Build Chainlink plugins - needs: [validate-inputs, load-test-configurations] - if: ${{ needs.validate-inputs.outputs.require_chainlink_plugin_versions_in_qa_ecr_matrix != '' }} - runs-on: ubuntu-latest - environment: integration - permissions: - id-token: write - contents: read - env: - CHAINLINK_IMAGE: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink - strategy: - matrix: - version: ${{ fromJson(needs.validate-inputs.outputs.require_chainlink_plugin_versions_in_qa_ecr_matrix) }} - steps: - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - - name: Build Chainlink plugins image for ${{ matrix.version }} - uses: ./.github/actions/build-chainlink-image - with: - dockerfile: plugins/chainlink.Dockerfile - git_commit_sha: ${{ matrix.version }} - tag_suffix: '-plugins' - check_image_exists: 'true' - AWS_REGION: ${{ secrets.QA_AWS_REGION }} - AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - - # Run Docker tests - run-docker-tests: - name: Run ${{ 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() }} - runs-on: ${{ matrix.tests.runs_on }} - strategy: - fail-fast: false - matrix: ${{fromJson(needs.load-test-configurations.outputs.docker-matrix)}} - environment: integration - permissions: - actions: read - checks: write - pull-requests: write - id-token: write - contents: read - env: - LATEST_CHAINLINK_RELEASE_VERSION: ${{ needs.get_latest_chainlink_release_version.outputs.latest_chainlink_release_version }} - steps: - - name: Collect Metrics - if: always() - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: e2e_tests_${{ matrix.tests.id_sanitized }} - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: Run E2E Tests / Run ${{ matrix.tests.id }} - test-results-file: '{"testType":"go","filePath":"/tmp/gotest.log"}' - continue-on-error: true - - - name: Checkout repository - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - name: Install jq - run: sudo apt-get install -y jq - - name: Show test configuration - run: echo '${{ toJson(matrix.tests) }}' | jq . - - name: Setup Go - uses: ./.github/actions/setup-go - - name: Setup GAP for Grafana - uses: smartcontractkit/.github/actions/setup-gap@d316f66b2990ea4daa479daa3de6fc92b00f863e # setup-gap@0.3.2 - id: setup-gap - with: - aws-region: ${{ secrets.AWS_REGION }} - aws-role-arn: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} - api-gateway-host: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} - duplicate-authorization-header: "true" - - - name: Run tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@aa8eea635029ab8d95abd3c206f56dae1e22e623 # v2.3.28 - env: - DETACH_RUNNER: true - with: - test_command_to_run: ${{ matrix.tests.test_cmd }} 2>&1 | tee /tmp/gotest.log | gotestloghelper -ci -singlepackage -hidepassingtests=false -hidepassinglogs - test_download_vendor_packages_command: cd ./integration-tests && go mod download - test_secrets_override_base64: ${{ secrets.TEST_SECRETS_OVERRIDE_BASE64 }} - # TODO: Uncomment once Test Config does not have any secrets. Related ticket https://smartcontract-it.atlassian.net/browse/TT-1392 - # test_config_override_base64: ${{ inputs.test_config_override_base64 }} - test_config_chainlink_version: ${{ matrix.tests.test_inputs.chainlink_version || inputs.chainlink_version || github.sha }} - test_config_chainlink_upgrade_version: ${{ matrix.tests.test_inputs.chainlink_upgrade_version }} - test_config_chainlink_postgres_version: ${{ matrix.tests.test_inputs.chainlink_postgres_version }} - test_config_selected_networks: ${{ matrix.tests.test_inputs.selected_networks || env.SELECTED_NETWORKS}} - test_config_logging_run_id: ${{ github.run_id }} - test_config_logstream_log_targets: ${{ vars.LOGSTREAM_LOG_TARGETS }} - test_type: ${{ matrix.tests.test_inputs.test_type }} - test_suite: ${{ matrix.tests.test_inputs.test_suite }} - aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - artifacts_name: ${{ matrix.tests.id_sanitized }}-test-logs - artifacts_location: | - ./integration-tests/smoke/logs/ - /tmp/gotest.log - publish_check_name: ${{ matrix.tests.id_sanitized }} - token: ${{ secrets.GH_TOKEN }} - no_cache: true # Do not restore cache since go was already configured in the previous step - go_mod_path: ./integration-tests/go.mod - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: "" - should_tidy: "false" - go_coverage_src_dir: /var/tmp/go-coverage - go_coverage_dest_dir: ${{ github.workspace }}/.covdata - DEFAULT_CHAINLINK_IMAGE: ${{ matrix.tests.test_inputs.chainlink_image || env.CHAINLINK_IMAGE }} - DEFAULT_CHAINLINK_UPGRADE_IMAGE: ${{ matrix.tests.test_inputs.chainlink_upgrade_image }} - DEFAULT_LOKI_TENANT_ID: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - DEFAULT_LOKI_ENDPOINT: https://${{ secrets.GRAFANA_INTERNAL_HOST }}/loki/api/v1/push - DEFAULT_LOKI_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - DEFAULT_GRAFANA_BASE_URL: "http://localhost:8080/primary" - DEFAULT_GRAFANA_DASHBOARD_URL: "/d/ddf75041-1e39-42af-aa46-361fe4c36e9e/ci-e2e-tests-logs" - DEFAULT_GRAFANA_BEARER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} - DEFAULT_PYROSCOPE_ENVIRONMENT: ${{ matrix.tests.pyroscope_env }} - DEFAULT_PYROSCOPE_SERVER_URL: ${{ matrix.tests.pyroscope_env != '' && secrets.QA_PYROSCOPE_INSTANCE || '' }} - DEFAULT_PYROSCOPE_KEY: ${{ matrix.tests.pyroscope_env != '' && secrets.QA_PYROSCOPE_KEY || '' }} - DEFAULT_PYROSCOPE_ENABLED: ${{ matrix.tests.pyroscope_env != '' && 'true' || '' }} - - - name: Upload test log as Github artifact - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 - if: inputs.test_log_upload_on_failure && failure() - with: - name: test_log_${{ matrix.tests.id_sanitized }} - path: /tmp/gotest.log - retention-days: ${{ inputs.test_log_upload_retention_days }} - continue-on-error: true - - # Run K8s tests using old remote runner - - prepare-remote-runner-test-image: - needs: [load-test-configurations, require-chainlink-image-versions-in-qa-ecr, require-chainlink-plugin-versions-in-qa-ecr] - if: ${{ needs.load-test-configurations.outputs.run-k8s-tests == 'true' && always() && !failure() && !cancelled() }} - name: Prepare remote runner test image - runs-on: ubuntu-latest - environment: integration - permissions: - actions: read - checks: write - pull-requests: write - id-token: write - contents: read - outputs: - remote-runner-version: ${{ steps.set-remote-runner-version.outputs.remote-runner-version }} - env: - ENV_JOB_IMAGE_BASE: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink-tests - steps: - - name: Checkout repository - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - name: Build Test Runner Image - uses: ./.github/actions/build-test-image - if: ${{ inputs.with_existing_remote_runner_version == '' }} - with: - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - - name: Set Remote Runner Version - id: set-remote-runner-version - run: | - if [[ -z "${{ inputs.with_existing_remote_runner_version }}" ]]; then - echo "remote-runner-version=${{ github.sha }}" >> $GITHUB_OUTPUT - else - echo "remote-runner-version=${{ inputs.with_existing_remote_runner_version }}" >> $GITHUB_OUTPUT - fi - - 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 }} - runs-on: ${{ matrix.tests.runs_on }} - strategy: - fail-fast: false - matrix: ${{fromJson(needs.load-test-configurations.outputs.k8s-runner-matrix)}} - environment: integration - permissions: - actions: read - checks: write - pull-requests: write - id-token: write - contents: read - env: - LATEST_CHAINLINK_RELEASE_VERSION: ${{ needs.get_latest_chainlink_release_version.outputs.latest_chainlink_release_version }} - steps: - - name: Collect Metrics - if: always() - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: e2e_tests_${{ matrix.tests.id_sanitized }} - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: Run E2E Tests / Run ${{ matrix.tests.id }} - continue-on-error: true - - - name: Checkout repository - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - name: Install jq - run: sudo apt-get install -y jq - - name: Show Test Configuration - run: echo '${{ toJson(matrix.tests) }}' | jq . - - name: Show Remote Runner Version - run: | - echo "Remote Runner Version: ${{ needs.prepare-remote-runner-test-image.outputs.remote-runner-version }}" - - - name: Run tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@aa8eea635029ab8d95abd3c206f56dae1e22e623 # v2.3.28 - env: - DETACH_RUNNER: true - RR_MEM: ${{ matrix.tests.remote_runner_memory }} - TEST_ARGS: -test.timeout 900h -test.memprofile memprofile.out -test.cpuprofile profile.out - ENV_JOB_IMAGE: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink-tests:${{ needs.prepare-remote-runner-test-image.outputs.remote-runner-version }} - INTERNAL_DOCKER_REPO: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com - # We can comment these out when we have a stable soak test and aren't worried about resource consumption - TEST_UPLOAD_CPU_PROFILE: true - TEST_UPLOAD_MEM_PROFILE: true - TEST_LOG_LEVEL: debug - REF_NAME: ${{ github.head_ref || github.ref_name }} - with: - test_command_to_run: ${{ matrix.tests.test_cmd }} 2>&1 | tee /tmp/gotest.log | gotestloghelper -ci -singlepackage -hidepassingtests=false -hidepassinglogs - test_download_vendor_packages_command: make gomod - test_secrets_override_base64: ${{ secrets.TEST_SECRETS_OVERRIDE_BASE64 }} - # TODO: Uncomment once Test Config does not have any secrets. Related ticket https://smartcontract-it.atlassian.net/browse/TT-1392 - # test_config_override_base64: ${{ inputs.test_config_override_base64 }} - test_config_chainlink_version: ${{ matrix.tests.test_inputs.chainlink_version || inputs.chainlink_version || github.sha }} - test_config_chainlink_upgrade_version: ${{ matrix.tests.test_inputs.chainlink_upgrade_version }} - test_config_chainlink_postgres_version: ${{ matrix.tests.test_inputs.chainlink_postgres_version }} - test_config_selected_networks: ${{ matrix.tests.test_inputs.selected_networks || env.SELECTED_NETWORKS}} - test_config_logging_run_id: ${{ github.run_id }} - test_config_logstream_log_targets: ${{ vars.LOGSTREAM_LOG_TARGETS }} - test_type: ${{ matrix.tests.test_inputs.test_type }} - test_suite: ${{ matrix.tests.test_inputs.test_suite }} - token: ${{ secrets.GH_TOKEN }} - should_cleanup: false - no_cache: true # Do not restore cache since go was already configured in the previous step - go_mod_path: ./integration-tests/go.mod - QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} - QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} - DEFAULT_CHAINLINK_IMAGE: ${{ matrix.tests.test_inputs.chainlink_image || env.CHAINLINK_IMAGE }} - DEFAULT_CHAINLINK_UPGRADE_IMAGE: ${{ matrix.tests.test_inputs.chainlink_upgrade_image }} - DEFAULT_LOKI_TENANT_ID: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - DEFAULT_LOKI_ENDPOINT: https://${{ secrets.GRAFANA_INTERNAL_HOST }}/loki/api/v1/push - DEFAULT_LOKI_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - DEFAULT_GRAFANA_BASE_URL: "http://localhost:8080/primary" - DEFAULT_GRAFANA_DASHBOARD_URL: "/d/ddf75041-1e39-42af-aa46-361fe4c36e9e/ci-e2e-tests-logs" - DEFAULT_GRAFANA_BEARER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} - DEFAULT_PYROSCOPE_ENVIRONMENT: ${{ matrix.tests.pyroscope_env }} - DEFAULT_PYROSCOPE_SERVER_URL: ${{ matrix.tests.pyroscope_env != '' && secrets.QA_PYROSCOPE_INSTANCE || '' }} - DEFAULT_PYROSCOPE_KEY: ${{ matrix.tests.pyroscope_env != '' && secrets.QA_PYROSCOPE_KEY || '' }} - DEFAULT_PYROSCOPE_ENABLED: ${{ matrix.tests.pyroscope_env != '' && 'true' || '' }} - - - name: Upload test log as Github artifact - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 - if: inputs.test_log_upload_on_failure && failure() - with: - name: test_log_${{ matrix.tests.id_sanitized }} - path: /tmp/gotest.log - retention-days: ${{ inputs.test_log_upload_retention_days }} - continue-on-error: true - - after_tests: - needs: [run-docker-tests, run-k8s-runner-tests] - if: always() - name: After tests notifications - runs-on: ubuntu-latest - steps: - - name: Determine combined test results - id: combine_results - run: | - docker_result="${{ needs.run-docker-tests.result }}" - k8s_result="${{ needs.run-k8s-runner-tests.result }}" - - function map_outcome { - case "$1" in - success|skipped) - echo "success" - ;; - cancelled) - echo "cancelled" - ;; - *) - echo "failure" - ;; - esac - } - - combined_docker_result=$(map_outcome $docker_result) - combined_k8s_result=$(map_outcome $k8s_result) - - if [[ $combined_docker_result == "failure" || $combined_k8s_result == "failure" ]]; then - echo "result=failure" >> $GITHUB_OUTPUT - elif [[ $combined_docker_result == "cancelled" || $combined_k8s_result == "cancelled" ]]; then - echo "result=cancelled" >> $GITHUB_OUTPUT - else - echo "result=success" >> $GITHUB_OUTPUT - fi - - - name: Send Slack notification - uses: slackapi/slack-github-action@6c661ce58804a1a20f6dc5fbee7f0381b469e001 # v1.25.0 - if: ${{ inputs.slack_notification_after_tests }} - id: slack - env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} - with: - channel-id: ${{ inputs.slack_notification_after_tests_channel_id }} - payload: | - { - "blocks": [ - { - "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:' }}" - } - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Build Details>" - } - } - ] - } - - # Run K8s tests using new remote runner - # remote-runner-k8s-tests: - # runs-on: ubuntu-latest - # container: - # image: golang:1.18 - # steps: - # - name: Checkout repository - # uses: actions/checkout@v2 - - # - name: Set up Go - # uses: actions/setup-go@v2 - # with: - # go-version: '1.18' - - # - name: Load Runner Config - # run: echo "$RUNNER_CONFIG" > runner.toml - # env: - # RUNNER_CONFIG: | - # # Runner configuration - # detached_mode = true - # debug = false - - # [[test_runs]] - # namespace = "dev-env" - # rbac_role_name = "dev-role" - # rbac_service_account_name = "dev-service-account" - # sync_value = "unique-sync-value-1" - # ttl_seconds_after_finished = 300 - # image_registry_url = "https://myregistry.dev/" - # image_name = "dev-image" - # image_tag = "v1.0.0" - # test_name = "TestMercuryLoad/all_endpoints" - # test_config_base64_env_name = "CONFIG_ENV_DEV" - # test_config_file_path = "/configs/dev/test-config.toml" - # test_config_base64 = "dGVzdCBjb25maWcgdmFsdWUgZGV2" - # test_timeout = "30m" - # resources_requests_cpu = "500m" - # resources_requests_memory = "1Gi" - # resources_limits_cpu = "1000m" - # resources_limits_memory = "2Gi" - # job_count = 2 - # chart_path = "/charts/dev" - # [envs] - # WASP_LOG_LEVEL = "info" - # TEST_LOG_LEVEL = "info" - # MERCURY_TEST_LOG_LEVEL = "info" - - # [[test_runs]] - # namespace = "prod-env" - # rbac_role_name = "prod-role" - # rbac_service_account_name = "prod-service-account" - # sync_value = "unique-sync-value-2" - # ttl_seconds_after_finished = 600 - # image_registry_url = "https://myregistry.prod/" - # image_name = "prod-image" - # image_tag = "v1.0.1" - # test_name = "TestMercuryLoad/all_endpoints" - # test_config_base64_env_name = "CONFIG_ENV_PROD" - # test_config_file_path = "/configs/prod/test-config.toml" - # test_config_base64 = "dGVzdCBjb25maWcgdmFsdWUgcHJvZA==" - # test_timeout = "45m" - # resources_requests_cpu = "800m" - # resources_requests_memory = "2Gi" - # resources_limits_cpu = "1500m" - # resources_limits_memory = "4Gi" - # job_count = 3 - # chart_path = "/charts/prod" - # [envs] - # WASP_LOG_LEVEL = "info" - # TEST_LOG_LEVEL = "info" - # MERCURY_TEST_LOG_LEVEL = "info" - - # # Schedule the tests in K8s in remote runner - # - name: Run Kubernetes Tests - # run: go run ./cmd/main.go run -c runner.toml \ No newline at end of file diff --git a/.github/workflows/run-nightly-e2e-tests.yml b/.github/workflows/run-nightly-e2e-tests.yml index cb8550fb70..397258c237 100644 --- a/.github/workflows/run-nightly-e2e-tests.yml +++ b/.github/workflows/run-nightly-e2e-tests.yml @@ -10,10 +10,11 @@ on: jobs: call-run-e2e-tests-workflow: name: Run E2E Tests - uses: ./.github/workflows/run-e2e-tests-reusable-workflow.yml + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@aad83f232743646faa35f5ac03ee3829148d37ce with: chainlink_version: develop - test_workflow: Run Nightly E2E Tests + test_path: .github/e2e-tests.yml + test_trigger: 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 @@ -28,6 +29,9 @@ jobs: GRAFANA_INTERNAL_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} GRAFANA_INTERNAL_HOST: ${{ secrets.GRAFANA_INTERNAL_HOST }} GRAFANA_INTERNAL_URL_SHORTENER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} + LOKI_TENANT_ID: ${{ secrets.LOKI_TENANT_ID }} + LOKI_URL: ${{ secrets.LOKI_URL }} + LOKI_BASIC_AUTH: ${{ secrets.LOKI_BASIC_AUTH }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} AWS_REGION: ${{ secrets.QA_AWS_REGION }} AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} diff --git a/.github/workflows/run-selected-e2e-tests.yml b/.github/workflows/run-selected-e2e-tests.yml index ab064531fd..e81d4f0d6d 100644 --- a/.github/workflows/run-selected-e2e-tests.yml +++ b/.github/workflows/run-selected-e2e-tests.yml @@ -16,16 +16,10 @@ on: description: 'Enter the secret key to override test secrets' required: false type: string - # TODO: Uncomment once Test Config does not have any secrets. Related ticket https://smartcontract-it.atlassian.net/browse/TT-1392 - # test_config_override_base64: - # required: false - # description: The base64-encoded test config override - # type: string - enable_check_test_configurations: - description: 'Set to "true" to enable check-test-configurations job' + test_config_override_path: + description: 'Path to a test config file used to override the default test config' required: false - type: boolean - default: false + type: string with_existing_remote_runner_version: description: 'Use the existing remote runner version for k8s tests. Example: "d3bf5044af33e08be788a2df31c4a745cf69d787"' required: false @@ -41,15 +35,13 @@ run-name: ${{ inputs.workflow_run_name }} jobs: call-run-e2e-tests-workflow: name: Run E2E Tests - uses: ./.github/workflows/run-e2e-tests-reusable-workflow.yml + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@aad83f232743646faa35f5ac03ee3829148d37ce with: chainlink_version: ${{ github.event.inputs.chainlink_version }} + test_path: .github/e2e-tests.yml test_ids: ${{ github.event.inputs.test_ids }} - # TODO: Uncomment once Test Config does not have any secrets. Related ticket https://smartcontract-it.atlassian.net/browse/TT-1392 - # test_config_override_base64: ${{ github.event.inputs.test_config_override_base64 }} + test_config_override_path: ${{ github.event.inputs.test_config_override_path }} with_existing_remote_runner_version: ${{ github.event.inputs.with_existing_remote_runner_version }} - # Use fromJSON to convert string to boolean. More info: https://github.com/actions/runner/issues/2206#issuecomment-1532246677 - enable_check_test_configurations: ${{ fromJSON(github.event.inputs.enable_check_test_configurations) }} secrets: QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} @@ -61,10 +53,12 @@ jobs: GRAFANA_INTERNAL_BASIC_AUTH: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} GRAFANA_INTERNAL_HOST: ${{ secrets.GRAFANA_INTERNAL_HOST }} GRAFANA_INTERNAL_URL_SHORTENER_TOKEN: ${{ secrets.GRAFANA_INTERNAL_URL_SHORTENER_TOKEN }} + LOKI_TENANT_ID: ${{ secrets.LOKI_TENANT_ID }} + LOKI_URL: ${{ secrets.LOKI_URL }} + LOKI_BASIC_AUTH: ${{ secrets.LOKI_BASIC_AUTH }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} AWS_REGION: ${{ secrets.QA_AWS_REGION }} AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN: ${{ secrets.AWS_OIDC_IAM_ROLE_VALIDATION_PROD_ARN }} AWS_API_GW_HOST_GRAFANA: ${{ secrets.AWS_API_GW_HOST_GRAFANA }} TEST_SECRETS_OVERRIDE_BASE64: ${{ secrets[inputs.test_secrets_override_key] }} SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} - diff --git a/.github/workflows/solidity-foundry-artifacts.yml b/.github/workflows/solidity-foundry-artifacts.yml index 50a77e2846..90d39f36a0 100644 --- a/.github/workflows/solidity-foundry-artifacts.yml +++ b/.github/workflows/solidity-foundry-artifacts.yml @@ -18,19 +18,30 @@ on: - "shared" - "transmission" - "vrf" + commit_to_use: + type: string + description: 'commit SHA to use for artifact generation; if empty HEAD will be used' + required: false base_ref: - description: 'commit or tag to be used as base reference, when looking for modified Solidity files' + description: 'commit or tag to use as base reference, when looking for modified Solidity files' required: true + link_with_jira: + description: 'link generated artifacts with Jira issues?' + type: boolean + default: true + required: false env: FOUNDRY_PROFILE: ci + # Unfortunately, we can't use the "default" field in the inputs section, because it does not have + # access to the workflow context + head_ref: ${{ inputs.commit_to_use || github.sha }} jobs: changes: name: Detect changes runs-on: ubuntu-latest outputs: - changes: ${{ steps.changes.outputs.sol }} product_changes: ${{ steps.changes-transform.outputs.product_changes }} product_files: ${{ steps.changes-transform.outputs.product_files }} changeset_changes: ${{ steps.changes-dorny.outputs.changeset }} @@ -38,12 +49,15 @@ jobs: steps: - name: Checkout the repo uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + with: + ref: ${{ env.head_ref }} - name: Find modified contracts uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 - id: changes + id: changes-dorny with: list-files: 'csv' base: ${{ inputs.base_ref }} + # This is a valid input, see https://github.com/dorny/paths-filter/pull/226 predicate-quantifier: every filters: | ignored: &ignored @@ -61,12 +75,12 @@ jobs: - *ignored sol: - modified|added: 'contracts/src/v0.8/**/*.sol' - - *ignored + - *ignored product: &product - - modified|added: 'contracts/src/v0.8/${{ inputs.product }}/**/*.sol' + - modified|added: 'contracts/src/v0.8/${{ inputs.product }}/**/*.sol' - *ignored changeset: - - modified|added: 'contracts/.changeset/!(README)*.md' + - modified|added: 'contracts/.changeset/!(README)*.md' # Manual transformation needed, because shared contracts have a different folder structure - name: Transform modified files @@ -75,47 +89,45 @@ jobs: run: | if [ "${{ inputs.product }}" = "shared" ]; then echo "::debug:: Product is shared, transforming changes" - if [[ "${{ steps.changes.outputs.product }}" == "true" && "${{ steps.changes.outputs.other_shared }}" == "true" ]]; then + if [[ "${{ steps.changes-dorny.outputs.product }}" == "true" && "${{ steps.changes-dorny.outputs.other_shared }}" == "true" ]]; then echo "::debug:: Changes were found in 'shared' folder and in 'interfaces' and root folders" echo "product_changes=true" >> $GITHUB_OUTPUT - echo "product_files=${{ steps.changes.outputs.product_files }},${{ steps.changes.outputs.other_shared_files }}" >> $GITHUB_OUTPUT - elif [[ "${{ steps.changes.outputs.product }}" == "false" && "${{ steps.changes.outputs.other_shared }}" == "true" ]]; then + echo "product_files=${{ steps.changes-dorny.outputs.product_files }},${{ steps.changes-dorny.outputs.other_shared_files }}" >> $GITHUB_OUTPUT + elif [[ "${{ steps.changes-dorny.outputs.product }}" == "false" && "${{ steps.changes-dorny.outputs.other_shared }}" == "true" ]]; then echo "::debug:: Only contracts in' interfaces' and root folders were modified" echo "product_changes=true" >> $GITHUB_OUTPUT - echo "product_files=${{ steps.changes.outputs.other_shared_files }}" >> $GITHUB_OUTPUT - elif [[ "${{ steps.changes.outputs.product }}" == "true" && "${{ steps.changes.outputs.other_shared }}" == "false" ]]; then + echo "product_files=${{ steps.changes-dorny.outputs.other_shared_files }}" >> $GITHUB_OUTPUT + elif [[ "${{ steps.changes-dorny.outputs.product }}" == "true" && "${{ steps.changes-dorny.outputs.other_shared }}" == "false" ]]; then echo "::debug:: Only contracts in 'shared' folder were modified" echo "product_changes=true" >> $GITHUB_OUTPUT - echo "product_files=${{ steps.changes.outputs.product_files }}" >> $GITHUB_OUTPUT + echo "product_files=${{ steps.changes-dorny.outputs.product_files }}" >> $GITHUB_OUTPUT else echo "::debug:: No contracts were modified" echo "product_changes=false" >> $GITHUB_OUTPUT echo "product_files=" >> $GITHUB_OUTPUT fi else - echo "product_changes=${{ steps.changes.outputs.product }}" >> $GITHUB_OUTPUT - echo "product_files=${{ steps.changes.outputs.product_files }}" >> $GITHUB_OUTPUT + echo "product_changes=${{ steps.changes-dorny.outputs.product }}" >> $GITHUB_OUTPUT + echo "product_files=${{ steps.changes-dorny.outputs.product_files }}" >> $GITHUB_OUTPUT fi - name: Check for changes outside of artifact scope uses: ./.github/actions/validate-artifact-scope - if: ${{ steps.changes.outputs.sol == 'true' }} + if: ${{ steps.changes-dorny.outputs.sol == 'true' }} with: - sol_files: ${{ steps.changes.outputs.sol_files }} + sol_files: ${{ steps.changes-dorny.outputs.sol_files }} product: ${{ inputs.product }} - gather-basic-info: - name: Gather basic info - if: ${{ needs.changes.outputs.product_changes == 'true' }} + prepare-workflow-inputs: + name: Prepare workflow inputs runs-on: ubuntu-22.04 needs: [ changes ] outputs: foundry_version: ${{ steps.extract-foundry-version.outputs.foundry-version }} + generate_code_coverage: ${{ steps.skip-code-coverage.outputs.generate_code_coverage }} steps: - name: Checkout the repo uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - fetch-depth: 0 - name: Extract Foundry version id: extract-foundry-version @@ -123,250 +135,35 @@ jobs: with: working-directory: contracts - - name: Copy modified changesets - if: ${{ needs.changes.outputs.changeset_changes == 'true' }} - run: | - mkdir -p contracts/changesets - files="${{ needs.changes.outputs.changeset_files }}" - IFS=",' - for changeset in $files; do - echo "::debug:: Copying $changeset" - cp $changeset contracts/changesets - done - - - name: Generate basic info and modified contracts list - shell: bash - run: | - echo "Commit SHA used to generate artifacts: ${{ github.sha }}" > contracts/commit_sha_base_ref.txt - echo "Base reference SHA used to find modified contracts: ${{ inputs.base_ref }}" >> contracts/commit_sha_base_ref.txt - - IFS=',' read -r -a modified_files <<< "${{ needs.changes.outputs.product_files }}" - echo "# Modified contracts:" > contracts/modified_contracts.md - for file in "${modified_files[@]}"; do - echo " - [$file](${{ github.server_url }}/${{ github.repository }}/blob/${{ github.sha }}/$file)" >> contracts/modified_contracts.md - echo "$file" >> contracts/modified_contracts.txt - done - - - name: Upload basic info and modified contracts list - uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 - timeout-minutes: 2 - continue-on-error: true - with: - name: tmp-basic-info - path: | - contracts/modified_contracts.md - contracts/modified_contracts.txt - contracts/commit_sha_base_ref.txt - contracts/changesets - retention-days: 7 - - # some of the artifacts can only be generated on product level, and we cannot scope them to single contracts - # some product-level modifications might also require shared contracts changes, so if these happened we need to generate artifacts for shared contracts as well - coverage-and-book: - if: ${{ needs.changes.outputs.product_changes == 'true' }} - name: Generate Docs and Code Coverage reports - runs-on: ubuntu-22.04 - needs: [changes, gather-basic-info] - steps: - - name: Prepare exclusion list - id: prepare-exclusion-list - run: | - cat < coverage_exclusions.json - ["automation", "functions", "vrf"] - EOF - coverage_exclusions=$(cat coverage_exclusions.json | jq -c .) - echo "coverage_exclusions=$coverage_exclusions" >> $GITHUB_OUTPUT - - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - - name: Setup NodeJS - uses: ./.github/actions/setup-nodejs - - - name: Create directories - shell: bash + - name: Should skip code coverage report + id: skip-code-coverage run: | - mkdir -p contracts/code-coverage - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@8f1998e9878d786675189ef566a2e4bf24869773 # v1.2.0 - with: - version: ${{ needs.gather-basic-info.outputs.foundry_version }} - - # required for code coverage report generation - - name: Setup LCOV - uses: hrishikesh-kadam/setup-lcov@f5da1b26b0dcf5d893077a3c4f29cf78079c841d # v1.0.0 - - - name: Run Forge build for product contracts - if: ${{ needs.changes.outputs.product_changes == 'true' }} - run: | - forge --version - forge build - working-directory: contracts - env: - FOUNDRY_PROFILE: ${{ inputs.product }} - - - name: Run coverage for product contracts - if: ${{ !contains(fromJson(steps.prepare-exclusion-list.outputs.coverage_exclusions), inputs.product) && needs.changes.outputs.product_changes == 'true' }} - working-directory: contracts - run: forge coverage --report lcov --report-file code-coverage/lcov.info - env: - FOUNDRY_PROFILE: ${{ inputs.product }} - - - name: Generate Code Coverage HTML report for product contracts - if: ${{ !contains(fromJson(steps.prepare-exclusion-list.outputs.coverage_exclusions), inputs.product) && needs.changes.outputs.product_changes == 'true' }} - shell: bash - working-directory: contracts - run: genhtml code-coverage/lcov.info --branch-coverage --output-directory code-coverage - - - name: Run Forge doc for product contracts - if: ${{ needs.changes.outputs.product_changes == 'true' }} - run: forge doc --build -o docs - working-directory: contracts - env: - FOUNDRY_PROFILE: ${{ inputs.product }} - - - name: Upload Artifacts for product contracts - if: ${{ needs.changes.outputs.product_changes == 'true' }} - uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 - timeout-minutes: 2 - continue-on-error: true - with: - name: tmp-${{ inputs.product }}-artifacts - path: | - contracts/docs - contracts/code-coverage/lcov-.info - contracts/code-coverage - retention-days: 7 - - # Generates UML diagrams and Slither reports for modified contracts - uml-static-analysis: - if: ${{ needs.changes.outputs.product_changes == 'true' }} - name: Generate UML and Slither reports for modified contracts - runs-on: ubuntu-22.04 - needs: [changes, gather-basic-info] - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - fetch-depth: 0 - - - name: Setup NodeJS - uses: ./.github/actions/setup-nodejs - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@8f1998e9878d786675189ef566a2e4bf24869773 # v1.2.0 - with: - version: ${{ needs.gather-basic-info.outputs.foundry_version }} - - - name: Install Sol2uml - run: | - npm link sol2uml --only=production - - - name: Set up Python - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f #v5.1.1 - with: - python-version: '3.8' - - - name: Install solc-select and solc - uses: ./.github/actions/setup-solc-select - with: - to_install: '0.8.19' - to_use: '0.8.19' - - - name: Install Slither - uses: ./.github/actions/setup-slither - - - name: Generate UML - shell: bash - run: | - contract_list="${{ needs.changes.outputs.product_files }}" - - # modify remappings so that solc can find dependencies - ./contracts/scripts/ci/modify_remappings.sh contracts contracts/remappings.txt - mv remappings_modified.txt remappings.txt - - ./contracts/scripts/ci/generate_uml.sh "./" "contracts/uml-diagrams" "$contract_list" - - - name: Generate Slither Markdown reports - run: | - contract_list="${{ needs.changes.outputs.product_files }}" - - echo "::debug::Processing contracts: $contract_list" - ./contracts/scripts/ci/generate_slither_report.sh "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.sha }}/" contracts/configs/slither/.slither.config-artifacts.json "." "$contract_list" "contracts/slither-reports" "--solc-remaps @=contracts/node_modules/@" - - - name: Upload UMLs and Slither reports - uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 - timeout-minutes: 10 - continue-on-error: true - with: - name: tmp-contracts-artifacts - path: | - contracts/uml-diagrams - contracts/slither-reports - retention-days: 7 - - - name: Validate if all Slither run for all contracts - uses: ./.github/actions/validate-solidity-artifacts - with: - validate_slither_reports: 'true' - validate_uml_diagrams: 'true' - slither_reports_path: 'contracts/slither-reports' - uml_diagrams_path: 'contracts/uml-diagrams' - sol_files: ${{ needs.changes.outputs.product_files }} - - gather-all-artifacts: - name: Gather all artifacts - if: ${{ needs.changes.outputs.product_changes == 'true' }} - runs-on: ubuntu-latest - needs: [coverage-and-book, uml-static-analysis, gather-basic-info, changes] - steps: - - name: Download all artifacts - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 - with: - path: review_artifacts - merge-multiple: true - - - name: Upload all artifacts as single package - uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 - with: - name: review-artifacts-${{ inputs.product }}-${{ github.sha }} - path: review_artifacts - retention-days: 60 - - - name: Remove temporary artifacts - uses: geekyeggo/delete-artifact@24928e75e6e6590170563b8ddae9fac674508aa1 # v5.0 - with: - name: tmp-* - - - name: Print Artifact URL in job summary - 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=="review-artifacts-${{ inputs.product }}-${{ github.sha }}") | .id') - echo "Artifact ID: $ARTIFACT_ID" - - echo "# Solidity Review Artifact Generated" >> $GITHUB_STEP_SUMMARY - echo "Base Ref used: **${{ inputs.base_ref }}**" >> $GITHUB_STEP_SUMMARY - echo "Commit SHA used: **${{ github.sha }}**" >> $GITHUB_STEP_SUMMARY - echo "[Artifact URL](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/$ARTIFACT_ID)" >> $GITHUB_STEP_SUMMARY + if [[ "${{ inputs.product }}" = "automation" || "${{ inputs.product }}" = "vrf" || "${{ inputs.product }}" = "functions" ]]; then + echo "generate_code_coverage=false" >> $GITHUB_OUTPUT + else + echo "generate_code_coverage=true" >> $GITHUB_OUTPUT + fi - notify-no-changes: - if: ${{ needs.changes.outputs.product_changes == 'false' }} - needs: [changes] - runs-on: ubuntu-latest - steps: - - name: Print warning in job summary - shell: bash - run: | - echo "# Solidity Review Artifact NOT Generated" >> $GITHUB_STEP_SUMMARY - echo "Base Ref used: **${{ inputs.base_ref }}**" >> $GITHUB_STEP_SUMMARY - echo "Commit SHA used: **${{ github.sha }}**" >> $GITHUB_STEP_SUMMARY - echo "## Reason: No modified Solidity files found for ${{ inputs.product }}" >> $GITHUB_STEP_SUMMARY - echo "* no modified Solidity files found between ${{ inputs.base_ref }} and ${{ github.sha }} commits" >> $GITHUB_STEP_SUMMARY - echo "* or they are located outside of ./contracts/src/v0.8 folder" >> $GITHUB_STEP_SUMMARY - echo "* or they were limited to test files" >> $GITHUB_STEP_SUMMARY - exit 1 + generate-artifacts: + name: Generate Solidity Review Artifacts + needs: [changes, prepare-workflow-inputs] + uses: smartcontractkit/.github/.github/workflows/solidity-review-artifacts.yml@b6e37806737eef87e8c9137ceeb23ef0bff8b1db # validate-solidity-artifacts@0.1.0 + with: + product: ${{ inputs.product }} + commit_to_use: ${{ inputs.commit_to_use }} + base_ref: ${{ inputs.base_ref }} + product_changes: ${{ needs.changes.outputs.product_changes }} + product_files: ${{ needs.changes.outputs.product_files }} + changeset_changes: ${{ needs.changes.outputs.changeset_changes }} + changeset_files: ${{ needs.changes.outputs.changeset_files }} + foundry_version: ${{ needs.prepare-workflow-inputs.outputs.foundry_version }} + contracts_directory: './contracts' + generate_code_coverage: ${{ needs.prepare-workflow-inputs.outputs.generate_code_coverage == 'true' }} + link_with_jira: ${{ inputs.link_with_jira }} + jira_host: ${{ vars.JIRA_HOST }} + install_semver: false + slither_config_file_path: 'contracts/configs/slither/.slither.config-artifacts.json' + lcov_prune_script_path: 'scripts/lcov_prune' + secrets: + jira_username: ${{ secrets.JIRA_USERNAME }} + jira_api_token: ${{ secrets.JIRA_API_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/solidity-foundry.yml b/.github/workflows/solidity-foundry.yml index 7ba1d9bba2..36cfb660e1 100644 --- a/.github/workflows/solidity-foundry.yml +++ b/.github/workflows/solidity-foundry.yml @@ -28,19 +28,19 @@ jobs: cat < matrix.json [ { "name": "automation", "setup": { "run-coverage": false, "min-coverage": 98.5, "run-gas-snapshot": false, "run-forge-fmt": false }}, - { "name": "ccip", "setup": { "run-coverage": true, "min-coverage": 98.5, "run-gas-snapshot": true, "run-forge-fmt": true }}, + { "name": "ccip", "setup": { "run-coverage": true, "min-coverage": 97.6, "run-gas-snapshot": true, "run-forge-fmt": true }}, { "name": "functions", "setup": { "run-coverage": false, "min-coverage": 98.5, "run-gas-snapshot": true, "run-forge-fmt": false }}, - { "name": "keystone", "setup": { "run-coverage": true, "min-coverage": 74.1, "run-gas-snapshot": false, "run-forge-fmt": false }}, - { "name": "l2ep", "setup": { "run-coverage": true, "min-coverage": 65.6, "run-gas-snapshot": true, "run-forge-fmt": false }}, + { "name": "keystone", "setup": { "run-coverage": true, "min-coverage": 72.8, "run-gas-snapshot": false, "run-forge-fmt": false }}, + { "name": "l2ep", "setup": { "run-coverage": true, "min-coverage": 61.0, "run-gas-snapshot": true, "run-forge-fmt": false }}, { "name": "liquiditymanager", "setup": { "run-coverage": true, "min-coverage": 46.3, "run-gas-snapshot": true, "run-forge-fmt": false }}, { "name": "llo-feeds", "setup": { "run-coverage": true, "min-coverage": 49.3, "run-gas-snapshot": true, "run-forge-fmt": false }}, { "name": "operatorforwarder", "setup": { "run-coverage": true, "min-coverage": 55.7, "run-gas-snapshot": true, "run-forge-fmt": false }}, { "name": "shared", "setup": { "run-coverage": true, "extra-coverage-params": "--no-match-path='*CallWithExactGas*'", "min-coverage": 32.6, "run-gas-snapshot": true, "run-forge-fmt": false }}, - { "name": "transmission", "setup": { "run-coverage": true, "min-coverage": 65.6, "run-gas-snapshot": true, "run-forge-fmt": false }}, + { "name": "transmission", "setup": { "run-coverage": true, "min-coverage": 61.5, "run-gas-snapshot": true, "run-forge-fmt": false }}, { "name": "vrf", "setup": { "run-coverage": false, "min-coverage": 98.5, "run-gas-snapshot": false, "run-forge-fmt": false }} ] EOF - + matrix=$(cat matrix.json | jq -c .) echo "matrix=$matrix" >> $GITHUB_OUTPUT @@ -59,7 +59,6 @@ jobs: outputs: non_src_changes: ${{ steps.changes.outputs.non_src }} sol_modified_added: ${{ steps.changes.outputs.sol }} - sol_modified_added_files: ${{ steps.changes.outputs.sol_files }} sol_mod_only: ${{ steps.changes.outputs.sol_mod_only }} sol_mod_only_files: ${{ steps.changes.outputs.sol_mod_only_files }} not_test_sol_modified: ${{ steps.changes-non-test.outputs.not_test_sol }} @@ -75,10 +74,11 @@ 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' + - 'contracts/GNUmakefile' sol: - modified|added: 'contracts/src/v0.8/**/*.sol' sol_mod_only: @@ -130,9 +130,10 @@ jobs: - '!contracts/src/v0.8/*.t.sol' - '!contracts/src/v0.8/**/testhelpers/**' - '!contracts/src/v0.8/testhelpers/**' - - '!contracts/src/v0.8/vendor/**' + - '!contracts/src/v0.8/vendor/**' tests: + if: ${{ needs.changes.outputs.non_src_changes == 'true' || needs.changes.outputs.sol_modified_added == 'true' }} strategy: fail-fast: false matrix: @@ -160,6 +161,8 @@ jobs: || contains(fromJson(needs.changes.outputs.all_changes), 'shared') || needs.changes.outputs.non_src_changes == 'true' }} uses: ./.github/actions/setup-nodejs + with: + prod: "true" - name: Install Foundry if: ${{ contains(fromJson(needs.changes.outputs.all_changes), matrix.product.name) @@ -245,7 +248,7 @@ jobs: with: update-comment: false coverage-files: ./contracts/lcov.info.pruned - minimum-coverage: ${{ matrix.product.min-coverage }} + minimum-coverage: ${{ matrix.product.setup.min-coverage }} artifact-name: code-coverage-report-${{ matrix.product.name }} working-directory: ./contracts @@ -267,13 +270,18 @@ jobs: analyze: needs: [ changes, define-matrix ] name: Run static analysis - if: needs.changes.outputs.not_test_sol_modified == 'true' && false + if: needs.changes.outputs.not_test_sol_modified == 'true' runs-on: ubuntu-22.04 steps: - - name: Checkout the repo + - name: Checkout this repository + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + + - name: Checkout .github repository uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 with: - submodules: recursive + repository: smartcontractkit/.github + ref: b6e37806737eef87e8c9137ceeb23ef0bff8b1db # validate-solidity-artifacts@0.1.0 + path: ./dot_github - name: Setup NodeJS uses: ./.github/actions/setup-nodejs @@ -289,26 +297,26 @@ jobs: python-version: '3.8' - name: Install solc-select and solc - uses: ./.github/actions/setup-solc-select + uses: smartcontractkit/.github/actions/setup-solc-select@b6e37806737eef87e8c9137ceeb23ef0bff8b1db # validate-solidity-artifacts@0.1.0 with: - to_install: '0.8.19' - to_use: '0.8.19' + to_install: '0.8.24' + to_use: '0.8.24' - name: Install Slither - uses: ./.github/actions/setup-slither + uses: smartcontractkit/.github/actions/setup-slither@b6e37806737eef87e8c9137ceeb23ef0bff8b1db # validate-solidity-artifacts@0.1.0 - name: Run Slither shell: bash - run: | + run: | # modify remappings so that solc can find dependencies - ./contracts/scripts/ci/modify_remappings.sh contracts contracts/remappings.txt + ./dot_github/tools/scripts/solidity/modify_remappings.sh contracts contracts/remappings.txt mv remappings_modified.txt remappings.txt - + # without it Slither sometimes fails to use remappings correctly - cp contracts/foundry.toml foundry.toml + cp contracts/foundry.toml foundry.toml + + FILES="${{ needs.changes.outputs.not_test_sol_modified_files }}" - FILES="${{ needs.changes.outputs.not_test_sol_modified_files }}" - for FILE in $FILES; do PRODUCT=$(echo "$FILE" | awk -F'src/[^/]*/' '{print $2}' | cut -d'/' -f1) echo "::debug::Running Slither for $FILE in $PRODUCT" @@ -317,7 +325,7 @@ jobs: echo "::debug::No Slither config found for $PRODUCT, using default" SLITHER_CONFIG="contracts/configs/slither/.slither.config-default-pr.json" fi - ./contracts/scripts/ci/generate_slither_report.sh "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.sha }}/" "$SLITHER_CONFIG" "." "$FILE" "contracts/slither-reports-current" "--solc-remaps @=contracts/node_modules/@" + ./dot_github/tools/scripts/solidity/generate_slither_report.sh "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.sha }}/" "$SLITHER_CONFIG" "./contracts" "$FILE" "contracts/slither-reports-current" "--solc-remaps @=contracts/node_modules/@" done # all the actions below, up to printing results, run only if any existing contracts were modified @@ -340,7 +348,7 @@ jobs: continue-on-error: true with: name: tmp-slither-scripts-${{ github.sha }} - path: contracts/scripts/ci + path: ./dot_github/tools/scripts/solidity retention-days: 7 - name: Upload configs @@ -353,7 +361,7 @@ jobs: path: contracts/configs retention-days: 7 - - name: Checkout the repo + - name: Checkout earlier version of this repository if: needs.changes.outputs.sol_mod_only == 'true' uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 with: @@ -364,7 +372,7 @@ jobs: uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: tmp-slither-scripts-${{ github.sha }} - path: contracts/scripts/ci + path: ./dot_github/tools/scripts/solidity - name: Download configs if: needs.changes.outputs.sol_mod_only == 'true' @@ -383,19 +391,19 @@ jobs: shell: bash run: | # we need to set file permission again since they are lost during download - for file in contracts/scripts/ci/*.sh; do + for file in ./dot_github/tools/scripts/solidity/*.sh; do chmod +x "$file" done - + # modify remappings so that solc can find dependencies - ./contracts/scripts/ci/modify_remappings.sh contracts contracts/remappings.txt + ./dot_github/tools/scripts/solidity/modify_remappings.sh contracts contracts/remappings.txt mv remappings_modified.txt remappings.txt - + # without it Slither sometimes fails to use remappings correctly cp contracts/foundry.toml foundry.toml - + FILES="${{ needs.changes.outputs.sol_mod_only_files }}" - + for FILE in $FILES; do PRODUCT=$(echo "$FILE" | awk -F'src/[^/]*/' '{print $2}' | cut -d'/' -f1) echo "::debug::Running Slither for $FILE in $PRODUCT" @@ -404,8 +412,8 @@ jobs: echo "::debug::No Slither config found for $PRODUCT, using default" SLITHER_CONFIG="contracts/configs/slither/.slither.config-default-pr.json" fi - ./contracts/scripts/ci/generate_slither_report.sh "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.sha }}/" "$SLITHER_CONFIG" "." "$FILE" "contracts/slither-reports-base-ref" "--solc-remaps @=contracts/node_modules/@" - done + ./dot_github/tools/scripts/solidity/generate_slither_report.sh "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.sha }}/" "$SLITHER_CONFIG" "./contracts" "$FILE" "contracts/slither-reports-base-ref" "--solc-remaps @=contracts/node_modules/@" + done - name: Upload Slither report if: needs.changes.outputs.sol_mod_only == 'true' @@ -437,19 +445,19 @@ jobs: current_report="contracts/slither-reports-current/$filename" new_issues_report="contracts/slither-reports-current/${filename%.md}_new_issues.md" if [ -f "$current_report" ]; then - if ./contracts/scripts/ci/find_slither_report_diff.sh "$base_report" "$current_report" "$new_issues_report" "contracts/scripts/ci/prompt-difference.md" "contracts/scripts/ci/prompt-validation.md"; then - if [[ -s $new_issues_report ]]; then - awk 'NR==2{print "*This new issues report has been automatically generated by LLM model using two Slither reports. One based on `${{ github.base_ref}}` and another on `${{ github.sha }}` commits.*"}1' $new_issues_report > tmp.md && mv tmp.md $new_issues_report - echo "Replacing full Slither report with diff for $current_report" + if ./contracts/scripts/ci/find_slither_report_diff.sh "$base_report" "$current_report" "$new_issues_report" "contracts/scripts/ci/prompt-difference.md" "contracts/scripts/ci/prompt-validation.md"; then + if [[ -s $new_issues_report ]]; then + awk 'NR==2{print "*This new issues report has been automatically generated by LLM model using two Slither reports. One based on `${{ github.base_ref}}` and another on `${{ github.sha }}` commits.*"}1' $new_issues_report > tmp.md && mv tmp.md $new_issues_report + echo "Replacing full Slither report with diff for $current_report" rm $current_report && mv $new_issues_report $current_report - else + else echo "No difference detected between $base_report and $current_report reports. Won't include any of them." rm $current_report fi else echo "::warning::Failed to generate a diff report with new issues for $base_report using an LLM model, will use full report." fi - + else 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 @@ -467,7 +475,7 @@ jobs: done - name: Validate if all Slither run for all contracts - uses: ./.github/actions/validate-solidity-artifacts + uses: smartcontractkit/.github/actions/validate-solidity-artifacts@b6e37806737eef87e8c9137ceeb23ef0bff8b1db # validate-solidity-artifacts@0.1.0 with: validate_slither_reports: 'true' slither_reports_path: 'contracts/slither-reports-current' @@ -484,6 +492,8 @@ jobs: retention-days: 7 - name: Find Slither comment in the PR + # We only want to create the comment if the PR is not modified by a bot + if: "(github.event_name == 'push' && github.event.pusher.username && ! contains(github.event.pusher.username, '[bot]')) || (github.event_name != 'push' && ! contains(github.actor, '[bot]'))" uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.0.0 id: find-comment with: @@ -505,11 +515,13 @@ jobs: 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 + echo "slither_artifact_url=$slither_artifact_url" >> $GITHUB_OUTPUT - name: Create or update Slither comment in the PR + # We only want to create the comment if the PR is not modified by a bot + if: "(github.event_name == 'push' && github.event.pusher.username && ! contains(github.event.pusher.username, '[bot]')) || (github.event_name != 'push' && ! contains(github.actor, '[bot]'))" uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 with: comment-id: ${{ steps.find-comment.outputs.comment-id }} @@ -517,7 +529,7 @@ jobs: 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 @@ -548,23 +560,23 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout the repo - if: ${{ contains(fromJson(needs.changes.outputs.all_changes), matrix.product.name) && matrix.product.setup.run-forge-fmt }} + if: ${{ (contains(fromJson(needs.changes.outputs.all_changes), matrix.product.name) || needs.changes.outputs.non_src_changes == 'true') && matrix.product.setup.run-forge-fmt }} uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 with: submodules: recursive - name: Setup NodeJS - if: ${{ contains(fromJson(needs.changes.outputs.all_changes), matrix.product.name) && matrix.product.setup.run-forge-fmt }} + if: ${{ (contains(fromJson(needs.changes.outputs.all_changes), matrix.product.name) || needs.changes.outputs.non_src_changes == 'true') && matrix.product.setup.run-forge-fmt }} uses: ./.github/actions/setup-nodejs - name: Install Foundry - if: ${{ contains(fromJson(needs.changes.outputs.all_changes), matrix.product.name) && matrix.product.setup.run-forge-fmt }} + if: ${{ (contains(fromJson(needs.changes.outputs.all_changes), matrix.product.name) || needs.changes.outputs.non_src_changes == 'true') && matrix.product.setup.run-forge-fmt }} uses: foundry-rs/foundry-toolchain@8f1998e9878d786675189ef566a2e4bf24869773 # v1.2.0 with: version: ${{ needs.define-matrix.outputs.foundry-version }} - name: Run Forge fmt - if: ${{ contains(fromJson(needs.changes.outputs.all_changes), matrix.product.name) && matrix.product.setup.run-forge-fmt }} + if: ${{ (contains(fromJson(needs.changes.outputs.all_changes), matrix.product.name) || needs.changes.outputs.non_src_changes == 'true') && matrix.product.setup.run-forge-fmt }} run: forge fmt --check id: fmt working-directory: contracts @@ -572,7 +584,7 @@ jobs: FOUNDRY_PROFILE: ${{ matrix.product.name }} - name: Collect Metrics - if: ${{ contains(fromJson(needs.changes.outputs.all_changes), matrix.product.name) && matrix.product.setup.run-forge-fmt }} + if: ${{ (contains(fromJson(needs.changes.outputs.all_changes), matrix.product.name) || needs.changes.outputs.non_src_changes == 'true') && matrix.product.setup.run-forge-fmt }} id: collect-gha-metrics uses: smartcontractkit/push-gha-metrics-action@dea9b546553cb4ca936607c2267a09c004e4ab3f # v3.0.0 with: @@ -581,70 +593,4 @@ jobs: basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} this-job-name: Forge fmt ${{ matrix.product.name }} - continue-on-error: true - - coverage: - needs: [define-matrix, changes] - name: Coverage - runs-on: ubuntu-latest - env: - FOUNDRY_PROFILE: ccip - - steps: - - name: Collect Metrics - if: ${{ needs.changes.outputs.non_src_changes == 'true' || needs.changes.outputs.sol_modified_added == 'true' }} - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@dea9b546553cb4ca936607c2267a09c004e4ab3f # v3.0.0 - with: - id: ccip-solidity-foundry-coverage - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: Coverage - continue-on-error: true - - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - with: - submodules: recursive - - # Only needed because we use the NPM versions of packages - # and not native Foundry. This is to make sure the dependencies - # stay in sync. - - name: Setup NodeJS - uses: ./.github/actions/setup-nodejs - - - name: Install Foundry - if: ${{ needs.changes.outputs.non_src_changes == 'true' || needs.changes.outputs.sol_modified_added == 'true' }} - uses: foundry-rs/foundry-toolchain@8f1998e9878d786675189ef566a2e4bf24869773 # v1.2.0 - with: - version: ${{ needs.define-matrix.outputs.foundry-version }} - - - name: Run Forge build - if: ${{ needs.changes.outputs.non_src_changes == 'true' || needs.changes.outputs.sol_modified_added == 'true' }} - working-directory: contracts - run: | - forge --version - forge build - id: build - - - name: Run coverage - if: ${{ needs.changes.outputs.non_src_changes == 'true' || needs.changes.outputs.sol_modified_added == 'true' }} - working-directory: contracts - run: forge coverage --report lcov - - - name: Prune report - if: ${{ needs.changes.outputs.non_src_changes == 'true' || needs.changes.outputs.sol_modified_added == 'true' }} - run: | - sudo apt-get install lcov - ./tools/ci/ccip_lcov_prune ./contracts/lcov.info ./lcov.info.pruned - - - name: Report code coverage - if: ${{ needs.changes.outputs.non_src_changes == 'true' || needs.changes.outputs.sol_modified_added == 'true' }} - uses: zgosalvez/github-actions-report-lcov@a546f89a65a0cdcd82a92ae8d65e74d450ff3fbc # v4.1.4 - with: - update-comment: true - coverage-files: lcov.info.pruned - minimum-coverage: 97.6 - artifact-name: code-coverage-report - working-directory: ./contracts - github-token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + continue-on-error: true \ No newline at end of file diff --git a/.github/workflows/solidity-hardhat.yml b/.github/workflows/solidity-hardhat.yml deleted file mode 100644 index 4fb43ee962..0000000000 --- a/.github/workflows/solidity-hardhat.yml +++ /dev/null @@ -1,87 +0,0 @@ -name: Solidity-Hardhat - -on: - merge_group: - push: - -env: - NODE_OPTIONS: --max_old_space_size=8192 - -defaults: - run: - shell: bash - -jobs: - changes: - name: Detect changes - runs-on: ubuntu-latest - outputs: - changes: ${{ steps.changes.outputs.src }} - steps: - - name: Checkout the repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 - id: changes - with: - filters: | - src: - - 'contracts/src/!(v0.8/(ccip|functions|keystone|l2ep|liquiditymanager|llo-feeds|transmission|vrf)/**)/**/*' - - 'contracts/test/**/*' - - 'contracts/package.json' - - 'contracts/pnpm-lock.yaml' - - 'contracts/hardhat.config.ts' - - '.github/workflows/solidity-hardhat.yml' - -# hardhat-test: -# needs: [changes] -# if: needs.changes.outputs.changes == 'true' -# name: Solidity ${{ fromJSON('["(skipped)", ""]')[needs.changes.outputs.changes == 'true'] }} -# runs-on: ubuntu-latest -# steps: -# - name: Checkout the repo -# uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 -# - name: Setup NodeJS -# uses: ./.github/actions/setup-nodejs -# - name: Setup Hardhat -# uses: ./.github/actions/setup-hardhat -# with: -# namespace: coverage -# - name: Run tests -# working-directory: contracts -# run: pnpm test -# - name: Collect Metrics -# id: collect-gha-metrics -# uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 -# with: -# id: hardhat-test -# org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} -# basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} -# hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} -# continue-on-error: true -# -# solidity: -# needs: [changes, hardhat-test] -# name: Solidity -# runs-on: ubuntu-latest -# if: always() -# steps: -# - run: echo 'Solidity tests finished!' -# - name: Check test results -# run: | -# if [[ "${{ needs.changes.result }}" = "failure" || "${{ needs.solidity-splits.result }}" = "failure" ]]; then -# echo "One or more changes / solidity-splits jobs failed" -# exit 1 -# else -# echo "All test jobs passed successfully" -# fi -# - name: Collect Metrics -# if: always() -# id: collect-gha-metrics -# uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 -# with: -# id: solidity-tests -# org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} -# basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} -# hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} -# this-job-name: Solidity -# continue-on-error: true diff --git a/.github/workflows/solidity-tracability.yml b/.github/workflows/solidity-tracability.yml new file mode 100644 index 0000000000..75774713d9 --- /dev/null +++ b/.github/workflows/solidity-tracability.yml @@ -0,0 +1,203 @@ +# This workflow handles the enforcement of code Traceability via changesets and jira issue linking for our Solidity codebase. +name: Solidity Tracability + +on: + merge_group: + pull_request: + +defaults: + run: + shell: bash + +jobs: + files-changed: + # The job skips on merge_group events, and any release branches, and forks + # Since we only want to enforce Jira issues on pull requests related to feature branches + if: ${{ github.event_name != 'merge_group' && !startsWith(github.head_ref, 'release/') && github.event.pull_request.head.repo.full_name == 'smartcontractkit/chainlink' }} + name: Detect Changes + runs-on: ubuntu-latest + outputs: + source: ${{ steps.files-changed.outputs.source }} + changesets: ${{ steps.files-changed.outputs.changesets }} + changesets_files: ${{ steps.files-changed.outputs.changesets_files }} + steps: + - name: Checkout the repo + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + + - name: Filter paths + uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + id: files-changed + with: + list-files: "csv" + # This is a valid input, see https://github.com/dorny/paths-filter/pull/226 + predicate-quantifier: "every" + filters: | + source: + - contracts/**/*.sol + - '!contracts/**/*.t.sol' + changesets: + - added|modified: 'contracts/.changeset/**' + + enforce-traceability: + # Note: A job that is skipped will report its status as "Success". + # It will not prevent a pull request from merging, even if it is a required check. + needs: [files-changed] + # We only want to run this job if the source files have changed + if: ${{ needs.files-changed.outputs.source == 'true' }} + name: Enforce Traceability + runs-on: ubuntu-latest + permissions: + actions: read + id-token: write + contents: read + pull-requests: write + steps: + # https://github.com/planetscale/ghcommit-action/blob/c7915d6c18d5ce4eb42b0eff3f10a29fe0766e4c/README.md?plain=1#L41 + # + # Include the pull request ref in the checkout action to prevent merge commit + # https://github.com/actions/checkout?tab=readme-ov-file#checkout-pull-request-head-commit-instead-of-merge-commit + - name: Checkout the repo + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Assume role capable of dispatching action + uses: smartcontractkit/.github/actions/setup-github-token@ef78fa97bf3c77de6563db1175422703e9e6674f # setup-github-token@0.2.1 + id: get-gh-token + with: + aws-role-arn: ${{ secrets.AWS_OIDC_CHAINLINK_CI_AUTO_PR_TOKEN_ISSUER_ROLE_ARN }} + aws-lambda-url: ${{ secrets.AWS_INFRA_RELENG_TOKEN_ISSUER_LAMBDA_URL }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Make a comment + uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0 + with: + message: | + I see you updated files related to `contracts`. Please run `pnpm changeset` in the `contracts` directory to add a changeset. + reactions: eyes + comment_tag: changeset-contracts + # If the changeset is added, then we delete the comment, otherwise we add it. + mode: ${{ needs.files-changed.outputs.changesets == 'true' && 'delete' || 'upsert' }} + # We only create the comment if the changeset is not added + create_if_not_exists: ${{ needs.files-changed.outputs.changesets == 'true' && 'false' || 'true' }} + + - name: Check for new changeset for contracts + if: ${{ needs.files-changed.outputs.changesets == 'false' }} + shell: bash + run: | + echo "Please run pnpm changeset to add a changeset for contracts." + exit 1 + + - name: Setup NodeJS + uses: ./.github/actions/setup-nodejs + + - name: Checkout .Github repository + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + with: + repository: smartcontractkit/.github + ref: 9aed33e5298471f20a3d630d711b96ae5538728c # jira-tracing@0.2.0 + path: ./dot_github + + # we need to set the top level directory for the jira-tracing action manually + # because now we are working with two repositories and automatic detection would + # select the repository with jira-tracing and not the chainlink repository + - name: Setup git top level directory + id: find-git-top-level-dir + run: echo "top_level_dir=$(pwd)" >> $GITHUB_OUTPUT + + - name: Setup Jira + working-directory: ./dot_github + run: pnpm install --filter jira-tracing + + # Because of our earlier checks, we know that both the source and changeset files have changed + - name: Enforce Traceability + working-directory: ./dot_github + run: | + echo "COMMIT_MESSAGE=$(git log -1 --pretty=format:'%s')" >> $GITHUB_ENV + pnpm --filter jira-tracing issue:enforce + env: + CHANGESET_FILES: ${{ needs.files-changed.outputs.changesets_files }} + GIT_TOP_LEVEL_DIR: ${{ steps.find-git-top-level-dir.outputs.top_level_dir }} + + PR_TITLE: ${{ github.event.pull_request.title }} + BRANCH_NAME: ${{ github.event.pull_request.head.ref }} + + JIRA_HOST: ${{ vars.JIRA_HOST }} + JIRA_USERNAME: ${{ secrets.JIRA_USERNAME }} + JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} + + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Enforce Solidity Review Jira issue + working-directory: ./dot_github + shell: bash + run: | + # we do not want to fail the workflow if there are issues with the script + if ! pnpm --filter jira-tracing issue:enforce-solidity-review; then + echo "::warning::Failed to enforce Solidity Review Jira issue, this is not a blocking issue. You can safely ignore it." + fi + env: + CHANGESET_FILES: ${{ needs.files-changed.outputs.changesets_files }} + GIT_TOP_LEVEL_DIR: ${{ steps.find-git-top-level-dir.outputs.top_level_dir }} + + SOLIDITY_REVIEW_TEMPLATE_KEY: 'TT-1634' + EXPORT_JIRA_ISSUE_KEYS: 'true' + + JIRA_HOST: ${{ vars.JIRA_HOST }} + JIRA_USERNAME: ${{ secrets.JIRA_USERNAME }} + JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} + + # Commit appended changeset file back to repo + - uses: planetscale/ghcommit-action@13a844326508cdefc72235201bb0446d6d10a85f # v0.1.6 + with: + commit_message: "[Bot] Update changeset file with jira issues" + repo: ${{ github.repository }} + branch: ${{ github.head_ref }} + file_pattern: "contracts/.changeset/*" + env: + GITHUB_TOKEN: ${{ steps.get-gh-token.outputs.access-token }} + + - name: Read issue keys from env vars + shell: bash + id: read-issue-keys + run: | + # issue:enforce-solidity-review should have set two env vars with the issue keys + echo "Jira issue key related to pr: ${{ env.PR_JIRA_ISSUE_KEY }}" + echo "Jira issue key related to solidity review: ${{ env.SOLIDITY_REVIEW_JIRA_ISSUE_KEY }}" + + - name: Find traceability 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: 'Solidity Review Jira issue' + + - name: Create or update traceability 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: | + ## Solidity Review Jira issue + Hey! We have taken the liberty to link this PR to a Jira issue for Solidity Review. + + This is a new feature, that's currently in the pilot phase, so please make sure that the linkage is correct. In a contrary case, please update it manually in JIRA and replace Solidity Review issue key in the changeset file with the correct one. + Please reach out to the Test Tooling team and notify them about any issues you encounter. + + Any changes to the Solidity Review Jira issue should be reflected in the changeset file. If you need to update the issue key, please do so manually in the following changeset file: `${{ needs.files-changed.outputs.changesets_files }}` + + This PR has been linked to Solidity Review Jira issue: [${{ env.SOLIDITY_REVIEW_JIRA_ISSUE_KEY }}](${{ vars.JIRA_HOST }}browse/${{ env.SOLIDITY_REVIEW_JIRA_ISSUE_KEY }}) + edit-mode: replace + + - name: Collect Metrics + id: collect-gha-metrics + if: always() + uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 + with: + id: soldity-traceability + org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} + basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} + hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} + this-job-name: Enforce Traceability + continue-on-error: true diff --git a/.github/workflows/solidity-wrappers.yml b/.github/workflows/solidity-wrappers.yml index 40737774d5..42371755c9 100644 --- a/.github/workflows/solidity-wrappers.yml +++ b/.github/workflows/solidity-wrappers.yml @@ -2,7 +2,7 @@ name: Solidity Wrappers # This is its own workflow file rather than being merged into "solidity.yml" to avoid over complicating the conditionals # used for job execution. The jobs in "solidity.yml" are configured around push events, whereas # we only want to generate gethwrappers during pull requests. -on: +on: pull_request: types: - opened @@ -31,7 +31,7 @@ jobs: # On a pull request event, make updates to gethwrappers if there are changes. update-wrappers: needs: [changes] - if: needs.changes.outputs.changes == 'true' + if: needs.changes.outputs.changes == 'true' name: Update Wrappers permissions: actions: read @@ -65,7 +65,7 @@ jobs: - name: Commit any wrapper changes uses: planetscale/ghcommit-action@21a8cda29f55e5cc2cdae0cdbdd08e38dd148c25 # v0.1.37 with: - commit_message: "Update gethwrappers" + commit_message: "Update gethwrappers" repo: ${{ github.repository }} branch: ${{ github.head_ref }} file_pattern: "core/gethwrappers/**/generated/*.go core/gethwrappers/**/generated-wrapper-dependency-versions-do-not-edit.txt" @@ -80,5 +80,5 @@ jobs: org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: Update Wrappers + this-job-name: Update Wrappers continue-on-error: true diff --git a/.gitignore b/.gitignore index 622455dd29..f875a12363 100644 --- a/.gitignore +++ b/.gitignore @@ -72,11 +72,13 @@ ztarrepo.tar.gz **/test-ledger/* __debug_bin* .test_summary/ +db_dumps/ .run.id integration-tests/**/traces/ benchmark_report.csv benchmark_summary.json -integration-tests/citool/output.csv +secrets.toml +tmp_laneconfig/ # goreleaser builds cosign.* @@ -97,6 +99,7 @@ core/scripts/ccip/csv/node-wallets *report.xml *report.json *.out +dot_graphs/ contracts/yarn.lock @@ -119,7 +122,9 @@ tools/flakeytests/coverage.txt # Runtime test configuration that might contain secrets override*.toml -# Pythin venv +# Python venv .venv/ ocr_soak_report.csv + +vendor/* diff --git a/.golangci.yml b/.golangci.yml index 7155cff2d5..0696493075 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -127,9 +127,9 @@ linters-settings: desc: Use github.com/stretchr/testify/mock instead - pkg: github.com/test-go/testify/require desc: Use github.com/stretchr/testify/require instead -# TODO https://smartcontract-it.atlassian.net/browse/BCI-2589 -# - pkg: go.uber.org/multierr -# desc: Use the standard library instead, for example https://pkg.go.dev/errors#Join + # TODO https://smartcontract-it.atlassian.net/browse/BCI-2589 + # - pkg: go.uber.org/multierr + # desc: Use the standard library instead, for example https://pkg.go.dev/errors#Join - pkg: gopkg.in/guregu/null.v1 desc: Use gopkg.in/guregu/null.v4 instead - pkg: gopkg.in/guregu/null.v2 @@ -165,4 +165,4 @@ issues: - path: test text: "^G404:" linters: - - gosec + - gosec \ No newline at end of file diff --git a/.goreleaser.ccip.develop.yaml b/.goreleaser.ccip.develop.yaml new file mode 100644 index 0000000000..595fe14c27 --- /dev/null +++ b/.goreleaser.ccip.develop.yaml @@ -0,0 +1,229 @@ +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 }} + - IMAGE_NAME={{ if index .Env "IMAGE_NAME" }}{{ .Env.IMAGE_NAME }}{{ else }}chainlink{{ end }} + - IMAGE_TAG={{ if index .Env "IMAGE_TAG" }}{{ .Env.IMAGE_TAG }}{{ else }}develop{{ end }} + - IMAGE_LABEL_DESCRIPTION="node of the decentralized oracle network, bridging on and off-chain computation" + - IMAGE_LABEL_LICENSES="MIT" + - IMAGE_LABEL_SOURCE="https://github.com/smartcontractkit/{{ .ProjectName }}" + +before: + hooks: + - go mod tidy + - ./tools/bin/goreleaser_utils before_hook + +# See https://goreleaser.com/customization/build/ +builds: + - binary: chainlink + id: linux-arm64 + goos: + - linux + goarch: + - arm64 + hooks: + post: ./tools/bin/goreleaser_utils build_post_hook {{ dir .Path }} {{ .Os }} {{ .Arch }} + env: + - CGO_ENABLED=1 + - CC=$ZIG_EXEC cc -target aarch64-linux-gnu + - CCX=$ZIG_EXEC c++ -target aarch64-linux-gnu + flags: + - -trimpath + - -buildmode=pie + ldflags: + - -s -w -r=$ORIGIN/libs + - -X github.com/smartcontractkit/chainlink/v2/core/static.Version={{ .Env.CHAINLINK_VERSION }} + - -X github.com/smartcontractkit/chainlink/v2/core/static.Sha={{ .FullCommit }} + - binary: chainlink + id: linux-amd64 + goos: + - linux + goarch: + - amd64 + hooks: + post: ./tools/bin/goreleaser_utils build_post_hook {{ dir .Path }} {{ .Os }} {{ .Arch }} + env: + - CGO_ENABLED=1 + - CC=$ZIG_EXEC cc -target x86_64-linux-gnu + - CCX=$ZIG_EXEC c++ -target x86_64-linux-gnu + flags: + - -trimpath + - -buildmode=pie + ldflags: + - -s -w -r=$ORIGIN/libs + - -X github.com/smartcontractkit/chainlink/v2/core/static.Version={{ .Env.CHAINLINK_VERSION }} + - -X github.com/smartcontractkit/chainlink/v2/core/static.Sha={{ .FullCommit }} + +# See https://goreleaser.com/customization/docker/ +dockers: + - id: linux-amd64 + dockerfile: core/chainlink.goreleaser.Dockerfile + use: buildx + goos: linux + goarch: amd64 + extra_files: + - tmp/linux_amd64/libs + - tools/bin/ldd_fix + - ccip/config + build_flag_templates: + - "--platform=linux/amd64" + - "--pull" + - "--build-arg=CHAINLINK_USER=chainlink" + - "--build-arg=COMMIT_SHA={{ .FullCommit }}" + - "--build-arg=CL_CHAIN_DEFAULTS=/chainlink/ccip-config" + - "--label=org.opencontainers.image.created={{ .Date }}" + - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" + - "--label=org.opencontainers.image.licenses={{ .Env.IMAGE_LABEL_LICENSES }}" + - "--label=org.opencontainers.image.revision={{ .FullCommit }}" + - "--label=org.opencontainers.image.source={{ .Env.IMAGE_LABEL_SOURCE }}" + - "--label=org.opencontainers.image.title={{ .ProjectName }}" + - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" + - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" + image_templates: + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-amd64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-amd64" + - id: linux-arm64 + dockerfile: core/chainlink.goreleaser.Dockerfile + use: buildx + goos: linux + goarch: arm64 + extra_files: + - tmp/linux_arm64/libs + - tools/bin/ldd_fix + - ccip/config + build_flag_templates: + - "--platform=linux/arm64" + - "--pull" + - "--build-arg=CHAINLINK_USER=chainlink" + - "--build-arg=COMMIT_SHA={{ .FullCommit }}" + - "--build-arg=CL_CHAIN_DEFAULTS=/chainlink/ccip-config" + - "--label=org.opencontainers.image.created={{ .Date }}" + - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" + - "--label=org.opencontainers.image.licenses={{ .Env.IMAGE_LABEL_LICENSES }}" + - "--label=org.opencontainers.image.revision={{ .FullCommit }}" + - "--label=org.opencontainers.image.source={{ .Env.IMAGE_LABEL_SOURCE }}" + - "--label=org.opencontainers.image.title={{ .ProjectName }}" + - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" + - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" + image_templates: + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-arm64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-arm64" + - id: linux-amd64-plugins + dockerfile: core/chainlink.goreleaser.Dockerfile + use: buildx + goos: linux + goarch: amd64 + extra_files: + - tmp/linux_amd64/libs + - tmp/linux_amd64/plugins + - tools/bin/ldd_fix + - ccip/config + build_flag_templates: + - "--platform=linux/amd64" + - "--pull" + - "--build-arg=CHAINLINK_USER=chainlink" + - "--build-arg=COMMIT_SHA={{ .FullCommit }}" + - "--build-arg=CL_MEDIAN_CMD=chainlink-feeds" + - "--build-arg=CL_MERCURY_CMD=chainlink-mercury" + - "--build-arg=CL_SOLANA_CMD=chainlink-solana" + - "--build-arg=CL_STARKNET_CMD=chainlink-starknet" + - "--build-arg=CL_CHAIN_DEFAULTS=/chainlink/ccip-config" + - "--label=org.opencontainers.image.created={{ .Date }}" + - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" + - "--label=org.opencontainers.image.licenses={{ .Env.IMAGE_LABEL_LICENSES }}" + - "--label=org.opencontainers.image.revision={{ .FullCommit }}" + - "--label=org.opencontainers.image.source={{ .Env.IMAGE_LABEL_SOURCE }}" + - "--label=org.opencontainers.image.title={{ .ProjectName }}" + - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" + - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" + image_templates: + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-amd64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-amd64" + - id: linux-arm64-plugins + dockerfile: core/chainlink.goreleaser.Dockerfile + use: buildx + goos: linux + goarch: arm64 + extra_files: + - tmp/linux_arm64/libs + - tmp/linux_arm64/plugins + - tools/bin/ldd_fix + - ccip/config + build_flag_templates: + - "--platform=linux/arm64" + - "--pull" + - "--build-arg=CHAINLINK_USER=chainlink" + - "--build-arg=COMMIT_SHA={{ .FullCommit }}" + - "--build-arg=CL_MEDIAN_CMD=chainlink-feeds" + - "--build-arg=CL_MERCURY_CMD=chainlink-mercury" + - "--build-arg=CL_SOLANA_CMD=chainlink-solana" + - "--build-arg=CL_STARKNET_CMD=chainlink-starknet" + - "--build-arg=CL_CHAIN_DEFAULTS=/chainlink/ccip-config" + - "--label=org.opencontainers.image.created={{ .Date }}" + - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" + - "--label=org.opencontainers.image.licenses={{ .Env.IMAGE_LABEL_LICENSES }}" + - "--label=org.opencontainers.image.revision={{ .FullCommit }}" + - "--label=org.opencontainers.image.source={{ .Env.IMAGE_LABEL_SOURCE }}" + - "--label=org.opencontainers.image.title={{ .ProjectName }}" + - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" + - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" + image_templates: + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-arm64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-arm64" + +# See https://goreleaser.com/customization/docker_manifest/ +docker_manifests: + - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}" + image_templates: + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-amd64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-arm64" + - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}" + image_templates: + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-amd64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-arm64" + - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins" + image_templates: + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-amd64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-arm64" + - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins" + image_templates: + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-amd64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-arm64" + +# See https://goreleaser.com/customization/docker_sign/ +docker_signs: + - artifacts: all + args: + - "sign" + - "${artifact}" + - "--yes" + +checksum: + name_template: "checksums.txt" + +snapshot: + version_template: "{{ .Env.CHAINLINK_VERSION }}-{{ .ShortCommit }}" + +partial: + by: target + +# See https://goreleaser.com/customization/release/ +release: + disable: true + +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" +# modelines, feel free to remove those if you don't want/use them: +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json +# vim: set ts=2 sw=2 tw=0 fo=cnqoj diff --git a/.goreleaser.ccip.production.yaml b/.goreleaser.ccip.production.yaml new file mode 100644 index 0000000000..1247be143c --- /dev/null +++ b/.goreleaser.ccip.production.yaml @@ -0,0 +1,229 @@ +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 }} + - IMAGE_NAME={{ if index .Env "IMAGE_NAME" }}{{ .Env.IMAGE_NAME }}{{ else }}chainlink{{ end }} + - IMAGE_TAG={{ if index .Env "IMAGE_TAG" }}{{ .Env.IMAGE_TAG }}{{ else }}develop{{ end }} + - IMAGE_LABEL_DESCRIPTION="node of the decentralized oracle network, bridging on and off-chain computation" + - IMAGE_LABEL_LICENSES="MIT" + - IMAGE_LABEL_SOURCE="https://github.com/smartcontractkit/{{ .ProjectName }}" + +before: + hooks: + - go mod tidy + - ./tools/bin/goreleaser_utils before_hook + +# See https://goreleaser.com/customization/build/ +builds: + - binary: chainlink + id: linux-arm64 + goos: + - linux + goarch: + - arm64 + hooks: + post: ./tools/bin/goreleaser_utils build_post_hook {{ dir .Path }} {{ .Os }} {{ .Arch }} + env: + - CGO_ENABLED=1 + - CC=$ZIG_EXEC cc -target aarch64-linux-gnu + - CCX=$ZIG_EXEC c++ -target aarch64-linux-gnu + flags: + - -trimpath + - -buildmode=pie + ldflags: + - -s -w -r=$ORIGIN/libs + - -X github.com/smartcontractkit/chainlink/v2/core/static.Version={{ .Env.CHAINLINK_VERSION }} + - -X github.com/smartcontractkit/chainlink/v2/core/static.Sha={{ .FullCommit }} + - binary: chainlink + id: linux-amd64 + goos: + - linux + goarch: + - amd64 + hooks: + post: ./tools/bin/goreleaser_utils build_post_hook {{ dir .Path }} {{ .Os }} {{ .Arch }} + env: + - CGO_ENABLED=1 + - CC=$ZIG_EXEC cc -target x86_64-linux-gnu + - CCX=$ZIG_EXEC c++ -target x86_64-linux-gnu + flags: + - -trimpath + - -buildmode=pie + ldflags: + - -s -w -r=$ORIGIN/libs + - -X github.com/smartcontractkit/chainlink/v2/core/static.Version={{ .Env.CHAINLINK_VERSION }} + - -X github.com/smartcontractkit/chainlink/v2/core/static.Sha={{ .FullCommit }} + +# See https://goreleaser.com/customization/docker/ +dockers: + - id: linux-amd64 + dockerfile: core/chainlink.goreleaser.Dockerfile + use: buildx + goos: linux + goarch: amd64 + extra_files: + - tmp/linux_amd64/libs + - tools/bin/ldd_fix + - ccip/config + build_flag_templates: + - "--platform=linux/amd64" + - "--pull" + - "--build-arg=CHAINLINK_USER=chainlink" + - "--build-arg=COMMIT_SHA={{ .FullCommit }}" + - "--build-arg=CL_CHAIN_DEFAULTS=/chainlink/ccip-config" + - "--label=org.opencontainers.image.created={{ .Date }}" + - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" + - "--label=org.opencontainers.image.licenses={{ .Env.IMAGE_LABEL_LICENSES }}" + - "--label=org.opencontainers.image.revision={{ .FullCommit }}" + - "--label=org.opencontainers.image.source={{ .Env.IMAGE_LABEL_SOURCE }}" + - "--label=org.opencontainers.image.title={{ .ProjectName }}" + - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" + - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" + image_templates: + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-amd64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-amd64" + - id: linux-arm64 + dockerfile: core/chainlink.goreleaser.Dockerfile + use: buildx + goos: linux + goarch: arm64 + extra_files: + - tmp/linux_arm64/libs + - tools/bin/ldd_fix + - ccip/config + build_flag_templates: + - "--platform=linux/arm64" + - "--pull" + - "--build-arg=CHAINLINK_USER=chainlink" + - "--build-arg=COMMIT_SHA={{ .FullCommit }}" + - "--build-arg=CL_CHAIN_DEFAULTS=/chainlink/ccip-config" + - "--label=org.opencontainers.image.created={{ .Date }}" + - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" + - "--label=org.opencontainers.image.licenses={{ .Env.IMAGE_LABEL_LICENSES }}" + - "--label=org.opencontainers.image.revision={{ .FullCommit }}" + - "--label=org.opencontainers.image.source={{ .Env.IMAGE_LABEL_SOURCE }}" + - "--label=org.opencontainers.image.title={{ .ProjectName }}" + - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" + - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" + image_templates: + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-arm64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-arm64" + - id: linux-amd64-plugins + dockerfile: core/chainlink.goreleaser.Dockerfile + use: buildx + goos: linux + goarch: amd64 + extra_files: + - tmp/linux_amd64/libs + - tmp/linux_amd64/plugins + - tools/bin/ldd_fix + - ccip/config + build_flag_templates: + - "--platform=linux/amd64" + - "--pull" + - "--build-arg=CHAINLINK_USER=chainlink" + - "--build-arg=COMMIT_SHA={{ .FullCommit }}" + - "--build-arg=CL_MEDIAN_CMD=chainlink-feeds" + - "--build-arg=CL_MERCURY_CMD=chainlink-mercury" + - "--build-arg=CL_SOLANA_CMD=chainlink-solana" + - "--build-arg=CL_STARKNET_CMD=chainlink-starknet" + - "--build-arg=CL_CHAIN_DEFAULTS=/chainlink/ccip-config" + - "--label=org.opencontainers.image.created={{ .Date }}" + - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" + - "--label=org.opencontainers.image.licenses={{ .Env.IMAGE_LABEL_LICENSES }}" + - "--label=org.opencontainers.image.revision={{ .FullCommit }}" + - "--label=org.opencontainers.image.source={{ .Env.IMAGE_LABEL_SOURCE }}" + - "--label=org.opencontainers.image.title={{ .ProjectName }}" + - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" + - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" + image_templates: + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-amd64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-amd64" + - id: linux-arm64-plugins + dockerfile: core/chainlink.goreleaser.Dockerfile + use: buildx + goos: linux + goarch: arm64 + extra_files: + - tmp/linux_arm64/libs + - tmp/linux_arm64/plugins + - tools/bin/ldd_fix + - ccip/config + build_flag_templates: + - "--platform=linux/arm64" + - "--pull" + - "--build-arg=CHAINLINK_USER=chainlink" + - "--build-arg=COMMIT_SHA={{ .FullCommit }}" + - "--build-arg=CL_MEDIAN_CMD=chainlink-feeds" + - "--build-arg=CL_MERCURY_CMD=chainlink-mercury" + - "--build-arg=CL_SOLANA_CMD=chainlink-solana" + - "--build-arg=CL_STARKNET_CMD=chainlink-starknet" + - "--build-arg=CL_CHAIN_DEFAULTS=/chainlink/ccip-config" + - "--label=org.opencontainers.image.created={{ .Date }}" + - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" + - "--label=org.opencontainers.image.licenses={{ .Env.IMAGE_LABEL_LICENSES }}" + - "--label=org.opencontainers.image.revision={{ .FullCommit }}" + - "--label=org.opencontainers.image.source={{ .Env.IMAGE_LABEL_SOURCE }}" + - "--label=org.opencontainers.image.title={{ .ProjectName }}" + - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" + - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" + image_templates: + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-arm64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-arm64" + +# See https://goreleaser.com/customization/docker_manifest/ +docker_manifests: + - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}" + image_templates: + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-amd64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-arm64" + - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}" + image_templates: + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-amd64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-arm64" + - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins" + image_templates: + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-amd64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-arm64" + - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins" + image_templates: + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-amd64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-arm64" + +# See https://goreleaser.com/customization/docker_sign/ +docker_signs: + - artifacts: all + args: + - "sign" + - "${artifact}" + - "--yes" + +checksum: + name_template: "checksums.txt" + +# See https://goreleaser.com/customization/sbom +sboms: + - artifacts: archive + +snapshot: + version_template: "{{ .Env.CHAINLINK_VERSION }}-{{ .ShortCommit }}" + +partial: + by: target + +# See https://goreleaser.com/customization/release/ +release: + disable: true + +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" +# modelines, feel free to remove those if you don't want/use them: +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json +# vim: set ts=2 sw=2 tw=0 fo=cnqoj diff --git a/.goreleaser.develop.yaml b/.goreleaser.develop.yaml index 7fbd2aa667..f8757676f8 100644 --- a/.goreleaser.develop.yaml +++ b/.goreleaser.develop.yaml @@ -1,9 +1,12 @@ -## goreleaser <1.14.0 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 }} + - IMAGE_NAME={{ if index .Env "IMAGE_NAME" }}{{ .Env.IMAGE_NAME }}{{ else }}chainlink{{ end }} + - IMAGE_TAG={{ if index .Env "IMAGE_TAG" }}{{ .Env.IMAGE_TAG }}{{ else }}develop{{ end }} - IMAGE_LABEL_DESCRIPTION="node of the decentralized oracle network, bridging on and off-chain computation" - IMAGE_LABEL_LICENSES="MIT" - IMAGE_LABEL_SOURCE="https://github.com/smartcontractkit/{{ .ProjectName }}" @@ -56,18 +59,18 @@ builds: # See https://goreleaser.com/customization/docker/ dockers: - - id: root-linux-amd64 + - id: linux-amd64 dockerfile: core/chainlink.goreleaser.Dockerfile use: buildx goos: linux goarch: amd64 extra_files: - tmp/linux_amd64/libs - - tmp/linux_amd64/plugins - tools/bin/ldd_fix build_flag_templates: - "--platform=linux/amd64" - "--pull" + - "--build-arg=CHAINLINK_USER=chainlink" - "--build-arg=COMMIT_SHA={{ .FullCommit }}" - "--label=org.opencontainers.image.created={{ .Date }}" - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" @@ -78,20 +81,21 @@ dockers: - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .ProjectName }}-develop:develop-root-amd64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .ProjectName }}-develop:sha-{{ .ShortCommit }}-root-amd64" - - id: root-linux-arm64 + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-amd64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-amd64" + - id: linux-arm64 dockerfile: core/chainlink.goreleaser.Dockerfile use: buildx goos: linux goarch: arm64 extra_files: - tmp/linux_arm64/libs - - tmp/linux_arm64/plugins - tools/bin/ldd_fix build_flag_templates: - "--platform=linux/arm64" - "--pull" + - "--build-arg=CHAINLINK_USER=chainlink" - "--build-arg=COMMIT_SHA={{ .FullCommit }}" - "--label=org.opencontainers.image.created={{ .Date }}" - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" @@ -102,9 +106,9 @@ dockers: - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .ProjectName }}-develop:develop-root-arm64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .ProjectName }}-develop:sha-{{ .ShortCommit }}-root-arm64" - - id: linux-amd64 + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-arm64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-arm64" + - id: linux-amd64-plugins dockerfile: core/chainlink.goreleaser.Dockerfile use: buildx goos: linux @@ -118,6 +122,10 @@ dockers: - "--pull" - "--build-arg=CHAINLINK_USER=chainlink" - "--build-arg=COMMIT_SHA={{ .FullCommit }}" + - "--build-arg=CL_MEDIAN_CMD=chainlink-feeds" + - "--build-arg=CL_MERCURY_CMD=chainlink-mercury" + - "--build-arg=CL_SOLANA_CMD=chainlink-solana" + - "--build-arg=CL_STARKNET_CMD=chainlink-starknet" - "--label=org.opencontainers.image.created={{ .Date }}" - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" - "--label=org.opencontainers.image.licenses={{ .Env.IMAGE_LABEL_LICENSES }}" @@ -127,9 +135,10 @@ dockers: - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .ProjectName }}-develop:develop-amd64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .ProjectName }}-develop:sha-{{ .ShortCommit }}-amd64" - - id: linux-arm64 + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-amd64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-amd64" + - id: linux-arm64-plugins dockerfile: core/chainlink.goreleaser.Dockerfile use: buildx goos: linux @@ -143,6 +152,10 @@ dockers: - "--pull" - "--build-arg=CHAINLINK_USER=chainlink" - "--build-arg=COMMIT_SHA={{ .FullCommit }}" + - "--build-arg=CL_MEDIAN_CMD=chainlink-feeds" + - "--build-arg=CL_MERCURY_CMD=chainlink-mercury" + - "--build-arg=CL_SOLANA_CMD=chainlink-solana" + - "--build-arg=CL_STARKNET_CMD=chainlink-starknet" - "--label=org.opencontainers.image.created={{ .Date }}" - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" - "--label=org.opencontainers.image.licenses={{ .Env.IMAGE_LABEL_LICENSES }}" @@ -152,38 +165,50 @@ dockers: - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .ProjectName }}-develop:develop-arm64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .ProjectName }}-develop:sha-{{ .ShortCommit }}-arm64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-arm64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-arm64" # See https://goreleaser.com/customization/docker_manifest/ docker_manifests: - - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .ProjectName }}-develop:develop-root" + - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}" image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .ProjectName }}-develop:develop-root-amd64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .ProjectName }}-develop:develop-root-arm64" - - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .ProjectName }}-develop:sha-{{ .ShortCommit }}-root" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-amd64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-arm64" + - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}" image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .ProjectName }}-develop:sha-{{ .ShortCommit }}-root-amd64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .ProjectName }}-develop:sha-{{ .ShortCommit }}-root-arm64" - - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .ProjectName }}-develop:develop" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-amd64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-arm64" + - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins" image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .ProjectName }}-develop:develop-amd64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .ProjectName }}-develop:develop-arm64" - - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .ProjectName }}-develop:sha-{{ .ShortCommit }}" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-amd64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-arm64" + - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins" image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .ProjectName }}-develop:sha-{{ .ShortCommit }}-amd64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .ProjectName }}-develop:sha-{{ .ShortCommit }}-arm64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-amd64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-arm64" # See https://goreleaser.com/customization/docker_sign/ docker_signs: - artifacts: all - stdin: "{{ .Env.COSIGN_PASSWORD }}" + args: + - "sign" + - "${artifact}" + - "--yes" checksum: name_template: "checksums.txt" snapshot: - name_template: "{{ .Env.CHAINLINK_VERSION }}-{{ .ShortCommit }}" + version_template: "{{ .Env.CHAINLINK_VERSION }}-{{ .ShortCommit }}" + +partial: + by: target + +# See https://goreleaser.com/customization/release/ +release: + disable: true changelog: sort: asc diff --git a/.goreleaser.devspace.yaml b/.goreleaser.devspace.yaml index 2f1e9754b4..ccea1de96b 100644 --- a/.goreleaser.devspace.yaml +++ b/.goreleaser.devspace.yaml @@ -1,6 +1,8 @@ ## goreleaser <1.14.0 project_name: ccip +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 +77,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/.goreleaser.production.yaml b/.goreleaser.production.yaml new file mode 100644 index 0000000000..0274f1322b --- /dev/null +++ b/.goreleaser.production.yaml @@ -0,0 +1,221 @@ +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 }} + - IMAGE_NAME={{ if index .Env "IMAGE_NAME" }}{{ .Env.IMAGE_NAME }}{{ else }}chainlink{{ end }} + - IMAGE_TAG={{ if index .Env "IMAGE_TAG" }}{{ .Env.IMAGE_TAG }}{{ else }}develop{{ end }} + - IMAGE_LABEL_DESCRIPTION="node of the decentralized oracle network, bridging on and off-chain computation" + - IMAGE_LABEL_LICENSES="MIT" + - IMAGE_LABEL_SOURCE="https://github.com/smartcontractkit/{{ .ProjectName }}" + +before: + hooks: + - go mod tidy + - ./tools/bin/goreleaser_utils before_hook + +# See https://goreleaser.com/customization/build/ +builds: + - binary: chainlink + id: linux-arm64 + goos: + - linux + goarch: + - arm64 + hooks: + post: ./tools/bin/goreleaser_utils build_post_hook {{ dir .Path }} {{ .Os }} {{ .Arch }} + env: + - CGO_ENABLED=1 + - CC=$ZIG_EXEC cc -target aarch64-linux-gnu + - CCX=$ZIG_EXEC c++ -target aarch64-linux-gnu + flags: + - -trimpath + - -buildmode=pie + ldflags: + - -s -w -r=$ORIGIN/libs + - -X github.com/smartcontractkit/chainlink/v2/core/static.Version={{ .Env.CHAINLINK_VERSION }} + - -X github.com/smartcontractkit/chainlink/v2/core/static.Sha={{ .FullCommit }} + - binary: chainlink + id: linux-amd64 + goos: + - linux + goarch: + - amd64 + hooks: + post: ./tools/bin/goreleaser_utils build_post_hook {{ dir .Path }} {{ .Os }} {{ .Arch }} + env: + - CGO_ENABLED=1 + - CC=$ZIG_EXEC cc -target x86_64-linux-gnu + - CCX=$ZIG_EXEC c++ -target x86_64-linux-gnu + flags: + - -trimpath + - -buildmode=pie + ldflags: + - -s -w -r=$ORIGIN/libs + - -X github.com/smartcontractkit/chainlink/v2/core/static.Version={{ .Env.CHAINLINK_VERSION }} + - -X github.com/smartcontractkit/chainlink/v2/core/static.Sha={{ .FullCommit }} + +# See https://goreleaser.com/customization/docker/ +dockers: + - id: linux-amd64 + dockerfile: core/chainlink.goreleaser.Dockerfile + use: buildx + goos: linux + goarch: amd64 + extra_files: + - tmp/linux_amd64/libs + - tools/bin/ldd_fix + build_flag_templates: + - "--platform=linux/amd64" + - "--pull" + - "--build-arg=CHAINLINK_USER=chainlink" + - "--build-arg=COMMIT_SHA={{ .FullCommit }}" + - "--label=org.opencontainers.image.created={{ .Date }}" + - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" + - "--label=org.opencontainers.image.licenses={{ .Env.IMAGE_LABEL_LICENSES }}" + - "--label=org.opencontainers.image.revision={{ .FullCommit }}" + - "--label=org.opencontainers.image.source={{ .Env.IMAGE_LABEL_SOURCE }}" + - "--label=org.opencontainers.image.title={{ .ProjectName }}" + - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" + - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" + image_templates: + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-amd64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-amd64" + - id: linux-arm64 + dockerfile: core/chainlink.goreleaser.Dockerfile + use: buildx + goos: linux + goarch: arm64 + extra_files: + - tmp/linux_arm64/libs + - tools/bin/ldd_fix + build_flag_templates: + - "--platform=linux/arm64" + - "--pull" + - "--build-arg=CHAINLINK_USER=chainlink" + - "--build-arg=COMMIT_SHA={{ .FullCommit }}" + - "--label=org.opencontainers.image.created={{ .Date }}" + - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" + - "--label=org.opencontainers.image.licenses={{ .Env.IMAGE_LABEL_LICENSES }}" + - "--label=org.opencontainers.image.revision={{ .FullCommit }}" + - "--label=org.opencontainers.image.source={{ .Env.IMAGE_LABEL_SOURCE }}" + - "--label=org.opencontainers.image.title={{ .ProjectName }}" + - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" + - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" + image_templates: + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-arm64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-arm64" + - id: linux-amd64-plugins + dockerfile: core/chainlink.goreleaser.Dockerfile + use: buildx + goos: linux + goarch: amd64 + extra_files: + - tmp/linux_amd64/libs + - tmp/linux_amd64/plugins + - tools/bin/ldd_fix + build_flag_templates: + - "--platform=linux/amd64" + - "--pull" + - "--build-arg=CHAINLINK_USER=chainlink" + - "--build-arg=COMMIT_SHA={{ .FullCommit }}" + - "--build-arg=CL_MEDIAN_CMD=chainlink-feeds" + - "--build-arg=CL_MERCURY_CMD=chainlink-mercury" + - "--build-arg=CL_SOLANA_CMD=chainlink-solana" + - "--build-arg=CL_STARKNET_CMD=chainlink-starknet" + - "--label=org.opencontainers.image.created={{ .Date }}" + - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" + - "--label=org.opencontainers.image.licenses={{ .Env.IMAGE_LABEL_LICENSES }}" + - "--label=org.opencontainers.image.revision={{ .FullCommit }}" + - "--label=org.opencontainers.image.source={{ .Env.IMAGE_LABEL_SOURCE }}" + - "--label=org.opencontainers.image.title={{ .ProjectName }}" + - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" + - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" + image_templates: + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-amd64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-amd64" + - id: linux-arm64-plugins + dockerfile: core/chainlink.goreleaser.Dockerfile + use: buildx + goos: linux + goarch: arm64 + extra_files: + - tmp/linux_arm64/libs + - tmp/linux_arm64/plugins + - tools/bin/ldd_fix + build_flag_templates: + - "--platform=linux/arm64" + - "--pull" + - "--build-arg=CHAINLINK_USER=chainlink" + - "--build-arg=COMMIT_SHA={{ .FullCommit }}" + - "--build-arg=CL_MEDIAN_CMD=chainlink-feeds" + - "--build-arg=CL_MERCURY_CMD=chainlink-mercury" + - "--build-arg=CL_SOLANA_CMD=chainlink-solana" + - "--build-arg=CL_STARKNET_CMD=chainlink-starknet" + - "--label=org.opencontainers.image.created={{ .Date }}" + - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" + - "--label=org.opencontainers.image.licenses={{ .Env.IMAGE_LABEL_LICENSES }}" + - "--label=org.opencontainers.image.revision={{ .FullCommit }}" + - "--label=org.opencontainers.image.source={{ .Env.IMAGE_LABEL_SOURCE }}" + - "--label=org.opencontainers.image.title={{ .ProjectName }}" + - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" + - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" + image_templates: + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-arm64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-arm64" + +# See https://goreleaser.com/customization/docker_manifest/ +docker_manifests: + - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}" + image_templates: + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-amd64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-arm64" + - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}" + image_templates: + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-amd64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-arm64" + - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins" + image_templates: + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-amd64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-arm64" + - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins" + image_templates: + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-amd64" + - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-arm64" + +# See https://goreleaser.com/customization/docker_sign/ +docker_signs: + - artifacts: all + args: + - "sign" + - "${artifact}" + - "--yes" + +checksum: + name_template: "checksums.txt" + +# See https://goreleaser.com/customization/sbom +sboms: + - artifacts: archive + +snapshot: + version_template: "{{ .Env.CHAINLINK_VERSION }}-{{ .ShortCommit }}" + +partial: + by: target + +# See https://goreleaser.com/customization/release/ +release: + disable: true + +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" +# modelines, feel free to remove those if you don't want/use them: +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json +# vim: set ts=2 sw=2 tw=0 fo=cnqoj diff --git a/.mockery.yaml b/.mockery.yaml index cfe202ac59..5c15653568 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -261,10 +261,14 @@ packages: ORM: Runner: PipelineParamUnmarshaler: - github.com/smartcontractkit/chainlink/v2/core/services/promreporter: + github.com/smartcontractkit/chainlink/v2/core/services/headreporter: config: - dir: core/internal/mocks + dir: "{{ .InterfaceDir }}" + filename: "{{ .InterfaceName | snakecase }}_mock.go" + inpackage: true + mockname: "Mock{{ .InterfaceName | camelcase }}" interfaces: + HeadReporter: PrometheusBackend: github.com/smartcontractkit/libocr/commontypes: config: @@ -318,6 +322,13 @@ packages: interfaces: ExternalInitiatorManager: HTTPClient: + github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/read: + config: + dir: "{{ .InterfaceDir }}/mocks" + interfaces: + Registrar: + Reader: + BatchCaller: github.com/smartcontractkit/chainlink/v2/core/sessions: interfaces: BasicAdminUsersORM: @@ -333,22 +344,22 @@ packages: Codec: config: dir: core/services/relay/evm/mocks - ChainReader: ChainWriter: + ContractReader: github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_onramp: config: dir: core/gethwrappers/ccip/mocks/ filename: evm2_evm_on_ramp_interface.go outpkg: mock_contracts interfaces: - EVM2EVMOnRampInterface: + EVM2EVMOnRampInterface: github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_offramp: config: dir: core/gethwrappers/ccip/mocks/ filename: evm2_evm_off_ramp_interface.go outpkg: mock_contracts interfaces: - EVM2EVMOffRampInterface: + EVM2EVMOffRampInterface: github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_offramp_1_2_0: config: dir: core/gethwrappers/ccip/mocks/v1_2_0/ @@ -383,28 +394,28 @@ packages: filename: link_token_interface.go outpkg: mock_contracts interfaces: - LinkTokenInterface: + LinkTokenInterface: github.com/smartcontractkit/chainlink/v2/core/gethwrappers/liquiditymanager/generated/arbitrum_l1_bridge_adapter: config: dir: core/gethwrappers/liquiditymanager/mocks/mock_arbitrum_l1_bridge_adapter/ filename: arbitrum_l1_bridge_adapter_interface.go outpkg: mock_arbitrum_l1_bridge_adapter interfaces: - ArbitrumL1BridgeAdapterInterface: + ArbitrumL1BridgeAdapterInterface: github.com/smartcontractkit/chainlink/v2/core/gethwrappers/liquiditymanager/generated/arbitrum_l2_bridge_adapter: config: dir: core/gethwrappers/liquiditymanager/mocks/mock_arbitrum_l2_bridge_adapter/ filename: arbitrum_l2_bridge_adapter_interface.go outpkg: mock_arbitrum_l2_bridge_adapter interfaces: - ArbitrumL2BridgeAdapterInterface: + ArbitrumL2BridgeAdapterInterface: github.com/smartcontractkit/chainlink/v2/core/gethwrappers/liquiditymanager/generated/arbitrum_gateway_router: config: dir: core/gethwrappers/liquiditymanager/mocks/mock_arbitrum_gateway_router/ filename: arbitrum_gateway_router_interface.go outpkg: mock_arbitrum_gateway_router interfaces: - ArbitrumGatewayRouterInterface: + ArbitrumGatewayRouterInterface: github.com/smartcontractkit/chainlink/v2/core/gethwrappers/liquiditymanager/generated/arbitrum_inbox: config: dir: core/gethwrappers/liquiditymanager/mocks/mock_arbitrum_inbox/ @@ -418,7 +429,7 @@ packages: filename: l2_arbitrum_gateway_interface.go outpkg: mock_l2_arbitrum_gateway interfaces: - L2ArbitrumGatewayInterface: + L2ArbitrumGatewayInterface: github.com/smartcontractkit/chainlink/v2/core/gethwrappers/liquiditymanager/generated/arbsys: config: dir: core/gethwrappers/liquiditymanager/mocks/mock_arbsys/ @@ -432,7 +443,7 @@ packages: filename: node_interface_interface.go outpkg: mock_node_interface interfaces: - NodeInterfaceInterface: + NodeInterfaceInterface: github.com/smartcontractkit/chainlink/v2/core/gethwrappers/liquiditymanager/generated/l2_arbitrum_messenger: config: dir: core/gethwrappers/liquiditymanager/mocks/mock_l2_arbitrum_messenger/ @@ -446,7 +457,7 @@ packages: filename: arb_rollup_core_interface.go outpkg: mock_arbitrum_rollup_core interfaces: - ArbRollupCoreInterface: + ArbRollupCoreInterface: github.com/smartcontractkit/chainlink/v2/core/gethwrappers/liquiditymanager/generated/optimism_portal: config: dir: core/gethwrappers/liquiditymanager/mocks/mock_optimism_portal/ @@ -460,7 +471,7 @@ packages: filename: optimism_l2_output_oracle_interface.go outpkg: mock_optimism_l2_output_oracle interfaces: - OptimismL2OutputOracleInterface: + OptimismL2OutputOracleInterface: github.com/smartcontractkit/chainlink/v2/core/gethwrappers/liquiditymanager/generated/optimism_portal_2: config: dir: core/gethwrappers/liquiditymanager/mocks/mock_optimism_portal_2/ @@ -474,7 +485,7 @@ packages: filename: optimism_dispute_game_factory_interface.go outpkg: mock_optimism_dispute_game_factory interfaces: - OptimismDisputeGameFactoryInterface: + OptimismDisputeGameFactoryInterface: github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/cache: config: filename: chain_health_mock.go @@ -552,7 +563,6 @@ packages: config: filename: evm_mock.go dir: "{{ .InterfaceDir }}/rpclibmocks" - github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/prices: config: dir: "{{ .InterfaceDir }}/" @@ -612,4 +622,10 @@ packages: dir: "{{ .InterfaceDir }}/../mocks" filename: rebalancer_mock.go interfaces: - RebalancingAlgo: \ No newline at end of file + RebalancingAlgo: + github.com/smartcontractkit/chainlink/v2/core/services/registrysyncer: + interfaces: + ORM: + github.com/smartcontractkit/chainlink/v2/core/capabilities/targets: + interfaces: + ContractValueGetter: diff --git a/.tool-versions b/.tool-versions index 077946cbee..12a86bb1c1 100644 --- a/.tool-versions +++ b/.tool-versions @@ -5,7 +5,7 @@ pnpm 9.4.0 postgres 15.1 helm 3.10.3 zig 0.11.0 -golangci-lint 1.59.1 +golangci-lint 1.60.3 protoc 25.1 python 3.10.5 task 3.35.1 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index a78e070b43..c1a09677ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,320 @@ # Changelog Chainlink Core -## 2.15.0 - 2024-08-19 +## 2.17.0 - 2024-10-10 + +### Minor Changes + +- [#14316](https://github.com/smartcontractkit/chainlink/pull/14316) [`2a21b170f3`](https://github.com/smartcontractkit/chainlink/commit/2a21b170f38ded4cf0f15db283035e69b53aeeb9) Thanks [@graham-chainlink](https://github.com/graham-chainlink)! - #internal updated to latest operator-ui to bring in new UI changes for supporting multiple job distributors + +- [#14264](https://github.com/smartcontractkit/chainlink/pull/14264) [`33e6a0c1e4`](https://github.com/smartcontractkit/chainlink/commit/33e6a0c1e44b805064fe423b5570a40c07daed41) Thanks [@chainchad](https://github.com/chainchad)! - Bump to start the next version + +- [#14109](https://github.com/smartcontractkit/chainlink/pull/14109) [`2761cd5bc5`](https://github.com/smartcontractkit/chainlink/commit/2761cd5bc5ed91bc17d4d67265ddc8fa03b84540) Thanks [@Farber98](https://github.com/Farber98)! - FilteredLogs receive Expression instead of whole KeyFilter. #internal + +- [#14239](https://github.com/smartcontractkit/chainlink/pull/14239) [`674eac31cc`](https://github.com/smartcontractkit/chainlink/commit/674eac31cc161250fdadb838ba2a0fc7c796e932) Thanks [@bolekk](https://github.com/bolekk)! - #added Implements rate limiter for capabilities dispatcher + +- [#14470](https://github.com/smartcontractkit/chainlink/pull/14470) [`5885454e9a`](https://github.com/smartcontractkit/chainlink/commit/5885454e9a7eaa8f8c180ac3708afbdf5bdb08cd) Thanks [@austinborn](https://github.com/austinborn)! - #changed: Add new OCR3DataFeeds telemetry type for Mercury jobs + +- [#14266](https://github.com/smartcontractkit/chainlink/pull/14266) [`c323e0d600`](https://github.com/smartcontractkit/chainlink/commit/c323e0d600c659a4ea584dbae0a0db187afd51eb) Thanks [@asoliman92](https://github.com/asoliman92)! - #updated move latest capabilities code from ccip repo to chainlink repo [CCIP-2946] + + PR issue: CCIP-2946 + +- [#14197](https://github.com/smartcontractkit/chainlink/pull/14197) [`7f69993c86`](https://github.com/smartcontractkit/chainlink/commit/7f69993c8655053b7550f50b817ba9c6888037e2) Thanks [@graham-chainlink](https://github.com/graham-chainlink)! - #changed Connect to multiple feeds managers on app start instead of just one (default to first) + +- [#14373](https://github.com/smartcontractkit/chainlink/pull/14373) [`5acca3719e`](https://github.com/smartcontractkit/chainlink/commit/5acca3719ecd7a3189db3a8a8d09418ed8423016) Thanks [@huangzhen1997](https://github.com/huangzhen1997)! - This PR introduce few changes: + + - Add a new config option `EVM.NodePool.NewHeadsPollInterval` (0 by default indicate disabled), which is an interval for polling new block periodically using http client rather than subscribe to ws feed. + - Updated new head handler for polling new head over http, and register the subscription in node lifecycle logic. + - If the polling new heads is enabled, WS new heads subscription will be replaced with the new http based polling. + + Note: There will be another PR for making WS URL optional with some extra condition. + #added + +- [#14438](https://github.com/smartcontractkit/chainlink/pull/14438) [`6814bcef45`](https://github.com/smartcontractkit/chainlink/commit/6814bcef45a7157ac9835c25a9a5b95a135bdc01) Thanks [@graham-chainlink](https://github.com/graham-chainlink)! - #internal Update to latest UI - PeerId field is introduced for OCR2 bootstrap node in chain config page + +- [#14354](https://github.com/smartcontractkit/chainlink/pull/14354) [`bf6618da8a`](https://github.com/smartcontractkit/chainlink/commit/bf6618da8aa9695c747b81df172acdd43e379cb2) Thanks [@huangzhen1997](https://github.com/huangzhen1997)! - Adding feature flag for `LogBroadcaster` called `LogBroadcasterEnabled`, which is `true` by default to support backwards compatibility. + Adding `LogBroadcasterEnabled` allows certain chains to completely disable the `LogBroadcaster` feature, which is an old feature (getting replaced by logPoller) that only few products are using it: + + - OCR1 Median + - \*OCR2 Median when ChainReader is disabled + - \*pre-OCR2 Keeper + - Flux Monitor + - Direct RequestOCR1 Median + + #added + +- [#13735](https://github.com/smartcontractkit/chainlink/pull/13735) [`920413c3ce`](https://github.com/smartcontractkit/chainlink/commit/920413c3ce2ca8effc138e69ec063b0ce5e94c6b) Thanks [@silaslenihan](https://github.com/silaslenihan)! - #internal Added ChainWriter to ChainReader tests + +- [#14041](https://github.com/smartcontractkit/chainlink/pull/14041) [`8d818ea265`](https://github.com/smartcontractkit/chainlink/commit/8d818ea265ff08887e61ace4f83364a3ee149ef0) Thanks [@amit-momin](https://github.com/amit-momin)! - Added gas limit estimation feature to EVM gas estimators. Introduced a new config `EVM.GasEstimator.EstimateLimit` to toggle this feature. #added + +- [#14207](https://github.com/smartcontractkit/chainlink/pull/14207) [`328b62ae50`](https://github.com/smartcontractkit/chainlink/commit/328b62ae5067619e59da42f6db6703d3b327f1a2) Thanks [@ilija42](https://github.com/ilija42)! - #internal Implement EVM ChainReader ValueComparator filtering by non-indexed event data. Right now only simple non indexed data where byte offsets don't exist is supported. + + PR issue: BCFR-203 + +- [#14197](https://github.com/smartcontractkit/chainlink/pull/14197) [`7f69993c86`](https://github.com/smartcontractkit/chainlink/commit/7f69993c8655053b7550f50b817ba9c6888037e2) Thanks [@graham-chainlink](https://github.com/graham-chainlink)! - #changed Allow registration of more than 1 feeds manager on CreateFeedsManager + +- [#14394](https://github.com/smartcontractkit/chainlink/pull/14394) [`28989b30d9`](https://github.com/smartcontractkit/chainlink/commit/28989b30d94bbd3490330cd8e50e7d9223d33cff) Thanks [@ilija42](https://github.com/ilija42)! - #internal Implement LatestHead for ChainService + +- [#14234](https://github.com/smartcontractkit/chainlink/pull/14234) [`a234e14ebd`](https://github.com/smartcontractkit/chainlink/commit/a234e14ebd266269b4d5893b0d2aeeb01bc58a70) Thanks [@huangzhen1997](https://github.com/huangzhen1997)! - use new estimation for insufficient fund instead of retry to overcome gas spike #internal + +- [#14369](https://github.com/smartcontractkit/chainlink/pull/14369) [`e51472763d`](https://github.com/smartcontractkit/chainlink/commit/e51472763da4039242ebd4c3939ab44c87e595d1) Thanks [@archseer](https://github.com/archseer)! - Small fixes to multichain keyring adapter #internal + +- [#13833](https://github.com/smartcontractkit/chainlink/pull/13833) [`1ea9f79793`](https://github.com/smartcontractkit/chainlink/commit/1ea9f79793f646977b44e38a34b2e70c28b2849e) Thanks [@dimriou](https://github.com/dimriou)! - Introduce new gas estimator #internal + +- [#14110](https://github.com/smartcontractkit/chainlink/pull/14110) [`8454f46db1`](https://github.com/smartcontractkit/chainlink/commit/8454f46db1985c0a4968b4eb5e0a4a6b81dfef5c) Thanks [@jmank88](https://github.com/jmank88)! - #added Full Open Telemetry support, configurable via `Telemetry` + +- [#14504](https://github.com/smartcontractkit/chainlink/pull/14504) [`10f7aabc29`](https://github.com/smartcontractkit/chainlink/commit/10f7aabc2972615cae4edd8f3532ad6aea521cee) Thanks [@austinborn](https://github.com/austinborn)! - #bugfix Fix potential nil ptr reference for LinkFeedID and NativeFeedID in Mercury specs + #bugfix Ensure Streams PluginConfig is checked for contents correctly when validated + #changed New Feed IDs with 0x01 prefix can be parsed for Mercury report schemas + +- [#14370](https://github.com/smartcontractkit/chainlink/pull/14370) [`882cdce681`](https://github.com/smartcontractkit/chainlink/commit/882cdce6811a952a38c61c3fb88349990d635d59) Thanks [@dimriou](https://github.com/dimriou)! - Remove PriceMin and TipCapMin check from attempt builder #internal + +- [#13888](https://github.com/smartcontractkit/chainlink/pull/13888) [`37c5a2ff29`](https://github.com/smartcontractkit/chainlink/commit/37c5a2ff29ad1fe661547777e60a077430530be9) Thanks [@karen-stepanyan](https://github.com/karen-stepanyan)! - #updated mercury plugin to consider PluginConfig as optional if EnableTriggerCapability relay config is true. Then if PluginConfig is nil, skip fetching latestPrice for linkFeedId and nativeFeedId. + +### Patch Changes + +- [#14350](https://github.com/smartcontractkit/chainlink/pull/14350) [`070b272f30`](https://github.com/smartcontractkit/chainlink/commit/070b272f30054be6d4239d078121ca3b3054fc33) Thanks [@DeividasK](https://github.com/DeividasK)! - #internal + +- [#14317](https://github.com/smartcontractkit/chainlink/pull/14317) [`72f4cc8aaa`](https://github.com/smartcontractkit/chainlink/commit/72f4cc8aaa128202fd5974c6dbfb29c6beb1be12) Thanks [@ettec](https://github.com/ettec)! - #internal changes required for capability api chance to sync + +- [#14471](https://github.com/smartcontractkit/chainlink/pull/14471) [`a9a4f746bf`](https://github.com/smartcontractkit/chainlink/commit/a9a4f746bf3e18d2bf9228d591166c437d5a9e6a) Thanks [@matYang](https://github.com/matYang)! - #changed Make Mantle use default OP stack l1 gas oracle in core + +- [#14416](https://github.com/smartcontractkit/chainlink/pull/14416) [`3c5bdf8d4b`](https://github.com/smartcontractkit/chainlink/commit/3c5bdf8d4b2244b3826ab54a56ec172bb9a8459c) Thanks [@dimkouv](https://github.com/dimkouv)! - RMNCrypto evm implementation for CCIP - RMN Integration #added + +- [#14313](https://github.com/smartcontractkit/chainlink/pull/14313) [`b71e692e7b`](https://github.com/smartcontractkit/chainlink/commit/b71e692e7ba8523ec57ea5e10c5d9c6810e038e5) Thanks [@ferglor](https://github.com/ferglor)! - Use a lock to sync access to the ConfigDigest #internal + +- [#14423](https://github.com/smartcontractkit/chainlink/pull/14423) [`0187f18ba6`](https://github.com/smartcontractkit/chainlink/commit/0187f18ba62b44d4c8ff20f07ef8dfd6e0d7b451) Thanks [@asoliman92](https://github.com/asoliman92)! - #updated refactor ccip oracle creator + +- [#14314](https://github.com/smartcontractkit/chainlink/pull/14314) [`8fa3ebee3e`](https://github.com/smartcontractkit/chainlink/commit/8fa3ebee3e0ce09bf2e8270ab46c168756d25db0) Thanks [@DeividasK](https://github.com/DeividasK)! - #internal validate capability trigger event ID before executing + +- [#14366](https://github.com/smartcontractkit/chainlink/pull/14366) [`27d5cbf578`](https://github.com/smartcontractkit/chainlink/commit/27d5cbf5787531d541ba774397b3abdfcb8b20a7) Thanks [@dhaidashenko](https://github.com/dhaidashenko)! - LogPoller polls logs even if chain have not reached finality #internal + +- [#14401](https://github.com/smartcontractkit/chainlink/pull/14401) [`f6443a14e8`](https://github.com/smartcontractkit/chainlink/commit/f6443a14e836523dfa8a78b1b98a00999832f204) Thanks [@george-dorin](https://github.com/george-dorin)! - #updated Changed TelemetryIngress.UniConn default to false + +- [#14282](https://github.com/smartcontractkit/chainlink/pull/14282) [`1a2b7b61cb`](https://github.com/smartcontractkit/chainlink/commit/1a2b7b61cbd22256e4e29e891a74228fa453fc9d) Thanks [@amit-momin](https://github.com/amit-momin)! - Updated TXM Confirmer logic to resume pending task runs with failure if transaction is terminally stuck #internal + +- [#14325](https://github.com/smartcontractkit/chainlink/pull/14325) [`b1c59ddfe3`](https://github.com/smartcontractkit/chainlink/commit/b1c59ddfe31d53be6669df8f1cf246b222fbe3b0) Thanks [@DavidOrchard](https://github.com/DavidOrchard)! - configuration updates + +- [#14486](https://github.com/smartcontractkit/chainlink/pull/14486) [`1d6a88ee73`](https://github.com/smartcontractkit/chainlink/commit/1d6a88ee7313ccb857db3995d7d6ed363d7d6589) Thanks [@simsonraj](https://github.com/simsonraj)! - #added Soneium testnet chain configs + +- [#14315](https://github.com/smartcontractkit/chainlink/pull/14315) [`adb3c95799`](https://github.com/smartcontractkit/chainlink/commit/adb3c957993f9f022db395fd54e65528631c1030) Thanks [@friedemannf](https://github.com/friedemannf)! - Handle zkEVM node level OOC error as TerminallyStuck #internal + +- [#14541](https://github.com/smartcontractkit/chainlink/pull/14541) [`d9894d129d`](https://github.com/smartcontractkit/chainlink/commit/d9894d129d12204bdb14dcb0a7ce42fd19205a6d) Thanks [@friedemannf](https://github.com/friedemannf)! - #added #nops Add Zircuit Configs + +- [#14241](https://github.com/smartcontractkit/chainlink/pull/14241) [`7c248e7c46`](https://github.com/smartcontractkit/chainlink/commit/7c248e7c466ad278b0024e4ac743813009b16805) Thanks [@cds95](https://github.com/cds95)! - #internal index don ID in ConfigSet event + +- [#14543](https://github.com/smartcontractkit/chainlink/pull/14543) [`c4fa565f54`](https://github.com/smartcontractkit/chainlink/commit/c4fa565f5441bfa997907256e1990f9be276934d) Thanks [@DeividasK](https://github.com/DeividasK)! - #internal + +- [#14129](https://github.com/smartcontractkit/chainlink/pull/14129) [`85a8d09845`](https://github.com/smartcontractkit/chainlink/commit/85a8d09845d6bd30f62b1de4bf8c62f3a77a6c8e) Thanks [@simsonraj](https://github.com/simsonraj)! - #added Hedera configs + +- [#14252](https://github.com/smartcontractkit/chainlink/pull/14252) [`8490c9610b`](https://github.com/smartcontractkit/chainlink/commit/8490c9610b1208f3efafe29587032679e5727247) Thanks [@martin-cll](https://github.com/martin-cll)! - Remove bid/ask fields for Mercury v4 schema #internal + +- [#14516](https://github.com/smartcontractkit/chainlink/pull/14516) [`0e32c07d22`](https://github.com/smartcontractkit/chainlink/commit/0e32c07d22973343e722a228ff1c3b1e8f9bc04e) Thanks [@mateusz-sekara](https://github.com/mateusz-sekara)! - Adding USDCReaderTester contract for CCIP integration tests #internal + +- [#14345](https://github.com/smartcontractkit/chainlink/pull/14345) [`c83c68735b`](https://github.com/smartcontractkit/chainlink/commit/c83c68735bdee6bbd8510733b7415797cd08ecbd) Thanks [@makramkd](https://github.com/makramkd)! - #internal merge ccip contracts + +- [#14392](https://github.com/smartcontractkit/chainlink/pull/14392) [`3f83f9e8e6`](https://github.com/smartcontractkit/chainlink/commit/3f83f9e8e66029c78a52e2c1eeb5dfb95a615f55) Thanks [@kalverra](https://github.com/kalverra)! - #added Adds the ability to use out of order execution transactions in CCIP E2E tests + +- [#14318](https://github.com/smartcontractkit/chainlink/pull/14318) [`544ded0afa`](https://github.com/smartcontractkit/chainlink/commit/544ded0afa685de146da215a949ad08b3667bb99) Thanks [@winder](https://github.com/winder)! - #internal ccip reader nonces work. + +- [#13992](https://github.com/smartcontractkit/chainlink/pull/13992) [`c1878f7374`](https://github.com/smartcontractkit/chainlink/commit/c1878f7374b7fb2de450c83b6dcae62d2a36f3bf) Thanks [@EasterTheBunny](https://github.com/EasterTheBunny)! - #internal `ContractReader` interface update to accept `BoundContract` for all methods + +- [#14130](https://github.com/smartcontractkit/chainlink/pull/14130) [`31874ba5a4`](https://github.com/smartcontractkit/chainlink/commit/31874ba5a4abbc2dca7b985f04019485a339a71c) Thanks [@dhaidashenko](https://github.com/dhaidashenko)! - Optimize HeadTracker's memory usage #internal + +- [#14474](https://github.com/smartcontractkit/chainlink/pull/14474) [`aa04bfab89`](https://github.com/smartcontractkit/chainlink/commit/aa04bfab8950f001b92635388b2fb63ab1bbcec9) Thanks [@dimkouv](https://github.com/dimkouv)! - bump chainlink-ccip #updated + +- [#14488](https://github.com/smartcontractkit/chainlink/pull/14488) [`700dd7c074`](https://github.com/smartcontractkit/chainlink/commit/700dd7c074706b1a5fa89328876bdc4f3d39e025) Thanks [@ettec](https://github.com/ettec)! - #internal add support for values.Value type in the contract reader GetLatestValue and QueryKey methods + +- [#14361](https://github.com/smartcontractkit/chainlink/pull/14361) [`3a89dceab7`](https://github.com/smartcontractkit/chainlink/commit/3a89dceab79217880625f7af75db0d798cf79488) Thanks [@dhaidashenko](https://github.com/dhaidashenko)! - Use tx in insertLogsWithinTx #internal + +- [#14258](https://github.com/smartcontractkit/chainlink/pull/14258) [`7905901c40`](https://github.com/smartcontractkit/chainlink/commit/7905901c40fc6ab7c65066d02e2d63324e2d640f) Thanks [@ettec](https://github.com/ettec)! - #internal gas limit default value + +- [#14415](https://github.com/smartcontractkit/chainlink/pull/14415) [`d2d9568318`](https://github.com/smartcontractkit/chainlink/commit/d2d9568318abe0ce88bc12a1308e0e96131b0223) Thanks [@martin-cll](https://github.com/martin-cll)! - Skip telemetry for market-status bridges #internal + +- [#14484](https://github.com/smartcontractkit/chainlink/pull/14484) [`d2a01ca51b`](https://github.com/smartcontractkit/chainlink/commit/d2a01ca51bb4a7654d2ceb4f5c25f2ca2de3df11) Thanks [@ogtownsend](https://github.com/ogtownsend)! - #internal KMS client for deployment + +- [#14398](https://github.com/smartcontractkit/chainlink/pull/14398) [`52b480fcc5`](https://github.com/smartcontractkit/chainlink/commit/52b480fcc53bf0162cb3aa04cc13f946babb643a) Thanks [@bolekk](https://github.com/bolekk)! - #added [Keystone] Batch identical trigger events + +- [#14355](https://github.com/smartcontractkit/chainlink/pull/14355) [`356c70cb80`](https://github.com/smartcontractkit/chainlink/commit/356c70cb8079b1052faa45d0c53fa1d8212db355) Thanks [@samsondav](https://github.com/samsondav)! - #changed + + Productionize transmitter for LLO + + Note that some minor changes to prometheus metrics will occur in the transition to LLO. Since feed IDs no longer apply, the metrics for transmissions change as follows: + + ``` + "mercury_transmit_*" + []string{"feedID", ...}, + ``` + + Will change to: + + ``` + "llo_mercury_transmit_*" + []string{"donID", ...}, + ``` + +- [#14352](https://github.com/smartcontractkit/chainlink/pull/14352) [`718e885a53`](https://github.com/smartcontractkit/chainlink/commit/718e885a53d003e16f6bc2d1be5596e63ac88b24) Thanks [@winder](https://github.com/winder)! - #internal update chainlink-ccip version + +- [#14161](https://github.com/smartcontractkit/chainlink/pull/14161) [`2b1e8ad51b`](https://github.com/smartcontractkit/chainlink/commit/2b1e8ad51b98aa41eca78758d2041ffcd7fba94a) Thanks [@friedemannf](https://github.com/friedemannf)! - Enable FeeHistory estimator for Polygon zkEVM #nops + +- [#14367](https://github.com/smartcontractkit/chainlink/pull/14367) [`cd8be702ff`](https://github.com/smartcontractkit/chainlink/commit/cd8be702ffdaef0a9176da977411ab237e544da5) Thanks [@bolekk](https://github.com/bolekk)! - Support per-method handlers in GatewayConnector + +- [#14298](https://github.com/smartcontractkit/chainlink/pull/14298) [`85b33fd9ac`](https://github.com/smartcontractkit/chainlink/commit/85b33fd9acbd342d25bd84804d08451ab2590b97) Thanks [@AnieeG](https://github.com/AnieeG)! - moved deployments ccip tooling from ccip repo to chainlink repo #added + +- [#14281](https://github.com/smartcontractkit/chainlink/pull/14281) [`73c41d1f27`](https://github.com/smartcontractkit/chainlink/commit/73c41d1f27ac43ec6ed6a27368776b187c5e5e45) Thanks [@eutopian](https://github.com/eutopian)! - skip checking isJobManaged if the proposal in fms has already been deleted #changed + +- [#14467](https://github.com/smartcontractkit/chainlink/pull/14467) [`358fc17d5b`](https://github.com/smartcontractkit/chainlink/commit/358fc17d5b5149d962002225cee7c44215cc77d4) Thanks [@akuzni2](https://github.com/akuzni2)! - #added + + - Adds support for "tags" to Tasks that can be used generically. + - Adds a descendent task search method + - Added support in Mercury EA telemetry to utilize tags for telemetry extraction + +- [#14418](https://github.com/smartcontractkit/chainlink/pull/14418) [`a2c03fc380`](https://github.com/smartcontractkit/chainlink/commit/a2c03fc380ca5919bf2f33f771a6efd98a6f4103) Thanks [@mateusz-sekara](https://github.com/mateusz-sekara)! - Updating CCIP OCR3 integration tests according to changes in the chainlink-ccip repo #internal + +- [#14357](https://github.com/smartcontractkit/chainlink/pull/14357) [`ac3523aaa4`](https://github.com/smartcontractkit/chainlink/commit/ac3523aaa4cee6f30b9ac0f25cc7cce559067594) Thanks [@AnieeG](https://github.com/AnieeG)! - #internal Add ccip JobType in feeds service and other jobtype validations + +- [#14461](https://github.com/smartcontractkit/chainlink/pull/14461) [`22a8c993ae`](https://github.com/smartcontractkit/chainlink/commit/22a8c993ae6ae6ee69626bd239ba2a419fbad450) Thanks [@asoliman92](https://github.com/asoliman92)! - #added feed deployment to ccip integration tests + +## 2.16.0 - 2024-09-23 + +### Minor Changes + +- [#14138](https://github.com/smartcontractkit/chainlink/pull/14138) [`69335dc6b0`](https://github.com/smartcontractkit/chainlink/commit/69335dc6b0837ba9726a2772bf1dc98174c03310) Thanks [@silaslenihan](https://github.com/silaslenihan)! - #internal Exposed Confirmed state to ChainWriter GetTransactionStatus method + +- [#14157](https://github.com/smartcontractkit/chainlink/pull/14157) [`1852353bbf`](https://github.com/smartcontractkit/chainlink/commit/1852353bbf6ae4726287cb376bc7a323f657c92a) Thanks [@dimriou](https://github.com/dimriou)! - Fix bhe datarace #internal + +- [#14132](https://github.com/smartcontractkit/chainlink/pull/14132) [`2e314cddf0`](https://github.com/smartcontractkit/chainlink/commit/2e314cddf0f4dbd29cad4a43926dc1a5390cc70f) Thanks [@amit-momin](https://github.com/amit-momin)! - Updated ZK overflow detection to skip transactions with non-broadcasted attempts. Delayed detection for zkEVM using the MinAttempts config. Updated XLayer to use the same detection logic as zkEVM. #internal + +- [#13948](https://github.com/smartcontractkit/chainlink/pull/13948) [`3b4c2b58c3`](https://github.com/smartcontractkit/chainlink/commit/3b4c2b58c3ebb04a2261108e758a3419de436a71) Thanks [@chainchad](https://github.com/chainchad)! - Initialize start of v2.16.0 release + +- [#14100](https://github.com/smartcontractkit/chainlink/pull/14100) [`6a9528db29`](https://github.com/smartcontractkit/chainlink/commit/6a9528db29dadd231ec592f10d655e5367301d8f) Thanks [@huangzhen1997](https://github.com/huangzhen1997)! - add error handling when arbitrum sequencer is not accessible #added + +- [#13794](https://github.com/smartcontractkit/chainlink/pull/13794) [`c330defde2`](https://github.com/smartcontractkit/chainlink/commit/c330defde2211aa4a0d8392f867400a829220b2f) Thanks [@Farber98](https://github.com/Farber98)! - remove dependency on FinalityDepth in EVM TXM code. #internal + +- [#14099](https://github.com/smartcontractkit/chainlink/pull/14099) [`1d1af81c51`](https://github.com/smartcontractkit/chainlink/commit/1d1af81c51d78a7e1406d3e182b8740a2ae43c9c) Thanks [@huangzhen1997](https://github.com/huangzhen1997)! - add error handle for gnosis chiado for seen tx #added + +- [#14039](https://github.com/smartcontractkit/chainlink/pull/14039) [`b0e31e08d5`](https://github.com/smartcontractkit/chainlink/commit/b0e31e08d5a635521afc48570a4b2a01e1daa0fb) Thanks [@huangzhen1997](https://github.com/huangzhen1997)! - Improve TXM performance by optimizing Confirmer and Finalizer queries to stop pulling EVM receipt. #internal + +- [#14096](https://github.com/smartcontractkit/chainlink/pull/14096) [`3f0fad643d`](https://github.com/smartcontractkit/chainlink/commit/3f0fad643d554d2445273a67f58974cb6a785ec4) Thanks [@Farber98](https://github.com/Farber98)! - use FilteredLogs in EventBinding GetLatestValue instead of manual filtering. #internal + +- [#14068](https://github.com/smartcontractkit/chainlink/pull/14068) [`6ab3eb5b67`](https://github.com/smartcontractkit/chainlink/commit/6ab3eb5b67739ff88d3c4cf8ea125fd8273bc2b1) Thanks [@asoliman92](https://github.com/asoliman92)! - #added merging core/capabilities/ccip from https://github.com/smartcontractkit/ccip + +- [#14095](https://github.com/smartcontractkit/chainlink/pull/14095) [`aa4e981c8f`](https://github.com/smartcontractkit/chainlink/commit/aa4e981c8f51692ae19f57569260171736a3e4d9) Thanks [@cedric-cordenier](https://github.com/cedric-cordenier)! - #internal Change CapabilityType to string; remove possiblity of a panic + +- [#13957](https://github.com/smartcontractkit/chainlink/pull/13957) [`20dbba8e76`](https://github.com/smartcontractkit/chainlink/commit/20dbba8e76604a2488b0717d53d706ee11b11a9c) Thanks [@amit-momin](https://github.com/amit-momin)! - Added nonce validation immediately after broadcast for Hedera #internal + +- [#13638](https://github.com/smartcontractkit/chainlink/pull/13638) [`2312827156`](https://github.com/smartcontractkit/chainlink/commit/2312827156f24fa4a6e420aec12e5a3aeac81e2b) Thanks [@amit-momin](https://github.com/amit-momin)! - Introduced finalized transaction state. Added a finalizer component to the TXM to mark transactions as finalized. #internal + +- [#14041](https://github.com/smartcontractkit/chainlink/pull/14041) [`8d818ea265`](https://github.com/smartcontractkit/chainlink/commit/8d818ea265ff08887e61ace4f83364a3ee149ef0) Thanks [@amit-momin](https://github.com/amit-momin)! - Added gas limit estimation feature to EVM gas estimators. Introduced a new config `EVM.GasEstimator.EstimateLimit` to toggle this feature. #added + +- [#14165](https://github.com/smartcontractkit/chainlink/pull/14165) [`e76463cfa9`](https://github.com/smartcontractkit/chainlink/commit/e76463cfa9a0fbe6e35a74cbb3f7d63c85efcd88) Thanks [@silaslenihan](https://github.com/silaslenihan)! - #internal Add hexutil Bytes encoding to batchcall data + +- [#11654](https://github.com/smartcontractkit/chainlink/pull/11654) [`bf2b72d164`](https://github.com/smartcontractkit/chainlink/commit/bf2b72d164f8cc714cfbf57df59a3f3bf952b153) Thanks [@reductionista](https://github.com/reductionista)! - #bugfix More robust error handling in LogPoller, including no more misleading CRITICAL errors emitted under non-critical conditions + +- [#13647](https://github.com/smartcontractkit/chainlink/pull/13647) [`a41b353a20`](https://github.com/smartcontractkit/chainlink/commit/a41b353a20d73aa2d3fe3e8e979a0bcacc46fafe) Thanks [@bukata-sa](https://github.com/bukata-sa)! - #added Report new heads as a telemetry to OTI + +- [#13981](https://github.com/smartcontractkit/chainlink/pull/13981) [`6ef1d6eb44`](https://github.com/smartcontractkit/chainlink/commit/6ef1d6eb449ee1dc1d7d10d50990de7da55561ee) Thanks [@amaechiokolobi](https://github.com/amaechiokolobi)! - error handling for Treasure #added + +- [#14057](https://github.com/smartcontractkit/chainlink/pull/14057) [`e0850a6a31`](https://github.com/smartcontractkit/chainlink/commit/e0850a6a31843606015d1c49d52b5a6ad8727378) Thanks [@reductionista](https://github.com/reductionista)! - #bugfix Addresses 2 minor issues with the pruning of LogPoller's db tables: logs not matching any filter will now be pruned, and rows deleted are now properly reported for observability + +- [#14146](https://github.com/smartcontractkit/chainlink/pull/14146) [`d0d2f3046d`](https://github.com/smartcontractkit/chainlink/commit/d0d2f3046d44dc929b97bfff69b2daf4de2d4c8e) Thanks [@Farber98](https://github.com/Farber98)! - remove chainReader from the Relayer struct. #internal + +- [#14016](https://github.com/smartcontractkit/chainlink/pull/14016) [`8b9f2b6b90`](https://github.com/smartcontractkit/chainlink/commit/8b9f2b6b9098e8ec2368773368239106d066e4e3) Thanks [@ilija42](https://github.com/ilija42)! - #internal Add evm Chain Reader GetLatestValue support for filtering on indexed topic types that get hashed. + +- [#14033](https://github.com/smartcontractkit/chainlink/pull/14033) [`375e17b70f`](https://github.com/smartcontractkit/chainlink/commit/375e17b70fe6f17483556a491370e72218896dbc) Thanks [@Farber98](https://github.com/Farber98)! - Change ChainReader Block primitive field from int to string. #internal + +- [#14160](https://github.com/smartcontractkit/chainlink/pull/14160) [`c98feb205d`](https://github.com/smartcontractkit/chainlink/commit/c98feb205d5eef64d71c42b43516a87b83796a1d) Thanks [@ma33r](https://github.com/ma33r)! - Edited the Optimism Stack L1 Oracle to add support for Mantle #added + +- [#13999](https://github.com/smartcontractkit/chainlink/pull/13999) [`2a032e83a5`](https://github.com/smartcontractkit/chainlink/commit/2a032e83a5e09ae128e8c751779a7d1eebb729ea) Thanks [@amit-momin](https://github.com/amit-momin)! - Updated AutoPurge.Threshold and AutoPurge.MinAttempts configs to only be required for heuristic and added content-type header for Scroll API #internal + +- [#14021](https://github.com/smartcontractkit/chainlink/pull/14021) [`bd648bd73d`](https://github.com/smartcontractkit/chainlink/commit/bd648bd73df2a1de91a463a988f4c5b61e74b240) Thanks [@dhaidashenko](https://github.com/dhaidashenko)! - Added custom finality calculation for Astar #internal + +- [#14145](https://github.com/smartcontractkit/chainlink/pull/14145) [`567ce229ed`](https://github.com/smartcontractkit/chainlink/commit/567ce229ed434a74b09124feadf3265017ec5313) Thanks [@cedric-cordenier](https://github.com/cedric-cordenier)! - Formalize trigger API #internal + +- [#14127](https://github.com/smartcontractkit/chainlink/pull/14127) [`5e99bdb764`](https://github.com/smartcontractkit/chainlink/commit/5e99bdb764171f584df1fc6e10495c8ec0a3bb63) Thanks [@amit-momin](https://github.com/amit-momin)! - Added client error classification for terminally stuck transactions in the TXM #internal + +- [#14043](https://github.com/smartcontractkit/chainlink/pull/14043) [`55e7c8b505`](https://github.com/smartcontractkit/chainlink/commit/55e7c8b5055c975665a59199d5eda9fa21801a07) Thanks [@asoliman92](https://github.com/asoliman92)! - Added CCIP plugins code from https://github.com/smartcontractkit/ccip/ #added + +### Patch Changes + +- [#14148](https://github.com/smartcontractkit/chainlink/pull/14148) [`0ceb9b5fc6`](https://github.com/smartcontractkit/chainlink/commit/0ceb9b5fc67199b850d16b6a5ab1848327e91a5b) Thanks [@vyzaldysanchez](https://github.com/vyzaldysanchez)! - #bugfix Fixes test flake + +- [#14174](https://github.com/smartcontractkit/chainlink/pull/14174) [`b9a433bff5`](https://github.com/smartcontractkit/chainlink/commit/b9a433bff513223378b8b29c6f694446d00c345b) Thanks [@DeividasK](https://github.com/DeividasK)! - #added Allow workflows to run without external registry configured + +- [#13987](https://github.com/smartcontractkit/chainlink/pull/13987) [`c1bd103e9b`](https://github.com/smartcontractkit/chainlink/commit/c1bd103e9b134a90e0bd5f77b6e54797c7c881a8) Thanks [@KodeyThomas](https://github.com/KodeyThomas)! - #added L3X Config + +- [#14236](https://github.com/smartcontractkit/chainlink/pull/14236) [`0294e1f381`](https://github.com/smartcontractkit/chainlink/commit/0294e1f3813c0643b61af828ec438307dcab3123) Thanks [@dhaidashenko](https://github.com/dhaidashenko)! - Fixed deadlock in RPCClient causing CL Node to stop performing RPC requests for the affected chain #bugfix + +- [#14206](https://github.com/smartcontractkit/chainlink/pull/14206) [`621e87538c`](https://github.com/smartcontractkit/chainlink/commit/621e87538c931d5d3996974589dc27a0ab43f758) Thanks [@bukata-sa](https://github.com/bukata-sa)! - #bugfix head reporter non-zero reporting period + +- [#13862](https://github.com/smartcontractkit/chainlink/pull/13862) [`05ef7fdbb1`](https://github.com/smartcontractkit/chainlink/commit/05ef7fdbb115f55a85bcbbc5402350818501e1f5) Thanks [@martin-cll](https://github.com/martin-cll)! - New Mercury v4 report schema #added + +- [#14112](https://github.com/smartcontractkit/chainlink/pull/14112) [`1b584366d6`](https://github.com/smartcontractkit/chainlink/commit/1b584366d6bedc114946d0c8e202e95d031d5d37) Thanks [@giogam](https://github.com/giogam)! - #updated Sync feeds-manager wsrpc proto + +- [#14246](https://github.com/smartcontractkit/chainlink/pull/14246) [`f1bc2e7ad3`](https://github.com/smartcontractkit/chainlink/commit/f1bc2e7ad3610339145930991bf6a3c9ef94fa52) Thanks [@amit-momin](https://github.com/amit-momin)! - Updated gas limit estimation feature to set From address #internal + +- [#14018](https://github.com/smartcontractkit/chainlink/pull/14018) [`82accfff5c`](https://github.com/smartcontractkit/chainlink/commit/82accfff5c445fd1d29a26607234eba73e6b30fd) Thanks [@ettec](https://github.com/ettec)! - #internal fix to keystone e2e test dispatcher to correctly mock duplicate registration error + +- [#13990](https://github.com/smartcontractkit/chainlink/pull/13990) [`98fc8813dd`](https://github.com/smartcontractkit/chainlink/commit/98fc8813dd7f46e86a15fc3e838bbb681f835d0b) Thanks [@flodesi](https://github.com/flodesi)! - #added Add Astar TerminallyUnderpriced error mapping + +- [#14179](https://github.com/smartcontractkit/chainlink/pull/14179) [`633eb41a44`](https://github.com/smartcontractkit/chainlink/commit/633eb41a4467f91506e05e7fda6873c7b34f4731) Thanks [@bukata-sa](https://github.com/bukata-sa)! - #internal log info on missed finalized head instead of returning an error + +- [#14154](https://github.com/smartcontractkit/chainlink/pull/14154) [`a937d5c577`](https://github.com/smartcontractkit/chainlink/commit/a937d5c577d8ba13dc7542a757359339442ae33f) Thanks [@mateusz-sekara](https://github.com/mateusz-sekara)! - Separate price updates schedule for token prices in CCIP #updated + +- [#14185](https://github.com/smartcontractkit/chainlink/pull/14185) [`b563d77dd3`](https://github.com/smartcontractkit/chainlink/commit/b563d77dd30ad96253ae6586c06fd34a66d61936) Thanks [@mateusz-sekara](https://github.com/mateusz-sekara)! - Reporting all the token prices from the job spec for CCIP #updated + +- [#13756](https://github.com/smartcontractkit/chainlink/pull/13756) [`c92a7212ee`](https://github.com/smartcontractkit/chainlink/commit/c92a7212ee77b08c40d62925216e5081278a4e3f) Thanks [@vyzaldysanchez](https://github.com/vyzaldysanchez)! - #updated Adds DB syncing for registry syncer + +- [#13876](https://github.com/smartcontractkit/chainlink/pull/13876) [`15dc74cabd`](https://github.com/smartcontractkit/chainlink/commit/15dc74cabd3a83041ca97df54ea0fbb7e76e2a0a) Thanks [@dhaidashenko](https://github.com/dhaidashenko)! - Custom (30s) timeout for Hedera RPC requests with large payloads (SendTransaction, CallContext, etc.) #internal + +- [#14214](https://github.com/smartcontractkit/chainlink/pull/14214) [`32a2ccd2ba`](https://github.com/smartcontractkit/chainlink/commit/32a2ccd2ba4cbe59e46779c82ec35c909141ba2a) Thanks [@ettec](https://github.com/ettec)! - #internal allow gas limit to be specified when submitting transaction + +- [#14092](https://github.com/smartcontractkit/chainlink/pull/14092) [`3399dd6d7f`](https://github.com/smartcontractkit/chainlink/commit/3399dd6d7fee12bd8d099b74397edcc4dc56c11d) Thanks [@cds95](https://github.com/cds95)! - #internal prevent editing whether or not a DON accepts workflows + +- [#13780](https://github.com/smartcontractkit/chainlink/pull/13780) [`af335c1a52`](https://github.com/smartcontractkit/chainlink/commit/af335c1a522769c8c29858d8d6510330af3204cf) Thanks [@samsondav](https://github.com/samsondav)! - Further development of LLO plugin (parallel composition) #wip + +- [#14030](https://github.com/smartcontractkit/chainlink/pull/14030) [`d90bb66934`](https://github.com/smartcontractkit/chainlink/commit/d90bb66934a46bb1c6d376b000d860e1588d91c7) Thanks [@ettec](https://github.com/ettec)! - #internal restore common version to head of develop + +- [#14105](https://github.com/smartcontractkit/chainlink/pull/14105) [`eb31cf7970`](https://github.com/smartcontractkit/chainlink/commit/eb31cf7970bef1615b10b5a734c16879b448f30a) Thanks [@ettec](https://github.com/ettec)! - #internal speed up keystone e2e tests + +- [#14047](https://github.com/smartcontractkit/chainlink/pull/14047) [`d963b0aaac`](https://github.com/smartcontractkit/chainlink/commit/d963b0aaac2117902742cf1d6fc8471e82ae711b) Thanks [@ettec](https://github.com/ettec)! - #internal fix the mock trigger to ensure events are sent + +- [#13853](https://github.com/smartcontractkit/chainlink/pull/13853) [`0f557ae1e0`](https://github.com/smartcontractkit/chainlink/commit/0f557ae1e08040c931f6f3e5c6a96b93b1ca2182) Thanks [@flodesi](https://github.com/flodesi)! - #bugfix Bump BSC PriceMin to 3 gwei to match BSC node's required gas price. This value can be pushed back down to 1 gwei to enable cheaper transactions if the GasPrice field under the Eth.Miner header in the BSC node's config is also pushed down to 1000000000 + +- [#13935](https://github.com/smartcontractkit/chainlink/pull/13935) [`7ec99efc64`](https://github.com/smartcontractkit/chainlink/commit/7ec99efc64832750825f8bc6711fb9794d6e40df) Thanks [@ettec](https://github.com/ettec)! - #internal ensure remote target request hash is deterministic + +- [#14017](https://github.com/smartcontractkit/chainlink/pull/14017) [`1257d33913`](https://github.com/smartcontractkit/chainlink/commit/1257d33913d243c146bccbf4bda67a2bb1c7d5af) Thanks [@DeividasK](https://github.com/DeividasK)! - #internal + +- [#14053](https://github.com/smartcontractkit/chainlink/pull/14053) [`4f0f7802a8`](https://github.com/smartcontractkit/chainlink/commit/4f0f7802a884e831cd76d9578ee5c4a7134034db) Thanks [@DylanTinianov](https://github.com/DylanTinianov)! - Added custom client error messages for Mantle to capture InsufficientEth and Fatal errors. #added + +- [#14059](https://github.com/smartcontractkit/chainlink/pull/14059) [`40f4becb1e`](https://github.com/smartcontractkit/chainlink/commit/40f4becb1eab96920d8bfd59019cdb9358a94122) Thanks [@DeividasK](https://github.com/DeividasK)! - #internal + +- [#14116](https://github.com/smartcontractkit/chainlink/pull/14116) [`7fdc0c8e95`](https://github.com/smartcontractkit/chainlink/commit/7fdc0c8e95c4157dd9e3ce3f9a4efe370554a19c) Thanks [@ettec](https://github.com/ettec)! - #internal ks-404 validate ids before using as seed of transmission schedule + +- [#13993](https://github.com/smartcontractkit/chainlink/pull/13993) [`f5e0bd614a`](https://github.com/smartcontractkit/chainlink/commit/f5e0bd614a6c42d195c4ad74a10f7070970d01d5) Thanks [@DeividasK](https://github.com/DeividasK)! - #internal + +- [#14209](https://github.com/smartcontractkit/chainlink/pull/14209) [`c00ac968e6`](https://github.com/smartcontractkit/chainlink/commit/c00ac968e651fd7b09f473d20f0fe4755ba57367) Thanks [@AnieeG](https://github.com/AnieeG)! - #internal Adding deployment package as new pattern for product deployment/configuration + +- [#14183](https://github.com/smartcontractkit/chainlink/pull/14183) [`35f68c806b`](https://github.com/smartcontractkit/chainlink/commit/35f68c806b10cc0fe4a565293e32e2f5581bfeb5) Thanks [@graham-chainlink](https://github.com/graham-chainlink)! - #bugfix Fix incorrect error handling when registering a new feed manager + +- [#14212](https://github.com/smartcontractkit/chainlink/pull/14212) [`25d2961154`](https://github.com/smartcontractkit/chainlink/commit/25d29611543c3d43484c168e7efc23a7bf83f035) Thanks [@bukata-sa](https://github.com/bukata-sa)! - #internal add head report chain_id + +- [#14066](https://github.com/smartcontractkit/chainlink/pull/14066) [`98b9054397`](https://github.com/smartcontractkit/chainlink/commit/98b90543972d37e4c00196f3f00bcf5f380ea04d) Thanks [@DeividasK](https://github.com/DeividasK)! - #internal + +- [#14014](https://github.com/smartcontractkit/chainlink/pull/14014) [`c2c31c05ac`](https://github.com/smartcontractkit/chainlink/commit/c2c31c05ac3fe19d4df8313af25eb740953b935a) Thanks [@Madalosso](https://github.com/Madalosso)! - #updated Update Polygon configs to match PIP-35 + +- [#14125](https://github.com/smartcontractkit/chainlink/pull/14125) [`8fa8c3a075`](https://github.com/smartcontractkit/chainlink/commit/8fa8c3a07512bb8358abdabc3fdcc8ae310c6c1c) Thanks [@bukata-sa](https://github.com/bukata-sa)! - #bugfix balance shutdown deadlock + +- [#14181](https://github.com/smartcontractkit/chainlink/pull/14181) [`ee57b4f940`](https://github.com/smartcontractkit/chainlink/commit/ee57b4f940b8a9d9d7bba41a74e4757874755f5f) Thanks [@ettec](https://github.com/ettec)! - #internal topeerid should validate []byte length + +- [#14074](https://github.com/smartcontractkit/chainlink/pull/14074) [`a865709ea1`](https://github.com/smartcontractkit/chainlink/commit/a865709ea18bfc792db758b60de6f03e953f141f) Thanks [@mateusz-sekara](https://github.com/mateusz-sekara)! - Simplify how token and gas prices are stored in the database - user upsert instead of insert/delete flow #db_update + +- [#14050](https://github.com/smartcontractkit/chainlink/pull/14050) [`537d2ec1ad`](https://github.com/smartcontractkit/chainlink/commit/537d2ec1ad846898f820874442c3f69915096bad) Thanks [@ettec](https://github.com/ettec)! - #internal fix data race in syncer launcher + +- [#13970](https://github.com/smartcontractkit/chainlink/pull/13970) [`cefbb09797`](https://github.com/smartcontractkit/chainlink/commit/cefbb09797249309ac18e4ef81147e30f7c24360) Thanks [@cds95](https://github.com/cds95)! - #internal prevent reentrancy when configuring DON in Capabilities Registry + +- [#13907](https://github.com/smartcontractkit/chainlink/pull/13907) [`1eaf5e087a`](https://github.com/smartcontractkit/chainlink/commit/1eaf5e087a5ac204e0b472e1c307722887104678) Thanks [@dhaidashenko](https://github.com/dhaidashenko)! - Added new health check that ensures RPC provides new finalized heads at least every `NoNewFinalizedHeadsThreshold` #added + +## 2.15.0 - 2024-08-21 ### Minor Changes diff --git a/GNUmakefile b/GNUmakefile index 62af10572d..5f04d6c15e 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -74,6 +74,16 @@ docker: --build-arg COMMIT_SHA=$(COMMIT_SHA) \ -f core/chainlink.Dockerfile . +.PHONY: docker-ccip ## Build the chainlink docker image +docker-ccip: + docker buildx build \ + --build-arg COMMIT_SHA=$(COMMIT_SHA) \ + -f core/chainlink.Dockerfile . -t chainlink-ccip:latest + + docker buildx build \ + --build-arg COMMIT_SHA=$(COMMIT_SHA) \ + -f ccip/ccip.Dockerfile . + .PHONY: docker-plugins ## Build the chainlink-plugins docker image docker-plugins: docker buildx build \ @@ -89,7 +99,7 @@ abigen: ## Build & install abigen. ./tools/bin/build_abigen .PHONY: generate -generate: pnpmdep abigen codecgen mockery protoc gomods ## Execute all go:generate commands. +generate: abigen codecgen mockery protoc gomods ## Execute all go:generate commands. gomods -w go generate -x ./... mockery @@ -164,8 +174,7 @@ config-docs: ## Generate core node configuration documentation .PHONY: golangci-lint golangci-lint: ## Run golangci-lint for all issues. [ -d "./golangci-lint" ] || mkdir ./golangci-lint && \ - docker run --rm -v $(shell pwd):/app -w /app golangci/golangci-lint:v1.59.1 golangci-lint run --max-issues-per-linter 0 --max-same-issues 0 > ./golangci-lint/$(shell date +%Y-%m-%d_%H:%M:%S).txt - + docker run --rm -v $(shell pwd):/app -w /app golangci/golangci-lint:v1.59.1 golangci-lint run --max-issues-per-linter 0 --max-same-issues 0 | tee ./golangci-lint/$(shell date +%Y-%m-%d_%H:%M:%S).txt GORELEASER_CONFIG ?= .goreleaser.yaml @@ -183,7 +192,7 @@ modgraph: .PHONY: test-short test-short: ## Run 'go test -short' and suppress uninteresting output - go test -short ./... | grep -v "[no test files]" | grep -v "\(cached\)" + go test -short ./... | grep -v "no test files" | grep -v "\(cached\)" help: @echo "" diff --git a/README.md b/README.md index 58e6516f4c..e7c21c1e09 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,21 @@ External adapters are what make Chainlink easily extensible, providing simple in For more information on creating and using external adapters, please see our [external adapters page](https://docs.chain.link/docs/external-adapters). +## Verify Official Chainlink Releases + +We use `cosign` with OIDC keyless signing during the [Build, Sign and Publish Chainlink](https://github.com/smartcontractkit/chainlink/actions/workflows/build-publish.yml) workflow. + +It is encourage for any node operator building from the official Chainlink docker image to verify the tagged release version was did indeed built from this workflow. + +You will need `cosign` in order to do this verification. [Follow the instruction here to install cosign](https://docs.sigstore.dev/system_config/installation/). + +```bash +# tag is the tagged release version - ie. v2.16.0 +cosign verify public.ecr.aws/chainlink/chainlink:${tag} \ + --certificate-oidc-issuer https://token.actions.githubusercontent.com \ + --certificate-identity "https://github.com/smartcontractkit/chainlink/.github/workflows/build-publish.yml@refs/tags/${tag}" +``` + ## Development ### Running tests diff --git a/bors.toml b/bors.toml deleted file mode 100644 index 5970c638ce..0000000000 --- a/bors.toml +++ /dev/null @@ -1,20 +0,0 @@ -# https://github.com/bors-ng/bors-ng/issues/730 -status = [ - "sigscanner-check", - "lint", - "Core Tests (go_core_tests)", - "Core Tests (go_core_race_tests)", - "Solana Smoke Tests", - "Prettier Formatting", - "ETH Smoke Tests", - "Solidity" -] -block_labels = [ "do-not-merge", "do-not-merge-yet", "needs changes", "wip" ] -timeout_sec = 3600 # one hour -required_approvals = 1 -up_to_date_approvals = true -delete_merged_branches = true -update_base_for_deletes = true -# todo: enable after organizing codeowners -# use_codeowners = true -use_squash_merge = true diff --git a/ccip/config/README.md b/ccip/config/README.md new file mode 100644 index 0000000000..f5cd87e688 --- /dev/null +++ b/ccip/config/README.md @@ -0,0 +1,10 @@ +# Default Configurations + +:warning: IMPORTANT :warning: + +This directory contains configs that are specific to the CCIP build. Apply changes here only if related to the CCIP. + + +All config sets **inherit** from `fallback.toml` first and overwrite +fields as necessary. Do not create a new full configuration from +scratch. diff --git a/ccip/config/evm/Arbitrum_Mainnet.toml b/ccip/config/evm/Arbitrum_Mainnet.toml new file mode 100644 index 0000000000..e3dcafd56f --- /dev/null +++ b/ccip/config/evm/Arbitrum_Mainnet.toml @@ -0,0 +1,29 @@ +# Arbitrum is an L2 chain. Pending proper L2 support, for now we rely on their sequencer +ChainID = '42161' +ChainType = 'arbitrum' +FinalityTagEnabled = true +LinkContractAddress = "0xf97f4df75117a78c1A5a0DBb814Af92458539FB4" +LogPollInterval = '1s' +# Arbitrum only emits blocks when a new tx is received, so this method of liveness detection is not useful +NoNewHeadsThreshold = '0' +OCR.ContractConfirmations = 1 + +[GasEstimator] +Mode = 'Arbitrum' +LimitMax = 1_000_000_000 +# Arbitrum uses the suggested gas price, so we don't want to place any limits on the minimum +PriceMin = '0' +PriceDefault = '0.1 gwei' +PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' +FeeCapDefault = '1000 gwei' +BumpThreshold = 5 + +[GasEstimator.BlockHistory] +# Force an error if someone set GAS_UPDATER_ENABLED=true by accident; we never want to run the block history estimator on arbitrum +BlockHistorySize = 0 + +[NodePool] +SyncThreshold = 10 + +[OCR2.Automation] +GasLimit = 14500000 diff --git a/ccip/config/evm/Arbitrum_Sepolia.toml b/ccip/config/evm/Arbitrum_Sepolia.toml new file mode 100644 index 0000000000..ea994cf7c7 --- /dev/null +++ b/ccip/config/evm/Arbitrum_Sepolia.toml @@ -0,0 +1,27 @@ +ChainID = '421614' +ChainType = 'arbitrum' +FinalityTagEnabled = true +LinkContractAddress = '0xE4aB69C077896252FAFBD49EFD26B5D171A32410' +NoNewHeadsThreshold = '0' +OCR.ContractConfirmations = 1 +LogPollInterval = '1s' + +[GasEstimator] +Mode = 'Arbitrum' +LimitMax = 1_000_000_000 +# Arbitrum uses the suggested gas price, so we don't want to place any limits on the minimum +PriceMin = '0' +PriceDefault = '0.1 gwei' +PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' +FeeCapDefault = '1000 gwei' +BumpThreshold = 5 + +[GasEstimator.BlockHistory] +# Force an error if someone set GAS_UPDATER_ENABLED=true by accident; we never want to run the block history estimator on arbitrum +BlockHistorySize = 0 + +[NodePool] +SyncThreshold = 10 + +[OCR2.Automation] +GasLimit = 14500000 diff --git a/ccip/config/evm/Astar_Mainnet.toml b/ccip/config/evm/Astar_Mainnet.toml new file mode 100644 index 0000000000..87808001eb --- /dev/null +++ b/ccip/config/evm/Astar_Mainnet.toml @@ -0,0 +1,9 @@ +ChainID = '592' +FinalityTagEnabled = true +FinalityDepth = 100 +LogPollInterval = '6s' + +[GasEstimator] +EIP1559DynamicFees = false +PriceMax = '100000 gwei' +LimitDefault = 8000000 \ No newline at end of file diff --git a/ccip/config/evm/Astar_Shibuya.toml b/ccip/config/evm/Astar_Shibuya.toml new file mode 100644 index 0000000000..5a5df06f6f --- /dev/null +++ b/ccip/config/evm/Astar_Shibuya.toml @@ -0,0 +1,9 @@ +ChainID = '81' +FinalityTagEnabled = true +FinalityDepth = 100 +LogPollInterval = '6s' + +[GasEstimator] +EIP1559DynamicFees = false +PriceMax = '100000 gwei' +LimitDefault = 8000000 \ No newline at end of file diff --git a/ccip/config/evm/Avalanche_ANZ_testnet.toml b/ccip/config/evm/Avalanche_ANZ_testnet.toml new file mode 100644 index 0000000000..4833881bf4 --- /dev/null +++ b/ccip/config/evm/Avalanche_ANZ_testnet.toml @@ -0,0 +1,19 @@ +ChainID = '76578' +FinalityDepth = 1 +FinalityTagEnabled = false +LinkContractAddress = '0x779877A7B0D9E8603169DdbD7836e478b4624789' +LogPollInterval = '3s' +MinIncomingConfirmations = 1 +# Avax subnet only emits blocks when a new tx is received, so this method of liveness detection is not useful. +NoNewHeadsThreshold = '0' +OCR.ContractConfirmations = 1 +RPCBlockQueryDelay = 2 + +[GasEstimator] +Mode = 'BlockHistory' +PriceDefault = '25 gwei' +PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' +PriceMin = '25 gwei' + +[GasEstimator.BlockHistory] +BlockHistorySize = 24 diff --git a/ccip/config/evm/Avalanche_Fuji.toml b/ccip/config/evm/Avalanche_Fuji.toml new file mode 100644 index 0000000000..5ba2e3cdc7 --- /dev/null +++ b/ccip/config/evm/Avalanche_Fuji.toml @@ -0,0 +1,21 @@ +ChainID = '43113' +FinalityDepth = 1 +FinalityTagEnabled = true +LinkContractAddress = '0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846' +LogPollInterval = '3s' +MinIncomingConfirmations = 1 +NoNewHeadsThreshold = '30s' +OCR.ContractConfirmations = 1 +RPCBlockQueryDelay = 2 +NoNewFinalizedHeadsThreshold = '1m' + +[GasEstimator] +PriceDefault = '25 gwei' +PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' +PriceMin = '25 gwei' + +[GasEstimator.BlockHistory] +BlockHistorySize = 24 + +[HeadTracker] +FinalityTagBypass = false diff --git a/ccip/config/evm/Avalanche_Mainnet.toml b/ccip/config/evm/Avalanche_Mainnet.toml new file mode 100644 index 0000000000..e7813842b4 --- /dev/null +++ b/ccip/config/evm/Avalanche_Mainnet.toml @@ -0,0 +1,19 @@ +ChainID = '43114' +FinalityDepth = 1 +FinalityTagEnabled = true +LinkContractAddress = '0x5947BB275c521040051D82396192181b413227A3' +LogPollInterval = '3s' +MinIncomingConfirmations = 1 +NoNewHeadsThreshold = '30s' +OCR.ContractConfirmations = 1 +RPCBlockQueryDelay = 2 +NoNewFinalizedHeadsThreshold = '1m' + +[GasEstimator] +PriceDefault = '25 gwei' +PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' +PriceMin = '25 gwei' + +[GasEstimator.BlockHistory] +# Average block time of 2s +BlockHistorySize = 24 diff --git a/ccip/config/evm/BSC_Mainnet.toml b/ccip/config/evm/BSC_Mainnet.toml new file mode 100644 index 0000000000..10f4c570be --- /dev/null +++ b/ccip/config/evm/BSC_Mainnet.toml @@ -0,0 +1,28 @@ +# BSC uses Clique consensus with ~3s block times +# Clique offers finality within (N/2)+1 blocks where N is number of signers +# There are 21 BSC validators so theoretically finality should occur after 21/2+1 = 11 blocks +ChainID = '56' +# Keeping this >> 11 because it's not expensive and gives us a safety margin +FinalityDepth = 50 +FinalityTagEnabled = true +LinkContractAddress = '0x404460C6A5EdE2D891e8297795264fDe62ADBB75' +LogPollInterval = '3s' +NoNewHeadsThreshold = '30s' +RPCBlockQueryDelay = 2 +NoNewFinalizedHeadsThreshold = '45s' + +[GasEstimator] +PriceDefault = '5 gwei' +# 15s delay since feeds update every minute in volatile situations +BumpThreshold = 5 + +[GasEstimator.BlockHistory] +BlockHistorySize = 24 + +[OCR] +DatabaseTimeout = '2s' +ContractTransmitterTransmitTimeout = '2s' +ObservationGracePeriod = '500ms' + +[NodePool] +SyncThreshold = 10 diff --git a/ccip/config/evm/BSC_Testnet.toml b/ccip/config/evm/BSC_Testnet.toml new file mode 100644 index 0000000000..b27a877812 --- /dev/null +++ b/ccip/config/evm/BSC_Testnet.toml @@ -0,0 +1,33 @@ +# BSC uses Clique consensus with ~3s block times +# Clique offers finality within (N/2)+1 blocks where N is number of signers +# There are 21 BSC validators so theoretically finality should occur after 21/2+1 = 11 blocks +ChainID = '97' +# Keeping this >> 11 because it's not expensive and gives us a safety margin +FinalityDepth = 50 +FinalityTagEnabled = true +LinkContractAddress = '0x84b9B910527Ad5C03A9Ca831909E21e236EA7b06' +LogPollInterval = '3s' +NoNewHeadsThreshold = '30s' +RPCBlockQueryDelay = 2 +NoNewFinalizedHeadsThreshold = '40s' + +[GasEstimator] +PriceDefault = '5 gwei' +# 15s delay since feeds update every minute in volatile situations +BumpThreshold = 5 + +[GasEstimator.BlockHistory] +BlockHistorySize = 24 + +[HeadTracker] +HistoryDepth = 100 +SamplingInterval = '1s' +FinalityTagBypass = false + +[OCR] +DatabaseTimeout = '2s' +ContractTransmitterTransmitTimeout = '2s' +ObservationGracePeriod = '500ms' + +[NodePool] +SyncThreshold = 10 diff --git a/ccip/config/evm/Base_Mainnet.toml b/ccip/config/evm/Base_Mainnet.toml new file mode 100644 index 0000000000..da38182b19 --- /dev/null +++ b/ccip/config/evm/Base_Mainnet.toml @@ -0,0 +1,31 @@ +ChainID = '8453' +ChainType = 'optimismBedrock' +FinalityDepth = 200 +FinalityTagEnabled = true +LogPollInterval = '2s' +NoNewHeadsThreshold = '40s' +MinIncomingConfirmations = 1 +NoNewFinalizedHeadsThreshold = '15m' + +[GasEstimator] +EIP1559DynamicFees = true +PriceMin = '1 wei' +BumpMin = '100 wei' + +[GasEstimator.BlockHistory] +BlockHistorySize = 24 + +[Transactions] +ResendAfterThreshold = '30s' + +[HeadTracker] +HistoryDepth = 300 + +[NodePool] +SyncThreshold = 10 + +[OCR] +ContractConfirmations = 1 + +[OCR2.Automation] +GasLimit = 6500000 diff --git a/ccip/config/evm/Base_Sepolia.toml b/ccip/config/evm/Base_Sepolia.toml new file mode 100644 index 0000000000..92f7717b27 --- /dev/null +++ b/ccip/config/evm/Base_Sepolia.toml @@ -0,0 +1,32 @@ +ChainID = '84532' +ChainType = 'optimismBedrock' +FinalityDepth = 200 +FinalityTagEnabled = true +LinkContractAddress = '0xE4aB69C077896252FAFBD49EFD26B5D171A32410' +LogPollInterval = '2s' +NoNewHeadsThreshold = '40s' +MinIncomingConfirmations = 1 +NoNewFinalizedHeadsThreshold = '12m' + +[GasEstimator] +EIP1559DynamicFees = true +PriceMin = '1 wei' +BumpMin = '100 wei' + +[GasEstimator.BlockHistory] +BlockHistorySize = 60 + +[Transactions] +ResendAfterThreshold = '30s' + +[HeadTracker] +HistoryDepth = 300 + +[NodePool] +SyncThreshold = 10 + +[OCR] +ContractConfirmations = 1 + +[OCR2.Automation] +GasLimit = 6500000 diff --git a/ccip/config/evm/Blast_Mainnet.toml b/ccip/config/evm/Blast_Mainnet.toml new file mode 100644 index 0000000000..f8b501723f --- /dev/null +++ b/ccip/config/evm/Blast_Mainnet.toml @@ -0,0 +1,34 @@ +ChainID = '81457' +FinalityDepth = 200 +FinalityTagEnabled = true +ChainType = 'optimismBedrock' +# block rate is ~2sec, so this ensures blocks are polled correctly +LogPollInterval = '2s' + +[GasEstimator] +EIP1559DynamicFees = true +BumpThreshold = 60 +BumpPercent = 20 +BumpMin = '100 wei' +PriceMax = '120 gwei' +LimitDefault = 8000000 +FeeCapDefault = '120 gwei' + +[GasEstimator.BlockHistory] +# Default is 24, which leads to bumpy gas prices. In CCIP +# we want to smooth out the gas prices, so we increase the sample size. +BlockHistorySize = 200 +# The formula for FeeCap is (current block base fee * (1.125 ^ EIP1559FeeCapBufferBlocks) + tipcap) +# where tipcap is managed by the block history estimators. In the context of CCIP, +# the gas price is relayed to other changes for quotes so we want accurate/avg not pessimistic values. +# So we set this to zero so FeeCap = baseFee + tipcap. +EIP1559FeeCapBufferBlocks = 0 + +[HeadTracker] +HistoryDepth = 300 + +[NodePool] +# 4 block sync time between nodes to ensure they aren't labelled unreachable too soon +PollFailureThreshold = 4 +# polls every 4sec to check if there is a block produced, since blockRate is ~3sec +PollInterval = '4s' \ No newline at end of file diff --git a/ccip/config/evm/Blast_Sepolia.toml b/ccip/config/evm/Blast_Sepolia.toml new file mode 100644 index 0000000000..96dc5c6787 --- /dev/null +++ b/ccip/config/evm/Blast_Sepolia.toml @@ -0,0 +1,34 @@ +ChainID = '168587773' +FinalityDepth = 200 +FinalityTagEnabled = true +ChainType = 'optimismBedrock' +# block rate is ~2sec, so this ensures blocks are polled correctly +LogPollInterval = '2s' + +[GasEstimator] +EIP1559DynamicFees = true +BumpThreshold = 60 +BumpPercent = 20 +BumpMin = '100 wei' +PriceMax = '120 gwei' +LimitDefault = 8000000 +FeeCapDefault = '120 gwei' + +[GasEstimator.BlockHistory] +# Default is 24, which leads to bumpy gas prices. In CCIP +# we want to smooth out the gas prices, so we increase the sample size. +BlockHistorySize = 200 +# The formula for FeeCap is (current block base fee * (1.125 ^ EIP1559FeeCapBufferBlocks) + tipcap) +# where tipcap is managed by the block history estimators. In the context of CCIP, +# the gas price is relayed to other changes for quotes so we want accurate/avg not pessimistic values. +# So we set this to zero so FeeCap = baseFee + tipcap. +EIP1559FeeCapBufferBlocks = 0 + +[HeadTracker] +HistoryDepth = 300 + +[NodePool] +# 4 block sync time between nodes to ensure they aren't labelled unreachable too soon +PollFailureThreshold = 4 +# polls every 4sec to check if there is a block produced, since blockRate is ~3sec +PollInterval = '4s' \ No newline at end of file diff --git a/ccip/config/evm/Celo_Mainnet.toml b/ccip/config/evm/Celo_Mainnet.toml new file mode 100644 index 0000000000..a494862037 --- /dev/null +++ b/ccip/config/evm/Celo_Mainnet.toml @@ -0,0 +1,20 @@ +ChainID = '42220' +ChainType = 'celo' +FinalityDepth = 10 +LogPollInterval = '5s' +MinIncomingConfirmations = 1 +NoNewHeadsThreshold = '1m' +OCR.ContractConfirmations = 1 +NoNewFinalizedHeadsThreshold = '1m' + +[GasEstimator] +PriceDefault = '5 gwei' +PriceMax = '500 gwei' +PriceMin = '5 gwei' +BumpMin = '2 gwei' + +[GasEstimator.BlockHistory] +BlockHistorySize = 12 + +[HeadTracker] +HistoryDepth = 50 diff --git a/ccip/config/evm/Celo_Testnet.toml b/ccip/config/evm/Celo_Testnet.toml new file mode 100644 index 0000000000..eb43f080b7 --- /dev/null +++ b/ccip/config/evm/Celo_Testnet.toml @@ -0,0 +1,20 @@ +ChainID = '44787' +ChainType = 'celo' +FinalityDepth = 10 +LogPollInterval = '5s' +MinIncomingConfirmations = 1 +NoNewHeadsThreshold = '1m' +OCR.ContractConfirmations = 1 +NoNewFinalizedHeadsThreshold = '1m' + +[GasEstimator] +PriceDefault = '5 gwei' +PriceMax = '500 gwei' +PriceMin = '5 gwei' +BumpMin = '2 gwei' + +[GasEstimator.BlockHistory] +BlockHistorySize = 24 + +[HeadTracker] +HistoryDepth = 50 diff --git a/ccip/config/evm/Ethereum_Mainnet.toml b/ccip/config/evm/Ethereum_Mainnet.toml new file mode 100644 index 0000000000..0bcaf35c64 --- /dev/null +++ b/ccip/config/evm/Ethereum_Mainnet.toml @@ -0,0 +1,17 @@ +ChainID = '1' +LinkContractAddress = '0x514910771AF9Ca656af840dff83E8264EcF986CA' +MinContractPayment = '0.1 link' +OperatorFactoryAddress = '0x3E64Cd889482443324F91bFA9c84fE72A511f48A' +FinalityTagEnabled = true +NoNewFinalizedHeadsThreshold = '9m' + +[GasEstimator] +EIP1559DynamicFees = true + +[GasEstimator.BlockHistory] +# EIP-1559 does well on a smaller block history size +BlockHistorySize = 4 +TransactionPercentile = 50 + +[OCR2.Automation] +GasLimit = 10500000 diff --git a/ccip/config/evm/Ethereum_Sepolia.toml b/ccip/config/evm/Ethereum_Sepolia.toml new file mode 100644 index 0000000000..24a0e68f77 --- /dev/null +++ b/ccip/config/evm/Ethereum_Sepolia.toml @@ -0,0 +1,17 @@ +ChainID = '11155111' +LinkContractAddress = '0x779877A7B0D9E8603169DdbD7836e478b4624789' +MinContractPayment = '0.1 link' +FinalityTagEnabled = true + +[GasEstimator] +EIP1559DynamicFees = true + +[GasEstimator.BlockHistory] +BlockHistorySize = 4 +TransactionPercentile = 50 + +[OCR2.Automation] +GasLimit = 10500000 + +[HeadTracker] +FinalityTagBypass = false diff --git a/ccip/config/evm/Fantom_Mainnet.toml b/ccip/config/evm/Fantom_Mainnet.toml new file mode 100644 index 0000000000..7e76d94278 --- /dev/null +++ b/ccip/config/evm/Fantom_Mainnet.toml @@ -0,0 +1,12 @@ +ChainID = '250' +LinkContractAddress = '0x6F43FF82CCA38001B6699a8AC47A2d0E66939407' +LogPollInterval = '1s' +NoNewHeadsThreshold = '30s' +RPCBlockQueryDelay = 2 + +[GasEstimator] +# Fantom network has been slow to include txs at times when using the BlockHistory estimator, and the recommendation is to use SuggestedPrice mode. +Mode = 'SuggestedPrice' + +[OCR2.Automation] +GasLimit = 3800000 \ No newline at end of file diff --git a/ccip/config/evm/Fantom_Testnet.toml b/ccip/config/evm/Fantom_Testnet.toml new file mode 100644 index 0000000000..5f24a76c2e --- /dev/null +++ b/ccip/config/evm/Fantom_Testnet.toml @@ -0,0 +1,12 @@ +ChainID = '4002' +LinkContractAddress = '0xfaFedb041c0DD4fA2Dc0d87a6B0979Ee6FA7af5F' +LogPollInterval = '1s' +# Fantom testnet only emits blocks when a new tx is received, so this method of liveness detection is not useful +NoNewHeadsThreshold = '0' +RPCBlockQueryDelay = 2 + +[GasEstimator] +Mode = 'SuggestedPrice' + +[OCR2.Automation] +GasLimit = 3800000 \ No newline at end of file diff --git a/ccip/config/evm/Gnosis_Chiado.toml b/ccip/config/evm/Gnosis_Chiado.toml new file mode 100644 index 0000000000..379377a226 --- /dev/null +++ b/ccip/config/evm/Gnosis_Chiado.toml @@ -0,0 +1,10 @@ +ChainID = '10200' +# Gnoisis Finality is approx 8 minutes @ 12 blocks per minute, so 96 blocks +FinalityDepth = 100 +ChainType = 'gnosis' +LogPollInterval = '5s' +NoNewFinalizedHeadsThreshold = '2m' + +[GasEstimator] +EIP1559DynamicFees = true +PriceMax = '500 gwei' diff --git a/ccip/config/evm/Gnosis_Mainnet.toml b/ccip/config/evm/Gnosis_Mainnet.toml new file mode 100644 index 0000000000..628646364f --- /dev/null +++ b/ccip/config/evm/Gnosis_Mainnet.toml @@ -0,0 +1,18 @@ +# xDai currently uses AuRa (like Parity) consensus so finality rules will be similar to parity +# See: https://www.poa.network/for-users/whitepaper/poadao-v1/proof-of-authority +# NOTE: xDai is planning to move to Honeybadger BFT which might have different finality guarantees +# https://www.xdaichain.com/for-validators/consensus/honeybadger-bft-consensus +# For worst case re-org depth on AuRa, assume 2n+2 (see: https://github.com/poanetwork/wiki/wiki/Aura-Consensus-Protocol-Audit) +# With xDai's current maximum of 19 validators then 40 blocks is the maximum possible re-org) +# The mainnet default of 50 blocks is ok here +ChainID = '100' +ChainType = 'gnosis' +LinkContractAddress = '0xE2e73A1c69ecF83F464EFCE6A5be353a37cA09b2' +LogPollInterval = '5s' +NoNewFinalizedHeadsThreshold = '2m' + +[GasEstimator] +PriceDefault = '1 gwei' +PriceMax = '500 gwei' +# 1 Gwei is the minimum accepted by the validators (unless whitelisted) +PriceMin = '1 gwei' diff --git a/ccip/config/evm/Kroma_Mainnet.toml b/ccip/config/evm/Kroma_Mainnet.toml new file mode 100644 index 0000000000..3a48aa8ae1 --- /dev/null +++ b/ccip/config/evm/Kroma_Mainnet.toml @@ -0,0 +1,27 @@ +ChainID = '255' +ChainType = 'kroma' # Kroma is based on the Optimism Bedrock architechture +FinalityDepth = 400 +FinalityTagEnabled = true +LogPollInterval = '2s' +NoNewHeadsThreshold = '40s' +MinIncomingConfirmations = 1 + +[GasEstimator] +EIP1559DynamicFees = true +PriceMin = '1 wei' +BumpMin = '100 wei' + +[GasEstimator.BlockHistory] +BlockHistorySize = 24 + +[Transactions] +ResendAfterThreshold = '30s' + +[HeadTracker] +HistoryDepth = 400 + +[NodePool] +SyncThreshold = 10 + +[OCR] +ContractConfirmations = 1 diff --git a/ccip/config/evm/Kroma_Sepolia.toml b/ccip/config/evm/Kroma_Sepolia.toml new file mode 100644 index 0000000000..9609a09e07 --- /dev/null +++ b/ccip/config/evm/Kroma_Sepolia.toml @@ -0,0 +1,27 @@ +ChainID = '2358' +ChainType = 'kroma' # Kroma is based on the Optimism Bedrock architechture +FinalityDepth = 400 +FinalityTagEnabled = true +LogPollInterval = '2s' +NoNewHeadsThreshold = '40s' +MinIncomingConfirmations = 1 + +[GasEstimator] +EIP1559DynamicFees = true +PriceMin = '1 wei' +BumpMin = '100 wei' + +[GasEstimator.BlockHistory] +BlockHistorySize = 24 + +[Transactions] +ResendAfterThreshold = '30s' + +[HeadTracker] +HistoryDepth = 400 + +[NodePool] +SyncThreshold = 10 + +[OCR] +ContractConfirmations = 1 diff --git a/ccip/config/evm/L3X_Mainnet.toml b/ccip/config/evm/L3X_Mainnet.toml new file mode 100644 index 0000000000..1fbda42fd2 --- /dev/null +++ b/ccip/config/evm/L3X_Mainnet.toml @@ -0,0 +1,18 @@ +ChainID = '12324' +ChainType = 'arbitrum' +FinalityTagEnabled = true +FinalityDepth = 10 +LinkContractAddress = '0x79f531a3D07214304F259DC28c7191513223bcf3' +# Produces blocks on-demand +NoNewHeadsThreshold = '0' +OCR.ContractConfirmations = 1 +LogPollInterval = '10s' + +[GasEstimator] +Mode = 'Arbitrum' +LimitMax = 1_000_000_000 +# Arbitrum-based chains uses the suggested gas price, so we don't want to place any limits on the minimum +PriceMin = '0' +PriceDefault = '0.1 gwei' +FeeCapDefault = '1000 gwei' +BumpThreshold = 5 diff --git a/ccip/config/evm/L3X_Sepolia.toml b/ccip/config/evm/L3X_Sepolia.toml new file mode 100644 index 0000000000..ee515bb72b --- /dev/null +++ b/ccip/config/evm/L3X_Sepolia.toml @@ -0,0 +1,18 @@ +ChainID = '12325' +ChainType = 'arbitrum' +FinalityTagEnabled = true +FinalityDepth = 10 +LinkContractAddress = '0xa71848C99155DA0b245981E5ebD1C94C4be51c43' +# Produces blocks on-demand +NoNewHeadsThreshold = '0' +OCR.ContractConfirmations = 1 +LogPollInterval = '10s' + +[GasEstimator] +Mode = 'Arbitrum' +LimitMax = 1_000_000_000 +# Arbitrum-based chains uses the suggested gas price, so we don't want to place any limits on the minimum +PriceMin = '0' +PriceDefault = '0.1 gwei' +FeeCapDefault = '1000 gwei' +BumpThreshold = 5 diff --git a/ccip/config/evm/Linea_Mainnet.toml b/ccip/config/evm/Linea_Mainnet.toml new file mode 100644 index 0000000000..94d8bedc44 --- /dev/null +++ b/ccip/config/evm/Linea_Mainnet.toml @@ -0,0 +1,17 @@ +ChainID = '59144' +# Block time 12s, finality < 60m +FinalityDepth = 300 +# Blocks are only emitted when a transaction happens / no empty blocks +NoNewHeadsThreshold = '0' + +[GasEstimator] +BumpPercent = 40 +PriceMin = '400 mwei' + +[Transactions] +# increase resend time to align with finality +ResendAfterThreshold = '3m' + +# set greater than finality depth +[HeadTracker] +HistoryDepth = 350 diff --git a/ccip/config/evm/Linea_Sepolia.toml b/ccip/config/evm/Linea_Sepolia.toml new file mode 100644 index 0000000000..ac5e18a09b --- /dev/null +++ b/ccip/config/evm/Linea_Sepolia.toml @@ -0,0 +1,13 @@ +ChainID = '59141' +FinalityDepth = 900 +NoNewHeadsThreshold = '0' + +[GasEstimator] +EIP1559DynamicFees = true +PriceMin = '1 wei' + +[Transactions] +ResendAfterThreshold = '3m' + +[HeadTracker] +HistoryDepth = 1000 \ No newline at end of file diff --git a/ccip/config/evm/Mantle_Sepolia.toml b/ccip/config/evm/Mantle_Sepolia.toml new file mode 100644 index 0000000000..ee994a7182 --- /dev/null +++ b/ccip/config/evm/Mantle_Sepolia.toml @@ -0,0 +1,19 @@ +ChainID = '5003' +ChainType = 'optimismBedrock' +FinalityDepth = 500 +LogPollInterval = '2s' +NoNewHeadsThreshold = '0' +MinIncomingConfirmations = 1 + +[HeadTracker] +HistoryDepth = 600 + +[GasEstimator] +Mode = 'L2Suggested' +PriceMax = '200 gwei' +LimitDefault = 100000000 +FeeCapDefault = '200 gwei' + +[GasEstimator.BlockHistory] +BlockHistorySize = 200 +EIP1559FeeCapBufferBlocks = 0 \ No newline at end of file diff --git a/ccip/config/evm/Metis_Mainnet.toml b/ccip/config/evm/Metis_Mainnet.toml new file mode 100644 index 0000000000..f057400d01 --- /dev/null +++ b/ccip/config/evm/Metis_Mainnet.toml @@ -0,0 +1,21 @@ +# Metis is an L2 chain based on Optimism. +ChainID = '1088' +ChainType = 'metis' +# Sequencer offers absolute finality +FinalityDepth = 10 +FinalityTagEnabled = true +MinIncomingConfirmations = 1 +NoNewHeadsThreshold = '0' +OCR.ContractConfirmations = 1 + +[GasEstimator] +Mode = 'SuggestedPrice' +# Metis uses the SuggestedPrice estimator; we don't want to place any limits on the minimum gas price +PriceMin = '0' + +[GasEstimator.BlockHistory] +# Force an error if someone enables the estimator by accident; we never want to run the block history estimator on metisaa +BlockHistorySize = 0 + +[NodePool] +SyncThreshold = 10 diff --git a/ccip/config/evm/Metis_Sepolia.toml b/ccip/config/evm/Metis_Sepolia.toml new file mode 100644 index 0000000000..4ff4056c75 --- /dev/null +++ b/ccip/config/evm/Metis_Sepolia.toml @@ -0,0 +1,17 @@ +ChainID = '59902' +ChainType = 'optimismBedrock' +FinalityDepth = 10 +FinalityTagEnabled = true +MinIncomingConfirmations = 1 +NoNewHeadsThreshold = '0' +OCR.ContractConfirmations = 1 + +[GasEstimator] +Mode = 'SuggestedPrice' +PriceMin = '0' + +[GasEstimator.BlockHistory] +BlockHistorySize = 0 + +[NodePool] +SyncThreshold = 10 diff --git a/ccip/config/evm/Mode_Mainnet.toml b/ccip/config/evm/Mode_Mainnet.toml new file mode 100644 index 0000000000..69a8e93fec --- /dev/null +++ b/ccip/config/evm/Mode_Mainnet.toml @@ -0,0 +1,30 @@ +ChainID = '34443' +FinalityDepth = 200 +FinalityTagEnabled = true +ChainType = 'optimismBedrock' + +[GasEstimator] +EIP1559DynamicFees = true +BumpThreshold = 60 +BumpPercent = 20 +BumpMin = '100 wei' +PriceMax = '120 gwei' +LimitDefault = 8000000 +FeeCapDefault = '120 gwei' + +[GasEstimator.BlockHistory] +# Default is 24, which leads to bumpy gas prices. In CCIP +# we want to smooth out the gas prices, so we increase the sample size. +BlockHistorySize = 200 +# The formula for FeeCap is (current block base fee * (1.125 ^ EIP1559FeeCapBufferBlocks) + tipcap) +# where tipcap is managed by the block history estimators. In the context of CCIP, +# the gas price is relayed to other changes for quotes so we want accurate/avg not pessimistic values. +# So we set this to zero so FeeCap = baseFee + tipcap. +EIP1559FeeCapBufferBlocks = 0 + +[HeadTracker] +HistoryDepth = 300 + +[NodePool] +PollFailureThreshold = 2 +PollInterval = '3s' diff --git a/ccip/config/evm/Mode_Sepolia.toml b/ccip/config/evm/Mode_Sepolia.toml new file mode 100644 index 0000000000..f7398869be --- /dev/null +++ b/ccip/config/evm/Mode_Sepolia.toml @@ -0,0 +1,30 @@ +ChainID = '919' +FinalityDepth = 200 +FinalityTagEnabled = true +ChainType = 'optimismBedrock' + +[GasEstimator] +EIP1559DynamicFees = true +BumpThreshold = 60 +BumpPercent = 20 +BumpMin = '100 wei' +PriceMax = '120 gwei' +LimitDefault = 8000000 +FeeCapDefault = '120 gwei' + +[GasEstimator.BlockHistory] +# Default is 24, which leads to bumpy gas prices. In CCIP +# we want to smooth out the gas prices, so we increase the sample size. +BlockHistorySize = 200 +# The formula for FeeCap is (current block base fee * (1.125 ^ EIP1559FeeCapBufferBlocks) + tipcap) +# where tipcap is managed by the block history estimators. In the context of CCIP, +# the gas price is relayed to other changes for quotes so we want accurate/avg not pessimistic values. +# So we set this to zero so FeeCap = baseFee + tipcap. +EIP1559FeeCapBufferBlocks = 0 + +[HeadTracker] +HistoryDepth = 300 + +[NodePool] +PollFailureThreshold = 2 +PollInterval = '3s' diff --git a/ccip/config/evm/OKX_Mainnet.toml b/ccip/config/evm/OKX_Mainnet.toml new file mode 100644 index 0000000000..d0b26ede2e --- /dev/null +++ b/ccip/config/evm/OKX_Mainnet.toml @@ -0,0 +1 @@ +ChainID = '66' diff --git a/ccip/config/evm/OKX_Testnet.toml b/ccip/config/evm/OKX_Testnet.toml new file mode 100644 index 0000000000..2587f010b1 --- /dev/null +++ b/ccip/config/evm/OKX_Testnet.toml @@ -0,0 +1 @@ +ChainID = '65' diff --git a/ccip/config/evm/Optimism_Mainnet.toml b/ccip/config/evm/Optimism_Mainnet.toml new file mode 100644 index 0000000000..b0f56a49d9 --- /dev/null +++ b/ccip/config/evm/Optimism_Mainnet.toml @@ -0,0 +1,32 @@ +ChainID = '10' +ChainType = 'optimismBedrock' +FinalityDepth = 200 +FinalityTagEnabled = true +LinkContractAddress = '0x350a791Bfc2C21F9Ed5d10980Dad2e2638ffa7f6' +LogPollInterval = '2s' +NoNewHeadsThreshold = '40s' +MinIncomingConfirmations = 1 +NoNewFinalizedHeadsThreshold = '13m' + +[GasEstimator] +EIP1559DynamicFees = true +PriceMin = '1 wei' +BumpMin = '100 wei' + +[GasEstimator.BlockHistory] +BlockHistorySize = 24 + +[Transactions] +ResendAfterThreshold = '30s' + +[HeadTracker] +HistoryDepth = 300 + +[NodePool] +SyncThreshold = 10 + +[OCR] +ContractConfirmations = 1 + +[OCR2.Automation] +GasLimit = 6500000 diff --git a/ccip/config/evm/Optimism_Sepolia.toml b/ccip/config/evm/Optimism_Sepolia.toml new file mode 100644 index 0000000000..1c71aa5dd8 --- /dev/null +++ b/ccip/config/evm/Optimism_Sepolia.toml @@ -0,0 +1,31 @@ +ChainID = '11155420' +ChainType = 'optimismBedrock' +FinalityDepth = 200 +FinalityTagEnabled = true +LogPollInterval = '2s' +NoNewHeadsThreshold = '40s' +MinIncomingConfirmations = 1 +NoNewFinalizedHeadsThreshold = '15m' + +[GasEstimator] +EIP1559DynamicFees = true +PriceMin = '1 wei' +BumpMin = '100 wei' + +[GasEstimator.BlockHistory] +BlockHistorySize = 60 + +[Transactions] +ResendAfterThreshold = '30s' + +[HeadTracker] +HistoryDepth = 300 + +[NodePool] +SyncThreshold = 10 + +[OCR] +ContractConfirmations = 1 + +[OCR2.Automation] +GasLimit = 6500000 diff --git a/ccip/config/evm/Polygon_Amoy.toml b/ccip/config/evm/Polygon_Amoy.toml new file mode 100644 index 0000000000..b05b3053a8 --- /dev/null +++ b/ccip/config/evm/Polygon_Amoy.toml @@ -0,0 +1,28 @@ +ChainID = '80002' +FinalityDepth = 500 +LogPollInterval = '1s' +MinIncomingConfirmations = 5 +NoNewHeadsThreshold = '30s' +RPCBlockQueryDelay = 10 +RPCDefaultBatchSize = 100 +NoNewFinalizedHeadsThreshold = '12m' + +[Transactions] +MaxQueued = 5000 + +[GasEstimator] +EIP1559DynamicFees = true +PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' +PriceDefault = '25 gwei' +PriceMin = '25 gwei' +BumpMin = '20 gwei' +BumpThreshold = 5 + +[GasEstimator.BlockHistory] +BlockHistorySize = 24 + +[HeadTracker] +HistoryDepth = 2000 + +[NodePool] +SyncThreshold = 10 diff --git a/ccip/config/evm/Polygon_Mainnet.toml b/ccip/config/evm/Polygon_Mainnet.toml new file mode 100644 index 0000000000..bf605cab3c --- /dev/null +++ b/ccip/config/evm/Polygon_Mainnet.toml @@ -0,0 +1,38 @@ +# Polygon has a 1s block time and looser finality guarantees than ethereum. +ChainID = '137' +# It is quite common to see re-orgs on polygon go several hundred blocks deep. See: https://polygonscan.com/blocks_forked +FinalityDepth = 500 +FinalityTagEnabled = true +LinkContractAddress = '0xb0897686c545045aFc77CF20eC7A532E3120E0F1' +LogPollInterval = '1s' +MinIncomingConfirmations = 5 +NoNewHeadsThreshold = '30s' +# Must be set to something large here because Polygon has so many re-orgs that otherwise we are constantly refetching +RPCBlockQueryDelay = 10 +RPCDefaultBatchSize = 100 +NoNewFinalizedHeadsThreshold = '6m' + +[Transactions] +# Matic nodes under high mempool pressure are liable to drop txes, we need to ensure we keep sending them +# Since re-orgs on Polygon can be so large, we need a large safety buffer to allow time for the queue to clear down before we start dropping transactions +MaxQueued = 5000 + +[GasEstimator] +# Many Polygon RPC providers set a minimum of 30 GWei on mainnet to prevent spam +PriceDefault = '30 gwei' +PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' +# Many Polygon RPC providers set a minimum of 30 GWei on mainnet to prevent spam +PriceMin = '30 gwei' +BumpMin = '20 gwei' +# 10s delay since feeds update every minute in volatile situations +BumpThreshold = 5 + +[GasEstimator.BlockHistory] +BlockHistorySize = 24 + +[HeadTracker] +# Polygon suffers from a tremendous number of re-orgs, we need to set this to something very large to be conservative enough +HistoryDepth = 2000 + +[NodePool] +SyncThreshold = 10 diff --git a/ccip/config/evm/Polygon_Zkevm_Mainnet.toml b/ccip/config/evm/Polygon_Zkevm_Mainnet.toml new file mode 100644 index 0000000000..79e0cb0fce --- /dev/null +++ b/ccip/config/evm/Polygon_Zkevm_Mainnet.toml @@ -0,0 +1,26 @@ +ChainID = '1101' +ChainType = 'zkevm' +FinalityDepth = 500 +NoNewHeadsThreshold = '6m' +MinIncomingConfirmations = 1 +LogPollInterval = '30s' +RPCBlockQueryDelay = 15 +RPCDefaultBatchSize = 100 + +[OCR] +ContractConfirmations = 1 + +[Transactions] +ResendAfterThreshold = '3m' + +[GasEstimator] +PriceMin = '100 mwei' +BumpPercent = 40 +BumpMin = '100 mwei' + +[GasEstimator.BlockHistory] +BlockHistorySize = 12 + +[HeadTracker] +# Polygon suffers from a tremendous number of re-orgs, we need to set this to something very large to be conservative enough +HistoryDepth = 2000 diff --git a/ccip/config/evm/Scroll_Mainnet.toml b/ccip/config/evm/Scroll_Mainnet.toml new file mode 100644 index 0000000000..4a887b504d --- /dev/null +++ b/ccip/config/evm/Scroll_Mainnet.toml @@ -0,0 +1,22 @@ +ChainID = '534352' +FinalityDepth = 10 +FinalityTagEnabled = true +ChainType = 'scroll' +LogPollInterval = '5s' +MinIncomingConfirmations = 1 +# Scroll only emits blocks when a new tx is received, so this method of liveness detection is not useful +NoNewHeadsThreshold = '0' + +[GasEstimator] +EIP1559DynamicFees = true +PriceMin = '1 wei' +BumpMin = '1 gwei' + +[GasEstimator.BlockHistory] +BlockHistorySize = 24 + +[HeadTracker] +HistoryDepth = 50 + +[OCR] +ContractConfirmations = 1 diff --git a/ccip/config/evm/Scroll_Sepolia.toml b/ccip/config/evm/Scroll_Sepolia.toml new file mode 100644 index 0000000000..b2e1cfbd73 --- /dev/null +++ b/ccip/config/evm/Scroll_Sepolia.toml @@ -0,0 +1,22 @@ +ChainID = '534351' +FinalityDepth = 10 +FinalityTagEnabled = true +ChainType = 'scroll' +LogPollInterval = '5s' +MinIncomingConfirmations = 1 +# Scroll only emits blocks when a new tx is received, so this method of liveness detection is not useful +NoNewHeadsThreshold = '0' + +[GasEstimator] +EIP1559DynamicFees = true +PriceMin = '1 wei' +BumpMin = '1 gwei' + +[GasEstimator.BlockHistory] +BlockHistorySize = 24 + +[HeadTracker] +HistoryDepth = 50 + +[OCR] +ContractConfirmations = 1 diff --git a/ccip/config/evm/Simulated.toml b/ccip/config/evm/Simulated.toml new file mode 100644 index 0000000000..52e78c94ed --- /dev/null +++ b/ccip/config/evm/Simulated.toml @@ -0,0 +1,24 @@ +ChainID = '1337' +FinalityDepth = 10 +MinIncomingConfirmations = 1 +MinContractPayment = '100' +NoNewHeadsThreshold = '0s' + +[Transactions] +ReaperThreshold = '0s' +ResendAfterThreshold = '0s' + +[GasEstimator] +Mode = 'FixedPrice' +PriceMin = '0' +BumpThreshold = 0 +FeeCapDefault = '100 micro' +PriceMax = '100 micro' + +[HeadTracker] +HistoryDepth = 10 +MaxBufferSize = 100 +SamplingInterval = '0s' + +[OCR] +ContractConfirmations = 1 diff --git a/ccip/config/evm/WeMix_Mainnet.toml b/ccip/config/evm/WeMix_Mainnet.toml new file mode 100644 index 0000000000..7d3fcc6bc2 --- /dev/null +++ b/ccip/config/evm/WeMix_Mainnet.toml @@ -0,0 +1,16 @@ +ChainID = '1111' +ChainType = 'wemix' +FinalityDepth = 1 +FinalityTagEnabled = true +MinIncomingConfirmations = 1 +# WeMix emits a block every 1 second, regardless of transactions +LogPollInterval = '3s' +NoNewHeadsThreshold = '30s' +NoNewFinalizedHeadsThreshold = '40s' + +[OCR] +ContractConfirmations = 1 + +[GasEstimator] +EIP1559DynamicFees = true +TipCapDefault = '100 gwei' diff --git a/ccip/config/evm/WeMix_Testnet.toml b/ccip/config/evm/WeMix_Testnet.toml new file mode 100644 index 0000000000..5775097967 --- /dev/null +++ b/ccip/config/evm/WeMix_Testnet.toml @@ -0,0 +1,19 @@ +ChainID = '1112' +ChainType = 'wemix' +FinalityDepth = 1 +FinalityTagEnabled = true +MinIncomingConfirmations = 1 +# WeMix emits a block every 1 second, regardless of transactions +LogPollInterval = '3s' +NoNewHeadsThreshold = '30s' +NoNewFinalizedHeadsThreshold = '40s' + +[OCR] +ContractConfirmations = 1 + +[GasEstimator] +EIP1559DynamicFees = true +TipCapDefault = '100 gwei' + +[HeadTracker] +FinalityTagBypass = false diff --git a/ccip/config/evm/XLayer_Mainnet.toml b/ccip/config/evm/XLayer_Mainnet.toml new file mode 100644 index 0000000000..4096a4db24 --- /dev/null +++ b/ccip/config/evm/XLayer_Mainnet.toml @@ -0,0 +1,25 @@ +ChainID = '196' +ChainType = 'xlayer' +FinalityDepth = 500 +NoNewHeadsThreshold = '6m' +MinIncomingConfirmations = 1 +LogPollInterval = '30s' +RPCBlockQueryDelay = 15 +RPCDefaultBatchSize = 100 + +[OCR] +ContractConfirmations = 1 + +[Transactions] +ResendAfterThreshold = '3m' + +[GasEstimator] +PriceMin = '100 mwei' +BumpPercent = 40 +BumpMin = '100 mwei' + +[GasEstimator.BlockHistory] +BlockHistorySize = 12 + +[HeadTracker] +HistoryDepth = 2000 diff --git a/ccip/config/evm/XLayer_Sepolia.toml b/ccip/config/evm/XLayer_Sepolia.toml new file mode 100644 index 0000000000..62e2c1e8ad --- /dev/null +++ b/ccip/config/evm/XLayer_Sepolia.toml @@ -0,0 +1,25 @@ +ChainID = '195' +ChainType = 'xlayer' +FinalityDepth = 500 +NoNewHeadsThreshold = '12m' +MinIncomingConfirmations = 1 +LogPollInterval = '30s' +RPCBlockQueryDelay = 15 +RPCDefaultBatchSize = 100 + +[OCR] +ContractConfirmations = 1 + +[Transactions] +ResendAfterThreshold = '3m' + +[GasEstimator] +PriceMin = '1 mwei' +BumpPercent = 40 +BumpMin = '20 mwei' + +[GasEstimator.BlockHistory] +BlockHistorySize = 12 + +[HeadTracker] +HistoryDepth = 2000 diff --git a/ccip/config/evm/zkSync_Mainnet.toml b/ccip/config/evm/zkSync_Mainnet.toml new file mode 100644 index 0000000000..a434cd3815 --- /dev/null +++ b/ccip/config/evm/zkSync_Mainnet.toml @@ -0,0 +1,28 @@ +ChainID = '324' +ChainType = 'zksync' +# 1200block ~ 20min concurrent with the l1_committed tag +FinalityDepth = 1200 +# block rate is ~2-5sec, so this ensures blocks are polled correctly +LogPollInterval = '5s' +# sufficient time for RPC to be labelled out of sync, since blockRate is pretty fast +NoNewHeadsThreshold = '1m' + +[GasEstimator] +# no EIP1559 to ensure our estimator doesnot estimate gas with MaxPriorityFee which will break minFunding requirement +EIP1559DynamicFees = false +# high LimitDefault for worst case pubdata bytes with BatchGasLimit reduced to 4M in OCR2Config +LimitDefault = 2_500_000_000 +FeeCapDefault = '500 mwei' +PriceDefault = '25 mwei' +# p999 value for gasPrice based on historical data +PriceMax = '500 mwei' +# avg gasPrices are at 0.025 gwei +PriceMin = '25 mwei' + +[GasEstimator.BlockHistory] +# increasing this to smooth out gas estimation +BlockHistorySize = 200 + +[HeadTracker] +# tracks top N blocks to keep in heads database table. Should store atleast the same # of blocks as finalityDepth +HistoryDepth = 1500 \ No newline at end of file diff --git a/ccip/config/evm/zkSync_Sepolia.toml b/ccip/config/evm/zkSync_Sepolia.toml new file mode 100644 index 0000000000..f3bc594886 --- /dev/null +++ b/ccip/config/evm/zkSync_Sepolia.toml @@ -0,0 +1,28 @@ +ChainID = '300' +ChainType = 'zksync' +# 200block ~ 20min concurrent with the l1_committed tag +FinalityDepth = 200 +# block rate is ~2-5sec, so this ensures blocks are polled correctly +LogPollInterval = '5s' +# sufficient time for RPC to be labelled out of sync, since blockRate is pretty fast +NoNewHeadsThreshold = '1m' + +[GasEstimator] +# no EIP1559 to ensure our estimator doesnot estimate gas with MaxPriorityFee which will break minFunding requirement +EIP1559DynamicFees = false +# high LimitDefault for worst case pubdata bytes with BatchGasLimit reduced to 4M in OCR2Config +LimitDefault = 2_500_000_000 +FeeCapDefault = '500 mwei' +PriceDefault = '25 mwei' +# p999 value for gasPrice based on historical data +PriceMax = '500 mwei' +# avg gasPrices are at 0.025 gwei +PriceMin = '25 mwei' + +[GasEstimator.BlockHistory] +# increasing this to smooth out gas estimation +BlockHistorySize = 200 + +[HeadTracker] +# tracks top N blocks to keep in heads database table. Should store atleast the same # of blocks as finalityDepth +HistoryDepth = 250 \ No newline at end of file diff --git a/common/client/node.go b/common/client/node.go index d6543c772a..1f55e69cac 100644 --- a/common/client/node.go +++ b/common/client/node.go @@ -45,6 +45,7 @@ type NodeConfig interface { FinalizedBlockPollInterval() time.Duration EnforceRepeatableRead() bool DeathDeclarationDelay() time.Duration + NewHeadsPollInterval() time.Duration } type ChainConfig interface { diff --git a/common/client/node_test.go b/common/client/node_test.go index 3b971e8490..66bb50fc94 100644 --- a/common/client/node_test.go +++ b/common/client/node_test.go @@ -20,6 +20,11 @@ type testNodeConfig struct { enforceRepeatableRead bool finalizedBlockPollInterval time.Duration deathDeclarationDelay time.Duration + newHeadsPollInterval time.Duration +} + +func (n testNodeConfig) NewHeadsPollInterval() time.Duration { + return n.newHeadsPollInterval } func (n testNodeConfig) PollFailureThreshold() uint32 { diff --git a/common/client/poller.go b/common/client/poller.go index d6080722c5..eeb6c3af57 100644 --- a/common/client/poller.go +++ b/common/client/poller.go @@ -2,7 +2,6 @@ package client import ( "context" - "sync" "time" "github.com/smartcontractkit/chainlink-common/pkg/logger" @@ -15,83 +14,80 @@ import ( // and delivers the result to a channel. It is used by multinode to poll // for new heads and implements the Subscription interface. type Poller[T any] struct { - services.StateMachine + services.Service + eng *services.Engine + pollingInterval time.Duration pollingFunc func(ctx context.Context) (T, error) pollingTimeout time.Duration - logger logger.Logger channel chan<- T errCh chan error - - stopCh services.StopChan - wg sync.WaitGroup } // NewPoller creates a new Poller instance and returns a channel to receive the polled data func NewPoller[ T any, -](pollingInterval time.Duration, pollingFunc func(ctx context.Context) (T, error), pollingTimeout time.Duration, logger logger.Logger) (Poller[T], <-chan T) { +](pollingInterval time.Duration, pollingFunc func(ctx context.Context) (T, error), pollingTimeout time.Duration, lggr logger.Logger) (Poller[T], <-chan T) { channel := make(chan T) - return Poller[T]{ + p := Poller[T]{ pollingInterval: pollingInterval, pollingFunc: pollingFunc, pollingTimeout: pollingTimeout, channel: channel, - logger: logger, errCh: make(chan error), - stopCh: make(chan struct{}), - }, channel + } + p.Service, p.eng = services.Config{ + Name: "Poller", + Start: p.start, + Close: p.close, + }.NewServiceEngine(lggr) + return p, channel } var _ types.Subscription = &Poller[any]{} -func (p *Poller[T]) Start() error { - return p.StartOnce("Poller", func() error { - p.wg.Add(1) - go p.pollingLoop() - return nil - }) +func (p *Poller[T]) start(ctx context.Context) error { + p.eng.Go(p.pollingLoop) + return nil } // Unsubscribe cancels the sending of events to the data channel func (p *Poller[T]) Unsubscribe() { - _ = p.StopOnce("Poller", func() error { - close(p.stopCh) - p.wg.Wait() - close(p.errCh) - close(p.channel) - return nil - }) + _ = p.Close() +} + +func (p *Poller[T]) close() error { + close(p.errCh) + close(p.channel) + return nil } func (p *Poller[T]) Err() <-chan error { return p.errCh } -func (p *Poller[T]) pollingLoop() { - defer p.wg.Done() - +func (p *Poller[T]) pollingLoop(ctx context.Context) { ticker := time.NewTicker(p.pollingInterval) defer ticker.Stop() for { select { - case <-p.stopCh: + case <-ctx.Done(): return case <-ticker.C: // Set polling timeout - pollingCtx, cancelPolling := p.stopCh.CtxCancel(context.WithTimeout(context.Background(), p.pollingTimeout)) + pollingCtx, cancelPolling := context.WithTimeout(ctx, p.pollingTimeout) // Execute polling function result, err := p.pollingFunc(pollingCtx) cancelPolling() if err != nil { - p.logger.Warnf("polling error: %v", err) + p.eng.Warnf("polling error: %v", err) continue } // Send result to channel or block if channel is full select { case p.channel <- result: - case <-p.stopCh: + case <-ctx.Done(): return } } diff --git a/common/client/poller_test.go b/common/client/poller_test.go index 91af579307..930b101751 100644 --- a/common/client/poller_test.go +++ b/common/client/poller_test.go @@ -20,20 +20,22 @@ func Test_Poller(t *testing.T) { lggr := logger.Test(t) t.Run("Test multiple start", func(t *testing.T) { + ctx := tests.Context(t) pollFunc := func(ctx context.Context) (Head, error) { return nil, nil } poller, _ := NewPoller[Head](time.Millisecond, pollFunc, time.Second, lggr) - err := poller.Start() + err := poller.Start(ctx) require.NoError(t, err) - err = poller.Start() + err = poller.Start(ctx) require.Error(t, err) poller.Unsubscribe() }) t.Run("Test polling for heads", func(t *testing.T) { + ctx := tests.Context(t) // Mock polling function that returns a new value every time it's called var pollNumber int pollLock := sync.Mutex{} @@ -50,7 +52,7 @@ func Test_Poller(t *testing.T) { // Create poller and start to receive data poller, channel := NewPoller[Head](time.Millisecond, pollFunc, time.Second, lggr) - require.NoError(t, poller.Start()) + require.NoError(t, poller.Start(ctx)) defer poller.Unsubscribe() // Receive updates from the poller @@ -63,6 +65,7 @@ func Test_Poller(t *testing.T) { }) t.Run("Test polling errors", func(t *testing.T) { + ctx := tests.Context(t) // Mock polling function that returns an error var pollNumber int pollLock := sync.Mutex{} @@ -77,7 +80,7 @@ func Test_Poller(t *testing.T) { // Create poller and subscribe to receive data poller, _ := NewPoller[Head](time.Millisecond, pollFunc, time.Second, olggr) - require.NoError(t, poller.Start()) + require.NoError(t, poller.Start(ctx)) defer poller.Unsubscribe() // Ensure that all errors were logged as expected @@ -94,6 +97,7 @@ func Test_Poller(t *testing.T) { }) t.Run("Test polling timeout", func(t *testing.T) { + ctx := tests.Context(t) pollFunc := func(ctx context.Context) (Head, error) { if <-ctx.Done(); true { return nil, ctx.Err() @@ -108,7 +112,7 @@ func Test_Poller(t *testing.T) { // Create poller and subscribe to receive data poller, _ := NewPoller[Head](time.Millisecond, pollFunc, pollingTimeout, olggr) - require.NoError(t, poller.Start()) + require.NoError(t, poller.Start(ctx)) defer poller.Unsubscribe() // Ensure that timeout errors were logged as expected @@ -119,6 +123,7 @@ func Test_Poller(t *testing.T) { }) t.Run("Test unsubscribe during polling", func(t *testing.T) { + ctx := tests.Context(t) wait := make(chan struct{}) closeOnce := sync.OnceFunc(func() { close(wait) }) pollFunc := func(ctx context.Context) (Head, error) { @@ -137,7 +142,7 @@ func Test_Poller(t *testing.T) { // Create poller and subscribe to receive data poller, _ := NewPoller[Head](time.Millisecond, pollFunc, pollingTimeout, olggr) - require.NoError(t, poller.Start()) + require.NoError(t, poller.Start(ctx)) // Unsubscribe while blocked in polling function <-wait @@ -167,8 +172,9 @@ func Test_Poller_Unsubscribe(t *testing.T) { } t.Run("Test multiple unsubscribe", func(t *testing.T) { + ctx := tests.Context(t) poller, channel := NewPoller[Head](time.Millisecond, pollFunc, time.Second, lggr) - err := poller.Start() + err := poller.Start(ctx) require.NoError(t, err) <-channel @@ -177,8 +183,9 @@ func Test_Poller_Unsubscribe(t *testing.T) { }) t.Run("Read channel after unsubscribe", func(t *testing.T) { + ctx := tests.Context(t) poller, channel := NewPoller[Head](time.Millisecond, pollFunc, time.Second, lggr) - err := poller.Start() + err := poller.Start(ctx) require.NoError(t, err) poller.Unsubscribe() diff --git a/common/headtracker/head_broadcaster.go b/common/headtracker/head_broadcaster.go index 7edcccfccb..c81c61141f 100644 --- a/common/headtracker/head_broadcaster.go +++ b/common/headtracker/head_broadcaster.go @@ -42,13 +42,12 @@ type HeadBroadcaster[H types.Head[BLOCK_HASH], BLOCK_HASH types.Hashable] interf } type headBroadcaster[H types.Head[BLOCK_HASH], BLOCK_HASH types.Hashable] struct { - services.StateMachine - logger logger.Logger + services.Service + eng *services.Engine + callbacks callbackSet[H, BLOCK_HASH] mailbox *mailbox.Mailbox[H] mutex sync.Mutex - chClose services.StopChan - wgDone sync.WaitGroup latest H lastCallbackID int } @@ -60,41 +59,29 @@ func NewHeadBroadcaster[ ]( lggr logger.Logger, ) HeadBroadcaster[H, BLOCK_HASH] { - return &headBroadcaster[H, BLOCK_HASH]{ - logger: logger.Named(lggr, "HeadBroadcaster"), + hb := &headBroadcaster[H, BLOCK_HASH]{ callbacks: make(callbackSet[H, BLOCK_HASH]), mailbox: mailbox.NewSingle[H](), - chClose: make(chan struct{}), } + hb.Service, hb.eng = services.Config{ + Name: "HeadBroadcaster", + Start: hb.start, + Close: hb.close, + }.NewServiceEngine(lggr) + return hb } -func (hb *headBroadcaster[H, BLOCK_HASH]) Start(context.Context) error { - return hb.StartOnce("HeadBroadcaster", func() error { - hb.wgDone.Add(1) - go hb.run() - return nil - }) -} - -func (hb *headBroadcaster[H, BLOCK_HASH]) Close() error { - return hb.StopOnce("HeadBroadcaster", func() error { - hb.mutex.Lock() - // clear all callbacks - hb.callbacks = make(callbackSet[H, BLOCK_HASH]) - hb.mutex.Unlock() - - close(hb.chClose) - hb.wgDone.Wait() - return nil - }) +func (hb *headBroadcaster[H, BLOCK_HASH]) start(context.Context) error { + hb.eng.Go(hb.run) + return nil } -func (hb *headBroadcaster[H, BLOCK_HASH]) Name() string { - return hb.logger.Name() -} - -func (hb *headBroadcaster[H, BLOCK_HASH]) HealthReport() map[string]error { - return map[string]error{hb.Name(): hb.Healthy()} +func (hb *headBroadcaster[H, BLOCK_HASH]) close() error { + hb.mutex.Lock() + // clear all callbacks + hb.callbacks = make(callbackSet[H, BLOCK_HASH]) + hb.mutex.Unlock() + return nil } func (hb *headBroadcaster[H, BLOCK_HASH]) BroadcastNewLongestChain(head H) { @@ -121,15 +108,13 @@ func (hb *headBroadcaster[H, BLOCK_HASH]) Subscribe(callback HeadTrackable[H, BL return } -func (hb *headBroadcaster[H, BLOCK_HASH]) run() { - defer hb.wgDone.Done() - +func (hb *headBroadcaster[H, BLOCK_HASH]) run(ctx context.Context) { for { select { - case <-hb.chClose: + case <-ctx.Done(): return case <-hb.mailbox.Notify(): - hb.executeCallbacks() + hb.executeCallbacks(ctx) } } } @@ -137,10 +122,10 @@ func (hb *headBroadcaster[H, BLOCK_HASH]) run() { // DEV: the head relayer makes no promises about head delivery! Subscribing // Jobs should expect to the relayer to skip heads if there is a large number of listeners // and all callbacks cannot be completed in the allotted time. -func (hb *headBroadcaster[H, BLOCK_HASH]) executeCallbacks() { +func (hb *headBroadcaster[H, BLOCK_HASH]) executeCallbacks(ctx context.Context) { head, exists := hb.mailbox.Retrieve() if !exists { - hb.logger.Info("No head to retrieve. It might have been skipped") + hb.eng.Info("No head to retrieve. It might have been skipped") return } @@ -149,7 +134,7 @@ func (hb *headBroadcaster[H, BLOCK_HASH]) executeCallbacks() { hb.latest = head hb.mutex.Unlock() - hb.logger.Debugw("Initiating callbacks", + hb.eng.Debugw("Initiating callbacks", "headNum", head.BlockNumber(), "numCallbacks", len(callbacks), ) @@ -157,9 +142,6 @@ func (hb *headBroadcaster[H, BLOCK_HASH]) executeCallbacks() { wg := sync.WaitGroup{} wg.Add(len(callbacks)) - ctx, cancel := hb.chClose.NewCtx() - defer cancel() - for _, callback := range callbacks { go func(trackable HeadTrackable[H, BLOCK_HASH]) { defer wg.Done() @@ -168,7 +150,7 @@ func (hb *headBroadcaster[H, BLOCK_HASH]) executeCallbacks() { defer cancel() trackable.OnNewLongestChain(cctx, head) elapsed := time.Since(start) - hb.logger.Debugw(fmt.Sprintf("Finished callback in %s", elapsed), + hb.eng.Debugw(fmt.Sprintf("Finished callback in %s", elapsed), "callbackType", reflect.TypeOf(trackable), "blockNumber", head.BlockNumber(), "time", elapsed) }(callback) } diff --git a/common/headtracker/head_listener.go b/common/headtracker/head_listener.go index 25715b3528..d240caab3c 100644 --- a/common/headtracker/head_listener.go +++ b/common/headtracker/head_listener.go @@ -29,14 +29,15 @@ var ( }, []string{"ChainID"}) ) -// headHandler is a callback that handles incoming heads -type headHandler[H types.Head[BLOCK_HASH], BLOCK_HASH types.Hashable] func(ctx context.Context, header H) error +// HeadHandler is a callback that handles incoming heads +type HeadHandler[H types.Head[BLOCK_HASH], BLOCK_HASH types.Hashable] func(ctx context.Context, header H) error // HeadListener is a chain agnostic interface that manages connection of Client that receives heads from the blockchain node type HeadListener[H types.Head[BLOCK_HASH], BLOCK_HASH types.Hashable] interface { - // ListenForNewHeads kicks off the listen loop (not thread safe) - // done() must be executed upon leaving ListenForNewHeads() - ListenForNewHeads(onSubscribe func(), handleNewHead headHandler[H, BLOCK_HASH], done func()) + services.Service + + // ListenForNewHeads runs the listen loop (not thread safe) + ListenForNewHeads(ctx context.Context) // ReceivingHeads returns true if the listener is receiving heads (thread safe) ReceivingHeads() bool @@ -54,10 +55,13 @@ type headListener[ ID types.ID, BLOCK_HASH types.Hashable, ] struct { + services.Service + eng *services.Engine + config htrktypes.Config client htrktypes.Client[HTH, S, ID, BLOCK_HASH] - logger logger.Logger - chStop services.StopChan + onSubscription func(context.Context) + handleNewHead HeadHandler[HTH, BLOCK_HASH] chHeaders chan HTH headSubscription types.Subscription connected atomic.Bool @@ -74,38 +78,43 @@ func NewHeadListener[ lggr logger.Logger, client CLIENT, config htrktypes.Config, - chStop chan struct{}, + onSubscription func(context.Context), + handleNewHead HeadHandler[HTH, BLOCK_HASH], ) HeadListener[HTH, BLOCK_HASH] { - return &headListener[HTH, S, ID, BLOCK_HASH]{ - config: config, - client: client, - logger: logger.Named(lggr, "HeadListener"), - chStop: chStop, + hl := &headListener[HTH, S, ID, BLOCK_HASH]{ + config: config, + client: client, + onSubscription: onSubscription, + handleNewHead: handleNewHead, } + hl.Service, hl.eng = services.Config{ + Name: "HeadListener", + Start: hl.start, + }.NewServiceEngine(lggr) + return hl } -func (hl *headListener[HTH, S, ID, BLOCK_HASH]) Name() string { - return hl.logger.Name() +func (hl *headListener[HTH, S, ID, BLOCK_HASH]) start(context.Context) error { + hl.eng.Go(hl.ListenForNewHeads) + return nil } -func (hl *headListener[HTH, S, ID, BLOCK_HASH]) ListenForNewHeads(onSubscription func(), handleNewHead headHandler[HTH, BLOCK_HASH], done func()) { - defer done() +func (hl *headListener[HTH, S, ID, BLOCK_HASH]) ListenForNewHeads(ctx context.Context) { defer hl.unsubscribe() - ctx, cancel := hl.chStop.NewCtx() - defer cancel() - for { if !hl.subscribe(ctx) { break } - onSubscription() - err := hl.receiveHeaders(ctx, handleNewHead) + if hl.onSubscription != nil { + hl.onSubscription(ctx) + } + err := hl.receiveHeaders(ctx, hl.handleNewHead) if ctx.Err() != nil { break } else if err != nil { - hl.logger.Errorw("Error in new head subscription, unsubscribed", "err", err) + hl.eng.Errorw("Error in new head subscription, unsubscribed", "err", err) continue } break @@ -131,7 +140,7 @@ func (hl *headListener[HTH, S, ID, BLOCK_HASH]) HealthReport() map[string]error return map[string]error{hl.Name(): err} } -func (hl *headListener[HTH, S, ID, BLOCK_HASH]) receiveHeaders(ctx context.Context, handleNewHead headHandler[HTH, BLOCK_HASH]) error { +func (hl *headListener[HTH, S, ID, BLOCK_HASH]) receiveHeaders(ctx context.Context, handleNewHead HeadHandler[HTH, BLOCK_HASH]) error { var noHeadsAlarmC <-chan time.Time var noHeadsAlarmT *time.Ticker noHeadsAlarmDuration := hl.config.BlockEmissionIdleWarningThreshold() @@ -142,7 +151,7 @@ func (hl *headListener[HTH, S, ID, BLOCK_HASH]) receiveHeaders(ctx context.Conte for { select { - case <-hl.chStop: + case <-ctx.Done(): return nil case blockHeader, open := <-hl.chHeaders: @@ -158,13 +167,13 @@ func (hl *headListener[HTH, S, ID, BLOCK_HASH]) receiveHeaders(ctx context.Conte return errors.New("head listener: chHeaders prematurely closed") } if !blockHeader.IsValid() { - hl.logger.Error("got nil block header") + hl.eng.Error("got nil block header") continue } // Compare the chain ID of the block header to the chain ID of the client if !blockHeader.HasChainID() || blockHeader.ChainID().String() != chainId.String() { - hl.logger.Panicf("head listener for %s received block header for %s", chainId, blockHeader.ChainID()) + hl.eng.Panicf("head listener for %s received block header for %s", chainId, blockHeader.ChainID()) } promNumHeadsReceived.WithLabelValues(chainId.String()).Inc() @@ -184,7 +193,7 @@ func (hl *headListener[HTH, S, ID, BLOCK_HASH]) receiveHeaders(ctx context.Conte case <-noHeadsAlarmC: // We haven't received a head on the channel for a long time, log a warning - hl.logger.Warnf("have not received a head for %v", noHeadsAlarmDuration) + hl.eng.Warnf("have not received a head for %v", noHeadsAlarmDuration) hl.receivingHeads.Store(false) } } @@ -198,19 +207,19 @@ func (hl *headListener[HTH, S, ID, BLOCK_HASH]) subscribe(ctx context.Context) b for { hl.unsubscribe() - hl.logger.Debugf("Subscribing to new heads on chain %s", chainId.String()) + hl.eng.Debugf("Subscribing to new heads on chain %s", chainId.String()) select { - case <-hl.chStop: + case <-ctx.Done(): return false case <-time.After(subscribeRetryBackoff.Duration()): err := hl.subscribeToHead(ctx) if err != nil { promEthConnectionErrors.WithLabelValues(chainId.String()).Inc() - hl.logger.Warnw("Failed to subscribe to heads on chain", "chainID", chainId.String(), "err", err) + hl.eng.Warnw("Failed to subscribe to heads on chain", "chainID", chainId.String(), "err", err) } else { - hl.logger.Debugf("Subscribed to heads on chain %s", chainId.String()) + hl.eng.Debugf("Subscribed to heads on chain %s", chainId.String()) return true } } diff --git a/common/headtracker/head_tracker.go b/common/headtracker/head_tracker.go index afa5d931ee..8546d856b6 100644 --- a/common/headtracker/head_tracker.go +++ b/common/headtracker/head_tracker.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "math/big" - "sync" "time" "github.com/prometheus/client_golang/prometheus" @@ -51,7 +50,9 @@ type headTracker[ ID types.ID, BLOCK_HASH types.Hashable, ] struct { - services.StateMachine + services.Service + eng *services.Engine + log logger.SugaredLogger headBroadcaster HeadBroadcaster[HTH, BLOCK_HASH] headSaver HeadSaver[HTH, BLOCK_HASH] @@ -64,8 +65,6 @@ type headTracker[ backfillMB *mailbox.Mailbox[HTH] broadcastMB *mailbox.Mailbox[HTH] headListener HeadListener[HTH, BLOCK_HASH] - chStop services.StopChan - wgDone sync.WaitGroup getNilHead func() HTH } @@ -85,52 +84,52 @@ func NewHeadTracker[ mailMon *mailbox.Monitor, getNilHead func() HTH, ) HeadTracker[HTH, BLOCK_HASH] { - chStop := make(chan struct{}) - lggr = logger.Named(lggr, "HeadTracker") - return &headTracker[HTH, S, ID, BLOCK_HASH]{ + ht := &headTracker[HTH, S, ID, BLOCK_HASH]{ headBroadcaster: headBroadcaster, client: client, chainID: client.ConfiguredChainID(), config: config, htConfig: htConfig, - log: logger.Sugared(lggr), backfillMB: mailbox.NewSingle[HTH](), broadcastMB: mailbox.New[HTH](HeadsBufferSize), - chStop: chStop, - headListener: NewHeadListener[HTH, S, ID, BLOCK_HASH](lggr, client, config, chStop), headSaver: headSaver, mailMon: mailMon, getNilHead: getNilHead, } + ht.Service, ht.eng = services.Config{ + Name: "HeadTracker", + NewSubServices: func(lggr logger.Logger) []services.Service { + ht.headListener = NewHeadListener[HTH, S, ID, BLOCK_HASH](lggr, client, config, + // NOTE: Always try to start the head tracker off with whatever the + // latest head is, without waiting for the subscription to send us one. + // + // In some cases the subscription will send us the most recent head + // anyway when we connect (but we should not rely on this because it is + // not specced). If it happens this is fine, and the head will be + // ignored as a duplicate. + func(ctx context.Context) { + err := ht.handleInitialHead(ctx) + if err != nil { + ht.log.Errorw("Error handling initial head", "err", err.Error()) + } + }, ht.handleNewHead) + return []services.Service{ht.headListener} + }, + Start: ht.start, + Close: ht.close, + }.NewServiceEngine(lggr) + ht.log = logger.Sugared(ht.eng) + return ht } // Start starts HeadTracker service. -func (ht *headTracker[HTH, S, ID, BLOCK_HASH]) Start(ctx context.Context) error { - return ht.StartOnce("HeadTracker", func() error { - ht.log.Debugw("Starting HeadTracker", "chainID", ht.chainID) - // NOTE: Always try to start the head tracker off with whatever the - // latest head is, without waiting for the subscription to send us one. - // - // In some cases the subscription will send us the most recent head - // anyway when we connect (but we should not rely on this because it is - // not specced). If it happens this is fine, and the head will be - // ignored as a duplicate. - onSubscribe := func() { - err := ht.handleInitialHead(ctx) - if err != nil { - ht.log.Errorw("Error handling initial head", "err", err.Error()) - } - } +func (ht *headTracker[HTH, S, ID, BLOCK_HASH]) start(context.Context) error { + ht.eng.Go(ht.backfillLoop) + ht.eng.Go(ht.broadcastLoop) - ht.wgDone.Add(3) - go ht.headListener.ListenForNewHeads(onSubscribe, ht.handleNewHead, ht.wgDone.Done) - go ht.backfillLoop() - go ht.broadcastLoop() + ht.mailMon.Monitor(ht.broadcastMB, "HeadTracker", "Broadcast", ht.chainID.String()) - ht.mailMon.Monitor(ht.broadcastMB, "HeadTracker", "Broadcast", ht.chainID.String()) - - return nil - }) + return nil } func (ht *headTracker[HTH, S, ID, BLOCK_HASH]) handleInitialHead(ctx context.Context) error { @@ -176,23 +175,8 @@ func (ht *headTracker[HTH, S, ID, BLOCK_HASH]) handleInitialHead(ctx context.Con return nil } -// Close stops HeadTracker service. -func (ht *headTracker[HTH, S, ID, BLOCK_HASH]) Close() error { - return ht.StopOnce("HeadTracker", func() error { - close(ht.chStop) - ht.wgDone.Wait() - return ht.broadcastMB.Close() - }) -} - -func (ht *headTracker[HTH, S, ID, BLOCK_HASH]) Name() string { - return ht.log.Name() -} - -func (ht *headTracker[HTH, S, ID, BLOCK_HASH]) HealthReport() map[string]error { - report := map[string]error{ht.Name(): ht.Healthy()} - services.CopyHealth(report, ht.headListener.HealthReport()) - return report +func (ht *headTracker[HTH, S, ID, BLOCK_HASH]) close() error { + return ht.broadcastMB.Close() } func (ht *headTracker[HTH, S, ID, BLOCK_HASH]) Backfill(ctx context.Context, headWithChain HTH) (err error) { @@ -236,8 +220,7 @@ func (ht *headTracker[HTH, S, ID, BLOCK_HASH]) handleNewHead(ctx context.Context "blockDifficulty", head.BlockDifficulty(), ) - err := ht.headSaver.Save(ctx, head) - if ctx.Err() != nil { + if err := ht.headSaver.Save(ctx, head); ctx.Err() != nil { return nil } else if err != nil { return fmt.Errorf("failed to save head: %#v: %w", head, err) @@ -264,16 +247,15 @@ func (ht *headTracker[HTH, S, ID, BLOCK_HASH]) handleNewHead(ctx context.Context if prevLatestFinalized != nil && head.BlockNumber() <= prevLatestFinalized.BlockNumber() { promOldHead.WithLabelValues(ht.chainID.String()).Inc() - ht.log.Criticalf("Got very old block with number %d (highest seen was %d). This is a problem and either means a very deep re-org occurred, one of the RPC nodes has gotten far out of sync, or the chain went backwards in block numbers. This node may not function correctly without manual intervention.", head.BlockNumber(), prevHead.BlockNumber()) - ht.SvcErrBuffer.Append(errors.New("got very old block")) + err := fmt.Errorf("got very old block with number %d (highest seen was %d)", head.BlockNumber(), prevHead.BlockNumber()) + ht.log.Critical("Got very old block. Either a very deep re-org occurred, one of the RPC nodes has gotten far out of sync, or the chain went backwards in block numbers. This node may not function correctly without manual intervention.", "err", err) + ht.eng.EmitHealthErr(err) } } return nil } -func (ht *headTracker[HTH, S, ID, BLOCK_HASH]) broadcastLoop() { - defer ht.wgDone.Done() - +func (ht *headTracker[HTH, S, ID, BLOCK_HASH]) broadcastLoop(ctx context.Context) { samplingInterval := ht.htConfig.SamplingInterval() if samplingInterval > 0 { ht.log.Debugf("Head sampling is enabled - sampling interval is set to: %v", samplingInterval) @@ -281,7 +263,7 @@ func (ht *headTracker[HTH, S, ID, BLOCK_HASH]) broadcastLoop() { defer debounceHead.Stop() for { select { - case <-ht.chStop: + case <-ctx.Done(): return case <-debounceHead.C: item := ht.broadcastMB.RetrieveLatestAndClear() @@ -295,7 +277,7 @@ func (ht *headTracker[HTH, S, ID, BLOCK_HASH]) broadcastLoop() { ht.log.Info("Head sampling is disabled - callback will be called on every head") for { select { - case <-ht.chStop: + case <-ctx.Done(): return case <-ht.broadcastMB.Notify(): for { @@ -310,15 +292,10 @@ func (ht *headTracker[HTH, S, ID, BLOCK_HASH]) broadcastLoop() { } } -func (ht *headTracker[HTH, S, ID, BLOCK_HASH]) backfillLoop() { - defer ht.wgDone.Done() - - ctx, cancel := ht.chStop.NewCtx() - defer cancel() - +func (ht *headTracker[HTH, S, ID, BLOCK_HASH]) backfillLoop(ctx context.Context) { for { select { - case <-ht.chStop: + case <-ctx.Done(): return case <-ht.backfillMB.Notify(): for { diff --git a/common/txmgr/broadcaster.go b/common/txmgr/broadcaster.go index 1606f58ce0..8ecd6dbf46 100644 --- a/common/txmgr/broadcaster.go +++ b/common/txmgr/broadcaster.go @@ -558,21 +558,33 @@ func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) hand eb.sequenceTracker.GenerateNextSequence(etx.FromAddress, *etx.Sequence) return err, true case client.Underpriced: - return eb.tryAgainBumpingGas(ctx, lgr, err, etx, attempt, initialBroadcastAt, retryCount+1) + bumpedAttempt, retryable, replaceErr := eb.replaceAttemptWithBumpedGas(ctx, lgr, err, etx, attempt) + if replaceErr != nil { + return replaceErr, retryable + } + + return eb.handleInProgressTx(ctx, etx, bumpedAttempt, initialBroadcastAt, retryCount+1) case client.InsufficientFunds: - // NOTE: This bails out of the entire cycle and essentially "blocks" on - // any transaction that gets insufficient_funds. This is OK if a - // transaction with a large VALUE blocks because this always comes last - // in the processing list. - // If it blocks because of a transaction that is expensive due to large - // gas limit, we could have smaller transactions "above" it that could - // theoretically be sent, but will instead be blocked. + // NOTE: This can occur due to either insufficient funds or a gas spike + // combined with a high gas limit. Regardless of the cause, we need to obtain a new estimate, + // replace the current attempt, and retry after the backoff duration. + // The new attempt must be replaced immediately because of a database constraint. eb.SvcErrBuffer.Append(err) - fallthrough + if _, _, replaceErr := eb.replaceAttemptWithNewEstimation(ctx, lgr, etx, attempt); replaceErr != nil { + return replaceErr, true + } + return err, true case client.Retryable: return err, true case client.FeeOutOfValidRange: - return eb.tryAgainWithNewEstimation(ctx, lgr, err, etx, attempt, initialBroadcastAt) + replacementAttempt, retryable, replaceErr := eb.replaceAttemptWithNewEstimation(ctx, lgr, etx, attempt) + if replaceErr != nil { + return replaceErr, retryable + } + + lgr.Warnw("L2 rejected transaction due to incorrect fee, re-estimated and will try again", + "etxID", etx.ID, "err", err, "newGasPrice", replacementAttempt.TxFee, "newGasLimit", replacementAttempt.ChainSpecificFeeLimit) + return eb.handleInProgressTx(ctx, etx, *replacementAttempt, initialBroadcastAt, 0) case client.Unsupported: return err, false case client.ExceedsMaxFee: @@ -680,7 +692,8 @@ func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) next return etx, nil } -func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) tryAgainBumpingGas(ctx context.Context, lgr logger.Logger, txError error, etx txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], attempt txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], initialBroadcastAt time.Time, retry int) (err error, retryable bool) { +// replaceAttemptWithBumpedGas performs the replacement of the existing tx attempt with a new bumped fee attempt. +func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) replaceAttemptWithBumpedGas(ctx context.Context, lgr logger.Logger, txError error, etx txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], attempt txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) (replacedAttempt txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], retryable bool, err error) { // This log error is not applicable to Hedera since the action required would not be needed for its gas estimator if eb.chainType != hederaChainType { logger.With(lgr, @@ -694,37 +707,32 @@ func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) tryA attempt.TxFee, txError.Error(), eb.feeConfig.FeePriceDefault()) } - replacementAttempt, bumpedFee, bumpedFeeLimit, retryable, err := eb.NewBumpTxAttempt(ctx, etx, attempt, nil, lgr) + bumpedAttempt, bumpedFee, bumpedFeeLimit, retryable, err := eb.NewBumpTxAttempt(ctx, etx, attempt, nil, lgr) if err != nil { - return fmt.Errorf("tryAgainBumpFee failed: %w", err), retryable + return bumpedAttempt, retryable, err } - return eb.saveTryAgainAttempt(ctx, lgr, etx, attempt, replacementAttempt, initialBroadcastAt, bumpedFee, bumpedFeeLimit, retry) -} - -func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) tryAgainWithNewEstimation(ctx context.Context, lgr logger.Logger, txError error, etx txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], attempt txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], initialBroadcastAt time.Time) (err error, retryable bool) { - if attempt.TxType == 0x2 { - err = fmt.Errorf("re-estimation is not supported for EIP-1559 transactions. Node returned error: %v. This is a bug", txError.Error()) - logger.Sugared(eb.lggr).AssumptionViolation(err.Error()) - return err, false + if err = eb.txStore.SaveReplacementInProgressAttempt(ctx, attempt, &bumpedAttempt); err != nil { + return bumpedAttempt, true, err } - replacementAttempt, fee, feeLimit, retryable, err := eb.NewTxAttemptWithType(ctx, etx, lgr, attempt.TxType, feetypes.OptForceRefetch) + lgr.Debugw("Bumped fee on initial send", "oldFee", attempt.TxFee.String(), "newFee", bumpedFee.String(), "newFeeLimit", bumpedFeeLimit) + return bumpedAttempt, true, err +} + +// replaceAttemptWithNewEstimation performs the replacement of the existing tx attempt with a new estimated fee attempt. +func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) replaceAttemptWithNewEstimation(ctx context.Context, lgr logger.Logger, etx txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], attempt txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) (updatedAttempt *txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], retryable bool, err error) { + newEstimatedAttempt, fee, feeLimit, retryable, err := eb.NewTxAttemptWithType(ctx, etx, lgr, attempt.TxType, feetypes.OptForceRefetch) if err != nil { - return fmt.Errorf("tryAgainWithNewEstimation failed to build new attempt: %w", err), retryable + return &newEstimatedAttempt, retryable, err } - lgr.Warnw("L2 rejected transaction due to incorrect fee, re-estimated and will try again", - "etxID", etx.ID, "err", err, "newGasPrice", fee, "newGasLimit", feeLimit) - return eb.saveTryAgainAttempt(ctx, lgr, etx, attempt, replacementAttempt, initialBroadcastAt, fee, feeLimit, 0) -} - -func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) saveTryAgainAttempt(ctx context.Context, lgr logger.Logger, etx txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], attempt txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], replacementAttempt txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], initialBroadcastAt time.Time, newFee FEE, newFeeLimit uint64, retry int) (err error, retyrable bool) { - if err = eb.txStore.SaveReplacementInProgressAttempt(ctx, attempt, &replacementAttempt); err != nil { - return fmt.Errorf("tryAgainWithNewFee failed: %w", err), true + if err = eb.txStore.SaveReplacementInProgressAttempt(ctx, attempt, &newEstimatedAttempt); err != nil { + return &newEstimatedAttempt, true, err } - lgr.Debugw("Bumped fee on initial send", "oldFee", attempt.TxFee.String(), "newFee", newFee.String(), "newFeeLimit", newFeeLimit) - return eb.handleInProgressTx(ctx, etx, replacementAttempt, initialBroadcastAt, retry) + + lgr.Debugw("new estimated fee on initial send", "oldFee", attempt.TxFee.String(), "newFee", fee.String(), "newFeeLimit", feeLimit) + return &newEstimatedAttempt, true, err } func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) saveFatallyErroredTransaction(lgr logger.Logger, etx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) error { @@ -748,7 +756,7 @@ func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) save // Now we have an errored pipeline even though the tx succeeded. This case // is relatively benign and probably nobody will ever run into it in // practice, but something to be aware of. - if etx.PipelineTaskRunID.Valid && eb.resumeCallback != nil && etx.SignalCallback { + if etx.PipelineTaskRunID.Valid && eb.resumeCallback != nil && etx.SignalCallback && !etx.CallbackCompleted { err := eb.resumeCallback(ctx, etx.PipelineTaskRunID.UUID, nil, fmt.Errorf("fatal error while sending transaction: %s", etx.Error.String)) if errors.Is(err, sql.ErrNoRows) { lgr.Debugw("callback missing or already resumed", "etxID", etx.ID) diff --git a/common/txmgr/confirmer.go b/common/txmgr/confirmer.go index 0099f4a2ee..4eaa6739d5 100644 --- a/common/txmgr/confirmer.go +++ b/common/txmgr/confirmer.go @@ -2,6 +2,7 @@ package txmgr import ( "context" + "database/sql" "encoding/hex" "errors" "fmt" @@ -102,6 +103,10 @@ var ( }, []string{"chainID"}) ) +type confirmerHeadTracker[HEAD types.Head[BLOCK_HASH], BLOCK_HASH types.Hashable] interface { + LatestAndFinalizedBlock(ctx context.Context) (latest, finalized HEAD, err error) +} + // Confirmer is a broad service which performs four different tasks in sequence on every new longest chain // Step 1: Mark that all currently pending transaction attempts were broadcast before this block // Step 2: Check pending transactions for receipts @@ -133,14 +138,15 @@ type Confirmer[ ks txmgrtypes.KeyStore[ADDR, CHAIN_ID, SEQ] enabledAddresses []ADDR - mb *mailbox.Mailbox[HEAD] - stopCh services.StopChan - wg sync.WaitGroup - initSync sync.Mutex - isStarted bool - + mb *mailbox.Mailbox[HEAD] + stopCh services.StopChan + wg sync.WaitGroup + initSync sync.Mutex + isStarted bool nConsecutiveBlocksChainTooShort int isReceiptNil func(R) bool + + headTracker confirmerHeadTracker[HEAD, BLOCK_HASH] } func NewConfirmer[ @@ -164,6 +170,7 @@ func NewConfirmer[ lggr logger.Logger, isReceiptNil func(R) bool, stuckTxDetector txmgrtypes.StuckTxDetector[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], + headTracker confirmerHeadTracker[HEAD, BLOCK_HASH], ) *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE] { lggr = logger.Named(lggr, "Confirmer") return &Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]{ @@ -181,6 +188,7 @@ func NewConfirmer[ mb: mailbox.NewSingle[HEAD](), isReceiptNil: isReceiptNil, stuckTxDetector: stuckTxDetector, + headTracker: headTracker, } } @@ -297,7 +305,20 @@ func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) pro return fmt.Errorf("CheckConfirmedMissingReceipt failed: %w", err) } - if err := ec.CheckForReceipts(ctx, head.BlockNumber()); err != nil { + _, latestFinalizedHead, err := ec.headTracker.LatestAndFinalizedBlock(ctx) + if err != nil { + return fmt.Errorf("failed to retrieve latest finalized head: %w", err) + } + + if !latestFinalizedHead.IsValid() { + return fmt.Errorf("latest finalized head is not valid") + } + + if latestFinalizedHead.BlockNumber() > head.BlockNumber() { + ec.lggr.Debugw("processHead received old block", "latestFinalizedHead", latestFinalizedHead.BlockNumber(), "headNum", head.BlockNumber(), "time", time.Since(mark), "id", "confirmer") + } + + if err := ec.CheckForReceipts(ctx, head.BlockNumber(), latestFinalizedHead.BlockNumber()); err != nil { return fmt.Errorf("CheckForReceipts failed: %w", err) } @@ -318,7 +339,7 @@ func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) pro ec.lggr.Debugw("Finished RebroadcastWhereNecessary", "headNum", head.BlockNumber(), "time", time.Since(mark), "id", "confirmer") mark = time.Now() - if err := ec.EnsureConfirmedTransactionsInLongestChain(ctx, head); err != nil { + if err := ec.EnsureConfirmedTransactionsInLongestChain(ctx, head, latestFinalizedHead.BlockNumber()); err != nil { return fmt.Errorf("EnsureConfirmedTransactionsInLongestChain failed: %w", err) } @@ -395,8 +416,8 @@ func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Che return } -// CheckForReceipts finds attempts that are still pending and checks to see if a receipt is present for the given block number -func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) CheckForReceipts(ctx context.Context, blockNum int64) error { +// CheckForReceipts finds attempts that are still pending and checks to see if a receipt is present for the given block number. +func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) CheckForReceipts(ctx context.Context, blockNum int64, latestFinalizedBlockNum int64) error { attempts, err := ec.txStore.FindTxAttemptsRequiringReceiptFetch(ctx, ec.chainID) if err != nil { return fmt.Errorf("FindTxAttemptsRequiringReceiptFetch failed: %w", err) @@ -443,7 +464,7 @@ func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Che return fmt.Errorf("unable to mark txes as 'confirmed_missing_receipt': %w", err) } - if err := ec.txStore.MarkOldTxesMissingReceiptAsErrored(ctx, blockNum, ec.chainConfig.FinalityDepth(), ec.chainID); err != nil { + if err := ec.txStore.MarkOldTxesMissingReceiptAsErrored(ctx, blockNum, latestFinalizedBlockNum, ec.chainID); err != nil { return fmt.Errorf("unable to confirm buried unconfirmed txes': %w", err) } return nil @@ -494,6 +515,13 @@ func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Pro errMu.Unlock() return } + // Resume pending task runs with failure for stuck transactions + if err := ec.resumeFailedTaskRuns(ctx, tx); err != nil { + errMu.Lock() + errorList = append(errorList, fmt.Errorf("failed to resume pending task run for transaction: %w", err)) + errMu.Unlock() + return + } }(tx) } wg.Wait() @@ -564,7 +592,8 @@ func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) fet return fmt.Errorf("saveFetchedReceipts failed: %w", err) } // Save the receipts but mark the associated transactions as Fatal Error since the original transaction was purged - if err := ec.txStore.SaveFetchedReceipts(ctx, purgeReceipts, TxFatalError, ec.stuckTxDetector.StuckTxFatalError(), ec.chainID); err != nil { + stuckTxFatalErrMsg := ec.stuckTxDetector.StuckTxFatalError() + if err := ec.txStore.SaveFetchedReceipts(ctx, purgeReceipts, TxFatalError, &stuckTxFatalErrMsg, ec.chainID); err != nil { return fmt.Errorf("saveFetchedReceipts failed: %w", err) } promNumConfirmedTxs.WithLabelValues(ec.chainID.String()).Add(float64(len(receipts))) @@ -596,6 +625,24 @@ func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) sep return } +func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) resumeFailedTaskRuns(ctx context.Context, etx txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) error { + if !etx.PipelineTaskRunID.Valid || ec.resumeCallback == nil || !etx.SignalCallback || etx.CallbackCompleted { + return nil + } + err := ec.resumeCallback(ctx, etx.PipelineTaskRunID.UUID, nil, errors.New(ec.stuckTxDetector.StuckTxFatalError())) + if errors.Is(err, sql.ErrNoRows) { + ec.lggr.Debugw("callback missing or already resumed", "etxID", etx.ID) + } else if err != nil { + return fmt.Errorf("failed to resume pipeline: %w", err) + } else { + // Mark tx as having completed callback + if err = ec.txStore.UpdateTxCallbackCompleted(ctx, etx.PipelineTaskRunID.UUID, ec.chainID); err != nil { + return err + } + } + return nil +} + func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) getMinedSequenceForAddress(ctx context.Context, from ADDR) (SEQ, error) { return ec.client.SequenceAt(ctx, from, nil) } @@ -1019,22 +1066,30 @@ func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) han } } -// EnsureConfirmedTransactionsInLongestChain finds all confirmed txes up to the depth +// EnsureConfirmedTransactionsInLongestChain finds all confirmed txes up to the earliest head // of the given chain and ensures that every one has a receipt with a block hash that is // in the given chain. // // If any of the confirmed transactions does not have a receipt in the chain, it has been // re-org'd out and will be rebroadcast. -func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) EnsureConfirmedTransactionsInLongestChain(ctx context.Context, head types.Head[BLOCK_HASH]) error { - if head.ChainLength() < ec.chainConfig.FinalityDepth() { - logArgs := []interface{}{ - "chainLength", head.ChainLength(), "finalityDepth", ec.chainConfig.FinalityDepth(), - } +func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) EnsureConfirmedTransactionsInLongestChain(ctx context.Context, head types.Head[BLOCK_HASH], latestFinalizedHeadNumber int64) error { + logArgs := []interface{}{ + "chainLength", head.ChainLength(), "latestFinalizedHead number", latestFinalizedHeadNumber, + } + + if head.BlockNumber() < latestFinalizedHeadNumber { + errMsg := "current head is shorter than latest finalized head" + ec.lggr.Errorw(errMsg, append(logArgs, "head block number", head.BlockNumber())...) + return errors.New(errMsg) + } + + calculatedFinalityDepth := uint32(head.BlockNumber() - latestFinalizedHeadNumber) + if head.ChainLength() < calculatedFinalityDepth { if ec.nConsecutiveBlocksChainTooShort > logAfterNConsecutiveBlocksChainTooShort { - warnMsg := "Chain length supplied for re-org detection was shorter than FinalityDepth. Re-org protection is not working properly. This could indicate a problem with the remote RPC endpoint, a compatibility issue with a particular blockchain, a bug with this particular blockchain, heads table being truncated too early, remote node out of sync, or something else. If this happens a lot please raise a bug with the Chainlink team including a log output sample and details of the chain and RPC endpoint you are using." + warnMsg := "Chain length supplied for re-org detection was shorter than the depth from the latest head to the finalized head. Re-org protection is not working properly. This could indicate a problem with the remote RPC endpoint, a compatibility issue with a particular blockchain, a bug with this particular blockchain, heads table being truncated too early, remote node out of sync, or something else. If this happens a lot please raise a bug with the Chainlink team including a log output sample and details of the chain and RPC endpoint you are using." ec.lggr.Warnw(warnMsg, append(logArgs, "nConsecutiveBlocksChainTooShort", ec.nConsecutiveBlocksChainTooShort)...) } else { - logMsg := "Chain length supplied for re-org detection was shorter than FinalityDepth" + logMsg := "Chain length supplied for re-org detection was shorter than the depth from the latest head to the finalized head" ec.lggr.Debugw(logMsg, append(logArgs, "nConsecutiveBlocksChainTooShort", ec.nConsecutiveBlocksChainTooShort)...) } ec.nConsecutiveBlocksChainTooShort++ @@ -1093,10 +1148,11 @@ func hasReceiptInLongestChain[ } } } - if head.GetParent() == nil { + + head = head.GetParent() + if head == nil { return false } - head = head.GetParent() } } diff --git a/common/txmgr/models.go b/common/txmgr/models.go index dd121a2c7c..ca5e7d4f25 100644 --- a/common/txmgr/models.go +++ b/common/txmgr/models.go @@ -11,4 +11,5 @@ const ( TxUnconfirmed = txmgrtypes.TxState("unconfirmed") TxConfirmed = txmgrtypes.TxState("confirmed") TxConfirmedMissingReceipt = txmgrtypes.TxState("confirmed_missing_receipt") + TxFinalized = txmgrtypes.TxState("finalized") ) diff --git a/common/txmgr/reaper.go b/common/txmgr/reaper.go index 932b58f643..0c797548b1 100644 --- a/common/txmgr/reaper.go +++ b/common/txmgr/reaper.go @@ -14,7 +14,6 @@ import ( // Reaper handles periodic database cleanup for Txm type Reaper[CHAIN_ID types.ID] struct { store txmgrtypes.TxHistoryReaper[CHAIN_ID] - config txmgrtypes.ReaperChainConfig txConfig txmgrtypes.ReaperTransactionsConfig chainID CHAIN_ID log logger.Logger @@ -25,10 +24,9 @@ type Reaper[CHAIN_ID types.ID] struct { } // NewReaper instantiates a new reaper object -func NewReaper[CHAIN_ID types.ID](lggr logger.Logger, store txmgrtypes.TxHistoryReaper[CHAIN_ID], config txmgrtypes.ReaperChainConfig, txConfig txmgrtypes.ReaperTransactionsConfig, chainID CHAIN_ID) *Reaper[CHAIN_ID] { +func NewReaper[CHAIN_ID types.ID](lggr logger.Logger, store txmgrtypes.TxHistoryReaper[CHAIN_ID], txConfig txmgrtypes.ReaperTransactionsConfig, chainID CHAIN_ID) *Reaper[CHAIN_ID] { r := &Reaper[CHAIN_ID]{ store, - config, txConfig, chainID, logger.Named(lggr, "Reaper"), @@ -103,13 +101,12 @@ func (r *Reaper[CHAIN_ID]) ReapTxes(headNum int64) error { r.log.Debug("Transactions.ReaperThreshold set to 0; skipping ReapTxes") return nil } - minBlockNumberToKeep := headNum - int64(r.config.FinalityDepth()) mark := time.Now() timeThreshold := mark.Add(-threshold) - r.log.Debugw(fmt.Sprintf("reaping old txes created before %s", timeThreshold.Format(time.RFC3339)), "ageThreshold", threshold, "timeThreshold", timeThreshold, "minBlockNumberToKeep", minBlockNumberToKeep) + r.log.Debugw(fmt.Sprintf("reaping old txes created before %s", timeThreshold.Format(time.RFC3339)), "ageThreshold", threshold, "timeThreshold", timeThreshold) - if err := r.store.ReapTxHistory(ctx, minBlockNumberToKeep, timeThreshold, r.chainID); err != nil { + if err := r.store.ReapTxHistory(ctx, timeThreshold, r.chainID); err != nil { return err } diff --git a/common/txmgr/txmgr.go b/common/txmgr/txmgr.go index fc27e930c3..28d505e5e0 100644 --- a/common/txmgr/txmgr.go +++ b/common/txmgr/txmgr.go @@ -108,6 +108,7 @@ type Txm[ broadcaster *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] confirmer *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE] tracker *Tracker[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE] + finalizer txmgrtypes.Finalizer[BLOCK_HASH, HEAD] fwdMgr txmgrtypes.ForwarderManager[ADDR] txAttemptBuilder txmgrtypes.TxAttemptBuilder[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] newErrorClassifier NewErrorClassifier @@ -143,6 +144,7 @@ func NewTxm[ confirmer *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE], resender *Resender[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE], tracker *Tracker[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE], + finalizer txmgrtypes.Finalizer[BLOCK_HASH, HEAD], newErrorClassifierFunc NewErrorClassifier, ) *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE] { b := Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]{ @@ -165,13 +167,14 @@ func NewTxm[ resender: resender, tracker: tracker, newErrorClassifier: newErrorClassifierFunc, + finalizer: finalizer, } if txCfg.ResendAfterThreshold() <= 0 { b.logger.Info("Resender: Disabled") } if txCfg.ReaperThreshold() > 0 && txCfg.ReaperInterval() > 0 { - b.reaper = NewReaper[CHAIN_ID](lggr, b.txStore, cfg, txCfg, chainId) + b.reaper = NewReaper[CHAIN_ID](lggr, b.txStore, txCfg, chainId) } else { b.logger.Info("TxReaper: Disabled") } @@ -199,6 +202,10 @@ func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Start(ctx return fmt.Errorf("Txm: Tracker failed to start: %w", err) } + if err := ms.Start(ctx, b.finalizer); err != nil { + return fmt.Errorf("Txm: Finalizer failed to start: %w", err) + } + b.logger.Info("Txm starting runLoop") b.wg.Add(1) go b.runLoop() @@ -293,6 +300,7 @@ func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) HealthRepo services.CopyHealth(report, b.broadcaster.HealthReport()) services.CopyHealth(report, b.confirmer.HealthReport()) services.CopyHealth(report, b.txAttemptBuilder.HealthReport()) + services.CopyHealth(report, b.finalizer.HealthReport()) }) if b.txConfig.ForwardersEnabled() { @@ -415,6 +423,7 @@ func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) runLoop() case head := <-b.chHeads: b.confirmer.mb.Deliver(head) b.tracker.mb.Deliver(head.BlockNumber()) + b.finalizer.DeliverLatestHead(head) case reset := <-b.reset: // This check prevents the weird edge-case where you can select // into this block after chStop has already been closed and the @@ -446,6 +455,10 @@ func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) runLoop() if err != nil && (!errors.Is(err, services.ErrAlreadyStopped) || !errors.Is(err, services.ErrCannotStopUnstarted)) { b.logger.Errorw(fmt.Sprintf("Failed to Close Tracker: %v", err), "err", err) } + err = b.finalizer.Close() + if err != nil && (!errors.Is(err, services.ErrAlreadyStopped) || !errors.Is(err, services.ErrCannotStopUnstarted)) { + b.logger.Errorw(fmt.Sprintf("Failed to Close Finalizer: %v", err), "err", err) + } return case <-keysChanged: // This check prevents the weird edge-case where you can select @@ -641,12 +654,13 @@ func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) GetTransac } switch tx.State { case TxUnconfirmed, TxConfirmedMissingReceipt: - // Return unconfirmed for ConfirmedMissingReceipt since a receipt is required to determine if it is finalized - return commontypes.Unconfirmed, nil + // Return pending for ConfirmedMissingReceipt since a receipt is required to consider it as unconfirmed + return commontypes.Pending, nil case TxConfirmed: - // TODO: Check for finality and return finalized status - // Return unconfirmed if tx receipt's block is newer than the latest finalized block + // Return unconfirmed for confirmed transactions because they are not yet finalized return commontypes.Unconfirmed, nil + case TxFinalized: + return commontypes.Finalized, nil case TxFatalError: // Use an ErrorClassifier to determine if the transaction is considered Fatal txErr := b.newErrorClassifier(tx.GetError()) diff --git a/common/txmgr/types/config.go b/common/txmgr/types/config.go index 4d9af5f067..8b11a45d11 100644 --- a/common/txmgr/types/config.go +++ b/common/txmgr/types/config.go @@ -5,7 +5,6 @@ import "time" type TransactionManagerChainConfig interface { BroadcasterChainConfig ConfirmerChainConfig - ReaperChainConfig } type TransactionManagerFeeConfig interface { @@ -74,11 +73,6 @@ type ResenderTransactionsConfig interface { MaxInFlight() uint32 } -// ReaperConfig is the config subset used by the reaper -type ReaperChainConfig interface { - FinalityDepth() uint32 -} - type ReaperTransactionsConfig interface { ReaperInterval() time.Duration ReaperThreshold() time.Duration diff --git a/common/txmgr/types/finalizer.go b/common/txmgr/types/finalizer.go new file mode 100644 index 0000000000..be3c897d0e --- /dev/null +++ b/common/txmgr/types/finalizer.go @@ -0,0 +1,12 @@ +package types + +import ( + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink/v2/common/types" +) + +type Finalizer[BLOCK_HASH types.Hashable, HEAD types.Head[BLOCK_HASH]] interface { + // interfaces for running the underlying estimator + services.Service + DeliverLatestHead(head HEAD) bool +} diff --git a/common/txmgr/types/mocks/reaper_chain_config.go b/common/txmgr/types/mocks/reaper_chain_config.go deleted file mode 100644 index 0531b07170..0000000000 --- a/common/txmgr/types/mocks/reaper_chain_config.go +++ /dev/null @@ -1,77 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -package mocks - -import mock "github.com/stretchr/testify/mock" - -// ReaperConfig is an autogenerated mock type for the ReaperChainConfig type -type ReaperConfig struct { - mock.Mock -} - -type ReaperConfig_Expecter struct { - mock *mock.Mock -} - -func (_m *ReaperConfig) EXPECT() *ReaperConfig_Expecter { - return &ReaperConfig_Expecter{mock: &_m.Mock} -} - -// FinalityDepth provides a mock function with given fields: -func (_m *ReaperConfig) FinalityDepth() uint32 { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for FinalityDepth") - } - - var r0 uint32 - if rf, ok := ret.Get(0).(func() uint32); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(uint32) - } - - return r0 -} - -// ReaperConfig_FinalityDepth_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FinalityDepth' -type ReaperConfig_FinalityDepth_Call struct { - *mock.Call -} - -// FinalityDepth is a helper method to define mock.On call -func (_e *ReaperConfig_Expecter) FinalityDepth() *ReaperConfig_FinalityDepth_Call { - return &ReaperConfig_FinalityDepth_Call{Call: _e.mock.On("FinalityDepth")} -} - -func (_c *ReaperConfig_FinalityDepth_Call) Run(run func()) *ReaperConfig_FinalityDepth_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *ReaperConfig_FinalityDepth_Call) Return(_a0 uint32) *ReaperConfig_FinalityDepth_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *ReaperConfig_FinalityDepth_Call) RunAndReturn(run func() uint32) *ReaperConfig_FinalityDepth_Call { - _c.Call.Return(run) - return _c -} - -// NewReaperConfig creates a new instance of ReaperConfig. 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 NewReaperConfig(t interface { - mock.TestingT - Cleanup(func()) -}) *ReaperConfig { - mock := &ReaperConfig{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/common/txmgr/types/mocks/tx_store.go b/common/txmgr/types/mocks/tx_store.go index ee166638e3..4467729e16 100644 --- a/common/txmgr/types/mocks/tx_store.go +++ b/common/txmgr/types/mocks/tx_store.go @@ -1760,65 +1760,6 @@ func (_c *TxStore_HasInProgressTransaction_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_H return _c } -// IsTxFinalized provides a mock function with given fields: ctx, blockHeight, txID, chainID -func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) IsTxFinalized(ctx context.Context, blockHeight int64, txID int64, chainID CHAIN_ID) (bool, error) { - ret := _m.Called(ctx, blockHeight, txID, chainID) - - if len(ret) == 0 { - panic("no return value specified for IsTxFinalized") - } - - var r0 bool - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, int64, int64, CHAIN_ID) (bool, error)); ok { - return rf(ctx, blockHeight, txID, chainID) - } - if rf, ok := ret.Get(0).(func(context.Context, int64, int64, CHAIN_ID) bool); ok { - r0 = rf(ctx, blockHeight, txID, chainID) - } else { - r0 = ret.Get(0).(bool) - } - - if rf, ok := ret.Get(1).(func(context.Context, int64, int64, CHAIN_ID) error); ok { - r1 = rf(ctx, blockHeight, txID, chainID) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// TxStore_IsTxFinalized_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsTxFinalized' -type TxStore_IsTxFinalized_Call[ADDR types.Hashable, CHAIN_ID types.ID, TX_HASH types.Hashable, BLOCK_HASH types.Hashable, R txmgrtypes.ChainReceipt[TX_HASH, BLOCK_HASH], SEQ types.Sequence, FEE feetypes.Fee] struct { - *mock.Call -} - -// IsTxFinalized is a helper method to define mock.On call -// - ctx context.Context -// - blockHeight int64 -// - txID int64 -// - chainID CHAIN_ID -func (_e *TxStore_Expecter[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) IsTxFinalized(ctx interface{}, blockHeight interface{}, txID interface{}, chainID interface{}) *TxStore_IsTxFinalized_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE] { - return &TxStore_IsTxFinalized_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]{Call: _e.mock.On("IsTxFinalized", ctx, blockHeight, txID, chainID)} -} - -func (_c *TxStore_IsTxFinalized_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Run(run func(ctx context.Context, blockHeight int64, txID int64, chainID CHAIN_ID)) *TxStore_IsTxFinalized_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE] { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(int64), args[2].(int64), args[3].(CHAIN_ID)) - }) - return _c -} - -func (_c *TxStore_IsTxFinalized_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Return(finalized bool, err error) *TxStore_IsTxFinalized_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE] { - _c.Call.Return(finalized, err) - return _c -} - -func (_c *TxStore_IsTxFinalized_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) RunAndReturn(run func(context.Context, int64, int64, CHAIN_ID) (bool, error)) *TxStore_IsTxFinalized_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE] { - _c.Call.Return(run) - return _c -} - // LoadTxAttempts provides a mock function with given fields: ctx, etx func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) LoadTxAttempts(ctx context.Context, etx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) error { ret := _m.Called(ctx, etx) @@ -1913,17 +1854,17 @@ func (_c *TxStore_MarkAllConfirmedMissingReceipt_Call[ADDR, CHAIN_ID, TX_HASH, B return _c } -// MarkOldTxesMissingReceiptAsErrored provides a mock function with given fields: ctx, blockNum, finalityDepth, chainID -func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) MarkOldTxesMissingReceiptAsErrored(ctx context.Context, blockNum int64, finalityDepth uint32, chainID CHAIN_ID) error { - ret := _m.Called(ctx, blockNum, finalityDepth, chainID) +// MarkOldTxesMissingReceiptAsErrored provides a mock function with given fields: ctx, blockNum, latestFinalizedBlockNum, chainID +func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) MarkOldTxesMissingReceiptAsErrored(ctx context.Context, blockNum int64, latestFinalizedBlockNum int64, chainID CHAIN_ID) error { + ret := _m.Called(ctx, blockNum, latestFinalizedBlockNum, chainID) if len(ret) == 0 { panic("no return value specified for MarkOldTxesMissingReceiptAsErrored") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, int64, uint32, CHAIN_ID) error); ok { - r0 = rf(ctx, blockNum, finalityDepth, chainID) + if rf, ok := ret.Get(0).(func(context.Context, int64, int64, CHAIN_ID) error); ok { + r0 = rf(ctx, blockNum, latestFinalizedBlockNum, chainID) } else { r0 = ret.Error(0) } @@ -1939,15 +1880,15 @@ type TxStore_MarkOldTxesMissingReceiptAsErrored_Call[ADDR types.Hashable, CHAIN_ // MarkOldTxesMissingReceiptAsErrored is a helper method to define mock.On call // - ctx context.Context // - blockNum int64 -// - finalityDepth uint32 +// - latestFinalizedBlockNum int64 // - chainID CHAIN_ID -func (_e *TxStore_Expecter[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) MarkOldTxesMissingReceiptAsErrored(ctx interface{}, blockNum interface{}, finalityDepth interface{}, chainID interface{}) *TxStore_MarkOldTxesMissingReceiptAsErrored_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE] { - return &TxStore_MarkOldTxesMissingReceiptAsErrored_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]{Call: _e.mock.On("MarkOldTxesMissingReceiptAsErrored", ctx, blockNum, finalityDepth, chainID)} +func (_e *TxStore_Expecter[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) MarkOldTxesMissingReceiptAsErrored(ctx interface{}, blockNum interface{}, latestFinalizedBlockNum interface{}, chainID interface{}) *TxStore_MarkOldTxesMissingReceiptAsErrored_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE] { + return &TxStore_MarkOldTxesMissingReceiptAsErrored_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]{Call: _e.mock.On("MarkOldTxesMissingReceiptAsErrored", ctx, blockNum, latestFinalizedBlockNum, chainID)} } -func (_c *TxStore_MarkOldTxesMissingReceiptAsErrored_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Run(run func(ctx context.Context, blockNum int64, finalityDepth uint32, chainID CHAIN_ID)) *TxStore_MarkOldTxesMissingReceiptAsErrored_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE] { +func (_c *TxStore_MarkOldTxesMissingReceiptAsErrored_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Run(run func(ctx context.Context, blockNum int64, latestFinalizedBlockNum int64, chainID CHAIN_ID)) *TxStore_MarkOldTxesMissingReceiptAsErrored_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE] { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(int64), args[2].(uint32), args[3].(CHAIN_ID)) + run(args[0].(context.Context), args[1].(int64), args[2].(int64), args[3].(CHAIN_ID)) }) return _c } @@ -1957,7 +1898,7 @@ func (_c *TxStore_MarkOldTxesMissingReceiptAsErrored_Call[ADDR, CHAIN_ID, TX_HAS return _c } -func (_c *TxStore_MarkOldTxesMissingReceiptAsErrored_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) RunAndReturn(run func(context.Context, int64, uint32, CHAIN_ID) error) *TxStore_MarkOldTxesMissingReceiptAsErrored_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE] { +func (_c *TxStore_MarkOldTxesMissingReceiptAsErrored_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) RunAndReturn(run func(context.Context, int64, int64, CHAIN_ID) error) *TxStore_MarkOldTxesMissingReceiptAsErrored_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE] { _c.Call.Return(run) return _c } @@ -2069,17 +2010,17 @@ func (_c *TxStore_PruneUnstartedTxQueue_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH return _c } -// ReapTxHistory provides a mock function with given fields: ctx, minBlockNumberToKeep, timeThreshold, chainID -func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) ReapTxHistory(ctx context.Context, minBlockNumberToKeep int64, timeThreshold time.Time, chainID CHAIN_ID) error { - ret := _m.Called(ctx, minBlockNumberToKeep, timeThreshold, chainID) +// ReapTxHistory provides a mock function with given fields: ctx, timeThreshold, chainID +func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) ReapTxHistory(ctx context.Context, timeThreshold time.Time, chainID CHAIN_ID) error { + ret := _m.Called(ctx, timeThreshold, chainID) if len(ret) == 0 { panic("no return value specified for ReapTxHistory") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, int64, time.Time, CHAIN_ID) error); ok { - r0 = rf(ctx, minBlockNumberToKeep, timeThreshold, chainID) + if rf, ok := ret.Get(0).(func(context.Context, time.Time, CHAIN_ID) error); ok { + r0 = rf(ctx, timeThreshold, chainID) } else { r0 = ret.Error(0) } @@ -2094,16 +2035,15 @@ type TxStore_ReapTxHistory_Call[ADDR types.Hashable, CHAIN_ID types.ID, TX_HASH // ReapTxHistory is a helper method to define mock.On call // - ctx context.Context -// - minBlockNumberToKeep int64 // - timeThreshold time.Time // - chainID CHAIN_ID -func (_e *TxStore_Expecter[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) ReapTxHistory(ctx interface{}, minBlockNumberToKeep interface{}, timeThreshold interface{}, chainID interface{}) *TxStore_ReapTxHistory_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE] { - return &TxStore_ReapTxHistory_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]{Call: _e.mock.On("ReapTxHistory", ctx, minBlockNumberToKeep, timeThreshold, chainID)} +func (_e *TxStore_Expecter[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) ReapTxHistory(ctx interface{}, timeThreshold interface{}, chainID interface{}) *TxStore_ReapTxHistory_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE] { + return &TxStore_ReapTxHistory_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]{Call: _e.mock.On("ReapTxHistory", ctx, timeThreshold, chainID)} } -func (_c *TxStore_ReapTxHistory_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Run(run func(ctx context.Context, minBlockNumberToKeep int64, timeThreshold time.Time, chainID CHAIN_ID)) *TxStore_ReapTxHistory_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE] { +func (_c *TxStore_ReapTxHistory_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Run(run func(ctx context.Context, timeThreshold time.Time, chainID CHAIN_ID)) *TxStore_ReapTxHistory_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE] { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(int64), args[2].(time.Time), args[3].(CHAIN_ID)) + run(args[0].(context.Context), args[1].(time.Time), args[2].(CHAIN_ID)) }) return _c } @@ -2113,7 +2053,7 @@ func (_c *TxStore_ReapTxHistory_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ return _c } -func (_c *TxStore_ReapTxHistory_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) RunAndReturn(run func(context.Context, int64, time.Time, CHAIN_ID) error) *TxStore_ReapTxHistory_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE] { +func (_c *TxStore_ReapTxHistory_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) RunAndReturn(run func(context.Context, time.Time, CHAIN_ID) error) *TxStore_ReapTxHistory_Call[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE] { _c.Call.Return(run) return _c } diff --git a/common/txmgr/types/stuck_tx_detector.go b/common/txmgr/types/stuck_tx_detector.go index c4ca94b87f..dc09eea980 100644 --- a/common/txmgr/types/stuck_tx_detector.go +++ b/common/txmgr/types/stuck_tx_detector.go @@ -22,5 +22,5 @@ type StuckTxDetector[ // Sets the last purged block num after a transaction has been successfully purged with receipt SetPurgeBlockNum(fromAddress ADDR, blockNum int64) // Returns the error message to set in the transaction error field to mark it as terminally stuck - StuckTxFatalError() *string + StuckTxFatalError() string } diff --git a/common/txmgr/types/tx_store.go b/common/txmgr/types/tx_store.go index 875339cfba..3d874cc436 100644 --- a/common/txmgr/types/tx_store.go +++ b/common/txmgr/types/tx_store.go @@ -79,6 +79,8 @@ type TransactionStore[ // Search for Tx using the fromAddress and sequence FindTxWithSequence(ctx context.Context, fromAddress ADDR, seq SEQ) (etx *Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], err error) FindNextUnstartedTransactionFromAddress(ctx context.Context, fromAddress ADDR, chainID CHAIN_ID) (*Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) + + // FindTransactionsConfirmedInBlockRange retrieves tx with attempts and partial receipt values for optimization purpose FindTransactionsConfirmedInBlockRange(ctx context.Context, highBlockNumber, lowBlockNumber int64, chainID CHAIN_ID) (etxs []*Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], err error) FindEarliestUnconfirmedBroadcastTime(ctx context.Context, chainID CHAIN_ID) (null.Time, error) FindEarliestUnconfirmedTxAttemptBlock(ctx context.Context, chainID CHAIN_ID) (null.Int, error) @@ -89,7 +91,7 @@ type TransactionStore[ HasInProgressTransaction(ctx context.Context, account ADDR, chainID CHAIN_ID) (exists bool, err error) LoadTxAttempts(ctx context.Context, etx *Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) error MarkAllConfirmedMissingReceipt(ctx context.Context, chainID CHAIN_ID) (err error) - MarkOldTxesMissingReceiptAsErrored(ctx context.Context, blockNum int64, finalityDepth uint32, chainID CHAIN_ID) error + MarkOldTxesMissingReceiptAsErrored(ctx context.Context, blockNum int64, latestFinalizedBlockNum int64, chainID CHAIN_ID) error PreloadTxes(ctx context.Context, attempts []TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) error SaveConfirmedMissingReceiptAttempt(ctx context.Context, timeout time.Duration, attempt *TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], broadcastAt time.Time) error SaveInProgressAttempt(ctx context.Context, attempt *TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) error @@ -105,11 +107,10 @@ type TransactionStore[ UpdateTxUnstartedToInProgress(ctx context.Context, etx *Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], attempt *TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) error UpdateTxFatalError(ctx context.Context, etx *Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) error UpdateTxForRebroadcast(ctx context.Context, etx Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], etxAttempt TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) error - IsTxFinalized(ctx context.Context, blockHeight int64, txID int64, chainID CHAIN_ID) (finalized bool, err error) } type TxHistoryReaper[CHAIN_ID types.ID] interface { - ReapTxHistory(ctx context.Context, minBlockNumberToKeep int64, timeThreshold time.Time, chainID CHAIN_ID) error + ReapTxHistory(ctx context.Context, timeThreshold time.Time, chainID CHAIN_ID) error } type UnstartedTxQueuePruner interface { diff --git a/contracts/.changeset/chilled-melons-warn.md b/contracts/.changeset/chilled-melons-warn.md new file mode 100644 index 0000000000..f94192bb60 --- /dev/null +++ b/contracts/.changeset/chilled-melons-warn.md @@ -0,0 +1,5 @@ +--- +'@chainlink/contracts': patch +--- + +#internal index don ID in ConfigSet event diff --git a/contracts/.changeset/eight-timers-sip.md b/contracts/.changeset/eight-timers-sip.md new file mode 100644 index 0000000000..3f81544e34 --- /dev/null +++ b/contracts/.changeset/eight-timers-sip.md @@ -0,0 +1,5 @@ +--- +'@chainlink/contracts': patch +--- + +Add new channel definitions config store contract for parallel compositions #added diff --git a/contracts/.changeset/eighty-ways-vanish.md b/contracts/.changeset/eighty-ways-vanish.md new file mode 100644 index 0000000000..3a48ca4e71 --- /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/few-parents-punch.md b/contracts/.changeset/few-parents-punch.md new file mode 100644 index 0000000000..88a885606b --- /dev/null +++ b/contracts/.changeset/few-parents-punch.md @@ -0,0 +1,8 @@ +--- +'@chainlink/contracts': patch +--- + +update comments + + +PR issue: SHIP-3557 diff --git a/contracts/.changeset/flat-turkeys-rule.md b/contracts/.changeset/flat-turkeys-rule.md new file mode 100644 index 0000000000..2dedbe653e --- /dev/null +++ b/contracts/.changeset/flat-turkeys-rule.md @@ -0,0 +1,5 @@ +--- +'@chainlink/contracts': patch +--- + +#internal merge ccip contracts diff --git a/contracts/.changeset/fluffy-papayas-chew.md b/contracts/.changeset/fluffy-papayas-chew.md new file mode 100644 index 0000000000..aa7b145e8f --- /dev/null +++ b/contracts/.changeset/fluffy-papayas-chew.md @@ -0,0 +1,8 @@ +--- +'@chainlink/contracts': minor +--- + +#internal Change Chain Reader testing contract Triggered event for easier testing of filtering by non indexed evm data. + + +PR issue: BCFR-203 diff --git a/contracts/.changeset/forty-radios-brush.md b/contracts/.changeset/forty-radios-brush.md new file mode 100644 index 0000000000..5356ffedb1 --- /dev/null +++ b/contracts/.changeset/forty-radios-brush.md @@ -0,0 +1,8 @@ +--- +'@chainlink/contracts': patch +--- + +Add Configurator contract + + +PR issue: MERC-6185 diff --git a/contracts/.changeset/heavy-balloons-cheat.md b/contracts/.changeset/heavy-balloons-cheat.md new file mode 100644 index 0000000000..c0a39c5db7 --- /dev/null +++ b/contracts/.changeset/heavy-balloons-cheat.md @@ -0,0 +1,9 @@ +--- +"chainlink": patch +--- + +#added Add ZKSync L2EP SequencerUptimeFeed contract +#added Add ZKSync L2EP Validator contract + + +PR issue: SHIP-3004 diff --git a/contracts/.changeset/itchy-turtles-agree.md b/contracts/.changeset/itchy-turtles-agree.md new file mode 100644 index 0000000000..930ab850d9 --- /dev/null +++ b/contracts/.changeset/itchy-turtles-agree.md @@ -0,0 +1,5 @@ +--- +'@chainlink/contracts': minor +--- + +#internal Add an event with indexed topics that get hashed to Chain Reader Tester contract. diff --git a/contracts/.changeset/loud-lobsters-guess.md b/contracts/.changeset/loud-lobsters-guess.md new file mode 100644 index 0000000000..e470267e4e --- /dev/null +++ b/contracts/.changeset/loud-lobsters-guess.md @@ -0,0 +1,5 @@ +--- +'@chainlink/contracts': patch +--- + +auto: create a replication from v2_3 to v2_3_zksync diff --git a/contracts/.changeset/nasty-llamas-prove.md b/contracts/.changeset/nasty-llamas-prove.md new file mode 100644 index 0000000000..dd34467680 --- /dev/null +++ b/contracts/.changeset/nasty-llamas-prove.md @@ -0,0 +1,7 @@ +--- +'@chainlink/contracts': patch +--- + +DEVSVCS-147: add a reentrancy guard for balance monitor + +PR issue: DEVSVCS-147 diff --git a/contracts/.changeset/orange-plums-fold.md b/contracts/.changeset/orange-plums-fold.md new file mode 100644 index 0000000000..b6cf88c81b --- /dev/null +++ b/contracts/.changeset/orange-plums-fold.md @@ -0,0 +1,8 @@ +--- +'@chainlink/contracts': patch +--- + +Adding USDCReaderTester contract for CCIP integration tests #internal + + +CCIP-2881 \ No newline at end of file diff --git a/contracts/.changeset/polite-masks-jog.md b/contracts/.changeset/polite-masks-jog.md new file mode 100644 index 0000000000..93fba83b55 --- /dev/null +++ b/contracts/.changeset/polite-masks-jog.md @@ -0,0 +1,5 @@ +--- +'@chainlink/contracts': patch +--- + +#internal diff --git a/contracts/.changeset/proud-pears-tie.md b/contracts/.changeset/proud-pears-tie.md new file mode 100644 index 0000000000..93fba83b55 --- /dev/null +++ b/contracts/.changeset/proud-pears-tie.md @@ -0,0 +1,5 @@ +--- +'@chainlink/contracts': patch +--- + +#internal diff --git a/contracts/.changeset/quick-olives-accept.md b/contracts/.changeset/quick-olives-accept.md new file mode 100644 index 0000000000..31c59983e4 --- /dev/null +++ b/contracts/.changeset/quick-olives-accept.md @@ -0,0 +1,7 @@ +--- +'@chainlink/contracts': minor +--- + +#updated move latest ccip contracts code from ccip repo to chainlink repo + +PR issue: CCIP-2946 diff --git a/contracts/.changeset/quiet-lamps-walk.md b/contracts/.changeset/quiet-lamps-walk.md new file mode 100644 index 0000000000..93fba83b55 --- /dev/null +++ b/contracts/.changeset/quiet-lamps-walk.md @@ -0,0 +1,5 @@ +--- +'@chainlink/contracts': patch +--- + +#internal diff --git a/contracts/.changeset/quiet-moles-retire.md b/contracts/.changeset/quiet-moles-retire.md new file mode 100644 index 0000000000..6351f36a16 --- /dev/null +++ b/contracts/.changeset/quiet-moles-retire.md @@ -0,0 +1,8 @@ +--- +'@chainlink/contracts': patch +--- + +Use reusable workflow for Solidity Artifacts pipeline, move some actions to chainlink-github-actions repository + + +PR issue: TT-1693 diff --git a/contracts/.changeset/rich-lamps-do.md b/contracts/.changeset/rich-lamps-do.md new file mode 100644 index 0000000000..3f432d3de6 --- /dev/null +++ b/contracts/.changeset/rich-lamps-do.md @@ -0,0 +1,5 @@ +--- +'@chainlink/contracts': patch +--- + +update zksync automation contract version and small fixes diff --git a/contracts/.changeset/seven-donkeys-live.md b/contracts/.changeset/seven-donkeys-live.md new file mode 100644 index 0000000000..141588f5b9 --- /dev/null +++ b/contracts/.changeset/seven-donkeys-live.md @@ -0,0 +1,5 @@ +--- +'@chainlink/contracts': patch +--- + +improve cron contracts imports diff --git a/contracts/.changeset/silver-pots-cover.md b/contracts/.changeset/silver-pots-cover.md new file mode 100644 index 0000000000..93fba83b55 --- /dev/null +++ b/contracts/.changeset/silver-pots-cover.md @@ -0,0 +1,5 @@ +--- +'@chainlink/contracts': patch +--- + +#internal diff --git a/contracts/.changeset/slimy-pens-listen.md b/contracts/.changeset/slimy-pens-listen.md new file mode 100644 index 0000000000..ff81d22237 --- /dev/null +++ b/contracts/.changeset/slimy-pens-listen.md @@ -0,0 +1,5 @@ +--- +'@chainlink/contracts': patch +--- + +#internal prevent editing whether or not a DON accepts workflows diff --git a/contracts/.changeset/strong-boats-brake.md b/contracts/.changeset/strong-boats-brake.md new file mode 100644 index 0000000000..4f1b780565 --- /dev/null +++ b/contracts/.changeset/strong-boats-brake.md @@ -0,0 +1,8 @@ +--- +'@chainlink/contracts': patch +--- + +Add linkage between PR and Jira's Solidity Review issue + + +PR issue: TT-1624 diff --git a/contracts/.changeset/tender-comics-check.md b/contracts/.changeset/tender-comics-check.md new file mode 100644 index 0000000000..6ea48d92e4 --- /dev/null +++ b/contracts/.changeset/tender-comics-check.md @@ -0,0 +1,5 @@ +--- +'@chainlink/contracts': patch +--- + +#internal prevent reentrancy when configuring DON in capabilities registry diff --git a/contracts/.changeset/thirty-lamps-reply.md b/contracts/.changeset/thirty-lamps-reply.md new file mode 100644 index 0000000000..d8bcf8d4e8 --- /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/.changeset/three-stingrays-compete.md b/contracts/.changeset/three-stingrays-compete.md new file mode 100644 index 0000000000..613b278465 --- /dev/null +++ b/contracts/.changeset/three-stingrays-compete.md @@ -0,0 +1,5 @@ +--- +'@chainlink/contracts': minor +--- + +add ccip contracts to the repo diff --git a/contracts/.changeset/twenty-pears-battle.md b/contracts/.changeset/twenty-pears-battle.md new file mode 100644 index 0000000000..5a204f6889 --- /dev/null +++ b/contracts/.changeset/twenty-pears-battle.md @@ -0,0 +1,5 @@ +--- +'@chainlink/contracts': patch +--- + +update token transfer logic in weth9 diff --git a/contracts/.changeset/unlucky-rocks-marry.md b/contracts/.changeset/unlucky-rocks-marry.md new file mode 100644 index 0000000000..723bb1e130 --- /dev/null +++ b/contracts/.changeset/unlucky-rocks-marry.md @@ -0,0 +1,5 @@ +--- +'@chainlink/contracts': patch +--- + +#internal use ERC165Checker diff --git a/contracts/.gas-snapshot b/contracts/.gas-snapshot new file mode 100644 index 0000000000..e793f8ce54 --- /dev/null +++ b/contracts/.gas-snapshot @@ -0,0 +1,142 @@ +ArbitrumCrossDomainForwarder_AcceptL1Ownership:test_CallableByPendingL1Owner() (gas: 37568) +ArbitrumCrossDomainForwarder_AcceptL1Ownership:test_NotCallableByNonPendingOwners() (gas: 12963) +ArbitrumCrossDomainForwarder_Constructor:test_InitialState() (gas: 22163) +ArbitrumCrossDomainForwarder_Forward:test_Forward() (gas: 47867) +ArbitrumCrossDomainForwarder_Forward:test_ForwardRevert() (gas: 22181) +ArbitrumCrossDomainForwarder_Forward:test_NotCallableByUnknownAddress() (gas: 16056) +ArbitrumCrossDomainForwarder_TransferL1Ownership:test_CallableByL1Owner() (gas: 41453) +ArbitrumCrossDomainForwarder_TransferL1Ownership:test_CallableByL1OwnerOrZeroAddress() (gas: 19290) +ArbitrumCrossDomainForwarder_TransferL1Ownership:test_NotCallableByL2Owner() (gas: 18637) +ArbitrumCrossDomainForwarder_TransferL1Ownership:test_NotCallableByNonOwners() (gas: 13242) +ArbitrumCrossDomainGovernor_AcceptL1Ownership:test_CallableByPendingL1Owner() (gas: 37568) +ArbitrumCrossDomainGovernor_AcceptL1Ownership:test_NotCallableByNonPendingOwners() (gas: 12963) +ArbitrumCrossDomainGovernor_Constructor:test_InitialState() (gas: 22186) +ArbitrumCrossDomainGovernor_Forward:test_CallableByL2Owner() (gas: 50003) +ArbitrumCrossDomainGovernor_Forward:test_Forward() (gas: 47896) +ArbitrumCrossDomainGovernor_Forward:test_ForwardRevert() (gas: 24326) +ArbitrumCrossDomainGovernor_Forward:test_NotCallableByUnknownAddress() (gas: 18233) +ArbitrumCrossDomainGovernor_ForwardDelegate:test_BubbleUpRevert() (gas: 19386) +ArbitrumCrossDomainGovernor_ForwardDelegate:test_CallableByCrossDomainMessengerAddressOrL1Owner() (gas: 60874) +ArbitrumCrossDomainGovernor_ForwardDelegate:test_CallableByL2Owner() (gas: 63003) +ArbitrumCrossDomainGovernor_ForwardDelegate:test_NotCallableByUnknownAddress() (gas: 18245) +ArbitrumCrossDomainGovernor_ForwardDelegate:test_RevertsBatchWhenOneCallFails() (gas: 64368) +ArbitrumCrossDomainGovernor_TransferL1Ownership:test_CallableByL1Owner() (gas: 41453) +ArbitrumCrossDomainGovernor_TransferL1Ownership:test_CallableByL1OwnerOrZeroAddress() (gas: 19290) +ArbitrumCrossDomainGovernor_TransferL1Ownership:test_NotCallableByL2Owner() (gas: 18637) +ArbitrumCrossDomainGovernor_TransferL1Ownership:test_NotCallableByNonOwners() (gas: 13242) +ArbitrumSequencerUptimeFeed_AggregatorV3Interface:test_AggregatorV3Interface() (gas: 104862) +ArbitrumSequencerUptimeFeed_AggregatorV3Interface:test_Return0WhenRoundDoesNotExistYet() (gas: 19967) +ArbitrumSequencerUptimeFeed_Constants:test_InitialState() (gas: 8518) +ArbitrumSequencerUptimeFeed_ProtectReadsOnAggregatorV2V3InterfaceFunctions:test_AggregatorV2V3InterfaceAllowReadsIfConsumingContractIsWhitelisted() (gas: 604370) +ArbitrumSequencerUptimeFeed_ProtectReadsOnAggregatorV2V3InterfaceFunctions:test_AggregatorV2V3InterfaceDisallowReadsIfConsumingContractIsNotWhitelisted() (gas: 574432) +ArbitrumSequencerUptimeFeed_UpdateStatus:test_IgnoreOutOfOrderUpdates() (gas: 99629) +ArbitrumSequencerUptimeFeed_UpdateStatus:test_RevertIfNotL2CrossDomainMessengerAddr() (gas: 15447) +ArbitrumSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenStatusChangeAndNoTimeChange() (gas: 114625) +ArbitrumSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenStatusChangeAndTimeChange() (gas: 114708) +ArbitrumValidator_Validate:test_PostSequencerOffline() (gas: 69086) +OptimismCrossDomainForwarder_AcceptL1Ownership:test_CallableByPendingL1Owner() (gas: 47160) +OptimismCrossDomainForwarder_AcceptL1Ownership:test_NotCallableByNonPendingOwners() (gas: 22160) +OptimismCrossDomainForwarder_Constructor:test_InitialState() (gas: 21998) +OptimismCrossDomainForwarder_Forward:test_Forward() (gas: 58281) +OptimismCrossDomainForwarder_Forward:test_ForwardRevert() (gas: 32560) +OptimismCrossDomainForwarder_Forward:test_NotCallableByUnknownAddress() (gas: 13867) +OptimismCrossDomainForwarder_TransferL1Ownership:test_CallableByL1Owner() (gas: 48933) +OptimismCrossDomainForwarder_TransferL1Ownership:test_CallableByL1OwnerOrZeroAddress() (gas: 28753) +OptimismCrossDomainForwarder_TransferL1Ownership:test_NotCallableByL2Owner() (gas: 16448) +OptimismCrossDomainForwarder_TransferL1Ownership:test_NotCallableByNonOwners() (gas: 11053) +OptimismCrossDomainGovernor_AcceptL1Ownership:test_CallableByPendingL1Owner() (gas: 47160) +OptimismCrossDomainGovernor_AcceptL1Ownership:test_NotCallableByNonPendingOwners() (gas: 22160) +OptimismCrossDomainGovernor_Constructor:test_InitialState() (gas: 22021) +OptimismCrossDomainGovernor_Forward:test_CallableByL2Owner() (gas: 47846) +OptimismCrossDomainGovernor_Forward:test_Forward() (gas: 58330) +OptimismCrossDomainGovernor_Forward:test_ForwardRevert() (gas: 32619) +OptimismCrossDomainGovernor_Forward:test_NotCallableByUnknownAddress() (gas: 16047) +OptimismCrossDomainGovernor_ForwardDelegate:test_BubbleUpRevert() (gas: 29189) +OptimismCrossDomainGovernor_ForwardDelegate:test_CallableByCrossDomainMessengerAddressOrL1Owner() (gas: 72942) +OptimismCrossDomainGovernor_ForwardDelegate:test_CallableByL2Owner() (gas: 72947) +OptimismCrossDomainGovernor_ForwardDelegate:test_NotCallableByUnknownAddress() (gas: 16059) +OptimismCrossDomainGovernor_ForwardDelegate:test_RevertsBatchWhenOneCallFails() (gas: 76156) +OptimismCrossDomainGovernor_TransferL1Ownership:test_CallableByL1Owner() (gas: 48933) +OptimismCrossDomainGovernor_TransferL1Ownership:test_CallableByL1OwnerOrZeroAddress() (gas: 28753) +OptimismCrossDomainGovernor_TransferL1Ownership:test_NotCallableByL2Owner() (gas: 16448) +OptimismCrossDomainGovernor_TransferL1Ownership:test_NotCallableByNonOwners() (gas: 11053) +OptimismSequencerUptimeFeed_AggregatorV3Interface:test_AggregatorV3Interface() (gas: 72400) +OptimismSequencerUptimeFeed_AggregatorV3Interface:test_RevertGetAnswerWhenRoundDoesNotExistYet() (gas: 17653) +OptimismSequencerUptimeFeed_AggregatorV3Interface:test_RevertGetRoundDataWhenRoundDoesNotExistYet() (gas: 17893) +OptimismSequencerUptimeFeed_AggregatorV3Interface:test_RevertGetTimestampWhenRoundDoesNotExistYet() (gas: 17642) +OptimismSequencerUptimeFeed_Constructor:test_InitialState() (gas: 22050) +OptimismSequencerUptimeFeed_ProtectReadsOnAggregatorV2V3InterfaceFunctions:test_AggregatorV2V3InterfaceAllowReadsIfConsumingContractIsWhitelisted() (gas: 601594) +OptimismSequencerUptimeFeed_ProtectReadsOnAggregatorV2V3InterfaceFunctions:test_AggregatorV2V3InterfaceDisallowReadsIfConsumingContractIsNotWhitelisted() (gas: 574437) +OptimismSequencerUptimeFeed_UpdateStatus:test_IgnoreOutOfOrderUpdates() (gas: 67873) +OptimismSequencerUptimeFeed_UpdateStatus:test_RevertIfNotL2CrossDomainMessengerAddr() (gas: 13079) +OptimismSequencerUptimeFeed_UpdateStatus:test_RevertIfNotL2CrossDomainMessengerAddrAndNotL1SenderAddr() (gas: 23542) +OptimismSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenNoChange() (gas: 77322) +OptimismSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenStatusChangeAndNoTimeChange() (gas: 96182) +OptimismSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenStatusChangeAndTimeChange() (gas: 96265) +OptimismValidator_SetGasLimit:test_CorrectlyUpdatesTheGasLimit() (gas: 18671) +OptimismValidator_Validate:test_PostSequencerOffline() (gas: 74790) +OptimismValidator_Validate:test_PostSequencerStatusWhenThereIsNotStatusChange() (gas: 74869) +OptimismValidator_Validate:test_RevertsIfCalledByAnAccountWithNoAccess() (gas: 15571) +ScrollCrossDomainForwarder_AcceptL1Ownership:test_CallableByPendingL1Owner() (gas: 47255) +ScrollCrossDomainForwarder_AcceptL1Ownership:test_NotCallableByNonPendingOwners() (gas: 22212) +ScrollCrossDomainForwarder_Constructor:test_InitialState() (gas: 21674) +ScrollCrossDomainForwarder_Forward:test_Forward() (gas: 58348) +ScrollCrossDomainForwarder_Forward:test_ForwardRevert() (gas: 32618) +ScrollCrossDomainForwarder_Forward:test_NotCallableByUnknownAddress() (gas: 13867) +ScrollCrossDomainForwarder_TransferL1Ownership:test_CallableByL1Owner() (gas: 48999) +ScrollCrossDomainForwarder_TransferL1Ownership:test_CallableByL1OwnerOrZeroAddress() (gas: 28819) +ScrollCrossDomainForwarder_TransferL1Ownership:test_NotCallableByL2Owner() (gas: 16448) +ScrollCrossDomainForwarder_TransferL1Ownership:test_NotCallableByNonOwners() (gas: 11053) +ScrollCrossDomainGovernor_AcceptL1Ownership:test_CallableByPendingL1Owner() (gas: 47255) +ScrollCrossDomainGovernor_AcceptL1Ownership:test_NotCallableByNonPendingOwners() (gas: 22212) +ScrollCrossDomainGovernor_Constructor:test_InitialState() (gas: 21697) +ScrollCrossDomainGovernor_Forward:test_CallableByL2Owner() (gas: 47841) +ScrollCrossDomainGovernor_Forward:test_Forward() (gas: 58392) +ScrollCrossDomainGovernor_Forward:test_ForwardRevert() (gas: 32674) +ScrollCrossDomainGovernor_Forward:test_NotCallableByUnknownAddress() (gas: 16044) +ScrollCrossDomainGovernor_ForwardDelegate:test_BubbleUpRevert() (gas: 29250) +ScrollCrossDomainGovernor_ForwardDelegate:test_CallableByCrossDomainMessengerAddressOrL1Owner() (gas: 73009) +ScrollCrossDomainGovernor_ForwardDelegate:test_CallableByL2Owner() (gas: 73014) +ScrollCrossDomainGovernor_ForwardDelegate:test_NotCallableByUnknownAddress() (gas: 16056) +ScrollCrossDomainGovernor_ForwardDelegate:test_RevertsBatchWhenOneCallFails() (gas: 76224) +ScrollCrossDomainGovernor_TransferL1Ownership:test_CallableByL1Owner() (gas: 48999) +ScrollCrossDomainGovernor_TransferL1Ownership:test_CallableByL1OwnerOrZeroAddress() (gas: 28819) +ScrollCrossDomainGovernor_TransferL1Ownership:test_NotCallableByL2Owner() (gas: 16448) +ScrollCrossDomainGovernor_TransferL1Ownership:test_NotCallableByNonOwners() (gas: 11053) +ScrollSequencerUptimeFeed_AggregatorV3Interface:test_AggregatorV3Interface() (gas: 72423) +ScrollSequencerUptimeFeed_AggregatorV3Interface:test_RevertGetAnswerWhenRoundDoesNotExistYet() (gas: 17653) +ScrollSequencerUptimeFeed_AggregatorV3Interface:test_RevertGetRoundDataWhenRoundDoesNotExistYet() (gas: 17893) +ScrollSequencerUptimeFeed_AggregatorV3Interface:test_RevertGetTimestampWhenRoundDoesNotExistYet() (gas: 17642) +ScrollSequencerUptimeFeed_Constructor:test_InitialState() (gas: 173935) +ScrollSequencerUptimeFeed_ProtectReadsOnAggregatorV2V3InterfaceFunctions:test_AggregatorV2V3InterfaceAllowReadsIfConsumingContractIsWhitelisted() (gas: 601594) +ScrollSequencerUptimeFeed_ProtectReadsOnAggregatorV2V3InterfaceFunctions:test_AggregatorV2V3InterfaceDisallowReadsIfConsumingContractIsNotWhitelisted() (gas: 574437) +ScrollSequencerUptimeFeed_UpdateStatus:test_IgnoreOutOfOrderUpdates() (gas: 67919) +ScrollSequencerUptimeFeed_UpdateStatus:test_RevertIfNotL2CrossDomainMessengerAddr() (gas: 13079) +ScrollSequencerUptimeFeed_UpdateStatus:test_RevertIfNotL2CrossDomainMessengerAddrAndNotL1SenderAddr() (gas: 23542) +ScrollSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenNoChange() (gas: 77368) +ScrollSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenStatusChangeAndNoTimeChange() (gas: 96228) +ScrollSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenStatusChangeAndTimeChange() (gas: 96311) +ScrollValidator_SetGasLimit:test_CorrectlyUpdatesTheGasLimit() (gas: 18805) +ScrollValidator_Validate:test_PostSequencerOffline() (gas: 78326) +ScrollValidator_Validate:test_PostSequencerStatusWhenThereIsNotStatusChange() (gas: 78411) +ScrollValidator_Validate:test_RevertsIfCalledByAnAccountWithNoAccess() (gas: 15571) +ZKSyncSequencerUptimeFeed_AggregatorV3Interface:test_AggregatorV3Interface() (gas: 67166) +ZKSyncSequencerUptimeFeed_AggregatorV3Interface:test_RevertGetAnswerWhenRoundDoesNotExistYet() (gas: 17653) +ZKSyncSequencerUptimeFeed_AggregatorV3Interface:test_RevertGetRoundDataWhenRoundDoesNotExistYet() (gas: 17893) +ZKSyncSequencerUptimeFeed_AggregatorV3Interface:test_RevertGetTimestampWhenRoundDoesNotExistYet() (gas: 17642) +ZKSyncSequencerUptimeFeed_Constructor:test_InitialState() (gas: 22054) +ZKSyncSequencerUptimeFeed_ProtectReadsOnAggregatorV2V3InterfaceFunctions:test_AggregatorV2V3InterfaceAllowReadsIfConsumingContractIsWhitelisted() (gas: 601614) +ZKSyncSequencerUptimeFeed_ProtectReadsOnAggregatorV2V3InterfaceFunctions:test_AggregatorV2V3InterfaceDisallowReadsIfConsumingContractIsNotWhitelisted() (gas: 574443) +ZKSyncSequencerUptimeFeed_UpdateStatus:test_IgnoreOutOfOrderUpdates() (gas: 61924) +ZKSyncSequencerUptimeFeed_UpdateStatus:test_RevertIfNotL2CrossDomainMessengerAddr() (gas: 13035) +ZKSyncSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenNoChange() (gas: 71379) +ZKSyncSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenStatusChangeAndNoTimeChange() (gas: 90241) +ZKSyncSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenStatusChangeAndTimeChange() (gas: 90324) +ZKSyncValidator_Constructor:test_ConstructingRevertedWithInvalidChainId() (gas: 103725) +ZKSyncValidator_Constructor:test_ConstructingRevertedWithZeroL1BridgeAddress() (gas: 81440) +ZKSyncValidator_Constructor:test_ConstructingRevertedWithZeroL2UpdateFeedAddress() (gas: 81497) +ZKSyncValidator_GetChainId:test_CorrectlyGetsTheChainId() (gas: 8350) +ZKSyncValidator_GetSetL2GasPerPubdataByteLimit:test_CorrectlyGetsAndUpdatesTheGasPerPubdataByteLimit() (gas: 18915) +ZKSyncValidator_Validate:test_PostSequencerOffline() (gas: 52255) +ZKSyncValidator_Validate:test_PostSequencerStatusWhenThereIsNotStatusChange() (gas: 52355) +ZKSyncValidator_Validate:test_RevertsIfCalledByAnAccountWithNoAccess() (gas: 15644) \ No newline at end of file diff --git a/contracts/.prettierignore b/contracts/.prettierignore index 4b0b1887fd..440cf95afa 100644 --- a/contracts/.prettierignore +++ b/contracts/.prettierignore @@ -37,4 +37,3 @@ venv/ src/v0.8/mocks/FunctionsOracleEventsMock.sol src/v0.8/mocks/FunctionsBillingRegistryEventsMock.sol - diff --git a/contracts/.solhintignore b/contracts/.solhintignore index d076b14dc6..1f1d0d8961 100644 --- a/contracts/.solhintignore +++ b/contracts/.solhintignore @@ -43,4 +43,4 @@ ./node_modules/ # Ignore tweaked vendored contracts -./src/v0.8/shared/enumerable/EnumerableSetWithBytes16.sol \ No newline at end of file +./src/v0.8/shared/enumerable/EnumerableSetWithBytes16.sol diff --git a/contracts/.tool-versions b/contracts/.tool-versions new file mode 100644 index 0000000000..dfe63496b3 --- /dev/null +++ b/contracts/.tool-versions @@ -0,0 +1 @@ +nodejs 20.13.1 diff --git a/contracts/README.md b/contracts/README.md index bcb07cf906..16ba2e8254 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -44,10 +44,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## License +Most of the contracts are licensed under the [MIT](https://choosealicense.com/licenses/mit/) license. +An exception to this is the ccip folder, which defaults to be licensed under the [BUSL-1.1](./src/v0.8/ccip/LICENSE.md) license, however, there are a few exceptions The CCIP repo is licensed under the [BUSL-1.1](./src/v0.8/ccip/LICENSE.md) license, however, there are a few exceptions - `src/v0.8/ccip/applications/*` is licensed under the [MIT](./src/v0.8/ccip/LICENSE-MIT.md) license - `src/v0.8/ccip/interfaces/*` is licensed under the [MIT](./src/v0.8/ccip/LICENSE-MIT.md) license - `src/v0.8/ccip/libraries/{Client.sol, Internal.sol}` is licensed under the [MIT](./src/v0.8/ccip/LICENSE-MIT.md) license - diff --git a/contracts/STYLE_GUIDE.md b/contracts/STYLE_GUIDE.md index 4b2285143b..3cc169719d 100644 --- a/contracts/STYLE_GUIDE.md +++ b/contracts/STYLE_GUIDE.md @@ -33,6 +33,7 @@ We will be looking into `forge fmt`, but for now, we still use `prettier`. This will help massively during audits and onboarding new team members. - Headers should be used to group functionality, the following header style and length are recommended. - Don’t use headers for a single function, or to say “getters”. Group by functionality e.g. the `Tokens and pools`, or `fees` logic within the CCIP OnRamp. +- Comments should start with a capital letter and end with a period. ```solidity // ================================================================ @@ -219,7 +220,8 @@ The original error will not be human-readable in an off-chain explorer because i - Otherwise, Solidity contracts should have a pragma that is locked to a specific version. - Example: Most concrete contracts. - Avoid changing pragmas after the audit. Unless there is a bug that affects your contract, then you should try to stick to a known good pragma. In practice, this means we typically only support one (occasionally two) pragma for any “major”(minor by Semver naming) Solidity version. -- The current advised pragma is `0.8.19` or higher, lower versions should be avoided when starting a new project. Newer versions can be considered. +- The current advised pragma is `0.8.24`, lower versions should be avoided when starting a new project. Newer versions can be considered. + - Explicitly use the `Paris` hardfork when compiling with 0.8.24 to keep the bytecode compatible with all chains. - All contracts should have an SPDX license identifier. If unsure about which one to pick, please consult with legal. Most older contracts have been MIT, but some of the newer products have been using BUSL-1.1 @@ -265,8 +267,8 @@ contract SuperDuperAggregator is ITypeAndVersion { All contracts will expose a `typeAndVersion` constant. The string has the following format: `-` with the `-dev` part only being applicable to contracts that have not been fully released. Try to fit it into 32 bytes to keep the impact on contract sizes minimal. -Solhint will complain about a public constant variable that isn’t FULL_CAPS without the solhint-disable comment. +Note that `ITypeAndVersion` should be used, not `TypeAndVersionInterface`. @@ -420,4 +422,3 @@ function setConfig(uint64 _foo, uint64 _bar, uint64 _baz) external { ``` rule: `tbd` - diff --git a/contracts/foundry.toml b/contracts/foundry.toml index 501390eb80..781e875eb6 100644 --- a/contracts/foundry.toml +++ b/contracts/foundry.toml @@ -26,7 +26,7 @@ single_line_statement_blocks = "preserve" solc_version = '0.8.24' src = 'src/v0.8/ccip' test = 'src/v0.8/ccip/test' -optimizer_runs = 800 +optimizer_runs = 800 evm_version = 'paris' [profile.functions] @@ -54,10 +54,10 @@ src = 'src/v0.8/automation' test = 'src/v0.8/automation/test' [profile.l2ep] +solc_version = '0.8.24' optimizer_runs = 1_000_000 src = 'src/v0.8/l2ep' test = 'src/v0.8/l2ep/test' -solc_version = '0.8.19' [profile.llo-feeds] optimizer_runs = 1_000_000 diff --git a/contracts/gas-snapshots/keystone.gas-snapshot b/contracts/gas-snapshots/keystone.gas-snapshot index 2880e4c0e3..3a21b733b0 100644 --- a/contracts/gas-snapshots/keystone.gas-snapshot +++ b/contracts/gas-snapshots/keystone.gas-snapshot @@ -1,107 +1,115 @@ -CapabilitiesRegistry_AddCapabilitiesTest:test_AddCapability_NoConfigurationContract() (gas: 154832) -CapabilitiesRegistry_AddCapabilitiesTest:test_AddCapability_WithConfiguration() (gas: 178813) -CapabilitiesRegistry_AddCapabilitiesTest:test_RevertWhen_CalledByNonAdmin() (gas: 24723) -CapabilitiesRegistry_AddCapabilitiesTest:test_RevertWhen_CapabilityExists() (gas: 145703) -CapabilitiesRegistry_AddCapabilitiesTest:test_RevertWhen_ConfigurationContractDoesNotMatchInterface() (gas: 94606) -CapabilitiesRegistry_AddCapabilitiesTest:test_RevertWhen_ConfigurationContractNotDeployed() (gas: 92961) -CapabilitiesRegistry_AddDONTest:test_AddDON() (gas: 372302) -CapabilitiesRegistry_AddDONTest:test_RevertWhen_CalledByNonAdmin() (gas: 19273) -CapabilitiesRegistry_AddDONTest:test_RevertWhen_CapabilityDoesNotExist() (gas: 169752) -CapabilitiesRegistry_AddDONTest:test_RevertWhen_DeprecatedCapabilityAdded() (gas: 239789) -CapabilitiesRegistry_AddDONTest:test_RevertWhen_DuplicateCapabilityAdded() (gas: 249596) -CapabilitiesRegistry_AddDONTest:test_RevertWhen_DuplicateNodeAdded() (gas: 116890) -CapabilitiesRegistry_AddDONTest:test_RevertWhen_FaultToleranceIsZero() (gas: 43358) -CapabilitiesRegistry_AddDONTest:test_RevertWhen_NodeAlreadyBelongsToWorkflowDON() (gas: 343924) -CapabilitiesRegistry_AddDONTest:test_RevertWhen_NodeDoesNotSupportCapability() (gas: 180150) -CapabilitiesRegistry_AddNodeOperatorsTest:test_AddNodeOperators() (gas: 184135) -CapabilitiesRegistry_AddNodeOperatorsTest:test_RevertWhen_CalledByNonAdmin() (gas: 17602) -CapabilitiesRegistry_AddNodeOperatorsTest:test_RevertWhen_NodeOperatorAdminAddressZero() (gas: 18498) -CapabilitiesRegistry_AddNodesTest:test_AddsNodeParams() (gas: 358448) -CapabilitiesRegistry_AddNodesTest:test_OwnerCanAddNodes() (gas: 358414) -CapabilitiesRegistry_AddNodesTest:test_RevertWhen_AddingDuplicateP2PId() (gas: 301229) -CapabilitiesRegistry_AddNodesTest:test_RevertWhen_AddingNodeWithInvalidCapability() (gas: 55174) -CapabilitiesRegistry_AddNodesTest:test_RevertWhen_AddingNodeWithInvalidNodeOperator() (gas: 24895) -CapabilitiesRegistry_AddNodesTest:test_RevertWhen_AddingNodeWithoutCapabilities() (gas: 27669) -CapabilitiesRegistry_AddNodesTest:test_RevertWhen_CalledByNonNodeOperatorAdminAndNonOwner() (gas: 25108) -CapabilitiesRegistry_AddNodesTest:test_RevertWhen_P2PIDEmpty() (gas: 27408) -CapabilitiesRegistry_AddNodesTest:test_RevertWhen_SignerAddressEmpty() (gas: 27047) -CapabilitiesRegistry_AddNodesTest:test_RevertWhen_SignerAddressNotUnique() (gas: 309679) -CapabilitiesRegistry_DeprecateCapabilitiesTest:test_DeprecatesCapability() (gas: 89807) -CapabilitiesRegistry_DeprecateCapabilitiesTest:test_EmitsEvent() (gas: 89935) -CapabilitiesRegistry_DeprecateCapabilitiesTest:test_RevertWhen_CalledByNonAdmin() (gas: 22944) -CapabilitiesRegistry_DeprecateCapabilitiesTest:test_RevertWhen_CapabilityDoesNotExist() (gas: 16231) -CapabilitiesRegistry_DeprecateCapabilitiesTest:test_RevertWhen_CapabilityIsDeprecated() (gas: 91264) -CapabilitiesRegistry_GetCapabilitiesTest:test_ReturnsCapabilities() (gas: 135553) -CapabilitiesRegistry_GetDONsTest:test_CorrectlyFetchesDONs() (gas: 65468) -CapabilitiesRegistry_GetDONsTest:test_DoesNotIncludeRemovedDONs() (gas: 64924) -CapabilitiesRegistry_GetHashedCapabilityTest:test_CorrectlyGeneratesHashedCapabilityId() (gas: 11428) -CapabilitiesRegistry_GetHashedCapabilityTest:test_DoesNotCauseIncorrectClashes() (gas: 13087) -CapabilitiesRegistry_GetNodeOperatorsTest:test_CorrectlyFetchesNodeOperators() (gas: 36407) -CapabilitiesRegistry_GetNodeOperatorsTest:test_DoesNotIncludeRemovedNodeOperators() (gas: 38692) -CapabilitiesRegistry_GetNodesTest:test_CorrectlyFetchesNodes() (gas: 65288) -CapabilitiesRegistry_GetNodesTest:test_DoesNotIncludeRemovedNodes() (gas: 73533) -CapabilitiesRegistry_RemoveDONsTest:test_RemovesDON() (gas: 54761) -CapabilitiesRegistry_RemoveDONsTest:test_RevertWhen_CalledByNonAdmin() (gas: 15647) -CapabilitiesRegistry_RemoveDONsTest:test_RevertWhen_DONDoesNotExist() (gas: 16550) -CapabilitiesRegistry_RemoveNodeOperatorsTest:test_RemovesNodeOperator() (gas: 36122) -CapabilitiesRegistry_RemoveNodeOperatorsTest:test_RevertWhen_CalledByNonOwner() (gas: 15816) -CapabilitiesRegistry_RemoveNodesTest:test_CanAddNodeWithSameSignerAddressAfterRemoving() (gas: 115151) -CapabilitiesRegistry_RemoveNodesTest:test_CanRemoveWhenDONDeleted() (gas: 287716) -CapabilitiesRegistry_RemoveNodesTest:test_CanRemoveWhenNodeNoLongerPartOfDON() (gas: 561023) -CapabilitiesRegistry_RemoveNodesTest:test_OwnerCanRemoveNodes() (gas: 73376) -CapabilitiesRegistry_RemoveNodesTest:test_RemovesNode() (gas: 75211) -CapabilitiesRegistry_RemoveNodesTest:test_RevertWhen_CalledByNonNodeOperatorAdminAndNonOwner() (gas: 25053) -CapabilitiesRegistry_RemoveNodesTest:test_RevertWhen_NodeDoesNotExist() (gas: 18418) -CapabilitiesRegistry_RemoveNodesTest:test_RevertWhen_NodePartOfCapabilitiesDON() (gas: 385369) -CapabilitiesRegistry_RemoveNodesTest:test_RevertWhen_P2PIDEmpty() (gas: 18408) -CapabilitiesRegistry_TypeAndVersionTest:test_TypeAndVersion() (gas: 9796) -CapabilitiesRegistry_UpdateDONTest:test_RevertWhen_CalledByNonAdmin() (gas: 19415) -CapabilitiesRegistry_UpdateDONTest:test_RevertWhen_CapabilityDoesNotExist() (gas: 152914) -CapabilitiesRegistry_UpdateDONTest:test_RevertWhen_DONDoesNotExist() (gas: 17835) -CapabilitiesRegistry_UpdateDONTest:test_RevertWhen_DeprecatedCapabilityAdded() (gas: 222996) -CapabilitiesRegistry_UpdateDONTest:test_RevertWhen_DuplicateCapabilityAdded() (gas: 232804) -CapabilitiesRegistry_UpdateDONTest:test_RevertWhen_DuplicateNodeAdded() (gas: 107643) -CapabilitiesRegistry_UpdateDONTest:test_RevertWhen_NodeDoesNotSupportCapability() (gas: 163357) -CapabilitiesRegistry_UpdateDONTest:test_UpdatesDON() (gas: 371909) -CapabilitiesRegistry_UpdateNodeOperatorTest:test_RevertWhen_CalledByNonAdminAndNonOwner() (gas: 20631) -CapabilitiesRegistry_UpdateNodeOperatorTest:test_RevertWhen_NodeOperatorAdminIsZeroAddress() (gas: 20052) -CapabilitiesRegistry_UpdateNodeOperatorTest:test_RevertWhen_NodeOperatorDoesNotExist() (gas: 19790) -CapabilitiesRegistry_UpdateNodeOperatorTest:test_RevertWhen_NodeOperatorIdAndParamLengthsMismatch() (gas: 15430) -CapabilitiesRegistry_UpdateNodeOperatorTest:test_UpdatesNodeOperator() (gas: 36937) -CapabilitiesRegistry_UpdateNodesTest:test_CanUpdateParamsIfNodeSignerAddressNoLongerUsed() (gas: 256157) -CapabilitiesRegistry_UpdateNodesTest:test_OwnerCanUpdateNodes() (gas: 162059) -CapabilitiesRegistry_UpdateNodesTest:test_RevertWhen_AddingNodeWithInvalidCapability() (gas: 35766) -CapabilitiesRegistry_UpdateNodesTest:test_RevertWhen_CalledByNonNodeOperatorAdminAndNonOwner() (gas: 25069) -CapabilitiesRegistry_UpdateNodesTest:test_RevertWhen_NodeDoesNotExist() (gas: 27308) -CapabilitiesRegistry_UpdateNodesTest:test_RevertWhen_NodeSignerAlreadyAssignedToAnotherNode() (gas: 29219) -CapabilitiesRegistry_UpdateNodesTest:test_RevertWhen_P2PIDEmpty() (gas: 27296) -CapabilitiesRegistry_UpdateNodesTest:test_RevertWhen_RemovingCapabilityRequiredByCapabilityDON() (gas: 470803) -CapabilitiesRegistry_UpdateNodesTest:test_RevertWhen_RemovingCapabilityRequiredByWorkflowDON() (gas: 341084) -CapabilitiesRegistry_UpdateNodesTest:test_RevertWhen_SignerAddressEmpty() (gas: 26951) -CapabilitiesRegistry_UpdateNodesTest:test_RevertWhen_UpdatingNodeWithoutCapabilities() (gas: 25480) -CapabilitiesRegistry_UpdateNodesTest:test_UpdatesNodeParams() (gas: 162113) -KeystoneForwarder_ReportTest:test_Report_ConfigVersion() (gas: 1797755) -KeystoneForwarder_ReportTest:test_Report_FailedDeliveryWhenReceiverInterfaceNotSupported() (gas: 125910) -KeystoneForwarder_ReportTest:test_Report_FailedDeliveryWhenReceiverNotContract() (gas: 127403) -KeystoneForwarder_ReportTest:test_Report_SuccessfulDelivery() (gas: 155928) -KeystoneForwarder_ReportTest:test_RevertWhen_AlreadyAttempted() (gas: 152358) +CapabilitiesRegistry_AddCapabilitiesTest:test_AddCapability_NoConfigurationContract() (gas: 165160) +CapabilitiesRegistry_AddCapabilitiesTest:test_AddCapability_WithConfiguration() (gas: 191990) +CapabilitiesRegistry_AddCapabilitiesTest:test_RevertWhen_CalledByNonAdmin() (gas: 27250) +CapabilitiesRegistry_AddCapabilitiesTest:test_RevertWhen_CapabilityExists() (gas: 152585) +CapabilitiesRegistry_AddCapabilitiesTest:test_RevertWhen_ConfigurationContractDoesNotMatchInterface() (gas: 99251) +CapabilitiesRegistry_AddCapabilitiesTest:test_RevertWhen_ConfigurationContractNotDeployed() (gas: 100235) +CapabilitiesRegistry_AddDONTest:test_AddDON() (gas: 397662) +CapabilitiesRegistry_AddDONTest:test_RevertWhen_CalledByNonAdmin() (gas: 21955) +CapabilitiesRegistry_AddDONTest:test_RevertWhen_CapabilityDoesNotExist() (gas: 173196) +CapabilitiesRegistry_AddDONTest:test_RevertWhen_DeprecatedCapabilityAdded() (gas: 244098) +CapabilitiesRegistry_AddDONTest:test_RevertWhen_DuplicateCapabilityAdded() (gas: 256604) +CapabilitiesRegistry_AddDONTest:test_RevertWhen_DuplicateNodeAdded() (gas: 120335) +CapabilitiesRegistry_AddDONTest:test_RevertWhen_FaultToleranceIsZero() (gas: 46314) +CapabilitiesRegistry_AddDONTest:test_RevertWhen_NodeAlreadyBelongsToWorkflowDON() (gas: 351528) +CapabilitiesRegistry_AddDONTest:test_RevertWhen_NodeDoesNotSupportCapability() (gas: 184109) +CapabilitiesRegistry_AddDONTest_WhenMaliciousCapabilityConfigurationConfigured:test_RevertWhen_MaliciousCapabilitiesConfigContractTriesToRemoveCapabilitiesFromDONNodes() (gas: 351499) +CapabilitiesRegistry_AddNodeOperatorsTest:test_AddNodeOperators() (gas: 196412) +CapabilitiesRegistry_AddNodeOperatorsTest:test_RevertWhen_CalledByNonAdmin() (gas: 20517) +CapabilitiesRegistry_AddNodeOperatorsTest:test_RevertWhen_NodeOperatorAdminAddressZero() (gas: 21853) +CapabilitiesRegistry_AddNodesTest:test_AddsNodeParams() (gas: 370469) +CapabilitiesRegistry_AddNodesTest:test_OwnerCanAddNodes() (gas: 370412) +CapabilitiesRegistry_AddNodesTest:test_RevertWhen_AddingDuplicateP2PId() (gas: 308840) +CapabilitiesRegistry_AddNodesTest:test_RevertWhen_AddingNodeWithInvalidCapability() (gas: 60242) +CapabilitiesRegistry_AddNodesTest:test_RevertWhen_AddingNodeWithInvalidNodeOperator() (gas: 29057) +CapabilitiesRegistry_AddNodesTest:test_RevertWhen_AddingNodeWithoutCapabilities() (gas: 31777) +CapabilitiesRegistry_AddNodesTest:test_RevertWhen_CalledByNonNodeOperatorAdminAndNonOwner() (gas: 29369) +CapabilitiesRegistry_AddNodesTest:test_RevertWhen_P2PIDEmpty() (gas: 31590) +CapabilitiesRegistry_AddNodesTest:test_RevertWhen_SignerAddressEmpty() (gas: 30458) +CapabilitiesRegistry_AddNodesTest:test_RevertWhen_SignerAddressNotUnique() (gas: 318363) +CapabilitiesRegistry_DeprecateCapabilitiesTest:test_DeprecatesCapability() (gas: 92947) +CapabilitiesRegistry_DeprecateCapabilitiesTest:test_EmitsEvent() (gas: 92416) +CapabilitiesRegistry_DeprecateCapabilitiesTest:test_RevertWhen_CalledByNonAdmin() (gas: 25861) +CapabilitiesRegistry_DeprecateCapabilitiesTest:test_RevertWhen_CapabilityDoesNotExist() (gas: 17253) +CapabilitiesRegistry_DeprecateCapabilitiesTest:test_RevertWhen_CapabilityIsDeprecated() (gas: 94546) +CapabilitiesRegistry_GetCapabilitiesTest:test_ReturnsCapabilities() (gas: 148509) +CapabilitiesRegistry_GetDONsTest:test_CorrectlyFetchesDONs() (gas: 79258) +CapabilitiesRegistry_GetDONsTest:test_DoesNotIncludeRemovedDONs() (gas: 75791) +CapabilitiesRegistry_GetHashedCapabilityTest:test_CorrectlyGeneratesHashedCapabilityId() (gas: 13678) +CapabilitiesRegistry_GetHashedCapabilityTest:test_DoesNotCauseIncorrectClashes() (gas: 16541) +CapabilitiesRegistry_GetNodeOperatorsTest:test_CorrectlyFetchesNodeOperators() (gas: 43532) +CapabilitiesRegistry_GetNodeOperatorsTest:test_DoesNotIncludeRemovedNodeOperators() (gas: 45407) +CapabilitiesRegistry_GetNodesTest:test_CorrectlyFetchesNodes() (gas: 78072) +CapabilitiesRegistry_GetNodesTest:test_DoesNotIncludeRemovedNodes() (gas: 81113) +CapabilitiesRegistry_RemoveDONsTest:test_RemovesDON() (gas: 65888) +CapabilitiesRegistry_RemoveDONsTest:test_RevertWhen_CalledByNonAdmin() (gas: 17319) +CapabilitiesRegistry_RemoveDONsTest:test_RevertWhen_DONDoesNotExist() (gas: 18221) +CapabilitiesRegistry_RemoveNodeOperatorsTest:test_RemovesNodeOperator() (gas: 42131) +CapabilitiesRegistry_RemoveNodeOperatorsTest:test_RevertWhen_CalledByNonOwner() (gas: 17570) +CapabilitiesRegistry_RemoveNodesTest:test_CanAddNodeWithSameSignerAddressAfterRemoving() (gas: 128198) +CapabilitiesRegistry_RemoveNodesTest:test_CanRemoveWhenDONDeleted() (gas: 304051) +CapabilitiesRegistry_RemoveNodesTest:test_CanRemoveWhenNodeNoLongerPartOfDON() (gas: 580021) +CapabilitiesRegistry_RemoveNodesTest:test_OwnerCanRemoveNodes() (gas: 79607) +CapabilitiesRegistry_RemoveNodesTest:test_RemovesNode() (gas: 81525) +CapabilitiesRegistry_RemoveNodesTest:test_RevertWhen_CalledByNonNodeOperatorAdminAndNonOwner() (gas: 27098) +CapabilitiesRegistry_RemoveNodesTest:test_RevertWhen_NodeDoesNotExist() (gas: 20122) +CapabilitiesRegistry_RemoveNodesTest:test_RevertWhen_NodePartOfCapabilitiesDON() (gas: 391972) +CapabilitiesRegistry_RemoveNodesTest:test_RevertWhen_P2PIDEmpty() (gas: 20101) +CapabilitiesRegistry_TypeAndVersionTest:test_TypeAndVersion() (gas: 10699) +CapabilitiesRegistry_UpdateDONTest:test_RevertWhen_CalledByNonAdmin() (gas: 21979) +CapabilitiesRegistry_UpdateDONTest:test_RevertWhen_CapabilityDoesNotExist() (gas: 157054) +CapabilitiesRegistry_UpdateDONTest:test_RevertWhen_DONDoesNotExist() (gas: 20190) +CapabilitiesRegistry_UpdateDONTest:test_RevertWhen_DeprecatedCapabilityAdded() (gas: 227956) +CapabilitiesRegistry_UpdateDONTest:test_RevertWhen_DuplicateCapabilityAdded() (gas: 243262) +CapabilitiesRegistry_UpdateDONTest:test_RevertWhen_DuplicateNodeAdded() (gas: 111705) +CapabilitiesRegistry_UpdateDONTest:test_RevertWhen_NodeDoesNotSupportCapability() (gas: 167967) +CapabilitiesRegistry_UpdateDONTest:test_UpdatesDON() (gas: 399442) +CapabilitiesRegistry_UpdateNodeOperatorTest:test_RevertWhen_CalledByNonAdminAndNonOwner() (gas: 24121) +CapabilitiesRegistry_UpdateNodeOperatorTest:test_RevertWhen_NodeOperatorAdminIsZeroAddress() (gas: 22970) +CapabilitiesRegistry_UpdateNodeOperatorTest:test_RevertWhen_NodeOperatorDoesNotExist() (gas: 22368) +CapabilitiesRegistry_UpdateNodeOperatorTest:test_RevertWhen_NodeOperatorIdAndParamLengthsMismatch() (gas: 18126) +CapabilitiesRegistry_UpdateNodeOperatorTest:test_UpdatesNodeOperator() (gas: 43652) +CapabilitiesRegistry_UpdateNodesTest:test_CanUpdateParamsIfNodeSignerAddressNoLongerUsed() (gas: 270986) +CapabilitiesRegistry_UpdateNodesTest:test_OwnerCanUpdateNodes() (gas: 172430) +CapabilitiesRegistry_UpdateNodesTest:test_RevertWhen_AddingNodeWithInvalidCapability() (gas: 40754) +CapabilitiesRegistry_UpdateNodesTest:test_RevertWhen_CalledByAnotherNodeOperatorAdmin() (gas: 32851) +CapabilitiesRegistry_UpdateNodesTest:test_RevertWhen_CalledByNonNodeOperatorAdminAndNonOwner() (gas: 33584) +CapabilitiesRegistry_UpdateNodesTest:test_RevertWhen_NodeDoesNotExist() (gas: 33290) +CapabilitiesRegistry_UpdateNodesTest:test_RevertWhen_NodeSignerAlreadyAssignedToAnotherNode() (gas: 35211) +CapabilitiesRegistry_UpdateNodesTest:test_RevertWhen_P2PIDEmpty() (gas: 33256) +CapabilitiesRegistry_UpdateNodesTest:test_RevertWhen_RemovingCapabilityRequiredByCapabilityDON() (gas: 480598) +CapabilitiesRegistry_UpdateNodesTest:test_RevertWhen_RemovingCapabilityRequiredByWorkflowDON() (gas: 350838) +CapabilitiesRegistry_UpdateNodesTest:test_RevertWhen_SignerAddressEmpty() (gas: 32442) +CapabilitiesRegistry_UpdateNodesTest:test_RevertWhen_UpdatingNodeWithoutCapabilities() (gas: 31565) +CapabilitiesRegistry_UpdateNodesTest:test_UpdatesNodeParams() (gas: 172552) +KeystoneForwarder_ReportTest:test_Report_ConfigVersion() (gas: 2006826) +KeystoneForwarder_ReportTest:test_Report_FailedDelieryWhenReportReceiverConsumesAllGas() (gas: 1004827) +KeystoneForwarder_ReportTest:test_Report_FailedDeliveryWhenReceiverInterfaceNotSupported() (gas: 124974) +KeystoneForwarder_ReportTest:test_Report_FailedDeliveryWhenReceiverNotContract() (gas: 127233) +KeystoneForwarder_ReportTest:test_Report_SuccessfulDelivery() (gas: 362832) +KeystoneForwarder_ReportTest:test_Report_SuccessfulRetryWithMoreGas() (gas: 510687) KeystoneForwarder_ReportTest:test_RevertWhen_AnySignatureIsInvalid() (gas: 86348) -KeystoneForwarder_ReportTest:test_RevertWhen_AnySignerIsInvalid() (gas: 118486) -KeystoneForwarder_ReportTest:test_RevertWhen_ReportHasDuplicateSignatures() (gas: 94516) +KeystoneForwarder_ReportTest:test_RevertWhen_AnySignerIsInvalid() (gas: 118476) +KeystoneForwarder_ReportTest:test_RevertWhen_AttemptingTransmissionWithInsufficientGas() (gas: 96673) +KeystoneForwarder_ReportTest:test_RevertWhen_ReportHasDuplicateSignatures() (gas: 94538) KeystoneForwarder_ReportTest:test_RevertWhen_ReportHasIncorrectDON() (gas: 75930) -KeystoneForwarder_ReportTest:test_RevertWhen_ReportHasInexistentConfigVersion() (gas: 76298) +KeystoneForwarder_ReportTest:test_RevertWhen_ReportHasInexistentConfigVersion() (gas: 76320) KeystoneForwarder_ReportTest:test_RevertWhen_ReportIsMalformed() (gas: 45585) -KeystoneForwarder_ReportTest:test_RevertWhen_TooFewSignatures() (gas: 55292) +KeystoneForwarder_ReportTest:test_RevertWhen_RetryingInvalidContractTransmission() (gas: 144181) +KeystoneForwarder_ReportTest:test_RevertWhen_RetryingSuccessfulTransmission() (gas: 355992) +KeystoneForwarder_ReportTest:test_RevertWhen_TooFewSignatures() (gas: 55314) KeystoneForwarder_ReportTest:test_RevertWhen_TooManySignatures() (gas: 56050) -KeystoneForwarder_SetConfigTest:test_RevertWhen_ExcessSigners() (gas: 20184) -KeystoneForwarder_SetConfigTest:test_RevertWhen_FaultToleranceIsZero() (gas: 88057) -KeystoneForwarder_SetConfigTest:test_RevertWhen_InsufficientSigners() (gas: 14533) -KeystoneForwarder_SetConfigTest:test_RevertWhen_NotOwner() (gas: 88788) -KeystoneForwarder_SetConfigTest:test_RevertWhen_ProvidingDuplicateSigners() (gas: 114507) -KeystoneForwarder_SetConfigTest:test_SetConfig_FirstTime() (gas: 1539921) -KeystoneForwarder_SetConfigTest:test_SetConfig_WhenSignersAreRemoved() (gas: 1534476) -KeystoneForwarder_TypeAndVersionTest:test_TypeAndVersion() (gas: 9641) -KeystoneRouter_SetConfigTest:test_AddForwarder_RevertWhen_NotOwner() (gas: 10978) -KeystoneRouter_SetConfigTest:test_RemoveForwarder_RevertWhen_NotOwner() (gas: 10923) -KeystoneRouter_SetConfigTest:test_Route_RevertWhen_UnauthorizedForwarder() (gas: 18553) -KeystoneRouter_SetConfigTest:test_Route_Success() (gas: 75629) \ No newline at end of file +KeystoneForwarder_SetConfigTest:test_RevertWhen_ExcessSigners() (gas: 36989) +KeystoneForwarder_SetConfigTest:test_RevertWhen_FaultToleranceIsZero() (gas: 96835) +KeystoneForwarder_SetConfigTest:test_RevertWhen_InsufficientSigners() (gas: 17476) +KeystoneForwarder_SetConfigTest:test_RevertWhen_NotOwner() (gas: 97845) +KeystoneForwarder_SetConfigTest:test_RevertWhen_ProvidingDuplicateSigners() (gas: 124504) +KeystoneForwarder_SetConfigTest:test_RevertWhen_ProvidingZeroAddressSigner() (gas: 124123) +KeystoneForwarder_SetConfigTest:test_SetConfig_FirstTime() (gas: 1574325) +KeystoneForwarder_SetConfigTest:test_SetConfig_WhenSignersAreRemoved() (gas: 1580648) +KeystoneForwarder_TypeAndVersionTest:test_TypeAndVersion() (gas: 10619) +KeystoneRouter_SetConfigTest:test_AddForwarder_RevertWhen_NotOwner() (gas: 11782) +KeystoneRouter_SetConfigTest:test_RemoveForwarder_RevertWhen_NotOwner() (gas: 11738) +KeystoneRouter_SetConfigTest:test_RemoveForwarder_Success() (gas: 18831) +KeystoneRouter_SetConfigTest:test_Route_RevertWhen_UnauthorizedForwarder() (gas: 20110) +KeystoneRouter_SetConfigTest:test_Route_Success() (gas: 89986) \ No newline at end of file diff --git a/contracts/gas-snapshots/l2ep.gas-snapshot b/contracts/gas-snapshots/l2ep.gas-snapshot index 42a9aa0b35..e793f8ce54 100644 --- a/contracts/gas-snapshots/l2ep.gas-snapshot +++ b/contracts/gas-snapshots/l2ep.gas-snapshot @@ -1,122 +1,142 @@ -ArbitrumCrossDomainForwarder_AcceptL1Ownership:test_CallableByPendingL1Owner() (gas: 37613) +ArbitrumCrossDomainForwarder_AcceptL1Ownership:test_CallableByPendingL1Owner() (gas: 37568) ArbitrumCrossDomainForwarder_AcceptL1Ownership:test_NotCallableByNonPendingOwners() (gas: 12963) -ArbitrumCrossDomainForwarder_Constructor:test_InitialState() (gas: 22196) +ArbitrumCrossDomainForwarder_Constructor:test_InitialState() (gas: 22163) ArbitrumCrossDomainForwarder_Forward:test_Forward() (gas: 47867) ArbitrumCrossDomainForwarder_Forward:test_ForwardRevert() (gas: 22181) ArbitrumCrossDomainForwarder_Forward:test_NotCallableByUnknownAddress() (gas: 16056) -ArbitrumCrossDomainForwarder_TransferL1Ownership:test_CallableByL1Owner() (gas: 41430) -ArbitrumCrossDomainForwarder_TransferL1Ownership:test_CallableByL1OwnerOrZeroAddress() (gas: 19312) -ArbitrumCrossDomainForwarder_TransferL1Ownership:test_NotCallableByL2Owner() (gas: 18671) -ArbitrumCrossDomainForwarder_TransferL1Ownership:test_NotCallableByNonOwners() (gas: 13219) -ArbitrumCrossDomainGovernor_AcceptL1Ownership:test_CallableByPendingL1Owner() (gas: 37613) +ArbitrumCrossDomainForwarder_TransferL1Ownership:test_CallableByL1Owner() (gas: 41453) +ArbitrumCrossDomainForwarder_TransferL1Ownership:test_CallableByL1OwnerOrZeroAddress() (gas: 19290) +ArbitrumCrossDomainForwarder_TransferL1Ownership:test_NotCallableByL2Owner() (gas: 18637) +ArbitrumCrossDomainForwarder_TransferL1Ownership:test_NotCallableByNonOwners() (gas: 13242) +ArbitrumCrossDomainGovernor_AcceptL1Ownership:test_CallableByPendingL1Owner() (gas: 37568) ArbitrumCrossDomainGovernor_AcceptL1Ownership:test_NotCallableByNonPendingOwners() (gas: 12963) -ArbitrumCrossDomainGovernor_Constructor:test_InitialState() (gas: 22219) -ArbitrumCrossDomainGovernor_Forward:test_CallableByL2Owner() (gas: 49980) -ArbitrumCrossDomainGovernor_Forward:test_Forward() (gas: 47918) -ArbitrumCrossDomainGovernor_Forward:test_ForwardRevert() (gas: 24348) -ArbitrumCrossDomainGovernor_Forward:test_NotCallableByUnknownAddress() (gas: 18255) +ArbitrumCrossDomainGovernor_Constructor:test_InitialState() (gas: 22186) +ArbitrumCrossDomainGovernor_Forward:test_CallableByL2Owner() (gas: 50003) +ArbitrumCrossDomainGovernor_Forward:test_Forward() (gas: 47896) +ArbitrumCrossDomainGovernor_Forward:test_ForwardRevert() (gas: 24326) +ArbitrumCrossDomainGovernor_Forward:test_NotCallableByUnknownAddress() (gas: 18233) ArbitrumCrossDomainGovernor_ForwardDelegate:test_BubbleUpRevert() (gas: 19386) ArbitrumCrossDomainGovernor_ForwardDelegate:test_CallableByCrossDomainMessengerAddressOrL1Owner() (gas: 60874) -ArbitrumCrossDomainGovernor_ForwardDelegate:test_CallableByL2Owner() (gas: 62980) +ArbitrumCrossDomainGovernor_ForwardDelegate:test_CallableByL2Owner() (gas: 63003) ArbitrumCrossDomainGovernor_ForwardDelegate:test_NotCallableByUnknownAddress() (gas: 18245) -ArbitrumCrossDomainGovernor_ForwardDelegate:test_RevertsBatchWhenOneCallFails() (gas: 64379) -ArbitrumCrossDomainGovernor_TransferL1Ownership:test_CallableByL1Owner() (gas: 41430) -ArbitrumCrossDomainGovernor_TransferL1Ownership:test_CallableByL1OwnerOrZeroAddress() (gas: 19312) -ArbitrumCrossDomainGovernor_TransferL1Ownership:test_NotCallableByL2Owner() (gas: 18671) -ArbitrumCrossDomainGovernor_TransferL1Ownership:test_NotCallableByNonOwners() (gas: 13219) -ArbitrumSequencerUptimeFeed_AggregatorV3Interface:test_AggregatorV3Interface() (gas: 104994) -ArbitrumSequencerUptimeFeed_AggregatorV3Interface:test_Return0WhenRoundDoesNotExistYet() (gas: 20033) -ArbitrumSequencerUptimeFeed_Constants:test_InitialState() (gas: 8530) -ArbitrumSequencerUptimeFeed_ProtectReadsOnAggregatorV2V3InterfaceFunctions:test_AggregatorV2V3InterfaceAllowReadsIfConsumingContractIsWhitelisted() (gas: 604414) -ArbitrumSequencerUptimeFeed_ProtectReadsOnAggregatorV2V3InterfaceFunctions:test_AggregatorV2V3InterfaceDisallowReadsIfConsumingContractIsNotWhitelisted() (gas: 574476) -ArbitrumSequencerUptimeFeed_UpdateStatus:test_IgnoreOutOfOrderUpdates() (gas: 99662) -ArbitrumSequencerUptimeFeed_UpdateStatus:test_RevertIfNotL2CrossDomainMessengerAddr() (gas: 15424) -ArbitrumSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenStatusChangeAndNoTimeChange() (gas: 114647) -ArbitrumSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenStatusChangeAndTimeChange() (gas: 114707) +ArbitrumCrossDomainGovernor_ForwardDelegate:test_RevertsBatchWhenOneCallFails() (gas: 64368) +ArbitrumCrossDomainGovernor_TransferL1Ownership:test_CallableByL1Owner() (gas: 41453) +ArbitrumCrossDomainGovernor_TransferL1Ownership:test_CallableByL1OwnerOrZeroAddress() (gas: 19290) +ArbitrumCrossDomainGovernor_TransferL1Ownership:test_NotCallableByL2Owner() (gas: 18637) +ArbitrumCrossDomainGovernor_TransferL1Ownership:test_NotCallableByNonOwners() (gas: 13242) +ArbitrumSequencerUptimeFeed_AggregatorV3Interface:test_AggregatorV3Interface() (gas: 104862) +ArbitrumSequencerUptimeFeed_AggregatorV3Interface:test_Return0WhenRoundDoesNotExistYet() (gas: 19967) +ArbitrumSequencerUptimeFeed_Constants:test_InitialState() (gas: 8518) +ArbitrumSequencerUptimeFeed_ProtectReadsOnAggregatorV2V3InterfaceFunctions:test_AggregatorV2V3InterfaceAllowReadsIfConsumingContractIsWhitelisted() (gas: 604370) +ArbitrumSequencerUptimeFeed_ProtectReadsOnAggregatorV2V3InterfaceFunctions:test_AggregatorV2V3InterfaceDisallowReadsIfConsumingContractIsNotWhitelisted() (gas: 574432) +ArbitrumSequencerUptimeFeed_UpdateStatus:test_IgnoreOutOfOrderUpdates() (gas: 99629) +ArbitrumSequencerUptimeFeed_UpdateStatus:test_RevertIfNotL2CrossDomainMessengerAddr() (gas: 15447) +ArbitrumSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenStatusChangeAndNoTimeChange() (gas: 114625) +ArbitrumSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenStatusChangeAndTimeChange() (gas: 114708) ArbitrumValidator_Validate:test_PostSequencerOffline() (gas: 69086) -OptimismCrossDomainForwarder_AcceptL1Ownership:test_CallableByPendingL1Owner() (gas: 47206) +OptimismCrossDomainForwarder_AcceptL1Ownership:test_CallableByPendingL1Owner() (gas: 47160) OptimismCrossDomainForwarder_AcceptL1Ownership:test_NotCallableByNonPendingOwners() (gas: 22160) -OptimismCrossDomainForwarder_Constructor:test_InitialState() (gas: 22031) +OptimismCrossDomainForwarder_Constructor:test_InitialState() (gas: 21998) OptimismCrossDomainForwarder_Forward:test_Forward() (gas: 58281) OptimismCrossDomainForwarder_Forward:test_ForwardRevert() (gas: 32560) OptimismCrossDomainForwarder_Forward:test_NotCallableByUnknownAddress() (gas: 13867) -OptimismCrossDomainForwarder_TransferL1Ownership:test_CallableByL1Owner() (gas: 48910) -OptimismCrossDomainForwarder_TransferL1Ownership:test_CallableByL1OwnerOrZeroAddress() (gas: 28775) -OptimismCrossDomainForwarder_TransferL1Ownership:test_NotCallableByL2Owner() (gas: 16482) -OptimismCrossDomainForwarder_TransferL1Ownership:test_NotCallableByNonOwners() (gas: 11030) -OptimismCrossDomainGovernor_AcceptL1Ownership:test_CallableByPendingL1Owner() (gas: 47206) +OptimismCrossDomainForwarder_TransferL1Ownership:test_CallableByL1Owner() (gas: 48933) +OptimismCrossDomainForwarder_TransferL1Ownership:test_CallableByL1OwnerOrZeroAddress() (gas: 28753) +OptimismCrossDomainForwarder_TransferL1Ownership:test_NotCallableByL2Owner() (gas: 16448) +OptimismCrossDomainForwarder_TransferL1Ownership:test_NotCallableByNonOwners() (gas: 11053) +OptimismCrossDomainGovernor_AcceptL1Ownership:test_CallableByPendingL1Owner() (gas: 47160) OptimismCrossDomainGovernor_AcceptL1Ownership:test_NotCallableByNonPendingOwners() (gas: 22160) -OptimismCrossDomainGovernor_Constructor:test_InitialState() (gas: 22054) -OptimismCrossDomainGovernor_Forward:test_CallableByL2Owner() (gas: 47823) -OptimismCrossDomainGovernor_Forward:test_Forward() (gas: 58352) -OptimismCrossDomainGovernor_Forward:test_ForwardRevert() (gas: 32641) -OptimismCrossDomainGovernor_Forward:test_NotCallableByUnknownAddress() (gas: 16069) +OptimismCrossDomainGovernor_Constructor:test_InitialState() (gas: 22021) +OptimismCrossDomainGovernor_Forward:test_CallableByL2Owner() (gas: 47846) +OptimismCrossDomainGovernor_Forward:test_Forward() (gas: 58330) +OptimismCrossDomainGovernor_Forward:test_ForwardRevert() (gas: 32619) +OptimismCrossDomainGovernor_Forward:test_NotCallableByUnknownAddress() (gas: 16047) OptimismCrossDomainGovernor_ForwardDelegate:test_BubbleUpRevert() (gas: 29189) OptimismCrossDomainGovernor_ForwardDelegate:test_CallableByCrossDomainMessengerAddressOrL1Owner() (gas: 72942) -OptimismCrossDomainGovernor_ForwardDelegate:test_CallableByL2Owner() (gas: 72924) +OptimismCrossDomainGovernor_ForwardDelegate:test_CallableByL2Owner() (gas: 72947) OptimismCrossDomainGovernor_ForwardDelegate:test_NotCallableByUnknownAddress() (gas: 16059) -OptimismCrossDomainGovernor_ForwardDelegate:test_RevertsBatchWhenOneCallFails() (gas: 76167) -OptimismCrossDomainGovernor_TransferL1Ownership:test_CallableByL1Owner() (gas: 48910) -OptimismCrossDomainGovernor_TransferL1Ownership:test_CallableByL1OwnerOrZeroAddress() (gas: 28775) -OptimismCrossDomainGovernor_TransferL1Ownership:test_NotCallableByL2Owner() (gas: 16482) -OptimismCrossDomainGovernor_TransferL1Ownership:test_NotCallableByNonOwners() (gas: 11030) -OptimismSequencerUptimeFeed_AggregatorV3Interface:test_AggregatorV3Interface() (gas: 74304) -OptimismSequencerUptimeFeed_AggregatorV3Interface:test_RevertGetAnswerWhenRoundDoesNotExistYet() (gas: 17679) -OptimismSequencerUptimeFeed_AggregatorV3Interface:test_RevertGetRoundDataWhenRoundDoesNotExistYet() (gas: 17897) -OptimismSequencerUptimeFeed_AggregatorV3Interface:test_RevertGetTimestampWhenRoundDoesNotExistYet() (gas: 17603) -OptimismSequencerUptimeFeed_Constructor:test_InitialState() (gas: 22110) -OptimismSequencerUptimeFeed_ProtectReadsOnAggregatorV2V3InterfaceFunctions:test_AggregatorV2V3InterfaceAllowReadsIfConsumingContractIsWhitelisted() (gas: 601843) -OptimismSequencerUptimeFeed_ProtectReadsOnAggregatorV2V3InterfaceFunctions:test_AggregatorV2V3InterfaceDisallowReadsIfConsumingContractIsNotWhitelisted() (gas: 574481) -OptimismSequencerUptimeFeed_UpdateStatus:test_IgnoreOutOfOrderUpdates() (gas: 69730) -OptimismSequencerUptimeFeed_UpdateStatus:test_RevertIfNotL2CrossDomainMessengerAddr() (gas: 13214) -OptimismSequencerUptimeFeed_UpdateStatus:test_RevertIfNotL2CrossDomainMessengerAddrAndNotL1SenderAddr() (gas: 23632) -OptimismSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenNoChange() (gas: 79637) -OptimismSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenStatusChangeAndNoTimeChange() (gas: 100045) -OptimismSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenStatusChangeAndTimeChange() (gas: 100105) -OptimismValidator_SetGasLimit:test_CorrectlyUpdatesTheGasLimit() (gas: 18695) -OptimismValidator_Validate:test_PostSequencerOffline() (gas: 74813) +OptimismCrossDomainGovernor_ForwardDelegate:test_RevertsBatchWhenOneCallFails() (gas: 76156) +OptimismCrossDomainGovernor_TransferL1Ownership:test_CallableByL1Owner() (gas: 48933) +OptimismCrossDomainGovernor_TransferL1Ownership:test_CallableByL1OwnerOrZeroAddress() (gas: 28753) +OptimismCrossDomainGovernor_TransferL1Ownership:test_NotCallableByL2Owner() (gas: 16448) +OptimismCrossDomainGovernor_TransferL1Ownership:test_NotCallableByNonOwners() (gas: 11053) +OptimismSequencerUptimeFeed_AggregatorV3Interface:test_AggregatorV3Interface() (gas: 72400) +OptimismSequencerUptimeFeed_AggregatorV3Interface:test_RevertGetAnswerWhenRoundDoesNotExistYet() (gas: 17653) +OptimismSequencerUptimeFeed_AggregatorV3Interface:test_RevertGetRoundDataWhenRoundDoesNotExistYet() (gas: 17893) +OptimismSequencerUptimeFeed_AggregatorV3Interface:test_RevertGetTimestampWhenRoundDoesNotExistYet() (gas: 17642) +OptimismSequencerUptimeFeed_Constructor:test_InitialState() (gas: 22050) +OptimismSequencerUptimeFeed_ProtectReadsOnAggregatorV2V3InterfaceFunctions:test_AggregatorV2V3InterfaceAllowReadsIfConsumingContractIsWhitelisted() (gas: 601594) +OptimismSequencerUptimeFeed_ProtectReadsOnAggregatorV2V3InterfaceFunctions:test_AggregatorV2V3InterfaceDisallowReadsIfConsumingContractIsNotWhitelisted() (gas: 574437) +OptimismSequencerUptimeFeed_UpdateStatus:test_IgnoreOutOfOrderUpdates() (gas: 67873) +OptimismSequencerUptimeFeed_UpdateStatus:test_RevertIfNotL2CrossDomainMessengerAddr() (gas: 13079) +OptimismSequencerUptimeFeed_UpdateStatus:test_RevertIfNotL2CrossDomainMessengerAddrAndNotL1SenderAddr() (gas: 23542) +OptimismSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenNoChange() (gas: 77322) +OptimismSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenStatusChangeAndNoTimeChange() (gas: 96182) +OptimismSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenStatusChangeAndTimeChange() (gas: 96265) +OptimismValidator_SetGasLimit:test_CorrectlyUpdatesTheGasLimit() (gas: 18671) +OptimismValidator_Validate:test_PostSequencerOffline() (gas: 74790) OptimismValidator_Validate:test_PostSequencerStatusWhenThereIsNotStatusChange() (gas: 74869) OptimismValidator_Validate:test_RevertsIfCalledByAnAccountWithNoAccess() (gas: 15571) -ScrollCrossDomainForwarder_AcceptL1Ownership:test_CallableByPendingL1Owner() (gas: 47300) +ScrollCrossDomainForwarder_AcceptL1Ownership:test_CallableByPendingL1Owner() (gas: 47255) ScrollCrossDomainForwarder_AcceptL1Ownership:test_NotCallableByNonPendingOwners() (gas: 22212) -ScrollCrossDomainForwarder_Constructor:test_InitialState() (gas: 21707) +ScrollCrossDomainForwarder_Constructor:test_InitialState() (gas: 21674) ScrollCrossDomainForwarder_Forward:test_Forward() (gas: 58348) ScrollCrossDomainForwarder_Forward:test_ForwardRevert() (gas: 32618) ScrollCrossDomainForwarder_Forward:test_NotCallableByUnknownAddress() (gas: 13867) -ScrollCrossDomainForwarder_TransferL1Ownership:test_CallableByL1Owner() (gas: 48976) -ScrollCrossDomainForwarder_TransferL1Ownership:test_CallableByL1OwnerOrZeroAddress() (gas: 28841) -ScrollCrossDomainForwarder_TransferL1Ownership:test_NotCallableByL2Owner() (gas: 16482) -ScrollCrossDomainForwarder_TransferL1Ownership:test_NotCallableByNonOwners() (gas: 11030) -ScrollCrossDomainGovernor_AcceptL1Ownership:test_CallableByPendingL1Owner() (gas: 47300) +ScrollCrossDomainForwarder_TransferL1Ownership:test_CallableByL1Owner() (gas: 48999) +ScrollCrossDomainForwarder_TransferL1Ownership:test_CallableByL1OwnerOrZeroAddress() (gas: 28819) +ScrollCrossDomainForwarder_TransferL1Ownership:test_NotCallableByL2Owner() (gas: 16448) +ScrollCrossDomainForwarder_TransferL1Ownership:test_NotCallableByNonOwners() (gas: 11053) +ScrollCrossDomainGovernor_AcceptL1Ownership:test_CallableByPendingL1Owner() (gas: 47255) ScrollCrossDomainGovernor_AcceptL1Ownership:test_NotCallableByNonPendingOwners() (gas: 22212) -ScrollCrossDomainGovernor_Constructor:test_InitialState() (gas: 21730) -ScrollCrossDomainGovernor_Forward:test_CallableByL2Owner() (gas: 47818) -ScrollCrossDomainGovernor_Forward:test_Forward() (gas: 58414) -ScrollCrossDomainGovernor_Forward:test_ForwardRevert() (gas: 32696) -ScrollCrossDomainGovernor_Forward:test_NotCallableByUnknownAddress() (gas: 16066) +ScrollCrossDomainGovernor_Constructor:test_InitialState() (gas: 21697) +ScrollCrossDomainGovernor_Forward:test_CallableByL2Owner() (gas: 47841) +ScrollCrossDomainGovernor_Forward:test_Forward() (gas: 58392) +ScrollCrossDomainGovernor_Forward:test_ForwardRevert() (gas: 32674) +ScrollCrossDomainGovernor_Forward:test_NotCallableByUnknownAddress() (gas: 16044) ScrollCrossDomainGovernor_ForwardDelegate:test_BubbleUpRevert() (gas: 29250) ScrollCrossDomainGovernor_ForwardDelegate:test_CallableByCrossDomainMessengerAddressOrL1Owner() (gas: 73009) -ScrollCrossDomainGovernor_ForwardDelegate:test_CallableByL2Owner() (gas: 72991) +ScrollCrossDomainGovernor_ForwardDelegate:test_CallableByL2Owner() (gas: 73014) ScrollCrossDomainGovernor_ForwardDelegate:test_NotCallableByUnknownAddress() (gas: 16056) -ScrollCrossDomainGovernor_ForwardDelegate:test_RevertsBatchWhenOneCallFails() (gas: 76235) -ScrollCrossDomainGovernor_TransferL1Ownership:test_CallableByL1Owner() (gas: 48976) -ScrollCrossDomainGovernor_TransferL1Ownership:test_CallableByL1OwnerOrZeroAddress() (gas: 28841) -ScrollCrossDomainGovernor_TransferL1Ownership:test_NotCallableByL2Owner() (gas: 16482) -ScrollCrossDomainGovernor_TransferL1Ownership:test_NotCallableByNonOwners() (gas: 11030) -ScrollSequencerUptimeFeed_AggregatorV3Interface:test_AggregatorV3Interface() (gas: 72590) -ScrollSequencerUptimeFeed_AggregatorV3Interface:test_RevertGetAnswerWhenRoundDoesNotExistYet() (gas: 17675) +ScrollCrossDomainGovernor_ForwardDelegate:test_RevertsBatchWhenOneCallFails() (gas: 76224) +ScrollCrossDomainGovernor_TransferL1Ownership:test_CallableByL1Owner() (gas: 48999) +ScrollCrossDomainGovernor_TransferL1Ownership:test_CallableByL1OwnerOrZeroAddress() (gas: 28819) +ScrollCrossDomainGovernor_TransferL1Ownership:test_NotCallableByL2Owner() (gas: 16448) +ScrollCrossDomainGovernor_TransferL1Ownership:test_NotCallableByNonOwners() (gas: 11053) +ScrollSequencerUptimeFeed_AggregatorV3Interface:test_AggregatorV3Interface() (gas: 72423) +ScrollSequencerUptimeFeed_AggregatorV3Interface:test_RevertGetAnswerWhenRoundDoesNotExistYet() (gas: 17653) ScrollSequencerUptimeFeed_AggregatorV3Interface:test_RevertGetRoundDataWhenRoundDoesNotExistYet() (gas: 17893) -ScrollSequencerUptimeFeed_AggregatorV3Interface:test_RevertGetTimestampWhenRoundDoesNotExistYet() (gas: 17599) -ScrollSequencerUptimeFeed_Constructor:test_InitialState() (gas: 103508) -ScrollSequencerUptimeFeed_ProtectReadsOnAggregatorV2V3InterfaceFunctions:test_AggregatorV2V3InterfaceAllowReadsIfConsumingContractIsWhitelisted() (gas: 601694) -ScrollSequencerUptimeFeed_ProtectReadsOnAggregatorV2V3InterfaceFunctions:test_AggregatorV2V3InterfaceDisallowReadsIfConsumingContractIsNotWhitelisted() (gas: 574481) -ScrollSequencerUptimeFeed_UpdateStatus:test_IgnoreOutOfOrderUpdates() (gas: 67615) -ScrollSequencerUptimeFeed_UpdateStatus:test_RevertIfNotL2CrossDomainMessengerAddr() (gas: 13214) -ScrollSequencerUptimeFeed_UpdateStatus:test_RevertIfNotL2CrossDomainMessengerAddrAndNotL1SenderAddr() (gas: 23632) -ScrollSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenNoChange() (gas: 77220) -ScrollSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenStatusChangeAndNoTimeChange() (gas: 95908) -ScrollSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenStatusChangeAndTimeChange() (gas: 95968) -ScrollValidator_SetGasLimit:test_CorrectlyUpdatesTheGasLimit() (gas: 18829) -ScrollValidator_Validate:test_PostSequencerOffline() (gas: 78349) +ScrollSequencerUptimeFeed_AggregatorV3Interface:test_RevertGetTimestampWhenRoundDoesNotExistYet() (gas: 17642) +ScrollSequencerUptimeFeed_Constructor:test_InitialState() (gas: 173935) +ScrollSequencerUptimeFeed_ProtectReadsOnAggregatorV2V3InterfaceFunctions:test_AggregatorV2V3InterfaceAllowReadsIfConsumingContractIsWhitelisted() (gas: 601594) +ScrollSequencerUptimeFeed_ProtectReadsOnAggregatorV2V3InterfaceFunctions:test_AggregatorV2V3InterfaceDisallowReadsIfConsumingContractIsNotWhitelisted() (gas: 574437) +ScrollSequencerUptimeFeed_UpdateStatus:test_IgnoreOutOfOrderUpdates() (gas: 67919) +ScrollSequencerUptimeFeed_UpdateStatus:test_RevertIfNotL2CrossDomainMessengerAddr() (gas: 13079) +ScrollSequencerUptimeFeed_UpdateStatus:test_RevertIfNotL2CrossDomainMessengerAddrAndNotL1SenderAddr() (gas: 23542) +ScrollSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenNoChange() (gas: 77368) +ScrollSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenStatusChangeAndNoTimeChange() (gas: 96228) +ScrollSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenStatusChangeAndTimeChange() (gas: 96311) +ScrollValidator_SetGasLimit:test_CorrectlyUpdatesTheGasLimit() (gas: 18805) +ScrollValidator_Validate:test_PostSequencerOffline() (gas: 78326) ScrollValidator_Validate:test_PostSequencerStatusWhenThereIsNotStatusChange() (gas: 78411) -ScrollValidator_Validate:test_RevertsIfCalledByAnAccountWithNoAccess() (gas: 15571) \ No newline at end of file +ScrollValidator_Validate:test_RevertsIfCalledByAnAccountWithNoAccess() (gas: 15571) +ZKSyncSequencerUptimeFeed_AggregatorV3Interface:test_AggregatorV3Interface() (gas: 67166) +ZKSyncSequencerUptimeFeed_AggregatorV3Interface:test_RevertGetAnswerWhenRoundDoesNotExistYet() (gas: 17653) +ZKSyncSequencerUptimeFeed_AggregatorV3Interface:test_RevertGetRoundDataWhenRoundDoesNotExistYet() (gas: 17893) +ZKSyncSequencerUptimeFeed_AggregatorV3Interface:test_RevertGetTimestampWhenRoundDoesNotExistYet() (gas: 17642) +ZKSyncSequencerUptimeFeed_Constructor:test_InitialState() (gas: 22054) +ZKSyncSequencerUptimeFeed_ProtectReadsOnAggregatorV2V3InterfaceFunctions:test_AggregatorV2V3InterfaceAllowReadsIfConsumingContractIsWhitelisted() (gas: 601614) +ZKSyncSequencerUptimeFeed_ProtectReadsOnAggregatorV2V3InterfaceFunctions:test_AggregatorV2V3InterfaceDisallowReadsIfConsumingContractIsNotWhitelisted() (gas: 574443) +ZKSyncSequencerUptimeFeed_UpdateStatus:test_IgnoreOutOfOrderUpdates() (gas: 61924) +ZKSyncSequencerUptimeFeed_UpdateStatus:test_RevertIfNotL2CrossDomainMessengerAddr() (gas: 13035) +ZKSyncSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenNoChange() (gas: 71379) +ZKSyncSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenStatusChangeAndNoTimeChange() (gas: 90241) +ZKSyncSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenStatusChangeAndTimeChange() (gas: 90324) +ZKSyncValidator_Constructor:test_ConstructingRevertedWithInvalidChainId() (gas: 103725) +ZKSyncValidator_Constructor:test_ConstructingRevertedWithZeroL1BridgeAddress() (gas: 81440) +ZKSyncValidator_Constructor:test_ConstructingRevertedWithZeroL2UpdateFeedAddress() (gas: 81497) +ZKSyncValidator_GetChainId:test_CorrectlyGetsTheChainId() (gas: 8350) +ZKSyncValidator_GetSetL2GasPerPubdataByteLimit:test_CorrectlyGetsAndUpdatesTheGasPerPubdataByteLimit() (gas: 18915) +ZKSyncValidator_Validate:test_PostSequencerOffline() (gas: 52255) +ZKSyncValidator_Validate:test_PostSequencerStatusWhenThereIsNotStatusChange() (gas: 52355) +ZKSyncValidator_Validate:test_RevertsIfCalledByAnAccountWithNoAccess() (gas: 15644) \ No newline at end of file diff --git a/contracts/gas-snapshots/llo-feeds.gas-snapshot b/contracts/gas-snapshots/llo-feeds.gas-snapshot index 187d3cd89f..231bff7c9d 100644 --- a/contracts/gas-snapshots/llo-feeds.gas-snapshot +++ b/contracts/gas-snapshots/llo-feeds.gas-snapshot @@ -18,6 +18,233 @@ ByteUtilTest:test_readUint32MultiWord() (gas: 3393) ByteUtilTest:test_readUint32WithEmptyArray() (gas: 3253) ByteUtilTest:test_readUint32WithNotEnoughBytes() (gas: 3272) ByteUtilTest:test_readZeroAddress() (gas: 3365) +ChannelConfigStoreTest:testSetChannelDefinitions() (gas: 46927) +ChannelConfigStoreTest:testSupportsInterface() (gas: 8367) +ChannelConfigStoreTest:testTypeAndVersion() (gas: 9621) +DestinationFeeManagerProcessFeeTest:test_DiscountIsAppliedForNative() (gas: 52717) +DestinationFeeManagerProcessFeeTest:test_DiscountIsReturnedForNative() (gas: 52645) +DestinationFeeManagerProcessFeeTest:test_DiscountIsReturnedForNativeWithSurcharge() (gas: 78879) +DestinationFeeManagerProcessFeeTest:test_GlobalDiscountCantBeSetToMoreThanMaximum() (gas: 19544) +DestinationFeeManagerProcessFeeTest:test_GlobalDiscountIsOverridenByIndividualDiscountLink() (gas: 79509) +DestinationFeeManagerProcessFeeTest:test_GlobalDiscountIsOverridenByIndividualDiscountNative() (gas: 82523) +DestinationFeeManagerProcessFeeTest:test_GlobalDiscountIsUpdatedAfterBeingSetToZeroLink() (gas: 48409) +DestinationFeeManagerProcessFeeTest:test_GlobalDiscountIsUpdatedAfterBeingSetToZeroNative() (gas: 51589) +DestinationFeeManagerProcessFeeTest:test_GlobalDiscountWithLink() (gas: 51902) +DestinationFeeManagerProcessFeeTest:test_GlobalDiscountWithNative() (gas: 54892) +DestinationFeeManagerProcessFeeTest:test_GlobalDiscountWithNativeAndLink() (gas: 83739) +DestinationFeeManagerProcessFeeTest:test_V1PayloadVerifies() (gas: 29280) +DestinationFeeManagerProcessFeeTest:test_V1PayloadVerifiesAndReturnsChange() (gas: 61209) +DestinationFeeManagerProcessFeeTest:test_V2PayloadVerifies() (gas: 123469) +DestinationFeeManagerProcessFeeTest:test_V2PayloadWithoutQuoteFails() (gas: 29691) +DestinationFeeManagerProcessFeeTest:test_V2PayloadWithoutZeroFee() (gas: 77080) +DestinationFeeManagerProcessFeeTest:test_WithdrawERC20() (gas: 72819) +DestinationFeeManagerProcessFeeTest:test_WithdrawNonAdminAddr() (gas: 56357) +DestinationFeeManagerProcessFeeTest:test_WithdrawUnwrappedNative() (gas: 26434) +DestinationFeeManagerProcessFeeTest:test_addVerifier() (gas: 128899) +DestinationFeeManagerProcessFeeTest:test_addVerifierExistingAddress() (gas: 34192) +DestinationFeeManagerProcessFeeTest:test_baseFeeIsAppliedForLink() (gas: 19497) +DestinationFeeManagerProcessFeeTest:test_baseFeeIsAppliedForNative() (gas: 22502) +DestinationFeeManagerProcessFeeTest:test_correctDiscountIsAppliedWhenBothTokensAreDiscounted() (gas: 91133) +DestinationFeeManagerProcessFeeTest:test_discountAIsNotAppliedWhenSetForOtherUsers() (gas: 58864) +DestinationFeeManagerProcessFeeTest:test_discountFeeRoundsDownWhenUneven() (gas: 52919) +DestinationFeeManagerProcessFeeTest:test_discountIsAppliedForLink() (gas: 49730) +DestinationFeeManagerProcessFeeTest:test_discountIsAppliedWith100PercentSurcharge() (gas: 78908) +DestinationFeeManagerProcessFeeTest:test_discountIsNoLongerAppliedAfterRemoving() (gas: 48362) +DestinationFeeManagerProcessFeeTest:test_discountIsNotAppliedForInvalidTokenAddress() (gas: 17516) +DestinationFeeManagerProcessFeeTest:test_discountIsNotAppliedToOtherFeeds() (gas: 56912) +DestinationFeeManagerProcessFeeTest:test_discountIsReturnedForLink() (gas: 49657) +DestinationFeeManagerProcessFeeTest:test_emptyQuoteRevertsWithError() (gas: 12253) +DestinationFeeManagerProcessFeeTest:test_eventIsEmittedAfterSurchargeIsSet() (gas: 41379) +DestinationFeeManagerProcessFeeTest:test_eventIsEmittedIfNotEnoughLink() (gas: 182671) +DestinationFeeManagerProcessFeeTest:test_eventIsEmittedUponWithdraw() (gas: 69080) +DestinationFeeManagerProcessFeeTest:test_feeIsUpdatedAfterDiscountIsRemoved() (gas: 51626) +DestinationFeeManagerProcessFeeTest:test_feeIsUpdatedAfterNewDiscountIsApplied() (gas: 67777) +DestinationFeeManagerProcessFeeTest:test_feeIsUpdatedAfterNewSurchargeIsApplied() (gas: 66960) +DestinationFeeManagerProcessFeeTest:test_feeIsZeroWith100PercentDiscount() (gas: 52073) +DestinationFeeManagerProcessFeeTest:test_getBaseRewardWithLinkQuote() (gas: 19495) +DestinationFeeManagerProcessFeeTest:test_getLinkFeeIsRoundedUp() (gas: 49857) +DestinationFeeManagerProcessFeeTest:test_getLinkRewardIsSameAsFee() (gas: 55762) +DestinationFeeManagerProcessFeeTest:test_getLinkRewardWithNativeQuoteAndSurchargeWithLinkDiscount() (gas: 85050) +DestinationFeeManagerProcessFeeTest:test_getRewardWithLinkDiscount() (gas: 49726) +DestinationFeeManagerProcessFeeTest:test_getRewardWithLinkQuoteAndLinkDiscount() (gas: 49685) +DestinationFeeManagerProcessFeeTest:test_getRewardWithNativeQuote() (gas: 22456) +DestinationFeeManagerProcessFeeTest:test_getRewardWithNativeQuoteAndSurcharge() (gas: 53190) +DestinationFeeManagerProcessFeeTest:test_linkAvailableForPaymentReturnsLinkBalance() (gas: 53194) +DestinationFeeManagerProcessFeeTest:test_nativeSurcharge0Percent() (gas: 33198) +DestinationFeeManagerProcessFeeTest:test_nativeSurcharge100Percent() (gas: 53170) +DestinationFeeManagerProcessFeeTest:test_nativeSurchargeCannotExceed100Percent() (gas: 17152) +DestinationFeeManagerProcessFeeTest:test_nativeSurchargeEventIsEmittedOnUpdate() (gas: 41357) +DestinationFeeManagerProcessFeeTest:test_noFeeIsAppliedWhenReportHasZeroFee() (gas: 51918) +DestinationFeeManagerProcessFeeTest:test_noFeeIsAppliedWhenReportHasZeroFeeAndDiscountAndSurchargeIsSet() (gas: 78108) +DestinationFeeManagerProcessFeeTest:test_nonAdminProxyUserCannotProcessFee() (gas: 24141) +DestinationFeeManagerProcessFeeTest:test_nonAdminUserCanNotSetDiscount() (gas: 19784) +DestinationFeeManagerProcessFeeTest:test_onlyCallableByOwnerReverts() (gas: 15475) +DestinationFeeManagerProcessFeeTest:test_onlyOwnerCanSetGlobalDiscount() (gas: 19929) +DestinationFeeManagerProcessFeeTest:test_payLinkDeficit() (gas: 199884) +DestinationFeeManagerProcessFeeTest:test_payLinkDeficitOnlyCallableByAdmin() (gas: 17348) +DestinationFeeManagerProcessFeeTest:test_payLinkDeficitPaysAllFeesProcessed() (gas: 221262) +DestinationFeeManagerProcessFeeTest:test_payLinkDeficitTwice() (gas: 204204) +DestinationFeeManagerProcessFeeTest:test_poolIdsCannotBeZeroAddress() (gas: 117907) +DestinationFeeManagerProcessFeeTest:test_processFeeAsProxy() (gas: 123807) +DestinationFeeManagerProcessFeeTest:test_processFeeDefaultReportsStillVerifiesWithEmptyQuote() (gas: 29767) +DestinationFeeManagerProcessFeeTest:test_processFeeEmitsEventIfNotEnoughLink() (gas: 167721) +DestinationFeeManagerProcessFeeTest:test_processFeeIfSubscriberIsSelf() (gas: 32607) +DestinationFeeManagerProcessFeeTest:test_processFeeNative() (gas: 180514) +DestinationFeeManagerProcessFeeTest:test_processFeeUsesCorrectDigest() (gas: 125076) +DestinationFeeManagerProcessFeeTest:test_processFeeWithDefaultReportPayloadAndQuoteStillVerifies() (gas: 31844) +DestinationFeeManagerProcessFeeTest:test_processFeeWithDiscountEmitsEvent() (gas: 245978) +DestinationFeeManagerProcessFeeTest:test_processFeeWithInvalidReportVersionFailsToDecode() (gas: 30814) +DestinationFeeManagerProcessFeeTest:test_processFeeWithNoDiscountDoesNotEmitEvent() (gas: 173419) +DestinationFeeManagerProcessFeeTest:test_processFeeWithUnwrappedNative() (gas: 188379) +DestinationFeeManagerProcessFeeTest:test_processFeeWithUnwrappedNativeLinkAddress() (gas: 138180) +DestinationFeeManagerProcessFeeTest:test_processFeeWithUnwrappedNativeLinkAddressExcessiveFee() (gas: 163791) +DestinationFeeManagerProcessFeeTest:test_processFeeWithUnwrappedNativeShortFunds() (gas: 97147) +DestinationFeeManagerProcessFeeTest:test_processFeeWithUnwrappedNativeWithExcessiveFee() (gas: 195364) +DestinationFeeManagerProcessFeeTest:test_processFeeWithWithCorruptQuotePayload() (gas: 77390) +DestinationFeeManagerProcessFeeTest:test_processFeeWithWithEmptyQuotePayload() (gas: 30028) +DestinationFeeManagerProcessFeeTest:test_processFeeWithWithZeroQuotePayload() (gas: 30078) +DestinationFeeManagerProcessFeeTest:test_processFeeWithZeroLinkNonZeroNativeWithLinkQuote() (gas: 37626) +DestinationFeeManagerProcessFeeTest:test_processFeeWithZeroLinkNonZeroNativeWithNativeQuote() (gas: 160391) +DestinationFeeManagerProcessFeeTest:test_processFeeWithZeroNativeNonZeroLinkReturnsChange() (gas: 58387) +DestinationFeeManagerProcessFeeTest:test_processFeeWithZeroNativeNonZeroLinkWithLinkQuote() (gas: 123718) +DestinationFeeManagerProcessFeeTest:test_processFeeWithZeroNativeNonZeroLinkWithNativeQuote() (gas: 40330) +DestinationFeeManagerProcessFeeTest:test_processMultipleLinkReports() (gas: 233880) +DestinationFeeManagerProcessFeeTest:test_processMultipleUnwrappedNativeReports() (gas: 267669) +DestinationFeeManagerProcessFeeTest:test_processMultipleV1Reports() (gas: 81177) +DestinationFeeManagerProcessFeeTest:test_processMultipleWrappedNativeReports() (gas: 250518) +DestinationFeeManagerProcessFeeTest:test_processPoolIdsPassedMismatched() (gas: 98815) +DestinationFeeManagerProcessFeeTest:test_processV1V2V3Reports() (gas: 218585) +DestinationFeeManagerProcessFeeTest:test_processV1V2V3ReportsWithUnwrapped() (gas: 260249) +DestinationFeeManagerProcessFeeTest:test_removeVerifierNonExistentAddress() (gas: 12822) +DestinationFeeManagerProcessFeeTest:test_removeVerifierZeroAaddress() (gas: 10700) +DestinationFeeManagerProcessFeeTest:test_reportWithNoExpiryOrFeeReturnsZero() (gas: 13682) +DestinationFeeManagerProcessFeeTest:test_revertOnSettingAnAddressZeroVerifier() (gas: 10636) +DestinationFeeManagerProcessFeeTest:test_rewardsAreCorrectlySentToEachAssociatedPoolWhenVerifyingInBulk() (gas: 266627) +DestinationFeeManagerProcessFeeTest:test_setDiscountOver100Percent() (gas: 19540) +DestinationFeeManagerProcessFeeTest:test_setRewardManagerZeroAddress() (gas: 10626) +DestinationFeeManagerProcessFeeTest:test_subscriberDiscountEventIsEmittedOnUpdate() (gas: 46285) +DestinationFeeManagerProcessFeeTest:test_surchargeFeeRoundsUpWhenUneven() (gas: 53501) +DestinationFeeManagerProcessFeeTest:test_surchargeIsApplied() (gas: 53426) +DestinationFeeManagerProcessFeeTest:test_surchargeIsAppliedForNativeFeeWithDiscount() (gas: 79315) +DestinationFeeManagerProcessFeeTest:test_surchargeIsNoLongerAppliedAfterRemoving() (gas: 49149) +DestinationFeeManagerProcessFeeTest:test_surchargeIsNotAppliedForLinkFee() (gas: 52223) +DestinationFeeManagerProcessFeeTest:test_surchargeIsNotAppliedWith100PercentDiscount() (gas: 78311) +DestinationFeeManagerProcessFeeTest:test_testRevertIfReportHasExpired() (gas: 14987) +DestinationRewardManagerClaimTest:test_claimAllRecipients() (gas: 277223) +DestinationRewardManagerClaimTest:test_claimMultipleRecipients() (gas: 154387) +DestinationRewardManagerClaimTest:test_claimRewardsWithDuplicatePoolIdsDoesNotPayoutTwice() (gas: 330244) +DestinationRewardManagerClaimTest:test_claimSingleRecipient() (gas: 89047) +DestinationRewardManagerClaimTest:test_claimUnevenAmountRoundsDown() (gas: 315447) +DestinationRewardManagerClaimTest:test_claimUnregisteredPoolId() (gas: 35168) +DestinationRewardManagerClaimTest:test_claimUnregisteredRecipient() (gas: 41205) +DestinationRewardManagerClaimTest:test_eventIsEmittedUponClaim() (gas: 86092) +DestinationRewardManagerClaimTest:test_eventIsNotEmittedUponUnsuccessfulClaim() (gas: 25054) +DestinationRewardManagerClaimTest:test_recipientsClaimMultipleDeposits() (gas: 386925) +DestinationRewardManagerClaimTest:test_singleRecipientClaimMultipleDeposits() (gas: 137797) +DestinationRewardManagerNoRecipientSet:test_claimAllRecipientsAfterRecipientsSet() (gas: 494460) +DestinationRewardManagerPayRecipientsTest:test_addFundsToPoolAsNonOwnerOrFeeManager() (gas: 11503) +DestinationRewardManagerPayRecipientsTest:test_addFundsToPoolAsOwner() (gas: 53947) +DestinationRewardManagerPayRecipientsTest:test_payAllRecipients() (gas: 253082) +DestinationRewardManagerPayRecipientsTest:test_payAllRecipientsFromNonAdminUser() (gas: 20472) +DestinationRewardManagerPayRecipientsTest:test_payAllRecipientsFromRecipientInPool() (gas: 248964) +DestinationRewardManagerPayRecipientsTest:test_payAllRecipientsWithAdditionalInvalidRecipient() (gas: 264532) +DestinationRewardManagerPayRecipientsTest:test_payAllRecipientsWithAdditionalUnregisteredRecipient() (gas: 268017) +DestinationRewardManagerPayRecipientsTest:test_payRecipientWithInvalidPool() (gas: 31133) +DestinationRewardManagerPayRecipientsTest:test_payRecipientsEmptyRecipientList() (gas: 27554) +DestinationRewardManagerPayRecipientsTest:test_payRecipientsWithInvalidPoolId() (gas: 33639) +DestinationRewardManagerPayRecipientsTest:test_paySingleRecipient() (gas: 86938) +DestinationRewardManagerPayRecipientsTest:test_paySubsetOfRecipientsInPool() (gas: 200719) +DestinationRewardManagerRecipientClaimDifferentWeightsTest:test_allRecipientsClaimingReceiveExpectedAmount() (gas: 280885) +DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_claimAllRecipientsMultiplePools() (gas: 512553) +DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_claimAllRecipientsSinglePool() (gas: 283681) +DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_claimEmptyPoolWhenSecondPoolContainsFunds() (gas: 293533) +DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_claimMultipleRecipientsMultiplePools() (gas: 263107) +DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_claimMultipleRecipientsSinglePool() (gas: 154553) +DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_claimSingleRecipientMultiplePools() (gas: 132669) +DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_claimSingleUniqueRecipient() (gas: 106068) +DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_claimUnevenAmountRoundsDown() (gas: 579848) +DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_claimUnregisteredRecipient() (gas: 64672) +DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_getAvailableRewardsCursorAndTotalPoolsEqual() (gas: 13074) +DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_getAvailableRewardsCursorCannotBeGreaterThanTotalPools() (gas: 12703) +DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_getAvailableRewardsCursorSingleResult() (gas: 22471) +DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_getRewardsAvailableToRecipientInBothPools() (gas: 32248) +DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_getRewardsAvailableToRecipientInBothPoolsWhereAlreadyClaimed() (gas: 148645) +DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_getRewardsAvailableToRecipientInNoPools() (gas: 21728) +DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_getRewardsAvailableToRecipientInSinglePool() (gas: 27765) +DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_recipientsClaimMultipleDeposits() (gas: 391495) +DestinationRewardManagerRecipientClaimMultiplePoolsTest:test_singleRecipientClaimMultipleDeposits() (gas: 137882) +DestinationRewardManagerRecipientClaimUnevenWeightTest:test_allRecipientsClaimingReceiveExpectedAmount() (gas: 199566) +DestinationRewardManagerRecipientClaimUnevenWeightTest:test_allRecipientsClaimingReceiveExpectedAmountWithSmallDeposit() (gas: 219439) +DestinationRewardManagerSetRecipientsTest:test_eventIsEmittedUponSetRecipients() (gas: 193892) +DestinationRewardManagerSetRecipientsTest:test_setRecipientContainsDuplicateRecipients() (gas: 128245) +DestinationRewardManagerSetRecipientsTest:test_setRewardRecipientFromManagerAddress() (gas: 213998) +DestinationRewardManagerSetRecipientsTest:test_setRewardRecipientFromNonOwnerOrFeeManagerAddress() (gas: 21496) +DestinationRewardManagerSetRecipientsTest:test_setRewardRecipientTwice() (gas: 195650) +DestinationRewardManagerSetRecipientsTest:test_setRewardRecipientWeights() (gas: 182793) +DestinationRewardManagerSetRecipientsTest:test_setRewardRecipientWithZeroAddress() (gas: 92387) +DestinationRewardManagerSetRecipientsTest:test_setRewardRecipientWithZeroWeight() (gas: 193497) +DestinationRewardManagerSetRecipientsTest:test_setRewardRecipients() (gas: 187752) +DestinationRewardManagerSetRecipientsTest:test_setRewardRecipientsIsEmpty() (gas: 89276) +DestinationRewardManagerSetRecipientsTest:test_setSingleRewardRecipient() (gas: 112534) +DestinationRewardManagerSetupTest:test_addFeeManagerExistingAddress() (gas: 35281) +DestinationRewardManagerSetupTest:test_addFeeManagerZeroAddress() (gas: 10580) +DestinationRewardManagerSetupTest:test_addRemoveFeeManager() (gas: 48248) +DestinationRewardManagerSetupTest:test_eventEmittedUponFeeManagerUpdate() (gas: 41581) +DestinationRewardManagerSetupTest:test_eventEmittedUponFeePaid() (gas: 261361) +DestinationRewardManagerSetupTest:test_rejectsZeroLinkAddressOnConstruction() (gas: 59481) +DestinationRewardManagerSetupTest:test_removeFeeManagerNonExistentAddress() (gas: 12778) +DestinationRewardManagerSetupTest:test_setFeeManagerZeroAddress() (gas: 17084) +DestinationRewardManagerUpdateRewardRecipientsMultiplePoolsTest:test_updatePrimaryRecipientWeights() (gas: 376742) +DestinationRewardManagerUpdateRewardRecipientsTest:test_eventIsEmittedUponUpdateRecipients() (gas: 280443) +DestinationRewardManagerUpdateRewardRecipientsTest:test_onlyAdminCanUpdateRecipients() (gas: 19705) +DestinationRewardManagerUpdateRewardRecipientsTest:test_partialUpdateRecipientWeights() (gas: 221040) +DestinationRewardManagerUpdateRewardRecipientsTest:test_updateAllRecipientsWithSameAddressAndWeight() (gas: 274265) +DestinationRewardManagerUpdateRewardRecipientsTest:test_updatePartialRecipientsToSubset() (gas: 254188) +DestinationRewardManagerUpdateRewardRecipientsTest:test_updatePartialRecipientsWithExcessiveWeight() (gas: 259175) +DestinationRewardManagerUpdateRewardRecipientsTest:test_updatePartialRecipientsWithSameAddressAndWeight() (gas: 149872) +DestinationRewardManagerUpdateRewardRecipientsTest:test_updatePartialRecipientsWithUnderWeightSet() (gas: 259249) +DestinationRewardManagerUpdateRewardRecipientsTest:test_updateRecipientWeights() (gas: 372223) +DestinationRewardManagerUpdateRewardRecipientsTest:test_updateRecipientWithNewZeroAddress() (gas: 270736) +DestinationRewardManagerUpdateRewardRecipientsTest:test_updateRecipientsContainsDuplicateRecipients() (gas: 288531) +DestinationRewardManagerUpdateRewardRecipientsTest:test_updateRecipientsToDifferentLargerSet() (gas: 407832) +DestinationRewardManagerUpdateRewardRecipientsTest:test_updateRecipientsToDifferentPartialSet() (gas: 317985) +DestinationRewardManagerUpdateRewardRecipientsTest:test_updateRecipientsToDifferentSet() (gas: 377740) +DestinationRewardManagerUpdateRewardRecipientsTest:test_updateRecipientsToDifferentSetWithInvalidWeights() (gas: 312078) +DestinationRewardManagerUpdateRewardRecipientsTest:test_updateRecipientsUpdateAndRemoveExistingForLargerSet() (gas: 399655) +DestinationRewardManagerUpdateRewardRecipientsTest:test_updateRecipientsUpdateAndRemoveExistingForSmallerSet() (gas: 289469) +DestinationVerifierBulkVerifyBillingReport:test_verifyWithBulkLink() (gas: 642599) +DestinationVerifierBulkVerifyBillingReport:test_verifyWithBulkNative() (gas: 643674) +DestinationVerifierBulkVerifyBillingReport:test_verifyWithBulkNativeUnwrapped() (gas: 665238) +DestinationVerifierBulkVerifyBillingReport:test_verifyWithBulkNativeUnwrappedReturnsChange() (gas: 665193) +DestinationVerifierConstructorTest:test_falseIfIsNotCorrectInterface() (gas: 8481) +DestinationVerifierConstructorTest:test_revertsIfInitializedWithEmptyVerifierProxy() (gas: 60885) +DestinationVerifierConstructorTest:test_trueIfIsCorrectInterface() (gas: 9383) +DestinationVerifierConstructorTest:test_typeAndVersion() (gas: 2624729) +DestinationVerifierProxyInitializeVerifierTest:test_correctlySetsTheOwner() (gas: 862226) +DestinationVerifierProxyInitializeVerifierTest:test_correctlySetsVersion() (gas: 9841) +DestinationVerifierProxyInitializeVerifierTest:test_setVerifierCalledByNoOwner() (gas: 17483) +DestinationVerifierProxyInitializeVerifierTest:test_setVerifierOk() (gas: 27727) +DestinationVerifierProxyInitializeVerifierTest:test_setVerifierWhichDoesntHonourInterface() (gas: 16535) +DestinationVerifierSetAccessControllerTest:test_emitsTheCorrectEvent() (gas: 35391) +DestinationVerifierSetAccessControllerTest:test_revertsIfCalledByNonOwner() (gas: 15089) +DestinationVerifierSetAccessControllerTest:test_successfullySetsNewAccessController() (gas: 34885) +DestinationVerifierSetAccessControllerTest:test_successfullySetsNewAccessControllerIsEmpty() (gas: 15007) +DestinationVerifierSetConfigTest:test_NoDonConfigAlreadyExists() (gas: 2877761) +DestinationVerifierSetConfigTest:test_addressesAndWeightsDoNotProduceSideEffectsInDonConfigIds() (gas: 1323254) +DestinationVerifierSetConfigTest:test_donConfigIdIsSameForSignersInDifferentOrder() (gas: 1290451) +DestinationVerifierSetConfigTest:test_removeLatestConfig() (gas: 786161) +DestinationVerifierSetConfigTest:test_removeLatestConfigWhenNoConfigShouldFail() (gas: 12870) +DestinationVerifierSetConfigTest:test_revertsIfCalledByNonOwner() (gas: 174936) +DestinationVerifierSetConfigTest:test_revertsIfDuplicateSigners() (gas: 171299) +DestinationVerifierSetConfigTest:test_revertsIfFaultToleranceIsZero() (gas: 168506) +DestinationVerifierSetConfigTest:test_revertsIfNotEnoughSigners() (gas: 11571) +DestinationVerifierSetConfigTest:test_revertsIfSetWithTooManySigners() (gas: 17943) +DestinationVerifierSetConfigTest:test_revertsIfSignerContainsZeroAddress() (gas: 324006) +DestinationVerifierSetConfigTest:test_setConfigActiveUnknownDonConfigId() (gas: 13124) +DestinationVerifierSetConfigTest:test_setConfigWithActivationTime() (gas: 1088159) +DestinationVerifierSetConfigTest:test_setConfigWithActivationTimeEarlierThanLatestConfigShouldFail() (gas: 1963073) +DestinationVerifierSetConfigTest:test_setConfigWithActivationTimeNoFutureTimeShouldFail() (gas: 259470) +DestinationVerifierSetConfigTest:test_setConfigWithActivationTimeTheSameAsLatestConfigShouldFail() (gas: 1283783) FeeManagerProcessFeeTest:test_DiscountIsAppliedForNative() (gas: 52645) FeeManagerProcessFeeTest:test_DiscountIsReturnedForNative() (gas: 52595) FeeManagerProcessFeeTest:test_DiscountIsReturnedForNativeWithSurcharge() (gas: 78808) @@ -108,6 +335,7 @@ FeeManagerProcessFeeTest:test_surchargeIsNoLongerAppliedAfterRemoving() (gas: 47 FeeManagerProcessFeeTest:test_surchargeIsNotAppliedForLinkFee() (gas: 49938) FeeManagerProcessFeeTest:test_surchargeIsNotAppliedWith100PercentDiscount() (gas: 78261) FeeManagerProcessFeeTest:test_testRevertIfReportHasExpired() (gas: 14919) +MultiVerifierBillingTests:test_multipleFeeManagersAndVerifiers() (gas: 4598487) RewardManagerClaimTest:test_claimAllRecipients() (gas: 277131) RewardManagerClaimTest:test_claimMultipleRecipients() (gas: 154341) RewardManagerClaimTest:test_claimRewardsWithDuplicatePoolIdsDoesNotPayoutTwice() (gas: 330086) @@ -200,6 +428,13 @@ VerifierActivateFeedTest:test_revertsIfNoFeedExistsActivate() (gas: 13179) VerifierActivateFeedTest:test_revertsIfNoFeedExistsDeactivate() (gas: 13157) VerifierActivateFeedTest:test_revertsIfNotOwnerActivateFeed() (gas: 17109) VerifierActivateFeedTest:test_revertsIfNotOwnerDeactivateFeed() (gas: 17164) +VerifierBillingTests:test_rewardsAreDistributedAccordingToWeights() (gas: 1736216) +VerifierBillingTests:test_rewardsAreDistributedAccordingToWeightsMultipleWeigths() (gas: 4468029) +VerifierBillingTests:test_rewardsAreDistributedAccordingToWeightsUsingHistoricalConfigs() (gas: 2106504) +VerifierBillingTests:test_verifyWithLinkV3Report() (gas: 1593617) +VerifierBillingTests:test_verifyWithNativeERC20() (gas: 1467526) +VerifierBillingTests:test_verifyWithNativeUnwrapped() (gas: 1378718) +VerifierBillingTests:test_verifyWithNativeUnwrappedReturnsChange() (gas: 1385764) VerifierBulkVerifyBillingReport:test_verifyMultiVersions() (gas: 476595) VerifierBulkVerifyBillingReport:test_verifyMultiVersionsReturnsVerifiedReports() (gas: 474853) VerifierBulkVerifyBillingReport:test_verifyWithBulkLink() (gas: 557541) @@ -212,6 +447,7 @@ VerifierDeactivateFeedWithVerifyTest:test_currentReportAllowsVerification() (gas VerifierDeactivateFeedWithVerifyTest:test_currentReportFailsVerification() (gas: 113388) VerifierDeactivateFeedWithVerifyTest:test_previousReportAllowsVerification() (gas: 99624) VerifierDeactivateFeedWithVerifyTest:test_previousReportFailsVerification() (gas: 69943) +VerifierInterfacesTest:test_DestinationContractInterfaces() (gas: 628127) VerifierProxyAccessControlledVerificationTest:test_proxiesToTheVerifierIfHasAccess() (gas: 208529) VerifierProxyAccessControlledVerificationTest:test_revertsIfNoAccess() (gas: 112345) VerifierProxyConstructorTest:test_correctlySetsTheCorrectAccessControllerInterface() (gas: 1485359) @@ -236,6 +472,9 @@ VerifierProxyUnsetVerifierWithPreviouslySetVerifierTest:test_correctlyUnsetsVeri VerifierProxyUnsetVerifierWithPreviouslySetVerifierTest:test_emitsAnEventAfterUnsettingVerifier() (gas: 17961) VerifierProxyVerifyTest:test_proxiesToTheCorrectVerifier() (gas: 204342) VerifierProxyVerifyTest:test_revertsIfNoVerifierConfigured() (gas: 117264) +VerifierSetAccessControllerTest:test_revertsIfCalledByNonOwner() (gas: 17196) +VerifierSetAccessControllerTest:test_setFeeManagerWhichDoesntHonourInterface() (gas: 16272) +VerifierSetAccessControllerTest:test_successfullySetsNewFeeManager() (gas: 42226) VerifierSetConfigFromSourceMultipleDigestsTest:test_correctlySetsConfigWhenDigestsAreRemoved() (gas: 542302) VerifierSetConfigFromSourceMultipleDigestsTest:test_correctlyUpdatesDigestsOnMultipleVerifiersInTheProxy() (gas: 967768) VerifierSetConfigFromSourceMultipleDigestsTest:test_correctlyUpdatesTheDigestInTheProxy() (gas: 523251) @@ -256,6 +495,9 @@ VerifierTestBillingReport:test_verifyWithLink() (gas: 275293) VerifierTestBillingReport:test_verifyWithNative() (gas: 316326) VerifierTestBillingReport:test_verifyWithNativeUnwrapped() (gas: 318574) VerifierTestBillingReport:test_verifyWithNativeUnwrappedReturnsChange() (gas: 325642) +VerifierVerifyBulkTest:test_revertsVerifyBulkIfNoAccess() (gas: 112867) +VerifierVerifyBulkTest:test_verifyBulkSingleCaseWithSingleConfig() (gas: 745006) +VerifierVerifyBulkTest:test_verifyBulkWithSingleConfigOneVerifyFails() (gas: 698163) VerifierVerifyMultipleConfigDigestTest:test_canVerifyNewerReportsWithNewerConfigs() (gas: 133961) VerifierVerifyMultipleConfigDigestTest:test_canVerifyOlderReportsWithOlderConfigs() (gas: 189865) VerifierVerifyMultipleConfigDigestTest:test_revertsIfAReportIsVerifiedWithAnExistingButIncorrectDigest() (gas: 88216) @@ -270,6 +512,18 @@ VerifierVerifySingleConfigDigestTest:test_revertsIfVerifiedByNonProxy() (gas: 10 VerifierVerifySingleConfigDigestTest:test_revertsIfVerifiedWithIncorrectAddresses() (gas: 184077) VerifierVerifySingleConfigDigestTest:test_revertsIfWrongNumberOfSigners() (gas: 110042) VerifierVerifySingleConfigDigestTest:test_setsTheCorrectEpoch() (gas: 194592) +VerifierVerifyTest:test_canVerifyNewerReportsWithNewerConfigs() (gas: 862947) +VerifierVerifyTest:test_canVerifyOlderV3ReportsWithOlderConfigs() (gas: 815907) +VerifierVerifyTest:test_failToVerifyReportIfDupSigners() (gas: 450675) +VerifierVerifyTest:test_failToVerifyReportIfNoSigners() (gas: 426452) +VerifierVerifyTest:test_failToVerifyReportIfNotEnoughSigners() (gas: 434774) +VerifierVerifyTest:test_failToVerifyReportIfSignerNotInConfig() (gas: 456826) +VerifierVerifyTest:test_revertsVerifyIfNoAccess() (gas: 109465) +VerifierVerifyTest:test_rollingOutConfiguration() (gas: 1497140) +VerifierVerifyTest:test_scenarioRollingNewChainWithHistoricConfigs() (gas: 976048) +VerifierVerifyTest:test_verifyFailsWhenReportIsOlderThanConfig() (gas: 2303291) +VerifierVerifyTest:test_verifyReport() (gas: 1434772) +VerifierVerifyTest:test_verifyTooglingActiveFlagsDonConfigs() (gas: 1918758) Verifier_accessControlledVerify:testVerifyWithAccessControl_gas() (gas: 212077) Verifier_bulkVerifyWithFee:testBulkVerifyProxyWithLinkFeeSuccess_gas() (gas: 519389) Verifier_bulkVerifyWithFee:testBulkVerifyProxyWithNativeFeeSuccess_gas() (gas: 542808) diff --git a/contracts/gas-snapshots/operatorforwarder.gas-snapshot b/contracts/gas-snapshots/operatorforwarder.gas-snapshot index 66bb19f1f6..551fde38f3 100644 --- a/contracts/gas-snapshots/operatorforwarder.gas-snapshot +++ b/contracts/gas-snapshots/operatorforwarder.gas-snapshot @@ -2,8 +2,8 @@ FactoryTest:test_DeployNewForwarderAndTransferOwnership_Success() (gas: 1059722) FactoryTest:test_DeployNewForwarder_Success() (gas: 1048209) FactoryTest:test_DeployNewOperatorAndForwarder_Success() (gas: 4069305) FactoryTest:test_DeployNewOperator_Success() (gas: 3020464) -ForwarderTest:test_Forward_Success(uint256) (runs: 257, μ: 226979, ~: 227289) -ForwarderTest:test_MultiForward_Success(uint256,uint256) (runs: 257, μ: 258577, ~: 259120) +ForwarderTest:test_Forward_Success(uint256) (runs: 256, μ: 226978, ~: 227289) +ForwarderTest:test_MultiForward_Success(uint256,uint256) (runs: 256, μ: 258575, ~: 259120) ForwarderTest:test_OwnerForward_Success() (gas: 30118) ForwarderTest:test_SetAuthorizedSenders_Success() (gas: 160524) ForwarderTest:test_TransferOwnershipWithMessage_Success() (gas: 35123) @@ -11,5 +11,5 @@ OperatorTest:test_CancelOracleRequest_Success() (gas: 274436) OperatorTest:test_FulfillOracleRequest_Success() (gas: 330603) OperatorTest:test_NotAuthorizedSender_Revert() (gas: 246716) OperatorTest:test_OracleRequest_Success() (gas: 250019) -OperatorTest:test_SendRequestAndCancelRequest_Success(uint96) (runs: 257, μ: 387121, ~: 387124) -OperatorTest:test_SendRequest_Success(uint96) (runs: 257, μ: 303612, ~: 303615) \ No newline at end of file +OperatorTest:test_SendRequestAndCancelRequest_Success(uint96) (runs: 256, μ: 387121, ~: 387124) +OperatorTest:test_SendRequest_Success(uint96) (runs: 256, μ: 303612, ~: 303615) \ No newline at end of file diff --git a/contracts/gas-snapshots/shared.gas-snapshot b/contracts/gas-snapshots/shared.gas-snapshot index 0848baa098..dda850089c 100644 --- a/contracts/gas-snapshots/shared.gas-snapshot +++ b/contracts/gas-snapshots/shared.gas-snapshot @@ -39,10 +39,10 @@ CallWithExactGas__callWithExactGas:test_CallWithExactGasSafeReturnDataExactGas() CallWithExactGas__callWithExactGas:test_NoContractReverts() (gas: 11559) CallWithExactGas__callWithExactGas:test_NoGasForCallExactCheckReverts() (gas: 15788) CallWithExactGas__callWithExactGas:test_NotEnoughGasForCallReverts() (gas: 16241) -CallWithExactGas__callWithExactGas:test_callWithExactGasSuccess(bytes,bytes4) (runs: 257, μ: 15767, ~: 15719) +CallWithExactGas__callWithExactGas:test_callWithExactGasSuccess(bytes,bytes4) (runs: 256, μ: 15766, ~: 15719) CallWithExactGas__callWithExactGasEvenIfTargetIsNoContract:test_CallWithExactGasEvenIfTargetIsNoContractExactGasSuccess() (gas: 20116) CallWithExactGas__callWithExactGasEvenIfTargetIsNoContract:test_CallWithExactGasEvenIfTargetIsNoContractReceiverErrorSuccess() (gas: 67721) -CallWithExactGas__callWithExactGasEvenIfTargetIsNoContract:test_CallWithExactGasEvenIfTargetIsNoContractSuccess(bytes,bytes4) (runs: 257, μ: 16277, ~: 16229) +CallWithExactGas__callWithExactGasEvenIfTargetIsNoContract:test_CallWithExactGasEvenIfTargetIsNoContractSuccess(bytes,bytes4) (runs: 256, μ: 16276, ~: 16229) CallWithExactGas__callWithExactGasEvenIfTargetIsNoContract:test_NoContractSuccess() (gas: 12962) CallWithExactGas__callWithExactGasEvenIfTargetIsNoContract:test_NoGasForCallExactCheckReturnFalseSuccess() (gas: 13005) CallWithExactGas__callWithExactGasEvenIfTargetIsNoContract:test_NotEnoughGasForCallReturnsFalseSuccess() (gas: 13317) diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index 65934d3866..577ef9675d 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -21,10 +21,8 @@ subtask(TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS).setAction( async (_, __, runSuper) => { const paths = await runSuper() const noTests = paths.filter((p: string) => !p.endsWith('.t.sol')) - const noCCIPTests = noTests.filter( - (p: string) => !p.includes('/v0.8/ccip/test'), - ) - return noCCIPTests.filter( + const noCCIP = noTests.filter((p: string) => !p.includes('/v0.8/ccip')) + return noCCIP.filter( (p: string) => !p.includes('src/v0.8/vendor/forge-std'), ) }, diff --git a/contracts/package.json b/contracts/package.json index 8afd81e3a4..ca007745ce 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -129,6 +129,7 @@ "@openzeppelin/contracts": "4.9.3", "@openzeppelin/contracts-upgradeable": "4.9.3", "@scroll-tech/contracts": "0.1.0", + "@zksync/contracts": "git+https://github.com/matter-labs/era-contracts.git#446d391d34bdb48255d5f8fef8a8248925fc98b9", "semver": "^7.6.3" }, "lint-staged": { diff --git a/contracts/pnpm-lock.yaml b/contracts/pnpm-lock.yaml index 225a0ec34c..865ea9e4d7 100644 --- a/contracts/pnpm-lock.yaml +++ b/contracts/pnpm-lock.yaml @@ -22,13 +22,13 @@ importers: version: 0.5.0 '@changesets/cli': specifier: ~2.27.7 - version: 2.27.8 + version: 2.27.7 '@eth-optimism/contracts': specifier: 0.6.0 version: 0.6.0(ethers@5.7.2) '@matterlabs/hardhat-zksync-verify': specifier: ^1.6.0 - version: 1.6.0(@nomicfoundation/hardhat-verify@2.0.10(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4)))(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4)) + version: 1.6.0(@nomicfoundation/hardhat-verify@2.0.9(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4)))(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4)) '@openzeppelin/contracts': specifier: 4.9.3 version: 4.9.3 @@ -38,6 +38,9 @@ importers: '@scroll-tech/contracts': specifier: 0.1.0 version: 0.1.0 + '@zksync/contracts': + specifier: git+https://github.com/matter-labs/era-contracts.git#446d391d34bdb48255d5f8fef8a8248925fc98b9 + version: era-contracts@https://codeload.github.com/matter-labs/era-contracts/tar.gz/446d391d34bdb48255d5f8fef8a8248925fc98b9 semver: specifier: ^7.6.3 version: 7.6.3 @@ -59,31 +62,31 @@ importers: version: 5.7.2 '@matterlabs/hardhat-zksync-solc': specifier: ^1.2.1 - version: 1.2.3(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4)) + version: 1.2.5(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4)) '@nomicfoundation/hardhat-chai-matchers': specifier: ^1.0.6 - version: 1.0.6(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4)))(chai@4.5.0)(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4)) + version: 1.0.6(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4)))(chai@4.5.0)(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4)) '@nomicfoundation/hardhat-ethers': specifier: ^3.0.6 - version: 3.0.6(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4)) + version: 3.0.6(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4)) '@nomicfoundation/hardhat-network-helpers': specifier: ^1.0.11 - version: 1.0.11(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4)) + version: 1.0.11(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4)) '@nomicfoundation/hardhat-verify': specifier: ^2.0.9 - version: 2.0.10(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4)) + version: 2.0.9(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4)) '@typechain/ethers-v5': specifier: ^7.2.0 version: 7.2.0(@ethersproject/abi@5.7.0)(@ethersproject/bytes@5.7.0)(@ethersproject/providers@5.7.2)(ethers@5.7.2)(typechain@8.3.2(typescript@5.5.4))(typescript@5.5.4) '@typechain/hardhat': specifier: ^7.0.0 - version: 7.0.0(@ethersproject/abi@5.7.0)(@ethersproject/providers@5.7.2)(@typechain/ethers-v5@7.2.0(@ethersproject/abi@5.7.0)(@ethersproject/bytes@5.7.0)(@ethersproject/providers@5.7.2)(ethers@5.7.2)(typechain@8.3.2(typescript@5.5.4))(typescript@5.5.4))(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4))(typechain@8.3.2(typescript@5.5.4)) + version: 7.0.0(@ethersproject/abi@5.7.0)(@ethersproject/providers@5.7.2)(@typechain/ethers-v5@7.2.0(@ethersproject/abi@5.7.0)(@ethersproject/bytes@5.7.0)(@ethersproject/providers@5.7.2)(ethers@5.7.2)(typechain@8.3.2(typescript@5.5.4))(typescript@5.5.4))(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4))(typechain@8.3.2(typescript@5.5.4)) '@types/cbor': specifier: ~5.0.1 version: 5.0.1 '@types/chai': specifier: ^4.3.17 - version: 4.3.19 + version: 4.3.17 '@types/debug': specifier: ^4.1.12 version: 4.1.12 @@ -95,7 +98,7 @@ importers: version: 10.0.7 '@types/node': specifier: ^20.14.15 - version: 20.16.4 + version: 20.14.15 '@typescript-eslint/eslint-plugin': specifier: ^7.18.0 version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4) @@ -131,19 +134,19 @@ importers: version: 5.7.2 hardhat: specifier: ~2.20.1 - version: 2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4) + version: 2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4) hardhat-abi-exporter: specifier: ^2.10.1 - version: 2.10.1(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4)) + version: 2.10.1(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4)) hardhat-ignore-warnings: specifier: ^0.2.6 version: 0.2.11 husky: specifier: ^9.0.11 - version: 9.0.11 + version: 9.1.5 lint-staged: specifier: ^15.2.2 - version: 15.2.2 + version: 15.2.9 moment: specifier: ^2.30.1 version: 2.30.1 @@ -164,7 +167,7 @@ importers: version: 0.1.0(prettier-plugin-solidity@1.3.1(prettier@3.3.3))(prettier@3.3.3) ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@20.16.4)(typescript@5.5.4) + version: 10.9.2(@types/node@20.14.15)(typescript@5.5.4) typechain: specifier: ^8.2.1 version: 8.3.2(typescript@5.5.4) @@ -207,11 +210,11 @@ packages: resolution: {tarball: https://codeload.github.com/smartcontractkit/chainlink-solhint-rules/tar.gz/1b4c0c2663fcd983589d4f33a2e73908624ed43c} version: 1.2.0 - '@changesets/apply-release-plan@7.0.5': - resolution: {integrity: sha512-1cWCk+ZshEkSVEZrm2fSj1Gz8sYvxgUL4Q78+1ZZqeqfuevPTPk033/yUZ3df8BKMohkqqHfzj0HOOrG0KtXTw==} + '@changesets/apply-release-plan@7.0.4': + resolution: {integrity: sha512-HLFwhKWayKinWAul0Vj+76jVx1Pc2v55MGPVjZ924Y/ROeSsBMFutv9heHmCUj48lJyRfOTJG5+ar+29FUky/A==} - '@changesets/assemble-release-plan@6.0.4': - resolution: {integrity: sha512-nqICnvmrwWj4w2x0fOhVj2QEGdlUuwVAwESrUo5HLzWMI1rE5SWfsr9ln+rDqWB6RQ2ZyaMZHUcU7/IRaUJS+Q==} + '@changesets/assemble-release-plan@6.0.3': + resolution: {integrity: sha512-bLNh9/Lgl1VwkjWZTq8JmRqH+hj7/Yzfz0jsQ/zJJ+FTmVqmqPj3szeKOri8O/hEM8JmHW019vh2gTO9iq5Cuw==} '@changesets/changelog-git@0.2.0': resolution: {integrity: sha512-bHOx97iFI4OClIT35Lok3sJAwM31VbUM++gnMBV16fdbtBhgYu4dxsphBF/0AZZsyAHMrnM0yFcj5gZM1py6uQ==} @@ -219,45 +222,45 @@ packages: '@changesets/changelog-github@0.5.0': resolution: {integrity: sha512-zoeq2LJJVcPJcIotHRJEEA2qCqX0AQIeFE+L21L8sRLPVqDhSXY8ZWAt2sohtBpFZkBwu+LUwMSKRr2lMy3LJA==} - '@changesets/cli@2.27.8': - resolution: {integrity: sha512-gZNyh+LdSsI82wBSHLQ3QN5J30P4uHKJ4fXgoGwQxfXwYFTJzDdvIJasZn8rYQtmKhyQuiBj4SSnLuKlxKWq4w==} + '@changesets/cli@2.27.7': + resolution: {integrity: sha512-6lr8JltiiXPIjDeYg4iM2MeePP6VN/JkmqBsVA5XRiy01hGS3y629LtSDvKcycj/w/5Eur1rEwby/MjcYS+e2A==} hasBin: true - '@changesets/config@3.0.3': - resolution: {integrity: sha512-vqgQZMyIcuIpw9nqFIpTSNyc/wgm/Lu1zKN5vECy74u95Qx/Wa9g27HdgO4NkVAaq+BGA8wUc/qvbvVNs93n6A==} + '@changesets/config@3.0.2': + resolution: {integrity: sha512-cdEhS4t8woKCX2M8AotcV2BOWnBp09sqICxKapgLHf9m5KdENpWjyrFNMjkLqGJtUys9U+w93OxWT0czorVDfw==} '@changesets/errors@0.2.0': resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==} - '@changesets/get-dependents-graph@2.1.2': - resolution: {integrity: sha512-sgcHRkiBY9i4zWYBwlVyAjEM9sAzs4wYVwJUdnbDLnVG3QwAaia1Mk5P8M7kraTOZN+vBET7n8KyB0YXCbFRLQ==} + '@changesets/get-dependents-graph@2.1.1': + resolution: {integrity: sha512-LRFjjvigBSzfnPU2n/AhFsuWR5DK++1x47aq6qZ8dzYsPtS/I5mNhIGAS68IAxh1xjO9BTtz55FwefhANZ+FCA==} '@changesets/get-github-info@0.6.0': resolution: {integrity: sha512-v/TSnFVXI8vzX9/w3DU2Ol+UlTZcu3m0kXTjTT4KlAdwSvwutcByYwyYn9hwerPWfPkT2JfpoX0KgvCEi8Q/SA==} - '@changesets/get-release-plan@4.0.4': - resolution: {integrity: sha512-SicG/S67JmPTrdcc9Vpu0wSQt7IiuN0dc8iR5VScnnTVPfIaLvKmEGRvIaF0kcn8u5ZqLbormZNTO77bCEvyWw==} + '@changesets/get-release-plan@4.0.3': + resolution: {integrity: sha512-6PLgvOIwTSdJPTtpdcr3sLtGatT+Jr22+cQwEBJBy6wP0rjB4yJ9lv583J9fVpn1bfQlBkDa8JxbS2g/n9lIyA==} '@changesets/get-version-range-type@0.4.0': resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} - '@changesets/git@3.0.1': - resolution: {integrity: sha512-pdgHcYBLCPcLd82aRcuO0kxCDbw/yISlOtkmwmE8Odo1L6hSiZrBOsRl84eYG7DRCab/iHnOkWqExqc4wxk2LQ==} + '@changesets/git@3.0.0': + resolution: {integrity: sha512-vvhnZDHe2eiBNRFHEgMiGd2CT+164dfYyrJDhwwxTVD/OW0FUD6G7+4DIx1dNwkwjHyzisxGAU96q0sVNBns0w==} - '@changesets/logger@0.1.1': - resolution: {integrity: sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==} + '@changesets/logger@0.1.0': + resolution: {integrity: sha512-pBrJm4CQm9VqFVwWnSqKEfsS2ESnwqwH+xR7jETxIErZcfd1u2zBSqrHbRHR7xjhSgep9x2PSKFKY//FAshA3g==} '@changesets/parse@0.4.0': resolution: {integrity: sha512-TS/9KG2CdGXS27S+QxbZXgr8uPsP4yNJYb4BC2/NeFUj80Rni3TeD2qwWmabymxmrLo7JEsytXH1FbpKTbvivw==} - '@changesets/pre@2.0.1': - resolution: {integrity: sha512-vvBJ/If4jKM4tPz9JdY2kGOgWmCowUYOi5Ycv8dyLnEE8FgpYYUo1mgJZxcdtGGP3aG8rAQulGLyyXGSLkIMTQ==} + '@changesets/pre@2.0.0': + resolution: {integrity: sha512-HLTNYX/A4jZxc+Sq8D1AMBsv+1qD6rmmJtjsCJa/9MSRybdxh0mjbTvE6JYZQ/ZiQ0mMlDOlGPXTm9KLTU3jyw==} - '@changesets/read@0.6.1': - resolution: {integrity: sha512-jYMbyXQk3nwP25nRzQQGa1nKLY0KfoOV7VLgwucI0bUO8t8ZLCr6LZmgjXsiKuRDc+5A6doKPr9w2d+FEJ55zQ==} + '@changesets/read@0.6.0': + resolution: {integrity: sha512-ZypqX8+/im1Fm98K4YcZtmLKgjs1kDQ5zHpc2U1qdtNBmZZfo/IBiG162RoP0CUF05tvp2y4IspH11PLnPxuuw==} - '@changesets/should-skip-package@0.1.1': - resolution: {integrity: sha512-H9LjLbF6mMHLtJIc/eHR9Na+MifJ3VxtgP/Y+XLn4BF7tDTEN1HNYtH6QMcjP1uxp9sjaFYmW8xqloaCi/ckTg==} + '@changesets/should-skip-package@0.1.0': + resolution: {integrity: sha512-FxG6Mhjw7yFStlSM7Z0Gmg3RiyQ98d/9VpQAZ3Fzr59dCOM9G6ZdYbjiSAt0XtFr9JR5U2tBaJWPjrkGGc618g==} '@changesets/types@4.1.0': resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} @@ -265,8 +268,8 @@ packages: '@changesets/types@6.0.0': resolution: {integrity: sha512-b1UkfNulgKoWfqyHtzKS5fOZYSJO+77adgL7DLRDr+/7jhChN+QcHnbjiQVOz/U+Ts3PGNySq7diAItzDgugfQ==} - '@changesets/write@0.3.2': - resolution: {integrity: sha512-kDxDrPNpUgsjDbWBvUo27PzKX4gqeKOlhibaOXDJA6kuBisGqNHv/HwGJrAu8U/dSf8ZEFIeHIPtvSlZI1kULw==} + '@changesets/write@0.3.1': + resolution: {integrity: sha512-SyGtMXzH3qFqlHKcvFY2eX+6b0NGiFcNav8AFsYwy5l8hejOeoeTDemu5Yjmke2V5jpzY+pBvM0vCCQ3gdZpfw==} '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} @@ -423,8 +426,8 @@ packages: '@manypkg/get-packages@1.1.3': resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} - '@matterlabs/hardhat-zksync-solc@1.2.3': - resolution: {integrity: sha512-vRvA89DEV49vBcm1/lZVVp+k3OHjuFzhGnzzgwk9zmV9rr4onRDtTShPbu7fP6MdJOTZQ0F3f82rYKsh0ERqNA==} + '@matterlabs/hardhat-zksync-solc@1.2.5': + resolution: {integrity: sha512-iZyznWl1Hoe/Z46hnUe1s2drBZBjJOS/eN+Ql2lIBX9B6NevBl9DYzkKzH5HEIMCLGnX9sWpRAJqUQJWy9UB6w==} peerDependencies: hardhat: ^2.22.5 @@ -537,10 +540,10 @@ packages: peerDependencies: hardhat: ^2.9.5 - '@nomicfoundation/hardhat-verify@2.0.10': - resolution: {integrity: sha512-3zoTZGQhpeOm6piJDdsGb6euzZAd7N5Tk0zPQvGnfKQ0+AoxKz/7i4if12goi8IDTuUGElAUuZyQB8PMQoXA5g==} + '@nomicfoundation/hardhat-verify@2.0.9': + resolution: {integrity: sha512-7kD8hu1+zlnX87gC+UN4S0HTKBnIsDfXZ/pproq1gYsK94hgCk+exvzXbwR0X2giiY/RZPkqY9oKRi0Uev91hQ==} peerDependencies: - hardhat: ^2.0.4 + hardhat: ^2.22.72.0.4 '@nomicfoundation/solidity-analyzer-darwin-arm64@0.1.0': resolution: {integrity: sha512-vEF3yKuuzfMHsZecHQcnkUrqm8mnTWfJeEVFHpg+cO+le96xQA4lAJYdUan8pXZohQxv1fSReQsn4QGNuBNuCw==} @@ -642,8 +645,8 @@ packages: '@openzeppelin/contracts@4.9.3': resolution: {integrity: sha512-He3LieZ1pP2TNt5JbkPA4PNT9WC3gOTOlDcFGJW4Le4QKqwmiNJCRt44APfxMxvq7OugU/cqYuPcSBzOw38DAg==} - '@openzeppelin/upgrades-core@1.32.5': - resolution: {integrity: sha512-R0wprsyJ4xWiRW05kaTfZZkRVpG2g0af3/hpjE7t2mX0Eb2n40MQLokTwqIk4LDzpp910JfLSpB0vBuZ6WNPog==} + '@openzeppelin/upgrades-core@1.34.4': + resolution: {integrity: sha512-iGN3StqYHYVqqSKs8hWY+Gz6VkiEqOkQccBhHl7lHLGBJF91QUZ8wNMZ59SA5Usg1Fstu/HurvZTCEshPJAZ8w==} hasBin: true '@pkgr/core@0.1.1': @@ -711,17 +714,17 @@ packages: resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} engines: {node: '>=10'} - '@sinonjs/commons@2.0.0': - resolution: {integrity: sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==} - '@sinonjs/commons@3.0.1': resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} - '@sinonjs/fake-timers@11.3.1': - resolution: {integrity: sha512-EVJO7nW5M/F5Tur0Rf2z/QoMo+1Ia963RiMtapiQrEWvY0iBUvADo8Beegwjpnle5BHkyHuoxSTW3jF43H1XRA==} + '@sinonjs/fake-timers@11.2.2': + resolution: {integrity: sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==} + + '@sinonjs/fake-timers@13.0.3': + resolution: {integrity: sha512-golm/Sc4CqLV/ZalIP14Nre7zPgd8xG/S3nHULMTBHMX0llyTNhE1O6nrgbfvLX2o0y849CnLKdu8OE05Ztiiw==} - '@sinonjs/samsam@8.0.0': - resolution: {integrity: sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==} + '@sinonjs/samsam@8.0.2': + resolution: {integrity: sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==} '@sinonjs/text-encoding@0.7.3': resolution: {integrity: sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==} @@ -789,8 +792,8 @@ packages: '@types/chai-as-promised@7.1.8': resolution: {integrity: sha512-ThlRVIJhr69FLlh6IctTXFkmhtP3NpMZ2QGq69StYLyKZFp/HOp1VdKZj7RvfNWYYcJ1xlbLGLLWj1UvP5u/Gw==} - '@types/chai@4.3.19': - resolution: {integrity: sha512-2hHHvQBVE2FiSK4eN0Br6snX9MtolHaTo/batnLjlGRhoQzlCL61iVpxoqO7SfFyOw+P/pwv+0zNHzKoGWz9Cw==} + '@types/chai@4.3.17': + resolution: {integrity: sha512-zmZ21EWzR71B4Sscphjief5djsLre50M6lI622OSySTmn9DB3j+C3kWroHfBQWXbOBwbgg/M8CG/hUxDLIloow==} '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} @@ -816,8 +819,8 @@ packages: '@types/node@12.19.16': resolution: {integrity: sha512-7xHmXm/QJ7cbK2laF+YYD7gb5MggHIIQwqyjin3bpEGiSuvScMQ5JZZXPvRipi1MwckTQbJZROMns/JxdnIL1Q==} - '@types/node@20.16.4': - resolution: {integrity: sha512-ioyQ1zK9aGEomJ45zz8S8IdzElyxhvP1RVWnPrXDf6wFaUb+kk1tEcVVJkF7RPGM0VWI7cp5U57oCPIn5iN1qg==} + '@types/node@20.14.15': + resolution: {integrity: sha512-Fz1xDMCF/B00/tYSVMlmK7hVeLh7jE5f3B7X1/hmV0MJBwE27KlS7EvD/Yp+z1lm8mVhwV5w+n8jOZG8AfTlKw==} '@types/pbkdf2@3.1.0': resolution: {integrity: sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ==} @@ -964,9 +967,9 @@ packages: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} - ansi-escapes@6.2.1: - resolution: {integrity: sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==} - engines: {node: '>=14.16'} + ansi-escapes@7.0.0: + resolution: {integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==} + engines: {node: '>=18'} ansi-regex@2.1.1: resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==} @@ -1138,6 +1141,10 @@ packages: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} engines: {node: '>=8'} + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + brorand@1.1.0: resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} @@ -1276,9 +1283,9 @@ packages: resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==} engines: {node: '>=6'} - cli-cursor@4.0.0: - resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} cli-truncate@4.0.0: resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} @@ -1329,15 +1336,15 @@ packages: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} - commander@11.1.0: - resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} - engines: {node: '>=16'} + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} commander@3.0.2: resolution: {integrity: sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==} - compare-versions@6.1.0: - resolution: {integrity: sha512-LNZQXhqUvqUTotpZ00qLSaify3b4VFD588aRr8MKFw4CMUr98ytzCW5wDH5qx/DEY5kCDXcbcRuCqL0szEf2tg==} + compare-versions@6.1.1: + resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -1532,8 +1539,8 @@ packages: elliptic@6.5.4: resolution: {integrity: sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==} - emoji-regex@10.3.0: - resolution: {integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==} + emoji-regex@10.4.0: + resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1549,6 +1556,14 @@ packages: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + + era-contracts@https://codeload.github.com/matter-labs/era-contracts/tar.gz/446d391d34bdb48255d5f8fef8a8248925fc98b9: + resolution: {tarball: https://codeload.github.com/matter-labs/era-contracts/tar.gz/446d391d34bdb48255d5f8fef8a8248925fc98b9} + version: 0.1.0 + error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} @@ -1735,6 +1750,10 @@ packages: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} engines: {node: '>=8'} + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + find-replace@3.0.0: resolution: {integrity: sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==} engines: {node: '>=4.0.0'} @@ -1751,6 +1770,9 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} + find-yarn-workspace-root2@1.2.16: + resolution: {integrity: sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==} + find-yarn-workspace-root@2.0.0: resolution: {integrity: sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==} @@ -1780,8 +1802,8 @@ packages: form-data-encoder@1.7.1: resolution: {integrity: sha512-EFRDrsMm/kyqbTQocNvRXMLjc7Es2Vk+IQFx/YW7hkUH1eBl4J1fqiP34l74Yt0pFLCNpc06fkbVk00008mzjg==} - form-data@4.0.0: - resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + form-data@4.0.1: + resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} engines: {node: '>= 6'} fp-ts@1.19.3: @@ -2019,8 +2041,8 @@ packages: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} - husky@9.0.11: - resolution: {integrity: sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==} + husky@9.1.5: + resolution: {integrity: sha512-rowAVRUBfI0b4+niA4SJMhfQwc107VLkBUgEYYAOQAbqDCnra1nYh83hF/MDmhYs9t9n1E3DuKOrs2LYNC+0Ag==} engines: {node: '>=18'} hasBin: true @@ -2316,22 +2338,26 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} - lilconfig@3.0.0: - resolution: {integrity: sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==} + lilconfig@3.1.2: + resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} engines: {node: '>=14'} lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - lint-staged@15.2.2: - resolution: {integrity: sha512-TiTt93OPh1OZOsb5B7k96A/ATl2AjIZo+vnzFZ6oHK5FuTk63ByDtxGQpHm+kFETjEWqgkF95M8FRXKR/LEBcw==} + lint-staged@15.2.9: + resolution: {integrity: sha512-BZAt8Lk3sEnxw7tfxM7jeZlPRuT4M68O0/CwZhhaw6eeWu0Lz5eERE3m386InivXB64fp/mDID452h48tvKlRQ==} engines: {node: '>=18.12.0'} hasBin: true - listr2@8.0.1: - resolution: {integrity: sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA==} + listr2@8.2.4: + resolution: {integrity: sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g==} engines: {node: '>=18.0.0'} + load-yaml-file@0.2.0: + resolution: {integrity: sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==} + engines: {node: '>=6'} + locate-path@2.0.0: resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} engines: {node: '>=4'} @@ -2375,8 +2401,8 @@ packages: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} - log-update@6.0.0: - resolution: {integrity: sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==} + log-update@6.1.0: + resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} engines: {node: '>=18'} loupe@2.3.7: @@ -2427,6 +2453,10 @@ packages: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} engines: {node: '>=8.6'} + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -2435,14 +2465,14 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - mimic-fn@4.0.0: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + mimic-response@1.0.1: resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} engines: {node: '>=4'} @@ -2508,8 +2538,8 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - nan@2.20.0: - resolution: {integrity: sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==} + nan@2.22.0: + resolution: {integrity: sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==} nanoid@3.3.3: resolution: {integrity: sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==} @@ -2525,8 +2555,8 @@ packages: nice-try@1.0.5: resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} - nise@6.0.0: - resolution: {integrity: sha512-K8ePqo9BFvN31HXwEtTNGzgrPpmvgciDsFz8aztFjt4LqKO/JeFD8tBOeuDiCMXrIl/m1YvfH8auSpxfaD09wg==} + nise@6.1.1: + resolution: {integrity: sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g==} no-case@2.3.2: resolution: {integrity: sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==} @@ -2596,14 +2626,14 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - onetime@6.0.0: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + open@7.4.2: resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==} engines: {node: '>=8'} @@ -2674,9 +2704,6 @@ packages: resolution: {integrity: sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==} engines: {node: '>=14.16'} - package-manager-detector@0.2.0: - resolution: {integrity: sha512-E385OSk9qDcXhcM9LNSe4sdhx8a9mAPrZ4sMLW+tmxl5ZuGtPUcdFu+MPP2jbgiWAZ6Pfe5soGFMd+0Db5Vrog==} - param-case@2.1.1: resolution: {integrity: sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==} @@ -2730,8 +2757,9 @@ packages: resolution: {integrity: sha512-wZ3AeiRBRlNwkdUxvBANh0+esnt38DLffHDujZyRHkqkaKHTglnY2EP5UX3b8rdeiSutgO4y9NEJwXezNP5vHg==} engines: {node: '>=8'} - path-to-regexp@6.2.2: - resolution: {integrity: sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==} + path-to-regexp@8.2.0: + resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==} + engines: {node: '>=16'} path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} @@ -2744,9 +2772,6 @@ packages: resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==} engines: {node: '>=0.12'} - picocolors@1.1.0: - resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} - picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -2760,6 +2785,10 @@ packages: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} @@ -2768,6 +2797,10 @@ packages: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} + preferred-pm@3.1.3: + resolution: {integrity: sha512-MkXsENfftWSRpzCzImcp4FRsCc3y1opwB73CfCNWyzMqArju2CrlMHlqB7VexKiPEOjGMbttv1r9fSCn5S610w==} + engines: {node: '>=10'} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -2901,9 +2934,9 @@ packages: responselike@2.0.1: resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} - restore-cursor@4.0.0: - resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} retry@0.12.0: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} @@ -2913,8 +2946,8 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rfdc@1.3.1: - resolution: {integrity: sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==} + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} rimraf@2.7.1: resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} @@ -3047,8 +3080,8 @@ packages: chai: ^4.0.0 sinon: '>=4.0.0' - sinon@18.0.0: - resolution: {integrity: sha512-+dXDXzD1sBO6HlmZDd7mXZCR/y5ECiEiGCBSGuFD/kZ0bDTofPYc6JaeGmPSF+1j1MejGUWkORbYOLDyvqCWpA==} + sinon@18.0.1: + resolution: {integrity: sha512-a2N2TDY1uGviajJ6r4D1CyRAkzE9NNVlYOV1wX5xQDuAk0ONgzgRl0EjCQuRCPxOwp13ghsMwt9Gdldujs39qw==} slash@2.0.0: resolution: {integrity: sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==} @@ -3177,8 +3210,8 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - ssh2@1.15.0: - resolution: {integrity: sha512-C0PHgX4h6lBxYx7hcXwu3QWdh4tg6tZZsTfXcdvc5caW/EMxaB4H9dWsl7qk+F7LAW762hp8VbXOX7x4xUYvEw==} + ssh2@1.16.0: + resolution: {integrity: sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg==} engines: {node: '>=10.16.0'} stacktrace-parser@0.1.10: @@ -3200,8 +3233,8 @@ packages: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} - string-width@7.1.0: - resolution: {integrity: sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==} + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} string.prototype.trim@1.2.8: @@ -3461,15 +3494,15 @@ packages: unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} - undici-types@6.19.8: - resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} undici@5.28.4: resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} engines: {node: '>=14.0'} - undici@6.19.8: - resolution: {integrity: sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==} + undici@6.20.1: + resolution: {integrity: sha512-AjQF1QsmqfJys+LXfGTNum+qw4S88CojRInG/6t31W/1fk6G59s92bnAvGz5Cmur+kQv2SURXEvvudLmbrE8QA==} engines: {node: '>=18.17'} universalify@0.1.2: @@ -3519,6 +3552,10 @@ packages: which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + which-pm@2.0.0: + resolution: {integrity: sha512-Lhs9Pmyph0p5n5Z3mVnN0yWcbQYUAD7rbQUiMsQxOJ3T57k7RFe35SUwWMf7dsbDZks1uOmw4AecB/JMDj3v/w==} + engines: {node: '>=8.15'} + which-typed-array@1.1.13: resolution: {integrity: sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==} engines: {node: '>= 0.4'} @@ -3597,9 +3634,10 @@ packages: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} - yaml@2.3.4: - resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==} + yaml@2.5.0: + resolution: {integrity: sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==} engines: {node: '>= 14'} + hasBin: true yargs-parser@20.2.4: resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==} @@ -3639,7 +3677,7 @@ snapshots: '@openzeppelin/contracts': 4.8.3 '@openzeppelin/contracts-upgradeable': 4.8.3 optionalDependencies: - '@openzeppelin/upgrades-core': 1.32.5 + '@openzeppelin/upgrades-core': 1.34.4 transitivePeerDependencies: - supports-color @@ -3663,12 +3701,13 @@ snapshots: '@chainlink/solhint-plugin-chainlink-solidity@https://codeload.github.com/smartcontractkit/chainlink-solhint-rules/tar.gz/1b4c0c2663fcd983589d4f33a2e73908624ed43c': {} - '@changesets/apply-release-plan@7.0.5': + '@changesets/apply-release-plan@7.0.4': dependencies: - '@changesets/config': 3.0.3 + '@babel/runtime': 7.24.0 + '@changesets/config': 3.0.2 '@changesets/get-version-range-type': 0.4.0 - '@changesets/git': 3.0.1 - '@changesets/should-skip-package': 0.1.1 + '@changesets/git': 3.0.0 + '@changesets/should-skip-package': 0.1.0 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 detect-indent: 6.1.0 @@ -3679,11 +3718,12 @@ snapshots: resolve-from: 5.0.0 semver: 7.6.3 - '@changesets/assemble-release-plan@6.0.4': + '@changesets/assemble-release-plan@6.0.3': dependencies: + '@babel/runtime': 7.24.0 '@changesets/errors': 0.2.0 - '@changesets/get-dependents-graph': 2.1.2 - '@changesets/should-skip-package': 0.1.1 + '@changesets/get-dependents-graph': 2.1.1 + '@changesets/should-skip-package': 0.1.0 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 semver: 7.6.3 @@ -3700,44 +3740,46 @@ snapshots: transitivePeerDependencies: - encoding - '@changesets/cli@2.27.8': + '@changesets/cli@2.27.7': dependencies: - '@changesets/apply-release-plan': 7.0.5 - '@changesets/assemble-release-plan': 6.0.4 + '@babel/runtime': 7.24.0 + '@changesets/apply-release-plan': 7.0.4 + '@changesets/assemble-release-plan': 6.0.3 '@changesets/changelog-git': 0.2.0 - '@changesets/config': 3.0.3 + '@changesets/config': 3.0.2 '@changesets/errors': 0.2.0 - '@changesets/get-dependents-graph': 2.1.2 - '@changesets/get-release-plan': 4.0.4 - '@changesets/git': 3.0.1 - '@changesets/logger': 0.1.1 - '@changesets/pre': 2.0.1 - '@changesets/read': 0.6.1 - '@changesets/should-skip-package': 0.1.1 + '@changesets/get-dependents-graph': 2.1.1 + '@changesets/get-release-plan': 4.0.3 + '@changesets/git': 3.0.0 + '@changesets/logger': 0.1.0 + '@changesets/pre': 2.0.0 + '@changesets/read': 0.6.0 + '@changesets/should-skip-package': 0.1.0 '@changesets/types': 6.0.0 - '@changesets/write': 0.3.2 + '@changesets/write': 0.3.1 '@manypkg/get-packages': 1.1.3 '@types/semver': 7.5.0 ansi-colors: 4.1.3 + chalk: 2.4.2 ci-info: 3.9.0 enquirer: 2.3.6 external-editor: 3.1.0 fs-extra: 7.0.1 + human-id: 1.0.2 mri: 1.2.0 outdent: 0.5.0 p-limit: 2.3.0 - package-manager-detector: 0.2.0 - picocolors: 1.1.0 + preferred-pm: 3.1.3 resolve-from: 5.0.0 semver: 7.6.3 spawndamnit: 2.0.0 term-size: 2.2.1 - '@changesets/config@3.0.3': + '@changesets/config@3.0.2': dependencies: '@changesets/errors': 0.2.0 - '@changesets/get-dependents-graph': 2.1.2 - '@changesets/logger': 0.1.1 + '@changesets/get-dependents-graph': 2.1.1 + '@changesets/logger': 0.1.0 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 fs-extra: 7.0.1 @@ -3747,11 +3789,12 @@ snapshots: dependencies: extendable-error: 0.1.7 - '@changesets/get-dependents-graph@2.1.2': + '@changesets/get-dependents-graph@2.1.1': dependencies: '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 - picocolors: 1.1.0 + chalk: 2.4.2 + fs-extra: 7.0.1 semver: 7.6.3 '@changesets/get-github-info@0.6.0': @@ -3761,53 +3804,59 @@ snapshots: transitivePeerDependencies: - encoding - '@changesets/get-release-plan@4.0.4': + '@changesets/get-release-plan@4.0.3': dependencies: - '@changesets/assemble-release-plan': 6.0.4 - '@changesets/config': 3.0.3 - '@changesets/pre': 2.0.1 - '@changesets/read': 0.6.1 + '@babel/runtime': 7.24.0 + '@changesets/assemble-release-plan': 6.0.3 + '@changesets/config': 3.0.2 + '@changesets/pre': 2.0.0 + '@changesets/read': 0.6.0 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 '@changesets/get-version-range-type@0.4.0': {} - '@changesets/git@3.0.1': + '@changesets/git@3.0.0': dependencies: + '@babel/runtime': 7.24.0 '@changesets/errors': 0.2.0 + '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 is-subdir: 1.2.0 micromatch: 4.0.5 spawndamnit: 2.0.0 - '@changesets/logger@0.1.1': + '@changesets/logger@0.1.0': dependencies: - picocolors: 1.1.0 + chalk: 2.4.2 '@changesets/parse@0.4.0': dependencies: '@changesets/types': 6.0.0 js-yaml: 3.14.1 - '@changesets/pre@2.0.1': + '@changesets/pre@2.0.0': dependencies: + '@babel/runtime': 7.24.0 '@changesets/errors': 0.2.0 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 fs-extra: 7.0.1 - '@changesets/read@0.6.1': + '@changesets/read@0.6.0': dependencies: - '@changesets/git': 3.0.1 - '@changesets/logger': 0.1.1 + '@babel/runtime': 7.24.0 + '@changesets/git': 3.0.0 + '@changesets/logger': 0.1.0 '@changesets/parse': 0.4.0 '@changesets/types': 6.0.0 + chalk: 2.4.2 fs-extra: 7.0.1 p-filter: 2.1.0 - picocolors: 1.1.0 - '@changesets/should-skip-package@0.1.1': + '@changesets/should-skip-package@0.1.0': dependencies: + '@babel/runtime': 7.24.0 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 @@ -3815,8 +3864,9 @@ snapshots: '@changesets/types@6.0.0': {} - '@changesets/write@0.3.2': + '@changesets/write@0.3.1': dependencies: + '@babel/runtime': 7.24.0 '@changesets/types': 6.0.0 fs-extra: 7.0.1 human-id: 1.0.2 @@ -4183,7 +4233,7 @@ snapshots: globby: 11.1.0 read-yaml-file: 1.1.0 - '@matterlabs/hardhat-zksync-solc@1.2.3(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4))': + '@matterlabs/hardhat-zksync-solc@1.2.5(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4))': dependencies: '@nomiclabs/hardhat-docker': 2.0.2 chai: 4.5.0 @@ -4191,31 +4241,31 @@ snapshots: debug: 4.3.6 dockerode: 4.0.2 fs-extra: 11.2.0 - hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4) + hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4) proper-lockfile: 4.1.2 semver: 7.6.3 - sinon: 18.0.0 - sinon-chai: 3.7.0(chai@4.5.0)(sinon@18.0.0) - undici: 6.19.8 + sinon: 18.0.1 + sinon-chai: 3.7.0(chai@4.5.0)(sinon@18.0.1) + undici: 6.20.1 transitivePeerDependencies: - encoding - supports-color - '@matterlabs/hardhat-zksync-verify@1.6.0(@nomicfoundation/hardhat-verify@2.0.10(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4)))(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4))': + '@matterlabs/hardhat-zksync-verify@1.6.0(@nomicfoundation/hardhat-verify@2.0.9(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4)))(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4))': dependencies: '@ethersproject/abi': 5.7.0 '@ethersproject/address': 5.7.0 - '@matterlabs/hardhat-zksync-solc': 1.2.3(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4)) - '@nomicfoundation/hardhat-verify': 2.0.10(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4)) + '@matterlabs/hardhat-zksync-solc': 1.2.5(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4)) + '@nomicfoundation/hardhat-verify': 2.0.9(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4)) axios: 1.7.7(debug@4.3.6) cbor: 9.0.2 chai: 4.5.0 chalk: 4.1.2 debug: 4.3.6 - hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4) + hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4) semver: 7.6.3 - sinon: 18.0.0 - sinon-chai: 3.7.0(chai@4.5.0)(sinon@18.0.0) + sinon: 18.0.1 + sinon-chai: 3.7.0(chai@4.5.0)(sinon@18.0.1) transitivePeerDependencies: - encoding - supports-color @@ -4370,40 +4420,40 @@ snapshots: - c-kzg - supports-color - '@nomicfoundation/hardhat-chai-matchers@1.0.6(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4)))(chai@4.5.0)(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4))': + '@nomicfoundation/hardhat-chai-matchers@1.0.6(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4)))(chai@4.5.0)(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4))': dependencies: '@ethersproject/abi': 5.7.0 - '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4)) + '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4)) '@types/chai-as-promised': 7.1.8 chai: 4.5.0 chai-as-promised: 7.1.1(chai@4.5.0) deep-eql: 4.1.3 ethers: 5.7.2 - hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4) + hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4) ordinal: 1.0.3 - '@nomicfoundation/hardhat-ethers@3.0.6(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4))': + '@nomicfoundation/hardhat-ethers@3.0.6(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4))': dependencies: debug: 4.3.6 ethers: 5.7.2 - hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4) + hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4) lodash.isequal: 4.5.0 transitivePeerDependencies: - supports-color - '@nomicfoundation/hardhat-network-helpers@1.0.11(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4))': + '@nomicfoundation/hardhat-network-helpers@1.0.11(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4))': dependencies: ethereumjs-util: 7.1.5 - hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4) + hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4) - '@nomicfoundation/hardhat-verify@2.0.10(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4))': + '@nomicfoundation/hardhat-verify@2.0.9(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4))': dependencies: '@ethersproject/abi': 5.7.0 '@ethersproject/address': 5.7.0 cbor: 8.1.0 chalk: 2.4.2 debug: 4.3.6 - hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4) + hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4) lodash.clonedeep: 4.5.0 semver: 6.3.0 table: 6.8.1 @@ -4463,10 +4513,10 @@ snapshots: - encoding - supports-color - '@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4))': + '@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4))': dependencies: ethers: 5.7.2 - hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4) + hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4) '@offchainlabs/upgrade-executor@1.1.0-beta.0': dependencies: @@ -4489,11 +4539,11 @@ snapshots: '@openzeppelin/contracts@4.9.3': {} - '@openzeppelin/upgrades-core@1.32.5': + '@openzeppelin/upgrades-core@1.34.4': dependencies: cbor: 9.0.2 chalk: 4.1.2 - compare-versions: 6.1.0 + compare-versions: 6.1.1 debug: 4.3.6 ethereumjs-util: 7.1.5 minimist: 1.2.8 @@ -4587,23 +4637,23 @@ snapshots: '@sindresorhus/is@4.6.0': {} - '@sinonjs/commons@2.0.0': + '@sinonjs/commons@3.0.1': dependencies: type-detect: 4.0.8 - '@sinonjs/commons@3.0.1': + '@sinonjs/fake-timers@11.2.2': dependencies: - type-detect: 4.0.8 + '@sinonjs/commons': 3.0.1 - '@sinonjs/fake-timers@11.3.1': + '@sinonjs/fake-timers@13.0.3': dependencies: '@sinonjs/commons': 3.0.1 - '@sinonjs/samsam@8.0.0': + '@sinonjs/samsam@8.0.2': dependencies: - '@sinonjs/commons': 2.0.0 + '@sinonjs/commons': 3.0.1 lodash.get: 4.4.2 - type-detect: 4.0.8 + type-detect: 4.1.0 '@sinonjs/text-encoding@0.7.3': {} @@ -4647,40 +4697,40 @@ snapshots: typechain: 8.3.2(typescript@5.5.4) typescript: 5.5.4 - '@typechain/hardhat@7.0.0(@ethersproject/abi@5.7.0)(@ethersproject/providers@5.7.2)(@typechain/ethers-v5@7.2.0(@ethersproject/abi@5.7.0)(@ethersproject/bytes@5.7.0)(@ethersproject/providers@5.7.2)(ethers@5.7.2)(typechain@8.3.2(typescript@5.5.4))(typescript@5.5.4))(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4))(typechain@8.3.2(typescript@5.5.4))': + '@typechain/hardhat@7.0.0(@ethersproject/abi@5.7.0)(@ethersproject/providers@5.7.2)(@typechain/ethers-v5@7.2.0(@ethersproject/abi@5.7.0)(@ethersproject/bytes@5.7.0)(@ethersproject/providers@5.7.2)(ethers@5.7.2)(typechain@8.3.2(typescript@5.5.4))(typescript@5.5.4))(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4))(typechain@8.3.2(typescript@5.5.4))': dependencies: '@ethersproject/abi': 5.7.0 '@ethersproject/providers': 5.7.2 '@typechain/ethers-v5': 7.2.0(@ethersproject/abi@5.7.0)(@ethersproject/bytes@5.7.0)(@ethersproject/providers@5.7.2)(ethers@5.7.2)(typechain@8.3.2(typescript@5.5.4))(typescript@5.5.4) ethers: 5.7.2 fs-extra: 9.1.0 - hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4) + hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4) typechain: 8.3.2(typescript@5.5.4) '@types/bn.js@4.11.6': dependencies: - '@types/node': 20.16.4 + '@types/node': 20.14.15 '@types/bn.js@5.1.1': dependencies: - '@types/node': 20.16.4 + '@types/node': 20.14.15 '@types/cacheable-request@6.0.2': dependencies: '@types/http-cache-semantics': 4.0.1 '@types/keyv': 3.1.4 - '@types/node': 20.16.4 + '@types/node': 20.14.15 '@types/responselike': 1.0.0 '@types/cbor@5.0.1': dependencies: - '@types/node': 20.16.4 + '@types/node': 20.14.15 '@types/chai-as-promised@7.1.8': dependencies: - '@types/chai': 4.3.19 + '@types/chai': 4.3.17 - '@types/chai@4.3.19': {} + '@types/chai@4.3.17': {} '@types/debug@4.1.12': dependencies: @@ -4692,7 +4742,7 @@ snapshots: '@types/keyv@3.1.4': dependencies: - '@types/node': 20.16.4 + '@types/node': 20.14.15 '@types/lru-cache@5.1.1': {} @@ -4702,28 +4752,28 @@ snapshots: '@types/node@12.19.16': {} - '@types/node@20.16.4': + '@types/node@20.14.15': dependencies: - undici-types: 6.19.8 + undici-types: 5.26.5 '@types/pbkdf2@3.1.0': dependencies: - '@types/node': 20.16.4 + '@types/node': 20.14.15 '@types/prettier@2.7.1': {} '@types/readable-stream@2.3.15': dependencies: - '@types/node': 20.16.4 + '@types/node': 20.14.15 safe-buffer: 5.1.2 '@types/responselike@1.0.0': dependencies: - '@types/node': 20.16.4 + '@types/node': 20.14.15 '@types/secp256k1@4.0.3': dependencies: - '@types/node': 20.16.4 + '@types/node': 20.14.15 '@types/semver@7.5.0': {} @@ -4883,7 +4933,9 @@ snapshots: dependencies: type-fest: 0.21.3 - ansi-escapes@6.2.1: {} + ansi-escapes@7.0.0: + dependencies: + environment: 1.1.0 ansi-regex@2.1.1: {} @@ -4992,7 +5044,7 @@ snapshots: axios@1.7.7(debug@4.3.6): dependencies: follow-redirects: 1.15.6(debug@4.3.6) - form-data: 4.0.0 + form-data: 4.0.1 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug @@ -5075,6 +5127,10 @@ snapshots: dependencies: fill-range: 7.0.1 + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + brorand@1.1.0: {} browser-stdout@1.3.1: {} @@ -5255,14 +5311,14 @@ snapshots: cli-boxes@2.2.1: {} - cli-cursor@4.0.0: + cli-cursor@5.0.0: dependencies: - restore-cursor: 4.0.0 + restore-cursor: 5.1.0 cli-truncate@4.0.0: dependencies: slice-ansi: 5.0.0 - string-width: 7.1.0 + string-width: 7.2.0 cliui@7.0.4: dependencies: @@ -5312,11 +5368,11 @@ snapshots: commander@10.0.1: {} - commander@11.1.0: {} + commander@12.1.0: {} commander@3.0.2: {} - compare-versions@6.1.0: + compare-versions@6.1.1: optional: true concat-map@0.0.1: {} @@ -5354,7 +5410,7 @@ snapshots: cpu-features@0.0.10: dependencies: buildcheck: 0.0.6 - nan: 2.20.0 + nan: 2.22.0 optional: true create-hash@1.2.0: @@ -5512,7 +5568,7 @@ snapshots: debug: 4.3.6 readable-stream: 3.6.0 split-ca: 1.0.1 - ssh2: 1.15.0 + ssh2: 1.16.0 transitivePeerDependencies: - supports-color @@ -5552,7 +5608,7 @@ snapshots: minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 - emoji-regex@10.3.0: {} + emoji-regex@10.4.0: {} emoji-regex@8.0.0: {} @@ -5566,6 +5622,10 @@ snapshots: env-paths@2.2.1: {} + environment@1.1.0: {} + + era-contracts@https://codeload.github.com/matter-labs/era-contracts/tar.gz/446d391d34bdb48255d5f8fef8a8248925fc98b9: {} + error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 @@ -5948,6 +6008,10 @@ snapshots: dependencies: to-regex-range: 5.0.1 + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + find-replace@3.0.0: dependencies: array-back: 3.1.0 @@ -5966,6 +6030,11 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 + find-yarn-workspace-root2@1.2.16: + dependencies: + micromatch: 4.0.5 + pkg-dir: 4.2.0 + find-yarn-workspace-root@2.0.0: dependencies: micromatch: 4.0.5 @@ -5991,7 +6060,7 @@ snapshots: form-data-encoder@1.7.1: {} - form-data@4.0.0: + form-data@4.0.1: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 @@ -6154,7 +6223,7 @@ snapshots: array-union: 2.1.0 dir-glob: 3.0.1 fast-glob: 3.3.1 - ignore: 5.3.1 + ignore: 5.2.4 merge2: 1.4.1 slash: 3.0.0 @@ -6185,11 +6254,11 @@ snapshots: graphemer@1.4.0: {} - hardhat-abi-exporter@2.10.1(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4)): + hardhat-abi-exporter@2.10.1(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4)): dependencies: '@ethersproject/abi': 5.7.0 delete-empty: 3.0.0 - hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4) + hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4) hardhat-ignore-warnings@0.2.11: dependencies: @@ -6197,7 +6266,7 @@ snapshots: node-interval-tree: 2.1.2 solidity-comments: 0.0.2 - hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4))(typescript@5.5.4): + hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4): dependencies: '@ethersproject/abi': 5.7.0 '@metamask/eth-sig-util': 4.0.1 @@ -6250,7 +6319,7 @@ snapshots: uuid: 8.3.2 ws: 7.5.9 optionalDependencies: - ts-node: 10.9.2(@types/node@20.16.4)(typescript@5.5.4) + ts-node: 10.9.2(@types/node@20.14.15)(typescript@5.5.4) typescript: 5.5.4 transitivePeerDependencies: - bufferutil @@ -6354,7 +6423,7 @@ snapshots: human-signals@5.0.0: {} - husky@9.0.11: {} + husky@9.1.5: {} iconv-lite@0.4.24: dependencies: @@ -6639,34 +6708,41 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - lilconfig@3.0.0: {} + lilconfig@3.1.2: {} lines-and-columns@1.2.4: {} - lint-staged@15.2.2: + lint-staged@15.2.9: dependencies: chalk: 5.3.0 - commander: 11.1.0 - debug: 4.3.4(supports-color@8.1.1) + commander: 12.1.0 + debug: 4.3.6 execa: 8.0.1 - lilconfig: 3.0.0 - listr2: 8.0.1 - micromatch: 4.0.5 + lilconfig: 3.1.2 + listr2: 8.2.4 + micromatch: 4.0.8 pidtree: 0.6.0 string-argv: 0.3.2 - yaml: 2.3.4 + yaml: 2.5.0 transitivePeerDependencies: - supports-color - listr2@8.0.1: + listr2@8.2.4: dependencies: cli-truncate: 4.0.0 colorette: 2.0.20 eventemitter3: 5.0.1 - log-update: 6.0.0 - rfdc: 1.3.1 + log-update: 6.1.0 + rfdc: 1.4.1 wrap-ansi: 9.0.0 + load-yaml-file@0.2.0: + dependencies: + graceful-fs: 4.2.10 + js-yaml: 3.14.1 + pify: 4.0.1 + strip-bom: 3.0.0 + locate-path@2.0.0: dependencies: p-locate: 2.0.0 @@ -6703,10 +6779,10 @@ snapshots: chalk: 4.1.2 is-unicode-supported: 0.1.0 - log-update@6.0.0: + log-update@6.1.0: dependencies: - ansi-escapes: 6.2.1 - cli-cursor: 4.0.0 + ansi-escapes: 7.0.0 + cli-cursor: 5.0.0 slice-ansi: 7.1.0 strip-ansi: 7.1.0 wrap-ansi: 9.0.0 @@ -6753,16 +6829,21 @@ snapshots: braces: 3.0.2 picomatch: 2.3.1 + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + mime-db@1.52.0: {} mime-types@2.1.35: dependencies: mime-db: 1.52.0 - mimic-fn@2.1.0: {} - mimic-fn@4.0.0: {} + mimic-function@5.0.1: {} + mimic-response@1.0.1: {} mimic-response@3.1.0: {} @@ -6833,7 +6914,7 @@ snapshots: ms@2.1.3: {} - nan@2.20.0: + nan@2.22.0: optional: true nanoid@3.3.3: {} @@ -6846,13 +6927,13 @@ snapshots: nice-try@1.0.5: {} - nise@6.0.0: + nise@6.1.1: dependencies: '@sinonjs/commons': 3.0.1 - '@sinonjs/fake-timers': 11.3.1 + '@sinonjs/fake-timers': 13.0.3 '@sinonjs/text-encoding': 0.7.3 just-extend: 6.2.0 - path-to-regexp: 6.2.2 + path-to-regexp: 8.2.0 no-case@2.3.2: dependencies: @@ -6915,14 +6996,14 @@ snapshots: dependencies: wrappy: 1.0.2 - onetime@5.1.2: - dependencies: - mimic-fn: 2.1.0 - onetime@6.0.0: dependencies: mimic-fn: 4.0.0 + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + open@7.4.2: dependencies: is-docker: 2.2.1 @@ -6990,8 +7071,6 @@ snapshots: registry-url: 6.0.1 semver: 7.6.3 - package-manager-detector@0.2.0: {} - param-case@2.1.1: dependencies: no-case: 2.3.2 @@ -7049,7 +7128,7 @@ snapshots: path-starts-with@2.0.1: {} - path-to-regexp@6.2.2: {} + path-to-regexp@8.2.0: {} path-type@4.0.0: {} @@ -7063,19 +7142,28 @@ snapshots: safe-buffer: 5.2.1 sha.js: 2.4.11 - picocolors@1.1.0: {} - picomatch@2.3.1: {} pidtree@0.6.0: {} pify@4.0.1: {} + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + pluralize@8.0.0: {} possible-typed-array-names@1.0.0: optional: true + preferred-pm@3.1.3: + dependencies: + find-up: 5.0.0 + find-yarn-workspace-root2: 1.2.16 + path-exists: 4.0.0 + which-pm: 2.0.0 + prelude-ls@1.2.1: {} prettier-linter-helpers@1.0.0: @@ -7228,16 +7316,16 @@ snapshots: dependencies: lowercase-keys: 2.0.0 - restore-cursor@4.0.0: + restore-cursor@5.1.0: dependencies: - onetime: 5.1.2 - signal-exit: 3.0.7 + onetime: 7.0.0 + signal-exit: 4.1.0 retry@0.12.0: {} reusify@1.0.4: {} - rfdc@1.3.1: {} + rfdc@1.4.1: {} rimraf@2.7.1: dependencies: @@ -7382,18 +7470,18 @@ snapshots: signal-exit@4.1.0: {} - sinon-chai@3.7.0(chai@4.5.0)(sinon@18.0.0): + sinon-chai@3.7.0(chai@4.5.0)(sinon@18.0.1): dependencies: chai: 4.5.0 - sinon: 18.0.0 + sinon: 18.0.1 - sinon@18.0.0: + sinon@18.0.1: dependencies: '@sinonjs/commons': 3.0.1 - '@sinonjs/fake-timers': 11.3.1 - '@sinonjs/samsam': 8.0.0 + '@sinonjs/fake-timers': 11.2.2 + '@sinonjs/samsam': 8.0.2 diff: 5.2.0 - nise: 6.0.0 + nise: 6.1.1 supports-color: 7.2.0 slash@2.0.0: {} @@ -7534,13 +7622,13 @@ snapshots: sprintf-js@1.0.3: {} - ssh2@1.15.0: + ssh2@1.16.0: dependencies: asn1: 0.2.6 bcrypt-pbkdf: 1.0.2 optionalDependencies: cpu-features: 0.0.10 - nan: 2.20.0 + nan: 2.22.0 stacktrace-parser@0.1.10: dependencies: @@ -7558,9 +7646,9 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - string-width@7.1.0: + string-width@7.2.0: dependencies: - emoji-regex: 10.3.0 + emoji-regex: 10.4.0 get-east-asian-width: 1.2.0 strip-ansi: 7.1.0 @@ -7746,14 +7834,14 @@ snapshots: dependencies: typescript: 5.5.4 - ts-node@10.9.2(@types/node@20.16.4)(typescript@5.5.4): + ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.9 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.3 - '@types/node': 20.16.4 + '@types/node': 20.14.15 acorn: 8.10.0 acorn-walk: 8.2.0 arg: 4.1.3 @@ -7889,13 +7977,13 @@ snapshots: which-boxed-primitive: 1.0.2 optional: true - undici-types@6.19.8: {} + undici-types@5.26.5: {} undici@5.28.4: dependencies: '@fastify/busboy': 2.1.1 - undici@6.19.8: {} + undici@6.20.1: {} universalify@0.1.2: {} @@ -7947,6 +8035,11 @@ snapshots: is-symbol: 1.0.3 optional: true + which-pm@2.0.0: + dependencies: + load-yaml-file: 0.2.0 + path-exists: 4.0.0 + which-typed-array@1.1.13: dependencies: available-typed-arrays: 1.0.5 @@ -7993,7 +8086,7 @@ snapshots: wrap-ansi@9.0.0: dependencies: ansi-styles: 6.2.1 - string-width: 7.1.0 + string-width: 7.2.0 strip-ansi: 7.1.0 wrappy@1.0.2: {} @@ -8010,7 +8103,7 @@ snapshots: yaml@1.10.2: {} - yaml@2.3.4: {} + yaml@2.5.0: {} yargs-parser@20.2.4: {} diff --git a/contracts/remappings.txt b/contracts/remappings.txt index ec64b1b211..4ed0fcfd9a 100644 --- a/contracts/remappings.txt +++ b/contracts/remappings.txt @@ -5,3 +5,4 @@ forge-std/=src/v0.8/vendor/forge-std/src/ hardhat/=node_modules/hardhat/ @eth-optimism/=node_modules/@eth-optimism/ @scroll-tech/=node_modules/@scroll-tech/ +@zksync/=node_modules/@zksync/ diff --git a/contracts/scripts/ci/find_slither_report_diff.sh b/contracts/scripts/ci/find_slither_report_diff.sh new file mode 100755 index 0000000000..d0b5238a1a --- /dev/null +++ b/contracts/scripts/ci/find_slither_report_diff.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if [[ "$#" -lt 4 ]]; then + >&2 echo "Generates a markdown file with diff in new issues detected by ChatGPT between two Slither reports." + >&2 echo "Usage: $0 [path-to-validation-prompt]" + exit 1 +fi + +if [[ -z "${OPEN_API_KEY+x}" ]]; then + >&2 echo "OPEN_API_KEY is not set." + exit 1 +fi + +first_report_path=$1 +second_report_path=$2 +new_issues_report_path=$3 +report_prompt_path=$4 +if [[ "$#" -eq 5 ]]; then + validation_prompt_path=$5 +else + validation_prompt_path="" +fi + +first_report_content=$(cat "$first_report_path" | sed 's/"//g' | sed -E 's/\\+$//g' | sed -E 's/\\+ //g') +second_report_content=$(cat "$second_report_path" | sed 's/"//g' | sed -E 's/\\+$//g' | sed -E 's/\\+ //g') +openai_prompt=$(cat "$report_prompt_path" | sed 's/"/\\"/g' | sed -E 's/\\+$//g' | sed -E 's/\\+ //g') +openai_model="gpt-4o-2024-05-13" +openai_result=$(echo '{ + "model": "'$openai_model'", + "temperature": 0.01, + "messages": [ + { + "role": "system", + "content": "'$openai_prompt' \nreport1:\n```'$first_report_content'```\nreport2:\n```'$second_report_content'```" + } + ] +}' | envsubst | curl https://api.openai.com/v1/chat/completions \ + -w "%{http_code}" \ + -o prompt_response.json \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $OPEN_API_KEY" \ + -d @- +) + +# throw error openai_result when is not 200 +if [ "$openai_result" != '200' ]; then + echo "::error::OpenAI API call failed with status $openai_result: $(cat prompt_response.json)" + exit 1 +fi + +# replace lines starting with ' -' (1space) with ' -' (2spaces) +response_content=$(cat prompt_response.json | jq -r '.choices[0].message.content') +new_issues_report_content=$(echo "$response_content" | sed -e 's/^ -/ -/g') +echo "$new_issues_report_content" > "$new_issues_report_path" + +if [[ -n "$validation_prompt_path" ]]; then + echo "::debug::Validating the diff report using the validation prompt" + openai_model="gpt-4-turbo-2024-04-09" + report_input=$(echo "$new_issues_report_content" | sed 's/"//g' | sed -E 's/\\+$//g' | sed -E 's/\\+ //g') + validation_prompt_content=$(cat "$validation_prompt_path" | sed 's/"/\\"/g' | sed -E 's/\\+$//g' | sed -E 's/\\+ //g') + validation_result=$(echo '{ + "model": "'$openai_model'", + "temperature": 0.01, + "messages": [ + { + "role": "system", + "content": "'$validation_prompt_content' \nreport1:\n```'$first_report_content'```\nreport2:\n```'$second_report_content'```\nnew_issues:\n```'$report_input'```" + } + ] + }' | envsubst | curl https://api.openai.com/v1/chat/completions \ + -w "%{http_code}" \ + -o prompt_validation_response.json \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $OPEN_API_KEY" \ + -d @- + ) + + # throw error openai_result when is not 200 + if [ "$validation_result" != '200' ]; then + echo "::error::OpenAI API call failed with status $validation_result: $(cat prompt_validation_response.json)" + exit 1 + fi + + # replace lines starting with ' -' (1space) with ' -' (2spaces) + response_content=$(cat prompt_validation_response.json | jq -r '.choices[0].message.content') + + echo "$response_content" | sed -e 's/^ -/ -/g' >> "$new_issues_report_path" + echo "" >> "$new_issues_report_path" + echo "*Confidence rating presented above is an automatic validation (self-check) of the differences between two reports generated by ChatGPT ${openai_model} model. It has a scale of 1 to 5, where 1 means that all new issues are missing and 5 that all new issues are present*." >> "$new_issues_report_path" + echo "" >> "$new_issues_report_path" + echo "*If confidence rating is low it's advised to look for differences manually by downloading Slither reports for base reference and current commit from job's artifacts*." >> "$new_issues_report_path" +fi diff --git a/contracts/scripts/ci/prompt-difference.md b/contracts/scripts/ci/prompt-difference.md new file mode 100644 index 0000000000..b7603c9748 --- /dev/null +++ b/contracts/scripts/ci/prompt-difference.md @@ -0,0 +1,21 @@ +You are a helpful expert data engineer with expertise in Blockchain and Decentralized Oracle Networks. + +Given two reports generated by Slither - a Solidity static analysis tool - provided at the bottom of the reply, your task is to help create a report for your peers with new issues introduced in the second report in order to decrease noise resulting from irrelevant changes to the report, by focusing on a single topic: **New Issues**. + +First report is provided under Heading 2 (##) called `report1` and is surrounded by triple backticks (```) to indicate the beginning and end of the report. +Second report is provided under Heading 2 (##) called `report2` and is surrounded by triple backticks (```) to indicate the beginning and end of the report. + +First report is report generated by Slither using default branch of the code repository. Second report is report generated by Slither using a feature branch of the code repository. You want to help your peers understand the impact of changes they introduced in the pull request on the codebase and whether they introduced any new issues. + +**New Issues** + +Provide a bullet point summary of new issues that were introduced in the second report. If a given issue is not present in first report, but is present in the second one, it is considered a new issue. If the count for given issue type is higher in the second report than in the first one, it is considered a new issue. +For each issue include original description text from the report together with severity level, issue ID, line number and a link to problematic line in the code. +Group the issues by their type, which is defined as Heading 2 (##). + +Output your response starting from**New Issues** in escaped, markdown text that can be sent as http body to API. Do not wrap output in code blocks. +Extract the name of the file from the first line of the report and title the new report with it in a following way: "# Slither's new issues in: " + +Remember that it might be possible that second report does not introduce any new issues. In such case, provide an empty report. + +Format **New Issues** as Heading 2 using double sharp characters (##). Otherwise, do not include any another preamble and postamble to your answer. diff --git a/contracts/scripts/ci/prompt-validation.md b/contracts/scripts/ci/prompt-validation.md new file mode 100644 index 0000000000..5fcf08e146 --- /dev/null +++ b/contracts/scripts/ci/prompt-validation.md @@ -0,0 +1,33 @@ +You are a helpful expert data engineer with expertise in Blockchain and Decentralized Oracle Networks. + +At the bottom of the reply you will find two reports generated by Slither - a Solidity static analysis tool - and another report that contains new issues found in the second report. +Your task is to evaluate how well that new issues report shows all new issues mentioned in the second Slither report and assert its completeness. +Rate your confidence in the completeness of the new issues report on a scale from 1 to 5, where 1 means it's missing all new issues and 5 means that all new issues are present. + +First report is provided under Heading 2 (##) called `report1` and is surrounded by triple backticks (```) to indicate the beginning and end of the report. +Second report is provided under Heading 2 (##) called `report2` and is surrounded by triple backticks (```) to indicate the beginning and end of the report. +New issues report is provided under Heading 2 (##) called `new_issues` and is surrounded by triple backticks (```) to indicate the beginning and end of the report. + +Use the following steps to evaluate the new issues report: +* each report begins with a summary with types of issues found and number of issues found for each type, called "# Summary for " +* group issues by type and count for each report and calculate the expected difference in number of issues for each type for each report +* exclude all issue types, for which the count for is higher in the first report than in the second one +* for each remaining issue type, compare the number of issues found in the new issues report with the expected difference +* evaluate if the new issues report captures all new issues introduced in the second report + +Do not focus on: +* the quality of the Slither reports themselves, but rather on whether all new issues from the second report are present in the new issues report +* how well the new issues report is structured or written and how well it presents new issues + +It is crucial that you ignore all differences in the reports that are not related to new issues, such as resolved issues or issues, which count has decreased. + +If a given issue is not present in first report, but is present in the second one, it is considered a new issue. Similar behaviour is expected from the new issues report. +If the count for given issue type is higher in the second report than in the first one, it is considered a new issue. + +Your report should include only a single section titled "Confidence level". +Your evaluation of the completeness of the new issues report should be displayed as a Heading 3 using triple sharp characters (###). In a new line a brief explanation of the scale used, with minimum and maximum possible values. + +Remember that it might be possible that second report does not introduce any new issues. In such case, confidence rating should be 5. + +Output your response as escaped, markdown text that can be sent as http body to API. Do not wrap output in code blocks. Do not include any partial results or statistics regarding the number of new and resolved issues in any of the reports. +Format **Confidence level** as Heading 2 using double sharp characters (##). Otherwise, do not include any another preamble and postamble to your answer. 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 0000000000..385d1fb45b --- /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.19 --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 f144e4f7dc..e189e78cb0 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 @@ -108,4 +108,4 @@ compileContract automation/v2_3/AutomationUtils2_3.sol compileContract automation/interfaces/v2_3/IAutomationRegistryMaster2_3.sol compileContract automation/testhelpers/MockETHUSDAggregator.sol -compileContract automation/test/v2_3/WETH9.sol +compileContract automation/test/WETH9.sol diff --git a/contracts/scripts/native_solc_compile_all_ccip b/contracts/scripts/native_solc_compile_all_ccip index 0274380cfe..2e1072411c 100755 --- a/contracts/scripts/native_solc_compile_all_ccip +++ b/contracts/scripts/native_solc_compile_all_ccip @@ -40,10 +40,6 @@ compileContract () { echo "OnRamp uses $OPTIMIZE_RUNS_ONRAMP optimizer runs." optimize_runs=$OPTIMIZE_RUNS_ONRAMP ;; - "ccip/test/helpers/CCIPReaderTester.sol") - echo "CCIPReaderTester uses 1 optimizer runs for reduced contract size." - optimize_runs=1 - ;; esac solc --overwrite --optimize --optimize-runs $optimize_runs --metadata-hash none \ diff --git a/contracts/scripts/native_solc_compile_all_l2ep b/contracts/scripts/native_solc_compile_all_l2ep index 1b9f5fb611..1f67115806 100755 --- a/contracts/scripts/native_solc_compile_all_l2ep +++ b/contracts/scripts/native_solc_compile_all_l2ep @@ -1,59 +1,38 @@ #!/usr/bin/env bash -########### -# Logging # -########### - set -e echo " ┌──────────────────────────────────────────────┐" echo " │ Compiling L2EP contracts... │" echo " └──────────────────────────────────────────────┘" -###################### -# Helper Variable(s) # -###################### - -export SOLC_VERSION="0.8.19" +SOLC_VERSION="0.8.24" +OPTIMIZE_RUNS=1000000 -SCRIPTPATH="$( - cd "$(dirname "$0")" >/dev/null 2>&1 - pwd -P -)" +SCRIPTPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" +python3 -m pip install --require-hashes -r $SCRIPTPATH/requirements.txt +solc-select install $SOLC_VERSION +solc-select use $SOLC_VERSION ROOT="$( cd "$(dirname "$0")" >/dev/null 2>&1 cd ../ && pwd -P )" -###################### -# Helper Function(s) # -###################### - compileContract() { - local optimize_runs=1000000 local version="$1" local srcpath="$2" solc \ @openzeppelin/=$ROOT/node_modules/@openzeppelin/ \ @eth-optimism/=$ROOT/node_modules/@eth-optimism/ \ @scroll-tech/=$ROOT/node_modules/@scroll-tech/ \ - --overwrite --optimize --optimize-runs $optimize_runs --metadata-hash none \ + --overwrite --optimize --optimize-runs $OPTIMIZE_RUNS --metadata-hash none \ -o $ROOT/solc/v$SOLC_VERSION/l2ep/"$version" \ - --abi --bin \ - --allow-paths $ROOT/src/v0.8,$ROOT/node_modules \ + --abi --bin --allow-paths $ROOT/src/v0.8,$ROOT/node_modules \ + --evm-version paris \ $ROOT/src/v0.8/l2ep/"$srcpath" } -################# -# Version 1.0.0 # -################# - -python3 -m pip install --require-hashes -r $SCRIPTPATH/requirements.txt - -solc-select install $SOLC_VERSION -solc-select use $SOLC_VERSION - compileContract v1_0_0 dev/arbitrum/ArbitrumValidator.sol compileContract v1_0_0 dev/arbitrum/ArbitrumSequencerUptimeFeed.sol compileContract v1_0_0 dev/arbitrum/ArbitrumCrossDomainForwarder.sol diff --git a/contracts/scripts/native_solc_compile_all_llo-feeds b/contracts/scripts/native_solc_compile_all_llo-feeds index eb17f93b0d..ba5e6a4c8b 100755 --- a/contracts/scripts/native_solc_compile_all_llo-feeds +++ b/contracts/scripts/native_solc_compile_all_llo-feeds @@ -28,17 +28,17 @@ compileContract () { "$ROOT"/contracts/src/v0.8/"$1" } -compileContract llo-feeds/Verifier.sol -compileContract llo-feeds/VerifierProxy.sol -compileContract llo-feeds/FeeManager.sol -compileContract llo-feeds/RewardManager.sol - +compileContract llo-feeds/v0.3.0/Verifier.sol +compileContract llo-feeds/v0.3.0/VerifierProxy.sol +compileContract llo-feeds/v0.3.0/FeeManager.sol +compileContract llo-feeds/v0.3.0/RewardManager.sol +compileContract llo-feeds/v0.4.0/DestinationVerifier.sol +compileContract llo-feeds/v0.4.0/DestinationVerifierProxy.sol +compileContract llo-feeds/v0.4.0/DestinationFeeManager.sol +compileContract llo-feeds/v0.4.0/DestinationRewardManager.sol +compileContract llo-feeds/v0.5.0/configuration/ChannelConfigStore.sol +compileContract llo-feeds/v0.5.0/configuration/Configurator.sol # Test | Mocks -compileContract llo-feeds/test/mocks/ErroredVerifier.sol -compileContract llo-feeds/test/mocks/ExposedVerifier.sol - -# LLO -compileContract llo-feeds/dev/ChannelConfigStore.sol -compileContract llo-feeds/dev/ChannelVerifier.sol -compileContract llo-feeds/dev/test/mocks/ExposedChannelVerifier.sol +compileContract llo-feeds/v0.3.0/test/mocks/ErroredVerifier.sol +compileContract llo-feeds/v0.3.0/test/mocks/ExposedVerifier.sol diff --git a/contracts/scripts/native_solc_compile_all_vrf b/contracts/scripts/native_solc_compile_all_vrf index 4bfd4ecac7..88e7b3e147 100755 --- a/contracts/scripts/native_solc_compile_all_vrf +++ b/contracts/scripts/native_solc_compile_all_vrf @@ -114,6 +114,7 @@ compileContract vrf/dev/testhelpers/VRFV2PlusWrapperConsumerExample.sol compileContract vrf/dev/testhelpers/VRFV2PlusRevertingExample.sol compileContract vrf/dev/testhelpers/VRFConsumerV2PlusUpgradeableExample.sol compileContract vrf/dev/testhelpers/VRFV2PlusMaliciousMigrator.sol +compileContractAltOpts vrf/dev/testhelpers/VRFCoordinatorTestV2_5.sol 500 compileContract vrf/dev/libraries/VRFV2PlusClient.sol compileContract vrf/dev/testhelpers/VRFCoordinatorV2Plus_V2Example.sol compileContract vrf/dev/TrustedBlockhashStore.sol diff --git a/contracts/src/v0.8/automation/ZKSyncAutomationForwarder.sol b/contracts/src/v0.8/automation/ZKSyncAutomationForwarder.sol new file mode 100644 index 0000000000..f4616ba132 --- /dev/null +++ b/contracts/src/v0.8/automation/ZKSyncAutomationForwarder.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.19; + +import {IAutomationRegistryConsumer} from "./interfaces/IAutomationRegistryConsumer.sol"; +import {GAS_BOUND_CALLER, IGasBoundCaller} from "./interfaces/zksync/IGasBoundCaller.sol"; + +uint256 constant PERFORM_GAS_CUSHION = 50_000; + +/** + * @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; + + /// @notice the shared logic address + address private immutable i_logic; + + IAutomationRegistryConsumer private s_registry; + + constructor(address target, address registry, address logic) { + s_registry = IAutomationRegistryConsumer(registry); + i_target = target; + i_logic = logic; + } + + /** + * @notice forward is called by the registry and forwards the call to the target + * @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 InvalidCaller(msg.sender); + + uint256 g1 = gasleft(); + address target = i_target; + + assembly { + let g := gas() + // Compute g -= PERFORM_GAS_CUSHION and check for underflow + if lt(g, PERFORM_GAS_CUSHION) { + revert(0, 0) + } + g := sub(g, PERFORM_GAS_CUSHION) + // if g - g//64 <= gasAmount, revert + // (we subtract g//64 because of EIP-150) + if iszero(gt(sub(g, div(g, 64)), gasAmount)) { + revert(0, 0) + } + // solidity calls check that a contract actually exists at the destination, so we do the same + if iszero(extcodesize(target)) { + revert(0, 0) + } + } + + 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); + } + + function getTarget() external view returns (address) { + return i_target; + } + + // 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 + assembly { + // Copy msg.data. We take full control of memory in this inline assembly + // block because it will not return to Solidity code. We overwrite the + // Solidity scratch pad at memory position 0. + calldatacopy(0, 0, calldatasize()) + + // out and outsize are 0 because we don't know the size yet. + let result := delegatecall(gas(), logic, 0, calldatasize(), 0, 0) + + // Copy the returned data. + returndatacopy(0, 0, returndatasize()) + + switch result + // delegatecall returns 0 on error. + case 0 { + revert(0, returndatasize()) + } + default { + return(0, returndatasize()) + } + } + } +} diff --git a/contracts/src/v0.8/automation/chains/ArbitrumModule.sol b/contracts/src/v0.8/automation/chains/ArbitrumModule.sol index e27a0809b7..2ad6fdddc8 100644 --- a/contracts/src/v0.8/automation/chains/ArbitrumModule.sol +++ b/contracts/src/v0.8/automation/chains/ArbitrumModule.sol @@ -31,7 +31,7 @@ contract ArbitrumModule is ChainModuleBase { return ARB_SYS.arbBlockNumber(); } - function getCurrentL1Fee() external view override returns (uint256) { + function getCurrentL1Fee(uint256) external view override returns (uint256) { return ARB_GAS.getCurrentTxL1GasFees(); } diff --git a/contracts/src/v0.8/automation/chains/ChainModuleBase.sol b/contracts/src/v0.8/automation/chains/ChainModuleBase.sol index e9b082063b..c595dbd640 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() 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 91c1c0ed96..7a46429496 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 = @@ -16,11 +20,22 @@ contract OptimismModule is ChainModuleBase { uint256 private constant FIXED_GAS_OVERHEAD = 60_000; uint256 private constant PER_CALLDATA_BYTE_GAS_OVERHEAD = 270; - function getCurrentL1Fee() external view override returns (uint256) { - return OVM_GASPRICEORACLE.getL1Fee(bytes.concat(msg.data, OP_L1_DATA_FEE_PADDING)); + // @dev This will be updated to use the new function introduced by OP team + function getCurrentL1Fee(uint256 dataSize) external view override returns (uint256) { + return _getL1Fee(dataSize); } 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 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. bytes memory txCallData = new bytes(4 * dataSize); 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 0000000000..772b66cdf9 --- /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 1e41ed3805..30fdc908d4 100644 --- a/contracts/src/v0.8/automation/chains/ScrollModule.sol +++ b/contracts/src/v0.8/automation/chains/ScrollModule.sol @@ -3,8 +3,12 @@ pragma solidity 0.8.19; import {IScrollL1GasPriceOracle} from "../../vendor/@scroll-tech/contracts/src/L2/predeploys/IScrollL1GasPriceOracle.sol"; import {ChainModuleBase} from "./ChainModuleBase.sol"; +import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; + +contract ScrollModule is ChainModuleBase, ConfirmedOwner { + error InvalidL1FeeCoefficient(uint8 coefficient); + event L1FeeCoefficientSet(uint8 coefficient); -contract ScrollModule is ChainModuleBase { /// @dev SCROLL_L1_FEE_DATA_PADDING includes 140 bytes for L1 data padding for Scroll /// @dev according to testing, this padding allows automation registry to properly estimates L1 data fee with 3-5% buffer /// @dev this MAY NOT work for a different product and this may get out of date if transmit function is changed @@ -15,14 +19,26 @@ contract ScrollModule is ChainModuleBase { address private constant SCROLL_ORACLE_ADDR = 0x5300000000000000000000000000000000000002; IScrollL1GasPriceOracle private constant SCROLL_ORACLE = IScrollL1GasPriceOracle(SCROLL_ORACLE_ADDR); + /// @dev L1 fee coefficient can be applied to reduce possibly inflated gas cost + uint8 private s_l1FeeCoefficient = 100; uint256 private constant FIXED_GAS_OVERHEAD = 45_000; uint256 private constant PER_CALLDATA_BYTE_GAS_OVERHEAD = 170; - function getCurrentL1Fee() external view override returns (uint256) { - return SCROLL_ORACLE.getL1Fee(bytes.concat(msg.data, SCROLL_L1_FEE_DATA_PADDING)); + 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 + * @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. @@ -38,4 +54,25 @@ contract ScrollModule is ChainModuleBase { { 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/interfaces/IChainModule.sol b/contracts/src/v0.8/automation/interfaces/IChainModule.sol index e3a4b32c9b..15e84f7984 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() 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 0000000000..9edb541f9a --- /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 0000000000..c8f480065c --- /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 0000000000..8943721559 --- /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.19; + +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/WETH9.sol b/contracts/src/v0.8/automation/test/WETH9.sol similarity index 98% rename from contracts/src/v0.8/automation/test/v2_3/WETH9.sol rename to contracts/src/v0.8/automation/test/WETH9.sol index b8452d832b..1225e6e39a 100644 --- a/contracts/src/v0.8/automation/test/v2_3/WETH9.sol +++ b/contracts/src/v0.8/automation/test/WETH9.sol @@ -53,7 +53,7 @@ contract WETH9 { revert InsufficientBalance(); } balanceOf[msg.sender] -= wad; - payable(msg.sender).transfer(wad); + payable(msg.sender).call{value: wad}(""); emit Withdrawal(msg.sender, wad); } diff --git a/contracts/src/v0.8/automation/test/v2_3/AutomationRegistry2_3.t.sol b/contracts/src/v0.8/automation/test/v2_3/AutomationRegistry2_3.t.sol index dbc0c203c0..41aabf1bbe 100644 --- a/contracts/src/v0.8/automation/test/v2_3/AutomationRegistry2_3.t.sol +++ b/contracts/src/v0.8/automation/test/v2_3/AutomationRegistry2_3.t.sol @@ -22,11 +22,11 @@ contract SetUp is BaseTest { 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; + uint256 internal linkUpkeepID; + uint256 internal linkUpkeepID2; // 2 upkeeps use the same billing token (LINK) to test migration scenario + uint256 internal usdUpkeepID18; // 1 upkeep uses ERC20 token with 18 decimals + uint256 internal usdUpkeepID6; // 1 upkeep uses ERC20 token with 6 decimals + uint256 internal nativeUpkeepID; function setUp() public virtual override { super.setUp(); @@ -790,6 +790,7 @@ contract SetConfig is SetUp { } function testSetConfigOnTransmittersAndPayees() public { + registry.setPayees(PAYEES); AutomationRegistryBase2_3.TransmitterPayeeInfo[] memory transmitterPayeeInfos = registry .getTransmittersWithPayees(); assertEq(transmitterPayeeInfos.length, TRANSMITTERS.length); @@ -975,6 +976,7 @@ contract NOPsSettlement is SetUp { function testSettleNOPsOffchainSuccess() public { // deploy and configure a registry with OFF_CHAIN payout (Registry registry, ) = deployAndConfigureRegistryAndRegistrar(AutoBase.PayoutMode.OFF_CHAIN); + registry.setPayees(PAYEES); uint256[] memory payments = new uint256[](TRANSMITTERS.length); for (uint256 i = 0; i < TRANSMITTERS.length; i++) { @@ -991,6 +993,7 @@ contract NOPsSettlement is SetUp { function testSettleNOPsOffchainSuccessWithERC20MultiSteps() public { // deploy and configure a registry with OFF_CHAIN payout (Registry registry, ) = deployAndConfigureRegistryAndRegistrar(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), "", "", ""); @@ -1186,6 +1189,7 @@ contract NOPsSettlement is SetUp { function testSinglePerformAndNodesCanWithdrawOnchain() public { // deploy and configure a registry with OFF_CHAIN payout (Registry registry, ) = deployAndConfigureRegistryAndRegistrar(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), "", "", ""); @@ -1224,6 +1228,7 @@ contract NOPsSettlement is SetUp { function testMultiplePerformsAndNodesCanWithdrawOnchain() public { // deploy and configure a registry with OFF_CHAIN payout (Registry registry, ) = deployAndConfigureRegistryAndRegistrar(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), "", "", ""); @@ -1977,3 +1982,772 @@ contract MigrateReceive is SetUp { 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_transmitInPausedRegistry() external { + vm.startPrank(registry.owner()); + registry.pause(); + + _transmitAndExpectRevert(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, ) = deployAndConfigureRegistryAndRegistrar(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, ) = deployAndConfigureRegistryAndRegistrar(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/test/v2_3/BaseTest.t.sol b/contracts/src/v0.8/automation/test/v2_3/BaseTest.t.sol index 9016f52c55..e0d15daab6 100644 --- a/contracts/src/v0.8/automation/test/v2_3/BaseTest.t.sol +++ b/contracts/src/v0.8/automation/test/v2_3/BaseTest.t.sol @@ -20,14 +20,14 @@ 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 {WETH9} from "../WETH9.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 derrived tests) + // test state (not exposed to derived tests) uint256 private nonce; // constants @@ -283,7 +283,6 @@ contract BaseTest is Test { billingTokenAddresses, billingTokenConfigs ); - registry.setPayees(PAYEES); return (registry, registrar); } @@ -356,40 +355,58 @@ contract BaseTest is Test { ); } + // tests single upkeep, expects success function _transmit(uint256 id, Registry registry) internal { uint256[] memory ids = new uint256[](1); ids[0] = id; - _transmit(ids, registry); + _handleTransmit(ids, registry, bytes4(0)); } + // tests multiple upkeeps, expects success function _transmit(uint256[] memory ids, Registry registry) internal { - 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( - AutoBase.ConditionalTrigger(uint32(block.number - 1), blockhash(block.number - 1)) - ); - } else { - revert("not implemented"); + _handleTransmit(ids, registry, bytes4(0)); + } + + // tests single upkeep, expects revert + function _transmitAndExpectRevert(uint256 id, Registry registry, bytes4 selector) internal { + uint256[] memory ids = new uint256[](1); + ids[0] = id; + _handleTransmit(ids, registry, selector); + } + + // private function not exposed to actual testing contract + function _handleTransmit(uint256[] memory ids, Registry registry, bytes4 selector) private { + 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( + AutoBase.ConditionalTrigger(uint32(block.number - 1), blockhash(block.number - 1)) + ); + } else { + revert("not implemented"); + } } - } - AutoBase.Report memory report = AutoBase.Report( - uint256(1000000000), - uint256(2000000000), - upkeepIds, - gasLimits, - triggers, - performDatas - ); - bytes memory reportBytes = _encodeReport(report); + AutoBase.Report memory report = AutoBase.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); @@ -398,6 +415,9 @@ contract BaseTest is Test { (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(); } 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 0000000000..cde05ab3a2 --- /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 0000000000..7098d9f38f --- /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 0000000000..76b3877689 --- /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/upkeeps/CronUpkeep.sol b/contracts/src/v0.8/automation/upkeeps/CronUpkeep.sol index 614b84635a..b9eda1f400 100644 --- a/contracts/src/v0.8/automation/upkeeps/CronUpkeep.sol +++ b/contracts/src/v0.8/automation/upkeeps/CronUpkeep.sol @@ -18,12 +18,12 @@ pragma solidity 0.8.6; -import "@openzeppelin/contracts/security/Pausable.sol"; -import "@openzeppelin/contracts/proxy/Proxy.sol"; -import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import "../../shared/access/ConfirmedOwner.sol"; -import "../KeeperBase.sol"; -import "../interfaces/KeeperCompatibleInterface.sol"; +import {Pausable} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/security/Pausable.sol"; +import {Proxy} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/proxy/Proxy.sol"; +import {EnumerableSet} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/structs/EnumerableSet.sol"; +import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; +import {KeeperBase as KeeperBase} from "../KeeperBase.sol"; +import {KeeperCompatibleInterface as KeeperCompatibleInterface} from "../interfaces/KeeperCompatibleInterface.sol"; import {Cron as CronInternal, Spec} from "../libraries/internal/Cron.sol"; import {Cron as CronExternal} from "../libraries/external/Cron.sol"; diff --git a/contracts/src/v0.8/automation/upkeeps/CronUpkeepDelegate.sol b/contracts/src/v0.8/automation/upkeeps/CronUpkeepDelegate.sol index ec2c2a0fd9..ed8d031c86 100644 --- a/contracts/src/v0.8/automation/upkeeps/CronUpkeepDelegate.sol +++ b/contracts/src/v0.8/automation/upkeeps/CronUpkeepDelegate.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.6; -import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {EnumerableSet} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/structs/EnumerableSet.sol"; import {Cron, Spec} from "../libraries/internal/Cron.sol"; /** diff --git a/contracts/src/v0.8/automation/upkeeps/CronUpkeepFactory.sol b/contracts/src/v0.8/automation/upkeeps/CronUpkeepFactory.sol index cd9ae5d7a9..2b6e97e4d0 100644 --- a/contracts/src/v0.8/automation/upkeeps/CronUpkeepFactory.sol +++ b/contracts/src/v0.8/automation/upkeeps/CronUpkeepFactory.sol @@ -2,9 +2,9 @@ pragma solidity 0.8.6; -import "./CronUpkeep.sol"; -import "./CronUpkeepDelegate.sol"; -import "../../shared/access/ConfirmedOwner.sol"; +import {CronUpkeep} from "./CronUpkeep.sol"; +import {CronUpkeepDelegate} from "./CronUpkeepDelegate.sol"; +import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; import {Spec, Cron as CronExternal} from "../libraries/external/Cron.sol"; /** diff --git a/contracts/src/v0.8/automation/upkeeps/LinkAvailableBalanceMonitor.sol b/contracts/src/v0.8/automation/upkeeps/LinkAvailableBalanceMonitor.sol index 5d8a8d58c8..824bce711b 100644 --- a/contracts/src/v0.8/automation/upkeeps/LinkAvailableBalanceMonitor.sol +++ b/contracts/src/v0.8/automation/upkeeps/LinkAvailableBalanceMonitor.sol @@ -61,6 +61,7 @@ contract LinkAvailableBalanceMonitor is AccessControl, AutomationCompatibleInter error InvalidWatchList(); error InvalidChainSelector(); error DuplicateAddress(address duplicate); + error ReentrantCall(); struct MonitoredAddress { uint96 minBalance; @@ -94,6 +95,8 @@ contract LinkAvailableBalanceMonitor is AccessControl, AutomationCompatibleInter /// whenever a new one is deployed with the same dstChainSelector. EnumerableMap.UintToAddressMap private s_onRampAddresses; + bool private reentrancyGuard; + /// @param admin is the administrator address of this contract /// @param linkToken the LINK token address /// @param minWaitPeriodSeconds represents the amount of time that has to wait a contract to be funded @@ -116,6 +119,7 @@ contract LinkAvailableBalanceMonitor is AccessControl, AutomationCompatibleInter setMaxPerform(maxPerform); setMaxCheck(maxCheck); setUpkeepInterval(upkeepInterval); + reentrancyGuard = false; } /// @notice Sets the list of subscriptions to watch and their funding parameters @@ -259,7 +263,7 @@ contract LinkAvailableBalanceMonitor is AccessControl, AutomationCompatibleInter /// @notice tries to fund an array of target addresses, checking if they're underfunded in the process /// @param targetAddresses is an array of contract addresses to be funded in case they're underfunded - function topUp(address[] memory targetAddresses) public whenNotPaused { + function topUp(address[] memory targetAddresses) public whenNotPaused nonReentrant { MonitoredAddress memory contractToFund; uint256 minWaitPeriod = s_minWaitPeriodSeconds; uint256 localBalance = i_linkToken.balanceOf(address(this)); @@ -457,6 +461,13 @@ contract LinkAvailableBalanceMonitor is AccessControl, AutomationCompatibleInter _; } + modifier nonReentrant() { + if (reentrancyGuard) revert ReentrantCall(); + reentrancyGuard = true; + _; + reentrancyGuard = false; + } + /// @notice Pause the contract, which prevents executing performUpkeep function pause() external onlyRole(ADMIN_ROLE) { _pause(); diff --git a/contracts/src/v0.8/automation/v2_2/AutomationRegistry2_2.sol b/contracts/src/v0.8/automation/v2_2/AutomationRegistry2_2.sol index f0c703679c..464e874639 100644 --- a/contracts/src/v0.8/automation/v2_2/AutomationRegistry2_2.sol +++ b/contracts/src/v0.8/automation/v2_2/AutomationRegistry2_2.sol @@ -115,7 +115,7 @@ contract AutomationRegistry2_2 is AutomationRegistryBase2_2, OCR2Abstract, Chain }); uint256 blocknumber = hotVars.chainModule.blockNumber(); - uint256 l1Fee = hotVars.chainModule.getCurrentL1Fee(); + uint256 l1Fee = hotVars.chainModule.getCurrentL1Fee(msg.data.length); for (uint256 i = 0; i < report.upkeepIds.length; i++) { upkeepTransmitInfo[i].upkeep = s_upkeep[report.upkeepIds[i]]; diff --git a/contracts/src/v0.8/automation/v2_3/AutomationRegistry2_3.sol b/contracts/src/v0.8/automation/v2_3/AutomationRegistry2_3.sol index 6113cbf9fd..031d7b5dfb 100644 --- a/contracts/src/v0.8/automation/v2_3/AutomationRegistry2_3.sol +++ b/contracts/src/v0.8/automation/v2_3/AutomationRegistry2_3.sol @@ -136,7 +136,7 @@ contract AutomationRegistry2_3 is AutomationRegistryBase2_3, OCR2Abstract, Chain }); uint256 blocknumber = hotVars.chainModule.blockNumber(); - uint256 l1Fee = hotVars.chainModule.getCurrentL1Fee(); + uint256 l1Fee = hotVars.chainModule.getCurrentL1Fee(msg.data.length); for (uint256 i = 0; i < report.upkeepIds.length; i++) { upkeepTransmitInfo[i].upkeep = s_upkeep[report.upkeepIds[i]]; diff --git a/contracts/src/v0.8/automation/v2_3/AutomationRegistryBase2_3.sol b/contracts/src/v0.8/automation/v2_3/AutomationRegistryBase2_3.sol index fa8f06ffc0..354a6a9b47 100644 --- a/contracts/src/v0.8/automation/v2_3/AutomationRegistryBase2_3.sol +++ b/contracts/src/v0.8/automation/v2_3/AutomationRegistryBase2_3.sol @@ -45,7 +45,7 @@ abstract contract AutomationRegistryBase2_3 is ConfirmedOwner { // These values are calibrated using hardhat tests which simulate various cases and verify that // the variables result in accurate estimation 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_LOG_OVERHEAD = 123_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 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 new file mode 100644 index 0000000000..5d5bf23aa2 --- /dev/null +++ b/contracts/src/v0.8/automation/v2_3_zksync/ZKSyncAutomationRegistry2_3.sol @@ -0,0 +1,386 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.19; + +import {EnumerableSet} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/structs/EnumerableSet.sol"; +import {Address} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol"; +import {ZKSyncAutomationRegistryBase2_3} from "./ZKSyncAutomationRegistryBase2_3.sol"; +import {ZKSyncAutomationRegistryLogicA2_3} from "./ZKSyncAutomationRegistryLogicA2_3.sol"; +import {ZKSyncAutomationRegistryLogicC2_3} from "./ZKSyncAutomationRegistryLogicC2_3.sol"; +import {Chainable} from "../Chainable.sol"; +import {OCR2Abstract} from "../../shared/ocr2/OCR2Abstract.sol"; +import {IERC20Metadata as IERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/extensions/IERC20Metadata.sol"; + +/** + * @notice Registry for adding work for Chainlink nodes to perform on client + * contracts. Clients must support the AutomationCompatibleInterface interface. + */ +contract ZKSyncAutomationRegistry2_3 is ZKSyncAutomationRegistryBase2_3, OCR2Abstract, Chainable { + using Address for address; + using EnumerableSet for EnumerableSet.UintSet; + using EnumerableSet for EnumerableSet.AddressSet; + + /** + * @notice versions: + * AutomationRegistry 2.3.0: supports native and ERC20 billing + * changes flat fee to USD-denominated + * adds support for custom billing overrides + * AutomationRegistry 2.2.0: moves chain-specific integration code into a separate module + * KeeperRegistry 2.1.0: introduces support for log triggers + * removes the need for "wrapped perform data" + * KeeperRegistry 2.0.2: pass revert bytes as performData when target contract reverts + * fixes issue with arbitrum block number + * does an early return in case of stale report instead of revert + * KeeperRegistry 2.0.1: implements workaround for buggy migrate function in 1.X + * KeeperRegistry 2.0.0: implement OCR interface + * KeeperRegistry 1.3.0: split contract into Proxy and Logic + * account for Arbitrum and Optimism L1 gas fee + * allow users to configure upkeeps + * KeeperRegistry 1.2.0: allow funding within performUpkeep + * allow configurable registry maxPerformGas + * add function to let admin change upkeep gas limit + * add minUpkeepSpend requirement + * upgrade to solidity v0.8 + * KeeperRegistry 1.1.0: added flatFeeMicroLink + * KeeperRegistry 1.0.0: initial release + */ + string public constant override typeAndVersion = "AutomationRegistry 2.3.0"; + + /** + * @param logicA the address of the first logic contract + * @dev we cast the contract to logicC in order to call logicC functions (via fallback) + */ + constructor( + ZKSyncAutomationRegistryLogicA2_3 logicA + ) + ZKSyncAutomationRegistryBase2_3( + ZKSyncAutomationRegistryLogicC2_3(address(logicA)).getLinkAddress(), + ZKSyncAutomationRegistryLogicC2_3(address(logicA)).getLinkUSDFeedAddress(), + ZKSyncAutomationRegistryLogicC2_3(address(logicA)).getNativeUSDFeedAddress(), + ZKSyncAutomationRegistryLogicC2_3(address(logicA)).getFastGasFeedAddress(), + ZKSyncAutomationRegistryLogicC2_3(address(logicA)).getAutomationForwarderLogic(), + ZKSyncAutomationRegistryLogicC2_3(address(logicA)).getAllowedReadOnlyAddress(), + ZKSyncAutomationRegistryLogicC2_3(address(logicA)).getPayoutMode(), + ZKSyncAutomationRegistryLogicC2_3(address(logicA)).getWrappedNativeTokenAddress() + ) + Chainable(address(logicA)) + {} + + /** + * @notice holds the variables used in the transmit function, necessary to avoid stack too deep errors + */ + struct TransmitVars { + uint16 numUpkeepsPassedChecks; + uint96 totalReimbursement; + uint96 totalPremium; + } + + // ================================================================ + // | HOT PATH ACTIONS | + // ================================================================ + + /** + * @inheritdoc OCR2Abstract + */ + function transmit( + bytes32[3] calldata reportContext, + bytes calldata rawReport, + bytes32[] calldata rs, + bytes32[] calldata ss, + bytes32 rawVs + ) external override { + // 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 + uint256 requiredLength = 324 + rawReport.length + 64 * rs.length; + if (msg.data.length != requiredLength) revert InvalidDataLength(); + HotVars memory hotVars = s_hotVars; + + if (hotVars.paused) revert RegistryPaused(); + if (!s_transmitters[msg.sender].active) revert OnlyActiveTransmitters(); + + // Verify signatures + if (s_latestConfigDigest != reportContext[0]) revert ConfigDigestMismatch(); + if (rs.length != hotVars.f + 1 || rs.length != ss.length) revert IncorrectNumberOfSignatures(); + _verifyReportSignature(reportContext, rawReport, rs, ss, rawVs); + + Report memory report = _decodeReport(rawReport); + + uint40 epochAndRound = uint40(uint256(reportContext[1])); + uint32 epoch = uint32(epochAndRound >> 8); + + _handleReport(hotVars, report); + + if (epoch > hotVars.latestEpoch) { + s_hotVars.latestEpoch = epoch; + } + } + + /** + * @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) + * @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) private { + UpkeepTransmitInfo[] memory upkeepTransmitInfo = new UpkeepTransmitInfo[](report.upkeepIds.length); + TransmitVars memory transmitVars = TransmitVars({ + numUpkeepsPassedChecks: 0, + totalReimbursement: 0, + totalPremium: 0 + }); + + uint256 blocknumber = hotVars.chainModule.blockNumber(); + uint256 gasOverhead; + + for (uint256 i = 0; i < report.upkeepIds.length; i++) { + upkeepTransmitInfo[i].upkeep = s_upkeep[report.upkeepIds[i]]; + upkeepTransmitInfo[i].triggerType = _getTriggerType(report.upkeepIds[i]); + + (upkeepTransmitInfo[i].earlyChecksPassed, upkeepTransmitInfo[i].dedupID) = _prePerformChecks( + report.upkeepIds[i], + blocknumber, + report.triggers[i], + upkeepTransmitInfo[i], + hotVars + ); + + if (upkeepTransmitInfo[i].earlyChecksPassed) { + transmitVars.numUpkeepsPassedChecks += 1; + } else { + continue; + } + + // Actually perform the target upkeep + (upkeepTransmitInfo[i].performSuccess, upkeepTransmitInfo[i].gasUsed) = _performUpkeep( + upkeepTransmitInfo[i].upkeep.forwarder, + report.gasLimits[i], + report.performDatas[i] + ); + + // 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 if (upkeepTransmitInfo[i].triggerType == Trigger.LOG) { + gasOverhead += REGISTRY_LOG_OVERHEAD; + } else { + revert InvalidTriggerType(); + } + } + // No upkeeps to be performed in this report + if (transmitVars.numUpkeepsPassedChecks == 0) { + return; + } + + 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; + + { + BillingTokenPaymentParams memory billingTokenParams; + uint256 nativeUSD = _getNativeUSD(hotVars); + for (uint256 i = 0; i < report.upkeepIds.length; i++) { + if (upkeepTransmitInfo[i].earlyChecksPassed) { + if (i == 0 || upkeepTransmitInfo[i].upkeep.billingToken != upkeepTransmitInfo[i - 1].upkeep.billingToken) { + billingTokenParams = _getBillingTokenPaymentParams(hotVars, upkeepTransmitInfo[i].upkeep.billingToken); + } + PaymentReceipt memory receipt = _handlePayment( + hotVars, + PaymentParams({ + gasLimit: upkeepTransmitInfo[i].gasUsed, + gasOverhead: gasOverhead, + l1CostWei: 0, + fastGasWei: report.fastGasWei, + linkUSD: report.linkUSD, + nativeUSD: nativeUSD, + billingToken: upkeepTransmitInfo[i].upkeep.billingToken, + billingTokenParams: billingTokenParams, + isTransaction: true + }), + report.upkeepIds[i], + upkeepTransmitInfo[i].upkeep + ); + transmitVars.totalPremium += receipt.premiumInJuels; + transmitVars.totalReimbursement += receipt.gasReimbursementInJuels; + + emit UpkeepPerformed( + report.upkeepIds[i], + upkeepTransmitInfo[i].performSuccess, + receipt.gasChargeInBillingToken + receipt.premiumInBillingToken, + upkeepTransmitInfo[i].gasUsed, + gasOverhead, + report.triggers[i] + ); + } + } + } + // record payments to NOPs, all in LINK + s_transmitters[msg.sender].balance += transmitVars.totalReimbursement; + s_hotVars.totalPremium += transmitVars.totalPremium; + s_reserveAmounts[IERC20(address(i_link))] += transmitVars.totalReimbursement + transmitVars.totalPremium; + } + + // ================================================================ + // | OCR2ABSTRACT | + // ================================================================ + + /** + * @inheritdoc OCR2Abstract + * @dev prefer the type-safe version of setConfig (below) whenever possible. The OnchainConfig could differ between registry versions + * @dev this function takes up precious space on the root contract, but must be implemented to conform to the OCR2Abstract interface + */ + function setConfig( + address[] memory signers, + address[] memory transmitters, + uint8 f, + bytes memory onchainConfigBytes, + uint64 offchainConfigVersion, + bytes memory offchainConfig + ) external override { + (OnchainConfig memory config, IERC20[] memory billingTokens, BillingConfig[] memory billingConfigs) = abi.decode( + onchainConfigBytes, + (OnchainConfig, IERC20[], BillingConfig[]) + ); + + setConfigTypeSafe( + signers, + transmitters, + f, + config, + offchainConfigVersion, + offchainConfig, + billingTokens, + billingConfigs + ); + } + + /** + * @notice sets the configuration for the registry + * @param signers the list of permitted signers + * @param transmitters the list of permitted transmitters + * @param f the maximum tolerance for faulty nodes + * @param onchainConfig configuration values that are used on-chain + * @param offchainConfigVersion the version of the offchainConfig + * @param offchainConfig configuration values that are used off-chain + * @param billingTokens the list of valid billing tokens + * @param billingConfigs the configurations for each billing token + */ + function setConfigTypeSafe( + address[] memory signers, + address[] memory transmitters, + uint8 f, + OnchainConfig memory onchainConfig, + uint64 offchainConfigVersion, + bytes memory offchainConfig, + IERC20[] memory billingTokens, + BillingConfig[] memory billingConfigs + ) public onlyOwner { + if (signers.length > MAX_NUM_ORACLES) revert TooManyOracles(); + if (f == 0) revert IncorrectNumberOfFaultyOracles(); + if (signers.length != transmitters.length || signers.length <= 3 * f) revert IncorrectNumberOfSigners(); + if (billingTokens.length != billingConfigs.length) revert ParameterLengthError(); + // set billing config for tokens + _setBillingConfig(billingTokens, billingConfigs); + + _updateTransmitters(signers, transmitters); + + s_hotVars = HotVars({ + f: f, + stalenessSeconds: onchainConfig.stalenessSeconds, + gasCeilingMultiplier: onchainConfig.gasCeilingMultiplier, + paused: s_hotVars.paused, + reentrancyGuard: s_hotVars.reentrancyGuard, + totalPremium: s_hotVars.totalPremium, + latestEpoch: 0, // DON restarts epoch + reorgProtectionEnabled: onchainConfig.reorgProtectionEnabled, + chainModule: onchainConfig.chainModule + }); + + uint32 previousConfigBlockNumber = s_storage.latestConfigBlockNumber; + uint32 newLatestConfigBlockNumber = uint32(onchainConfig.chainModule.blockNumber()); + uint32 newConfigCount = s_storage.configCount + 1; + + s_storage = Storage({ + checkGasLimit: onchainConfig.checkGasLimit, + maxPerformGas: onchainConfig.maxPerformGas, + transcoder: onchainConfig.transcoder, + maxCheckDataSize: onchainConfig.maxCheckDataSize, + maxPerformDataSize: onchainConfig.maxPerformDataSize, + maxRevertDataSize: onchainConfig.maxRevertDataSize, + upkeepPrivilegeManager: onchainConfig.upkeepPrivilegeManager, + financeAdmin: onchainConfig.financeAdmin, + nonce: s_storage.nonce, + configCount: newConfigCount, + latestConfigBlockNumber: newLatestConfigBlockNumber + }); + s_fallbackGasPrice = onchainConfig.fallbackGasPrice; + s_fallbackLinkPrice = onchainConfig.fallbackLinkPrice; + s_fallbackNativePrice = onchainConfig.fallbackNativePrice; + + bytes memory onchainConfigBytes = abi.encode(onchainConfig); + + s_latestConfigDigest = _configDigestFromConfigData( + block.chainid, + address(this), + s_storage.configCount, + signers, + transmitters, + f, + onchainConfigBytes, + offchainConfigVersion, + offchainConfig + ); + + for (uint256 idx = s_registrars.length(); idx > 0; idx--) { + s_registrars.remove(s_registrars.at(idx - 1)); + } + + for (uint256 idx = 0; idx < onchainConfig.registrars.length; idx++) { + s_registrars.add(onchainConfig.registrars[idx]); + } + + emit ConfigSet( + previousConfigBlockNumber, + s_latestConfigDigest, + s_storage.configCount, + signers, + transmitters, + f, + onchainConfigBytes, + offchainConfigVersion, + offchainConfig + ); + } + + /** + * @inheritdoc OCR2Abstract + * @dev this function takes up precious space on the root contract, but must be implemented to conform to the OCR2Abstract interface + */ + function latestConfigDetails() + external + view + override + returns (uint32 configCount, uint32 blockNumber, bytes32 configDigest) + { + return (s_storage.configCount, s_storage.latestConfigBlockNumber, s_latestConfigDigest); + } + + /** + * @inheritdoc OCR2Abstract + * @dev this function takes up precious space on the root contract, but must be implemented to conform to the OCR2Abstract interface + */ + function latestConfigDigestAndEpoch() + external + view + override + returns (bool scanLogs, bytes32 configDigest, uint32 epoch) + { + return (false, s_latestConfigDigest, s_hotVars.latestEpoch); + } +} 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 new file mode 100644 index 0000000000..41097af7f2 --- /dev/null +++ b/contracts/src/v0.8/automation/v2_3_zksync/ZKSyncAutomationRegistryBase2_3.sol @@ -0,0 +1,1197 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.19; + +import {EnumerableSet} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/structs/EnumerableSet.sol"; +import {Address} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol"; +import {StreamsLookupCompatibleInterface} from "../interfaces/StreamsLookupCompatibleInterface.sol"; +import {ILogAutomation, Log} from "../interfaces/ILogAutomation.sol"; +import {IAutomationForwarder} from "../interfaces/IAutomationForwarder.sol"; +import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; +import {AggregatorV3Interface} from "../../shared/interfaces/AggregatorV3Interface.sol"; +import {LinkTokenInterface} from "../../shared/interfaces/LinkTokenInterface.sol"; +import {KeeperCompatibleInterface} from "../interfaces/KeeperCompatibleInterface.sol"; +import {IChainModule} from "../interfaces/IChainModule.sol"; +import {IERC20Metadata as IERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import {SafeCast} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/math/SafeCast.sol"; +import {IWrappedNative} from "../interfaces/v2_3/IWrappedNative.sol"; + +/** + * @notice Base Keeper Registry contract, contains shared logic between + * AutomationRegistry and AutomationRegistryLogic + * @dev all errors, events, and internal functions should live here + */ +// solhint-disable-next-line max-states-count +abstract contract ZKSyncAutomationRegistryBase2_3 is ConfirmedOwner { + using Address for address; + using EnumerableSet for EnumerableSet.UintSet; + using EnumerableSet for EnumerableSet.AddressSet; + + address internal constant ZERO_ADDRESS = address(0); + address internal constant IGNORE_ADDRESS = 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF; + bytes4 internal constant CHECK_SELECTOR = KeeperCompatibleInterface.checkUpkeep.selector; + bytes4 internal constant PERFORM_SELECTOR = KeeperCompatibleInterface.performUpkeep.selector; + bytes4 internal constant CHECK_CALLBACK_SELECTOR = StreamsLookupCompatibleInterface.checkCallback.selector; + bytes4 internal constant CHECK_LOG_SELECTOR = ILogAutomation.checkLog.selector; + uint256 internal constant PERFORM_GAS_MIN = 2_300; + uint256 internal constant CANCELLATION_DELAY = 50; + 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; + uint8 internal constant UPKEEP_VERSION_BASE = 4; + + // Next block of constants are only used in maxPayment estimation during checkUpkeep simulation + // These values are calibrated using hardhat tests which simulate various cases and verify that + // the variables result in accurate estimation + 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 + + // 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_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; + AggregatorV3Interface internal immutable i_nativeUSDFeed; + AggregatorV3Interface internal immutable i_fastGasFeed; + address internal immutable i_automationForwarderLogic; + address internal immutable i_allowedReadOnlyAddress; + IWrappedNative internal immutable i_wrappedNativeToken; + + /** + * @dev - The storage is gas optimised for one and only one function - transmit. All the storage accessed in transmit + * is stored compactly. Rest of the storage layout is not of much concern as transmit is the only hot path + */ + + // Upkeep storage + EnumerableSet.UintSet internal s_upkeepIDs; + mapping(uint256 => Upkeep) internal s_upkeep; // accessed during transmit + mapping(uint256 => address) internal s_upkeepAdmin; + mapping(uint256 => address) internal s_proposedAdmin; + mapping(uint256 => bytes) internal s_checkData; + mapping(bytes32 => bool) internal s_dedupKeys; + // Registry config and state + EnumerableSet.AddressSet internal s_registrars; + mapping(address => Transmitter) internal s_transmitters; + mapping(address => Signer) internal s_signers; + address[] internal s_signersList; // s_signersList contains the signing address of each oracle + address[] internal s_transmittersList; // s_transmittersList contains the transmission address of each oracle + EnumerableSet.AddressSet internal s_deactivatedTransmitters; + mapping(address => address) internal s_transmitterPayees; // s_payees contains the mapping from transmitter to payee. + mapping(address => address) internal s_proposedPayee; // proposed payee for a transmitter + bytes32 internal s_latestConfigDigest; // Read on transmit path in case of signature verification + HotVars internal s_hotVars; // Mixture of config and state, used in transmit + Storage internal s_storage; // Mixture of config and state, not used in transmit + uint256 internal s_fallbackGasPrice; + uint256 internal s_fallbackLinkPrice; + uint256 internal s_fallbackNativePrice; + mapping(address => MigrationPermission) internal s_peerRegistryMigrationPermission; // Permissions for migration to and fro + mapping(uint256 => bytes) internal s_upkeepTriggerConfig; // upkeep triggers + mapping(uint256 => bytes) internal s_upkeepOffchainConfig; // general config set by users for each upkeep + mapping(uint256 => bytes) internal s_upkeepPrivilegeConfig; // general config set by an administrative role for an upkeep + mapping(address => bytes) internal s_adminPrivilegeConfig; // general config set by an administrative role for an admin + // billing + mapping(IERC20 billingToken => uint256 reserveAmount) internal s_reserveAmounts; // unspent user deposits + unwithdrawn NOP payments + mapping(IERC20 billingToken => BillingConfig billingConfig) internal s_billingConfigs; // billing configurations for different tokens + mapping(uint256 upkeepID => BillingOverrides billingOverrides) internal s_billingOverrides; // billing overrides for specific upkeeps + IERC20[] internal s_billingTokens; // list of billing tokens + PayoutMode internal s_payoutMode; + + 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 InvalidTrigger(); + error InvalidPayee(); + error InvalidRecipient(); + error InvalidReport(); + error InvalidSigner(); + error InvalidToken(); + error InvalidTransmitter(); + 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(); + + enum MigrationPermission { + NONE, + OUTGOING, + INCOMING, + BIDIRECTIONAL + } + + enum Trigger { + CONDITION, + LOG + } + + enum UpkeepFailureReason { + NONE, + UPKEEP_CANCELLED, + UPKEEP_PAUSED, + TARGET_CHECK_REVERTED, + UPKEEP_NOT_NEEDED, + PERFORM_DATA_EXCEEDS_LIMIT, + INSUFFICIENT_BALANCE, + CALLBACK_REVERTED, + REVERT_DATA_EXCEEDS_LIMIT, + REGISTRY_PAUSED + } + + enum PayoutMode { + ON_CHAIN, + OFF_CHAIN + } + + /** + * @notice OnchainConfig of the registry + * @dev used only in setConfig() + * @member checkGasLimit gas limit when checking for upkeep + * @member stalenessSeconds number of seconds that is allowed for feed data to + * be stale before switching to the fallback pricing + * @member gasCeilingMultiplier multiplier to apply to the fast gas feed price + * when calculating the payment ceiling for keepers + * @member maxPerformGas max performGas allowed for an upkeep on this registry + * @member maxCheckDataSize max length of checkData bytes + * @member maxPerformDataSize max length of performData bytes + * @member maxRevertDataSize max length of revertData bytes + * @member fallbackGasPrice gas price used if the gas price feed is stale + * @member fallbackLinkPrice LINK price used if the LINK price feed is stale + * @member transcoder address of the transcoder contract + * @member registrars addresses of the registrar contracts + * @member upkeepPrivilegeManager address which can set privilege for upkeeps + * @member reorgProtectionEnabled if this registry enables re-org protection checks + * @member chainModule the chain specific module + */ + struct OnchainConfig { + uint32 checkGasLimit; + uint32 maxPerformGas; + uint32 maxCheckDataSize; + address transcoder; + // 1 word full + bool reorgProtectionEnabled; + uint24 stalenessSeconds; + uint32 maxPerformDataSize; + uint32 maxRevertDataSize; + address upkeepPrivilegeManager; + // 2 words full + uint16 gasCeilingMultiplier; + address financeAdmin; + // 3 words + uint256 fallbackGasPrice; + uint256 fallbackLinkPrice; + uint256 fallbackNativePrice; + address[] registrars; + IChainModule chainModule; + } + + /** + * @notice relevant state of an upkeep which is used in transmit function + * @member paused if this upkeep has been paused + * @member overridesEnabled if this upkeep has overrides enabled + * @member performGas the gas limit of upkeep execution + * @member maxValidBlocknumber until which block this upkeep is valid + * @member forwarder the forwarder contract to use for this upkeep + * @member amountSpent the amount this upkeep has spent, in the upkeep's billing token + * @member balance the balance of this upkeep + * @member lastPerformedBlockNumber the last block number when this upkeep was performed + */ + struct Upkeep { + bool paused; + bool overridesEnabled; + uint32 performGas; + uint32 maxValidBlocknumber; + IAutomationForwarder forwarder; + // 2 bytes left in 1st EVM word - read in transmit path + uint128 amountSpent; + uint96 balance; + uint32 lastPerformedBlockNumber; + // 0 bytes left in 2nd EVM word - written in transmit path + IERC20 billingToken; + // 12 bytes left in 3rd EVM word - read in transmit path + } + + /// @dev Config + State storage struct which is on hot transmit path + struct HotVars { + uint96 totalPremium; // ─────────╮ total historical payment to oracles for premium + uint32 latestEpoch; // │ latest epoch for which a report was transmitted + uint24 stalenessSeconds; // │ Staleness tolerance for feeds + uint16 gasCeilingMultiplier; // │ multiplier on top of fast gas feed for upper bound + uint8 f; // │ maximum number of faulty oracles + bool paused; // │ pause switch for all upkeeps in the registry + bool reentrancyGuard; // | guard against reentrancy + bool reorgProtectionEnabled; // ─╯ if this registry should enable the re-org protection mechanism + IChainModule chainModule; // the interface of chain specific module + } + + /// @dev Config + State storage struct which is not on hot transmit path + struct Storage { + address transcoder; // Address of transcoder contract used in migrations + uint32 checkGasLimit; // Gas limit allowed in checkUpkeep + uint32 maxPerformGas; // Max gas an upkeep can use on this registry + uint32 nonce; // Nonce for each upkeep created + // 1 EVM word full + address upkeepPrivilegeManager; // address which can set privilege for upkeeps + uint32 configCount; // incremented each time a new config is posted, The count is incorporated into the config digest to prevent replay attacks. + uint32 latestConfigBlockNumber; // makes it easier for offchain systems to extract config from logs + uint32 maxCheckDataSize; // max length of checkData bytes + // 2 EVM word full + address financeAdmin; // address which can withdraw funds from the contract + uint32 maxPerformDataSize; // max length of performData bytes + uint32 maxRevertDataSize; // max length of revertData bytes + // 4 bytes left in 3rd EVM word + } + + /// @dev Report transmitted by OCR to transmit function + struct Report { + uint256 fastGasWei; + uint256 linkUSD; + uint256[] upkeepIds; + uint256[] gasLimits; + bytes[] triggers; + bytes[] performDatas; + } + + /** + * @dev This struct is used to maintain run time information about an upkeep in transmit function + * @member upkeep the upkeep struct + * @member earlyChecksPassed whether the upkeep passed early checks before perform + * @member performSuccess whether the perform was successful + * @member triggerType the type of trigger + * @member gasUsed gasUsed by this upkeep in perform + * @member dedupID unique ID used to dedup an upkeep/trigger combo + */ + struct UpkeepTransmitInfo { + Upkeep upkeep; + bool earlyChecksPassed; + bool performSuccess; + Trigger triggerType; + uint256 gasUsed; + bytes32 dedupID; + } + + /** + * @notice holds information about a transmiter / node in the DON + * @member active can this transmitter submit reports + * @member index of oracle in s_signersList/s_transmittersList + * @member balance a node's balance in LINK + * @member lastCollected the total balance at which the node last withdrew + * @dev uint96 is safe for balance / last collected because transmitters are only ever paid in LINK + */ + struct Transmitter { + bool active; + uint8 index; + uint96 balance; + uint96 lastCollected; + } + + struct TransmitterPayeeInfo { + address transmitterAddress; + address payeeAddress; + } + + struct Signer { + bool active; + // Index of oracle in s_signersList/s_transmittersList + uint8 index; + } + + /** + * @notice the trigger structure conditional trigger type + */ + struct ConditionalTrigger { + uint32 blockNum; + bytes32 blockHash; + } + + /** + * @notice the trigger structure of log upkeeps + * @dev NOTE that blockNum / blockHash describe the block used for the callback, + * not necessarily the block number that the log was emitted in!!!! + */ + struct LogTrigger { + bytes32 logBlockHash; + bytes32 txHash; + uint32 logIndex; + uint32 blockNum; + bytes32 blockHash; + } + + /** + * @notice the billing config of a token + * @dev this is a storage struct + */ + // solhint-disable-next-line gas-struct-packing + struct BillingConfig { + uint32 gasFeePPB; + uint24 flatFeeMilliCents; // min fee is $0.00001, max fee is $167 + AggregatorV3Interface priceFeed; + uint8 decimals; + // 1st word, read in calculating BillingTokenPaymentParams + uint256 fallbackPrice; + // 2nd word only read if stale + uint96 minSpend; + // 3rd word only read during cancellation + } + + /** + * @notice override-able billing params of a billing token + */ + struct BillingOverrides { + uint32 gasFeePPB; + uint24 flatFeeMilliCents; + } + + /** + * @notice pricing params for a billing token + * @dev this is a memory-only struct, so struct packing is less important + */ + struct BillingTokenPaymentParams { + uint8 decimals; + uint32 gasFeePPB; + uint24 flatFeeMilliCents; + uint256 priceUSD; + } + + /** + * @notice struct containing price & payment information used in calculating payment amount + * @member gasLimit the amount of gas used + * @member gasOverhead the amount of gas overhead + * @member l1CostWei the amount to be charged for L1 fee in wei + * @member fastGasWei the fast gas price + * @member linkUSD the exchange ratio between LINK and USD + * @member nativeUSD the exchange ratio between the chain's native token and USD + * @member billingToken the billing token + * @member billingTokenParams the payment params specific to a particular payment token + * @member isTransaction is this an eth_call or a transaction + */ + struct PaymentParams { + uint256 gasLimit; + uint256 gasOverhead; + uint256 l1CostWei; + uint256 fastGasWei; + uint256 linkUSD; + uint256 nativeUSD; + IERC20 billingToken; + BillingTokenPaymentParams billingTokenParams; + bool isTransaction; + } + + /** + * @notice struct containing receipt information about a payment or cost estimation + * @member gasChargeInBillingToken the amount to charge a user for gas spent using the billing token's native decimals + * @member premiumInBillingToken the premium charged to the user, shared between all nodes, using the billing token's native decimals + * @member gasReimbursementInJuels the amount to reimburse a node for gas spent + * @member premiumInJuels the premium paid to NOPs, shared between all nodes + */ + // solhint-disable-next-line gas-struct-packing + struct PaymentReceipt { + uint96 gasChargeInBillingToken; + uint96 premiumInBillingToken; + // one word ends + uint96 gasReimbursementInJuels; + uint96 premiumInJuels; + // second word ends + IERC20 billingToken; + uint96 linkUSD; + // third word ends + uint96 nativeUSD; + uint96 billingUSD; + // fourth word ends + } + + event AdminPrivilegeConfigSet(address indexed admin, bytes privilegeConfig); + event BillingConfigOverridden(uint256 indexed id, BillingOverrides overrides); + event BillingConfigOverrideRemoved(uint256 indexed id); + event BillingConfigSet(IERC20 indexed token, BillingConfig config); + event CancelledUpkeepReport(uint256 indexed id, bytes trigger); + event ChainSpecificModuleUpdated(address newModule); + 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 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 UpkeepAdminTransferred(uint256 indexed id, address indexed from, address indexed to); + event UpkeepAdminTransferRequested(uint256 indexed id, address indexed from, address indexed to); + event UpkeepCanceled(uint256 indexed id, uint64 indexed atBlockHeight); + 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 UpkeepCharged(uint256 indexed id, PaymentReceipt receipt); + 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); + event Unpaused(address account); + + /** + * @param link address of the LINK Token + * @param linkUSDFeed address of the LINK/USD price feed + * @param nativeUSDFeed address of the Native/USD price feed + * @param fastGasFeed address of the Fast Gas price feed + * @param automationForwarderLogic the address of automation forwarder logic + * @param allowedReadOnlyAddress the address of the allowed read only address + * @param payoutMode the payout mode + */ + constructor( + address link, + address linkUSDFeed, + address nativeUSDFeed, + address fastGasFeed, + address automationForwarderLogic, + address allowedReadOnlyAddress, + PayoutMode payoutMode, + address wrappedNativeTokenAddress + ) ConfirmedOwner(msg.sender) { + i_link = LinkTokenInterface(link); + i_linkUSDFeed = AggregatorV3Interface(linkUSDFeed); + i_nativeUSDFeed = AggregatorV3Interface(nativeUSDFeed); + i_fastGasFeed = AggregatorV3Interface(fastGasFeed); + i_automationForwarderLogic = automationForwarderLogic; + i_allowedReadOnlyAddress = allowedReadOnlyAddress; + s_payoutMode = payoutMode; + i_wrappedNativeToken = IWrappedNative(wrappedNativeTokenAddress); + if (i_linkUSDFeed.decimals() != i_nativeUSDFeed.decimals()) { + revert InvalidFeed(); + } + } + + // ================================================================ + // | INTERNAL FUNCTIONS ONLY | + // ================================================================ + + /** + * @dev creates a new upkeep with the given fields + * @param id the id of the upkeep + * @param upkeep the upkeep to create + * @param admin address to cancel upkeep and withdraw remaining funds + * @param checkData data which is passed to user's checkUpkeep + * @param triggerConfig the trigger config for this upkeep + * @param offchainConfig the off-chain config of this upkeep + */ + function _createUpkeep( + uint256 id, + Upkeep memory upkeep, + address admin, + bytes memory checkData, + bytes memory triggerConfig, + bytes memory offchainConfig + ) internal { + if (s_hotVars.paused) revert RegistryPaused(); + if (checkData.length > s_storage.maxCheckDataSize) revert CheckDataExceedsLimit(); + if (upkeep.performGas < PERFORM_GAS_MIN || upkeep.performGas > s_storage.maxPerformGas) + revert GasLimitOutsideRange(); + if (address(s_upkeep[id].forwarder) != address(0)) revert UpkeepAlreadyExists(); + if (address(s_billingConfigs[upkeep.billingToken].priceFeed) == address(0)) revert InvalidToken(); + s_upkeep[id] = upkeep; + s_upkeepAdmin[id] = admin; + s_checkData[id] = checkData; + s_reserveAmounts[upkeep.billingToken] = s_reserveAmounts[upkeep.billingToken] + upkeep.balance; + s_upkeepTriggerConfig[id] = triggerConfig; + s_upkeepOffchainConfig[id] = offchainConfig; + s_upkeepIDs.add(id); + } + + /** + * @dev creates an ID for the upkeep based on the upkeep's type + * @dev the format of the ID looks like this: + * ****00000000000X**************** + * 4 bytes of entropy + * 11 bytes of zeros + * 1 identifying byte for the trigger type + * 16 bytes of entropy + * @dev this maintains the same level of entropy as eth addresses, so IDs will still be unique + * @dev we add the "identifying" part in the middle so that it is mostly hidden from users who usually only + * see the first 4 and last 4 hex values ex 0x1234...ABCD + */ + function _createID(Trigger triggerType) internal view returns (uint256) { + bytes1 empty; + IChainModule chainModule = s_hotVars.chainModule; + bytes memory idBytes = abi.encodePacked( + keccak256(abi.encode(chainModule.blockHash((chainModule.blockNumber() - 1)), address(this), s_storage.nonce)) + ); + for (uint256 idx = 4; idx < 15; idx++) { + idBytes[idx] = empty; + } + idBytes[15] = bytes1(uint8(triggerType)); + return uint256(bytes32(idBytes)); + } + + /** + * @dev retrieves feed data for fast gas/native and link/native prices. if the feed + * data is stale it uses the configured fallback price. Once a price is picked + * for gas it takes the min of gas price in the transaction or the fast gas + * price in order to reduce costs for the upkeep clients. + */ + function _getFeedData( + HotVars memory hotVars + ) internal view returns (uint256 gasWei, uint256 linkUSD, uint256 nativeUSD) { + uint32 stalenessSeconds = hotVars.stalenessSeconds; + bool staleFallback = stalenessSeconds > 0; + uint256 timestamp; + int256 feedValue; + (, feedValue, , timestamp, ) = i_fastGasFeed.latestRoundData(); + if ( + feedValue <= 0 || block.timestamp < timestamp || (staleFallback && stalenessSeconds < block.timestamp - timestamp) + ) { + gasWei = s_fallbackGasPrice; + } else { + gasWei = uint256(feedValue); + } + (, feedValue, , timestamp, ) = i_linkUSDFeed.latestRoundData(); + if ( + feedValue <= 0 || block.timestamp < timestamp || (staleFallback && stalenessSeconds < block.timestamp - timestamp) + ) { + linkUSD = s_fallbackLinkPrice; + } else { + linkUSD = uint256(feedValue); + } + return (gasWei, linkUSD, _getNativeUSD(hotVars)); + } + + /** + * @dev this price has it's own getter for use in the transmit() hot path + * in the future, all price data should be included in the report instead of + * getting read during execution + */ + function _getNativeUSD(HotVars memory hotVars) internal view returns (uint256) { + (, int256 feedValue, , uint256 timestamp, ) = i_nativeUSDFeed.latestRoundData(); + if ( + feedValue <= 0 || + block.timestamp < timestamp || + (hotVars.stalenessSeconds > 0 && hotVars.stalenessSeconds < block.timestamp - timestamp) + ) { + return s_fallbackNativePrice; + } else { + return uint256(feedValue); + } + } + + /** + * @dev gets the price and billing params for a specific billing token + */ + function _getBillingTokenPaymentParams( + HotVars memory hotVars, + IERC20 billingToken + ) internal view returns (BillingTokenPaymentParams memory paymentParams) { + BillingConfig storage config = s_billingConfigs[billingToken]; + paymentParams.flatFeeMilliCents = config.flatFeeMilliCents; + paymentParams.gasFeePPB = config.gasFeePPB; + paymentParams.decimals = config.decimals; + (, int256 feedValue, , uint256 timestamp, ) = config.priceFeed.latestRoundData(); + if ( + feedValue <= 0 || + block.timestamp < timestamp || + (hotVars.stalenessSeconds > 0 && hotVars.stalenessSeconds < block.timestamp - timestamp) + ) { + paymentParams.priceUSD = config.fallbackPrice; + } else { + paymentParams.priceUSD = uint256(feedValue); + } + return paymentParams; + } + + /** + * @param hotVars the hot path variables + * @param paymentParams the pricing data and gas usage data + * @return receipt the receipt of payment with pricing breakdown + * @dev use of PaymentParams struct is necessary to avoid stack too deep errors + * @dev calculates LINK paid for gas spent plus a configure premium percentage + * @dev 1 USD = 1e18 attoUSD + * @dev 1 USD = 1e26 hexaicosaUSD (had to borrow this prefix from geometry because there is no metric prefix for 1e-26) + * @dev 1 millicent = 1e-5 USD = 1e13 attoUSD + */ + function _calculatePaymentAmount( + HotVars memory hotVars, + PaymentParams memory paymentParams + ) internal view returns (PaymentReceipt memory receipt) { + uint256 decimals = paymentParams.billingTokenParams.decimals; + uint256 gasWei = paymentParams.fastGasWei * hotVars.gasCeilingMultiplier; + // in case it's actual execution use actual gas price, capped by fastGasWei * gasCeilingMultiplier + if (paymentParams.isTransaction && tx.gasprice < gasWei) { + gasWei = tx.gasprice; + } + + // scaling factor is based on decimals of billing token, and applies to premium and gasCharge + uint256 numeratorScalingFactor = decimals > 18 ? 10 ** (decimals - 18) : 1; + uint256 denominatorScalingFactor = decimals < 18 ? 10 ** (18 - decimals) : 1; + + // gas calculation + uint256 gasPaymentHexaicosaUSD = (gasWei * + (paymentParams.gasLimit + paymentParams.gasOverhead) + + paymentParams.l1CostWei) * paymentParams.nativeUSD; // gasPaymentHexaicosaUSD has an extra 8 zeros because of decimals on nativeUSD feed + // gasChargeInBillingToken is scaled by the billing token's decimals. Round up to ensure a minimum billing token is charged for gas + receipt.gasChargeInBillingToken = SafeCast.toUint96( + ((gasPaymentHexaicosaUSD * numeratorScalingFactor) + + (paymentParams.billingTokenParams.priceUSD * denominatorScalingFactor - 1)) / + (paymentParams.billingTokenParams.priceUSD * denominatorScalingFactor) + ); + // 18 decimals: 26 decimals / 8 decimals + receipt.gasReimbursementInJuels = SafeCast.toUint96(gasPaymentHexaicosaUSD / paymentParams.linkUSD); + + // premium calculation + uint256 flatFeeHexaicosaUSD = uint256(paymentParams.billingTokenParams.flatFeeMilliCents) * 1e21; // 1e13 for milliCents to attoUSD and 1e8 for attoUSD to hexaicosaUSD + uint256 premiumHexaicosaUSD = ((((gasWei * paymentParams.gasLimit) + paymentParams.l1CostWei) * + paymentParams.billingTokenParams.gasFeePPB * + paymentParams.nativeUSD) / 1e9) + flatFeeHexaicosaUSD; + // premium is scaled by the billing token's decimals. Round up to ensure at least minimum charge + receipt.premiumInBillingToken = SafeCast.toUint96( + ((premiumHexaicosaUSD * numeratorScalingFactor) + + (paymentParams.billingTokenParams.priceUSD * denominatorScalingFactor - 1)) / + (paymentParams.billingTokenParams.priceUSD * denominatorScalingFactor) + ); + receipt.premiumInJuels = SafeCast.toUint96(premiumHexaicosaUSD / paymentParams.linkUSD); + + receipt.billingToken = paymentParams.billingToken; + receipt.linkUSD = SafeCast.toUint96(paymentParams.linkUSD); + receipt.nativeUSD = SafeCast.toUint96(paymentParams.nativeUSD); + receipt.billingUSD = SafeCast.toUint96(paymentParams.billingTokenParams.priceUSD); + + return receipt; + } + + /** + * @dev calculates the max payment for an upkeep. Called during checkUpkeep simulation and assumes + * maximum gas overhead, L1 fee + */ + function _getMaxPayment( + uint256 upkeepId, + HotVars memory hotVars, + Trigger triggerType, + uint32 performGas, + uint256 fastGasWei, + uint256 linkUSD, + uint256 nativeUSD, + IERC20 billingToken + ) internal view returns (uint96) { + uint256 maxGasOverhead; + + { + if (triggerType == Trigger.CONDITION) { + maxGasOverhead = REGISTRY_CONDITIONAL_OVERHEAD; + } else if (triggerType == Trigger.LOG) { + maxGasOverhead = REGISTRY_LOG_OVERHEAD; + } else { + revert InvalidTriggerType(); + } + (uint256 chainModuleFixedOverhead, ) = s_hotVars.chainModule.getGasOverhead(); + maxGasOverhead += (REGISTRY_PER_SIGNER_GAS_OVERHEAD * (hotVars.f + 1)) + chainModuleFixedOverhead; + } + + BillingTokenPaymentParams memory paymentParams = _getBillingTokenPaymentParams(hotVars, billingToken); + if (s_upkeep[upkeepId].overridesEnabled) { + BillingOverrides memory billingOverrides = s_billingOverrides[upkeepId]; + // use the overridden configs + paymentParams.gasFeePPB = billingOverrides.gasFeePPB; + paymentParams.flatFeeMilliCents = billingOverrides.flatFeeMilliCents; + } + + PaymentReceipt memory receipt = _calculatePaymentAmount( + hotVars, + PaymentParams({ + gasLimit: performGas, + gasOverhead: maxGasOverhead, + l1CostWei: 0, + fastGasWei: fastGasWei, + linkUSD: linkUSD, + nativeUSD: nativeUSD, + billingToken: billingToken, + billingTokenParams: paymentParams, + isTransaction: false + }) + ); + + return receipt.gasChargeInBillingToken + receipt.premiumInBillingToken; + } + + /** + * @dev move a transmitter's balance from total pool to withdrawable balance + */ + function _updateTransmitterBalanceFromPool( + address transmitterAddress, + uint96 totalPremium, + uint96 payeeCount + ) internal returns (uint96) { + Transmitter memory transmitter = s_transmitters[transmitterAddress]; + + if (transmitter.active) { + uint96 uncollected = totalPremium - transmitter.lastCollected; + uint96 due = uncollected / payeeCount; + transmitter.balance += due; + transmitter.lastCollected += due * payeeCount; + s_transmitters[transmitterAddress] = transmitter; + } + + return transmitter.balance; + } + + /** + * @dev gets the trigger type from an upkeepID (trigger type is encoded in the middle of the ID) + */ + function _getTriggerType(uint256 upkeepId) internal pure returns (Trigger) { + bytes32 rawID = bytes32(upkeepId); + bytes1 empty = bytes1(0); + for (uint256 idx = 4; idx < 15; idx++) { + if (rawID[idx] != empty) { + // old IDs that were created before this standard and migrated to this registry + return Trigger.CONDITION; + } + } + return Trigger(uint8(rawID[15])); + } + + function _checkPayload( + uint256 upkeepId, + Trigger triggerType, + bytes memory triggerData + ) internal view returns (bytes memory) { + if (triggerType == Trigger.CONDITION) { + return abi.encodeWithSelector(CHECK_SELECTOR, s_checkData[upkeepId]); + } else if (triggerType == Trigger.LOG) { + Log memory log = abi.decode(triggerData, (Log)); + return abi.encodeWithSelector(CHECK_LOG_SELECTOR, log, s_checkData[upkeepId]); + } + revert InvalidTriggerType(); + } + + /** + * @dev _decodeReport decodes a serialized report into a Report struct + */ + function _decodeReport(bytes calldata rawReport) internal pure returns (Report memory) { + Report memory report = abi.decode(rawReport, (Report)); + uint256 expectedLength = report.upkeepIds.length; + if ( + report.gasLimits.length != expectedLength || + report.triggers.length != expectedLength || + report.performDatas.length != expectedLength + ) { + revert InvalidReport(); + } + return report; + } + + /** + * @dev Does some early sanity checks before actually performing an upkeep + * @return bool whether the upkeep should be performed + * @return bytes32 dedupID for preventing duplicate performances of this trigger + */ + function _prePerformChecks( + uint256 upkeepId, + uint256 blocknumber, + bytes memory rawTrigger, + UpkeepTransmitInfo memory transmitInfo, + HotVars memory hotVars + ) internal returns (bool, bytes32) { + bytes32 dedupID; + if (transmitInfo.triggerType == Trigger.CONDITION) { + if (!_validateConditionalTrigger(upkeepId, blocknumber, rawTrigger, transmitInfo, hotVars)) + return (false, dedupID); + } else if (transmitInfo.triggerType == Trigger.LOG) { + bool valid; + (valid, dedupID) = _validateLogTrigger(upkeepId, blocknumber, rawTrigger, hotVars); + if (!valid) return (false, dedupID); + } else { + revert InvalidTriggerType(); + } + if (transmitInfo.upkeep.maxValidBlocknumber <= blocknumber) { + // Can happen when an upkeep got cancelled after report was generated. + // However we have a CANCELLATION_DELAY of 50 blocks so shouldn't happen in practice + emit CancelledUpkeepReport(upkeepId, rawTrigger); + return (false, dedupID); + } + return (true, dedupID); + } + + /** + * @dev Does some early sanity checks before actually performing an upkeep + */ + function _validateConditionalTrigger( + uint256 upkeepId, + uint256 blocknumber, + bytes memory rawTrigger, + UpkeepTransmitInfo memory transmitInfo, + HotVars memory hotVars + ) internal returns (bool) { + ConditionalTrigger memory trigger = abi.decode(rawTrigger, (ConditionalTrigger)); + if (trigger.blockNum < transmitInfo.upkeep.lastPerformedBlockNumber) { + // Can happen when another report performed this upkeep after this report was generated + emit StaleUpkeepReport(upkeepId, rawTrigger); + return false; + } + if ( + (hotVars.reorgProtectionEnabled && + (trigger.blockHash != bytes32("") && hotVars.chainModule.blockHash(trigger.blockNum) != trigger.blockHash)) || + trigger.blockNum >= blocknumber + ) { + // There are two cases of reorged report + // 1. trigger block number is in future: this is an edge case during extreme deep reorgs of chain + // which is always protected against + // 2. blockHash at trigger block number was same as trigger time. This is an optional check which is + // applied if DON sends non empty trigger.blockHash. Note: It only works for last 256 blocks on chain + // when it is sent + emit ReorgedUpkeepReport(upkeepId, rawTrigger); + return false; + } + return true; + } + + function _validateLogTrigger( + uint256 upkeepId, + uint256 blocknumber, + bytes memory rawTrigger, + HotVars memory hotVars + ) internal returns (bool, bytes32) { + LogTrigger memory trigger = abi.decode(rawTrigger, (LogTrigger)); + bytes32 dedupID = keccak256(abi.encodePacked(upkeepId, trigger.logBlockHash, trigger.txHash, trigger.logIndex)); + if ( + (hotVars.reorgProtectionEnabled && + (trigger.blockHash != bytes32("") && hotVars.chainModule.blockHash(trigger.blockNum) != trigger.blockHash)) || + trigger.blockNum >= blocknumber + ) { + // Reorg protection is same as conditional trigger upkeeps + emit ReorgedUpkeepReport(upkeepId, rawTrigger); + return (false, dedupID); + } + if (s_dedupKeys[dedupID]) { + emit StaleUpkeepReport(upkeepId, rawTrigger); + return (false, dedupID); + } + return (true, dedupID); + } + + /** + * @dev Verify signatures attached to report + */ + function _verifyReportSignature( + bytes32[3] calldata reportContext, + bytes calldata report, + bytes32[] calldata rs, + bytes32[] calldata ss, + bytes32 rawVs + ) internal view { + bytes32 h = keccak256(abi.encode(keccak256(report), reportContext)); + // i-th byte counts number of sigs made by i-th signer + uint256 signedCount = 0; + + Signer memory signer; + address signerAddress; + for (uint256 i = 0; i < rs.length; i++) { + signerAddress = ecrecover(h, uint8(rawVs[i]) + 27, rs[i], ss[i]); + signer = s_signers[signerAddress]; + if (!signer.active) revert OnlyActiveSigners(); + unchecked { + signedCount += 1 << (8 * signer.index); + } + } + + if (signedCount & ORACLE_MASK != signedCount) revert DuplicateSigners(); + } + + /** + * @dev updates a storage marker for this upkeep to prevent duplicate and out of order performances + * @dev for conditional triggers we set the latest block number, for log triggers we store a dedupID + */ + function _updateTriggerMarker( + uint256 upkeepID, + uint256 blocknumber, + UpkeepTransmitInfo memory upkeepTransmitInfo + ) internal { + if (upkeepTransmitInfo.triggerType == Trigger.CONDITION) { + s_upkeep[upkeepID].lastPerformedBlockNumber = uint32(blocknumber); + } else if (upkeepTransmitInfo.triggerType == Trigger.LOG) { + s_dedupKeys[upkeepTransmitInfo.dedupID] = true; + emit DedupKeyAdded(upkeepTransmitInfo.dedupID); + } + } + + /** + * @dev calls the Upkeep target with the performData param passed in by the + * transmitter and the exact gas required by the Upkeep + */ + function _performUpkeep( + IAutomationForwarder forwarder, + uint256 performGas, + bytes memory performData + ) internal nonReentrant returns (bool success, uint256 gasUsed) { + performData = abi.encodeWithSelector(PERFORM_SELECTOR, performData); + return forwarder.forward(performGas, performData); + } + + /** + * @dev handles the payment processing after an upkeep has been performed. + * Deducts an upkeep's balance and increases the amount spent. + */ + function _handlePayment( + HotVars memory hotVars, + PaymentParams memory paymentParams, + uint256 upkeepId, + Upkeep memory upkeep + ) internal returns (PaymentReceipt memory) { + if (upkeep.overridesEnabled) { + BillingOverrides memory billingOverrides = s_billingOverrides[upkeepId]; + // use the overridden configs + paymentParams.billingTokenParams.gasFeePPB = billingOverrides.gasFeePPB; + paymentParams.billingTokenParams.flatFeeMilliCents = billingOverrides.flatFeeMilliCents; + } + + PaymentReceipt memory receipt = _calculatePaymentAmount(hotVars, paymentParams); + + // balance is in the token's native decimals + uint96 balance = upkeep.balance; + // payment is in the token's native decimals + uint96 payment = receipt.gasChargeInBillingToken + receipt.premiumInBillingToken; + + // scaling factors to adjust decimals between billing token and LINK + uint256 decimals = paymentParams.billingTokenParams.decimals; + uint256 scalingFactor1 = decimals < 18 ? 10 ** (18 - decimals) : 1; + uint256 scalingFactor2 = decimals > 18 ? 10 ** (decimals - 18) : 1; + + // this shouldn't happen, but in rare edge cases, we charge the full balance in case the user + // can't cover the amount owed + if (balance < receipt.gasChargeInBillingToken) { + // if the user can't cover the gas fee, then direct all of the payment to the transmitter and distribute no premium to the DON + payment = balance; + receipt.gasReimbursementInJuels = SafeCast.toUint96( + (balance * paymentParams.billingTokenParams.priceUSD * scalingFactor1) / + (paymentParams.linkUSD * scalingFactor2) + ); + receipt.premiumInJuels = 0; + receipt.premiumInBillingToken = 0; + receipt.gasChargeInBillingToken = balance; + } else if (balance < payment) { + // if the user can cover the gas fee, but not the premium, then reduce the premium + payment = balance; + receipt.premiumInJuels = SafeCast.toUint96( + ((balance * paymentParams.billingTokenParams.priceUSD * scalingFactor1) / + (paymentParams.linkUSD * scalingFactor2)) - receipt.gasReimbursementInJuels + ); + // round up + receipt.premiumInBillingToken = SafeCast.toUint96( + ((receipt.premiumInJuels * paymentParams.linkUSD * scalingFactor2) + + (paymentParams.billingTokenParams.priceUSD * scalingFactor1 - 1)) / + (paymentParams.billingTokenParams.priceUSD * scalingFactor1) + ); + } + + s_upkeep[upkeepId].balance -= payment; + s_upkeep[upkeepId].amountSpent += payment; + s_reserveAmounts[paymentParams.billingToken] -= payment; + + emit UpkeepCharged(upkeepId, receipt); + return receipt; + } + + /** + * @dev ensures the upkeep is not cancelled and the caller is the upkeep admin + */ + function _requireAdminAndNotCancelled(uint256 upkeepId) internal view { + if (msg.sender != s_upkeepAdmin[upkeepId]) revert OnlyCallableByAdmin(); + if (s_upkeep[upkeepId].maxValidBlocknumber != UINT32_MAX) revert UpkeepCancelled(); + } + + /** + * @dev replicates Open Zeppelin's ReentrancyGuard but optimized to fit our storage + */ + modifier nonReentrant() { + if (s_hotVars.reentrancyGuard) revert ReentrantCall(); + s_hotVars.reentrancyGuard = true; + _; + s_hotVars.reentrancyGuard = false; + } + + /** + * @notice only allows a pre-configured address to initiate offchain read + */ + function _preventExecution() internal view { + // solhint-disable-next-line avoid-tx-origin + if (tx.origin != i_allowedReadOnlyAddress) { + revert OnlySimulatedBackend(); + } + } + + /** + * @notice only allows finance admin to call the function + */ + function _onlyFinanceAdminAllowed() internal view { + if (msg.sender != s_storage.financeAdmin) { + revert OnlyFinanceAdmin(); + } + } + + /** + * @notice only allows privilege manager to call the function + */ + function _onlyPrivilegeManagerAllowed() internal view { + if (msg.sender != s_storage.upkeepPrivilegeManager) { + revert OnlyCallableByUpkeepPrivilegeManager(); + } + } + + /** + * @notice sets billing configuration for a token + * @param billingTokens the addresses of tokens + * @param billingConfigs the configs for tokens + */ + function _setBillingConfig(IERC20[] memory billingTokens, BillingConfig[] memory billingConfigs) internal { + // Clear existing data + for (uint256 i = 0; i < s_billingTokens.length; i++) { + delete s_billingConfigs[s_billingTokens[i]]; + } + delete s_billingTokens; + + PayoutMode mode = s_payoutMode; + for (uint256 i = 0; i < billingTokens.length; i++) { + IERC20 token = billingTokens[i]; + BillingConfig memory config = billingConfigs[i]; + + // most ERC20 tokens are 18 decimals, priceFeed must be 8 decimals + if (config.decimals != token.decimals() || config.priceFeed.decimals() != 8) { + revert InvalidToken(); + } + + // if LINK is a billing option, payout mode must be ON_CHAIN + if (address(token) == address(i_link) && mode == PayoutMode.OFF_CHAIN) { + revert InvalidToken(); + } + if (address(token) == ZERO_ADDRESS || address(config.priceFeed) == ZERO_ADDRESS) { + revert ZeroAddressNotAllowed(); + } + + // if this is a new token, add it to tokens list. Otherwise revert + if (address(s_billingConfigs[token].priceFeed) != ZERO_ADDRESS) { + revert DuplicateEntry(); + } + s_billingTokens.push(token); + + // update the billing config for an existing token or add a new one + s_billingConfigs[token] = config; + + emit BillingConfigSet(token, config); + } + } + + /** + * @notice updates the signers and transmitters lists + */ + function _updateTransmitters(address[] memory signers, address[] memory transmitters) internal { + uint96 transmittersListLength = uint96(s_transmittersList.length); + uint96 totalPremium = s_hotVars.totalPremium; + + // move all pooled payments out of the pool to each transmitter's balance + for (uint256 i = 0; i < s_transmittersList.length; i++) { + _updateTransmitterBalanceFromPool(s_transmittersList[i], totalPremium, transmittersListLength); + } + + // remove any old signer/transmitter addresses + address transmitterAddress; + PayoutMode mode = s_payoutMode; + for (uint256 i = 0; i < s_transmittersList.length; i++) { + transmitterAddress = s_transmittersList[i]; + delete s_signers[s_signersList[i]]; + // Do not delete the whole transmitter struct as it has balance information stored + s_transmitters[transmitterAddress].active = false; + if (mode == PayoutMode.OFF_CHAIN && s_transmitters[transmitterAddress].balance > 0) { + s_deactivatedTransmitters.add(transmitterAddress); + } + } + delete s_signersList; + delete s_transmittersList; + + // add new signer/transmitter addresses + Transmitter memory transmitter; + for (uint256 i = 0; i < signers.length; i++) { + if (s_signers[signers[i]].active) revert RepeatedSigner(); + if (signers[i] == ZERO_ADDRESS) revert InvalidSigner(); + s_signers[signers[i]] = Signer({active: true, index: uint8(i)}); + + transmitterAddress = transmitters[i]; + if (transmitterAddress == ZERO_ADDRESS) revert InvalidTransmitter(); + transmitter = s_transmitters[transmitterAddress]; + if (transmitter.active) revert RepeatedTransmitter(); + transmitter.active = true; + transmitter.index = uint8(i); + // new transmitters start afresh from current totalPremium + // some spare change of premium from previous pool will be forfeited + transmitter.lastCollected = s_hotVars.totalPremium; + s_transmitters[transmitterAddress] = transmitter; + if (mode == PayoutMode.OFF_CHAIN) { + s_deactivatedTransmitters.remove(transmitterAddress); + } + } + + s_signersList = signers; + s_transmittersList = transmitters; + } + + /** + * @notice returns the size of the LINK liquidity pool + # @dev LINK max supply < 2^96, so casting to int256 is safe + */ + function _linkAvailableForPayment() internal view returns (int256) { + return int256(i_link.balanceOf(address(this))) - int256(s_reserveAmounts[IERC20(address(i_link))]); + } +} diff --git a/contracts/src/v0.8/automation/v2_3_zksync/ZKSyncAutomationRegistryLogicA2_3.sol b/contracts/src/v0.8/automation/v2_3_zksync/ZKSyncAutomationRegistryLogicA2_3.sol new file mode 100644 index 0000000000..64d697c70f --- /dev/null +++ b/contracts/src/v0.8/automation/v2_3_zksync/ZKSyncAutomationRegistryLogicA2_3.sol @@ -0,0 +1,283 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.19; + +import {EnumerableSet} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/structs/EnumerableSet.sol"; +import {Address} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol"; +import {ZKSyncAutomationRegistryBase2_3} from "./ZKSyncAutomationRegistryBase2_3.sol"; +import {ZKSyncAutomationRegistryLogicC2_3} from "./ZKSyncAutomationRegistryLogicC2_3.sol"; +import {ZKSyncAutomationRegistryLogicB2_3} from "./ZKSyncAutomationRegistryLogicB2_3.sol"; +import {Chainable} from "../Chainable.sol"; +import {ZKSyncAutomationForwarder} from "../ZKSyncAutomationForwarder.sol"; +import {IAutomationForwarder} from "../interfaces/IAutomationForwarder.sol"; +import {UpkeepTranscoderInterfaceV2} from "../interfaces/UpkeepTranscoderInterfaceV2.sol"; +import {MigratableKeeperRegistryInterfaceV2} from "../interfaces/MigratableKeeperRegistryInterfaceV2.sol"; +import {IERC20Metadata as IERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import {SafeERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC677Receiver} from "../../shared/interfaces/IERC677Receiver.sol"; + +/** + * @notice Logic contract, works in tandem with AutomationRegistry as a proxy + */ +contract ZKSyncAutomationRegistryLogicA2_3 is ZKSyncAutomationRegistryBase2_3, Chainable, IERC677Receiver { + using Address for address; + using EnumerableSet for EnumerableSet.UintSet; + using EnumerableSet for EnumerableSet.AddressSet; + using SafeERC20 for IERC20; + + /** + * @param logicB the address of the second logic contract + * @dev we cast the contract to logicC in order to call logicC functions (via fallback) + */ + constructor( + ZKSyncAutomationRegistryLogicB2_3 logicB + ) + ZKSyncAutomationRegistryBase2_3( + ZKSyncAutomationRegistryLogicC2_3(address(logicB)).getLinkAddress(), + ZKSyncAutomationRegistryLogicC2_3(address(logicB)).getLinkUSDFeedAddress(), + ZKSyncAutomationRegistryLogicC2_3(address(logicB)).getNativeUSDFeedAddress(), + ZKSyncAutomationRegistryLogicC2_3(address(logicB)).getFastGasFeedAddress(), + ZKSyncAutomationRegistryLogicC2_3(address(logicB)).getAutomationForwarderLogic(), + ZKSyncAutomationRegistryLogicC2_3(address(logicB)).getAllowedReadOnlyAddress(), + ZKSyncAutomationRegistryLogicC2_3(address(logicB)).getPayoutMode(), + ZKSyncAutomationRegistryLogicC2_3(address(logicB)).getWrappedNativeTokenAddress() + ) + Chainable(address(logicB)) + {} + + /** + * @notice uses LINK's transferAndCall to LINK and add funding to an upkeep + * @dev safe to cast uint256 to uint96 as total LINK supply is under UINT96MAX + * @param sender the account which transferred the funds + * @param amount number of LINK transfer + */ + function onTokenTransfer(address sender, uint256 amount, bytes calldata data) external override { + if (msg.sender != address(i_link)) revert OnlyCallableByLINKToken(); + if (data.length != 32) revert InvalidDataLength(); + uint256 id = abi.decode(data, (uint256)); + if (s_upkeep[id].maxValidBlocknumber != UINT32_MAX) revert UpkeepCancelled(); + if (address(s_upkeep[id].billingToken) != address(i_link)) revert InvalidToken(); + s_upkeep[id].balance = s_upkeep[id].balance + uint96(amount); + s_reserveAmounts[IERC20(address(i_link))] = s_reserveAmounts[IERC20(address(i_link))] + amount; + emit FundsAdded(id, sender, uint96(amount)); + } + + // ================================================================ + // | UPKEEP MANAGEMENT | + // ================================================================ + + /** + * @notice adds a new upkeep + * @param target address to perform upkeep on + * @param gasLimit amount of gas to provide the target contract when + * performing upkeep + * @param admin address to cancel upkeep and withdraw remaining funds + * @param triggerType the trigger for the upkeep + * @param billingToken the billing token for the upkeep + * @param checkData data passed to the contract when checking for upkeep + * @param triggerConfig the config for the trigger + * @param offchainConfig arbitrary offchain config for the upkeep + */ + function registerUpkeep( + address target, + uint32 gasLimit, + address admin, + Trigger triggerType, + IERC20 billingToken, + bytes calldata checkData, + bytes memory triggerConfig, + bytes memory offchainConfig + ) public returns (uint256 id) { + if (msg.sender != owner() && !s_registrars.contains(msg.sender)) revert OnlyCallableByOwnerOrRegistrar(); + if (!target.isContract()) revert NotAContract(); + id = _createID(triggerType); + IAutomationForwarder forwarder = IAutomationForwarder( + address(new ZKSyncAutomationForwarder(target, address(this), i_automationForwarderLogic)) + ); + _createUpkeep( + id, + Upkeep({ + overridesEnabled: false, + performGas: gasLimit, + balance: 0, + maxValidBlocknumber: UINT32_MAX, + lastPerformedBlockNumber: 0, + amountSpent: 0, + paused: false, + forwarder: forwarder, + billingToken: billingToken + }), + admin, + checkData, + triggerConfig, + offchainConfig + ); + s_storage.nonce++; + emit UpkeepRegistered(id, gasLimit, admin); + emit UpkeepCheckDataSet(id, checkData); + emit UpkeepTriggerConfigSet(id, triggerConfig); + emit UpkeepOffchainConfigSet(id, offchainConfig); + return (id); + } + + /** + * @notice cancels an upkeep + * @param id the upkeepID to cancel + * @dev if a user cancels an upkeep, their funds are locked for CANCELLATION_DELAY blocks to + * allow any pending performUpkeep txs time to get confirmed + */ + function cancelUpkeep(uint256 id) external { + Upkeep memory upkeep = s_upkeep[id]; + bool isOwner = msg.sender == owner(); + uint96 minSpend = s_billingConfigs[upkeep.billingToken].minSpend; + + uint256 height = s_hotVars.chainModule.blockNumber(); + if (upkeep.maxValidBlocknumber == 0) revert CannotCancel(); + if (upkeep.maxValidBlocknumber != UINT32_MAX) revert UpkeepCancelled(); + if (!isOwner && msg.sender != s_upkeepAdmin[id]) revert OnlyCallableByOwnerOrAdmin(); + + if (!isOwner) { + height = height + CANCELLATION_DELAY; + } + s_upkeep[id].maxValidBlocknumber = uint32(height); + s_upkeepIDs.remove(id); + + // charge the cancellation fee if the minSpend is not met + uint96 cancellationFee = 0; + // cancellationFee is min(max(minSpend - amountSpent, 0), amountLeft) + if (upkeep.amountSpent < minSpend) { + cancellationFee = minSpend - uint96(upkeep.amountSpent); + if (cancellationFee > upkeep.balance) { + cancellationFee = upkeep.balance; + } + } + s_upkeep[id].balance = upkeep.balance - cancellationFee; + s_reserveAmounts[upkeep.billingToken] = s_reserveAmounts[upkeep.billingToken] - cancellationFee; + + emit UpkeepCanceled(id, uint64(height)); + } + + /** + * @notice migrates upkeeps from one registry to another. + * @param ids the upkeepIDs to migrate + * @param destination the destination registry address + * @dev a transcoder must be set in order to enable migration + * @dev migration permissions must be set on *both* sending and receiving registries + * @dev only an upkeep admin can migrate their upkeeps + * @dev this function is most gas-efficient if upkeepIDs are sorted by billing token + * @dev s_billingOverrides and s_upkeepPrivilegeConfig are not migrated in this function + */ + function migrateUpkeeps(uint256[] calldata ids, address destination) external { + if ( + s_peerRegistryMigrationPermission[destination] != MigrationPermission.OUTGOING && + s_peerRegistryMigrationPermission[destination] != MigrationPermission.BIDIRECTIONAL + ) revert MigrationNotPermitted(); + if (s_storage.transcoder == ZERO_ADDRESS) revert TranscoderNotSet(); + if (ids.length == 0) revert ArrayHasNoEntries(); + + IERC20 billingToken; + uint256 balanceToTransfer; + uint256 id; + Upkeep memory upkeep; + address[] memory admins = new address[](ids.length); + Upkeep[] memory upkeeps = new Upkeep[](ids.length); + bytes[] memory checkDatas = new bytes[](ids.length); + bytes[] memory triggerConfigs = new bytes[](ids.length); + bytes[] memory offchainConfigs = new bytes[](ids.length); + + for (uint256 idx = 0; idx < ids.length; idx++) { + id = ids[idx]; + upkeep = s_upkeep[id]; + + if (idx == 0) { + billingToken = upkeep.billingToken; + balanceToTransfer = upkeep.balance; + } + + // if we encounter a new billing token, send the sum from the last billing token to the destination registry + if (upkeep.billingToken != billingToken) { + s_reserveAmounts[billingToken] = s_reserveAmounts[billingToken] - balanceToTransfer; + billingToken.safeTransfer(destination, balanceToTransfer); + billingToken = upkeep.billingToken; + balanceToTransfer = upkeep.balance; + } else if (idx != 0) { + balanceToTransfer += upkeep.balance; + } + + _requireAdminAndNotCancelled(id); + upkeep.forwarder.updateRegistry(destination); + + upkeeps[idx] = upkeep; + admins[idx] = s_upkeepAdmin[id]; + checkDatas[idx] = s_checkData[id]; + triggerConfigs[idx] = s_upkeepTriggerConfig[id]; + offchainConfigs[idx] = s_upkeepOffchainConfig[id]; + delete s_upkeep[id]; + delete s_checkData[id]; + delete s_upkeepTriggerConfig[id]; + delete s_upkeepOffchainConfig[id]; + // nullify existing proposed admin change if an upkeep is being migrated + delete s_proposedAdmin[id]; + delete s_upkeepAdmin[id]; + s_upkeepIDs.remove(id); + emit UpkeepMigrated(id, upkeep.balance, destination); + } + // always transfer the rolling sum in the end + s_reserveAmounts[billingToken] = s_reserveAmounts[billingToken] - balanceToTransfer; + billingToken.safeTransfer(destination, balanceToTransfer); + + bytes memory encodedUpkeeps = abi.encode( + ids, + upkeeps, + new address[](ids.length), + admins, + checkDatas, + triggerConfigs, + offchainConfigs + ); + MigratableKeeperRegistryInterfaceV2(destination).receiveUpkeeps( + UpkeepTranscoderInterfaceV2(s_storage.transcoder).transcodeUpkeeps( + UPKEEP_VERSION_BASE, + MigratableKeeperRegistryInterfaceV2(destination).upkeepVersion(), + encodedUpkeeps + ) + ); + } + + /** + * @notice received upkeeps migrated from another registry + * @param encodedUpkeeps the raw upkeep data to import + * @dev this function is never called directly, it is only called by another registry's migrate function + * @dev s_billingOverrides and s_upkeepPrivilegeConfig are not handled in this function + */ + function receiveUpkeeps(bytes calldata encodedUpkeeps) external { + if ( + s_peerRegistryMigrationPermission[msg.sender] != MigrationPermission.INCOMING && + s_peerRegistryMigrationPermission[msg.sender] != MigrationPermission.BIDIRECTIONAL + ) revert MigrationNotPermitted(); + ( + uint256[] memory ids, + Upkeep[] memory upkeeps, + address[] memory targets, + address[] memory upkeepAdmins, + bytes[] memory checkDatas, + bytes[] memory triggerConfigs, + bytes[] memory offchainConfigs + ) = abi.decode(encodedUpkeeps, (uint256[], Upkeep[], address[], address[], bytes[], bytes[], bytes[])); + for (uint256 idx = 0; idx < ids.length; idx++) { + if (address(upkeeps[idx].forwarder) == ZERO_ADDRESS) { + upkeeps[idx].forwarder = IAutomationForwarder( + address(new ZKSyncAutomationForwarder(targets[idx], address(this), i_automationForwarderLogic)) + ); + } + _createUpkeep( + ids[idx], + upkeeps[idx], + upkeepAdmins[idx], + checkDatas[idx], + triggerConfigs[idx], + offchainConfigs[idx] + ); + emit UpkeepReceived(ids[idx], upkeeps[idx].balance, msg.sender); + } + } +} diff --git a/contracts/src/v0.8/automation/v2_3_zksync/ZKSyncAutomationRegistryLogicB2_3.sol b/contracts/src/v0.8/automation/v2_3_zksync/ZKSyncAutomationRegistryLogicB2_3.sol new file mode 100644 index 0000000000..55af99fde8 --- /dev/null +++ b/contracts/src/v0.8/automation/v2_3_zksync/ZKSyncAutomationRegistryLogicB2_3.sol @@ -0,0 +1,449 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.19; + +import {ZKSyncAutomationRegistryBase2_3} from "./ZKSyncAutomationRegistryBase2_3.sol"; +import {EnumerableSet} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/structs/EnumerableSet.sol"; +import {Address} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol"; +import {ZKSyncAutomationRegistryLogicC2_3} from "./ZKSyncAutomationRegistryLogicC2_3.sol"; +import {Chainable} from "../Chainable.sol"; +import {IERC20Metadata as IERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import {SafeERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol"; +import {SafeCast} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/math/SafeCast.sol"; + +contract ZKSyncAutomationRegistryLogicB2_3 is ZKSyncAutomationRegistryBase2_3, Chainable { + using Address for address; + using EnumerableSet for EnumerableSet.UintSet; + using EnumerableSet for EnumerableSet.AddressSet; + using SafeERC20 for IERC20; + + /** + * @param logicC the address of the third logic contract + */ + constructor( + ZKSyncAutomationRegistryLogicC2_3 logicC + ) + ZKSyncAutomationRegistryBase2_3( + logicC.getLinkAddress(), + logicC.getLinkUSDFeedAddress(), + logicC.getNativeUSDFeedAddress(), + logicC.getFastGasFeedAddress(), + logicC.getAutomationForwarderLogic(), + logicC.getAllowedReadOnlyAddress(), + logicC.getPayoutMode(), + logicC.getWrappedNativeTokenAddress() + ) + Chainable(address(logicC)) + {} + + // ================================================================ + // | PIPELINE FUNCTIONS | + // ================================================================ + + /** + * @notice called by the automation DON to check if work is needed + * @param id the upkeep ID to check for work needed + * @param triggerData extra contextual data about the trigger (not used in all code paths) + * @dev this one of the core functions called in the hot path + * @dev there is a 2nd checkUpkeep function (below) that is being maintained for backwards compatibility + * @dev there is an incongruency on what gets returned during failure modes + * ex sometimes we include price data, sometimes we omit it depending on the failure + */ + function checkUpkeep( + uint256 id, + bytes memory triggerData + ) + public + returns ( + bool upkeepNeeded, + bytes memory performData, + UpkeepFailureReason upkeepFailureReason, + uint256 gasUsed, + uint256 gasLimit, + uint256 fastGasWei, + uint256 linkUSD + ) + { + _preventExecution(); + + Trigger triggerType = _getTriggerType(id); + HotVars memory hotVars = s_hotVars; + Upkeep memory upkeep = s_upkeep[id]; + + { + uint256 nativeUSD; + uint96 maxPayment; + if (hotVars.paused) return (false, bytes(""), UpkeepFailureReason.REGISTRY_PAUSED, 0, upkeep.performGas, 0, 0); + if (upkeep.maxValidBlocknumber != UINT32_MAX) + return (false, bytes(""), UpkeepFailureReason.UPKEEP_CANCELLED, 0, upkeep.performGas, 0, 0); + if (upkeep.paused) return (false, bytes(""), UpkeepFailureReason.UPKEEP_PAUSED, 0, upkeep.performGas, 0, 0); + (fastGasWei, linkUSD, nativeUSD) = _getFeedData(hotVars); + maxPayment = _getMaxPayment( + id, + hotVars, + triggerType, + upkeep.performGas, + fastGasWei, + linkUSD, + nativeUSD, + upkeep.billingToken + ); + if (upkeep.balance < maxPayment) { + return (false, bytes(""), UpkeepFailureReason.INSUFFICIENT_BALANCE, 0, upkeep.performGas, 0, 0); + } + } + + bytes memory callData = _checkPayload(id, triggerType, triggerData); + + gasUsed = gasleft(); + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory result) = upkeep.forwarder.getTarget().call{gas: s_storage.checkGasLimit}(callData); + gasUsed = gasUsed - gasleft(); + + if (!success) { + // User's target check reverted. We capture the revert data here and pass it within performData + if (result.length > s_storage.maxRevertDataSize) { + return ( + false, + bytes(""), + UpkeepFailureReason.REVERT_DATA_EXCEEDS_LIMIT, + gasUsed, + upkeep.performGas, + fastGasWei, + linkUSD + ); + } + return ( + upkeepNeeded, + result, + UpkeepFailureReason.TARGET_CHECK_REVERTED, + gasUsed, + upkeep.performGas, + fastGasWei, + linkUSD + ); + } + + (upkeepNeeded, performData) = abi.decode(result, (bool, bytes)); + if (!upkeepNeeded) + return (false, bytes(""), UpkeepFailureReason.UPKEEP_NOT_NEEDED, gasUsed, upkeep.performGas, fastGasWei, linkUSD); + + if (performData.length > s_storage.maxPerformDataSize) + return ( + false, + bytes(""), + UpkeepFailureReason.PERFORM_DATA_EXCEEDS_LIMIT, + gasUsed, + upkeep.performGas, + fastGasWei, + linkUSD + ); + + return (upkeepNeeded, performData, upkeepFailureReason, gasUsed, upkeep.performGas, fastGasWei, linkUSD); + } + + /** + * @notice see other checkUpkeep function for description + * @dev this function may be deprecated in a future version of chainlink automation + */ + function checkUpkeep( + uint256 id + ) + external + returns ( + bool upkeepNeeded, + bytes memory performData, + UpkeepFailureReason upkeepFailureReason, + uint256 gasUsed, + uint256 gasLimit, + uint256 fastGasWei, + uint256 linkUSD + ) + { + return checkUpkeep(id, bytes("")); + } + + /** + * @dev checkCallback is used specifically for automation data streams lookups (see StreamsLookupCompatibleInterface.sol) + * @param id the upkeepID to execute a callback for + * @param values the values returned from the data streams lookup + * @param extraData the user-provided extra context data + */ + function checkCallback( + uint256 id, + bytes[] memory values, + bytes calldata extraData + ) + external + returns (bool upkeepNeeded, bytes memory performData, UpkeepFailureReason upkeepFailureReason, uint256 gasUsed) + { + bytes memory payload = abi.encodeWithSelector(CHECK_CALLBACK_SELECTOR, values, extraData); + return executeCallback(id, payload); + } + + /** + * @notice this is a generic callback executor that forwards a call to a user's contract with the configured + * gas limit + * @param id the upkeepID to execute a callback for + * @param payload the data (including function selector) to call on the upkeep target contract + */ + function executeCallback( + uint256 id, + bytes memory payload + ) + public + returns (bool upkeepNeeded, bytes memory performData, UpkeepFailureReason upkeepFailureReason, uint256 gasUsed) + { + _preventExecution(); + + Upkeep memory upkeep = s_upkeep[id]; + gasUsed = gasleft(); + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory result) = upkeep.forwarder.getTarget().call{gas: s_storage.checkGasLimit}(payload); + gasUsed = gasUsed - gasleft(); + if (!success) { + return (false, bytes(""), UpkeepFailureReason.CALLBACK_REVERTED, gasUsed); + } + (upkeepNeeded, performData) = abi.decode(result, (bool, bytes)); + if (!upkeepNeeded) { + return (false, bytes(""), UpkeepFailureReason.UPKEEP_NOT_NEEDED, gasUsed); + } + if (performData.length > s_storage.maxPerformDataSize) { + return (false, bytes(""), UpkeepFailureReason.PERFORM_DATA_EXCEEDS_LIMIT, gasUsed); + } + return (upkeepNeeded, performData, upkeepFailureReason, gasUsed); + } + + /** + * @notice simulates the upkeep with the perform data returned from checkUpkeep + * @param id identifier of the upkeep to execute the data with. + * @param performData calldata parameter to be passed to the target upkeep. + * @return success whether the call reverted or not + * @return gasUsed the amount of gas the target contract consumed + */ + function simulatePerformUpkeep( + uint256 id, + bytes calldata performData + ) external returns (bool success, uint256 gasUsed) { + _preventExecution(); + + if (s_hotVars.paused) revert RegistryPaused(); + Upkeep memory upkeep = s_upkeep[id]; + (success, gasUsed) = _performUpkeep(upkeep.forwarder, upkeep.performGas, performData); + return (success, gasUsed); + } + + // ================================================================ + // | UPKEEP MANAGEMENT | + // ================================================================ + + /** + * @notice adds fund to an upkeep + * @param id the upkeepID + * @param amount the amount of funds to add, in the upkeep's billing token + */ + function addFunds(uint256 id, uint96 amount) external payable { + Upkeep memory upkeep = s_upkeep[id]; + if (upkeep.maxValidBlocknumber != UINT32_MAX) revert UpkeepCancelled(); + + if (msg.value != 0) { + if (upkeep.billingToken != IERC20(i_wrappedNativeToken)) { + revert InvalidToken(); + } + amount = SafeCast.toUint96(msg.value); + } + + s_upkeep[id].balance = upkeep.balance + amount; + s_reserveAmounts[upkeep.billingToken] = s_reserveAmounts[upkeep.billingToken] + amount; + + if (msg.value == 0) { + // ERC20 payment + upkeep.billingToken.safeTransferFrom(msg.sender, address(this), amount); + } else { + // native payment + i_wrappedNativeToken.deposit{value: amount}(); + } + + emit FundsAdded(id, msg.sender, amount); + } + + /** + * @notice overrides the billing config for an upkeep + * @param id the upkeepID + * @param billingOverrides the override-able billing config + */ + function setBillingOverrides(uint256 id, BillingOverrides calldata billingOverrides) external { + _onlyPrivilegeManagerAllowed(); + if (s_upkeep[id].maxValidBlocknumber != UINT32_MAX) revert UpkeepCancelled(); + + s_upkeep[id].overridesEnabled = true; + s_billingOverrides[id] = billingOverrides; + emit BillingConfigOverridden(id, billingOverrides); + } + + /** + * @notice remove the overridden billing config for an upkeep + * @param id the upkeepID + */ + function removeBillingOverrides(uint256 id) external { + _onlyPrivilegeManagerAllowed(); + + s_upkeep[id].overridesEnabled = false; + delete s_billingOverrides[id]; + emit BillingConfigOverrideRemoved(id); + } + + /** + * @notice transfers the address of an admin for an upkeep + */ + function transferUpkeepAdmin(uint256 id, address proposed) external { + _requireAdminAndNotCancelled(id); + if (proposed == msg.sender) revert ValueNotChanged(); + + if (s_proposedAdmin[id] != proposed) { + s_proposedAdmin[id] = proposed; + emit UpkeepAdminTransferRequested(id, msg.sender, proposed); + } + } + + /** + * @notice accepts the transfer of an upkeep admin + */ + function acceptUpkeepAdmin(uint256 id) external { + Upkeep memory upkeep = s_upkeep[id]; + if (upkeep.maxValidBlocknumber != UINT32_MAX) revert UpkeepCancelled(); + if (s_proposedAdmin[id] != msg.sender) revert OnlyCallableByProposedAdmin(); + address past = s_upkeepAdmin[id]; + s_upkeepAdmin[id] = msg.sender; + s_proposedAdmin[id] = ZERO_ADDRESS; + + emit UpkeepAdminTransferred(id, past, msg.sender); + } + + /** + * @notice pauses an upkeep - an upkeep will be neither checked nor performed while paused + */ + function pauseUpkeep(uint256 id) external { + _requireAdminAndNotCancelled(id); + Upkeep memory upkeep = s_upkeep[id]; + if (upkeep.paused) revert OnlyUnpausedUpkeep(); + s_upkeep[id].paused = true; + s_upkeepIDs.remove(id); + emit UpkeepPaused(id); + } + + /** + * @notice unpauses an upkeep + */ + function unpauseUpkeep(uint256 id) external { + _requireAdminAndNotCancelled(id); + Upkeep memory upkeep = s_upkeep[id]; + if (!upkeep.paused) revert OnlyPausedUpkeep(); + s_upkeep[id].paused = false; + s_upkeepIDs.add(id); + emit UpkeepUnpaused(id); + } + + /** + * @notice updates the checkData for an upkeep + */ + function setUpkeepCheckData(uint256 id, bytes calldata newCheckData) external { + _requireAdminAndNotCancelled(id); + if (newCheckData.length > s_storage.maxCheckDataSize) revert CheckDataExceedsLimit(); + s_checkData[id] = newCheckData; + emit UpkeepCheckDataSet(id, newCheckData); + } + + /** + * @notice updates the gas limit for an upkeep + */ + function setUpkeepGasLimit(uint256 id, uint32 gasLimit) external { + if (gasLimit < PERFORM_GAS_MIN || gasLimit > s_storage.maxPerformGas) revert GasLimitOutsideRange(); + _requireAdminAndNotCancelled(id); + s_upkeep[id].performGas = gasLimit; + + emit UpkeepGasLimitSet(id, gasLimit); + } + + /** + * @notice updates the offchain config for an upkeep + */ + function setUpkeepOffchainConfig(uint256 id, bytes calldata config) external { + _requireAdminAndNotCancelled(id); + s_upkeepOffchainConfig[id] = config; + emit UpkeepOffchainConfigSet(id, config); + } + + /** + * @notice sets the upkeep trigger config + * @param id the upkeepID to change the trigger for + * @param triggerConfig the new trigger config + */ + function setUpkeepTriggerConfig(uint256 id, bytes calldata triggerConfig) external { + _requireAdminAndNotCancelled(id); + s_upkeepTriggerConfig[id] = triggerConfig; + emit UpkeepTriggerConfigSet(id, triggerConfig); + } + + /** + * @notice withdraws an upkeep's funds from an upkeep + * @dev note that an upkeep must be cancelled first!! + */ + function withdrawFunds(uint256 id, address to) external nonReentrant { + if (to == ZERO_ADDRESS) revert InvalidRecipient(); + Upkeep memory upkeep = s_upkeep[id]; + if (s_upkeepAdmin[id] != msg.sender) revert OnlyCallableByAdmin(); + if (upkeep.maxValidBlocknumber > s_hotVars.chainModule.blockNumber()) revert UpkeepNotCanceled(); + uint96 amountToWithdraw = s_upkeep[id].balance; + s_reserveAmounts[upkeep.billingToken] = s_reserveAmounts[upkeep.billingToken] - amountToWithdraw; + s_upkeep[id].balance = 0; + upkeep.billingToken.safeTransfer(to, amountToWithdraw); + emit FundsWithdrawn(id, amountToWithdraw, to); + } + + // ================================================================ + // | FINANCE ACTIONS | + // ================================================================ + + /** + * @notice withdraws excess LINK from the liquidity pool + * @param to the address to send the fees to + * @param amount the amount to withdraw + */ + function withdrawLink(address to, uint256 amount) external { + _onlyFinanceAdminAllowed(); + if (to == ZERO_ADDRESS) revert InvalidRecipient(); + + int256 available = _linkAvailableForPayment(); + if (available < 0) { + revert InsufficientBalance(0, amount); + } else if (amount > uint256(available)) { + revert InsufficientBalance(uint256(available), amount); + } + + bool transferStatus = i_link.transfer(to, amount); + if (!transferStatus) { + revert TransferFailed(); + } + emit FeesWithdrawn(address(i_link), to, amount); + } + + /** + * @notice withdraws non-LINK fees earned by the contract + * @param asset the asset to withdraw + * @param to the address to send the fees to + * @param amount the amount to withdraw + * @dev in ON_CHAIN mode, we prevent withdrawing non-LINK fees unless there is sufficient LINK liquidity + * to cover all outstanding debts on the registry + */ + function withdrawERC20Fees(IERC20 asset, address to, uint256 amount) external { + _onlyFinanceAdminAllowed(); + if (to == ZERO_ADDRESS) revert InvalidRecipient(); + if (address(asset) == address(i_link)) revert InvalidToken(); + if (_linkAvailableForPayment() < 0 && s_payoutMode == PayoutMode.ON_CHAIN) revert InsufficientLinkLiquidity(); + uint256 available = asset.balanceOf(address(this)) - s_reserveAmounts[asset]; + if (amount > available) revert InsufficientBalance(available, amount); + + asset.safeTransfer(to, amount); + emit FeesWithdrawn(address(asset), to, amount); + } +} 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 new file mode 100644 index 0000000000..3b4b023c7a --- /dev/null +++ b/contracts/src/v0.8/automation/v2_3_zksync/ZKSyncAutomationRegistryLogicC2_3.sol @@ -0,0 +1,626 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.19; + +import {ZKSyncAutomationRegistryBase2_3} from "./ZKSyncAutomationRegistryBase2_3.sol"; +import {EnumerableSet} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/structs/EnumerableSet.sol"; +import {Address} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol"; +import {IAutomationForwarder} from "../interfaces/IAutomationForwarder.sol"; +import {IChainModule} from "../interfaces/IChainModule.sol"; +import {IERC20Metadata as IERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import {IAutomationV21PlusCommon} from "../interfaces/IAutomationV21PlusCommon.sol"; + +contract ZKSyncAutomationRegistryLogicC2_3 is ZKSyncAutomationRegistryBase2_3 { + using Address for address; + using EnumerableSet for EnumerableSet.UintSet; + using EnumerableSet for EnumerableSet.AddressSet; + + /** + * @dev see AutomationRegistry master contract for constructor description + */ + constructor( + address link, + address linkUSDFeed, + address nativeUSDFeed, + address fastGasFeed, + address automationForwarderLogic, + address allowedReadOnlyAddress, + PayoutMode payoutMode, + address wrappedNativeTokenAddress + ) + ZKSyncAutomationRegistryBase2_3( + link, + linkUSDFeed, + nativeUSDFeed, + fastGasFeed, + automationForwarderLogic, + allowedReadOnlyAddress, + payoutMode, + wrappedNativeTokenAddress + ) + {} + + // ================================================================ + // | NODE ACTIONS | + // ================================================================ + + /** + * @notice transfers the address of payee for a transmitter + */ + function transferPayeeship(address transmitter, address proposed) external { + if (s_transmitterPayees[transmitter] != msg.sender) revert OnlyCallableByPayee(); + if (proposed == msg.sender) revert ValueNotChanged(); + + if (s_proposedPayee[transmitter] != proposed) { + s_proposedPayee[transmitter] = proposed; + emit PayeeshipTransferRequested(transmitter, msg.sender, proposed); + } + } + + /** + * @notice accepts the transfer of the payee + */ + function acceptPayeeship(address transmitter) external { + if (s_proposedPayee[transmitter] != msg.sender) revert OnlyCallableByProposedPayee(); + address past = s_transmitterPayees[transmitter]; + s_transmitterPayees[transmitter] = msg.sender; + s_proposedPayee[transmitter] = ZERO_ADDRESS; + + emit PayeeshipTransferred(transmitter, past, msg.sender); + } + + /** + * @notice this is for NOPs to withdraw LINK received as payment for work performed + */ + function withdrawPayment(address from, address to) external { + if (to == ZERO_ADDRESS) revert InvalidRecipient(); + if (s_payoutMode == PayoutMode.OFF_CHAIN) revert MustSettleOffchain(); + if (s_transmitterPayees[from] != msg.sender) revert OnlyCallableByPayee(); + uint96 balance = _updateTransmitterBalanceFromPool(from, s_hotVars.totalPremium, uint96(s_transmittersList.length)); + s_transmitters[from].balance = 0; + s_reserveAmounts[IERC20(address(i_link))] = s_reserveAmounts[IERC20(address(i_link))] - balance; + bool transferStatus = i_link.transfer(to, balance); + if (!transferStatus) { + revert TransferFailed(); + } + emit PaymentWithdrawn(from, balance, to, msg.sender); + } + + // ================================================================ + // | OWNER / MANAGER ACTIONS | + // ================================================================ + + /** + * @notice sets the privilege config for an upkeep + */ + function setUpkeepPrivilegeConfig(uint256 upkeepId, bytes calldata newPrivilegeConfig) external { + _onlyPrivilegeManagerAllowed(); + s_upkeepPrivilegeConfig[upkeepId] = newPrivilegeConfig; + emit UpkeepPrivilegeConfigSet(upkeepId, newPrivilegeConfig); + } + + /** + * @notice this is used by the owner to set the initial payees for newly added transmitters. The owner is not allowed to change payees for existing transmitters. + * @dev the IGNORE_ADDRESS is a "helper" that makes it easier to construct a list of payees when you only care about setting the payee for a small number of transmitters. + */ + function setPayees(address[] calldata payees) external onlyOwner { + if (s_transmittersList.length != payees.length) revert ParameterLengthError(); + for (uint256 i = 0; i < s_transmittersList.length; i++) { + address transmitter = s_transmittersList[i]; + address oldPayee = s_transmitterPayees[transmitter]; + address newPayee = payees[i]; + + if ( + (newPayee == ZERO_ADDRESS) || (oldPayee != ZERO_ADDRESS && oldPayee != newPayee && newPayee != IGNORE_ADDRESS) + ) { + revert InvalidPayee(); + } + + if (newPayee != IGNORE_ADDRESS) { + s_transmitterPayees[transmitter] = newPayee; + } + } + emit PayeesUpdated(s_transmittersList, payees); + } + + /** + * @notice sets the migration permission for a peer registry + * @dev this must be done before upkeeps can be migrated to/from another registry + */ + function setPeerRegistryMigrationPermission(address peer, MigrationPermission permission) external onlyOwner { + s_peerRegistryMigrationPermission[peer] = permission; + } + + /** + * @notice pauses the entire registry + */ + function pause() external onlyOwner { + s_hotVars.paused = true; + emit Paused(msg.sender); + } + + /** + * @notice unpauses the entire registry + */ + function unpause() external onlyOwner { + s_hotVars.paused = false; + emit Unpaused(msg.sender); + } + + /** + * @notice sets a generic bytes field used to indicate the privilege that this admin address had + * @param admin the address to set privilege for + * @param newPrivilegeConfig the privileges that this admin has + */ + function setAdminPrivilegeConfig(address admin, bytes calldata newPrivilegeConfig) external { + _onlyPrivilegeManagerAllowed(); + s_adminPrivilegeConfig[admin] = newPrivilegeConfig; + emit AdminPrivilegeConfigSet(admin, newPrivilegeConfig); + } + + /** + * @notice settles NOPs' LINK payment offchain + */ + function settleNOPsOffchain() external { + _onlyFinanceAdminAllowed(); + if (s_payoutMode == PayoutMode.ON_CHAIN) revert MustSettleOnchain(); + + uint96 totalPremium = s_hotVars.totalPremium; + uint256 activeTransmittersLength = s_transmittersList.length; + uint256 deactivatedTransmittersLength = s_deactivatedTransmitters.length(); + uint256 length = activeTransmittersLength + deactivatedTransmittersLength; + uint256[] memory payments = new uint256[](length); + address[] memory payees = new address[](length); + + for (uint256 i = 0; i < activeTransmittersLength; i++) { + address transmitterAddr = s_transmittersList[i]; + uint96 balance = _updateTransmitterBalanceFromPool( + transmitterAddr, + totalPremium, + uint96(activeTransmittersLength) + ); + + payments[i] = balance; + payees[i] = s_transmitterPayees[transmitterAddr]; + s_transmitters[transmitterAddr].balance = 0; + } + + for (uint256 i = 0; i < deactivatedTransmittersLength; i++) { + address deactivatedAddr = s_deactivatedTransmitters.at(i); + Transmitter memory transmitter = s_transmitters[deactivatedAddr]; + + payees[i + activeTransmittersLength] = s_transmitterPayees[deactivatedAddr]; + payments[i + activeTransmittersLength] = transmitter.balance; + s_transmitters[deactivatedAddr].balance = 0; + } + + // reserve amount of LINK is reset to 0 since no user deposits of LINK are expected in offchain mode + s_reserveAmounts[IERC20(address(i_link))] = 0; + + for (uint256 idx = s_deactivatedTransmitters.length(); idx > 0; idx--) { + s_deactivatedTransmitters.remove(s_deactivatedTransmitters.at(idx - 1)); + } + + emit NOPsSettledOffchain(payees, payments); + } + + /** + * @notice disables offchain payment for NOPs + */ + function disableOffchainPayments() external onlyOwner { + s_payoutMode = PayoutMode.ON_CHAIN; + } + + // ================================================================ + // | GETTERS | + // ================================================================ + + function getConditionalGasOverhead() external pure returns (uint256) { + return REGISTRY_CONDITIONAL_OVERHEAD; + } + + function getLogGasOverhead() external pure returns (uint256) { + return REGISTRY_LOG_OVERHEAD; + } + + function getPerSignerGasOverhead() external pure returns (uint256) { + return REGISTRY_PER_SIGNER_GAS_OVERHEAD; + } + + function getCancellationDelay() external pure returns (uint256) { + return CANCELLATION_DELAY; + } + + function getLinkAddress() external view returns (address) { + return address(i_link); + } + + function getLinkUSDFeedAddress() external view returns (address) { + return address(i_linkUSDFeed); + } + + function getNativeUSDFeedAddress() external view returns (address) { + return address(i_nativeUSDFeed); + } + + function getFastGasFeedAddress() external view returns (address) { + return address(i_fastGasFeed); + } + + function getAutomationForwarderLogic() external view returns (address) { + return i_automationForwarderLogic; + } + + function getAllowedReadOnlyAddress() external view returns (address) { + return i_allowedReadOnlyAddress; + } + + function getWrappedNativeTokenAddress() external view returns (address) { + return address(i_wrappedNativeToken); + } + + function getBillingToken(uint256 upkeepID) external view returns (IERC20) { + return s_upkeep[upkeepID].billingToken; + } + + function getBillingTokens() external view returns (IERC20[] memory) { + return s_billingTokens; + } + + function supportsBillingToken(IERC20 token) external view returns (bool) { + return address(s_billingConfigs[token].priceFeed) != address(0); + } + + function getBillingTokenConfig(IERC20 token) external view returns (BillingConfig memory) { + return s_billingConfigs[token]; + } + + function getBillingOverridesEnabled(uint256 upkeepID) external view returns (bool) { + return s_upkeep[upkeepID].overridesEnabled; + } + + function getPayoutMode() external view returns (PayoutMode) { + return s_payoutMode; + } + + function upkeepVersion() public pure returns (uint8) { + return UPKEEP_VERSION_BASE; + } + + /** + * @notice gets the number of upkeeps on the registry + */ + function getNumUpkeeps() external view returns (uint256) { + return s_upkeepIDs.length(); + } + + /** + * @notice read all of the details about an upkeep + * @dev this function may be deprecated in a future version of automation in favor of individual + * getters for each field + */ + function getUpkeep(uint256 id) external view returns (IAutomationV21PlusCommon.UpkeepInfoLegacy memory upkeepInfo) { + Upkeep memory reg = s_upkeep[id]; + address target = address(reg.forwarder) == address(0) ? address(0) : reg.forwarder.getTarget(); + upkeepInfo = IAutomationV21PlusCommon.UpkeepInfoLegacy({ + target: target, + performGas: reg.performGas, + checkData: s_checkData[id], + balance: reg.balance, + admin: s_upkeepAdmin[id], + maxValidBlocknumber: reg.maxValidBlocknumber, + lastPerformedBlockNumber: reg.lastPerformedBlockNumber, + amountSpent: uint96(reg.amountSpent), // force casting to uint96 for backwards compatibility. Not an issue if it overflows. + paused: reg.paused, + offchainConfig: s_upkeepOffchainConfig[id] + }); + return upkeepInfo; + } + + /** + * @notice retrieve active upkeep IDs. Active upkeep is defined as an upkeep which is not paused and not canceled. + * @param startIndex starting index in list + * @param maxCount max count to retrieve (0 = unlimited) + * @dev the order of IDs in the list is **not guaranteed**, therefore, if making successive calls, one + * should consider keeping the blockheight constant to ensure a holistic picture of the contract state + */ + function getActiveUpkeepIDs(uint256 startIndex, uint256 maxCount) external view returns (uint256[] memory) { + uint256 numUpkeeps = s_upkeepIDs.length(); + if (startIndex >= numUpkeeps) revert IndexOutOfRange(); + uint256 endIndex = startIndex + maxCount; + endIndex = endIndex > numUpkeeps || maxCount == 0 ? numUpkeeps : endIndex; + uint256[] memory ids = new uint256[](endIndex - startIndex); + for (uint256 idx = 0; idx < ids.length; idx++) { + ids[idx] = s_upkeepIDs.at(idx + startIndex); + } + return ids; + } + + /** + * @notice returns the upkeep's trigger type + */ + function getTriggerType(uint256 upkeepId) external pure returns (Trigger) { + return _getTriggerType(upkeepId); + } + + /** + * @notice returns the trigger config for an upkeeep + */ + function getUpkeepTriggerConfig(uint256 upkeepId) public view returns (bytes memory) { + return s_upkeepTriggerConfig[upkeepId]; + } + + /** + * @notice read the current info about any transmitter address + */ + function getTransmitterInfo( + address query + ) external view returns (bool active, uint8 index, uint96 balance, uint96 lastCollected, address payee) { + Transmitter memory transmitter = s_transmitters[query]; + + uint96 pooledShare = 0; + if (transmitter.active) { + uint96 totalDifference = s_hotVars.totalPremium - transmitter.lastCollected; + pooledShare = totalDifference / uint96(s_transmittersList.length); + } + + return ( + transmitter.active, + transmitter.index, + (transmitter.balance + pooledShare), + transmitter.lastCollected, + s_transmitterPayees[query] + ); + } + + /** + * @notice read the current info about any signer address + */ + function getSignerInfo(address query) external view returns (bool active, uint8 index) { + Signer memory signer = s_signers[query]; + return (signer.active, signer.index); + } + + /** + * @notice read the current on-chain config of the registry + * @dev this function will change between versions, it should never be used where + * backwards compatibility matters! + */ + function getConfig() external view returns (OnchainConfig memory) { + return + OnchainConfig({ + checkGasLimit: s_storage.checkGasLimit, + stalenessSeconds: s_hotVars.stalenessSeconds, + gasCeilingMultiplier: s_hotVars.gasCeilingMultiplier, + maxPerformGas: s_storage.maxPerformGas, + maxCheckDataSize: s_storage.maxCheckDataSize, + maxPerformDataSize: s_storage.maxPerformDataSize, + maxRevertDataSize: s_storage.maxRevertDataSize, + fallbackGasPrice: s_fallbackGasPrice, + fallbackLinkPrice: s_fallbackLinkPrice, + fallbackNativePrice: s_fallbackNativePrice, + transcoder: s_storage.transcoder, + registrars: s_registrars.values(), + upkeepPrivilegeManager: s_storage.upkeepPrivilegeManager, + chainModule: s_hotVars.chainModule, + reorgProtectionEnabled: s_hotVars.reorgProtectionEnabled, + financeAdmin: s_storage.financeAdmin + }); + } + + /** + * @notice read the current state of the registry + * @dev this function is deprecated + */ + function getState() + external + view + returns ( + IAutomationV21PlusCommon.StateLegacy memory state, + IAutomationV21PlusCommon.OnchainConfigLegacy memory config, + address[] memory signers, + address[] memory transmitters, + uint8 f + ) + { + state = IAutomationV21PlusCommon.StateLegacy({ + nonce: s_storage.nonce, + ownerLinkBalance: 0, // deprecated + expectedLinkBalance: 0, // deprecated + totalPremium: s_hotVars.totalPremium, + numUpkeeps: s_upkeepIDs.length(), + configCount: s_storage.configCount, + latestConfigBlockNumber: s_storage.latestConfigBlockNumber, + latestConfigDigest: s_latestConfigDigest, + latestEpoch: s_hotVars.latestEpoch, + paused: s_hotVars.paused + }); + + config = IAutomationV21PlusCommon.OnchainConfigLegacy({ + paymentPremiumPPB: 0, // deprecated + flatFeeMicroLink: 0, // deprecated + checkGasLimit: s_storage.checkGasLimit, + stalenessSeconds: s_hotVars.stalenessSeconds, + gasCeilingMultiplier: s_hotVars.gasCeilingMultiplier, + minUpkeepSpend: 0, // deprecated + maxPerformGas: s_storage.maxPerformGas, + maxCheckDataSize: s_storage.maxCheckDataSize, + maxPerformDataSize: s_storage.maxPerformDataSize, + maxRevertDataSize: s_storage.maxRevertDataSize, + fallbackGasPrice: s_fallbackGasPrice, + fallbackLinkPrice: s_fallbackLinkPrice, + transcoder: s_storage.transcoder, + registrars: s_registrars.values(), + upkeepPrivilegeManager: s_storage.upkeepPrivilegeManager + }); + + return (state, config, s_signersList, s_transmittersList, s_hotVars.f); + } + + /** + * @notice read the Storage data + * @dev this function signature will change with each version of automation + * this should not be treated as a stable function + */ + function getStorage() external view returns (Storage memory) { + return s_storage; + } + + /** + * @notice read the HotVars data + * @dev this function signature will change with each version of automation + * this should not be treated as a stable function + */ + function getHotVars() external view returns (HotVars memory) { + return s_hotVars; + } + + /** + * @notice get the chain module + */ + function getChainModule() external view returns (IChainModule chainModule) { + return s_hotVars.chainModule; + } + + /** + * @notice if this registry has reorg protection enabled + */ + function getReorgProtectionEnabled() external view returns (bool reorgProtectionEnabled) { + return s_hotVars.reorgProtectionEnabled; + } + + /** + * @notice calculates the minimum balance required for an upkeep to remain eligible + * @param id the upkeep id to calculate minimum balance for + */ + function getBalance(uint256 id) external view returns (uint96 balance) { + return s_upkeep[id].balance; + } + + /** + * @notice calculates the minimum balance required for an upkeep to remain eligible + * @param id the upkeep id to calculate minimum balance for + */ + function getMinBalance(uint256 id) external view returns (uint96) { + return getMinBalanceForUpkeep(id); + } + + /** + * @notice calculates the minimum balance required for an upkeep to remain eligible + * @param id the upkeep id to calculate minimum balance for + * @dev this will be deprecated in a future version in favor of getMinBalance + */ + function getMinBalanceForUpkeep(uint256 id) public view returns (uint96 minBalance) { + Upkeep memory upkeep = s_upkeep[id]; + return getMaxPaymentForGas(id, _getTriggerType(id), upkeep.performGas, upkeep.billingToken); + } + + /** + * @notice calculates the maximum payment for a given gas limit + * @param gasLimit the gas to calculate payment for + */ + function getMaxPaymentForGas( + uint256 id, + Trigger triggerType, + uint32 gasLimit, + IERC20 billingToken + ) public view returns (uint96 maxPayment) { + HotVars memory hotVars = s_hotVars; + (uint256 fastGasWei, uint256 linkUSD, uint256 nativeUSD) = _getFeedData(hotVars); + return _getMaxPayment(id, hotVars, triggerType, gasLimit, fastGasWei, linkUSD, nativeUSD, billingToken); + } + + /** + * @notice retrieves the migration permission for a peer registry + */ + function getPeerRegistryMigrationPermission(address peer) external view returns (MigrationPermission) { + return s_peerRegistryMigrationPermission[peer]; + } + + /** + * @notice returns the upkeep privilege config + */ + function getUpkeepPrivilegeConfig(uint256 upkeepId) external view returns (bytes memory) { + return s_upkeepPrivilegeConfig[upkeepId]; + } + + /** + * @notice returns the admin's privilege config + */ + function getAdminPrivilegeConfig(address admin) external view returns (bytes memory) { + return s_adminPrivilegeConfig[admin]; + } + + /** + * @notice returns the upkeep's forwarder contract + */ + function getForwarder(uint256 upkeepID) external view returns (IAutomationForwarder) { + return s_upkeep[upkeepID].forwarder; + } + + /** + * @notice returns if the dedupKey exists or not + */ + function hasDedupKey(bytes32 dedupKey) external view returns (bool) { + return s_dedupKeys[dedupKey]; + } + + /** + * @notice returns the fallback native price + */ + function getFallbackNativePrice() external view returns (uint256) { + return s_fallbackNativePrice; + } + + /** + * @notice returns the amount of a particular token that is reserved as + * user deposits / NOP payments + */ + function getReserveAmount(IERC20 billingToken) external view returns (uint256) { + return s_reserveAmounts[billingToken]; + } + + /** + * @notice returns the amount of a particular token that is withdraw-able by finance admin + */ + function getAvailableERC20ForPayment(IERC20 billingToken) external view returns (uint256) { + return billingToken.balanceOf(address(this)) - s_reserveAmounts[IERC20(address(billingToken))]; + } + + /** + * @notice returns the size of the LINK liquidity pool + */ + function linkAvailableForPayment() public view returns (int256) { + return _linkAvailableForPayment(); + } + + /** + * @notice returns the BillingOverrides config for a given upkeep + */ + function getBillingOverrides(uint256 upkeepID) external view returns (BillingOverrides memory) { + return s_billingOverrides[upkeepID]; + } + + /** + * @notice returns the BillingConfig for a given billing token, this includes decimals and price feed etc + */ + function getBillingConfig(IERC20 billingToken) external view returns (BillingConfig memory) { + return s_billingConfigs[billingToken]; + } + + /** + * @notice returns all active transmitters with their associated payees + */ + function getTransmittersWithPayees() external view returns (TransmitterPayeeInfo[] memory) { + uint256 transmitterCount = s_transmittersList.length; + TransmitterPayeeInfo[] memory transmitters = new TransmitterPayeeInfo[](transmitterCount); + + for (uint256 i = 0; i < transmitterCount; i++) { + address transmitterAddress = s_transmittersList[i]; + address payeeAddress = s_transmitterPayees[transmitterAddress]; + + transmitters[i] = TransmitterPayeeInfo(transmitterAddress, payeeAddress); + } + + return transmitters; + } +} diff --git a/contracts/src/v0.8/keystone/CapabilitiesRegistry.sol b/contracts/src/v0.8/keystone/CapabilitiesRegistry.sol index ba61584d0a..68c59c7f8c 100644 --- a/contracts/src/v0.8/keystone/CapabilitiesRegistry.sol +++ b/contracts/src/v0.8/keystone/CapabilitiesRegistry.sol @@ -3,8 +3,8 @@ pragma solidity 0.8.24; import {TypeAndVersionInterface} from "../interfaces/TypeAndVersionInterface.sol"; import {OwnerIsCreator} from "../shared/access/OwnerIsCreator.sol"; -import {IERC165} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; import {EnumerableSet} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/structs/EnumerableSet.sol"; +import {ERC165Checker} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/introspection/ERC165Checker.sol"; import {ICapabilityConfiguration} from "./interfaces/ICapabilityConfiguration.sol"; /// @notice CapabilitiesRegistry is used to manage Nodes (including their links to Node @@ -396,7 +396,7 @@ contract CapabilitiesRegistry is OwnerIsCreator, TypeAndVersionInterface { /// @param donId The ID of the DON the config was set for /// @param configCount The number of times the DON has been /// configured - event ConfigSet(uint32 donId, uint32 configCount); + event ConfigSet(uint32 indexed donId, uint32 configCount); /// @notice This event is emitted when a new node operator is added /// @param nodeOperatorId The ID of the newly added node operator @@ -775,7 +775,9 @@ contract CapabilitiesRegistry is OwnerIsCreator, TypeAndVersionInterface { /// @param nodes The nodes making up the DON /// @param capabilityConfigurations The list of configurations for the /// capabilities supported by the DON - /// @param isPublic True if the DON is public + /// @param isPublic True if the DON is can accept external capability requests + /// @param acceptsWorkflows True if the DON can accept workflows + /// @param f The maximum number of faulty nodes the DON can tolerate function addDON( bytes32[] calldata nodes, CapabilityConfiguration[] calldata capabilityConfigurations, @@ -797,24 +799,32 @@ contract CapabilitiesRegistry is OwnerIsCreator, TypeAndVersionInterface { /// the admin to reconfigure the list of capabilities supported /// by the DON, the list of nodes that make up the DON as well /// as whether or not the DON can accept external workflows + /// @param donId The ID of the DON to update /// @param nodes The nodes making up the DON /// @param capabilityConfigurations The list of configurations for the /// capabilities supported by the DON - /// @param isPublic True if the DON is can accept external workflows + /// @param isPublic True if the DON is can accept external capability requests + /// @param f The maximum number of nodes that can fail function updateDON( uint32 donId, bytes32[] calldata nodes, CapabilityConfiguration[] calldata capabilityConfigurations, bool isPublic, - bool acceptsWorkflows, uint8 f ) external onlyOwner { - uint32 configCount = s_dons[donId].configCount; + DON storage don = s_dons[donId]; + uint32 configCount = don.configCount; if (configCount == 0) revert DONDoesNotExist(donId); _setDONConfig( nodes, capabilityConfigurations, - DONParams({id: donId, configCount: ++configCount, isPublic: isPublic, acceptsWorkflows: acceptsWorkflows, f: f}) + DONParams({ + id: donId, + configCount: ++configCount, + isPublic: isPublic, + acceptsWorkflows: don.acceptsWorkflows, + f: f + }) ); } @@ -961,6 +971,11 @@ contract CapabilitiesRegistry is OwnerIsCreator, TypeAndVersionInterface { donCapabilityConfig.capabilityIds.push(configuration.capabilityId); donCapabilityConfig.capabilityConfigs[configuration.capabilityId] = configuration.config; + s_dons[donParams.id].isPublic = donParams.isPublic; + s_dons[donParams.id].acceptsWorkflows = donParams.acceptsWorkflows; + s_dons[donParams.id].f = donParams.f; + s_dons[donParams.id].configCount = donParams.configCount; + _setDONCapabilityConfig( donParams.id, donParams.configCount, @@ -969,10 +984,6 @@ contract CapabilitiesRegistry is OwnerIsCreator, TypeAndVersionInterface { configuration.config ); } - s_dons[donParams.id].isPublic = donParams.isPublic; - s_dons[donParams.id].acceptsWorkflows = donParams.acceptsWorkflows; - s_dons[donParams.id].f = donParams.f; - s_dons[donParams.id].configCount = donParams.configCount; emit ConfigSet(donParams.id, donParams.configCount); } @@ -1010,8 +1021,7 @@ contract CapabilitiesRegistry is OwnerIsCreator, TypeAndVersionInterface { /// by implementing both getCapabilityConfiguration and /// beforeCapabilityConfigSet if ( - capability.configurationContract.code.length == 0 || - !IERC165(capability.configurationContract).supportsInterface(type(ICapabilityConfiguration).interfaceId) + !ERC165Checker.supportsInterface(capability.configurationContract, type(ICapabilityConfiguration).interfaceId) ) revert InvalidCapabilityConfigurationContractInterface(capability.configurationContract); } s_capabilities[hashedCapabilityId] = capability; diff --git a/contracts/src/v0.8/keystone/KeystoneFeedsConsumer.sol b/contracts/src/v0.8/keystone/KeystoneFeedsConsumer.sol index ba1a7c6a8c..306b211f33 100644 --- a/contracts/src/v0.8/keystone/KeystoneFeedsConsumer.sol +++ b/contracts/src/v0.8/keystone/KeystoneFeedsConsumer.sol @@ -99,7 +99,7 @@ contract KeystoneFeedsConsumer is IReceiver, OwnerIsCreator, IERC165 { return (report.Price, report.Timestamp); } - function supportsInterface(bytes4 interfaceId) public pure override returns (bool) { - return interfaceId == this.onReport.selector; + function supportsInterface(bytes4 interfaceId) public pure returns (bool) { + return interfaceId == type(IReceiver).interfaceId || interfaceId == type(IERC165).interfaceId; } } diff --git a/contracts/src/v0.8/keystone/KeystoneForwarder.sol b/contracts/src/v0.8/keystone/KeystoneForwarder.sol index 4b44feccbf..c4511124cd 100644 --- a/contracts/src/v0.8/keystone/KeystoneForwarder.sol +++ b/contracts/src/v0.8/keystone/KeystoneForwarder.sol @@ -1,12 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.24; -import {IReceiver} from "./interfaces/IReceiver.sol"; -import {IRouter} from "./interfaces/IRouter.sol"; -import {ITypeAndVersion} from "../shared/interfaces/ITypeAndVersion.sol"; +import {ERC165Checker} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/introspection/ERC165Checker.sol"; +import {ITypeAndVersion} from "../shared/interfaces/ITypeAndVersion.sol"; import {OwnerIsCreator} from "../shared/access/OwnerIsCreator.sol"; +import {IReceiver} from "./interfaces/IReceiver.sol"; +import {IRouter} from "./interfaces/IRouter.sol"; + /// @notice This is an entry point for `write_${chain}` Target capability. It /// allows nodes to determine if reports have been processed (successfully or /// not) in a decentralized and product-agnostic way by recording processed @@ -49,7 +51,7 @@ contract KeystoneForwarder is OwnerIsCreator, ITypeAndVersion, IRouter { error InvalidConfig(uint64 configId); /// @notice This error is thrown whenever a signer address is not in the - /// configuration. + /// configuration or when trying to set a zero address as a signer. /// @param signer The signer address that was not in the configuration error InvalidSigner(address signer); @@ -64,9 +66,25 @@ contract KeystoneForwarder is OwnerIsCreator, ITypeAndVersion, IRouter { mapping(address signer => uint256 position) _positions; // 1-indexed to detect unset values } + struct Transmission { + address transmitter; + // This is true if the receiver is not a contract or does not implement the + // `IReceiver` interface. + bool invalidReceiver; + // Whether the transmission attempt was successful. If `false`, the + // transmission can be retried with an increased gas limit. + bool success; + // The amount of gas allocated for the `IReceiver.onReport` call. uint80 + // allows storing gas for known EVM block gas limits. + // Ensures that the minimum gas requested by the user is available during + // the transmission attempt. If the transmission fails (indicated by a + // `false` success state), it can be retried with an increased gas limit. + uint80 gasLimit; + } + /// @notice Contains the configuration for each DON ID // @param configId (uint64(donId) << 32) | configVersion - mapping(uint64 configId => OracleSet) internal s_configs; + mapping(uint64 configId => OracleSet oracleSet) internal s_configs; event ConfigSet(uint32 indexed donId, uint32 indexed configVersion, uint8 f, address[] signers); @@ -90,12 +108,22 @@ contract KeystoneForwarder is OwnerIsCreator, ITypeAndVersion, IRouter { uint256 internal constant FORWARDER_METADATA_LENGTH = 45; uint256 internal constant SIGNATURE_LENGTH = 65; + /// @dev This is the gas required to store `success` after the report is processed. + /// It is a warm storage write because of the packed struct. In practice it will cost less. + uint256 internal constant INTERNAL_GAS_REQUIREMENTS_AFTER_REPORT = 5_000; + /// @dev This is the gas required to store the transmission struct and perform other checks. + uint256 internal constant INTERNAL_GAS_REQUIREMENTS = 25_000 + INTERNAL_GAS_REQUIREMENTS_AFTER_REPORT; + /// @dev This is the minimum gas required to route a report. This includes internal gas requirements + /// as well as the minimum gas that the user contract will receive. 30k * 3 gas is to account for + /// cases where consumers need close to the 30k limit provided in the supportsInterface check. + uint256 internal constant MINIMUM_GAS_LIMIT = INTERNAL_GAS_REQUIREMENTS + 30_000 * 3 + 10_000; + // ================================================================ // │ Router │ // ================================================================ - mapping(address forwarder => bool) internal s_forwarders; - mapping(bytes32 transmissionId => TransmissionInfo) internal s_transmissions; + mapping(address forwarder => bool isForwarder) internal s_forwarders; + mapping(bytes32 transmissionId => Transmission transmission) internal s_transmissions; function addForwarder(address forwarder) external onlyOwner { s_forwarders[forwarder] = true; @@ -114,21 +142,37 @@ contract KeystoneForwarder is OwnerIsCreator, ITypeAndVersion, IRouter { bytes calldata metadata, bytes calldata validatedReport ) public returns (bool) { - if (!s_forwarders[msg.sender]) { - revert UnauthorizedForwarder(); - } + if (!s_forwarders[msg.sender]) revert UnauthorizedForwarder(); + + uint256 gasLimit = gasleft() - INTERNAL_GAS_REQUIREMENTS; + if (gasLimit < MINIMUM_GAS_LIMIT) revert InsufficientGasForRouting(transmissionId); + + Transmission memory transmission = s_transmissions[transmissionId]; + if (transmission.success || transmission.invalidReceiver) revert AlreadyAttempted(transmissionId); - if (s_transmissions[transmissionId].transmitter != address(0)) revert AlreadyAttempted(transmissionId); s_transmissions[transmissionId].transmitter = transmitter; + s_transmissions[transmissionId].gasLimit = uint80(gasLimit); - if (receiver.code.length == 0) return false; + // This call can consume up to 90k gas. + if (!ERC165Checker.supportsInterface(receiver, type(IReceiver).interfaceId)) { + s_transmissions[transmissionId].invalidReceiver = true; + return false; + } + + bool success; + bytes memory payload = abi.encodeCall(IReceiver.onReport, (metadata, validatedReport)); + + uint256 remainingGas = gasleft() - INTERNAL_GAS_REQUIREMENTS_AFTER_REPORT; + assembly { + // call and return whether we succeeded. ignore return data + // call(gas,addr,value,argsOffset,argsLength,retOffset,retLength) + success := call(remainingGas, receiver, 0, add(payload, 0x20), mload(payload), 0x0, 0x0) + } - try IReceiver(receiver).onReport(metadata, validatedReport) { + if (success) { s_transmissions[transmissionId].success = true; - return true; - } catch { - return false; } + return success; } function getTransmissionId( @@ -141,26 +185,43 @@ contract KeystoneForwarder is OwnerIsCreator, ITypeAndVersion, IRouter { return keccak256(bytes.concat(bytes20(uint160(receiver)), workflowExecutionId, reportId)); } - /// @notice Get transmitter of a given report or 0x0 if it wasn't transmitted yet - function getTransmitter( + function getTransmissionInfo( address receiver, bytes32 workflowExecutionId, bytes2 reportId - ) external view returns (address) { - return s_transmissions[getTransmissionId(receiver, workflowExecutionId, reportId)].transmitter; + ) external view returns (TransmissionInfo memory) { + bytes32 transmissionId = getTransmissionId(receiver, workflowExecutionId, reportId); + + Transmission memory transmission = s_transmissions[transmissionId]; + + TransmissionState state; + + if (transmission.transmitter == address(0)) { + state = IRouter.TransmissionState.NOT_ATTEMPTED; + } else if (transmission.invalidReceiver) { + state = IRouter.TransmissionState.INVALID_RECEIVER; + } else { + state = transmission.success ? IRouter.TransmissionState.SUCCEEDED : IRouter.TransmissionState.FAILED; + } + + return + TransmissionInfo({ + gasLimit: transmission.gasLimit, + invalidReceiver: transmission.invalidReceiver, + state: state, + success: transmission.success, + transmissionId: transmissionId, + transmitter: transmission.transmitter + }); } - /// @notice Get delivery status of a given report - function getTransmissionState( + /// @notice Get transmitter of a given report or 0x0 if it wasn't transmitted yet + function getTransmitter( address receiver, bytes32 workflowExecutionId, bytes2 reportId - ) external view returns (IRouter.TransmissionState) { - bytes32 transmissionId = getTransmissionId(receiver, workflowExecutionId, reportId); - - if (s_transmissions[transmissionId].transmitter == address(0)) return IRouter.TransmissionState.NOT_ATTEMPTED; - return - s_transmissions[transmissionId].success ? IRouter.TransmissionState.SUCCEEDED : IRouter.TransmissionState.FAILED; + ) external view returns (address) { + return s_transmissions[getTransmissionId(receiver, workflowExecutionId, reportId)].transmitter; } function isForwarder(address forwarder) external view returns (bool) { @@ -187,6 +248,7 @@ contract KeystoneForwarder is OwnerIsCreator, ITypeAndVersion, IRouter { for (uint256 i = 0; i < signers.length; ++i) { // assign indices, detect duplicates address signer = signers[i]; + if (signer == address(0)) revert InvalidSigner(signer); if (s_configs[configId]._positions[signer] != 0) revert DuplicateSigner(signer); s_configs[configId]._positions[signer] = i + 1; } diff --git a/contracts/src/v0.8/keystone/OCR3Capability.sol b/contracts/src/v0.8/keystone/OCR3Capability.sol index 8613a803b2..c7ab8299b7 100644 --- a/contracts/src/v0.8/keystone/OCR3Capability.sol +++ b/contracts/src/v0.8/keystone/OCR3Capability.sol @@ -1,29 +1,186 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity 0.8.24; -import {OCR2Base} from "./ocr/OCR2Base.sol"; +import {ConfirmedOwner} from "../shared/access/ConfirmedOwner.sol"; +import {OCR2Abstract} from "./ocr/OCR2Abstract.sol"; // OCR2Base provides config management compatible with OCR3 -contract OCR3Capability is OCR2Base { +contract OCR3Capability is ConfirmedOwner, OCR2Abstract { + error InvalidConfig(string message); error ReportingUnsupported(); - constructor() OCR2Base() {} + constructor() ConfirmedOwner(msg.sender) {} + // incremented each time a new config is posted. This count is incorporated + // into the config digest, to prevent replay attacks. + uint32 internal s_configCount; + uint32 internal s_latestConfigBlockNumber; // makes it easier for offchain systems + // to extract config from logs. - function typeAndVersion() external pure override returns (string memory) { - return "Keystone 1.0.0"; + // Storing these fields used on the hot path in a ConfigInfo variable reduces the + // retrieval of all of them to a single SLOAD. If any further fields are + // added, make sure that storage of the struct still takes at most 32 bytes. + struct ConfigInfo { + bytes32 latestConfigDigest; + uint8 f; // TODO: could be optimized by squeezing into one slot + uint8 n; + } + ConfigInfo internal s_configInfo; + + /* + * Config logic + */ + + // Reverts transaction if config args are invalid + modifier checkConfigValid( + uint256 numSigners, + uint256 numTransmitters, + uint256 f + ) { + if (numSigners > MAX_NUM_ORACLES) revert InvalidConfig("too many signers"); + if (f == 0) revert InvalidConfig("f must be positive"); + if (numSigners != numTransmitters) revert InvalidConfig("oracle addresses out of registration"); + if (numSigners <= 3 * f) revert InvalidConfig("faulty-oracle f too high"); + _; } - function _beforeSetConfig(uint8 /* _f */, bytes memory /* _onchainConfig */) internal override { - // no-op + /// @inheritdoc OCR2Abstract + function latestConfigDigestAndEpoch() + external + view + virtual + override + returns (bool scanLogs, bytes32 configDigest, uint32 epoch) + { + return (true, bytes32(0), uint32(0)); + } + + // signer = [ 1 byte type | 2 byte len | n byte value ]... + + /** + * @notice sets offchain reporting protocol configuration incl. participating oracles + * @param _signers addresses with which oracles sign the reports + * @param _transmitters addresses oracles use to transmit the reports + * @param _f number of faulty oracles the system can tolerate + * @param _onchainConfig encoded on-chain contract configuration + * @param _offchainConfigVersion version number for offchainEncoding schema + * @param _offchainConfig encoded off-chain oracle configuration + */ + function setConfig( + bytes[] calldata _signers, + address[] calldata _transmitters, + uint8 _f, + bytes memory _onchainConfig, + uint64 _offchainConfigVersion, + bytes memory _offchainConfig + ) external override checkConfigValid(_signers.length, _transmitters.length, _f) onlyOwner { + // Bounded by MAX_NUM_ORACLES in OCR2Abstract.sol + for (uint256 i = 0; i < _signers.length; i++) { + if (_transmitters[i] == address(0)) revert InvalidConfig("transmitter must not be empty"); + // add new signers + bytes calldata publicKeys = _signers[i]; + uint16 offset = 0; + uint16 len = uint16(publicKeys.length); + // scan through public keys to validate encoded format + while (offset < len) { + // solhint-disable-next-line no-unused-vars + uint8 keyType = uint8(publicKeys[offset]); + uint16 keyLen = uint16(uint8(publicKeys[offset + 1])) + (uint16(uint8(publicKeys[offset + 2])) << 8); + // solhint-disable-next-line no-unused-vars + bytes calldata publicKey = publicKeys[offset + 3:offset + 3 + keyLen]; + offset += 3 + keyLen; + } + } + s_configInfo.f = _f; + uint32 previousConfigBlockNumber = s_latestConfigBlockNumber; + s_latestConfigBlockNumber = uint32(block.number); + s_configCount += 1; + { + s_configInfo.latestConfigDigest = _configDigestFromConfigData( + block.chainid, + address(this), + s_configCount, + _signers, + _transmitters, + _f, + _onchainConfig, + _offchainConfigVersion, + _offchainConfig + ); + } + s_configInfo.n = uint8(_signers.length); + + emit ConfigSet( + previousConfigBlockNumber, + s_configInfo.latestConfigDigest, + s_configCount, + _signers, + _transmitters, + _f, + _onchainConfig, + _offchainConfigVersion, + _offchainConfig + ); + } + + function _configDigestFromConfigData( + uint256 _chainId, + address _contractAddress, + uint64 _configCount, + bytes[] calldata _signers, + address[] calldata _transmitters, + uint8 _f, + bytes memory _onchainConfig, + uint64 _encodedConfigVersion, + bytes memory _encodedConfig + ) internal pure returns (bytes32) { + uint256 h = uint256( + keccak256( + abi.encode( + _chainId, + _contractAddress, + _configCount, + _signers, + _transmitters, + _f, + _onchainConfig, + _encodedConfigVersion, + _encodedConfig + ) + ) + ); + uint256 prefixMask = type(uint256).max << (256 - 16); // 0xFFFF00..00 + uint256 prefix = 0x000e << (256 - 16); // 0x000e00..00 + return bytes32((prefix & prefixMask) | (h & ~prefixMask)); + } + + /** + * @notice information about current offchain reporting protocol configuration + * @return configCount ordinal number of current config, out of all configs applied to this contract so far + * @return blockNumber block at which this config was set + * @return configDigest domain-separation tag for current config (see __configDigestFromConfigData) + */ + function latestConfigDetails() + external + view + override + returns (uint32 configCount, uint32 blockNumber, bytes32 configDigest) + { + return (s_configCount, s_latestConfigBlockNumber, s_configInfo.latestConfigDigest); + } + + function typeAndVersion() external pure override returns (string memory) { + return "Keystone 1.0.0"; } - function _report( - uint256 /* initialGas */, - address /* transmitter */, - uint8 /* signerCount */, - address[MAX_NUM_ORACLES] memory /* signers */, - bytes calldata /* report */ - ) internal virtual override { + function transmit( + // NOTE: If these parameters are changed, expectedMsgDataLength and/or + // TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT need to be changed accordingly + bytes32[3] calldata /* reportContext */, + bytes calldata /* report */, + bytes32[] calldata /* rs */, + bytes32[] calldata /* ss */, + bytes32 /* rawVs */ // signatures + ) external pure override { revert ReportingUnsupported(); } } diff --git a/contracts/src/v0.8/keystone/interfaces/ICapabilityConfiguration.sol b/contracts/src/v0.8/keystone/interfaces/ICapabilityConfiguration.sol index 429c2a1d3a..702d55dba9 100644 --- a/contracts/src/v0.8/keystone/interfaces/ICapabilityConfiguration.sol +++ b/contracts/src/v0.8/keystone/interfaces/ICapabilityConfiguration.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity 0.8.24; /// @notice Interface for capability configuration contract. It MUST be /// implemented for a contract to be used as a capability configuration. diff --git a/contracts/src/v0.8/keystone/interfaces/IReceiver.sol b/contracts/src/v0.8/keystone/interfaces/IReceiver.sol index f58c2da7ae..debe58feea 100644 --- a/contracts/src/v0.8/keystone/interfaces/IReceiver.sol +++ b/contracts/src/v0.8/keystone/interfaces/IReceiver.sol @@ -1,7 +1,12 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity 0.8.24; /// @title IReceiver - receives keystone reports interface IReceiver { + /// @notice Handles incoming keystone reports. + /// @dev If this function call reverts, it can be retried with a higher gas + /// limit. The receiver is responsible for discarding stale reports. + /// @param metadata Report's metadata. + /// @param report Workflow report. function onReport(bytes calldata metadata, bytes calldata report) external; } diff --git a/contracts/src/v0.8/keystone/interfaces/IRouter.sol b/contracts/src/v0.8/keystone/interfaces/IRouter.sol index a36c17c14d..3209ae5831 100644 --- a/contracts/src/v0.8/keystone/interfaces/IRouter.sol +++ b/contracts/src/v0.8/keystone/interfaces/IRouter.sol @@ -1,9 +1,12 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity 0.8.24; /// @title IRouter - delivers keystone reports to receiver interface IRouter { error UnauthorizedForwarder(); + /// @dev Thrown when the gas limit is insufficient for handling state after + /// calling the receiver function. + error InsufficientGasForRouting(bytes32 transmissionId); error AlreadyAttempted(bytes32 transmissionId); event ForwarderAdded(address indexed forwarder); @@ -12,12 +15,26 @@ interface IRouter { enum TransmissionState { NOT_ATTEMPTED, SUCCEEDED, + INVALID_RECEIVER, FAILED } struct TransmissionInfo { + bytes32 transmissionId; + TransmissionState state; address transmitter; + // This is true if the receiver is not a contract or does not implement the + // `IReceiver` interface. + bool invalidReceiver; + // Whether the transmission attempt was successful. If `false`, the + // transmission can be retried with an increased gas limit. bool success; + // The amount of gas allocated for the `IReceiver.onReport` call. uint80 + // allows storing gas for known EVM block gas limits. + // Ensures that the minimum gas requested by the user is available during + // the transmission attempt. If the transmission fails (indicated by a + // `false` success state), it can be retried with an increased gas limit. + uint80 gasLimit; } function addForwarder(address forwarder) external; @@ -36,15 +53,14 @@ interface IRouter { bytes32 workflowExecutionId, bytes2 reportId ) external pure returns (bytes32); - function getTransmitter( + function getTransmissionInfo( address receiver, bytes32 workflowExecutionId, bytes2 reportId - ) external view returns (address); - function getTransmissionState( + ) external view returns (TransmissionInfo memory); + function getTransmitter( address receiver, bytes32 workflowExecutionId, bytes2 reportId - ) external view returns (TransmissionState); - function isForwarder(address forwarder) external view returns (bool); + ) external view returns (address); } diff --git a/contracts/src/v0.8/keystone/ocr/OCR2Abstract.sol b/contracts/src/v0.8/keystone/ocr/OCR2Abstract.sol index 083a404534..4eb5cb069b 100644 --- a/contracts/src/v0.8/keystone/ocr/OCR2Abstract.sol +++ b/contracts/src/v0.8/keystone/ocr/OCR2Abstract.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity 0.8.24; import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol"; @@ -23,7 +23,7 @@ abstract contract OCR2Abstract is ITypeAndVersion { uint32 previousConfigBlockNumber, bytes32 configDigest, uint64 configCount, - address[] signers, + bytes[] signers, address[] transmitters, uint8 f, bytes onchainConfig, @@ -41,7 +41,7 @@ abstract contract OCR2Abstract is ITypeAndVersion { * @param offchainConfig serialized configuration used by the oracles exclusively and only passed through the contract */ function setConfig( - address[] memory signers, + bytes[] memory signers, address[] memory transmitters, uint8 f, bytes memory onchainConfig, diff --git a/contracts/src/v0.8/keystone/ocr/OCR2Base.sol b/contracts/src/v0.8/keystone/ocr/OCR2Base.sol deleted file mode 100644 index efc7992e90..0000000000 --- a/contracts/src/v0.8/keystone/ocr/OCR2Base.sol +++ /dev/null @@ -1,352 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; -import {OCR2Abstract} from "./OCR2Abstract.sol"; - -/** - * @notice Onchain verification of reports from the offchain reporting protocol - * @dev For details on its operation, see the offchain reporting protocol design - * doc, which refers to this contract as simply the "contract". - */ -abstract contract OCR2Base is ConfirmedOwner, OCR2Abstract { - error ReportInvalid(string message); - error InvalidConfig(string message); - - constructor() ConfirmedOwner(msg.sender) {} - - // incremented each time a new config is posted. This count is incorporated - // into the config digest, to prevent replay attacks. - uint32 internal s_configCount; - uint32 internal s_latestConfigBlockNumber; // makes it easier for offchain systems - // to extract config from logs. - - // Storing these fields used on the hot path in a ConfigInfo variable reduces the - // retrieval of all of them to a single SLOAD. If any further fields are - // added, make sure that storage of the struct still takes at most 32 bytes. - struct ConfigInfo { - bytes32 latestConfigDigest; - uint8 f; // TODO: could be optimized by squeezing into one slot - uint8 n; - } - ConfigInfo internal s_configInfo; - - // Used for s_oracles[a].role, where a is an address, to track the purpose - // of the address, or to indicate that the address is unset. - enum Role { - // No oracle role has been set for address a - Unset, - // Signing address for the s_oracles[a].index'th oracle. I.e., report - // signatures from this oracle should ecrecover back to address a. - Signer, - // Transmission address for the s_oracles[a].index'th oracle. I.e., if a - // report is received by OCR2Aggregator.transmit in which msg.sender is - // a, it is attributed to the s_oracles[a].index'th oracle. - Transmitter - } - - struct Oracle { - uint8 index; // Index of oracle in s_signers/s_transmitters - Role role; // Role of the address which mapped to this struct - } - - mapping(address signerOrTransmitter => Oracle) internal s_oracles; - - // s_signers contains the signing address of each oracle - address[] internal s_signers; - - // s_transmitters contains the transmission address of each oracle, - // i.e. the address the oracle actually sends transactions to the contract from - address[] internal s_transmitters; - - /* - * Config logic - */ - - // Reverts transaction if config args are invalid - modifier checkConfigValid( - uint256 numSigners, - uint256 numTransmitters, - uint256 f - ) { - if (numSigners > MAX_NUM_ORACLES) revert InvalidConfig("too many signers"); - if (f == 0) revert InvalidConfig("f must be positive"); - if (numSigners != numTransmitters) revert InvalidConfig("oracle addresses out of registration"); - if (numSigners <= 3 * f) revert InvalidConfig("faulty-oracle f too high"); - _; - } - - // solhint-disable-next-line gas-struct-packing - struct SetConfigArgs { - address[] signers; - address[] transmitters; - uint8 f; - bytes onchainConfig; - uint64 offchainConfigVersion; - bytes offchainConfig; - } - - /// @inheritdoc OCR2Abstract - function latestConfigDigestAndEpoch() - external - view - virtual - override - returns (bool scanLogs, bytes32 configDigest, uint32 epoch) - { - return (true, bytes32(0), uint32(0)); - } - - /** - * @notice sets offchain reporting protocol configuration incl. participating oracles - * @param _signers addresses with which oracles sign the reports - * @param _transmitters addresses oracles use to transmit the reports - * @param _f number of faulty oracles the system can tolerate - * @param _onchainConfig encoded on-chain contract configuration - * @param _offchainConfigVersion version number for offchainEncoding schema - * @param _offchainConfig encoded off-chain oracle configuration - */ - function setConfig( - address[] memory _signers, - address[] memory _transmitters, - uint8 _f, - bytes memory _onchainConfig, - uint64 _offchainConfigVersion, - bytes memory _offchainConfig - ) external override checkConfigValid(_signers.length, _transmitters.length, _f) onlyOwner { - SetConfigArgs memory args = SetConfigArgs({ - signers: _signers, - transmitters: _transmitters, - f: _f, - onchainConfig: _onchainConfig, - offchainConfigVersion: _offchainConfigVersion, - offchainConfig: _offchainConfig - }); - - _beforeSetConfig(args.f, args.onchainConfig); - - while (s_signers.length != 0) { - // remove any old signer/transmitter addresses - uint256 lastIdx = s_signers.length - 1; - address signer = s_signers[lastIdx]; - address transmitter = s_transmitters[lastIdx]; - delete s_oracles[signer]; - delete s_oracles[transmitter]; - s_signers.pop(); - s_transmitters.pop(); - } - - // Bounded by MAX_NUM_ORACLES in OCR2Abstract.sol - for (uint256 i = 0; i < args.signers.length; i++) { - if (args.signers[i] == address(0)) revert InvalidConfig("signer must not be empty"); - if (args.transmitters[i] == address(0)) revert InvalidConfig("transmitter must not be empty"); - // add new signer/transmitter addresses - if (s_oracles[args.signers[i]].role != Role.Unset) revert InvalidConfig("repeated signer address"); - s_oracles[args.signers[i]] = Oracle(uint8(i), Role.Signer); - if (s_oracles[args.transmitters[i]].role != Role.Unset) revert InvalidConfig("repeated transmitter address"); - s_oracles[args.transmitters[i]] = Oracle(uint8(i), Role.Transmitter); - s_signers.push(args.signers[i]); - s_transmitters.push(args.transmitters[i]); - } - s_configInfo.f = args.f; - uint32 previousConfigBlockNumber = s_latestConfigBlockNumber; - s_latestConfigBlockNumber = uint32(block.number); - s_configCount += 1; - { - s_configInfo.latestConfigDigest = _configDigestFromConfigData( - block.chainid, - address(this), - s_configCount, - args.signers, - args.transmitters, - args.f, - args.onchainConfig, - args.offchainConfigVersion, - args.offchainConfig - ); - } - s_configInfo.n = uint8(args.signers.length); - - emit ConfigSet( - previousConfigBlockNumber, - s_configInfo.latestConfigDigest, - s_configCount, - args.signers, - args.transmitters, - args.f, - args.onchainConfig, - args.offchainConfigVersion, - args.offchainConfig - ); - } - - function _configDigestFromConfigData( - uint256 _chainId, - address _contractAddress, - uint64 _configCount, - address[] memory _signers, - address[] memory _transmitters, - uint8 _f, - bytes memory _onchainConfig, - uint64 _encodedConfigVersion, - bytes memory _encodedConfig - ) internal pure returns (bytes32) { - uint256 h = uint256( - keccak256( - abi.encode( - _chainId, - _contractAddress, - _configCount, - _signers, - _transmitters, - _f, - _onchainConfig, - _encodedConfigVersion, - _encodedConfig - ) - ) - ); - uint256 prefixMask = type(uint256).max << (256 - 16); // 0xFFFF00..00 - uint256 prefix = 0x0001 << (256 - 16); // 0x000100..00 - return bytes32((prefix & prefixMask) | (h & ~prefixMask)); - } - - /** - * @notice information about current offchain reporting protocol configuration - * @return configCount ordinal number of current config, out of all configs applied to this contract so far - * @return blockNumber block at which this config was set - * @return configDigest domain-separation tag for current config (see __configDigestFromConfigData) - */ - function latestConfigDetails() - external - view - override - returns (uint32 configCount, uint32 blockNumber, bytes32 configDigest) - { - return (s_configCount, s_latestConfigBlockNumber, s_configInfo.latestConfigDigest); - } - - /** - * @return list of addresses permitted to transmit reports to this contract - * @dev The list will match the order used to specify the transmitter during setConfig - */ - function transmitters() external view returns (address[] memory) { - return s_transmitters; - } - - function _beforeSetConfig(uint8 _f, bytes memory _onchainConfig) internal virtual; - - /** - * @dev hook called after the report has been fully validated - * for the extending contract to handle additional logic, such as oracle payment - * @param initialGas the amount of gas before validation - * @param transmitter the address of the account that submitted the report - * @param signers the addresses of all signing accounts - * @param report serialized report - */ - function _report( - uint256 initialGas, - address transmitter, - uint8 signerCount, - address[MAX_NUM_ORACLES] memory signers, - bytes calldata report - ) internal virtual; - - // The constant-length components of the msg.data sent to transmit. - // See the "If we wanted to call sam" example on for example reasoning - // https://solidity.readthedocs.io/en/v0.7.2/abi-spec.html - uint16 private constant TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT = - 4 + // function selector - 32 * - 3 + // 3 words containing reportContext - 32 + // word containing start location of abiencoded report value - 32 + // word containing location start of abiencoded rs value - 32 + // word containing start location of abiencoded ss value - 32 + // rawVs value - 32 + // word containing length of report - 32 + // word containing length rs - 32 + // word containing length of ss - 0; // placeholder - - function _requireExpectedMsgDataLength( - bytes calldata report, - bytes32[] calldata rs, - bytes32[] calldata ss - ) private pure { - // calldata will never be big enough to make this overflow - uint256 expected = uint256(TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT) + - report.length + // one byte pure entry in _report - rs.length * - 32 + // 32 bytes per entry in _rs - ss.length * - 32 + // 32 bytes per entry in _ss - 0; // placeholder - if (msg.data.length != expected) revert ReportInvalid("calldata length mismatch"); - } - - /** - * @notice transmit is called to post a new report to the contract - * @param report serialized report, which the signatures are signing. - * @param rs ith element is the R components of the ith signature on report. Must have at most maxNumOracles entries - * @param ss ith element is the S components of the ith signature on report. Must have at most maxNumOracles entries - * @param rawVs ith element is the the V component of the ith signature - */ - function transmit( - // NOTE: If these parameters are changed, expectedMsgDataLength and/or - // TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT need to be changed accordingly - bytes32[3] calldata reportContext, - bytes calldata report, - bytes32[] calldata rs, - bytes32[] calldata ss, - bytes32 rawVs // signatures - ) external override { - uint256 initialGas = gasleft(); // This line must come first - - { - // reportContext consists of: - // reportContext[0]: ConfigDigest - // reportContext[1]: 27 byte padding, 4-byte epoch and 1-byte round - // reportContext[2]: ExtraHash - bytes32 configDigest = reportContext[0]; - uint32 epochAndRound = uint32(uint256(reportContext[1])); - - emit Transmitted(configDigest, uint32(epochAndRound >> 8)); - - // The following check is disabled to allow both current and proposed routes to submit reports using the same OCR config digest - // Chainlink Functions uses globally unique request IDs. Metadata about the request is stored and checked in the Coordinator and Router - // require(configInfo.latestConfigDigest == configDigest, "configDigest mismatch"); - - _requireExpectedMsgDataLength(report, rs, ss); - - uint256 expectedNumSignatures = (s_configInfo.n + s_configInfo.f) / 2 + 1; - - if (rs.length != expectedNumSignatures) revert ReportInvalid("wrong number of signatures"); - if (rs.length != ss.length) revert ReportInvalid("report rs and ss must be of equal length"); - - Oracle memory transmitter = s_oracles[msg.sender]; - if (transmitter.role != Role.Transmitter && msg.sender != s_transmitters[transmitter.index]) - revert ReportInvalid("unauthorized transmitter"); - } - - address[MAX_NUM_ORACLES] memory signed; - uint8 signerCount = 0; - - { - // Verify signatures attached to report - bytes32 h = keccak256(abi.encodePacked(keccak256(report), reportContext)); - - Oracle memory o; - // Bounded by MAX_NUM_ORACLES in OCR2Abstract.sol - for (uint256 i = 0; i < rs.length; ++i) { - address signer = ecrecover(h, uint8(rawVs[i]) + 27, rs[i], ss[i]); - o = s_oracles[signer]; - if (o.role != Role.Signer) revert ReportInvalid("address not authorized to sign"); - if (signed[o.index] != address(0)) revert ReportInvalid("non-unique signature"); - signed[o.index] = signer; - signerCount += 1; - } - } - - _report(initialGas, msg.sender, signerCount, signed, report); - } -} diff --git a/contracts/src/v0.8/keystone/test/BaseTest.t.sol b/contracts/src/v0.8/keystone/test/BaseTest.t.sol index e637406c14..64dc018c3a 100644 --- a/contracts/src/v0.8/keystone/test/BaseTest.t.sol +++ b/contracts/src/v0.8/keystone/test/BaseTest.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity 0.8.24; import {Test} from "forge-std/Test.sol"; import {Constants} from "./Constants.t.sol"; diff --git a/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_AddDONTest.t.sol b/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_AddDONTest.t.sol index fff6623a59..dc0b85bfa3 100644 --- a/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_AddDONTest.t.sol +++ b/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_AddDONTest.t.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity 0.8.24; import {BaseTest} from "./BaseTest.t.sol"; import {ICapabilityConfiguration} from "../interfaces/ICapabilityConfiguration.sol"; import {CapabilitiesRegistry} from "../CapabilitiesRegistry.sol"; +import {MaliciousConfigurationContract} from "./mocks/MaliciousConfigurationContract.sol"; contract CapabilitiesRegistry_AddDONTest is BaseTest { function setUp() public override { @@ -245,3 +246,75 @@ contract CapabilitiesRegistry_AddDONTest is BaseTest { assertEq(donInfo.nodeP2PIds[1], P2P_ID_THREE); } } + +contract CapabilitiesRegistry_AddDONTest_WhenMaliciousCapabilityConfigurationConfigured is BaseTest { + function setUp() public override { + BaseTest.setUp(); + CapabilitiesRegistry.Capability[] memory capabilities = new CapabilitiesRegistry.Capability[](2); + + address maliciousConfigContractAddr = address( + new MaliciousConfigurationContract(s_capabilityWithConfigurationContractId) + ); + s_basicCapability.configurationContract = maliciousConfigContractAddr; + capabilities[0] = s_basicCapability; + capabilities[1] = s_capabilityWithConfigurationContract; + + CapabilitiesRegistry.NodeOperator[] memory nodeOperators = _getNodeOperators(); + nodeOperators[0].admin = maliciousConfigContractAddr; + nodeOperators[1].admin = maliciousConfigContractAddr; + nodeOperators[2].admin = maliciousConfigContractAddr; + + s_CapabilitiesRegistry.addNodeOperators(nodeOperators); + s_CapabilitiesRegistry.addCapabilities(capabilities); + + CapabilitiesRegistry.NodeParams[] memory nodes = new CapabilitiesRegistry.NodeParams[](3); + bytes32[] memory capabilityIds = new bytes32[](1); + capabilityIds[0] = s_basicHashedCapabilityId; + + nodes[0] = CapabilitiesRegistry.NodeParams({ + nodeOperatorId: TEST_NODE_OPERATOR_ONE_ID, + p2pId: P2P_ID, + signer: NODE_OPERATOR_ONE_SIGNER_ADDRESS, + hashedCapabilityIds: capabilityIds + }); + + bytes32[] memory nodeTwoCapabilityIds = new bytes32[](1); + nodeTwoCapabilityIds[0] = s_basicHashedCapabilityId; + + nodes[1] = CapabilitiesRegistry.NodeParams({ + nodeOperatorId: TEST_NODE_OPERATOR_TWO_ID, + p2pId: P2P_ID_TWO, + signer: NODE_OPERATOR_TWO_SIGNER_ADDRESS, + hashedCapabilityIds: nodeTwoCapabilityIds + }); + + nodes[2] = CapabilitiesRegistry.NodeParams({ + nodeOperatorId: TEST_NODE_OPERATOR_THREE_ID, + p2pId: P2P_ID_THREE, + signer: NODE_OPERATOR_THREE_SIGNER_ADDRESS, + hashedCapabilityIds: capabilityIds + }); + + s_CapabilitiesRegistry.addNodes(nodes); + + changePrank(ADMIN); + } + + function test_RevertWhen_MaliciousCapabilitiesConfigContractTriesToRemoveCapabilitiesFromDONNodes() public { + bytes32[] memory nodes = new bytes32[](2); + nodes[0] = P2P_ID; + nodes[1] = P2P_ID_THREE; + + CapabilitiesRegistry.CapabilityConfiguration[] + memory capabilityConfigs = new CapabilitiesRegistry.CapabilityConfiguration[](1); + capabilityConfigs[0] = CapabilitiesRegistry.CapabilityConfiguration({ + capabilityId: s_basicHashedCapabilityId, + config: BASIC_CAPABILITY_CONFIG + }); + + vm.expectRevert( + abi.encodeWithSelector(CapabilitiesRegistry.CapabilityRequiredByDON.selector, s_basicHashedCapabilityId, DON_ID) + ); + s_CapabilitiesRegistry.addDON(nodes, capabilityConfigs, true, true, F_VALUE); + } +} diff --git a/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_DeprecateCapabilitiesTest.t.sol b/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_DeprecateCapabilitiesTest.t.sol index 4d289e7c74..e06fa4a703 100644 --- a/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_DeprecateCapabilitiesTest.t.sol +++ b/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_DeprecateCapabilitiesTest.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity 0.8.24; import {BaseTest} from "./BaseTest.t.sol"; import {CapabilitiesRegistry} from "../CapabilitiesRegistry.sol"; diff --git a/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_GetCapabilitiesTest.t.sol b/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_GetCapabilitiesTest.t.sol index 9702c62b9c..8f39183ee7 100644 --- a/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_GetCapabilitiesTest.t.sol +++ b/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_GetCapabilitiesTest.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity 0.8.24; import {BaseTest} from "./BaseTest.t.sol"; import {CapabilitiesRegistry} from "../CapabilitiesRegistry.sol"; diff --git a/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_GetDONsTest.t.sol b/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_GetDONsTest.t.sol index a83b1421d3..a79485abad 100644 --- a/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_GetDONsTest.t.sol +++ b/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_GetDONsTest.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity 0.8.24; import {BaseTest} from "./BaseTest.t.sol"; diff --git a/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_GetHashedCapabilityIdTest.t.sol b/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_GetHashedCapabilityIdTest.t.sol index b9a6e6dc97..cdfb0eb643 100644 --- a/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_GetHashedCapabilityIdTest.t.sol +++ b/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_GetHashedCapabilityIdTest.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity 0.8.24; import {BaseTest} from "./BaseTest.t.sol"; import {CapabilityConfigurationContract} from "./mocks/CapabilityConfigurationContract.sol"; diff --git a/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_GetNodeOperatorsTest.t.sol b/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_GetNodeOperatorsTest.t.sol index 36ef201a99..471f4a86ad 100644 --- a/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_GetNodeOperatorsTest.t.sol +++ b/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_GetNodeOperatorsTest.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity 0.8.24; import {BaseTest} from "./BaseTest.t.sol"; import {CapabilitiesRegistry} from "../CapabilitiesRegistry.sol"; diff --git a/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_GetNodesTest.t.sol b/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_GetNodesTest.t.sol index 901e7b9272..a5fe5fa1d1 100644 --- a/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_GetNodesTest.t.sol +++ b/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_GetNodesTest.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity 0.8.24; import {BaseTest} from "./BaseTest.t.sol"; import {CapabilitiesRegistry} from "../CapabilitiesRegistry.sol"; diff --git a/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_RemoveNodesTest.t.sol b/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_RemoveNodesTest.t.sol index 9622c23876..08646600a6 100644 --- a/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_RemoveNodesTest.t.sol +++ b/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_RemoveNodesTest.t.sol @@ -158,7 +158,7 @@ contract CapabilitiesRegistry_RemoveNodesTest is BaseTest { bytes32[] memory updatedNodes = new bytes32[](2); updatedNodes[0] = P2P_ID; updatedNodes[1] = P2P_ID_THREE; - s_CapabilitiesRegistry.updateDON(DON_ID, updatedNodes, capabilityConfigs, true, true, F_VALUE); + s_CapabilitiesRegistry.updateDON(DON_ID, updatedNodes, capabilityConfigs, true, F_VALUE); // Remove node s_CapabilitiesRegistry.removeNodes(removedNodes); diff --git a/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_UpdateDONTest.t.sol b/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_UpdateDONTest.t.sol index 8b21b29506..825524ebe8 100644 --- a/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_UpdateDONTest.t.sol +++ b/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_UpdateDONTest.t.sol @@ -71,7 +71,7 @@ contract CapabilitiesRegistry_UpdateDONTest is BaseTest { capabilityId: s_basicHashedCapabilityId, config: BASIC_CAPABILITY_CONFIG }); - s_CapabilitiesRegistry.updateDON(DON_ID, nodes, capabilityConfigs, true, true, F_VALUE); + s_CapabilitiesRegistry.updateDON(DON_ID, nodes, capabilityConfigs, true, F_VALUE); } function test_RevertWhen_NodeDoesNotSupportCapability() public { @@ -91,7 +91,7 @@ contract CapabilitiesRegistry_UpdateDONTest is BaseTest { s_capabilityWithConfigurationContractId ) ); - s_CapabilitiesRegistry.updateDON(DON_ID, nodes, capabilityConfigs, true, true, F_VALUE); + s_CapabilitiesRegistry.updateDON(DON_ID, nodes, capabilityConfigs, true, F_VALUE); } function test_RevertWhen_DONDoesNotExist() public { @@ -106,7 +106,7 @@ contract CapabilitiesRegistry_UpdateDONTest is BaseTest { config: BASIC_CAPABILITY_CONFIG }); vm.expectRevert(abi.encodeWithSelector(CapabilitiesRegistry.DONDoesNotExist.selector, nonExistentDONId)); - s_CapabilitiesRegistry.updateDON(nonExistentDONId, nodes, capabilityConfigs, true, true, F_VALUE); + s_CapabilitiesRegistry.updateDON(nonExistentDONId, nodes, capabilityConfigs, true, F_VALUE); } function test_RevertWhen_CapabilityDoesNotExist() public { @@ -122,7 +122,7 @@ contract CapabilitiesRegistry_UpdateDONTest is BaseTest { vm.expectRevert( abi.encodeWithSelector(CapabilitiesRegistry.CapabilityDoesNotExist.selector, s_nonExistentHashedCapabilityId) ); - s_CapabilitiesRegistry.updateDON(DON_ID, nodes, capabilityConfigs, true, true, F_VALUE); + s_CapabilitiesRegistry.updateDON(DON_ID, nodes, capabilityConfigs, true, F_VALUE); } function test_RevertWhen_DuplicateCapabilityAdded() public { @@ -144,7 +144,7 @@ contract CapabilitiesRegistry_UpdateDONTest is BaseTest { vm.expectRevert( abi.encodeWithSelector(CapabilitiesRegistry.DuplicateDONCapability.selector, 1, s_basicHashedCapabilityId) ); - s_CapabilitiesRegistry.updateDON(DON_ID, nodes, capabilityConfigs, true, true, F_VALUE); + s_CapabilitiesRegistry.updateDON(DON_ID, nodes, capabilityConfigs, true, F_VALUE); } function test_RevertWhen_DeprecatedCapabilityAdded() public { @@ -165,7 +165,7 @@ contract CapabilitiesRegistry_UpdateDONTest is BaseTest { }); vm.expectRevert(abi.encodeWithSelector(CapabilitiesRegistry.CapabilityIsDeprecated.selector, capabilityId)); - s_CapabilitiesRegistry.updateDON(DON_ID, nodes, capabilityConfigs, true, true, F_VALUE); + s_CapabilitiesRegistry.updateDON(DON_ID, nodes, capabilityConfigs, true, F_VALUE); } function test_RevertWhen_DuplicateNodeAdded() public { @@ -180,7 +180,7 @@ contract CapabilitiesRegistry_UpdateDONTest is BaseTest { config: BASIC_CAPABILITY_CONFIG }); vm.expectRevert(abi.encodeWithSelector(CapabilitiesRegistry.DuplicateDONNode.selector, 1, P2P_ID)); - s_CapabilitiesRegistry.updateDON(DON_ID, nodes, capabilityConfigs, true, true, F_VALUE); + s_CapabilitiesRegistry.updateDON(DON_ID, nodes, capabilityConfigs, true, F_VALUE); } function test_UpdatesDON() public { @@ -217,7 +217,7 @@ contract CapabilitiesRegistry_UpdateDONTest is BaseTest { ), 1 ); - s_CapabilitiesRegistry.updateDON(DON_ID, nodes, capabilityConfigs, expectedDONIsPublic, true, F_VALUE); + s_CapabilitiesRegistry.updateDON(DON_ID, nodes, capabilityConfigs, expectedDONIsPublic, F_VALUE); CapabilitiesRegistry.DONInfo memory donInfo = s_CapabilitiesRegistry.getDON(DON_ID); assertEq(donInfo.id, DON_ID); diff --git a/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_UpdateNodeOperatorsTest.t.sol b/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_UpdateNodeOperatorsTest.t.sol index 721fd35eae..8f6be580f4 100644 --- a/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_UpdateNodeOperatorsTest.t.sol +++ b/contracts/src/v0.8/keystone/test/CapabilitiesRegistry_UpdateNodeOperatorsTest.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity 0.8.24; import {BaseTest} from "./BaseTest.t.sol"; import {CapabilitiesRegistry} from "../CapabilitiesRegistry.sol"; diff --git a/contracts/src/v0.8/keystone/test/Constants.t.sol b/contracts/src/v0.8/keystone/test/Constants.t.sol index 23c80eea9f..a540a25572 100644 --- a/contracts/src/v0.8/keystone/test/Constants.t.sol +++ b/contracts/src/v0.8/keystone/test/Constants.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity 0.8.24; contract Constants { address internal constant ADMIN = address(1); diff --git a/contracts/src/v0.8/keystone/test/KeystoneForwarderBaseTest.t.sol b/contracts/src/v0.8/keystone/test/KeystoneForwarderBaseTest.t.sol index 3b3c406078..c106c2b2b2 100644 --- a/contracts/src/v0.8/keystone/test/KeystoneForwarderBaseTest.t.sol +++ b/contracts/src/v0.8/keystone/test/KeystoneForwarderBaseTest.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity 0.8.24; import {Test} from "forge-std/Test.sol"; import {Receiver} from "./mocks/Receiver.sol"; diff --git a/contracts/src/v0.8/keystone/test/KeystoneForwarder_ReportTest.t.sol b/contracts/src/v0.8/keystone/test/KeystoneForwarder_ReportTest.t.sol index ccb398fac5..928401af96 100644 --- a/contracts/src/v0.8/keystone/test/KeystoneForwarder_ReportTest.t.sol +++ b/contracts/src/v0.8/keystone/test/KeystoneForwarder_ReportTest.t.sol @@ -1,9 +1,12 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity 0.8.24; import {BaseTest} from "./KeystoneForwarderBaseTest.t.sol"; import {IRouter} from "../interfaces/IRouter.sol"; +import {MaliciousReportReceiver} from "./mocks/MaliciousReportReceiver.sol"; +import {MaliciousRevertingReceiver} from "./mocks/MaliciousRevertingReceiver.sol"; import {KeystoneForwarder} from "../KeystoneForwarder.sol"; +import {console} from "forge-std/console.sol"; contract KeystoneForwarder_ReportTest is BaseTest { event MessageReceived(bytes metadata, bytes[] mercuryReports); @@ -141,15 +144,40 @@ contract KeystoneForwarder_ReportTest is BaseTest { s_forwarder.report(address(s_receiver), report, reportContext, signatures); } - function test_RevertWhen_AlreadyAttempted() public { - s_forwarder.report(address(s_receiver), report, reportContext, signatures); + function test_RevertWhen_RetryingSuccessfulTransmission() public { + s_forwarder.report{gas: 400_000}(address(s_receiver), report, reportContext, signatures); bytes32 transmissionId = s_forwarder.getTransmissionId(address(s_receiver), executionId, reportId); vm.expectRevert(abi.encodeWithSelector(IRouter.AlreadyAttempted.selector, transmissionId)); - s_forwarder.report(address(s_receiver), report, reportContext, signatures); + // Retyring with more gas + s_forwarder.report{gas: 450_000}(address(s_receiver), report, reportContext, signatures); + } + + function test_RevertWhen_RetryingInvalidContractTransmission() public { + // Receiver is not a contract + address receiver = address(404); + s_forwarder.report{gas: 400_000}(receiver, report, reportContext, signatures); + + bytes32 transmissionId = s_forwarder.getTransmissionId(receiver, executionId, reportId); + vm.expectRevert(abi.encodeWithSelector(IRouter.AlreadyAttempted.selector, transmissionId)); + // Retyring with more gas + s_forwarder.report{gas: 450_000}(receiver, report, reportContext, signatures); + } + + function test_RevertWhen_AttemptingTransmissionWithInsufficientGas() public { + bytes32 transmissionId = s_forwarder.getTransmissionId(address(s_receiver), executionId, reportId); + vm.expectRevert(abi.encodeWithSelector(IRouter.InsufficientGasForRouting.selector, transmissionId)); + s_forwarder.report{gas: 150_000}(address(s_receiver), report, reportContext, signatures); } function test_Report_SuccessfulDelivery() public { + IRouter.TransmissionInfo memory transmissionInfo = s_forwarder.getTransmissionInfo( + address(s_receiver), + executionId, + reportId + ); + assertEq(uint8(transmissionInfo.state), uint8(IRouter.TransmissionState.NOT_ATTEMPTED), "state mismatch"); + vm.expectEmit(address(s_receiver)); emit MessageReceived(metadata, mercuryReports); @@ -158,16 +186,31 @@ contract KeystoneForwarder_ReportTest is BaseTest { s_forwarder.report(address(s_receiver), report, reportContext, signatures); - assertEq( - s_forwarder.getTransmitter(address(s_receiver), executionId, reportId), - TRANSMITTER, - "transmitter mismatch" - ); - assertEq( - uint8(s_forwarder.getTransmissionState(address(s_receiver), executionId, reportId)), - uint8(IRouter.TransmissionState.SUCCEEDED), - "TransmissionState mismatch" + transmissionInfo = s_forwarder.getTransmissionInfo(address(s_receiver), executionId, reportId); + + assertEq(transmissionInfo.transmitter, TRANSMITTER, "transmitter mismatch"); + assertEq(uint8(transmissionInfo.state), uint8(IRouter.TransmissionState.SUCCEEDED), "state mismatch"); + assertGt(transmissionInfo.gasLimit, 100_000, "gas limit mismatch"); + } + + function test_Report_SuccessfulRetryWithMoreGas() public { + s_forwarder.report{gas: 200_000}(address(s_receiver), report, reportContext, signatures); + + IRouter.TransmissionInfo memory transmissionInfo = s_forwarder.getTransmissionInfo( + address(s_receiver), + executionId, + reportId ); + // Expect to fail with the receiver running out of gas + assertEq(uint8(transmissionInfo.state), uint8(IRouter.TransmissionState.FAILED), "state mismatch"); + assertGt(transmissionInfo.gasLimit, 100_000, "gas limit mismatch"); + + // Should succeed with more gas + s_forwarder.report{gas: 300_000}(address(s_receiver), report, reportContext, signatures); + + transmissionInfo = s_forwarder.getTransmissionInfo(address(s_receiver), executionId, reportId); + assertEq(uint8(transmissionInfo.state), uint8(IRouter.TransmissionState.SUCCEEDED), "state mismatch"); + assertGt(transmissionInfo.gasLimit, 200_000, "gas limit mismatch"); } function test_Report_FailedDeliveryWhenReceiverNotContract() public { @@ -179,29 +222,53 @@ contract KeystoneForwarder_ReportTest is BaseTest { s_forwarder.report(receiver, report, reportContext, signatures); - assertEq(s_forwarder.getTransmitter(receiver, executionId, reportId), TRANSMITTER, "transmitter mismatch"); - assertEq( - uint8(s_forwarder.getTransmissionState(receiver, executionId, reportId)), - uint8(IRouter.TransmissionState.FAILED), - "TransmissionState mismatch" - ); + IRouter.TransmissionInfo memory transmissionInfo = s_forwarder.getTransmissionInfo(receiver, executionId, reportId); + assertEq(uint8(transmissionInfo.state), uint8(IRouter.TransmissionState.INVALID_RECEIVER), "state mismatch"); } function test_Report_FailedDeliveryWhenReceiverInterfaceNotSupported() public { // Receiver is a contract but doesn't implement the required interface address receiver = address(s_forwarder); - vm.expectEmit(address(s_forwarder)); + vm.expectEmit(true, true, true, false); emit ReportProcessed(receiver, executionId, reportId, false); s_forwarder.report(receiver, report, reportContext, signatures); - assertEq(s_forwarder.getTransmitter(receiver, executionId, reportId), TRANSMITTER, "transmitter mismatch"); - assertEq( - uint8(s_forwarder.getTransmissionState(receiver, executionId, reportId)), - uint8(IRouter.TransmissionState.FAILED), - "TransmissionState mismatch" + IRouter.TransmissionInfo memory transmissionInfo = s_forwarder.getTransmissionInfo(receiver, executionId, reportId); + assertEq(uint8(transmissionInfo.state), uint8(IRouter.TransmissionState.INVALID_RECEIVER), "state mismatch"); + } + + // function test_Report_FailedDeliveryWhenReportReceiverConsumesAllGasAndInterfaceCheckUsesMax() public { + // MaliciousRevertingReceiver maliciousReceiver = new MaliciousRevertingReceiver(); + // // This should not revert if gas tracking is effective + // // It may revert if it fails to reserve sufficient gas for routing + // // This POC requires pretty specific initial gas, so that 1/64 of gas passed to `onReport()` is insufficient to store the success + // s_forwarder.report{gas: 200_000}(address(maliciousReceiver), report, reportContext, signatures); + // + // IRouter.TransmissionInfo memory transmissionInfo = s_forwarder.getTransmissionInfo( + // address(maliciousReceiver), + // executionId, + // reportId + // ); + // + // assertEq(transmissionInfo.transmitter, TRANSMITTER, "transmitter mismatch"); + // assertEq(uint8(transmissionInfo.state), uint8(IRouter.TransmissionState.SUCCEEDED), "state mismatch"); + // } + + function test_Report_FailedDelieryWhenReportReceiverConsumesAllGas() public { + MaliciousReportReceiver s_maliciousReceiver = new MaliciousReportReceiver(); + s_forwarder.report{gas: 500_000}(address(s_maliciousReceiver), report, reportContext, signatures); + + IRouter.TransmissionInfo memory transmissionInfo = s_forwarder.getTransmissionInfo( + address(s_maliciousReceiver), + executionId, + reportId ); + + assertEq(transmissionInfo.transmitter, TRANSMITTER, "transmitter mismatch"); + assertEq(uint8(transmissionInfo.state), uint8(IRouter.TransmissionState.FAILED), "state mismatch"); + assertGt(transmissionInfo.gasLimit, 410_000, "gas limit mismatch"); } function test_Report_ConfigVersion() public { diff --git a/contracts/src/v0.8/keystone/test/KeystoneForwarder_SetConfigTest.t.sol b/contracts/src/v0.8/keystone/test/KeystoneForwarder_SetConfigTest.t.sol index 4b908bb702..5dcf79b38e 100644 --- a/contracts/src/v0.8/keystone/test/KeystoneForwarder_SetConfigTest.t.sol +++ b/contracts/src/v0.8/keystone/test/KeystoneForwarder_SetConfigTest.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity 0.8.24; import {BaseTest} from "./KeystoneForwarderBaseTest.t.sol"; import {KeystoneForwarder} from "../KeystoneForwarder.sol"; @@ -41,6 +41,14 @@ contract KeystoneForwarder_SetConfigTest is BaseTest { s_forwarder.setConfig(DON_ID, CONFIG_VERSION, F, signers); } + function test_RevertWhen_ProvidingZeroAddressSigner() public { + address[] memory signers = _getSignerAddresses(); + signers[1] = address(0); + + vm.expectRevert(abi.encodeWithSelector(KeystoneForwarder.InvalidSigner.selector, signers[1])); + s_forwarder.setConfig(DON_ID, CONFIG_VERSION, F, signers); + } + function test_SetConfig_FirstTime() public { s_forwarder.setConfig(DON_ID, CONFIG_VERSION, F, _getSignerAddresses()); } diff --git a/contracts/src/v0.8/keystone/test/KeystoneForwarder_TypeAndVersionTest.t.sol b/contracts/src/v0.8/keystone/test/KeystoneForwarder_TypeAndVersionTest.t.sol index 8aad376649..5a5cc70d2b 100644 --- a/contracts/src/v0.8/keystone/test/KeystoneForwarder_TypeAndVersionTest.t.sol +++ b/contracts/src/v0.8/keystone/test/KeystoneForwarder_TypeAndVersionTest.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity 0.8.24; import {BaseTest} from "./KeystoneForwarderBaseTest.t.sol"; diff --git a/contracts/src/v0.8/keystone/test/KeystoneRouter_AccessTest.t.sol b/contracts/src/v0.8/keystone/test/KeystoneRouter_AccessTest.t.sol index c126f7ce31..0e43b72bdc 100644 --- a/contracts/src/v0.8/keystone/test/KeystoneRouter_AccessTest.t.sol +++ b/contracts/src/v0.8/keystone/test/KeystoneRouter_AccessTest.t.sol @@ -5,6 +5,7 @@ import {Test} from "forge-std/Test.sol"; import {IReceiver} from "../interfaces/IReceiver.sol"; import {IRouter} from "../interfaces/IRouter.sol"; import {KeystoneForwarder} from "../KeystoneForwarder.sol"; +import {Receiver} from "./mocks/Receiver.sol"; contract KeystoneRouter_SetConfigTest is Test { address internal ADMIN = address(1); @@ -18,10 +19,12 @@ contract KeystoneRouter_SetConfigTest is Test { bytes32 internal id = hex"6d795f657865637574696f6e5f69640000000000000000000000000000000000"; KeystoneForwarder internal s_router; + Receiver internal s_receiver; function setUp() public virtual { vm.prank(ADMIN); s_router = new KeystoneForwarder(); + s_receiver = new Receiver(); } function test_AddForwarder_RevertWhen_NotOwner() public { @@ -36,6 +39,13 @@ contract KeystoneRouter_SetConfigTest is Test { s_router.removeForwarder(FORWARDER); } + function test_RemoveForwarder_Success() public { + vm.prank(ADMIN); + vm.expectEmit(true, false, false, false); + emit IRouter.ForwarderRemoved(FORWARDER); + s_router.removeForwarder(FORWARDER); + } + function test_Route_RevertWhen_UnauthorizedForwarder() public { vm.prank(STRANGER); vm.expectRevert(IRouter.UnauthorizedForwarder.selector); @@ -50,8 +60,8 @@ contract KeystoneRouter_SetConfigTest is Test { assertEq(s_router.isForwarder(FORWARDER), true); vm.prank(FORWARDER); - vm.mockCall(RECEIVER, abi.encodeCall(IReceiver.onReport, (metadata, report)), abi.encode()); - vm.expectCall(RECEIVER, abi.encodeCall(IReceiver.onReport, (metadata, report))); - s_router.route(id, TRANSMITTER, RECEIVER, metadata, report); + vm.mockCall(address(s_receiver), abi.encodeCall(IReceiver.onReport, (metadata, report)), abi.encode()); + vm.expectCall(address(s_receiver), abi.encodeCall(IReceiver.onReport, (metadata, report))); + s_router.route(id, TRANSMITTER, address(s_receiver), metadata, report); } } diff --git a/contracts/src/v0.8/keystone/test/mocks/CapabilityConfigurationContract.sol b/contracts/src/v0.8/keystone/test/mocks/CapabilityConfigurationContract.sol index c2a916c87e..105c89006f 100644 --- a/contracts/src/v0.8/keystone/test/mocks/CapabilityConfigurationContract.sol +++ b/contracts/src/v0.8/keystone/test/mocks/CapabilityConfigurationContract.sol @@ -2,9 +2,9 @@ pragma solidity 0.8.24; import {ICapabilityConfiguration} from "../../interfaces/ICapabilityConfiguration.sol"; -import {ERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/introspection/ERC165.sol"; +import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; -contract CapabilityConfigurationContract is ICapabilityConfiguration, ERC165 { +contract CapabilityConfigurationContract is ICapabilityConfiguration, IERC165 { mapping(uint256 => bytes) private s_donConfiguration; function getCapabilityConfiguration(uint32 donId) external view returns (bytes memory configuration) { @@ -17,7 +17,7 @@ contract CapabilityConfigurationContract is ICapabilityConfiguration, ERC165 { s_donConfiguration[donId] = config; } - function supportsInterface(bytes4 interfaceId) public pure override returns (bool) { - return interfaceId == this.getCapabilityConfiguration.selector ^ this.beforeCapabilityConfigSet.selector; + function supportsInterface(bytes4 interfaceId) public pure returns (bool) { + return interfaceId == type(ICapabilityConfiguration).interfaceId || interfaceId == type(IERC165).interfaceId; } } diff --git a/contracts/src/v0.8/keystone/test/mocks/MaliciousConfigurationContract.sol b/contracts/src/v0.8/keystone/test/mocks/MaliciousConfigurationContract.sol new file mode 100644 index 0000000000..2a7fc4d18d --- /dev/null +++ b/contracts/src/v0.8/keystone/test/mocks/MaliciousConfigurationContract.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {ICapabilityConfiguration} from "../../interfaces/ICapabilityConfiguration.sol"; +import {CapabilitiesRegistry} from "../../CapabilitiesRegistry.sol"; +import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; +import {Constants} from "../Constants.t.sol"; + +contract MaliciousConfigurationContract is ICapabilityConfiguration, IERC165, Constants { + bytes32 internal s_capabilityWithConfigurationContractId; + + constructor(bytes32 capabilityWithConfigContractId) { + s_capabilityWithConfigurationContractId = capabilityWithConfigContractId; + } + + function getCapabilityConfiguration(uint32) external pure returns (bytes memory configuration) { + return bytes(""); + } + + function beforeCapabilityConfigSet(bytes32[] calldata, bytes calldata, uint64, uint32) external { + CapabilitiesRegistry.NodeParams[] memory nodes = new CapabilitiesRegistry.NodeParams[](2); + bytes32[] memory hashedCapabilityIds = new bytes32[](1); + + hashedCapabilityIds[0] = s_capabilityWithConfigurationContractId; + + // Set node one's signer to another address + nodes[0] = CapabilitiesRegistry.NodeParams({ + nodeOperatorId: TEST_NODE_OPERATOR_ONE_ID, + p2pId: P2P_ID, + signer: NODE_OPERATOR_ONE_SIGNER_ADDRESS, + hashedCapabilityIds: hashedCapabilityIds + }); + + nodes[1] = CapabilitiesRegistry.NodeParams({ + nodeOperatorId: TEST_NODE_OPERATOR_ONE_ID, + p2pId: P2P_ID_THREE, + signer: NODE_OPERATOR_THREE_SIGNER_ADDRESS, + hashedCapabilityIds: hashedCapabilityIds + }); + + CapabilitiesRegistry(msg.sender).updateNodes(nodes); + } + + function supportsInterface(bytes4 interfaceId) public pure returns (bool) { + return interfaceId == type(ICapabilityConfiguration).interfaceId || interfaceId == type(IERC165).interfaceId; + } +} diff --git a/contracts/src/v0.8/keystone/test/mocks/MaliciousReportReceiver.sol b/contracts/src/v0.8/keystone/test/mocks/MaliciousReportReceiver.sol new file mode 100644 index 0000000000..ac203cccf6 --- /dev/null +++ b/contracts/src/v0.8/keystone/test/mocks/MaliciousReportReceiver.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; +import {IReceiver} from "../../interfaces/IReceiver.sol"; + +contract MaliciousReportReceiver is IReceiver, IERC165 { + event MessageReceived(bytes metadata, bytes[] mercuryReports); + bytes public latestReport; + + function onReport(bytes calldata metadata, bytes calldata rawReport) external { + // Exhaust all gas that was provided + for (uint256 i = 0; i < 1_000_000_000; i++) { + bytes[] memory mercuryReports = abi.decode(rawReport, (bytes[])); + latestReport = rawReport; + emit MessageReceived(metadata, mercuryReports); + } + } + + function supportsInterface(bytes4 interfaceId) public pure override returns (bool) { + return interfaceId == type(IReceiver).interfaceId || interfaceId == type(IERC165).interfaceId; + } +} diff --git a/contracts/src/v0.8/keystone/test/mocks/MaliciousRevertingReceiver.sol b/contracts/src/v0.8/keystone/test/mocks/MaliciousRevertingReceiver.sol new file mode 100644 index 0000000000..3efb5d89cc --- /dev/null +++ b/contracts/src/v0.8/keystone/test/mocks/MaliciousRevertingReceiver.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; +import {IReceiver} from "../../interfaces/IReceiver.sol"; +import {Test} from "forge-std/Test.sol"; + +/// A malicious receiver that uses max allowed for ERC165 checks and consumes all gas in `onReport()` +/// Causes parent Forwarder contract to revert if it doesn't handle gas tracking accurately +contract MaliciousRevertingReceiver is IReceiver, IERC165, Test { + function onReport(bytes calldata, bytes calldata) external view override { + // consumes about 63/64 of all gas available + uint256 targetGasRemaining = 200; + for (uint256 i = 0; gasleft() > targetGasRemaining; i++) {} + } + + function supportsInterface(bytes4 interfaceId) public pure override returns (bool) { + // Consume up to the maximum amount of gas that can be consumed in this check + // This loop consumes roughly 29_000 gas + for (uint256 i = 0; i < 670; i++) {} + + return interfaceId == type(IReceiver).interfaceId || interfaceId == type(IERC165).interfaceId; + } +} diff --git a/contracts/src/v0.8/keystone/test/mocks/Receiver.sol b/contracts/src/v0.8/keystone/test/mocks/Receiver.sol index 25e8755641..d4005950ad 100644 --- a/contracts/src/v0.8/keystone/test/mocks/Receiver.sol +++ b/contracts/src/v0.8/keystone/test/mocks/Receiver.sol @@ -1,16 +1,24 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity 0.8.24; +import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; import {IReceiver} from "../../interfaces/IReceiver.sol"; -contract Receiver is IReceiver { +contract Receiver is IReceiver, IERC165 { event MessageReceived(bytes metadata, bytes[] mercuryReports); + bytes public latestReport; constructor() {} function onReport(bytes calldata metadata, bytes calldata rawReport) external { + latestReport = rawReport; + // parse actual report bytes[] memory mercuryReports = abi.decode(rawReport, (bytes[])); emit MessageReceived(metadata, mercuryReports); } + + function supportsInterface(bytes4 interfaceId) public pure returns (bool) { + return interfaceId == type(IReceiver).interfaceId || interfaceId == type(IERC165).interfaceId; + } } diff --git a/contracts/src/v0.8/l2ep/dev/CrossDomainDelegateForwarder.sol b/contracts/src/v0.8/l2ep/dev/CrossDomainDelegateForwarder.sol index 5dc73619af..859c6f0e7f 100644 --- a/contracts/src/v0.8/l2ep/dev/CrossDomainDelegateForwarder.sol +++ b/contracts/src/v0.8/l2ep/dev/CrossDomainDelegateForwarder.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; import {CrossDomainOwnable} from "./CrossDomainOwnable.sol"; -import {DelegateForwarderInterface} from "./interfaces/DelegateForwarderInterface.sol"; +import {IDelegateForwarder} from "./interfaces/IDelegateForwarder.sol"; /** * @title CrossDomainDelegateForwarder - L1 xDomain account representation (with delegatecall support) @@ -10,4 +10,4 @@ import {DelegateForwarderInterface} from "./interfaces/DelegateForwarderInterfac * @dev Any other L2 contract which uses this contract's address as a privileged position, * can consider that position to be held by the `l1Owner` */ -abstract contract CrossDomainDelegateForwarder is DelegateForwarderInterface, CrossDomainOwnable {} +abstract contract CrossDomainDelegateForwarder is IDelegateForwarder, CrossDomainOwnable {} diff --git a/contracts/src/v0.8/l2ep/dev/CrossDomainForwarder.sol b/contracts/src/v0.8/l2ep/dev/CrossDomainForwarder.sol index 8f218f66b8..c097057f8a 100644 --- a/contracts/src/v0.8/l2ep/dev/CrossDomainForwarder.sol +++ b/contracts/src/v0.8/l2ep/dev/CrossDomainForwarder.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; import {CrossDomainOwnable} from "./CrossDomainOwnable.sol"; -import {ForwarderInterface} from "./interfaces/ForwarderInterface.sol"; +import {IForwarder} from "./interfaces/IForwarder.sol"; /** * @title CrossDomainForwarder - L1 xDomain account representation @@ -10,4 +10,4 @@ import {ForwarderInterface} from "./interfaces/ForwarderInterface.sol"; * @dev Any other L2 contract which uses this contract's address as a privileged position, * can consider that position to be held by the `l1Owner` */ -abstract contract CrossDomainForwarder is ForwarderInterface, CrossDomainOwnable {} +abstract contract CrossDomainForwarder is IForwarder, CrossDomainOwnable {} diff --git a/contracts/src/v0.8/l2ep/dev/CrossDomainOwnable.sol b/contracts/src/v0.8/l2ep/dev/CrossDomainOwnable.sol index f861da32a7..c85762b6fc 100644 --- a/contracts/src/v0.8/l2ep/dev/CrossDomainOwnable.sol +++ b/contracts/src/v0.8/l2ep/dev/CrossDomainOwnable.sol @@ -2,13 +2,13 @@ pragma solidity ^0.8.0; import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; -import {CrossDomainOwnableInterface} from "./interfaces/CrossDomainOwnableInterface.sol"; +import {ICrossDomainOwnable} from "./interfaces/ICrossDomainOwnable.sol"; /** * @title The CrossDomainOwnable contract * @notice A contract with helpers for cross-domain contract ownership. */ -contract CrossDomainOwnable is CrossDomainOwnableInterface, ConfirmedOwner { +contract CrossDomainOwnable is ICrossDomainOwnable, ConfirmedOwner { address internal s_l1Owner; address internal s_l1PendingOwner; diff --git a/contracts/src/v0.8/l2ep/dev/Flags.sol b/contracts/src/v0.8/l2ep/dev/Flags.sol index 0fcd095ac8..2dc030d7d5 100644 --- a/contracts/src/v0.8/l2ep/dev/Flags.sol +++ b/contracts/src/v0.8/l2ep/dev/Flags.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.6; +pragma solidity ^0.8.24; import {SimpleReadAccessController} from "../../shared/access/SimpleReadAccessController.sol"; import {AccessControllerInterface} from "../../shared/interfaces/AccessControllerInterface.sol"; import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol"; /* dev dependencies - to be re/moved after audit */ -import {FlagsInterface} from "./interfaces/FlagsInterface.sol"; +import {IFlags} from "./interfaces/IFlags.sol"; /** * @title The Flags contract @@ -18,7 +18,7 @@ import {FlagsInterface} from "./interfaces/FlagsInterface.sol"; * FlagOn events you should filter for addresses you care about. */ // solhint-disable gas-custom-errors -contract Flags is ITypeAndVersion, FlagsInterface, SimpleReadAccessController { +contract Flags is ITypeAndVersion, IFlags, SimpleReadAccessController { AccessControllerInterface public raisingAccessController; AccessControllerInterface public loweringAccessController; diff --git a/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumCrossDomainForwarder.sol b/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumCrossDomainForwarder.sol index 158ffcc304..0db657ee71 100644 --- a/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumCrossDomainForwarder.sol +++ b/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumCrossDomainForwarder.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.24; -import {TypeAndVersionInterface} from "../../../interfaces/TypeAndVersionInterface.sol"; +import {ITypeAndVersion} from "../../../shared/interfaces/ITypeAndVersion.sol"; // solhint-disable-next-line no-unused-import -import {ForwarderInterface} from "../interfaces/ForwarderInterface.sol"; +import {IForwarder} from "../interfaces/IForwarder.sol"; import {CrossDomainForwarder} from "../CrossDomainForwarder.sol"; import {CrossDomainOwnable} from "../CrossDomainOwnable.sol"; @@ -17,7 +17,7 @@ import {Address} from "../../../vendor/openzeppelin-solidity/v4.7.3/contracts/ut * @dev Any other L2 contract which uses this contract's address as a privileged position, * can be considered to be owned by the `l1Owner` */ -contract ArbitrumCrossDomainForwarder is TypeAndVersionInterface, CrossDomainForwarder { +contract ArbitrumCrossDomainForwarder is ITypeAndVersion, CrossDomainForwarder { /** * @notice creates a new Arbitrum xDomain Forwarder contract * @param l1OwnerAddr the L1 owner address that will be allowed to call the forward fn @@ -31,7 +31,7 @@ contract ArbitrumCrossDomainForwarder is TypeAndVersionInterface, CrossDomainFor * - ArbitrumCrossDomainForwarder 0.1.0: initial release * - ArbitrumCrossDomainForwarder 1.0.0: Use OZ Address, CrossDomainOwnable * - * @inheritdoc TypeAndVersionInterface + * @inheritdoc ITypeAndVersion */ function typeAndVersion() external pure virtual override returns (string memory) { return "ArbitrumCrossDomainForwarder 1.0.0"; @@ -46,7 +46,7 @@ contract ArbitrumCrossDomainForwarder is TypeAndVersionInterface, CrossDomainFor /** * @dev forwarded only if L2 Messenger calls with `xDomainMessageSender` being the L1 owner address - * @inheritdoc ForwarderInterface + * @inheritdoc IForwarder */ function forward(address target, bytes memory data) external virtual override onlyL1Owner { Address.functionCall(target, data, "Forwarder call reverted"); diff --git a/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumCrossDomainGovernor.sol b/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumCrossDomainGovernor.sol index ebf579b849..60d9cc5266 100644 --- a/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumCrossDomainGovernor.sol +++ b/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumCrossDomainGovernor.sol @@ -2,10 +2,10 @@ pragma solidity ^0.8.0; // solhint-disable-next-line no-unused-import -import {TypeAndVersionInterface} from "../../../interfaces/TypeAndVersionInterface.sol"; +import {ITypeAndVersion} from "../../../shared/interfaces/ITypeAndVersion.sol"; // solhint-disable-next-line no-unused-import -import {ForwarderInterface} from "../interfaces/ForwarderInterface.sol"; -import {DelegateForwarderInterface} from "../interfaces/DelegateForwarderInterface.sol"; +import {IForwarder} from "../interfaces/IForwarder.sol"; +import {IDelegateForwarder} from "../interfaces/IDelegateForwarder.sol"; import {ArbitrumCrossDomainForwarder} from "./ArbitrumCrossDomainForwarder.sol"; @@ -17,7 +17,7 @@ import {Address} from "../../../vendor/openzeppelin-solidity/v4.7.3/contracts/ut * @dev Any other L2 contract which uses this contract's address as a privileged position, * can be considered to be simultaneously owned by the `l1Owner` and L2 `owner` */ -contract ArbitrumCrossDomainGovernor is DelegateForwarderInterface, ArbitrumCrossDomainForwarder { +contract ArbitrumCrossDomainGovernor is IDelegateForwarder, ArbitrumCrossDomainForwarder { /** * @notice creates a new Arbitrum xDomain Forwarder contract * @param l1OwnerAddr the L1 owner address that will be allowed to call the forward fn @@ -30,7 +30,7 @@ contract ArbitrumCrossDomainGovernor is DelegateForwarderInterface, ArbitrumCros * * - ArbitrumCrossDomainGovernor 1.0.0: initial release * - * @inheritdoc TypeAndVersionInterface + * @inheritdoc ITypeAndVersion */ function typeAndVersion() external pure virtual override returns (string memory) { return "ArbitrumCrossDomainGovernor 1.0.0"; @@ -38,7 +38,7 @@ contract ArbitrumCrossDomainGovernor is DelegateForwarderInterface, ArbitrumCros /** * @dev forwarded only if L2 Messenger calls with `msg.sender` being the L1 owner address, or called by the L2 owner - * @inheritdoc ForwarderInterface + * @inheritdoc IForwarder */ function forward(address target, bytes memory data) external override onlyLocalOrCrossDomainOwner { Address.functionCall(target, data, "Governor call reverted"); @@ -46,7 +46,7 @@ contract ArbitrumCrossDomainGovernor is DelegateForwarderInterface, ArbitrumCros /** * @dev forwarded only if L2 Messenger calls with `msg.sender` being the L1 owner address, or called by the L2 owner - * @inheritdoc DelegateForwarderInterface + * @inheritdoc IDelegateForwarder */ function forwardDelegate(address target, bytes memory data) external override onlyLocalOrCrossDomainOwner { Address.functionDelegateCall(target, data, "Governor delegatecall reverted"); diff --git a/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumSequencerUptimeFeed.sol b/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumSequencerUptimeFeed.sol index 63952ab7ba..678bef3a85 100644 --- a/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumSequencerUptimeFeed.sol +++ b/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumSequencerUptimeFeed.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.24; import {AddressAliasHelper} from "../../../vendor/arb-bridge-eth/v0.8.0-custom/contracts/libraries/AddressAliasHelper.sol"; import {AggregatorInterface} from "../../../shared/interfaces/AggregatorInterface.sol"; import {AggregatorV3Interface} from "../../../shared/interfaces/AggregatorV3Interface.sol"; import {AggregatorV2V3Interface} from "../../../shared/interfaces/AggregatorV2V3Interface.sol"; -import {TypeAndVersionInterface} from "../../../interfaces/TypeAndVersionInterface.sol"; -import {FlagsInterface} from "../interfaces/FlagsInterface.sol"; -import {ArbitrumSequencerUptimeFeedInterface} from "../interfaces/ArbitrumSequencerUptimeFeedInterface.sol"; +import {ITypeAndVersion} from "../../../shared/interfaces/ITypeAndVersion.sol"; +import {IFlags} from "../interfaces/IFlags.sol"; +import {ISequencerUptimeFeed} from "../interfaces/ISequencerUptimeFeed.sol"; import {SimpleReadAccessController} from "../../../shared/access/SimpleReadAccessController.sol"; /** @@ -18,8 +18,8 @@ import {SimpleReadAccessController} from "../../../shared/access/SimpleReadAcces */ contract ArbitrumSequencerUptimeFeed is AggregatorV2V3Interface, - ArbitrumSequencerUptimeFeedInterface, - TypeAndVersionInterface, + ISequencerUptimeFeed, + ITypeAndVersion, SimpleReadAccessController { /// @dev Round info (for uptime history) @@ -62,7 +62,7 @@ contract ArbitrumSequencerUptimeFeed is /// @dev Flags contract to raise/lower flags on, during status transitions // solhint-disable-next-line chainlink-solidity/prefix-immutable-variables-with-i - FlagsInterface public immutable FLAGS; + IFlags public immutable FLAGS; /// @dev L1 address address private s_l1Sender; /// @dev s_latestRoundId == 0 means this contract is uninitialized. @@ -76,7 +76,7 @@ contract ArbitrumSequencerUptimeFeed is constructor(address flagsAddress, address l1SenderAddress) { _setL1Sender(l1SenderAddress); - FLAGS = FlagsInterface(flagsAddress); + FLAGS = IFlags(flagsAddress); } /** @@ -120,7 +120,7 @@ contract ArbitrumSequencerUptimeFeed is * * - ArbitrumSequencerUptimeFeed 1.0.0: initial release * - * @inheritdoc TypeAndVersionInterface + * @inheritdoc ITypeAndVersion */ function typeAndVersion() external pure virtual override returns (string memory) { return "ArbitrumSequencerUptimeFeed 1.0.0"; diff --git a/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumValidator.sol b/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumValidator.sol index edcb62cae9..05f9349eb6 100644 --- a/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumValidator.sol +++ b/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumValidator.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.24; import {AggregatorValidatorInterface} from "../../../shared/interfaces/AggregatorValidatorInterface.sol"; -import {TypeAndVersionInterface} from "../../../interfaces/TypeAndVersionInterface.sol"; +import {ITypeAndVersion} from "../../../shared/interfaces/ITypeAndVersion.sol"; import {AccessControllerInterface} from "../../../shared/interfaces/AccessControllerInterface.sol"; import {SimpleWriteAccessController} from "../../../shared/access/SimpleWriteAccessController.sol"; /* ./dev dependencies - to be moved from ./dev after audit */ -import {ArbitrumSequencerUptimeFeedInterface} from "../interfaces/ArbitrumSequencerUptimeFeedInterface.sol"; +import {ISequencerUptimeFeed} from "../interfaces/ISequencerUptimeFeed.sol"; import {IArbitrumDelayedInbox} from "../interfaces/IArbitrumDelayedInbox.sol"; import {AddressAliasHelper} from "../../../vendor/arb-bridge-eth/v0.8.0-custom/contracts/libraries/AddressAliasHelper.sol"; import {ArbSys} from "../../../vendor/@arbitrum/nitro-contracts/src/precompiles/ArbSys.sol"; @@ -20,7 +20,7 @@ import {Address} from "../../../vendor/openzeppelin-solidity/v4.7.3/contracts/ut * - Gas configuration is controlled by a configurable external SimpleWriteAccessController * - Funds on the contract are managed by the owner */ -contract ArbitrumValidator is TypeAndVersionInterface, AggregatorValidatorInterface, SimpleWriteAccessController { +contract ArbitrumValidator is ITypeAndVersion, AggregatorValidatorInterface, SimpleWriteAccessController { enum PaymentStrategy { L1, L2 @@ -124,7 +124,7 @@ contract ArbitrumValidator is TypeAndVersionInterface, AggregatorValidatorInterf * - ArbitrumValidator 2.0.0: change how maxSubmissionCost is calculated when sending cross chain messages * - now calls `calculateRetryableSubmissionFee` instead of inlining equation to estimate * the maxSubmissionCost required to send the message to L2 - * @inheritdoc TypeAndVersionInterface + * @inheritdoc ITypeAndVersion */ function typeAndVersion() external pure virtual override returns (string memory) { return "ArbitrumValidator 2.0.0"; @@ -264,7 +264,7 @@ contract ArbitrumValidator is TypeAndVersionInterface, AggregatorValidatorInterf // Excess gas on L2 will be sent to the L2 xDomain alias address of this contract address refundAddr = L2_ALIAS; // Encode the ArbitrumSequencerUptimeFeed call - bytes4 selector = ArbitrumSequencerUptimeFeedInterface.updateStatus.selector; + bytes4 selector = ISequencerUptimeFeed.updateStatus.selector; bool status = currentAnswer == ANSWER_SEQ_OFFLINE; uint64 timestamp = uint64(block.timestamp); // Encode `status` and `timestamp` diff --git a/contracts/src/v0.8/l2ep/dev/interfaces/DelegateForwarderInterface.sol b/contracts/src/v0.8/l2ep/dev/interfaces/DelegateForwarderInterface.sol deleted file mode 100644 index 498dee586c..0000000000 --- a/contracts/src/v0.8/l2ep/dev/interfaces/DelegateForwarderInterface.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -/// @title DelegateForwarderInterface - forwards a delegatecall to a target, under some conditions -// solhint-disable-next-line interface-starts-with-i -interface DelegateForwarderInterface { - /** - * @notice forward delegatecalls the `target` with `data` - * @param target contract address to be delegatecalled - * @param data to send to target contract - */ - function forwardDelegate(address target, bytes memory data) external; -} diff --git a/contracts/src/v0.8/l2ep/dev/interfaces/ForwarderInterface.sol b/contracts/src/v0.8/l2ep/dev/interfaces/ForwarderInterface.sol deleted file mode 100644 index a6db32b923..0000000000 --- a/contracts/src/v0.8/l2ep/dev/interfaces/ForwarderInterface.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -/// @title ForwarderInterface - forwards a call to a target, under some conditions -// solhint-disable-next-line interface-starts-with-i -interface ForwarderInterface { - /** - * @notice forward calls the `target` with `data` - * @param target contract address to be called - * @param data to send to target contract - */ - function forward(address target, bytes memory data) external; -} diff --git a/contracts/src/v0.8/l2ep/dev/interfaces/CrossDomainOwnableInterface.sol b/contracts/src/v0.8/l2ep/dev/interfaces/ICrossDomainOwnable.sol similarity index 65% rename from contracts/src/v0.8/l2ep/dev/interfaces/CrossDomainOwnableInterface.sol rename to contracts/src/v0.8/l2ep/dev/interfaces/ICrossDomainOwnable.sol index ddcfded9ca..d5a01386e3 100644 --- a/contracts/src/v0.8/l2ep/dev/interfaces/CrossDomainOwnableInterface.sol +++ b/contracts/src/v0.8/l2ep/dev/interfaces/ICrossDomainOwnable.sol @@ -1,9 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -/// @title CrossDomainOwnableInterface - A contract with helpers for cross-domain contract ownership -// solhint-disable-next-line interface-starts-with-i -interface CrossDomainOwnableInterface { +/// @title A contract with helpers for cross-domain contract ownership +interface ICrossDomainOwnable { event L1OwnershipTransferRequested(address indexed from, address indexed to); event L1OwnershipTransferred(address indexed from, address indexed to); diff --git a/contracts/src/v0.8/l2ep/dev/interfaces/IDelegateForwarder.sol b/contracts/src/v0.8/l2ep/dev/interfaces/IDelegateForwarder.sol new file mode 100644 index 0000000000..3df532b94c --- /dev/null +++ b/contracts/src/v0.8/l2ep/dev/interfaces/IDelegateForwarder.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title Forwards a delegatecall to a target, under some conditions +interface IDelegateForwarder { + /// @notice forward delegatecalls the `target` with `data` + /// @param target contract address to be delegatecalled + /// @param data to send to target contract + function forwardDelegate(address target, bytes memory data) external; +} diff --git a/contracts/src/v0.8/l2ep/dev/interfaces/FlagsInterface.sol b/contracts/src/v0.8/l2ep/dev/interfaces/IFlags.sol similarity index 82% rename from contracts/src/v0.8/l2ep/dev/interfaces/FlagsInterface.sol rename to contracts/src/v0.8/l2ep/dev/interfaces/IFlags.sol index b6491a9d60..6ae5a3a3f3 100644 --- a/contracts/src/v0.8/l2ep/dev/interfaces/FlagsInterface.sol +++ b/contracts/src/v0.8/l2ep/dev/interfaces/IFlags.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.6; +pragma solidity ^0.8.0; -// solhint-disable-next-line interface-starts-with-i -interface FlagsInterface { +interface IFlags { function getFlag(address) external view returns (bool); function getFlags(address[] calldata) external view returns (bool[] memory); diff --git a/contracts/src/v0.8/l2ep/dev/interfaces/IForwarder.sol b/contracts/src/v0.8/l2ep/dev/interfaces/IForwarder.sol new file mode 100644 index 0000000000..374e381064 --- /dev/null +++ b/contracts/src/v0.8/l2ep/dev/interfaces/IForwarder.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title Forwards a call to a target, under some conditions +interface IForwarder { + /// @notice forward calls the `target` with `data` + /// @param target contract address to be called + /// @param data to send to target contract + function forward(address target, bytes memory data) external; +} diff --git a/contracts/src/v0.8/l2ep/dev/interfaces/ArbitrumSequencerUptimeFeedInterface.sol b/contracts/src/v0.8/l2ep/dev/interfaces/ISequencerUptimeFeed.sol similarity index 54% rename from contracts/src/v0.8/l2ep/dev/interfaces/ArbitrumSequencerUptimeFeedInterface.sol rename to contracts/src/v0.8/l2ep/dev/interfaces/ISequencerUptimeFeed.sol index 57b507bae8..879dc06d37 100644 --- a/contracts/src/v0.8/l2ep/dev/interfaces/ArbitrumSequencerUptimeFeedInterface.sol +++ b/contracts/src/v0.8/l2ep/dev/interfaces/ISequencerUptimeFeed.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -// solhint-disable-next-line interface-starts-with-i -interface ArbitrumSequencerUptimeFeedInterface { +interface ISequencerUptimeFeed { function updateStatus(bool status, uint64 timestamp) external; } diff --git a/contracts/src/v0.8/l2ep/dev/interfaces/OptimismSequencerUptimeFeedInterface.sol b/contracts/src/v0.8/l2ep/dev/interfaces/OptimismSequencerUptimeFeedInterface.sol deleted file mode 100644 index a08a1b2620..0000000000 --- a/contracts/src/v0.8/l2ep/dev/interfaces/OptimismSequencerUptimeFeedInterface.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -// solhint-disable-next-line interface-starts-with-i -interface OptimismSequencerUptimeFeedInterface { - function updateStatus(bool status, uint64 timestamp) external; -} diff --git a/contracts/src/v0.8/l2ep/dev/interfaces/ScrollSequencerUptimeFeedInterface.sol b/contracts/src/v0.8/l2ep/dev/interfaces/ScrollSequencerUptimeFeedInterface.sol deleted file mode 100644 index 89327fbc3a..0000000000 --- a/contracts/src/v0.8/l2ep/dev/interfaces/ScrollSequencerUptimeFeedInterface.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.19; - -// solhint-disable-next-line interface-starts-with-i -interface ScrollSequencerUptimeFeedInterface { - function updateStatus(bool status, uint64 timestamp) external; -} diff --git a/contracts/src/v0.8/l2ep/dev/optimism/OptimismCrossDomainForwarder.sol b/contracts/src/v0.8/l2ep/dev/optimism/OptimismCrossDomainForwarder.sol index 37d9260b47..1d03788696 100644 --- a/contracts/src/v0.8/l2ep/dev/optimism/OptimismCrossDomainForwarder.sol +++ b/contracts/src/v0.8/l2ep/dev/optimism/OptimismCrossDomainForwarder.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.24; -import {TypeAndVersionInterface} from "../../../interfaces/TypeAndVersionInterface.sol"; +import {ITypeAndVersion} from "../../../shared/interfaces/ITypeAndVersion.sol"; // solhint-disable-next-line no-unused-import -import {ForwarderInterface} from "../interfaces/ForwarderInterface.sol"; +import {IForwarder} from "../interfaces/IForwarder.sol"; /* ./dev dependencies - to be moved from ./dev after audit */ import {CrossDomainForwarder} from "../CrossDomainForwarder.sol"; @@ -18,7 +18,7 @@ import {Address} from "../../../vendor/openzeppelin-solidity/v4.7.3/contracts/ut * @dev Any other L2 contract which uses this contract's address as a privileged position, * can be considered to be owned by the `l1Owner` */ -contract OptimismCrossDomainForwarder is TypeAndVersionInterface, CrossDomainForwarder { +contract OptimismCrossDomainForwarder is ITypeAndVersion, CrossDomainForwarder { // OVM_L2CrossDomainMessenger is a precompile usually deployed to 0x4200000000000000000000000000000000000007 // solhint-disable-next-line chainlink-solidity/prefix-immutable-variables-with-i iOVM_CrossDomainMessenger private immutable OVM_CROSS_DOMAIN_MESSENGER; @@ -40,7 +40,7 @@ contract OptimismCrossDomainForwarder is TypeAndVersionInterface, CrossDomainFor * - OptimismCrossDomainForwarder 0.1.0: initial release * - OptimismCrossDomainForwarder 1.0.0: Use OZ Address, CrossDomainOwnable * - * @inheritdoc TypeAndVersionInterface + * @inheritdoc ITypeAndVersion */ function typeAndVersion() external pure virtual override returns (string memory) { return "OptimismCrossDomainForwarder 1.0.0"; @@ -48,7 +48,7 @@ contract OptimismCrossDomainForwarder is TypeAndVersionInterface, CrossDomainFor /** * @dev forwarded only if L2 Messenger calls with `xDomainMessageSender` being the L1 owner address - * @inheritdoc ForwarderInterface + * @inheritdoc IForwarder */ function forward(address target, bytes memory data) external virtual override onlyL1Owner { Address.functionCall(target, data, "Forwarder call reverted"); diff --git a/contracts/src/v0.8/l2ep/dev/optimism/OptimismCrossDomainGovernor.sol b/contracts/src/v0.8/l2ep/dev/optimism/OptimismCrossDomainGovernor.sol index ad78094691..6a41bd98f0 100644 --- a/contracts/src/v0.8/l2ep/dev/optimism/OptimismCrossDomainGovernor.sol +++ b/contracts/src/v0.8/l2ep/dev/optimism/OptimismCrossDomainGovernor.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.24; -import {DelegateForwarderInterface} from "../interfaces/DelegateForwarderInterface.sol"; +import {IDelegateForwarder} from "../interfaces/IDelegateForwarder.sol"; // solhint-disable-next-line no-unused-import -import {ForwarderInterface} from "../interfaces/ForwarderInterface.sol"; +import {IForwarder} from "../interfaces/IForwarder.sol"; import {OptimismCrossDomainForwarder} from "./OptimismCrossDomainForwarder.sol"; @@ -16,7 +16,7 @@ import {Address} from "../../../vendor/openzeppelin-solidity/v4.7.3/contracts/ut * @dev Any other L2 contract which uses this contract's address as a privileged position, * can be considered to be simultaneously owned by the `l1Owner` and L2 `owner` */ -contract OptimismCrossDomainGovernor is DelegateForwarderInterface, OptimismCrossDomainForwarder { +contract OptimismCrossDomainGovernor is IDelegateForwarder, OptimismCrossDomainForwarder { /** * @notice creates a new Optimism xDomain Forwarder contract * @param crossDomainMessengerAddr the xDomain bridge messenger (Optimism bridge L2) contract address @@ -39,7 +39,7 @@ contract OptimismCrossDomainGovernor is DelegateForwarderInterface, OptimismCros /** * @dev forwarded only if L2 Messenger calls with `msg.sender` being the L1 owner address, or called by the L2 owner - * @inheritdoc ForwarderInterface + * @inheritdoc IForwarder */ function forward(address target, bytes memory data) external override onlyLocalOrCrossDomainOwner { Address.functionCall(target, data, "Governor call reverted"); @@ -47,7 +47,7 @@ contract OptimismCrossDomainGovernor is DelegateForwarderInterface, OptimismCros /** * @dev forwarded only if L2 Messenger calls with `msg.sender` being the L1 owner address, or called by the L2 owner - * @inheritdoc DelegateForwarderInterface + * @inheritdoc IDelegateForwarder */ function forwardDelegate(address target, bytes memory data) external override onlyLocalOrCrossDomainOwner { Address.functionDelegateCall(target, data, "Governor delegatecall reverted"); diff --git a/contracts/src/v0.8/l2ep/dev/optimism/OptimismSequencerUptimeFeed.sol b/contracts/src/v0.8/l2ep/dev/optimism/OptimismSequencerUptimeFeed.sol index 947b1b0bf5..0e6f9c52f2 100644 --- a/contracts/src/v0.8/l2ep/dev/optimism/OptimismSequencerUptimeFeed.sol +++ b/contracts/src/v0.8/l2ep/dev/optimism/OptimismSequencerUptimeFeed.sol @@ -1,12 +1,8 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.19; + +import {BaseSequencerUptimeFeed} from "../shared/BaseSequencerUptimeFeed.sol"; -import {AggregatorInterface} from "../../../shared/interfaces/AggregatorInterface.sol"; -import {AggregatorV3Interface} from "../../../shared/interfaces/AggregatorV3Interface.sol"; -import {AggregatorV2V3Interface} from "../../../shared/interfaces/AggregatorV2V3Interface.sol"; -import {TypeAndVersionInterface} from "../../../interfaces/TypeAndVersionInterface.sol"; -import {OptimismSequencerUptimeFeedInterface} from "./../interfaces/OptimismSequencerUptimeFeedInterface.sol"; -import {SimpleReadAccessController} from "../../../shared/access/SimpleReadAccessController.sol"; import {IL2CrossDomainMessenger} from "@eth-optimism/contracts/L2/messaging/IL2CrossDomainMessenger.sol"; /** @@ -14,50 +10,8 @@ import {IL2CrossDomainMessenger} from "@eth-optimism/contracts/L2/messaging/IL2C * @notice L2 contract that receives status updates from a specific L1 address, * records a new answer if the status changed */ -contract OptimismSequencerUptimeFeed is - AggregatorV2V3Interface, - OptimismSequencerUptimeFeedInterface, - TypeAndVersionInterface, - SimpleReadAccessController -{ - /// @dev Round info (for uptime history) - struct Round { - bool status; - uint64 startedAt; - uint64 updatedAt; - } - - /// @dev Packed state struct to save sloads - struct FeedState { - uint80 latestRoundId; - bool latestStatus; - uint64 startedAt; - uint64 updatedAt; - } - - /// @notice Sender is not the L2 messenger - error InvalidSender(); - /// @notice Replacement for AggregatorV3Interface "No data present" - error NoDataPresent(); - - event L1SenderTransferred(address indexed from, address indexed to); - /// @dev Emitted when an `updateStatus` call is ignored due to unchanged status or stale timestamp - event UpdateIgnored(bool latestStatus, uint64 latestTimestamp, bool incomingStatus, uint64 incomingTimestamp); - /// @dev Emitted when a updateStatus is called without the status changing - event RoundUpdated(int256 status, uint64 updatedAt); - - // solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables - uint8 public constant override decimals = 0; - // solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables - string public constant override description = "L2 Sequencer Uptime Status Feed"; - // solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables - uint256 public constant override version = 1; - - /// @dev L1 address - address private s_l1Sender; - /// @dev s_latestRoundId == 0 means this contract is uninitialized. - FeedState private s_feedState = FeedState({latestRoundId: 0, latestStatus: false, startedAt: 0, updatedAt: 0}); - mapping(uint80 => Round) private s_rounds; +contract OptimismSequencerUptimeFeed is BaseSequencerUptimeFeed { + string public constant override typeAndVersion = "OptimismSequencerUptimeFeed 1.1.0-dev"; // solhint-disable-next-line chainlink-solidity/prefix-immutable-variables-with-i IL2CrossDomainMessenger private immutable s_l2CrossDomainMessenger; @@ -67,202 +21,19 @@ contract OptimismSequencerUptimeFeed is * @param l2CrossDomainMessengerAddr Address of the L2CrossDomainMessenger contract * @param initialStatus The initial status of the feed */ - constructor(address l1SenderAddress, address l2CrossDomainMessengerAddr, bool initialStatus) { - _setL1Sender(l1SenderAddress); + constructor( + address l1SenderAddress, + address l2CrossDomainMessengerAddr, + bool initialStatus + ) BaseSequencerUptimeFeed(l1SenderAddress, initialStatus) { s_l2CrossDomainMessenger = IL2CrossDomainMessenger(l2CrossDomainMessengerAddr); - uint64 timestamp = uint64(block.timestamp); - - // Initialise roundId == 1 as the first round - _recordRound(1, initialStatus, timestamp); - } - - /** - * @notice Check if a roundId is valid in this current contract state - * @dev Mainly used for AggregatorV2V3Interface functions - * @param roundId Round ID to check - */ - function _isValidRound(uint256 roundId) private view returns (bool) { - return roundId > 0 && roundId <= type(uint80).max && s_feedState.latestRoundId >= roundId; - } - - /** - * @notice versions: - * - * - OptimismSequencerUptimeFeed 1.0.0: initial release - * - * @inheritdoc TypeAndVersionInterface - */ - function typeAndVersion() external pure virtual override returns (string memory) { - return "OptimismSequencerUptimeFeed 1.0.0"; - } - - /// @return L1 sender address - function l1Sender() public view virtual returns (address) { - return s_l1Sender; - } - - /** - * @notice Set the allowed L1 sender for this contract to a new L1 sender - * @dev Can be disabled by setting the L1 sender as `address(0)`. Accessible only by owner. - * @param to new L1 sender that will be allowed to call `updateStatus` on this contract - */ - function transferL1Sender(address to) external virtual onlyOwner { - _setL1Sender(to); - } - - /// @notice internal method that stores the L1 sender - function _setL1Sender(address to) private { - address from = s_l1Sender; - if (from != to) { - s_l1Sender = to; - emit L1SenderTransferred(from, to); - } - } - - /** - * @dev Returns an AggregatorV2V3Interface compatible answer from status flag - * - * @param status The status flag to convert to an aggregator-compatible answer - */ - function _getStatusAnswer(bool status) private pure returns (int256) { - return status ? int256(1) : int256(0); - } - - /** - * @notice Helper function to record a round and set the latest feed state. - * - * @param roundId The round ID to record - * @param status Sequencer status - * @param timestamp The L1 block timestamp of status update - */ - function _recordRound(uint80 roundId, bool status, uint64 timestamp) private { - uint64 updatedAt = uint64(block.timestamp); - Round memory nextRound = Round(status, timestamp, updatedAt); - FeedState memory feedState = FeedState(roundId, status, timestamp, updatedAt); - - s_rounds[roundId] = nextRound; - s_feedState = feedState; - - emit NewRound(roundId, msg.sender, timestamp); - emit AnswerUpdated(_getStatusAnswer(status), roundId, timestamp); } - /** - * @notice Helper function to update when a round was last updated - * - * @param roundId The round ID to update - * @param status Sequencer status - */ - function _updateRound(uint80 roundId, bool status) private { - uint64 updatedAt = uint64(block.timestamp); - s_rounds[roundId].updatedAt = updatedAt; - s_feedState.updatedAt = updatedAt; - emit RoundUpdated(_getStatusAnswer(status), updatedAt); - } - - /** - * @notice Record a new status and timestamp if it has changed since the last round. - * @dev This function will revert if not called from `l1Sender` via the L1->L2 messenger. - * - * @param status Sequencer status - * @param timestamp Block timestamp of status update - */ - function updateStatus(bool status, uint64 timestamp) external override { - FeedState memory feedState = s_feedState; + function _validateSender(address l1Sender) internal view override { if ( - msg.sender != address(s_l2CrossDomainMessenger) || s_l2CrossDomainMessenger.xDomainMessageSender() != s_l1Sender + msg.sender != address(s_l2CrossDomainMessenger) || s_l2CrossDomainMessenger.xDomainMessageSender() != l1Sender ) { revert InvalidSender(); } - - // Ignore if latest recorded timestamp is newer - if (feedState.startedAt > timestamp) { - emit UpdateIgnored(feedState.latestStatus, feedState.startedAt, status, timestamp); - return; - } - - if (feedState.latestStatus == status) { - _updateRound(feedState.latestRoundId, status); - } else { - feedState.latestRoundId += 1; - _recordRound(feedState.latestRoundId, status, timestamp); - } - } - - /// @inheritdoc AggregatorInterface - function latestAnswer() external view override checkAccess returns (int256) { - FeedState memory feedState = s_feedState; - return _getStatusAnswer(feedState.latestStatus); - } - - /// @inheritdoc AggregatorInterface - function latestTimestamp() external view override checkAccess returns (uint256) { - FeedState memory feedState = s_feedState; - return feedState.startedAt; - } - - /// @inheritdoc AggregatorInterface - function latestRound() external view override checkAccess returns (uint256) { - FeedState memory feedState = s_feedState; - return feedState.latestRoundId; - } - - /// @inheritdoc AggregatorInterface - function getAnswer(uint256 roundId) external view override checkAccess returns (int256) { - if (_isValidRound(roundId)) { - return _getStatusAnswer(s_rounds[uint80(roundId)].status); - } - - revert NoDataPresent(); - } - - /// @inheritdoc AggregatorInterface - function getTimestamp(uint256 roundId) external view override checkAccess returns (uint256) { - if (_isValidRound(roundId)) { - return s_rounds[uint80(roundId)].startedAt; - } - - revert NoDataPresent(); - } - - /// @inheritdoc AggregatorV3Interface - function getRoundData( - uint80 _roundId - ) - public - view - override - checkAccess - returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) - { - if (_isValidRound(_roundId)) { - Round memory round = s_rounds[_roundId]; - answer = _getStatusAnswer(round.status); - startedAt = uint256(round.startedAt); - roundId = _roundId; - updatedAt = uint256(round.updatedAt); - answeredInRound = roundId; - } else { - revert NoDataPresent(); - } - return (roundId, answer, startedAt, updatedAt, answeredInRound); - } - - /// @inheritdoc AggregatorV3Interface - function latestRoundData() - external - view - override - checkAccess - returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) - { - FeedState memory feedState = s_feedState; - - roundId = feedState.latestRoundId; - answer = _getStatusAnswer(feedState.latestStatus); - startedAt = feedState.startedAt; - updatedAt = feedState.updatedAt; - answeredInRound = roundId; - return (roundId, answer, startedAt, updatedAt, answeredInRound); } } diff --git a/contracts/src/v0.8/l2ep/dev/optimism/OptimismValidator.sol b/contracts/src/v0.8/l2ep/dev/optimism/OptimismValidator.sol index a54a56ee60..cf5222f017 100644 --- a/contracts/src/v0.8/l2ep/dev/optimism/OptimismValidator.sol +++ b/contracts/src/v0.8/l2ep/dev/optimism/OptimismValidator.sol @@ -1,82 +1,28 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; -import {AggregatorValidatorInterface} from "../../../shared/interfaces/AggregatorValidatorInterface.sol"; -import {TypeAndVersionInterface} from "../../../interfaces/TypeAndVersionInterface.sol"; -import {OptimismSequencerUptimeFeedInterface} from "./../interfaces/OptimismSequencerUptimeFeedInterface.sol"; +import {ISequencerUptimeFeed} from "./../interfaces/ISequencerUptimeFeed.sol"; -import {SimpleWriteAccessController} from "../../../shared/access/SimpleWriteAccessController.sol"; +import {BaseValidator} from "../shared/BaseValidator.sol"; import {IL1CrossDomainMessenger} from "@eth-optimism/contracts/L1/messaging/IL1CrossDomainMessenger.sol"; -/** - * @title OptimismValidator - makes cross chain call to update the Sequencer Uptime Feed on L2 - */ -contract OptimismValidator is TypeAndVersionInterface, AggregatorValidatorInterface, SimpleWriteAccessController { - int256 private constant ANSWER_SEQ_OFFLINE = 1; - uint32 private s_gasLimit; - - // solhint-disable-next-line chainlink-solidity/prefix-immutable-variables-with-i - address public immutable L1_CROSS_DOMAIN_MESSENGER_ADDRESS; - // solhint-disable-next-line chainlink-solidity/prefix-immutable-variables-with-i - address public immutable L2_UPTIME_FEED_ADDR; - - /** - * @notice emitted when gas cost to spend on L2 is updated - * @param gasLimit updated gas cost - */ - event GasLimitUpdated(uint32 gasLimit); - - /** - * @param l1CrossDomainMessengerAddress address the L1CrossDomainMessenger contract address - * @param l2UptimeFeedAddr the address of the OptimismSequencerUptimeFeed contract address - * @param gasLimit the gasLimit to use for sending a message from L1 to L2 - */ - constructor(address l1CrossDomainMessengerAddress, address l2UptimeFeedAddr, uint32 gasLimit) { - // solhint-disable-next-line gas-custom-errors - require(l1CrossDomainMessengerAddress != address(0), "Invalid xDomain Messenger address"); - // solhint-disable-next-line gas-custom-errors - require(l2UptimeFeedAddr != address(0), "Invalid OptimismSequencerUptimeFeed contract address"); - L1_CROSS_DOMAIN_MESSENGER_ADDRESS = l1CrossDomainMessengerAddress; - L2_UPTIME_FEED_ADDR = l2UptimeFeedAddr; - s_gasLimit = gasLimit; - } - - /** - * @notice versions: - * - * - OptimismValidator 0.1.0: initial release - * - OptimismValidator 1.0.0: change target of L2 sequencer status update - * - now calls `updateStatus` on an L2 OptimismSequencerUptimeFeed contract instead of - * directly calling the Flags contract - * - * @inheritdoc TypeAndVersionInterface - */ - function typeAndVersion() external pure virtual override returns (string memory) { - return "OptimismValidator 1.0.0"; - } - - /** - * @notice sets the new gas cost to spend when sending cross chain message - * @param gasLimit the updated gas cost - */ - function setGasLimit(uint32 gasLimit) external onlyOwner { - s_gasLimit = gasLimit; - emit GasLimitUpdated(gasLimit); - } - - /** - * @notice fetches the gas cost of sending a cross chain message - */ - function getGasLimit() external view returns (uint32) { - return s_gasLimit; - } - - /** - * @notice validate method sends an xDomain L2 tx to update Uptime Feed contract on L2. - * @dev A message is sent using the L1CrossDomainMessenger. This method is accessed controlled. - * @param currentAnswer new aggregator answer - value of 1 considers the sequencer offline. - */ +/// @title OptimismValidator - makes cross chain call to update the Sequencer Uptime Feed on L2 +contract OptimismValidator is BaseValidator { + string public constant override typeAndVersion = "OptimismValidator 1.1.0-dev"; + + /// @param l1CrossDomainMessengerAddress address the L1CrossDomainMessenger contract address + /// @param l2UptimeFeedAddr the address of the OptimismSequencerUptimeFeed contract address + /// @param gasLimit the gasLimit to use for sending a message from L1 to L2 + constructor( + address l1CrossDomainMessengerAddress, + address l2UptimeFeedAddr, + uint32 gasLimit + ) BaseValidator(l1CrossDomainMessengerAddress, l2UptimeFeedAddr, gasLimit) {} + + /// @notice validate method sends an xDomain L2 tx to update Uptime Feed contract on L2. + /// @dev A message is sent using the L1CrossDomainMessenger. This method is accessed controlled. + /// @param currentAnswer new aggregator answer - value of 1 considers the sequencer offline. function validate( uint256 /* previousRoundId */, int256 /* previousAnswer */, @@ -84,7 +30,7 @@ contract OptimismValidator is TypeAndVersionInterface, AggregatorValidatorInterf int256 currentAnswer ) external override checkAccess returns (bool) { // Encode the OptimismSequencerUptimeFeed call - bytes4 selector = OptimismSequencerUptimeFeedInterface.updateStatus.selector; + bytes4 selector = ISequencerUptimeFeed.updateStatus.selector; bool status = currentAnswer == ANSWER_SEQ_OFFLINE; uint64 timestamp = uint64(block.timestamp); // Encode `status` and `timestamp` diff --git a/contracts/src/v0.8/l2ep/dev/scroll/ScrollCrossDomainForwarder.sol b/contracts/src/v0.8/l2ep/dev/scroll/ScrollCrossDomainForwarder.sol index 4ec51fc693..c70bc794af 100644 --- a/contracts/src/v0.8/l2ep/dev/scroll/ScrollCrossDomainForwarder.sol +++ b/contracts/src/v0.8/l2ep/dev/scroll/ScrollCrossDomainForwarder.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.19; +pragma solidity 0.8.24; -import {TypeAndVersionInterface} from "../../../interfaces/TypeAndVersionInterface.sol"; -import {ForwarderInterface} from "../interfaces/ForwarderInterface.sol"; +import {ITypeAndVersion} from "../../../shared/interfaces/ITypeAndVersion.sol"; +import {IForwarder} from "../interfaces/IForwarder.sol"; import {CrossDomainForwarder} from "../CrossDomainForwarder.sol"; import {CrossDomainOwnable} from "../CrossDomainOwnable.sol"; @@ -14,7 +14,7 @@ import {Address} from "../../../vendor/openzeppelin-solidity/v4.7.3/contracts/ut /// @notice L2 Contract which receives messages from a specific L1 address and transparently forwards them to the destination. /// @dev Any other L2 contract which uses this contract's address as a privileged position, /// can be considered to be owned by the `l1Owner` -contract ScrollCrossDomainForwarder is TypeAndVersionInterface, CrossDomainForwarder { +contract ScrollCrossDomainForwarder is ITypeAndVersion, CrossDomainForwarder { string public constant override typeAndVersion = "ScrollCrossDomainForwarder 1.0.0"; address internal immutable i_scrollCrossDomainMessenger; @@ -28,7 +28,7 @@ contract ScrollCrossDomainForwarder is TypeAndVersionInterface, CrossDomainForwa } /// @dev forwarded only if L2 Messenger calls with `xDomainMessageSender` being the L1 owner address - /// @inheritdoc ForwarderInterface + /// @inheritdoc IForwarder function forward(address target, bytes memory data) external override onlyL1Owner { Address.functionCall(target, data, "Forwarder call reverted"); } diff --git a/contracts/src/v0.8/l2ep/dev/scroll/ScrollCrossDomainGovernor.sol b/contracts/src/v0.8/l2ep/dev/scroll/ScrollCrossDomainGovernor.sol index f7d13059fe..dae621e9b0 100644 --- a/contracts/src/v0.8/l2ep/dev/scroll/ScrollCrossDomainGovernor.sol +++ b/contracts/src/v0.8/l2ep/dev/scroll/ScrollCrossDomainGovernor.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.19; +pragma solidity 0.8.24; -import {TypeAndVersionInterface} from "../../../interfaces/TypeAndVersionInterface.sol"; -import {DelegateForwarderInterface} from "../interfaces/DelegateForwarderInterface.sol"; +import {ITypeAndVersion} from "../../../shared/interfaces/ITypeAndVersion.sol"; +import {IDelegateForwarder} from "../interfaces/IDelegateForwarder.sol"; // solhint-disable-next-line no-unused-import -import {ForwarderInterface} from "../interfaces/ForwarderInterface.sol"; +import {IForwarder} from "../interfaces/IForwarder.sol"; import {CrossDomainForwarder} from "../CrossDomainForwarder.sol"; import {CrossDomainOwnable} from "../CrossDomainOwnable.sol"; @@ -16,7 +16,7 @@ import {Address} from "../../../vendor/openzeppelin-solidity/v4.7.3/contracts/ut /// @notice L2 Contract which receives messages from a specific L1 address and transparently forwards them to the destination. /// @dev Any other L2 contract which uses this contract's address as a privileged position, /// can be considered to be simultaneously owned by the `l1Owner` and L2 `owner` -contract ScrollCrossDomainGovernor is DelegateForwarderInterface, TypeAndVersionInterface, CrossDomainForwarder { +contract ScrollCrossDomainGovernor is IDelegateForwarder, ITypeAndVersion, CrossDomainForwarder { string public constant override typeAndVersion = "ScrollCrossDomainGovernor 1.0.0"; address internal immutable i_scrollCrossDomainMessenger; @@ -29,13 +29,13 @@ contract ScrollCrossDomainGovernor is DelegateForwarderInterface, TypeAndVersion i_scrollCrossDomainMessenger = address(crossDomainMessengerAddr); } - /// @inheritdoc ForwarderInterface + /// @inheritdoc IForwarder /// @dev forwarded only if L2 Messenger calls with `msg.sender` being the L1 owner address, or called by the L2 owner function forward(address target, bytes memory data) external override onlyLocalOrCrossDomainOwner { Address.functionCall(target, data, "Governor call reverted"); } - /// @inheritdoc DelegateForwarderInterface + /// @inheritdoc IDelegateForwarder /// @dev forwarded only if L2 Messenger calls with `msg.sender` being the L1 owner address, or called by the L2 owner function forwardDelegate(address target, bytes memory data) external override onlyLocalOrCrossDomainOwner { Address.functionDelegateCall(target, data, "Governor delegatecall reverted"); diff --git a/contracts/src/v0.8/l2ep/dev/scroll/ScrollSequencerUptimeFeed.sol b/contracts/src/v0.8/l2ep/dev/scroll/ScrollSequencerUptimeFeed.sol index e60e8703b7..40f2941aa6 100644 --- a/contracts/src/v0.8/l2ep/dev/scroll/ScrollSequencerUptimeFeed.sol +++ b/contracts/src/v0.8/l2ep/dev/scroll/ScrollSequencerUptimeFeed.sol @@ -1,66 +1,16 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.19; +pragma solidity 0.8.24; -import {ScrollSequencerUptimeFeedInterface} from "../interfaces/ScrollSequencerUptimeFeedInterface.sol"; -import {AggregatorInterface} from "../../../shared/interfaces/AggregatorInterface.sol"; -import {AggregatorV3Interface} from "../../../shared/interfaces/AggregatorV3Interface.sol"; -import {AggregatorV2V3Interface} from "../../../shared/interfaces/AggregatorV2V3Interface.sol"; -import {TypeAndVersionInterface} from "../../../interfaces/TypeAndVersionInterface.sol"; - -import {SimpleReadAccessController} from "../../../shared/access/SimpleReadAccessController.sol"; +import {BaseSequencerUptimeFeed} from "../shared/BaseSequencerUptimeFeed.sol"; import {IL2ScrollMessenger} from "@scroll-tech/contracts/L2/IL2ScrollMessenger.sol"; /// @title ScrollSequencerUptimeFeed - L2 sequencer uptime status aggregator /// @notice L2 contract that receives status updates, and records a new answer if the status changed -contract ScrollSequencerUptimeFeed is - AggregatorV2V3Interface, - ScrollSequencerUptimeFeedInterface, - TypeAndVersionInterface, - SimpleReadAccessController -{ - string public constant override typeAndVersion = "ScrollSequencerUptimeFeed 1.0.0"; - - /// @dev Round info (for uptime history) - struct Round { - bool status; - uint64 startedAt; - uint64 updatedAt; - } - - /// @dev Packed state struct to save sloads - struct FeedState { - uint80 latestRoundId; - bool latestStatus; - uint64 startedAt; - uint64 updatedAt; - } - - /// @notice Sender is not the L2 messenger - error InvalidSender(); - /// @notice Replacement for AggregatorV3Interface "No data present" - error NoDataPresent(); - /// @notice Address must not be the zero address +contract ScrollSequencerUptimeFeed is BaseSequencerUptimeFeed { error ZeroAddress(); - event L1SenderTransferred(address indexed from, address indexed to); - /// @dev Emitted when an `updateStatus` call is ignored due to unchanged status or stale timestamp - event UpdateIgnored(bool latestStatus, uint64 latestTimestamp, bool incomingStatus, uint64 incomingTimestamp); - /// @dev Emitted when a updateStatus is called without the status changing - event RoundUpdated(int256 status, uint64 updatedAt); - - // solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables - uint8 public constant override decimals = 0; - // solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables - string public constant override description = "L2 Sequencer Uptime Status Feed"; - // solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables - uint256 public constant override version = 1; - - /// @dev L1 address - address private s_l1Sender; - /// @dev s_latestRoundId == 0 means this contract is uninitialized. - FeedState private s_feedState = FeedState({latestRoundId: 0, latestStatus: false, startedAt: 0, updatedAt: 0}); - mapping(uint80 roundId => Round round) private s_rounds; + string public constant override typeAndVersion = "ScrollSequencerUptimeFeed 1.1.0-dev"; // solhint-disable-next-line chainlink-solidity/prefix-immutable-variables-with-i IL2ScrollMessenger private immutable s_l2CrossDomainMessenger; @@ -68,169 +18,23 @@ contract ScrollSequencerUptimeFeed is /// @param l1SenderAddress Address of the L1 contract that is permissioned to call this contract /// @param l2CrossDomainMessengerAddr Address of the L2CrossDomainMessenger contract /// @param initialStatus The initial status of the feed - constructor(address l1SenderAddress, address l2CrossDomainMessengerAddr, bool initialStatus) { + constructor( + address l1SenderAddress, + address l2CrossDomainMessengerAddr, + bool initialStatus + ) BaseSequencerUptimeFeed(l1SenderAddress, initialStatus) { if (l2CrossDomainMessengerAddr == address(0)) { revert ZeroAddress(); } - _setL1Sender(l1SenderAddress); s_l2CrossDomainMessenger = IL2ScrollMessenger(l2CrossDomainMessengerAddr); - - // Initialise roundId == 1 as the first round - _recordRound(1, initialStatus, uint64(block.timestamp)); - } - - /// @notice Check if a roundId is valid in this current contract state - /// @dev Mainly used for AggregatorV2V3Interface functions - /// @param roundId Round ID to check - function _isValidRound(uint256 roundId) private view returns (bool) { - return roundId > 0 && roundId <= type(uint80).max && s_feedState.latestRoundId >= roundId; - } - - /// @return L1 sender address - function l1Sender() public view virtual returns (address) { - return s_l1Sender; } - /// @notice Set the allowed L1 sender for this contract to a new L1 sender - /// @dev Can be disabled by setting the L1 sender as `address(0)`. Accessible only by owner. - /// @param to new L1 sender that will be allowed to call `updateStatus` on this contract - function transferL1Sender(address to) external virtual onlyOwner { - _setL1Sender(to); - } - - /// @notice internal method that stores the L1 sender - function _setL1Sender(address to) private { - address from = s_l1Sender; - if (from != to) { - s_l1Sender = to; - emit L1SenderTransferred(from, to); - } - } - - /// @dev Returns an AggregatorV2V3Interface compatible answer from status flag - /// @param status The status flag to convert to an aggregator-compatible answer - function _getStatusAnswer(bool status) private pure returns (int256) { - return status ? int256(1) : int256(0); - } - - /// @notice Helper function to record a round and set the latest feed state. - /// @param roundId The round ID to record - /// @param status Sequencer status - /// @param timestamp The L1 block timestamp of status update - function _recordRound(uint80 roundId, bool status, uint64 timestamp) private { - s_feedState = FeedState(roundId, status, timestamp, uint64(block.timestamp)); - s_rounds[roundId] = Round(status, timestamp, uint64(block.timestamp)); - - emit NewRound(roundId, msg.sender, timestamp); - emit AnswerUpdated(_getStatusAnswer(status), roundId, timestamp); - } - - /// @notice Helper function to update when a round was last updated - /// @param roundId The round ID to update - /// @param status Sequencer status - function _updateRound(uint80 roundId, bool status) private { - s_feedState.updatedAt = uint64(block.timestamp); - s_rounds[roundId].updatedAt = uint64(block.timestamp); - emit RoundUpdated(_getStatusAnswer(status), uint64(block.timestamp)); - } - - /// @notice Record a new status and timestamp if it has changed since the last round. - /// @dev This function will revert if not called from `l1Sender` via the L1->L2 messenger. - /// - /// @param status Sequencer status - /// @param timestamp Block timestamp of status update - function updateStatus(bool status, uint64 timestamp) external override { - FeedState memory feedState = s_feedState; - + function _validateSender(address l1Sender) internal view override { if ( - msg.sender != address(s_l2CrossDomainMessenger) || s_l2CrossDomainMessenger.xDomainMessageSender() != s_l1Sender + msg.sender != address(s_l2CrossDomainMessenger) || s_l2CrossDomainMessenger.xDomainMessageSender() != l1Sender ) { revert InvalidSender(); } - - // Ignore if latest recorded timestamp is newer - if (feedState.startedAt > timestamp) { - emit UpdateIgnored(feedState.latestStatus, feedState.startedAt, status, timestamp); - return; - } - - if (feedState.latestStatus == status) { - _updateRound(feedState.latestRoundId, status); - } else { - feedState.latestRoundId += 1; - _recordRound(feedState.latestRoundId, status, timestamp); - } - } - - /// @inheritdoc AggregatorInterface - function latestAnswer() external view override checkAccess returns (int256) { - return _getStatusAnswer(s_feedState.latestStatus); - } - - /// @inheritdoc AggregatorInterface - function latestTimestamp() external view override checkAccess returns (uint256) { - return s_feedState.startedAt; - } - - /// @inheritdoc AggregatorInterface - function latestRound() external view override checkAccess returns (uint256) { - return s_feedState.latestRoundId; - } - - /// @inheritdoc AggregatorInterface - function getAnswer(uint256 roundId) external view override checkAccess returns (int256) { - if (!_isValidRound(roundId)) { - revert NoDataPresent(); - } - - return _getStatusAnswer(s_rounds[uint80(roundId)].status); - } - - /// @inheritdoc AggregatorInterface - function getTimestamp(uint256 roundId) external view override checkAccess returns (uint256) { - if (!_isValidRound(roundId)) { - revert NoDataPresent(); - } - - return s_rounds[uint80(roundId)].startedAt; - } - - /// @inheritdoc AggregatorV3Interface - function getRoundData( - uint80 _roundId - ) - public - view - override - checkAccess - returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) - { - if (!_isValidRound(_roundId)) { - revert NoDataPresent(); - } - - Round memory round = s_rounds[_roundId]; - - return (_roundId, _getStatusAnswer(round.status), uint256(round.startedAt), uint256(round.updatedAt), _roundId); - } - - /// @inheritdoc AggregatorV3Interface - function latestRoundData() - external - view - override - checkAccess - returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) - { - FeedState memory feedState = s_feedState; - - return ( - feedState.latestRoundId, - _getStatusAnswer(feedState.latestStatus), - feedState.startedAt, - feedState.updatedAt, - feedState.latestRoundId - ); } } diff --git a/contracts/src/v0.8/l2ep/dev/scroll/ScrollValidator.sol b/contracts/src/v0.8/l2ep/dev/scroll/ScrollValidator.sol index 9df4a12ac6..b009c80fdf 100644 --- a/contracts/src/v0.8/l2ep/dev/scroll/ScrollValidator.sol +++ b/contracts/src/v0.8/l2ep/dev/scroll/ScrollValidator.sol @@ -1,71 +1,31 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.19; +pragma solidity 0.8.24; -import {AggregatorValidatorInterface} from "../../../shared/interfaces/AggregatorValidatorInterface.sol"; -import {TypeAndVersionInterface} from "../../../interfaces/TypeAndVersionInterface.sol"; -import {ScrollSequencerUptimeFeedInterface} from "../interfaces/ScrollSequencerUptimeFeedInterface.sol"; +import {ISequencerUptimeFeed} from "../interfaces/ISequencerUptimeFeed.sol"; -import {SimpleWriteAccessController} from "../../../shared/access/SimpleWriteAccessController.sol"; +import {BaseValidator} from "../shared/BaseValidator.sol"; import {IL1MessageQueue} from "@scroll-tech/contracts/L1/rollup/IL1MessageQueue.sol"; import {IL1ScrollMessenger} from "@scroll-tech/contracts/L1/IL1ScrollMessenger.sol"; /// @title ScrollValidator - makes cross chain call to update the Sequencer Uptime Feed on L2 -contract ScrollValidator is TypeAndVersionInterface, AggregatorValidatorInterface, SimpleWriteAccessController { - // solhint-disable-next-line chainlink-solidity/prefix-immutable-variables-with-i - address public immutable L1_CROSS_DOMAIN_MESSENGER_ADDRESS; - // solhint-disable-next-line chainlink-solidity/prefix-immutable-variables-with-i - address public immutable L2_UPTIME_FEED_ADDR; +contract ScrollValidator is BaseValidator { + string public constant override typeAndVersion = "ScrollValidator 1.1.0-dev"; + // solhint-disable-next-line chainlink-solidity/prefix-immutable-variables-with-i address public immutable L1_MSG_QUEUE_ADDR; - string public constant override typeAndVersion = "ScrollValidator 1.0.0"; - int256 private constant ANSWER_SEQ_OFFLINE = 1; - uint32 private s_gasLimit; - - /// @notice emitted when gas cost to spend on L2 is updated - /// @param gasLimit updated gas cost - event GasLimitUpdated(uint32 gasLimit); - - /// @param l1CrossDomainMessengerAddress address the L1CrossDomainMessenger contract address - /// @param l2UptimeFeedAddr the address of the ScrollSequencerUptimeFeed contract address - /// @param gasLimit the gasLimit to use for sending a message from L1 to L2 constructor( address l1CrossDomainMessengerAddress, address l2UptimeFeedAddr, address l1MessageQueueAddr, uint32 gasLimit - ) { - // solhint-disable-next-line gas-custom-errors - require(l1CrossDomainMessengerAddress != address(0), "Invalid xDomain Messenger address"); + ) BaseValidator(l1CrossDomainMessengerAddress, l2UptimeFeedAddr, gasLimit) { // solhint-disable-next-line gas-custom-errors require(l1MessageQueueAddr != address(0), "Invalid L1 message queue address"); - // solhint-disable-next-line gas-custom-errors - require(l2UptimeFeedAddr != address(0), "Invalid ScrollSequencerUptimeFeed contract address"); - L1_CROSS_DOMAIN_MESSENGER_ADDRESS = l1CrossDomainMessengerAddress; - L2_UPTIME_FEED_ADDR = l2UptimeFeedAddr; L1_MSG_QUEUE_ADDR = l1MessageQueueAddr; - s_gasLimit = gasLimit; - } - - /// @notice sets the new gas cost to spend when sending cross chain message - /// @param gasLimit the updated gas cost - function setGasLimit(uint32 gasLimit) external onlyOwner { - s_gasLimit = gasLimit; - emit GasLimitUpdated(gasLimit); } - /// @notice fetches the gas cost of sending a cross chain message - function getGasLimit() external view returns (uint32) { - return s_gasLimit; - } - - /// @notice makes this contract payable - /// @dev receives funds: - /// - to use them (if configured) to pay for L2 execution on L1 - /// - when withdrawing funds from L2 xDomain alias address (pay for L2 execution on L2) - receive() external payable {} - /// @notice validate method sends an xDomain L2 tx to update Uptime Feed contract on L2. /// @dev A message is sent using the L1CrossDomainMessenger. This method is accessed controlled. /// @param currentAnswer new aggregator answer - value of 1 considers the sequencer offline. @@ -82,7 +42,7 @@ contract ScrollValidator is TypeAndVersionInterface, AggregatorValidatorInterfac L2_UPTIME_FEED_ADDR, 0, abi.encodeWithSelector( - ScrollSequencerUptimeFeedInterface.updateStatus.selector, + ISequencerUptimeFeed.updateStatus.selector, currentAnswer == ANSWER_SEQ_OFFLINE, uint64(block.timestamp) ), diff --git a/contracts/src/v0.8/l2ep/dev/shared/BaseSequencerUptimeFeed.sol b/contracts/src/v0.8/l2ep/dev/shared/BaseSequencerUptimeFeed.sol new file mode 100644 index 0000000000..15c2050456 --- /dev/null +++ b/contracts/src/v0.8/l2ep/dev/shared/BaseSequencerUptimeFeed.sol @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {ITypeAndVersion} from "../../../shared/interfaces/ITypeAndVersion.sol"; +import {AggregatorInterface} from "../../../shared/interfaces/AggregatorInterface.sol"; +import {AggregatorV3Interface} from "../../../shared/interfaces/AggregatorV3Interface.sol"; +import {AggregatorV2V3Interface} from "../../../shared/interfaces/AggregatorV2V3Interface.sol"; +import {ISequencerUptimeFeed} from "./../interfaces/ISequencerUptimeFeed.sol"; + +import {SimpleReadAccessController} from "../../../shared/access/SimpleReadAccessController.sol"; + +/// @title L2 sequencer uptime status aggregator +/// @notice L2 contract that receives status updates from a specific L1 address, +/// records a new answer if the status changed +abstract contract BaseSequencerUptimeFeed is + ITypeAndVersion, + AggregatorV2V3Interface, + ISequencerUptimeFeed, + SimpleReadAccessController +{ + /// @dev Round info for uptime history + struct Round { + uint64 startedAt; // ─╮ The timestamp at which the round started + uint64 updatedAt; // │ The timestamp at which the round was updated + bool status; // ──────╯ The sequencer status for the round + } + + struct FeedState { + uint80 latestRoundId; // ─╮ The ID of the latest round + uint64 startedAt; // │ The date at which the latest round started + uint64 updatedAt; // │ The date at which the latest round was updated + bool latestStatus; // ────╯ The status of the latest round + } + + /// @notice Sender is not the L2 messenger + error InvalidSender(); + /// @notice Replacement for AggregatorV3Interface "No data present" + error NoDataPresent(); + + event L1SenderTransferred(address indexed from, address indexed to); + /// @dev Emitted when an `updateStatus` call is ignored due to unchanged status or stale timestamp + event UpdateIgnored(bool latestStatus, uint64 latestTimestamp, bool incomingStatus, uint64 incomingTimestamp); + /// @dev Emitted when a updateStatus is called without the status changing + event RoundUpdated(int256 status, uint64 updatedAt); + + // solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables + uint8 public constant override decimals = 0; + // solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables + string public constant override description = "L2 Sequencer Uptime Status Feed"; + // solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables + uint256 public constant override version = 1; + + /// @dev L1 address + address private s_l1Sender; + /// @dev s_latestRoundId == 0 means this contract is uninitialized. + FeedState private s_feedState = FeedState({latestRoundId: 0, latestStatus: false, startedAt: 0, updatedAt: 0}); + mapping(uint80 roundId => Round round) private s_rounds; + + /// @param l1SenderAddress Address of the L1 contract that is permissioned to call this contract + /// @param initialStatus The initial status of the feed + constructor(address l1SenderAddress, bool initialStatus) { + // We neet to allow l1SenderAddress to be zero because this contract is deployed first + // After deploying the validator contract, this contract will be updated with the correct L1 sender address + _setL1Sender(l1SenderAddress); + + // Initialise roundId == 1 as the first round + _recordRound(1, initialStatus, uint64(block.timestamp)); + } + + /// @notice Check if a roundId is valid in this current contract state + /// @dev Mainly used for AggregatorV2V3Interface functions + /// @param roundId Round ID to check + function _isValidRound(uint256 roundId) private view returns (bool) { + return roundId > 0 && roundId <= type(uint80).max && s_feedState.latestRoundId >= roundId; + } + + /// @return L1 sender address + function l1Sender() public view virtual returns (address) { + return s_l1Sender; + } + + /// @notice Set the allowed L1 sender for this contract to a new L1 sender + /// @dev Can be disabled by setting the L1 sender as `address(0)`. Accessible only by owner. + /// @param to new L1 sender that will be allowed to call `updateStatus` on this contract + function transferL1Sender(address to) external virtual onlyOwner { + _setL1Sender(to); + } + + /// @notice internal method that stores the L1 sender + function _setL1Sender(address newSender) internal { + address oldSender = s_l1Sender; + if (oldSender != newSender) { + s_l1Sender = newSender; + emit L1SenderTransferred(oldSender, newSender); + } + } + + /// @dev Returns an AggregatorV2V3Interface compatible answer from status flag + /// @param status The status flag to convert to an aggregator-compatible answer + function _getStatusAnswer(bool status) internal pure returns (int256) { + return status ? int256(1) : int256(0); + } + + /// @notice Helper function to record a round and set the latest feed state. + /// @param roundId The round ID to record + /// @param status Sequencer status + /// @param timestamp The L1 block timestamp of status update + function _recordRound(uint80 roundId, bool status, uint64 timestamp) internal { + s_rounds[roundId] = Round({status: status, startedAt: timestamp, updatedAt: uint64(block.timestamp)}); + s_feedState = FeedState({ + latestRoundId: roundId, + latestStatus: status, + startedAt: timestamp, + updatedAt: uint64(block.timestamp) + }); + + emit NewRound(roundId, msg.sender, timestamp); + emit AnswerUpdated(_getStatusAnswer(status), roundId, timestamp); + } + + /// @notice Helper function to update when a round was last updated + /// @param roundId The round ID to update + /// @param status Sequencer status + function _updateRound(uint80 roundId, bool status) internal { + s_rounds[roundId].updatedAt = uint64(block.timestamp); + s_feedState.updatedAt = uint64(block.timestamp); + emit RoundUpdated(_getStatusAnswer(status), uint64(block.timestamp)); + } + + function _getFeedState() internal view returns (FeedState memory) { + return s_feedState; + } + + /// @inheritdoc AggregatorInterface + function latestAnswer() external view override checkAccess returns (int256) { + return _getStatusAnswer(s_feedState.latestStatus); + } + + /// @inheritdoc AggregatorInterface + function latestTimestamp() external view override checkAccess returns (uint256) { + return s_feedState.startedAt; + } + + /// @inheritdoc AggregatorInterface + function latestRound() external view override checkAccess returns (uint256) { + return s_feedState.latestRoundId; + } + + /// @inheritdoc AggregatorInterface + function getAnswer(uint256 roundId) external view override checkAccess returns (int256) { + if (!_isValidRound(roundId)) { + revert NoDataPresent(); + } + + return _getStatusAnswer(s_rounds[uint80(roundId)].status); + } + + /// @inheritdoc AggregatorInterface + function getTimestamp(uint256 roundId) external view override checkAccess returns (uint256) { + if (!_isValidRound(roundId)) { + revert NoDataPresent(); + } + + return s_rounds[uint80(roundId)].startedAt; + } + + /** + * @notice Record a new status and timestamp if it has changed since the last round. + * @dev This function will revert if not called from `l1Sender` via the L1->L2 messenger. + * + * @param status Sequencer status + * @param timestamp Block timestamp of status update + */ + function updateStatus(bool status, uint64 timestamp) external override { + _validateSender(s_l1Sender); + + FeedState memory feedState = _getFeedState(); + // Ignore if latest recorded timestamp is newer + if (feedState.startedAt > timestamp) { + emit UpdateIgnored(feedState.latestStatus, feedState.startedAt, status, timestamp); + return; + } + + if (feedState.latestStatus == status) { + _updateRound(feedState.latestRoundId, status); + } else { + feedState.latestRoundId += 1; + _recordRound(feedState.latestRoundId, status, timestamp); + } + } + + function _validateSender(address l1Sender) internal virtual; + + /// @inheritdoc AggregatorV3Interface + function getRoundData( + uint80 _roundId + ) + public + view + override + checkAccess + returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) + { + if (!_isValidRound(_roundId)) { + revert NoDataPresent(); + } + + Round storage round = s_rounds[_roundId]; + + return (_roundId, _getStatusAnswer(round.status), uint256(round.startedAt), uint256(round.updatedAt), _roundId); + } + + /// @inheritdoc AggregatorV3Interface + function latestRoundData() + external + view + override + checkAccess + returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) + { + FeedState storage feedState = s_feedState; + + return ( + feedState.latestRoundId, + _getStatusAnswer(feedState.latestStatus), + feedState.startedAt, + feedState.updatedAt, + feedState.latestRoundId + ); + } +} diff --git a/contracts/src/v0.8/l2ep/dev/shared/BaseValidator.sol b/contracts/src/v0.8/l2ep/dev/shared/BaseValidator.sol new file mode 100644 index 0000000000..33df388972 --- /dev/null +++ b/contracts/src/v0.8/l2ep/dev/shared/BaseValidator.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {AggregatorValidatorInterface} from "../../../shared/interfaces/AggregatorValidatorInterface.sol"; +import {ITypeAndVersion} from "../../../shared/interfaces/ITypeAndVersion.sol"; + +import {SimpleWriteAccessController} from "../../../shared/access/SimpleWriteAccessController.sol"; + +abstract contract BaseValidator is SimpleWriteAccessController, AggregatorValidatorInterface, ITypeAndVersion { + /// @notice emitted when gas cost to spend on L2 is updated + /// @param gasLimit updated gas cost + event GasLimitUpdated(uint32 gasLimit); + + error L1CrossDomainMessengerAddressZero(); + error L2UptimeFeedAddrZero(); + + // solhint-disable-next-line chainlink-solidity/prefix-immutable-variables-with-i + address public immutable L1_CROSS_DOMAIN_MESSENGER_ADDRESS; + // solhint-disable-next-line chainlink-solidity/prefix-immutable-variables-with-i + address public immutable L2_UPTIME_FEED_ADDR; + + int256 internal constant ANSWER_SEQ_OFFLINE = 1; + + uint32 internal s_gasLimit; + + /// @param l1CrossDomainMessengerAddress address the L1CrossDomainMessenger contract address + /// @param l2UptimeFeedAddr the address of the SequencerUptimeFeed contract address + /// @param gasLimit the gasLimit to use for sending a message from L1 to L2 + constructor(address l1CrossDomainMessengerAddress, address l2UptimeFeedAddr, uint32 gasLimit) { + if (l1CrossDomainMessengerAddress == address(0)) { + revert L1CrossDomainMessengerAddressZero(); + } + if (l2UptimeFeedAddr == address(0)) { + revert L2UptimeFeedAddrZero(); + } + + L1_CROSS_DOMAIN_MESSENGER_ADDRESS = l1CrossDomainMessengerAddress; + L2_UPTIME_FEED_ADDR = l2UptimeFeedAddr; + s_gasLimit = gasLimit; + } + + /// @notice fetches the gas cost of sending a cross chain message + function getGasLimit() external view returns (uint32) { + return s_gasLimit; + } + + /// @notice sets the new gas cost to spend when sending cross chain message + /// @param gasLimit the updated gas cost + function setGasLimit(uint32 gasLimit) external onlyOwner { + s_gasLimit = gasLimit; + emit GasLimitUpdated(gasLimit); + } + + /// @notice makes this contract payable + /// @dev receives funds: + /// - to use them (if configured) to pay for L2 execution on L1 + /// - when withdrawing funds from L2 xDomain alias address (pay for L2 execution on L2) + receive() external payable {} +} diff --git a/contracts/src/v0.8/l2ep/dev/zksync/ZKSyncSequencerUptimeFeed.sol b/contracts/src/v0.8/l2ep/dev/zksync/ZKSyncSequencerUptimeFeed.sol new file mode 100644 index 0000000000..0074a0277d --- /dev/null +++ b/contracts/src/v0.8/l2ep/dev/zksync/ZKSyncSequencerUptimeFeed.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {BaseSequencerUptimeFeed} from "../shared/BaseSequencerUptimeFeed.sol"; + +import {AddressAliasHelper} from "../../../vendor/arb-bridge-eth/v0.8.0-custom/contracts/libraries/AddressAliasHelper.sol"; + +/// @title ZKSyncSequencerUptimeFeed - L2 sequencer uptime status aggregator +/// @notice L2 contract that receives status updates from a specific L1 address, +/// records a new answer if the status changed +contract ZKSyncSequencerUptimeFeed is BaseSequencerUptimeFeed { + string public constant override typeAndVersion = "ZKSyncSequencerUptimeFeed 1.1.0-dev"; + + /// @param l1SenderAddress Address of the L1 contract that is permissioned to call this contract + /// @param initialStatus The initial status of the feed + constructor(address l1SenderAddress, bool initialStatus) BaseSequencerUptimeFeed(l1SenderAddress, initialStatus) {} + + function _validateSender(address l1Sender) internal view override { + address aliasedL1Sender = AddressAliasHelper.applyL1ToL2Alias(l1Sender); + + if (msg.sender != aliasedL1Sender) { + revert InvalidSender(); + } + } +} diff --git a/contracts/src/v0.8/l2ep/dev/zksync/ZKSyncValidator.sol b/contracts/src/v0.8/l2ep/dev/zksync/ZKSyncValidator.sol new file mode 100644 index 0000000000..10f68ce286 --- /dev/null +++ b/contracts/src/v0.8/l2ep/dev/zksync/ZKSyncValidator.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {ISequencerUptimeFeed} from "./../interfaces/ISequencerUptimeFeed.sol"; + +import {BaseValidator} from "../shared/BaseValidator.sol"; + +import {IBridgehub, L2TransactionRequestDirect} from "@zksync/contracts/l1-contracts/contracts/bridgehub/IBridgehub.sol"; + +/// @title ZKSyncValidator - makes cross chain call to update the Sequencer Uptime Feed on L2 +contract ZKSyncValidator is BaseValidator { + /// Contract state variables + string public constant override typeAndVersion = "ZKSyncValidator 1.1.0-dev"; + uint32 private constant ZKSYNC_TEST_NET_CHAIN_ID = 300; + uint32 private constant ZKSYNC_MAIN_NET_CHAIN_ID = 324; + // solhint-disable-next-line chainlink-solidity/prefix-immutable-variables-with-i + uint32 private immutable CHAIN_ID; + /// @dev how much l2 gas each byte of pubdata costs + uint32 private s_l2GasPerPubdataByteLimit; + + /// @notice emitted when the gas per pubdata byte limit is updated + /// @param l2GasPerPubdataByteLimit updated gas per pubdata byte limit + event GasPerPubdataByteLimitUpdated(uint32 l2GasPerPubdataByteLimit); + + /// @notice ChainID is not a valid value + error InvalidChainID(); + + /// @param l1CrossDomainMessengerAddress address the Bridgehub contract address + /// @param l2UptimeFeedAddr the address of the SequencerUptimeFeedInterface contract address + /// @param gasLimit the gasLimit to use for sending a message from L1 to L2 + constructor( + address l1CrossDomainMessengerAddress, + address l2UptimeFeedAddr, + uint32 gasLimit, + uint32 chainId, + uint32 l2GasPerPubdataByteLimit + ) BaseValidator(l1CrossDomainMessengerAddress, l2UptimeFeedAddr, gasLimit) { + if (chainId != ZKSYNC_TEST_NET_CHAIN_ID && chainId != ZKSYNC_MAIN_NET_CHAIN_ID) { + revert InvalidChainID(); + } + + s_l2GasPerPubdataByteLimit = l2GasPerPubdataByteLimit; + CHAIN_ID = chainId; + } + + /// @notice sets the l2GasPerPubdataByteLimit for the L2 transaction request + /// @param l2GasPerPubdataByteLimit the updated l2GasPerPubdataByteLimit + function setL2GasPerPubdataByteLimit(uint32 l2GasPerPubdataByteLimit) external onlyOwner { + if (s_l2GasPerPubdataByteLimit != l2GasPerPubdataByteLimit) { + s_l2GasPerPubdataByteLimit = l2GasPerPubdataByteLimit; + emit GasPerPubdataByteLimitUpdated(l2GasPerPubdataByteLimit); + } + } + + /// @notice fetches the l2GasPerPubdataByteLimit for the L2 transaction request + function getL2GasPerPubdataByteLimit() external view returns (uint32) { + return s_l2GasPerPubdataByteLimit; + } + + /// @notice fetches the chain id + function getChainId() external view returns (uint32) { + return CHAIN_ID; + } + + /// @notice validate method sends an xDomain L2 tx to update Uptime Feed contract on L2. + /// @dev A message is sent using the Bridgehub. This method is accessed controlled. + /// @param currentAnswer new aggregator answer - value of 1 considers the sequencer offline. + function validate( + uint256 /* previousRoundId */, + int256 /* previousAnswer */, + uint256 /* currentRoundId */, + int256 currentAnswer + ) external override checkAccess returns (bool) { + IBridgehub bridgeHub = IBridgehub(L1_CROSS_DOMAIN_MESSENGER_ADDRESS); + + uint256 transactionBaseCostEstimate = bridgeHub.l2TransactionBaseCost( + CHAIN_ID, + tx.gasprice, + s_gasLimit, + s_l2GasPerPubdataByteLimit + ); + + L2TransactionRequestDirect memory l2TransactionRequestDirect = L2TransactionRequestDirect({ + chainId: CHAIN_ID, + mintValue: transactionBaseCostEstimate, + l2Contract: L2_UPTIME_FEED_ADDR, + l2Value: 0, + l2Calldata: abi.encodeWithSelector( + ISequencerUptimeFeed.updateStatus.selector, + currentAnswer == ANSWER_SEQ_OFFLINE, + uint64(block.timestamp) + ), + l2GasLimit: s_gasLimit, + l2GasPerPubdataByteLimit: s_l2GasPerPubdataByteLimit, + factoryDeps: new bytes[](0), + refundRecipient: msg.sender + }); + + // Make the xDomain call + bridgeHub.requestL2TransactionDirect{value: transactionBaseCostEstimate}(l2TransactionRequestDirect); + + return true; + } +} diff --git a/contracts/src/v0.8/l2ep/test/mocks/MockAggregatorV2V3.sol b/contracts/src/v0.8/l2ep/test/mocks/MockAggregatorV2V3.sol index c4e2f71030..52019324f5 100644 --- a/contracts/src/v0.8/l2ep/test/mocks/MockAggregatorV2V3.sol +++ b/contracts/src/v0.8/l2ep/test/mocks/MockAggregatorV2V3.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.9; +pragma solidity ^0.8.24; import {AggregatorV2V3Interface} from "../../../shared/interfaces/AggregatorV2V3Interface.sol"; diff --git a/contracts/src/v0.8/l2ep/test/mocks/scroll/MockScrollL1CrossDomainMessenger.sol b/contracts/src/v0.8/l2ep/test/mocks/scroll/MockScrollL1CrossDomainMessenger.sol index e63847d655..42147fbd91 100644 --- a/contracts/src/v0.8/l2ep/test/mocks/scroll/MockScrollL1CrossDomainMessenger.sol +++ b/contracts/src/v0.8/l2ep/test/mocks/scroll/MockScrollL1CrossDomainMessenger.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.9; +pragma solidity ^0.8.24; import {IL1ScrollMessenger} from "@scroll-tech/contracts/L1/IL1ScrollMessenger.sol"; diff --git a/contracts/src/v0.8/l2ep/test/mocks/scroll/MockScrollL1MessageQueue.sol b/contracts/src/v0.8/l2ep/test/mocks/scroll/MockScrollL1MessageQueue.sol index 1700bcbe16..e43f1bf136 100644 --- a/contracts/src/v0.8/l2ep/test/mocks/scroll/MockScrollL1MessageQueue.sol +++ b/contracts/src/v0.8/l2ep/test/mocks/scroll/MockScrollL1MessageQueue.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.19; +pragma solidity 0.8.24; import {IL1MessageQueue} from "@scroll-tech/contracts/L1/rollup/IL1MessageQueue.sol"; diff --git a/contracts/src/v0.8/l2ep/test/mocks/scroll/MockScrollL2CrossDomainMessenger.sol b/contracts/src/v0.8/l2ep/test/mocks/scroll/MockScrollL2CrossDomainMessenger.sol index 66400b7d30..af0e0b5ca5 100644 --- a/contracts/src/v0.8/l2ep/test/mocks/scroll/MockScrollL2CrossDomainMessenger.sol +++ b/contracts/src/v0.8/l2ep/test/mocks/scroll/MockScrollL2CrossDomainMessenger.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.9; +pragma solidity ^0.8.24; import {IL2ScrollMessenger} from "@scroll-tech/contracts/L2/IL2ScrollMessenger.sol"; diff --git a/contracts/src/v0.8/l2ep/test/mocks/zksync/MockZKSyncL1Bridge.sol b/contracts/src/v0.8/l2ep/test/mocks/zksync/MockZKSyncL1Bridge.sol new file mode 100644 index 0000000000..b46b9d9fdf --- /dev/null +++ b/contracts/src/v0.8/l2ep/test/mocks/zksync/MockZKSyncL1Bridge.sol @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {IBridgehub, L2TransactionRequestDirect, L2TransactionRequestTwoBridgesOuter} from "@zksync/contracts/l1-contracts/contracts/bridgehub/IBridgehub.sol"; +import {IL1SharedBridge} from "@zksync/contracts/l1-contracts/contracts/bridge/interfaces/IL1SharedBridge.sol"; +import {L2Message, L2Log, TxStatus} from "@zksync/contracts/l1-contracts/contracts/common/Messaging.sol"; + +contract MockBridgehub is IBridgehub { + address public pendingAdmin; + address public admin; + address public sharedBridgeAddr; + + mapping(address stateTransitionManager => bool stateTransitionManagerIsRegistered) + public registeredStateTransitionManagers; + mapping(uint256 chainId => address stateTransitionManagerAddress) public stateTransitionManagers; + mapping(address baseToken => bool tokenIsRegistered) public registeredTokens; + mapping(uint256 chainId => address baseToken) public baseTokens; + mapping(uint256 chainId => address hyperChain) public hyperchains; + + /// Generic error for unauthorized actions + error NotAuthorized(string msg); + + /// Fake event that will get emitted when `requestL2TransactionDirect` is called + event SentMessage(address indexed sender, bytes message); + + /// Admin functions + function setPendingAdmin(address _newPendingAdmin) external override { + emit NewPendingAdmin(pendingAdmin, _newPendingAdmin); + pendingAdmin = _newPendingAdmin; + } + + function acceptAdmin() external override { + if (msg.sender != pendingAdmin) { + revert NotAuthorized("Only pending admin can accept"); + } + + emit NewAdmin(admin, pendingAdmin); + admin = pendingAdmin; + pendingAdmin = address(0); + } + + /// Getters + function stateTransitionManagerIsRegistered(address _stateTransitionManager) external view override returns (bool) { + return registeredStateTransitionManagers[_stateTransitionManager]; + } + + function stateTransitionManager(uint256 _chainId) external view override returns (address) { + return stateTransitionManagers[_chainId]; + } + + function tokenIsRegistered(address _baseToken) external view override returns (bool) { + return registeredTokens[_baseToken]; + } + + function baseToken(uint256 _chainId) external view override returns (address) { + return baseTokens[_chainId]; + } + + function sharedBridge() external view override returns (IL1SharedBridge) { + return IL1SharedBridge(sharedBridgeAddr); + } + + function getHyperchain(uint256 _chainId) external view override returns (address) { + return hyperchains[_chainId]; + } + + /// Mailbox forwarder + function proveL2MessageInclusion( + uint256, + uint256, + uint256, + L2Message calldata, + bytes32[] calldata + ) external pure override returns (bool) { + return true; + } + + function proveL2LogInclusion( + uint256, + uint256, + uint256, + L2Log memory, + bytes32[] calldata + ) external pure override returns (bool) { + return true; + } + + function proveL1ToL2TransactionStatus( + uint256, + bytes32, + uint256, + uint256, + uint16, + bytes32[] calldata, + TxStatus + ) external pure override returns (bool) { + return true; + } + + function requestL2TransactionDirect( + L2TransactionRequestDirect calldata txRequest + ) external payable override returns (bytes32) { + emit SentMessage(msg.sender, txRequest.l2Calldata); + return keccak256(abi.encodePacked("L2TransactionDirect")); + } + + function requestL2TransactionTwoBridges( + L2TransactionRequestTwoBridgesOuter calldata + ) external payable override returns (bytes32) { + return keccak256(abi.encodePacked("L2TransactionTwoBridges")); + } + + function l2TransactionBaseCost(uint256, uint256, uint256, uint256) external pure override returns (uint256) { + return 0; + } + + /// Registry + function createNewChain( + uint256 _chainId, + address _stateTransitionManager, + address _baseToken, + uint256, + address, + bytes calldata + ) external override returns (uint256 chainId) { + hyperchains[_chainId] = _stateTransitionManager; + baseTokens[_chainId] = _baseToken; + emit NewChain(_chainId, _stateTransitionManager, address(this)); + return _chainId; + } + + function addStateTransitionManager(address _stateTransitionManager) external override { + registeredStateTransitionManagers[_stateTransitionManager] = true; + } + + function removeStateTransitionManager(address _stateTransitionManager) external override { + registeredStateTransitionManagers[_stateTransitionManager] = false; + } + + function addToken(address _token) external override { + registeredTokens[_token] = true; + } + + function setSharedBridge(address _sharedBridgeAddr) external override { + sharedBridgeAddr = _sharedBridgeAddr; + } +} diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/L2EPTest.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/L2EPTest.t.sol index 561e32be1a..93640f4bcb 100644 --- a/contracts/src/v0.8/l2ep/test/v1_0_0/L2EPTest.t.sol +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/L2EPTest.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.19; +pragma solidity ^0.8.24; import {Greeter} from "../../../tests/Greeter.sol"; @@ -13,21 +13,6 @@ contract L2EPTest is Test { address internal s_eoaValidator = vm.addr(0x3); address internal s_deployerAddr = vm.addr(0x4); - /// @param expectedGasUsage - the expected gas usage - /// @param startGasUsage - the gas usage before the code of interest is run - /// @param finalGasUsage - the gas usage after the code of interest is run - /// @param deviation - the amount of gas that the actual usage is allowed to deviate by (e.g. (expectedGas - deviation) <= actualGasUsage <= (expectedGas + deviation)) - function assertGasUsageIsCloseTo( - uint256 expectedGasUsage, - uint256 startGasUsage, - uint256 finalGasUsage, - uint256 deviation - ) public { - uint256 gasUsed = (startGasUsage - finalGasUsage) * tx.gasprice; - assertLe(gasUsed, expectedGasUsage + deviation); - assertGe(gasUsed, expectedGasUsage - deviation); - } - /// @param selector - the function selector /// @param greeterAddr - the address of the Greeter contract /// @param message - the new greeting message, which will be passed as an argument to Greeter#setGreeting diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumCrossDomainForwarder.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumCrossDomainForwarder.t.sol index be3851c5b5..cff9b953e2 100644 --- a/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumCrossDomainForwarder.t.sol +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumCrossDomainForwarder.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.19; +pragma solidity 0.8.24; import {ArbitrumCrossDomainForwarder} from "../../../dev/arbitrum/ArbitrumCrossDomainForwarder.sol"; import {Greeter} from "../../../../tests/Greeter.sol"; diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumCrossDomainGovernor.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumCrossDomainGovernor.t.sol index c5b8adaf7d..610f49f16c 100644 --- a/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumCrossDomainGovernor.t.sol +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumCrossDomainGovernor.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.19; +pragma solidity 0.8.24; import {ArbitrumCrossDomainGovernor} from "../../../dev/arbitrum/ArbitrumCrossDomainGovernor.sol"; import {Greeter} from "../../../../tests/Greeter.sol"; diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumSequencerUptimeFeed.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumSequencerUptimeFeed.t.sol index 5565409709..e308ead343 100644 --- a/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumSequencerUptimeFeed.t.sol +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumSequencerUptimeFeed.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.19; +pragma solidity 0.8.24; import {SimpleWriteAccessController} from "../../../../shared/access/SimpleWriteAccessController.sol"; import {ArbitrumSequencerUptimeFeed} from "../../../dev/arbitrum/ArbitrumSequencerUptimeFeed.sol"; diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumValidator.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumValidator.t.sol index 504635540c..ab87299174 100644 --- a/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumValidator.t.sol +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumValidator.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.19; +pragma solidity 0.8.24; import {AccessControllerInterface} from "../../../../shared/interfaces/AccessControllerInterface.sol"; diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismCrossDomainForwarder.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismCrossDomainForwarder.t.sol index d5c482dce9..c0e82ab8d5 100644 --- a/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismCrossDomainForwarder.t.sol +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismCrossDomainForwarder.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.19; +pragma solidity 0.8.24; import {OptimismCrossDomainForwarder} from "../../../dev/optimism/OptimismCrossDomainForwarder.sol"; import {MockOVMCrossDomainMessenger} from "../../mocks/optimism/MockOVMCrossDomainMessenger.sol"; diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismCrossDomainGovernor.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismCrossDomainGovernor.t.sol index e1a5aef95a..8f8fb9d7e7 100644 --- a/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismCrossDomainGovernor.t.sol +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismCrossDomainGovernor.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.19; +pragma solidity 0.8.24; import {OptimismCrossDomainGovernor} from "../../../dev/optimism/OptimismCrossDomainGovernor.sol"; import {MockOVMCrossDomainMessenger} from "../../mocks/optimism/MockOVMCrossDomainMessenger.sol"; diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismSequencerUptimeFeed.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismSequencerUptimeFeed.t.sol index 071d6e5b42..eec9657ac1 100644 --- a/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismSequencerUptimeFeed.t.sol +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismSequencerUptimeFeed.t.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.19; +pragma solidity 0.8.24; import {MockOptimismL1CrossDomainMessenger} from "../../../../tests/MockOptimismL1CrossDomainMessenger.sol"; import {MockOptimismL2CrossDomainMessenger} from "../../../../tests/MockOptimismL2CrossDomainMessenger.sol"; import {OptimismSequencerUptimeFeed} from "../../../dev/optimism/OptimismSequencerUptimeFeed.sol"; +import {BaseSequencerUptimeFeed} from "../../../dev/shared/BaseSequencerUptimeFeed.sol"; import {FeedConsumer} from "../../../../tests/FeedConsumer.sol"; import {L2EPTest} from "../L2EPTest.t.sol"; @@ -61,7 +62,7 @@ contract OptimismSequencerUptimeFeed_UpdateStatus is OptimismSequencerUptimeFeed vm.startPrank(s_strangerAddr, s_strangerAddr); // Tries to update the status from an unauthorized account - vm.expectRevert(OptimismSequencerUptimeFeed.InvalidSender.selector); + vm.expectRevert(BaseSequencerUptimeFeed.InvalidSender.selector); s_optimismSequencerUptimeFeed.updateStatus(true, uint64(1)); } @@ -74,7 +75,7 @@ contract OptimismSequencerUptimeFeed_UpdateStatus is OptimismSequencerUptimeFeed s_mockOptimismL2CrossDomainMessenger.setSender(s_strangerAddr); // Tries to update the status from an unauthorized account - vm.expectRevert(OptimismSequencerUptimeFeed.InvalidSender.selector); + vm.expectRevert(BaseSequencerUptimeFeed.InvalidSender.selector); s_optimismSequencerUptimeFeed.updateStatus(true, uint64(1)); } @@ -257,7 +258,7 @@ contract OptimismSequencerUptimeFeed_AggregatorV3Interface is OptimismSequencerU vm.startPrank(s_l1OwnerAddr, s_l1OwnerAddr); // Gets data from a round that has not happened yet - vm.expectRevert(OptimismSequencerUptimeFeed.NoDataPresent.selector); + vm.expectRevert(BaseSequencerUptimeFeed.NoDataPresent.selector); s_optimismSequencerUptimeFeed.getRoundData(2); } @@ -267,7 +268,7 @@ contract OptimismSequencerUptimeFeed_AggregatorV3Interface is OptimismSequencerU vm.startPrank(s_l1OwnerAddr, s_l1OwnerAddr); // Gets data from a round that has not happened yet - vm.expectRevert(OptimismSequencerUptimeFeed.NoDataPresent.selector); + vm.expectRevert(BaseSequencerUptimeFeed.NoDataPresent.selector); s_optimismSequencerUptimeFeed.getAnswer(2); } @@ -277,7 +278,7 @@ contract OptimismSequencerUptimeFeed_AggregatorV3Interface is OptimismSequencerU vm.startPrank(s_l1OwnerAddr, s_l1OwnerAddr); // Gets data from a round that has not happened yet - vm.expectRevert(OptimismSequencerUptimeFeed.NoDataPresent.selector); + vm.expectRevert(BaseSequencerUptimeFeed.NoDataPresent.selector); s_optimismSequencerUptimeFeed.getTimestamp(2); } } diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismValidator.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismValidator.t.sol index 9364396817..59395bf5d8 100644 --- a/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismValidator.t.sol +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismValidator.t.sol @@ -1,5 +1,7 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.19; +pragma solidity 0.8.24; + +import {ISequencerUptimeFeed} from "../../../dev/interfaces/ISequencerUptimeFeed.sol"; import {MockOptimismL1CrossDomainMessenger} from "../../../../tests/MockOptimismL1CrossDomainMessenger.sol"; import {MockOptimismL2CrossDomainMessenger} from "../../../../tests/MockOptimismL2CrossDomainMessenger.sol"; @@ -73,7 +75,7 @@ contract OptimismValidator_Validate is OptimismValidatorTest { emit SentMessage( L2_SEQ_STATUS_RECORDER_ADDRESS, // target address(s_optimismValidator), // sender - abi.encodeWithSelector(OptimismSequencerUptimeFeed.updateStatus.selector, false, futureTimestampInSeconds), // message + abi.encodeWithSelector(ISequencerUptimeFeed.updateStatus.selector, false, futureTimestampInSeconds), // message 0, // nonce INIT_GAS_LIMIT // gas limit ); @@ -97,7 +99,7 @@ contract OptimismValidator_Validate is OptimismValidatorTest { emit SentMessage( L2_SEQ_STATUS_RECORDER_ADDRESS, // target address(s_optimismValidator), // sender - abi.encodeWithSelector(OptimismSequencerUptimeFeed.updateStatus.selector, true, futureTimestampInSeconds), // message + abi.encodeWithSelector(ISequencerUptimeFeed.updateStatus.selector, true, futureTimestampInSeconds), // message 0, // nonce INIT_GAS_LIMIT // gas limit ); diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollCrossDomainForwarder.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollCrossDomainForwarder.t.sol index f921fa9242..e34e84f400 100644 --- a/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollCrossDomainForwarder.t.sol +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollCrossDomainForwarder.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.19; +pragma solidity 0.8.24; import {MockScrollCrossDomainMessenger} from "../../mocks/scroll/MockScrollCrossDomainMessenger.sol"; import {ScrollCrossDomainForwarder} from "../../../dev/scroll/ScrollCrossDomainForwarder.sol"; diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollCrossDomainGovernor.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollCrossDomainGovernor.t.sol index 9c44460494..8c3d56d156 100644 --- a/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollCrossDomainGovernor.t.sol +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollCrossDomainGovernor.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.19; +pragma solidity 0.8.24; import {MockScrollCrossDomainMessenger} from "../../mocks/scroll/MockScrollCrossDomainMessenger.sol"; import {ScrollCrossDomainGovernor} from "../../../dev/scroll/ScrollCrossDomainGovernor.sol"; diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollSequencerUptimeFeed.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollSequencerUptimeFeed.t.sol index 3aac50e7c1..0968c69415 100644 --- a/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollSequencerUptimeFeed.t.sol +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollSequencerUptimeFeed.t.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.19; +pragma solidity 0.8.24; import {MockScrollL1CrossDomainMessenger} from "../../mocks/scroll/MockScrollL1CrossDomainMessenger.sol"; import {MockScrollL2CrossDomainMessenger} from "../../mocks/scroll/MockScrollL2CrossDomainMessenger.sol"; import {ScrollSequencerUptimeFeed} from "../../../dev/scroll/ScrollSequencerUptimeFeed.sol"; +import {BaseSequencerUptimeFeed} from "../../../dev/shared/BaseSequencerUptimeFeed.sol"; import {FeedConsumer} from "../../../../tests/FeedConsumer.sol"; import {L2EPTest} from "../L2EPTest.t.sol"; @@ -65,7 +66,7 @@ contract ScrollSequencerUptimeFeed_UpdateStatus is ScrollSequencerUptimeFeedTest vm.startPrank(s_strangerAddr, s_strangerAddr); // Tries to update the status from an unauthorized account - vm.expectRevert(ScrollSequencerUptimeFeed.InvalidSender.selector); + vm.expectRevert(BaseSequencerUptimeFeed.InvalidSender.selector); s_scrollSequencerUptimeFeed.updateStatus(true, uint64(1)); } @@ -78,7 +79,7 @@ contract ScrollSequencerUptimeFeed_UpdateStatus is ScrollSequencerUptimeFeedTest s_mockScrollL2CrossDomainMessenger.setSender(s_strangerAddr); // Tries to update the status from an unauthorized account - vm.expectRevert(ScrollSequencerUptimeFeed.InvalidSender.selector); + vm.expectRevert(BaseSequencerUptimeFeed.InvalidSender.selector); s_scrollSequencerUptimeFeed.updateStatus(true, uint64(1)); } @@ -261,7 +262,7 @@ contract ScrollSequencerUptimeFeed_AggregatorV3Interface is ScrollSequencerUptim vm.startPrank(s_l1OwnerAddr, s_l1OwnerAddr); // Gets data from a round that has not happened yet - vm.expectRevert(ScrollSequencerUptimeFeed.NoDataPresent.selector); + vm.expectRevert(BaseSequencerUptimeFeed.NoDataPresent.selector); s_scrollSequencerUptimeFeed.getRoundData(2); } @@ -271,7 +272,7 @@ contract ScrollSequencerUptimeFeed_AggregatorV3Interface is ScrollSequencerUptim vm.startPrank(s_l1OwnerAddr, s_l1OwnerAddr); // Gets data from a round that has not happened yet - vm.expectRevert(ScrollSequencerUptimeFeed.NoDataPresent.selector); + vm.expectRevert(BaseSequencerUptimeFeed.NoDataPresent.selector); s_scrollSequencerUptimeFeed.getAnswer(2); } @@ -281,7 +282,7 @@ contract ScrollSequencerUptimeFeed_AggregatorV3Interface is ScrollSequencerUptim vm.startPrank(s_l1OwnerAddr, s_l1OwnerAddr); // Gets data from a round that has not happened yet - vm.expectRevert(ScrollSequencerUptimeFeed.NoDataPresent.selector); + vm.expectRevert(BaseSequencerUptimeFeed.NoDataPresent.selector); s_scrollSequencerUptimeFeed.getTimestamp(2); } } diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollValidator.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollValidator.t.sol index f425ca1c6e..3d5298d518 100644 --- a/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollValidator.t.sol +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollValidator.t.sol @@ -1,5 +1,7 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.19; +pragma solidity 0.8.24; + +import {ISequencerUptimeFeed} from "../../../dev/interfaces/ISequencerUptimeFeed.sol"; import {MockScrollL1CrossDomainMessenger} from "../../mocks/scroll/MockScrollL1CrossDomainMessenger.sol"; import {MockScrollL2CrossDomainMessenger} from "../../mocks/scroll/MockScrollL2CrossDomainMessenger.sol"; @@ -87,7 +89,7 @@ contract ScrollValidator_Validate is ScrollValidatorTest { 0, // value 0, // nonce INIT_GAS_LIMIT, // gas limit - abi.encodeWithSelector(ScrollSequencerUptimeFeed.updateStatus.selector, false, futureTimestampInSeconds) // message + abi.encodeWithSelector(ISequencerUptimeFeed.updateStatus.selector, false, futureTimestampInSeconds) // message ); // Runs the function (which produces the event to test) @@ -112,7 +114,7 @@ contract ScrollValidator_Validate is ScrollValidatorTest { 0, // value 0, // nonce INIT_GAS_LIMIT, // gas limit - abi.encodeWithSelector(ScrollSequencerUptimeFeed.updateStatus.selector, true, futureTimestampInSeconds) // message + abi.encodeWithSelector(ISequencerUptimeFeed.updateStatus.selector, true, futureTimestampInSeconds) // message ); // Runs the function (which produces the event to test) diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/zksync/ZKSyncSequencerUptimeFeed.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/zksync/ZKSyncSequencerUptimeFeed.t.sol new file mode 100644 index 0000000000..6d90b3973e --- /dev/null +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/zksync/ZKSyncSequencerUptimeFeed.t.sol @@ -0,0 +1,291 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {AddressAliasHelper} from "../../../../vendor/arb-bridge-eth/v0.8.0-custom/contracts/libraries/AddressAliasHelper.sol"; +import {ZKSyncSequencerUptimeFeed} from "../../../dev/zksync/ZKSyncSequencerUptimeFeed.sol"; +import {BaseSequencerUptimeFeed} from "../../../dev/shared/BaseSequencerUptimeFeed.sol"; +import {FeedConsumer} from "../../../../tests/FeedConsumer.sol"; +import {L2EPTest} from "../L2EPTest.t.sol"; + +contract ZKSyncSequencerUptimeFeedTest is L2EPTest { + /// Helper Variables + address internal s_aliasedL1OwnerAddress = AddressAliasHelper.applyL1ToL2Alias(s_l1OwnerAddr); + + /// L2EP contracts + ZKSyncSequencerUptimeFeed internal s_zksyncSequencerUptimeFeed; + + /// Events + event UpdateIgnored(bool latestStatus, uint64 latestTimestamp, bool incomingStatus, uint64 incomingTimestamp); + event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt); + event RoundUpdated(int256 status, uint64 updatedAt); + + /// Setup + function setUp() public { + // Deploys contracts + s_zksyncSequencerUptimeFeed = new ZKSyncSequencerUptimeFeed(s_l1OwnerAddr, false); + } +} + +contract ZKSyncSequencerUptimeFeed_Constructor is ZKSyncSequencerUptimeFeedTest { + /// @notice it should have been deployed with the correct initial state + function test_InitialState() public { + // Sets msg.sender and tx.origin to a valid address + vm.startPrank(s_l1OwnerAddr, s_l1OwnerAddr); + + // Checks L1 sender + address actualL1Addr = s_zksyncSequencerUptimeFeed.l1Sender(); + assertEq(actualL1Addr, s_l1OwnerAddr); + + // Checks latest round data + (uint80 roundId, int256 answer, , , ) = s_zksyncSequencerUptimeFeed.latestRoundData(); + assertEq(roundId, 1); + assertEq(answer, 0); + } +} + +contract ZKSyncSequencerUptimeFeed_UpdateStatus is ZKSyncSequencerUptimeFeedTest { + /// @notice it should revert if called by an unauthorized account + function test_RevertIfNotL2CrossDomainMessengerAddr() public { + // Sets msg.sender and tx.origin to an unauthorized address + vm.startPrank(s_strangerAddr, s_strangerAddr); + + // Tries to update the status from an unauthorized account + vm.expectRevert(BaseSequencerUptimeFeed.InvalidSender.selector); + s_zksyncSequencerUptimeFeed.updateStatus(true, uint64(1)); + } + + /// @notice it should update status when status has not changed and incoming timestamp is the same as latest + function test_UpdateStatusWhenNoChange() public { + // Sets msg.sender and tx.origin to a valid address + vm.startPrank(s_aliasedL1OwnerAddress, s_aliasedL1OwnerAddress); + + // Fetches the latest timestamp + uint256 timestamp = s_zksyncSequencerUptimeFeed.latestTimestamp(); + + // Submits a status update + vm.expectEmit(); + emit AnswerUpdated(1, 2, timestamp); + s_zksyncSequencerUptimeFeed.updateStatus(true, uint64(timestamp)); + assertEq(s_zksyncSequencerUptimeFeed.latestAnswer(), 1); + assertEq(s_zksyncSequencerUptimeFeed.latestTimestamp(), uint64(timestamp)); + + // Stores the current round data before updating it + ( + uint80 roundIdBeforeUpdate, + int256 answerBeforeUpdate, + uint256 startedAtBeforeUpdate, + , + uint80 answeredInRoundBeforeUpdate + ) = s_zksyncSequencerUptimeFeed.latestRoundData(); + + // Submit another status update with the same status + vm.expectEmit(); + emit RoundUpdated(1, uint64(block.timestamp)); + s_zksyncSequencerUptimeFeed.updateStatus(true, uint64(timestamp + 200)); + assertEq(s_zksyncSequencerUptimeFeed.latestAnswer(), 1); + assertEq(s_zksyncSequencerUptimeFeed.latestTimestamp(), uint64(timestamp)); + + // Stores the current round data after updating it + ( + uint80 roundIdAfterUpdate, + int256 answerAfterUpdate, + uint256 startedAtAfterUpdate, + uint256 updatedAtAfterUpdate, + uint80 answeredInRoundAfterUpdate + ) = s_zksyncSequencerUptimeFeed.latestRoundData(); + + // Verifies the latest round data has been properly updated + assertEq(roundIdAfterUpdate, roundIdBeforeUpdate); + assertEq(answerAfterUpdate, answerBeforeUpdate); + assertEq(startedAtAfterUpdate, startedAtBeforeUpdate); + assertEq(answeredInRoundAfterUpdate, answeredInRoundBeforeUpdate); + assertEq(updatedAtAfterUpdate, block.timestamp); + } + + /// @notice it should update status when status has changed and incoming timestamp is newer than the latest + function test_UpdateStatusWhenStatusChangeAndTimeChange() public { + // Sets msg.sender and tx.origin to a valid address + vm.startPrank(s_aliasedL1OwnerAddress, s_aliasedL1OwnerAddress); + + // Submits a status update + uint256 timestamp = s_zksyncSequencerUptimeFeed.latestTimestamp(); + vm.expectEmit(); + emit AnswerUpdated(1, 2, timestamp); + s_zksyncSequencerUptimeFeed.updateStatus(true, uint64(timestamp)); + assertEq(s_zksyncSequencerUptimeFeed.latestAnswer(), 1); + assertEq(s_zksyncSequencerUptimeFeed.latestTimestamp(), uint64(timestamp)); + + // Submit another status update, different status, newer timestamp should update + timestamp = timestamp + 200; + vm.expectEmit(); + emit AnswerUpdated(0, 3, timestamp); + s_zksyncSequencerUptimeFeed.updateStatus(false, uint64(timestamp)); + assertEq(s_zksyncSequencerUptimeFeed.latestAnswer(), 0); + assertEq(s_zksyncSequencerUptimeFeed.latestTimestamp(), uint64(timestamp)); + } + + /// @notice it should update status when status has changed and incoming timestamp is the same as latest + function test_UpdateStatusWhenStatusChangeAndNoTimeChange() public { + // Sets msg.sender and tx.origin to a valid address + vm.startPrank(s_aliasedL1OwnerAddress, s_aliasedL1OwnerAddress); + + // Fetches the latest timestamp + uint256 timestamp = s_zksyncSequencerUptimeFeed.latestTimestamp(); + + // Submits a status update + vm.expectEmit(); + emit AnswerUpdated(1, 2, timestamp); + s_zksyncSequencerUptimeFeed.updateStatus(true, uint64(timestamp)); + assertEq(s_zksyncSequencerUptimeFeed.latestAnswer(), 1); + assertEq(s_zksyncSequencerUptimeFeed.latestTimestamp(), uint64(timestamp)); + + // Submit another status update, different status, same timestamp should update + vm.expectEmit(); + emit AnswerUpdated(0, 3, timestamp); + s_zksyncSequencerUptimeFeed.updateStatus(false, uint64(timestamp)); + assertEq(s_zksyncSequencerUptimeFeed.latestAnswer(), 0); + assertEq(s_zksyncSequencerUptimeFeed.latestTimestamp(), uint64(timestamp)); + } + + /// @notice it should ignore out-of-order updates + function test_IgnoreOutOfOrderUpdates() public { + // Sets msg.sender and tx.origin to a valid address + vm.startPrank(s_aliasedL1OwnerAddress, s_aliasedL1OwnerAddress); + + // Submits a status update + uint256 timestamp = s_zksyncSequencerUptimeFeed.latestTimestamp() + 10000; + vm.expectEmit(); + emit AnswerUpdated(1, 2, timestamp); + s_zksyncSequencerUptimeFeed.updateStatus(true, uint64(timestamp)); + assertEq(s_zksyncSequencerUptimeFeed.latestAnswer(), 1); + assertEq(s_zksyncSequencerUptimeFeed.latestTimestamp(), uint64(timestamp)); + + // Update with different status, but stale timestamp, should be ignored + timestamp = timestamp - 1000; + vm.expectEmit(false, false, false, false); + emit UpdateIgnored(true, 0, true, 0); // arguments are dummy values + // TODO: how can we check that an AnswerUpdated event was NOT emitted + s_zksyncSequencerUptimeFeed.updateStatus(false, uint64(timestamp)); + } +} + +contract ZKSyncSequencerUptimeFeed_AggregatorV3Interface is ZKSyncSequencerUptimeFeedTest { + /// @notice it should return valid answer from getRoundData and latestRoundData + function test_AggregatorV3Interface() public { + // Sets msg.sender and tx.origin to a valid address + vm.startPrank(s_aliasedL1OwnerAddress, s_aliasedL1OwnerAddress); + + // Defines helper variables + uint80 roundId; + int256 answer; + uint256 startedAt; + uint256 updatedAt; + uint80 answeredInRound; + + // Checks initial state + (roundId, answer, startedAt, updatedAt, answeredInRound) = s_zksyncSequencerUptimeFeed.latestRoundData(); + assertEq(roundId, 1); + assertEq(answer, 0); + assertEq(answeredInRound, roundId); + assertEq(startedAt, updatedAt); + + // Submits status update with different status and newer timestamp, should update + uint256 timestamp = startedAt + 1000; + s_zksyncSequencerUptimeFeed.updateStatus(true, uint64(timestamp)); + (roundId, answer, startedAt, updatedAt, answeredInRound) = s_zksyncSequencerUptimeFeed.getRoundData(2); + assertEq(roundId, 2); + assertEq(answer, 1); + assertEq(answeredInRound, roundId); + assertEq(startedAt, timestamp); + assertLe(updatedAt, startedAt); + + // Saves round 2 data + uint80 roundId2 = roundId; + int256 answer2 = answer; + uint256 startedAt2 = startedAt; + uint256 updatedAt2 = updatedAt; + uint80 answeredInRound2 = answeredInRound; + + // Checks that last round is still returning the correct data + (roundId, answer, startedAt, updatedAt, answeredInRound) = s_zksyncSequencerUptimeFeed.getRoundData(1); + assertEq(roundId, 1); + assertEq(answer, 0); + assertEq(answeredInRound, roundId); + assertEq(startedAt, updatedAt); + + // Assert latestRoundData corresponds to latest round id + (roundId, answer, startedAt, updatedAt, answeredInRound) = s_zksyncSequencerUptimeFeed.latestRoundData(); + assertEq(roundId2, roundId); + assertEq(answer2, answer); + assertEq(startedAt2, startedAt); + assertEq(updatedAt2, updatedAt); + assertEq(answeredInRound2, answeredInRound); + } + + /// @notice it should revert from #getRoundData when round does not yet exist (future roundId) + function test_RevertGetRoundDataWhenRoundDoesNotExistYet() public { + // Sets msg.sender and tx.origin to a valid address + vm.startPrank(s_l1OwnerAddr, s_l1OwnerAddr); + + // Gets data from a round that has not happened yet + vm.expectRevert(BaseSequencerUptimeFeed.NoDataPresent.selector); + s_zksyncSequencerUptimeFeed.getRoundData(2); + } + + /// @notice it should revert from #getAnswer when round does not yet exist (future roundId) + function test_RevertGetAnswerWhenRoundDoesNotExistYet() public { + // Sets msg.sender and tx.origin to a valid address + vm.startPrank(s_l1OwnerAddr, s_l1OwnerAddr); + + // Gets data from a round that has not happened yet + vm.expectRevert(BaseSequencerUptimeFeed.NoDataPresent.selector); + s_zksyncSequencerUptimeFeed.getAnswer(2); + } + + /// @notice it should revert from #getTimestamp when round does not yet exist (future roundId) + function test_RevertGetTimestampWhenRoundDoesNotExistYet() public { + // Sets msg.sender and tx.origin to a valid address + vm.startPrank(s_l1OwnerAddr, s_l1OwnerAddr); + + // Gets data from a round that has not happened yet + vm.expectRevert(BaseSequencerUptimeFeed.NoDataPresent.selector); + s_zksyncSequencerUptimeFeed.getTimestamp(2); + } +} + +contract ZKSyncSequencerUptimeFeed_ProtectReadsOnAggregatorV2V3InterfaceFunctions is ZKSyncSequencerUptimeFeedTest { + /// @notice it should disallow reads on AggregatorV2V3Interface functions when consuming contract is not whitelisted + function test_AggregatorV2V3InterfaceDisallowReadsIfConsumingContractIsNotWhitelisted() public { + // Deploys a FeedConsumer contract + FeedConsumer feedConsumer = new FeedConsumer(address(s_zksyncSequencerUptimeFeed)); + + // Sanity - consumer is not whitelisted + assertEq(s_zksyncSequencerUptimeFeed.checkEnabled(), true); + assertEq(s_zksyncSequencerUptimeFeed.hasAccess(address(feedConsumer), abi.encode("")), false); + + // Asserts reads are not possible from consuming contract + vm.expectRevert("No access"); + feedConsumer.latestAnswer(); + vm.expectRevert("No access"); + feedConsumer.latestRoundData(); + } + + /// @notice it should allow reads on AggregatorV2V3Interface functions when consuming contract is whitelisted + function test_AggregatorV2V3InterfaceAllowReadsIfConsumingContractIsWhitelisted() public { + // Deploys a FeedConsumer contract + FeedConsumer feedConsumer = new FeedConsumer(address(s_zksyncSequencerUptimeFeed)); + + // Whitelist consumer + s_zksyncSequencerUptimeFeed.addAccess(address(feedConsumer)); + + // Sanity - consumer is whitelisted + assertEq(s_zksyncSequencerUptimeFeed.checkEnabled(), true); + assertEq(s_zksyncSequencerUptimeFeed.hasAccess(address(feedConsumer), abi.encode("")), true); + + // Asserts reads are possible from consuming contract + (uint80 roundId, int256 answer, , , ) = feedConsumer.latestRoundData(); + assertEq(feedConsumer.latestAnswer(), 0); + assertEq(roundId, 1); + assertEq(answer, 0); + } +} diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/zksync/ZKSyncValidator.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/zksync/ZKSyncValidator.t.sol new file mode 100644 index 0000000000..0bea147c8c --- /dev/null +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/zksync/ZKSyncValidator.t.sol @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {MockBridgehub} from "../../mocks/zksync/MockZKSyncL1Bridge.sol"; +import {ISequencerUptimeFeed} from "../../../dev/interfaces/ISequencerUptimeFeed.sol"; +import {ZKSyncValidator} from "../../../dev/zksync/ZKSyncValidator.sol"; +import {BaseValidator} from "../../../dev/shared/BaseValidator.sol"; +import {L2EPTest} from "../L2EPTest.t.sol"; + +contract ZKSyncValidatorTest is L2EPTest { + address internal constant L2_SEQ_STATUS_RECORDER_ADDRESS = address(0x491B1dDA0A8fa069bbC1125133A975BF4e85a91b); + address internal constant DUMMY_L1_XDOMAIN_MSNGR_ADDR = address(0xa04Fc18f012B1a5A8231c7Ee4b916Dd6dbd271b6); + address internal constant DUMMY_L2_UPTIME_FEED_ADDR = address(0xFe31891940A2e5f04B76eD8bD1038E44127d1512); + uint32 internal constant INIT_GAS_PER_PUBDATA_BYTE_LIMIT = 800; + uint32 internal constant INIT_GAS_LIMIT = 1900000; + uint32 internal constant MAIN_NET_CHAIN_ID = 300; + uint32 internal constant BAD_CHAIN_ID = 0; + + ISequencerUptimeFeed internal s_zksyncSequencerUptimeFeed; + MockBridgehub internal s_mockZKSyncL1Bridge; + ZKSyncValidator internal s_zksyncValidator; + + /// Fake event that will get emitted when `requestL2TransactionDirect` is called + /// Definition is taken from MockZKSyncL1Bridge + event SentMessage(address indexed sender, bytes message); + + /// Setup + function setUp() public { + s_mockZKSyncL1Bridge = new MockBridgehub(); + + s_zksyncValidator = new ZKSyncValidator( + address(s_mockZKSyncL1Bridge), + DUMMY_L2_UPTIME_FEED_ADDR, + INIT_GAS_LIMIT, + MAIN_NET_CHAIN_ID, + INIT_GAS_PER_PUBDATA_BYTE_LIMIT + ); + } +} + +contract ZKSyncValidator_Constructor is ZKSyncValidatorTest { + /// @notice it correctly validates that the chain id is valid + function test_ConstructingRevertedWithInvalidChainId() public { + vm.expectRevert(ZKSyncValidator.InvalidChainID.selector); + new ZKSyncValidator( + DUMMY_L1_XDOMAIN_MSNGR_ADDR, + DUMMY_L2_UPTIME_FEED_ADDR, + INIT_GAS_LIMIT, + BAD_CHAIN_ID, + INIT_GAS_PER_PUBDATA_BYTE_LIMIT + ); + } + + /// @notice it correctly validates that the L1 bridge address is not zero + function test_ConstructingRevertedWithZeroL1BridgeAddress() public { + vm.expectRevert(BaseValidator.L1CrossDomainMessengerAddressZero.selector); + new ZKSyncValidator( + address(0), + DUMMY_L2_UPTIME_FEED_ADDR, + INIT_GAS_LIMIT, + MAIN_NET_CHAIN_ID, + INIT_GAS_PER_PUBDATA_BYTE_LIMIT + ); + } + + /// @notice it correctly validates that the L2 Uptime feed address is not zero + function test_ConstructingRevertedWithZeroL2UpdateFeedAddress() public { + vm.expectRevert(BaseValidator.L2UptimeFeedAddrZero.selector); + new ZKSyncValidator( + DUMMY_L1_XDOMAIN_MSNGR_ADDR, + address(0), + INIT_GAS_LIMIT, + MAIN_NET_CHAIN_ID, + INIT_GAS_PER_PUBDATA_BYTE_LIMIT + ); + } +} + +contract ZKSyncValidator_GetSetL2GasPerPubdataByteLimit is ZKSyncValidatorTest { + /// @notice it correctly updates the gas limit per pubdata byte + function test_CorrectlyGetsAndUpdatesTheGasPerPubdataByteLimit() public { + assertEq(s_zksyncValidator.getL2GasPerPubdataByteLimit(), INIT_GAS_PER_PUBDATA_BYTE_LIMIT); + + uint32 newGasPerPubDataByteLimit = 2000000; + s_zksyncValidator.setL2GasPerPubdataByteLimit(newGasPerPubDataByteLimit); + assertEq(s_zksyncValidator.getL2GasPerPubdataByteLimit(), newGasPerPubDataByteLimit); + } +} + +contract ZKSyncValidator_GetChainId is ZKSyncValidatorTest { + /// @notice it correctly gets the chain id + function test_CorrectlyGetsTheChainId() public { + assertEq(s_zksyncValidator.getChainId(), MAIN_NET_CHAIN_ID); + } +} + +contract ZKSyncValidator_Validate is ZKSyncValidatorTest { + /// @notice it reverts if called by account with no access + function test_RevertsIfCalledByAnAccountWithNoAccess() public { + vm.startPrank(s_strangerAddr); + vm.expectRevert("No access"); + s_zksyncValidator.validate(0, 0, 1, 1); + } + + /// @notice it posts sequencer status when there is not status change + function test_PostSequencerStatusWhenThereIsNotStatusChange() public { + // Gives access to the s_eoaValidator + s_zksyncValidator.addAccess(s_eoaValidator); + + // Sets block.timestamp to a later date + uint256 futureTimestampInSeconds = block.timestamp + 5000; + vm.startPrank(s_eoaValidator); + vm.warp(futureTimestampInSeconds); + + // Sets up the expected event data + bytes memory message = abi.encodeWithSelector( + ISequencerUptimeFeed.updateStatus.selector, + false, + futureTimestampInSeconds + ); + + vm.expectEmit(false, false, false, true); + emit SentMessage(address(s_zksyncValidator), message); + + // Runs the function (which produces the event to test) + s_zksyncValidator.validate(0, 0, 0, 0); + } + + /// @notice it post sequencer offline + function test_PostSequencerOffline() public { + // Gives access to the s_eoaValidator + s_zksyncValidator.addAccess(s_eoaValidator); + + // Sets block.timestamp to a later date + uint256 futureTimestampInSeconds = block.timestamp + 10000; + vm.startPrank(s_eoaValidator); + vm.warp(futureTimestampInSeconds); + + // Sets up the expected event data + vm.expectEmit(false, false, false, true); + emit SentMessage( + address(s_zksyncValidator), + abi.encodeWithSelector(ISequencerUptimeFeed.updateStatus.selector, true, futureTimestampInSeconds) + ); + + // Runs the function (which produces the event to test) + s_zksyncValidator.validate(0, 0, 1, 1); + } +} diff --git a/contracts/src/v0.8/llo-feeds/dev/ChannelConfigStore.sol b/contracts/src/v0.8/llo-feeds/dev/ChannelConfigStore.sol deleted file mode 100644 index 6c6a5ec9b3..0000000000 --- a/contracts/src/v0.8/llo-feeds/dev/ChannelConfigStore.sol +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; -import {IChannelConfigStore} from "./interfaces/IChannelConfigStore.sol"; -import {TypeAndVersionInterface} from "../../interfaces/TypeAndVersionInterface.sol"; - -contract ChannelConfigStore is ConfirmedOwner, IChannelConfigStore, TypeAndVersionInterface { - mapping(uint32 => ChannelDefinition) private s_channelDefinitions; - - // mapping(bytes32 => ChannelConfiguration) private s_channelProductionConfigurations; - // mapping(bytes32 => ChannelConfiguration) private s_channelStagingConfigurations; - - event NewChannelDefinition(uint32 channelId, ChannelDefinition channelDefinition); - event ChannelDefinitionRemoved(uint32 channelId); - // event NewProductionConfig(ChannelConfiguration channelConfig); - // event NewStagingConfig(ChannelConfiguration channelConfig); - event PromoteStagingConfig(uint32 channelId); - - error OnlyCallableByEOA(); - error StagingConfigAlreadyPromoted(); - error EmptyStreamIDs(); - error ZeroReportFormat(); - error ZeroChainSelector(); - error ChannelDefinitionNotFound(); - - constructor() ConfirmedOwner(msg.sender) {} - - // function setStagingConfig(bytes32 configDigest, ChannelConfiguration calldata channelConfig) external onlyOwner { - // s_channelStagingConfigurations[channelId] = channelConfig; - - // emit NewStagingConfig(channelConfig); - // } - - //// this will trigger the following: - //// - offchain ShouldRetireCache will start returning true for the old (production) - //// protocol instance - //// - once the old production instance retires it will generate a handover - //// retirement report - //// - the staging instance will become the new production instance once - //// any honest oracle that is on both instances forward the retirement - //// report from the old instance to the new instace via the - //// PredecessorRetirementReportCache - //// - //// Note: the promotion flow only works if the previous production instance - //// is working correctly & generating reports. If that's not the case, the - //// owner is expected to "setProductionConfig" directly instead. This will - //// cause "gaps" to be created, but that seems unavoidable in such a scenario. - // function promoteStagingConfig(bytes32 configDigest) external onlyOwner { - // ChannelConfiguration memory stagingConfig = s_channelStagingConfigurations[channelId]; - - // if(stagingConfig.channelConfigId.length == 0) { - // revert StagingConfigAlreadyPromoted(); - // } - - // s_channelProductionConfigurations[channelId] = s_channelStagingConfigurations[channelId]; - - // emit PromoteStagingConfig(channelId); - // } - - function addChannel(uint32 channelId, ChannelDefinition calldata channelDefinition) external onlyOwner { - if (channelDefinition.streamIDs.length == 0) { - revert EmptyStreamIDs(); - } - - if (channelDefinition.chainSelector == 0) { - revert ZeroChainSelector(); - } - - if (channelDefinition.reportFormat == 0) { - revert ZeroReportFormat(); - } - - s_channelDefinitions[channelId] = channelDefinition; - - emit NewChannelDefinition(channelId, channelDefinition); - } - - function removeChannel(uint32 channelId) external onlyOwner { - if (s_channelDefinitions[channelId].streamIDs.length == 0) { - revert ChannelDefinitionNotFound(); - } - - delete s_channelDefinitions[channelId]; - - emit ChannelDefinitionRemoved(channelId); - } - - function getChannelDefinitions(uint32 channelId) external view returns (ChannelDefinition memory) { - // solhint-disable-next-line avoid-tx-origin - if (msg.sender != tx.origin) { - revert OnlyCallableByEOA(); - } - - return s_channelDefinitions[channelId]; - } - - function typeAndVersion() external pure override returns (string memory) { - return "ChannelConfigStore 0.0.0"; - } - - function supportsInterface(bytes4 interfaceId) external pure returns (bool) { - return interfaceId == type(IChannelConfigStore).interfaceId; - } -} diff --git a/contracts/src/v0.8/llo-feeds/dev/ChannelVerifier.sol b/contracts/src/v0.8/llo-feeds/dev/ChannelVerifier.sol deleted file mode 100644 index 424022dc44..0000000000 --- a/contracts/src/v0.8/llo-feeds/dev/ChannelVerifier.sol +++ /dev/null @@ -1,536 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.19; - -import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; -import {TypeAndVersionInterface} from "../../interfaces/TypeAndVersionInterface.sol"; -import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; -import {Common} from "../libraries/Common.sol"; -import {IChannelVerifier} from "./interfaces/IChannelVerifier.sol"; - -// OCR2 standard -uint256 constant MAX_NUM_ORACLES = 31; - -/* - * The verifier contract is used to verify offchain reports signed - * by DONs. A report consists of a price, block number and feed Id. It - * represents the observed price of an asset at a specified block number for - * a feed. The verifier contract is used to verify that such reports have - * been signed by the correct signers. - **/ -contract ChannelVerifier is IChannelVerifier, ConfirmedOwner, TypeAndVersionInterface { - // The first byte of the mask can be 0, because we only ever have 31 oracles - uint256 internal constant ORACLE_MASK = 0x0001010101010101010101010101010101010101010101010101010101010101; - - enum Role { - // Default role for an oracle address. This means that the oracle address - // is not a signer - Unset, - // Role given to an oracle address that is allowed to sign feed data - Signer - } - - struct Signer { - // Index of oracle in a configuration - uint8 index; - // The oracle's role - Role role; - } - - struct Config { - // Fault tolerance - uint8 f; - // Marks whether or not a configuration is active - bool isActive; - // Map of signer addresses to oracles - mapping(address => Signer) oracles; - } - - struct VerifierState { - // The number of times a new configuration - /// has been set - uint32 configCount; - // The block number of the block the last time - /// the configuration was updated. - uint32 latestConfigBlockNumber; - // The latest epoch a report was verified for - uint32 latestEpoch; - /// The latest config digest set - bytes32 latestConfigDigest; - /// List of deactivated feeds - mapping(bytes32 => bool) deactivatedFeeds; - /// The historical record of all previously set configs by feedId - mapping(bytes32 => Config) s_verificationDataConfigs; - } - - /// @notice This event is emitted when a new report is verified. - /// It is used to keep a historical record of verified reports. - event ReportVerified(bytes32 indexed feedId, address requester); - - /// @notice This event is emitted whenever a new configuration is set. It triggers a new run of the offchain reporting protocol. - event ConfigSet( - uint32 previousConfigBlockNumber, - bytes32 configDigest, - uint64 configCount, - address[] signers, - bytes32[] offchainTransmitters, - uint8 f, - bytes onchainConfig, - uint64 offchainConfigVersion, - bytes offchainConfig - ); - - /// @notice This event is emitted whenever a configuration is deactivated - event ConfigDeactivated(bytes32 configDigest); - - /// @notice This event is emitted whenever a configuration is activated - event ConfigActivated(bytes32 configDigest); - - /// @notice This event is emitted whenever a feed is activated - event FeedActivated(bytes32 indexed feedId); - - /// @notice This event is emitted whenever a feed is deactivated - event FeedDeactivated(bytes32 indexed feedId); - - /// @notice This error is thrown whenever an address tries - /// to exeecute a transaction that it is not authorized to do so - error AccessForbidden(); - - /// @notice This error is thrown whenever a zero address is passed - error ZeroAddress(); - - /// @notice This error is thrown whenever the feed ID passed in - /// a signed report is empty - error FeedIdEmpty(); - - /// @notice This error is thrown whenever the config digest - /// is empty - error DigestEmpty(); - - /// @notice This error is thrown whenever the config digest - /// passed in has not been set in this verifier - /// @param configDigest The config digest that has not been set - error DigestNotSet(bytes32 configDigest); - - /// @notice This error is thrown whenever the config digest - /// has been deactivated - /// @param configDigest The config digest that is inactive - error DigestInactive(bytes32 configDigest); - - /// @notice This error is thrown whenever trying to set a config - /// with a fault tolerance of 0 - error FaultToleranceMustBePositive(); - - /// @notice This error is thrown whenever a report is signed - /// with more than the max number of signers - /// @param numSigners The number of signers who have signed the report - /// @param maxSigners The maximum number of signers that can sign a report - error ExcessSigners(uint256 numSigners, uint256 maxSigners); - - /// @notice This error is thrown whenever a report is signed - /// with less than the minimum number of signers - /// @param numSigners The number of signers who have signed the report - /// @param minSigners The minimum number of signers that need to sign a report - error InsufficientSigners(uint256 numSigners, uint256 minSigners); - - /// @notice This error is thrown whenever a report is signed - /// with an incorrect number of signers - /// @param numSigners The number of signers who have signed the report - /// @param expectedNumSigners The expected number of signers that need to sign - /// a report - error IncorrectSignatureCount(uint256 numSigners, uint256 expectedNumSigners); - - /// @notice This error is thrown whenever the R and S signer components - /// have different lengths - /// @param rsLength The number of r signature components - /// @param ssLength The number of s signature components - error MismatchedSignatures(uint256 rsLength, uint256 ssLength); - - /// @notice This error is thrown whenever setting a config with duplicate signatures - error NonUniqueSignatures(); - - /// @notice This error is thrown whenever a report fails to verify due to bad or duplicate signatures - error BadVerification(); - - /// @notice This error is thrown whenever the admin tries to deactivate - /// the latest config digest - /// @param configDigest The latest config digest - error CannotDeactivateLatestConfig(bytes32 configDigest); - - /// @notice This error is thrown whenever the feed ID passed in is deactivated - /// @param feedId The feed ID - error InactiveFeed(bytes32 feedId); - - /// @notice This error is thrown whenever the feed ID passed in is not found - /// @param feedId The feed ID - error InvalidFeed(bytes32 feedId); - - /// @notice The address of the verifier proxy - address private immutable i_verifierProxyAddr; - - /// @notice Verifier states keyed on Feed ID - VerifierState internal s_feedVerifierState; - - /// @param verifierProxyAddr The address of the VerifierProxy contract - constructor(address verifierProxyAddr) ConfirmedOwner(msg.sender) { - if (verifierProxyAddr == address(0)) revert ZeroAddress(); - i_verifierProxyAddr = verifierProxyAddr; - } - - modifier checkConfigValid(uint256 numSigners, uint256 f) { - if (f == 0) revert FaultToleranceMustBePositive(); - if (numSigners > MAX_NUM_ORACLES) revert ExcessSigners(numSigners, MAX_NUM_ORACLES); - if (numSigners <= 3 * f) revert InsufficientSigners(numSigners, 3 * f + 1); - _; - } - - /// @inheritdoc IERC165 - function supportsInterface(bytes4 interfaceId) external pure override returns (bool isVerifier) { - return interfaceId == this.verify.selector; - } - - /// @inheritdoc TypeAndVersionInterface - function typeAndVersion() external pure override returns (string memory) { - return "ChannelVerifier 0.0.0"; - } - - /// @inheritdoc IChannelVerifier - function verify( - bytes calldata signedReport, - address sender - ) external override returns (bytes memory verifierResponse) { - if (msg.sender != i_verifierProxyAddr) revert AccessForbidden(); - ( - bytes32[3] memory reportContext, - bytes memory reportData, - bytes32[] memory rs, - bytes32[] memory ss, - bytes32 rawVs - ) = abi.decode(signedReport, (bytes32[3], bytes, bytes32[], bytes32[], bytes32)); - - // The feed ID is the first 32 bytes of the report data. - bytes32 feedId = bytes32(reportData); - - // If the feed has been deactivated, do not verify the report - if (s_feedVerifierState.deactivatedFeeds[feedId]) { - revert InactiveFeed(feedId); - } - - // reportContext consists of: - // reportContext[0]: ConfigDigest - // reportContext[1]: 27 byte padding, 4-byte epoch and 1-byte round - // reportContext[2]: ExtraHash - bytes32 configDigest = reportContext[0]; - Config storage s_config = s_feedVerifierState.s_verificationDataConfigs[configDigest]; - - _validateReport(configDigest, rs, ss, s_config); - _updateEpoch(reportContext, s_feedVerifierState); - - bytes32 hashedReport = keccak256(reportData); - - _verifySignatures(hashedReport, reportContext, rs, ss, rawVs, s_config); - emit ReportVerified(feedId, sender); - - return reportData; - } - - /// @notice Validates parameters of the report - /// @param configDigest Config digest from the report - /// @param rs R components from the report - /// @param ss S components from the report - /// @param config Config for the given feed ID keyed on the config digest - function _validateReport( - bytes32 configDigest, - bytes32[] memory rs, - bytes32[] memory ss, - Config storage config - ) private view { - uint8 expectedNumSignatures = config.f + 1; - - if (!config.isActive) revert DigestInactive(configDigest); - if (rs.length != expectedNumSignatures) revert IncorrectSignatureCount(rs.length, expectedNumSignatures); - if (rs.length != ss.length) revert MismatchedSignatures(rs.length, ss.length); - } - - /** - * @notice Conditionally update the epoch for a feed - * @param reportContext Report context containing the epoch and round - * @param feedVerifierState Feed verifier state to conditionally update - */ - function _updateEpoch(bytes32[3] memory reportContext, VerifierState storage feedVerifierState) private { - uint40 epochAndRound = uint40(uint256(reportContext[1])); - uint32 epoch = uint32(epochAndRound >> 8); - if (epoch > feedVerifierState.latestEpoch) { - feedVerifierState.latestEpoch = epoch; - } - } - - /// @notice Verifies that a report has been signed by the correct - /// signers and that enough signers have signed the reports. - /// @param hashedReport The keccak256 hash of the raw report's bytes - /// @param reportContext The context the report was signed in - /// @param rs ith element is the R components of the ith signature on report. Must have at most MAX_NUM_ORACLES entries - /// @param ss ith element is the S components of the ith signature on report. Must have at most MAX_NUM_ORACLES entries - /// @param rawVs ith element is the the V component of the ith signature - /// @param s_config The config digest the report was signed for - function _verifySignatures( - bytes32 hashedReport, - bytes32[3] memory reportContext, - bytes32[] memory rs, - bytes32[] memory ss, - bytes32 rawVs, - Config storage s_config - ) private view { - bytes32 h = keccak256(abi.encodePacked(hashedReport, reportContext)); - // i-th byte counts number of sigs made by i-th signer - uint256 signedCount; - - Signer memory o; - address signerAddress; - uint256 numSigners = rs.length; - for (uint256 i; i < numSigners; ++i) { - signerAddress = ecrecover(h, uint8(rawVs[i]) + 27, rs[i], ss[i]); - o = s_config.oracles[signerAddress]; - if (o.role != Role.Signer) revert BadVerification(); - unchecked { - signedCount += 1 << (8 * o.index); - } - } - - if (signedCount & ORACLE_MASK != signedCount) revert BadVerification(); - } - - /// @inheritdoc IChannelVerifier - function setConfig( - address[] memory signers, - bytes32[] memory offchainTransmitters, - uint8 f, - bytes memory onchainConfig, - uint64 offchainConfigVersion, - bytes memory offchainConfig, - Common.AddressAndWeight[] memory recipientAddressesAndWeights - ) external override checkConfigValid(signers.length, f) onlyOwner { - _setConfig( - block.chainid, - address(this), - 0, // 0 defaults to feedConfig.configCount + 1 - signers, - offchainTransmitters, - f, - onchainConfig, - offchainConfigVersion, - offchainConfig, - recipientAddressesAndWeights - ); - } - - /// @inheritdoc IChannelVerifier - function setConfigFromSource( - uint256 sourceChainId, - address sourceAddress, - uint32 newConfigCount, - address[] memory signers, - bytes32[] memory offchainTransmitters, - uint8 f, - bytes memory onchainConfig, - uint64 offchainConfigVersion, - bytes memory offchainConfig, - Common.AddressAndWeight[] memory recipientAddressesAndWeights - ) external override checkConfigValid(signers.length, f) onlyOwner { - _setConfig( - sourceChainId, - sourceAddress, - newConfigCount, - signers, - offchainTransmitters, - f, - onchainConfig, - offchainConfigVersion, - offchainConfig, - recipientAddressesAndWeights - ); - } - - /// @notice Sets config based on the given arguments - /// @param sourceChainId Chain ID of source config - /// @param sourceAddress Address of source config Verifier - /// @param newConfigCount Optional param to force the new config count - /// @param signers addresses with which oracles sign the reports - /// @param offchainTransmitters CSA key for the ith Oracle - /// @param f number of faulty oracles the system can tolerate - /// @param onchainConfig serialized configuration used by the contract (and possibly oracles) - /// @param offchainConfigVersion version number for offchainEncoding schema - /// @param offchainConfig serialized configuration used by the oracles exclusively and only passed through the contract - /// @param recipientAddressesAndWeights the addresses and weights of all the recipients to receive rewards - function _setConfig( - uint256 sourceChainId, - address sourceAddress, - uint32 newConfigCount, - address[] memory signers, - bytes32[] memory offchainTransmitters, - uint8 f, - bytes memory onchainConfig, - uint64 offchainConfigVersion, - bytes memory offchainConfig, - Common.AddressAndWeight[] memory recipientAddressesAndWeights - ) internal { - // Increment the number of times a config has been set first - if (newConfigCount > 0) s_feedVerifierState.configCount = newConfigCount; - else s_feedVerifierState.configCount++; - - bytes32 configDigest = _configDigestFromConfigData( - sourceChainId, - sourceAddress, - s_feedVerifierState.configCount, - signers, - offchainTransmitters, - f, - onchainConfig, - offchainConfigVersion, - offchainConfig - ); - - s_feedVerifierState.s_verificationDataConfigs[configDigest].f = f; - s_feedVerifierState.s_verificationDataConfigs[configDigest].isActive = true; - for (uint8 i; i < signers.length; ++i) { - address signerAddr = signers[i]; - if (signerAddr == address(0)) revert ZeroAddress(); - - // All signer roles are unset by default for a new config digest. - // Here the contract checks to see if a signer's address has already - // been set to ensure that the group of signer addresses that will - // sign reports with the config digest are unique. - bool isSignerAlreadySet = s_feedVerifierState.s_verificationDataConfigs[configDigest].oracles[signerAddr].role != - Role.Unset; - if (isSignerAlreadySet) revert NonUniqueSignatures(); - s_feedVerifierState.s_verificationDataConfigs[configDigest].oracles[signerAddr] = Signer({ - role: Role.Signer, - index: i - }); - } - - recipientAddressesAndWeights; // silence unused var warning - // IVerifierProxy(i_verifierProxyAddr).setVerifier( - // feedVerifierState.latestConfigDigest, - // configDigest, - // recipientAddressesAndWeights - // ); - - emit ConfigSet( - s_feedVerifierState.latestConfigBlockNumber, - configDigest, - s_feedVerifierState.configCount, - signers, - offchainTransmitters, - f, - onchainConfig, - offchainConfigVersion, - offchainConfig - ); - - s_feedVerifierState.latestEpoch = 0; - s_feedVerifierState.latestConfigBlockNumber = uint32(block.number); - s_feedVerifierState.latestConfigDigest = configDigest; - } - - /// @notice Generates the config digest from config data - /// @param sourceChainId Chain ID of source config - /// @param sourceAddress Address of source config Verifier - /// @param configCount ordinal number of this config setting among all config settings over the life of this contract - /// @param signers ith element is address ith oracle uses to sign a report - /// @param offchainTransmitters ith element is address ith oracle used to transmit reports (in this case used for flexible additional field, such as CSA pub keys) - /// @param f maximum number of faulty/dishonest oracles the protocol can tolerate while still working correctly - /// @param onchainConfig serialized configuration used by the contract (and possibly oracles) - /// @param offchainConfigVersion version of the serialization format used for "offchainConfig" parameter - /// @param offchainConfig serialized configuration used by the oracles exclusively and only passed through the contract - /// @dev This function is a modified version of the method from OCR2Abstract - function _configDigestFromConfigData( - uint256 sourceChainId, - address sourceAddress, - uint64 configCount, - address[] memory signers, - bytes32[] memory offchainTransmitters, - uint8 f, - bytes memory onchainConfig, - uint64 offchainConfigVersion, - bytes memory offchainConfig - ) internal pure returns (bytes32) { - uint256 h = uint256( - keccak256( - abi.encode( - sourceChainId, - sourceAddress, - configCount, - signers, - offchainTransmitters, - f, - onchainConfig, - offchainConfigVersion, - offchainConfig - ) - ) - ); - uint256 prefixMask = type(uint256).max << (256 - 16); // 0xFFFF00..00 - // 0x0009 corresponds to ConfigDigestPrefixLLO in libocr - uint256 prefix = 0x0009 << (256 - 16); // 0x000900..00 - return bytes32((prefix & prefixMask) | (h & ~prefixMask)); - } - - /// @inheritdoc IChannelVerifier - function activateConfig(bytes32 configDigest) external onlyOwner { - if (configDigest == bytes32("")) revert DigestEmpty(); - if (s_feedVerifierState.s_verificationDataConfigs[configDigest].f == 0) revert DigestNotSet(configDigest); - s_feedVerifierState.s_verificationDataConfigs[configDigest].isActive = true; - emit ConfigActivated(configDigest); - } - - /// @inheritdoc IChannelVerifier - function deactivateConfig(bytes32 configDigest) external onlyOwner { - if (configDigest == bytes32("")) revert DigestEmpty(); - if (s_feedVerifierState.s_verificationDataConfigs[configDigest].f == 0) revert DigestNotSet(configDigest); - if (configDigest == s_feedVerifierState.latestConfigDigest) { - revert CannotDeactivateLatestConfig(configDigest); - } - s_feedVerifierState.s_verificationDataConfigs[configDigest].isActive = false; - emit ConfigDeactivated(configDigest); - } - - /// @inheritdoc IChannelVerifier - function activateFeed(bytes32 feedId) external onlyOwner { - if (s_feedVerifierState.deactivatedFeeds[feedId]) return; - - s_feedVerifierState.deactivatedFeeds[feedId] = false; - emit FeedActivated(feedId); - } - - /// @inheritdoc IChannelVerifier - function deactivateFeed(bytes32 feedId) external onlyOwner { - if (s_feedVerifierState.deactivatedFeeds[feedId] == false) return; - - s_feedVerifierState.deactivatedFeeds[feedId] = true; - emit FeedDeactivated(feedId); - } - - /// @inheritdoc IChannelVerifier - function latestConfigDigestAndEpoch() - external - view - override - returns (bool scanLogs, bytes32 configDigest, uint32 epoch) - { - return (false, s_feedVerifierState.latestConfigDigest, s_feedVerifierState.latestEpoch); - } - - /// @inheritdoc IChannelVerifier - function latestConfigDetails() - external - view - override - returns (uint32 configCount, uint32 blockNumber, bytes32 configDigest) - { - return ( - s_feedVerifierState.configCount, - s_feedVerifierState.latestConfigBlockNumber, - s_feedVerifierState.latestConfigDigest - ); - } -} diff --git a/contracts/src/v0.8/llo-feeds/dev/interfaces/IChannelConfigStore.sol b/contracts/src/v0.8/llo-feeds/dev/interfaces/IChannelConfigStore.sol deleted file mode 100644 index 45e3ee313d..0000000000 --- a/contracts/src/v0.8/llo-feeds/dev/interfaces/IChannelConfigStore.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.19; - -import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; - -interface IChannelConfigStore is IERC165 { - // function setStagingConfig(bytes32 configDigest, ChannelConfiguration calldata channelConfig) external; - - // function promoteStagingConfig(bytes32 configDigest) external; - - function addChannel(uint32 channelId, ChannelDefinition calldata channelDefinition) external; - - function removeChannel(uint32 channelId) external; - - function getChannelDefinitions(uint32 channelId) external view returns (ChannelDefinition memory); - - // struct ChannelConfiguration { - // bytes32 configDigest; - // } - - struct ChannelDefinition { - // e.g. evm, solana, CosmWasm, kalechain, etc... - uint32 reportFormat; - // Specifies the chain on which this channel can be verified. Currently uses - // CCIP chain selectors, but lots of other schemes are possible as well. - uint64 chainSelector; - // We assume that StreamIDs is always non-empty and that the 0-th stream - // contains the verification price in LINK and the 1-st stream contains the - // verification price in the native coin. - uint32[] streamIDs; - } -} diff --git a/contracts/src/v0.8/llo-feeds/dev/interfaces/IChannelVerifier.sol b/contracts/src/v0.8/llo-feeds/dev/interfaces/IChannelVerifier.sol deleted file mode 100644 index 6bab5912a7..0000000000 --- a/contracts/src/v0.8/llo-feeds/dev/interfaces/IChannelVerifier.sol +++ /dev/null @@ -1,111 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.19; - -import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; -import {Common} from "../../libraries/Common.sol"; - -interface IChannelVerifier is IERC165 { - /** - * @notice Verifies that the data encoded has been signed - * correctly by routing to the correct verifier. - * @param signedReport The encoded data to be verified. - * @param sender The address that requested to verify the contract. - * This is only used for logging purposes. - * @dev Verification is typically only done through the proxy contract so - * we can't just use msg.sender to log the requester as the msg.sender - * contract will always be the proxy. - * @return verifierResponse The encoded verified response. - */ - function verify(bytes calldata signedReport, address sender) external returns (bytes memory verifierResponse); - - /** - * @notice sets offchain reporting protocol configuration incl. participating oracles - * @param signers addresses with which oracles sign the reports - * @param offchainTransmitters CSA key for the ith Oracle - * @param f number of faulty oracles the system can tolerate - * @param onchainConfig serialized configuration used by the contract (and possibly oracles) - * @param offchainConfigVersion version number for offchainEncoding schema - * @param offchainConfig serialized configuration used by the oracles exclusively and only passed through the contract - * @param recipientAddressesAndWeights the addresses and weights of all the recipients to receive rewards - */ - function setConfig( - address[] memory signers, - bytes32[] memory offchainTransmitters, - uint8 f, - bytes memory onchainConfig, - uint64 offchainConfigVersion, - bytes memory offchainConfig, - Common.AddressAndWeight[] memory recipientAddressesAndWeights - ) external; - - /** - * @notice identical to `setConfig` except with args for sourceChainId and sourceAddress - * @param sourceChainId Chain ID of source config - * @param sourceAddress Address of source config Verifier - * @param newConfigCount Param to force the new config count - * @param signers addresses with which oracles sign the reports - * @param offchainTransmitters CSA key for the ith Oracle - * @param f number of faulty oracles the system can tolerate - * @param onchainConfig serialized configuration used by the contract (and possibly oracles) - * @param offchainConfigVersion version number for offchainEncoding schema - * @param offchainConfig serialized configuration used by the oracles exclusively and only passed through the contract - * @param recipientAddressesAndWeights the addresses and weights of all the recipients to receive rewards - */ - function setConfigFromSource( - uint256 sourceChainId, - address sourceAddress, - uint32 newConfigCount, - address[] memory signers, - bytes32[] memory offchainTransmitters, - uint8 f, - bytes memory onchainConfig, - uint64 offchainConfigVersion, - bytes memory offchainConfig, - Common.AddressAndWeight[] memory recipientAddressesAndWeights - ) external; - - /** - * @notice Activates the configuration for a config digest - * @param configDigest The config digest to activate - * @dev This function can be called by the contract admin to activate a configuration. - */ - function activateConfig(bytes32 configDigest) external; - - /** - * @notice Deactivates the configuration for a config digest - * @param configDigest The config digest to deactivate - * @dev This function can be called by the contract admin to deactivate an incorrect configuration. - */ - function deactivateConfig(bytes32 configDigest) external; - - /** - * @notice Activates the given feed - * @param feedId Feed ID to activated - * @dev This function can be called by the contract admin to activate a feed - */ - function activateFeed(bytes32 feedId) external; - - /** - * @notice Deactivates the given feed - * @param feedId Feed ID to deactivated - * @dev This function can be called by the contract admin to deactivate a feed - */ - function deactivateFeed(bytes32 feedId) external; - - /** - * @notice returns the latest config digest and epoch - * @return scanLogs indicates whether to rely on the configDigest and epoch - * returned or whether to scan logs for the Transmitted event instead. - * @return configDigest - * @return epoch - */ - function latestConfigDigestAndEpoch() external view returns (bool scanLogs, bytes32 configDigest, uint32 epoch); - - /** - * @notice information about current offchain reporting protocol configuration - * @return configCount ordinal number of current config, out of all configs applied to this contract so far - * @return blockNumber block at which this config was set - * @return configDigest domain-separation tag for current config - */ - function latestConfigDetails() external view returns (uint32 configCount, uint32 blockNumber, bytes32 configDigest); -} diff --git a/contracts/src/v0.8/llo-feeds/dev/test/mocks/ExposedChannelVerifier.sol b/contracts/src/v0.8/llo-feeds/dev/test/mocks/ExposedChannelVerifier.sol deleted file mode 100644 index 650b3b4a81..0000000000 --- a/contracts/src/v0.8/llo-feeds/dev/test/mocks/ExposedChannelVerifier.sol +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.19; - -// ExposedChannelVerifier exposes certain internal Verifier -// methods/structures so that golang code can access them, and we get -// reliable type checking on their usage -contract ExposedChannelVerifier { - constructor() {} - - function _configDigestFromConfigData( - uint256 chainId, - address contractAddress, - uint64 configCount, - address[] memory signers, - bytes32[] memory offchainTransmitters, - uint8 f, - bytes memory onchainConfig, - uint64 offchainConfigVersion, - bytes memory offchainConfig - ) internal pure returns (bytes32) { - uint256 h = uint256( - keccak256( - abi.encode( - chainId, - contractAddress, - configCount, - signers, - offchainTransmitters, - f, - onchainConfig, - offchainConfigVersion, - offchainConfig - ) - ) - ); - uint256 prefixMask = type(uint256).max << (256 - 16); // 0xFFFF00..00 - // 0x0009 corresponds to ConfigDigestPrefixLLO in libocr - uint256 prefix = 0x0009 << (256 - 16); // 0x000900..00 - return bytes32((prefix & prefixMask) | (h & ~prefixMask)); - } - - function exposedConfigDigestFromConfigData( - uint256 _chainId, - address _contractAddress, - uint64 _configCount, - address[] memory _signers, - bytes32[] memory _offchainTransmitters, - uint8 _f, - bytes calldata _onchainConfig, - uint64 _encodedConfigVersion, - bytes memory _encodedConfig - ) public pure returns (bytes32) { - return - _configDigestFromConfigData( - _chainId, - _contractAddress, - _configCount, - _signers, - _offchainTransmitters, - _f, - _onchainConfig, - _encodedConfigVersion, - _encodedConfig - ); - } -} diff --git a/contracts/src/v0.8/llo-feeds/interfaces/IConfigurator.sol b/contracts/src/v0.8/llo-feeds/interfaces/IConfigurator.sol new file mode 100644 index 0000000000..478afa918c --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/interfaces/IConfigurator.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +interface IConfigurator { + /// @notice This event is emitted whenever a new configuration is set for a feed. It triggers a new run of the offchain reporting protocol. + event ConfigSet( + bytes32 indexed configId, + uint32 previousConfigBlockNumber, + bytes32 configDigest, + uint64 configCount, + address[] signers, + bytes32[] offchainTransmitters, + uint8 f, + bytes onchainConfig, + uint64 offchainConfigVersion, + bytes offchainConfig + ); + + function setConfig( + bytes32 configId, + address[] memory signers, + bytes32[] memory offchainTransmitters, + uint8 f, + bytes memory onchainConfig, + uint64 offchainConfigVersion, + bytes memory offchainConfig + ) external; +} diff --git a/contracts/src/v0.8/llo-feeds/libraries/Common.sol b/contracts/src/v0.8/llo-feeds/libraries/Common.sol index f732ced004..23418bf41a 100644 --- a/contracts/src/v0.8/llo-feeds/libraries/Common.sol +++ b/contracts/src/v0.8/llo-feeds/libraries/Common.sol @@ -19,6 +19,28 @@ library Common { uint64 weight; } + /** + * @notice Checks if an array of AddressAndWeight has duplicate addresses + * @param recipients The array of AddressAndWeight to check + * @return bool True if there are duplicates, false otherwise + */ + function _hasDuplicateAddresses(address[] memory recipients) internal pure returns (bool) { + for (uint256 i = 0; i < recipients.length; ) { + for (uint256 j = i + 1; j < recipients.length; ) { + if (recipients[i] == recipients[j]) { + return true; + } + unchecked { + ++j; + } + } + unchecked { + ++i; + } + } + return false; + } + /** * @notice Checks if an array of AddressAndWeight has duplicate addresses * @param recipients The array of AddressAndWeight to check @@ -40,4 +62,28 @@ library Common { } return false; } + + /** + * @notice sorts a list of addresses numerically + * @param arr The array of addresses to sort + * @param left the start index + * @param right the end index + */ + function _quickSort(address[] memory arr, int256 left, int256 right) internal pure { + int256 i = left; + int256 j = right; + if (i == j) return; + address pivot = arr[uint256(left + (right - left) / 2)]; + while (i <= j) { + while (uint160(arr[uint256(i)]) < uint160(pivot)) i++; + while (uint160(pivot) < uint160(arr[uint256(j)])) j--; + if (i <= j) { + (arr[uint256(i)], arr[uint256(j)]) = (arr[uint256(j)], arr[uint256(i)]); + i++; + j--; + } + } + if (left < j) _quickSort(arr, left, j); + if (i < right) _quickSort(arr, i, right); + } } diff --git a/contracts/src/v0.8/llo-feeds/test/ByteUtilTest.t.sol b/contracts/src/v0.8/llo-feeds/libraries/test/ByteUtilTest.t.sol similarity index 99% rename from contracts/src/v0.8/llo-feeds/test/ByteUtilTest.t.sol rename to contracts/src/v0.8/llo-feeds/libraries/test/ByteUtilTest.t.sol index fdabaaf3d9..8f11dab093 100644 --- a/contracts/src/v0.8/llo-feeds/test/ByteUtilTest.t.sol +++ b/contracts/src/v0.8/llo-feeds/libraries/test/ByteUtilTest.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.19; import {Test} from "forge-std/Test.sol"; -import {ByteUtil} from "../libraries/ByteUtil.sol"; +import {ByteUtil} from "../ByteUtil.sol"; contract ByteUtilTest is Test { using ByteUtil for bytes; diff --git a/contracts/src/v0.8/llo-feeds/test/mocks/ExposedVerifier.sol b/contracts/src/v0.8/llo-feeds/test/mocks/ExposedVerifier.sol deleted file mode 100644 index 1c004bf384..0000000000 --- a/contracts/src/v0.8/llo-feeds/test/mocks/ExposedVerifier.sol +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.19; - -// ExposedVerifier exposes certain internal Verifier -// methods/structures so that golang code can access them, and we get -// reliable type checking on their usage -contract ExposedVerifier { - constructor() {} - - function _configDigestFromConfigData( - bytes32 feedId, - uint256 chainId, - address contractAddress, - uint64 configCount, - address[] memory signers, - bytes32[] memory offchainTransmitters, - uint8 f, - bytes memory onchainConfig, - uint64 offchainConfigVersion, - bytes memory offchainConfig - ) internal pure returns (bytes32) { - uint256 h = uint256( - keccak256( - abi.encode( - feedId, - chainId, - contractAddress, - configCount, - signers, - offchainTransmitters, - f, - onchainConfig, - offchainConfigVersion, - offchainConfig - ) - ) - ); - uint256 prefixMask = type(uint256).max << (256 - 16); // 0xFFFF00..00 - uint256 prefix = 0x0006 << (256 - 16); // 0x000600..00 - return bytes32((prefix & prefixMask) | (h & ~prefixMask)); - } - - function exposedConfigDigestFromConfigData( - bytes32 _feedId, - uint256 _chainId, - address _contractAddress, - uint64 _configCount, - address[] memory _signers, - bytes32[] memory _offchainTransmitters, - uint8 _f, - bytes calldata _onchainConfig, - uint64 _encodedConfigVersion, - bytes memory _encodedConfig - ) public pure returns (bytes32) { - return - _configDigestFromConfigData( - _feedId, - _chainId, - _contractAddress, - _configCount, - _signers, - _offchainTransmitters, - _f, - _onchainConfig, - _encodedConfigVersion, - _encodedConfig - ); - } -} diff --git a/contracts/src/v0.8/llo-feeds/FeeManager.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/FeeManager.sol similarity index 96% rename from contracts/src/v0.8/llo-feeds/FeeManager.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/FeeManager.sol index 665940d467..44f550e325 100644 --- a/contracts/src/v0.8/llo-feeds/FeeManager.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/FeeManager.sol @@ -1,16 +1,16 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.19; -import {ConfirmedOwner} from "../shared/access/ConfirmedOwner.sol"; +import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; import {IFeeManager} from "./interfaces/IFeeManager.sol"; -import {TypeAndVersionInterface} from "../interfaces/TypeAndVersionInterface.sol"; -import {IERC165} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; -import {Common} from "./libraries/Common.sol"; +import {TypeAndVersionInterface} from "../../interfaces/TypeAndVersionInterface.sol"; +import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; +import {Common} from "../libraries/Common.sol"; import {IRewardManager} from "./interfaces/IRewardManager.sol"; -import {IWERC20} from "../shared/interfaces/IWERC20.sol"; -import {IERC20} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC20.sol"; -import {Math} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/math/Math.sol"; -import {SafeERC20} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IWERC20} from "../../shared/interfaces/IWERC20.sol"; +import {IERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC20.sol"; +import {Math} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/math/Math.sol"; +import {SafeERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol"; import {IVerifierFeeManager} from "./interfaces/IVerifierFeeManager.sol"; /** diff --git a/contracts/src/v0.8/llo-feeds/RewardManager.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/RewardManager.sol similarity index 96% rename from contracts/src/v0.8/llo-feeds/RewardManager.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/RewardManager.sol index 1929289a36..49fef51c56 100644 --- a/contracts/src/v0.8/llo-feeds/RewardManager.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/RewardManager.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.19; -import {ConfirmedOwner} from "../shared/access/ConfirmedOwner.sol"; +import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; import {IRewardManager} from "./interfaces/IRewardManager.sol"; -import {IERC20} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC20.sol"; -import {TypeAndVersionInterface} from "../interfaces/TypeAndVersionInterface.sol"; -import {Common} from "./libraries/Common.sol"; -import {SafeERC20} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC20.sol"; +import {TypeAndVersionInterface} from "../../interfaces/TypeAndVersionInterface.sol"; +import {Common} from "../libraries/Common.sol"; +import {SafeERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol"; /** * @title RewardManager diff --git a/contracts/src/v0.8/llo-feeds/Verifier.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/Verifier.sol similarity index 98% rename from contracts/src/v0.8/llo-feeds/Verifier.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/Verifier.sol index c8858999da..fe5742108a 100644 --- a/contracts/src/v0.8/llo-feeds/Verifier.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/Verifier.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.19; -import {ConfirmedOwner} from "../shared/access/ConfirmedOwner.sol"; +import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; import {IVerifier} from "./interfaces/IVerifier.sol"; import {IVerifierProxy} from "./interfaces/IVerifierProxy.sol"; -import {TypeAndVersionInterface} from "../interfaces/TypeAndVersionInterface.sol"; -import {IERC165} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; -import {Common} from "./libraries/Common.sol"; +import {TypeAndVersionInterface} from "../../interfaces/TypeAndVersionInterface.sol"; +import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; +import {Common} from "../libraries/Common.sol"; // OCR2 standard uint256 constant MAX_NUM_ORACLES = 31; diff --git a/contracts/src/v0.8/llo-feeds/VerifierProxy.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/VerifierProxy.sol similarity index 95% rename from contracts/src/v0.8/llo-feeds/VerifierProxy.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/VerifierProxy.sol index c32a27178c..c06312dd7b 100644 --- a/contracts/src/v0.8/llo-feeds/VerifierProxy.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/VerifierProxy.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.19; -import {ConfirmedOwner} from "../shared/access/ConfirmedOwner.sol"; +import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; import {IVerifierProxy} from "./interfaces/IVerifierProxy.sol"; import {IVerifier} from "./interfaces/IVerifier.sol"; -import {TypeAndVersionInterface} from "../interfaces/TypeAndVersionInterface.sol"; -import {AccessControllerInterface} from "../shared/interfaces/AccessControllerInterface.sol"; -import {IERC165} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; +import {TypeAndVersionInterface} from "../../interfaces/TypeAndVersionInterface.sol"; +import {AccessControllerInterface} from "../../shared/interfaces/AccessControllerInterface.sol"; +import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; import {IVerifierFeeManager} from "./interfaces/IVerifierFeeManager.sol"; -import {Common} from "./libraries/Common.sol"; +import {Common} from "../libraries/Common.sol"; /** * The verifier proxy contract is the gateway for all report verification requests diff --git a/contracts/src/v0.8/llo-feeds/interfaces/IFeeManager.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IFeeManager.sol similarity index 94% rename from contracts/src/v0.8/llo-feeds/interfaces/IFeeManager.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IFeeManager.sol index 4095607b91..818a3a09a4 100644 --- a/contracts/src/v0.8/llo-feeds/interfaces/IFeeManager.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IFeeManager.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.19; -import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; -import {Common} from "../libraries/Common.sol"; +import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; +import {Common} from "../../libraries/Common.sol"; import {IVerifierFeeManager} from "./IVerifierFeeManager.sol"; interface IFeeManager is IERC165, IVerifierFeeManager { diff --git a/contracts/src/v0.8/llo-feeds/interfaces/IRewardManager.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IRewardManager.sol similarity index 94% rename from contracts/src/v0.8/llo-feeds/interfaces/IRewardManager.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IRewardManager.sol index 5a6e03f1c9..f08ce34db2 100644 --- a/contracts/src/v0.8/llo-feeds/interfaces/IRewardManager.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IRewardManager.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.19; -import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; -import {Common} from "../libraries/Common.sol"; +import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; +import {Common} from "../../libraries/Common.sol"; interface IRewardManager is IERC165 { /** diff --git a/contracts/src/v0.8/llo-feeds/interfaces/IVerifier.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IVerifier.sol similarity index 97% rename from contracts/src/v0.8/llo-feeds/interfaces/IVerifier.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IVerifier.sol index 617d702d3f..94b260399e 100644 --- a/contracts/src/v0.8/llo-feeds/interfaces/IVerifier.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IVerifier.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.19; -import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; -import {Common} from "../libraries/Common.sol"; +import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; +import {Common} from "../../libraries/Common.sol"; interface IVerifier is IERC165 { /** diff --git a/contracts/src/v0.8/llo-feeds/interfaces/IVerifierFeeManager.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IVerifierFeeManager.sol similarity index 88% rename from contracts/src/v0.8/llo-feeds/interfaces/IVerifierFeeManager.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IVerifierFeeManager.sol index 522db952e5..da3fdfac15 100644 --- a/contracts/src/v0.8/llo-feeds/interfaces/IVerifierFeeManager.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IVerifierFeeManager.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.19; -import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; -import {Common} from "../libraries/Common.sol"; +import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; +import {Common} from "../../libraries/Common.sol"; interface IVerifierFeeManager is IERC165 { /** diff --git a/contracts/src/v0.8/llo-feeds/interfaces/IVerifierProxy.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IVerifierProxy.sol similarity index 95% rename from contracts/src/v0.8/llo-feeds/interfaces/IVerifierProxy.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IVerifierProxy.sol index 2eb1b4aff4..6609e4869a 100644 --- a/contracts/src/v0.8/llo-feeds/interfaces/IVerifierProxy.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IVerifierProxy.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.19; -import {Common} from "../libraries/Common.sol"; -import {AccessControllerInterface} from "../../shared/interfaces/AccessControllerInterface.sol"; +import {Common} from "../../libraries/Common.sol"; +import {AccessControllerInterface} from "../../../shared/interfaces/AccessControllerInterface.sol"; import {IVerifierFeeManager} from "./IVerifierFeeManager.sol"; interface IVerifierProxy { diff --git a/contracts/src/v0.8/llo-feeds/test/fee-manager/BaseFeeManager.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/fee-manager/BaseFeeManager.t.sol similarity index 98% rename from contracts/src/v0.8/llo-feeds/test/fee-manager/BaseFeeManager.t.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/fee-manager/BaseFeeManager.t.sol index edde26b2ee..0d598cdac5 100644 --- a/contracts/src/v0.8/llo-feeds/test/fee-manager/BaseFeeManager.t.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/fee-manager/BaseFeeManager.t.sol @@ -4,9 +4,9 @@ pragma solidity 0.8.19; import {Test} from "forge-std/Test.sol"; import {FeeManager} from "../../FeeManager.sol"; import {RewardManager} from "../../RewardManager.sol"; -import {Common} from "../../libraries/Common.sol"; -import {ERC20Mock} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol"; -import {WERC20Mock} from "../../../shared/mocks/WERC20Mock.sol"; +import {Common} from "../../../libraries/Common.sol"; +import {ERC20Mock} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol"; +import {WERC20Mock} from "../../../../shared/mocks/WERC20Mock.sol"; import {IRewardManager} from "../../interfaces/IRewardManager.sol"; import {FeeManagerProxy} from "../mocks/FeeManagerProxy.sol"; diff --git a/contracts/src/v0.8/llo-feeds/test/fee-manager/FeeManager.general.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/fee-manager/FeeManager.general.t.sol similarity index 100% rename from contracts/src/v0.8/llo-feeds/test/fee-manager/FeeManager.general.t.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/fee-manager/FeeManager.general.t.sol diff --git a/contracts/src/v0.8/llo-feeds/test/fee-manager/FeeManager.getFeeAndReward.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/fee-manager/FeeManager.getFeeAndReward.t.sol similarity index 98% rename from contracts/src/v0.8/llo-feeds/test/fee-manager/FeeManager.getFeeAndReward.t.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/fee-manager/FeeManager.getFeeAndReward.t.sol index 299a7f09d5..1b0f95d51b 100644 --- a/contracts/src/v0.8/llo-feeds/test/fee-manager/FeeManager.getFeeAndReward.t.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/fee-manager/FeeManager.getFeeAndReward.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.19; -import {Common} from "../../libraries/Common.sol"; +import {Common} from "../../../libraries/Common.sol"; import "./BaseFeeManager.t.sol"; /** @@ -10,7 +10,7 @@ import "./BaseFeeManager.t.sol"; * @notice This contract will test the functionality of the feeManager's getFeeAndReward */ contract FeeManagerProcessFeeTest is BaseFeeManagerTest { - function test_baseFeeIsAppliedForNative() public { + function test_baseFeeIsAppliedForNative() public view { //get the fee required by the feeManager Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); @@ -18,7 +18,7 @@ contract FeeManagerProcessFeeTest is BaseFeeManagerTest { assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE); } - function test_baseFeeIsAppliedForLink() public { + function test_baseFeeIsAppliedForLink() public view { //get the fee required by the feeManager Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); @@ -378,7 +378,7 @@ contract FeeManagerProcessFeeTest is BaseFeeManagerTest { assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE - expectedDiscount); } - function test_reportWithNoExpiryOrFeeReturnsZero() public { + function test_reportWithNoExpiryOrFeeReturnsZero() public view { //get the fee required by the feeManager Common.Asset memory fee = getFee(getV1Report(DEFAULT_FEED_1_V1), getNativeQuote(), USER); @@ -462,7 +462,7 @@ contract FeeManagerProcessFeeTest is BaseFeeManagerTest { setNativeSurcharge(nativeSurcharge, ADMIN); } - function test_getBaseRewardWithLinkQuote() public { + function test_getBaseRewardWithLinkQuote() public view { //get the fee required by the feeManager Common.Asset memory reward = getReward(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); @@ -481,7 +481,7 @@ contract FeeManagerProcessFeeTest is BaseFeeManagerTest { assertEq(reward.amount, DEFAULT_REPORT_LINK_FEE / 2); } - function test_getRewardWithNativeQuote() public { + function test_getRewardWithNativeQuote() public view { //get the fee required by the feeManager Common.Asset memory reward = getReward(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); diff --git a/contracts/src/v0.8/llo-feeds/test/fee-manager/FeeManager.processFee.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/fee-manager/FeeManager.processFee.t.sol similarity index 99% rename from contracts/src/v0.8/llo-feeds/test/fee-manager/FeeManager.processFee.t.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/fee-manager/FeeManager.processFee.t.sol index f8c1d47d4d..0e0ed8977b 100644 --- a/contracts/src/v0.8/llo-feeds/test/fee-manager/FeeManager.processFee.t.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/fee-manager/FeeManager.processFee.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.19; -import {Common} from "../../libraries/Common.sol"; +import {Common} from "../../../libraries/Common.sol"; import "./BaseFeeManager.t.sol"; import {IRewardManager} from "../../interfaces/IRewardManager.sol"; diff --git a/contracts/src/v0.8/llo-feeds/test/fee-manager/FeeManager.processFeeBulk.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/fee-manager/FeeManager.processFeeBulk.t.sol similarity index 100% rename from contracts/src/v0.8/llo-feeds/test/fee-manager/FeeManager.processFeeBulk.t.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/fee-manager/FeeManager.processFeeBulk.t.sol diff --git a/contracts/src/v0.8/llo-feeds/test/gas/Gas_VerifierTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/gas/Gas_VerifierTest.t.sol similarity index 98% rename from contracts/src/v0.8/llo-feeds/test/gas/Gas_VerifierTest.t.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/gas/Gas_VerifierTest.t.sol index 29b488fb32..ee8ba4c3e3 100644 --- a/contracts/src/v0.8/llo-feeds/test/gas/Gas_VerifierTest.t.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/gas/Gas_VerifierTest.t.sol @@ -2,8 +2,8 @@ pragma solidity 0.8.19; import {BaseTest, BaseTestWithConfiguredVerifierAndFeeManager} from "../verifier/BaseVerifierTest.t.sol"; -import {SimpleWriteAccessController} from "../../../shared/access/SimpleWriteAccessController.sol"; -import {Common} from "../../libraries/Common.sol"; +import {SimpleWriteAccessController} from "../../../../shared/access/SimpleWriteAccessController.sol"; +import {Common} from "../../../libraries/Common.sol"; import {IRewardManager} from "../../interfaces/IRewardManager.sol"; contract Verifier_setConfig is BaseTest { diff --git a/contracts/src/v0.8/llo-feeds/test/mocks/ErroredVerifier.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/mocks/ErroredVerifier.sol similarity index 66% rename from contracts/src/v0.8/llo-feeds/test/mocks/ErroredVerifier.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/mocks/ErroredVerifier.sol index 01cb1a5061..e9dcd589e2 100644 --- a/contracts/src/v0.8/llo-feeds/test/mocks/ErroredVerifier.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/mocks/ErroredVerifier.sol @@ -2,13 +2,24 @@ pragma solidity 0.8.19; import {IVerifier} from "../../interfaces/IVerifier.sol"; -import {Common} from "../../libraries/Common.sol"; +import {Common} from "../../../libraries/Common.sol"; contract ErroredVerifier is IVerifier { function supportsInterface(bytes4 interfaceId) public pure override returns (bool) { return interfaceId == this.verify.selector; } + //define each of the errors thrown in the revert below + + error FailedToVerify(); + error FailedToSetConfig(); + error FailedToActivateConfig(); + error FailedToDeactivateConfig(); + error FailedToActivateFeed(); + error FailedToDeactivateFeed(); + error FailedToGetLatestConfigDigestAndEpoch(); + error FailedToGetLatestConfigDetails(); + function verify( bytes memory, /** @@ -26,7 +37,7 @@ contract ErroredVerifier is IVerifier { bytes memory ) { - revert("Failed to verify"); + revert FailedToVerify(); } function setConfig( @@ -39,7 +50,7 @@ contract ErroredVerifier is IVerifier { bytes memory, Common.AddressAndWeight[] memory ) external pure override { - revert("Failed to set config"); + revert FailedToSetConfig(); } function setConfigFromSource( @@ -55,30 +66,30 @@ contract ErroredVerifier is IVerifier { bytes memory, Common.AddressAndWeight[] memory ) external pure override { - revert("Failed to set config"); + revert FailedToSetConfig(); } function activateConfig(bytes32, bytes32) external pure { - revert("Failed to activate config"); + revert FailedToActivateConfig(); } function deactivateConfig(bytes32, bytes32) external pure { - revert("Failed to deactivate config"); + revert FailedToDeactivateConfig(); } function activateFeed(bytes32) external pure { - revert("Failed to activate feed"); + revert FailedToActivateFeed(); } function deactivateFeed(bytes32) external pure { - revert("Failed to deactivate feed"); + revert FailedToDeactivateFeed(); } function latestConfigDigestAndEpoch(bytes32) external pure override returns (bool, bytes32, uint32) { - revert("Failed to get latest config digest and epoch"); + revert FailedToGetLatestConfigDigestAndEpoch(); } function latestConfigDetails(bytes32) external pure override returns (uint32, uint32, bytes32) { - revert("Failed to get latest config details"); + revert FailedToGetLatestConfigDetails(); } } diff --git a/contracts/src/v0.8/llo-feeds/dev/test/mocks/ExposedVerifier.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/mocks/ExposedVerifier.sol similarity index 100% rename from contracts/src/v0.8/llo-feeds/dev/test/mocks/ExposedVerifier.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/mocks/ExposedVerifier.sol diff --git a/contracts/src/v0.8/llo-feeds/test/mocks/FeeManagerProxy.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/mocks/FeeManagerProxy.sol similarity index 61% rename from contracts/src/v0.8/llo-feeds/test/mocks/FeeManagerProxy.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/mocks/FeeManagerProxy.sol index 16935f69d6..9abb4c50c2 100644 --- a/contracts/src/v0.8/llo-feeds/test/mocks/FeeManagerProxy.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/mocks/FeeManagerProxy.sol @@ -1,20 +1,20 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.19; -import "../../interfaces/IFeeManager.sol"; +import {IFeeManager} from "../../interfaces/IFeeManager.sol"; contract FeeManagerProxy { - IFeeManager internal i_feeManager; + IFeeManager internal s_feeManager; function processFee(bytes calldata payload, bytes calldata parameterPayload) public payable { - i_feeManager.processFee{value: msg.value}(payload, parameterPayload, msg.sender); + s_feeManager.processFee{value: msg.value}(payload, parameterPayload, msg.sender); } function processFeeBulk(bytes[] calldata payloads, bytes calldata parameterPayload) public payable { - i_feeManager.processFeeBulk{value: msg.value}(payloads, parameterPayload, msg.sender); + s_feeManager.processFeeBulk{value: msg.value}(payloads, parameterPayload, msg.sender); } function setFeeManager(IFeeManager feeManager) public { - i_feeManager = feeManager; + s_feeManager = feeManager; } } diff --git a/contracts/src/v0.8/llo-feeds/test/reward-manager/BaseRewardManager.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/BaseRewardManager.t.sol similarity index 97% rename from contracts/src/v0.8/llo-feeds/test/reward-manager/BaseRewardManager.t.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/BaseRewardManager.t.sol index 65481513f0..c7bd7528c3 100644 --- a/contracts/src/v0.8/llo-feeds/test/reward-manager/BaseRewardManager.t.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/BaseRewardManager.t.sol @@ -2,9 +2,9 @@ pragma solidity 0.8.19; import {Test} from "forge-std/Test.sol"; -import {ERC20Mock} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol"; -import {RewardManager} from "../../RewardManager.sol"; -import {Common} from "../../libraries/Common.sol"; +import {ERC20Mock} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol"; +import {RewardManager} from "../../../v0.3.0/RewardManager.sol"; +import {Common} from "../../../libraries/Common.sol"; import {IRewardManager} from "../../interfaces/IRewardManager.sol"; /** diff --git a/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.claim.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/RewardManager.claim.t.sol similarity index 99% rename from contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.claim.t.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/RewardManager.claim.t.sol index 5f07d36c72..efbe9fd6b3 100644 --- a/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.claim.t.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/RewardManager.claim.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.19; import {BaseRewardManagerTest} from "./BaseRewardManager.t.sol"; -import {Common} from "../../libraries/Common.sol"; +import {Common} from "../../../libraries/Common.sol"; /** * @title BaseRewardManagerTest @@ -574,7 +574,7 @@ contract RewardManagerRecipientClaimMultiplePoolsTest is BaseRewardManagerTest { assertEq(poolIds[1], ZERO_POOL_ID); } - function test_getRewardsAvailableToRecipientInNoPools() public { + function test_getRewardsAvailableToRecipientInNoPools() public view { //get index 0 as this recipient is in both default pools bytes32[] memory poolIds = rewardManager.getAvailableRewardPoolIds(FEE_MANAGER, 0, type(uint256).max); diff --git a/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.general.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/RewardManager.general.t.sol similarity index 92% rename from contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.general.t.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/RewardManager.general.t.sol index 7fde76d528..baff388769 100644 --- a/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.general.t.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/RewardManager.general.t.sol @@ -2,8 +2,8 @@ pragma solidity 0.8.19; import {BaseRewardManagerTest} from "./BaseRewardManager.t.sol"; -import {RewardManager} from "../../RewardManager.sol"; -import {ERC20Mock} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol"; +import {RewardManager} from "../../../v0.3.0/RewardManager.sol"; +import {ERC20Mock} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol"; import {IRewardManager} from "../../interfaces/IRewardManager.sol"; /** diff --git a/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.payRecipients.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/RewardManager.payRecipients.t.sol similarity index 100% rename from contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.payRecipients.t.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/RewardManager.payRecipients.t.sol diff --git a/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.setRecipients.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/RewardManager.setRecipients.t.sol similarity index 98% rename from contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.setRecipients.t.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/RewardManager.setRecipients.t.sol index 1cf5b51f62..d3e6990bd9 100644 --- a/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.setRecipients.t.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/RewardManager.setRecipients.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.19; import {BaseRewardManagerTest} from "./BaseRewardManager.t.sol"; -import {Common} from "../../libraries/Common.sol"; +import {Common} from "../../../libraries/Common.sol"; /** * @title BaseRewardManagerTest diff --git a/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.updateRewardRecipients.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/RewardManager.updateRewardRecipients.t.sol similarity index 99% rename from contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.updateRewardRecipients.t.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/RewardManager.updateRewardRecipients.t.sol index 6c51a0fbfd..0d3a2b69b3 100644 --- a/contracts/src/v0.8/llo-feeds/test/reward-manager/RewardManager.updateRewardRecipients.t.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/reward-manager/RewardManager.updateRewardRecipients.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.19; import {BaseRewardManagerTest} from "./BaseRewardManager.t.sol"; -import {Common} from "../../libraries/Common.sol"; +import {Common} from "../../../libraries/Common.sol"; /** * @title BaseRewardManagerTest diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/BaseVerifierTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/BaseVerifierTest.t.sol similarity index 97% rename from contracts/src/v0.8/llo-feeds/test/verifier/BaseVerifierTest.t.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/BaseVerifierTest.t.sol index daf3187503..4d65414676 100644 --- a/contracts/src/v0.8/llo-feeds/test/verifier/BaseVerifierTest.t.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/BaseVerifierTest.t.sol @@ -3,17 +3,16 @@ pragma solidity 0.8.19; import {Test} from "forge-std/Test.sol"; import {VerifierProxy} from "../../VerifierProxy.sol"; -import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; +import {IERC165} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; import {IVerifier} from "../../interfaces/IVerifier.sol"; import {ErroredVerifier} from "../mocks/ErroredVerifier.sol"; import {Verifier} from "../../Verifier.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; -import {AccessControllerInterface} from "../../../shared/interfaces/AccessControllerInterface.sol"; -import {FeeManager} from "../../FeeManager.sol"; -import {Common} from "../../libraries/Common.sol"; -import {ERC20Mock} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol"; -import {WERC20Mock} from "../../../shared/mocks/WERC20Mock.sol"; +import {AccessControllerInterface} from "../../../../shared/interfaces/AccessControllerInterface.sol"; import {FeeManager} from "../../FeeManager.sol"; +import {Common} from "../../../libraries/Common.sol"; +import {ERC20Mock} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol"; +import {WERC20Mock} from "../../../../shared/mocks/WERC20Mock.sol"; import {RewardManager} from "../../RewardManager.sol"; contract BaseTest is Test { diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierActivateConfigTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierActivateConfigTest.t.sol similarity index 97% rename from contracts/src/v0.8/llo-feeds/test/verifier/VerifierActivateConfigTest.t.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierActivateConfigTest.t.sol index f53c26ba19..99daabe206 100644 --- a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierActivateConfigTest.t.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierActivateConfigTest.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.19; import {BaseTestWithConfiguredVerifierAndFeeManager, BaseTestWithMultipleConfiguredDigests} from "./BaseVerifierTest.t.sol"; -import {Verifier} from "../../Verifier.sol"; +import {Verifier} from "../../../v0.3.0/Verifier.sol"; contract VerifierActivateConfigTest is BaseTestWithConfiguredVerifierAndFeeManager { function test_revertsIfNotOwner() public { diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierDeactivateFeedTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierDeactivateFeedTest.t.sol similarity index 98% rename from contracts/src/v0.8/llo-feeds/test/verifier/VerifierDeactivateFeedTest.t.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierDeactivateFeedTest.t.sol index 97647c8863..fb52c1c93e 100644 --- a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierDeactivateFeedTest.t.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierDeactivateFeedTest.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.19; import {BaseTestWithConfiguredVerifierAndFeeManager, BaseTestWithMultipleConfiguredDigests} from "./BaseVerifierTest.t.sol"; -import {Verifier} from "../../Verifier.sol"; +import {Verifier} from "../../../v0.3.0/Verifier.sol"; contract VerifierActivateFeedTest is BaseTestWithConfiguredVerifierAndFeeManager { function test_revertsIfNotOwnerActivateFeed() public { diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxyConstructorTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyConstructorTest.t.sol similarity index 77% rename from contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxyConstructorTest.t.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyConstructorTest.t.sol index b085dc8a65..82efd8907b 100644 --- a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxyConstructorTest.t.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyConstructorTest.t.sol @@ -2,8 +2,8 @@ pragma solidity 0.8.19; import {BaseTest} from "./BaseVerifierTest.t.sol"; -import {VerifierProxy} from "../../VerifierProxy.sol"; -import {AccessControllerInterface} from "../../../shared/interfaces/AccessControllerInterface.sol"; +import {VerifierProxy} from "../../../v0.3.0/VerifierProxy.sol"; +import {AccessControllerInterface} from "../../../../shared/interfaces/AccessControllerInterface.sol"; contract VerifierProxyConstructorTest is BaseTest { function test_correctlySetsTheOwner() public { @@ -17,7 +17,7 @@ contract VerifierProxyConstructorTest is BaseTest { assertEq(address(proxy.s_accessController()), accessControllerAddr); } - function test_correctlySetsVersion() public { + function test_correctlySetsVersion() public view { string memory version = s_verifierProxy.typeAndVersion(); assertEq(version, "VerifierProxy 2.0.0"); } diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxyInitializeVerifierTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyInitializeVerifierTest.t.sol similarity index 93% rename from contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxyInitializeVerifierTest.t.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyInitializeVerifierTest.t.sol index e02b14fe56..5537d273be 100644 --- a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxyInitializeVerifierTest.t.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyInitializeVerifierTest.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.19; import {BaseTest} from "./BaseVerifierTest.t.sol"; -import {VerifierProxy} from "../../VerifierProxy.sol"; +import {VerifierProxy} from "../../../v0.3.0/VerifierProxy.sol"; contract VerifierProxyInitializeVerifierTest is BaseTest { bytes32 latestDigest; diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxySetAccessControllerTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxySetAccessControllerTest.t.sol similarity index 92% rename from contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxySetAccessControllerTest.t.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxySetAccessControllerTest.t.sol index 04889e0d5f..03bd6d97ee 100644 --- a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxySetAccessControllerTest.t.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxySetAccessControllerTest.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.19; import {BaseTest} from "./BaseVerifierTest.t.sol"; -import {AccessControllerInterface} from "../../../shared/interfaces/AccessControllerInterface.sol"; +import {AccessControllerInterface} from "../../../../shared/interfaces/AccessControllerInterface.sol"; contract VerifierProxySetAccessControllerTest is BaseTest { event AccessControllerSet(address oldAccessController, address newAccessController); diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxySetVerifierTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxySetVerifierTest.t.sol similarity index 88% rename from contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxySetVerifierTest.t.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxySetVerifierTest.t.sol index ea23f880ba..78e5ff0766 100644 --- a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxySetVerifierTest.t.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxySetVerifierTest.t.sol @@ -3,9 +3,9 @@ pragma solidity 0.8.19; import {BaseTestWithConfiguredVerifierAndFeeManager} from "./BaseVerifierTest.t.sol"; import {IVerifier} from "../../interfaces/IVerifier.sol"; -import {VerifierProxy} from "../../VerifierProxy.sol"; -import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; -import {Common} from "../../libraries/Common.sol"; +import {VerifierProxy} from "../../../v0.3.0/VerifierProxy.sol"; +import {IERC165} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; +import {Common} from "../../../libraries/Common.sol"; contract VerifierProxyInitializeVerifierTest is BaseTestWithConfiguredVerifierAndFeeManager { function test_revertsIfNotCorrectVerifier() public { diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxyTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyTest.t.sol similarity index 87% rename from contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxyTest.t.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyTest.t.sol index ea7e02d740..441626e575 100644 --- a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxyTest.t.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyTest.t.sol @@ -2,8 +2,8 @@ pragma solidity 0.8.19; import {BaseTestWithConfiguredVerifierAndFeeManager} from "./BaseVerifierTest.t.sol"; -import {VerifierProxy} from "../../VerifierProxy.sol"; -import {FeeManager} from "../../FeeManager.sol"; +import {VerifierProxy} from "../../../v0.3.0/VerifierProxy.sol"; +import {FeeManager} from "../../../v0.3.0/FeeManager.sol"; contract VerifierProxyInitializeVerifierTest is BaseTestWithConfiguredVerifierAndFeeManager { function test_setFeeManagerZeroAddress() public { diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxyUnsetVerifierTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyUnsetVerifierTest.t.sol similarity index 95% rename from contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxyUnsetVerifierTest.t.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyUnsetVerifierTest.t.sol index 746aa95574..a51c67e336 100644 --- a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierProxyUnsetVerifierTest.t.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyUnsetVerifierTest.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.19; import {BaseTest, BaseTestWithConfiguredVerifierAndFeeManager} from "./BaseVerifierTest.t.sol"; -import {VerifierProxy} from "../../VerifierProxy.sol"; +import {VerifierProxy} from "../../../v0.3.0/VerifierProxy.sol"; contract VerifierProxyUnsetVerifierTest is BaseTest { function test_revertsIfNotAdmin() public { diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierSetConfigFromSourceTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierSetConfigFromSourceTest.t.sol similarity index 98% rename from contracts/src/v0.8/llo-feeds/test/verifier/VerifierSetConfigFromSourceTest.t.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierSetConfigFromSourceTest.t.sol index 0cd5902161..9ee9b5272a 100644 --- a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierSetConfigFromSourceTest.t.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierSetConfigFromSourceTest.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.19; import {BaseTest, BaseTestWithMultipleConfiguredDigests} from "./BaseVerifierTest.t.sol"; -import {Common} from "../../libraries/Common.sol"; +import {Common} from "../../../libraries/Common.sol"; contract VerifierSetConfigFromSourceTest is BaseTest { function setUp() public virtual override { diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierSetConfigTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierSetConfigTest.t.sol similarity index 98% rename from contracts/src/v0.8/llo-feeds/test/verifier/VerifierSetConfigTest.t.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierSetConfigTest.t.sol index a4e15dcdd4..972ead8123 100644 --- a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierSetConfigTest.t.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierSetConfigTest.t.sol @@ -2,8 +2,8 @@ pragma solidity 0.8.19; import {BaseTest, BaseTestWithMultipleConfiguredDigests} from "./BaseVerifierTest.t.sol"; -import {Verifier} from "../../Verifier.sol"; -import {Common} from "../../libraries/Common.sol"; +import {Verifier} from "../../../v0.3.0/Verifier.sol"; +import {Common} from "../../../libraries/Common.sol"; contract VerifierSetConfigTest is BaseTest { function setUp() public virtual override { diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierTest.t.sol similarity index 88% rename from contracts/src/v0.8/llo-feeds/test/verifier/VerifierTest.t.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierTest.t.sol index 2857b8f4d3..81f65f0c6e 100644 --- a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierTest.t.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierTest.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.19; import {BaseTest} from "./BaseVerifierTest.t.sol"; -import {Verifier} from "../../Verifier.sol"; +import {Verifier} from "../../../v0.3.0/Verifier.sol"; contract VerifierConstructorTest is BaseTest { function test_revertsIfInitializedWithEmptyVerifierProxy() public { @@ -30,12 +30,12 @@ contract VerifierConstructorTest is BaseTest { } contract VerifierSupportsInterfaceTest is BaseTest { - function test_falseIfIsNotCorrectInterface() public { + function test_falseIfIsNotCorrectInterface() public view { bool isInterface = s_verifier.supportsInterface(bytes4("abcd")); assertEq(isInterface, false); } - function test_trueIfIsCorrectInterface() public { + function test_trueIfIsCorrectInterface() public view { bool isInterface = s_verifier.supportsInterface(Verifier.verify.selector); assertEq(isInterface, true); } diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierTestBillingReport.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierTestBillingReport.t.sol similarity index 100% rename from contracts/src/v0.8/llo-feeds/test/verifier/VerifierTestBillingReport.t.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierTestBillingReport.t.sol diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierUnsetConfigTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierUnsetConfigTest.t.sol similarity index 97% rename from contracts/src/v0.8/llo-feeds/test/verifier/VerifierUnsetConfigTest.t.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierUnsetConfigTest.t.sol index cc3c33331d..e192a2e9e0 100644 --- a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierUnsetConfigTest.t.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierUnsetConfigTest.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.19; import {BaseTestWithMultipleConfiguredDigests} from "./BaseVerifierTest.t.sol"; -import {Verifier} from "../../Verifier.sol"; +import {Verifier} from "../../../v0.3.0/Verifier.sol"; contract VerificationdeactivateConfigWhenThereAreMultipleDigestsTest is BaseTestWithMultipleConfiguredDigests { function test_revertsIfCalledByNonOwner() public { diff --git a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierVerifyTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierVerifyTest.t.sol similarity index 97% rename from contracts/src/v0.8/llo-feeds/test/verifier/VerifierVerifyTest.t.sol rename to contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierVerifyTest.t.sol index db7be5ca54..1c14ba974c 100644 --- a/contracts/src/v0.8/llo-feeds/test/verifier/VerifierVerifyTest.t.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierVerifyTest.t.sol @@ -2,10 +2,10 @@ pragma solidity 0.8.19; import {BaseTestWithConfiguredVerifierAndFeeManager} from "./BaseVerifierTest.t.sol"; -import {Verifier} from "../../Verifier.sol"; -import {VerifierProxy} from "../../VerifierProxy.sol"; -import {AccessControllerInterface} from "../../../shared/interfaces/AccessControllerInterface.sol"; -import {Common} from "../../libraries/Common.sol"; +import {Verifier} from "../../../v0.3.0/Verifier.sol"; +import {VerifierProxy} from "../../../v0.3.0/VerifierProxy.sol"; +import {AccessControllerInterface} from "../../../../shared/interfaces/AccessControllerInterface.sol"; +import {Common} from "../../../libraries/Common.sol"; contract VerifierVerifyTest is BaseTestWithConfiguredVerifierAndFeeManager { bytes32[3] internal s_reportContext; @@ -32,7 +32,7 @@ contract VerifierVerifyTest is BaseTestWithConfiguredVerifierAndFeeManager { ); } - function assertReportsEqual(bytes memory response, V1Report memory testReport) public { + function assertReportsEqual(bytes memory response, V1Report memory testReport) public pure { ( bytes32 feedId, uint32 timestamp, diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/DestinationFeeManager.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/DestinationFeeManager.sol new file mode 100644 index 0000000000..08ac1d45f5 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/DestinationFeeManager.sol @@ -0,0 +1,573 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; +import {TypeAndVersionInterface} from "../../interfaces/TypeAndVersionInterface.sol"; +import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; +import {Common} from "../libraries/Common.sol"; +import {IWERC20} from "../../shared/interfaces/IWERC20.sol"; +import {IERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC20.sol"; +import {Math} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/math/Math.sol"; +import {SafeERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IDestinationRewardManager} from "./interfaces/IDestinationRewardManager.sol"; +import {IDestinationFeeManager} from "./interfaces/IDestinationFeeManager.sol"; +import {IDestinationVerifierFeeManager} from "./interfaces/IDestinationVerifierFeeManager.sol"; + +/** + * @title FeeManager + * @author Michael Fletcher + * @author Austin Born + * @notice This contract is used for the handling of fees required for users verifying reports. + */ +contract DestinationFeeManager is + IDestinationFeeManager, + IDestinationVerifierFeeManager, + ConfirmedOwner, + TypeAndVersionInterface +{ + using SafeERC20 for IERC20; + + /// @notice list of subscribers and their discounts subscriberDiscounts[subscriber][feedId][token] + mapping(address => mapping(bytes32 => mapping(address => uint256))) public s_subscriberDiscounts; + + /// @notice map of global discounts + mapping(address => mapping(address => uint256)) public s_globalDiscounts; + + /// @notice keep track of any subsidised link that is owed to the reward manager. + mapping(bytes32 => uint256) public s_linkDeficit; + + /// @notice the total discount that can be applied to a fee, 1e18 = 100% discount + uint64 private constant PERCENTAGE_SCALAR = 1e18; + + /// @notice the LINK token address + address public immutable i_linkAddress; + + /// @notice the native token address + address public immutable i_nativeAddress; + + /// @notice the verifier address + mapping(address => address) public s_verifierAddressList; + + /// @notice the reward manager address + IDestinationRewardManager public i_rewardManager; + + // @notice the mask to apply to get the report version + bytes32 private constant REPORT_VERSION_MASK = 0xffff000000000000000000000000000000000000000000000000000000000000; + + // @notice the different report versions + bytes32 private constant REPORT_V1 = 0x0001000000000000000000000000000000000000000000000000000000000000; + + /// @notice the surcharge fee to be paid if paying in native + uint256 public s_nativeSurcharge; + + /// @notice the error thrown if the discount or surcharge is invalid + error InvalidSurcharge(); + + /// @notice the error thrown if the discount is invalid + error InvalidDiscount(); + + /// @notice the error thrown if the address is invalid + error InvalidAddress(); + + /// @notice thrown if msg.value is supplied with a bad quote + error InvalidDeposit(); + + /// @notice thrown if a report has expired + error ExpiredReport(); + + /// @notice thrown if a report has no quote + error InvalidQuote(); + + // @notice thrown when the caller is not authorized + error Unauthorized(); + + // @notice thrown when trying to clear a zero deficit + error ZeroDeficit(); + + /// @notice thrown when trying to pay an address that cannot except funds + error InvalidReceivingAddress(); + + /// @notice thrown when trying to bulk verify reports where theres not a matching number of poolIds + error PoolIdMismatch(); + + /// @notice Emitted whenever a subscriber's discount is updated + /// @param subscriber address of the subscriber to update discounts for + /// @param feedId Feed ID for the discount + /// @param token Token address for the discount + /// @param discount Discount to apply, in relation to the PERCENTAGE_SCALAR + event SubscriberDiscountUpdated(address indexed subscriber, bytes32 indexed feedId, address token, uint64 discount); + + /// @notice Emitted when updating the native surcharge + /// @param newSurcharge Surcharge amount to apply relative to PERCENTAGE_SCALAR + event NativeSurchargeUpdated(uint64 newSurcharge); + + /// @notice Emits when this contract does not have enough LINK to send to the reward manager when paying in native + /// @param rewards Config digest and link fees which could not be subsidised + event InsufficientLink(IDestinationRewardManager.FeePayment[] rewards); + + /// @notice Emitted when funds are withdrawn + /// @param adminAddress Address of the admin + /// @param recipient Address of the recipient + /// @param assetAddress Address of the asset withdrawn + /// @param quantity Amount of the asset withdrawn + event Withdraw(address adminAddress, address recipient, address assetAddress, uint192 quantity); + + /// @notice Emits when a deficit has been cleared for a particular config digest + /// @param configDigest Config digest of the deficit cleared + /// @param linkQuantity Amount of LINK required to pay the deficit + event LinkDeficitCleared(bytes32 indexed configDigest, uint256 linkQuantity); + + /// @notice Emits when a fee has been processed + /// @param configDigest Config digest of the fee processed + /// @param subscriber Address of the subscriber who paid the fee + /// @param fee Fee paid + /// @param reward Reward paid + /// @param appliedDiscount Discount applied to the fee + event DiscountApplied( + bytes32 indexed configDigest, + address indexed subscriber, + Common.Asset fee, + Common.Asset reward, + uint256 appliedDiscount + ); + + /** + * @notice Construct the FeeManager contract + * @param _linkAddress The address of the LINK token + * @param _nativeAddress The address of the wrapped ERC-20 version of the native token (represents fee in native or wrapped) + * @param _verifierAddress The address of the verifier contract + * @param _rewardManagerAddress The address of the reward manager contract + */ + constructor( + address _linkAddress, + address _nativeAddress, + address _verifierAddress, + address _rewardManagerAddress + ) ConfirmedOwner(msg.sender) { + if ( + _linkAddress == address(0) || + _nativeAddress == address(0) || + _verifierAddress == address(0) || + _rewardManagerAddress == address(0) + ) revert InvalidAddress(); + + i_linkAddress = _linkAddress; + i_nativeAddress = _nativeAddress; + s_verifierAddressList[_verifierAddress] = _verifierAddress; + i_rewardManager = IDestinationRewardManager(_rewardManagerAddress); + + IERC20(i_linkAddress).approve(address(i_rewardManager), type(uint256).max); + } + + modifier onlyVerifier() { + if (msg.sender != s_verifierAddressList[msg.sender]) revert Unauthorized(); + _; + } + + /// @inheritdoc TypeAndVersionInterface + function typeAndVersion() external pure override returns (string memory) { + return "DestinationFeeManager 0.4.0"; + } + + /// @inheritdoc IERC165 + function supportsInterface(bytes4 interfaceId) external pure override returns (bool) { + return + interfaceId == type(IDestinationFeeManager).interfaceId || + interfaceId == type(IDestinationVerifierFeeManager).interfaceId; + } + + /// @inheritdoc IDestinationVerifierFeeManager + function processFee( + bytes32 recipient, + bytes calldata payload, + bytes calldata parameterPayload, + address subscriber + ) external payable override onlyVerifier { + (Common.Asset memory fee, Common.Asset memory reward, uint256 appliedDiscount) = _calculateFee( + payload, + parameterPayload, + subscriber + ); + + if (fee.amount == 0) { + _tryReturnChange(subscriber, msg.value); + return; + } + + IDestinationFeeManager.FeeAndReward[] memory feeAndReward = new IDestinationFeeManager.FeeAndReward[](1); + feeAndReward[0] = IDestinationFeeManager.FeeAndReward(recipient, fee, reward, appliedDiscount); + + if (fee.assetAddress == i_linkAddress) { + _handleFeesAndRewards(subscriber, feeAndReward, 1, 0); + } else { + _handleFeesAndRewards(subscriber, feeAndReward, 0, 1); + } + } + + /// @inheritdoc IDestinationVerifierFeeManager + function processFeeBulk( + bytes32[] memory poolIds, + bytes[] calldata payloads, + bytes calldata parameterPayload, + address subscriber + ) external payable override onlyVerifier { + //poolIDs are mapped to payloads, so they should be the same length + if (poolIds.length != payloads.length) revert PoolIdMismatch(); + + IDestinationFeeManager.FeeAndReward[] memory feesAndRewards = new IDestinationFeeManager.FeeAndReward[]( + payloads.length + ); + + //keep track of the number of fees to prevent over initialising the FeePayment array within _convertToLinkAndNativeFees + uint256 numberOfLinkFees; + uint256 numberOfNativeFees; + + uint256 feesAndRewardsIndex; + for (uint256 i; i < payloads.length; ++i) { + if (poolIds[i] == bytes32(0)) revert InvalidAddress(); + + (Common.Asset memory fee, Common.Asset memory reward, uint256 appliedDiscount) = _calculateFee( + payloads[i], + parameterPayload, + subscriber + ); + + if (fee.amount != 0) { + feesAndRewards[feesAndRewardsIndex++] = IDestinationFeeManager.FeeAndReward( + poolIds[i], + fee, + reward, + appliedDiscount + ); + + unchecked { + //keep track of some tallys to make downstream calculations more efficient + if (fee.assetAddress == i_linkAddress) { + ++numberOfLinkFees; + } else { + ++numberOfNativeFees; + } + } + } + } + + if (numberOfLinkFees != 0 || numberOfNativeFees != 0) { + _handleFeesAndRewards(subscriber, feesAndRewards, numberOfLinkFees, numberOfNativeFees); + } else { + _tryReturnChange(subscriber, msg.value); + } + } + + /// @inheritdoc IDestinationFeeManager + function getFeeAndReward( + address subscriber, + bytes memory report, + address quoteAddress + ) public view returns (Common.Asset memory, Common.Asset memory, uint256) { + Common.Asset memory fee; + Common.Asset memory reward; + + //get the feedId from the report + bytes32 feedId = bytes32(report); + + //the report needs to be a support version + bytes32 reportVersion = _getReportVersion(feedId); + + //version 1 of the reports don't require quotes, so the fee will be 0 + if (reportVersion == REPORT_V1) { + fee.assetAddress = i_nativeAddress; + reward.assetAddress = i_linkAddress; + return (fee, reward, 0); + } + + //verify the quote payload is a supported token + if (quoteAddress != i_nativeAddress && quoteAddress != i_linkAddress) { + revert InvalidQuote(); + } + + //decode the report depending on the version + uint256 linkQuantity; + uint256 nativeQuantity; + uint256 expiresAt; + (, , , nativeQuantity, linkQuantity, expiresAt) = abi.decode( + report, + (bytes32, uint32, uint32, uint192, uint192, uint32) + ); + + //read the timestamp bytes from the report data and verify it has not expired + if (expiresAt < block.timestamp) { + revert ExpiredReport(); + } + + //check if feed discount has been applied + uint256 discount = s_subscriberDiscounts[subscriber][feedId][quoteAddress]; + + if (discount == 0) { + //check if a global discount has been applied + discount = s_globalDiscounts[subscriber][quoteAddress]; + } + + //the reward is always set in LINK + reward.assetAddress = i_linkAddress; + reward.amount = Math.ceilDiv(linkQuantity * (PERCENTAGE_SCALAR - discount), PERCENTAGE_SCALAR); + + //calculate either the LINK fee or native fee if it's within the report + if (quoteAddress == i_linkAddress) { + fee.assetAddress = i_linkAddress; + fee.amount = reward.amount; + } else { + uint256 surchargedFee = Math.ceilDiv(nativeQuantity * (PERCENTAGE_SCALAR + s_nativeSurcharge), PERCENTAGE_SCALAR); + + fee.assetAddress = i_nativeAddress; + fee.amount = Math.ceilDiv(surchargedFee * (PERCENTAGE_SCALAR - discount), PERCENTAGE_SCALAR); + } + + //return the fee + return (fee, reward, discount); + } + + /// @inheritdoc IDestinationVerifierFeeManager + function setFeeRecipients( + bytes32 configDigest, + Common.AddressAndWeight[] calldata rewardRecipientAndWeights + ) external onlyVerifier { + i_rewardManager.setRewardRecipients(configDigest, rewardRecipientAndWeights); + } + + /// @inheritdoc IDestinationFeeManager + function setNativeSurcharge(uint64 surcharge) external onlyOwner { + if (surcharge > PERCENTAGE_SCALAR) revert InvalidSurcharge(); + + s_nativeSurcharge = surcharge; + + emit NativeSurchargeUpdated(surcharge); + } + + /// @inheritdoc IDestinationFeeManager + function updateSubscriberDiscount( + address subscriber, + bytes32 feedId, + address token, + uint64 discount + ) external onlyOwner { + //make sure the discount is not greater than the total discount that can be applied + if (discount > PERCENTAGE_SCALAR) revert InvalidDiscount(); + //make sure the token is either LINK or native + if (token != i_linkAddress && token != i_nativeAddress) revert InvalidAddress(); + + s_subscriberDiscounts[subscriber][feedId][token] = discount; + + emit SubscriberDiscountUpdated(subscriber, feedId, token, discount); + } + + function updateSubscriberGlobalDiscount(address subscriber, address token, uint64 discount) external onlyOwner { + //make sure the discount is not greater than the total discount that can be applied + if (discount > PERCENTAGE_SCALAR) revert InvalidDiscount(); + //make sure the token is either LINK or native + if (token != i_linkAddress && token != i_nativeAddress) revert InvalidAddress(); + + s_globalDiscounts[subscriber][token] = discount; + + emit SubscriberDiscountUpdated(subscriber, bytes32(0), token, discount); + } + + /// @inheritdoc IDestinationFeeManager + function withdraw(address assetAddress, address recipient, uint192 quantity) external onlyOwner { + //address 0 is used to withdraw native in the context of withdrawing + if (assetAddress == address(0)) { + (bool success, ) = payable(recipient).call{value: quantity}(""); + + if (!success) revert InvalidReceivingAddress(); + return; + } + + //withdraw the requested asset + IERC20(assetAddress).safeTransfer(recipient, quantity); + + //emit event when funds are withdrawn + emit Withdraw(msg.sender, recipient, assetAddress, uint192(quantity)); + } + + /// @inheritdoc IDestinationFeeManager + function linkAvailableForPayment() external view returns (uint256) { + //return the amount of LINK this contact has available to pay rewards + return IERC20(i_linkAddress).balanceOf(address(this)); + } + + /** + * @notice Gets the current version of the report that is encoded as the last two bytes of the feed + * @param feedId feed id to get the report version for + */ + function _getReportVersion(bytes32 feedId) internal pure returns (bytes32) { + return REPORT_VERSION_MASK & feedId; + } + + function _calculateFee( + bytes calldata payload, + bytes calldata parameterPayload, + address subscriber + ) internal view returns (Common.Asset memory, Common.Asset memory, uint256) { + if (subscriber == address(this)) revert InvalidAddress(); + + //decode the report from the payload + (, bytes memory report) = abi.decode(payload, (bytes32[3], bytes)); + + //get the feedId from the report + bytes32 feedId = bytes32(report); + + //v1 doesn't need a quote payload, so skip the decoding + address quote; + if (_getReportVersion(feedId) != REPORT_V1) { + //decode the quote from the bytes + (quote) = abi.decode(parameterPayload, (address)); + } + + //decode the fee, it will always be native or LINK + return getFeeAndReward(subscriber, report, quote); + } + + function _handleFeesAndRewards( + address subscriber, + IDestinationFeeManager.FeeAndReward[] memory feesAndRewards, + uint256 numberOfLinkFees, + uint256 numberOfNativeFees + ) internal { + IDestinationRewardManager.FeePayment[] memory linkRewards = new IDestinationRewardManager.FeePayment[]( + numberOfLinkFees + ); + IDestinationRewardManager.FeePayment[] memory nativeFeeLinkRewards = new IDestinationRewardManager.FeePayment[]( + numberOfNativeFees + ); + + uint256 totalNativeFee; + uint256 totalNativeFeeLinkValue; + + uint256 linkRewardsIndex; + uint256 nativeFeeLinkRewardsIndex; + + uint256 totalNumberOfFees = numberOfLinkFees + numberOfNativeFees; + for (uint256 i; i < totalNumberOfFees; ++i) { + if (feesAndRewards[i].fee.assetAddress == i_linkAddress) { + linkRewards[linkRewardsIndex++] = IDestinationRewardManager.FeePayment( + feesAndRewards[i].configDigest, + uint192(feesAndRewards[i].reward.amount) + ); + } else { + nativeFeeLinkRewards[nativeFeeLinkRewardsIndex++] = IDestinationRewardManager.FeePayment( + feesAndRewards[i].configDigest, + uint192(feesAndRewards[i].reward.amount) + ); + totalNativeFee += feesAndRewards[i].fee.amount; + totalNativeFeeLinkValue += feesAndRewards[i].reward.amount; + } + + if (feesAndRewards[i].appliedDiscount != 0) { + emit DiscountApplied( + feesAndRewards[i].configDigest, + subscriber, + feesAndRewards[i].fee, + feesAndRewards[i].reward, + feesAndRewards[i].appliedDiscount + ); + } + } + + //keep track of change in case of any over payment + uint256 change; + + if (msg.value != 0) { + //there must be enough to cover the fee + if (totalNativeFee > msg.value) revert InvalidDeposit(); + + //wrap the amount required to pay the fee & approve as the subscriber paid in wrapped native + IWERC20(i_nativeAddress).deposit{value: totalNativeFee}(); + + unchecked { + //msg.value is always >= to fee.amount + change = msg.value - totalNativeFee; + } + } else { + if (totalNativeFee != 0) { + //subscriber has paid in wrapped native, so transfer the native to this contract + IERC20(i_nativeAddress).safeTransferFrom(subscriber, address(this), totalNativeFee); + } + } + + if (linkRewards.length != 0) { + i_rewardManager.onFeePaid(linkRewards, subscriber); + } + + if (nativeFeeLinkRewards.length != 0) { + //distribute subsidised fees paid in Native + if (totalNativeFeeLinkValue > IERC20(i_linkAddress).balanceOf(address(this))) { + // If not enough LINK on this contract to forward for rewards, tally the deficit to be paid by out-of-band LINK + for (uint256 i; i < nativeFeeLinkRewards.length; ++i) { + unchecked { + //we have previously tallied the fees, any overflows would have already reverted + s_linkDeficit[nativeFeeLinkRewards[i].poolId] += nativeFeeLinkRewards[i].amount; + } + } + + emit InsufficientLink(nativeFeeLinkRewards); + } else { + //distribute the fees + i_rewardManager.onFeePaid(nativeFeeLinkRewards, address(this)); + } + } + + // a refund may be needed if the payee has paid in excess of the fee + _tryReturnChange(subscriber, change); + } + + function _tryReturnChange(address subscriber, uint256 quantity) internal { + if (quantity != 0) { + payable(subscriber).transfer(quantity); + } + } + + /// @inheritdoc IDestinationFeeManager + function payLinkDeficit(bytes32 configDigest) external onlyOwner { + uint256 deficit = s_linkDeficit[configDigest]; + + if (deficit == 0) revert ZeroDeficit(); + + delete s_linkDeficit[configDigest]; + + IDestinationRewardManager.FeePayment[] memory deficitFeePayment = new IDestinationRewardManager.FeePayment[](1); + + deficitFeePayment[0] = IDestinationRewardManager.FeePayment(configDigest, uint192(deficit)); + + i_rewardManager.onFeePaid(deficitFeePayment, address(this)); + + emit LinkDeficitCleared(configDigest, deficit); + } + + /// @inheritdoc IDestinationFeeManager + function addVerifier(address verifierAddress) external onlyOwner { + if (verifierAddress == address(0)) revert InvalidAddress(); + //check doesn't already exist + if (s_verifierAddressList[verifierAddress] != address(0)) revert InvalidAddress(); + s_verifierAddressList[verifierAddress] = verifierAddress; + } + + /// @inheritdoc IDestinationFeeManager + function removeVerifier(address verifierAddress) external onlyOwner { + if (verifierAddress == address(0)) revert InvalidAddress(); + //check doesn't already exist + if (s_verifierAddressList[verifierAddress] == address(0)) revert InvalidAddress(); + delete s_verifierAddressList[verifierAddress]; + } + + /// @inheritdoc IDestinationFeeManager + function setRewardManager(address rewardManagerAddress) external onlyOwner { + if (rewardManagerAddress == address(0)) revert InvalidAddress(); + + if (!IERC165(rewardManagerAddress).supportsInterface(type(IDestinationRewardManager).interfaceId)) { + revert InvalidAddress(); + } + + IERC20(i_linkAddress).approve(address(i_rewardManager), 0); + i_rewardManager = IDestinationRewardManager(rewardManagerAddress); + IERC20(i_linkAddress).approve(address(rewardManagerAddress), type(uint256).max); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/DestinationRewardManager.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/DestinationRewardManager.sol new file mode 100644 index 0000000000..4b4c1f50ef --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/DestinationRewardManager.sol @@ -0,0 +1,328 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; +import {IDestinationRewardManager} from "./interfaces/IDestinationRewardManager.sol"; +import {IERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC20.sol"; +import {TypeAndVersionInterface} from "../../interfaces/TypeAndVersionInterface.sol"; +import {Common} from "../libraries/Common.sol"; +import {SafeERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol"; + +/** + * @title DestinationRewardManager + * @author Michael Fletcher + * @author Austin Born + * @notice This contract will be used to reward any configured recipients within a pool. Recipients will receive a share of their pool relative to their configured weight. + */ +contract DestinationRewardManager is IDestinationRewardManager, ConfirmedOwner, TypeAndVersionInterface { + using SafeERC20 for IERC20; + + // @dev The mapping of total fees collected for a particular pot: s_totalRewardRecipientFees[poolId] + mapping(bytes32 => uint256) public s_totalRewardRecipientFees; + + // @dev The mapping of fee balances for each pot last time the recipient claimed: s_totalRewardRecipientFeesLastClaimedAmounts[poolId][recipient] + mapping(bytes32 => mapping(address => uint256)) public s_totalRewardRecipientFeesLastClaimedAmounts; + + // @dev The mapping of RewardRecipient weights for a particular poolId: s_rewardRecipientWeights[poolId][rewardRecipient]. + mapping(bytes32 => mapping(address => uint256)) public s_rewardRecipientWeights; + + // @dev Keep track of the reward recipient weights that have been set to prevent duplicates + mapping(bytes32 => bool) public s_rewardRecipientWeightsSet; + + // @dev Store a list of pool ids that have been registered, to make off chain lookups easier + bytes32[] public s_registeredPoolIds; + + // @dev The address for the LINK contract + address public immutable i_linkAddress; + + // The total weight of all RewardRecipients. 1e18 = 100% of the pool fees + uint64 private constant PERCENTAGE_SCALAR = 1e18; + + // The fee manager address + mapping(address => address) public s_feeManagerAddressList; + + // @notice Thrown whenever the RewardRecipient weights are invalid + error InvalidWeights(); + + // @notice Thrown when any given address is invalid + error InvalidAddress(); + + // @notice Thrown when the pool id is invalid + error InvalidPoolId(); + + // @notice Thrown when the calling contract is not within the authorized contracts + error Unauthorized(); + + // @notice Thrown when getAvailableRewardPoolIds parameters are incorrectly set + error InvalidPoolLength(); + + // Events emitted upon state change + event RewardRecipientsUpdated(bytes32 indexed poolId, Common.AddressAndWeight[] newRewardRecipients); + event RewardsClaimed(bytes32 indexed poolId, address indexed recipient, uint192 quantity); + event FeeManagerUpdated(address newFeeManagerAddress); + event FeePaid(FeePayment[] payments, address payer); + + /** + * @notice Constructor + * @param linkAddress address of the wrapped LINK token + */ + constructor(address linkAddress) ConfirmedOwner(msg.sender) { + //ensure that the address ia not zero + if (linkAddress == address(0)) revert InvalidAddress(); + + i_linkAddress = linkAddress; + } + + // @inheritdoc TypeAndVersionInterface + function typeAndVersion() external pure override returns (string memory) { + return "DestinationRewardManager 0.4.0"; + } + + // @inheritdoc IERC165 + function supportsInterface(bytes4 interfaceId) external pure override returns (bool) { + return interfaceId == type(IDestinationRewardManager).interfaceId; + } + + modifier onlyOwnerOrFeeManager() { + if (msg.sender != s_feeManagerAddressList[msg.sender] && msg.sender != owner()) revert Unauthorized(); + _; + } + + modifier onlyOwnerOrRecipientInPool(bytes32 poolId) { + if (s_rewardRecipientWeights[poolId][msg.sender] == 0 && msg.sender != owner()) revert Unauthorized(); + _; + } + + modifier onlyFeeManager() { + if (msg.sender != s_feeManagerAddressList[msg.sender]) revert Unauthorized(); + _; + } + + /// @inheritdoc IDestinationRewardManager + function onFeePaid(FeePayment[] calldata payments, address payer) external override onlyFeeManager { + uint256 totalFeeAmount; + for (uint256 i; i < payments.length; ++i) { + unchecked { + //the total amount for any ERC-20 asset cannot exceed 2^256 - 1 + //see https://github.com/OpenZeppelin/openzeppelin-contracts/blob/36bf1e46fa811f0f07d38eb9cfbc69a955f300ce/contracts/token/ERC20/ERC20.sol#L266 + //for example implementation. + s_totalRewardRecipientFees[payments[i].poolId] += payments[i].amount; + + //tally the total payable fees + totalFeeAmount += payments[i].amount; + } + } + + //transfer the fees to this contract + IERC20(i_linkAddress).safeTransferFrom(payer, address(this), totalFeeAmount); + + emit FeePaid(payments, payer); + } + + /// @inheritdoc IDestinationRewardManager + function claimRewards(bytes32[] memory poolIds) external override { + _claimRewards(msg.sender, poolIds); + } + + // wrapper impl for claimRewards + function _claimRewards(address recipient, bytes32[] memory poolIds) internal returns (uint256) { + //get the total amount claimable for this recipient + uint256 claimAmount; + + //loop and claim all the rewards in the poolId pot + for (uint256 i; i < poolIds.length; ++i) { + //get the poolId to be claimed + bytes32 poolId = poolIds[i]; + + //get the total fees for the pot + uint256 totalFeesInPot = s_totalRewardRecipientFees[poolId]; + + unchecked { + //avoid unnecessary storage reads if there's no fees in the pot + if (totalFeesInPot == 0) continue; + + //get the claimable amount for this recipient, this calculation will never exceed the amount in the pot + uint256 claimableAmount = totalFeesInPot - s_totalRewardRecipientFeesLastClaimedAmounts[poolId][recipient]; + + //calculate the recipients share of the fees, which is their weighted share of the difference between the last amount they claimed and the current amount in the pot. This can never be more than the total amount in existence + uint256 recipientShare = (claimableAmount * s_rewardRecipientWeights[poolId][recipient]) / PERCENTAGE_SCALAR; + + //if there's no fees to claim, continue as there's nothing to update + if (recipientShare == 0) continue; + + //keep track of the total amount claimable, this can never be more than the total amount in existence + claimAmount += recipientShare; + + //set the current total amount of fees in the pot as it's used to calculate future claims + s_totalRewardRecipientFeesLastClaimedAmounts[poolId][recipient] = totalFeesInPot; + + //emit event if the recipient has rewards to claim + emit RewardsClaimed(poolIds[i], recipient, uint192(recipientShare)); + } + } + + //check if there's any rewards to claim in the given poolId + if (claimAmount != 0) { + //transfer the reward to the recipient + IERC20(i_linkAddress).safeTransfer(recipient, claimAmount); + } + + return claimAmount; + } + + /// @inheritdoc IDestinationRewardManager + function setRewardRecipients( + bytes32 poolId, + Common.AddressAndWeight[] calldata rewardRecipientAndWeights + ) external override onlyOwnerOrFeeManager { + //revert if there are no recipients to set + if (rewardRecipientAndWeights.length == 0) revert InvalidAddress(); + + //check that the weights have not been previously set + if (s_rewardRecipientWeightsSet[poolId]) revert InvalidPoolId(); + + //keep track of the registered poolIds to make off chain lookups easier + s_registeredPoolIds.push(poolId); + + //keep track of which pools have had their reward recipients set + s_rewardRecipientWeightsSet[poolId] = true; + + //set the reward recipients, this will only be called once and contain the full set of RewardRecipients with a total weight of 100% + _setRewardRecipientWeights(poolId, rewardRecipientAndWeights, PERCENTAGE_SCALAR); + + emit RewardRecipientsUpdated(poolId, rewardRecipientAndWeights); + } + + function _setRewardRecipientWeights( + bytes32 poolId, + Common.AddressAndWeight[] calldata rewardRecipientAndWeights, + uint256 expectedWeight + ) internal { + //we can't update the weights if it contains duplicates + if (Common._hasDuplicateAddresses(rewardRecipientAndWeights)) revert InvalidAddress(); + + //loop all the reward recipients and validate the weight and address + uint256 totalWeight; + for (uint256 i; i < rewardRecipientAndWeights.length; ++i) { + //get the weight + uint256 recipientWeight = rewardRecipientAndWeights[i].weight; + //get the address + address recipientAddress = rewardRecipientAndWeights[i].addr; + + //ensure the reward recipient address is not zero + if (recipientAddress == address(0)) revert InvalidAddress(); + + //save/overwrite the weight for the reward recipient + s_rewardRecipientWeights[poolId][recipientAddress] = recipientWeight; + + unchecked { + //keep track of the cumulative weight, this cannot overflow as the total weight is restricted at 1e18 + totalWeight += recipientWeight; + } + } + + //if total weight is not met, the fees will either be under or over distributed + if (totalWeight != expectedWeight) revert InvalidWeights(); + } + + /// @inheritdoc IDestinationRewardManager + function updateRewardRecipients( + bytes32 poolId, + Common.AddressAndWeight[] calldata newRewardRecipients + ) external override onlyOwner { + //create an array of poolIds to pass to _claimRewards if required + bytes32[] memory poolIds = new bytes32[](1); + poolIds[0] = poolId; + + //loop all the reward recipients and claim their rewards before updating their weights + uint256 existingTotalWeight; + for (uint256 i; i < newRewardRecipients.length; ++i) { + //get the address + address recipientAddress = newRewardRecipients[i].addr; + //get the existing weight + uint256 existingWeight = s_rewardRecipientWeights[poolId][recipientAddress]; + + //if a recipient is updated, the rewards must be claimed first as they can't claim previous fees at the new weight + _claimRewards(newRewardRecipients[i].addr, poolIds); + + unchecked { + //keep tally of the weights so that the expected collective weight is known + existingTotalWeight += existingWeight; + } + } + + //update the reward recipients, if the new collective weight isn't equal to the previous collective weight, the fees will either be under or over distributed + _setRewardRecipientWeights(poolId, newRewardRecipients, existingTotalWeight); + + //emit event + emit RewardRecipientsUpdated(poolId, newRewardRecipients); + } + + /// @inheritdoc IDestinationRewardManager + function payRecipients(bytes32 poolId, address[] calldata recipients) external onlyOwnerOrRecipientInPool(poolId) { + //convert poolIds to an array to match the interface of _claimRewards + bytes32[] memory poolIdsArray = new bytes32[](1); + poolIdsArray[0] = poolId; + + //loop each recipient and claim the rewards for each of the pools and assets + for (uint256 i; i < recipients.length; ++i) { + _claimRewards(recipients[i], poolIdsArray); + } + } + + /// @inheritdoc IDestinationRewardManager + function addFeeManager(address newFeeManagerAddress) external onlyOwner { + if (newFeeManagerAddress == address(0)) revert InvalidAddress(); + if (s_feeManagerAddressList[newFeeManagerAddress] != address(0)) revert InvalidAddress(); + + s_feeManagerAddressList[newFeeManagerAddress] = newFeeManagerAddress; + + emit FeeManagerUpdated(newFeeManagerAddress); + } + + /// @inheritdoc IDestinationRewardManager + function removeFeeManager(address feeManagerAddress) external onlyOwner { + if (s_feeManagerAddressList[feeManagerAddress] == address(0)) revert InvalidAddress(); + delete s_feeManagerAddressList[feeManagerAddress]; + } + + /// @inheritdoc IDestinationRewardManager + function getAvailableRewardPoolIds( + address recipient, + uint256 startIndex, + uint256 endIndex + ) external view returns (bytes32[] memory) { + //get the length of the pool ids which we will loop through and potentially return + uint256 registeredPoolIdsLength = s_registeredPoolIds.length; + + uint256 lastIndex = endIndex > registeredPoolIdsLength ? registeredPoolIdsLength : endIndex; + + if (startIndex > lastIndex) revert InvalidPoolLength(); + + //create a new array with the maximum amount of potential pool ids + bytes32[] memory claimablePoolIds = new bytes32[](lastIndex - startIndex); + //we want the pools which a recipient has funds for to be sequential, so we need to keep track of the index + uint256 poolIdArrayIndex; + + //loop all the pool ids, and check if the recipient has a registered weight and a claimable amount + for (uint256 i = startIndex; i < lastIndex; ++i) { + //get the poolId + bytes32 poolId = s_registeredPoolIds[i]; + + //if the recipient has a weight, they are a recipient of this poolId + if (s_rewardRecipientWeights[poolId][recipient] != 0) { + //get the total in this pool + uint256 totalPoolAmount = s_totalRewardRecipientFees[poolId]; + //if the recipient has any LINK, then add the poolId to the array + unchecked { + //s_totalRewardRecipientFeesLastClaimedAmounts can never exceed total pool amount, and the number of pools can't exceed the max array length + if (totalPoolAmount - s_totalRewardRecipientFeesLastClaimedAmounts[poolId][recipient] != 0) { + claimablePoolIds[poolIdArrayIndex++] = poolId; + } + } + } + } + + return claimablePoolIds; + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/DestinationVerifier.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/DestinationVerifier.sol new file mode 100644 index 0000000000..8ab0f6acc2 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/DestinationVerifier.sol @@ -0,0 +1,443 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; +import {IDestinationVerifier} from "./interfaces/IDestinationVerifier.sol"; +import {TypeAndVersionInterface} from "../../interfaces/TypeAndVersionInterface.sol"; +import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; +import {Common} from "../libraries/Common.sol"; +import {IAccessController} from "../../shared/interfaces/IAccessController.sol"; +import {IDestinationVerifierProxy} from "./interfaces/IDestinationVerifierProxy.sol"; +import {IDestinationVerifierProxyVerifier} from "./interfaces/IDestinationVerifierProxyVerifier.sol"; +import {IDestinationVerifierFeeManager} from "./interfaces/IDestinationVerifierFeeManager.sol"; + +// OCR2 standard +uint256 constant MAX_NUM_ORACLES = 31; + +/** + * @title DestinationVerifier + * @author Michael Fletcher + * @notice This contract will be used to verify reports based on the oracle signatures. This is not the source verifier which required individual fee configurations, instead, this checks that a report has been signed by one of the configured oracles. + */ +contract DestinationVerifier is + IDestinationVerifier, + IDestinationVerifierProxyVerifier, + ConfirmedOwner, + TypeAndVersionInterface +{ + /// @notice The list of DON configurations by hash(address|donConfigId) - set to true if the signer is part of the config + mapping(bytes32 => bool) private s_signerByAddressAndDonConfigId; + + /// array of DON configs + DonConfig[] private s_donConfigs; + + /// @notice The address of the verifierProxy + address public s_feeManager; + + /// @notice The address of the access controller + address public s_accessController; + + /// @notice The address of the verifierProxy + IDestinationVerifierProxy public immutable i_verifierProxy; + + /// @notice This error is thrown whenever trying to set a config + /// with a fault tolerance of 0 + error FaultToleranceMustBePositive(); + + /// @notice This error is thrown whenever a report is signed + /// with more than the max number of signers + /// @param numSigners The number of signers who have signed the report + /// @param maxSigners The maximum number of signers that can sign a report + error ExcessSigners(uint256 numSigners, uint256 maxSigners); + + /// @notice This error is thrown whenever a report is signed or expected to be signed with less than the minimum number of signers + /// @param numSigners The number of signers who have signed the report + /// @param minSigners The minimum number of signers that need to sign a report + error InsufficientSigners(uint256 numSigners, uint256 minSigners); + + /// @notice This error is thrown whenever a report is submitted with no signatures + error NoSigners(); + + /// @notice This error is thrown whenever a DonConfig already exists + /// @param donConfigId The ID of the DonConfig that already exists + error DonConfigAlreadyExists(bytes24 donConfigId); + + /// @notice This error is thrown whenever the R and S signer components + /// have different lengths + /// @param rsLength The number of r signature components + /// @param ssLength The number of s signature components + error MismatchedSignatures(uint256 rsLength, uint256 ssLength); + + /// @notice This error is thrown whenever setting a config with duplicate signatures + error NonUniqueSignatures(); + + /* @notice This error is thrown whenever a report fails to verify. This error be thrown for multiple reasons and it's purposely like + * this to prevent information being leaked about the verification process which could be used to enable free verifications maliciously + */ + error BadVerification(); + + /// @notice This error is thrown whenever a zero address is passed + error ZeroAddress(); + + /// @notice This error is thrown when the fee manager at an address does + /// not conform to the fee manager interface + error FeeManagerInvalid(); + + /// @notice This error is thrown whenever an address tries + /// to execute a verification that it is not authorized to do so + error AccessForbidden(); + + /// @notice This error is thrown whenever a config does not exist + error DonConfigDoesNotExist(); + + /// @notice This error is thrown when the activation time is either in the future or less than the current configs + error BadActivationTime(); + + /// @notice This event is emitted when a new report is verified. + /// It is used to keep a historical record of verified reports. + event ReportVerified(bytes32 indexed feedId, address requester); + + /// @notice This event is emitted whenever a configuration is activated or deactivated + event ConfigActivated(bytes24 donConfigId, bool isActive); + + /// @notice This event is emitted whenever a configuration is removed + event ConfigRemoved(bytes24 donConfigId); + + /// @notice event is emitted whenever a new DON Config is set + event ConfigSet( + bytes24 indexed donConfigId, + address[] signers, + uint8 f, + Common.AddressAndWeight[] recipientAddressesAndWeights, + uint16 donConfigIndex + ); + + /// @notice This event is emitted when a new fee manager is set + /// @param oldFeeManager The old fee manager address + /// @param newFeeManager The new fee manager address + event FeeManagerSet(address oldFeeManager, address newFeeManager); + + /// @notice This event is emitted when a new access controller is set + /// @param oldAccessController The old access controller address + /// @param newAccessController The new access controller address + event AccessControllerSet(address oldAccessController, address newAccessController); + + struct DonConfig { + // The ID of the DonConfig + bytes24 donConfigId; + // Fault tolerance of the DON + uint8 f; + // Whether the config is active + bool isActive; + // The time the config was set + uint32 activationTime; + } + + constructor(address verifierProxy) ConfirmedOwner(msg.sender) { + if (verifierProxy == address(0)) { + revert ZeroAddress(); + } + + i_verifierProxy = IDestinationVerifierProxy(verifierProxy); + } + + /// @inheritdoc IDestinationVerifierProxyVerifier + function verify( + bytes calldata signedReport, + bytes calldata parameterPayload, + address sender + ) external payable override onlyProxy checkAccess(sender) returns (bytes memory) { + (bytes memory verifierResponse, bytes32 donConfigId) = _verify(signedReport, sender); + + address fm = s_feeManager; + if (fm != address(0)) { + //process the fee and catch the error + try + IDestinationVerifierFeeManager(fm).processFee{value: msg.value}( + donConfigId, + signedReport, + parameterPayload, + sender + ) + { + //do nothing + } catch { + // we purposefully obfuscate the error here to prevent information leaking leading to free verifications + revert BadVerification(); + } + } + + return verifierResponse; + } + + /// @inheritdoc IDestinationVerifierProxyVerifier + function verifyBulk( + bytes[] calldata signedReports, + bytes calldata parameterPayload, + address sender + ) external payable override onlyProxy checkAccess(sender) returns (bytes[] memory) { + bytes[] memory verifierResponses = new bytes[](signedReports.length); + bytes32[] memory donConfigs = new bytes32[](signedReports.length); + + for (uint256 i; i < signedReports.length; ++i) { + (bytes memory report, bytes32 config) = _verify(signedReports[i], sender); + verifierResponses[i] = report; + donConfigs[i] = config; + } + + address fm = s_feeManager; + if (fm != address(0)) { + //process the fee and catch the error + try + IDestinationVerifierFeeManager(fm).processFeeBulk{value: msg.value}( + donConfigs, + signedReports, + parameterPayload, + sender + ) + { + //do nothing + } catch { + // we purposefully obfuscate the error here to prevent information leaking leading to free verifications + revert BadVerification(); + } + } + + return verifierResponses; + } + + function _verify(bytes calldata signedReport, address sender) internal returns (bytes memory, bytes32) { + ( + bytes32[3] memory reportContext, + bytes memory reportData, + bytes32[] memory rs, + bytes32[] memory ss, + bytes32 rawVs + ) = abi.decode(signedReport, (bytes32[3], bytes, bytes32[], bytes32[], bytes32)); + + // Signature lengths must match + if (rs.length != ss.length) revert MismatchedSignatures(rs.length, ss.length); + + //Must always be at least 1 signer + if (rs.length == 0) revert NoSigners(); + + // The payload is hashed and signed by the oracles - we need to recover the addresses + bytes32 signedPayload = keccak256(abi.encodePacked(keccak256(reportData), reportContext)); + address[] memory signers = new address[](rs.length); + for (uint256 i; i < rs.length; ++i) { + signers[i] = ecrecover(signedPayload, uint8(rawVs[i]) + 27, rs[i], ss[i]); + } + + // Duplicate signatures are not allowed + if (Common._hasDuplicateAddresses(signers)) { + revert BadVerification(); + } + + //We need to know the timestamp the report was generated to lookup the active activeDonConfig + uint256 reportTimestamp = _decodeReportTimestamp(reportData); + + // Find the latest config for this report + DonConfig memory activeDonConfig = _findActiveConfig(reportTimestamp); + + // Check a config has been set + if (activeDonConfig.donConfigId == bytes24(0)) { + revert BadVerification(); + } + + //check the config is active + if (!activeDonConfig.isActive) { + revert BadVerification(); + } + + //check we have enough signatures + if (signers.length <= activeDonConfig.f) { + revert BadVerification(); + } + + //check each signer is registered against the active DON + bytes32 signerDonConfigKey; + for (uint256 i; i < signers.length; ++i) { + signerDonConfigKey = keccak256(abi.encodePacked(signers[i], activeDonConfig.donConfigId)); + if (!s_signerByAddressAndDonConfigId[signerDonConfigKey]) { + revert BadVerification(); + } + } + + emit ReportVerified(bytes32(reportData), sender); + + return (reportData, activeDonConfig.donConfigId); + } + + /// @inheritdoc IDestinationVerifier + function setConfigWithActivationTime( + address[] memory signers, + uint8 f, + Common.AddressAndWeight[] memory recipientAddressesAndWeights, + uint32 activationTime + ) external override checkConfigValid(signers.length, f) onlyOwner { + _setConfig(signers, f, recipientAddressesAndWeights, activationTime); + } + + /// @inheritdoc IDestinationVerifier + function setConfig( + address[] memory signers, + uint8 f, + Common.AddressAndWeight[] memory recipientAddressesAndWeights + ) external override checkConfigValid(signers.length, f) onlyOwner { + _setConfig(signers, f, recipientAddressesAndWeights, uint32(block.timestamp)); + } + + function _setConfig( + address[] memory signers, + uint8 f, + Common.AddressAndWeight[] memory recipientAddressesAndWeights, + uint32 activationTime + ) internal { + // Duplicate addresses would break protocol rules + if (Common._hasDuplicateAddresses(signers)) { + revert NonUniqueSignatures(); + } + + //activation time cannot be in the future + if (activationTime > block.timestamp) { + revert BadActivationTime(); + } + + // Sort signers to ensure donConfigId is deterministic + Common._quickSort(signers, 0, int256(signers.length - 1)); + + //DonConfig is made up of hash(signers|f) + bytes24 donConfigId = bytes24(keccak256(abi.encodePacked(signers, f))); + + // Register the signers for this DON + for (uint256 i; i < signers.length; ++i) { + if (signers[i] == address(0)) revert ZeroAddress(); + /** This index is registered so we can efficiently lookup whether a NOP is part of a config without having to + loop through the entire config each verification. It's effectively a DonConfig <-> Signer + composite key which keys track of all historic configs for a signer */ + s_signerByAddressAndDonConfigId[keccak256(abi.encodePacked(signers[i], donConfigId))] = true; + } + + // Check the activation time is greater than the latest config + uint256 donConfigLength = s_donConfigs.length; + if (donConfigLength > 0 && s_donConfigs[donConfigLength - 1].activationTime >= activationTime) { + revert BadActivationTime(); + } + + // Check the config we're setting isn't already set as the current active config as this will increase search costs unnecessarily when verifying historic reports + if (donConfigLength > 0 && s_donConfigs[donConfigLength - 1].donConfigId == donConfigId) { + revert DonConfigAlreadyExists(donConfigId); + } + + // We may want to register these later or skip this step in the unlikely scenario they've previously been registered in the RewardsManager + if (recipientAddressesAndWeights.length != 0) { + if (s_feeManager == address(0)) { + revert FeeManagerInvalid(); + } + IDestinationVerifierFeeManager(s_feeManager).setFeeRecipients(donConfigId, recipientAddressesAndWeights); + } + + // push the DonConfig + s_donConfigs.push(DonConfig(donConfigId, f, true, activationTime)); + + emit ConfigSet(donConfigId, signers, f, recipientAddressesAndWeights, uint16(donConfigLength)); + } + + /// @inheritdoc IDestinationVerifier + function setFeeManager(address feeManager) external override onlyOwner { + if (!IERC165(feeManager).supportsInterface(type(IDestinationVerifierFeeManager).interfaceId)) + revert FeeManagerInvalid(); + + address oldFeeManager = s_feeManager; + s_feeManager = feeManager; + + emit FeeManagerSet(oldFeeManager, feeManager); + } + + /// @inheritdoc IDestinationVerifier + function setAccessController(address accessController) external override onlyOwner { + address oldAccessController = s_accessController; + s_accessController = accessController; + emit AccessControllerSet(oldAccessController, accessController); + } + + /// @inheritdoc IDestinationVerifier + function setConfigActive(uint256 donConfigIndex, bool isActive) external onlyOwner { + // Config must exist + if (donConfigIndex >= s_donConfigs.length) { + revert DonConfigDoesNotExist(); + } + + // Update the config + DonConfig storage config = s_donConfigs[donConfigIndex]; + config.isActive = isActive; + + emit ConfigActivated(config.donConfigId, isActive); + } + + /// @inheritdoc IDestinationVerifier + function removeLatestConfig() external onlyOwner { + if (s_donConfigs.length == 0) { + revert DonConfigDoesNotExist(); + } + + DonConfig memory config = s_donConfigs[s_donConfigs.length - 1]; + + s_donConfigs.pop(); + + emit ConfigRemoved(config.donConfigId); + } + + function _decodeReportTimestamp(bytes memory reportPayload) internal pure returns (uint256) { + (, , uint256 timestamp) = abi.decode(reportPayload, (bytes32, uint32, uint32)); + + return timestamp; + } + + function _findActiveConfig(uint256 timestamp) internal view returns (DonConfig memory) { + DonConfig memory activeDonConfig; + + // 99% of the time the signer config will be the last index, however for historic reports generated by a previous configuration we'll need to cycle back + uint256 i = s_donConfigs.length; + while (i > 0) { + --i; + if (s_donConfigs[i].activationTime <= timestamp) { + activeDonConfig = s_donConfigs[i]; + break; + } + } + return activeDonConfig; + } + + modifier checkConfigValid(uint256 numSigners, uint256 f) { + if (f == 0) revert FaultToleranceMustBePositive(); + if (numSigners > MAX_NUM_ORACLES) revert ExcessSigners(numSigners, MAX_NUM_ORACLES); + if (numSigners <= 3 * f) revert InsufficientSigners(numSigners, 3 * f + 1); + _; + } + + modifier onlyProxy() { + if (address(i_verifierProxy) != msg.sender) { + revert AccessForbidden(); + } + _; + } + + modifier checkAccess(address sender) { + address ac = s_accessController; + if (address(ac) != address(0) && !IAccessController(ac).hasAccess(sender, msg.data)) revert AccessForbidden(); + _; + } + + /// @inheritdoc IERC165 + function supportsInterface(bytes4 interfaceId) public pure override returns (bool) { + return + interfaceId == type(IDestinationVerifier).interfaceId || + interfaceId == type(IDestinationVerifierProxyVerifier).interfaceId; + } + + /// @inheritdoc TypeAndVersionInterface + function typeAndVersion() external pure override returns (string memory) { + return "DestinationVerifier 0.4.0"; + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/DestinationVerifierProxy.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/DestinationVerifierProxy.sol new file mode 100644 index 0000000000..6790883ba3 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/DestinationVerifierProxy.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; +import {TypeAndVersionInterface} from "../../interfaces/TypeAndVersionInterface.sol"; +import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; +import {IDestinationVerifierProxy} from "./interfaces/IDestinationVerifierProxy.sol"; +import {IDestinationVerifierProxyVerifier} from "./interfaces/IDestinationVerifierProxyVerifier.sol"; + +/** + * @title DestinationVerifierProxy + * @author Michael Fletcher + * @notice This contract will be used to route all requests through to the assigned verifier contract. This contract does not support individual feed configurations and is aimed at being a simple proxy for the verifier contract on any destination chain. + */ +contract DestinationVerifierProxy is IDestinationVerifierProxy, ConfirmedOwner, TypeAndVersionInterface { + /// @notice The active verifier for this proxy + IDestinationVerifierProxyVerifier private s_verifier; + + /// @notice This error is thrown whenever a zero address is passed + error ZeroAddress(); + + /// @notice This error is thrown when trying to set a verifier address that does not implement the expected interface + error VerifierInvalid(address verifierAddress); + + constructor() ConfirmedOwner(msg.sender) {} + + /// @inheritdoc TypeAndVersionInterface + function typeAndVersion() external pure override returns (string memory) { + return "DestinationVerifierProxy 0.4.0"; + } + + /// @inheritdoc IDestinationVerifierProxy + function verify(bytes calldata payload, bytes calldata parameterPayload) external payable returns (bytes memory) { + return s_verifier.verify{value: msg.value}(payload, parameterPayload, msg.sender); + } + + /// @inheritdoc IDestinationVerifierProxy + function verifyBulk( + bytes[] calldata payloads, + bytes calldata parameterPayload + ) external payable returns (bytes[] memory verifiedReports) { + return s_verifier.verifyBulk{value: msg.value}(payloads, parameterPayload, msg.sender); + } + + /// @inheritdoc IDestinationVerifierProxy + function setVerifier(address verifierAddress) external onlyOwner { + //check it supports the functions we need + if (!IERC165(verifierAddress).supportsInterface(type(IDestinationVerifierProxyVerifier).interfaceId)) + revert VerifierInvalid(verifierAddress); + + s_verifier = IDestinationVerifierProxyVerifier(verifierAddress); + } + + /// @inheritdoc IDestinationVerifierProxy + // solhint-disable-next-line func-name-mixedcase + function s_feeManager() external view override returns (address) { + return s_verifier.s_feeManager(); + } + + /// @inheritdoc IDestinationVerifierProxy + // solhint-disable-next-line func-name-mixedcase + function s_accessController() external view override returns (address) { + return s_verifier.s_accessController(); + } + + /// @inheritdoc IERC165 + function supportsInterface(bytes4 interfaceId) external pure override returns (bool) { + return interfaceId == type(IDestinationVerifierProxy).interfaceId; + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationFeeManager.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationFeeManager.sol new file mode 100644 index 0000000000..00420a4edb --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationFeeManager.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; +import {Common} from "../../libraries/Common.sol"; + +interface IDestinationFeeManager is IERC165 { + /** + * @notice Calculate the applied fee and the reward from a report. If the sender is a subscriber, they will receive a discount. + * @param subscriber address trying to verify + * @param report report to calculate the fee for + * @param quoteAddress address of the quote payment token + * @return (fee, reward, totalDiscount) fee and the reward data with the discount applied + */ + function getFeeAndReward( + address subscriber, + bytes memory report, + address quoteAddress + ) external returns (Common.Asset memory, Common.Asset memory, uint256); + + /** + * @notice Sets the native surcharge + * @param surcharge surcharge to be paid if paying in native + */ + function setNativeSurcharge(uint64 surcharge) external; + + /** + * @notice Adds a subscriber to the fee manager + * @param subscriber address of the subscriber + * @param feedId feed id to apply the discount to + * @param token token to apply the discount to + * @param discount discount to be applied to the fee + */ + function updateSubscriberDiscount(address subscriber, bytes32 feedId, address token, uint64 discount) external; + + /** + * @notice Adds a subscriber to the fee manager + * @param subscriber address of the subscriber + * @param token token to apply the discount to + * @param discount discount to be applied to the fee + */ + function updateSubscriberGlobalDiscount(address subscriber, address token, uint64 discount) external; + + /** + * @notice Withdraws any native or LINK rewards to the owner address + * @param assetAddress address of the asset to withdraw + * @param recipientAddress address to withdraw to + * @param quantity quantity to withdraw + */ + function withdraw(address assetAddress, address recipientAddress, uint192 quantity) external; + + /** + * @notice Returns the link balance of the fee manager + * @return link balance of the fee manager + */ + function linkAvailableForPayment() external returns (uint256); + + /** + * @notice Admin function to pay the LINK deficit for a given config digest + * @param configDigest the config digest to pay the deficit for + */ + function payLinkDeficit(bytes32 configDigest) external; + + /** + * @notice Adds the verifier to the list of verifiers able to use the feeManager + * @param verifier address of the verifier + */ + function addVerifier(address verifier) external; + + /** + * @notice Removes the verifier from the list of verifiers able to use the feeManager + * @param verifier address of the verifier + */ + function removeVerifier(address verifier) external; + + /** + * @notice Sets the reward manager to the address + * @param rewardManager address of the reward manager + */ + function setRewardManager(address rewardManager) external; + + /** + * @notice The structure to hold a fee and reward to verify a report + * @param digest the digest linked to the fee and reward + * @param fee the fee paid to verify the report + * @param reward the reward paid upon verification + & @param appliedDiscount the discount applied to the reward + */ + struct FeeAndReward { + bytes32 configDigest; + Common.Asset fee; + Common.Asset reward; + uint256 appliedDiscount; + } + + /** + * @notice The structure to hold quote metadata + * @param quoteAddress the address of the quote + */ + struct Quote { + address quoteAddress; + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationRewardManager.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationRewardManager.sol new file mode 100644 index 0000000000..95f07937ae --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationRewardManager.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; +import {Common} from "../../libraries/Common.sol"; + +interface IDestinationRewardManager is IERC165 { + /** + * @notice Record the fee received for a particular pool + * @param payments array of structs containing pool id and amount + * @param payee the user the funds should be retrieved from + */ + function onFeePaid(FeePayment[] calldata payments, address payee) external; + + /** + * @notice Claims the rewards in a specific pool + * @param poolIds array of poolIds to claim rewards for + */ + function claimRewards(bytes32[] calldata poolIds) external; + + /** + * @notice Set the RewardRecipients and weights for a specific pool. This should only be called once per pool Id. Else updateRewardRecipients should be used. + * @param poolId poolId to set RewardRecipients and weights for + * @param rewardRecipientAndWeights array of each RewardRecipient and associated weight + */ + function setRewardRecipients(bytes32 poolId, Common.AddressAndWeight[] calldata rewardRecipientAndWeights) external; + + /** + * @notice Updates a subset the reward recipients for a specific poolId. The collective weight of the recipients should add up to the recipients existing weights. Any recipients with a weight of 0 will be removed. + * @param poolId the poolId to update + * @param newRewardRecipients array of new reward recipients + */ + function updateRewardRecipients(bytes32 poolId, Common.AddressAndWeight[] calldata newRewardRecipients) external; + + /** + * @notice Pays all the recipients for each of the pool ids + * @param poolId the pool id to pay recipients for + * @param recipients array of recipients to pay within the pool + */ + function payRecipients(bytes32 poolId, address[] calldata recipients) external; + + /** + * @notice Add the fee manager to the list of feeManagers able to call the reward manager + * @param newFeeManager address of the new verifier proxy + */ + function addFeeManager(address newFeeManager) external; + + /** + * @notice Removes the fee manager. This needs to be done post construction to prevent a circular dependency. + * @param feeManager address of the verifier proxy to remove + */ + function removeFeeManager(address feeManager) external; + + /** + * @notice Gets a list of pool ids which have reward for a specific recipient. + * @param recipient address of the recipient to get pool ids for + * @param startIndex the index to start from + * @param endIndex the index to stop at + */ + function getAvailableRewardPoolIds( + address recipient, + uint256 startIndex, + uint256 endIndex + ) external view returns (bytes32[] memory); + + /** + * @notice The structure to hold a fee payment notice + * @param poolId the poolId receiving the payment + * @param amount the amount being paid + */ + struct FeePayment { + bytes32 poolId; + uint192 amount; + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationVerifier.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationVerifier.sol new file mode 100644 index 0000000000..041a8c8f72 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationVerifier.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {Common} from "../../libraries/Common.sol"; +import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; + +interface IDestinationVerifier is IERC165 { + /** + * @notice sets off-chain reporting protocol configuration incl. participating oracles + * @param signers addresses with which oracles sign the reports + * @param f number of faulty oracles the system can tolerate + * @param recipientAddressesAndWeights the addresses and weights of all the recipients to receive rewards + */ + function setConfig( + address[] memory signers, + uint8 f, + Common.AddressAndWeight[] memory recipientAddressesAndWeights + ) external; + + /** + * @notice sets off-chain reporting protocol configuration incl. participating oracles + * @param signers addresses with which oracles sign the reports + * @param f number of faulty oracles the system can tolerate + * @param recipientAddressesAndWeights the addresses and weights of all the recipients to receive rewards + * @param activationTime the time at which the config was activated + */ + function setConfigWithActivationTime( + address[] memory signers, + uint8 f, + Common.AddressAndWeight[] memory recipientAddressesAndWeights, + uint32 activationTime + ) external; + + /** + * @notice Sets the fee manager address + * @param feeManager The address of the fee manager + */ + function setFeeManager(address feeManager) external; + + /** + * @notice Sets the access controller address + * @param accessController The address of the access controller + */ + function setAccessController(address accessController) external; + + /** + * @notice Updates the config active status + * @param donConfigId The ID of the config to update + * @param isActive The new config active status + */ + function setConfigActive(uint256 donConfigId, bool isActive) external; + + /** + * @notice Removes the latest config + */ + function removeLatestConfig() external; +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationVerifierFeeManager.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationVerifierFeeManager.sol new file mode 100644 index 0000000000..291f3706b3 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationVerifierFeeManager.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; +import {Common} from "../../libraries/Common.sol"; + +interface IDestinationVerifierFeeManager is IERC165 { + /** + * @notice Handles fees for a report from the subscriber and manages rewards + * @param poolId pool id of the pool to pay into + * @param payload report to process the fee for + * @param parameterPayload fee payload + * @param subscriber address of the fee will be applied + */ + function processFee( + bytes32 poolId, + bytes calldata payload, + bytes calldata parameterPayload, + address subscriber + ) external payable; + + /** + * @notice Processes the fees for each report in the payload, billing the subscriber and paying the reward manager + * @param poolIds pool ids of the pool to pay into + * @param payloads reports to process + * @param parameterPayload fee payload + * @param subscriber address of the user to process fee for + */ + function processFeeBulk( + bytes32[] memory poolIds, + bytes[] calldata payloads, + bytes calldata parameterPayload, + address subscriber + ) external payable; + + /** + * @notice Sets the fee recipients according to the fee manager + * @param configDigest digest of the configuration + * @param rewardRecipientAndWeights the address and weights of all the recipients to receive rewards + */ + function setFeeRecipients( + bytes32 configDigest, + Common.AddressAndWeight[] calldata rewardRecipientAndWeights + ) external; +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationVerifierProxy.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationVerifierProxy.sol new file mode 100644 index 0000000000..e0dcb30d54 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationVerifierProxy.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; + +interface IDestinationVerifierProxy is IERC165 { + /** + * @notice Verifies that the data encoded has been signed + * correctly by routing to the verifier, and bills the user if applicable. + * @param payload The encoded data to be verified, including the signed + * report. + * @param parameterPayload fee metadata for billing + * @return verifierResponse The encoded report from the verifier. + */ + function verify( + bytes calldata payload, + bytes calldata parameterPayload + ) external payable returns (bytes memory verifierResponse); + + /** + * @notice Bulk verifies that the data encoded has been signed + * correctly by routing to the correct verifier, and bills the user if applicable. + * @param payloads The encoded payloads to be verified, including the signed + * report. + * @param parameterPayload fee metadata for billing + * @return verifiedReports The encoded reports from the verifier. + */ + function verifyBulk( + bytes[] calldata payloads, + bytes calldata parameterPayload + ) external payable returns (bytes[] memory verifiedReports); + + /** + * @notice Sets the active verifier for this proxy + * @param verifierAddress The address of the verifier contract + */ + function setVerifier(address verifierAddress) external; + + /** + * @notice Used to honor the source verifierProxy feeManager interface + * @return IVerifierFeeManager + */ + // solhint-disable-next-line func-name-mixedcase + function s_feeManager() external view returns (address); + + /** + * @notice Used to honor the source verifierProxy feeManager interface + * @return AccessControllerInterface + */ + // solhint-disable-next-line func-name-mixedcase + function s_accessController() external view returns (address); +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationVerifierProxyVerifier.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationVerifierProxyVerifier.sol new file mode 100644 index 0000000000..a957f8f928 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/interfaces/IDestinationVerifierProxyVerifier.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; + +interface IDestinationVerifierProxyVerifier is IERC165 { + /** + * @notice Verifies that the data encoded has been signed correctly using the signatures included within the payload. + * @param signedReport The encoded data to be verified. + * @param parameterPayload The encoded parameters to be used in the verification and billing process. + * @param sender The address that requested to verify the contract.Used for logging and applying the fee. + * @dev Verification is typically only done through the proxy contract so we can't just use msg.sender. + * @return verifierResponse The encoded verified response. + */ + function verify( + bytes calldata signedReport, + bytes calldata parameterPayload, + address sender + ) external payable returns (bytes memory verifierResponse); + + /** + * @notice Bulk verifies that the data encoded has been signed correctly using the signatures included within the payload. + * @param signedReports The encoded data to be verified. + * @param parameterPayload The encoded parameters to be used in the verification and billing process. + * @param sender The address that requested to verify the contract. Used for logging and applying the fee. + * @dev Verification is typically only done through the proxy contract so we can't just use msg.sender. + * @return verifiedReports The encoded verified responses. + */ + function verifyBulk( + bytes[] calldata signedReports, + bytes calldata parameterPayload, + address sender + ) external payable returns (bytes[] memory verifiedReports); + + /* + * @notice Returns the reward manager + * @return IDestinationRewardManager + */ + // solhint-disable-next-line func-name-mixedcase + function s_feeManager() external view returns (address); + + /** + * @notice Returns the access controller + * @return IDestinationFeeManager + */ + // solhint-disable-next-line func-name-mixedcase + function s_accessController() external view returns (address); +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/BaseDestinationFeeManager.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/BaseDestinationFeeManager.t.sol new file mode 100644 index 0000000000..38b1bad217 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/BaseDestinationFeeManager.t.sol @@ -0,0 +1,406 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {Test} from "forge-std/Test.sol"; +import {DestinationFeeManager} from "../../DestinationFeeManager.sol"; +import {DestinationRewardManager} from "../../DestinationRewardManager.sol"; +import {Common} from "../../../libraries/Common.sol"; +import {ERC20Mock} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol"; +import {WERC20Mock} from "../../../../shared/mocks/WERC20Mock.sol"; +import {IDestinationRewardManager} from "../../interfaces/IDestinationRewardManager.sol"; +import {DestinationFeeManagerProxy} from "../mocks/DestinationFeeManagerProxy.sol"; + +/** + * @title BaseDestinationFeeManagerTest + * @author Michael Fletcher + * @notice Base class for all feeManager tests + * @dev This contract is intended to be inherited from and not used directly. It contains functionality to setup the feeManager + */ +contract BaseDestinationFeeManagerTest is Test { + //contracts + DestinationFeeManager internal feeManager; + DestinationRewardManager internal rewardManager; + DestinationFeeManagerProxy internal feeManagerProxy; + + ERC20Mock internal link; + WERC20Mock internal native; + + //erc20 config + uint256 internal constant DEFAULT_LINK_MINT_QUANTITY = 100 ether; + uint256 internal constant DEFAULT_NATIVE_MINT_QUANTITY = 100 ether; + + //contract owner + address internal constant INVALID_ADDRESS = address(0); + address internal constant ADMIN = address(uint160(uint256(keccak256("ADMIN")))); + address internal constant USER = address(uint160(uint256(keccak256("USER")))); + address internal constant PROXY = address(uint160(uint256(keccak256("PROXY")))); + + //version masks + bytes32 internal constant V_MASK = 0x0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; + bytes32 internal constant V1_BITMASK = 0x0001000000000000000000000000000000000000000000000000000000000000; + bytes32 internal constant V2_BITMASK = 0x0002000000000000000000000000000000000000000000000000000000000000; + bytes32 internal constant V3_BITMASK = 0x0003000000000000000000000000000000000000000000000000000000000000; + + //feed ids & config digests + bytes32 internal constant DEFAULT_FEED_1_V1 = (keccak256("ETH-USD") & V_MASK) | V1_BITMASK; + bytes32 internal constant DEFAULT_FEED_1_V2 = (keccak256("ETH-USD") & V_MASK) | V2_BITMASK; + bytes32 internal constant DEFAULT_FEED_1_V3 = (keccak256("ETH-USD") & V_MASK) | V3_BITMASK; + + bytes32 internal constant DEFAULT_FEED_2_V3 = (keccak256("LINK-USD") & V_MASK) | V3_BITMASK; + bytes32 internal constant DEFAULT_CONFIG_DIGEST = keccak256("DEFAULT_CONFIG_DIGEST"); + bytes32 internal constant DEFAULT_CONFIG_DIGEST2 = keccak256("DEFAULT_CONFIG_DIGEST2"); + + //report + uint256 internal constant DEFAULT_REPORT_LINK_FEE = 1e10; + uint256 internal constant DEFAULT_REPORT_NATIVE_FEE = 1e12; + + //rewards + uint64 internal constant FEE_SCALAR = 1e18; + + address internal constant NATIVE_WITHDRAW_ADDRESS = address(0); + + //the selector for each error + bytes4 internal immutable INVALID_DISCOUNT_ERROR = DestinationFeeManager.InvalidDiscount.selector; + bytes4 internal immutable INVALID_ADDRESS_ERROR = DestinationFeeManager.InvalidAddress.selector; + bytes4 internal immutable INVALID_SURCHARGE_ERROR = DestinationFeeManager.InvalidSurcharge.selector; + bytes4 internal immutable EXPIRED_REPORT_ERROR = DestinationFeeManager.ExpiredReport.selector; + bytes4 internal immutable INVALID_DEPOSIT_ERROR = DestinationFeeManager.InvalidDeposit.selector; + bytes4 internal immutable INVALID_QUOTE_ERROR = DestinationFeeManager.InvalidQuote.selector; + bytes4 internal immutable UNAUTHORIZED_ERROR = DestinationFeeManager.Unauthorized.selector; + bytes4 internal immutable POOLID_MISMATCH_ERROR = DestinationFeeManager.PoolIdMismatch.selector; + bytes internal constant ONLY_CALLABLE_BY_OWNER_ERROR = "Only callable by owner"; + bytes internal constant INSUFFICIENT_ALLOWANCE_ERROR = "ERC20: insufficient allowance"; + bytes4 internal immutable ZERO_DEFICIT = DestinationFeeManager.ZeroDeficit.selector; + + //events emitted + event SubscriberDiscountUpdated(address indexed subscriber, bytes32 indexed feedId, address token, uint64 discount); + event NativeSurchargeUpdated(uint64 newSurcharge); + event InsufficientLink(IDestinationRewardManager.FeePayment[] feesAndRewards); + event Withdraw(address adminAddress, address recipient, address assetAddress, uint192 quantity); + event LinkDeficitCleared(bytes32 indexed configDigest, uint256 linkQuantity); + event DiscountApplied( + bytes32 indexed configDigest, + address indexed subscriber, + Common.Asset fee, + Common.Asset reward, + uint256 appliedDiscountQuantity + ); + + function setUp() public virtual { + //change to admin user + vm.startPrank(ADMIN); + + //init required contracts + _initializeContracts(); + } + + function _initializeContracts() internal { + link = new ERC20Mock("LINK", "LINK", ADMIN, 0); + native = new WERC20Mock(); + + feeManagerProxy = new DestinationFeeManagerProxy(); + rewardManager = new DestinationRewardManager(address(link)); + feeManager = new DestinationFeeManager( + address(link), + address(native), + address(feeManagerProxy), + address(rewardManager) + ); + + //link the feeManager to the proxy + feeManagerProxy.setDestinationFeeManager(address(feeManager)); + + //link the feeManager to the reward manager + rewardManager.addFeeManager(address(feeManager)); + + //mint some tokens to the admin + link.mint(ADMIN, DEFAULT_LINK_MINT_QUANTITY); + native.mint(ADMIN, DEFAULT_NATIVE_MINT_QUANTITY); + vm.deal(ADMIN, DEFAULT_NATIVE_MINT_QUANTITY); + + //mint some tokens to the user + link.mint(USER, DEFAULT_LINK_MINT_QUANTITY); + native.mint(USER, DEFAULT_NATIVE_MINT_QUANTITY); + vm.deal(USER, DEFAULT_NATIVE_MINT_QUANTITY); + + //mint some tokens to the proxy + link.mint(PROXY, DEFAULT_LINK_MINT_QUANTITY); + native.mint(PROXY, DEFAULT_NATIVE_MINT_QUANTITY); + vm.deal(PROXY, DEFAULT_NATIVE_MINT_QUANTITY); + } + + function setSubscriberDiscount( + address subscriber, + bytes32 feedId, + address token, + uint256 discount, + address sender + ) internal { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(sender); + + //set the discount + feeManager.updateSubscriberDiscount(subscriber, feedId, token, uint64(discount)); + + //change back to the original address + changePrank(originalAddr); + } + + function setSubscriberGlobalDiscount(address subscriber, address token, uint256 discount, address sender) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(sender); + + //set the discount + feeManager.updateSubscriberGlobalDiscount(subscriber, token, uint64(discount)); + + //change back to the original address + changePrank(originalAddr); + } + + function setNativeSurcharge(uint256 surcharge, address sender) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(sender); + + //set the surcharge + feeManager.setNativeSurcharge(uint64(surcharge)); + + //change back to the original address + changePrank(originalAddr); + } + + // solium-disable-next-line no-unused-vars + function getFee(bytes memory report, address quote, address subscriber) public view returns (Common.Asset memory) { + //get the fee + (Common.Asset memory fee, , ) = feeManager.getFeeAndReward(subscriber, report, quote); + + return fee; + } + + function getReward(bytes memory report, address quote, address subscriber) public view returns (Common.Asset memory) { + //get the reward + (, Common.Asset memory reward, ) = feeManager.getFeeAndReward(subscriber, report, quote); + + return reward; + } + + function getAppliedDiscount(bytes memory report, address quote, address subscriber) public view returns (uint256) { + //get the reward + (, , uint256 appliedDiscount) = feeManager.getFeeAndReward(subscriber, report, quote); + + return appliedDiscount; + } + + function getV1Report(bytes32 feedId) public pure returns (bytes memory) { + return abi.encode(feedId, uint32(0), int192(0), int192(0), int192(0), uint64(0), bytes32(0), uint64(0), uint64(0)); + } + + function getV2Report(bytes32 feedId) public view returns (bytes memory) { + return + abi.encode( + feedId, + uint32(0), + uint32(0), + uint192(DEFAULT_REPORT_NATIVE_FEE), + uint192(DEFAULT_REPORT_LINK_FEE), + uint32(block.timestamp), + int192(0) + ); + } + + function getV3Report(bytes32 feedId) public view returns (bytes memory) { + return + abi.encode( + feedId, + uint32(0), + uint32(0), + uint192(DEFAULT_REPORT_NATIVE_FEE), + uint192(DEFAULT_REPORT_LINK_FEE), + uint32(block.timestamp), + int192(0), + int192(0), + int192(0) + ); + } + + function getV3ReportWithCustomExpiryAndFee( + bytes32 feedId, + uint256 expiry, + uint256 linkFee, + uint256 nativeFee + ) public pure returns (bytes memory) { + return + abi.encode( + feedId, + uint32(0), + uint32(0), + uint192(nativeFee), + uint192(linkFee), + uint32(expiry), + int192(0), + int192(0), + int192(0) + ); + } + + function getLinkQuote() public view returns (address) { + return address(link); + } + + function getNativeQuote() public view returns (address) { + return address(native); + } + + function withdraw(address assetAddress, address recipient, uint256 amount, address sender) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(sender); + + //set the surcharge + feeManager.withdraw(assetAddress, recipient, uint192(amount)); + + //change back to the original address + changePrank(originalAddr); + } + + function getLinkBalance(address balanceAddress) public view returns (uint256) { + return link.balanceOf(balanceAddress); + } + + function getNativeBalance(address balanceAddress) public view returns (uint256) { + return native.balanceOf(balanceAddress); + } + + function getNativeUnwrappedBalance(address balanceAddress) public view returns (uint256) { + return balanceAddress.balance; + } + + function mintLink(address recipient, uint256 amount) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(ADMIN); + + //mint the link to the recipient + link.mint(recipient, amount); + + //change back to the original address + changePrank(originalAddr); + } + + function mintNative(address recipient, uint256 amount, address sender) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(sender); + + //mint the native to the recipient + native.mint(recipient, amount); + + //change back to the original address + changePrank(originalAddr); + } + + function issueUnwrappedNative(address recipient, uint256 quantity) public { + vm.deal(recipient, quantity); + } + + function ProcessFeeAsUser( + bytes32 poolId, + bytes memory payload, + address subscriber, + address tokenAddress, + uint256 wrappedNativeValue, + address sender + ) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(sender); + + //process the fee + feeManager.processFee{value: wrappedNativeValue}(poolId, payload, abi.encode(tokenAddress), subscriber); + + //change ProcessFeeAsUserback to the original address + changePrank(originalAddr); + } + + function processFee( + bytes32 poolId, + bytes memory payload, + address subscriber, + address feeAddress, + uint256 wrappedNativeValue + ) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(subscriber); + + //process the fee + feeManagerProxy.processFee{value: wrappedNativeValue}(poolId, payload, abi.encode(feeAddress)); + + //change back to the original address + changePrank(originalAddr); + } + + function processFee( + bytes32[] memory poolIds, + bytes[] memory payloads, + address subscriber, + address feeAddress, + uint256 wrappedNativeValue + ) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(subscriber); + + //process the fee + feeManagerProxy.processFeeBulk{value: wrappedNativeValue}(poolIds, payloads, abi.encode(feeAddress)); + + //change back to the original address + changePrank(originalAddr); + } + + function getPayload(bytes memory reportPayload) public pure returns (bytes memory) { + return abi.encode([DEFAULT_CONFIG_DIGEST, 0, 0], reportPayload, new bytes32[](1), new bytes32[](1), bytes32("")); + } + + function approveLink(address spender, uint256 quantity, address sender) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(sender); + + //approve the link to be transferred + link.approve(spender, quantity); + + //change back to the original address + changePrank(originalAddr); + } + + function approveNative(address spender, uint256 quantity, address sender) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(sender); + + //approve the link to be transferred + native.approve(spender, quantity); + + //change back to the original address + changePrank(originalAddr); + } + + function payLinkDeficit(bytes32 configDigest, address sender) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(sender); + + //approve the link to be transferred + feeManager.payLinkDeficit(configDigest); + + //change back to the original address + changePrank(originalAddr); + } + + function getLinkDeficit(bytes32 configDigest) public view returns (uint256) { + return feeManager.s_linkDeficit(configDigest); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/DestinationFeeManager.general.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/DestinationFeeManager.general.t.sol new file mode 100644 index 0000000000..305125c332 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/DestinationFeeManager.general.t.sol @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import "./BaseDestinationFeeManager.t.sol"; + +/** + * @title BaseDestinationFeeManagerTest + * @author Michael Fletcher + * @notice This contract will test the setup functionality of the feemanager + */ +contract DestinationFeeManagerProcessFeeTest is BaseDestinationFeeManagerTest { + function setUp() public override { + super.setUp(); + } + + function test_WithdrawERC20() public { + //simulate a fee + mintLink(address(feeManager), DEFAULT_LINK_MINT_QUANTITY); + + //get the balances to ne used for comparison + uint256 contractBalance = getLinkBalance(address(feeManager)); + uint256 adminBalance = getLinkBalance(ADMIN); + + //the amount to withdraw + uint256 withdrawAmount = contractBalance / 2; + + //withdraw some balance + withdraw(address(link), ADMIN, withdrawAmount, ADMIN); + + //check the balance has been reduced + uint256 newContractBalance = getLinkBalance(address(feeManager)); + uint256 newAdminBalance = getLinkBalance(ADMIN); + + //check the balance is greater than zero + assertGt(newContractBalance, 0); + //check the balance has been reduced by the correct amount + assertEq(newContractBalance, contractBalance - withdrawAmount); + //check the admin balance has increased by the correct amount + assertEq(newAdminBalance, adminBalance + withdrawAmount); + } + + function test_WithdrawUnwrappedNative() public { + //issue funds straight to the contract to bypass the lack of fallback function + issueUnwrappedNative(address(feeManager), DEFAULT_NATIVE_MINT_QUANTITY); + + //get the balances to be used for comparison + uint256 contractBalance = getNativeUnwrappedBalance(address(feeManager)); + uint256 adminBalance = getNativeUnwrappedBalance(ADMIN); + + //the amount to withdraw + uint256 withdrawAmount = contractBalance / 2; + + //withdraw some balance + withdraw(NATIVE_WITHDRAW_ADDRESS, ADMIN, withdrawAmount, ADMIN); + + //check the balance has been reduced + uint256 newContractBalance = getNativeUnwrappedBalance(address(feeManager)); + uint256 newAdminBalance = getNativeUnwrappedBalance(ADMIN); + + //check the balance is greater than zero + assertGt(newContractBalance, 0); + //check the balance has been reduced by the correct amount + assertEq(newContractBalance, contractBalance - withdrawAmount); + //check the admin balance has increased by the correct amount + assertEq(newAdminBalance, adminBalance + withdrawAmount); + } + + function test_WithdrawNonAdminAddr() public { + //simulate a fee + mintLink(address(feeManager), DEFAULT_LINK_MINT_QUANTITY); + + //should revert if not admin + vm.expectRevert(ONLY_CALLABLE_BY_OWNER_ERROR); + + //withdraw some balance + withdraw(address(link), ADMIN, DEFAULT_LINK_MINT_QUANTITY, USER); + } + + function test_eventIsEmittedAfterSurchargeIsSet() public { + //native surcharge + uint64 nativeSurcharge = FEE_SCALAR / 5; + + //expect an emit + vm.expectEmit(); + + //emit the event that is expected to be emitted + emit NativeSurchargeUpdated(nativeSurcharge); + + //set the surcharge + setNativeSurcharge(nativeSurcharge, ADMIN); + } + + function test_subscriberDiscountEventIsEmittedOnUpdate() public { + //native surcharge + uint64 discount = FEE_SCALAR / 3; + + //an event should be emitted + vm.expectEmit(); + + //emit the event that is expected to be emitted + emit SubscriberDiscountUpdated(USER, DEFAULT_FEED_1_V3, address(native), discount); + + //set the surcharge + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), discount, ADMIN); + } + + function test_eventIsEmittedUponWithdraw() public { + //simulate a fee + mintLink(address(feeManager), DEFAULT_LINK_MINT_QUANTITY); + + //the amount to withdraw + uint192 withdrawAmount = 1; + + //expect an emit + vm.expectEmit(); + + //the event to be emitted + emit Withdraw(ADMIN, ADMIN, address(link), withdrawAmount); + + //withdraw some balance + withdraw(address(link), ADMIN, withdrawAmount, ADMIN); + } + + function test_linkAvailableForPaymentReturnsLinkBalance() public { + //simulate a deposit of link for the conversion pool + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE); + + //check there's a balance + assertGt(getLinkBalance(address(feeManager)), 0); + + //check the link available for payment is the link balance + assertEq(feeManager.linkAvailableForPayment(), getLinkBalance(address(feeManager))); + } + + function test_payLinkDeficit() public { + //get the default payload + bytes memory payload = getPayload(getV2Report(DEFAULT_FEED_1_V3)); + + approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE, USER); + + //not enough funds in the reward pool should trigger an insufficient link event + vm.expectEmit(); + + IDestinationRewardManager.FeePayment[] memory contractFees = new IDestinationRewardManager.FeePayment[](1); + contractFees[0] = IDestinationRewardManager.FeePayment(DEFAULT_CONFIG_DIGEST, uint192(DEFAULT_REPORT_LINK_FEE)); + + emit InsufficientLink(contractFees); + + //process the fee + processFee(contractFees[0].poolId, payload, USER, address(native), 0); + + //double check the rewardManager balance is 0 + assertEq(getLinkBalance(address(rewardManager)), 0); + + //simulate a deposit of link to cover the deficit + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE); + + vm.expectEmit(); + emit LinkDeficitCleared(DEFAULT_CONFIG_DIGEST, DEFAULT_REPORT_LINK_FEE); + + //pay the deficit which will transfer link from the rewardManager to the rewardManager + payLinkDeficit(DEFAULT_CONFIG_DIGEST, ADMIN); + + //check the rewardManager received the link + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); + } + + function test_payLinkDeficitTwice() public { + //get the default payload + bytes memory payload = getPayload(getV2Report(DEFAULT_FEED_1_V3)); + + approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE, USER); + + //not enough funds in the reward pool should trigger an insufficient link event + vm.expectEmit(); + + IDestinationRewardManager.FeePayment[] memory contractFees = new IDestinationRewardManager.FeePayment[](1); + contractFees[0] = IDestinationRewardManager.FeePayment(DEFAULT_CONFIG_DIGEST, uint192(DEFAULT_REPORT_LINK_FEE)); + + //emit the event that is expected to be emitted + emit InsufficientLink(contractFees); + + //process the fee + processFee(contractFees[0].poolId, payload, USER, address(native), 0); + + //double check the rewardManager balance is 0 + assertEq(getLinkBalance(address(rewardManager)), 0); + + //simulate a deposit of link to cover the deficit + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE); + + vm.expectEmit(); + emit LinkDeficitCleared(DEFAULT_CONFIG_DIGEST, DEFAULT_REPORT_LINK_FEE); + + //pay the deficit which will transfer link from the rewardManager to the rewardManager + payLinkDeficit(DEFAULT_CONFIG_DIGEST, ADMIN); + + //check the rewardManager received the link + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); + + //paying again should revert with 0 + vm.expectRevert(ZERO_DEFICIT); + + payLinkDeficit(DEFAULT_CONFIG_DIGEST, ADMIN); + } + + function test_payLinkDeficitPaysAllFeesProcessed() public { + //get the default payload + bytes memory payload = getPayload(getV2Report(DEFAULT_FEED_1_V3)); + + //approve the native to be transferred from the user + approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE * 2, USER); + + //processing the fee will transfer the native from the user to the feeManager + processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(native), 0); + processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(native), 0); + + //check the deficit has been increased twice + assertEq(getLinkDeficit(DEFAULT_CONFIG_DIGEST), DEFAULT_REPORT_LINK_FEE * 2); + + //double check the rewardManager balance is 0 + assertEq(getLinkBalance(address(rewardManager)), 0); + + //simulate a deposit of link to cover the deficit + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE * 2); + + vm.expectEmit(); + emit LinkDeficitCleared(DEFAULT_CONFIG_DIGEST, DEFAULT_REPORT_LINK_FEE * 2); + + //pay the deficit which will transfer link from the rewardManager to the rewardManager + payLinkDeficit(DEFAULT_CONFIG_DIGEST, ADMIN); + + //check the rewardManager received the link + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE * 2); + } + + function test_payLinkDeficitOnlyCallableByAdmin() public { + vm.expectRevert(ONLY_CALLABLE_BY_OWNER_ERROR); + + payLinkDeficit(DEFAULT_CONFIG_DIGEST, USER); + } + + function test_revertOnSettingAnAddressZeroVerifier() public { + vm.expectRevert(INVALID_ADDRESS_ERROR); + feeManager.addVerifier(address(0)); + } + + function test_onlyCallableByOwnerReverts() public { + address STRANGER = address(999); + changePrank(STRANGER); + vm.expectRevert(bytes("Only callable by owner")); + feeManager.addVerifier(address(0)); + } + + function test_addVerifierExistingAddress() public { + address dummyAddress = address(998); + feeManager.addVerifier(dummyAddress); + vm.expectRevert(INVALID_ADDRESS_ERROR); + feeManager.addVerifier(dummyAddress); + } + + function test_addVerifier() public { + address dummyAddress = address(998); + feeManager.addVerifier(dummyAddress); + vm.expectRevert(INVALID_ADDRESS_ERROR); + feeManager.addVerifier(dummyAddress); + + // check calls to setFeeRecipients it should not error unauthorized + changePrank(dummyAddress); + bytes32 dummyConfigDigest = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef; + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](1); + recipients[0] = Common.AddressAndWeight(address(991), 1e18); + feeManager.setFeeRecipients(dummyConfigDigest, recipients); + + // removing this verifier should result in unauthorized when calling setFeeRecipients + changePrank(ADMIN); + feeManager.removeVerifier(dummyAddress); + changePrank(dummyAddress); + vm.expectRevert(UNAUTHORIZED_ERROR); + feeManager.setFeeRecipients(dummyConfigDigest, recipients); + } + + function test_removeVerifierZeroAaddress() public { + address dummyAddress = address(0); + vm.expectRevert(INVALID_ADDRESS_ERROR); + feeManager.removeVerifier(dummyAddress); + } + + function test_removeVerifierNonExistentAddress() public { + address dummyAddress = address(991); + vm.expectRevert(INVALID_ADDRESS_ERROR); + feeManager.removeVerifier(dummyAddress); + } + + function test_setRewardManagerZeroAddress() public { + vm.expectRevert(INVALID_ADDRESS_ERROR); + feeManager.setRewardManager(address(0)); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/DestinationFeeManager.getFeeAndReward.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/DestinationFeeManager.getFeeAndReward.t.sol new file mode 100644 index 0000000000..ddd3ac5a8e --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/DestinationFeeManager.getFeeAndReward.t.sol @@ -0,0 +1,720 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {Common} from "../../../libraries/Common.sol"; +import "./BaseDestinationFeeManager.t.sol"; + +/** + * @title BaseFeeManagerTest + * @author Michael Fletcher + * @notice This contract will test the functionality of the feeManager's getFeeAndReward + */ +contract DestinationFeeManagerProcessFeeTest is BaseDestinationFeeManagerTest { + function test_baseFeeIsAppliedForNative() public view { + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //fee should be the default + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE); + } + + function test_baseFeeIsAppliedForLink() public view { + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //fee should be the default + assertEq(fee.amount, DEFAULT_REPORT_LINK_FEE); + } + + function test_discountAIsNotAppliedWhenSetForOtherUsers() public { + //set the subscriber discount for another user + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 2, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), INVALID_ADDRESS); + + //fee should be the default + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE); + } + + function test_discountIsNotAppliedForInvalidTokenAddress() public { + //should revert with invalid address as it's not a configured token + vm.expectRevert(INVALID_ADDRESS_ERROR); + + //set the subscriber discount for another user + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, INVALID_ADDRESS, FEE_SCALAR / 2, ADMIN); + } + + function test_discountIsAppliedForLink() public { + //set the subscriber discount to 50% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 2, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //fee should be half the default + assertEq(fee.amount, DEFAULT_REPORT_LINK_FEE / 2); + } + + function test_DiscountIsAppliedForNative() public { + //set the subscriber discount to 50% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //fee should be half the default + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE / 2); + } + + function test_discountIsNoLongerAppliedAfterRemoving() public { + //set the subscriber discount to 50% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 2, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //fee should be half the default + assertEq(fee.amount, DEFAULT_REPORT_LINK_FEE / 2); + + //remove the discount + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), 0, ADMIN); + + //get the fee required by the feeManager + fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //fee should be the default + assertEq(fee.amount, DEFAULT_REPORT_LINK_FEE); + } + + function test_surchargeIsApplied() public { + //native surcharge + uint256 nativeSurcharge = FEE_SCALAR / 5; + + //set the surcharge + setNativeSurcharge(nativeSurcharge, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //calculate the expected surcharge + uint256 expectedSurcharge = ((DEFAULT_REPORT_NATIVE_FEE * nativeSurcharge) / FEE_SCALAR); + + //expected fee should the base fee offset by the surcharge and discount + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE + expectedSurcharge); + } + + function test_surchargeIsNotAppliedForLinkFee() public { + //native surcharge + uint256 nativeSurcharge = FEE_SCALAR / 5; + + //set the surcharge + setNativeSurcharge(nativeSurcharge, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //fee should be the default + assertEq(fee.amount, DEFAULT_REPORT_LINK_FEE); + } + + function test_surchargeIsNoLongerAppliedAfterRemoving() public { + //native surcharge + uint256 nativeSurcharge = FEE_SCALAR / 5; + + //set the surcharge + setNativeSurcharge(nativeSurcharge, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //calculate the expected surcharge + uint256 expectedSurcharge = ((DEFAULT_REPORT_NATIVE_FEE * nativeSurcharge) / FEE_SCALAR); + + //expected fee should be the base fee offset by the surcharge and discount + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE + expectedSurcharge); + + //remove the surcharge + setNativeSurcharge(0, ADMIN); + + //get the fee required by the feeManager + fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //fee should be the default + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE); + } + + function test_feeIsUpdatedAfterNewSurchargeIsApplied() public { + //native surcharge + uint256 nativeSurcharge = FEE_SCALAR / 5; + + //set the surcharge + setNativeSurcharge(nativeSurcharge, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //calculate the expected surcharge + uint256 expectedSurcharge = ((DEFAULT_REPORT_NATIVE_FEE * nativeSurcharge) / FEE_SCALAR); + + //expected fee should the base fee offset by the surcharge and discount + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE + expectedSurcharge); + + //change the surcharge + setNativeSurcharge(nativeSurcharge, ADMIN); + + //get the fee required by the feeManager + fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //calculate the expected surcharge + expectedSurcharge = ((DEFAULT_REPORT_NATIVE_FEE * nativeSurcharge) / FEE_SCALAR); + + //expected fee should the base fee offset by the surcharge and discount + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE + expectedSurcharge); + } + + function test_surchargeIsAppliedForNativeFeeWithDiscount() public { + //native surcharge + uint256 nativeSurcharge = FEE_SCALAR / 5; + + //set the subscriber discount to 50% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN); + + //set the surcharge + setNativeSurcharge(nativeSurcharge, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //calculate the expected surcharge quantity + uint256 expectedSurcharge = ((DEFAULT_REPORT_NATIVE_FEE * nativeSurcharge) / FEE_SCALAR); + + //calculate the expected discount quantity + uint256 expectedDiscount = ((DEFAULT_REPORT_NATIVE_FEE + expectedSurcharge) / 2); + + //expected fee should the base fee offset by the surcharge and discount + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE + expectedSurcharge - expectedDiscount); + } + + function test_emptyQuoteRevertsWithError() public { + //expect a revert + vm.expectRevert(INVALID_QUOTE_ERROR); + + //get the fee required by the feeManager + getFee(getV3Report(DEFAULT_FEED_1_V3), address(0), USER); + } + + function test_nativeSurcharge100Percent() public { + //set the surcharge + setNativeSurcharge(FEE_SCALAR, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //fee should be twice the base fee + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE * 2); + } + + function test_nativeSurcharge0Percent() public { + //set the surcharge + setNativeSurcharge(0, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //fee should base fee + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE); + } + + function test_nativeSurchargeCannotExceed100Percent() public { + //should revert if surcharge is greater than 100% + vm.expectRevert(INVALID_SURCHARGE_ERROR); + + //set the surcharge above the max + setNativeSurcharge(FEE_SCALAR + 1, ADMIN); + } + + function test_discountIsAppliedWith100PercentSurcharge() public { + //set the subscriber discount to 50% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN); + + //set the surcharge + setNativeSurcharge(FEE_SCALAR, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //calculate the expected discount quantity + uint256 expectedDiscount = DEFAULT_REPORT_NATIVE_FEE; + + //fee should be twice the surcharge minus the discount + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE * 2 - expectedDiscount); + } + + function test_feeIsZeroWith100PercentDiscount() public { + //set the subscriber discount to 100% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //fee should be zero + assertEq(fee.amount, 0); + } + + function test_feeIsUpdatedAfterDiscountIsRemoved() public { + //set the subscriber discount to 50% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //calculate the expected discount quantity + uint256 expectedDiscount = DEFAULT_REPORT_NATIVE_FEE / 2; + + //fee should be 50% of the base fee + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE - expectedDiscount); + + //remove the discount + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), 0, ADMIN); + + //get the fee required by the feeManager + fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //fee should be the base fee + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE); + } + + function test_feeIsUpdatedAfterNewDiscountIsApplied() public { + //set the subscriber discount to 50% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //calculate the expected discount quantity + uint256 expectedDiscount = DEFAULT_REPORT_NATIVE_FEE / 2; + + //fee should be 50% of the base fee + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE - expectedDiscount); + + //change the discount to 25% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 4, ADMIN); + + //get the fee required by the feeManager + fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //expected discount is now 25% + expectedDiscount = DEFAULT_REPORT_NATIVE_FEE / 4; + + //fee should be the base fee minus the expected discount + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE - expectedDiscount); + } + + function test_setDiscountOver100Percent() public { + //should revert with invalid discount + vm.expectRevert(INVALID_DISCOUNT_ERROR); + + //set the subscriber discount to over 100% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR + 1, ADMIN); + } + + function test_surchargeIsNotAppliedWith100PercentDiscount() public { + //native surcharge + uint256 nativeSurcharge = FEE_SCALAR / 5; + + //set the subscriber discount to 100% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR, ADMIN); + + //set the surcharge + setNativeSurcharge(nativeSurcharge, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //fee should be zero + assertEq(fee.amount, 0); + } + + function test_nonAdminUserCanNotSetDiscount() public { + //should revert with unauthorized + vm.expectRevert(ONLY_CALLABLE_BY_OWNER_ERROR); + + //set the subscriber discount to 50% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR, USER); + } + + function test_surchargeFeeRoundsUpWhenUneven() public { + //native surcharge + uint256 nativeSurcharge = FEE_SCALAR / 3; + + //set the surcharge + setNativeSurcharge(nativeSurcharge, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //calculate the expected surcharge quantity + uint256 expectedSurcharge = (DEFAULT_REPORT_NATIVE_FEE * nativeSurcharge) / FEE_SCALAR; + + //expected fee should the base fee offset by the expected surcharge + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE + expectedSurcharge + 1); + } + + function test_discountFeeRoundsDownWhenUneven() public { + //native surcharge + uint256 discount = FEE_SCALAR / 3; + + //set the subscriber discount to 33.333% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), discount, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //calculate the expected quantity + uint256 expectedDiscount = ((DEFAULT_REPORT_NATIVE_FEE * discount) / FEE_SCALAR); + + //expected fee should the base fee offset by the expected surcharge + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE - expectedDiscount); + } + + function test_reportWithNoExpiryOrFeeReturnsZero() public view { + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV1Report(DEFAULT_FEED_1_V1), getNativeQuote(), USER); + + //fee should be zero + assertEq(fee.amount, 0); + } + + function test_correctDiscountIsAppliedWhenBothTokensAreDiscounted() public { + //set the subscriber and native discounts + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 4, ADMIN); + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN); + + //get the fee required by the feeManager for both tokens + Common.Asset memory linkFee = getFee(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + Common.Asset memory nativeFee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //calculate the expected discount quantity for each token + uint256 expectedDiscountLink = (DEFAULT_REPORT_LINK_FEE * FEE_SCALAR) / 4 / FEE_SCALAR; + uint256 expectedDiscountNative = (DEFAULT_REPORT_NATIVE_FEE * FEE_SCALAR) / 2 / FEE_SCALAR; + + //check the fee calculation for each token + assertEq(linkFee.amount, DEFAULT_REPORT_LINK_FEE - expectedDiscountLink); + assertEq(nativeFee.amount, DEFAULT_REPORT_NATIVE_FEE - expectedDiscountNative); + } + + function test_discountIsNotAppliedToOtherFeeds() public { + //set the subscriber discount to 50% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_2_V3), getNativeQuote(), USER); + + //fee should be the base fee + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE); + } + + function test_noFeeIsAppliedWhenReportHasZeroFee() public { + //set the subscriber discount to 50% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee( + getV3ReportWithCustomExpiryAndFee(DEFAULT_FEED_1_V3, uint32(block.timestamp), 0, 0), + getNativeQuote(), + USER + ); + + //fee should be zero + assertEq(fee.amount, 0); + } + + function test_noFeeIsAppliedWhenReportHasZeroFeeAndDiscountAndSurchargeIsSet() public { + //set the subscriber discount to 50% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN); + + //set the surcharge + setNativeSurcharge(FEE_SCALAR / 2, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee( + getV3ReportWithCustomExpiryAndFee(DEFAULT_FEED_1_V3, uint32(block.timestamp), 0, 0), + getNativeQuote(), + USER + ); + + //fee should be zero + assertEq(fee.amount, 0); + } + + function test_nativeSurchargeEventIsEmittedOnUpdate() public { + //native surcharge + uint64 nativeSurcharge = FEE_SCALAR / 3; + + //an event should be emitted + vm.expectEmit(); + + //emit the event that is expected to be emitted + emit NativeSurchargeUpdated(nativeSurcharge); + + //set the surcharge + setNativeSurcharge(nativeSurcharge, ADMIN); + } + + function test_getBaseRewardWithLinkQuote() public view { + //get the fee required by the feeManager + Common.Asset memory reward = getReward(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //the reward should equal the base fee + assertEq(reward.amount, DEFAULT_REPORT_LINK_FEE); + } + + function test_getRewardWithLinkQuoteAndLinkDiscount() public { + //set the link discount + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 2, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory reward = getReward(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //the reward should equal the discounted base fee + assertEq(reward.amount, DEFAULT_REPORT_LINK_FEE / 2); + } + + function test_getRewardWithNativeQuote() public view { + //get the fee required by the feeManager + Common.Asset memory reward = getReward(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //the reward should equal the base fee in link + assertEq(reward.amount, DEFAULT_REPORT_LINK_FEE); + } + + function test_getRewardWithNativeQuoteAndSurcharge() public { + //set the native surcharge + setNativeSurcharge(FEE_SCALAR / 2, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory reward = getReward(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //the reward should equal the base fee in link regardless of the surcharge + assertEq(reward.amount, DEFAULT_REPORT_LINK_FEE); + } + + function test_getRewardWithLinkDiscount() public { + //set the link discount + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 2, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory reward = getReward(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //the reward should equal the discounted base fee + assertEq(reward.amount, DEFAULT_REPORT_LINK_FEE / 2); + } + + function test_getLinkFeeIsRoundedUp() public { + //set the link discount + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 3, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //the reward should equal .66% + 1 of the base fee due to a 33% discount rounded up + assertEq(fee.amount, (DEFAULT_REPORT_LINK_FEE * 2) / 3 + 1); + } + + function test_getLinkRewardIsSameAsFee() public { + //set the link discount + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 3, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + Common.Asset memory reward = getReward(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //check the reward is in link + assertEq(fee.assetAddress, address(link)); + + //the reward should equal .66% of the base fee due to a 33% discount rounded down + assertEq(reward.amount, fee.amount); + } + + function test_getLinkRewardWithNativeQuoteAndSurchargeWithLinkDiscount() public { + //set the native surcharge + setNativeSurcharge(FEE_SCALAR / 2, ADMIN); + + //set the link discount + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 3, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory reward = getReward(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //the reward should equal the base fee in link regardless of the surcharge + assertEq(reward.amount, DEFAULT_REPORT_LINK_FEE); + } + + function test_testRevertIfReportHasExpired() public { + //expect a revert + vm.expectRevert(EXPIRED_REPORT_ERROR); + + //get the fee required by the feeManager + getFee( + getV3ReportWithCustomExpiryAndFee( + DEFAULT_FEED_1_V3, + block.timestamp - 1, + DEFAULT_REPORT_LINK_FEE, + DEFAULT_REPORT_NATIVE_FEE + ), + getNativeQuote(), + USER + ); + } + + function test_discountIsReturnedForLink() public { + //set the subscriber discount to 50% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 2, ADMIN); + + //get the fee applied + uint256 discount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //fee should be half the default + assertEq(discount, FEE_SCALAR / 2); + } + + function test_DiscountIsReturnedForNative() public { + //set the subscriber discount to 50% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN); + + //get the discount applied + uint256 discount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //fee should be half the default + assertEq(discount, FEE_SCALAR / 2); + } + + function test_DiscountIsReturnedForNativeWithSurcharge() public { + //set the subscriber discount to 50% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN); + + //set the surcharge + setNativeSurcharge(FEE_SCALAR / 5, ADMIN); + + //get the discount applied + uint256 discount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //fee should be half the default + assertEq(discount, FEE_SCALAR / 2); + } + + function test_GlobalDiscountWithNative() public { + //set the global discount to 50% + setSubscriberGlobalDiscount(USER, address(native), FEE_SCALAR / 2, ADMIN); + + //get the discount applied + uint256 discount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //fee should be half the default + assertEq(discount, FEE_SCALAR / 2); + } + + function test_GlobalDiscountWithLink() public { + //set the global discount to 50% + setSubscriberGlobalDiscount(USER, address(link), FEE_SCALAR / 2, ADMIN); + + //get the discount applied + uint256 discount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //fee should be half the default + assertEq(discount, FEE_SCALAR / 2); + } + + function test_GlobalDiscountWithNativeAndLink() public { + //set the global discount to 50% + setSubscriberGlobalDiscount(USER, address(native), FEE_SCALAR / 2, ADMIN); + setSubscriberGlobalDiscount(USER, address(link), FEE_SCALAR / 2, ADMIN); + + //get the discount applied + uint256 discount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //fee should be half the default + assertEq(discount, FEE_SCALAR / 2); + } + + function test_GlobalDiscountIsOverridenByIndividualDiscountNative() public { + //set the global discount to 50% + setSubscriberGlobalDiscount(USER, address(native), FEE_SCALAR / 2, ADMIN); + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 4, ADMIN); + + //get the discount applied + uint256 discount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //fee should be half the default + assertEq(discount, FEE_SCALAR / 4); + } + + function test_GlobalDiscountIsOverridenByIndividualDiscountLink() public { + //set the global discount to 50% + setSubscriberGlobalDiscount(USER, address(link), FEE_SCALAR / 2, ADMIN); + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 4, ADMIN); + + //get the discount applied + uint256 discount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //fee should be half the default + assertEq(discount, FEE_SCALAR / 4); + } + + function test_GlobalDiscountIsUpdatedAfterBeingSetToZeroLink() public { + //set the global discount to 50% + setSubscriberGlobalDiscount(USER, address(link), FEE_SCALAR / 2, ADMIN); + + //get the discount applied + uint256 discount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //fee should be half the default + assertEq(discount, FEE_SCALAR / 2); + + //set the global discount to zero + setSubscriberGlobalDiscount(USER, address(link), 0, ADMIN); + + //get the discount applied + discount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //fee should be zero + assertEq(discount, 0); + } + + function test_GlobalDiscountIsUpdatedAfterBeingSetToZeroNative() public { + //set the global discount to 50% + setSubscriberGlobalDiscount(USER, address(native), FEE_SCALAR / 2, ADMIN); + + //get the discount applied + uint256 discount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //fee should be half the default + assertEq(discount, FEE_SCALAR / 2); + + //set the global discount to zero + setSubscriberGlobalDiscount(USER, address(native), 0, ADMIN); + + //get the discount applied + discount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //fee should be zero + assertEq(discount, 0); + } + + function test_GlobalDiscountCantBeSetToMoreThanMaximum() public { + //should revert with invalid discount + vm.expectRevert(INVALID_DISCOUNT_ERROR); + + //set the global discount to 101% + setSubscriberGlobalDiscount(USER, address(native), FEE_SCALAR + 1, ADMIN); + } + + function test_onlyOwnerCanSetGlobalDiscount() public { + //should revert with unauthorized + vm.expectRevert(ONLY_CALLABLE_BY_OWNER_ERROR); + + //set the global discount to 50% + setSubscriberGlobalDiscount(USER, address(native), FEE_SCALAR / 2, USER); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/DestinationFeeManager.processFee.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/DestinationFeeManager.processFee.t.sol new file mode 100644 index 0000000000..0880352dca --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/DestinationFeeManager.processFee.t.sol @@ -0,0 +1,492 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {Common} from "../../../libraries/Common.sol"; +import "./BaseDestinationFeeManager.t.sol"; +import {IDestinationRewardManager} from "../../interfaces/IDestinationRewardManager.sol"; + +/** + * @title BaseFeeManagerTest + * @author Michael Fletcher + * @notice This contract will test the functionality of the feeManager processFee + */ +contract DestinationFeeManagerProcessFeeTest is BaseDestinationFeeManagerTest { + function setUp() public override { + super.setUp(); + } + + function test_nonAdminProxyUserCannotProcessFee() public { + //get the default payload + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + //should revert as the user is not the owner + vm.expectRevert(UNAUTHORIZED_ERROR); + + //process the fee + ProcessFeeAsUser(DEFAULT_CONFIG_DIGEST, payload, USER, address(link), 0, USER); + } + + function test_processFeeAsProxy() public { + //get the default payload + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + //approve the link to be transferred from the from the subscriber to the rewardManager + approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER); + + //processing the fee will transfer the link from the user to the rewardManager + processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(link), 0); + + //check the link has been transferred + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); + + //check the user has had the link fee deducted + assertEq(getLinkBalance(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE); + } + + function test_processFeeIfSubscriberIsSelf() public { + //get the default payload + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + //expect a revert due to the feeManager being the subscriber + vm.expectRevert(INVALID_ADDRESS_ERROR); + + //process the fee will fail due to assertion + processFee(DEFAULT_CONFIG_DIGEST, payload, address(feeManager), address(native), 0); + } + + function test_processFeeWithWithEmptyQuotePayload() public { + //get the default payload + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + //expect a revert as the quote is invalid + vm.expectRevert(); + + //processing the fee will transfer the link by default + processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(0), 0); + } + + function test_processFeeWithWithZeroQuotePayload() public { + //get the default payload + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + //expect a revert as the quote is invalid + vm.expectRevert(INVALID_QUOTE_ERROR); + + //processing the fee will transfer the link by default + processFee(DEFAULT_CONFIG_DIGEST, payload, USER, INVALID_ADDRESS, 0); + } + + function test_processFeeWithWithCorruptQuotePayload() public { + //get the default payload + bytes memory payload = abi.encode( + [DEFAULT_CONFIG_DIGEST, 0, 0], + getV3Report(DEFAULT_FEED_1_V3), + new bytes32[](1), + new bytes32[](1), + bytes32("") + ); + + //expect an evm revert as the quote is corrupt + vm.expectRevert(); + + //processing the fee will not withdraw anything as there is no fee to collect + processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(link), 0); + } + + function test_processFeeDefaultReportsStillVerifiesWithEmptyQuote() public { + //get the default payload + bytes memory payload = getPayload(getV1Report(DEFAULT_FEED_1_V1)); + + //processing the fee will transfer the link from the user to the rewardManager + processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(0), 0); + } + + function test_processFeeWithDefaultReportPayloadAndQuoteStillVerifies() public { + //get the default payload + bytes memory payload = getPayload(getV1Report(DEFAULT_FEED_1_V1)); + + //processing the fee will not withdraw anything as there is no fee to collect + processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(link), 0); + } + + function test_processFeeNative() public { + //simulate a deposit of link for the conversion pool + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE); + + //get the default payload + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + //approve the native to be transferred from the user + approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE, USER); + + //processing the fee will transfer the native from the user to the feeManager + processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(native), 0); + + //check the native has been transferred + assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE); + + //check the link has been transferred to the rewardManager + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); + + //check the feeManager has had the link deducted, the remaining balance should be 0 + assertEq(getLinkBalance(address(feeManager)), 0); + + //check the subscriber has had the native deducted + assertEq(getNativeBalance(USER), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE); + } + + function test_processFeeEmitsEventIfNotEnoughLink() public { + //simulate a deposit of half the link required for the fee + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE / 2); + + //get the default payload + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + //approve the native to be transferred from the user + approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE, USER); + + //expect an emit as there's not enough link + vm.expectEmit(); + + IDestinationRewardManager.FeePayment[] memory contractFees = new IDestinationRewardManager.FeePayment[](1); + contractFees[0] = IDestinationRewardManager.FeePayment(DEFAULT_CONFIG_DIGEST, uint192(DEFAULT_REPORT_LINK_FEE)); + + //emit the event that is expected to be emitted + emit InsufficientLink(contractFees); + + //processing the fee will transfer the native from the user to the feeManager + processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(native), 0); + + //check the native has been transferred + assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE); + + //check no link has been transferred to the rewardManager + assertEq(getLinkBalance(address(rewardManager)), 0); + assertEq(getLinkBalance(address(feeManager)), DEFAULT_REPORT_LINK_FEE / 2); + + //check the subscriber has had the native deducted + assertEq(getNativeBalance(USER), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE); + } + + function test_processFeeWithUnwrappedNative() public { + //simulate a deposit of link for the conversion pool + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE); + + //get the default payload + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + //only the proxy or admin can call processFee, they will pass in the native value on the users behalf + processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(native), DEFAULT_REPORT_NATIVE_FEE); + + //check the native has been transferred and converted to wrapped native + assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE); + assertEq(getNativeUnwrappedBalance(address(feeManager)), 0); + + //check the link has been transferred to the rewardManager + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); + + //check the feeManager has had the link deducted, the remaining balance should be 0 + assertEq(getLinkBalance(address(feeManager)), 0); + + //check the subscriber has had the native deducted + assertEq(getNativeUnwrappedBalance(USER), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE); + } + + function test_processFeeWithUnwrappedNativeShortFunds() public { + //simulate a deposit of link for the conversion pool + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE); + + //get the default payload + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + //expect a revert as not enough funds + vm.expectRevert(INVALID_DEPOSIT_ERROR); + + //only the proxy or admin can call processFee, they will pass in the native value on the users behalf + processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(native), DEFAULT_REPORT_NATIVE_FEE - 1); + } + + function test_processFeeWithUnwrappedNativeLinkAddress() public { + //simulate a deposit of link for the conversion pool + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE); + + //get the default payload + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + //expect a revert as not enough funds + vm.expectRevert(INSUFFICIENT_ALLOWANCE_ERROR); + + //the change will be returned and the user will attempted to be billed in LINK + processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(link), DEFAULT_REPORT_NATIVE_FEE - 1); + } + + function test_processFeeWithUnwrappedNativeLinkAddressExcessiveFee() public { + //approve the link to be transferred from the from the subscriber to the rewardManager + approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, PROXY); + + //get the default payload + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + //call processFee from the proxy to test whether the funds are returned to the subscriber. In reality, the funds would be returned to the caller of the proxy. + processFee(DEFAULT_CONFIG_DIGEST, payload, PROXY, address(link), DEFAULT_REPORT_NATIVE_FEE); + + //check the native unwrapped is no longer in the account + assertEq(getNativeBalance(address(feeManager)), 0); + assertEq(getNativeUnwrappedBalance(address(feeManager)), 0); + + //check the link has been transferred to the rewardManager + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); + + //check the feeManager has had the link deducted, the remaining balance should be 0 + assertEq(getLinkBalance(address(feeManager)), 0); + + //native should not be deducted + assertEq(getNativeUnwrappedBalance(PROXY), DEFAULT_NATIVE_MINT_QUANTITY); + } + + function test_processFeeWithUnwrappedNativeWithExcessiveFee() public { + //simulate a deposit of link for the conversion pool + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE); + + //get the default payload + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + //call processFee from the proxy to test whether the funds are returned to the subscriber. In reality, the funds would be returned to the caller of the proxy. + processFee(DEFAULT_CONFIG_DIGEST, payload, PROXY, address(native), DEFAULT_REPORT_NATIVE_FEE * 2); + + //check the native has been transferred and converted to wrapped native + assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE); + assertEq(getNativeUnwrappedBalance(address(feeManager)), 0); + + //check the link has been transferred to the rewardManager + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); + + //check the feeManager has had the link deducted, the remaining balance should be 0 + assertEq(getLinkBalance(address(feeManager)), 0); + + //check the subscriber has had the native deducted + assertEq(getNativeUnwrappedBalance(PROXY), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE); + } + + function test_processFeeUsesCorrectDigest() public { + //get the default payload + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + //approve the link to be transferred from the from the subscriber to the rewardManager + approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER); + + //processing the fee will transfer the link from the user to the rewardManager + processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(link), 0); + + //check the link has been transferred + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); + + //check the user has had the link fee deducted + assertEq(getLinkBalance(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE); + + //check funds have been paid to the reward manager + assertEq(rewardManager.s_totalRewardRecipientFees(DEFAULT_CONFIG_DIGEST), DEFAULT_REPORT_LINK_FEE); + } + + function test_V1PayloadVerifies() public { + //replicate a default payload + bytes memory payload = abi.encode( + [DEFAULT_CONFIG_DIGEST, 0, 0], + getV2Report(DEFAULT_FEED_1_V1), + new bytes32[](1), + new bytes32[](1), + bytes32("") + ); + + //processing the fee will transfer the link from the user to the rewardManager + processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(0), 0); + } + + function test_V2PayloadVerifies() public { + //get the default payload + bytes memory payload = getPayload(getV2Report(DEFAULT_FEED_1_V2)); + + //approve the link to be transferred from the from the subscriber to the rewardManager + approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER); + + //processing the fee will transfer the link from the user to the rewardManager + processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(link), 0); + + //check the link has been transferred + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); + + //check the user has had the link fee deducted + assertEq(getLinkBalance(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE); + } + + function test_V2PayloadWithoutQuoteFails() public { + //get the default payload + bytes memory payload = getPayload(getV2Report(DEFAULT_FEED_1_V2)); + + //expect a revert as the quote is invalid + vm.expectRevert(); + + //processing the fee will transfer the link from the user to the rewardManager + processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(0), 0); + } + + function test_V2PayloadWithoutZeroFee() public { + //get the default payload + bytes memory payload = getPayload(getV2Report(DEFAULT_FEED_1_V2)); + + //expect a revert as the quote is invalid + vm.expectRevert(); + + //processing the fee will transfer the link from the user to the rewardManager + processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(link), 0); + } + + function test_processFeeWithInvalidReportVersionFailsToDecode() public { + bytes memory data = abi.encode(0x0000100000000000000000000000000000000000000000000000000000000000); + + //get the default payload + bytes memory payload = getPayload(data); + + //serialization will fail as there is no report to decode + vm.expectRevert(); + + //processing the fee will not withdraw anything as there is no fee to collect + processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(link), 0); + } + + function test_processFeeWithZeroNativeNonZeroLinkWithNativeQuote() public { + //get the default payload + bytes memory payload = getPayload( + getV3ReportWithCustomExpiryAndFee(DEFAULT_FEED_1_V3, block.timestamp, DEFAULT_REPORT_LINK_FEE, 0) + ); + + //call processFee should not revert as the fee is 0 + processFee(DEFAULT_CONFIG_DIGEST, payload, PROXY, address(native), 0); + } + + function test_processFeeWithZeroNativeNonZeroLinkWithLinkQuote() public { + //get the default payload + bytes memory payload = getPayload( + getV3ReportWithCustomExpiryAndFee(DEFAULT_FEED_1_V3, block.timestamp, DEFAULT_REPORT_LINK_FEE, 0) + ); + + //approve the link to be transferred from the from the subscriber to the rewardManager + approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER); + + //processing the fee will transfer the link to the rewardManager from the user + processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(link), 0); + + //check the link has been transferred + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); + + //check the user has had the link fee deducted + assertEq(getLinkBalance(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE); + } + + function test_processFeeWithZeroLinkNonZeroNativeWithNativeQuote() public { + //simulate a deposit of link for the conversion pool + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE); + + //get the default payload + bytes memory payload = getPayload( + getV3ReportWithCustomExpiryAndFee(DEFAULT_FEED_1_V3, block.timestamp, 0, DEFAULT_REPORT_NATIVE_FEE) + ); + + //approve the native to be transferred from the user + approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE, USER); + + //processing the fee will transfer the native from the user to the feeManager + processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(native), 0); + + //check the native has been transferred + assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE); + + //check no link has been transferred to the rewardManager + assertEq(getLinkBalance(address(rewardManager)), 0); + + //check the feeManager has had no link deducted + assertEq(getLinkBalance(address(feeManager)), DEFAULT_REPORT_LINK_FEE); + + //check the subscriber has had the native deducted + assertEq(getNativeBalance(USER), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE); + } + + function test_processFeeWithZeroLinkNonZeroNativeWithLinkQuote() public { + //get the default payload + bytes memory payload = getPayload( + getV3ReportWithCustomExpiryAndFee(DEFAULT_FEED_1_V3, block.timestamp, 0, DEFAULT_REPORT_NATIVE_FEE) + ); + + //call processFee should not revert as the fee is 0 + processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(link), 0); + } + + function test_processFeeWithZeroNativeNonZeroLinkReturnsChange() public { + //get the default payload + bytes memory payload = getPayload( + getV3ReportWithCustomExpiryAndFee(DEFAULT_FEED_1_V3, block.timestamp, 0, DEFAULT_REPORT_NATIVE_FEE) + ); + + //call processFee should not revert as the fee is 0 + processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(link), DEFAULT_REPORT_NATIVE_FEE); + + //check the change has been returned + assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY); + } + + function test_V1PayloadVerifiesAndReturnsChange() public { + //emulate a V1 payload with no quote + bytes memory payload = getPayload(getV1Report(DEFAULT_FEED_1_V1)); + + processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(0), DEFAULT_REPORT_NATIVE_FEE); + + //Fee manager should not contain any native + assertEq(address(feeManager).balance, 0); + assertEq(getNativeBalance(address(feeManager)), 0); + + //check the unused native passed in is returned + assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY); + } + + function test_processFeeWithDiscountEmitsEvent() public { + //simulate a deposit of link for the conversion pool + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE); + + //set the subscriber discount to 50% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN); + + //approve the native to be transferred from the user + approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE / 2, USER); + + //get the default payload + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + Common.Asset memory reward = getReward(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + uint256 appliedDiscount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + vm.expectEmit(); + + emit DiscountApplied(DEFAULT_CONFIG_DIGEST, USER, fee, reward, appliedDiscount); + + //call processFee should not revert as the fee is 0 + processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(native), 0); + } + + function test_processFeeWithNoDiscountDoesNotEmitEvent() public { + //simulate a deposit of link for the conversion pool + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE); + + //approve the native to be transferred from the user + approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE, USER); + + //get the default payload + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + //call processFee should not revert as the fee is 0 + processFee(DEFAULT_CONFIG_DIGEST, payload, USER, address(native), 0); + + //no logs should have been emitted + assertEq(vm.getRecordedLogs().length, 0); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/DestinationFeeManager.processFeeBulk.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/DestinationFeeManager.processFeeBulk.t.sol new file mode 100644 index 0000000000..a50441bed6 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/fee-manager/DestinationFeeManager.processFeeBulk.t.sol @@ -0,0 +1,310 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import "./BaseDestinationFeeManager.t.sol"; +import {IDestinationRewardManager} from "../../interfaces/IDestinationRewardManager.sol"; + +/** + * @title BaseFeeManagerTest + * @author Michael Fletcher + * @notice This contract will test the functionality of the feeManager processFee + */ +contract DestinationFeeManagerProcessFeeTest is BaseDestinationFeeManagerTest { + uint256 internal constant NUMBER_OF_REPORTS = 5; + + function setUp() public override { + super.setUp(); + } + + function test_processMultipleLinkReports() public { + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + bytes[] memory payloads = new bytes[](NUMBER_OF_REPORTS); + for (uint256 i = 0; i < NUMBER_OF_REPORTS; ++i) { + payloads[i] = payload; + } + + bytes32[] memory poolIds = new bytes32[](NUMBER_OF_REPORTS); + for (uint256 i = 0; i < NUMBER_OF_REPORTS; ++i) { + poolIds[i] = DEFAULT_CONFIG_DIGEST; + } + + approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS, USER); + + processFee(poolIds, payloads, USER, address(link), DEFAULT_NATIVE_MINT_QUANTITY); + + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS); + assertEq(getLinkBalance(address(feeManager)), 0); + assertEq(getLinkBalance(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS); + + //the subscriber (user) should receive funds back and not the proxy, although when live the proxy will forward the funds sent and not cover it seen here + assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY); + assertEq(PROXY.balance, DEFAULT_NATIVE_MINT_QUANTITY); + } + + function test_processMultipleWrappedNativeReports() public { + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS + 1); + + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + bytes[] memory payloads = new bytes[](NUMBER_OF_REPORTS); + for (uint256 i; i < NUMBER_OF_REPORTS; ++i) { + payloads[i] = payload; + } + + bytes32[] memory poolIds = new bytes32[](NUMBER_OF_REPORTS); + for (uint256 i = 0; i < NUMBER_OF_REPORTS; ++i) { + poolIds[i] = DEFAULT_CONFIG_DIGEST; + } + + approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE * NUMBER_OF_REPORTS, USER); + + processFee(poolIds, payloads, USER, address(native), 0); + + assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE * NUMBER_OF_REPORTS); + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS); + assertEq(getLinkBalance(address(feeManager)), 1); + assertEq(getNativeBalance(USER), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE * NUMBER_OF_REPORTS); + } + + function test_processMultipleUnwrappedNativeReports() public { + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS + 1); + + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + bytes[] memory payloads = new bytes[](NUMBER_OF_REPORTS); + for (uint256 i; i < NUMBER_OF_REPORTS; ++i) { + payloads[i] = payload; + } + + bytes32[] memory poolIds = new bytes32[](NUMBER_OF_REPORTS); + for (uint256 i = 0; i < NUMBER_OF_REPORTS; ++i) { + poolIds[i] = DEFAULT_CONFIG_DIGEST; + } + + processFee(poolIds, payloads, USER, address(native), DEFAULT_REPORT_NATIVE_FEE * NUMBER_OF_REPORTS * 2); + + assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE * NUMBER_OF_REPORTS); + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS); + assertEq(getLinkBalance(address(feeManager)), 1); + + assertEq(PROXY.balance, DEFAULT_NATIVE_MINT_QUANTITY); + assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE * NUMBER_OF_REPORTS); + } + + function test_processV1V2V3Reports() public { + mintLink(address(feeManager), 1); + + bytes memory payloadV1 = abi.encode( + [DEFAULT_CONFIG_DIGEST, 0, 0], + getV1Report(DEFAULT_FEED_1_V1), + new bytes32[](1), + new bytes32[](1), + bytes32("") + ); + + bytes memory linkPayloadV2 = getPayload(getV2Report(DEFAULT_FEED_1_V2)); + bytes memory linkPayloadV3 = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + bytes[] memory payloads = new bytes[](5); + payloads[0] = payloadV1; + payloads[1] = linkPayloadV2; + payloads[2] = linkPayloadV2; + payloads[3] = linkPayloadV3; + payloads[4] = linkPayloadV3; + + approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE * 4, USER); + + bytes32[] memory poolIds = new bytes32[](5); + for (uint256 i = 0; i < 5; ++i) { + poolIds[i] = DEFAULT_CONFIG_DIGEST; + } + + processFee(poolIds, payloads, USER, address(link), 0); + + assertEq(getNativeBalance(address(feeManager)), 0); + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE * 4); + assertEq(getLinkBalance(address(feeManager)), 1); + + assertEq(getLinkBalance(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE * 4); + assertEq(getNativeBalance(USER), DEFAULT_NATIVE_MINT_QUANTITY - 0); + } + + function test_processV1V2V3ReportsWithUnwrapped() public { + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE * 4 + 1); + + bytes memory payloadV1 = abi.encode( + [DEFAULT_CONFIG_DIGEST, 0, 0], + getV1Report(DEFAULT_FEED_1_V1), + new bytes32[](1), + new bytes32[](1), + bytes32("") + ); + + bytes memory nativePayloadV2 = getPayload(getV2Report(DEFAULT_FEED_1_V2)); + bytes memory nativePayloadV3 = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + bytes[] memory payloads = new bytes[](5); + payloads[0] = payloadV1; + payloads[1] = nativePayloadV2; + payloads[2] = nativePayloadV2; + payloads[3] = nativePayloadV3; + payloads[4] = nativePayloadV3; + + bytes32[] memory poolIds = new bytes32[](5); + for (uint256 i = 0; i < 5; ++i) { + poolIds[i] = DEFAULT_CONFIG_DIGEST; + } + + processFee(poolIds, payloads, USER, address(native), DEFAULT_REPORT_NATIVE_FEE * 4); + + assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE * 4); + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE * 4); + assertEq(getLinkBalance(address(feeManager)), 1); + + assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE * 4); + assertEq(PROXY.balance, DEFAULT_NATIVE_MINT_QUANTITY); + } + + function test_processMultipleV1Reports() public { + bytes memory payload = abi.encode( + [DEFAULT_CONFIG_DIGEST, 0, 0], + getV1Report(DEFAULT_FEED_1_V1), + new bytes32[](1), + new bytes32[](1), + bytes32("") + ); + + bytes[] memory payloads = new bytes[](NUMBER_OF_REPORTS); + for (uint256 i = 0; i < NUMBER_OF_REPORTS; ++i) { + payloads[i] = payload; + } + + bytes32[] memory poolIds = new bytes32[](NUMBER_OF_REPORTS); + for (uint256 i = 0; i < NUMBER_OF_REPORTS; ++i) { + poolIds[i] = DEFAULT_CONFIG_DIGEST; + } + + processFee(poolIds, payloads, USER, address(native), DEFAULT_REPORT_NATIVE_FEE * 5); + + assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY); + assertEq(PROXY.balance, DEFAULT_NATIVE_MINT_QUANTITY); + } + + function test_eventIsEmittedIfNotEnoughLink() public { + bytes memory nativePayload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + bytes[] memory payloads = new bytes[](5); + payloads[0] = nativePayload; + payloads[1] = nativePayload; + payloads[2] = nativePayload; + payloads[3] = nativePayload; + payloads[4] = nativePayload; + + approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE * 5, USER); + + IDestinationRewardManager.FeePayment[] memory payments = new IDestinationRewardManager.FeePayment[](5); + payments[0] = IDestinationRewardManager.FeePayment(DEFAULT_CONFIG_DIGEST, uint192(DEFAULT_REPORT_LINK_FEE)); + payments[1] = IDestinationRewardManager.FeePayment(DEFAULT_CONFIG_DIGEST, uint192(DEFAULT_REPORT_LINK_FEE)); + payments[2] = IDestinationRewardManager.FeePayment(DEFAULT_CONFIG_DIGEST, uint192(DEFAULT_REPORT_LINK_FEE)); + payments[3] = IDestinationRewardManager.FeePayment(DEFAULT_CONFIG_DIGEST, uint192(DEFAULT_REPORT_LINK_FEE)); + payments[4] = IDestinationRewardManager.FeePayment(DEFAULT_CONFIG_DIGEST, uint192(DEFAULT_REPORT_LINK_FEE)); + + vm.expectEmit(); + + bytes32[] memory poolIds = new bytes32[](5); + for (uint256 i = 0; i < 5; ++i) { + poolIds[i] = payments[i].poolId; + } + + emit InsufficientLink(payments); + + processFee(poolIds, payloads, USER, address(native), 0); + + assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE * 5); + assertEq(getNativeBalance(USER), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE * 5); + assertEq(getLinkBalance(USER), DEFAULT_LINK_MINT_QUANTITY); + } + + function test_processPoolIdsPassedMismatched() public { + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS + 1); + + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + bytes[] memory payloads = new bytes[](NUMBER_OF_REPORTS); + for (uint256 i; i < NUMBER_OF_REPORTS; ++i) { + payloads[i] = payload; + } + + // poolIds passed are different that number of reports in payload + bytes32[] memory poolIds = new bytes32[](NUMBER_OF_REPORTS - 1); + for (uint256 i = 0; i < NUMBER_OF_REPORTS - 1; ++i) { + poolIds[i] = DEFAULT_CONFIG_DIGEST; + } + + vm.expectRevert(POOLID_MISMATCH_ERROR); + processFee(poolIds, payloads, USER, address(native), DEFAULT_REPORT_NATIVE_FEE * NUMBER_OF_REPORTS * 2); + } + + function test_poolIdsCannotBeZeroAddress() public { + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS + 1); + + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + bytes[] memory payloads = new bytes[](NUMBER_OF_REPORTS); + for (uint256 i; i < NUMBER_OF_REPORTS; ++i) { + payloads[i] = payload; + } + + bytes32[] memory poolIds = new bytes32[](NUMBER_OF_REPORTS); + for (uint256 i = 0; i < NUMBER_OF_REPORTS; ++i) { + poolIds[i] = DEFAULT_CONFIG_DIGEST; + } + + poolIds[2] = 0x000; + vm.expectRevert(INVALID_ADDRESS_ERROR); + processFee(poolIds, payloads, USER, address(native), DEFAULT_REPORT_NATIVE_FEE * NUMBER_OF_REPORTS * 2); + } + + function test_rewardsAreCorrectlySentToEachAssociatedPoolWhenVerifyingInBulk() public { + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + bytes[] memory payloads = new bytes[](NUMBER_OF_REPORTS); + for (uint256 i = 0; i < NUMBER_OF_REPORTS; ++i) { + payloads[i] = payload; + } + + bytes32[] memory poolIds = new bytes32[](NUMBER_OF_REPORTS); + for (uint256 i = 0; i < NUMBER_OF_REPORTS - 1; ++i) { + poolIds[i] = DEFAULT_CONFIG_DIGEST; + } + poolIds[NUMBER_OF_REPORTS - 1] = DEFAULT_CONFIG_DIGEST2; + + approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS, USER); + + // Checking no rewards yet for each pool + for (uint256 i = 0; i < NUMBER_OF_REPORTS; ++i) { + bytes32 p_id = poolIds[i]; + uint256 poolDeficit = rewardManager.s_totalRewardRecipientFees(p_id); + assertEq(poolDeficit, 0); + } + + processFee(poolIds, payloads, USER, address(link), DEFAULT_NATIVE_MINT_QUANTITY); + + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS); + assertEq(getLinkBalance(address(feeManager)), 0); + assertEq(getLinkBalance(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS); + + assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY); + assertEq(PROXY.balance, DEFAULT_NATIVE_MINT_QUANTITY); + + // Checking each pool got the correct rewards + uint256 expectedRewards = DEFAULT_REPORT_LINK_FEE * (NUMBER_OF_REPORTS - 1); + uint256 poolRewards = rewardManager.s_totalRewardRecipientFees(DEFAULT_CONFIG_DIGEST); + assertEq(poolRewards, expectedRewards); + + expectedRewards = DEFAULT_REPORT_LINK_FEE; + poolRewards = rewardManager.s_totalRewardRecipientFees(DEFAULT_CONFIG_DIGEST2); + assertEq(poolRewards, expectedRewards); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/mocks/DestinationFeeManagerProxy.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/mocks/DestinationFeeManagerProxy.sol new file mode 100644 index 0000000000..7dde878321 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/mocks/DestinationFeeManagerProxy.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {IDestinationVerifierFeeManager} from "../../interfaces/IDestinationVerifierFeeManager.sol"; + +contract DestinationFeeManagerProxy { + IDestinationVerifierFeeManager internal s_feeManager; + + function processFee(bytes32 poolId, bytes calldata payload, bytes calldata parameterPayload) public payable { + s_feeManager.processFee{value: msg.value}(poolId, payload, parameterPayload, msg.sender); + } + + function processFeeBulk( + bytes32[] memory poolIds, + bytes[] calldata payloads, + bytes calldata parameterPayload + ) public payable { + s_feeManager.processFeeBulk{value: msg.value}(poolIds, payloads, parameterPayload, msg.sender); + } + + function setDestinationFeeManager(address feeManager) public { + s_feeManager = IDestinationVerifierFeeManager(feeManager); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/BaseDestinationRewardManager.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/BaseDestinationRewardManager.t.sol new file mode 100644 index 0000000000..7cafb1629d --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/BaseDestinationRewardManager.t.sol @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {Test} from "forge-std/Test.sol"; +import {ERC20Mock} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol"; +import {DestinationRewardManager} from "../../../v0.4.0/DestinationRewardManager.sol"; +import {Common} from "../../../libraries/Common.sol"; +import {IDestinationRewardManager} from "../../interfaces/IDestinationRewardManager.sol"; + +/** + * @title DestinationRewardManagerTest + * @author Michael Fletcher + * @notice Base class for all reward manager tests + * @dev This contract is intended to be inherited from and not used directly. It contains functionality to setup a primary and secondary pool + */ +contract BaseDestinationRewardManagerTest is Test { + //contracts + ERC20Mock internal asset; + ERC20Mock internal unsupported; + DestinationRewardManager internal rewardManager; + + //default address for unregistered recipient + address internal constant INVALID_ADDRESS = address(0); + //contract owner + address internal constant ADMIN = address(uint160(uint256(keccak256("ADMIN")))); + //address to represent feeManager contract + address internal constant FEE_MANAGER = address(uint160(uint256(keccak256("FEE_MANAGER")))); + //address to represent another feeManager + address internal constant FEE_MANAGER_2 = address(uint160(uint256(keccak256("FEE_MANAGER_2")))); + //a general user + address internal constant USER = address(uint160(uint256(keccak256("USER")))); + + //default recipients configured in reward manager + address internal constant DEFAULT_RECIPIENT_1 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_1")))); + address internal constant DEFAULT_RECIPIENT_2 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_2")))); + address internal constant DEFAULT_RECIPIENT_3 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_3")))); + address internal constant DEFAULT_RECIPIENT_4 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_4")))); + address internal constant DEFAULT_RECIPIENT_5 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_5")))); + address internal constant DEFAULT_RECIPIENT_6 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_6")))); + address internal constant DEFAULT_RECIPIENT_7 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_7")))); + + //additional recipients not in the reward manager + address internal constant DEFAULT_RECIPIENT_8 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_8")))); + address internal constant DEFAULT_RECIPIENT_9 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_9")))); + + //two pools should be enough to test all edge cases + bytes32 internal constant PRIMARY_POOL_ID = keccak256("primary_pool"); + bytes32 internal constant SECONDARY_POOL_ID = keccak256("secondary_pool"); + bytes32 internal constant INVALID_POOL_ID = keccak256("invalid_pool"); + bytes32 internal constant ZERO_POOL_ID = bytes32(0); + + //convenience arrays of all pool combinations used for testing + bytes32[] internal PRIMARY_POOL_ARRAY = [PRIMARY_POOL_ID]; + bytes32[] internal SECONDARY_POOL_ARRAY = [SECONDARY_POOL_ID]; + bytes32[] internal ALL_POOLS = [PRIMARY_POOL_ID, SECONDARY_POOL_ID]; + + //erc20 config + uint256 internal constant DEFAULT_MINT_QUANTITY = 100 ether; + + //reward scalar (this should match the const in the contract) + uint64 internal constant POOL_SCALAR = 1e18; + uint64 internal constant ONE_PERCENT = POOL_SCALAR / 100; + uint64 internal constant FIFTY_PERCENT = POOL_SCALAR / 2; + uint64 internal constant TEN_PERCENT = POOL_SCALAR / 10; + + //the selector for each error + bytes4 internal immutable UNAUTHORIZED_ERROR_SELECTOR = DestinationRewardManager.Unauthorized.selector; + bytes4 internal immutable INVALID_ADDRESS_ERROR_SELECTOR = DestinationRewardManager.InvalidAddress.selector; + bytes4 internal immutable INVALID_WEIGHT_ERROR_SELECTOR = DestinationRewardManager.InvalidWeights.selector; + bytes4 internal immutable INVALID_POOL_ID_ERROR_SELECTOR = DestinationRewardManager.InvalidPoolId.selector; + bytes internal constant ONLY_CALLABLE_BY_OWNER_ERROR = "Only callable by owner"; + bytes4 internal immutable INVALID_POOL_LENGTH_SELECTOR = DestinationRewardManager.InvalidPoolLength.selector; + + // Events emitted within the reward manager + event RewardRecipientsUpdated(bytes32 indexed poolId, Common.AddressAndWeight[] newRewardRecipients); + event RewardsClaimed(bytes32 indexed poolId, address indexed recipient, uint192 quantity); + event FeeManagerUpdated(address newProxyAddress); + event FeePaid(IDestinationRewardManager.FeePayment[] payments, address payee); + + function setUp() public virtual { + //change to admin user + vm.startPrank(ADMIN); + + //init required contracts + _initializeERC20Contracts(); + _initializeRewardManager(); + } + + function _initializeERC20Contracts() internal { + //create the contracts + asset = new ERC20Mock("ASSET", "AST", ADMIN, 0); + unsupported = new ERC20Mock("UNSUPPORTED", "UNS", ADMIN, 0); + + //mint some tokens to the admin + asset.mint(ADMIN, DEFAULT_MINT_QUANTITY); + unsupported.mint(ADMIN, DEFAULT_MINT_QUANTITY); + + //mint some tokens to the user + asset.mint(FEE_MANAGER, DEFAULT_MINT_QUANTITY); + unsupported.mint(FEE_MANAGER, DEFAULT_MINT_QUANTITY); + } + + function _initializeRewardManager() internal { + //create the contract + rewardManager = new DestinationRewardManager(address(asset)); + + rewardManager.addFeeManager(FEE_MANAGER); + } + + function createPrimaryPool() public { + rewardManager.setRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients()); + } + + function createSecondaryPool() public { + rewardManager.setRewardRecipients(SECONDARY_POOL_ID, getSecondaryRecipients()); + } + + //override this to test variations of different recipients. changing this function will require existing tests to be updated as constants are hardcoded to be explicit + function getPrimaryRecipients() public virtual returns (Common.AddressAndWeight[] memory) { + //array of recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4); + + //init each recipient with even weights. 2500 = 25% of pool + recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, POOL_SCALAR / 4); + recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, POOL_SCALAR / 4); + recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, POOL_SCALAR / 4); + recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, POOL_SCALAR / 4); + + return recipients; + } + + function getPrimaryRecipientAddresses() public pure returns (address[] memory) { + //array of recipients + address[] memory recipients = new address[](4); + + recipients[0] = DEFAULT_RECIPIENT_1; + recipients[1] = DEFAULT_RECIPIENT_2; + recipients[2] = DEFAULT_RECIPIENT_3; + recipients[3] = DEFAULT_RECIPIENT_4; + + return recipients; + } + + //override this to test variations of different recipients. + function getSecondaryRecipients() public virtual returns (Common.AddressAndWeight[] memory) { + //array of recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4); + + //init each recipient with even weights. 2500 = 25% of pool + recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, POOL_SCALAR / 4); + recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_5, POOL_SCALAR / 4); + recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_6, POOL_SCALAR / 4); + recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_7, POOL_SCALAR / 4); + + return recipients; + } + + function getSecondaryRecipientAddresses() public pure returns (address[] memory) { + //array of recipients + address[] memory recipients = new address[](4); + + recipients[0] = DEFAULT_RECIPIENT_1; + recipients[1] = DEFAULT_RECIPIENT_5; + recipients[2] = DEFAULT_RECIPIENT_6; + recipients[3] = DEFAULT_RECIPIENT_7; + + return recipients; + } + + function addFundsToPool(bytes32 poolId, Common.Asset memory amount, address sender) public { + IDestinationRewardManager.FeePayment[] memory payments = new IDestinationRewardManager.FeePayment[](1); + payments[0] = IDestinationRewardManager.FeePayment(poolId, uint192(amount.amount)); + + addFundsToPool(payments, sender); + } + + function addFundsToPool(IDestinationRewardManager.FeePayment[] memory payments, address sender) public { + //record the current address and switch to the sender + address originalAddr = msg.sender; + changePrank(sender); + + uint256 totalPayment; + for (uint256 i; i < payments.length; ++i) { + totalPayment += payments[i].amount; + } + + //approve the amount being paid into the pool + ERC20Mock(address(asset)).approve(address(rewardManager), totalPayment); + + //this represents the verifier adding some funds to the pool + rewardManager.onFeePaid(payments, sender); + + //change back to the original address + changePrank(originalAddr); + } + + function getAsset(uint256 quantity) public view returns (Common.Asset memory) { + return Common.Asset(address(asset), quantity); + } + + function getAssetBalance(address addr) public view returns (uint256) { + return asset.balanceOf(addr); + } + + function claimRewards(bytes32[] memory poolIds, address sender) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(sender); + + //claim the rewards + rewardManager.claimRewards(poolIds); + + //change back to the original address + changePrank(originalAddr); + } + + function payRecipients(bytes32 poolId, address[] memory recipients, address sender) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(sender); + + //pay the recipients + rewardManager.payRecipients(poolId, recipients); + + //change back to the original address + changePrank(originalAddr); + } + + function setRewardRecipients(bytes32 poolId, Common.AddressAndWeight[] memory recipients, address sender) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(sender); + + //pay the recipients + rewardManager.setRewardRecipients(poolId, recipients); + + //change back to the original address + changePrank(originalAddr); + } + + function setFeeManager(address feeManager, address sender) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(sender); + + //update the proxy + rewardManager.addFeeManager(feeManager); + + //change back to the original address + changePrank(originalAddr); + } + + function updateRewardRecipients(bytes32 poolId, Common.AddressAndWeight[] memory recipients, address sender) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(sender); + + //pay the recipients + rewardManager.updateRewardRecipients(poolId, recipients); + + //change back to the original address + changePrank(originalAddr); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.claim.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.claim.t.sol new file mode 100644 index 0000000000..c0a67d0875 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.claim.t.sol @@ -0,0 +1,790 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {BaseDestinationRewardManagerTest} from "./BaseDestinationRewardManager.t.sol"; +import {Common} from "../../../libraries/Common.sol"; + +/** + * @title DestinationRewardManagerClaimTest + * @author Michael Fletcher + * @notice This contract will test the claim functionality of the RewardManager contract. + */ +contract DestinationRewardManagerClaimTest is BaseDestinationRewardManagerTest { + uint256 internal constant POOL_DEPOSIT_AMOUNT = 10e18; + + function setUp() public override { + //setup contracts + super.setUp(); + + //create a single pool for these tests + createPrimaryPool(); + + //add funds to the pool to be split among the recipients + addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + } + + function test_claimAllRecipients() public { + //expected recipient amount is 1/4 of the pool deposit + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //claim funds for each recipient within the pool + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + } + } + + function test_claimRewardsWithDuplicatePoolIdsDoesNotPayoutTwice() public { + //add funds to a different pool to ensure they're not claimed + addFundsToPool(SECONDARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + + //expected recipient amount is 1/4 of the pool deposit + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //create an array containing duplicate poolIds + bytes32[] memory poolIds = new bytes32[](2); + poolIds[0] = PRIMARY_POOL_ID; + poolIds[1] = PRIMARY_POOL_ID; + + //claim funds for each recipient within the pool + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(poolIds, recipient.addr); + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + } + + //the pool should still have the remaining + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT); + } + + function test_claimSingleRecipient() public { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[0]; + + //claim the individual rewards for this recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //expected recipient amount is 1/4 of the pool deposit + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //check the recipients balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + + //check the rewardManager has the remaining quantity + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT - expectedRecipientAmount); + } + + function test_claimMultipleRecipients() public { + //claim the individual rewards for each recipient + claimRewards(PRIMARY_POOL_ARRAY, getPrimaryRecipients()[0].addr); + claimRewards(PRIMARY_POOL_ARRAY, getPrimaryRecipients()[1].addr); + + //expected recipient amount is 1/4 of the pool deposit + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //check the recipients balance matches the ratio the recipient should have received + assertEq(getAssetBalance(getPrimaryRecipients()[0].addr), expectedRecipientAmount); + assertEq(getAssetBalance(getPrimaryRecipients()[1].addr), expectedRecipientAmount); + + //check the rewardManager has the remaining quantity + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT - (expectedRecipientAmount * 2)); + } + + function test_claimUnregisteredRecipient() public { + //claim the rewards for a recipient who isn't in this pool + claimRewards(PRIMARY_POOL_ARRAY, getSecondaryRecipients()[1].addr); + + //check the recipients didn't receive any fees from this pool + assertEq(getAssetBalance(getSecondaryRecipients()[1].addr), 0); + + //check the rewardManager has the remaining quantity + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT); + } + + function test_claimUnevenAmountRoundsDown() public { + //adding 1 to the pool should leave 1 wei worth of dust, which the contract doesn't handle due to it being economically infeasible + addFundsToPool(PRIMARY_POOL_ID, getAsset(1), FEE_MANAGER); + + //expected recipient amount is 1/4 of the pool deposit + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //claim funds for each recipient within the pool + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + } + + //check the rewardManager has the remaining quantity equals 1 wei + assertEq(getAssetBalance(address(rewardManager)), 1); + } + + function test_claimUnregisteredPoolId() public { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[0]; + + //claim the individual rewards for this recipient + claimRewards(SECONDARY_POOL_ARRAY, recipient.addr); + + //check the recipients balance is still 0 as there's no pool to receive fees from + assertEq(getAssetBalance(recipient.addr), 0); + + //check the rewardManager has the full amount + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT); + } + + function test_singleRecipientClaimMultipleDeposits() public { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[0]; + + //claim the individual rewards for this recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //expected recipient amount is 1/4 of the pool deposit + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //check the recipients balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + + //check the rewardManager has the remaining quantity, which is 3/4 of the initial deposit + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT - expectedRecipientAmount); + + //add funds to the pool to be split among the recipients + addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + + //claim the individual rewards for this recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //check the recipients balance matches the ratio the recipient should have received, which is 1/4 of each deposit + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount * 2); + + //check the rewardManager has the remaining quantity, which is now 3/4 of both deposits + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT * 2 - (expectedRecipientAmount * 2)); + } + + function test_recipientsClaimMultipleDeposits() public { + //expected recipient amount is 1/4 of the pool deposit + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //claim funds for each recipient within the pool + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + } + + //the reward manager balance should be 0 as all of the funds have been claimed + assertEq(getAssetBalance(address(rewardManager)), 0); + + //add funds to the pool to be split among the recipients + addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + + //claim funds for each recipient within the pool + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //expected recipient amount is 1/4 of the pool deposit + expectedRecipientAmount = (POOL_DEPOSIT_AMOUNT / 4) * 2; + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + } + + //the reward manager balance should again be 0 as all of the funds have been claimed + assertEq(getAssetBalance(address(rewardManager)), 0); + } + + function test_eventIsEmittedUponClaim() public { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[0]; + + //expect an emit + vm.expectEmit(); + + //emit the event that is expected to be emitted + emit RewardsClaimed(PRIMARY_POOL_ID, recipient.addr, uint192(POOL_DEPOSIT_AMOUNT / 4)); + + //claim the individual rewards for each recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + } + + function test_eventIsNotEmittedUponUnsuccessfulClaim() public { + //record logs to check no events were emitted + vm.recordLogs(); + + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[0]; + + //claim the individual rewards for each recipient + claimRewards(SECONDARY_POOL_ARRAY, recipient.addr); + + //no logs should have been emitted + assertEq(vm.getRecordedLogs().length, 0); + } +} + +contract DestinationRewardManagerRecipientClaimMultiplePoolsTest is BaseDestinationRewardManagerTest { + uint256 internal constant POOL_DEPOSIT_AMOUNT = 10e18; + + function setUp() public override { + //setup contracts + super.setUp(); + + //create a two pools + createPrimaryPool(); + createSecondaryPool(); + + //add funds to each of the pools to be split among the recipients + addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + addFundsToPool(SECONDARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + } + + function test_claimAllRecipientsSinglePool() public { + //expected recipient amount is 1/4 of the pool deposit + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //claim funds for each recipient within the pool + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + } + + //check the pool balance is still equal to DEPOSIT_AMOUNT as the test only claims for one of the pools + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT); + } + + function test_claimMultipleRecipientsSinglePool() public { + //claim the individual rewards for each recipient + claimRewards(SECONDARY_POOL_ARRAY, getSecondaryRecipients()[0].addr); + claimRewards(SECONDARY_POOL_ARRAY, getSecondaryRecipients()[1].addr); + + //expected recipient amount is 1/4 of the pool deposit + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //check the recipients balance matches the ratio the recipient should have received + assertEq(getAssetBalance(getSecondaryRecipients()[0].addr), expectedRecipientAmount); + assertEq(getAssetBalance(getSecondaryRecipients()[1].addr), expectedRecipientAmount); + + //check the rewardManager has the remaining quantity + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT * 2 - (expectedRecipientAmount * 2)); + } + + function test_claimMultipleRecipientsMultiplePools() public { + //claim the individual rewards for each recipient + claimRewards(PRIMARY_POOL_ARRAY, getPrimaryRecipients()[0].addr); + claimRewards(PRIMARY_POOL_ARRAY, getPrimaryRecipients()[1].addr); + claimRewards(SECONDARY_POOL_ARRAY, getSecondaryRecipients()[0].addr); + claimRewards(SECONDARY_POOL_ARRAY, getSecondaryRecipients()[1].addr); + + //expected recipient amount is 1/4 of the pool deposit + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //check the recipients balance matches the ratio the recipient should have received. The first recipient is shared across both pools so should receive 1/4 of each pool + assertEq(getAssetBalance(getPrimaryRecipients()[0].addr), expectedRecipientAmount * 2); + assertEq(getAssetBalance(getPrimaryRecipients()[1].addr), expectedRecipientAmount); + assertEq(getAssetBalance(getSecondaryRecipients()[1].addr), expectedRecipientAmount); + + //check the rewardManager has the remaining quantity + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT); + } + + function test_claimAllRecipientsMultiplePools() public { + //expected recipient amount is 1/4 of the pool deposit + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //claim funds for each recipient within the pool + for (uint256 i = 1; i < getPrimaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + } + + //claim funds for each recipient within the pool + for (uint256 i = 1; i < getSecondaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory secondaryRecipient = getSecondaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(SECONDARY_POOL_ARRAY, secondaryRecipient.addr); + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(secondaryRecipient.addr), expectedRecipientAmount); + } + + //special case to handle the first recipient of each pool as they're the same address + Common.AddressAndWeight memory commonRecipient = getPrimaryRecipients()[0]; + + //claim the individual rewards for each pool + claimRewards(PRIMARY_POOL_ARRAY, commonRecipient.addr); + claimRewards(SECONDARY_POOL_ARRAY, commonRecipient.addr); + + //check the balance matches the ratio the recipient should have received, which is 1/4 of each deposit for each pool + assertEq(getAssetBalance(commonRecipient.addr), expectedRecipientAmount * 2); + } + + function test_claimSingleUniqueRecipient() public { + //the first recipient of the secondary pool is in both pools, so take the second recipient which is unique + Common.AddressAndWeight memory recipient = getSecondaryRecipients()[1]; + + //claim the individual rewards for this recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + claimRewards(SECONDARY_POOL_ARRAY, recipient.addr); + + //the recipient should have received 1/4 of the deposit amount + uint256 recipientExpectedAmount = POOL_DEPOSIT_AMOUNT / 4; + + //the recipient should have received 1/4 of the deposit amount + assertEq(getAssetBalance(recipient.addr), recipientExpectedAmount); + + //check the rewardManager has the remaining quantity + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT * 2 - recipientExpectedAmount); + } + + function test_claimSingleRecipientMultiplePools() public { + //the first recipient of the secondary pool is in both pools + Common.AddressAndWeight memory recipient = getSecondaryRecipients()[0]; + + //claim the individual rewards for this recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + claimRewards(SECONDARY_POOL_ARRAY, recipient.addr); + + //the recipient should have received 1/4 of the deposit amount for each pool + uint256 recipientExpectedAmount = (POOL_DEPOSIT_AMOUNT / 4) * 2; + + //this recipient belongs in both pools so should have received 1/4 of each + assertEq(getAssetBalance(recipient.addr), recipientExpectedAmount); + + //check the rewardManager has the remaining quantity + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT * 2 - recipientExpectedAmount); + } + + function test_claimUnregisteredRecipient() public { + //claim the individual rewards for this recipient + claimRewards(PRIMARY_POOL_ARRAY, getSecondaryRecipients()[1].addr); + claimRewards(SECONDARY_POOL_ARRAY, getPrimaryRecipients()[1].addr); + + //check the recipients didn't receive any fees from this pool + assertEq(getAssetBalance(getSecondaryRecipients()[1].addr), 0); + assertEq(getAssetBalance(getPrimaryRecipients()[1].addr), 0); + + //check the rewardManager has the remaining quantity + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT * 2); + } + + function test_claimUnevenAmountRoundsDown() public { + //adding an uneven amount of dust to each pool, this should round down to the nearest whole number with 4 remaining in the contract + addFundsToPool(PRIMARY_POOL_ID, getAsset(3), FEE_MANAGER); + addFundsToPool(SECONDARY_POOL_ID, getAsset(1), FEE_MANAGER); + + //the recipient should have received 1/4 of the deposit amount for each pool + uint256 recipientExpectedAmount = POOL_DEPOSIT_AMOUNT / 4; + + //claim funds for each recipient within the pool + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), recipientExpectedAmount); + } + + //special case to handle the first recipient of each pool as they're the same address + claimRewards(SECONDARY_POOL_ARRAY, getSecondaryRecipients()[0].addr); + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(getSecondaryRecipients()[0].addr), recipientExpectedAmount * 2); + + //claim funds for each recipient of the secondary pool except the first + for (uint256 i = 1; i < getSecondaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getSecondaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(SECONDARY_POOL_ARRAY, recipient.addr); + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), recipientExpectedAmount); + } + + //contract should have 4 remaining + assertEq(getAssetBalance(address(rewardManager)), 4); + } + + function test_singleRecipientClaimMultipleDeposits() public { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getSecondaryRecipients()[0]; + + //claim the individual rewards for this recipient + claimRewards(SECONDARY_POOL_ARRAY, recipient.addr); + + //the recipient should have received 1/4 of the deposit amount + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //check the recipients balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + + //check the rewardManager has the remaining quantity, which is 3/4 of the initial deposit plus the deposit from the second pool + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT * 2 - expectedRecipientAmount); + + //add funds to the pool to be split among the recipients + addFundsToPool(SECONDARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + + //claim the individual rewards for this recipient + claimRewards(SECONDARY_POOL_ARRAY, recipient.addr); + + //the recipient should have received 1/4 of the next deposit amount + expectedRecipientAmount += POOL_DEPOSIT_AMOUNT / 4; + + //check the recipients balance matches the ratio the recipient should have received, which is 1/4 of each deposit + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + + //check the rewardManager has the remaining quantity, which is now 3/4 of both deposits + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT * 3 - expectedRecipientAmount); + } + + function test_recipientsClaimMultipleDeposits() public { + //the recipient should have received 1/4 of the deposit amount + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //claim funds for each recipient within the pool + for (uint256 i; i < getSecondaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getSecondaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(SECONDARY_POOL_ARRAY, recipient.addr); + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + } + + //the reward manager balance should contain only the funds of the secondary pool + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT); + + //add funds to the pool to be split among the recipients + addFundsToPool(SECONDARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + + //special case to handle the first recipient of each pool as they're the same address + claimRewards(SECONDARY_POOL_ARRAY, getSecondaryRecipients()[0].addr); + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(getSecondaryRecipients()[0].addr), expectedRecipientAmount * 2); + + //claim funds for each recipient within the pool except the first + for (uint256 i = 1; i < getSecondaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getSecondaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(SECONDARY_POOL_ARRAY, recipient.addr); + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount * 2); + } + + //the reward manager balance should again be the balance of the secondary pool as the primary pool has been emptied twice + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT); + } + + function test_claimEmptyPoolWhenSecondPoolContainsFunds() public { + //the recipient should have received 1/4 of the deposit amount + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //claim all rewards for each recipient in the primary pool + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + } + + //claim all the rewards again for the first recipient as that address is a member of both pools + claimRewards(PRIMARY_POOL_ARRAY, getSecondaryRecipients()[0].addr); + + //check the balance + assertEq(getAssetBalance(getSecondaryRecipients()[0].addr), expectedRecipientAmount); + } + + function test_getRewardsAvailableToRecipientInBothPools() public { + //get index 0 as this recipient is in both default pools + bytes32[] memory poolIds = rewardManager.getAvailableRewardPoolIds( + getPrimaryRecipients()[0].addr, + 0, + type(uint256).max + ); + + //check the recipient is in both pools + assertEq(poolIds[0], PRIMARY_POOL_ID); + assertEq(poolIds[1], SECONDARY_POOL_ID); + } + + function test_getRewardsAvailableToRecipientInSinglePool() public { + //get index 0 as this recipient is in both default pools + bytes32[] memory poolIds = rewardManager.getAvailableRewardPoolIds( + getPrimaryRecipients()[1].addr, + 0, + type(uint256).max + ); + + //check the recipient is in both pools + assertEq(poolIds[0], PRIMARY_POOL_ID); + assertEq(poolIds[1], ZERO_POOL_ID); + } + + function test_getRewardsAvailableToRecipientInNoPools() public view { + //get index 0 as this recipient is in both default pools + bytes32[] memory poolIds = rewardManager.getAvailableRewardPoolIds(FEE_MANAGER, 0, type(uint256).max); + + //check the recipient is in neither pool + assertEq(poolIds[0], ZERO_POOL_ID); + assertEq(poolIds[1], ZERO_POOL_ID); + } + + function test_getRewardsAvailableToRecipientInBothPoolsWhereAlreadyClaimed() public { + //get index 0 as this recipient is in both default pools + bytes32[] memory poolIds = rewardManager.getAvailableRewardPoolIds( + getPrimaryRecipients()[0].addr, + 0, + type(uint256).max + ); + + //check the recipient is in both pools + assertEq(poolIds[0], PRIMARY_POOL_ID); + assertEq(poolIds[1], SECONDARY_POOL_ID); + + //claim the rewards for each pool + claimRewards(PRIMARY_POOL_ARRAY, getPrimaryRecipients()[0].addr); + claimRewards(SECONDARY_POOL_ARRAY, getSecondaryRecipients()[0].addr); + + //get the available pools again + poolIds = rewardManager.getAvailableRewardPoolIds(getPrimaryRecipients()[0].addr, 0, type(uint256).max); + + //user should not be in any pool + assertEq(poolIds[0], ZERO_POOL_ID); + assertEq(poolIds[1], ZERO_POOL_ID); + } + + function test_getAvailableRewardsCursorCannotBeGreaterThanTotalPools() public { + vm.expectRevert(INVALID_POOL_LENGTH_SELECTOR); + + rewardManager.getAvailableRewardPoolIds(FEE_MANAGER, type(uint256).max, 0); + } + + function test_getAvailableRewardsCursorAndTotalPoolsEqual() public { + bytes32[] memory poolIds = rewardManager.getAvailableRewardPoolIds(getPrimaryRecipients()[0].addr, 2, 2); + + assertEq(poolIds.length, 0); + } + + function test_getAvailableRewardsCursorSingleResult() public { + bytes32[] memory poolIds = rewardManager.getAvailableRewardPoolIds(getPrimaryRecipients()[0].addr, 0, 1); + + assertEq(poolIds[0], PRIMARY_POOL_ID); + } +} + +contract DestinationRewardManagerRecipientClaimDifferentWeightsTest is BaseDestinationRewardManagerTest { + uint256 internal constant POOL_DEPOSIT_AMOUNT = 10e18; + + function setUp() public override { + //setup contracts + super.setUp(); + + //create a single pool for these tests + createPrimaryPool(); + + //add funds to the pool to be split among the recipients + addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + } + + function getPrimaryRecipients() public virtual override returns (Common.AddressAndWeight[] memory) { + //array of recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4); + + //init each recipient with uneven weights + recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, TEN_PERCENT); + recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, TEN_PERCENT * 8); + recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, ONE_PERCENT * 6); + recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, ONE_PERCENT * 4); + + return recipients; + } + + function test_allRecipientsClaimingReceiveExpectedAmount() public { + //loop all the recipients and claim their expected amount + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //the recipient should have received a share proportional to their weight + uint256 expectedRecipientAmount = (POOL_DEPOSIT_AMOUNT * recipient.weight) / POOL_SCALAR; + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + } + } +} + +contract DestinationRewardManagerRecipientClaimUnevenWeightTest is BaseDestinationRewardManagerTest { + uint256 internal constant POOL_DEPOSIT_AMOUNT = 10e18; + + function setUp() public override { + //setup contracts + super.setUp(); + + //create a single pool for these tests + createPrimaryPool(); + } + + function getPrimaryRecipients() public virtual override returns (Common.AddressAndWeight[] memory) { + //array of recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](2); + + uint64 oneThird = POOL_SCALAR / 3; + + //init each recipient with even weights. + recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, oneThird); + recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, 2 * oneThird + 1); + + return recipients; + } + + function test_allRecipientsClaimingReceiveExpectedAmountWithSmallDeposit() public { + //add a smaller amount of funds to the pool + uint256 smallDeposit = 1e8; + + //add a smaller amount of funds to the pool + addFundsToPool(PRIMARY_POOL_ID, getAsset(smallDeposit), FEE_MANAGER); + + //loop all the recipients and claim their expected amount + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //the recipient should have received a share proportional to their weight + uint256 expectedRecipientAmount = (smallDeposit * recipient.weight) / POOL_SCALAR; + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + } + + //smaller deposits will consequently have less precision and will not be able to be split as evenly, the remaining 1 will be lost due to 333...|... being paid out instead of 333...4| + assertEq(getAssetBalance(address(rewardManager)), 1); + } + + function test_allRecipientsClaimingReceiveExpectedAmount() public { + //add funds to the pool to be split among the recipients + addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + + //loop all the recipients and claim their expected amount + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //the recipient should have received a share proportional to their weight + uint256 expectedRecipientAmount = (POOL_DEPOSIT_AMOUNT * recipient.weight) / POOL_SCALAR; + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + } + + //their should be 0 wei left over indicating a successful split + assertEq(getAssetBalance(address(rewardManager)), 0); + } +} + +contract DestinationRewardManagerNoRecipientSet is BaseDestinationRewardManagerTest { + uint256 internal constant POOL_DEPOSIT_AMOUNT = 10e18; + + function setUp() public override { + //setup contracts + super.setUp(); + + //add funds to the pool to be split among the recipients once registered + addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + } + + function test_claimAllRecipientsAfterRecipientsSet() public { + //expected recipient amount is 1/4 of the pool deposit + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //try and claim funds for each recipient within the pool + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i]; + + //there should be no rewards claimed as the recipient is not registered + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //check the recipient received nothing + assertEq(getAssetBalance(recipient.addr), 0); + } + + //Set the recipients after the rewards have been paid into the pool + setRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients(), ADMIN); + + //claim funds for each recipient within the pool + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i]; + + //there should be no rewards claimed as the recipient is registered + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + } + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.general.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.general.t.sol new file mode 100644 index 0000000000..4c79d2cba5 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.general.t.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {BaseDestinationRewardManagerTest} from "./BaseDestinationRewardManager.t.sol"; +import {DestinationRewardManager} from "../../../v0.4.0/DestinationRewardManager.sol"; +import {ERC20Mock} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol"; +import {IDestinationRewardManager} from "../../interfaces/IDestinationRewardManager.sol"; + +/** + * @title DestinationRewardManagerSetupTest + * @author Michael Fletcher + * @notice This contract will test the core functionality of the DestinationRewardManager contract + */ +contract DestinationRewardManagerSetupTest is BaseDestinationRewardManagerTest { + uint256 internal constant POOL_DEPOSIT_AMOUNT = 10e18; + + function setUp() public override { + //setup contracts + super.setUp(); + } + + function test_rejectsZeroLinkAddressOnConstruction() public { + //should revert if the contract is a zero address + vm.expectRevert(INVALID_ADDRESS_ERROR_SELECTOR); + + //create a rewardManager with a zero link address + new DestinationRewardManager(address(0)); + } + + function test_eventEmittedUponFeeManagerUpdate() public { + //expect the event to be emitted + vm.expectEmit(); + + //emit the event that is expected to be emitted + emit FeeManagerUpdated(FEE_MANAGER_2); + + //set the verifier proxy + setFeeManager(FEE_MANAGER_2, ADMIN); + } + + function test_eventEmittedUponFeePaid() public { + //create pool and add funds + createPrimaryPool(); + + //change to the feeManager who is the one who will be paying the fees + changePrank(FEE_MANAGER); + + //approve the amount being paid into the pool + ERC20Mock(getAsset(POOL_DEPOSIT_AMOUNT).assetAddress).approve(address(rewardManager), POOL_DEPOSIT_AMOUNT); + + IDestinationRewardManager.FeePayment[] memory payments = new IDestinationRewardManager.FeePayment[](1); + payments[0] = IDestinationRewardManager.FeePayment(PRIMARY_POOL_ID, uint192(POOL_DEPOSIT_AMOUNT)); + + //event is emitted when funds are added + vm.expectEmit(); + emit FeePaid(payments, FEE_MANAGER); + + //this represents the verifier adding some funds to the pool + rewardManager.onFeePaid(payments, FEE_MANAGER); + } + + function test_setFeeManagerZeroAddress() public { + //should revert if the contract is a zero address + vm.expectRevert(INVALID_ADDRESS_ERROR_SELECTOR); + + //set the verifier proxy + setFeeManager(address(0), ADMIN); + } + + function test_addFeeManagerZeroAddress() public { + vm.expectRevert(INVALID_ADDRESS_ERROR_SELECTOR); + rewardManager.addFeeManager(address(0)); + } + + function test_addFeeManagerExistingAddress() public { + address dummyAddress = address(998); + rewardManager.addFeeManager(dummyAddress); + vm.expectRevert(INVALID_ADDRESS_ERROR_SELECTOR); + rewardManager.addFeeManager(dummyAddress); + } + + function test_removeFeeManagerNonExistentAddress() public { + address dummyAddress = address(991); + vm.expectRevert(INVALID_ADDRESS_ERROR_SELECTOR); + rewardManager.removeFeeManager(dummyAddress); + } + + function test_addRemoveFeeManager() public { + address dummyAddress1 = address(1); + address dummyAddress2 = address(2); + rewardManager.addFeeManager(dummyAddress1); + rewardManager.addFeeManager(dummyAddress2); + assertEq(rewardManager.s_feeManagerAddressList(dummyAddress1), dummyAddress1); + assertEq(rewardManager.s_feeManagerAddressList(dummyAddress2), dummyAddress2); + rewardManager.removeFeeManager(dummyAddress1); + assertEq(rewardManager.s_feeManagerAddressList(dummyAddress1), address(0)); + assertEq(rewardManager.s_feeManagerAddressList(dummyAddress2), dummyAddress2); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.payRecipients.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.payRecipients.t.sol new file mode 100644 index 0000000000..4aa3c868b3 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.payRecipients.t.sol @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {BaseDestinationRewardManagerTest} from "./BaseDestinationRewardManager.t.sol"; +import {IDestinationRewardManager} from "../../interfaces/IDestinationRewardManager.sol"; + +/** + * @title DestinationRewardManagerPayRecipientsTest + * @author Michael Fletcher + * @notice This contract will test the payRecipients functionality of the RewardManager contract + */ +contract DestinationRewardManagerPayRecipientsTest is BaseDestinationRewardManagerTest { + uint256 internal constant POOL_DEPOSIT_AMOUNT = 10e18; + + function setUp() public override { + //setup contracts + super.setUp(); + + //create a single pool for these tests + createPrimaryPool(); + + //add funds to the pool to be split among the recipients + addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + } + + function test_payAllRecipients() public { + //pay all the recipients in the pool + payRecipients(PRIMARY_POOL_ID, getPrimaryRecipientAddresses(), ADMIN); + + //each recipient should receive 1/4 of the pool + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //check each recipient received the correct amount + for (uint256 i = 0; i < getPrimaryRecipientAddresses().length; i++) { + assertEq(getAssetBalance(getPrimaryRecipientAddresses()[i]), expectedRecipientAmount); + } + } + + function test_paySingleRecipient() public { + //get the first individual recipient + address recipient = getPrimaryRecipientAddresses()[0]; + + //get a single recipient as an array + address[] memory recipients = new address[](1); + recipients[0] = recipient; + + //pay a single recipient + payRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + + //the recipient should have received 1/4 of the deposit amount + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + assertEq(getAssetBalance(recipient), expectedRecipientAmount); + } + + function test_payRecipientWithInvalidPool() public { + //get the first individual recipient + address recipient = getPrimaryRecipientAddresses()[0]; + + //get a single recipient as an array + address[] memory recipients = new address[](1); + recipients[0] = recipient; + + //pay a single recipient + payRecipients(SECONDARY_POOL_ID, recipients, ADMIN); + + //the recipient should have received nothing + assertEq(getAssetBalance(recipient), 0); + } + + function test_payRecipientsEmptyRecipientList() public { + //get a single recipient + address[] memory recipients = new address[](0); + + //pay a single recipient + payRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + + //rewardManager should have the full balance + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT); + } + + function test_payAllRecipientsWithAdditionalUnregisteredRecipient() public { + //load all the recipients and add an additional one who is not in the pool + address[] memory recipients = new address[](getPrimaryRecipientAddresses().length + 1); + for (uint256 i = 0; i < getPrimaryRecipientAddresses().length; i++) { + recipients[i] = getPrimaryRecipientAddresses()[i]; + } + recipients[recipients.length - 1] = DEFAULT_RECIPIENT_5; + + //pay the recipients + payRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + + //each recipient should receive 1/4 of the pool except the last + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //check each recipient received the correct amount + for (uint256 i = 0; i < getPrimaryRecipientAddresses().length; i++) { + assertEq(getAssetBalance(getPrimaryRecipientAddresses()[i]), expectedRecipientAmount); + } + + //the unregistered recipient should receive nothing + assertEq(getAssetBalance(DEFAULT_RECIPIENT_5), 0); + } + + function test_payAllRecipientsWithAdditionalInvalidRecipient() public { + //load all the recipients and add an additional one which is invalid, that should receive nothing + address[] memory recipients = new address[](getPrimaryRecipientAddresses().length + 1); + for (uint256 i = 0; i < getPrimaryRecipientAddresses().length; i++) { + recipients[i] = getPrimaryRecipientAddresses()[i]; + } + recipients[recipients.length - 1] = INVALID_ADDRESS; + + //pay the recipients + payRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + + //each recipient should receive 1/4 of the pool except the last + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //check each recipient received the correct amount + for (uint256 i = 0; i < getPrimaryRecipientAddresses().length; i++) { + assertEq(getAssetBalance(getPrimaryRecipientAddresses()[i]), expectedRecipientAmount); + } + } + + function test_paySubsetOfRecipientsInPool() public { + //load a subset of the recipients into an array + address[] memory recipients = new address[](getPrimaryRecipientAddresses().length - 1); + for (uint256 i = 0; i < recipients.length; i++) { + recipients[i] = getPrimaryRecipientAddresses()[i]; + } + + //pay the subset of recipients + payRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + + //each recipient should receive 1/4 of the pool except the last + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //check each subset of recipients received the correct amount + for (uint256 i = 0; i < recipients.length - 1; i++) { + assertEq(getAssetBalance(recipients[i]), expectedRecipientAmount); + } + + //check the pool has the remaining balance + assertEq( + getAssetBalance(address(rewardManager)), + POOL_DEPOSIT_AMOUNT - expectedRecipientAmount * recipients.length + ); + } + + function test_payAllRecipientsFromNonAdminUser() public { + //should revert if the caller isn't an admin or recipient within the pool + vm.expectRevert(UNAUTHORIZED_ERROR_SELECTOR); + + //pay all the recipients in the pool + payRecipients(PRIMARY_POOL_ID, getPrimaryRecipientAddresses(), FEE_MANAGER); + } + + function test_payAllRecipientsFromRecipientInPool() public { + //pay all the recipients in the pool + payRecipients(PRIMARY_POOL_ID, getPrimaryRecipientAddresses(), DEFAULT_RECIPIENT_1); + + //each recipient should receive 1/4 of the pool + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //check each recipient received the correct amount + for (uint256 i = 0; i < getPrimaryRecipientAddresses().length; i++) { + assertEq(getAssetBalance(getPrimaryRecipientAddresses()[i]), expectedRecipientAmount); + } + } + + function test_payRecipientsWithInvalidPoolId() public { + //pay all the recipients in the pool + payRecipients(INVALID_POOL_ID, getPrimaryRecipientAddresses(), ADMIN); + + //pool should still contain the full balance + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT); + } + + function test_addFundsToPoolAsOwner() public { + //add funds to the pool + addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + } + + function test_addFundsToPoolAsNonOwnerOrFeeManager() public { + //should revert if the caller isn't an admin or recipient within the pool + vm.expectRevert(UNAUTHORIZED_ERROR_SELECTOR); + + IDestinationRewardManager.FeePayment[] memory payments = new IDestinationRewardManager.FeePayment[](1); + payments[0] = IDestinationRewardManager.FeePayment(PRIMARY_POOL_ID, uint192(POOL_DEPOSIT_AMOUNT)); + + //add funds to the pool + rewardManager.onFeePaid(payments, USER); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.setRecipients.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.setRecipients.t.sol new file mode 100644 index 0000000000..facbaa1ab7 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.setRecipients.t.sol @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {BaseDestinationRewardManagerTest} from "./BaseDestinationRewardManager.t.sol"; +import {Common} from "../../../libraries/Common.sol"; + +/** + * @title DestinationRewardManagerSetRecipientsTest + * @author Michael Fletcher + * @notice This contract will test the setRecipient functionality of the RewardManager contract + */ +contract DestinationRewardManagerSetRecipientsTest is BaseDestinationRewardManagerTest { + function setUp() public override { + //setup contracts + super.setUp(); + } + + function test_setRewardRecipients() public { + //set the recipients + setRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients(), ADMIN); + } + + function test_setRewardRecipientsIsEmpty() public { + //array of recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4); + + //should revert if the recipients array is empty + vm.expectRevert(INVALID_ADDRESS_ERROR_SELECTOR); + + //set the recipients + setRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_setRewardRecipientWithZeroWeight() public { + //array of recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](5); + + //init each recipient with even weights + recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, ONE_PERCENT * 25); + recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, ONE_PERCENT * 25); + recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, ONE_PERCENT * 25); + recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, ONE_PERCENT * 25); + recipients[4] = Common.AddressAndWeight(DEFAULT_RECIPIENT_5, 0); + + //set the recipients + setRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_setRewardRecipientWithZeroAddress() public { + //array of recipients + Common.AddressAndWeight[] memory recipients = getPrimaryRecipients(); + + //override the first recipient with a zero address + recipients[0].addr = address(0); + + //should revert if the recipients array is empty + vm.expectRevert(INVALID_ADDRESS_ERROR_SELECTOR); + + //set the recipients + setRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_setRewardRecipientWeights() public { + //array of recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4); + + //init each recipient with even weights + recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, 25); + recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, 25); + recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, 25); + recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, 25); + + //should revert if the recipients array is empty + vm.expectRevert(INVALID_WEIGHT_ERROR_SELECTOR); + + //set the recipients with a recipient with a weight of 100% + setRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_setSingleRewardRecipient() public { + //array of recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](1); + + //init each recipient with even weights + recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, POOL_SCALAR); + + //set the recipients with a recipient with a weight of 100% + setRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_setRewardRecipientTwice() public { + //set the recipients + setRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients(), ADMIN); + + //should revert if recipients for this pool have already been set + vm.expectRevert(INVALID_POOL_ID_ERROR_SELECTOR); + + //set the recipients again + setRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients(), ADMIN); + } + + function test_setRewardRecipientFromNonOwnerOrFeeManagerAddress() public { + //should revert if the sender is not the owner or proxy + vm.expectRevert(UNAUTHORIZED_ERROR_SELECTOR); + + //set the recipients + setRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients(), USER); + } + + function test_setRewardRecipientFromManagerAddress() public { + //update the proxy address + setFeeManager(FEE_MANAGER_2, ADMIN); + + //set the recipients + setRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients(), FEE_MANAGER_2); + } + + function test_eventIsEmittedUponSetRecipients() public { + //expect an emit + vm.expectEmit(); + + //emit the event that is expected to be emitted + emit RewardRecipientsUpdated(PRIMARY_POOL_ID, getPrimaryRecipients()); + + //set the recipients + setRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients(), ADMIN); + } + + function test_setRecipientContainsDuplicateRecipients() public { + //create a new array to hold the existing recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](getPrimaryRecipients().length * 2); + + //add all the existing recipients + for (uint256 i; i < getPrimaryRecipients().length; i++) { + recipients[i] = getPrimaryRecipients()[i]; + } + //add all the existing recipients again + for (uint256 i; i < getPrimaryRecipients().length; i++) { + recipients[i + getPrimaryRecipients().length] = getPrimaryRecipients()[i]; + } + + //should revert as the list contains a duplicate + vm.expectRevert(INVALID_ADDRESS_ERROR_SELECTOR); + + //set the recipients + setRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.updateRewardRecipients.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.updateRewardRecipients.t.sol new file mode 100644 index 0000000000..226be8ed32 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/reward-manager/DestinationRewardManager.updateRewardRecipients.t.sol @@ -0,0 +1,450 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {BaseDestinationRewardManagerTest} from "./BaseDestinationRewardManager.t.sol"; +import {Common} from "../../../libraries/Common.sol"; + +/** + * @title DestinationRewardManagerUpdateRewardRecipientsTest + * @author Michael Fletcher + * @notice This contract will test the updateRecipient functionality of the RewardManager contract + */ +contract DestinationRewardManagerUpdateRewardRecipientsTest is BaseDestinationRewardManagerTest { + uint256 internal constant POOL_DEPOSIT_AMOUNT = 10e18; + + function setUp() public override { + //setup contracts + super.setUp(); + + //create a single pool for these tests + createPrimaryPool(); + + //add funds to the pool to be split among the recipients + addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + } + + function test_onlyAdminCanUpdateRecipients() public { + //should revert if the caller is not the admin + vm.expectRevert(ONLY_CALLABLE_BY_OWNER_ERROR); + + //updating a recipient should force the funds to be paid out + updateRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients(), FEE_MANAGER); + } + + function test_updateAllRecipientsWithSameAddressAndWeight() public { + //expected recipient amount is 1/4 of the pool deposit + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //updating a recipient should force the funds to be paid out + updateRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients(), ADMIN); + + //check each recipient received the correct amount + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(getPrimaryRecipients()[i].addr), expectedRecipientAmount); + } + } + + function test_updatePartialRecipientsWithSameAddressAndWeight() public { + //expected recipient amount is 1/4 of the pool deposit + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //get a subset of the recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](2); + recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, ONE_PERCENT * 25); + recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, ONE_PERCENT * 25); + + //updating a recipient should force the funds to be paid out + updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + + //check each recipient received the correct amount + for (uint256 i; i < recipients.length; i++) { + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipients[i].addr), expectedRecipientAmount); + } + + //the reward manager should still have half remaining funds + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT / 2); + } + + function test_updateRecipientWithNewZeroAddress() public { + //create a new array to hold the existing recipients plus a new zero address + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](getPrimaryRecipients().length + 1); + + //add all the existing recipients + for (uint256 i; i < getPrimaryRecipients().length; i++) { + recipients[i] = getPrimaryRecipients()[i]; + } + //add a new address to the primary recipients + recipients[recipients.length - 1] = Common.AddressAndWeight(address(0), 0); + + //should revert if the recipient is a zero address + vm.expectRevert(INVALID_ADDRESS_ERROR_SELECTOR); + + //update the recipients with invalid address + updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_updateRecipientsContainsDuplicateRecipients() public { + //create a new array to hold the existing recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](getPrimaryRecipients().length * 2); + + //add all the existing recipients + for (uint256 i; i < getPrimaryRecipients().length; i++) { + recipients[i] = getPrimaryRecipients()[i]; + } + //add all the existing recipients again + for (uint256 i; i < getPrimaryRecipients().length; i++) { + recipients[i + getPrimaryRecipients().length] = getPrimaryRecipients()[i]; + } + + //should revert as the list contains a duplicate + vm.expectRevert(INVALID_ADDRESS_ERROR_SELECTOR); + + //update the recipients with the duplicate addresses + updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_updateRecipientsToDifferentSet() public { + //create a list of containing recipients from the primary configured set, and new recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](getPrimaryRecipients().length + 4); + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //copy the recipient and set the weight to 0 which implies the recipient is being replaced + recipients[i] = Common.AddressAndWeight(getPrimaryRecipients()[i].addr, 0); + } + + //add the new recipients individually + recipients[4] = Common.AddressAndWeight(DEFAULT_RECIPIENT_5, ONE_PERCENT * 25); + recipients[5] = Common.AddressAndWeight(DEFAULT_RECIPIENT_6, ONE_PERCENT * 25); + recipients[6] = Common.AddressAndWeight(DEFAULT_RECIPIENT_7, ONE_PERCENT * 25); + recipients[7] = Common.AddressAndWeight(DEFAULT_RECIPIENT_8, ONE_PERCENT * 25); + + //updating a recipient should force the funds to be paid out for the primary recipients + updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_updateRecipientsToDifferentPartialSet() public { + //create a list of containing recipients from the primary configured set, and new recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](getPrimaryRecipients().length + 2); + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //copy the recipient and set the weight to 0 which implies the recipient is being replaced + recipients[i] = Common.AddressAndWeight(getPrimaryRecipients()[i].addr, 0); + } + + //add the new recipients individually + recipients[4] = Common.AddressAndWeight(DEFAULT_RECIPIENT_5, FIFTY_PERCENT); + recipients[5] = Common.AddressAndWeight(DEFAULT_RECIPIENT_6, FIFTY_PERCENT); + + //updating a recipient should force the funds to be paid out for the primary recipients + updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_updateRecipientsToDifferentLargerSet() public { + //create a list of containing recipients from the primary configured set, and new recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](getPrimaryRecipients().length + 5); + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //copy the recipient and set the weight to 0 which implies the recipient is being replaced + recipients[i] = Common.AddressAndWeight(getPrimaryRecipients()[i].addr, 0); + } + + //add the new recipients individually + recipients[4] = Common.AddressAndWeight(DEFAULT_RECIPIENT_5, TEN_PERCENT * 2); + recipients[5] = Common.AddressAndWeight(DEFAULT_RECIPIENT_6, TEN_PERCENT * 2); + recipients[6] = Common.AddressAndWeight(DEFAULT_RECIPIENT_7, TEN_PERCENT * 2); + recipients[7] = Common.AddressAndWeight(DEFAULT_RECIPIENT_8, TEN_PERCENT * 2); + recipients[8] = Common.AddressAndWeight(DEFAULT_RECIPIENT_9, TEN_PERCENT * 2); + + //updating a recipient should force the funds to be paid out for the primary recipients + updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_updateRecipientsUpdateAndRemoveExistingForLargerSet() public { + //create a list of containing recipients from the primary configured set, and new recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](9); + + //update the existing recipients + recipients[0] = Common.AddressAndWeight(getPrimaryRecipients()[0].addr, 0); + recipients[1] = Common.AddressAndWeight(getPrimaryRecipients()[1].addr, 0); + recipients[2] = Common.AddressAndWeight(getPrimaryRecipients()[2].addr, TEN_PERCENT * 3); + recipients[3] = Common.AddressAndWeight(getPrimaryRecipients()[3].addr, TEN_PERCENT * 3); + + //add the new recipients individually + recipients[4] = Common.AddressAndWeight(DEFAULT_RECIPIENT_5, TEN_PERCENT); + recipients[5] = Common.AddressAndWeight(DEFAULT_RECIPIENT_6, TEN_PERCENT); + recipients[6] = Common.AddressAndWeight(DEFAULT_RECIPIENT_7, TEN_PERCENT); + recipients[7] = Common.AddressAndWeight(DEFAULT_RECIPIENT_8, TEN_PERCENT); + recipients[8] = Common.AddressAndWeight(DEFAULT_RECIPIENT_9, TEN_PERCENT); + + //should revert as the weight does not equal 100% + vm.expectRevert(INVALID_WEIGHT_ERROR_SELECTOR); + + //updating a recipient should force the funds to be paid out for the primary recipients + updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_updateRecipientsUpdateAndRemoveExistingForSmallerSet() public { + //create a list of containing recipients from the primary configured set, and new recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](5); + + //update the existing recipients + recipients[0] = Common.AddressAndWeight(getPrimaryRecipients()[0].addr, 0); + recipients[1] = Common.AddressAndWeight(getPrimaryRecipients()[1].addr, 0); + recipients[2] = Common.AddressAndWeight(getPrimaryRecipients()[2].addr, TEN_PERCENT * 3); + recipients[3] = Common.AddressAndWeight(getPrimaryRecipients()[3].addr, TEN_PERCENT * 2); + + //add the new recipients individually + recipients[4] = Common.AddressAndWeight(DEFAULT_RECIPIENT_5, TEN_PERCENT * 5); + + //updating a recipient should force the funds to be paid out for the primary recipients + updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_updateRecipientsToDifferentSetWithInvalidWeights() public { + //create a list of containing recipients from the primary configured set, and new recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](getPrimaryRecipients().length + 2); + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //copy the recipient and set the weight to 0 which implies the recipient is being replaced + recipients[i] = Common.AddressAndWeight(getPrimaryRecipients()[i].addr, 0); + } + + //add the new recipients individually + recipients[4] = Common.AddressAndWeight(DEFAULT_RECIPIENT_5, TEN_PERCENT * 5); + recipients[5] = Common.AddressAndWeight(DEFAULT_RECIPIENT_6, TEN_PERCENT); + + //should revert as the weight will not equal 100% + vm.expectRevert(INVALID_WEIGHT_ERROR_SELECTOR); + + //updating a recipient should force the funds to be paid out for the primary recipients + updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_updatePartialRecipientsToSubset() public { + //create a list of containing recipients from the primary configured set, and new recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4); + recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, 0); + recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, 0); + recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, TEN_PERCENT * 5); + recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, TEN_PERCENT * 5); + + //updating a recipient should force the funds to be paid out for the primary recipients + updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_updatePartialRecipientsWithUnderWeightSet() public { + //create a list of containing recipients from the primary configured set, and new recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4); + recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, TEN_PERCENT); + recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, TEN_PERCENT); + recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, TEN_PERCENT); + recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, TEN_PERCENT); + + //should revert as the new weights exceed the previous weights being replaced + vm.expectRevert(INVALID_WEIGHT_ERROR_SELECTOR); + + //updating a recipient should force the funds to be paid out for the primary recipients + updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_updatePartialRecipientsWithExcessiveWeight() public { + //create a list of containing recipients from the primary configured set, and new recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4); + recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, TEN_PERCENT); + recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, TEN_PERCENT); + recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, TEN_PERCENT); + recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, POOL_SCALAR); + + //should revert as the new weights exceed the previous weights being replaced + vm.expectRevert(INVALID_WEIGHT_ERROR_SELECTOR); + + //updating a recipient should force the funds to be paid out for the primary recipients + updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_updateRecipientWeights() public { + //expected recipient amount is 1/4 of the pool deposit for original recipients + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //create a list of containing recipients from the primary configured set with their new weights + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4); + recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, TEN_PERCENT); + recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, TEN_PERCENT); + recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, TEN_PERCENT * 3); + recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, TEN_PERCENT * 5); + + //updating a recipient should force the funds to be paid out for the primary recipients + updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + + //check each recipient received the correct amount + for (uint256 i; i < recipients.length; i++) { + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipients[i].addr), expectedRecipientAmount); + } + + //the reward manager should have no funds remaining + assertEq(getAssetBalance(address(rewardManager)), 0); + + //add more funds to the pool to check new distribution + addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + + //loop each user and claim the rewards + for (uint256 i; i < recipients.length; i++) { + //claim the rewards for this recipient + claimRewards(PRIMARY_POOL_ARRAY, recipients[i].addr); + } + + //manually check the balance of each recipient + assertEq( + getAssetBalance(DEFAULT_RECIPIENT_1), + (POOL_DEPOSIT_AMOUNT * TEN_PERCENT) / POOL_SCALAR + expectedRecipientAmount + ); + assertEq( + getAssetBalance(DEFAULT_RECIPIENT_2), + (POOL_DEPOSIT_AMOUNT * TEN_PERCENT) / POOL_SCALAR + expectedRecipientAmount + ); + assertEq( + getAssetBalance(DEFAULT_RECIPIENT_3), + (POOL_DEPOSIT_AMOUNT * TEN_PERCENT * 3) / POOL_SCALAR + expectedRecipientAmount + ); + assertEq( + getAssetBalance(DEFAULT_RECIPIENT_4), + (POOL_DEPOSIT_AMOUNT * TEN_PERCENT * 5) / POOL_SCALAR + expectedRecipientAmount + ); + } + + function test_partialUpdateRecipientWeights() public { + //expected recipient amount is 1/4 of the pool deposit for original recipients + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //create a list of containing recipients from the primary configured set with their new weights + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](2); + recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, TEN_PERCENT); + recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, TEN_PERCENT * 4); + + //updating a recipient should force the funds to be paid out for the primary recipients + updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + + //check each recipient received the correct amount + for (uint256 i; i < recipients.length; i++) { + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipients[i].addr), expectedRecipientAmount); + } + + //the reward manager should have half the funds remaining + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT / 2); + + //add more funds to the pool to check new distribution + addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + + //loop each user and claim the rewards + for (uint256 i; i < recipients.length; i++) { + //claim the rewards for this recipient + claimRewards(PRIMARY_POOL_ARRAY, recipients[i].addr); + } + + //manually check the balance of each recipient + assertEq( + getAssetBalance(DEFAULT_RECIPIENT_1), + (POOL_DEPOSIT_AMOUNT * TEN_PERCENT) / POOL_SCALAR + expectedRecipientAmount + ); + assertEq( + getAssetBalance(DEFAULT_RECIPIENT_2), + (POOL_DEPOSIT_AMOUNT * TEN_PERCENT * 4) / POOL_SCALAR + expectedRecipientAmount + ); + + //the reward manager should have half the funds remaining + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT); + } + + function test_eventIsEmittedUponUpdateRecipients() public { + //expect an emit + vm.expectEmit(); + + //emit the event that is expected to be emitted + emit RewardRecipientsUpdated(PRIMARY_POOL_ID, getPrimaryRecipients()); + + //expected recipient amount is 1/4 of the pool deposit + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //updating a recipient should force the funds to be paid out + updateRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients(), ADMIN); + + //check each recipient received the correct amount + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(getPrimaryRecipients()[i].addr), expectedRecipientAmount); + } + } +} + +contract DestinationRewardManagerUpdateRewardRecipientsMultiplePoolsTest is BaseDestinationRewardManagerTest { + uint256 internal constant POOL_DEPOSIT_AMOUNT = 10e18; + + function setUp() public override { + //setup contracts + super.setUp(); + + //create a single pool for these tests + createPrimaryPool(); + createSecondaryPool(); + + //add funds to the pool to be split among the recipients + addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + addFundsToPool(SECONDARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + } + + function getSecondaryRecipients() public override returns (Common.AddressAndWeight[] memory) { + //for testing purposes, the primary and secondary pool to contain the same recipients + return getPrimaryRecipients(); + } + + function test_updatePrimaryRecipientWeights() public { + //expected recipient amount is 1/4 of the pool deposit for original recipients + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //create a list of containing recipients from the primary configured set, and new recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4); + recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, TEN_PERCENT * 4); + recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, TEN_PERCENT * 4); + recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, TEN_PERCENT); + recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, TEN_PERCENT); + + //updating a recipient should force the funds to be paid out for the primary recipients + updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + + //check each recipient received the correct amount + for (uint256 i; i < recipients.length; i++) { + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipients[i].addr), expectedRecipientAmount); + } + + //the reward manager should still have the funds for the secondary pool + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT); + + //add more funds to the pool to check new distribution + addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + + //claim the rewards for the updated recipients manually + claimRewards(PRIMARY_POOL_ARRAY, recipients[0].addr); + claimRewards(PRIMARY_POOL_ARRAY, recipients[1].addr); + claimRewards(PRIMARY_POOL_ARRAY, recipients[2].addr); + claimRewards(PRIMARY_POOL_ARRAY, recipients[3].addr); + + //check the balance matches the ratio the recipient who were updated should have received + assertEq( + getAssetBalance(recipients[0].addr), + (POOL_DEPOSIT_AMOUNT * TEN_PERCENT * 4) / POOL_SCALAR + expectedRecipientAmount + ); + assertEq( + getAssetBalance(recipients[1].addr), + (POOL_DEPOSIT_AMOUNT * TEN_PERCENT * 4) / POOL_SCALAR + expectedRecipientAmount + ); + assertEq( + getAssetBalance(recipients[2].addr), + (POOL_DEPOSIT_AMOUNT * TEN_PERCENT) / POOL_SCALAR + expectedRecipientAmount + ); + assertEq( + getAssetBalance(recipients[3].addr), + (POOL_DEPOSIT_AMOUNT * TEN_PERCENT) / POOL_SCALAR + expectedRecipientAmount + ); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/BaseDestinationVerifierTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/BaseDestinationVerifierTest.t.sol new file mode 100644 index 0000000000..ec3b3a0eed --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/BaseDestinationVerifierTest.t.sol @@ -0,0 +1,347 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {Test} from "forge-std/Test.sol"; +import {DestinationVerifierProxy} from "../../DestinationVerifierProxy.sol"; +import {IERC165} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; +import {IDestinationVerifier} from "../../interfaces/IDestinationVerifier.sol"; +import {IDestinationVerifierProxy} from "../../interfaces/IDestinationVerifierProxy.sol"; +import {DestinationVerifier} from "../../DestinationVerifier.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {AccessControllerInterface} from "../../../../shared/interfaces/AccessControllerInterface.sol"; +import {DestinationFeeManager} from "../../DestinationFeeManager.sol"; +import {Common} from "../../../libraries/Common.sol"; +import {ERC20Mock} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol"; +import {WERC20Mock} from "../../../../shared/mocks/WERC20Mock.sol"; +import {DestinationRewardManager} from "../../DestinationRewardManager.sol"; +import {IDestinationRewardManager} from "../../interfaces/IDestinationRewardManager.sol"; + +contract BaseTest is Test { + uint64 internal constant POOL_SCALAR = 1e18; + uint64 internal constant ONE_PERCENT = POOL_SCALAR / 100; + uint256 internal constant MAX_ORACLES = 31; + address internal constant ADMIN = address(1); + address internal constant USER = address(2); + + address internal constant MOCK_VERIFIER_ADDRESS = address(100); + address internal constant ACCESS_CONTROLLER_ADDRESS = address(300); + + uint256 internal constant DEFAULT_REPORT_LINK_FEE = 1e10; + uint256 internal constant DEFAULT_REPORT_NATIVE_FEE = 1e12; + + uint64 internal constant VERIFIER_VERSION = 1; + + uint8 internal constant FAULT_TOLERANCE = 10; + + DestinationVerifierProxy internal s_verifierProxy; + DestinationVerifier internal s_verifier; + DestinationFeeManager internal feeManager; + DestinationRewardManager internal rewardManager; + ERC20Mock internal link; + WERC20Mock internal native; + + struct Signer { + uint256 mockPrivateKey; + address signerAddress; + } + + Signer[MAX_ORACLES] internal s_signers; + bytes32[] internal s_offchaintransmitters; + bool private s_baseTestInitialized; + + struct V3Report { + // The feed ID the report has data for + bytes32 feedId; + // The time the median value was observed on + uint32 observationsTimestamp; + // The timestamp the report is valid from + uint32 validFromTimestamp; + // The link fee + uint192 linkFee; + // The native fee + uint192 nativeFee; + // The expiry of the report + uint32 expiresAt; + // The median value agreed in an OCR round + int192 benchmarkPrice; + // The best bid value agreed in an OCR round + int192 bid; + // The best ask value agreed in an OCR round + int192 ask; + } + + bytes32 internal constant V_MASK = 0x0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; + bytes32 internal constant V1_BITMASK = 0x0001000000000000000000000000000000000000000000000000000000000000; + bytes32 internal constant V2_BITMASK = 0x0002000000000000000000000000000000000000000000000000000000000000; + bytes32 internal constant V3_BITMASK = 0x0003000000000000000000000000000000000000000000000000000000000000; + + bytes32 internal constant INVALID_FEED = keccak256("INVALID"); + uint32 internal constant OBSERVATIONS_TIMESTAMP = 1000; + uint64 internal constant BLOCKNUMBER_LOWER_BOUND = 1000; + uint64 internal constant BLOCKNUMBER_UPPER_BOUND = BLOCKNUMBER_LOWER_BOUND + 5; + int192 internal constant MEDIAN = 1 ether; + int192 internal constant BID = 500000000 gwei; + int192 internal constant ASK = 2 ether; + + //version 0 feeds + bytes32 internal constant FEED_ID = (keccak256("ETH-USD") & V_MASK) | V1_BITMASK; + bytes32 internal constant FEED_ID_2 = (keccak256("LINK-USD") & V_MASK) | V1_BITMASK; + bytes32 internal constant FEED_ID_3 = (keccak256("BTC-USD") & V_MASK) | V1_BITMASK; + + //version 3 feeds + bytes32 internal constant FEED_ID_V3 = (keccak256("ETH-USD") & V_MASK) | V3_BITMASK; + + function _encodeReport(V3Report memory report) internal pure returns (bytes memory) { + return + abi.encode( + report.feedId, + report.observationsTimestamp, + report.validFromTimestamp, + report.nativeFee, + report.linkFee, + report.expiresAt, + report.benchmarkPrice, + report.bid, + report.ask + ); + } + + function _generateSignerSignatures( + bytes memory report, + bytes32[3] memory reportContext, + Signer[] memory signers + ) internal pure returns (bytes32[] memory rawRs, bytes32[] memory rawSs, bytes32 rawVs) { + bytes32[] memory rs = new bytes32[](signers.length); + bytes32[] memory ss = new bytes32[](signers.length); + bytes memory vs = new bytes(signers.length); + + bytes32 hash = keccak256(abi.encodePacked(keccak256(report), reportContext)); + + for (uint256 i = 0; i < signers.length; i++) { + (uint8 v, bytes32 r, bytes32 s) = vm.sign(signers[i].mockPrivateKey, hash); + rs[i] = r; + ss[i] = s; + vs[i] = bytes1(v - 27); + } + return (rs, ss, bytes32(vs)); + } + + function _generateV3EncodedBlob( + V3Report memory report, + bytes32[3] memory reportContext, + Signer[] memory signers + ) internal pure returns (bytes memory) { + bytes memory reportBytes = _encodeReport(report); + (bytes32[] memory rs, bytes32[] memory ss, bytes32 rawVs) = _generateSignerSignatures( + reportBytes, + reportContext, + signers + ); + return abi.encode(reportContext, reportBytes, rs, ss, rawVs); + } + + function _verify(bytes memory payload, address feeAddress, uint256 wrappedNativeValue, address sender) internal { + address originalAddr = msg.sender; + changePrank(sender); + + s_verifierProxy.verify{value: wrappedNativeValue}(payload, abi.encode(feeAddress)); + + changePrank(originalAddr); + } + + function _generateV3Report() internal view returns (V3Report memory) { + return + V3Report({ + feedId: FEED_ID_V3, + observationsTimestamp: OBSERVATIONS_TIMESTAMP, + validFromTimestamp: uint32(block.timestamp), + nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE), + linkFee: uint192(DEFAULT_REPORT_LINK_FEE), + expiresAt: uint32(block.timestamp), + benchmarkPrice: MEDIAN, + bid: BID, + ask: ASK + }); + } + + function _verifyBulk( + bytes[] memory payload, + address feeAddress, + uint256 wrappedNativeValue, + address sender + ) internal { + address originalAddr = msg.sender; + changePrank(sender); + + s_verifierProxy.verifyBulk{value: wrappedNativeValue}(payload, abi.encode(feeAddress)); + + changePrank(originalAddr); + } + + function _approveLink(address spender, uint256 quantity, address sender) internal { + address originalAddr = msg.sender; + changePrank(sender); + + link.approve(spender, quantity); + changePrank(originalAddr); + } + + function setUp() public virtual { + // BaseTest.setUp is often called multiple times from tests' setUp due to inheritance. + if (s_baseTestInitialized) return; + s_baseTestInitialized = true; + vm.startPrank(ADMIN); + + s_verifierProxy = new DestinationVerifierProxy(); + s_verifier = new DestinationVerifier(address(s_verifierProxy)); + s_verifierProxy.setVerifier(address(s_verifier)); + + // setting up FeeManager and RewardManager + native = new WERC20Mock(); + link = new ERC20Mock("LINK", "LINK", ADMIN, 0); + rewardManager = new DestinationRewardManager(address(link)); + feeManager = new DestinationFeeManager(address(link), address(native), address(s_verifier), address(rewardManager)); + + for (uint256 i; i < MAX_ORACLES; i++) { + uint256 mockPK = i + 1; + s_signers[i].mockPrivateKey = mockPK; + s_signers[i].signerAddress = vm.addr(mockPK); + } + } + + function _getSigners(uint256 numSigners) internal view returns (Signer[] memory) { + Signer[] memory signers = new Signer[](numSigners); + for (uint256 i; i < numSigners; i++) { + signers[i] = s_signers[i]; + } + return signers; + } + + function _getSignerAddresses(Signer[] memory signers) internal pure returns (address[] memory) { + address[] memory signerAddrs = new address[](signers.length); + for (uint256 i = 0; i < signerAddrs.length; i++) { + signerAddrs[i] = signers[i].signerAddress; + } + return signerAddrs; + } + + function _signerAddressAndDonConfigKey(address signer, bytes24 donConfigId) internal pure returns (bytes32) { + return keccak256(abi.encodePacked(signer, donConfigId)); + } + + function _donConfigIdFromConfigData(address[] memory signers, uint8 f) internal pure returns (bytes24) { + Common._quickSort(signers, 0, int256(signers.length - 1)); + bytes24 donConfigId = bytes24(keccak256(abi.encodePacked(signers, f))); + return donConfigId; + } + + function assertReportsEqual(bytes memory response, V3Report memory testReport) public pure { + ( + bytes32 feedId, + uint32 observationsTimestamp, + uint32 validFromTimestamp, + uint192 nativeFee, + uint192 linkFee, + uint32 expiresAt, + int192 benchmarkPrice, + int192 bid, + int192 ask + ) = abi.decode(response, (bytes32, uint32, uint32, uint192, uint192, uint32, int192, int192, int192)); + assertEq(feedId, testReport.feedId); + assertEq(observationsTimestamp, testReport.observationsTimestamp); + assertEq(validFromTimestamp, testReport.validFromTimestamp); + assertEq(expiresAt, testReport.expiresAt); + assertEq(benchmarkPrice, testReport.benchmarkPrice); + assertEq(bid, testReport.bid); + assertEq(ask, testReport.ask); + assertEq(linkFee, testReport.linkFee); + assertEq(nativeFee, testReport.nativeFee); + } + + function _approveNative(address spender, uint256 quantity, address sender) internal { + address originalAddr = msg.sender; + changePrank(sender); + + native.approve(spender, quantity); + changePrank(originalAddr); + } +} + +contract VerifierWithFeeManager is BaseTest { + uint256 internal constant DEFAULT_LINK_MINT_QUANTITY = 100 ether; + uint256 internal constant DEFAULT_NATIVE_MINT_QUANTITY = 100 ether; + + function setUp() public virtual override { + BaseTest.setUp(); + + s_verifierProxy.setVerifier(address(s_verifier)); + s_verifier.setFeeManager(address(feeManager)); + rewardManager.addFeeManager(address(feeManager)); + + //mint some tokens to the user + link.mint(USER, DEFAULT_LINK_MINT_QUANTITY); + native.mint(USER, DEFAULT_NATIVE_MINT_QUANTITY); + vm.deal(USER, DEFAULT_NATIVE_MINT_QUANTITY); + + //mint some link tokens to the feeManager pool + link.mint(address(feeManager), DEFAULT_REPORT_LINK_FEE); + } +} + +contract MultipleVerifierWithMultipleFeeManagers is BaseTest { + uint256 internal constant DEFAULT_LINK_MINT_QUANTITY = 100 ether; + uint256 internal constant DEFAULT_NATIVE_MINT_QUANTITY = 100 ether; + + DestinationVerifier internal s_verifier2; + DestinationVerifier internal s_verifier3; + + DestinationVerifierProxy internal s_verifierProxy2; + DestinationVerifierProxy internal s_verifierProxy3; + + DestinationFeeManager internal feeManager2; + + function setUp() public virtual override { + /* + - Sets up 3 verifiers + - Sets up 2 Fee managers, wire the fee managers and verifiers + - Sets up a Reward Manager which can be used by both fee managers + */ + BaseTest.setUp(); + + s_verifierProxy2 = new DestinationVerifierProxy(); + s_verifierProxy3 = new DestinationVerifierProxy(); + + s_verifier2 = new DestinationVerifier(address(s_verifierProxy2)); + s_verifier3 = new DestinationVerifier(address(s_verifierProxy3)); + + s_verifierProxy2.setVerifier(address(s_verifier2)); + s_verifierProxy3.setVerifier(address(s_verifier3)); + + feeManager2 = new DestinationFeeManager( + address(link), + address(native), + address(s_verifier), + address(rewardManager) + ); + + s_verifier.setFeeManager(address(feeManager)); + s_verifier2.setFeeManager(address(feeManager)); + s_verifier3.setFeeManager(address(feeManager2)); + + // this is already set in the base contract + // feeManager.addVerifier(address(s_verifier)); + feeManager.addVerifier(address(s_verifier2)); + feeManager2.addVerifier(address(s_verifier3)); + + rewardManager.addFeeManager(address(feeManager)); + rewardManager.addFeeManager(address(feeManager2)); + + //mint some tokens to the user + link.mint(USER, DEFAULT_LINK_MINT_QUANTITY); + native.mint(USER, DEFAULT_NATIVE_MINT_QUANTITY); + vm.deal(USER, DEFAULT_NATIVE_MINT_QUANTITY); + + //mint some link tokens to the feeManager pool + link.mint(address(feeManager), DEFAULT_REPORT_LINK_FEE); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierInterfacesTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierInterfacesTest.t.sol new file mode 100644 index 0000000000..d4772ba185 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierInterfacesTest.t.sol @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {Test} from "forge-std/Test.sol"; +import {VerifierWithFeeManager} from "./BaseDestinationVerifierTest.t.sol"; +import {DestinationVerifier} from "../../../v0.4.0/DestinationVerifier.sol"; +import {DestinationVerifierProxy} from "../../../v0.4.0/DestinationVerifierProxy.sol"; +import {AccessControllerInterface} from "../../../../shared/interfaces/AccessControllerInterface.sol"; +import {IDestinationFeeManager} from "../../../v0.4.0/interfaces/IDestinationFeeManager.sol"; +import {IDestinationRewardManager} from "../../../v0.4.0/interfaces/IDestinationRewardManager.sol"; +import {IDestinationVerifierProxy} from "../../../v0.4.0/interfaces/IDestinationVerifierProxy.sol"; +import {Common} from "../../../libraries/Common.sol"; +import {BaseTest} from "./BaseDestinationVerifierTest.t.sol"; +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC20.sol"; + +/* +This test checks the interfaces of destination verifier matches the expectations. +The code here comes from this example: + +https://docs.chain.link/chainlink-automation/guides/streams-lookup + +*/ + +// Custom interfaces for IVerifierProxy and IFeeManager +interface IVerifierProxy { + /** + * @notice Verifies that the data encoded has been signed. + * correctly by routing to the correct verifier, and bills the user if applicable. + * @param payload The encoded data to be verified, including the signed + * report. + * @param parameterPayload Fee metadata for billing. For the current implementation this is just the abi-encoded fee token ERC-20 address. + * @return verifierResponse The encoded report from the verifier. + */ + function verify( + bytes calldata payload, + bytes calldata parameterPayload + ) external payable returns (bytes memory verifierResponse); + + function s_feeManager() external view returns (IDestinationFeeManager); +} + +interface IFeeManager { + /** + * @notice Calculates the fee and reward associated with verifying a report, including discounts for subscribers. + * This function assesses the fee and reward for report verification, applying a discount for recognized subscriber addresses. + * @param subscriber The address attempting to verify the report. A discount is applied if this address + * is recognized as a subscriber. + * @param unverifiedReport The report data awaiting verification. The content of this report is used to + * determine the base fee and reward, before considering subscriber discounts. + * @param quoteAddress The payment token address used for quoting fees and rewards. + * @return fee The fee assessed for verifying the report, with subscriber discounts applied where applicable. + * @return reward The reward allocated to the caller for successfully verifying the report. + * @return totalDiscount The total discount amount deducted from the fee for subscribers. + */ + function getFeeAndReward( + address subscriber, + bytes memory unverifiedReport, + address quoteAddress + ) external returns (Common.Asset memory, Common.Asset memory, uint256); + + function i_linkAddress() external view returns (address); + + function i_nativeAddress() external view returns (address); + + function i_rewardManager() external view returns (address); +} + +//Tests +// https://docs.chain.link/chainlink-automation/guides/streams-lookup +contract VerifierInterfacesTest is VerifierWithFeeManager { + address internal constant DEFAULT_RECIPIENT_1 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_1")))); + + IVerifierProxy public verifier; + V3Report internal s_testReport; + + address public FEE_ADDRESS; + string public constant DATASTREAMS_FEEDLABEL = "feedIDs"; + string public constant DATASTREAMS_QUERYLABEL = "timestamp"; + int192 public last_retrieved_price; + bytes internal signedReport; + bytes32[3] internal s_reportContext; + uint8 MINIMAL_FAULT_TOLERANCE = 2; + + function setUp() public virtual override { + VerifierWithFeeManager.setUp(); + s_reportContext[0] = bytes32(abi.encode(uint32(5), uint8(1))); + Signer[] memory signers = _getSigners(MAX_ORACLES); + + s_testReport = V3Report({ + feedId: FEED_ID_V3, + observationsTimestamp: OBSERVATIONS_TIMESTAMP, + validFromTimestamp: uint32(block.timestamp), + nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE), + linkFee: uint192(DEFAULT_REPORT_LINK_FEE), + expiresAt: uint32(block.timestamp), + benchmarkPrice: MEDIAN, + bid: BID, + ask: ASK + }); + address[] memory signerAddrs = _getSignerAddresses(signers); + Common.AddressAndWeight[] memory weights = new Common.AddressAndWeight[](1); + weights[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, ONE_PERCENT * 100); + s_verifier.setConfig(signerAddrs, MINIMAL_FAULT_TOLERANCE, weights); + signedReport = _generateV3EncodedBlob(s_testReport, s_reportContext, signers); + + verifier = IVerifierProxy(address(s_verifierProxy)); + } + + function test_DestinationContractInterfaces() public { + bytes memory unverifiedReport = signedReport; + + (, bytes memory reportData) = abi.decode(unverifiedReport, (bytes32[3], bytes)); + + // Report verification fees + IFeeManager feeManager = IFeeManager(address(verifier.s_feeManager())); + IDestinationRewardManager rewardManager = IDestinationRewardManager(address(feeManager.i_rewardManager())); + + address feeTokenAddress = feeManager.i_linkAddress(); + (Common.Asset memory fee, , ) = feeManager.getFeeAndReward(address(this), reportData, feeTokenAddress); + + // Approve rewardManager to spend this contract's balance in fees + _approveLink(address(rewardManager), fee.amount, USER); + _verify(unverifiedReport, address(feeTokenAddress), 0, USER); + + assertEq(link.balanceOf(USER), DEFAULT_LINK_MINT_QUANTITY - fee.amount); + assertEq(link.balanceOf(address(rewardManager)), fee.amount); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierProxyTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierProxyTest.t.sol new file mode 100644 index 0000000000..2851057d0e --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierProxyTest.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {BaseTest} from "./BaseDestinationVerifierTest.t.sol"; +import {DestinationVerifierProxy} from "../../../v0.4.0/DestinationVerifierProxy.sol"; +import {DestinationVerifier} from "../../../v0.4.0/DestinationVerifier.sol"; +import {DestinationFeeManager} from "../../../v0.4.0/DestinationFeeManager.sol"; +import {IERC165} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; + +contract DestinationVerifierProxyInitializeVerifierTest is BaseTest { + function test_setVerifierCalledByNoOwner() public { + address STRANGER = address(999); + changePrank(STRANGER); + vm.expectRevert(bytes("Only callable by owner")); + s_verifierProxy.setVerifier(address(s_verifier)); + } + + function test_setVerifierWhichDoesntHonourInterface() public { + vm.expectRevert(abi.encodeWithSelector(DestinationVerifierProxy.VerifierInvalid.selector, address(rewardManager))); + s_verifierProxy.setVerifier(address(rewardManager)); + } + + function test_setVerifierOk() public { + s_verifierProxy.setVerifier(address(s_verifier)); + assertEq(s_verifierProxy.s_feeManager(), s_verifier.s_feeManager()); + assertEq(s_verifierProxy.s_accessController(), s_verifier.s_accessController()); + } + + function test_correctlySetsTheOwner() public { + DestinationVerifierProxy proxy = new DestinationVerifierProxy(); + assertEq(proxy.owner(), ADMIN); + } + + function test_correctlySetsVersion() public view { + string memory version = s_verifierProxy.typeAndVersion(); + assertEq(version, "DestinationVerifierProxy 0.4.0"); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierRemoveLatestConfigTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierRemoveLatestConfigTest.t.sol new file mode 100644 index 0000000000..6309efc995 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierRemoveLatestConfigTest.t.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {BaseTest} from "./BaseDestinationVerifierTest.t.sol"; +import {DestinationVerifier} from "../../../v0.4.0/DestinationVerifier.sol"; +import {DestinationRewardManager} from "../../../v0.4.0/DestinationRewardManager.sol"; +import {Common} from "../../../libraries/Common.sol"; + +contract DestinationVerifierSetConfigTest is BaseTest { + bytes32[3] internal s_reportContext; + V3Report internal s_testReport; + + function setUp() public virtual override { + BaseTest.setUp(); + s_reportContext[0] = bytes32(abi.encode(uint32(5), uint8(1))); + } + + function test_removeLatestConfigWhenNoConfigShouldFail() public { + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.DonConfigDoesNotExist.selector)); + s_verifier.removeLatestConfig(); + } + + function test_removeLatestConfig() public { + /* + This test sets two Configs: Config A and Config B. + - it removes and readds config B multiple times while trying Config A verifications + */ + Signer[] memory signers = _getSigners(MAX_ORACLES); + + uint8 MINIMAL_FAULT_TOLERANCE = 2; + BaseTest.Signer[] memory signersA = new BaseTest.Signer[](7); + signersA[0] = signers[0]; + signersA[1] = signers[1]; + signersA[2] = signers[2]; + signersA[3] = signers[3]; + signersA[4] = signers[4]; + signersA[5] = signers[5]; + signersA[6] = signers[6]; + + // ConfigA + address[] memory signersAddrA = _getSignerAddresses(signersA); + s_verifier.setConfig(signersAddrA, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + vm.warp(block.timestamp + 10); + V3Report memory s_testReportA = V3Report({ + feedId: FEED_ID_V3, + observationsTimestamp: OBSERVATIONS_TIMESTAMP, + validFromTimestamp: uint32(block.timestamp), + nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE), + linkFee: uint192(DEFAULT_REPORT_LINK_FEE), + expiresAt: uint32(block.timestamp), + benchmarkPrice: MEDIAN, + bid: BID, + ask: ASK + }); + + vm.warp(block.timestamp + 100); + // Config B + BaseTest.Signer[] memory signersB = new BaseTest.Signer[](7); + // signers in ConfigA + signersB[0] = signers[8]; + signersB[1] = signers[9]; + signersB[2] = signers[10]; + signersB[3] = signers[11]; + signersB[4] = signers[12]; + signersB[5] = signers[13]; + signersB[6] = signers[14]; + address[] memory signersAddrsB = _getSignerAddresses(signersB); + s_verifier.setConfig(signersAddrsB, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + + V3Report memory s_testReportB = V3Report({ + feedId: FEED_ID_V3, + observationsTimestamp: OBSERVATIONS_TIMESTAMP, + validFromTimestamp: uint32(block.timestamp), + nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE), + linkFee: uint192(DEFAULT_REPORT_LINK_FEE), + expiresAt: uint32(block.timestamp), + benchmarkPrice: MEDIAN, + bid: BID, + ask: ASK + }); + + BaseTest.Signer[] memory reportSignersA = new BaseTest.Signer[](3); + reportSignersA[0] = signers[0]; + reportSignersA[1] = signers[1]; + reportSignersA[2] = signers[2]; + + BaseTest.Signer[] memory reportSignersB = new BaseTest.Signer[](3); + reportSignersB[0] = signers[8]; + reportSignersB[1] = signers[9]; + reportSignersB[2] = signers[10]; + + bytes memory signedReportA = _generateV3EncodedBlob(s_testReportA, s_reportContext, reportSignersA); + bytes memory signedReportB = _generateV3EncodedBlob(s_testReportB, s_reportContext, reportSignersB); + + // verifying should work + s_verifierProxy.verify(signedReportA, abi.encode(native)); + s_verifierProxy.verify(signedReportB, abi.encode(native)); + + s_verifier.removeLatestConfig(); + + // this should remove the latest config, so ConfigA should be able to verify reports still + s_verifierProxy.verify(signedReportA, abi.encode(native)); + // this report cannot be verified any longer because ConfigB is not there + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector)); + s_verifierProxy.verify(signedReportB, abi.encode(native)); + + // since ConfigB is removed we should be able to set it again with no errors + s_verifier.setConfig(signersAddrsB, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + + // we should be able to remove ConfigB + s_verifier.removeLatestConfig(); + // removing configA + s_verifier.removeLatestConfig(); + + // verifigny should fail + // verifying should work + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector)); + s_verifierProxy.verify(signedReportA, abi.encode(native)); + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector)); + s_verifierProxy.verify(signedReportB, abi.encode(native)); + + // removing again should fail. no other configs exist + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.DonConfigDoesNotExist.selector)); + s_verifier.removeLatestConfig(); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierSetAccessControllerTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierSetAccessControllerTest.t.sol new file mode 100644 index 0000000000..d40b674f23 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierSetAccessControllerTest.t.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {BaseTest} from "./BaseDestinationVerifierTest.t.sol"; + +contract DestinationVerifierSetAccessControllerTest is BaseTest { + event AccessControllerSet(address oldAccessController, address newAccessController); + + function test_revertsIfCalledByNonOwner() public { + vm.expectRevert("Only callable by owner"); + + changePrank(USER); + s_verifier.setAccessController(ACCESS_CONTROLLER_ADDRESS); + } + + function test_successfullySetsNewAccessController() public { + s_verifier.setAccessController(ACCESS_CONTROLLER_ADDRESS); + address ac = s_verifier.s_accessController(); + assertEq(ac, ACCESS_CONTROLLER_ADDRESS); + } + + function test_successfullySetsNewAccessControllerIsEmpty() public { + s_verifier.setAccessController(address(0)); + address ac = s_verifier.s_accessController(); + assertEq(ac, address(0)); + } + + function test_emitsTheCorrectEvent() public { + vm.expectEmit(true, false, false, false); + emit AccessControllerSet(address(0), ACCESS_CONTROLLER_ADDRESS); + s_verifier.setAccessController(ACCESS_CONTROLLER_ADDRESS); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierSetConfigTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierSetConfigTest.t.sol new file mode 100644 index 0000000000..4f96e4969a --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierSetConfigTest.t.sol @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {BaseTest} from "./BaseDestinationVerifierTest.t.sol"; +import {DestinationVerifier} from "../../../v0.4.0/DestinationVerifier.sol"; +import {DestinationRewardManager} from "../../../v0.4.0/DestinationRewardManager.sol"; +import {Common} from "../../../libraries/Common.sol"; + +contract DestinationVerifierSetConfigTest is BaseTest { + function setUp() public virtual override { + BaseTest.setUp(); + } + + function test_revertsIfCalledByNonOwner() public { + vm.expectRevert("Only callable by owner"); + Signer[] memory signers = _getSigners(MAX_ORACLES); + changePrank(USER); + s_verifier.setConfig(_getSignerAddresses(signers), FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + } + + function test_revertsIfSetWithTooManySigners() public { + address[] memory signers = new address[](MAX_ORACLES + 1); + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.ExcessSigners.selector, signers.length, MAX_ORACLES)); + s_verifier.setConfig(signers, FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + } + + function test_revertsIfFaultToleranceIsZero() public { + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.FaultToleranceMustBePositive.selector)); + Signer[] memory signers = _getSigners(MAX_ORACLES); + s_verifier.setConfig(_getSignerAddresses(signers), 0, new Common.AddressAndWeight[](0)); + } + + function test_revertsIfNotEnoughSigners() public { + address[] memory signers = new address[](2); + signers[0] = address(1000); + signers[1] = address(1001); + + vm.expectRevert( + abi.encodeWithSelector(DestinationVerifier.InsufficientSigners.selector, signers.length, FAULT_TOLERANCE * 3 + 1) + ); + s_verifier.setConfig(signers, FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + } + + function test_revertsIfDuplicateSigners() public { + Signer[] memory signers = _getSigners(MAX_ORACLES); + address[] memory signerAddrs = _getSignerAddresses(signers); + signerAddrs[0] = signerAddrs[1]; + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.NonUniqueSignatures.selector)); + s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + } + + function test_revertsIfSignerContainsZeroAddress() public { + Signer[] memory signers = _getSigners(MAX_ORACLES); + address[] memory signerAddrs = _getSignerAddresses(signers); + signerAddrs[0] = address(0); + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.ZeroAddress.selector)); + s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + } + + function test_donConfigIdIsSameForSignersInDifferentOrder() public { + Signer[] memory signers = _getSigners(MAX_ORACLES); + address[] memory signerAddrs = _getSignerAddresses(signers); + + bytes24 expectedDonConfigId = _donConfigIdFromConfigData(signerAddrs, FAULT_TOLERANCE); + + s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + vm.warp(block.timestamp + 1); + + address temp = signerAddrs[0]; + signerAddrs[0] = signerAddrs[1]; + signerAddrs[1] = temp; + + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.DonConfigAlreadyExists.selector, expectedDonConfigId)); + + s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + } + + function test_NoDonConfigAlreadyExists() public { + Signer[] memory signers = _getSigners(MAX_ORACLES); + address[] memory signerAddrs = _getSignerAddresses(signers); + + s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + + vm.warp(block.timestamp + 1); + + // testing adding same set of Signers but different FAULT_TOLERENCE does not result in DonConfigAlreadyExists revert + s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE - 1, new Common.AddressAndWeight[](0)); + + vm.warp(block.timestamp + 1); + + // testing adding a different set of Signers with same FAULT_TOLERENCE does not result in DonConfigAlreadyExists revert + address[] memory signerAddrsMinusOne = new address[](signerAddrs.length - 1); + for (uint256 i = 0; i < signerAddrs.length - 1; i++) { + signerAddrsMinusOne[i] = signerAddrs[i]; + } + s_verifier.setConfig(signerAddrsMinusOne, FAULT_TOLERANCE - 1, new Common.AddressAndWeight[](0)); + } + + function test_addressesAndWeightsDoNotProduceSideEffectsInDonConfigIds() public { + Signer[] memory signers = _getSigners(MAX_ORACLES); + address[] memory signerAddrs = _getSignerAddresses(signers); + + s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + vm.warp(block.timestamp + 1); + + bytes24 expectedDonConfigId = _donConfigIdFromConfigData(signerAddrs, FAULT_TOLERANCE); + + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.DonConfigAlreadyExists.selector, expectedDonConfigId)); + + // Same call to setConfig with different addressAndWeights do not entail a new DonConfigID + // Resulting in a DonConfigAlreadyExists error + Common.AddressAndWeight[] memory weights = new Common.AddressAndWeight[](1); + weights[0] = Common.AddressAndWeight(signers[0].signerAddress, 1); + s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, weights); + } + + function test_setConfigActiveUnknownDonConfigId() public { + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.DonConfigDoesNotExist.selector)); + s_verifier.setConfigActive(3, true); + } + + function test_setConfigWithActivationTime() public { + // simple case setting a config with specific activation time + Signer[] memory signers = _getSigners(MAX_ORACLES); + address[] memory signerAddrs = _getSignerAddresses(signers); + uint32 activationTime = 10; + s_verifier.setConfigWithActivationTime( + signerAddrs, + FAULT_TOLERANCE, + new Common.AddressAndWeight[](0), + activationTime + ); + } + + function test_setConfigWithActivationTimeNoFutureTimeShouldFail() public { + // calling setConfigWithActivationTime with a future timestamp should fail + Signer[] memory signers = _getSigners(MAX_ORACLES); + address[] memory signerAddrs = _getSignerAddresses(signers); + uint32 activationTime = uint32(block.timestamp) + 100; + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadActivationTime.selector)); + s_verifier.setConfigWithActivationTime( + signerAddrs, + FAULT_TOLERANCE, + new Common.AddressAndWeight[](0), + activationTime + ); + } + + function test_setConfigWithActivationTimeEarlierThanLatestConfigShouldFail() public { + // setting a config older than the latest current config should fail + Signer[] memory signers = _getSigners(MAX_ORACLES); + address[] memory signerAddrs = _getSignerAddresses(signers); + uint32 oldActivationTime = uint32(block.timestamp) - 1; + // sets a config with timestamp = block.timestamp + s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + // setting a config with ealier timestamp retuls in failure + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadActivationTime.selector)); + s_verifier.setConfigWithActivationTime( + signerAddrs, + FAULT_TOLERANCE - 1, + new Common.AddressAndWeight[](0), + oldActivationTime + ); + } + + function test_setConfigWithActivationTimeTheSameAsLatestConfigShouldFail() public { + // setting a config older than the latest current config should fail + Signer[] memory signers = _getSigners(MAX_ORACLES); + address[] memory signerAddrs = _getSignerAddresses(signers); + // sets a config with timestamp = block.timestamp + s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + // setting a config with ealier timestamp retuls in failure + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadActivationTime.selector)); + s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierSetFeeManagerTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierSetFeeManagerTest.t.sol new file mode 100644 index 0000000000..fdf75d6845 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierSetFeeManagerTest.t.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {BaseTest} from "./BaseDestinationVerifierTest.t.sol"; +import {DestinationVerifier} from "../../../v0.4.0/DestinationVerifier.sol"; + +contract VerifierSetAccessControllerTest is BaseTest { + event FeeManagerSet(address oldFeeManager, address newFeeManager); + + function test_revertsIfCalledByNonOwner() public { + vm.expectRevert("Only callable by owner"); + changePrank(USER); + s_verifier.setFeeManager(address(feeManager)); + } + + function test_successfullySetsNewFeeManager() public { + vm.expectEmit(true, false, false, false); + emit FeeManagerSet(address(0), ACCESS_CONTROLLER_ADDRESS); + s_verifier.setFeeManager(address(feeManager)); + address ac = s_verifier.s_feeManager(); + assertEq(ac, address(feeManager)); + } + + function test_setFeeManagerWhichDoesntHonourInterface() public { + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.FeeManagerInvalid.selector)); + s_verifier.setFeeManager(address(rewardManager)); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierTest.t.sol new file mode 100644 index 0000000000..476acbf806 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierTest.t.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {BaseTest} from "./BaseDestinationVerifierTest.t.sol"; +import {DestinationVerifier} from "../../../v0.4.0/DestinationVerifier.sol"; +import {IDestinationVerifier} from "../../../v0.4.0/interfaces/IDestinationVerifier.sol"; +import {IDestinationVerifierProxyVerifier} from "../../../v0.4.0/interfaces/IDestinationVerifierProxyVerifier.sol"; + +contract DestinationVerifierConstructorTest is BaseTest { + bytes32[3] internal s_reportContext; + + function test_revertsIfInitializedWithEmptyVerifierProxy() public { + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.ZeroAddress.selector)); + new DestinationVerifier(address(0)); + } + + function test_typeAndVersion() public { + DestinationVerifier v = new DestinationVerifier(address(s_verifierProxy)); + assertEq(v.owner(), ADMIN); + string memory typeAndVersion = s_verifier.typeAndVersion(); + assertEq(typeAndVersion, "DestinationVerifier 0.4.0"); + } + + function test_falseIfIsNotCorrectInterface() public view { + bool isInterface = s_verifier.supportsInterface(bytes4("abcd")); + assertEq(isInterface, false); + } + + function test_trueIfIsCorrectInterface() public view { + bool isInterface = s_verifier.supportsInterface(type(IDestinationVerifier).interfaceId) && + s_verifier.supportsInterface(type(IDestinationVerifierProxyVerifier).interfaceId); + assertEq(isInterface, true); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierTestBillingReport.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierTestBillingReport.t.sol new file mode 100644 index 0000000000..574e169cf2 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierTestBillingReport.t.sol @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {VerifierWithFeeManager} from "./BaseDestinationVerifierTest.t.sol"; +import {DestinationVerifier} from "../../../v0.4.0/DestinationVerifier.sol"; +import {DestinationVerifierProxy} from "../../../v0.4.0/DestinationVerifierProxy.sol"; +import {AccessControllerInterface} from "../../../../shared/interfaces/AccessControllerInterface.sol"; +import {Common} from "../../../libraries/Common.sol"; + +contract VerifierBillingTests is VerifierWithFeeManager { + bytes32[3] internal s_reportContext; + V3Report internal s_testReportThree; + + function setUp() public virtual override { + VerifierWithFeeManager.setUp(); + s_reportContext[0] = bytes32(abi.encode(uint32(5), uint8(1))); + s_testReportThree = V3Report({ + feedId: FEED_ID_V3, + observationsTimestamp: OBSERVATIONS_TIMESTAMP, + validFromTimestamp: uint32(block.timestamp), + nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE), + linkFee: uint192(DEFAULT_REPORT_LINK_FEE), + expiresAt: uint32(block.timestamp), + benchmarkPrice: MEDIAN, + bid: BID, + ask: ASK + }); + } + + function test_verifyWithLinkV3Report() public { + Signer[] memory signers = _getSigners(MAX_ORACLES); + address[] memory signerAddrs = _getSignerAddresses(signers); + Common.AddressAndWeight[] memory weights = new Common.AddressAndWeight[](0); + s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, weights); + bytes memory signedReport = _generateV3EncodedBlob(s_testReportThree, s_reportContext, signers); + bytes32 expectedDonConfigId = _donConfigIdFromConfigData(signerAddrs, FAULT_TOLERANCE); + + _approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER); + _verify(signedReport, address(link), 0, USER); + assertEq(link.balanceOf(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE); + + // internal state checks + assertEq(feeManager.s_linkDeficit(expectedDonConfigId), 0); + assertEq(rewardManager.s_totalRewardRecipientFees(expectedDonConfigId), DEFAULT_REPORT_LINK_FEE); + assertEq(link.balanceOf(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); + } + + function test_verifyWithNativeERC20() public { + Signer[] memory signers = _getSigners(MAX_ORACLES); + address[] memory signerAddrs = _getSignerAddresses(signers); + Common.AddressAndWeight[] memory weights = new Common.AddressAndWeight[](1); + weights[0] = Common.AddressAndWeight(signerAddrs[0], ONE_PERCENT * 100); + + s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, weights); + bytes memory signedReport = _generateV3EncodedBlob( + s_testReportThree, + s_reportContext, + _getSigners(FAULT_TOLERANCE + 1) + ); + _approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE, USER); + _verify(signedReport, address(native), 0, USER); + assertEq(native.balanceOf(USER), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE); + + assertEq(link.balanceOf(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); + } + + function test_verifyWithNativeUnwrapped() public { + Signer[] memory signers = _getSigners(MAX_ORACLES); + address[] memory signerAddrs = _getSignerAddresses(signers); + Common.AddressAndWeight[] memory weights = new Common.AddressAndWeight[](0); + + s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, weights); + bytes memory signedReport = _generateV3EncodedBlob( + s_testReportThree, + s_reportContext, + _getSigners(FAULT_TOLERANCE + 1) + ); + _verify(signedReport, address(native), DEFAULT_REPORT_NATIVE_FEE, USER); + + assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE); + assertEq(address(feeManager).balance, 0); + } + + function test_verifyWithNativeUnwrappedReturnsChange() public { + Signer[] memory signers = _getSigners(MAX_ORACLES); + address[] memory signerAddrs = _getSignerAddresses(signers); + Common.AddressAndWeight[] memory weights = new Common.AddressAndWeight[](0); + + s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, weights); + bytes memory signedReport = _generateV3EncodedBlob( + s_testReportThree, + s_reportContext, + _getSigners(FAULT_TOLERANCE + 1) + ); + + _verify(signedReport, address(native), DEFAULT_REPORT_NATIVE_FEE * 2, USER); + assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE); + assertEq(address(feeManager).balance, 0); + } +} + +contract DestinationVerifierBulkVerifyBillingReport is VerifierWithFeeManager { + uint256 internal constant NUMBERS_OF_REPORTS = 5; + + bytes32[3] internal s_reportContext; + + function setUp() public virtual override { + VerifierWithFeeManager.setUp(); + // setting a DonConfig we can reuse in the rest of tests + s_reportContext[0] = bytes32(abi.encode(uint32(5), uint8(1))); + Signer[] memory signers = _getSigners(MAX_ORACLES); + address[] memory signerAddrs = _getSignerAddresses(signers); + Common.AddressAndWeight[] memory weights = new Common.AddressAndWeight[](0); + s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, weights); + } + + function test_verifyWithBulkLink() public { + bytes memory signedReport = _generateV3EncodedBlob( + _generateV3Report(), + s_reportContext, + _getSigners(FAULT_TOLERANCE + 1) + ); + + bytes[] memory signedReports = new bytes[](NUMBERS_OF_REPORTS); + for (uint256 i = 0; i < NUMBERS_OF_REPORTS; i++) { + signedReports[i] = signedReport; + } + + _approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE * NUMBERS_OF_REPORTS, USER); + + _verifyBulk(signedReports, address(link), 0, USER); + + assertEq(link.balanceOf(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE * NUMBERS_OF_REPORTS); + assertEq(link.balanceOf(address(rewardManager)), DEFAULT_REPORT_LINK_FEE * NUMBERS_OF_REPORTS); + } + + function test_verifyWithBulkNative() public { + bytes memory signedReport = _generateV3EncodedBlob( + _generateV3Report(), + s_reportContext, + _getSigners(FAULT_TOLERANCE + 1) + ); + + bytes[] memory signedReports = new bytes[](NUMBERS_OF_REPORTS); + for (uint256 i = 0; i < NUMBERS_OF_REPORTS; i++) { + signedReports[i] = signedReport; + } + + _approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE * NUMBERS_OF_REPORTS, USER); + _verifyBulk(signedReports, address(native), 0, USER); + assertEq(native.balanceOf(USER), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE * NUMBERS_OF_REPORTS); + } + + function test_verifyWithBulkNativeUnwrapped() public { + bytes memory signedReport = _generateV3EncodedBlob( + _generateV3Report(), + s_reportContext, + _getSigners(FAULT_TOLERANCE + 1) + ); + + bytes[] memory signedReports = new bytes[](NUMBERS_OF_REPORTS); + for (uint256 i; i < NUMBERS_OF_REPORTS; i++) { + signedReports[i] = signedReport; + } + + _verifyBulk(signedReports, address(native), 200 * DEFAULT_REPORT_NATIVE_FEE * NUMBERS_OF_REPORTS, USER); + + assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE * 5); + assertEq(address(feeManager).balance, 0); + } + + function test_verifyWithBulkNativeUnwrappedReturnsChange() public { + bytes memory signedReport = _generateV3EncodedBlob( + _generateV3Report(), + s_reportContext, + _getSigners(FAULT_TOLERANCE + 1) + ); + + bytes[] memory signedReports = new bytes[](NUMBERS_OF_REPORTS); + for (uint256 i = 0; i < NUMBERS_OF_REPORTS; i++) { + signedReports[i] = signedReport; + } + + _verifyBulk(signedReports, address(native), DEFAULT_REPORT_NATIVE_FEE * (NUMBERS_OF_REPORTS * 2), USER); + + assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE * NUMBERS_OF_REPORTS); + assertEq(address(feeManager).balance, 0); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierTestRewards.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierTestRewards.t.sol new file mode 100644 index 0000000000..8ca954b8ca --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierTestRewards.t.sol @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {VerifierWithFeeManager} from "./BaseDestinationVerifierTest.t.sol"; +import {DestinationVerifier} from "../../../v0.4.0/DestinationVerifier.sol"; +import {DestinationVerifierProxy} from "../../../v0.4.0/DestinationVerifierProxy.sol"; +import {AccessControllerInterface} from "../../../../shared/interfaces/AccessControllerInterface.sol"; +import {Common} from "../../../libraries/Common.sol"; + +contract VerifierBillingTests is VerifierWithFeeManager { + uint8 MINIMAL_FAULT_TOLERANCE = 2; + address internal constant DEFAULT_RECIPIENT_1 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_1")))); + address internal constant DEFAULT_RECIPIENT_2 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_2")))); + address internal constant DEFAULT_RECIPIENT_3 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_3")))); + address internal constant DEFAULT_RECIPIENT_4 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_4")))); + address internal constant DEFAULT_RECIPIENT_5 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_5")))); + address internal constant DEFAULT_RECIPIENT_6 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_6")))); + address internal constant DEFAULT_RECIPIENT_7 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_7")))); + + function payRecipients(bytes32 poolId, address[] memory recipients, address sender) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(sender); + + //pay the recipients + rewardManager.payRecipients(poolId, recipients); + + //change back to the original address + changePrank(originalAddr); + } + + bytes32[3] internal s_reportContext; + V3Report internal s_testReport; + + function setUp() public virtual override { + VerifierWithFeeManager.setUp(); + s_reportContext[0] = bytes32(abi.encode(uint32(5), uint8(1))); + s_testReport = generateReportAtTimestamp(block.timestamp); + } + + function generateReportAtTimestamp(uint256 timestamp) public pure returns (V3Report memory) { + return + V3Report({ + feedId: FEED_ID_V3, + observationsTimestamp: OBSERVATIONS_TIMESTAMP, + validFromTimestamp: uint32(timestamp), + nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE), + linkFee: uint192(DEFAULT_REPORT_LINK_FEE), + // ask michael about this expires at, is it usually set at what blocks + expiresAt: uint32(timestamp) + 500, + benchmarkPrice: MEDIAN, + bid: BID, + ask: ASK + }); + } + + function getRecipientAndWeightsGroup2() public pure returns (Common.AddressAndWeight[] memory, address[] memory) { + address[] memory recipients = new address[](4); + recipients[0] = DEFAULT_RECIPIENT_4; + recipients[1] = DEFAULT_RECIPIENT_5; + recipients[2] = DEFAULT_RECIPIENT_6; + recipients[3] = DEFAULT_RECIPIENT_7; + + Common.AddressAndWeight[] memory weights = new Common.AddressAndWeight[](4); + //init each recipient with even weights. 2500 = 25% of pool + weights[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, POOL_SCALAR / 4); + weights[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_5, POOL_SCALAR / 4); + weights[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_6, POOL_SCALAR / 4); + weights[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_7, POOL_SCALAR / 4); + return (weights, recipients); + } + + function getRecipientAndWeightsGroup1() public pure returns (Common.AddressAndWeight[] memory, address[] memory) { + address[] memory recipients = new address[](4); + recipients[0] = DEFAULT_RECIPIENT_1; + recipients[1] = DEFAULT_RECIPIENT_2; + recipients[2] = DEFAULT_RECIPIENT_3; + recipients[3] = DEFAULT_RECIPIENT_4; + + Common.AddressAndWeight[] memory weights = new Common.AddressAndWeight[](4); + //init each recipient with even weights. 2500 = 25% of pool + weights[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, POOL_SCALAR / 4); + weights[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, POOL_SCALAR / 4); + weights[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, POOL_SCALAR / 4); + weights[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, POOL_SCALAR / 4); + return (weights, recipients); + } + + function test_rewardsAreDistributedAccordingToWeights() public { + /* + Simple test verifying that rewards are distributed according to address and weights + associated to the DonConfig used to verify the report + */ + Signer[] memory signers = _getSigners(MAX_ORACLES); + address[] memory signerAddrs = _getSignerAddresses(signers); + Common.AddressAndWeight[] memory weights = new Common.AddressAndWeight[](1); + weights[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, ONE_PERCENT * 100); + s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, weights); + bytes memory signedReport = _generateV3EncodedBlob(s_testReport, s_reportContext, signers); + bytes32 expectedDonConfigId = _donConfigIdFromConfigData(signerAddrs, FAULT_TOLERANCE); + + _approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER); + _verify(signedReport, address(link), 0, USER); + assertEq(link.balanceOf(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE); + + // internal state checks + assertEq(feeManager.s_linkDeficit(expectedDonConfigId), 0); + assertEq(rewardManager.s_totalRewardRecipientFees(expectedDonConfigId), DEFAULT_REPORT_LINK_FEE); + assertEq(link.balanceOf(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); + + // check the recipients are paid according to weights + address[] memory recipients = new address[](1); + recipients[0] = DEFAULT_RECIPIENT_1; + payRecipients(expectedDonConfigId, recipients, ADMIN); + assertEq(link.balanceOf(recipients[0]), DEFAULT_REPORT_LINK_FEE); + assertEq(link.balanceOf(address(rewardManager)), 0); + } + + function test_rewardsAreDistributedAccordingToWeightsMultipleWeigths() public { + /* + Rewards are distributed according to AddressAndWeight's + associated to the DonConfig used to verify the report: + - multiple recipients + - multiple verifications + */ + Signer[] memory signers = _getSigners(MAX_ORACLES); + address[] memory signerAddrs = _getSignerAddresses(signers); + (Common.AddressAndWeight[] memory weights, address[] memory recipients) = getRecipientAndWeightsGroup1(); + s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, weights); + + bytes memory signedReport = _generateV3EncodedBlob(s_testReport, s_reportContext, signers); + bytes32 expectedDonConfigId = _donConfigIdFromConfigData(signerAddrs, FAULT_TOLERANCE); + + uint256 number_of_reports_verified = 10; + + for (uint256 i = 0; i < number_of_reports_verified; i++) { + _approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER); + _verify(signedReport, address(link), 0, USER); + } + + uint256 expected_pool_amount = DEFAULT_REPORT_LINK_FEE * number_of_reports_verified; + + //each recipient should receive 1/4 of the pool + uint256 expectedRecipientAmount = expected_pool_amount / 4; + + payRecipients(expectedDonConfigId, recipients, ADMIN); + for (uint256 i = 0; i < recipients.length; i++) { + // checking each recipient got rewards as set by the weights + assertEq(link.balanceOf(recipients[i]), expectedRecipientAmount); + } + // checking nothing left in reward manager + assertEq(link.balanceOf(address(rewardManager)), 0); + } + + function test_rewardsAreDistributedAccordingToWeightsUsingHistoricalConfigs() public { + /* + Verifies that reports verified with historical give rewards according to the verifying config AddressAndWeight. + - Sets two Configs: ConfigA and ConfigB, These two Configs have different Recipient and Weights + - Verifies a couple reports with each config + - Pays recipients + - Asserts expected rewards for each recipient + */ + + Signer[] memory signers = _getSigners(10); + address[] memory signerAddrs = _getSignerAddresses(signers); + + (Common.AddressAndWeight[] memory weights, address[] memory recipients) = getRecipientAndWeightsGroup1(); + + // Create ConfigA + s_verifier.setConfig(signerAddrs, MINIMAL_FAULT_TOLERANCE, weights); + vm.warp(block.timestamp + 100); + + V3Report memory testReportAtT1 = generateReportAtTimestamp(block.timestamp); + bytes memory signedReportT1 = _generateV3EncodedBlob(testReportAtT1, s_reportContext, signers); + bytes32 expectedDonConfigIdA = _donConfigIdFromConfigData(signerAddrs, MINIMAL_FAULT_TOLERANCE); + + uint256 number_of_reports_verified = 2; + + // advancing the blocktimestamp so we can test verifying with configs + vm.warp(block.timestamp + 100); + + Signer[] memory signers2 = _getSigners(12); + address[] memory signerAddrs2 = _getSignerAddresses(signers2); + (Common.AddressAndWeight[] memory weights2, address[] memory recipients2) = getRecipientAndWeightsGroup2(); + + // Create ConfigB + s_verifier.setConfig(signerAddrs2, MINIMAL_FAULT_TOLERANCE, weights2); + bytes32 expectedDonConfigIdB = _donConfigIdFromConfigData(signerAddrs2, MINIMAL_FAULT_TOLERANCE); + + V3Report memory testReportAtT2 = generateReportAtTimestamp(block.timestamp); + + // verifiying using ConfigA (report with Old timestamp) + for (uint256 i = 0; i < number_of_reports_verified; i++) { + _approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER); + _verify(signedReportT1, address(link), 0, USER); + } + + // verifying using ConfigB (report with new timestamp) + for (uint256 i = 0; i < number_of_reports_verified; i++) { + _approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER); + _verify(_generateV3EncodedBlob(testReportAtT2, s_reportContext, signers2), address(link), 0, USER); + } + + uint256 expected_pool_amount = DEFAULT_REPORT_LINK_FEE * number_of_reports_verified; + assertEq(rewardManager.s_totalRewardRecipientFees(expectedDonConfigIdA), expected_pool_amount); + assertEq(rewardManager.s_totalRewardRecipientFees(expectedDonConfigIdB), expected_pool_amount); + + // check the recipients are paid according to weights + payRecipients(expectedDonConfigIdA, recipients, ADMIN); + + for (uint256 i = 0; i < recipients.length; i++) { + // //each recipient should receive 1/4 of the pool + assertEq(link.balanceOf(recipients[i]), expected_pool_amount / 4); + } + + payRecipients(expectedDonConfigIdB, recipients2, ADMIN); + + for (uint256 i = 1; i < recipients2.length; i++) { + // //each recipient should receive 1/4 of the pool + assertEq(link.balanceOf(recipients2[i]), expected_pool_amount / 4); + } + + // this recipient was part of the two config weights + assertEq(link.balanceOf(recipients2[0]), (expected_pool_amount / 4) * 2); + assertEq(link.balanceOf(address(rewardManager)), 0); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierTestRewardsMultiVefifierFeeManager.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierTestRewardsMultiVefifierFeeManager.t.sol new file mode 100644 index 0000000000..6a90cbf373 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierTestRewardsMultiVefifierFeeManager.t.sol @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {MultipleVerifierWithMultipleFeeManagers} from "./BaseDestinationVerifierTest.t.sol"; +import {DestinationVerifier} from "../../../v0.4.0/DestinationVerifier.sol"; +import {DestinationVerifierProxy} from "../../../v0.4.0/DestinationVerifierProxy.sol"; +import {AccessControllerInterface} from "../../../../shared/interfaces/AccessControllerInterface.sol"; +import {Common} from "../../../libraries/Common.sol"; + +contract MultiVerifierBillingTests is MultipleVerifierWithMultipleFeeManagers { + uint8 MINIMAL_FAULT_TOLERANCE = 2; + address internal constant DEFAULT_RECIPIENT_1 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_1")))); + address internal constant DEFAULT_RECIPIENT_2 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_2")))); + address internal constant DEFAULT_RECIPIENT_3 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_3")))); + address internal constant DEFAULT_RECIPIENT_4 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_4")))); + address internal constant DEFAULT_RECIPIENT_5 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_5")))); + address internal constant DEFAULT_RECIPIENT_6 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_6")))); + address internal constant DEFAULT_RECIPIENT_7 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_7")))); + + bytes32[3] internal s_reportContext; + V3Report internal s_testReport; + + function setUp() public virtual override { + MultipleVerifierWithMultipleFeeManagers.setUp(); + s_reportContext[0] = bytes32(abi.encode(uint32(5), uint8(1))); + s_testReport = generateReportAtTimestamp(block.timestamp); + } + + function _verify( + DestinationVerifierProxy proxy, + bytes memory payload, + address feeAddress, + uint256 wrappedNativeValue, + address sender + ) internal { + address originalAddr = msg.sender; + changePrank(sender); + + proxy.verify{value: wrappedNativeValue}(payload, abi.encode(feeAddress)); + + changePrank(originalAddr); + } + + function generateReportAtTimestamp(uint256 timestamp) public pure returns (V3Report memory) { + return + V3Report({ + feedId: FEED_ID_V3, + observationsTimestamp: OBSERVATIONS_TIMESTAMP, + validFromTimestamp: uint32(timestamp), + nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE), + linkFee: uint192(DEFAULT_REPORT_LINK_FEE), + // ask michael about this expires at, is it usually set at what blocks + expiresAt: uint32(timestamp) + 500, + benchmarkPrice: MEDIAN, + bid: BID, + ask: ASK + }); + } + + function payRecipients(bytes32 poolId, address[] memory recipients, address sender) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(sender); + + //pay the recipients + rewardManager.payRecipients(poolId, recipients); + + //change back to the original address + changePrank(originalAddr); + } + + function test_multipleFeeManagersAndVerifiers() public { + /* + In this test we got: + - three verifiers (verifier, verifier2, verifier3). + - two fee managers (feeManager, feeManager2) + - one reward manager + + we glue: + - feeManager is used by verifier1 and verifier2 + - feeManager is used by verifier3 + - Rewardmanager is used by feeManager and feeManager2 + + In this test we do verificatons via verifier1, verifier2 and verifier3 and check that rewards are set accordingly + + */ + Signer[] memory signers = _getSigners(MAX_ORACLES); + address[] memory signerAddrs = _getSignerAddresses(signers); + Common.AddressAndWeight[] memory weights = new Common.AddressAndWeight[](1); + weights[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, ONE_PERCENT * 100); + + Common.AddressAndWeight[] memory weights2 = new Common.AddressAndWeight[](1); + weights2[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, ONE_PERCENT * 100); + + Common.AddressAndWeight[] memory weights3 = new Common.AddressAndWeight[](1); + weights3[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, ONE_PERCENT * 100); + + s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, weights); + s_verifier2.setConfig(signerAddrs, MINIMAL_FAULT_TOLERANCE, weights2); + s_verifier3.setConfig(signerAddrs, MINIMAL_FAULT_TOLERANCE + 1, weights3); + bytes memory signedReport = _generateV3EncodedBlob(s_testReport, s_reportContext, signers); + bytes32 expectedDonConfigID = _donConfigIdFromConfigData(signerAddrs, FAULT_TOLERANCE); + bytes32 expectedDonConfigID2 = _donConfigIdFromConfigData(signerAddrs, MINIMAL_FAULT_TOLERANCE); + bytes32 expectedDonConfigID3 = _donConfigIdFromConfigData(signerAddrs, MINIMAL_FAULT_TOLERANCE + 1); + + _approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER); + _verify(s_verifierProxy, signedReport, address(link), 0, USER); + assertEq(link.balanceOf(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE); + + // internal state checks + assertEq(feeManager.s_linkDeficit(expectedDonConfigID), 0); + assertEq(rewardManager.s_totalRewardRecipientFees(expectedDonConfigID), DEFAULT_REPORT_LINK_FEE); + assertEq(link.balanceOf(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); + + // check the recipients are paid according to weights + // These rewards happened through verifier1 and feeManager1 + address[] memory recipients = new address[](1); + recipients[0] = DEFAULT_RECIPIENT_1; + payRecipients(expectedDonConfigID, recipients, ADMIN); + assertEq(link.balanceOf(recipients[0]), DEFAULT_REPORT_LINK_FEE); + assertEq(link.balanceOf(address(rewardManager)), 0); + + // these rewards happaned through verifier2 and feeManager1 + address[] memory recipients2 = new address[](1); + recipients2[0] = DEFAULT_RECIPIENT_2; + _approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER); + _verify(s_verifierProxy2, signedReport, address(link), 0, USER); + payRecipients(expectedDonConfigID2, recipients2, ADMIN); + assertEq(link.balanceOf(recipients2[0]), DEFAULT_REPORT_LINK_FEE); + assertEq(link.balanceOf(address(rewardManager)), 0); + + // these rewards happened through verifier3 and feeManager2 + address[] memory recipients3 = new address[](1); + recipients3[0] = DEFAULT_RECIPIENT_3; + _approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER); + _verify(s_verifierProxy3, signedReport, address(link), 0, USER); + payRecipients(expectedDonConfigID3, recipients3, ADMIN); + assertEq(link.balanceOf(recipients3[0]), DEFAULT_REPORT_LINK_FEE); + assertEq(link.balanceOf(address(rewardManager)), 0); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierVerifyBulkTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierVerifyBulkTest.t.sol new file mode 100644 index 0000000000..1c57295bae --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierVerifyBulkTest.t.sol @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {BaseTest} from "./BaseDestinationVerifierTest.t.sol"; +import {DestinationVerifier} from "../../../v0.4.0/DestinationVerifier.sol"; +import {DestinationVerifierProxy} from "../../../v0.4.0/DestinationVerifierProxy.sol"; +import {AccessControllerInterface} from "../../../../shared/interfaces/AccessControllerInterface.sol"; +import {Common} from "../../../libraries/Common.sol"; + +contract VerifierVerifyBulkTest is BaseTest { + bytes32[3] internal s_reportContext; + V3Report internal s_testReportThree; + + function setUp() public virtual override { + BaseTest.setUp(); + s_reportContext[0] = bytes32(abi.encode(uint32(5), uint8(1))); + + s_testReportThree = V3Report({ + feedId: FEED_ID_V3, + observationsTimestamp: OBSERVATIONS_TIMESTAMP, + validFromTimestamp: uint32(block.timestamp), + nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE), + linkFee: uint192(DEFAULT_REPORT_LINK_FEE), + expiresAt: uint32(block.timestamp), + benchmarkPrice: MEDIAN, + bid: BID, + ask: ASK + }); + } + + function test_revertsVerifyBulkIfNoAccess() public { + vm.mockCall( + ACCESS_CONTROLLER_ADDRESS, + abi.encodeWithSelector(AccessControllerInterface.hasAccess.selector, USER), + abi.encode(false) + ); + bytes memory signedReport = _generateV3EncodedBlob( + s_testReportThree, + s_reportContext, + _getSigners(FAULT_TOLERANCE + 1) + ); + + bytes[] memory signedReports = new bytes[](2); + signedReports[0] = signedReport; + signedReports[1] = signedReport; + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.AccessForbidden.selector)); + changePrank(USER); + s_verifier.verifyBulk(signedReports, abi.encode(native), msg.sender); + } + + function test_verifyBulkSingleCaseWithSingleConfig() public { + Signer[] memory signers = _getSigners(MAX_ORACLES); + + uint8 MINIMAL_FAULT_TOLERANCE = 2; + BaseTest.Signer[] memory signersSubset1 = new BaseTest.Signer[](7); + signersSubset1[0] = signers[0]; + signersSubset1[1] = signers[1]; + signersSubset1[2] = signers[2]; + signersSubset1[3] = signers[3]; + signersSubset1[4] = signers[4]; + signersSubset1[5] = signers[5]; + signersSubset1[6] = signers[6]; + + address[] memory signersAddrSubset1 = _getSignerAddresses(signersSubset1); + // Config1 + s_verifier.setConfig(signersAddrSubset1, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + + V3Report memory report = V3Report({ + feedId: FEED_ID_V3, + observationsTimestamp: OBSERVATIONS_TIMESTAMP, + validFromTimestamp: uint32(block.timestamp), + nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE), + linkFee: uint192(DEFAULT_REPORT_LINK_FEE), + expiresAt: uint32(block.timestamp), + benchmarkPrice: MEDIAN, + bid: BID, + ask: ASK + }); + + BaseTest.Signer[] memory reportSigners = new BaseTest.Signer[](3); + reportSigners[0] = signers[0]; + reportSigners[1] = signers[1]; + reportSigners[2] = signers[2]; + + bytes[] memory signedReports = new bytes[](10); + + bytes memory signedReport = _generateV3EncodedBlob(report, s_reportContext, reportSigners); + + for (uint256 i = 0; i < signedReports.length; i++) { + signedReports[i] = signedReport; + } + + bytes[] memory verifierResponses = s_verifierProxy.verifyBulk(signedReports, abi.encode(native)); + + for (uint256 i = 0; i < verifierResponses.length; i++) { + bytes memory verifierResponse = verifierResponses[i]; + assertReportsEqual(verifierResponse, report); + } + } + + function test_verifyBulkWithSingleConfigOneVerifyFails() public { + Signer[] memory signers = _getSigners(MAX_ORACLES); + + uint8 MINIMAL_FAULT_TOLERANCE = 2; + BaseTest.Signer[] memory signersSubset1 = new BaseTest.Signer[](7); + signersSubset1[0] = signers[0]; + signersSubset1[1] = signers[1]; + signersSubset1[2] = signers[2]; + signersSubset1[3] = signers[3]; + signersSubset1[4] = signers[4]; + signersSubset1[5] = signers[5]; + signersSubset1[6] = signers[6]; + + address[] memory signersAddrSubset1 = _getSignerAddresses(signersSubset1); + // Config1 + s_verifier.setConfig(signersAddrSubset1, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + + BaseTest.Signer[] memory reportSigners = new BaseTest.Signer[](3); + reportSigners[0] = signers[0]; + reportSigners[1] = signers[1]; + reportSigners[2] = signers[2]; + + bytes[] memory signedReports = new bytes[](11); + bytes memory signedReport = _generateV3EncodedBlob(s_testReportThree, s_reportContext, reportSigners); + + for (uint256 i = 0; i < 10; i++) { + signedReports[i] = signedReport; + } + + // Making the last report in this batch not verifiable + BaseTest.Signer[] memory reportSigners2 = new BaseTest.Signer[](3); + reportSigners2[0] = signers[30]; + reportSigners2[1] = signers[29]; + reportSigners2[2] = signers[28]; + signedReports[10] = _generateV3EncodedBlob(s_testReportThree, s_reportContext, reportSigners2); + + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector)); + s_verifierProxy.verifyBulk(signedReports, abi.encode(native)); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierVerifyTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierVerifyTest.t.sol new file mode 100644 index 0000000000..e72cfd09b6 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.4.0/test/verifier/DestinationVerifierVerifyTest.t.sol @@ -0,0 +1,714 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {BaseTest} from "./BaseDestinationVerifierTest.t.sol"; +import {DestinationVerifier} from "../../../v0.4.0/DestinationVerifier.sol"; +import {DestinationVerifierProxy} from "../../../v0.4.0/DestinationVerifierProxy.sol"; +import {AccessControllerInterface} from "../../../../shared/interfaces/AccessControllerInterface.sol"; +import {Common} from "../../../libraries/Common.sol"; + +contract VerifierVerifyTest is BaseTest { + bytes32[3] internal s_reportContext; + V3Report internal s_testReportThree; + + function setUp() public virtual override { + BaseTest.setUp(); + + s_testReportThree = V3Report({ + feedId: FEED_ID_V3, + observationsTimestamp: OBSERVATIONS_TIMESTAMP, + validFromTimestamp: uint32(block.timestamp), + nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE), + linkFee: uint192(DEFAULT_REPORT_LINK_FEE), + expiresAt: uint32(block.timestamp), + benchmarkPrice: MEDIAN, + bid: BID, + ask: ASK + }); + } + + function test_verifyReport() public { + // Simple use case just setting a config and verifying a report + Signer[] memory signers = _getSigners(MAX_ORACLES); + address[] memory signerAddrs = _getSignerAddresses(signers); + s_reportContext[0] = bytes32(abi.encode(uint32(5), uint8(1))); + + s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + + bytes memory signedReport = _generateV3EncodedBlob(s_testReportThree, s_reportContext, signers); + + bytes memory verifierResponse = s_verifierProxy.verify(signedReport, abi.encode(native)); + assertReportsEqual(verifierResponse, s_testReportThree); + } + + function test_verifyTooglingActiveFlagsDonConfigs() public { + // sets config + Signer[] memory signers = _getSigners(MAX_ORACLES); + address[] memory signerAddrs = _getSignerAddresses(signers); + s_reportContext[0] = bytes32(abi.encode(uint32(5), uint8(1))); + bytes memory signedReport = _generateV3EncodedBlob(s_testReportThree, s_reportContext, signers); + s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + // verifies report + bytes memory verifierResponse = s_verifierProxy.verify(signedReport, abi.encode(native)); + assertReportsEqual(verifierResponse, s_testReportThree); + + // test verifying via a config that is deactivated + s_verifier.setConfigActive(0, false); + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector)); + verifierResponse = s_verifierProxy.verify(signedReport, abi.encode(native)); + + // test verifying via a reactivated config + s_verifier.setConfigActive(0, true); + verifierResponse = s_verifierProxy.verify(signedReport, abi.encode(native)); + assertReportsEqual(verifierResponse, s_testReportThree); + } + + function test_failToVerifyReportIfNotEnoughSigners() public { + Signer[] memory signers = _getSigners(MAX_ORACLES); + + uint8 MINIMAL_FAULT_TOLERANCE = 2; + BaseTest.Signer[] memory signersSubset1 = new BaseTest.Signer[](7); + signersSubset1[0] = signers[0]; + signersSubset1[1] = signers[1]; + signersSubset1[2] = signers[2]; + signersSubset1[3] = signers[3]; + signersSubset1[4] = signers[4]; + signersSubset1[5] = signers[5]; + signersSubset1[6] = signers[6]; + address[] memory signersAddrSubset1 = _getSignerAddresses(signersSubset1); + s_verifier.setConfig(signersAddrSubset1, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + + // only one signer, signers < MINIMAL_FAULT_TOLERANCE + BaseTest.Signer[] memory signersSubset2 = new BaseTest.Signer[](1); + signersSubset2[0] = signers[4]; + + bytes memory signedReport = _generateV3EncodedBlob(s_testReportThree, s_reportContext, signersSubset2); + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector)); + s_verifierProxy.verify(signedReport, abi.encode(native)); + } + + function test_failToVerifyReportIfNoSigners() public { + Signer[] memory signers = _getSigners(MAX_ORACLES); + + uint8 MINIMAL_FAULT_TOLERANCE = 2; + BaseTest.Signer[] memory signersSubset1 = new BaseTest.Signer[](7); + signersSubset1[0] = signers[0]; + signersSubset1[1] = signers[1]; + signersSubset1[2] = signers[2]; + signersSubset1[3] = signers[3]; + signersSubset1[4] = signers[4]; + signersSubset1[5] = signers[5]; + signersSubset1[6] = signers[6]; + address[] memory signersAddrSubset1 = _getSignerAddresses(signersSubset1); + s_verifier.setConfig(signersAddrSubset1, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + + // No signers for this report + BaseTest.Signer[] memory signersSubset2 = new BaseTest.Signer[](0); + bytes memory signedReport = _generateV3EncodedBlob(s_testReportThree, s_reportContext, signersSubset2); + + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.NoSigners.selector)); + s_verifierProxy.verify(signedReport, abi.encode(native)); + } + + function test_failToVerifyReportIfDupSigners() public { + Signer[] memory signers = _getSigners(MAX_ORACLES); + + uint8 MINIMAL_FAULT_TOLERANCE = 2; + BaseTest.Signer[] memory signersSubset1 = new BaseTest.Signer[](7); + signersSubset1[0] = signers[0]; + signersSubset1[1] = signers[1]; + signersSubset1[2] = signers[2]; + signersSubset1[3] = signers[3]; + signersSubset1[4] = signers[4]; + signersSubset1[5] = signers[5]; + signersSubset1[6] = signers[6]; + address[] memory signersAddrSubset1 = _getSignerAddresses(signersSubset1); + s_verifier.setConfig(signersAddrSubset1, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + // One signer is repeated + BaseTest.Signer[] memory signersSubset2 = new BaseTest.Signer[](4); + signersSubset2[0] = signers[0]; + signersSubset2[1] = signers[1]; + // repeated signers + signersSubset2[2] = signers[2]; + signersSubset2[3] = signers[2]; + + bytes memory signedReport = _generateV3EncodedBlob(s_testReportThree, s_reportContext, signersSubset2); + + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector)); + s_verifierProxy.verify(signedReport, abi.encode(native)); + } + + function test_failToVerifyReportIfSignerNotInConfig() public { + Signer[] memory signers = _getSigners(MAX_ORACLES); + + uint8 MINIMAL_FAULT_TOLERANCE = 2; + BaseTest.Signer[] memory signersSubset1 = new BaseTest.Signer[](7); + signersSubset1[0] = signers[0]; + signersSubset1[1] = signers[1]; + signersSubset1[2] = signers[2]; + signersSubset1[3] = signers[3]; + signersSubset1[4] = signers[4]; + signersSubset1[5] = signers[5]; + signersSubset1[6] = signers[6]; + address[] memory signersAddrSubset1 = _getSignerAddresses(signersSubset1); + s_verifier.setConfig(signersAddrSubset1, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + + // one report whose signer is not in the config + BaseTest.Signer[] memory reportSigners = new BaseTest.Signer[](4); + // these signers are part ofm the config + reportSigners[0] = signers[4]; + reportSigners[1] = signers[5]; + reportSigners[2] = signers[6]; + // this single signer is not in the config + reportSigners[3] = signers[7]; + + bytes memory signedReport = _generateV3EncodedBlob(s_testReportThree, s_reportContext, reportSigners); + + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector)); + s_verifierProxy.verify(signedReport, abi.encode(native)); + } + + function test_canVerifyOlderV3ReportsWithOlderConfigs() public { + /* + This test is checking we can use historical Configs to verify reports: + - DonConfigA has signers {A, B, C, E} is set at time T1 + - DonConfigB has signers {A, B, C, D} is set at time T2 + - checks we can verify a report with {B, C, D} signers (via DonConfigB) + - checks we can verify a report with {B, C, E} signers and timestamp below T2 (via DonConfigA historical config) + - checks we can't verify a report with {B, C, E} signers and timestamp above T2 (it gets verivied via DonConfigB) + - sets DonConfigA as deactivated + - checks we can't verify a report with {B, C, E} signers and timestamp below T2 (via DonConfigA) + */ + Signer[] memory signers = _getSigners(MAX_ORACLES); + + uint8 MINIMAL_FAULT_TOLERANCE = 2; + BaseTest.Signer[] memory signersSubset1 = new BaseTest.Signer[](7); + signersSubset1[0] = signers[0]; + signersSubset1[1] = signers[1]; + signersSubset1[2] = signers[2]; + signersSubset1[3] = signers[3]; + signersSubset1[4] = signers[4]; + signersSubset1[5] = signers[5]; + signersSubset1[6] = signers[6]; + + address[] memory signersAddrSubset1 = _getSignerAddresses(signersSubset1); + // Config1 + s_verifier.setConfig(signersAddrSubset1, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + + BaseTest.Signer[] memory signersSubset2 = new BaseTest.Signer[](7); + signersSubset2[0] = signers[0]; + signersSubset2[1] = signers[1]; + signersSubset2[2] = signers[2]; + signersSubset2[3] = signers[3]; + signersSubset2[4] = signers[4]; + signersSubset2[5] = signers[5]; + signersSubset2[6] = signers[29]; + address[] memory signersAddrSubset2 = _getSignerAddresses(signersSubset2); + + V3Report memory reportAtSetConfig1Timestmap = V3Report({ + feedId: FEED_ID_V3, + observationsTimestamp: OBSERVATIONS_TIMESTAMP, + validFromTimestamp: uint32(block.timestamp), + nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE), + linkFee: uint192(DEFAULT_REPORT_LINK_FEE), + expiresAt: uint32(block.timestamp), + benchmarkPrice: MEDIAN, + bid: BID, + ask: ASK + }); + + vm.warp(block.timestamp + 100); + + // Config2 + s_verifier.setConfig(signersAddrSubset2, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + + V3Report memory reportAtSetConfig2Timestmap = V3Report({ + feedId: FEED_ID_V3, + observationsTimestamp: OBSERVATIONS_TIMESTAMP, + validFromTimestamp: uint32(block.timestamp), + nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE), + linkFee: uint192(DEFAULT_REPORT_LINK_FEE), + expiresAt: uint32(block.timestamp), + benchmarkPrice: MEDIAN, + bid: BID, + ask: ASK + }); + + BaseTest.Signer[] memory reportSigners = new BaseTest.Signer[](5); + reportSigners[0] = signers[0]; + reportSigners[1] = signers[1]; + reportSigners[2] = signers[2]; + reportSigners[3] = signers[3]; + reportSigners[4] = signers[29]; + + bytes memory signedReport = _generateV3EncodedBlob(reportAtSetConfig2Timestmap, s_reportContext, reportSigners); + + // this report is verified via Config2 + bytes memory verifierResponse = s_verifierProxy.verify(signedReport, abi.encode(native)); + assertReportsEqual(verifierResponse, reportAtSetConfig2Timestmap); + + BaseTest.Signer[] memory reportSigners2 = new BaseTest.Signer[](5); + reportSigners2[0] = signers[0]; + reportSigners2[1] = signers[1]; + reportSigners2[2] = signers[2]; + reportSigners2[3] = signers[3]; + reportSigners2[4] = signers[6]; + + bytes memory signedReport2 = _generateV3EncodedBlob(reportAtSetConfig1Timestmap, s_reportContext, reportSigners2); + + // this report is verified via Config1 (using a historical config) + bytes memory verifierResponse2 = s_verifierProxy.verify(signedReport2, abi.encode(native)); + assertReportsEqual(verifierResponse2, reportAtSetConfig1Timestmap); + + // same report with same signers but with a higher timestamp gets verified via Config2 + // which means verification fails + bytes memory signedReport3 = _generateV3EncodedBlob(reportAtSetConfig2Timestmap, s_reportContext, reportSigners2); + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector)); + s_verifierProxy.verify(signedReport3, abi.encode(native)); + + // deactivating Config1 and trying a reverifications ends in failure + s_verifier.setConfigActive(0, false); + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector)); + s_verifierProxy.verify(signedReport2, abi.encode(native)); + } + + function test_revertsVerifyIfNoAccess() public { + vm.mockCall( + ACCESS_CONTROLLER_ADDRESS, + abi.encodeWithSelector(AccessControllerInterface.hasAccess.selector, USER), + abi.encode(false) + ); + bytes memory signedReport = _generateV3EncodedBlob( + s_testReportThree, + s_reportContext, + _getSigners(FAULT_TOLERANCE + 1) + ); + + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.AccessForbidden.selector)); + + changePrank(USER); + s_verifier.verify(signedReport, abi.encode(native), msg.sender); + } + + function test_canVerifyNewerReportsWithNewerConfigs() public { + /* + This test is checking that we use prefer verifiying via newer configs instead of old ones. + - DonConfigA has signers {A, B, C, E} is set at time T1 + - DonConfigB has signers {F, G, H, I} is set at time T2 + - DonConfigC has signers {J, K, L, M } is set at time T3 + - checks we can verify a report with {K, L, M} signers (via DonConfigC) + */ + Signer[] memory signers = _getSigners(MAX_ORACLES); + + uint8 MINIMAL_FAULT_TOLERANCE = 2; + BaseTest.Signer[] memory signersSubset1 = new BaseTest.Signer[](7); + signersSubset1[0] = signers[0]; + signersSubset1[1] = signers[1]; + signersSubset1[2] = signers[2]; + signersSubset1[3] = signers[3]; + signersSubset1[4] = signers[4]; + signersSubset1[5] = signers[5]; + signersSubset1[6] = signers[6]; + + address[] memory signersAddrSubset1 = _getSignerAddresses(signersSubset1); + // Config1 + s_verifier.setConfig(signersAddrSubset1, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + vm.warp(block.timestamp + 1); + + BaseTest.Signer[] memory signersSubset2 = new BaseTest.Signer[](7); + signersSubset2[0] = signers[7]; + signersSubset2[1] = signers[8]; + signersSubset2[2] = signers[9]; + signersSubset2[3] = signers[10]; + signersSubset2[4] = signers[11]; + signersSubset2[5] = signers[12]; + signersSubset2[6] = signers[13]; + + address[] memory signersAddrSubset2 = _getSignerAddresses(signersSubset2); + // Config2 + s_verifier.setConfig(signersAddrSubset2, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + vm.warp(block.timestamp + 1); + + BaseTest.Signer[] memory signersSubset3 = new BaseTest.Signer[](7); + signersSubset3[0] = signers[30]; + signersSubset3[1] = signers[29]; + signersSubset3[2] = signers[28]; + signersSubset3[3] = signers[27]; + signersSubset3[4] = signers[26]; + signersSubset3[5] = signers[25]; + signersSubset3[6] = signers[24]; + + address[] memory signersAddrSubset3 = _getSignerAddresses(signersSubset3); + // Config3 + s_verifier.setConfig(signersAddrSubset3, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + vm.warp(block.timestamp + 1); + + V3Report memory report = V3Report({ + feedId: FEED_ID_V3, + observationsTimestamp: OBSERVATIONS_TIMESTAMP, + validFromTimestamp: uint32(block.timestamp), + nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE), + linkFee: uint192(DEFAULT_REPORT_LINK_FEE), + expiresAt: uint32(block.timestamp), + benchmarkPrice: MEDIAN, + bid: BID, + ask: ASK + }); + + BaseTest.Signer[] memory reportSigners = new BaseTest.Signer[](3); + reportSigners[0] = signers[30]; + reportSigners[1] = signers[29]; + reportSigners[2] = signers[28]; + + bytes memory signedReport = _generateV3EncodedBlob(report, s_reportContext, reportSigners); + + s_verifierProxy.verify(signedReport, abi.encode(native)); + } + + function test_rollingOutConfiguration() public { + /* + This test is checking that we can roll out to a new DON without downtime using a transition configuration + - DonConfigA has signers {A, B, C} is set at time T1 + - DonConfigB (transition config) has signers {A, B, C, D, E, F} is set at time T2 + - DonConfigC has signers {D, E, F} is set at time T3 + + - checks we can verify a report with {A, B, C} signers (via DonConfigA) at time between T1 and T2 + - checks we can verify a report with {A, B, C} signers (via DonConfigB) at time between T2 and T3 + - checks we can verify a report with {D, E, F} signers (via DonConfigB) at time between T2 and T3 + - checks we can verify a report with {D, E, F} signers (via DonConfigC) at time > T3 + - checks we can't verify a report with {A, B, C} signers (via DonConfigC) and timestamp >T3 at time > T3 + - checks we can verify a report with {A, B, C} signers (via DonConfigC) and timestamp between T2 and T3 at time > T3 (historical check) + + */ + + Signer[] memory signers = _getSigners(MAX_ORACLES); + + uint8 MINIMAL_FAULT_TOLERANCE = 2; + BaseTest.Signer[] memory signersSubset1 = new BaseTest.Signer[](7); + signersSubset1[0] = signers[0]; + signersSubset1[1] = signers[1]; + signersSubset1[2] = signers[2]; + signersSubset1[3] = signers[3]; + signersSubset1[4] = signers[4]; + signersSubset1[5] = signers[5]; + signersSubset1[6] = signers[6]; + + // ConfigA + address[] memory signersAddrSubset1 = _getSignerAddresses(signersSubset1); + s_verifier.setConfig(signersAddrSubset1, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + + V3Report memory reportT1 = V3Report({ + feedId: FEED_ID_V3, + observationsTimestamp: OBSERVATIONS_TIMESTAMP, + validFromTimestamp: uint32(block.timestamp), + nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE), + linkFee: uint192(DEFAULT_REPORT_LINK_FEE), + expiresAt: uint32(block.timestamp), + benchmarkPrice: MEDIAN, + bid: BID, + ask: ASK + }); + + BaseTest.Signer[] memory reportSignersConfigA = new BaseTest.Signer[](3); + reportSignersConfigA[0] = signers[0]; + reportSignersConfigA[1] = signers[1]; + reportSignersConfigA[2] = signers[2]; + + // just testing ConfigA + bytes memory signedReport = _generateV3EncodedBlob(reportT1, s_reportContext, reportSignersConfigA); + s_verifierProxy.verify(signedReport, abi.encode(native)); + + vm.warp(block.timestamp + 100); + + BaseTest.Signer[] memory signersSuperset = new BaseTest.Signer[](14); + // signers in ConfigA + signersSuperset[0] = signers[0]; + signersSuperset[1] = signers[1]; + signersSuperset[2] = signers[2]; + signersSuperset[3] = signers[3]; + signersSuperset[4] = signers[4]; + signersSuperset[5] = signers[5]; + signersSuperset[6] = signers[6]; + // new signers + signersSuperset[7] = signers[7]; + signersSuperset[8] = signers[8]; + signersSuperset[9] = signers[9]; + signersSuperset[10] = signers[10]; + signersSuperset[11] = signers[11]; + signersSuperset[12] = signers[12]; + signersSuperset[13] = signers[13]; + + BaseTest.Signer[] memory reportSignersConfigC = new BaseTest.Signer[](3); + reportSignersConfigC[0] = signers[7]; + reportSignersConfigC[1] = signers[8]; + reportSignersConfigC[2] = signers[9]; + + // ConfigB (transition Config) + address[] memory signersAddrsSuperset = _getSignerAddresses(signersSuperset); + s_verifier.setConfig(signersAddrsSuperset, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + + V3Report memory reportT2 = V3Report({ + feedId: FEED_ID_V3, + observationsTimestamp: OBSERVATIONS_TIMESTAMP, + validFromTimestamp: uint32(block.timestamp), + nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE), + linkFee: uint192(DEFAULT_REPORT_LINK_FEE), + expiresAt: uint32(block.timestamp), + benchmarkPrice: MEDIAN, + bid: BID, + ask: ASK + }); + + // testing we can verify a fresh (block timestamp) report with ConfigA signers. This should use ConfigB + signedReport = _generateV3EncodedBlob(reportT2, s_reportContext, reportSignersConfigA); + s_verifierProxy.verify(signedReport, abi.encode(native)); + + // testing we can verify an old ( non fresh block timestamp) report with ConfigA signers. This should use ConfigA + signedReport = _generateV3EncodedBlob(reportT1, s_reportContext, reportSignersConfigA); + s_verifierProxy.verify(signedReport, abi.encode(native)); + // deactivating to make sure we are really verifiying via ConfigA + s_verifier.setConfigActive(0, false); + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector)); + s_verifierProxy.verify(signedReport, abi.encode(native)); + s_verifier.setConfigActive(0, true); + + // testing we can verify a fresh (block timestamp) report with the new signers. This should use ConfigB + signedReport = _generateV3EncodedBlob(reportT2, s_reportContext, reportSignersConfigC); + s_verifierProxy.verify(signedReport, abi.encode(native)); + + vm.warp(block.timestamp + 100); + + // Adding ConfigC + BaseTest.Signer[] memory signersSubset2 = new BaseTest.Signer[](7); + signersSubset2[0] = signers[7]; + signersSubset2[1] = signers[8]; + signersSubset2[2] = signers[9]; + signersSubset2[3] = signers[10]; + signersSubset2[4] = signers[11]; + signersSubset2[5] = signers[12]; + signersSubset2[6] = signers[13]; + address[] memory signersAddrsSubset2 = _getSignerAddresses(signersSubset2); + s_verifier.setConfig(signersAddrsSubset2, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + + V3Report memory reportT3 = V3Report({ + feedId: FEED_ID_V3, + observationsTimestamp: OBSERVATIONS_TIMESTAMP, + validFromTimestamp: uint32(block.timestamp), + nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE), + linkFee: uint192(DEFAULT_REPORT_LINK_FEE), + expiresAt: uint32(block.timestamp), + benchmarkPrice: MEDIAN, + bid: BID, + ask: ASK + }); + + // testing we can verify reports with ConfigC signers + signedReport = _generateV3EncodedBlob(reportT3, s_reportContext, reportSignersConfigC); + s_verifierProxy.verify(signedReport, abi.encode(native)); + + // testing an old report (block timestamp) with ConfigC signers should verify via ConfigB + signedReport = _generateV3EncodedBlob(reportT2, s_reportContext, reportSignersConfigC); + s_verifierProxy.verify(signedReport, abi.encode(native)); + // deactivating to make sure we are really verifiying via ConfigB + s_verifier.setConfigActive(1, false); + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector)); + s_verifierProxy.verify(signedReport, abi.encode(native)); + s_verifier.setConfigActive(1, true); + + // testing a recent report with ConfigA signers should not verify + signedReport = _generateV3EncodedBlob(reportT3, s_reportContext, reportSignersConfigA); + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector)); + s_verifierProxy.verify(signedReport, abi.encode(native)); + + // testing an old report (block timestamp) with ConfigA signers should verify via ConfigB + signedReport = _generateV3EncodedBlob(reportT2, s_reportContext, reportSignersConfigA); + s_verifierProxy.verify(signedReport, abi.encode(native)); + // deactivating to make sure we are really verifiying via ConfigB + s_verifier.setConfigActive(1, false); + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector)); + s_verifierProxy.verify(signedReport, abi.encode(native)); + s_verifier.setConfigActive(1, true); + + // testing an old report (block timestamp) with ConfigA signers should verify via ConfigA + signedReport = _generateV3EncodedBlob(reportT1, s_reportContext, reportSignersConfigA); + s_verifierProxy.verify(signedReport, abi.encode(native)); + // deactivating to make sure we are really verifiying via ConfigB + s_verifier.setConfigActive(0, false); + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector)); + s_verifierProxy.verify(signedReport, abi.encode(native)); + s_verifier.setConfigActive(0, true); + } + + function test_verifyFailsWhenReportIsOlderThanConfig() public { + /* + - SetConfig A at time T0 + - SetConfig B at time T1 + - tries verifing report issued at blocktimestmap < T0 + + this test is failing: ToDo Ask Michael + */ + Signer[] memory signers = _getSigners(MAX_ORACLES); + address[] memory signerAddrs = _getSignerAddresses(signers); + s_reportContext[0] = bytes32(abi.encode(uint32(5), uint8(1))); + + vm.warp(block.timestamp + 100); + + V3Report memory reportAtTMinus100 = V3Report({ + feedId: FEED_ID_V3, + observationsTimestamp: OBSERVATIONS_TIMESTAMP, + validFromTimestamp: uint32(block.timestamp - 100), + nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE), + linkFee: uint192(DEFAULT_REPORT_LINK_FEE), + expiresAt: uint32(block.timestamp), + benchmarkPrice: MEDIAN, + bid: BID, + ask: ASK + }); + + s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + vm.warp(block.timestamp + 100); + s_verifier.setConfig(signerAddrs, FAULT_TOLERANCE - 1, new Common.AddressAndWeight[](0)); + + bytes memory signedReport = _generateV3EncodedBlob(reportAtTMinus100, s_reportContext, signers); + + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector)); + s_verifierProxy.verify(signedReport, abi.encode(native)); + } + + function test_scenarioRollingNewChainWithHistoricConfigs() public { + /* + This test is checking that we can roll out in a new network and set historic configurations : + - Stars with a chain at blocktimestamp 1000 + - SetConfigA with teimstamp 100 + - SetConfigB with timesmtap 200 + - SetConfigC with timestamp current + - tries verifying reports for all the configs + */ + + vm.warp(block.timestamp + 1000); + + Signer[] memory signers = _getSigners(MAX_ORACLES); + + uint8 MINIMAL_FAULT_TOLERANCE = 2; + BaseTest.Signer[] memory signersA = new BaseTest.Signer[](7); + signersA[0] = signers[0]; + signersA[1] = signers[1]; + signersA[2] = signers[2]; + signersA[3] = signers[3]; + signersA[4] = signers[4]; + signersA[5] = signers[5]; + signersA[6] = signers[6]; + + // ConfigA (historical config) + uint32 configATimestmap = 100; + address[] memory signersAddrA = _getSignerAddresses(signersA); + s_verifier.setConfigWithActivationTime( + signersAddrA, + MINIMAL_FAULT_TOLERANCE, + new Common.AddressAndWeight[](0), + configATimestmap + ); + + // ConfigB (historical config) + uint32 configBTimestmap = 200; + // Config B + BaseTest.Signer[] memory signersB = new BaseTest.Signer[](7); + // signers in ConfigA + signersB[0] = signers[8]; + signersB[1] = signers[9]; + signersB[2] = signers[10]; + signersB[3] = signers[11]; + signersB[4] = signers[12]; + signersB[5] = signers[13]; + signersB[6] = signers[14]; + address[] memory signersAddrsB = _getSignerAddresses(signersB); + s_verifier.setConfigWithActivationTime( + signersAddrsB, + MINIMAL_FAULT_TOLERANCE, + new Common.AddressAndWeight[](0), + configBTimestmap + ); + + // ConfigC (config at current timestamp) + // BaseTest.Signer[] memory signersC = new BaseTest.Signer[](7); + // signers in ConfigA + signersB[6] = signers[15]; + address[] memory signersAddrsC = _getSignerAddresses(signersB); + s_verifier.setConfig(signersAddrsC, MINIMAL_FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + + vm.warp(block.timestamp + 10); + + // historical report + V3Report memory s_testReportA = V3Report({ + feedId: FEED_ID_V3, + observationsTimestamp: OBSERVATIONS_TIMESTAMP, + validFromTimestamp: uint32(101), + nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE), + linkFee: uint192(DEFAULT_REPORT_LINK_FEE), + expiresAt: uint32(block.timestamp + 1000), + benchmarkPrice: MEDIAN, + bid: BID, + ask: ASK + }); + + // historical report + V3Report memory s_testReportB = V3Report({ + feedId: FEED_ID_V3, + observationsTimestamp: OBSERVATIONS_TIMESTAMP, + validFromTimestamp: uint32(201), + nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE), + linkFee: uint192(DEFAULT_REPORT_LINK_FEE), + expiresAt: uint32(block.timestamp + 1000), + benchmarkPrice: MEDIAN, + bid: BID, + ask: ASK + }); + + // report at recent timestamp + V3Report memory s_testReportC = V3Report({ + feedId: FEED_ID_V3, + observationsTimestamp: OBSERVATIONS_TIMESTAMP, + validFromTimestamp: uint32(block.timestamp), + nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE), + linkFee: uint192(DEFAULT_REPORT_LINK_FEE), + expiresAt: uint32(block.timestamp + 1000), + benchmarkPrice: MEDIAN, + bid: BID, + ask: ASK + }); + + BaseTest.Signer[] memory reportSignersA = new BaseTest.Signer[](3); + reportSignersA[0] = signers[0]; + reportSignersA[1] = signers[1]; + reportSignersA[2] = signers[2]; + + BaseTest.Signer[] memory reportSignersB = new BaseTest.Signer[](3); + reportSignersB[0] = signers[8]; + reportSignersB[1] = signers[9]; + reportSignersB[2] = signers[14]; + + BaseTest.Signer[] memory reportSignersC = new BaseTest.Signer[](3); + reportSignersC[0] = signers[15]; + reportSignersC[1] = signers[13]; + reportSignersC[2] = signers[12]; + + bytes memory signedReportA = _generateV3EncodedBlob(s_testReportA, s_reportContext, reportSignersA); + bytes memory signedReportB = _generateV3EncodedBlob(s_testReportB, s_reportContext, reportSignersB); + bytes memory signedReportC = _generateV3EncodedBlob(s_testReportC, s_reportContext, reportSignersC); + + // verifying historical reports + s_verifierProxy.verify(signedReportA, abi.encode(native)); + s_verifierProxy.verify(signedReportB, abi.encode(native)); + // verifiying a current report + s_verifierProxy.verify(signedReportC, abi.encode(native)); + + // current report verified by historical report fails + bytes memory signedNewReportWithOldSignatures = _generateV3EncodedBlob( + s_testReportC, + s_reportContext, + reportSignersA + ); + vm.expectRevert(abi.encodeWithSelector(DestinationVerifier.BadVerification.selector)); + s_verifierProxy.verify(signedNewReportWithOldSignatures, abi.encode(native)); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/ChannelConfigStore.sol b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/ChannelConfigStore.sol new file mode 100644 index 0000000000..f5e5040bb8 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/ChannelConfigStore.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {ConfirmedOwner} from "../../../shared/access/ConfirmedOwner.sol"; +import {IChannelConfigStore} from "./interfaces/IChannelConfigStore.sol"; +import {TypeAndVersionInterface} from "../../../interfaces/TypeAndVersionInterface.sol"; + +contract ChannelConfigStore is ConfirmedOwner, IChannelConfigStore, TypeAndVersionInterface { + event NewChannelDefinition(uint256 indexed donId, uint32 version, string url, bytes32 sha); + + constructor() ConfirmedOwner(msg.sender) {} + + /// @notice The version of a channel definition keyed by DON ID + // Increments by 1 on every update + mapping(uint256 => uint256) internal s_channelDefinitionVersions; + + function setChannelDefinitions(uint32 donId, string calldata url, bytes32 sha) external onlyOwner { + uint32 newVersion = uint32(++s_channelDefinitionVersions[uint256(donId)]); + emit NewChannelDefinition(donId, newVersion, url, sha); + } + + function typeAndVersion() external pure override returns (string memory) { + return "ChannelConfigStore 0.0.1"; + } + + function supportsInterface(bytes4 interfaceId) external pure returns (bool) { + return interfaceId == type(IChannelConfigStore).interfaceId; + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/Configurator.sol b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/Configurator.sol new file mode 100644 index 0000000000..5945f7ab73 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/Configurator.sol @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {ConfirmedOwner} from "../../../shared/access/ConfirmedOwner.sol"; +import {TypeAndVersionInterface} from "../../../interfaces/TypeAndVersionInterface.sol"; +import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; +import {IConfigurator} from "../../interfaces/IConfigurator.sol"; + +// OCR2 standard +uint256 constant MAX_NUM_ORACLES = 31; + +/** + * @title Configurator + * @author samsondav + * @notice This contract is intended to be deployed on the source chain and acts as a OCR3 configurator for LLO/Mercury + **/ + +contract Configurator is IConfigurator, ConfirmedOwner, TypeAndVersionInterface, IERC165 { + /// @notice This error is thrown whenever trying to set a config + /// with a fault tolerance of 0 + error FaultToleranceMustBePositive(); + + /// @notice This error is thrown whenever a report is signed + /// with more than the max number of signers + /// @param numSigners The number of signers who have signed the report + /// @param maxSigners The maximum number of signers that can sign a report + error ExcessSigners(uint256 numSigners, uint256 maxSigners); + + /// @notice This error is thrown whenever a report is signed + /// with less than the minimum number of signers + /// @param numSigners The number of signers who have signed the report + /// @param minSigners The minimum number of signers that need to sign a report + error InsufficientSigners(uint256 numSigners, uint256 minSigners); + + struct ConfigurationState { + // The number of times a new configuration + // has been set + uint64 configCount; + // The block number of the block the last time + /// the configuration was updated. + uint32 latestConfigBlockNumber; + } + + constructor() ConfirmedOwner(msg.sender) {} + + /// @notice Configuration states keyed on DON ID + mapping(bytes32 => ConfigurationState) internal s_configurationStates; + + /// @inheritdoc IConfigurator + function setConfig( + bytes32 donId, + address[] memory signers, + bytes32[] memory offchainTransmitters, + uint8 f, + bytes memory onchainConfig, + uint64 offchainConfigVersion, + bytes memory offchainConfig + ) external override checkConfigValid(signers.length, f) onlyOwner { + _setConfig( + donId, + block.chainid, + address(this), + signers, + offchainTransmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig + ); + } + + /// @notice Sets config based on the given arguments + /// @param donId DON ID to set config for + /// @param sourceChainId Chain ID of source config + /// @param sourceAddress Address of source config Verifier + /// @param signers addresses with which oracles sign the reports + /// @param offchainTransmitters CSA key for the ith Oracle + /// @param f number of faulty oracles the system can tolerate + /// @param onchainConfig serialized configuration used by the contract (and possibly oracles) + /// @param offchainConfigVersion version number for offchainEncoding schema + /// @param offchainConfig serialized configuration used by the oracles exclusively and only passed through the contract + function _setConfig( + bytes32 donId, + uint256 sourceChainId, + address sourceAddress, + address[] memory signers, + bytes32[] memory offchainTransmitters, + uint8 f, + bytes memory onchainConfig, + uint64 offchainConfigVersion, + bytes memory offchainConfig + ) internal { + ConfigurationState storage configurationState = s_configurationStates[donId]; + + uint64 newConfigCount = ++configurationState.configCount; + + bytes32 configDigest = _configDigestFromConfigData( + donId, + sourceChainId, + sourceAddress, + newConfigCount, + signers, + offchainTransmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig + ); + + emit ConfigSet( + donId, + configurationState.latestConfigBlockNumber, + configDigest, + newConfigCount, + signers, + offchainTransmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig + ); + + configurationState.latestConfigBlockNumber = uint32(block.number); + } + + /// @notice Generates the config digest from config data + /// @param donId DON ID to set config for + /// @param sourceChainId Chain ID of configurator contract + /// @param sourceAddress Address of configurator contract + /// @param configCount ordinal number of this config setting among all config settings over the life of this contract + /// @param signers ith element is address ith oracle uses to sign a report + /// @param offchainTransmitters ith element is address ith oracle used to transmit reports (in this case used for flexible additional field, such as CSA pub keys) + /// @param f maximum number of faulty/dishonest oracles the protocol can tolerate while still working correctly + /// @param onchainConfig serialized configuration used by the contract (and possibly oracles) + /// @param offchainConfigVersion version of the serialization format used for "offchainConfig" parameter + /// @param offchainConfig serialized configuration used by the oracles exclusively and only passed through the contract + /// @dev This function is a modified version of the method from OCR2Abstract + function _configDigestFromConfigData( + bytes32 donId, + uint256 sourceChainId, + address sourceAddress, + uint64 configCount, + address[] memory signers, + bytes32[] memory offchainTransmitters, + uint8 f, + bytes memory onchainConfig, + uint64 offchainConfigVersion, + bytes memory offchainConfig + ) internal pure returns (bytes32) { + uint256 h = uint256( + keccak256( + abi.encode( + donId, + sourceChainId, + sourceAddress, + configCount, + signers, + offchainTransmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig + ) + ) + ); + uint256 prefixMask = type(uint256).max << (256 - 16); // 0xFFFF00..00 + // 0x0006 corresponds to ConfigDigestPrefixLLO in libocr + uint256 prefix = 0x0009 << (256 - 16); // 0x000900..00 + return bytes32((prefix & prefixMask) | (h & ~prefixMask)); + } + + /// @inheritdoc IERC165 + function supportsInterface(bytes4 interfaceId) external pure override returns (bool isVerifier) { + return interfaceId == type(IConfigurator).interfaceId; + } + + /// @inheritdoc TypeAndVersionInterface + function typeAndVersion() external pure override returns (string memory) { + return "Configurator 0.4.0"; + } + + modifier checkConfigValid(uint256 numSigners, uint256 f) { + if (f == 0) revert FaultToleranceMustBePositive(); + if (numSigners > MAX_NUM_ORACLES) revert ExcessSigners(numSigners, MAX_NUM_ORACLES); + if (numSigners <= 3 * f) revert InsufficientSigners(numSigners, 3 * f + 1); + _; + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/interfaces/IChannelConfigStore.sol b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/interfaces/IChannelConfigStore.sol new file mode 100644 index 0000000000..8628e3c1d5 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/interfaces/IChannelConfigStore.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {IERC165} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; + +interface IChannelConfigStore is IERC165 { + function setChannelDefinitions(uint32 donId, string calldata url, bytes32 sha) external; +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/ChannelConfigStore.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/ChannelConfigStore.t.sol new file mode 100644 index 0000000000..70b033e66b --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/ChannelConfigStore.t.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {IChannelConfigStore} from "../interfaces/IChannelConfigStore.sol"; +import {Test} from "forge-std/Test.sol"; +import {ChannelConfigStore} from "../ChannelConfigStore.sol"; +import {ExposedChannelConfigStore} from "./mocks/ExposedChannelConfigStore.sol"; + +/** + * @title ChannelConfigStoreTest + * @author samsondav + * @notice Base class for ChannelConfigStore tests + */ +contract ChannelConfigStoreTest is Test { + ExposedChannelConfigStore public channelConfigStore; + event NewChannelDefinition(uint256 indexed donId, uint32 version, string url, bytes32 sha); + + function setUp() public virtual { + channelConfigStore = new ExposedChannelConfigStore(); + } + + function testTypeAndVersion() public view { + assertEq(channelConfigStore.typeAndVersion(), "ChannelConfigStore 0.0.1"); + } + + function testSupportsInterface() public view { + assertTrue(channelConfigStore.supportsInterface(type(IChannelConfigStore).interfaceId)); + } + + function testSetChannelDefinitions() public { + vm.expectEmit(); + emit NewChannelDefinition(42, 1, "url", keccak256("sha")); + channelConfigStore.setChannelDefinitions(42, "url", keccak256("sha")); + + vm.expectEmit(); + emit NewChannelDefinition(42, 2, "url2", keccak256("sha2")); + channelConfigStore.setChannelDefinitions(42, "url2", keccak256("sha2")); + + assertEq(channelConfigStore.exposedReadChannelDefinitionStates(42), uint32(2)); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/mocks/ExposedChannelConfigStore.sol b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/mocks/ExposedChannelConfigStore.sol new file mode 100644 index 0000000000..1ffd51210f --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/mocks/ExposedChannelConfigStore.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {ChannelConfigStore} from "../../ChannelConfigStore.sol"; + +// Exposed ChannelConfigStore exposes certain internal ChannelConfigStore +// methods/structures so that golang code can access them, and we get +// reliable type checking on their usage +contract ExposedChannelConfigStore is ChannelConfigStore { + constructor() {} + + function exposedReadChannelDefinitionStates(uint256 donId) public view returns (uint256) { + return s_channelDefinitionVersions[donId]; + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/mocks/ExposedConfigurator.sol b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/mocks/ExposedConfigurator.sol new file mode 100644 index 0000000000..7b1370a846 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/mocks/ExposedConfigurator.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {Configurator} from "../../Configurator.sol"; + +// Exposed ChannelConfigStore exposes certain internal ChannelConfigStore +// methods/structures so that golang code can access them, and we get +// reliable type checking on their usage +contract ExposedConfigurator is Configurator { + constructor() {} + + function exposedReadConfigurationStates(bytes32 donId) public view returns (ConfigurationState memory) { + return s_configurationStates[donId]; + } +} diff --git a/contracts/src/v0.8/shared/test/helpers/ChainReaderTester.sol b/contracts/src/v0.8/shared/test/helpers/ChainReaderTester.sol index 58a4b9a25c..e3b277119a 100644 --- a/contracts/src/v0.8/shared/test/helpers/ChainReaderTester.sol +++ b/contracts/src/v0.8/shared/test/helpers/ChainReaderTester.sol @@ -26,11 +26,11 @@ struct InnerTestStruct { contract ChainReaderTester { event Triggered( int32 indexed field, - string differentField, uint8 oracleId, uint8[32] oracleIds, address Account, address[] Accounts, + string differentField, int192 bigField, MidLevelTestStruct nestedStruct ); @@ -40,6 +40,9 @@ contract ChainReaderTester { // First topic is event hash event TriggeredWithFourTopics(int32 indexed field1, int32 indexed field2, int32 indexed field3); + // first topic is event hash, second and third topics get hashed before getting stored + event TriggeredWithFourTopicsWithHashed(string indexed field1, uint8[32] indexed field2, bytes32 indexed field3); + TestStruct[] private s_seen; uint64[] private s_arr; uint64 private s_value; @@ -106,15 +109,15 @@ contract ChainReaderTester { function triggerEvent( int32 field, - string calldata differentField, uint8 oracleId, uint8[32] calldata oracleIds, address account, address[] calldata accounts, + string calldata differentField, int192 bigField, MidLevelTestStruct calldata nestedStruct ) public { - emit Triggered(field, differentField, oracleId, oracleIds, account, accounts, bigField, nestedStruct); + emit Triggered(field, oracleId, oracleIds, account, accounts, differentField, bigField, nestedStruct); } function triggerEventWithDynamicTopic(string calldata field) public { @@ -125,4 +128,9 @@ contract ChainReaderTester { function triggerWithFourTopics(int32 field1, int32 field2, int32 field3) public { emit TriggeredWithFourTopics(field1, field2, field3); } + + // first topic is event hash, second and third topics get hashed before getting stored + function triggerWithFourTopicsWithHashed(string memory field1, uint8[32] memory field2, bytes32 field3) public { + emit TriggeredWithFourTopicsWithHashed(field1, field2, field3); + } } diff --git a/contracts/src/v0.8/tests/MockGasBoundCaller.sol b/contracts/src/v0.8/tests/MockGasBoundCaller.sol new file mode 100644 index 0000000000..3184f9dba3 --- /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 29790b0e15..6bb0dae645 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 0000000000..265d4b678a --- /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/src/v0.8/vrf/dev/testhelpers/VRFCoordinatorTestV2_5.sol b/contracts/src/v0.8/vrf/dev/testhelpers/VRFCoordinatorTestV2_5.sol new file mode 100644 index 0000000000..2e9c4a2da7 --- /dev/null +++ b/contracts/src/v0.8/vrf/dev/testhelpers/VRFCoordinatorTestV2_5.sol @@ -0,0 +1,773 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {BlockhashStoreInterface} from "../../interfaces/BlockhashStoreInterface.sol"; +import {VRFOld} from "./VRFOld.sol"; +import {VRFTypes} from "../../VRFTypes.sol"; +import {VRFConsumerBaseV2Plus, IVRFMigratableConsumerV2Plus} from "../VRFConsumerBaseV2Plus.sol"; +import {ChainSpecificUtil} from "../../../ChainSpecificUtil.sol"; +import {SubscriptionAPI} from "../SubscriptionAPI.sol"; +import {VRFV2PlusClient} from "../libraries/VRFV2PlusClient.sol"; +import {IVRFCoordinatorV2PlusMigration} from "../interfaces/IVRFCoordinatorV2PlusMigration.sol"; +// solhint-disable-next-line no-unused-import +import {IVRFCoordinatorV2Plus, IVRFSubscriptionV2Plus} from "../interfaces/IVRFCoordinatorV2Plus.sol"; + +// solhint-disable-next-line contract-name-camelcase +contract VRFCoordinatorTestV2_5 is VRFOld, SubscriptionAPI, IVRFCoordinatorV2Plus { + /// @dev should always be available + // solhint-disable-next-line chainlink-solidity/prefix-immutable-variables-with-i + BlockhashStoreInterface public immutable BLOCKHASH_STORE; + + // Set this maximum to 200 to give us a 56 block window to fulfill + // the request before requiring the block hash feeder. + uint16 public constant MAX_REQUEST_CONFIRMATIONS = 200; + uint32 public constant MAX_NUM_WORDS = 500; + // 5k is plenty for an EXTCODESIZE call (2600) + warm CALL (100) + // and some arithmetic operations. + uint256 private constant GAS_FOR_CALL_EXACT_CHECK = 5_000; + // upper bound limit for premium percentages to make sure fee calculations don't overflow + uint8 private constant PREMIUM_PERCENTAGE_MAX = 155; + error InvalidRequestConfirmations(uint16 have, uint16 min, uint16 max); + error GasLimitTooBig(uint32 have, uint32 want); + error NumWordsTooBig(uint32 have, uint32 want); + error MsgDataTooBig(uint256 have, uint32 max); + error ProvingKeyAlreadyRegistered(bytes32 keyHash); + error NoSuchProvingKey(bytes32 keyHash); + error InvalidLinkWeiPrice(int256 linkWei); + error LinkDiscountTooHigh(uint32 flatFeeLinkDiscountPPM, uint32 flatFeeNativePPM); + error InvalidPremiumPercentage(uint8 premiumPercentage, uint8 max); + error NoCorrespondingRequest(); + error IncorrectCommitment(); + error BlockhashNotInStore(uint256 blockNum); + error PaymentTooLarge(); + error InvalidExtraArgsTag(); + error GasPriceExceeded(uint256 gasPrice, uint256 maxGas); + + struct ProvingKey { + bool exists; // proving key exists + uint64 maxGas; // gas lane max gas price for fulfilling requests + } + + mapping(bytes32 => ProvingKey) /* keyHash */ /* provingKey */ public s_provingKeys; + bytes32[] public s_provingKeyHashes; + mapping(uint256 => bytes32) /* requestID */ /* commitment */ public s_requestCommitments; + event ProvingKeyRegistered(bytes32 keyHash, uint64 maxGas); + event ProvingKeyDeregistered(bytes32 keyHash, uint64 maxGas); + + event RandomWordsRequested( + bytes32 indexed keyHash, + uint256 requestId, + uint256 preSeed, + uint256 indexed subId, + uint16 minimumRequestConfirmations, + uint32 callbackGasLimit, + uint32 numWords, + bytes extraArgs, + address indexed sender + ); + + event RandomWordsFulfilled( + uint256 indexed requestId, + uint256 outputSeed, + uint256 indexed subId, + uint96 payment, + bool nativePayment, + bool success, + bool onlyPremium + ); + + int256 public s_fallbackWeiPerUnitLink; + + event ConfigSet( + uint16 minimumRequestConfirmations, + uint32 maxGasLimit, + uint32 stalenessSeconds, + uint32 gasAfterPaymentCalculation, + int256 fallbackWeiPerUnitLink, + uint32 fulfillmentFlatFeeNativePPM, + uint32 fulfillmentFlatFeeLinkDiscountPPM, + uint8 nativePremiumPercentage, + uint8 linkPremiumPercentage + ); + + event FallbackWeiPerUnitLinkUsed(uint256 requestId, int256 fallbackWeiPerUnitLink); + + constructor(address blockhashStore) SubscriptionAPI() { + BLOCKHASH_STORE = BlockhashStoreInterface(blockhashStore); + } + + /** + * @notice Registers a proving key to. + * @param publicProvingKey key that oracle can use to submit vrf fulfillments + */ + function registerProvingKey(uint256[2] calldata publicProvingKey, uint64 maxGas) external onlyOwner { + bytes32 kh = hashOfKey(publicProvingKey); + if (s_provingKeys[kh].exists) { + revert ProvingKeyAlreadyRegistered(kh); + } + s_provingKeys[kh] = ProvingKey({exists: true, maxGas: maxGas}); + s_provingKeyHashes.push(kh); + emit ProvingKeyRegistered(kh, maxGas); + } + + /** + * @notice Deregisters a proving key. + * @param publicProvingKey key that oracle can use to submit vrf fulfillments + */ + function deregisterProvingKey(uint256[2] calldata publicProvingKey) external onlyOwner { + bytes32 kh = hashOfKey(publicProvingKey); + ProvingKey memory key = s_provingKeys[kh]; + if (!key.exists) { + revert NoSuchProvingKey(kh); + } + delete s_provingKeys[kh]; + uint256 s_provingKeyHashesLength = s_provingKeyHashes.length; + for (uint256 i = 0; i < s_provingKeyHashesLength; ++i) { + if (s_provingKeyHashes[i] == kh) { + // Copy last element and overwrite kh to be deleted with it + s_provingKeyHashes[i] = s_provingKeyHashes[s_provingKeyHashesLength - 1]; + s_provingKeyHashes.pop(); + break; + } + } + emit ProvingKeyDeregistered(kh, key.maxGas); + } + + /** + * @notice Returns the proving key hash key associated with this public key + * @param publicKey the key to return the hash of + */ + function hashOfKey(uint256[2] memory publicKey) public pure returns (bytes32) { + return keccak256(abi.encode(publicKey)); + } + + /** + * @notice Sets the configuration of the vrfv2 coordinator + * @param minimumRequestConfirmations global min for request confirmations + * @param maxGasLimit global max for request gas limit + * @param stalenessSeconds if the native/link feed is more stale then this, use the fallback price + * @param gasAfterPaymentCalculation gas used in doing accounting after completing the gas measurement + * @param fallbackWeiPerUnitLink fallback native/link price in the case of a stale feed + * @param fulfillmentFlatFeeNativePPM flat fee in native for native payment + * @param fulfillmentFlatFeeLinkDiscountPPM flat fee discount for link payment in native + * @param nativePremiumPercentage native premium percentage + * @param linkPremiumPercentage link premium percentage + */ + function setConfig( + uint16 minimumRequestConfirmations, + uint32 maxGasLimit, + uint32 stalenessSeconds, + uint32 gasAfterPaymentCalculation, + int256 fallbackWeiPerUnitLink, + uint32 fulfillmentFlatFeeNativePPM, + uint32 fulfillmentFlatFeeLinkDiscountPPM, + uint8 nativePremiumPercentage, + uint8 linkPremiumPercentage + ) external onlyOwner { + if (minimumRequestConfirmations > MAX_REQUEST_CONFIRMATIONS) { + revert InvalidRequestConfirmations( + minimumRequestConfirmations, + minimumRequestConfirmations, + MAX_REQUEST_CONFIRMATIONS + ); + } + if (fallbackWeiPerUnitLink <= 0) { + revert InvalidLinkWeiPrice(fallbackWeiPerUnitLink); + } + if (fulfillmentFlatFeeLinkDiscountPPM > fulfillmentFlatFeeNativePPM) { + revert LinkDiscountTooHigh(fulfillmentFlatFeeLinkDiscountPPM, fulfillmentFlatFeeNativePPM); + } + if (nativePremiumPercentage > PREMIUM_PERCENTAGE_MAX) { + revert InvalidPremiumPercentage(nativePremiumPercentage, PREMIUM_PERCENTAGE_MAX); + } + if (linkPremiumPercentage > PREMIUM_PERCENTAGE_MAX) { + revert InvalidPremiumPercentage(linkPremiumPercentage, PREMIUM_PERCENTAGE_MAX); + } + s_config = Config({ + minimumRequestConfirmations: minimumRequestConfirmations, + maxGasLimit: maxGasLimit, + stalenessSeconds: stalenessSeconds, + gasAfterPaymentCalculation: gasAfterPaymentCalculation, + reentrancyLock: false, + fulfillmentFlatFeeNativePPM: fulfillmentFlatFeeNativePPM, + fulfillmentFlatFeeLinkDiscountPPM: fulfillmentFlatFeeLinkDiscountPPM, + nativePremiumPercentage: nativePremiumPercentage, + linkPremiumPercentage: linkPremiumPercentage + }); + s_fallbackWeiPerUnitLink = fallbackWeiPerUnitLink; + emit ConfigSet( + minimumRequestConfirmations, + maxGasLimit, + stalenessSeconds, + gasAfterPaymentCalculation, + fallbackWeiPerUnitLink, + fulfillmentFlatFeeNativePPM, + fulfillmentFlatFeeLinkDiscountPPM, + nativePremiumPercentage, + linkPremiumPercentage + ); + } + + /// @dev Convert the extra args bytes into a struct + /// @param extraArgs The extra args bytes + /// @return The extra args struct + function _fromBytes(bytes calldata extraArgs) internal pure returns (VRFV2PlusClient.ExtraArgsV1 memory) { + if (extraArgs.length == 0) { + return VRFV2PlusClient.ExtraArgsV1({nativePayment: false}); + } + if (bytes4(extraArgs) != VRFV2PlusClient.EXTRA_ARGS_V1_TAG) revert InvalidExtraArgsTag(); + return abi.decode(extraArgs[4:], (VRFV2PlusClient.ExtraArgsV1)); + } + + /** + * @notice Request a set of random words. + * @param req - a struct containing following fiels for randomness request: + * keyHash - Corresponds to a particular oracle job which uses + * that key for generating the VRF proof. Different keyHash's have different gas price + * ceilings, so you can select a specific one to bound your maximum per request cost. + * subId - The ID of the VRF subscription. Must be funded + * with the minimum subscription balance required for the selected keyHash. + * requestConfirmations - How many blocks you'd like the + * oracle to wait before responding to the request. See SECURITY CONSIDERATIONS + * for why you may want to request more. The acceptable range is + * [minimumRequestBlockConfirmations, 200]. + * callbackGasLimit - How much gas you'd like to receive in your + * fulfillRandomWords callback. Note that gasleft() inside fulfillRandomWords + * may be slightly less than this amount because of gas used calling the function + * (argument decoding etc.), so you may need to request slightly more than you expect + * to have inside fulfillRandomWords. The acceptable range is + * [0, maxGasLimit] + * numWords - The number of uint256 random values you'd like to receive + * in your fulfillRandomWords callback. Note these numbers are expanded in a + * secure way by the VRFCoordinator from a single random value supplied by the oracle. + * extraArgs - Encoded extra arguments that has a boolean flag for whether payment + * should be made in native or LINK. Payment in LINK is only available if the LINK token is available to this contract. + * @return requestId - A unique identifier of the request. Can be used to match + * a request to a response in fulfillRandomWords. + */ + function requestRandomWords( + VRFV2PlusClient.RandomWordsRequest calldata req + ) external override nonReentrant returns (uint256 requestId) { + // Input validation using the subscription storage. + uint256 subId = req.subId; + if (s_subscriptionConfigs[subId].owner == address(0)) { + revert InvalidSubscription(); + } + // Its important to ensure that the consumer is in fact who they say they + // are, otherwise they could use someone else's subscription balance. + mapping(uint256 => ConsumerConfig) storage consumerConfigs = s_consumers[msg.sender]; + ConsumerConfig memory consumerConfig = consumerConfigs[subId]; + if (!consumerConfig.active) { + revert InvalidConsumer(subId, msg.sender); + } + // Input validation using the config storage word. + if ( + req.requestConfirmations < s_config.minimumRequestConfirmations || + req.requestConfirmations > MAX_REQUEST_CONFIRMATIONS + ) { + revert InvalidRequestConfirmations( + req.requestConfirmations, + s_config.minimumRequestConfirmations, + MAX_REQUEST_CONFIRMATIONS + ); + } + // No lower bound on the requested gas limit. A user could request 0 + // and they would simply be billed for the proof verification and wouldn't be + // able to do anything with the random value. + if (req.callbackGasLimit > s_config.maxGasLimit) { + revert GasLimitTooBig(req.callbackGasLimit, s_config.maxGasLimit); + } + if (req.numWords > MAX_NUM_WORDS) { + revert NumWordsTooBig(req.numWords, MAX_NUM_WORDS); + } + + // Note we do not check whether the keyHash is valid to save gas. + // The consequence for users is that they can send requests + // for invalid keyHashes which will simply not be fulfilled. + ++consumerConfig.nonce; + ++consumerConfig.pendingReqCount; + uint256 preSeed; + (requestId, preSeed) = _computeRequestId(req.keyHash, msg.sender, subId, consumerConfig.nonce); + + bytes memory extraArgsBytes = VRFV2PlusClient._argsToBytes(_fromBytes(req.extraArgs)); + s_requestCommitments[requestId] = keccak256( + abi.encode( + requestId, + ChainSpecificUtil._getBlockNumber(), + subId, + req.callbackGasLimit, + req.numWords, + msg.sender, + extraArgsBytes + ) + ); + emit RandomWordsRequested( + req.keyHash, + requestId, + preSeed, + subId, + req.requestConfirmations, + req.callbackGasLimit, + req.numWords, + extraArgsBytes, + msg.sender + ); + consumerConfigs[subId] = consumerConfig; + + return requestId; + } + + function _computeRequestId( + bytes32 keyHash, + address sender, + uint256 subId, + uint64 nonce + ) internal pure returns (uint256, uint256) { + uint256 preSeed = uint256(keccak256(abi.encode(keyHash, sender, subId, nonce))); + return (uint256(keccak256(abi.encode(keyHash, preSeed))), preSeed); + } + + /** + * @dev calls target address with exactly gasAmount gas and data as calldata + * or reverts if at least gasAmount gas is not available. + */ + function _callWithExactGas(uint256 gasAmount, address target, bytes memory data) private returns (bool success) { + assembly { + let g := gas() + // Compute g -= GAS_FOR_CALL_EXACT_CHECK and check for underflow + // The gas actually passed to the callee is min(gasAmount, 63//64*gas available). + // We want to ensure that we revert if gasAmount > 63//64*gas available + // as we do not want to provide them with less, however that check itself costs + // gas. GAS_FOR_CALL_EXACT_CHECK ensures we have at least enough gas to be able + // to revert if gasAmount > 63//64*gas available. + if lt(g, GAS_FOR_CALL_EXACT_CHECK) { + revert(0, 0) + } + g := sub(g, GAS_FOR_CALL_EXACT_CHECK) + // if g - g//64 <= gasAmount, revert + // (we subtract g//64 because of EIP-150) + if iszero(gt(sub(g, div(g, 64)), gasAmount)) { + revert(0, 0) + } + // solidity calls check that a contract actually exists at the destination, so we do the same + if iszero(extcodesize(target)) { + revert(0, 0) + } + // call and return whether we succeeded. ignore return data + // call(gas,addr,value,argsOffset,argsLength,retOffset,retLength) + success := call(gasAmount, target, 0, add(data, 0x20), mload(data), 0, 0) + } + return success; + } + + struct Output { + ProvingKey provingKey; + uint256 requestId; + uint256 randomness; + } + + function _getRandomnessFromProof( + Proof memory proof, + VRFTypes.RequestCommitmentV2Plus memory rc + ) internal view returns (Output memory) { + bytes32 keyHash = hashOfKey(proof.pk); + ProvingKey memory key = s_provingKeys[keyHash]; + // Only registered proving keys are permitted. + if (!key.exists) { + revert NoSuchProvingKey(keyHash); + } + uint256 requestId = uint256(keccak256(abi.encode(keyHash, proof.seed))); + bytes32 commitment = s_requestCommitments[requestId]; + if (commitment == 0) { + revert NoCorrespondingRequest(); + } + if ( + commitment != + keccak256(abi.encode(requestId, rc.blockNum, rc.subId, rc.callbackGasLimit, rc.numWords, rc.sender, rc.extraArgs)) + ) { + revert IncorrectCommitment(); + } + + bytes32 blockHash = ChainSpecificUtil._getBlockhash(rc.blockNum); + if (blockHash == bytes32(0)) { + blockHash = BLOCKHASH_STORE.getBlockhash(rc.blockNum); + if (blockHash == bytes32(0)) { + revert BlockhashNotInStore(rc.blockNum); + } + } + + // The seed actually used by the VRF machinery, mixing in the blockhash + uint256 actualSeed = uint256(keccak256(abi.encodePacked(proof.seed, blockHash))); + uint256 randomness = VRFOld._randomValueFromVRFProof(proof, actualSeed); // Reverts on failure + return Output(key, requestId, randomness); + } + + function _getValidatedGasPrice(bool onlyPremium, uint64 gasLaneMaxGas) internal view returns (uint256 gasPrice) { + if (tx.gasprice > gasLaneMaxGas) { + if (onlyPremium) { + // if only the premium amount needs to be billed, then the premium is capped by the gas lane max + return uint256(gasLaneMaxGas); + } else { + // Ensure gas price does not exceed the gas lane max gas price + revert GasPriceExceeded(tx.gasprice, gasLaneMaxGas); + } + } + return tx.gasprice; + } + + function _deliverRandomness( + uint256 requestId, + VRFTypes.RequestCommitmentV2Plus memory rc, + uint256[] memory randomWords + ) internal returns (bool success) { + VRFConsumerBaseV2Plus v; + bytes memory resp = abi.encodeWithSelector(v.rawFulfillRandomWords.selector, requestId, randomWords); + // Call with explicitly the amount of callback gas requested + // Important to not let them exhaust the gas budget and avoid oracle payment. + // Do not allow any non-view/non-pure coordinator functions to be called + // during the consumers callback code via reentrancyLock. + // Note that _callWithExactGas will revert if we do not have sufficient gas + // to give the callee their requested amount. + s_config.reentrancyLock = true; + success = _callWithExactGas(rc.callbackGasLimit, rc.sender, resp); + s_config.reentrancyLock = false; + return success; + } + + /* + * @notice Fulfill a randomness request. + * @param proof contains the proof and randomness + * @param rc request commitment pre-image, committed to at request time + * @param onlyPremium only charge premium + * @return payment amount billed to the subscription + * @dev simulated offchain to determine if sufficient balance is present to fulfill the request + */ + function fulfillRandomWords( + Proof memory proof, + VRFTypes.RequestCommitmentV2Plus memory rc, + bool onlyPremium + ) external nonReentrant returns (uint96 payment) { + uint256 startGas = gasleft(); + // fulfillRandomWords msg.data has 772 bytes and with an additional + // buffer of 32 bytes, we get 804 bytes. + /* Data size split: + * fulfillRandomWords function signature - 4 bytes + * proof - 416 bytes + * pk - 64 bytes + * gamma - 64 bytes + * c - 32 bytes + * s - 32 bytes + * seed - 32 bytes + * uWitness - 32 bytes + * cGammaWitness - 64 bytes + * sHashWitness - 64 bytes + * zInv - 32 bytes + * requestCommitment - 320 bytes + * blockNum - 32 bytes + * subId - 32 bytes + * callbackGasLimit - 32 bytes + * numWords - 32 bytes + * sender - 32 bytes + * extraArgs - 128 bytes + * onlyPremium - 32 bytes + */ + if (msg.data.length > 804) { + revert MsgDataTooBig(msg.data.length, 804); + } + Output memory output = _getRandomnessFromProof(proof, rc); + uint256 gasPrice = _getValidatedGasPrice(onlyPremium, output.provingKey.maxGas); + + uint256[] memory randomWords; + uint256 randomness = output.randomness; + // stack too deep error + { + uint256 numWords = rc.numWords; + randomWords = new uint256[](numWords); + for (uint256 i = 0; i < numWords; ++i) { + randomWords[i] = uint256(keccak256(abi.encode(randomness, i))); + } + } + + delete s_requestCommitments[output.requestId]; + bool success = _deliverRandomness(output.requestId, rc, randomWords); + + // Increment the req count for the subscription. + ++s_subscriptions[rc.subId].reqCount; + // Decrement the pending req count for the consumer. + --s_consumers[rc.sender][rc.subId].pendingReqCount; + + bool nativePayment = uint8(rc.extraArgs[rc.extraArgs.length - 1]) == 1; + + // stack too deep error + { + // We want to charge users exactly for how much gas they use in their callback with + // an additional premium. If onlyPremium is true, only premium is charged without + // the gas cost. The gasAfterPaymentCalculation is meant to cover these additional + // operations where we decrement the subscription balance and increment the + // withdrawable balance. + bool isFeedStale; + (payment, isFeedStale) = _calculatePaymentAmount(startGas, gasPrice, nativePayment, onlyPremium); + if (isFeedStale) { + emit FallbackWeiPerUnitLinkUsed(output.requestId, s_fallbackWeiPerUnitLink); + } + } + + _chargePayment(payment, nativePayment, rc.subId); + + // Include payment in the event for tracking costs. + emit RandomWordsFulfilled(output.requestId, randomness, rc.subId, payment, nativePayment, success, onlyPremium); + + return payment; + } + + function _chargePayment(uint96 payment, bool nativePayment, uint256 subId) internal { + Subscription storage subcription = s_subscriptions[subId]; + if (nativePayment) { + uint96 prevBal = subcription.nativeBalance; + if (prevBal < payment) { + revert InsufficientBalance(); + } + subcription.nativeBalance = prevBal - payment; + s_withdrawableNative += payment; + } else { + uint96 prevBal = subcription.balance; + if (prevBal < payment) { + revert InsufficientBalance(); + } + subcription.balance = prevBal - payment; + s_withdrawableTokens += payment; + } + } + + function _calculatePaymentAmount( + uint256 startGas, + uint256 weiPerUnitGas, + bool nativePayment, + bool onlyPremium + ) internal view returns (uint96, bool) { + if (nativePayment) { + return (_calculatePaymentAmountNative(startGas, weiPerUnitGas, onlyPremium), false); + } + return _calculatePaymentAmountLink(startGas, weiPerUnitGas, onlyPremium); + } + + function _calculatePaymentAmountNative( + uint256 startGas, + uint256 weiPerUnitGas, + bool onlyPremium + ) internal view returns (uint96) { + // Will return non-zero on chains that have this enabled + uint256 l1CostWei = ChainSpecificUtil._getCurrentTxL1GasFees(msg.data); + // calculate the payment without the premium + uint256 baseFeeWei = weiPerUnitGas * (s_config.gasAfterPaymentCalculation + startGas - gasleft()); + // calculate flat fee in native + uint256 flatFeeWei = 1e12 * uint256(s_config.fulfillmentFlatFeeNativePPM); + if (onlyPremium) { + return uint96((((l1CostWei + baseFeeWei) * (s_config.nativePremiumPercentage)) / 100) + flatFeeWei); + } else { + return uint96((((l1CostWei + baseFeeWei) * (100 + s_config.nativePremiumPercentage)) / 100) + flatFeeWei); + } + } + + // Get the amount of gas used for fulfillment + function _calculatePaymentAmountLink( + uint256 startGas, + uint256 weiPerUnitGas, + bool onlyPremium + ) internal view returns (uint96, bool) { + (int256 weiPerUnitLink, bool isFeedStale) = _getFeedData(); + if (weiPerUnitLink <= 0) { + revert InvalidLinkWeiPrice(weiPerUnitLink); + } + // Will return non-zero on chains that have this enabled + uint256 l1CostWei = ChainSpecificUtil._getCurrentTxL1GasFees(msg.data); + // (1e18 juels/link) ((wei/gas * gas) + l1wei) / (wei/link) = juels + uint256 paymentNoFee = (1e18 * + (weiPerUnitGas * (s_config.gasAfterPaymentCalculation + startGas - gasleft()) + l1CostWei)) / + uint256(weiPerUnitLink); + // calculate the flat fee in wei + uint256 flatFeeWei = 1e12 * + uint256(s_config.fulfillmentFlatFeeNativePPM - s_config.fulfillmentFlatFeeLinkDiscountPPM); + uint256 flatFeeJuels = (1e18 * flatFeeWei) / uint256(weiPerUnitLink); + uint256 payment; + if (onlyPremium) { + payment = ((paymentNoFee * (s_config.linkPremiumPercentage)) / 100 + flatFeeJuels); + } else { + payment = ((paymentNoFee * (100 + s_config.linkPremiumPercentage)) / 100 + flatFeeJuels); + } + if (payment > 1e27) { + revert PaymentTooLarge(); // Payment + fee cannot be more than all of the link in existence. + } + return (uint96(payment), isFeedStale); + } + + function _getFeedData() private view returns (int256 weiPerUnitLink, bool isFeedStale) { + uint32 stalenessSeconds = s_config.stalenessSeconds; + uint256 timestamp; + (, weiPerUnitLink, , timestamp, ) = LINK_NATIVE_FEED.latestRoundData(); + // solhint-disable-next-line not-rely-on-time + isFeedStale = stalenessSeconds > 0 && stalenessSeconds < block.timestamp - timestamp; + if (isFeedStale) { + weiPerUnitLink = s_fallbackWeiPerUnitLink; + } + return (weiPerUnitLink, isFeedStale); + } + + /** + * @inheritdoc IVRFSubscriptionV2Plus + */ + function pendingRequestExists(uint256 subId) public view override returns (bool) { + address[] storage consumers = s_subscriptionConfigs[subId].consumers; + uint256 consumersLength = consumers.length; + if (consumersLength == 0) { + return false; + } + for (uint256 i = 0; i < consumersLength; ++i) { + if (s_consumers[consumers[i]][subId].pendingReqCount > 0) { + return true; + } + } + return false; + } + + /** + * @inheritdoc IVRFSubscriptionV2Plus + */ + function removeConsumer(uint256 subId, address consumer) external override onlySubOwner(subId) nonReentrant { + if (pendingRequestExists(subId)) { + revert PendingRequestExists(); + } + if (!s_consumers[consumer][subId].active) { + revert InvalidConsumer(subId, consumer); + } + // Note bounded by MAX_CONSUMERS + address[] memory consumers = s_subscriptionConfigs[subId].consumers; + uint256 lastConsumerIndex = consumers.length - 1; + for (uint256 i = 0; i < consumers.length; ++i) { + if (consumers[i] == consumer) { + address last = consumers[lastConsumerIndex]; + // Storage write to preserve last element + s_subscriptionConfigs[subId].consumers[i] = last; + // Storage remove last element + s_subscriptionConfigs[subId].consumers.pop(); + break; + } + } + s_consumers[consumer][subId].active = false; + emit SubscriptionConsumerRemoved(subId, consumer); + } + + /** + * @inheritdoc IVRFSubscriptionV2Plus + */ + function cancelSubscription(uint256 subId, address to) external override onlySubOwner(subId) nonReentrant { + if (pendingRequestExists(subId)) { + revert PendingRequestExists(); + } + _cancelSubscriptionHelper(subId, to); + } + + /*************************************************************************** + * Section: Migration + ***************************************************************************/ + + address[] internal s_migrationTargets; + + /// @dev Emitted when new coordinator is registered as migratable target + event CoordinatorRegistered(address coordinatorAddress); + + /// @dev Emitted when new coordinator is deregistered + event CoordinatorDeregistered(address coordinatorAddress); + + /// @notice emitted when migration to new coordinator completes successfully + /// @param newCoordinator coordinator address after migration + /// @param subId subscription ID + event MigrationCompleted(address newCoordinator, uint256 subId); + + /// @notice emitted when migrate() is called and given coordinator is not registered as migratable target + error CoordinatorNotRegistered(address coordinatorAddress); + + /// @notice emitted when migrate() is called and given coordinator is registered as migratable target + error CoordinatorAlreadyRegistered(address coordinatorAddress); + + /// @dev encapsulates data to be migrated from current coordinator + // solhint-disable-next-line gas-struct-packing + struct V1MigrationData { + uint8 fromVersion; + uint256 subId; + address subOwner; + address[] consumers; + uint96 linkBalance; + uint96 nativeBalance; + } + + function _isTargetRegistered(address target) internal view returns (bool) { + uint256 migrationTargetsLength = s_migrationTargets.length; + for (uint256 i = 0; i < migrationTargetsLength; ++i) { + if (s_migrationTargets[i] == target) { + return true; + } + } + return false; + } + + function registerMigratableCoordinator(address target) external onlyOwner { + if (_isTargetRegistered(target)) { + revert CoordinatorAlreadyRegistered(target); + } + s_migrationTargets.push(target); + emit CoordinatorRegistered(target); + } + + function deregisterMigratableCoordinator(address target) external onlyOwner { + uint256 nTargets = s_migrationTargets.length; + for (uint256 i = 0; i < nTargets; ++i) { + if (s_migrationTargets[i] == target) { + s_migrationTargets[i] = s_migrationTargets[nTargets - 1]; + s_migrationTargets.pop(); + emit CoordinatorDeregistered(target); + return; + } + } + revert CoordinatorNotRegistered(target); + } + + function migrate(uint256 subId, address newCoordinator) external nonReentrant { + if (!_isTargetRegistered(newCoordinator)) { + revert CoordinatorNotRegistered(newCoordinator); + } + (uint96 balance, uint96 nativeBalance, , address subOwner, address[] memory consumers) = getSubscription(subId); + // solhint-disable-next-line gas-custom-errors + require(subOwner == msg.sender, "Not subscription owner"); + // solhint-disable-next-line gas-custom-errors + require(!pendingRequestExists(subId), "Pending request exists"); + + V1MigrationData memory migrationData = V1MigrationData({ + fromVersion: 1, + subId: subId, + subOwner: subOwner, + consumers: consumers, + linkBalance: balance, + nativeBalance: nativeBalance + }); + bytes memory encodedData = abi.encode(migrationData); + _deleteSubscription(subId); + IVRFCoordinatorV2PlusMigration(newCoordinator).onMigration{value: nativeBalance}(encodedData); + + // Only transfer LINK if the token is active and there is a balance. + if (address(LINK) != address(0) && balance != 0) { + // solhint-disable-next-line gas-custom-errors + require(LINK.transfer(address(newCoordinator), balance), "insufficient funds"); + } + + // despite the fact that we follow best practices this is still probably safest + // to prevent any re-entrancy possibilities. + s_config.reentrancyLock = true; + for (uint256 i = 0; i < consumers.length; ++i) { + IVRFMigratableConsumerV2Plus(consumers[i]).setCoordinator(newCoordinator); + } + s_config.reentrancyLock = false; + + emit MigrationCompleted(newCoordinator, subId); + } +} diff --git a/contracts/src/v0.8/vrf/dev/testhelpers/VRFOld.sol b/contracts/src/v0.8/vrf/dev/testhelpers/VRFOld.sol new file mode 100644 index 0000000000..137235fd0a --- /dev/null +++ b/contracts/src/v0.8/vrf/dev/testhelpers/VRFOld.sol @@ -0,0 +1,588 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/** **************************************************************************** + * @notice Verification of verifiable-random-function (VRF) proofs, following + * @notice https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.3 + * @notice See https://eprint.iacr.org/2017/099.pdf for security proofs. + + * @dev Bibliographic references: + + * @dev Goldberg, et al., "Verifiable Random Functions (VRFs)", Internet Draft + * @dev draft-irtf-cfrg-vrf-05, IETF, Aug 11 2019, + * @dev https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05 + + * @dev Papadopoulos, et al., "Making NSEC5 Practical for DNSSEC", Cryptology + * @dev ePrint Archive, Report 2017/099, https://eprint.iacr.org/2017/099.pdf + * **************************************************************************** + * @dev USAGE + + * @dev The main entry point is _randomValueFromVRFProof. See its docstring. + * **************************************************************************** + * @dev PURPOSE + + * @dev Reggie the Random Oracle (not his real job) wants to provide randomness + * @dev to Vera the verifier in such a way that Vera can be sure he's not + * @dev making his output up to suit himself. Reggie provides Vera a public key + * @dev to which he knows the secret key. Each time Vera provides a seed to + * @dev Reggie, he gives back a value which is computed completely + * @dev deterministically from the seed and the secret key. + + * @dev Reggie provides a proof by which Vera can verify that the output was + * @dev correctly computed once Reggie tells it to her, but without that proof, + * @dev the output is computationally indistinguishable to her from a uniform + * @dev random sample from the output space. + + * @dev The purpose of this contract is to perform that verification. + * **************************************************************************** + * @dev DESIGN NOTES + + * @dev The VRF algorithm verified here satisfies the full uniqueness, full + * @dev collision resistance, and full pseudo-randomness security properties. + * @dev See "SECURITY PROPERTIES" below, and + * @dev https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-3 + + * @dev An elliptic curve point is generally represented in the solidity code + * @dev as a uint256[2], corresponding to its affine coordinates in + * @dev GF(FIELD_SIZE). + + * @dev For the sake of efficiency, this implementation deviates from the spec + * @dev in some minor ways: + + * @dev - Keccak hash rather than the SHA256 hash recommended in + * @dev https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.5 + * @dev Keccak costs much less gas on the EVM, and provides similar security. + + * @dev - Secp256k1 curve instead of the P-256 or ED25519 curves recommended in + * @dev https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.5 + * @dev For curve-point multiplication, it's much cheaper to abuse ECRECOVER + + * @dev - _hashToCurve recursively hashes until it finds a curve x-ordinate. On + * @dev the EVM, this is slightly more efficient than the recommendation in + * @dev https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.4.1.1 + * @dev step 5, to concatenate with a nonce then hash, and rehash with the + * @dev nonce updated until a valid x-ordinate is found. + + * @dev - _hashToCurve does not include a cipher version string or the byte 0x1 + * @dev in the hash message, as recommended in step 5.B of the draft + * @dev standard. They are unnecessary here because no variation in the + * @dev cipher suite is allowed. + + * @dev - Similarly, the hash input in _scalarFromCurvePoints does not include a + * @dev commitment to the cipher suite, either, which differs from step 2 of + * @dev https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.4.3 + * @dev . Also, the hash input is the concatenation of the uncompressed + * @dev points, not the compressed points as recommended in step 3. + + * @dev - In the calculation of the challenge value "c", the "u" value (i.e. + * @dev the value computed by Reggie as the nonce times the secp256k1 + * @dev generator point, see steps 5 and 7 of + * @dev https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.3 + * @dev ) is replaced by its ethereum address, i.e. the lower 160 bits of the + * @dev keccak hash of the original u. This is because we only verify the + * @dev calculation of u up to its address, by abusing ECRECOVER. + * **************************************************************************** + * @dev SECURITY PROPERTIES + + * @dev Here are the security properties for this VRF: + + * @dev Full uniqueness: For any seed and valid VRF public key, there is + * @dev exactly one VRF output which can be proved to come from that seed, in + * @dev the sense that the proof will pass _verifyVRFProof. + + * @dev Full collision resistance: It's cryptographically infeasible to find + * @dev two seeds with same VRF output from a fixed, valid VRF key + + * @dev Full pseudorandomness: Absent the proofs that the VRF outputs are + * @dev derived from a given seed, the outputs are computationally + * @dev indistinguishable from randomness. + + * @dev https://eprint.iacr.org/2017/099.pdf, Appendix B contains the proofs + * @dev for these properties. + + * @dev For secp256k1, the key validation described in section + * @dev https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.6 + * @dev is unnecessary, because secp256k1 has cofactor 1, and the + * @dev representation of the public key used here (affine x- and y-ordinates + * @dev of the secp256k1 point on the standard y^2=x^3+7 curve) cannot refer to + * @dev the point at infinity. + * **************************************************************************** + * @dev OTHER SECURITY CONSIDERATIONS + * + * @dev The seed input to the VRF could in principle force an arbitrary amount + * @dev of work in _hashToCurve, by requiring extra rounds of hashing and + * @dev checking whether that's yielded the x ordinate of a secp256k1 point. + * @dev However, under the Random Oracle Model the probability of choosing a + * @dev point which forces n extra rounds in _hashToCurve is 2⁻ⁿ. The base cost + * @dev for calling _hashToCurve is about 25,000 gas, and each round of checking + * @dev for a valid x ordinate costs about 15,555 gas, so to find a seed for + * @dev which _hashToCurve would cost more than 2,017,000 gas, one would have to + * @dev try, in expectation, about 2¹²⁸ seeds, which is infeasible for any + * @dev foreseeable computational resources. (25,000 + 128 * 15,555 < 2,017,000.) + + * @dev Since the gas block limit for the Ethereum main net is 10,000,000 gas, + * @dev this means it is infeasible for an adversary to prevent correct + * @dev operation of this contract by choosing an adverse seed. + + * @dev (See TestMeasureHashToCurveGasCost for verification of the gas cost for + * @dev _hashToCurve.) + + * @dev It may be possible to make a secure constant-time _hashToCurve function. + * @dev See notes in _hashToCurve docstring. +*/ +contract VRFOld { + // See https://www.secg.org/sec2-v2.pdf, section 2.4.1, for these constants. + // Number of points in Secp256k1 + uint256 private constant GROUP_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141; + // Prime characteristic of the galois field over which Secp256k1 is defined + uint256 private constant FIELD_SIZE = + // solium-disable-next-line indentation + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F; + uint256 private constant WORD_LENGTH_BYTES = 0x20; + + // (base^exponent) % FIELD_SIZE + // Cribbed from https://medium.com/@rbkhmrcr/precompiles-solidity-e5d29bd428c4 + function _bigModExp(uint256 base, uint256 exponent) internal view returns (uint256 exponentiation) { + uint256 callResult; + uint256[6] memory bigModExpContractInputs; + bigModExpContractInputs[0] = WORD_LENGTH_BYTES; // Length of base + bigModExpContractInputs[1] = WORD_LENGTH_BYTES; // Length of exponent + bigModExpContractInputs[2] = WORD_LENGTH_BYTES; // Length of modulus + bigModExpContractInputs[3] = base; + bigModExpContractInputs[4] = exponent; + bigModExpContractInputs[5] = FIELD_SIZE; + uint256[1] memory output; + assembly { + callResult := staticcall( + not(0), // Gas cost: no limit + 0x05, // Bigmodexp contract address + bigModExpContractInputs, + 0xc0, // Length of input segment: 6*0x20-bytes + output, + 0x20 // Length of output segment + ) + } + if (callResult == 0) { + // solhint-disable-next-line gas-custom-errors + revert("bigModExp failure!"); + } + return output[0]; + } + + // Let q=FIELD_SIZE. q % 4 = 3, ∴ x≡r^2 mod q ⇒ x^SQRT_POWER≡±r mod q. See + // https://en.wikipedia.org/wiki/Modular_square_root#Prime_or_prime_power_modulus + uint256 private constant SQRT_POWER = (FIELD_SIZE + 1) >> 2; + + // Computes a s.t. a^2 = x in the field. Assumes a exists + function _squareRoot(uint256 x) internal view returns (uint256) { + return _bigModExp(x, SQRT_POWER); + } + + // The value of y^2 given that (x,y) is on secp256k1. + function _ySquared(uint256 x) internal pure returns (uint256) { + // Curve is y^2=x^3+7. See section 2.4.1 of https://www.secg.org/sec2-v2.pdf + uint256 xCubed = mulmod(x, mulmod(x, x, FIELD_SIZE), FIELD_SIZE); + return addmod(xCubed, 7, FIELD_SIZE); + } + + // True iff p is on secp256k1 + function _isOnCurve(uint256[2] memory p) internal pure returns (bool) { + // Section 2.3.6. in https://www.secg.org/sec1-v2.pdf + // requires each ordinate to be in [0, ..., FIELD_SIZE-1] + // solhint-disable-next-line gas-custom-errors + require(p[0] < FIELD_SIZE, "invalid x-ordinate"); + // solhint-disable-next-line gas-custom-errors + require(p[1] < FIELD_SIZE, "invalid y-ordinate"); + return _ySquared(p[0]) == mulmod(p[1], p[1], FIELD_SIZE); + } + + // Hash x uniformly into {0, ..., FIELD_SIZE-1}. + function _fieldHash(bytes memory b) internal pure returns (uint256 x_) { + x_ = uint256(keccak256(b)); + // Rejecting if x >= FIELD_SIZE corresponds to step 2.1 in section 2.3.4 of + // http://www.secg.org/sec1-v2.pdf , which is part of the definition of + // string_to_point in the IETF draft + while (x_ >= FIELD_SIZE) { + x_ = uint256(keccak256(abi.encodePacked(x_))); + } + return x_; + } + + // Hash b to a random point which hopefully lies on secp256k1. The y ordinate + // is always even, due to + // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.4.1.1 + // step 5.C, which references arbitrary_string_to_point, defined in + // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.5 as + // returning the point with given x ordinate, and even y ordinate. + function _newCandidateSecp256k1Point(bytes memory b) internal view returns (uint256[2] memory p) { + unchecked { + p[0] = _fieldHash(b); + p[1] = _squareRoot(_ySquared(p[0])); + if (p[1] % 2 == 1) { + // Note that 0 <= p[1] < FIELD_SIZE + // so this cannot wrap, we use unchecked to save gas. + p[1] = FIELD_SIZE - p[1]; + } + } + return p; + } + + // Domain-separation tag for initial hash in _hashToCurve. Corresponds to + // vrf.go/hashToCurveHashPrefix + uint256 internal constant HASH_TO_CURVE_HASH_PREFIX = 1; + + // Cryptographic hash function onto the curve. + // + // Corresponds to algorithm in section 5.4.1.1 of the draft standard. (But see + // DESIGN NOTES above for slight differences.) + // + // TODO(alx): Implement a bounded-computation hash-to-curve, as described in + // "Construction of Rational Points on Elliptic Curves over Finite Fields" + // http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.831.5299&rep=rep1&type=pdf + // and suggested by + // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-01#section-5.2.2 + // (Though we can't used exactly that because secp256k1's j-invariant is 0.) + // + // This would greatly simplify the analysis in "OTHER SECURITY CONSIDERATIONS" + // https://www.pivotaltracker.com/story/show/171120900 + function _hashToCurve(uint256[2] memory pk, uint256 input) internal view returns (uint256[2] memory rv) { + rv = _newCandidateSecp256k1Point(abi.encodePacked(HASH_TO_CURVE_HASH_PREFIX, pk, input)); + while (!_isOnCurve(rv)) { + rv = _newCandidateSecp256k1Point(abi.encodePacked(rv[0])); + } + return rv; + } + + /** ********************************************************************* + * @notice Check that product==scalar*multiplicand + * + * @dev Based on Vitalik Buterin's idea in ethresear.ch post cited below. + * + * @param multiplicand: secp256k1 point + * @param scalar: non-zero GF(GROUP_ORDER) scalar + * @param product: secp256k1 expected to be multiplier * multiplicand + * @return verifies true iff product==scalar*multiplicand, with cryptographically high probability + */ + function _ecmulVerify( + uint256[2] memory multiplicand, + uint256 scalar, + uint256[2] memory product + ) internal pure returns (bool verifies) { + // solhint-disable-next-line gas-custom-errors + require(scalar != 0, "zero scalar"); // Rules out an ecrecover failure case + uint256 x = multiplicand[0]; // x ordinate of multiplicand + uint8 v = multiplicand[1] % 2 == 0 ? 27 : 28; // parity of y ordinate + // https://ethresear.ch/t/you-can-kinda-abuse-ecrecover-to-do-ecmul-in-secp256k1-today/2384/9 + // Point corresponding to address ecrecover(0, v, x, s=scalar*x) is + // (x⁻¹ mod GROUP_ORDER) * (scalar * x * multiplicand - 0 * g), i.e. + // scalar*multiplicand. See https://crypto.stackexchange.com/a/18106 + bytes32 scalarTimesX = bytes32(mulmod(scalar, x, GROUP_ORDER)); + address actual = ecrecover(bytes32(0), v, bytes32(x), scalarTimesX); + // Explicit conversion to address takes bottom 160 bits + address expected = address(uint160(uint256(keccak256(abi.encodePacked(product))))); + return (actual == expected); + } + + // Returns x1/z1-x2/z2=(x1z2-x2z1)/(z1z2) in projective coordinates on P¹(𝔽ₙ) + function _projectiveSub( + uint256 x1, + uint256 z1, + uint256 x2, + uint256 z2 + ) internal pure returns (uint256 x3, uint256 z3) { + unchecked { + uint256 num1 = mulmod(z2, x1, FIELD_SIZE); + // Note this cannot wrap since x2 is a point in [0, FIELD_SIZE-1] + // we use unchecked to save gas. + uint256 num2 = mulmod(FIELD_SIZE - x2, z1, FIELD_SIZE); + (x3, z3) = (addmod(num1, num2, FIELD_SIZE), mulmod(z1, z2, FIELD_SIZE)); + } + return (x3, z3); + } + + // Returns x1/z1*x2/z2=(x1x2)/(z1z2), in projective coordinates on P¹(𝔽ₙ) + function _projectiveMul( + uint256 x1, + uint256 z1, + uint256 x2, + uint256 z2 + ) internal pure returns (uint256 x3, uint256 z3) { + (x3, z3) = (mulmod(x1, x2, FIELD_SIZE), mulmod(z1, z2, FIELD_SIZE)); + return (x3, z3); + } + + /** ************************************************************************** + @notice Computes elliptic-curve sum, in projective co-ordinates + + @dev Using projective coordinates avoids costly divisions + + @dev To use this with p and q in affine coordinates, call + @dev _projectiveECAdd(px, py, qx, qy). This will return + @dev the addition of (px, py, 1) and (qx, qy, 1), in the + @dev secp256k1 group. + + @dev This can be used to calculate the z which is the inverse to zInv + @dev in isValidVRFOutput. But consider using a faster + @dev re-implementation such as ProjectiveECAdd in the golang vrf package. + + @dev This function assumes [px,py,1],[qx,qy,1] are valid projective + coordinates of secp256k1 points. That is safe in this contract, + because this method is only used by _linearCombination, which checks + points are on the curve via ecrecover. + ************************************************************************** + @param px The first affine coordinate of the first summand + @param py The second affine coordinate of the first summand + @param qx The first affine coordinate of the second summand + @param qy The second affine coordinate of the second summand + + (px,py) and (qx,qy) must be distinct, valid secp256k1 points. + ************************************************************************** + Return values are projective coordinates of [px,py,1]+[qx,qy,1] as points + on secp256k1, in P²(𝔽ₙ) + @return sx + @return sy + @return sz + */ + function _projectiveECAdd( + uint256 px, + uint256 py, + uint256 qx, + uint256 qy + ) internal pure returns (uint256 sx, uint256 sy, uint256 sz) { + unchecked { + // See "Group law for E/K : y^2 = x^3 + ax + b", in section 3.1.2, p. 80, + // "Guide to Elliptic Curve Cryptography" by Hankerson, Menezes and Vanstone + // We take the equations there for (sx,sy), and homogenize them to + // projective coordinates. That way, no inverses are required, here, and we + // only need the one inverse in _affineECAdd. + + // We only need the "point addition" equations from Hankerson et al. Can + // skip the "point doubling" equations because p1 == p2 is cryptographically + // impossible, and required not to be the case in _linearCombination. + + // Add extra "projective coordinate" to the two points + (uint256 z1, uint256 z2) = (1, 1); + + // (lx, lz) = (qy-py)/(qx-px), i.e., gradient of secant line. + // Cannot wrap since px and py are in [0, FIELD_SIZE-1] + uint256 lx = addmod(qy, FIELD_SIZE - py, FIELD_SIZE); + uint256 lz = addmod(qx, FIELD_SIZE - px, FIELD_SIZE); + + uint256 dx; // Accumulates denominator from sx calculation + // sx=((qy-py)/(qx-px))^2-px-qx + (sx, dx) = _projectiveMul(lx, lz, lx, lz); // ((qy-py)/(qx-px))^2 + (sx, dx) = _projectiveSub(sx, dx, px, z1); // ((qy-py)/(qx-px))^2-px + (sx, dx) = _projectiveSub(sx, dx, qx, z2); // ((qy-py)/(qx-px))^2-px-qx + + uint256 dy; // Accumulates denominator from sy calculation + // sy=((qy-py)/(qx-px))(px-sx)-py + (sy, dy) = _projectiveSub(px, z1, sx, dx); // px-sx + (sy, dy) = _projectiveMul(sy, dy, lx, lz); // ((qy-py)/(qx-px))(px-sx) + (sy, dy) = _projectiveSub(sy, dy, py, z1); // ((qy-py)/(qx-px))(px-sx)-py + + if (dx != dy) { + // Cross-multiply to put everything over a common denominator + sx = mulmod(sx, dy, FIELD_SIZE); + sy = mulmod(sy, dx, FIELD_SIZE); + sz = mulmod(dx, dy, FIELD_SIZE); + } else { + // Already over a common denominator, use that for z ordinate + sz = dx; + } + } + return (sx, sy, sz); + } + + // p1+p2, as affine points on secp256k1. + // + // invZ must be the inverse of the z returned by _projectiveECAdd(p1, p2). + // It is computed off-chain to save gas. + // + // p1 and p2 must be distinct, because _projectiveECAdd doesn't handle + // point doubling. + function _affineECAdd( + uint256[2] memory p1, + uint256[2] memory p2, + uint256 invZ + ) internal pure returns (uint256[2] memory) { + uint256 x; + uint256 y; + uint256 z; + (x, y, z) = _projectiveECAdd(p1[0], p1[1], p2[0], p2[1]); + // solhint-disable-next-line gas-custom-errors + require(mulmod(z, invZ, FIELD_SIZE) == 1, "invZ must be inverse of z"); + // Clear the z ordinate of the projective representation by dividing through + // by it, to obtain the affine representation + return [mulmod(x, invZ, FIELD_SIZE), mulmod(y, invZ, FIELD_SIZE)]; + } + + // True iff address(c*p+s*g) == lcWitness, where g is generator. (With + // cryptographically high probability.) + function _verifyLinearCombinationWithGenerator( + uint256 c, + uint256[2] memory p, + uint256 s, + address lcWitness + ) internal pure returns (bool) { + // Rule out ecrecover failure modes which return address 0. + unchecked { + // solhint-disable-next-line gas-custom-errors + require(lcWitness != address(0), "bad witness"); + uint8 v = (p[1] % 2 == 0) ? 27 : 28; // parity of y-ordinate of p + // Note this cannot wrap (X - Y % X), but we use unchecked to save + // gas. + bytes32 pseudoHash = bytes32(GROUP_ORDER - mulmod(p[0], s, GROUP_ORDER)); // -s*p[0] + bytes32 pseudoSignature = bytes32(mulmod(c, p[0], GROUP_ORDER)); // c*p[0] + // https://ethresear.ch/t/you-can-kinda-abuse-ecrecover-to-do-ecmul-in-secp256k1-today/2384/9 + // The point corresponding to the address returned by + // ecrecover(-s*p[0],v,p[0],c*p[0]) is + // (p[0]⁻¹ mod GROUP_ORDER)*(c*p[0]-(-s)*p[0]*g)=c*p+s*g. + // See https://crypto.stackexchange.com/a/18106 + // https://bitcoin.stackexchange.com/questions/38351/ecdsa-v-r-s-what-is-v + address computed = ecrecover(pseudoHash, v, bytes32(p[0]), pseudoSignature); + return computed == lcWitness; + } + } + + // c*p1 + s*p2. Requires cp1Witness=c*p1 and sp2Witness=s*p2. Also + // requires cp1Witness != sp2Witness (which is fine for this application, + // since it is cryptographically impossible for them to be equal. In the + // (cryptographically impossible) case that a prover accidentally derives + // a proof with equal c*p1 and s*p2, they should retry with a different + // proof nonce.) Assumes that all points are on secp256k1 + // (which is checked in _verifyVRFProof below.) + function _linearCombination( + uint256 c, + uint256[2] memory p1, + uint256[2] memory cp1Witness, + uint256 s, + uint256[2] memory p2, + uint256[2] memory sp2Witness, + uint256 zInv + ) internal pure returns (uint256[2] memory) { + unchecked { + // Note we are relying on the wrap around here + // solhint-disable-next-line gas-custom-errors + require((cp1Witness[0] % FIELD_SIZE) != (sp2Witness[0] % FIELD_SIZE), "points in sum must be distinct"); + // solhint-disable-next-line gas-custom-errors + require(_ecmulVerify(p1, c, cp1Witness), "First mul check failed"); + // solhint-disable-next-line gas-custom-errors + require(_ecmulVerify(p2, s, sp2Witness), "Second mul check failed"); + return _affineECAdd(cp1Witness, sp2Witness, zInv); + } + } + + // Domain-separation tag for the hash taken in _scalarFromCurvePoints. + // Corresponds to scalarFromCurveHashPrefix in vrf.go + uint256 internal constant SCALAR_FROM_CURVE_POINTS_HASH_PREFIX = 2; + + // Pseudo-random number from inputs. Matches vrf.go/_scalarFromCurvePoints, and + // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.4.3 + // The draft calls (in step 7, via the definition of string_to_int, in + // https://datatracker.ietf.org/doc/html/rfc8017#section-4.2 ) for taking the + // first hash without checking that it corresponds to a number less than the + // group order, which will lead to a slight bias in the sample. + // + // TODO(alx): We could save a bit of gas by following the standard here and + // using the compressed representation of the points, if we collated the y + // parities into a single bytes32. + // https://www.pivotaltracker.com/story/show/171120588 + function _scalarFromCurvePoints( + uint256[2] memory hash, + uint256[2] memory pk, + uint256[2] memory gamma, + address uWitness, + uint256[2] memory v + ) internal pure returns (uint256 s) { + return uint256(keccak256(abi.encodePacked(SCALAR_FROM_CURVE_POINTS_HASH_PREFIX, hash, pk, gamma, v, uWitness))); + } + + // True if (gamma, c, s) is a correctly constructed randomness proof from pk + // and seed. zInv must be the inverse of the third ordinate from + // _projectiveECAdd applied to cGammaWitness and sHashWitness. Corresponds to + // section 5.3 of the IETF draft. + // + // TODO(alx): Since I'm only using pk in the ecrecover call, I could only pass + // the x ordinate, and the parity of the y ordinate in the top bit of uWitness + // (which I could make a uint256 without using any extra space.) Would save + // about 2000 gas. https://www.pivotaltracker.com/story/show/170828567 + function _verifyVRFProof( + uint256[2] memory pk, + uint256[2] memory gamma, + uint256 c, + uint256 s, + uint256 seed, + address uWitness, + uint256[2] memory cGammaWitness, + uint256[2] memory sHashWitness, + uint256 zInv + ) internal view { + unchecked { + // solhint-disable-next-line gas-custom-errors + require(_isOnCurve(pk), "public key is not on curve"); + // solhint-disable-next-line gas-custom-errors + require(_isOnCurve(gamma), "gamma is not on curve"); + // solhint-disable-next-line gas-custom-errors + require(_isOnCurve(cGammaWitness), "cGammaWitness is not on curve"); + // solhint-disable-next-line gas-custom-errors + require(_isOnCurve(sHashWitness), "sHashWitness is not on curve"); + // Step 5. of IETF draft section 5.3 (pk corresponds to 5.3's Y, and here + // we use the address of u instead of u itself. Also, here we add the + // terms instead of taking the difference, and in the proof construction in + // vrf.GenerateProof, we correspondingly take the difference instead of + // taking the sum as they do in step 7 of section 5.1.) + // solhint-disable-next-line gas-custom-errors + require(_verifyLinearCombinationWithGenerator(c, pk, s, uWitness), "addr(c*pk+s*g)!=_uWitness"); + // Step 4. of IETF draft section 5.3 (pk corresponds to Y, seed to alpha_string) + uint256[2] memory hash = _hashToCurve(pk, seed); + // Step 6. of IETF draft section 5.3, but see note for step 5 about +/- terms + uint256[2] memory v = _linearCombination(c, gamma, cGammaWitness, s, hash, sHashWitness, zInv); + // Steps 7. and 8. of IETF draft section 5.3 + uint256 derivedC = _scalarFromCurvePoints(hash, pk, gamma, uWitness, v); + // solhint-disable-next-line gas-custom-errors + require(c == derivedC, "invalid proof"); + } + } + + // Domain-separation tag for the hash used as the final VRF output. + // Corresponds to vrfRandomOutputHashPrefix in vrf.go + uint256 internal constant VRF_RANDOM_OUTPUT_HASH_PREFIX = 3; + + struct Proof { + uint256[2] pk; + uint256[2] gamma; + uint256 c; + uint256 s; + uint256 seed; + address uWitness; + uint256[2] cGammaWitness; + uint256[2] sHashWitness; + uint256 zInv; + } + + /* *************************************************************************** + * @notice Returns proof's output, if proof is valid. Otherwise reverts + + * @param proof vrf proof components + * @param seed seed used to generate the vrf output + * + * Throws if proof is invalid, otherwise: + * @return output i.e., the random output implied by the proof + * *************************************************************************** + */ + function _randomValueFromVRFProof(Proof memory proof, uint256 seed) internal view returns (uint256 output) { + _verifyVRFProof( + proof.pk, + proof.gamma, + proof.c, + proof.s, + seed, + proof.uWitness, + proof.cGammaWitness, + proof.sHashWitness, + proof.zInv + ); + output = uint256(keccak256(abi.encode(VRF_RANDOM_OUTPUT_HASH_PREFIX, proof.gamma))); + return output; + } +} diff --git a/contracts/test/v0.8/automation/AutomationRegistry2_2.test.ts b/contracts/test/v0.8/automation/AutomationRegistry2_2.test.ts index 62733bcad4..6b220f2f7c 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 9a57226969..f3c2d9bb98 100644 --- a/contracts/test/v0.8/automation/AutomationRegistry2_3.test.ts +++ b/contracts/test/v0.8/automation/AutomationRegistry2_3.test.ts @@ -23,9 +23,8 @@ 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' import { AutomationCompatibleUtils } from '../../../typechain/AutomationCompatibleUtils' import { MockArbGasInfo } from '../../../typechain/MockArbGasInfo' @@ -35,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 { @@ -153,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 @@ -175,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 @@ -443,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', ) @@ -3107,44 +3106,6 @@ describe('AutomationRegistry2_3', () => { await getTransmitTx(registry, keeper1, [upkeepId2]) }) - it('reverts if called on a non existing ID', async () => { - await evmRevertCustomError( - registry - .connect(admin) - .withdrawFunds(upkeepId.add(1), await payee1.getAddress()), - registry, - 'OnlyCallableByAdmin', - ) - }) - - it('reverts if called by anyone but the admin', async () => { - await evmRevertCustomError( - registry - .connect(owner) - .withdrawFunds(upkeepId, await payee1.getAddress()), - registry, - 'OnlyCallableByAdmin', - ) - }) - - it('reverts if called on an uncanceled upkeep', async () => { - await evmRevertCustomError( - registry - .connect(admin) - .withdrawFunds(upkeepId, await payee1.getAddress()), - registry, - 'UpkeepNotCanceled', - ) - }) - - it('reverts if called with the 0 address', async () => { - await evmRevertCustomError( - registry.connect(admin).withdrawFunds(upkeepId, zeroAddress), - registry, - 'InvalidRecipient', - ) - }) - describe('after the registration is paused, then cancelled', () => { it('allows the admin to withdraw', async () => { const balance = await registry.getBalance(upkeepId) @@ -3514,46 +3475,6 @@ describe('AutomationRegistry2_3', () => { }) }) - describe('#getActiveUpkeepIDs', () => { - it('reverts if startIndex is out of bounds ', async () => { - await evmRevertCustomError( - registry.getActiveUpkeepIDs(numUpkeeps, 0), - registry, - 'IndexOutOfRange', - ) - await evmRevertCustomError( - registry.getActiveUpkeepIDs(numUpkeeps + 1, 0), - registry, - 'IndexOutOfRange', - ) - }) - - it('returns upkeep IDs bounded by maxCount', async () => { - let upkeepIds = await registry.getActiveUpkeepIDs(0, 1) - assert(upkeepIds.length == 1) - assert(upkeepIds[0].eq(upkeepId)) - upkeepIds = await registry.getActiveUpkeepIDs(1, 3) - assert(upkeepIds.length == 3) - expect(upkeepIds).to.deep.equal([ - afUpkeepId, - logUpkeepId, - streamsLookupUpkeepId, - ]) - }) - - it('returns as many ids as possible if maxCount > num available', async () => { - const upkeepIds = await registry.getActiveUpkeepIDs(1, numUpkeeps + 100) - assert(upkeepIds.length == numUpkeeps - 1) - }) - - it('returns all upkeep IDs if maxCount is 0', async () => { - let upkeepIds = await registry.getActiveUpkeepIDs(0, 0) - assert(upkeepIds.length == numUpkeeps) - upkeepIds = await registry.getActiveUpkeepIDs(2, 0) - assert(upkeepIds.length == numUpkeeps - 2) - }) - }) - describe('#getMaxPaymentForGas', () => { let maxl1CostWeiArbWithoutMultiplier: BigNumber let maxl1CostWeiOptWithoutMultiplier: BigNumber @@ -4225,1140 +4146,180 @@ describe('AutomationRegistry2_3', () => { }) }) - describe('#setPeerRegistryMigrationPermission() / #getPeerRegistryMigrationPermission()', () => { - const peer = randomAddress() - it('allows the owner to set the peer registries', async () => { - let permission = await registry.getPeerRegistryMigrationPermission(peer) - expect(permission).to.equal(0) - await registry.setPeerRegistryMigrationPermission(peer, 1) - permission = await registry.getPeerRegistryMigrationPermission(peer) - expect(permission).to.equal(1) - await registry.setPeerRegistryMigrationPermission(peer, 2) - permission = await registry.getPeerRegistryMigrationPermission(peer) - expect(permission).to.equal(2) - await registry.setPeerRegistryMigrationPermission(peer, 0) - permission = await registry.getPeerRegistryMigrationPermission(peer) - expect(permission).to.equal(0) - }) - it('reverts if passed an unsupported permission', async () => { - await expect( - registry.connect(admin).setPeerRegistryMigrationPermission(peer, 10), - ).to.be.reverted - }) - it('reverts if not called by the owner', async () => { - await expect( - registry.connect(admin).setPeerRegistryMigrationPermission(peer, 1), - ).to.be.revertedWith('Only callable by owner') - }) - }) - - describe('#pauseUpkeep', () => { - it('reverts if the registration does not exist', async () => { - await evmRevertCustomError( - registry.connect(keeper1).pauseUpkeep(upkeepId.add(1)), - registry, - 'OnlyCallableByAdmin', - ) - }) - - it('reverts if the upkeep is already canceled', async () => { - await registry.connect(admin).cancelUpkeep(upkeepId) - - await evmRevertCustomError( - registry.connect(admin).pauseUpkeep(upkeepId), - registry, - 'UpkeepCancelled', - ) - }) - - it('reverts if the upkeep is already paused', async () => { - await registry.connect(admin).pauseUpkeep(upkeepId) - - await evmRevertCustomError( - registry.connect(admin).pauseUpkeep(upkeepId), - registry, - 'OnlyUnpausedUpkeep', - ) - }) - - it('reverts if the caller is not the upkeep admin', async () => { - await evmRevertCustomError( - registry.connect(keeper1).pauseUpkeep(upkeepId), - registry, - 'OnlyCallableByAdmin', - ) - }) - - it('pauses the upkeep and emits an event', async () => { - const tx = await registry.connect(admin).pauseUpkeep(upkeepId) - await expect(tx).to.emit(registry, 'UpkeepPaused').withArgs(upkeepId) + describe('#cancelUpkeep', () => { + describe('when called by the owner', async () => { + it('immediately prevents upkeep', async () => { + await registry.connect(owner).cancelUpkeep(upkeepId) - const registration = await registry.getUpkeep(upkeepId) - assert.equal(registration.paused, true) + const tx = await getTransmitTx(registry, keeper1, [upkeepId]) + const receipt = await tx.wait() + const cancelledUpkeepReportLogs = + parseCancelledUpkeepReportLogs(receipt) + // exactly 1 CancelledUpkeepReport log should be emitted + assert.equal(cancelledUpkeepReportLogs.length, 1) + }) }) - }) - describe('#unpauseUpkeep', () => { - it('reverts if the registration does not exist', async () => { - await evmRevertCustomError( - registry.connect(keeper1).unpauseUpkeep(upkeepId.add(1)), - registry, - 'OnlyCallableByAdmin', - ) - }) + describe('when called by the admin', async () => { + it('immediately prevents upkeep', async () => { + await linkToken.connect(owner).approve(registry.address, toWei('100')) + await registry.connect(owner).addFunds(upkeepId, toWei('100')) + await registry.connect(admin).cancelUpkeep(upkeepId) - it('reverts if the upkeep is already canceled', async () => { - await registry.connect(owner).cancelUpkeep(upkeepId) + await getTransmitTx(registry, keeper1, [upkeepId]) - await evmRevertCustomError( - registry.connect(admin).unpauseUpkeep(upkeepId), - registry, - 'UpkeepCancelled', - ) - }) + for (let i = 0; i < cancellationDelay; i++) { + await ethers.provider.send('evm_mine', []) + } - it('marks the contract as paused', async () => { - assert.isFalse((await registry.getState()).state.paused) + const tx = await getTransmitTx(registry, keeper1, [upkeepId]) - await registry.connect(owner).pause() + const receipt = await tx.wait() + const cancelledUpkeepReportLogs = + parseCancelledUpkeepReportLogs(receipt) + // exactly 1 CancelledUpkeepReport log should be emitted + assert.equal(cancelledUpkeepReportLogs.length, 1) + }) - assert.isTrue((await registry.getState()).state.paused) - }) + 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('reverts if the upkeep is not paused', async () => { - await evmRevertCustomError( - registry.connect(admin).unpauseUpkeep(upkeepId), - registry, - 'OnlyPausedUpkeep', - ) - }) + it('deducts a cancellation fee from the upkeep and adds to reserve', async () => { + const newMinUpkeepSpend = toWei('10') + const financeAdminAddress = await financeAdmin.getAddress() - it('reverts if the caller is not the upkeep admin', async () => { - await registry.connect(admin).pauseUpkeep(upkeepId) + 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: chainModuleBase.address, + reorgProtectionEnabled: true, + financeAdmin: financeAdminAddress, + }, + offchainVersion, + offchainBytes, + [linkToken.address], + [ + { + gasFeePPB: paymentPremiumPPB, + flatFeeMilliCents, + priceFeed: linkUSDFeed.address, + fallbackPrice: fallbackLinkPrice, + minSpend: newMinUpkeepSpend, + decimals: 18, + }, + ], + ) - const registration = await registry.getUpkeep(upkeepId) + const payee1Before = await linkToken.balanceOf( + await payee1.getAddress(), + ) + const upkeepBefore = (await registry.getUpkeep(upkeepId)).balance + const ownerBefore = await registry.linkAvailableForPayment() - assert.equal(registration.paused, true) + const amountSpent = toWei('100').sub(upkeepBefore) + const cancellationFee = newMinUpkeepSpend.sub(amountSpent) - await evmRevertCustomError( - registry.connect(keeper1).unpauseUpkeep(upkeepId), - registry, - 'OnlyCallableByAdmin', - ) - }) + await registry.connect(admin).cancelUpkeep(upkeepId) - it('unpauses the upkeep and emits an event', async () => { - const originalCount = (await registry.getActiveUpkeepIDs(0, 0)).length + const payee1After = await linkToken.balanceOf( + await payee1.getAddress(), + ) + const upkeepAfter = (await registry.getUpkeep(upkeepId)).balance + const ownerAfter = await registry.linkAvailableForPayment() - await registry.connect(admin).pauseUpkeep(upkeepId) + // 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)) + }) - const tx = await registry.connect(admin).unpauseUpkeep(upkeepId) + 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 expect(tx).to.emit(registry, 'UpkeepUnpaused').withArgs(upkeepId) + 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: chainModuleBase.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 registration = await registry.getUpkeep(upkeepId) - assert.equal(registration.paused, false) + 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 - const upkeepIds = await registry.getActiveUpkeepIDs(0, 0) - assert.equal(upkeepIds.length, originalCount) - }) - }) + // 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)) + }) - describe('#setUpkeepCheckData', () => { - it('reverts if the registration does not exist', async () => { - await evmRevertCustomError( - registry - .connect(keeper1) - .setUpkeepCheckData(upkeepId.add(1), randomBytes), - registry, - 'OnlyCallableByAdmin', - ) - }) - - it('reverts if the caller is not upkeep admin', async () => { - await evmRevertCustomError( - registry.connect(keeper1).setUpkeepCheckData(upkeepId, randomBytes), - registry, - 'OnlyCallableByAdmin', - ) - }) - - it('reverts if the upkeep is cancelled', async () => { - await registry.connect(admin).cancelUpkeep(upkeepId) - - await evmRevertCustomError( - registry.connect(admin).setUpkeepCheckData(upkeepId, randomBytes), - registry, - 'UpkeepCancelled', - ) - }) - - it('is allowed to update on paused upkeep', async () => { - await registry.connect(admin).pauseUpkeep(upkeepId) - await registry.connect(admin).setUpkeepCheckData(upkeepId, randomBytes) - - const registration = await registry.getUpkeep(upkeepId) - assert.equal(randomBytes, registration.checkData) - }) - - it('reverts if new data exceeds limit', async () => { - let longBytes = '0x' - for (let i = 0; i < 10000; i++) { - longBytes += '1' - } - - await evmRevertCustomError( - registry.connect(admin).setUpkeepCheckData(upkeepId, longBytes), - registry, - 'CheckDataExceedsLimit', - ) - }) - - it('updates the upkeep check data and emits an event', async () => { - const tx = await registry - .connect(admin) - .setUpkeepCheckData(upkeepId, randomBytes) - await expect(tx) - .to.emit(registry, 'UpkeepCheckDataSet') - .withArgs(upkeepId, randomBytes) - - const registration = await registry.getUpkeep(upkeepId) - assert.equal(randomBytes, registration.checkData) - }) - }) - - describe('#setUpkeepGasLimit', () => { - const newGasLimit = BigNumber.from('300000') - - it('reverts if the registration does not exist', async () => { - await evmRevertCustomError( - registry.connect(admin).setUpkeepGasLimit(upkeepId.add(1), newGasLimit), - registry, - 'OnlyCallableByAdmin', - ) - }) - - it('reverts if the upkeep is canceled', async () => { - await registry.connect(admin).cancelUpkeep(upkeepId) - await evmRevertCustomError( - registry.connect(admin).setUpkeepGasLimit(upkeepId, newGasLimit), - registry, - 'UpkeepCancelled', - ) - }) - - it('reverts if called by anyone but the admin', async () => { - await evmRevertCustomError( - registry.connect(owner).setUpkeepGasLimit(upkeepId, newGasLimit), - registry, - 'OnlyCallableByAdmin', - ) - }) - - it('reverts if new gas limit is out of bounds', async () => { - await evmRevertCustomError( - registry - .connect(admin) - .setUpkeepGasLimit(upkeepId, BigNumber.from('100')), - registry, - 'GasLimitOutsideRange', - ) - await evmRevertCustomError( - registry - .connect(admin) - .setUpkeepGasLimit(upkeepId, BigNumber.from('6000000')), - registry, - 'GasLimitOutsideRange', - ) - }) - - it('updates the gas limit successfully', async () => { - const initialGasLimit = (await registry.getUpkeep(upkeepId)).performGas - assert.equal(initialGasLimit, performGas.toNumber()) - await registry.connect(admin).setUpkeepGasLimit(upkeepId, newGasLimit) - const updatedGasLimit = (await registry.getUpkeep(upkeepId)).performGas - assert.equal(updatedGasLimit, newGasLimit.toNumber()) - }) - - it('emits a log', async () => { - const tx = await registry - .connect(admin) - .setUpkeepGasLimit(upkeepId, newGasLimit) - await expect(tx) - .to.emit(registry, 'UpkeepGasLimitSet') - .withArgs(upkeepId, newGasLimit) - }) - }) - - describe('#setUpkeepOffchainConfig', () => { - const newConfig = '0xc0ffeec0ffee' - - it('reverts if the registration does not exist', async () => { - await evmRevertCustomError( - registry - .connect(admin) - .setUpkeepOffchainConfig(upkeepId.add(1), newConfig), - registry, - 'OnlyCallableByAdmin', - ) - }) - - it('reverts if the upkeep is canceled', async () => { - await registry.connect(admin).cancelUpkeep(upkeepId) - await evmRevertCustomError( - registry.connect(admin).setUpkeepOffchainConfig(upkeepId, newConfig), - registry, - 'UpkeepCancelled', - ) - }) - - it('reverts if called by anyone but the admin', async () => { - await evmRevertCustomError( - registry.connect(owner).setUpkeepOffchainConfig(upkeepId, newConfig), - registry, - 'OnlyCallableByAdmin', - ) - }) - - it('updates the config successfully', async () => { - const initialConfig = (await registry.getUpkeep(upkeepId)).offchainConfig - assert.equal(initialConfig, '0x') - await registry.connect(admin).setUpkeepOffchainConfig(upkeepId, newConfig) - const updatedConfig = (await registry.getUpkeep(upkeepId)).offchainConfig - assert.equal(newConfig, updatedConfig) - }) - - it('emits a log', async () => { - const tx = await registry - .connect(admin) - .setUpkeepOffchainConfig(upkeepId, newConfig) - await expect(tx) - .to.emit(registry, 'UpkeepOffchainConfigSet') - .withArgs(upkeepId, newConfig) - }) - }) - - describe('#setUpkeepTriggerConfig', () => { - const newConfig = '0xdeadbeef' - - it('reverts if the registration does not exist', async () => { - await evmRevertCustomError( - registry - .connect(admin) - .setUpkeepTriggerConfig(upkeepId.add(1), newConfig), - registry, - 'OnlyCallableByAdmin', - ) - }) - - it('reverts if the upkeep is canceled', async () => { - await registry.connect(admin).cancelUpkeep(upkeepId) - await evmRevertCustomError( - registry.connect(admin).setUpkeepTriggerConfig(upkeepId, newConfig), - registry, - 'UpkeepCancelled', - ) - }) - - it('reverts if called by anyone but the admin', async () => { - await evmRevertCustomError( - registry.connect(owner).setUpkeepTriggerConfig(upkeepId, newConfig), - registry, - 'OnlyCallableByAdmin', - ) - }) - - it('emits a log', async () => { - const tx = await registry - .connect(admin) - .setUpkeepTriggerConfig(upkeepId, newConfig) - await expect(tx) - .to.emit(registry, 'UpkeepTriggerConfigSet') - .withArgs(upkeepId, newConfig) - }) - }) - - describe('#transferUpkeepAdmin', () => { - it('reverts when called by anyone but the current upkeep admin', async () => { - await evmRevertCustomError( - registry - .connect(payee1) - .transferUpkeepAdmin(upkeepId, await payee2.getAddress()), - registry, - 'OnlyCallableByAdmin', - ) - }) - - it('reverts when transferring to self', async () => { - await evmRevertCustomError( - registry - .connect(admin) - .transferUpkeepAdmin(upkeepId, await admin.getAddress()), - registry, - 'ValueNotChanged', - ) - }) - - it('reverts when the upkeep is cancelled', async () => { - await registry.connect(admin).cancelUpkeep(upkeepId) - - await evmRevertCustomError( - registry - .connect(admin) - .transferUpkeepAdmin(upkeepId, await keeper1.getAddress()), - registry, - 'UpkeepCancelled', - ) - }) - - it('allows cancelling transfer by reverting to zero address', async () => { - await registry - .connect(admin) - .transferUpkeepAdmin(upkeepId, await payee1.getAddress()) - const tx = await registry - .connect(admin) - .transferUpkeepAdmin(upkeepId, ethers.constants.AddressZero) - - await expect(tx) - .to.emit(registry, 'UpkeepAdminTransferRequested') - .withArgs( - upkeepId, - await admin.getAddress(), - ethers.constants.AddressZero, - ) - }) - - it('does not change the upkeep admin', async () => { - await registry - .connect(admin) - .transferUpkeepAdmin(upkeepId, await payee1.getAddress()) - - const upkeep = await registry.getUpkeep(upkeepId) - assert.equal(await admin.getAddress(), upkeep.admin) - }) - - it('emits an event announcing the new upkeep admin', async () => { - const tx = await registry - .connect(admin) - .transferUpkeepAdmin(upkeepId, await payee1.getAddress()) - - await expect(tx) - .to.emit(registry, 'UpkeepAdminTransferRequested') - .withArgs(upkeepId, await admin.getAddress(), await payee1.getAddress()) - }) - - it('does not emit an event when called with the same proposed upkeep admin', async () => { - await registry - .connect(admin) - .transferUpkeepAdmin(upkeepId, await payee1.getAddress()) - - const tx = await registry - .connect(admin) - .transferUpkeepAdmin(upkeepId, await payee1.getAddress()) - const receipt = await tx.wait() - assert.equal(receipt.logs.length, 0) - }) - }) - - describe('#acceptUpkeepAdmin', () => { - beforeEach(async () => { - // Start admin transfer to payee1 - await registry - .connect(admin) - .transferUpkeepAdmin(upkeepId, await payee1.getAddress()) - }) - - it('reverts when not called by the proposed upkeep admin', async () => { - await evmRevertCustomError( - registry.connect(payee2).acceptUpkeepAdmin(upkeepId), - registry, - 'OnlyCallableByProposedAdmin', - ) - }) - - it('reverts when the upkeep is cancelled', async () => { - await registry.connect(admin).cancelUpkeep(upkeepId) - - await evmRevertCustomError( - registry.connect(payee1).acceptUpkeepAdmin(upkeepId), - registry, - 'UpkeepCancelled', - ) - }) - - it('does change the admin', async () => { - await registry.connect(payee1).acceptUpkeepAdmin(upkeepId) - - const upkeep = await registry.getUpkeep(upkeepId) - assert.equal(await payee1.getAddress(), upkeep.admin) - }) - - it('emits an event announcing the new upkeep admin', async () => { - const tx = await registry.connect(payee1).acceptUpkeepAdmin(upkeepId) - await expect(tx) - .to.emit(registry, 'UpkeepAdminTransferred') - .withArgs(upkeepId, await admin.getAddress(), await payee1.getAddress()) - }) - }) - - describe('#withdrawOwnerFunds', () => { - it('can only be called by finance admin', async () => { - await evmRevertCustomError( - registry.connect(keeper1).withdrawLink(zeroAddress, 1), - registry, - 'OnlyFinanceAdmin', - ) - }) - - itMaybe('withdraws the collected fees to owner', async () => { - await registry.connect(admin).addFunds(upkeepId, toWei('100')) - const financeAdminAddress = await financeAdmin.getAddress() - // Very high min spend, whole balance as cancellation fees - const newMinUpkeepSpend = toWei('1000') - 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: chainModuleBase.address, - reorgProtectionEnabled: true, - financeAdmin: financeAdminAddress, - }, - offchainVersion, - offchainBytes, - [linkToken.address], - [ - { - gasFeePPB: paymentPremiumPPB, - flatFeeMilliCents, - priceFeed: linkUSDFeed.address, - fallbackPrice: fallbackLinkPrice, - minSpend: newMinUpkeepSpend, - decimals: 18, - }, - ], - ) - const upkeepBalance = (await registry.getUpkeep(upkeepId)).balance - const ownerBefore = await linkToken.balanceOf(await owner.getAddress()) - - await registry.connect(owner).cancelUpkeep(upkeepId) - - // Transfered to owner balance on registry - let ownerRegistryBalance = await registry.linkAvailableForPayment() - assert.isTrue(ownerRegistryBalance.eq(upkeepBalance)) - - // Now withdraw - await registry - .connect(financeAdmin) - .withdrawLink(await owner.getAddress(), ownerRegistryBalance) - - ownerRegistryBalance = await registry.linkAvailableForPayment() - const ownerAfter = await linkToken.balanceOf(await owner.getAddress()) - - // Owner registry balance should be changed to 0 - assert.isTrue(ownerRegistryBalance.eq(BigNumber.from('0'))) - - // Owner should be credited with the balance - assert.isTrue(ownerBefore.add(upkeepBalance).eq(ownerAfter)) - }) - }) - - describe('#transferPayeeship', () => { - it('reverts when called by anyone but the current payee', async () => { - await evmRevertCustomError( - registry - .connect(payee2) - .transferPayeeship( - await keeper1.getAddress(), - await payee2.getAddress(), - ), - registry, - 'OnlyCallableByPayee', - ) - }) - - it('reverts when transferring to self', async () => { - await evmRevertCustomError( - registry - .connect(payee1) - .transferPayeeship( - await keeper1.getAddress(), - await payee1.getAddress(), - ), - registry, - 'ValueNotChanged', - ) - }) - - it('does not change the payee', async () => { - await registry - .connect(payee1) - .transferPayeeship( - await keeper1.getAddress(), - await payee2.getAddress(), - ) - - const info = await registry.getTransmitterInfo(await keeper1.getAddress()) - assert.equal(await payee1.getAddress(), info.payee) - }) - - it('emits an event announcing the new payee', async () => { - const tx = await registry - .connect(payee1) - .transferPayeeship( - await keeper1.getAddress(), - await payee2.getAddress(), - ) - await expect(tx) - .to.emit(registry, 'PayeeshipTransferRequested') - .withArgs( - await keeper1.getAddress(), - await payee1.getAddress(), - await payee2.getAddress(), - ) - }) - - it('does not emit an event when called with the same proposal', async () => { - await registry - .connect(payee1) - .transferPayeeship( - await keeper1.getAddress(), - await payee2.getAddress(), - ) - - const tx = await registry - .connect(payee1) - .transferPayeeship( - await keeper1.getAddress(), - await payee2.getAddress(), - ) - const receipt = await tx.wait() - assert.equal(receipt.logs.length, 0) - }) - }) - - describe('#acceptPayeeship', () => { - beforeEach(async () => { - await registry - .connect(payee1) - .transferPayeeship( - await keeper1.getAddress(), - await payee2.getAddress(), - ) - }) - - it('reverts when called by anyone but the proposed payee', async () => { - await evmRevertCustomError( - registry.connect(payee1).acceptPayeeship(await keeper1.getAddress()), - registry, - 'OnlyCallableByProposedPayee', - ) - }) - - it('emits an event announcing the new payee', async () => { - const tx = await registry - .connect(payee2) - .acceptPayeeship(await keeper1.getAddress()) - await expect(tx) - .to.emit(registry, 'PayeeshipTransferred') - .withArgs( - await keeper1.getAddress(), - await payee1.getAddress(), - await payee2.getAddress(), - ) - }) - - it('does change the payee', async () => { - await registry.connect(payee2).acceptPayeeship(await keeper1.getAddress()) - - const info = await registry.getTransmitterInfo(await keeper1.getAddress()) - assert.equal(await payee2.getAddress(), info.payee) - }) - }) - - describe('#pause', () => { - it('reverts if called by a non-owner', async () => { - await evmRevert( - registry.connect(keeper1).pause(), - 'Only callable by owner', - ) - }) - - it('marks the contract as paused', async () => { - assert.isFalse((await registry.getState()).state.paused) - - await registry.connect(owner).pause() - - assert.isTrue((await registry.getState()).state.paused) - }) - - it('Does not allow transmits when paused', async () => { - await registry.connect(owner).pause() - - await evmRevertCustomError( - getTransmitTx(registry, keeper1, [upkeepId]), - registry, - 'RegistryPaused', - ) - }) - - it('Does not allow creation of new upkeeps when paused', async () => { - await registry.connect(owner).pause() - - await evmRevertCustomError( - registry - .connect(owner) - .registerUpkeep( - mock.address, - performGas, - await admin.getAddress(), - Trigger.CONDITION, - linkToken.address, - '0x', - '0x', - '0x', - ), - registry, - 'RegistryPaused', - ) - }) - }) - - describe('#unpause', () => { - beforeEach(async () => { - await registry.connect(owner).pause() - }) - - it('reverts if called by a non-owner', async () => { - await evmRevert( - registry.connect(keeper1).unpause(), - 'Only callable by owner', - ) - }) - - it('marks the contract as not paused', async () => { - assert.isTrue((await registry.getState()).state.paused) - - await registry.connect(owner).unpause() - - assert.isFalse((await registry.getState()).state.paused) - }) - }) - - describe('#setPayees', () => { - const IGNORE_ADDRESS = '0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF' - - it('reverts when not called by the owner', async () => { - await evmRevert( - registry.connect(keeper1).setPayees(payees), - 'Only callable by owner', - ) - }) - - it('reverts with different numbers of payees than transmitters', async () => { - await evmRevertCustomError( - registry.connect(owner).setPayees([...payees, randomAddress()]), - registry, - 'ParameterLengthError', - ) - }) - - it('reverts if the payee is the zero address', async () => { - await blankRegistry.connect(owner).setConfigTypeSafe(...baseConfig) // used to test initial config - - await evmRevertCustomError( - blankRegistry // used to test initial config - .connect(owner) - .setPayees([ethers.constants.AddressZero, ...payees.slice(1)]), - registry, - 'InvalidPayee', - ) - }) - - itMaybe( - 'sets the payees when exisitng payees are zero address', - async () => { - //Initial payees should be zero address - await blankRegistry.connect(owner).setConfigTypeSafe(...baseConfig) // used to test initial config - - for (let i = 0; i < keeperAddresses.length; i++) { - const payee = ( - await blankRegistry.getTransmitterInfo(keeperAddresses[i]) - ).payee // used to test initial config - assert.equal(payee, zeroAddress) - } - - await blankRegistry.connect(owner).setPayees(payees) // used to test initial config - - for (let i = 0; i < keeperAddresses.length; i++) { - const payee = ( - await blankRegistry.getTransmitterInfo(keeperAddresses[i]) - ).payee - assert.equal(payee, payees[i]) - } - }, - ) - - it('does not change the payee if IGNORE_ADDRESS is used as payee', async () => { - const signers = Array.from({ length: 5 }, randomAddress) - const keepers = Array.from({ length: 5 }, randomAddress) - const payees = Array.from({ length: 5 }, randomAddress) - const newTransmitter = randomAddress() - const newPayee = randomAddress() - const ignoreAddresses = new Array(payees.length).fill(IGNORE_ADDRESS) - const newPayees = [...ignoreAddresses, newPayee] - // arbitrum registry - // configure registry with 5 keepers // optimism registry - await blankRegistry // used to test initial configurations - .connect(owner) - .setConfigTypeSafe( - signers, - keepers, - f, - config, - offchainVersion, - offchainBytes, - [], - [], - ) - // arbitrum registry - // set initial payees // optimism registry - await blankRegistry.connect(owner).setPayees(payees) // used to test initial configurations - // arbitrum registry - // add another keeper // optimism registry - await blankRegistry // used to test initial configurations - .connect(owner) - .setConfigTypeSafe( - [...signers, randomAddress()], - [...keepers, newTransmitter], - f, - config, - offchainVersion, - offchainBytes, - [], - [], - ) - // arbitrum registry - // update payee list // optimism registry // arbitrum registry - await blankRegistry.connect(owner).setPayees(newPayees) // used to test initial configurations // optimism registry - const ignored = await blankRegistry.getTransmitterInfo(newTransmitter) // used to test initial configurations - assert.equal(newPayee, ignored.payee) - assert.equal(ignored.active, true) - }) - - it('reverts if payee is non zero and owner tries to change payee', async () => { - const newPayees = [randomAddress(), ...payees.slice(1)] - - await evmRevertCustomError( - registry.connect(owner).setPayees(newPayees), - registry, - 'InvalidPayee', - ) - }) - - it('emits events for every payee added and removed', async () => { - const tx = await registry.connect(owner).setPayees(payees) - await expect(tx) - .to.emit(registry, 'PayeesUpdated') - .withArgs(keeperAddresses, payees) - }) - }) - - describe('#cancelUpkeep', () => { - it('reverts if the ID is not valid', async () => { - await evmRevertCustomError( - registry.connect(owner).cancelUpkeep(upkeepId.add(1)), - registry, - 'CannotCancel', - ) - }) - - it('reverts if called by a non-owner/non-admin', async () => { - await evmRevertCustomError( - registry.connect(keeper1).cancelUpkeep(upkeepId), - registry, - 'OnlyCallableByOwnerOrAdmin', - ) - }) - - describe('when called by the owner', async () => { - it('sets the registration to invalid immediately', async () => { - const tx = await registry.connect(owner).cancelUpkeep(upkeepId) - const receipt = await tx.wait() - const registration = await registry.getUpkeep(upkeepId) - assert.equal( - registration.maxValidBlocknumber.toNumber(), - receipt.blockNumber, - ) - }) - - it('emits an event', async () => { - const tx = await registry.connect(owner).cancelUpkeep(upkeepId) - const receipt = await tx.wait() - await expect(tx) - .to.emit(registry, 'UpkeepCanceled') - .withArgs(upkeepId, BigNumber.from(receipt.blockNumber)) - }) - - it('immediately prevents upkeep', async () => { - await registry.connect(owner).cancelUpkeep(upkeepId) - - const tx = await getTransmitTx(registry, keeper1, [upkeepId]) - 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 reverts if called multiple times', async () => { - await registry.connect(owner).cancelUpkeep(upkeepId) - await evmRevertCustomError( - registry.connect(owner).cancelUpkeep(upkeepId), - registry, - 'UpkeepCancelled', - ) - }) - - describe('when called by the owner when the admin has just canceled', () => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - // @ts-ignore - let oldExpiration: BigNumber - - beforeEach(async () => { - await registry.connect(admin).cancelUpkeep(upkeepId) - const registration = await registry.getUpkeep(upkeepId) - oldExpiration = registration.maxValidBlocknumber - }) - - it('reverts with proper error', async () => { - await evmRevertCustomError( - registry.connect(owner).cancelUpkeep(upkeepId), - registry, - 'UpkeepCancelled', - ) - }) - }) - }) - - describe('when called by the admin', async () => { - it('reverts if called again by the admin', async () => { - await registry.connect(admin).cancelUpkeep(upkeepId) - - await evmRevertCustomError( - registry.connect(admin).cancelUpkeep(upkeepId), - registry, - 'UpkeepCancelled', - ) - }) - - it('reverts if called by the owner after the timeout', async () => { - await registry.connect(admin).cancelUpkeep(upkeepId) - - for (let i = 0; i < cancellationDelay; i++) { - await ethers.provider.send('evm_mine', []) - } - - await evmRevertCustomError( - registry.connect(owner).cancelUpkeep(upkeepId), - registry, - 'UpkeepCancelled', - ) - }) - - it('sets the registration to invalid in 50 blocks', async () => { - const tx = await registry.connect(admin).cancelUpkeep(upkeepId) - const receipt = await tx.wait() - const registration = await registry.getUpkeep(upkeepId) - assert.equal( - registration.maxValidBlocknumber.toNumber(), - receipt.blockNumber + 50, - ) - }) - - it('emits an event', async () => { - const tx = await registry.connect(admin).cancelUpkeep(upkeepId) - const receipt = await tx.wait() - await expect(tx) - .to.emit(registry, 'UpkeepCanceled') - .withArgs( - upkeepId, - BigNumber.from(receipt.blockNumber + cancellationDelay), - ) - }) - - it('immediately prevents upkeep', async () => { - await linkToken.connect(owner).approve(registry.address, toWei('100')) - await registry.connect(owner).addFunds(upkeepId, toWei('100')) - await registry.connect(admin).cancelUpkeep(upkeepId) - - await getTransmitTx(registry, keeper1, [upkeepId]) - - for (let i = 0; i < cancellationDelay; i++) { - await ethers.provider.send('evm_mine', []) - } - - const tx = await getTransmitTx(registry, keeper1, [upkeepId]) - - const receipt = await tx.wait() - const cancelledUpkeepReportLogs = - parseCancelledUpkeepReportLogs(receipt) - // exactly 1 CancelledUpkeepReport log should be emitted - assert.equal(cancelledUpkeepReportLogs.length, 1) - }) - - 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: chainModuleBase.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: chainModuleBase.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() + 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, @@ -5595,62 +4556,6 @@ describe('AutomationRegistry2_3', () => { }) }) - describe('#setUpkeepPrivilegeConfig() / #getUpkeepPrivilegeConfig()', () => { - it('reverts when non manager tries to set privilege config', async () => { - await evmRevertCustomError( - registry.connect(payee3).setUpkeepPrivilegeConfig(upkeepId, '0x1234'), - registry, - 'OnlyCallableByUpkeepPrivilegeManager', - ) - }) - - it('returns empty bytes for upkeep privilege config before setting', async () => { - const cfg = await registry.getUpkeepPrivilegeConfig(upkeepId) - assert.equal(cfg, '0x') - }) - - it('allows upkeep manager to set privilege config', async () => { - const tx = await registry - .connect(personas.Norbert) - .setUpkeepPrivilegeConfig(upkeepId, '0x1234') - await expect(tx) - .to.emit(registry, 'UpkeepPrivilegeConfigSet') - .withArgs(upkeepId, '0x1234') - - const cfg = await registry.getUpkeepPrivilegeConfig(upkeepId) - assert.equal(cfg, '0x1234') - }) - }) - - describe('#setAdminPrivilegeConfig() / #getAdminPrivilegeConfig()', () => { - const admin = randomAddress() - - it('reverts when non manager tries to set privilege config', async () => { - await evmRevertCustomError( - registry.connect(payee3).setAdminPrivilegeConfig(admin, '0x1234'), - registry, - 'OnlyCallableByUpkeepPrivilegeManager', - ) - }) - - it('returns empty bytes for upkeep privilege config before setting', async () => { - const cfg = await registry.getAdminPrivilegeConfig(admin) - assert.equal(cfg, '0x') - }) - - it('allows upkeep manager to set privilege config', async () => { - const tx = await registry - .connect(personas.Norbert) - .setAdminPrivilegeConfig(admin, '0x1234') - await expect(tx) - .to.emit(registry, 'AdminPrivilegeConfigSet') - .withArgs(admin, '0x1234') - - const cfg = await registry.getAdminPrivilegeConfig(admin) - assert.equal(cfg, '0x1234') - }) - }) - describe('transmitterPremiumSplit [ @skip-coverage ]', () => { beforeEach(async () => { await linkToken.connect(owner).approve(registry.address, toWei('100')) diff --git a/contracts/test/v0.8/automation/IAutomationRegistryMaster2_2.test.ts b/contracts/test/v0.8/automation/IAutomationRegistryMaster2_2.test.ts index 0c1e34a116..11da7273ab 100644 --- a/contracts/test/v0.8/automation/IAutomationRegistryMaster2_2.test.ts +++ b/contracts/test/v0.8/automation/IAutomationRegistryMaster2_2.test.ts @@ -93,7 +93,8 @@ describe('IAutomationRegistryMaster2_2', () => { ) }) - it('satisfies the OCR2Abstract interface', async () => { + // temporarily disable this test due to this update: https://github.com/smartcontractkit/chainlink/pull/14369/files#diff-6e79d46ea0ef204dea679ffd2a9f4dfccd090d8f405ba2d9bffad527d7b862c6L44 + it.skip('satisfies the OCR2Abstract interface', async () => { assertSatisfiesInterface( IAutomationRegistryMasterFactory.abi, OCR2AbstractFactory.abi, diff --git a/contracts/test/v0.8/automation/IAutomationRegistryMaster2_3.test.ts b/contracts/test/v0.8/automation/IAutomationRegistryMaster2_3.test.ts index 8bc4f69567..44fa277b5e 100644 --- a/contracts/test/v0.8/automation/IAutomationRegistryMaster2_3.test.ts +++ b/contracts/test/v0.8/automation/IAutomationRegistryMaster2_3.test.ts @@ -87,7 +87,8 @@ describe('IAutomationRegistryMaster2_3', () => { ) }) - it('satisfies the OCR2Abstract interface', async () => { + // temporarily disable this test due to this update: https://github.com/smartcontractkit/chainlink/pull/14369/files#diff-6e79d46ea0ef204dea679ffd2a9f4dfccd090d8f405ba2d9bffad527d7b862c6L44 + it.skip('satisfies the OCR2Abstract interface', async () => { assertSatisfiesInterface( IAutomationRegistryMasterFactory.abi, OCR2AbstractFactory.abi, 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 0000000000..95210cf644 --- /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 5a95fb482c..99f2cef9b8 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' @@ -170,10 +171,10 @@ export const deployRegistry23 = async ( link: Parameters[0], linkUSD: Parameters[1], nativeUSD: Parameters[2], - fastgas: Parameters[2], + fastgas: Parameters[3], allowedReadOnlyAddress: Parameters< AutomationRegistryLogicC2_3Factory['deploy'] - >[3], + >[5], payoutMode: Parameters[6], wrappedNativeTokenAddress: Parameters< AutomationRegistryLogicC2_3Factory['deploy'] @@ -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/bridges/cache.go b/core/bridges/cache.go index 4b5a655244..e97874a35e 100644 --- a/core/bridges/cache.go +++ b/core/bridges/cache.go @@ -10,11 +10,9 @@ import ( "golang.org/x/exp/maps" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" - - "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/utils" ) const ( @@ -25,13 +23,11 @@ const ( type Cache struct { // dependencies and configurations ORM - lggr logger.Logger interval time.Duration // service state - services.StateMachine - wg sync.WaitGroup - chStop services.StopChan + services.Service + eng *services.Engine // data state bridgeTypesCache sync.Map @@ -43,17 +39,20 @@ var _ ORM = (*Cache)(nil) var _ services.Service = (*Cache)(nil) func NewCache(base ORM, lggr logger.Logger, upsertInterval time.Duration) *Cache { - return &Cache{ + c := &Cache{ ORM: base, - lggr: lggr.Named(CacheServiceName), interval: upsertInterval, - chStop: make(chan struct{}), bridgeLastValueCache: make(map[string]BridgeResponse), } + c.Service, c.eng = services.Config{ + Name: CacheServiceName, + Start: c.start, + }.NewServiceEngine(lggr) + return c } func (c *Cache) WithDataSource(ds sqlutil.DataSource) ORM { - return NewCache(NewORM(ds), c.lggr, c.interval) + return NewCache(NewORM(ds), c.eng, c.interval) } func (c *Cache) FindBridge(ctx context.Context, name BridgeName) (BridgeType, error) { @@ -190,51 +189,17 @@ func (c *Cache) UpsertBridgeResponse(ctx context.Context, dotId string, specId i return nil } -func (c *Cache) Start(_ context.Context) error { - return c.StartOnce(CacheServiceName, func() error { - c.wg.Add(1) - - go c.run() - - return nil - }) -} - -func (c *Cache) Close() error { - return c.StopOnce(CacheServiceName, func() error { - close(c.chStop) - c.wg.Wait() - - return nil - }) -} - -func (c *Cache) HealthReport() map[string]error { - return map[string]error{c.Name(): c.Healthy()} -} - -func (c *Cache) Name() string { - return c.lggr.Name() -} - -func (c *Cache) run() { - defer c.wg.Done() - - for { - timer := time.NewTimer(utils.WithJitter(c.interval)) +func (c *Cache) start(_ context.Context) error { + ticker := services.TickerConfig{ + Initial: c.interval, + JitterPct: services.DefaultJitter, + }.NewTicker(c.interval) + c.eng.GoTick(ticker, c.doBulkUpsert) - select { - case <-timer.C: - c.doBulkUpsert() - case <-c.chStop: - timer.Stop() - - return - } - } + return nil } -func (c *Cache) doBulkUpsert() { +func (c *Cache) doBulkUpsert(ctx context.Context) { c.mu.RLock() values := maps.Values(c.bridgeLastValueCache) c.mu.RUnlock() @@ -243,11 +208,8 @@ func (c *Cache) doBulkUpsert() { return } - ctx, cancel := c.chStop.NewCtx() - defer cancel() - if err := c.ORM.BulkUpsertBridgeResponse(ctx, values); err != nil { - c.lggr.Warnf("bulk upsert of bridge responses failed: %s", err.Error()) + c.eng.Warnf("bulk upsert of bridge responses failed: %s", err.Error()) } } diff --git a/core/capabilities/gateway_connector/service_wrapper.go b/core/capabilities/gateway_connector/service_wrapper.go new file mode 100644 index 0000000000..824c92b4f8 --- /dev/null +++ b/core/capabilities/gateway_connector/service_wrapper.go @@ -0,0 +1,114 @@ +package gatewayconnector + +import ( + "context" + "crypto/ecdsa" + "errors" + "math/big" + "slices" + + "github.com/ethereum/go-ethereum/common" + "github.com/jonboulle/clockwork" + + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink/v2/core/config" + "github.com/smartcontractkit/chainlink/v2/core/logger" + gwcommon "github.com/smartcontractkit/chainlink/v2/core/services/gateway/common" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/connector" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/network" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" +) + +type ServiceWrapper struct { + services.StateMachine + + config config.GatewayConnector + keystore keystore.Eth + connector connector.GatewayConnector + signerKey *ecdsa.PrivateKey + lggr logger.Logger + clock clockwork.Clock +} + +func translateConfigs(f config.GatewayConnector) connector.ConnectorConfig { + r := connector.ConnectorConfig{} + r.NodeAddress = f.NodeAddress() + r.DonId = f.DonID() + + if len(f.Gateways()) != 0 { + r.Gateways = make([]connector.ConnectorGatewayConfig, len(f.Gateways())) + for index, element := range f.Gateways() { + r.Gateways[index] = connector.ConnectorGatewayConfig{Id: element.ID(), URL: element.URL()} + } + } + + r.WsClientConfig = network.WebSocketClientConfig{HandshakeTimeoutMillis: f.WSHandshakeTimeoutMillis()} + r.AuthMinChallengeLen = f.AuthMinChallengeLen() + r.AuthTimestampToleranceSec = f.AuthTimestampToleranceSec() + return r +} + +// NOTE: this wrapper is needed to make sure that our services are started after Keystore. +func NewGatewayConnectorServiceWrapper(config config.GatewayConnector, keystore keystore.Eth, clock clockwork.Clock, lggr logger.Logger) *ServiceWrapper { + return &ServiceWrapper{ + config: config, + keystore: keystore, + clock: clock, + lggr: lggr, + } +} + +func (e *ServiceWrapper) Start(ctx context.Context) error { + return e.StartOnce("GatewayConnectorServiceWrapper", func() error { + conf := e.config + nodeAddress := conf.NodeAddress() + chainID, _ := new(big.Int).SetString(conf.ChainIDForNodeKey(), 0) + enabledKeys, err := e.keystore.EnabledKeysForChain(ctx, chainID) + if err != nil { + return err + } + if len(enabledKeys) == 0 { + return errors.New("no available keys found") + } + configuredNodeAddress := common.HexToAddress(nodeAddress) + idx := slices.IndexFunc(enabledKeys, func(key ethkey.KeyV2) bool { return key.Address == configuredNodeAddress }) + + if idx == -1 { + return errors.New("key for configured node address not found") + } + e.signerKey = enabledKeys[idx].ToEcdsaPrivKey() + if enabledKeys[idx].ID() != nodeAddress { + return errors.New("node address mismatch") + } + + translated := translateConfigs(conf) + e.connector, err = connector.NewGatewayConnector(&translated, e, e.clock, e.lggr) + if err != nil { + return err + } + return e.connector.Start(ctx) + }) +} + +func (e *ServiceWrapper) Sign(data ...[]byte) ([]byte, error) { + return gwcommon.SignData(e.signerKey, data...) +} + +func (e *ServiceWrapper) Close() error { + return e.StopOnce("GatewayConnectorServiceWrapper", func() (err error) { + return e.connector.Close() + }) +} + +func (e *ServiceWrapper) HealthReport() map[string]error { + return map[string]error{e.Name(): e.Healthy()} +} + +func (e *ServiceWrapper) Name() string { + return "GatewayConnectorServiceWrapper" +} + +func (e *ServiceWrapper) GetGatewayConnector() connector.GatewayConnector { + return e.connector +} diff --git a/core/capabilities/gateway_connector/service_wrapper_test.go b/core/capabilities/gateway_connector/service_wrapper_test.go new file mode 100644 index 0000000000..c88622fcd2 --- /dev/null +++ b/core/capabilities/gateway_connector/service_wrapper_test.go @@ -0,0 +1,81 @@ +package gatewayconnector_test + +import ( + "crypto/ecdsa" + "testing" + + "github.com/jonboulle/clockwork" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + gatewayconnector "github.com/smartcontractkit/chainlink/v2/core/capabilities/gateway_connector" + "github.com/smartcontractkit/chainlink/v2/core/config/toml" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" + ksmocks "github.com/smartcontractkit/chainlink/v2/core/services/keystore/mocks" +) + +func generateWrapper(t *testing.T, privateKey *ecdsa.PrivateKey, keystoreKey *ecdsa.PrivateKey) (*gatewayconnector.ServiceWrapper, error) { + logger := logger.TestLogger(t) + privateKeyV2 := ethkey.FromPrivateKey(privateKey) + addr := privateKeyV2.Address + keystoreKeyV2 := ethkey.FromPrivateKey(keystoreKey) + + config, err := chainlink.GeneralConfigOpts{ + Config: chainlink.Config{ + Core: toml.Core{ + Capabilities: toml.Capabilities{ + GatewayConnector: toml.GatewayConnector{ + ChainIDForNodeKey: ptr("1"), + NodeAddress: ptr(addr.Hex()), + DonID: ptr("5"), + WSHandshakeTimeoutMillis: ptr[uint32](100), + AuthMinChallengeLen: ptr[int](0), + AuthTimestampToleranceSec: ptr[uint32](10), + Gateways: []toml.ConnectorGateway{{ID: ptr("example_gateway"), URL: ptr("wss://localhost:8081/node")}}, + }, + }, + }, + }, + }.New() + ethKeystore := ksmocks.NewEth(t) + ethKeystore.On("EnabledKeysForChain", mock.Anything, mock.Anything).Return([]ethkey.KeyV2{keystoreKeyV2}, nil) + gc := config.Capabilities().GatewayConnector() + wrapper := gatewayconnector.NewGatewayConnectorServiceWrapper(gc, ethKeystore, clockwork.NewFakeClock(), logger) + require.NoError(t, err) + return wrapper, err +} + +func TestGatewayConnectorServiceWrapper_CleanStartClose(t *testing.T) { + t.Parallel() + + key, _ := testutils.NewPrivateKeyAndAddress(t) + wrapper, err := generateWrapper(t, key, key) + require.NoError(t, err) + + ctx := testutils.Context(t) + err = wrapper.Start(ctx) + require.NoError(t, err) + + t.Cleanup(func() { + assert.NoError(t, wrapper.Close()) + }) +} + +func TestGatewayConnectorServiceWrapper_NonexistentKey(t *testing.T) { + t.Parallel() + + key, _ := testutils.NewPrivateKeyAndAddress(t) + keystoreKey, _ := testutils.NewPrivateKeyAndAddress(t) + wrapper, err := generateWrapper(t, key, keystoreKey) + require.NoError(t, err) + + ctx := testutils.Context(t) + err = wrapper.Start(ctx) + require.Error(t, err) +} + +func ptr[T any](t T) *T { return &t } diff --git a/core/capabilities/integration_tests/keystone_contracts_setup.go b/core/capabilities/integration_tests/keystone_contracts_setup.go index 42269d1bd4..fbd3da5cc5 100644 --- a/core/capabilities/integration_tests/keystone_contracts_setup.go +++ b/core/capabilities/integration_tests/keystone_contracts_setup.go @@ -26,7 +26,6 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/capabilities/pb" "github.com/smartcontractkit/chainlink-common/pkg/values" - "github.com/smartcontractkit/chainlink-common/pkg/capabilities" "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" @@ -40,6 +39,13 @@ import ( kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" ) +const ( + CapabilityTypeTrigger = 0 + CapabilityTypeAction = 1 + CapabilityTypeConsensus = 2 + CapabilityTypeTarget = 3 +) + type peer struct { PeerID string Signer string @@ -91,8 +97,8 @@ func peerToNode(nopID uint32, p peer) (kcr.CapabilitiesRegistryNodeParams, error }, nil } -func setupCapabilitiesRegistryContract(ctx context.Context, t *testing.T, workflowDonPeers []peer, triggerDonPeers []peer, - targetDonPeerIDs []peer, +func setupCapabilitiesRegistryContract(ctx context.Context, t *testing.T, workflowDon donInfo, triggerDon donInfo, + targetDon donInfo, transactOpts *bind.TransactOpts, backend *ethBackend) common.Address { addr, _, reg, err := kcr.DeployCapabilitiesRegistry(transactOpts, backend) require.NoError(t, err) @@ -102,15 +108,16 @@ func setupCapabilitiesRegistryContract(ctx context.Context, t *testing.T, workfl streamsTrigger := kcr.CapabilitiesRegistryCapability{ LabelledName: "streams-trigger", Version: "1.0.0", - CapabilityType: uint8(capabilities.CapabilityTypeTrigger), + CapabilityType: CapabilityTypeTrigger, } sid, err := reg.GetHashedCapabilityId(&bind.CallOpts{}, streamsTrigger.LabelledName, streamsTrigger.Version) require.NoError(t, err) writeChain := kcr.CapabilitiesRegistryCapability{ - LabelledName: "write_geth-testnet", - Version: "1.0.0", - CapabilityType: uint8(capabilities.CapabilityTypeTarget), + LabelledName: "write_geth-testnet", + Version: "1.0.0", + + CapabilityType: CapabilityTypeTarget, } wid, err := reg.GetHashedCapabilityId(&bind.CallOpts{}, writeChain.LabelledName, writeChain.Version) if err != nil { @@ -120,7 +127,7 @@ func setupCapabilitiesRegistryContract(ctx context.Context, t *testing.T, workfl ocr := kcr.CapabilitiesRegistryCapability{ LabelledName: "offchain_reporting", Version: "1.0.0", - CapabilityType: uint8(capabilities.CapabilityTypeConsensus), + CapabilityType: CapabilityTypeConsensus, } ocrid, err := reg.GetHashedCapabilityId(&bind.CallOpts{}, ocr.LabelledName, ocr.Version) require.NoError(t, err) @@ -157,7 +164,7 @@ func setupCapabilitiesRegistryContract(ctx context.Context, t *testing.T, workfl nopID := recLog.NodeOperatorId nodes := []kcr.CapabilitiesRegistryNodeParams{} - for _, wfPeer := range workflowDonPeers { + for _, wfPeer := range workflowDon.peerIDs { n, innerErr := peerToNode(nopID, wfPeer) require.NoError(t, innerErr) @@ -165,7 +172,7 @@ func setupCapabilitiesRegistryContract(ctx context.Context, t *testing.T, workfl nodes = append(nodes, n) } - for _, triggerPeer := range triggerDonPeers { + for _, triggerPeer := range triggerDon.peerIDs { n, innerErr := peerToNode(nopID, triggerPeer) require.NoError(t, innerErr) @@ -173,7 +180,7 @@ func setupCapabilitiesRegistryContract(ctx context.Context, t *testing.T, workfl nodes = append(nodes, n) } - for _, targetPeer := range targetDonPeerIDs { + for _, targetPeer := range targetDon.peerIDs { n, innerErr := peerToNode(nopID, targetPeer) require.NoError(t, innerErr) @@ -185,7 +192,7 @@ func setupCapabilitiesRegistryContract(ctx context.Context, t *testing.T, workfl require.NoError(t, err) // workflow DON - ps, err := peers(workflowDonPeers) + ps, err := peers(workflowDon.peerIDs) require.NoError(t, err) cc := newCapabilityConfig() @@ -199,22 +206,24 @@ func setupCapabilitiesRegistryContract(ctx context.Context, t *testing.T, workfl }, } - workflowDonF := uint8(2) - _, err = reg.AddDON(transactOpts, ps, cfgs, false, true, workflowDonF) + _, err = reg.AddDON(transactOpts, ps, cfgs, false, true, workflowDon.F) require.NoError(t, err) // trigger DON - ps, err = peers(triggerDonPeers) + ps, err = peers(triggerDon.peerIDs) require.NoError(t, err) - triggerDonF := 1 - config := &pb.RemoteTriggerConfig{ - RegistrationRefresh: durationpb.New(20000 * time.Millisecond), - RegistrationExpiry: durationpb.New(60000 * time.Millisecond), - // F + 1 - MinResponsesToAggregate: uint32(triggerDonF) + 1, + triggerCapabilityConfig := newCapabilityConfig() + triggerCapabilityConfig.RemoteConfig = &pb.CapabilityConfig_RemoteTriggerConfig{ + RemoteTriggerConfig: &pb.RemoteTriggerConfig{ + RegistrationRefresh: durationpb.New(1000 * time.Millisecond), + RegistrationExpiry: durationpb.New(60000 * time.Millisecond), + // F + 1 + MinResponsesToAggregate: uint32(triggerDon.F) + 1, + }, } - configb, err := proto.Marshal(config) + + configb, err := proto.Marshal(triggerCapabilityConfig) require.NoError(t, err) cfgs = []kcr.CapabilitiesRegistryCapabilityConfiguration{ @@ -224,22 +233,37 @@ func setupCapabilitiesRegistryContract(ctx context.Context, t *testing.T, workfl }, } - _, err = reg.AddDON(transactOpts, ps, cfgs, true, false, uint8(triggerDonF)) + _, err = reg.AddDON(transactOpts, ps, cfgs, true, false, triggerDon.F) require.NoError(t, err) // target DON - ps, err = peers(targetDonPeerIDs) + ps, err = peers(targetDon.peerIDs) + require.NoError(t, err) + + targetCapabilityConfig := newCapabilityConfig() + + configWithLimit, err := values.WrapMap(map[string]any{"gasLimit": 500000}) + require.NoError(t, err) + + targetCapabilityConfig.DefaultConfig = values.Proto(configWithLimit).GetMapValue() + + targetCapabilityConfig.RemoteConfig = &pb.CapabilityConfig_RemoteTargetConfig{ + RemoteTargetConfig: &pb.RemoteTargetConfig{ + RequestHashExcludedAttributes: []string{"signed_report.Signatures"}, + }, + } + + remoteTargetConfigBytes, err := proto.Marshal(targetCapabilityConfig) require.NoError(t, err) cfgs = []kcr.CapabilitiesRegistryCapabilityConfiguration{ { CapabilityId: wid, - Config: ccb, + Config: remoteTargetConfigBytes, }, } - targetDonF := uint8(1) - _, err = reg.AddDON(transactOpts, ps, cfgs, true, false, targetDonF) + _, err = reg.AddDON(transactOpts, ps, cfgs, true, false, targetDon.F) require.NoError(t, err) backend.Commit() @@ -253,19 +277,18 @@ func newCapabilityConfig() *pb.CapabilityConfig { } } -func setupForwarderContract(t *testing.T, workflowDonPeers []peer, workflowDonId uint32, - configVersion uint32, f uint8, +func setupForwarderContract(t *testing.T, workflowDon donInfo, transactOpts *bind.TransactOpts, backend *ethBackend) (common.Address, *forwarder.KeystoneForwarder) { addr, _, fwd, err := forwarder.DeployKeystoneForwarder(transactOpts, backend) require.NoError(t, err) backend.Commit() var signers []common.Address - for _, p := range workflowDonPeers { + for _, p := range workflowDon.peerIDs { signers = append(signers, common.HexToAddress(p.Signer)) } - _, err = fwd.SetConfig(transactOpts, workflowDonId, configVersion, f, signers) + _, err = fwd.SetConfig(transactOpts, workflowDon.ID, workflowDon.ConfigVersion, workflowDon.F, signers) require.NoError(t, err) backend.Commit() diff --git a/core/capabilities/integration_tests/mock_dispatcher.go b/core/capabilities/integration_tests/mock_dispatcher.go index f685f0ad2e..1230e59427 100644 --- a/core/capabilities/integration_tests/mock_dispatcher.go +++ b/core/capabilities/integration_tests/mock_dispatcher.go @@ -9,6 +9,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote" remotetypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote/types" p2ptypes "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types" @@ -58,6 +59,7 @@ func (a *testAsyncMessageBroker) NewDispatcherForNode(nodePeerID p2ptypes.PeerID return &brokerDispatcher{ callerPeerID: nodePeerID, broker: a, + receivers: map[key]remotetypes.Receiver{}, } } @@ -158,6 +160,14 @@ type broker interface { type brokerDispatcher struct { callerPeerID p2ptypes.PeerID broker broker + + receivers map[key]remotetypes.Receiver + mu sync.Mutex +} + +type key struct { + capId string + donId uint32 } func (t *brokerDispatcher) Send(peerID p2ptypes.PeerID, msgBody *remotetypes.MessageBody) error { @@ -171,6 +181,15 @@ func (t *brokerDispatcher) Send(peerID p2ptypes.PeerID, msgBody *remotetypes.Mes } func (t *brokerDispatcher) SetReceiver(capabilityId string, donId uint32, receiver remotetypes.Receiver) error { + t.mu.Lock() + defer t.mu.Unlock() + k := key{capabilityId, donId} + _, ok := t.receivers[k] + if ok { + return fmt.Errorf("%w: receiver already exists for capability %s and don %d", remote.ErrReceiverExists, capabilityId, donId) + } + t.receivers[k] = receiver + t.broker.(*testAsyncMessageBroker).registerReceiverNode(t.callerPeerID, capabilityId, donId, receiver) return nil } diff --git a/core/capabilities/integration_tests/mock_libocr.go b/core/capabilities/integration_tests/mock_libocr.go index 39c53d48af..14ccdce600 100644 --- a/core/capabilities/integration_tests/mock_libocr.go +++ b/core/capabilities/integration_tests/mock_libocr.go @@ -157,10 +157,6 @@ func (m *mockLibOCR) simulateProtocolRound(ctx context.Context) error { Signer: commontypes.OracleID(i), Signature: sig, }) - - if uint8(len(signatures)) == m.f+1 { - break - } } for _, node := range m.nodes { @@ -181,7 +177,16 @@ func (m *mockLibOCR) simulateProtocolRound(ctx context.Context) error { continue } - err = node.Transmit(ctx, types.ConfigDigest{}, 0, report, signatures) + // For each node select a random set of f+1 signatures to mimic libocr behaviour + s := rand.NewSource(time.Now().UnixNano()) + r := rand.New(s) + indices := r.Perm(len(signatures)) + selectedSignatures := make([]types.AttributedOnchainSignature, m.f+1) + for i := 0; i < int(m.f+1); i++ { + selectedSignatures[i] = signatures[indices[i]] + } + + err = node.Transmit(ctx, types.ConfigDigest{}, 0, report, selectedSignatures) if err != nil { return fmt.Errorf("failed to transmit report: %w", err) } diff --git a/core/capabilities/integration_tests/mock_trigger.go b/core/capabilities/integration_tests/mock_trigger.go index cb673f54ff..35b05b054c 100644 --- a/core/capabilities/integration_tests/mock_trigger.go +++ b/core/capabilities/integration_tests/mock_trigger.go @@ -2,8 +2,6 @@ package integration_tests import ( "context" - "fmt" - "strconv" "sync" "testing" @@ -45,7 +43,7 @@ func (r *reportsSink) Close() error { func (r *reportsSink) sendReports(reportList []*datastreams.FeedReport) { for _, trigger := range r.triggers { - resp, err := wrapReports(reportList, "1", 12, datastreams.SignersMetadata{}) + resp, err := wrapReports(reportList, "1", 12, datastreams.Metadata{}) if err != nil { panic(err) } @@ -54,7 +52,7 @@ func (r *reportsSink) sendReports(reportList []*datastreams.FeedReport) { } func (r *reportsSink) getNewTrigger(t *testing.T) *streamsTrigger { - trigger := streamsTrigger{t: t, toSend: make(chan capabilities.CapabilityResponse, 1000), + trigger := streamsTrigger{t: t, toSend: make(chan capabilities.TriggerResponse, 1000), wg: &r.wg, stopCh: r.stopCh} r.triggers = append(r.triggers, trigger) return &trigger @@ -63,13 +61,13 @@ func (r *reportsSink) getNewTrigger(t *testing.T) *streamsTrigger { type streamsTrigger struct { t *testing.T cancel context.CancelFunc - toSend chan capabilities.CapabilityResponse + toSend chan capabilities.TriggerResponse wg *sync.WaitGroup stopCh services.StopChan } -func (s *streamsTrigger) sendResponse(resp capabilities.CapabilityResponse) { +func (s *streamsTrigger) sendResponse(resp capabilities.TriggerResponse) { s.toSend <- resp } @@ -81,32 +79,34 @@ func (s *streamsTrigger) Info(ctx context.Context) (capabilities.CapabilityInfo, ), nil } -func (s *streamsTrigger) RegisterTrigger(ctx context.Context, request capabilities.CapabilityRequest) (<-chan capabilities.CapabilityResponse, error) { +func (s *streamsTrigger) RegisterTrigger(ctx context.Context, request capabilities.TriggerRegistrationRequest) (<-chan capabilities.TriggerResponse, error) { if s.cancel != nil { s.t.Fatal("trigger already registered") } - responseCh := make(chan capabilities.CapabilityResponse) + responseCh := make(chan capabilities.TriggerResponse) - ctxWithCancel, cancel := context.WithCancel(ctx) + ctxWithCancel, cancel := context.WithCancel(context.Background()) s.cancel = cancel s.wg.Add(1) go func() { defer s.wg.Done() - select { - case <-s.stopCh: - return - case <-ctxWithCancel.Done(): - return - case resp := <-s.toSend: - responseCh <- resp + for { + select { + case <-s.stopCh: + return + case <-ctxWithCancel.Done(): + return + case resp := <-s.toSend: + responseCh <- resp + } } }() return responseCh, nil } -func (s *streamsTrigger) UnregisterTrigger(ctx context.Context, request capabilities.CapabilityRequest) error { +func (s *streamsTrigger) UnregisterTrigger(ctx context.Context, request capabilities.TriggerRegistrationRequest) error { if s.cancel == nil { s.t.Fatal("trigger not registered") } @@ -116,32 +116,28 @@ func (s *streamsTrigger) UnregisterTrigger(ctx context.Context, request capabili return nil } -func wrapReports(reportList []*datastreams.FeedReport, eventID string, timestamp int64, meta datastreams.SignersMetadata) (capabilities.CapabilityResponse, error) { - val, err := values.Wrap(reportList) - if err != nil { - return capabilities.CapabilityResponse{}, err +func wrapReports(reportList []*datastreams.FeedReport, eventID string, timestamp int64, meta datastreams.Metadata) (capabilities.TriggerResponse, error) { + rl := []datastreams.FeedReport{} + for _, r := range reportList { + rl = append(rl, *r) } - - metaVal, err := values.Wrap(meta) + outputs, err := values.WrapMap(datastreams.StreamsTriggerEvent{ + Payload: rl, + Metadata: meta, + Timestamp: timestamp, + }) if err != nil { - return capabilities.CapabilityResponse{}, err + return capabilities.TriggerResponse{}, err } triggerEvent := capabilities.TriggerEvent{ TriggerType: triggerID, ID: eventID, - Timestamp: strconv.FormatInt(timestamp, 10), - Metadata: metaVal, - Payload: val, - } - - triggerEventMapValue, err := values.WrapMap(triggerEvent) - if err != nil { - return capabilities.CapabilityResponse{}, fmt.Errorf("failed to wrap trigger event: %w", err) + Outputs: outputs, } - // Create a new CapabilityResponse with the MercuryTriggerEvent - return capabilities.CapabilityResponse{ - Value: triggerEventMapValue, + // Create a new TriggerResponse with the MercuryTriggerEvent + return capabilities.TriggerResponse{ + Event: triggerEvent, }, nil } diff --git a/core/capabilities/integration_tests/setup.go b/core/capabilities/integration_tests/setup.go index 6347d55878..f419c05e6c 100644 --- a/core/capabilities/integration_tests/setup.go +++ b/core/capabilities/integration_tests/setup.go @@ -21,7 +21,6 @@ import ( ocrTypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/feeds_consumer" - "github.com/smartcontractkit/chainlink/v2/core/utils/testutils/heavyweight" commoncap "github.com/smartcontractkit/chainlink-common/pkg/capabilities" "github.com/smartcontractkit/chainlink-common/pkg/capabilities/consensus/ocr3" @@ -44,6 +43,7 @@ import ( p2ptypes "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v3/reportcodec" + "github.com/smartcontractkit/chainlink/v2/core/utils/testutils/heavyweight" ) const ( @@ -69,8 +69,8 @@ func setupStreamDonsWithTransmissionSchedule(ctx context.Context, t *testing.T, lggr.SetLogLevel(TestLogLevel) ethBlockchain, transactor := setupBlockchain(t, 1000, 1*time.Second) - capabilitiesRegistryAddr := setupCapabilitiesRegistryContract(ctx, t, workflowDonInfo.peerIDs, triggerDonInfo.peerIDs, targetDonInfo.peerIDs, transactor, ethBlockchain) - forwarderAddr, _ := setupForwarderContract(t, workflowDonInfo.peerIDs, workflowDonInfo.ID, 1, workflowDonInfo.F, transactor, ethBlockchain) + capabilitiesRegistryAddr := setupCapabilitiesRegistryContract(ctx, t, workflowDonInfo, triggerDonInfo, targetDonInfo, transactor, ethBlockchain) + forwarderAddr, _ := setupForwarderContract(t, workflowDonInfo, transactor, ethBlockchain) consumerAddr, consumer := setupConsumerContract(t, transactor, ethBlockchain, forwarderAddr, workflowOwnerID, workflowName) var feedIDs []string @@ -260,9 +260,10 @@ func createDonInfo(t *testing.T, don don) donInfo { triggerDonInfo := donInfo{ DON: commoncap.DON{ - ID: don.id, - Members: donPeers, - F: don.f, + ID: don.id, + Members: donPeers, + F: don.f, + ConfigVersion: 1, }, peerIDs: peerIDs, keys: donKeys, diff --git a/core/capabilities/integration_tests/streams_test.go b/core/capabilities/integration_tests/streams_test.go index 6216e36c85..06c56f722f 100644 --- a/core/capabilities/integration_tests/streams_test.go +++ b/core/capabilities/integration_tests/streams_test.go @@ -22,8 +22,8 @@ func Test_AllAtOnceTransmissionSchedule(t *testing.T) { // The don IDs set in the below calls are inferred from the order in which the dons are added to the capabilities registry // in the setupCapabilitiesRegistryContract function, should this order change the don IDs will need updating. - workflowDonInfo := createDonInfo(t, don{id: 1, numNodes: 5, f: 1}) - triggerDonInfo := createDonInfo(t, don{id: 2, numNodes: 7, f: 1}) + workflowDonInfo := createDonInfo(t, don{id: 1, numNodes: 7, f: 2}) + triggerDonInfo := createDonInfo(t, don{id: 2, numNodes: 7, f: 2}) targetDonInfo := createDonInfo(t, don{id: 3, numNodes: 4, f: 1}) consumer, feedIDs, triggerSink := setupStreamDonsWithTransmissionSchedule(ctx, t, workflowDonInfo, triggerDonInfo, targetDonInfo, 3, @@ -45,8 +45,8 @@ func Test_OneAtATimeTransmissionSchedule(t *testing.T) { // The don IDs set in the below calls are inferred from the order in which the dons are added to the capabilities registry // in the setupCapabilitiesRegistryContract function, should this order change the don IDs will need updating. - workflowDonInfo := createDonInfo(t, don{id: 1, numNodes: 5, f: 1}) - triggerDonInfo := createDonInfo(t, don{id: 2, numNodes: 7, f: 1}) + workflowDonInfo := createDonInfo(t, don{id: 1, numNodes: 7, f: 2}) + triggerDonInfo := createDonInfo(t, don{id: 2, numNodes: 7, f: 2}) targetDonInfo := createDonInfo(t, don{id: 3, numNodes: 4, f: 1}) consumer, feedIDs, triggerSink := setupStreamDonsWithTransmissionSchedule(ctx, t, workflowDonInfo, triggerDonInfo, targetDonInfo, 3, @@ -73,7 +73,7 @@ func waitForConsumerReports(ctx context.Context, t *testing.T, consumer *feeds_c feedToReport[report.FeedID] = report } - ctxWithTimeout, cancel := context.WithTimeout(ctx, 5*time.Minute) + ctxWithTimeout, cancel := context.WithTimeout(ctx, 1*time.Minute) defer cancel() feedCount := 0 for { diff --git a/core/capabilities/launcher.go b/core/capabilities/launcher.go index b4ade04127..f2bfd5e4b1 100644 --- a/core/capabilities/launcher.go +++ b/core/capabilities/launcher.go @@ -4,16 +4,21 @@ import ( "context" "errors" "fmt" + "slices" "strings" "time" + "google.golang.org/protobuf/proto" + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" "github.com/smartcontractkit/chainlink-common/pkg/capabilities/triggers" "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/values" "github.com/smartcontractkit/libocr/ragep2p" ragetypes "github.com/smartcontractkit/libocr/ragep2p/types" + capabilitiespb "github.com/smartcontractkit/chainlink-common/pkg/capabilities/pb" "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote" "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote/target" remotetypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote/types" @@ -24,16 +29,16 @@ import ( ) var defaultStreamConfig = p2ptypes.StreamConfig{ - IncomingMessageBufferSize: 1000000, - OutgoingMessageBufferSize: 1000000, - MaxMessageLenBytes: 100000, + IncomingMessageBufferSize: 500, + OutgoingMessageBufferSize: 500, + MaxMessageLenBytes: 500000, // 500 KB; max capacity = 500 * 500000 = 250 MB MessageRateLimiter: ragep2p.TokenBucketParams{ Rate: 100.0, - Capacity: 1000, + Capacity: 500, }, BytesRateLimiter: ragep2p.TokenBucketParams{ - Rate: 100000.0, - Capacity: 1000000, + Rate: 5000000.0, // 5 MB/s + Capacity: 10000000, // 10 MB }, } @@ -46,6 +51,42 @@ type launcher struct { subServices []services.Service } +func unmarshalCapabilityConfig(data []byte) (capabilities.CapabilityConfiguration, error) { + cconf := &capabilitiespb.CapabilityConfig{} + err := proto.Unmarshal(data, cconf) + if err != nil { + return capabilities.CapabilityConfiguration{}, err + } + + var remoteTriggerConfig *capabilities.RemoteTriggerConfig + var remoteTargetConfig *capabilities.RemoteTargetConfig + + switch cconf.GetRemoteConfig().(type) { + case *capabilitiespb.CapabilityConfig_RemoteTriggerConfig: + prtc := cconf.GetRemoteTriggerConfig() + remoteTriggerConfig = &capabilities.RemoteTriggerConfig{} + remoteTriggerConfig.RegistrationRefresh = prtc.RegistrationRefresh.AsDuration() + remoteTriggerConfig.RegistrationExpiry = prtc.RegistrationExpiry.AsDuration() + remoteTriggerConfig.MinResponsesToAggregate = prtc.MinResponsesToAggregate + remoteTriggerConfig.MessageExpiry = prtc.MessageExpiry.AsDuration() + case *capabilitiespb.CapabilityConfig_RemoteTargetConfig: + prtc := cconf.GetRemoteTargetConfig() + remoteTargetConfig = &capabilities.RemoteTargetConfig{} + remoteTargetConfig.RequestHashExcludedAttributes = prtc.RequestHashExcludedAttributes + } + + dc, err := values.FromMapValueProto(cconf.DefaultConfig) + if err != nil { + return capabilities.CapabilityConfiguration{}, err + } + + return capabilities.CapabilityConfiguration{ + DefaultConfig: dc, + RemoteTriggerConfig: remoteTriggerConfig, + RemoteTargetConfig: remoteTargetConfig, + }, nil +} + func NewLauncher( lggr logger.Logger, peerWrapper p2ptypes.PeerWrapper, @@ -90,6 +131,12 @@ func (w *launcher) Name() string { func (w *launcher) Launch(ctx context.Context, state *registrysyncer.LocalRegistry) error { w.registry.SetLocalRegistry(state) + allDONIDs := []registrysyncer.DonID{} + for id := range state.IDsToDONs { + allDONIDs = append(allDONIDs, id) + } + slices.Sort(allDONIDs) // ensure deterministic order + // Let's start by updating the list of Peers // We do this by creating a new entry for each node belonging // to a public DON. @@ -97,7 +144,8 @@ func (w *launcher) Launch(ctx context.Context, state *registrysyncer.LocalRegist allPeers := make(map[ragetypes.PeerID]p2ptypes.StreamConfig) publicDONs := []registrysyncer.DON{} - for _, d := range state.IDsToDONs { + for _, id := range allDONIDs { + d := state.IDsToDONs[id] if !d.DON.IsPublic { continue } @@ -127,7 +175,8 @@ func (w *launcher) Launch(ctx context.Context, state *registrysyncer.LocalRegist myWorkflowDONs := []registrysyncer.DON{} remoteWorkflowDONs := []registrysyncer.DON{} myDONs := map[uint32]bool{} - for _, d := range state.IDsToDONs { + for _, id := range allDONIDs { + d := state.IDsToDONs[id] for _, peerID := range d.Members { if peerID == myID { myDONs[d.ID] = true @@ -164,7 +213,7 @@ func (w *launcher) Launch(ctx context.Context, state *registrysyncer.LocalRegist // NOTE: this is enforced on-chain and so should never happen. if len(myWorkflowDONs) > 1 { - w.lggr.Error("invariant violation: node is part of more than one workflowDON: this shouldn't happen.") + return errors.New("invariant violation: node is part of more than one workflowDON") } for _, rcd := range remoteCapabilityDONs { @@ -196,6 +245,11 @@ func (w *launcher) addRemoteCapabilities(ctx context.Context, myDON registrysync return fmt.Errorf("could not find capability matching id %s", cid) } + capabilityConfig, err := unmarshalCapabilityConfig(c.Config) + if err != nil { + return fmt.Errorf("could not unmarshal capability config for id %s", cid) + } + switch capability.CapabilityType { case capabilities.CapabilityTypeTrigger: newTriggerFn := func(info capabilities.CapabilityInfo) (capabilityService, error) { @@ -216,6 +270,7 @@ func (w *launcher) addRemoteCapabilities(ctx context.Context, myDON registrysync int(remoteDON.F+1), w.lggr, ) + // TODO: We need to implement a custom, Mercury-specific // aggregator here, because there is no guarantee that // all trigger events in the workflow will have the same @@ -223,7 +278,7 @@ func (w *launcher) addRemoteCapabilities(ctx context.Context, myDON registrysync // When this is solved, we can move to a generic aggregator // and remove this. triggerCap := remote.NewTriggerSubscriber( - c.RemoteTriggerConfig, + capabilityConfig.RemoteTriggerConfig, info, remoteDON.DON, myDON.DON, @@ -332,11 +387,16 @@ func (w *launcher) exposeCapabilities(ctx context.Context, myPeerID p2ptypes.Pee return fmt.Errorf("could not find capability matching id %s", cid) } + capabilityConfig, err := unmarshalCapabilityConfig(c.Config) + if err != nil { + return fmt.Errorf("could not unmarshal capability config for id %s", cid) + } + switch capability.CapabilityType { case capabilities.CapabilityTypeTrigger: - newTriggerPublisher := func(capability capabilities.BaseCapability, info capabilities.CapabilityInfo) (receiverService, error) { + newTriggerPublisher := func(capability capabilities.BaseCapability, info capabilities.CapabilityInfo) (remotetypes.ReceiverService, error) { publisher := remote.NewTriggerPublisher( - c.RemoteTriggerConfig, + capabilityConfig.RemoteTriggerConfig, capability.(capabilities.TriggerCapability), info, don.DON, @@ -356,8 +416,9 @@ func (w *launcher) exposeCapabilities(ctx context.Context, myPeerID p2ptypes.Pee case capabilities.CapabilityTypeConsensus: w.lggr.Warn("no remote client configured for capability type consensus, skipping configuration") case capabilities.CapabilityTypeTarget: - newTargetServer := func(capability capabilities.BaseCapability, info capabilities.CapabilityInfo) (receiverService, error) { + newTargetServer := func(capability capabilities.BaseCapability, info capabilities.CapabilityInfo) (remotetypes.ReceiverService, error) { return target.NewServer( + capabilityConfig.RemoteTargetConfig, myPeerID, capability.(capabilities.TargetCapability), info, @@ -380,12 +441,7 @@ func (w *launcher) exposeCapabilities(ctx context.Context, myPeerID p2ptypes.Pee return nil } -type receiverService interface { - services.Service - remotetypes.Receiver -} - -func (w *launcher) addReceiver(ctx context.Context, capability registrysyncer.Capability, don registrysyncer.DON, newReceiverFn func(capability capabilities.BaseCapability, info capabilities.CapabilityInfo) (receiverService, error)) error { +func (w *launcher) addReceiver(ctx context.Context, capability registrysyncer.Capability, don registrysyncer.DON, newReceiverFn func(capability capabilities.BaseCapability, info capabilities.CapabilityInfo) (remotetypes.ReceiverService, error)) error { capID := capability.ID info, err := capabilities.NewRemoteCapabilityInfo( capID, diff --git a/core/capabilities/launcher_test.go b/core/capabilities/launcher_test.go index fb3e6837d0..425c6815a4 100644 --- a/core/capabilities/launcher_test.go +++ b/core/capabilities/launcher_test.go @@ -4,14 +4,18 @@ import ( "context" "crypto/rand" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/durationpb" ragetypes "github.com/smartcontractkit/libocr/ragep2p/types" "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + capabilitiespb "github.com/smartcontractkit/chainlink-common/pkg/capabilities/pb" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote" remoteMocks "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote/types/mocks" @@ -26,11 +30,11 @@ type mockTrigger struct { capabilities.CapabilityInfo } -func (m *mockTrigger) RegisterTrigger(ctx context.Context, request capabilities.CapabilityRequest) (<-chan capabilities.CapabilityResponse, error) { +func (m *mockTrigger) RegisterTrigger(ctx context.Context, request capabilities.TriggerRegistrationRequest) (<-chan capabilities.TriggerResponse, error) { return nil, nil } -func (m *mockTrigger) UnregisterTrigger(ctx context.Context, request capabilities.CapabilityRequest) error { +func (m *mockTrigger) UnregisterTrigger(ctx context.Context, request capabilities.TriggerRegistrationRequest) error { return nil } @@ -42,8 +46,8 @@ type mockCapability struct { capabilities.CapabilityInfo } -func (m *mockCapability) Execute(ctx context.Context, req capabilities.CapabilityRequest) (<-chan capabilities.CapabilityResponse, error) { - return nil, nil +func (m *mockCapability) Execute(ctx context.Context, req capabilities.CapabilityRequest) (capabilities.CapabilityResponse, error) { + return capabilities.CapabilityResponse{}, nil } func (m *mockCapability) RegisterToWorkflow(ctx context.Context, request capabilities.RegisterToWorkflowRequest) error { @@ -121,7 +125,7 @@ func TestLauncher_WiresUpExternalCapabilities(t *testing.T) { AcceptsWorkflows: true, Members: nodes, }, - CapabilityConfigurations: map[string]capabilities.CapabilityConfiguration{ + CapabilityConfigurations: map[string]registrysyncer.CapabilityConfiguration{ fullTriggerCapID: {}, fullTargetID: {}, }, @@ -223,7 +227,7 @@ func TestSyncer_IgnoresCapabilitiesForPrivateDON(t *testing.T) { AcceptsWorkflows: true, Members: nodes, }, - CapabilityConfigurations: map[string]capabilities.CapabilityConfiguration{ + CapabilityConfigurations: map[string]registrysyncer.CapabilityConfiguration{ triggerID: {}, targetID: {}, }, @@ -323,9 +327,18 @@ func TestLauncher_WiresUpClientsForPublicWorkflowDON(t *testing.T) { // The below state describes a Workflow DON (AcceptsWorkflows = true), // which exposes the streams-trigger and write_chain capabilities. // We expect receivers to be wired up and both capabilities to be added to the registry. - var rtc capabilities.RemoteTriggerConfig + rtc := &capabilities.RemoteTriggerConfig{} rtc.ApplyDefaults() + cfg, err := proto.Marshal(&capabilitiespb.CapabilityConfig{ + RemoteConfig: &capabilitiespb.CapabilityConfig_RemoteTriggerConfig{ + RemoteTriggerConfig: &capabilitiespb.RemoteTriggerConfig{ + RegistrationRefresh: durationpb.New(1 * time.Second), + }, + }, + }) + require.NoError(t, err) + state := ®istrysyncer.LocalRegistry{ IDsToDONs: map[registrysyncer.DonID]registrysyncer.DON{ registrysyncer.DonID(dID): { @@ -347,12 +360,12 @@ func TestLauncher_WiresUpClientsForPublicWorkflowDON(t *testing.T) { AcceptsWorkflows: false, Members: capabilityDonNodes, }, - CapabilityConfigurations: map[string]capabilities.CapabilityConfiguration{ + CapabilityConfigurations: map[string]registrysyncer.CapabilityConfiguration{ fullTriggerCapID: { - RemoteTriggerConfig: rtc, + Config: cfg, }, fullTargetID: { - RemoteTriggerConfig: rtc, + Config: cfg, }, }, }, @@ -496,7 +509,7 @@ func TestLauncher_WiresUpClientsForPublicWorkflowDONButIgnoresPrivateCapabilitie AcceptsWorkflows: false, Members: capabilityDonNodes, }, - CapabilityConfigurations: map[string]capabilities.CapabilityConfiguration{ + CapabilityConfigurations: map[string]registrysyncer.CapabilityConfiguration{ fullTriggerCapID: {}, }, }, @@ -509,7 +522,7 @@ func TestLauncher_WiresUpClientsForPublicWorkflowDONButIgnoresPrivateCapabilitie AcceptsWorkflows: false, Members: capabilityDonNodes, }, - CapabilityConfigurations: map[string]capabilities.CapabilityConfiguration{ + CapabilityConfigurations: map[string]registrysyncer.CapabilityConfiguration{ fullTargetID: {}, }, }, @@ -653,7 +666,7 @@ func TestLauncher_SucceedsEvenIfDispatcherAlreadyHasReceiver(t *testing.T) { AcceptsWorkflows: false, Members: capabilityDonNodes, }, - CapabilityConfigurations: map[string]capabilities.CapabilityConfiguration{ + CapabilityConfigurations: map[string]registrysyncer.CapabilityConfiguration{ fullTriggerCapID: {}, }, }, diff --git a/core/capabilities/registry.go b/core/capabilities/registry.go index 8a99450c09..4728550580 100644 --- a/core/capabilities/registry.go +++ b/core/capabilities/registry.go @@ -8,6 +8,8 @@ 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" ) var ( @@ -16,7 +18,7 @@ var ( type metadataRegistry interface { LocalNode(ctx context.Context) (capabilities.Node, error) - ConfigForCapability(ctx context.Context, capabilityID string, donID uint32) (capabilities.CapabilityConfiguration, error) + ConfigForCapability(ctx context.Context, capabilityID string, donID uint32) (registrysyncer.CapabilityConfiguration, error) } // Registry is a struct for the registry of capabilities. @@ -37,11 +39,18 @@ func (r *Registry) LocalNode(ctx context.Context) (capabilities.Node, error) { } func (r *Registry) ConfigForCapability(ctx context.Context, capabilityID string, donID uint32) (capabilities.CapabilityConfiguration, error) { + r.mu.RLock() + defer r.mu.RUnlock() if r.metadataRegistry == nil { return capabilities.CapabilityConfiguration{}, errors.New("metadataRegistry information not available") } - return r.metadataRegistry.ConfigForCapability(ctx, capabilityID, donID) + cfc, err := r.metadataRegistry.ConfigForCapability(ctx, capabilityID, donID) + if err != nil { + return capabilities.CapabilityConfiguration{}, err + } + + return unmarshalCapabilityConfig(cfc.Config) } // SetLocalRegistry sets a local copy of the offchain registry for the registry to use. @@ -191,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/capabilities/registry_test.go b/core/capabilities/registry_test.go index 77e5d9edcd..89f366da04 100644 --- a/core/capabilities/registry_test.go +++ b/core/capabilities/registry_test.go @@ -21,8 +21,8 @@ type mockCapability struct { capabilities.CapabilityInfo } -func (m *mockCapability) Execute(ctx context.Context, req capabilities.CapabilityRequest) (<-chan capabilities.CapabilityResponse, error) { - return nil, nil +func (m *mockCapability) Execute(ctx context.Context, req capabilities.CapabilityRequest) (capabilities.CapabilityResponse, error) { + return capabilities.CapabilityResponse{}, nil } func (m *mockCapability) RegisterToWorkflow(ctx context.Context, request capabilities.RegisterToWorkflowRequest) error { diff --git a/core/capabilities/remote/dispatcher.go b/core/capabilities/remote/dispatcher.go index c1ee5db294..f27d691bb6 100644 --- a/core/capabilities/remote/dispatcher.go +++ b/core/capabilities/remote/dispatcher.go @@ -2,11 +2,11 @@ package remote import ( "context" - "errors" "fmt" - sync "sync" + "sync" "time" + "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "google.golang.org/protobuf/proto" @@ -15,8 +15,9 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/types/core" "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote/types" - remotetypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote/types" + "github.com/smartcontractkit/chainlink/v2/core/config" "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/common" p2ptypes "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types" ) @@ -26,11 +27,13 @@ var ( // dispatcher en/decodes messages and routes traffic between peers and capabilities type dispatcher struct { + cfg config.Dispatcher peerWrapper p2ptypes.PeerWrapper peer p2ptypes.Peer peerID p2ptypes.PeerID signer p2ptypes.Signer registry core.CapabilitiesRegistry + rateLimiter *common.RateLimiter receivers map[key]*receiver mu sync.RWMutex stopCh services.StopChan @@ -45,17 +48,26 @@ type key struct { var _ services.Service = &dispatcher{} -const supportedVersion = 1 - -func NewDispatcher(peerWrapper p2ptypes.PeerWrapper, signer p2ptypes.Signer, registry core.CapabilitiesRegistry, lggr logger.Logger) *dispatcher { +func NewDispatcher(cfg config.Dispatcher, peerWrapper p2ptypes.PeerWrapper, signer p2ptypes.Signer, registry core.CapabilitiesRegistry, lggr logger.Logger) (*dispatcher, error) { + rl, err := common.NewRateLimiter(common.RateLimiterConfig{ + GlobalRPS: cfg.RateLimit().GlobalRPS(), + GlobalBurst: cfg.RateLimit().GlobalBurst(), + PerSenderRPS: cfg.RateLimit().PerSenderRPS(), + PerSenderBurst: cfg.RateLimit().PerSenderBurst(), + }) + if err != nil { + return nil, errors.Wrap(err, "failed to create rate limiter") + } return &dispatcher{ + cfg: cfg, peerWrapper: peerWrapper, signer: signer, registry: registry, + rateLimiter: rl, receivers: make(map[key]*receiver), stopCh: make(services.StopChan), lggr: lggr.Named("Dispatcher"), - } + }, nil } func (d *dispatcher) Start(ctx context.Context) error { @@ -86,14 +98,12 @@ var capReceiveChannelUsage = promauto.NewGaugeVec(prometheus.GaugeOpts{ Help: "The usage of the receive channel for each capability, 0 indicates empty, 1 indicates full.", }, []string{"capabilityId", "donId"}) -const receiverBufferSize = 10000 - type receiver struct { cancel context.CancelFunc - ch chan *remotetypes.MessageBody + ch chan *types.MessageBody } -func (d *dispatcher) SetReceiver(capabilityId string, donId uint32, rec remotetypes.Receiver) error { +func (d *dispatcher) SetReceiver(capabilityId string, donId uint32, rec types.Receiver) error { d.mu.Lock() defer d.mu.Unlock() k := key{capabilityId, donId} @@ -102,7 +112,7 @@ func (d *dispatcher) SetReceiver(capabilityId string, donId uint32, rec remotety return fmt.Errorf("%w: receiver already exists for capability %s and don %d", ErrReceiverExists, capabilityId, donId) } - receiverCh := make(chan *remotetypes.MessageBody, receiverBufferSize) + receiverCh := make(chan *types.MessageBody, d.cfg.ReceiverBufferSize()) ctx, cancelCtx := d.stopCh.NewCtx() d.wg.Add(1) @@ -140,8 +150,8 @@ func (d *dispatcher) RemoveReceiver(capabilityId string, donId uint32) { } } -func (d *dispatcher) Send(peerID p2ptypes.PeerID, msgBody *remotetypes.MessageBody) error { - msgBody.Version = supportedVersion +func (d *dispatcher) Send(peerID p2ptypes.PeerID, msgBody *types.MessageBody) error { + msgBody.Version = uint32(d.cfg.SupportedVersion()) msgBody.Sender = d.peerID[:] msgBody.Receiver = peerID[:] msgBody.Timestamp = time.Now().UnixMilli() @@ -153,7 +163,7 @@ func (d *dispatcher) Send(peerID p2ptypes.PeerID, msgBody *remotetypes.MessageBo if err != nil { return err } - msg := &remotetypes.Message{Signature: signature, Body: rawBody} + msg := &types.Message{Signature: signature, Body: rawBody} rawMsg, err := proto.Marshal(msg) if err != nil { return err @@ -169,6 +179,10 @@ func (d *dispatcher) receive() { d.lggr.Info("stopped - exiting receive") return case msg := <-recvCh: + if !d.rateLimiter.Allow(msg.Sender.String()) { + d.lggr.Debugw("rate limit exceeded, dropping message", "sender", msg.Sender) + continue + } body, err := ValidateMessage(msg, d.peerID) if err != nil { d.lggr.Debugw("received invalid message", "error", err) @@ -180,12 +194,12 @@ func (d *dispatcher) receive() { receiver, ok := d.receivers[k] d.mu.RUnlock() if !ok { - d.lggr.Debugw("received message for unregistered capability", "capabilityId", k.capId, "donId", k.donId) + d.lggr.Debugw("received message for unregistered capability", "capabilityId", SanitizeLogString(k.capId), "donId", k.donId) d.tryRespondWithError(msg.Sender, body, types.Error_CAPABILITY_NOT_FOUND) continue } - receiverQueueUsage := float64(len(receiver.ch)) / receiverBufferSize + receiverQueueUsage := float64(len(receiver.ch)) / float64(d.cfg.ReceiverBufferSize()) capReceiveChannelUsage.WithLabelValues(k.capId, fmt.Sprint(k.donId)).Set(receiverQueueUsage) select { case receiver.ch <- body: @@ -196,7 +210,7 @@ func (d *dispatcher) receive() { } } -func (d *dispatcher) tryRespondWithError(peerID p2ptypes.PeerID, body *remotetypes.MessageBody, errType types.Error) { +func (d *dispatcher) tryRespondWithError(peerID p2ptypes.PeerID, body *types.MessageBody, errType types.Error) { if body == nil { return } diff --git a/core/capabilities/remote/dispatcher_test.go b/core/capabilities/remote/dispatcher_test.go index 7ea4c2e262..50edc5f353 100644 --- a/core/capabilities/remote/dispatcher_test.go +++ b/core/capabilities/remote/dispatcher_test.go @@ -10,6 +10,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote" remotetypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote/types" + "github.com/smartcontractkit/chainlink/v2/core/config" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" p2ptypes "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types" @@ -32,6 +33,47 @@ func (r *testReceiver) Receive(_ context.Context, msg *remotetypes.MessageBody) r.ch <- msg } +type testRateLimitConfig struct { + globalRPS float64 + globalBurst int + rps float64 + burst int +} + +func (c testRateLimitConfig) GlobalRPS() float64 { + return c.globalRPS +} + +func (c testRateLimitConfig) GlobalBurst() int { + return c.globalBurst +} + +func (c testRateLimitConfig) PerSenderRPS() float64 { + return c.rps +} + +func (c testRateLimitConfig) PerSenderBurst() int { + return c.burst +} + +type testConfig struct { + supportedVersion int + receiverBufferSize int + rateLimit testRateLimitConfig +} + +func (c testConfig) SupportedVersion() int { + return c.supportedVersion +} + +func (c testConfig) ReceiverBufferSize() int { + return c.receiverBufferSize +} + +func (c testConfig) RateLimit() config.DispatcherRateLimit { + return c.rateLimit +} + func TestDispatcher_CleanStartClose(t *testing.T) { lggr := logger.TestLogger(t) ctx := testutils.Context(t) @@ -44,7 +86,17 @@ func TestDispatcher_CleanStartClose(t *testing.T) { signer := mocks.NewSigner(t) registry := commonMocks.NewCapabilitiesRegistry(t) - dispatcher := remote.NewDispatcher(wrapper, signer, registry, lggr) + dispatcher, err := remote.NewDispatcher(testConfig{ + supportedVersion: 1, + receiverBufferSize: 10000, + rateLimit: testRateLimitConfig{ + globalRPS: 800.0, + globalBurst: 100, + rps: 10.0, + burst: 50, + }, + }, wrapper, signer, registry, lggr) + require.NoError(t, err) require.NoError(t, dispatcher.Start(ctx)) require.NoError(t, dispatcher.Close()) } @@ -65,11 +117,21 @@ func TestDispatcher_Receive(t *testing.T) { signer.On("Sign", mock.Anything).Return(nil, errors.New("not implemented")) registry := commonMocks.NewCapabilitiesRegistry(t) - dispatcher := remote.NewDispatcher(wrapper, signer, registry, lggr) + dispatcher, err := remote.NewDispatcher(testConfig{ + supportedVersion: 1, + receiverBufferSize: 10000, + rateLimit: testRateLimitConfig{ + globalRPS: 800.0, + globalBurst: 100, + rps: 10.0, + burst: 50, + }, + }, wrapper, signer, registry, lggr) + require.NoError(t, err) require.NoError(t, dispatcher.Start(ctx)) rcv := newReceiver() - err := dispatcher.SetReceiver(capId1, donId1, rcv) + err = dispatcher.SetReceiver(capId1, donId1, rcv) require.NoError(t, err) // supported capability @@ -113,7 +175,17 @@ func TestDispatcher_RespondWithError(t *testing.T) { signer.On("Sign", mock.Anything).Return([]byte{}, nil) registry := commonMocks.NewCapabilitiesRegistry(t) - dispatcher := remote.NewDispatcher(wrapper, signer, registry, lggr) + dispatcher, err := remote.NewDispatcher(testConfig{ + supportedVersion: 1, + receiverBufferSize: 10000, + rateLimit: testRateLimitConfig{ + globalRPS: 800.0, + globalBurst: 100, + rps: 10.0, + burst: 50, + }, + }, wrapper, signer, registry, lggr) + require.NoError(t, err) require.NoError(t, dispatcher.Start(ctx)) // unknown capability diff --git a/core/capabilities/remote/message_cache.go b/core/capabilities/remote/message_cache.go index 27f909c516..f3a3a79b2c 100644 --- a/core/capabilities/remote/message_cache.go +++ b/core/capabilities/remote/message_cache.go @@ -60,12 +60,12 @@ func (c *messageCache[EventID, PeerID]) Ready(eventID EventID, minCount uint32, if msg.timestamp >= minTimestamp { countAboveMinTimestamp++ accPayloads = append(accPayloads, msg.payload) - if countAboveMinTimestamp >= minCount { - ev.wasReady = true - return true, accPayloads - } } } + if countAboveMinTimestamp >= minCount { + ev.wasReady = true + return true, accPayloads + } return false, nil } diff --git a/core/capabilities/remote/target/client.go b/core/capabilities/remote/target/client.go index 5b65bf63e4..64dcbd14f0 100644 --- a/core/capabilities/remote/target/client.go +++ b/core/capabilities/remote/target/client.go @@ -9,8 +9,10 @@ import ( commoncap "github.com/smartcontractkit/chainlink-common/pkg/capabilities" "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote" "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote/target/request" "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote/types" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/validation" "github.com/smartcontractkit/chainlink/v2/core/logger" ) @@ -121,7 +123,17 @@ func (c *client) UnregisterFromWorkflow(ctx context.Context, request commoncap.U return nil } -func (c *client) Execute(ctx context.Context, capReq commoncap.CapabilityRequest) (<-chan commoncap.CapabilityResponse, error) { +func (c *client) Execute(ctx context.Context, capReq commoncap.CapabilityRequest) (commoncap.CapabilityResponse, error) { + req, err := c.executeRequest(ctx, capReq) + if err != nil { + return commoncap.CapabilityResponse{}, fmt.Errorf("failed to execute request: %w", err) + } + + resp := <-req.ResponseChan() + return resp.CapabilityResponse, resp.Err +} + +func (c *client) executeRequest(ctx context.Context, capReq commoncap.CapabilityRequest) (*request.ClientRequest, error) { c.mutex.Lock() defer c.mutex.Unlock() @@ -143,15 +155,18 @@ func (c *client) Execute(ctx context.Context, capReq commoncap.CapabilityRequest } c.messageIDToCallerRequest[messageID] = req - - return req.ResponseChan(), nil + return req, nil } func (c *client) Receive(ctx context.Context, msg *types.MessageBody) { c.mutex.Lock() defer c.mutex.Unlock() - messageID := GetMessageID(msg) + messageID, err := GetMessageID(msg) + if err != nil { + c.lggr.Errorw("invalid message ID", "err", err, "id", remote.SanitizeLogString(string(msg.MessageId))) + return + } c.lggr.Debugw("Remote client target receiving message", "messageID", messageID) @@ -167,8 +182,12 @@ func (c *client) Receive(ctx context.Context, msg *types.MessageBody) { } func GetMessageIDForRequest(req commoncap.CapabilityRequest) (string, error) { - if req.Metadata.WorkflowID == "" || req.Metadata.WorkflowExecutionID == "" { - return "", errors.New("workflow ID and workflow execution ID must be set in request metadata") + if err := validation.ValidateWorkflowOrExecutionID(req.Metadata.WorkflowID); err != nil { + return "", fmt.Errorf("workflow ID is invalid: %w", err) + } + + if err := validation.ValidateWorkflowOrExecutionID(req.Metadata.WorkflowExecutionID); err != nil { + return "", fmt.Errorf("workflow execution ID is invalid: %w", err) } return req.Metadata.WorkflowID + req.Metadata.WorkflowExecutionID, nil diff --git a/core/capabilities/remote/target/client_test.go b/core/capabilities/remote/target/client_test.go index 6d26b51b8a..697cb6e383 100644 --- a/core/capabilities/remote/target/client_test.go +++ b/core/capabilities/remote/target/client_test.go @@ -21,6 +21,11 @@ import ( p2ptypes "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types" ) +const ( + workflowID1 = "15c631d295ef5e32deb99a10ee6804bc4af13855687559d7ff6552ac6dbb2ce0" + workflowExecutionID1 = "95ef5e32deb99a10ee6804bc4af13855687559d7ff6552ac6dbb2ce0abbadeed" +) + func Test_Client_DonTopologies(t *testing.T) { ctx := testutils.Context(t) @@ -30,9 +35,8 @@ func Test_Client_DonTopologies(t *testing.T) { }) require.NoError(t, err) - responseTest := func(t *testing.T, responseCh <-chan commoncap.CapabilityResponse, responseError error) { + responseTest := func(t *testing.T, response commoncap.CapabilityResponse, responseError error) { require.NoError(t, responseError) - response := <-responseCh mp, err := response.Value.Unwrap() require.NoError(t, err) assert.Equal(t, "aValue1", mp.(map[string]any)["response"].(string)) @@ -61,9 +65,8 @@ func Test_Client_DonTopologies(t *testing.T) { func Test_Client_TransmissionSchedules(t *testing.T) { ctx := testutils.Context(t) - responseTest := func(t *testing.T, responseCh <-chan commoncap.CapabilityResponse, responseError error) { + responseTest := func(t *testing.T, response commoncap.CapabilityResponse, responseError error) { require.NoError(t, responseError) - response := <-responseCh mp, err := response.Value.Unwrap() require.NoError(t, err) assert.Equal(t, "aValue1", mp.(map[string]any)["response"].(string)) @@ -99,10 +102,8 @@ func Test_Client_TransmissionSchedules(t *testing.T) { func Test_Client_TimesOutIfInsufficientCapabilityPeerResponses(t *testing.T) { ctx := testutils.Context(t) - responseTest := func(t *testing.T, responseCh <-chan commoncap.CapabilityResponse, responseError error) { - require.NoError(t, responseError) - response := <-responseCh - assert.NotNil(t, response.Err) + responseTest := func(t *testing.T, response commoncap.CapabilityResponse, responseError error) { + assert.NotNil(t, responseError) } capability := &TestCapability{} @@ -121,7 +122,7 @@ func Test_Client_TimesOutIfInsufficientCapabilityPeerResponses(t *testing.T) { func testClient(ctx context.Context, t *testing.T, numWorkflowPeers int, workflowNodeResponseTimeout time.Duration, numCapabilityPeers int, capabilityDonF uint8, underlying commoncap.TargetCapability, transmissionSchedule *values.Map, - responseTest func(t *testing.T, responseCh <-chan commoncap.CapabilityResponse, responseError error)) { + responseTest func(t *testing.T, responseCh commoncap.CapabilityResponse, responseError error)) { lggr := logger.TestLogger(t) capabilityPeers := make([]p2ptypes.PeerID, numCapabilityPeers) @@ -192,8 +193,8 @@ func testClient(ctx context.Context, t *testing.T, numWorkflowPeers int, workflo responseCh, err := caller.Execute(ctx, commoncap.CapabilityRequest{ Metadata: commoncap.RequestMetadata{ - WorkflowID: "workflowID", - WorkflowExecutionID: "workflowExecutionID", + WorkflowID: workflowID1, + WorkflowExecutionID: workflowExecutionID1, }, Config: transmissionSchedule, Inputs: executeInputs, @@ -234,7 +235,10 @@ func (t *clientTestServer) Receive(_ context.Context, msg *remotetypes.MessageBo defer t.mux.Unlock() sender := toPeerID(msg.Sender) - messageID := target.GetMessageID(msg) + messageID, err := target.GetMessageID(msg) + if err != nil { + panic(err) + } if t.messageIDToSenders[messageID] == nil { t.messageIDToSenders[messageID] = make(map[p2ptypes.PeerID]bool) @@ -253,8 +257,7 @@ func (t *clientTestServer) Receive(_ context.Context, msg *remotetypes.MessageBo panic(err) } - respCh, responseErr := t.targetCapability.Execute(context.Background(), capabilityRequest) - resp := <-respCh + resp, responseErr := t.targetCapability.Execute(context.Background(), capabilityRequest) for receiver := range t.messageIDToSenders[messageID] { var responseMsg = &remotetypes.MessageBody{ diff --git a/core/capabilities/remote/target/endtoend_test.go b/core/capabilities/remote/target/endtoend_test.go index 9bbb53d4f6..3b077bbafe 100644 --- a/core/capabilities/remote/target/endtoend_test.go +++ b/core/capabilities/remote/target/endtoend_test.go @@ -29,10 +29,8 @@ import ( func Test_RemoteTargetCapability_InsufficientCapabilityResponses(t *testing.T) { ctx := testutils.Context(t) - responseTest := func(t *testing.T, responseCh <-chan commoncap.CapabilityResponse, responseError error) { - require.NoError(t, responseError) - response := <-responseCh - assert.NotNil(t, response.Err) + responseTest := func(t *testing.T, responseCh commoncap.CapabilityResponse, responseError error) { + assert.NotNil(t, responseError) } capability := &TestCapability{} @@ -49,10 +47,8 @@ func Test_RemoteTargetCapability_InsufficientCapabilityResponses(t *testing.T) { func Test_RemoteTargetCapability_InsufficientWorkflowRequests(t *testing.T) { ctx := testutils.Context(t) - responseTest := func(t *testing.T, responseCh <-chan commoncap.CapabilityResponse, responseError error) { - require.NoError(t, responseError) - response := <-responseCh - assert.NotNil(t, response.Err) + responseTest := func(t *testing.T, responseCh commoncap.CapabilityResponse, responseError error) { + assert.NotNil(t, responseError) } timeOut := 10 * time.Minute @@ -71,9 +67,8 @@ func Test_RemoteTargetCapability_InsufficientWorkflowRequests(t *testing.T) { func Test_RemoteTargetCapability_TransmissionSchedules(t *testing.T) { ctx := testutils.Context(t) - responseTest := func(t *testing.T, responseCh <-chan commoncap.CapabilityResponse, responseError error) { + responseTest := func(t *testing.T, response commoncap.CapabilityResponse, responseError error) { require.NoError(t, responseError) - response := <-responseCh mp, err := response.Value.Unwrap() require.NoError(t, err) assert.Equal(t, "aValue1", mp.(map[string]any)["response"].(string)) @@ -103,9 +98,8 @@ func Test_RemoteTargetCapability_TransmissionSchedules(t *testing.T) { func Test_RemoteTargetCapability_DonTopologies(t *testing.T) { ctx := testutils.Context(t) - responseTest := func(t *testing.T, responseCh <-chan commoncap.CapabilityResponse, responseError error) { + responseTest := func(t *testing.T, response commoncap.CapabilityResponse, responseError error) { require.NoError(t, responseError) - response := <-responseCh mp, err := response.Value.Unwrap() require.NoError(t, err) assert.Equal(t, "aValue1", mp.(map[string]any)["response"].(string)) @@ -138,10 +132,8 @@ func Test_RemoteTargetCapability_DonTopologies(t *testing.T) { func Test_RemoteTargetCapability_CapabilityError(t *testing.T) { ctx := testutils.Context(t) - responseTest := func(t *testing.T, responseCh <-chan commoncap.CapabilityResponse, responseError error) { - require.NoError(t, responseError) - response := <-responseCh - assert.Equal(t, "failed to execute capability: an error", response.Err.Error()) + responseTest := func(t *testing.T, responseCh commoncap.CapabilityResponse, responseError error) { + assert.Equal(t, "failed to execute capability: an error", responseError.Error()) } capability := &TestErrorCapability{} @@ -158,10 +150,8 @@ func Test_RemoteTargetCapability_CapabilityError(t *testing.T) { func Test_RemoteTargetCapability_RandomCapabilityError(t *testing.T) { ctx := testutils.Context(t) - responseTest := func(t *testing.T, responseCh <-chan commoncap.CapabilityResponse, responseError error) { - require.NoError(t, responseError) - response := <-responseCh - assert.Equal(t, "request expired", response.Err.Error()) + responseTest := func(t *testing.T, response commoncap.CapabilityResponse, responseError error) { + assert.Equal(t, "request expired", responseError.Error()) } capability := &TestRandomErrorCapability{} @@ -177,7 +167,7 @@ func Test_RemoteTargetCapability_RandomCapabilityError(t *testing.T) { func testRemoteTarget(ctx context.Context, t *testing.T, underlying commoncap.TargetCapability, numWorkflowPeers int, workflowDonF uint8, workflowNodeTimeout time.Duration, numCapabilityPeers int, capabilityDonF uint8, capabilityNodeResponseTimeout time.Duration, transmissionSchedule *values.Map, - responseTest func(t *testing.T, responseCh <-chan commoncap.CapabilityResponse, responseError error)) { + responseTest func(t *testing.T, response commoncap.CapabilityResponse, responseError error)) { lggr := logger.TestLogger(t) capabilityPeers := make([]p2ptypes.PeerID, numCapabilityPeers) @@ -226,7 +216,7 @@ func testRemoteTarget(ctx context.Context, t *testing.T, underlying commoncap.Ta for i := 0; i < numCapabilityPeers; i++ { capabilityPeer := capabilityPeers[i] capabilityDispatcher := broker.NewDispatcherForNode(capabilityPeer) - capabilityNode := target.NewServer(capabilityPeer, underlying, capInfo, capDonInfo, workflowDONs, capabilityDispatcher, + capabilityNode := target.NewServer(&commoncap.RemoteTargetConfig{RequestHashExcludedAttributes: []string{}}, capabilityPeer, underlying, capInfo, capDonInfo, workflowDONs, capabilityDispatcher, capabilityNodeResponseTimeout, lggr) servicetest.Run(t, capabilityNode) broker.RegisterReceiverNode(capabilityPeer, capabilityNode) @@ -258,17 +248,17 @@ func testRemoteTarget(ctx context.Context, t *testing.T, underlying commoncap.Ta for _, caller := range workflowNodes { go func(caller commoncap.TargetCapability) { defer wg.Done() - responseCh, err := caller.Execute(ctx, + response, err := caller.Execute(ctx, commoncap.CapabilityRequest{ Metadata: commoncap.RequestMetadata{ - WorkflowID: "workflowID", - WorkflowExecutionID: "workflowExecutionID", + WorkflowID: workflowID1, + WorkflowExecutionID: workflowExecutionID1, }, Config: transmissionSchedule, Inputs: executeInputs, }) - responseTest(t, responseCh, err) + responseTest(t, response, err) }(caller) } @@ -276,67 +266,47 @@ func testRemoteTarget(ctx context.Context, t *testing.T, underlying commoncap.Ta } type testAsyncMessageBroker struct { - services.StateMachine - t *testing.T + services.Service + eng *services.Engine + t *testing.T nodes map[p2ptypes.PeerID]remotetypes.Receiver sendCh chan *remotetypes.MessageBody - - stopCh services.StopChan - wg sync.WaitGroup -} - -func (a *testAsyncMessageBroker) HealthReport() map[string]error { - return nil -} - -func (a *testAsyncMessageBroker) Name() string { - return "testAsyncMessageBroker" } func newTestAsyncMessageBroker(t *testing.T, sendChBufferSize int) *testAsyncMessageBroker { - return &testAsyncMessageBroker{ + b := &testAsyncMessageBroker{ t: t, nodes: make(map[p2ptypes.PeerID]remotetypes.Receiver), - stopCh: make(services.StopChan), sendCh: make(chan *remotetypes.MessageBody, sendChBufferSize), } -} - -func (a *testAsyncMessageBroker) Start(ctx context.Context) error { - return a.StartOnce("testAsyncMessageBroker", func() error { - a.wg.Add(1) - go func() { - defer a.wg.Done() - - for { - select { - case <-a.stopCh: - return - case msg := <-a.sendCh: - receiverId := toPeerID(msg.Receiver) - - receiver, ok := a.nodes[receiverId] - if !ok { - panic("server not found for peer id") - } - - receiver.Receive(tests.Context(a.t), msg) + b.Service, b.eng = services.Config{ + Name: "testAsyncMessageBroker", + Start: b.start, + }.NewServiceEngine(logger.TestLogger(t)) + return b +} + +func (a *testAsyncMessageBroker) start(ctx context.Context) error { + a.eng.Go(func(ctx context.Context) { + for { + select { + case <-ctx.Done(): + return + case msg := <-a.sendCh: + receiverId := toPeerID(msg.Receiver) + + receiver, ok := a.nodes[receiverId] + if !ok { + panic("server not found for peer id") } - } - }() - return nil - }) -} - -func (a *testAsyncMessageBroker) Close() error { - return a.StopOnce("testAsyncMessageBroker", func() error { - close(a.stopCh) - a.wg.Wait() - return nil + receiver.Receive(tests.Context(a.t), msg) + } + } }) + return nil } func (a *testAsyncMessageBroker) NewDispatcherForNode(nodePeerID p2ptypes.PeerID) remotetypes.Dispatcher { @@ -424,36 +394,31 @@ type TestCapability struct { abstractTestCapability } -func (t TestCapability) Execute(ctx context.Context, request commoncap.CapabilityRequest) (<-chan commoncap.CapabilityResponse, error) { - ch := make(chan commoncap.CapabilityResponse, 1) - +func (t TestCapability) Execute(ctx context.Context, request commoncap.CapabilityRequest) (commoncap.CapabilityResponse, error) { value := request.Inputs.Underlying["executeValue1"] - response, err := values.NewMap(map[string]any{"response": value}) if err != nil { - return nil, err + return commoncap.CapabilityResponse{}, err } - ch <- commoncap.CapabilityResponse{ + return commoncap.CapabilityResponse{ Value: response, - } - - return ch, nil + }, nil } type TestErrorCapability struct { abstractTestCapability } -func (t TestErrorCapability) Execute(ctx context.Context, request commoncap.CapabilityRequest) (<-chan commoncap.CapabilityResponse, error) { - return nil, errors.New("an error") +func (t TestErrorCapability) Execute(ctx context.Context, request commoncap.CapabilityRequest) (commoncap.CapabilityResponse, error) { + return commoncap.CapabilityResponse{}, errors.New("an error") } type TestRandomErrorCapability struct { abstractTestCapability } -func (t TestRandomErrorCapability) Execute(ctx context.Context, request commoncap.CapabilityRequest) (<-chan commoncap.CapabilityResponse, error) { - return nil, errors.New(uuid.New().String()) +func (t TestRandomErrorCapability) Execute(ctx context.Context, request commoncap.CapabilityRequest) (commoncap.CapabilityResponse, error) { + return commoncap.CapabilityResponse{}, errors.New(uuid.New().String()) } func NewP2PPeerID(t *testing.T) p2ptypes.PeerID { diff --git a/core/capabilities/remote/target/request/client_request.go b/core/capabilities/remote/target/request/client_request.go index 50a742c218..1a0d707146 100644 --- a/core/capabilities/remote/target/request/client_request.go +++ b/core/capabilities/remote/target/request/client_request.go @@ -22,9 +22,14 @@ import ( p2ptypes "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types" ) +type asyncCapabilityResponse struct { + capabilities.CapabilityResponse + Err error +} + type ClientRequest struct { cancelFn context.CancelFunc - responseCh chan commoncap.CapabilityResponse + responseCh chan asyncCapabilityResponse createdAt time.Time responseIDCount map[[32]byte]int errorCount map[string]int @@ -101,13 +106,13 @@ func NewClientRequest(ctx context.Context, lggr logger.Logger, req commoncap.Cap responseIDCount: make(map[[32]byte]int), errorCount: make(map[string]int), responseReceived: responseReceived, - responseCh: make(chan commoncap.CapabilityResponse, 1), + responseCh: make(chan asyncCapabilityResponse, 1), wg: wg, lggr: lggr, }, nil } -func (c *ClientRequest) ResponseChan() <-chan commoncap.CapabilityResponse { +func (c *ClientRequest) ResponseChan() <-chan asyncCapabilityResponse { return c.responseCh } @@ -121,11 +126,10 @@ func (c *ClientRequest) Cancel(err error) { c.mux.Lock() defer c.mux.Unlock() if !c.respSent { - c.sendResponse(commoncap.CapabilityResponse{Err: err}) + c.sendResponse(asyncCapabilityResponse{Err: err}) } } -// TODO OnMessage assumes that only one response is received from each peer, if streaming responses need to be supported this will need to be updated func (c *ClientRequest) OnMessage(_ context.Context, msg *types.MessageBody) error { c.mux.Lock() defer c.mux.Unlock() @@ -140,7 +144,10 @@ func (c *ClientRequest) OnMessage(_ context.Context, msg *types.MessageBody) err c.lggr.Debugw("OnMessage called for client request", "messageID", msg.MessageId) - sender := remote.ToPeerID(msg.Sender) + sender, err := remote.ToPeerID(msg.Sender) + if err != nil { + return fmt.Errorf("failed to convert message sender to PeerID: %w", err) + } received, expected := c.responseReceived[sender] if !expected { @@ -164,22 +171,22 @@ func (c *ClientRequest) OnMessage(_ context.Context, msg *types.MessageBody) err if c.responseIDCount[responseID] == c.requiredIdenticalResponses { capabilityResponse, err := pb.UnmarshalCapabilityResponse(msg.Payload) if err != nil { - c.sendResponse(commoncap.CapabilityResponse{Err: fmt.Errorf("failed to unmarshal capability response: %w", err)}) + c.sendResponse(asyncCapabilityResponse{Err: fmt.Errorf("failed to unmarshal capability response: %w", err)}) } else { - c.sendResponse(commoncap.CapabilityResponse{Value: capabilityResponse.Value}) + c.sendResponse(asyncCapabilityResponse{CapabilityResponse: commoncap.CapabilityResponse{Value: capabilityResponse.Value}}) } } } else { - c.lggr.Warnw("received error response", "error", msg.ErrorMsg) + c.lggr.Warnw("received error response", "error", remote.SanitizeLogString(msg.ErrorMsg)) c.errorCount[msg.ErrorMsg]++ if c.errorCount[msg.ErrorMsg] == c.requiredIdenticalResponses { - c.sendResponse(commoncap.CapabilityResponse{Err: errors.New(msg.ErrorMsg)}) + c.sendResponse(asyncCapabilityResponse{Err: errors.New(msg.ErrorMsg)}) } } return nil } -func (c *ClientRequest) sendResponse(response commoncap.CapabilityResponse) { +func (c *ClientRequest) sendResponse(response asyncCapabilityResponse) { c.responseCh <- response close(c.responseCh) c.respSent = true diff --git a/core/capabilities/remote/target/request/client_request_test.go b/core/capabilities/remote/target/request/client_request_test.go index 07f43dbc71..095c73e8ad 100644 --- a/core/capabilities/remote/target/request/client_request_test.go +++ b/core/capabilities/remote/target/request/client_request_test.go @@ -20,6 +20,11 @@ import ( p2ptypes "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types" ) +const ( + workflowID1 = "15c631d295ef5e32deb99a10ee6804bc4af13855687559d7ff6552ac6dbb2ce0" + workflowExecutionID1 = "95ef5e32deb99a10ee6804bc4af13855687559d7ff6552ac6dbb2ce0abbadeed" +) + func Test_ClientRequest_MessageValidation(t *testing.T) { lggr := logger.TestLogger(t) @@ -68,8 +73,8 @@ func Test_ClientRequest_MessageValidation(t *testing.T) { capabilityRequest := commoncap.CapabilityRequest{ Metadata: commoncap.RequestMetadata{ - WorkflowID: "workflowID", - WorkflowExecutionID: "workflowExecutionID", + WorkflowID: workflowID1, + WorkflowExecutionID: workflowExecutionID1, }, Inputs: executeInputs, Config: transmissionSchedule, @@ -79,7 +84,6 @@ func Test_ClientRequest_MessageValidation(t *testing.T) { require.NoError(t, err) capabilityResponse := commoncap.CapabilityResponse{ Value: m, - Err: nil, } rawResponse, err := pb.MarshalCapabilityResponse(capabilityResponse) @@ -112,7 +116,6 @@ func Test_ClientRequest_MessageValidation(t *testing.T) { require.NoError(t, err) capabilityResponse2 := commoncap.CapabilityResponse{ Value: nm, - Err: nil, } rawResponse2, err := pb.MarshalCapabilityResponse(capabilityResponse2) diff --git a/core/capabilities/remote/target/request/server_request.go b/core/capabilities/remote/target/request/server_request.go index b8ae05bc31..d23ba93a44 100644 --- a/core/capabilities/remote/target/request/server_request.go +++ b/core/capabilities/remote/target/request/server_request.go @@ -74,7 +74,11 @@ func (e *ServerRequest) OnMessage(ctx context.Context, msg *types.MessageBody) e return fmt.Errorf("sender missing from message") } - requester := remote.ToPeerID(msg.Sender) + requester, err := remote.ToPeerID(msg.Sender) + if err != nil { + return fmt.Errorf("failed to convert message sender to PeerID: %w", err) + } + if err := e.addRequester(requester); err != nil { return fmt.Errorf("failed to add requester to request: %w", err) } @@ -121,20 +125,19 @@ func (e *ServerRequest) executeRequest(ctx context.Context, payload []byte) erro } e.lggr.Debugw("executing capability", "metadata", capabilityRequest.Metadata) - capResponseCh, err := e.capability.Execute(ctxWithTimeout, capabilityRequest) + capResponse, err := e.capability.Execute(ctxWithTimeout, capabilityRequest) if err != nil { + e.lggr.Debugw("received execution error", "workflowExecutionID", capabilityRequest.Metadata.WorkflowExecutionID, "error", err) return fmt.Errorf("failed to execute capability: %w", err) } - // NOTE working on the assumption that the capability will only ever return one response from its channel - capResponse := <-capResponseCh responsePayload, err := pb.MarshalCapabilityResponse(capResponse) if err != nil { return fmt.Errorf("failed to marshal capability response: %w", err) } - e.lggr.Debugw("received execution results", "metadata", capabilityRequest.Metadata, "error", capResponse.Err) + e.lggr.Debugw("received execution results", "workflowExecutionID", capabilityRequest.Metadata.WorkflowExecutionID) e.setResult(responsePayload) return nil } diff --git a/core/capabilities/remote/target/request/server_request_test.go b/core/capabilities/remote/target/request/server_request_test.go index a35725d6b1..619142d0a6 100644 --- a/core/capabilities/remote/target/request/server_request_test.go +++ b/core/capabilities/remote/target/request/server_request_test.go @@ -240,28 +240,25 @@ type TestCapability struct { abstractTestCapability } -func (t TestCapability) Execute(ctx context.Context, request commoncap.CapabilityRequest) (<-chan commoncap.CapabilityResponse, error) { - ch := make(chan commoncap.CapabilityResponse, 1) - +func (t TestCapability) Execute(ctx context.Context, request commoncap.CapabilityRequest) (commoncap.CapabilityResponse, error) { value := request.Inputs.Underlying["executeValue1"] response, err := values.NewMap(map[string]any{"response": value}) if err != nil { - return nil, err - } - ch <- commoncap.CapabilityResponse{ - Value: response, + return commoncap.CapabilityResponse{}, err } - return ch, nil + return commoncap.CapabilityResponse{ + Value: response, + }, nil } type TestErrorCapability struct { abstractTestCapability } -func (t TestErrorCapability) Execute(ctx context.Context, request commoncap.CapabilityRequest) (<-chan commoncap.CapabilityResponse, error) { - return nil, errors.New("an error") +func (t TestErrorCapability) Execute(ctx context.Context, request commoncap.CapabilityRequest) (commoncap.CapabilityResponse, error) { + return commoncap.CapabilityResponse{}, errors.New("an error") } func NewP2PPeerID(t *testing.T) p2ptypes.PeerID { diff --git a/core/capabilities/remote/target/server.go b/core/capabilities/remote/target/server.go index ea9caf81ef..5324475b19 100644 --- a/core/capabilities/remote/target/server.go +++ b/core/capabilities/remote/target/server.go @@ -4,13 +4,17 @@ import ( "context" "crypto/sha256" "encoding/hex" + "fmt" "sync" "time" commoncap "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + "github.com/smartcontractkit/chainlink-common/pkg/capabilities/pb" "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote" "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote/target/request" "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote/types" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/validation" p2ptypes "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -24,7 +28,9 @@ import ( // server communicates with corresponding client on remote nodes. type server struct { services.StateMachine - lggr logger.Logger + lggr logger.Logger + + config *commoncap.RemoteTargetConfig peerID p2ptypes.PeerID underlying commoncap.TargetCapability capInfo commoncap.CapabilityInfo @@ -51,9 +57,14 @@ type requestAndMsgID struct { messageID string } -func NewServer(peerID p2ptypes.PeerID, underlying commoncap.TargetCapability, capInfo commoncap.CapabilityInfo, localDonInfo commoncap.DON, +func NewServer(config *commoncap.RemoteTargetConfig, peerID p2ptypes.PeerID, underlying commoncap.TargetCapability, capInfo commoncap.CapabilityInfo, localDonInfo commoncap.DON, workflowDONs map[uint32]commoncap.DON, dispatcher types.Dispatcher, requestTimeout time.Duration, lggr logger.Logger) *server { + if config == nil { + lggr.Info("no config provided, using default values") + config = &commoncap.RemoteTargetConfig{} + } return &server{ + config: config, underlying: underlying, peerID: peerID, capInfo: capInfo, @@ -120,17 +131,28 @@ func (r *server) Receive(ctx context.Context, msg *types.MessageBody) { r.receiveLock.Lock() defer r.receiveLock.Unlock() - r.lggr.Debugw("received request for msg", "msgId", msg.MessageId) if msg.Method != types.MethodExecute { - r.lggr.Errorw("received request for unsupported method type", "method", msg.Method) + r.lggr.Errorw("received request for unsupported method type", "method", remote.SanitizeLogString(msg.Method)) + return + } + + messageId, err := GetMessageID(msg) + if err != nil { + r.lggr.Errorw("invalid message id", "err", err, "id", remote.SanitizeLogString(string(msg.MessageId))) + return + } + + msgHash, err := r.getMessageHash(msg) + if err != nil { + r.lggr.Errorw("failed to get message hash", "err", err) return } // A request is uniquely identified by the message id and the hash of the payload to prevent a malicious // actor from sending a different payload with the same message id - messageId := GetMessageID(msg) - hash := sha256.Sum256(msg.Payload) - requestID := messageId + hex.EncodeToString(hash[:]) + requestID := messageId + hex.EncodeToString(msgHash[:]) + + r.lggr.Debugw("received request", "msgId", msg.MessageId, "requestID", requestID) if requestIDs, ok := r.messageIDToRequestIDsCount[messageId]; ok { requestIDs[requestID] = requestIDs[requestID] + 1 @@ -142,7 +164,7 @@ func (r *server) Receive(ctx context.Context, msg *types.MessageBody) { if len(requestIDs) > 1 { // This is a potential attack vector as well as a situation that will occur if the client is sending non-deterministic payloads // so a warning is logged - r.lggr.Warnw("received messages with the same id and different payloads", "messageID", messageId, "requestIDToCount", requestIDs) + r.lggr.Warnw("received messages with the same id and different payloads", "messageID", messageId, "lenRequestIDs", len(requestIDs)) } if _, ok := r.requestIDToRequest[requestID]; !ok { @@ -161,14 +183,38 @@ func (r *server) Receive(ctx context.Context, msg *types.MessageBody) { reqAndMsgID := r.requestIDToRequest[requestID] - err := reqAndMsgID.request.OnMessage(ctx, msg) + err = reqAndMsgID.request.OnMessage(ctx, msg) if err != nil { - r.lggr.Errorw("request failed to OnMessage new message", "request", reqAndMsgID, "err", err) + r.lggr.Errorw("request failed to OnMessage new message", "messageID", reqAndMsgID.messageID, "err", err) } } -func GetMessageID(msg *types.MessageBody) string { - return string(msg.MessageId) +func (r *server) getMessageHash(msg *types.MessageBody) ([32]byte, error) { + req, err := pb.UnmarshalCapabilityRequest(msg.Payload) + if err != nil { + return [32]byte{}, fmt.Errorf("failed to unmarshal capability request: %w", err) + } + + for _, path := range r.config.RequestHashExcludedAttributes { + if !req.Inputs.DeleteAtPath(path) { + return [32]byte{}, fmt.Errorf("failed to delete attribute from map at path: %s", path) + } + } + + reqBytes, err := pb.MarshalCapabilityRequest(req) + if err != nil { + return [32]byte{}, fmt.Errorf("failed to marshal capability request: %w", err) + } + hash := sha256.Sum256(reqBytes) + return hash, nil +} + +func GetMessageID(msg *types.MessageBody) (string, error) { + idStr := string(msg.MessageId) + if !validation.IsValidID(idStr) { + return "", fmt.Errorf("invalid message id") + } + return idStr, nil } func (r *server) Ready() error { diff --git a/core/capabilities/remote/target/server_test.go b/core/capabilities/remote/target/server_test.go index a5aa45efd0..505a2dcce5 100644 --- a/core/capabilities/remote/target/server_test.go +++ b/core/capabilities/remote/target/server_test.go @@ -2,6 +2,7 @@ package target_test import ( "context" + "strconv" "testing" "time" @@ -11,6 +12,7 @@ import ( commoncap "github.com/smartcontractkit/chainlink-common/pkg/capabilities" "github.com/smartcontractkit/chainlink-common/pkg/capabilities/pb" "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/values" "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote/target" remotetypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote/types" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" @@ -18,19 +20,55 @@ import ( p2ptypes "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types" ) +func Test_Server_ExcludesNonDeterministicInputAttributes(t *testing.T) { + ctx := testutils.Context(t) + + numCapabilityPeers := 4 + + callers, srvcs := testRemoteTargetServer(ctx, t, &commoncap.RemoteTargetConfig{RequestHashExcludedAttributes: []string{"signed_report.Signatures"}}, + &TestCapability{}, 10, 9, numCapabilityPeers, 3, 10*time.Minute) + + for idx, caller := range callers { + rawInputs := map[string]any{ + "signed_report": map[string]any{"Signatures": "sig" + strconv.Itoa(idx), "Price": 20}, + } + + inputs, err := values.NewMap(rawInputs) + require.NoError(t, err) + + _, err = caller.Execute(context.Background(), + commoncap.CapabilityRequest{ + Metadata: commoncap.RequestMetadata{ + WorkflowID: workflowID1, + WorkflowExecutionID: workflowExecutionID1, + }, + Inputs: inputs, + }) + require.NoError(t, err) + } + + for _, caller := range callers { + for i := 0; i < numCapabilityPeers; i++ { + msg := <-caller.receivedMessages + assert.Equal(t, remotetypes.Error_OK, msg.Error) + } + } + closeServices(t, srvcs) +} + func Test_Server_RespondsAfterSufficientRequests(t *testing.T) { ctx := testutils.Context(t) numCapabilityPeers := 4 - callers, srvcs := testRemoteTargetServer(ctx, t, &TestCapability{}, 10, 9, numCapabilityPeers, 3, 10*time.Minute) + callers, srvcs := testRemoteTargetServer(ctx, t, &commoncap.RemoteTargetConfig{}, &TestCapability{}, 10, 9, numCapabilityPeers, 3, 10*time.Minute) for _, caller := range callers { _, err := caller.Execute(context.Background(), commoncap.CapabilityRequest{ Metadata: commoncap.RequestMetadata{ - WorkflowID: "workflowID", - WorkflowExecutionID: "workflowExecutionID", + WorkflowID: workflowID1, + WorkflowExecutionID: workflowExecutionID1, }, }) require.NoError(t, err) @@ -50,14 +88,14 @@ func Test_Server_InsufficientCallers(t *testing.T) { numCapabilityPeers := 4 - callers, srvcs := testRemoteTargetServer(ctx, t, &TestCapability{}, 10, 10, numCapabilityPeers, 3, 100*time.Millisecond) + callers, srvcs := testRemoteTargetServer(ctx, t, &commoncap.RemoteTargetConfig{}, &TestCapability{}, 10, 10, numCapabilityPeers, 3, 100*time.Millisecond) for _, caller := range callers { _, err := caller.Execute(context.Background(), commoncap.CapabilityRequest{ Metadata: commoncap.RequestMetadata{ - WorkflowID: "workflowID", - WorkflowExecutionID: "workflowExecutionID", + WorkflowID: workflowID1, + WorkflowExecutionID: workflowExecutionID1, }, }) require.NoError(t, err) @@ -77,14 +115,14 @@ func Test_Server_CapabilityError(t *testing.T) { numCapabilityPeers := 4 - callers, srvcs := testRemoteTargetServer(ctx, t, &TestErrorCapability{}, 10, 9, numCapabilityPeers, 3, 100*time.Millisecond) + callers, srvcs := testRemoteTargetServer(ctx, t, &commoncap.RemoteTargetConfig{}, &TestErrorCapability{}, 10, 9, numCapabilityPeers, 3, 100*time.Millisecond) for _, caller := range callers { _, err := caller.Execute(context.Background(), commoncap.CapabilityRequest{ Metadata: commoncap.RequestMetadata{ - WorkflowID: "workflowID", - WorkflowExecutionID: "workflowExecutionID", + WorkflowID: workflowID1, + WorkflowExecutionID: workflowExecutionID1, }, }) require.NoError(t, err) @@ -100,6 +138,7 @@ func Test_Server_CapabilityError(t *testing.T) { } func testRemoteTargetServer(ctx context.Context, t *testing.T, + config *commoncap.RemoteTargetConfig, underlying commoncap.TargetCapability, numWorkflowPeers int, workflowDonF uint8, numCapabilityPeers int, capabilityDonF uint8, capabilityNodeResponseTimeout time.Duration) ([]*serverTestClient, []services.Service) { @@ -150,7 +189,7 @@ func testRemoteTargetServer(ctx context.Context, t *testing.T, for i := 0; i < numCapabilityPeers; i++ { capabilityPeer := capabilityPeers[i] capabilityDispatcher := broker.NewDispatcherForNode(capabilityPeer) - capabilityNode := target.NewServer(capabilityPeer, underlying, capInfo, capDonInfo, workflowDONs, capabilityDispatcher, + capabilityNode := target.NewServer(config, capabilityPeer, underlying, capInfo, capDonInfo, workflowDONs, capabilityDispatcher, capabilityNodeResponseTimeout, lggr) require.NoError(t, capabilityNode.Start(ctx)) broker.RegisterReceiverNode(capabilityPeer, capabilityNode) diff --git a/core/capabilities/remote/trigger_publisher.go b/core/capabilities/remote/trigger_publisher.go index 35ce41118f..e5a46c8791 100644 --- a/core/capabilities/remote/trigger_publisher.go +++ b/core/capabilities/remote/trigger_publisher.go @@ -2,14 +2,16 @@ package remote import ( "context" - sync "sync" + "crypto/sha256" + "encoding/binary" + "sync" "time" - "github.com/smartcontractkit/chainlink-common/pkg/capabilities" commoncap "github.com/smartcontractkit/chainlink-common/pkg/capabilities" "github.com/smartcontractkit/chainlink-common/pkg/capabilities/pb" "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote/types" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/validation" "github.com/smartcontractkit/chainlink/v2/core/logger" p2ptypes "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types" ) @@ -21,18 +23,22 @@ import ( // // TriggerPublisher communicates with corresponding TriggerSubscribers on remote nodes. type triggerPublisher struct { - config capabilities.RemoteTriggerConfig - underlying commoncap.TriggerCapability - capInfo commoncap.CapabilityInfo - capDonInfo commoncap.DON - workflowDONs map[uint32]commoncap.DON - dispatcher types.Dispatcher - messageCache *messageCache[registrationKey, p2ptypes.PeerID] - registrations map[registrationKey]*pubRegState - mu sync.RWMutex // protects messageCache and registrations - stopCh services.StopChan - wg sync.WaitGroup - lggr logger.Logger + config *commoncap.RemoteTriggerConfig + underlying commoncap.TriggerCapability + capInfo commoncap.CapabilityInfo + capDonInfo commoncap.DON + workflowDONs map[uint32]commoncap.DON + membersCache map[uint32]map[p2ptypes.PeerID]bool + dispatcher types.Dispatcher + messageCache *messageCache[registrationKey, p2ptypes.PeerID] + registrations map[registrationKey]*pubRegState + mu sync.RWMutex // protects messageCache and registrations + batchingQueue map[[32]byte]*batchedResponse + batchingEnabled bool + bqMu sync.Mutex // protects batchingQueue + stopCh services.StopChan + wg sync.WaitGroup + lggr logger.Logger } type registrationKey struct { @@ -41,42 +47,74 @@ type registrationKey struct { } type pubRegState struct { - callback <-chan commoncap.CapabilityResponse - request commoncap.CapabilityRequest + callback <-chan commoncap.TriggerResponse + request commoncap.TriggerRegistrationRequest } -var _ types.Receiver = &triggerPublisher{} -var _ services.Service = &triggerPublisher{} +type batchedResponse struct { + rawResponse []byte + callerDonID uint32 + triggerEventID string + workflowIDs []string +} + +var _ types.ReceiverService = &triggerPublisher{} + +const minAllowedBatchCollectionPeriod = 10 * time.Millisecond -func NewTriggerPublisher(config capabilities.RemoteTriggerConfig, underlying commoncap.TriggerCapability, capInfo commoncap.CapabilityInfo, capDonInfo commoncap.DON, workflowDONs map[uint32]commoncap.DON, dispatcher types.Dispatcher, lggr logger.Logger) *triggerPublisher { +func NewTriggerPublisher(config *commoncap.RemoteTriggerConfig, underlying commoncap.TriggerCapability, capInfo commoncap.CapabilityInfo, capDonInfo commoncap.DON, workflowDONs map[uint32]commoncap.DON, dispatcher types.Dispatcher, lggr logger.Logger) *triggerPublisher { + if config == nil { + lggr.Info("no config provided, using default values") + config = &commoncap.RemoteTriggerConfig{} + } config.ApplyDefaults() + membersCache := make(map[uint32]map[p2ptypes.PeerID]bool) + for id, don := range workflowDONs { + cache := make(map[p2ptypes.PeerID]bool) + for _, member := range don.Members { + cache[member] = true + } + membersCache[id] = cache + } return &triggerPublisher{ - config: config, - underlying: underlying, - capInfo: capInfo, - capDonInfo: capDonInfo, - workflowDONs: workflowDONs, - dispatcher: dispatcher, - messageCache: NewMessageCache[registrationKey, p2ptypes.PeerID](), - registrations: make(map[registrationKey]*pubRegState), - stopCh: make(services.StopChan), - lggr: lggr.Named("TriggerPublisher"), + config: config, + underlying: underlying, + capInfo: capInfo, + capDonInfo: capDonInfo, + workflowDONs: workflowDONs, + membersCache: membersCache, + dispatcher: dispatcher, + messageCache: NewMessageCache[registrationKey, p2ptypes.PeerID](), + registrations: make(map[registrationKey]*pubRegState), + batchingQueue: make(map[[32]byte]*batchedResponse), + batchingEnabled: config.MaxBatchSize > 1 && config.BatchCollectionPeriod >= minAllowedBatchCollectionPeriod, + stopCh: make(services.StopChan), + lggr: lggr.Named("TriggerPublisher"), } } func (p *triggerPublisher) Start(ctx context.Context) error { p.wg.Add(1) go p.registrationCleanupLoop() + if p.batchingEnabled { + p.wg.Add(1) + go p.batchingLoop() + } p.lggr.Info("TriggerPublisher started") return nil } func (p *triggerPublisher) Receive(_ context.Context, msg *types.MessageBody) { - sender := ToPeerID(msg.Sender) + sender, err := ToPeerID(msg.Sender) + if err != nil { + p.lggr.Errorw("failed to convert message sender to PeerID", "err", err) + return + } + if msg.Method == types.MethodRegisterTrigger { - req, err := pb.UnmarshalCapabilityRequest(msg.Payload) + req, err := pb.UnmarshalTriggerRegistrationRequest(msg.Payload) if err != nil { - p.lggr.Errorw("failed to unmarshal capability request", "capabilityId", p.capInfo.ID, "err", err) + p.lggr.Errorw("failed to unmarshal trigger registration request", "capabilityId", p.capInfo.ID, "err", err) return } callerDon, ok := p.workflowDONs[msg.CallerDonId] @@ -84,6 +122,14 @@ func (p *triggerPublisher) Receive(_ context.Context, msg *types.MessageBody) { p.lggr.Errorw("received a message from unsupported workflow DON", "capabilityId", p.capInfo.ID, "callerDonId", msg.CallerDonId) return } + if !p.membersCache[msg.CallerDonId][sender] { + p.lggr.Errorw("sender not a member of its workflow DON", "capabilityId", p.capInfo.ID, "callerDonId", msg.CallerDonId, "sender", sender) + return + } + if err = validation.ValidateWorkflowOrExecutionID(req.Metadata.WorkflowID); err != nil { + p.lggr.Errorw("received trigger request with invalid workflow ID", "capabilityId", p.capInfo.ID, "workflowId", SanitizeLogString(req.Metadata.WorkflowID), "err", err) + return + } p.lggr.Debugw("received trigger registration", "capabilityId", p.capInfo.ID, "workflowId", req.Metadata.WorkflowID, "sender", sender) key := registrationKey{msg.CallerDonId, req.Metadata.WorkflowID} nowMs := time.Now().UnixMilli() @@ -107,7 +153,7 @@ func (p *triggerPublisher) Receive(_ context.Context, msg *types.MessageBody) { p.lggr.Errorw("failed to aggregate trigger registrations", "capabilityId", p.capInfo.ID, "workflowId", req.Metadata.WorkflowID, "err", err) return } - unmarshaled, err := pb.UnmarshalCapabilityRequest(aggregated) + unmarshaled, err := pb.UnmarshalTriggerRegistrationRequest(aggregated) if err != nil { p.lggr.Errorw("failed to unmarshal request", "capabilityId", p.capInfo.ID, "err", err) return @@ -127,7 +173,7 @@ func (p *triggerPublisher) Receive(_ context.Context, msg *types.MessageBody) { p.lggr.Errorw("failed to register trigger", "capabilityId", p.capInfo.ID, "workflowId", req.Metadata.WorkflowID, "err", err) } } else { - p.lggr.Errorw("received trigger request with unknown method", "method", msg.Method, "sender", sender) + p.lggr.Errorw("received trigger request with unknown method", "method", SanitizeLogString(msg.Method), "sender", sender) } } @@ -141,7 +187,7 @@ func (p *triggerPublisher) registrationCleanupLoop() { return case <-ticker.C: now := time.Now().UnixMilli() - p.mu.RLock() + p.mu.Lock() for key, req := range p.registrations { callerDon := p.workflowDONs[key.callerDonId] ready, _ := p.messageCache.Ready(key, uint32(2*callerDon.F+1), now-p.config.RegistrationExpiry.Milliseconds(), false) @@ -156,12 +202,12 @@ func (p *triggerPublisher) registrationCleanupLoop() { p.messageCache.Delete(key) } } - p.mu.RUnlock() + p.mu.Unlock() } } } -func (p *triggerPublisher) triggerEventLoop(callbackCh <-chan commoncap.CapabilityResponse, key registrationKey) { +func (p *triggerPublisher) triggerEventLoop(callbackCh <-chan commoncap.TriggerResponse, key registrationKey) { defer p.wg.Done() for { select { @@ -172,38 +218,100 @@ func (p *triggerPublisher) triggerEventLoop(callbackCh <-chan commoncap.Capabili p.lggr.Infow("triggerEventLoop channel closed", "capabilityId", p.capInfo.ID, "workflowId", key.workflowId) return } - triggerEvent := capabilities.TriggerEvent{} - err := response.Value.UnwrapTo(&triggerEvent) - if err != nil { - p.lggr.Errorw("can't unwrap trigger event", "capabilityId", p.capInfo.ID, "workflowId", key.workflowId, "err", err) - break - } + triggerEvent := response.Event p.lggr.Debugw("received trigger event", "capabilityId", p.capInfo.ID, "workflowId", key.workflowId, "triggerEventID", triggerEvent.ID) - marshaled, err := pb.MarshalCapabilityResponse(response) + marshaledResponse, err := pb.MarshalTriggerResponse(response) if err != nil { p.lggr.Debugw("can't marshal trigger event", "err", err) break } - msg := &types.MessageBody{ - CapabilityId: p.capInfo.ID, - CapabilityDonId: p.capDonInfo.ID, - CallerDonId: key.callerDonId, - Method: types.MethodTriggerEvent, - Payload: marshaled, - Metadata: &types.MessageBody_TriggerEventMetadata{ - TriggerEventMetadata: &types.TriggerEventMetadata{ - // NOTE: optionally introduce batching across workflows as an optimization - WorkflowIds: []string{key.workflowId}, - TriggerEventId: triggerEvent.ID, - }, + + if p.batchingEnabled { + p.enqueueForBatching(marshaledResponse, key, triggerEvent.ID) + } else { + // a single-element "batch" + p.sendBatch(&batchedResponse{ + rawResponse: marshaledResponse, + callerDonID: key.callerDonId, + triggerEventID: triggerEvent.ID, + workflowIDs: []string{key.workflowId}, + }) + } + } + } +} + +func (p *triggerPublisher) enqueueForBatching(rawResponse []byte, key registrationKey, triggerEventID string) { + // put in batching queue, group by hash(callerDonId, triggerEventID, response) + combined := make([]byte, 4) + binary.LittleEndian.PutUint32(combined, key.callerDonId) + combined = append(combined, []byte(triggerEventID)...) + combined = append(combined, rawResponse...) + sha := sha256.Sum256(combined) + p.bqMu.Lock() + elem, exists := p.batchingQueue[sha] + if !exists { + elem = &batchedResponse{ + rawResponse: rawResponse, + callerDonID: key.callerDonId, + triggerEventID: triggerEventID, + workflowIDs: []string{key.workflowId}, + } + p.batchingQueue[sha] = elem + } else { + elem.workflowIDs = append(elem.workflowIDs, key.workflowId) + } + p.bqMu.Unlock() +} + +func (p *triggerPublisher) sendBatch(resp *batchedResponse) { + for len(resp.workflowIDs) > 0 { + idBatch := resp.workflowIDs + if p.batchingEnabled && int64(len(idBatch)) > int64(p.config.MaxBatchSize) { + idBatch = idBatch[:p.config.MaxBatchSize] + resp.workflowIDs = resp.workflowIDs[p.config.MaxBatchSize:] + } else { + resp.workflowIDs = nil + } + msg := &types.MessageBody{ + CapabilityId: p.capInfo.ID, + CapabilityDonId: p.capDonInfo.ID, + CallerDonId: resp.callerDonID, + Method: types.MethodTriggerEvent, + Payload: resp.rawResponse, + Metadata: &types.MessageBody_TriggerEventMetadata{ + TriggerEventMetadata: &types.TriggerEventMetadata{ + WorkflowIds: idBatch, + TriggerEventId: resp.triggerEventID, }, + }, + } + // NOTE: send to all nodes by default, introduce different strategies later (KS-76) + for _, peerID := range p.workflowDONs[resp.callerDonID].Members { + err := p.dispatcher.Send(peerID, msg) + if err != nil { + p.lggr.Errorw("failed to send trigger event", "capabilityId", p.capInfo.ID, "peerID", peerID, "err", err) } - // NOTE: send to all nodes by default, introduce different strategies later (KS-76) - for _, peerID := range p.workflowDONs[key.callerDonId].Members { - err = p.dispatcher.Send(peerID, msg) - if err != nil { - p.lggr.Errorw("failed to send trigger event", "capabilityId", p.capInfo.ID, "peerID", peerID, "err", err) - } + } + } +} + +func (p *triggerPublisher) batchingLoop() { + defer p.wg.Done() + ticker := time.NewTicker(p.config.BatchCollectionPeriod) + defer ticker.Stop() + for { + select { + case <-p.stopCh: + return + case <-ticker.C: + p.bqMu.Lock() + queue := p.batchingQueue + p.batchingQueue = make(map[[32]byte]*batchedResponse) + p.bqMu.Unlock() + + for _, elem := range queue { + p.sendBatch(elem) } } } diff --git a/core/capabilities/remote/trigger_publisher_test.go b/core/capabilities/remote/trigger_publisher_test.go index 1e3000d20c..8d078dc1aa 100644 --- a/core/capabilities/remote/trigger_publisher_test.go +++ b/core/capabilities/remote/trigger_publisher_test.go @@ -5,94 +5,176 @@ import ( "testing" "time" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink-common/pkg/capabilities" commoncap "github.com/smartcontractkit/chainlink-common/pkg/capabilities" "github.com/smartcontractkit/chainlink-common/pkg/capabilities/pb" "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote" remotetypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote/types" - remoteMocks "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote/types/mocks" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote/types/mocks" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" p2ptypes "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types" ) +const capID = "cap_id@1" + func TestTriggerPublisher_Register(t *testing.T) { + ctx := testutils.Context(t) + capabilityDONID, workflowDONID := uint32(1), uint32(2) + + underlyingTriggerCap, publisher, _, peers := newServices(t, capabilityDONID, workflowDONID, 1) + + // invalid sender case - node 0 is not a member of the workflow DON, registration shoudn't happen + regEvent := newRegisterTriggerMessage(t, workflowDONID, peers[0]) + publisher.Receive(ctx, regEvent) + require.Empty(t, underlyingTriggerCap.registrationsCh) + + // valid registration + regEvent = newRegisterTriggerMessage(t, workflowDONID, peers[1]) + publisher.Receive(ctx, regEvent) + require.NotEmpty(t, underlyingTriggerCap.registrationsCh) + forwarded := <-underlyingTriggerCap.registrationsCh + require.Equal(t, workflowID1, forwarded.Metadata.WorkflowID) + + require.NoError(t, publisher.Close()) +} + +func TestTriggerPublisher_ReceiveTriggerEvents_NoBatching(t *testing.T) { + ctx := testutils.Context(t) + capabilityDONID, workflowDONID := uint32(1), uint32(2) + + underlyingTriggerCap, publisher, dispatcher, peers := newServices(t, capabilityDONID, workflowDONID, 1) + regEvent := newRegisterTriggerMessage(t, workflowDONID, peers[1]) + publisher.Receive(ctx, regEvent) + require.NotEmpty(t, underlyingTriggerCap.registrationsCh) + + // send a trigger event and expect that it gets delivered right away + underlyingTriggerCap.eventCh <- commoncap.TriggerResponse{} + awaitOutgoingMessageCh := make(chan struct{}) + dispatcher.On("Send", peers[1], mock.Anything).Run(func(args mock.Arguments) { + awaitOutgoingMessageCh <- struct{}{} + }).Return(nil) + <-awaitOutgoingMessageCh + + require.NoError(t, publisher.Close()) +} + +func TestTriggerPublisher_ReceiveTriggerEvents_BatchingEnabled(t *testing.T) { + ctx := testutils.Context(t) + capabilityDONID, workflowDONID := uint32(1), uint32(2) + + underlyingTriggerCap, publisher, dispatcher, peers := newServices(t, capabilityDONID, workflowDONID, 2) + regEvent := newRegisterTriggerMessage(t, workflowDONID, peers[1]) + publisher.Receive(ctx, regEvent) + require.NotEmpty(t, underlyingTriggerCap.registrationsCh) + + // send two trigger events and expect them to be delivered in a batch + underlyingTriggerCap.eventCh <- commoncap.TriggerResponse{} + underlyingTriggerCap.eventCh <- commoncap.TriggerResponse{} + awaitOutgoingMessageCh := make(chan struct{}) + dispatcher.On("Send", peers[1], mock.Anything).Run(func(args mock.Arguments) { + msg := args.Get(1).(*remotetypes.MessageBody) + require.Equal(t, capID, msg.CapabilityId) + require.Equal(t, remotetypes.MethodTriggerEvent, msg.Method) + require.NotEmpty(t, msg.Payload) + metadata := msg.Metadata.(*remotetypes.MessageBody_TriggerEventMetadata) + require.Len(t, metadata.TriggerEventMetadata.WorkflowIds, 2) + awaitOutgoingMessageCh <- struct{}{} + }).Return(nil).Once() + <-awaitOutgoingMessageCh + + // if there are fewer pending event than the batch size, + // the events should still be sent after the batch collection period + underlyingTriggerCap.eventCh <- commoncap.TriggerResponse{} + dispatcher.On("Send", peers[1], mock.Anything).Run(func(args mock.Arguments) { + msg := args.Get(1).(*remotetypes.MessageBody) + metadata := msg.Metadata.(*remotetypes.MessageBody_TriggerEventMetadata) + require.Len(t, metadata.TriggerEventMetadata.WorkflowIds, 1) + awaitOutgoingMessageCh <- struct{}{} + }).Return(nil).Once() + <-awaitOutgoingMessageCh + + require.NoError(t, publisher.Close()) +} + +func newServices(t *testing.T, capabilityDONID uint32, workflowDONID uint32, maxBatchSize uint32) (*testTrigger, remotetypes.ReceiverService, *mocks.Dispatcher, []p2ptypes.PeerID) { lggr := logger.TestLogger(t) ctx := testutils.Context(t) capInfo := commoncap.CapabilityInfo{ - ID: "cap_id@1", + ID: capID, CapabilityType: commoncap.CapabilityTypeTrigger, Description: "Remote Trigger", } - p1 := p2ptypes.PeerID{} - require.NoError(t, p1.UnmarshalText([]byte(peerID1))) - p2 := p2ptypes.PeerID{} - require.NoError(t, p2.UnmarshalText([]byte(peerID2))) + peers := make([]p2ptypes.PeerID, 2) + require.NoError(t, peers[0].UnmarshalText([]byte(peerID1))) + require.NoError(t, peers[1].UnmarshalText([]byte(peerID2))) capDonInfo := commoncap.DON{ - ID: 1, - Members: []p2ptypes.PeerID{p1}, + ID: capabilityDONID, + Members: []p2ptypes.PeerID{peers[0]}, // peer 0 is in the capability DON F: 0, } workflowDonInfo := commoncap.DON{ - ID: 2, - Members: []p2ptypes.PeerID{p2}, + ID: workflowDONID, + Members: []p2ptypes.PeerID{peers[1]}, // peer 1 is in the workflow DON F: 0, } - dispatcher := remoteMocks.NewDispatcher(t) - config := capabilities.RemoteTriggerConfig{ + dispatcher := mocks.NewDispatcher(t) + config := &commoncap.RemoteTriggerConfig{ RegistrationRefresh: 100 * time.Millisecond, RegistrationExpiry: 100 * time.Second, MinResponsesToAggregate: 1, MessageExpiry: 100 * time.Second, + MaxBatchSize: maxBatchSize, + BatchCollectionPeriod: time.Second, } workflowDONs := map[uint32]commoncap.DON{ workflowDonInfo.ID: workflowDonInfo, } underlying := &testTrigger{ info: capInfo, - registrationsCh: make(chan commoncap.CapabilityRequest, 2), + registrationsCh: make(chan commoncap.TriggerRegistrationRequest, 2), + eventCh: make(chan commoncap.TriggerResponse, 2), } publisher := remote.NewTriggerPublisher(config, underlying, capInfo, capDonInfo, workflowDONs, dispatcher, lggr) require.NoError(t, publisher.Start(ctx)) + return underlying, publisher, dispatcher, peers +} +func newRegisterTriggerMessage(t *testing.T, callerDonID uint32, sender p2ptypes.PeerID) *remotetypes.MessageBody { // trigger registration event - capRequest := commoncap.CapabilityRequest{ + triggerRequest := commoncap.TriggerRegistrationRequest{ Metadata: commoncap.RequestMetadata{ WorkflowID: workflowID1, }, } - marshaled, err := pb.MarshalCapabilityRequest(capRequest) + marshaled, err := pb.MarshalTriggerRegistrationRequest(triggerRequest) require.NoError(t, err) - regEvent := &remotetypes.MessageBody{ - Sender: p1[:], + return &remotetypes.MessageBody{ + Sender: sender[:], Method: remotetypes.MethodRegisterTrigger, - CallerDonId: workflowDonInfo.ID, + CallerDonId: callerDonID, Payload: marshaled, } - publisher.Receive(ctx, regEvent) - forwarded := <-underlying.registrationsCh - require.Equal(t, capRequest.Metadata.WorkflowID, forwarded.Metadata.WorkflowID) - - require.NoError(t, publisher.Close()) } type testTrigger struct { info commoncap.CapabilityInfo - registrationsCh chan commoncap.CapabilityRequest + registrationsCh chan commoncap.TriggerRegistrationRequest + eventCh chan commoncap.TriggerResponse } -func (t *testTrigger) Info(_ context.Context) (commoncap.CapabilityInfo, error) { - return t.info, nil +func (tr *testTrigger) Info(_ context.Context) (commoncap.CapabilityInfo, error) { + return tr.info, nil } -func (t *testTrigger) RegisterTrigger(_ context.Context, request commoncap.CapabilityRequest) (<-chan commoncap.CapabilityResponse, error) { - t.registrationsCh <- request - return nil, nil +func (tr *testTrigger) RegisterTrigger(_ context.Context, request commoncap.TriggerRegistrationRequest) (<-chan commoncap.TriggerResponse, error) { + tr.registrationsCh <- request + return tr.eventCh, nil } -func (t *testTrigger) UnregisterTrigger(_ context.Context, request commoncap.CapabilityRequest) error { +func (tr *testTrigger) UnregisterTrigger(_ context.Context, request commoncap.TriggerRegistrationRequest) error { return nil } diff --git a/core/capabilities/remote/trigger_subscriber.go b/core/capabilities/remote/trigger_subscriber.go index 0ccbf37c61..967b59258a 100644 --- a/core/capabilities/remote/trigger_subscriber.go +++ b/core/capabilities/remote/trigger_subscriber.go @@ -6,7 +6,6 @@ import ( sync "sync" "time" - "github.com/smartcontractkit/chainlink-common/pkg/capabilities" commoncap "github.com/smartcontractkit/chainlink-common/pkg/capabilities" "github.com/smartcontractkit/chainlink-common/pkg/capabilities/pb" "github.com/smartcontractkit/chainlink-common/pkg/services" @@ -23,11 +22,11 @@ import ( // // TriggerSubscriber communicates with corresponding TriggerReceivers on remote nodes. type triggerSubscriber struct { - config capabilities.RemoteTriggerConfig + config *commoncap.RemoteTriggerConfig capInfo commoncap.CapabilityInfo - capDonInfo capabilities.DON + capDonInfo commoncap.DON capDonMembers map[p2ptypes.PeerID]struct{} - localDonInfo capabilities.DON + localDonInfo commoncap.DON dispatcher types.Dispatcher aggregator types.Aggregator messageCache *messageCache[triggerEventKey, p2ptypes.PeerID] @@ -44,7 +43,7 @@ type triggerEventKey struct { } type subRegState struct { - callback chan commoncap.CapabilityResponse + callback chan commoncap.TriggerResponse rawRequest []byte } @@ -53,13 +52,20 @@ var _ types.Receiver = &triggerSubscriber{} var _ services.Service = &triggerSubscriber{} // TODO makes this configurable with a default -const defaultSendChannelBufferSize = 1000 +const ( + defaultSendChannelBufferSize = 1000 + maxBatchedWorkflowIDs = 1000 +) -func NewTriggerSubscriber(config capabilities.RemoteTriggerConfig, capInfo commoncap.CapabilityInfo, capDonInfo capabilities.DON, localDonInfo capabilities.DON, dispatcher types.Dispatcher, aggregator types.Aggregator, lggr logger.Logger) *triggerSubscriber { +func NewTriggerSubscriber(config *commoncap.RemoteTriggerConfig, capInfo commoncap.CapabilityInfo, capDonInfo commoncap.DON, localDonInfo commoncap.DON, dispatcher types.Dispatcher, aggregator types.Aggregator, lggr logger.Logger) *triggerSubscriber { if aggregator == nil { lggr.Warnw("no aggregator provided, using default MODE aggregator", "capabilityId", capInfo.ID) aggregator = NewDefaultModeAggregator(uint32(capDonInfo.F + 1)) } + if config == nil { + lggr.Info("no config provided, using default values") + config = &commoncap.RemoteTriggerConfig{} + } config.ApplyDefaults() capDonMembers := make(map[p2ptypes.PeerID]struct{}) for _, member := range capDonInfo.Members { @@ -92,8 +98,8 @@ func (s *triggerSubscriber) Info(ctx context.Context) (commoncap.CapabilityInfo, return s.capInfo, nil } -func (s *triggerSubscriber) RegisterTrigger(ctx context.Context, request commoncap.CapabilityRequest) (<-chan commoncap.CapabilityResponse, error) { - rawRequest, err := pb.MarshalCapabilityRequest(request) +func (s *triggerSubscriber) RegisterTrigger(ctx context.Context, request commoncap.TriggerRegistrationRequest) (<-chan commoncap.TriggerResponse, error) { + rawRequest, err := pb.MarshalTriggerRegistrationRequest(request) if err != nil { return nil, err } @@ -107,7 +113,7 @@ func (s *triggerSubscriber) RegisterTrigger(ctx context.Context, request commonc regState, ok := s.registeredWorkflows[request.Metadata.WorkflowID] if !ok { regState = &subRegState{ - callback: make(chan commoncap.CapabilityResponse, defaultSendChannelBufferSize), + callback: make(chan commoncap.TriggerResponse, defaultSendChannelBufferSize), rawRequest: rawRequest, } s.registeredWorkflows[request.Metadata.WorkflowID] = regState @@ -154,7 +160,7 @@ func (s *triggerSubscriber) registrationLoop() { } } -func (s *triggerSubscriber) UnregisterTrigger(ctx context.Context, request commoncap.CapabilityRequest) error { +func (s *triggerSubscriber) UnregisterTrigger(ctx context.Context, request commoncap.TriggerRegistrationRequest) error { s.mu.Lock() defer s.mu.Unlock() @@ -169,7 +175,12 @@ func (s *triggerSubscriber) UnregisterTrigger(ctx context.Context, request commo } func (s *triggerSubscriber) Receive(_ context.Context, msg *types.MessageBody) { - sender := ToPeerID(msg.Sender) + sender, err := ToPeerID(msg.Sender) + if err != nil { + s.lggr.Errorw("failed to convert message sender to PeerID", "err", err) + return + } + if _, found := s.capDonMembers[sender]; !found { s.lggr.Errorw("received message from unexpected node", "capabilityId", s.capInfo.ID, "sender", sender) return @@ -180,12 +191,16 @@ func (s *triggerSubscriber) Receive(_ context.Context, msg *types.MessageBody) { s.lggr.Errorw("received message with invalid trigger metadata", "capabilityId", s.capInfo.ID, "sender", sender) return } + if len(meta.WorkflowIds) > maxBatchedWorkflowIDs { + s.lggr.Errorw("received message with too many workflow IDs - truncating", "capabilityId", s.capInfo.ID, "nWorkflows", len(meta.WorkflowIds), "sender", sender) + meta.WorkflowIds = meta.WorkflowIds[:maxBatchedWorkflowIDs] + } for _, workflowId := range meta.WorkflowIds { s.mu.RLock() registration, found := s.registeredWorkflows[workflowId] s.mu.RUnlock() if !found { - s.lggr.Errorw("received message for unregistered workflow", "capabilityId", s.capInfo.ID, "workflowID", workflowId, "sender", sender) + s.lggr.Errorw("received message for unregistered workflow", "capabilityId", s.capInfo.ID, "workflowID", SanitizeLogString(workflowId), "sender", sender) continue } key := triggerEventKey{ @@ -193,10 +208,10 @@ func (s *triggerSubscriber) Receive(_ context.Context, msg *types.MessageBody) { workflowId: workflowId, } nowMs := time.Now().UnixMilli() - s.mu.RLock() + s.mu.Lock() creationTs := s.messageCache.Insert(key, sender, nowMs, msg.Payload) ready, payloads := s.messageCache.Ready(key, s.config.MinResponsesToAggregate, nowMs-s.config.MessageExpiry.Milliseconds(), true) - s.mu.RUnlock() + s.mu.Unlock() if nowMs-creationTs > s.config.RegistrationExpiry.Milliseconds() { s.lggr.Warnw("received trigger event for an expired ID", "triggerEventID", meta.TriggerEventId, "capabilityId", s.capInfo.ID, "workflowId", workflowId, "sender", sender) continue @@ -213,7 +228,7 @@ func (s *triggerSubscriber) Receive(_ context.Context, msg *types.MessageBody) { } } } else { - s.lggr.Errorw("received trigger event with unknown method", "method", msg.Method, "sender", sender) + s.lggr.Errorw("received trigger event with unknown method", "method", SanitizeLogString(msg.Method), "sender", sender) } } diff --git a/core/capabilities/remote/trigger_subscriber_test.go b/core/capabilities/remote/trigger_subscriber_test.go index 93e962215a..b8cc3ddc7b 100644 --- a/core/capabilities/remote/trigger_subscriber_test.go +++ b/core/capabilities/remote/trigger_subscriber_test.go @@ -7,7 +7,6 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink-common/pkg/capabilities" commoncap "github.com/smartcontractkit/chainlink-common/pkg/capabilities" "github.com/smartcontractkit/chainlink-common/pkg/capabilities/pb" "github.com/smartcontractkit/chainlink-common/pkg/values" @@ -22,7 +21,7 @@ import ( const ( peerID1 = "12D3KooWF3dVeJ6YoT5HFnYhmwQWWMoEwVFzJQ5kKCMX3ZityxMC" peerID2 = "12D3KooWQsmok6aD8PZqt3RnJhQRrNzKHLficq7zYFRp7kZ1hHP8" - workflowID1 = "workflowID1" + workflowID1 = "15c631d295ef5e32deb99a10ee6804bc4af13855687559d7ff6552ac6dbb2ce0" ) var ( @@ -63,7 +62,7 @@ func TestTriggerSubscriber_RegisterAndReceive(t *testing.T) { }) // register trigger - config := capabilities.RemoteTriggerConfig{ + config := &commoncap.RemoteTriggerConfig{ RegistrationRefresh: 100 * time.Millisecond, RegistrationExpiry: 100 * time.Second, MinResponsesToAggregate: 1, @@ -72,7 +71,7 @@ func TestTriggerSubscriber_RegisterAndReceive(t *testing.T) { subscriber := remote.NewTriggerSubscriber(config, capInfo, capDonInfo, workflowDonInfo, dispatcher, nil, lggr) require.NoError(t, subscriber.Start(ctx)) - req := commoncap.CapabilityRequest{ + req := commoncap.TriggerRegistrationRequest{ Metadata: commoncap.RequestMetadata{ WorkflowID: workflowID1, }, @@ -84,11 +83,13 @@ func TestTriggerSubscriber_RegisterAndReceive(t *testing.T) { // receive trigger event triggerEventValue, err := values.NewMap(triggerEvent1) require.NoError(t, err) - capResponse := commoncap.CapabilityResponse{ - Value: triggerEventValue, - Err: nil, + capResponse := commoncap.TriggerResponse{ + Event: commoncap.TriggerEvent{ + Outputs: triggerEventValue, + }, + Err: nil, } - marshaled, err := pb.MarshalCapabilityResponse(capResponse) + marshaled, err := pb.MarshalTriggerResponse(capResponse) require.NoError(t, err) triggerEvent := &remotetypes.MessageBody{ Sender: p1[:], @@ -102,7 +103,7 @@ func TestTriggerSubscriber_RegisterAndReceive(t *testing.T) { } subscriber.Receive(ctx, triggerEvent) response := <-triggerEventCallbackCh - require.Equal(t, response.Value, triggerEventValue) + require.Equal(t, response.Event.Outputs, triggerEventValue) require.NoError(t, subscriber.UnregisterTrigger(ctx, req)) require.NoError(t, subscriber.UnregisterTrigger(ctx, req)) diff --git a/core/capabilities/remote/types/types.go b/core/capabilities/remote/types/types.go index 3629fc06fe..54ec16f09f 100644 --- a/core/capabilities/remote/types/types.go +++ b/core/capabilities/remote/types/types.go @@ -30,8 +30,13 @@ type Receiver interface { Receive(ctx context.Context, msg *MessageBody) } +type ReceiverService interface { + services.Service + Receiver +} + type Aggregator interface { - Aggregate(eventID string, responses [][]byte) (commoncap.CapabilityResponse, error) + Aggregate(eventID string, responses [][]byte) (commoncap.TriggerResponse, error) } // NOTE: this type will become part of the Registry (KS-108) diff --git a/core/capabilities/remote/utils.go b/core/capabilities/remote/utils.go index dba24b843c..ea6a3efb18 100644 --- a/core/capabilities/remote/utils.go +++ b/core/capabilities/remote/utils.go @@ -7,6 +7,7 @@ import ( "encoding/hex" "errors" "fmt" + "unicode" "google.golang.org/protobuf/proto" @@ -16,6 +17,10 @@ import ( p2ptypes "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types" ) +const ( + maxLoggedStringLen = 256 +) + func ValidateMessage(msg p2ptypes.Message, expectedReceiver p2ptypes.PeerID) (*remotetypes.MessageBody, error) { var topLevelMessage remotetypes.Message err := proto.Unmarshal(msg.Payload, &topLevelMessage) @@ -43,10 +48,14 @@ func ValidateMessage(msg p2ptypes.Message, expectedReceiver p2ptypes.PeerID) (*r return &body, nil } -func ToPeerID(peerID []byte) p2ptypes.PeerID { +func ToPeerID(peerID []byte) (p2ptypes.PeerID, error) { + if len(peerID) != p2ptypes.PeerIDLength { + return p2ptypes.PeerID{}, fmt.Errorf("invalid peer ID length: %d", len(peerID)) + } + var id p2ptypes.PeerID copy(id[:], peerID) - return id + return id, nil } // Default MODE Aggregator needs a configurable number of identical responses for aggregation to succeed @@ -62,15 +71,15 @@ func NewDefaultModeAggregator(minIdenticalResponses uint32) *defaultModeAggregat } } -func (a *defaultModeAggregator) Aggregate(_ string, responses [][]byte) (commoncap.CapabilityResponse, error) { +func (a *defaultModeAggregator) Aggregate(_ string, responses [][]byte) (commoncap.TriggerResponse, error) { found, err := AggregateModeRaw(responses, a.minIdenticalResponses) if err != nil { - return commoncap.CapabilityResponse{}, fmt.Errorf("failed to aggregate responses, err: %w", err) + return commoncap.TriggerResponse{}, fmt.Errorf("failed to aggregate responses, err: %w", err) } - unmarshaled, err := pb.UnmarshalCapabilityResponse(found) + unmarshaled, err := pb.UnmarshalTriggerResponse(found) if err != nil { - return commoncap.CapabilityResponse{}, fmt.Errorf("failed to unmarshal aggregated responses, err: %w", err) + return commoncap.TriggerResponse{}, fmt.Errorf("failed to unmarshal aggregated responses, err: %w", err) } return unmarshaled, nil } @@ -85,7 +94,8 @@ func AggregateModeRaw(elemList [][]byte, minIdenticalResponses uint32) ([]byte, hashToCount[sha]++ if hashToCount[sha] >= minIdenticalResponses { found = elem - break + // update in case we find another elem with an even higher count + minIdenticalResponses = hashToCount[sha] } } if found == nil { @@ -93,3 +103,17 @@ func AggregateModeRaw(elemList [][]byte, minIdenticalResponses uint32) ([]byte, } return found, nil } + +func SanitizeLogString(s string) string { + tooLongSuffix := "" + if len(s) > maxLoggedStringLen { + s = s[:maxLoggedStringLen] + tooLongSuffix = " [TRUNCATED]" + } + for i := 0; i < len(s); i++ { + if !unicode.IsPrint(rune(s[i])) { + return "[UNPRINTABLE] " + hex.EncodeToString([]byte(s)) + tooLongSuffix + } + } + return s + tooLongSuffix +} diff --git a/core/capabilities/remote/utils_test.go b/core/capabilities/remote/utils_test.go index 8bebf71fb6..6707e6ffb2 100644 --- a/core/capabilities/remote/utils_test.go +++ b/core/capabilities/remote/utils_test.go @@ -84,27 +84,32 @@ func encodeAndSign(t *testing.T, senderPrivKey ed25519.PrivateKey, senderId p2pt } func TestToPeerID(t *testing.T) { - id := remote.ToPeerID([]byte("12345678901234567890123456789012")) + id, err := remote.ToPeerID([]byte("12345678901234567890123456789012")) + require.NoError(t, err) require.Equal(t, "12D3KooWD8QYTQVYjB6oog4Ej8PcPpqTrPRnxLQap8yY8KUQRVvq", id.String()) } func TestDefaultModeAggregator_Aggregate(t *testing.T) { val, err := values.NewMap(triggerEvent1) require.NoError(t, err) - capResponse1 := commoncap.CapabilityResponse{ - Value: val, - Err: nil, + capResponse1 := commoncap.TriggerResponse{ + Event: commoncap.TriggerEvent{ + Outputs: val, + }, + Err: nil, } - marshaled1, err := pb.MarshalCapabilityResponse(capResponse1) + marshaled1, err := pb.MarshalTriggerResponse(capResponse1) require.NoError(t, err) val2, err := values.NewMap(triggerEvent2) require.NoError(t, err) - capResponse2 := commoncap.CapabilityResponse{ - Value: val2, - Err: nil, + capResponse2 := commoncap.TriggerResponse{ + Event: commoncap.TriggerEvent{ + Outputs: val2, + }, + Err: nil, } - marshaled2, err := pb.MarshalCapabilityResponse(capResponse2) + marshaled2, err := pb.MarshalTriggerResponse(capResponse2) require.NoError(t, err) agg := remote.NewDefaultModeAggregator(2) @@ -118,3 +123,14 @@ func TestDefaultModeAggregator_Aggregate(t *testing.T) { require.NoError(t, err) require.Equal(t, res, capResponse1) } + +func TestSanitizeLogString(t *testing.T) { + require.Equal(t, "hello", remote.SanitizeLogString("hello")) + require.Equal(t, "[UNPRINTABLE] 0a", remote.SanitizeLogString("\n")) + + longString := "" + for i := 0; i < 100; i++ { + longString += "aa-aa-aa-" + } + require.Equal(t, longString[:256]+" [TRUNCATED]", remote.SanitizeLogString(longString)) +} diff --git a/core/capabilities/streams/codec.go b/core/capabilities/streams/codec.go index d2bc451a39..d6918f0c73 100644 --- a/core/capabilities/streams/codec.go +++ b/core/capabilities/streams/codec.go @@ -1,6 +1,7 @@ package streams import ( + "encoding/hex" "fmt" "github.com/ethereum/go-ethereum/common" @@ -19,7 +20,7 @@ type codec struct { var _ datastreams.ReportCodec = &codec{} func (c *codec) Unwrap(wrapped values.Value) ([]datastreams.FeedReport, error) { - dest, err := datastreams.UnwrapFeedReportList(wrapped) + dest, err := datastreams.UnwrapStreamsTriggerEventToFeedReportList(wrapped) if err != nil { return nil, fmt.Errorf("failed to unwrap: %v", err) } @@ -34,6 +35,9 @@ func (c *codec) Unwrap(wrapped values.Value) ([]datastreams.FeedReport, error) { if err2 != nil { return nil, fmt.Errorf("failed to decode: %v", err2) } + if decoded.FeedId != id.Bytes() { + return nil, fmt.Errorf("feed ID mismatch: FeedID: %s, FullReport.FeedId: %s", id, hex.EncodeToString(decoded.FeedId[:])) + } dest[i].BenchmarkPrice = decoded.BenchmarkPrice.Bytes() dest[i].ObservationTimestamp = int64(decoded.ObservationsTimestamp) } @@ -41,7 +45,9 @@ func (c *codec) Unwrap(wrapped values.Value) ([]datastreams.FeedReport, error) { } func (c *codec) Wrap(reports []datastreams.FeedReport) (values.Value, error) { - return values.Wrap(reports) + return values.Wrap(&datastreams.StreamsTriggerEvent{ + Payload: reports, + }) } func (c *codec) Validate(report datastreams.FeedReport, allowedSigners [][]byte, minRequiredSignatures int) error { diff --git a/core/capabilities/streams/codec_test.go b/core/capabilities/streams/codec_test.go index e3ada731e4..02ec474fec 100644 --- a/core/capabilities/streams/codec_test.go +++ b/core/capabilities/streams/codec_test.go @@ -69,7 +69,7 @@ func TestCodec_WrapUnwrap(t *testing.T) { _, err = codec.Unwrap(values.NewBool(true)) require.Error(t, err) - // correct reports byt wrong signatures + // correct reports but wrong signatures unwrapped, err := codec.Unwrap(wrapped) require.NoError(t, err) require.Equal(t, 2, len(unwrapped)) @@ -85,6 +85,20 @@ func TestCodec_WrapUnwrap(t *testing.T) { for _, report := range unwrapped { require.NoError(t, codec.Validate(report, allowedSigners, 2)) } + + // invalid FeedID + wrappedInvalid, err := codec.Wrap([]datastreams.FeedReport{ + { + FeedID: id2Str, // ID #2 doesn't match what's in report #1 + FullReport: report1, + ReportContext: rawCtx, + Signatures: [][]byte{signatureK1R1, signatureK2R1}, + }, + }) + require.NoError(t, err) + _, err = codec.Unwrap(wrappedInvalid) + require.Error(t, err) + require.Contains(t, err.Error(), "feed ID mismatch") } func newFeedID(t *testing.T) ([32]byte, string) { diff --git a/core/capabilities/streams/consensus_agg_test.go b/core/capabilities/streams/consensus_agg_test.go index 506ad26f86..04396a38ba 100644 --- a/core/capabilities/streams/consensus_agg_test.go +++ b/core/capabilities/streams/consensus_agg_test.go @@ -9,7 +9,6 @@ import ( "github.com/smartcontractkit/libocr/commontypes" - "github.com/smartcontractkit/chainlink-common/pkg/capabilities" "github.com/smartcontractkit/chainlink-common/pkg/capabilities/consensus/ocr3/datafeeds" "github.com/smartcontractkit/chainlink-common/pkg/capabilities/datastreams" "github.com/smartcontractkit/chainlink-common/pkg/values" @@ -98,26 +97,18 @@ func newObservations(t *testing.T, nNodes int, feeds []feed, minRequiredSignatur reportList = append(reportList, signedStreamsReport) } - payloadVal, err := values.Wrap(reportList) - require.NoError(t, err) - - meta := datastreams.SignersMetadata{ + meta := datastreams.Metadata{ Signers: allowedSigners, MinRequiredSignatures: minRequiredSignatures, } - metaVal, err := values.Wrap(meta) - require.NoError(t, err) - - triggerEvent := capabilities.TriggerEvent{ - TriggerType: triggerID, - ID: "unused", - Timestamp: "1234", - Metadata: metaVal, - Payload: payloadVal, + p := datastreams.StreamsTriggerEvent{ + Payload: reportList, + Metadata: meta, } - wrappedEvent, err := values.Wrap(triggerEvent) + outputs, err := values.WrapMap(p) require.NoError(t, err) - observations[commontypes.OracleID(i)] = []values.Value{wrappedEvent} + + observations[commontypes.OracleID(i)] = []values.Value{outputs} } return observations } diff --git a/core/capabilities/streams/trigger_test.go b/core/capabilities/streams/trigger_test.go index cb4cfaa36b..3db6f2445e 100644 --- a/core/capabilities/streams/trigger_test.go +++ b/core/capabilities/streams/trigger_test.go @@ -87,13 +87,13 @@ func TestStreamsTrigger(t *testing.T) { Members: capMembers, F: uint8(F), } - config := capabilities.RemoteTriggerConfig{ + config := &capabilities.RemoteTriggerConfig{ MinResponsesToAggregate: uint32(F + 1), } subscriber := remote.NewTriggerSubscriber(config, capInfo, capDonInfo, capabilities.DON{}, nil, agg, lggr) // register trigger - req := capabilities.CapabilityRequest{ + req := capabilities.TriggerRegistrationRequest{ Metadata: capabilities.RequestMetadata{ WorkflowID: workflowID, }, @@ -131,7 +131,7 @@ func TestStreamsTrigger(t *testing.T) { } response := <-triggerEventCallbackCh - validateLatestReports(t, response.Value, P, basePrice+R-1, baseTimestamp+R-1) + validateLatestReports(t, response.Event.Outputs, P, basePrice+R-1, baseTimestamp+R-1) } totalTime := time.Now().UnixMilli() - startTs lggr.Infow("elapsed", "totalMs", totalTime, "processingMs", processingTime) @@ -180,24 +180,20 @@ func newFeedsWithSignedReports(t *testing.T, nodes []node, N, P, R int) []feed { } func newTriggerEvent(t *testing.T, reportList []datastreams.FeedReport, triggerEventID string, sender ragetypes.PeerID) *remotetypes.MessageBody { - val, err := values.Wrap(reportList) + outputs, err := values.WrapMap(&datastreams.StreamsTriggerEvent{ + Timestamp: 10, + Payload: reportList, + }) require.NoError(t, err) triggerEvent := capabilities.TriggerEvent{ TriggerType: triggerID, ID: triggerEventID, - Timestamp: strconv.FormatInt(1000, 10), - Metadata: nil, - Payload: val, + Outputs: outputs, } - eventVal, err := values.WrapMap(triggerEvent) - require.NoError(t, err) + marshaled, err := pb.MarshalTriggerResponse(capabilities.TriggerResponse{Event: triggerEvent}) - marshaled, err := pb.MarshalCapabilityResponse( - capabilities.CapabilityResponse{ - Value: eventVal, - }) require.NoError(t, err) msg := &remotetypes.MessageBody{ Sender: sender[:], @@ -214,13 +210,11 @@ func newTriggerEvent(t *testing.T, reportList []datastreams.FeedReport, triggerE } func validateLatestReports(t *testing.T, wrapped values.Value, expectedFeedsLen int, expectedPrice int, expectedTimestamp int) { - triggerEvent := capabilities.TriggerEvent{} + triggerEvent := datastreams.StreamsTriggerEvent{} require.NoError(t, wrapped.UnwrapTo(&triggerEvent)) - reports := []datastreams.FeedReport{} - require.NoError(t, triggerEvent.Payload.UnwrapTo(&reports)) - require.Equal(t, expectedFeedsLen, len(reports)) + require.Equal(t, expectedFeedsLen, len(triggerEvent.Payload)) priceBig := big.NewInt(int64(expectedPrice)) - for _, report := range reports { + for _, report := range triggerEvent.Payload { require.Equal(t, priceBig.Bytes(), report.BenchmarkPrice) require.Equal(t, int64(expectedTimestamp), report.ObservationTimestamp) } diff --git a/core/capabilities/targets/mocks/chain_reader.go b/core/capabilities/targets/mocks/chain_reader.go deleted file mode 100644 index 9c1c85ff60..0000000000 --- a/core/capabilities/targets/mocks/chain_reader.go +++ /dev/null @@ -1,487 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -package mocks - -import ( - context "context" - - query "github.com/smartcontractkit/chainlink-common/pkg/types/query" - primitives "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" - mock "github.com/stretchr/testify/mock" - - types "github.com/smartcontractkit/chainlink-common/pkg/types" -) - -// ChainReader is an autogenerated mock type for the ChainReader type -type ChainReader struct { - mock.Mock -} - -type ChainReader_Expecter struct { - mock *mock.Mock -} - -func (_m *ChainReader) EXPECT() *ChainReader_Expecter { - return &ChainReader_Expecter{mock: &_m.Mock} -} - -// BatchGetLatestValues provides a mock function with given fields: ctx, request -func (_m *ChainReader) BatchGetLatestValues(ctx context.Context, request types.BatchGetLatestValuesRequest) (types.BatchGetLatestValuesResult, error) { - ret := _m.Called(ctx, request) - - if len(ret) == 0 { - panic("no return value specified for BatchGetLatestValues") - } - - var r0 types.BatchGetLatestValuesResult - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, types.BatchGetLatestValuesRequest) (types.BatchGetLatestValuesResult, error)); ok { - return rf(ctx, request) - } - if rf, ok := ret.Get(0).(func(context.Context, types.BatchGetLatestValuesRequest) types.BatchGetLatestValuesResult); ok { - r0 = rf(ctx, request) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(types.BatchGetLatestValuesResult) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, types.BatchGetLatestValuesRequest) error); ok { - r1 = rf(ctx, request) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ChainReader_BatchGetLatestValues_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BatchGetLatestValues' -type ChainReader_BatchGetLatestValues_Call struct { - *mock.Call -} - -// BatchGetLatestValues is a helper method to define mock.On call -// - ctx context.Context -// - request types.BatchGetLatestValuesRequest -func (_e *ChainReader_Expecter) BatchGetLatestValues(ctx interface{}, request interface{}) *ChainReader_BatchGetLatestValues_Call { - return &ChainReader_BatchGetLatestValues_Call{Call: _e.mock.On("BatchGetLatestValues", ctx, request)} -} - -func (_c *ChainReader_BatchGetLatestValues_Call) Run(run func(ctx context.Context, request types.BatchGetLatestValuesRequest)) *ChainReader_BatchGetLatestValues_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(types.BatchGetLatestValuesRequest)) - }) - return _c -} - -func (_c *ChainReader_BatchGetLatestValues_Call) Return(_a0 types.BatchGetLatestValuesResult, _a1 error) *ChainReader_BatchGetLatestValues_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *ChainReader_BatchGetLatestValues_Call) RunAndReturn(run func(context.Context, types.BatchGetLatestValuesRequest) (types.BatchGetLatestValuesResult, error)) *ChainReader_BatchGetLatestValues_Call { - _c.Call.Return(run) - return _c -} - -// Bind provides a mock function with given fields: ctx, bindings -func (_m *ChainReader) Bind(ctx context.Context, bindings []types.BoundContract) error { - ret := _m.Called(ctx, bindings) - - if len(ret) == 0 { - panic("no return value specified for Bind") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, []types.BoundContract) error); ok { - r0 = rf(ctx, bindings) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// ChainReader_Bind_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Bind' -type ChainReader_Bind_Call struct { - *mock.Call -} - -// Bind is a helper method to define mock.On call -// - ctx context.Context -// - bindings []types.BoundContract -func (_e *ChainReader_Expecter) Bind(ctx interface{}, bindings interface{}) *ChainReader_Bind_Call { - return &ChainReader_Bind_Call{Call: _e.mock.On("Bind", ctx, bindings)} -} - -func (_c *ChainReader_Bind_Call) Run(run func(ctx context.Context, bindings []types.BoundContract)) *ChainReader_Bind_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].([]types.BoundContract)) - }) - return _c -} - -func (_c *ChainReader_Bind_Call) Return(_a0 error) *ChainReader_Bind_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *ChainReader_Bind_Call) RunAndReturn(run func(context.Context, []types.BoundContract) error) *ChainReader_Bind_Call { - _c.Call.Return(run) - return _c -} - -// Close provides a mock function with given fields: -func (_m *ChainReader) 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 -} - -// ChainReader_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' -type ChainReader_Close_Call struct { - *mock.Call -} - -// Close is a helper method to define mock.On call -func (_e *ChainReader_Expecter) Close() *ChainReader_Close_Call { - return &ChainReader_Close_Call{Call: _e.mock.On("Close")} -} - -func (_c *ChainReader_Close_Call) Run(run func()) *ChainReader_Close_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *ChainReader_Close_Call) Return(_a0 error) *ChainReader_Close_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *ChainReader_Close_Call) RunAndReturn(run func() error) *ChainReader_Close_Call { - _c.Call.Return(run) - return _c -} - -// GetLatestValue provides a mock function with given fields: ctx, contractName, method, confidenceLevel, params, returnVal -func (_m *ChainReader) GetLatestValue(ctx context.Context, contractName string, method string, confidenceLevel primitives.ConfidenceLevel, params interface{}, returnVal interface{}) error { - ret := _m.Called(ctx, contractName, method, confidenceLevel, params, returnVal) - - if len(ret) == 0 { - panic("no return value specified for GetLatestValue") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, primitives.ConfidenceLevel, interface{}, interface{}) error); ok { - r0 = rf(ctx, contractName, method, confidenceLevel, params, returnVal) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// ChainReader_GetLatestValue_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLatestValue' -type ChainReader_GetLatestValue_Call struct { - *mock.Call -} - -// GetLatestValue is a helper method to define mock.On call -// - ctx context.Context -// - contractName string -// - method string -// - confidenceLevel primitives.ConfidenceLevel -// - params interface{} -// - returnVal interface{} -func (_e *ChainReader_Expecter) GetLatestValue(ctx interface{}, contractName interface{}, method interface{}, confidenceLevel interface{}, params interface{}, returnVal interface{}) *ChainReader_GetLatestValue_Call { - return &ChainReader_GetLatestValue_Call{Call: _e.mock.On("GetLatestValue", ctx, contractName, method, confidenceLevel, params, returnVal)} -} - -func (_c *ChainReader_GetLatestValue_Call) Run(run func(ctx context.Context, contractName string, method string, confidenceLevel primitives.ConfidenceLevel, params interface{}, returnVal interface{})) *ChainReader_GetLatestValue_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(primitives.ConfidenceLevel), args[4].(interface{}), args[5].(interface{})) - }) - return _c -} - -func (_c *ChainReader_GetLatestValue_Call) Return(_a0 error) *ChainReader_GetLatestValue_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *ChainReader_GetLatestValue_Call) RunAndReturn(run func(context.Context, string, string, primitives.ConfidenceLevel, interface{}, interface{}) error) *ChainReader_GetLatestValue_Call { - _c.Call.Return(run) - return _c -} - -// HealthReport provides a mock function with given fields: -func (_m *ChainReader) HealthReport() map[string]error { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for HealthReport") - } - - var r0 map[string]error - if rf, ok := ret.Get(0).(func() map[string]error); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(map[string]error) - } - } - - return r0 -} - -// ChainReader_HealthReport_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HealthReport' -type ChainReader_HealthReport_Call struct { - *mock.Call -} - -// HealthReport is a helper method to define mock.On call -func (_e *ChainReader_Expecter) HealthReport() *ChainReader_HealthReport_Call { - return &ChainReader_HealthReport_Call{Call: _e.mock.On("HealthReport")} -} - -func (_c *ChainReader_HealthReport_Call) Run(run func()) *ChainReader_HealthReport_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *ChainReader_HealthReport_Call) Return(_a0 map[string]error) *ChainReader_HealthReport_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *ChainReader_HealthReport_Call) RunAndReturn(run func() map[string]error) *ChainReader_HealthReport_Call { - _c.Call.Return(run) - return _c -} - -// Name provides a mock function with given fields: -func (_m *ChainReader) Name() string { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for Name") - } - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -// ChainReader_Name_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Name' -type ChainReader_Name_Call struct { - *mock.Call -} - -// Name is a helper method to define mock.On call -func (_e *ChainReader_Expecter) Name() *ChainReader_Name_Call { - return &ChainReader_Name_Call{Call: _e.mock.On("Name")} -} - -func (_c *ChainReader_Name_Call) Run(run func()) *ChainReader_Name_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *ChainReader_Name_Call) Return(_a0 string) *ChainReader_Name_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *ChainReader_Name_Call) RunAndReturn(run func() string) *ChainReader_Name_Call { - _c.Call.Return(run) - return _c -} - -// QueryKey provides a mock function with given fields: ctx, contractName, filter, limitAndSort, sequenceDataType -func (_m *ChainReader) QueryKey(ctx context.Context, contractName string, filter query.KeyFilter, limitAndSort query.LimitAndSort, sequenceDataType interface{}) ([]types.Sequence, error) { - ret := _m.Called(ctx, contractName, filter, limitAndSort, sequenceDataType) - - if len(ret) == 0 { - panic("no return value specified for QueryKey") - } - - var r0 []types.Sequence - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, query.KeyFilter, query.LimitAndSort, interface{}) ([]types.Sequence, error)); ok { - return rf(ctx, contractName, filter, limitAndSort, sequenceDataType) - } - if rf, ok := ret.Get(0).(func(context.Context, string, query.KeyFilter, query.LimitAndSort, interface{}) []types.Sequence); ok { - r0 = rf(ctx, contractName, filter, limitAndSort, sequenceDataType) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]types.Sequence) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string, query.KeyFilter, query.LimitAndSort, interface{}) error); ok { - r1 = rf(ctx, contractName, filter, limitAndSort, sequenceDataType) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ChainReader_QueryKey_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'QueryKey' -type ChainReader_QueryKey_Call struct { - *mock.Call -} - -// QueryKey is a helper method to define mock.On call -// - ctx context.Context -// - contractName string -// - filter query.KeyFilter -// - limitAndSort query.LimitAndSort -// - sequenceDataType interface{} -func (_e *ChainReader_Expecter) QueryKey(ctx interface{}, contractName interface{}, filter interface{}, limitAndSort interface{}, sequenceDataType interface{}) *ChainReader_QueryKey_Call { - return &ChainReader_QueryKey_Call{Call: _e.mock.On("QueryKey", ctx, contractName, filter, limitAndSort, sequenceDataType)} -} - -func (_c *ChainReader_QueryKey_Call) Run(run func(ctx context.Context, contractName string, filter query.KeyFilter, limitAndSort query.LimitAndSort, sequenceDataType interface{})) *ChainReader_QueryKey_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(query.KeyFilter), args[3].(query.LimitAndSort), args[4].(interface{})) - }) - return _c -} - -func (_c *ChainReader_QueryKey_Call) Return(_a0 []types.Sequence, _a1 error) *ChainReader_QueryKey_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *ChainReader_QueryKey_Call) RunAndReturn(run func(context.Context, string, query.KeyFilter, query.LimitAndSort, interface{}) ([]types.Sequence, error)) *ChainReader_QueryKey_Call { - _c.Call.Return(run) - return _c -} - -// Ready provides a mock function with given fields: -func (_m *ChainReader) Ready() error { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for Ready") - } - - var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// ChainReader_Ready_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Ready' -type ChainReader_Ready_Call struct { - *mock.Call -} - -// Ready is a helper method to define mock.On call -func (_e *ChainReader_Expecter) Ready() *ChainReader_Ready_Call { - return &ChainReader_Ready_Call{Call: _e.mock.On("Ready")} -} - -func (_c *ChainReader_Ready_Call) Run(run func()) *ChainReader_Ready_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *ChainReader_Ready_Call) Return(_a0 error) *ChainReader_Ready_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *ChainReader_Ready_Call) RunAndReturn(run func() error) *ChainReader_Ready_Call { - _c.Call.Return(run) - return _c -} - -// Start provides a mock function with given fields: _a0 -func (_m *ChainReader) Start(_a0 context.Context) error { - ret := _m.Called(_a0) - - if len(ret) == 0 { - panic("no return value specified for Start") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context) error); ok { - r0 = rf(_a0) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// ChainReader_Start_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Start' -type ChainReader_Start_Call struct { - *mock.Call -} - -// Start is a helper method to define mock.On call -// - _a0 context.Context -func (_e *ChainReader_Expecter) Start(_a0 interface{}) *ChainReader_Start_Call { - return &ChainReader_Start_Call{Call: _e.mock.On("Start", _a0)} -} - -func (_c *ChainReader_Start_Call) Run(run func(_a0 context.Context)) *ChainReader_Start_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *ChainReader_Start_Call) Return(_a0 error) *ChainReader_Start_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *ChainReader_Start_Call) RunAndReturn(run func(context.Context) error) *ChainReader_Start_Call { - _c.Call.Return(run) - return _c -} - -// NewChainReader creates a new instance of ChainReader. 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 NewChainReader(t interface { - mock.TestingT - Cleanup(func()) -}) *ChainReader { - mock := &ChainReader{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/core/capabilities/targets/mocks/contract_reader.go b/core/capabilities/targets/mocks/contract_reader.go new file mode 100644 index 0000000000..dd92195ec2 --- /dev/null +++ b/core/capabilities/targets/mocks/contract_reader.go @@ -0,0 +1,533 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mocks + +import ( + context "context" + + query "github.com/smartcontractkit/chainlink-common/pkg/types/query" + primitives "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" + mock "github.com/stretchr/testify/mock" + + types "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +// ContractReader is an autogenerated mock type for the ContractReader type +type ContractReader struct { + mock.Mock +} + +type ContractReader_Expecter struct { + mock *mock.Mock +} + +func (_m *ContractReader) EXPECT() *ContractReader_Expecter { + return &ContractReader_Expecter{mock: &_m.Mock} +} + +// BatchGetLatestValues provides a mock function with given fields: ctx, request +func (_m *ContractReader) BatchGetLatestValues(ctx context.Context, request types.BatchGetLatestValuesRequest) (types.BatchGetLatestValuesResult, error) { + ret := _m.Called(ctx, request) + + if len(ret) == 0 { + panic("no return value specified for BatchGetLatestValues") + } + + var r0 types.BatchGetLatestValuesResult + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, types.BatchGetLatestValuesRequest) (types.BatchGetLatestValuesResult, error)); ok { + return rf(ctx, request) + } + if rf, ok := ret.Get(0).(func(context.Context, types.BatchGetLatestValuesRequest) types.BatchGetLatestValuesResult); ok { + r0 = rf(ctx, request) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(types.BatchGetLatestValuesResult) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, types.BatchGetLatestValuesRequest) error); ok { + r1 = rf(ctx, request) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ContractReader_BatchGetLatestValues_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BatchGetLatestValues' +type ContractReader_BatchGetLatestValues_Call struct { + *mock.Call +} + +// BatchGetLatestValues is a helper method to define mock.On call +// - ctx context.Context +// - request types.BatchGetLatestValuesRequest +func (_e *ContractReader_Expecter) BatchGetLatestValues(ctx interface{}, request interface{}) *ContractReader_BatchGetLatestValues_Call { + return &ContractReader_BatchGetLatestValues_Call{Call: _e.mock.On("BatchGetLatestValues", ctx, request)} +} + +func (_c *ContractReader_BatchGetLatestValues_Call) Run(run func(ctx context.Context, request types.BatchGetLatestValuesRequest)) *ContractReader_BatchGetLatestValues_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(types.BatchGetLatestValuesRequest)) + }) + return _c +} + +func (_c *ContractReader_BatchGetLatestValues_Call) Return(_a0 types.BatchGetLatestValuesResult, _a1 error) *ContractReader_BatchGetLatestValues_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ContractReader_BatchGetLatestValues_Call) RunAndReturn(run func(context.Context, types.BatchGetLatestValuesRequest) (types.BatchGetLatestValuesResult, error)) *ContractReader_BatchGetLatestValues_Call { + _c.Call.Return(run) + return _c +} + +// Bind provides a mock function with given fields: ctx, bindings +func (_m *ContractReader) Bind(ctx context.Context, bindings []types.BoundContract) error { + ret := _m.Called(ctx, bindings) + + if len(ret) == 0 { + panic("no return value specified for Bind") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, []types.BoundContract) error); ok { + r0 = rf(ctx, bindings) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ContractReader_Bind_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Bind' +type ContractReader_Bind_Call struct { + *mock.Call +} + +// Bind is a helper method to define mock.On call +// - ctx context.Context +// - bindings []types.BoundContract +func (_e *ContractReader_Expecter) Bind(ctx interface{}, bindings interface{}) *ContractReader_Bind_Call { + return &ContractReader_Bind_Call{Call: _e.mock.On("Bind", ctx, bindings)} +} + +func (_c *ContractReader_Bind_Call) Run(run func(ctx context.Context, bindings []types.BoundContract)) *ContractReader_Bind_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].([]types.BoundContract)) + }) + return _c +} + +func (_c *ContractReader_Bind_Call) Return(_a0 error) *ContractReader_Bind_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ContractReader_Bind_Call) RunAndReturn(run func(context.Context, []types.BoundContract) error) *ContractReader_Bind_Call { + _c.Call.Return(run) + return _c +} + +// Close provides a mock function with given fields: +func (_m *ContractReader) 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 +} + +// ContractReader_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' +type ContractReader_Close_Call struct { + *mock.Call +} + +// Close is a helper method to define mock.On call +func (_e *ContractReader_Expecter) Close() *ContractReader_Close_Call { + return &ContractReader_Close_Call{Call: _e.mock.On("Close")} +} + +func (_c *ContractReader_Close_Call) Run(run func()) *ContractReader_Close_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *ContractReader_Close_Call) Return(_a0 error) *ContractReader_Close_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ContractReader_Close_Call) RunAndReturn(run func() error) *ContractReader_Close_Call { + _c.Call.Return(run) + return _c +} + +// GetLatestValue provides a mock function with given fields: ctx, readIdentifier, confidenceLevel, params, returnVal +func (_m *ContractReader) GetLatestValue(ctx context.Context, readIdentifier string, confidenceLevel primitives.ConfidenceLevel, params interface{}, returnVal interface{}) error { + ret := _m.Called(ctx, readIdentifier, confidenceLevel, params, returnVal) + + if len(ret) == 0 { + panic("no return value specified for GetLatestValue") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, primitives.ConfidenceLevel, interface{}, interface{}) error); ok { + r0 = rf(ctx, readIdentifier, confidenceLevel, params, returnVal) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ContractReader_GetLatestValue_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLatestValue' +type ContractReader_GetLatestValue_Call struct { + *mock.Call +} + +// GetLatestValue is a helper method to define mock.On call +// - ctx context.Context +// - readIdentifier string +// - confidenceLevel primitives.ConfidenceLevel +// - params interface{} +// - returnVal interface{} +func (_e *ContractReader_Expecter) GetLatestValue(ctx interface{}, readIdentifier interface{}, confidenceLevel interface{}, params interface{}, returnVal interface{}) *ContractReader_GetLatestValue_Call { + return &ContractReader_GetLatestValue_Call{Call: _e.mock.On("GetLatestValue", ctx, readIdentifier, confidenceLevel, params, returnVal)} +} + +func (_c *ContractReader_GetLatestValue_Call) Run(run func(ctx context.Context, readIdentifier string, confidenceLevel primitives.ConfidenceLevel, params interface{}, returnVal interface{})) *ContractReader_GetLatestValue_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(primitives.ConfidenceLevel), args[3].(interface{}), args[4].(interface{})) + }) + return _c +} + +func (_c *ContractReader_GetLatestValue_Call) Return(_a0 error) *ContractReader_GetLatestValue_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ContractReader_GetLatestValue_Call) RunAndReturn(run func(context.Context, string, primitives.ConfidenceLevel, interface{}, interface{}) error) *ContractReader_GetLatestValue_Call { + _c.Call.Return(run) + return _c +} + +// HealthReport provides a mock function with given fields: +func (_m *ContractReader) HealthReport() map[string]error { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for HealthReport") + } + + var r0 map[string]error + if rf, ok := ret.Get(0).(func() map[string]error); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string]error) + } + } + + return r0 +} + +// ContractReader_HealthReport_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HealthReport' +type ContractReader_HealthReport_Call struct { + *mock.Call +} + +// HealthReport is a helper method to define mock.On call +func (_e *ContractReader_Expecter) HealthReport() *ContractReader_HealthReport_Call { + return &ContractReader_HealthReport_Call{Call: _e.mock.On("HealthReport")} +} + +func (_c *ContractReader_HealthReport_Call) Run(run func()) *ContractReader_HealthReport_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *ContractReader_HealthReport_Call) Return(_a0 map[string]error) *ContractReader_HealthReport_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ContractReader_HealthReport_Call) RunAndReturn(run func() map[string]error) *ContractReader_HealthReport_Call { + _c.Call.Return(run) + return _c +} + +// Name provides a mock function with given fields: +func (_m *ContractReader) Name() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Name") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// ContractReader_Name_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Name' +type ContractReader_Name_Call struct { + *mock.Call +} + +// Name is a helper method to define mock.On call +func (_e *ContractReader_Expecter) Name() *ContractReader_Name_Call { + return &ContractReader_Name_Call{Call: _e.mock.On("Name")} +} + +func (_c *ContractReader_Name_Call) Run(run func()) *ContractReader_Name_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *ContractReader_Name_Call) Return(_a0 string) *ContractReader_Name_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ContractReader_Name_Call) RunAndReturn(run func() string) *ContractReader_Name_Call { + _c.Call.Return(run) + return _c +} + +// QueryKey provides a mock function with given fields: ctx, contract, filter, limitAndSort, sequenceDataType +func (_m *ContractReader) QueryKey(ctx context.Context, contract types.BoundContract, filter query.KeyFilter, limitAndSort query.LimitAndSort, sequenceDataType interface{}) ([]types.Sequence, error) { + ret := _m.Called(ctx, contract, filter, limitAndSort, sequenceDataType) + + if len(ret) == 0 { + panic("no return value specified for QueryKey") + } + + var r0 []types.Sequence + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, types.BoundContract, query.KeyFilter, query.LimitAndSort, interface{}) ([]types.Sequence, error)); ok { + return rf(ctx, contract, filter, limitAndSort, sequenceDataType) + } + if rf, ok := ret.Get(0).(func(context.Context, types.BoundContract, query.KeyFilter, query.LimitAndSort, interface{}) []types.Sequence); ok { + r0 = rf(ctx, contract, filter, limitAndSort, sequenceDataType) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]types.Sequence) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, types.BoundContract, query.KeyFilter, query.LimitAndSort, interface{}) error); ok { + r1 = rf(ctx, contract, filter, limitAndSort, sequenceDataType) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ContractReader_QueryKey_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'QueryKey' +type ContractReader_QueryKey_Call struct { + *mock.Call +} + +// QueryKey is a helper method to define mock.On call +// - ctx context.Context +// - contract types.BoundContract +// - filter query.KeyFilter +// - limitAndSort query.LimitAndSort +// - sequenceDataType interface{} +func (_e *ContractReader_Expecter) QueryKey(ctx interface{}, contract interface{}, filter interface{}, limitAndSort interface{}, sequenceDataType interface{}) *ContractReader_QueryKey_Call { + return &ContractReader_QueryKey_Call{Call: _e.mock.On("QueryKey", ctx, contract, filter, limitAndSort, sequenceDataType)} +} + +func (_c *ContractReader_QueryKey_Call) Run(run func(ctx context.Context, contract types.BoundContract, filter query.KeyFilter, limitAndSort query.LimitAndSort, sequenceDataType interface{})) *ContractReader_QueryKey_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(types.BoundContract), args[2].(query.KeyFilter), args[3].(query.LimitAndSort), args[4].(interface{})) + }) + return _c +} + +func (_c *ContractReader_QueryKey_Call) Return(_a0 []types.Sequence, _a1 error) *ContractReader_QueryKey_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ContractReader_QueryKey_Call) RunAndReturn(run func(context.Context, types.BoundContract, query.KeyFilter, query.LimitAndSort, interface{}) ([]types.Sequence, error)) *ContractReader_QueryKey_Call { + _c.Call.Return(run) + return _c +} + +// Ready provides a mock function with given fields: +func (_m *ContractReader) Ready() error { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Ready") + } + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ContractReader_Ready_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Ready' +type ContractReader_Ready_Call struct { + *mock.Call +} + +// Ready is a helper method to define mock.On call +func (_e *ContractReader_Expecter) Ready() *ContractReader_Ready_Call { + return &ContractReader_Ready_Call{Call: _e.mock.On("Ready")} +} + +func (_c *ContractReader_Ready_Call) Run(run func()) *ContractReader_Ready_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *ContractReader_Ready_Call) Return(_a0 error) *ContractReader_Ready_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ContractReader_Ready_Call) RunAndReturn(run func() error) *ContractReader_Ready_Call { + _c.Call.Return(run) + return _c +} + +// Start provides a mock function with given fields: _a0 +func (_m *ContractReader) Start(_a0 context.Context) error { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for Start") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ContractReader_Start_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Start' +type ContractReader_Start_Call struct { + *mock.Call +} + +// Start is a helper method to define mock.On call +// - _a0 context.Context +func (_e *ContractReader_Expecter) Start(_a0 interface{}) *ContractReader_Start_Call { + return &ContractReader_Start_Call{Call: _e.mock.On("Start", _a0)} +} + +func (_c *ContractReader_Start_Call) Run(run func(_a0 context.Context)) *ContractReader_Start_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *ContractReader_Start_Call) Return(_a0 error) *ContractReader_Start_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ContractReader_Start_Call) RunAndReturn(run func(context.Context) error) *ContractReader_Start_Call { + _c.Call.Return(run) + return _c +} + +// Unbind provides a mock function with given fields: ctx, bindings +func (_m *ContractReader) Unbind(ctx context.Context, bindings []types.BoundContract) error { + ret := _m.Called(ctx, bindings) + + if len(ret) == 0 { + panic("no return value specified for Unbind") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, []types.BoundContract) error); ok { + r0 = rf(ctx, bindings) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ContractReader_Unbind_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Unbind' +type ContractReader_Unbind_Call struct { + *mock.Call +} + +// Unbind is a helper method to define mock.On call +// - ctx context.Context +// - bindings []types.BoundContract +func (_e *ContractReader_Expecter) Unbind(ctx interface{}, bindings interface{}) *ContractReader_Unbind_Call { + return &ContractReader_Unbind_Call{Call: _e.mock.On("Unbind", ctx, bindings)} +} + +func (_c *ContractReader_Unbind_Call) Run(run func(ctx context.Context, bindings []types.BoundContract)) *ContractReader_Unbind_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].([]types.BoundContract)) + }) + return _c +} + +func (_c *ContractReader_Unbind_Call) Return(_a0 error) *ContractReader_Unbind_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ContractReader_Unbind_Call) RunAndReturn(run func(context.Context, []types.BoundContract) error) *ContractReader_Unbind_Call { + _c.Call.Return(run) + return _c +} + +// NewContractReader creates a new instance of ContractReader. 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 NewContractReader(t interface { + mock.TestingT + Cleanup(func()) +}) *ContractReader { + mock := &ContractReader{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/capabilities/targets/mocks/contract_value_getter.go b/core/capabilities/targets/mocks/contract_value_getter.go new file mode 100644 index 0000000000..2621de4587 --- /dev/null +++ b/core/capabilities/targets/mocks/contract_value_getter.go @@ -0,0 +1,136 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mocks + +import ( + context "context" + + primitives "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" + mock "github.com/stretchr/testify/mock" + + types "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +// ContractValueGetter is an autogenerated mock type for the ContractValueGetter type +type ContractValueGetter struct { + mock.Mock +} + +type ContractValueGetter_Expecter struct { + mock *mock.Mock +} + +func (_m *ContractValueGetter) EXPECT() *ContractValueGetter_Expecter { + return &ContractValueGetter_Expecter{mock: &_m.Mock} +} + +// Bind provides a mock function with given fields: _a0, _a1 +func (_m *ContractValueGetter) Bind(_a0 context.Context, _a1 []types.BoundContract) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for Bind") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, []types.BoundContract) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ContractValueGetter_Bind_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Bind' +type ContractValueGetter_Bind_Call struct { + *mock.Call +} + +// Bind is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 []types.BoundContract +func (_e *ContractValueGetter_Expecter) Bind(_a0 interface{}, _a1 interface{}) *ContractValueGetter_Bind_Call { + return &ContractValueGetter_Bind_Call{Call: _e.mock.On("Bind", _a0, _a1)} +} + +func (_c *ContractValueGetter_Bind_Call) Run(run func(_a0 context.Context, _a1 []types.BoundContract)) *ContractValueGetter_Bind_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].([]types.BoundContract)) + }) + return _c +} + +func (_c *ContractValueGetter_Bind_Call) Return(_a0 error) *ContractValueGetter_Bind_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ContractValueGetter_Bind_Call) RunAndReturn(run func(context.Context, []types.BoundContract) error) *ContractValueGetter_Bind_Call { + _c.Call.Return(run) + return _c +} + +// GetLatestValue provides a mock function with given fields: _a0, _a1, _a2, _a3, _a4 +func (_m *ContractValueGetter) GetLatestValue(_a0 context.Context, _a1 string, _a2 primitives.ConfidenceLevel, _a3 interface{}, _a4 interface{}) error { + ret := _m.Called(_a0, _a1, _a2, _a3, _a4) + + if len(ret) == 0 { + panic("no return value specified for GetLatestValue") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, primitives.ConfidenceLevel, interface{}, interface{}) error); ok { + r0 = rf(_a0, _a1, _a2, _a3, _a4) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ContractValueGetter_GetLatestValue_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLatestValue' +type ContractValueGetter_GetLatestValue_Call struct { + *mock.Call +} + +// GetLatestValue is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 string +// - _a2 primitives.ConfidenceLevel +// - _a3 interface{} +// - _a4 interface{} +func (_e *ContractValueGetter_Expecter) GetLatestValue(_a0 interface{}, _a1 interface{}, _a2 interface{}, _a3 interface{}, _a4 interface{}) *ContractValueGetter_GetLatestValue_Call { + return &ContractValueGetter_GetLatestValue_Call{Call: _e.mock.On("GetLatestValue", _a0, _a1, _a2, _a3, _a4)} +} + +func (_c *ContractValueGetter_GetLatestValue_Call) Run(run func(_a0 context.Context, _a1 string, _a2 primitives.ConfidenceLevel, _a3 interface{}, _a4 interface{})) *ContractValueGetter_GetLatestValue_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(primitives.ConfidenceLevel), args[3].(interface{}), args[4].(interface{})) + }) + return _c +} + +func (_c *ContractValueGetter_GetLatestValue_Call) Return(_a0 error) *ContractValueGetter_GetLatestValue_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ContractValueGetter_GetLatestValue_Call) RunAndReturn(run func(context.Context, string, primitives.ConfidenceLevel, interface{}, interface{}) error) *ContractValueGetter_GetLatestValue_Call { + _c.Call.Return(run) + return _c +} + +// NewContractValueGetter creates a new instance of ContractValueGetter. 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 NewContractValueGetter(t interface { + mock.TestingT + Cleanup(func()) +}) *ContractValueGetter { + mock := &ContractValueGetter{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/capabilities/targets/write_target.go b/core/capabilities/targets/write_target.go index 330f15872d..0e0b207182 100644 --- a/core/capabilities/targets/write_target.go +++ b/core/capabilities/targets/write_target.go @@ -1,7 +1,9 @@ package targets import ( + "bytes" "context" + "encoding/binary" "encoding/hex" "fmt" "math/big" @@ -11,23 +13,22 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/capabilities" "github.com/smartcontractkit/chainlink-common/pkg/capabilities/consensus/ocr3/types" + "github.com/smartcontractkit/chainlink-common/pkg/logger" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" - "github.com/smartcontractkit/chainlink-common/pkg/values" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) var ( - _ capabilities.ActionCapability = &WriteTarget{} + _ capabilities.TargetCapability = &WriteTarget{} ) -// required field of target's config in the workflow spec -const signedReportField = "signed_report" - type WriteTarget struct { - cr commontypes.ContractReader + cr ContractValueGetter cw commontypes.ChainWriter + binding commontypes.BoundContract forwarderAddress string + // The minimum amount of gas that the receiver contract must get to process the forwarder report + receiverGasMinimum uint64 capabilities.CapabilityInfo lggr logger.Logger @@ -35,115 +36,237 @@ type WriteTarget struct { bound bool } -func NewWriteTarget(lggr logger.Logger, id string, cr commontypes.ContractReader, cw commontypes.ChainWriter, forwarderAddress string) *WriteTarget { +type TransmissionInfo struct { + GasLimit *big.Int + InvalidReceiver bool + State uint8 + Success bool + TransmissionId [32]byte + Transmitter common.Address +} + +// The gas cost of the forwarder contract logic, including state updates and event emission. +// This is a rough estimate and should be updated if the forwarder contract logic changes. +// TODO: Make this part of the on-chain capability configuration +const ForwarderContractLogicGasCost = 100_000 + +type ContractValueGetter interface { + Bind(context.Context, []commontypes.BoundContract) error + GetLatestValue(context.Context, string, primitives.ConfidenceLevel, any, any) error +} + +func NewWriteTarget( + lggr logger.Logger, + id string, + cr ContractValueGetter, + cw commontypes.ChainWriter, + forwarderAddress string, + txGasLimit uint64, +) *WriteTarget { info := capabilities.MustNewCapabilityInfo( id, capabilities.CapabilityTypeTarget, "Write target.", ) - logger := lggr.Named("WriteTarget") - return &WriteTarget{ cr, cw, + commontypes.BoundContract{ + Address: forwarderAddress, + Name: "forwarder", + }, forwarderAddress, + txGasLimit - ForwarderContractLogicGasCost, info, - logger, + logger.Named(lggr, "WriteTarget"), false, } } -type EvmConfig struct { - Address string +// Note: This should be a shared type that the OCR3 package validates as well +type ReportV1Metadata struct { + Version uint8 + WorkflowExecutionID [32]byte + Timestamp uint32 + DonID uint32 + DonConfigVersion uint32 + WorkflowCID [32]byte + WorkflowName [10]byte + WorkflowOwner [20]byte + ReportID [2]byte } -func parseConfig(rawConfig *values.Map) (config EvmConfig, err error) { - if err := rawConfig.UnwrapTo(&config); err != nil { - return config, err +func (rm ReportV1Metadata) Encode() ([]byte, error) { + buf := new(bytes.Buffer) + err := binary.Write(buf, binary.BigEndian, rm) + if err != nil { + return nil, err } - if !common.IsHexAddress(config.Address) { - return config, fmt.Errorf("'%v' is not a valid address", config.Address) + return buf.Bytes(), nil +} + +func (rm ReportV1Metadata) Length() int { + bytes, err := rm.Encode() + if err != nil { + return 0 } - return config, nil + return len(bytes) +} + +func decodeReportMetadata(data []byte) (metadata ReportV1Metadata, err error) { + if len(data) < metadata.Length() { + return metadata, fmt.Errorf("data too short: %d bytes", len(data)) + } + return metadata, binary.Read(bytes.NewReader(data[:metadata.Length()]), binary.BigEndian, &metadata) +} + +type Config struct { + // Address of the contract that will get the forwarded report + Address string + // Optional gas limit that overrides the default limit sent to the chain writer + GasLimit *uint64 +} + +type Inputs struct { + SignedReport types.SignedReport } -func success() <-chan capabilities.CapabilityResponse { - callback := make(chan capabilities.CapabilityResponse) - go func() { - callback <- capabilities.CapabilityResponse{} - close(callback) - }() - return callback +type Request struct { + Metadata capabilities.RequestMetadata + Config Config + Inputs Inputs } -func (cap *WriteTarget) Execute(ctx context.Context, request capabilities.CapabilityRequest) (<-chan capabilities.CapabilityResponse, error) { +func evaluate(rawRequest capabilities.CapabilityRequest) (r Request, err error) { + r.Metadata = rawRequest.Metadata + + if rawRequest.Config == nil { + return r, fmt.Errorf("missing config field") + } + + if err = rawRequest.Config.UnwrapTo(&r.Config); err != nil { + return r, err + } + + if !common.IsHexAddress(r.Config.Address) { + return r, fmt.Errorf("'%v' is not a valid address", r.Config.Address) + } + + if rawRequest.Inputs == nil { + return r, fmt.Errorf("missing inputs field") + } + + // required field of target's config in the workflow spec + const signedReportField = "signed_report" + signedReport, ok := rawRequest.Inputs.Underlying[signedReportField] + if !ok { + return r, fmt.Errorf("missing required field %s", signedReportField) + } + + if err = signedReport.UnwrapTo(&r.Inputs.SignedReport); err != nil { + return r, err + } + + reportMetadata, err := decodeReportMetadata(r.Inputs.SignedReport.Report) + if err != nil { + return r, err + } + + if reportMetadata.Version != 1 { + return r, fmt.Errorf("unsupported report version: %d", reportMetadata.Version) + } + + if hex.EncodeToString(reportMetadata.WorkflowExecutionID[:]) != rawRequest.Metadata.WorkflowExecutionID { + return r, fmt.Errorf("WorkflowExecutionID in the report does not match WorkflowExecutionID in the request metadata. Report WorkflowExecutionID: %+v, request WorkflowExecutionID: %+v", reportMetadata.WorkflowExecutionID, rawRequest.Metadata.WorkflowExecutionID) + } + + if hex.EncodeToString(reportMetadata.WorkflowOwner[:]) != rawRequest.Metadata.WorkflowOwner { + return r, fmt.Errorf("WorkflowOwner in the report does not match WorkflowOwner in the request metadata. Report WorkflowOwner: %+v, request WorkflowOwner: %+v", reportMetadata.WorkflowOwner, rawRequest.Metadata.WorkflowOwner) + } + + if hex.EncodeToString(reportMetadata.WorkflowName[:]) != rawRequest.Metadata.WorkflowName { + return r, fmt.Errorf("WorkflowName in the report does not match WorkflowName in the request metadata. Report WorkflowName: %+v, request WorkflowName: %+v", reportMetadata.WorkflowName, rawRequest.Metadata.WorkflowName) + } + + if hex.EncodeToString(reportMetadata.WorkflowCID[:]) != rawRequest.Metadata.WorkflowID { + return r, fmt.Errorf("WorkflowID in the report does not match WorkflowID in the request metadata. Report WorkflowID: %+v, request WorkflowID: %+v", reportMetadata.WorkflowCID, rawRequest.Metadata.WorkflowID) + } + + if !bytes.Equal(reportMetadata.ReportID[:], r.Inputs.SignedReport.ID) { + return r, fmt.Errorf("ReportID in the report does not match ReportID in the inputs. reportMetadata.ReportID: %x, Inputs.SignedReport.ID: %x", reportMetadata.ReportID, r.Inputs.SignedReport.ID) + } + + return r, nil +} + +func (cap *WriteTarget) Execute(ctx context.Context, rawRequest capabilities.CapabilityRequest) (capabilities.CapabilityResponse, error) { // Bind to the contract address on the write path. // Bind() requires a connection to the node's RPCs and // cannot be run during initialization. if !cap.bound { cap.lggr.Debugw("Binding to forwarder address") - err := cap.cr.Bind(ctx, []commontypes.BoundContract{{ - Address: cap.forwarderAddress, - Name: "forwarder", - }}) + err := cap.cr.Bind(ctx, []commontypes.BoundContract{cap.binding}) if err != nil { - return nil, err + return capabilities.CapabilityResponse{}, err } cap.bound = true } - cap.lggr.Debugw("Execute", "request", request) + cap.lggr.Debugw("Execute", "rawRequest", rawRequest) - reqConfig, err := parseConfig(request.Config) + request, err := evaluate(rawRequest) if err != nil { - return nil, err + return capabilities.CapabilityResponse{}, err } - signedReport, ok := request.Inputs.Underlying[signedReportField] - if !ok { - return nil, fmt.Errorf("missing required field %s", signedReportField) - } - - inputs := types.SignedReport{} - if err = signedReport.UnwrapTo(&inputs); err != nil { - return nil, err - } - - if len(inputs.Report) == 0 { - // We received any empty report -- this means we should skip transmission. - cap.lggr.Debugw("Skipping empty report", "request", request) - return success(), nil - } - // TODO: validate encoded report is prefixed with workflowID and executionID that match the request meta - rawExecutionID, err := hex.DecodeString(request.Metadata.WorkflowExecutionID) if err != nil { - return nil, err + return capabilities.CapabilityResponse{}, err } + // Check whether value was already transmitted on chain queryInputs := struct { Receiver string WorkflowExecutionID []byte ReportId []byte }{ - Receiver: reqConfig.Address, + Receiver: request.Config.Address, WorkflowExecutionID: rawExecutionID, - ReportId: inputs.ID, + ReportId: request.Inputs.SignedReport.ID, } - var transmitter common.Address - if err = cap.cr.GetLatestValue(ctx, "forwarder", "getTransmitter", primitives.Unconfirmed, queryInputs, &transmitter); err != nil { - return nil, fmt.Errorf("failed to getTransmitter latest value: %w", err) + var transmissionInfo TransmissionInfo + if err = cap.cr.GetLatestValue(ctx, cap.binding.ReadIdentifier("getTransmissionInfo"), primitives.Unconfirmed, queryInputs, &transmissionInfo); err != nil { + return capabilities.CapabilityResponse{}, fmt.Errorf("failed to getTransmissionInfo latest value: %w", err) } - if transmitter != common.HexToAddress("0x0") { - cap.lggr.Infow("WriteTarget report already onchain - returning without a tranmission attempt", "executionID", request.Metadata.WorkflowExecutionID) - return success(), nil + + switch { + case transmissionInfo.State == 0: // NOT_ATTEMPTED + cap.lggr.Infow("non-empty report - transmission not attempted - attempting to push to txmgr", "request", request, "reportLen", len(request.Inputs.SignedReport.Report), "reportContextLen", len(request.Inputs.SignedReport.Context), "nSignatures", len(request.Inputs.SignedReport.Signatures), "executionID", request.Metadata.WorkflowExecutionID) + case transmissionInfo.State == 1: // SUCCEEDED + cap.lggr.Infow("returning without a transmission attempt - report already onchain ", "executionID", request.Metadata.WorkflowExecutionID) + return capabilities.CapabilityResponse{}, nil + case transmissionInfo.State == 2: // INVALID_RECEIVER + cap.lggr.Infow("returning without a transmission attempt - transmission already attempted, receiver was marked as invalid", "executionID", request.Metadata.WorkflowExecutionID) + return capabilities.CapabilityResponse{}, nil + case transmissionInfo.State == 3: // FAILED + receiverGasMinimum := cap.receiverGasMinimum + if request.Config.GasLimit != nil { + receiverGasMinimum = *request.Config.GasLimit - ForwarderContractLogicGasCost + } + if transmissionInfo.GasLimit.Uint64() > receiverGasMinimum { + cap.lggr.Infow("returning without a transmission attempt - transmission already attempted and failed, sufficient gas was provided", "executionID", request.Metadata.WorkflowExecutionID, "receiverGasMinimum", receiverGasMinimum, "transmissionGasLimit", transmissionInfo.GasLimit) + return capabilities.CapabilityResponse{}, nil + } else { + cap.lggr.Infow("non-empty report - retrying a failed transmission - attempting to push to txmgr", "request", request, "reportLen", len(request.Inputs.SignedReport.Report), "reportContextLen", len(request.Inputs.SignedReport.Context), "nSignatures", len(request.Inputs.SignedReport.Signatures), "executionID", request.Metadata.WorkflowExecutionID, "receiverGasMinimum", receiverGasMinimum, "transmissionGasLimit", transmissionInfo.GasLimit) + } + default: + return capabilities.CapabilityResponse{}, fmt.Errorf("unexpected transmission state: %v", transmissionInfo.State) } - cap.lggr.Infow("WriteTarget non-empty report - attempting to push to txmgr", "request", request, "reportLen", len(inputs.Report), "reportContextLen", len(inputs.Context), "nSignatures", len(inputs.Signatures), "executionID", request.Metadata.WorkflowExecutionID) txID, err := uuid.NewUUID() // NOTE: CW expects us to generate an ID, rather than return one if err != nil { - return nil, err + return capabilities.CapabilityResponse{}, err } // Note: The codec that ChainWriter uses to encode the parameters for the contract ABI cannot handle @@ -154,7 +277,7 @@ func (cap *WriteTarget) Execute(ctx context.Context, request capabilities.Capabi RawReport []byte ReportContext []byte Signatures [][]byte - }{reqConfig.Address, inputs.Report, inputs.Context, inputs.Signatures} + }{request.Config.Address, request.Inputs.SignedReport.Report, request.Inputs.SignedReport.Context, request.Inputs.SignedReport.Signatures} if req.RawReport == nil { req.RawReport = make([]byte, 0) @@ -170,12 +293,23 @@ func (cap *WriteTarget) Execute(ctx context.Context, request capabilities.Capabi cap.lggr.Debugw("Transaction raw report", "report", hex.EncodeToString(req.RawReport)) meta := commontypes.TxMeta{WorkflowExecutionID: &request.Metadata.WorkflowExecutionID} + if request.Config.GasLimit != nil { + meta.GasLimit = new(big.Int).SetUint64(*request.Config.GasLimit) + } + value := big.NewInt(0) if err := cap.cw.SubmitTransaction(ctx, "forwarder", "report", req, txID.String(), cap.forwarderAddress, &meta, value); err != nil { - return nil, err + if !commontypes.ErrSettingTransactionGasLimitNotSupported.Is(err) { + return capabilities.CapabilityResponse{}, fmt.Errorf("failed to submit transaction: %w", err) + } + meta.GasLimit = nil + if err := cap.cw.SubmitTransaction(ctx, "forwarder", "report", req, txID.String(), cap.forwarderAddress, &meta, value); err != nil { + return capabilities.CapabilityResponse{}, fmt.Errorf("failed to submit transaction: %w", err) + } } + cap.lggr.Debugw("Transaction submitted", "request", request, "transaction", txID) - return success(), nil + return capabilities.CapabilityResponse{}, nil } func (cap *WriteTarget) RegisterToWorkflow(ctx context.Context, request capabilities.RegisterToWorkflowRequest) error { diff --git a/core/capabilities/targets/write_target_test.go b/core/capabilities/targets/write_target_test.go index e118433177..499f4f9b29 100644 --- a/core/capabilities/targets/write_target_test.go +++ b/core/capabilities/targets/write_target_test.go @@ -2,7 +2,9 @@ package targets_test import ( "context" + "encoding/hex" "errors" + "math/big" "testing" "github.com/ethereum/go-ethereum/common" @@ -12,7 +14,9 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/capabilities" "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" "github.com/smartcontractkit/chainlink-common/pkg/values" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/targets" "github.com/smartcontractkit/chainlink/v2/core/capabilities/targets/mocks" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" @@ -24,12 +28,12 @@ func TestWriteTarget(t *testing.T) { ctx := context.Background() cw := mocks.NewChainWriter(t) - cr := mocks.NewChainReader(t) + cr := mocks.NewContractValueGetter(t) forwarderA := testutils.NewAddress() forwarderAddr := forwarderA.Hex() - writeTarget := targets.NewWriteTarget(lggr, "test-write-target@1.0.0", cr, cw, forwarderAddr) + writeTarget := targets.NewWriteTarget(lggr, "test-write-target@1.0.0", cr, cw, forwarderAddr, 400_000) require.NotNil(t, writeTarget) config, err := values.NewMap(map[string]any{ @@ -37,99 +41,151 @@ func TestWriteTarget(t *testing.T) { }) require.NoError(t, err) + reportID := [2]byte{0x00, 0x01} + reportMetadata := targets.ReportV1Metadata{ + Version: 1, + WorkflowExecutionID: [32]byte{}, + Timestamp: 0, + DonID: 0, + DonConfigVersion: 0, + WorkflowCID: [32]byte{}, + WorkflowName: [10]byte{}, + WorkflowOwner: [20]byte{}, + ReportID: reportID, + } + + reportMetadataBytes, err := reportMetadata.Encode() + require.NoError(t, err) + validInputs, err := values.NewMap(map[string]any{ "signed_report": map[string]any{ - "report": []byte{1, 2, 3}, + "report": reportMetadataBytes, "signatures": [][]byte{}, + "context": []byte{4, 5}, + "id": reportID[:], }, }) require.NoError(t, err) - cr.On("Bind", mock.Anything, []types.BoundContract{ - { - Address: forwarderAddr, - Name: "forwarder", - }, - }).Return(nil) - - cr.On("GetLatestValue", mock.Anything, "forwarder", "getTransmitter", mock.Anything, mock.Anything, mock.Anything).Return(nil).Run(func(args mock.Arguments) { - transmitter := args.Get(5).(*common.Address) - *transmitter = common.HexToAddress("0x0") - }).Once() + validMetadata := capabilities.RequestMetadata{ + WorkflowID: hex.EncodeToString(reportMetadata.WorkflowCID[:]), + WorkflowOwner: hex.EncodeToString(reportMetadata.WorkflowOwner[:]), + WorkflowName: hex.EncodeToString(reportMetadata.WorkflowName[:]), + WorkflowExecutionID: hex.EncodeToString(reportMetadata.WorkflowExecutionID[:]), + } + + binding := types.BoundContract{ + Address: forwarderAddr, + Name: "forwarder", + } + + cr.On("Bind", mock.Anything, []types.BoundContract{binding}).Return(nil) + + cr.EXPECT().GetLatestValue(mock.Anything, binding.ReadIdentifier("getTransmissionInfo"), mock.Anything, mock.Anything, mock.Anything).Return(nil).Run(func(_ context.Context, _ string, _ primitives.ConfidenceLevel, _, retVal any) { + transmissionInfo := retVal.(*targets.TransmissionInfo) + *transmissionInfo = targets.TransmissionInfo{ + GasLimit: big.NewInt(0), + InvalidReceiver: false, + State: 0, + Success: false, + TransmissionId: [32]byte{}, + Transmitter: common.HexToAddress("0x0"), + } + }) cw.On("SubmitTransaction", mock.Anything, "forwarder", "report", mock.Anything, mock.Anything, forwarderAddr, mock.Anything, mock.Anything).Return(nil).Once() t.Run("succeeds with valid report", func(t *testing.T) { req := capabilities.CapabilityRequest{ - Metadata: capabilities.RequestMetadata{ - WorkflowID: "test-id", - }, - Config: config, - Inputs: validInputs, + Metadata: validMetadata, + Config: config, + Inputs: validInputs, } - ch, err2 := writeTarget.Execute(ctx, req) + response, err2 := writeTarget.Execute(ctx, req) require.NoError(t, err2) - response := <-ch require.NotNil(t, response) }) - t.Run("succeeds with empty report", func(t *testing.T) { - emptyInputs, err2 := values.NewMap(map[string]any{ - "signed_report": map[string]any{ - "report": []byte{}, - }, - "signatures": [][]byte{}, - }) + t.Run("fails when ChainWriter's SubmitTransaction returns error", func(t *testing.T) { + req := capabilities.CapabilityRequest{ + Metadata: validMetadata, + Config: config, + Inputs: validInputs, + } + cw.On("SubmitTransaction", mock.Anything, "forwarder", "report", mock.Anything, mock.Anything, forwarderAddr, mock.Anything, mock.Anything).Return(errors.New("writer error")) + + _, err = writeTarget.Execute(ctx, req) + require.Error(t, err) + }) + t.Run("passes gas limit set on config to the chain writer", func(t *testing.T) { + configGasLimit, err2 := values.NewMap(map[string]any{ + "Address": forwarderAddr, + "GasLimit": 500000, + }) require.NoError(t, err2) req := capabilities.CapabilityRequest{ - Metadata: capabilities.RequestMetadata{ - WorkflowExecutionID: "test-id", - }, - Config: config, - Inputs: emptyInputs, + Metadata: validMetadata, + Config: configGasLimit, + Inputs: validInputs, } - ch, err2 := writeTarget.Execute(ctx, req) - require.NoError(t, err2) - response := <-ch - require.Nil(t, response.Value) + meta := types.TxMeta{WorkflowExecutionID: &req.Metadata.WorkflowExecutionID, GasLimit: big.NewInt(500000)} + cw.On("SubmitTransaction", mock.Anything, "forwarder", "report", mock.Anything, mock.Anything, forwarderAddr, &meta, mock.Anything).Return(types.ErrSettingTransactionGasLimitNotSupported) + + _, err2 = writeTarget.Execute(ctx, req) + require.Error(t, err2) }) - t.Run("fails when ChainReader's GetLatestValue returns error", func(t *testing.T) { + t.Run("retries without gas limit when ChainWriter's SubmitTransaction returns error due to gas limit not supported", func(t *testing.T) { + configGasLimit, err2 := values.NewMap(map[string]any{ + "Address": forwarderAddr, + "GasLimit": 500000, + }) + require.NoError(t, err2) req := capabilities.CapabilityRequest{ - Metadata: capabilities.RequestMetadata{ - WorkflowID: "test-id", - }, - Config: config, - Inputs: validInputs, + Metadata: validMetadata, + Config: configGasLimit, + Inputs: validInputs, } - cr.On("GetLatestValue", mock.Anything, "forwarder", "getTransmitter", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("reader error")) - _, err = writeTarget.Execute(ctx, req) - require.Error(t, err) + meta := types.TxMeta{WorkflowExecutionID: &req.Metadata.WorkflowExecutionID, GasLimit: big.NewInt(500000)} + cw.On("SubmitTransaction", mock.Anything, "forwarder", "report", mock.Anything, mock.Anything, forwarderAddr, &meta, mock.Anything).Return(types.ErrSettingTransactionGasLimitNotSupported) + meta = types.TxMeta{WorkflowExecutionID: &req.Metadata.WorkflowExecutionID} + cw.On("SubmitTransaction", mock.Anything, "forwarder", "report", mock.Anything, mock.Anything, forwarderAddr, &meta, mock.Anything).Return(nil) + + configGasLimit, err = values.NewMap(map[string]any{ + "Address": forwarderAddr, + }) + require.NoError(t, err) + req = capabilities.CapabilityRequest{ + Metadata: validMetadata, + Config: configGasLimit, + Inputs: validInputs, + } + + _, err2 = writeTarget.Execute(ctx, req) + require.Error(t, err2) }) - t.Run("fails when ChainWriter's SubmitTransaction returns error", func(t *testing.T) { + t.Run("fails when ChainReader's GetLatestValue returns error", func(t *testing.T) { req := capabilities.CapabilityRequest{ - Metadata: capabilities.RequestMetadata{ - WorkflowID: "test-id", - }, - Config: config, - Inputs: validInputs, + Metadata: validMetadata, + Config: config, + Inputs: validInputs, } - cw.On("SubmitTransaction", mock.Anything, "forwarder", "report", mock.Anything, mock.Anything, forwarderAddr, mock.Anything, mock.Anything).Return(errors.New("writer error")) + cr.EXPECT().GetLatestValue(mock.Anything, binding.ReadIdentifier("getTransmissionInfo"), mock.Anything, mock.Anything, mock.Anything).Return(errors.New("reader error")) _, err = writeTarget.Execute(ctx, req) require.Error(t, err) }) t.Run("fails with invalid config", func(t *testing.T) { - invalidConfig, err := values.NewMap(map[string]any{ + invalidConfig, err2 := values.NewMap(map[string]any{ "Address": "invalid-address", }) - require.NoError(t, err) + require.NoError(t, err2) req := capabilities.CapabilityRequest{ Metadata: capabilities.RequestMetadata{ @@ -138,7 +194,27 @@ func TestWriteTarget(t *testing.T) { Config: invalidConfig, Inputs: validInputs, } - _, err = writeTarget.Execute(ctx, req) - require.Error(t, err) + _, err2 = writeTarget.Execute(ctx, req) + require.Error(t, err2) + }) + + t.Run("fails with nil config", func(t *testing.T) { + req := capabilities.CapabilityRequest{ + Metadata: validMetadata, + Config: nil, + Inputs: validInputs, + } + _, err2 := writeTarget.Execute(ctx, req) + require.Error(t, err2) + }) + + t.Run("fails with nil inputs", func(t *testing.T) { + req := capabilities.CapabilityRequest{ + Metadata: validMetadata, + Config: config, + Inputs: nil, + } + _, err2 := writeTarget.Execute(ctx, req) + require.Error(t, err2) }) } diff --git a/core/capabilities/transmission/local_target_capability.go b/core/capabilities/transmission/local_target_capability.go index 1240d3a0e7..637aea4523 100644 --- a/core/capabilities/transmission/local_target_capability.go +++ b/core/capabilities/transmission/local_target_capability.go @@ -27,7 +27,7 @@ func NewLocalTargetCapability(lggr logger.Logger, capabilityID string, localDON } } -func (l *LocalTargetCapability) Execute(ctx context.Context, req capabilities.CapabilityRequest) (<-chan capabilities.CapabilityResponse, error) { +func (l *LocalTargetCapability) Execute(ctx context.Context, req capabilities.CapabilityRequest) (capabilities.CapabilityResponse, error) { if l.localNode.PeerID == nil || l.localNode.WorkflowDON.ID == 0 { l.lggr.Debugf("empty DON info, executing immediately") return l.TargetCapability.Execute(ctx, req) @@ -40,17 +40,17 @@ func (l *LocalTargetCapability) Execute(ctx context.Context, req capabilities.Ca peerIDToTransmissionDelay, err := GetPeerIDToTransmissionDelay(l.localNode.WorkflowDON.Members, req) if err != nil { - return nil, fmt.Errorf("capability id: %s failed to get peer ID to transmission delay map: %w", l.capabilityID, err) + return capabilities.CapabilityResponse{}, fmt.Errorf("capability id: %s failed to get peer ID to transmission delay map: %w", l.capabilityID, err) } delay, existsForPeerID := peerIDToTransmissionDelay[*l.localNode.PeerID] if !existsForPeerID { - return nil, nil + return capabilities.CapabilityResponse{}, nil } select { case <-ctx.Done(): - return nil, ctx.Err() + return capabilities.CapabilityResponse{}, ctx.Err() case <-time.After(delay): return l.TargetCapability.Execute(ctx, req) } diff --git a/core/capabilities/transmission/local_target_capability_test.go b/core/capabilities/transmission/local_target_capability_test.go index 93bf708cce..67f22753bd 100644 --- a/core/capabilities/transmission/local_target_capability_test.go +++ b/core/capabilities/transmission/local_target_capability_test.go @@ -54,8 +54,8 @@ func TestScheduledExecutionStrategy_LocalDON(t *testing.T) { name: "position 0; oneAtATime", position: 0, schedule: "oneAtATime", - low: 300 * time.Millisecond, - high: 400 * time.Millisecond, + low: 200 * time.Millisecond, + high: 300 * time.Millisecond, }, { name: "position 1; oneAtATime", @@ -68,15 +68,15 @@ func TestScheduledExecutionStrategy_LocalDON(t *testing.T) { name: "position 2; oneAtATime", position: 2, schedule: "oneAtATime", - low: 0 * time.Millisecond, - high: 100 * time.Millisecond, + low: 300 * time.Millisecond, + high: 400 * time.Millisecond, }, { name: "position 3; oneAtATime", position: 3, schedule: "oneAtATime", - low: 100 * time.Millisecond, - high: 300 * time.Millisecond, + low: 0 * time.Millisecond, + high: 100 * time.Millisecond, }, { name: "position 0; allAtOnce", @@ -121,8 +121,8 @@ func TestScheduledExecutionStrategy_LocalDON(t *testing.T) { req := capabilities.CapabilityRequest{ Config: m, Metadata: capabilities.RequestMetadata{ - WorkflowID: "mock-workflow-id", - WorkflowExecutionID: "mock-execution-id-1", + WorkflowID: "15c631d295ef5e32deb99a10ee6804bc4af13855687559d7ff6552ac6dbb2ce0", + WorkflowExecutionID: "32c631d295ef5e32deb99a10ee6804bc4af13855687559d7ff6552ac6dbb2ce1", }, } @@ -162,7 +162,7 @@ func randKey() [32]byte { type mockCapability struct { capabilities.CapabilityInfo - capabilities.CallbackExecutable + capabilities.Executable response chan capabilities.CapabilityResponse transform func(capabilities.CapabilityRequest) (capabilities.CapabilityResponse, error) } @@ -175,18 +175,14 @@ func newMockCapability(info capabilities.CapabilityInfo, transform func(capabili } } -func (m *mockCapability) Execute(ctx context.Context, req capabilities.CapabilityRequest) (<-chan capabilities.CapabilityResponse, error) { +func (m *mockCapability) Execute(ctx context.Context, req capabilities.CapabilityRequest) (capabilities.CapabilityResponse, error) { cr, err := m.transform(req) if err != nil { - return nil, err + return capabilities.CapabilityResponse{}, err } - ch := make(chan capabilities.CapabilityResponse, 10) - m.response <- cr - ch <- cr - close(ch) - return ch, nil + return cr, nil } func (m *mockCapability) RegisterToWorkflow(ctx context.Context, request capabilities.RegisterToWorkflowRequest) error { diff --git a/core/capabilities/transmission/transmission.go b/core/capabilities/transmission/transmission.go index b41be5bcaa..88ce0fa3ed 100644 --- a/core/capabilities/transmission/transmission.go +++ b/core/capabilities/transmission/transmission.go @@ -4,10 +4,10 @@ import ( "fmt" "time" - "github.com/pkg/errors" - "github.com/smartcontractkit/libocr/permutation" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/validation" + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" "github.com/smartcontractkit/chainlink-common/pkg/values" "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types" @@ -56,8 +56,12 @@ func GetPeerIDToTransmissionDelay(donPeerIDs []types.PeerID, req capabilities.Ca return nil, fmt.Errorf("failed to extract transmission config from request: %w", err) } - if req.Metadata.WorkflowID == "" || req.Metadata.WorkflowExecutionID == "" { - return nil, errors.New("workflow ID and workflow execution ID must be set in request metadata") + if err = validation.ValidateWorkflowOrExecutionID(req.Metadata.WorkflowID); err != nil { + return nil, fmt.Errorf("workflow ID is invalid: %w", err) + } + + if err = validation.ValidateWorkflowOrExecutionID(req.Metadata.WorkflowExecutionID); err != nil { + return nil, fmt.Errorf("workflow execution ID is invalid: %w", err) } transmissionID := req.Metadata.WorkflowID + req.Metadata.WorkflowExecutionID diff --git a/core/capabilities/transmission/transmission_test.go b/core/capabilities/transmission/transmission_test.go index fba233eadb..aaa367e78c 100644 --- a/core/capabilities/transmission/transmission_test.go +++ b/core/capabilities/transmission/transmission_test.go @@ -36,20 +36,21 @@ func Test_GetPeerIDToTransmissionDelay(t *testing.T) { "one", "oneAtATime", "100ms", - "mock-execution-id", + "15c631d295ef5e32deb99a10ee6804bc4af13855687559d7ff6552ac6dbb2ce0", map[string]time.Duration{ "one": 300 * time.Millisecond, - "two": 100 * time.Millisecond, - "three": 0 * time.Millisecond, + "two": 0 * time.Millisecond, + "three": 100 * time.Millisecond, "four": 200 * time.Millisecond, }, }, + { "TestAllAtOnce", "one", "allAtOnce", "100ms", - "mock-execution-id", + "15c631d295ef5e32deb99a10ee6804bc4af13855687559d7ff6552ac6dbb2ce0", map[string]time.Duration{ "one": 0 * time.Millisecond, "two": 0 * time.Millisecond, @@ -57,17 +58,18 @@ func Test_GetPeerIDToTransmissionDelay(t *testing.T) { "four": 0 * time.Millisecond, }, }, + { "TestOneAtATimeWithDifferentExecutionID", "one", "oneAtATime", "100ms", - "mock-execution-id2", + "16c631d295ef5e32deb99a10ee6804bc4af13855687559d7ff6552ac6dbb2ce1", map[string]time.Duration{ - "one": 0 * time.Millisecond, - "two": 200 * time.Millisecond, - "three": 100 * time.Millisecond, - "four": 300 * time.Millisecond, + "one": 300 * time.Millisecond, + "two": 100 * time.Millisecond, + "three": 200 * time.Millisecond, + "four": 0 * time.Millisecond, }, }, } @@ -83,7 +85,7 @@ func Test_GetPeerIDToTransmissionDelay(t *testing.T) { capabilityRequest := capabilities.CapabilityRequest{ Config: transmissionCfg, Metadata: capabilities.RequestMetadata{ - WorkflowID: "mock-workflow-id", + WorkflowID: "17c631d295ef5e32deb99a10ee6804bc4af13855687559d7ff6552ac6dbb2ce0", WorkflowExecutionID: tc.workflowExecutionID, }, } diff --git a/core/capabilities/validation/validation.go b/core/capabilities/validation/validation.go new file mode 100644 index 0000000000..67ee3a504c --- /dev/null +++ b/core/capabilities/validation/validation.go @@ -0,0 +1,38 @@ +package validation + +import ( + "encoding/hex" + "errors" + "unicode" +) + +const ( + validWorkflowIDLen = 64 + maxIDLen = 128 +) + +// Workflow IDs and Execution IDs are 32-byte hex-encoded strings +func ValidateWorkflowOrExecutionID(id string) error { + if len(id) != validWorkflowIDLen { + return errors.New("must be 32 bytes long") + } + _, err := hex.DecodeString(id) + if err != nil { + return errors.New("must be a hex-encoded string") + } + + return nil +} + +// Trigger event IDs and message IDs can only contain printable characters and must be non-empty +func IsValidID(id string) bool { + if len(id) == 0 || len(id) > maxIDLen { + return false + } + for i := 0; i < len(id); i++ { + if !unicode.IsPrint(rune(id[i])) { + return false + } + } + return true +} diff --git a/core/capabilities/validation/validation_test.go b/core/capabilities/validation/validation_test.go new file mode 100644 index 0000000000..205898652f --- /dev/null +++ b/core/capabilities/validation/validation_test.go @@ -0,0 +1,19 @@ +package validation + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestIsValidWorkflowID(t *testing.T) { + require.NotNil(t, ValidateWorkflowOrExecutionID("too_short")) + require.NotNil(t, ValidateWorkflowOrExecutionID("nothex--95ef5e32deb99a10ee6804bc4af13855687559d7ff6552ac6dbb2ce0")) + require.NoError(t, ValidateWorkflowOrExecutionID("15c631d295ef5e32deb99a10ee6804bc4af13855687559d7ff6552ac6dbb2ce0")) +} + +func TestIsValidTriggerEventID(t *testing.T) { + require.False(t, IsValidID("")) + require.False(t, IsValidID("\n\n")) + require.True(t, IsValidID("id_id_2")) +} diff --git a/core/capabilities/webapi/trigger.go b/core/capabilities/webapi/trigger.go new file mode 100644 index 0000000000..db0df7d141 --- /dev/null +++ b/core/capabilities/webapi/trigger.go @@ -0,0 +1,18 @@ +package webapi + +import ( + "github.com/smartcontractkit/chainlink-common/pkg/types/core" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/connector" + "github.com/smartcontractkit/chainlink/v2/core/services/job" +) + +func NewTrigger(config string, registry core.CapabilitiesRegistry, connector connector.GatewayConnector, lggr logger.Logger) (job.ServiceCtx, error) { + // TODO (CAPPL-22, CAPPL-24): + // - decode config + // - create an implementation of the capability API and add it to the Registry + // - create a handler and register it with Gateway Connector + // - manage trigger subscriptions + // - process incoming trigger events and related metadata + return nil, nil +} diff --git a/core/chainlink.goreleaser.Dockerfile b/core/chainlink.goreleaser.Dockerfile index 5d172fd77e..c229ad488c 100644 --- a/core/chainlink.goreleaser.Dockerfile +++ b/core/chainlink.goreleaser.Dockerfile @@ -20,8 +20,9 @@ COPY ./chainlink /usr/local/bin/ # Copy native libs if cgo is enabled COPY ./tmp/linux_${TARGETARCH}/libs /usr/local/bin/libs -# Copy plugins and enable them -COPY ./tmp/linux_${TARGETARCH}/plugins/* /usr/local/bin/ +# Copy plugins if exist and enable them +# https://stackoverflow.com/questions/70096208/dockerfile-copy-folder-if-it-exists-conditional-copy/70096420#70096420 +COPY ./tmp/linux_${TARGETARCH}/plugin[s] /usr/local/bin/ # Allow individual plugins to be enabled by supplying their path ARG CL_MEDIAN_CMD ARG CL_MERCURY_CMD @@ -37,6 +38,11 @@ RUN chmod +x /usr/local/bin/ldd_fix RUN /usr/local/bin/ldd_fix RUN apt-get remove -y patchelf +# CCIP specific +COPY ./cci[p]/confi[g] /chainlink/ccip-config +ARG CL_CHAIN_DEFAULTS +ENV CL_CHAIN_DEFAULTS=${CL_CHAIN_DEFAULTS} + RUN if [ ${CHAINLINK_USER} != root ]; then \ useradd --uid 14933 --create-home ${CHAINLINK_USER}; \ fi diff --git a/core/chains/chain_kv_test.go b/core/chains/chain_kv_test.go index 205ee693d6..7c9336285e 100644 --- a/core/chains/chain_kv_test.go +++ b/core/chains/chain_kv_test.go @@ -89,6 +89,11 @@ func (s *testChainService) HealthReport() map[string]error { return map[string]error{} } +// Implement [types.LatestHead] interface +func (s *testChainService) LatestHead(_ context.Context) (head types.Head, err error) { + return +} + // Implement [types.ChainService] interface func (s *testChainService) GetChainStatus(ctx context.Context) (stat types.ChainStatus, err error) { return diff --git a/core/chains/evm/client/config_builder.go b/core/chains/evm/client/config_builder.go index fa702bac11..9a31f9e4b4 100644 --- a/core/chains/evm/client/config_builder.go +++ b/core/chains/evm/client/config_builder.go @@ -43,7 +43,7 @@ func NewClientConfigs( deathDeclarationDelay time.Duration, noNewFinalizedHeadsThreshold time.Duration, finalizedBlockPollInterval time.Duration, - + newHeadsPollInterval time.Duration, ) (commonclient.ChainConfig, evmconfig.NodePool, []*toml.Node, error) { nodes, err := parseNodeConfigs(nodeCfgs) if err != nil { @@ -59,12 +59,13 @@ func NewClientConfigs( EnforceRepeatableRead: enforceRepeatableRead, DeathDeclarationDelay: commonconfig.MustNewDuration(deathDeclarationDelay), FinalizedBlockPollInterval: commonconfig.MustNewDuration(finalizedBlockPollInterval), + NewHeadsPollInterval: commonconfig.MustNewDuration(newHeadsPollInterval), } nodePoolCfg := &evmconfig.NodePoolConfig{C: nodePool} chainConfig := &evmconfig.EVMConfig{ C: &toml.EVMConfig{ Chain: toml.Chain{ - ChainType: chaintype.NewChainTypeConfig(chainType), + ChainType: chaintype.NewConfig(chainType), FinalityDepth: finalityDepth, FinalityTagEnabled: finalityTagEnabled, NoNewHeadsThreshold: commonconfig.MustNewDuration(noNewHeadsThreshold), diff --git a/core/chains/evm/client/config_builder_test.go b/core/chains/evm/client/config_builder_test.go index 403c6c2d61..0339131171 100644 --- a/core/chains/evm/client/config_builder_test.go +++ b/core/chains/evm/client/config_builder_test.go @@ -37,9 +37,11 @@ func TestClientConfigBuilder(t *testing.T) { finalityDepth := ptr(uint32(10)) finalityTagEnabled := ptr(true) noNewHeadsThreshold := time.Second + newHeadsPollInterval := 0 * time.Second chainCfg, nodePool, nodes, err := client.NewClientConfigs(selectionMode, leaseDuration, chainTypeStr, nodeConfigs, pollFailureThreshold, pollInterval, syncThreshold, nodeIsSyncingEnabled, noNewHeadsThreshold, finalityDepth, - finalityTagEnabled, finalizedBlockOffset, enforceRepeatableRead, deathDeclarationDelay, noNewFinalizedBlocksThreshold, pollInterval) + finalityTagEnabled, finalizedBlockOffset, enforceRepeatableRead, deathDeclarationDelay, noNewFinalizedBlocksThreshold, + pollInterval, newHeadsPollInterval) require.NoError(t, err) // Validate node pool configs @@ -52,10 +54,8 @@ func TestClientConfigBuilder(t *testing.T) { require.Equal(t, *enforceRepeatableRead, nodePool.EnforceRepeatableRead()) require.Equal(t, deathDeclarationDelay, nodePool.DeathDeclarationDelay()) require.Equal(t, pollInterval, nodePool.FinalizedBlockPollInterval()) - - // Validate node configs + require.Equal(t, newHeadsPollInterval, nodePool.NewHeadsPollInterval()) require.Equal(t, *nodeConfigs[0].Name, *nodes[0].Name) - require.Equal(t, *nodeConfigs[0].WSURL, (*nodes[0].WSURL).String()) require.Equal(t, *nodeConfigs[0].HTTPURL, (*nodes[0].HTTPURL).String()) // Validate chain config diff --git a/core/chains/evm/client/errors.go b/core/chains/evm/client/errors.go index c3a2b068d3..7a21a3841c 100644 --- a/core/chains/evm/client/errors.go +++ b/core/chains/evm/client/errors.go @@ -10,6 +10,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rpc" pkgerrors "github.com/pkg/errors" "github.com/smartcontractkit/chainlink-common/pkg/logger" @@ -62,6 +63,7 @@ const ( Fatal ServiceUnavailable TerminallyStuck + TooManyResults ) type ClientErrors map[int]*regexp.Regexp @@ -266,6 +268,16 @@ var mantle = ClientErrors{ NonceTooLow: regexp.MustCompile(`(: |^)'*nonce too low`), } +var hederaFatal = regexp.MustCompile(`(: |^)(execution reverted)(:|$) | ^Transaction gas limit '(\d+)' exceeds block gas limit '(\d+)' | ^Transaction gas limit provided '(\d+)' is insufficient of intrinsic gas required '(\d+)' | ^Oversized data:|status INVALID_SIGNATURE`) +var hedera = ClientErrors{ + NonceTooLow: regexp.MustCompile(`Nonce too low`), + NonceTooHigh: regexp.MustCompile(`Nonce too high`), + TerminallyUnderpriced: regexp.MustCompile(`(Gas price '(\d+)' is below configured minimum gas price '(\d+)')|(Gas price too low)`), + InsufficientEth: regexp.MustCompile(`Insufficient funds for transfer| failed precheck with status INSUFFICIENT_PAYER_BALANCE`), + ServiceUnavailable: regexp.MustCompile(`Transaction execution returns a null value for transaction`), + Fatal: hederaFatal, +} + var gnosis = ClientErrors{ TransactionAlreadyInMempool: regexp.MustCompile(`(: |^)(alreadyknown)`), } @@ -277,7 +289,7 @@ var internal = ClientErrors{ TerminallyStuck: regexp.MustCompile(TerminallyStuckMsg), } -var clients = []ClientErrors{parity, geth, arbitrum, metis, substrate, avalanche, nethermind, harmony, besu, erigon, klaytn, celo, zkSync, zkEvm, treasure, mantle, aStar, gnosis, internal} +var clients = []ClientErrors{parity, geth, arbitrum, metis, substrate, avalanche, nethermind, harmony, besu, erigon, klaytn, celo, zkSync, zkEvm, treasure, mantle, aStar, hedera, gnosis, internal} // ClientErrorRegexes returns a map of compiled regexes for each error type func ClientErrorRegexes(errsRegex config.ClientErrors) *ClientErrors { @@ -299,6 +311,7 @@ func ClientErrorRegexes(errsRegex config.ClientErrors) *ClientErrors { TransactionAlreadyMined: regexp.MustCompile(errsRegex.TransactionAlreadyMined()), Fatal: regexp.MustCompile(errsRegex.Fatal()), ServiceUnavailable: regexp.MustCompile(errsRegex.ServiceUnavailable()), + TooManyResults: regexp.MustCompile(errsRegex.TooManyResults()), } } @@ -458,6 +471,11 @@ func isFatalSendError(err error) bool { return false } +var ( + _ rpc.Error = JsonError{} + _ rpc.DataError = JsonError{} +) + // go-ethereum@v1.10.0/rpc/json.go type JsonError struct { Code int `json:"code"` @@ -472,7 +490,17 @@ func (err JsonError) Error() string { return err.Message } -func (err *JsonError) String() string { +// To satisfy rpc.Error interface +func (err JsonError) ErrorCode() int { + return err.Code +} + +// To satisfy rpc.DataError +func (err JsonError) ErrorData() interface{} { + return err.Data +} + +func (err JsonError) String() string { return fmt.Sprintf("json-rpc error { Code = %d, Message = '%s', Data = '%v' }", err.Code, err.Message, err.Data) } @@ -611,3 +639,88 @@ func ClassifySendError(err error, clientErrors config.ClientErrors, lggr logger. lggr.Criticalw("Unknown error encountered when sending transaction", "err", err, "etx", tx) return commonclient.Unknown } + +var infura = ClientErrors{ + TooManyResults: regexp.MustCompile(`(: |^)query returned more than [0-9]+ results. Try with this block range \[0x[0-9A-F]+, 0x[0-9A-F]+\].$`), +} + +var alchemy = ClientErrors{ + TooManyResults: regexp.MustCompile(`(: |^)Log response size exceeded. You can make eth_getLogs requests with up to a [0-9A-Z]+ block range and no limit on the response size, or you can request any block range with a cap of [0-9A-Z]+ logs in the response. Based on your parameters and the response size limit, this block range should work: \[0x[0-9a-f]+, 0x[0-9a-f]+\]$`), +} + +var quicknode = ClientErrors{ + TooManyResults: regexp.MustCompile(`(: |^)eth_getLogs is limited to a [0-9,]+ range$`), +} + +var simplyvc = ClientErrors{ + TooManyResults: regexp.MustCompile(`too wide blocks range, the limit is [0-9,]+$`), +} + +var drpc = ClientErrors{ + TooManyResults: regexp.MustCompile(`(: |^)requested too many blocks from [0-9]+ to [0-9]+, maximum is set to [0-9,]+$`), +} + +// Linkpool, Blockdaemon, and Chainstack all return "request timed out" if the log results are too large for them to process +var defaultClient = ClientErrors{ + TooManyResults: regexp.MustCompile(`request timed out`), +} + +// JSON-RPC error codes which can indicate a refusal of the server to process an eth_getLogs request because the result set is too large +const ( + jsonRpcServerError = -32000 // Server error. SimplyVC uses this error code when too many results are returned + + // Server timeout. When the rpc server has its own limit on how long it can take to compile the results + // Examples: Linkpool, Chainstack, Block Daemon + jsonRpcTimedOut = -32002 + + // See: https://github.com/ethereum/go-ethereum/blob/master/rpc/errors.go#L63 + // Can occur if the rpc server is configured with a maximum byte limit on the response size of batch requests + jsonRpcResponseTooLarge = -32003 + + // Not implemented in geth by default, but is defined in EIP 1474 and implemented by infura and some other 3rd party rpc servers + // See: https://community.infura.io/t/getlogs-error-query-returned-more-than-1000-results/358/5 + jsonRpcLimitExceeded = -32005 // See also: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1474.md + + jsonRpcInvalidParams = -32602 // Invalid method params. Returned by alchemy if the block range is too large or there are too many results to return + + jsonRpcQuicknodeTooManyResults = -32614 // Undocumented error code used by Quicknode for too many results error +) + +func IsTooManyResults(err error, clientErrors config.ClientErrors) bool { + var rpcErr rpc.Error + + if !pkgerrors.As(err, &rpcErr) { + return false + } + configErrors := ClientErrorRegexes(clientErrors) + if configErrors.ErrIs(rpcErr, TooManyResults) { + return true + } + + switch rpcErr.ErrorCode() { + case jsonRpcResponseTooLarge: + return true + case jsonRpcLimitExceeded: + if infura.ErrIs(rpcErr, TooManyResults) { + return true + } + case jsonRpcInvalidParams: + if alchemy.ErrIs(rpcErr, TooManyResults) { + return true + } + case jsonRpcQuicknodeTooManyResults: + if quicknode.ErrIs(rpcErr, TooManyResults) { + return true + } + case jsonRpcTimedOut: + if defaultClient.ErrIs(rpcErr, TooManyResults) { + return true + } + case jsonRpcServerError: + if simplyvc.ErrIs(rpcErr, TooManyResults) || + drpc.ErrIs(rpcErr, TooManyResults) { + return true + } + } + return false +} diff --git a/core/chains/evm/client/errors_test.go b/core/chains/evm/client/errors_test.go index 8b9f67ebe5..e6d2f2d08b 100644 --- a/core/chains/evm/client/errors_test.go +++ b/core/chains/evm/client/errors_test.go @@ -1,7 +1,9 @@ package client_test import ( + "encoding/json" "errors" + "fmt" "testing" pkgerrors "github.com/pkg/errors" @@ -44,6 +46,7 @@ func Test_Eth_Errors(t *testing.T) { {"call failed: OldNonce, Current nonce: 22, nonce of rejected tx: 17", true, "Nethermind"}, {"nonce too low. allowed nonce range: 427 - 447, actual: 426", true, "zkSync"}, {"client error nonce too low", true, "tomlConfig"}, + {"[Request ID: 2e952947-ffad-408b-aed9-35f3ed152001] Nonce too low. Provided nonce: 15, current nonce: 15", true, "hedera"}, {"failed to forward tx to sequencer, please try again. Error message: 'nonce too low'", true, "Mantle"}, } @@ -66,6 +69,7 @@ func Test_Eth_Errors(t *testing.T) { {"nonce too high", true, "Erigon"}, {"nonce too high. allowed nonce range: 427 - 477, actual: 527", true, "zkSync"}, {"client error nonce too high", true, "tomlConfig"}, + {"[Request ID: 3ec591b4-9396-49f4-a03f-06c415a7cc6a] Nonce too high. Provided nonce: 16, current nonce: 15", true, "hedera"}, } for _, test := range tests { @@ -168,6 +172,8 @@ func Test_Eth_Errors(t *testing.T) { {"max fee per gas less than block base fee", true, "zkSync"}, {"virtual machine entered unexpected state. please contact developers and provide transaction details that caused this error. Error description: The operator included transaction with an unacceptable gas price", true, "zkSync"}, {"client error terminally underpriced", true, "tomlConfig"}, + {"gas price less than block base fee", true, "aStar"}, + {"[Request ID: e4d09e44-19a4-4eb7-babe-270db4c2ebc9] Gas price '830000000000' is below configured minimum gas price '950000000000'", true, "hedera"}, } for _, test := range tests { @@ -217,6 +223,8 @@ func Test_Eth_Errors(t *testing.T) { {"client error insufficient eth", true, "tomlConfig"}, {"transaction would cause overdraft", true, "Geth"}, {"failed to forward tx to sequencer, please try again. Error message: 'insufficient funds for gas * price + value'", true, "Mantle"}, + {"[Request ID: 9dd78806-58c8-4e6d-89a8-a60962abe705] Error invoking RPC: transaction 0.0.3041916@1717691931.680570179 failed precheck with status INSUFFICIENT_PAYER_BALANCE", true, "hedera"}, + {"[Request ID: 6198d2a3-590f-4724-aae5-69fecead0c49] Insufficient funds for transfer", true, "hedera"}, } for _, test := range tests { err = evmclient.NewSendErrorS(test.message) @@ -233,6 +241,7 @@ func Test_Eth_Errors(t *testing.T) { {"i/o timeout", true, "Arbitrum"}, {"network is unreachable", true, "Arbitrum"}, {"client error service unavailable", true, "tomlConfig"}, + {"[Request ID: 825608a8-fd8a-4b5b-aea7-92999509306d] Error invoking RPC: [Request ID: 825608a8-fd8a-4b5b-aea7-92999509306d] Transaction execution returns a null value for transaction", true, "hedera"}, } for _, test := range tests { err = evmclient.NewSendErrorS(test.message) @@ -407,6 +416,7 @@ func Test_Eth_Errors_Fatal(t *testing.T) { {"failed to forward tx to sequencer, please try again. Error message: 'invalid sender'", true, "Mantle"}, {"client error fatal", true, "tomlConfig"}, + {"[Request ID: d9711488-4c1e-4af2-bc1f-7969913d7b60] Error invoking RPC: transaction 0.0.4425573@1718213476.914320044 failed precheck with status INVALID_SIGNATURE", true, "hedera"}, {"invalid chain id for signer", true, "Treasure"}, } @@ -441,3 +451,86 @@ func Test_Config_Errors(t *testing.T) { assert.False(t, clientErrors.ErrIs(errors.New("some old bollocks"), evmclient.NonceTooLow)) }) } + +func Test_IsTooManyResultsError(t *testing.T) { + customErrors := evmclient.NewTestClientErrors() + + tests := []errorCase{ + {`{ + "code":-32602, + "message":"Log response size exceeded. You can make eth_getLogs requests with up to a 2K block range and no limit on the response size, or you can request any block range with a cap of 10K logs in the response. Based on your parameters and the response size limit, this block range should work: [0x0, 0x133e71]"}`, + true, + "alchemy", + }, {`{ + "code":-32005, + "data":{"from":"0xCB3D","limit":10000,"to":"0x7B737"}, + "message":"query returned more than 10000 results. Try with this block range [0xCB3D, 0x7B737]."}`, + true, + "infura", + }, {`{ + "code":-32002, + "message":"request timed out"}`, + true, + "LinkPool-Blockdaemon-Chainstack", + }, {`{ + "code":-32614, + "message":"eth_getLogs is limited to a 10,000 range"}`, + true, + "Quicknode", + }, {`{ + "code":-32000, + "message":"too wide blocks range, the limit is 100"}`, + true, + "SimplyVC", + }, {`{ + "message":"requested too many blocks from 0 to 16777216, maximum is set to 2048", + "code":-32000}`, + true, + "Drpc", + }, {` + + + + 503 Backend fetch failed + + +

Error 503 Backend fetch failed

+

Backend fetch failed

+

Guru Meditation:

+

XID: 343710611

+
+

Varnish cache server

+ +`, + false, + "Nirvana Labs"}, // This isn't an error response we can handle, but including for completeness. }, + + {`{ + "code":-32000", + "message":"unrelated server error"}`, + false, + "any", + }, {`{ + "code":-32500, + "message":"unrelated error code"}`, + false, + "any2", + }, {fmt.Sprintf(`{ + "code" : -43106, + "message" : "%s"}`, customErrors.TooManyResults()), + true, + "custom chain with error specified in toml config", + }, + } + + for _, test := range tests { + t.Run(test.network, func(t *testing.T) { + jsonRpcErr := evmclient.JsonError{} + err := json.Unmarshal([]byte(test.message), &jsonRpcErr) + if err == nil { + err = jsonRpcErr + } + assert.Equal(t, test.expect, evmclient.IsTooManyResults(err, &customErrors)) + }) + } +} diff --git a/core/chains/evm/client/evm_client.go b/core/chains/evm/client/evm_client.go index 1fd533d6aa..c26362d635 100644 --- a/core/chains/evm/client/evm_client.go +++ b/core/chains/evm/client/evm_client.go @@ -22,13 +22,13 @@ func NewEvmClient(cfg evmconfig.NodePool, chainCfg commonclient.ChainConfig, cli for i, node := range nodes { if node.SendOnly != nil && *node.SendOnly { rpc := NewRPCClient(lggr, empty, (*url.URL)(node.HTTPURL), *node.Name, int32(i), chainID, - commonclient.Secondary, cfg.FinalizedBlockPollInterval(), largePayloadRPCTimeout, defaultRPCTimeout, chainType) + commonclient.Secondary, cfg.FinalizedBlockPollInterval(), cfg.NewHeadsPollInterval(), largePayloadRPCTimeout, defaultRPCTimeout, chainType) sendonly := commonclient.NewSendOnlyNode(lggr, (url.URL)(*node.HTTPURL), *node.Name, chainID, rpc) sendonlys = append(sendonlys, sendonly) } else { rpc := NewRPCClient(lggr, (url.URL)(*node.WSURL), (*url.URL)(node.HTTPURL), *node.Name, int32(i), - chainID, commonclient.Primary, cfg.FinalizedBlockPollInterval(), largePayloadRPCTimeout, defaultRPCTimeout, chainType) + chainID, commonclient.Primary, cfg.FinalizedBlockPollInterval(), cfg.NewHeadsPollInterval(), largePayloadRPCTimeout, defaultRPCTimeout, chainType) primaryNode := commonclient.NewNode(cfg, chainCfg, lggr, (url.URL)(*node.WSURL), (*url.URL)(node.HTTPURL), *node.Name, int32(i), chainID, *node.Order, rpc, "EVM") diff --git a/core/chains/evm/client/evm_client_test.go b/core/chains/evm/client/evm_client_test.go index bdfcf42674..b762c14653 100644 --- a/core/chains/evm/client/evm_client_test.go +++ b/core/chains/evm/client/evm_client_test.go @@ -29,6 +29,7 @@ func TestNewEvmClient(t *testing.T) { deathDeclarationDelay := time.Second * 3 noNewFinalizedBlocksThreshold := time.Second * 5 finalizedBlockPollInterval := time.Second * 4 + newHeadsPollInterval := time.Second * 4 nodeConfigs := []client.NodeConfig{ { Name: ptr("foo"), @@ -40,7 +41,8 @@ func TestNewEvmClient(t *testing.T) { finalityTagEnabled := ptr(true) chainCfg, nodePool, nodes, err := client.NewClientConfigs(selectionMode, leaseDuration, chainTypeStr, nodeConfigs, pollFailureThreshold, pollInterval, syncThreshold, nodeIsSyncingEnabled, noNewHeadsThreshold, finalityDepth, - finalityTagEnabled, finalizedBlockOffset, enforceRepeatableRead, deathDeclarationDelay, noNewFinalizedBlocksThreshold, finalizedBlockPollInterval) + finalityTagEnabled, finalizedBlockOffset, enforceRepeatableRead, deathDeclarationDelay, noNewFinalizedBlocksThreshold, + finalizedBlockPollInterval, newHeadsPollInterval) require.NoError(t, err) client := client.NewEvmClient(nodePool, chainCfg, nil, logger.Test(t), testutils.FixtureChainID, nodes, chaintype.ChainType(chainTypeStr)) diff --git a/core/chains/evm/client/helpers_test.go b/core/chains/evm/client/helpers_test.go index e996ccc5e4..031f648157 100644 --- a/core/chains/evm/client/helpers_test.go +++ b/core/chains/evm/client/helpers_test.go @@ -34,6 +34,7 @@ type TestClientErrors struct { transactionAlreadyMined string fatal string serviceUnavailable string + tooManyResults string } func NewTestClientErrors() TestClientErrors { @@ -52,6 +53,7 @@ func NewTestClientErrors() TestClientErrors { transactionAlreadyMined: "client error transaction already mined", fatal: "client error fatal", serviceUnavailable: "client error service unavailable", + tooManyResults: "client error too many results", } } @@ -77,6 +79,7 @@ func (c *TestClientErrors) L2Full() string { return c.l2Full } func (c *TestClientErrors) TransactionAlreadyMined() string { return c.transactionAlreadyMined } func (c *TestClientErrors) Fatal() string { return c.fatal } func (c *TestClientErrors) ServiceUnavailable() string { return c.serviceUnavailable } +func (c *TestClientErrors) TooManyResults() string { return c.serviceUnavailable } type TestNodePoolConfig struct { NodePollFailureThreshold uint32 @@ -89,6 +92,7 @@ type TestNodePoolConfig struct { NodeErrors config.ClientErrors EnforceRepeatableReadVal bool NodeDeathDeclarationDelay time.Duration + NodeNewHeadsPollInterval time.Duration } func (tc TestNodePoolConfig) PollFailureThreshold() uint32 { return tc.NodePollFailureThreshold } @@ -107,6 +111,10 @@ func (tc TestNodePoolConfig) FinalizedBlockPollInterval() time.Duration { return tc.NodeFinalizedBlockPollInterval } +func (tc TestNodePoolConfig) NewHeadsPollInterval() time.Duration { + return tc.NodeNewHeadsPollInterval +} + func (tc TestNodePoolConfig) Errors() config.ClientErrors { return tc.NodeErrors } @@ -140,7 +148,7 @@ func NewChainClientWithTestNode( } lggr := logger.Test(t) - rpc := NewRPCClient(lggr, *parsed, rpcHTTPURL, "eth-primary-rpc-0", id, chainID, commonclient.Primary, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + rpc := NewRPCClient(lggr, *parsed, rpcHTTPURL, "eth-primary-rpc-0", id, chainID, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") n := commonclient.NewNode[*big.Int, *evmtypes.Head, RPCClient]( nodeCfg, clientMocks.ChainConfig{NoNewHeadsThresholdVal: noNewHeadsThreshold}, lggr, *parsed, rpcHTTPURL, "eth-primary-node-0", id, chainID, 1, rpc, "EVM") @@ -152,7 +160,7 @@ func NewChainClientWithTestNode( return nil, pkgerrors.Errorf("sendonly ethereum rpc url scheme must be http(s): %s", u.String()) } var empty url.URL - rpc := NewRPCClient(lggr, empty, &sendonlyRPCURLs[i], fmt.Sprintf("eth-sendonly-rpc-%d", i), id, chainID, commonclient.Secondary, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + rpc := NewRPCClient(lggr, empty, &sendonlyRPCURLs[i], fmt.Sprintf("eth-sendonly-rpc-%d", i), id, chainID, commonclient.Secondary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") s := commonclient.NewSendOnlyNode[*big.Int, RPCClient]( lggr, u, fmt.Sprintf("eth-sendonly-%d", i), chainID, rpc) sendonlys = append(sendonlys, s) diff --git a/core/chains/evm/client/rpc_client.go b/core/chains/evm/client/rpc_client.go index 295e24f7c9..763348173a 100644 --- a/core/chains/evm/client/rpc_client.go +++ b/core/chains/evm/client/rpc_client.go @@ -123,6 +123,7 @@ type rpcClient struct { largePayloadRpcTimeout time.Duration rpcTimeout time.Duration finalizedBlockPollInterval time.Duration + newHeadsPollInterval time.Duration chainType chaintype.ChainType ws rawclient @@ -159,6 +160,7 @@ func NewRPCClient( chainID *big.Int, tier commonclient.NodeTier, finalizedBlockPollInterval time.Duration, + newHeadsPollInterval time.Duration, largePayloadRpcTimeout time.Duration, rpcTimeout time.Duration, chainType chaintype.ChainType, @@ -174,6 +176,7 @@ func NewRPCClient( r.tier = tier r.ws.uri = wsuri r.finalizedBlockPollInterval = finalizedBlockPollInterval + r.newHeadsPollInterval = newHeadsPollInterval if httpuri != nil { r.http = &rawclient{uri: *httpuri} } @@ -490,6 +493,18 @@ func (r *rpcClient) SubscribeNewHead(ctx context.Context, channel chan<- *evmtyp args := []interface{}{"newHeads"} lggr := r.newRqLggr().With("args", args) + if r.newHeadsPollInterval > 0 { + interval := r.newHeadsPollInterval + timeout := interval + poller, _ := commonclient.NewPoller[*evmtypes.Head](interval, r.latestBlock, timeout, r.rpcLog) + if err = poller.Start(ctx); err != nil { + return nil, err + } + + lggr.Debugf("Polling new heads over http") + return &poller, nil + } + lggr.Debug("RPC call: evmclient.Client#EthSubscribe") start := time.Now() defer func() { @@ -523,6 +538,19 @@ func (r *rpcClient) SubscribeToHeads(ctx context.Context) (ch <-chan *evmtypes.H start := time.Now() lggr := r.newRqLggr().With("args", args) + // if new head based on http polling is enabled, we will replace it for WS newHead subscription + if r.newHeadsPollInterval > 0 { + interval := r.newHeadsPollInterval + timeout := interval + poller, channel := commonclient.NewPoller[*evmtypes.Head](interval, r.latestBlock, timeout, r.rpcLog) + if err = poller.Start(ctx); err != nil { + return nil, nil, err + } + + lggr.Debugf("Polling new heads over http") + return channel, &poller, nil + } + lggr.Debug("RPC call: evmclient.Client#EthSubscribe") defer func() { duration := time.Since(start) @@ -550,14 +578,14 @@ func (r *rpcClient) SubscribeToHeads(ctx context.Context) (ch <-chan *evmtypes.H return channel, forwarder, err } -func (r *rpcClient) SubscribeToFinalizedHeads(_ context.Context) (<-chan *evmtypes.Head, commontypes.Subscription, error) { +func (r *rpcClient) SubscribeToFinalizedHeads(ctx context.Context) (<-chan *evmtypes.Head, commontypes.Subscription, error) { interval := r.finalizedBlockPollInterval if interval == 0 { return nil, nil, errors.New("FinalizedBlockPollInterval is 0") } timeout := interval poller, channel := commonclient.NewPoller[*evmtypes.Head](interval, r.LatestFinalizedBlock, timeout, r.rpcLog) - if err := poller.Start(); err != nil { + if err := poller.Start(ctx); err != nil { return nil, nil, err } return channel, &poller, nil @@ -695,6 +723,10 @@ func (r *rpcClient) LatestFinalizedBlock(ctx context.Context) (head *evmtypes.He return } +func (r *rpcClient) latestBlock(ctx context.Context) (head *evmtypes.Head, err error) { + return r.BlockByNumber(ctx, nil) +} + func (r *rpcClient) astarLatestFinalizedBlock(ctx context.Context, result interface{}) (err error) { var hashResult string err = r.CallContext(ctx, &hashResult, "chain_getFinalizedHead") diff --git a/core/chains/evm/client/rpc_client_test.go b/core/chains/evm/client/rpc_client_test.go index 07e097727a..b594a0ca16 100644 --- a/core/chains/evm/client/rpc_client_test.go +++ b/core/chains/evm/client/rpc_client_test.go @@ -61,7 +61,7 @@ func TestRPCClient_SubscribeNewHead(t *testing.T) { server := testutils.NewWSServer(t, chainId, serverCallBack) wsURL := server.WSURL() - rpc := client.NewRPCClient(lggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + rpc := client.NewRPCClient(lggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") defer rpc.Close() require.NoError(t, rpc.Dial(ctx)) // set to default values @@ -111,7 +111,7 @@ func TestRPCClient_SubscribeNewHead(t *testing.T) { server := testutils.NewWSServer(t, chainId, serverCallBack) wsURL := server.WSURL() - rpc := client.NewRPCClient(lggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + rpc := client.NewRPCClient(lggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") defer rpc.Close() require.NoError(t, rpc.Dial(ctx)) ch := make(chan *evmtypes.Head) @@ -136,7 +136,7 @@ func TestRPCClient_SubscribeNewHead(t *testing.T) { server := testutils.NewWSServer(t, chainId, serverCallBack) wsURL := server.WSURL() - rpc := client.NewRPCClient(lggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + rpc := client.NewRPCClient(lggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") defer rpc.Close() require.NoError(t, rpc.Dial(ctx)) var wg sync.WaitGroup @@ -160,7 +160,7 @@ func TestRPCClient_SubscribeNewHead(t *testing.T) { t.Run("Block's chain ID matched configured", func(t *testing.T) { server := testutils.NewWSServer(t, chainId, serverCallBack) wsURL := server.WSURL() - rpc := client.NewRPCClient(lggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + rpc := client.NewRPCClient(lggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") defer rpc.Close() require.NoError(t, rpc.Dial(ctx)) ch := make(chan *evmtypes.Head) @@ -177,7 +177,7 @@ func TestRPCClient_SubscribeNewHead(t *testing.T) { }) wsURL := server.WSURL() observedLggr, observed := logger.TestObserved(t, zap.DebugLevel) - rpc := client.NewRPCClient(observedLggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + rpc := client.NewRPCClient(observedLggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") require.NoError(t, rpc.Dial(ctx)) server.Close() _, err := rpc.SubscribeNewHead(ctx, make(chan *evmtypes.Head)) @@ -187,7 +187,7 @@ func TestRPCClient_SubscribeNewHead(t *testing.T) { t.Run("Subscription error is properly wrapper", func(t *testing.T) { server := testutils.NewWSServer(t, chainId, serverCallBack) wsURL := server.WSURL() - rpc := client.NewRPCClient(lggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + rpc := client.NewRPCClient(lggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") defer rpc.Close() require.NoError(t, rpc.Dial(ctx)) sub, err := rpc.SubscribeNewHead(ctx, make(chan *evmtypes.Head)) @@ -215,7 +215,7 @@ func TestRPCClient_SubscribeFilterLogs(t *testing.T) { }) wsURL := server.WSURL() observedLggr, observed := logger.TestObserved(t, zap.DebugLevel) - rpc := client.NewRPCClient(observedLggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + rpc := client.NewRPCClient(observedLggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") require.NoError(t, rpc.Dial(ctx)) server.Close() _, err := rpc.SubscribeFilterLogs(ctx, ethereum.FilterQuery{}, make(chan types.Log)) @@ -232,7 +232,7 @@ func TestRPCClient_SubscribeFilterLogs(t *testing.T) { return resp }) wsURL := server.WSURL() - rpc := client.NewRPCClient(lggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + rpc := client.NewRPCClient(lggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") defer rpc.Close() require.NoError(t, rpc.Dial(ctx)) sub, err := rpc.SubscribeFilterLogs(ctx, ethereum.FilterQuery{}, make(chan types.Log)) @@ -281,7 +281,7 @@ func TestRPCClient_LatestFinalizedBlock(t *testing.T) { } server := createRPCServer() - rpc := client.NewRPCClient(lggr, *server.URL, nil, "rpc", 1, chainId, commonclient.Primary, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + rpc := client.NewRPCClient(lggr, *server.URL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") require.NoError(t, rpc.Dial(ctx)) defer rpc.Close() server.Head = &evmtypes.Head{Number: 128} @@ -391,7 +391,7 @@ func TestRpcClientLargePayloadTimeout(t *testing.T) { // use something unreasonably large for RPC timeout to ensure that we use largePayloadRPCTimeout const rpcTimeout = time.Hour const largePayloadRPCTimeout = tests.TestInterval - rpc := client.NewRPCClient(logger.Test(t), *rpcURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, largePayloadRPCTimeout, rpcTimeout, "") + rpc := client.NewRPCClient(logger.Test(t), *rpcURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, largePayloadRPCTimeout, rpcTimeout, "") require.NoError(t, rpc.Dial(ctx)) defer rpc.Close() err := testCase.Fn(ctx, rpc) @@ -431,7 +431,7 @@ func TestAstarCustomFinality(t *testing.T) { const expectedFinalizedBlockNumber = int64(4) const expectedFinalizedBlockHash = "0x7441e97acf83f555e0deefef86db636bc8a37eb84747603412884e4df4d22804" - rpcClient := client.NewRPCClient(logger.Test(t), *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, chaintype.ChainAstar) + rpcClient := client.NewRPCClient(logger.Test(t), *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, chaintype.ChainAstar) defer rpcClient.Close() err := rpcClient.Dial(tests.Context(t)) require.NoError(t, err) diff --git a/core/chains/evm/client/simulated_backend_client.go b/core/chains/evm/client/simulated_backend_client.go index 3ec1bff577..11828e5871 100644 --- a/core/chains/evm/client/simulated_backend_client.go +++ b/core/chains/evm/client/simulated_backend_client.go @@ -320,7 +320,15 @@ func (c *SimulatedBackendClient) SubscribeNewHead( case h := <-ch: var head *evmtypes.Head if h != nil { - head = &evmtypes.Head{Difficulty: h.Difficulty, Timestamp: time.Unix(int64(h.Time), 0), Number: h.Number.Int64(), Hash: h.Hash(), ParentHash: h.ParentHash, Parent: lastHead, EVMChainID: ubig.New(c.chainId)} + head = &evmtypes.Head{ + Difficulty: h.Difficulty, + Timestamp: time.Unix(int64(h.Time), 0), //nolint:gosec + Number: h.Number.Int64(), + Hash: h.Hash(), + ParentHash: h.ParentHash, + EVMChainID: ubig.New(c.chainId), + } + head.Parent.Store(lastHead) lastHead = head } select { @@ -419,7 +427,7 @@ func (c *SimulatedBackendClient) CallContract(ctx context.Context, msg ethereum. res, err := c.b.CallContract(ctx, msg, blockNumber) if err != nil { dataErr := revertError{} - if errors.Is(err, &dataErr) { + if errors.As(err, &dataErr) { return nil, &JsonError{Data: dataErr.ErrorData(), Message: dataErr.Error(), Code: 3} } // Generic revert, no data @@ -438,7 +446,7 @@ func (c *SimulatedBackendClient) PendingCallContract(ctx context.Context, msg et res, err := c.b.PendingCallContract(ctx, msg) if err != nil { dataErr := revertError{} - if errors.Is(err, &dataErr) { + if errors.As(err, &dataErr) { return nil, &JsonError{Data: dataErr.ErrorData(), Message: dataErr.Error(), Code: 3} } // Generic revert, no data diff --git a/core/chains/evm/config/chain_scoped.go b/core/chains/evm/config/chain_scoped.go index b9b19cdc2c..de89272b5e 100644 --- a/core/chains/evm/config/chain_scoped.go +++ b/core/chains/evm/config/chain_scoped.go @@ -176,6 +176,10 @@ func (e *EVMConfig) OperatorFactoryAddress() string { return e.C.OperatorFactoryAddress.String() } +func (e *EVMConfig) LogBroadcasterEnabled() bool { + return e.C.LogBroadcasterEnabled == nil || *e.C.LogBroadcasterEnabled +} + func (e *EVMConfig) LogPrunePageSize() uint32 { return *e.C.LogPrunePageSize } diff --git a/core/chains/evm/config/chain_scoped_client_errors.go b/core/chains/evm/config/chain_scoped_client_errors.go index 53bb04846d..f9d2096e90 100644 --- a/core/chains/evm/config/chain_scoped_client_errors.go +++ b/core/chains/evm/config/chain_scoped_client_errors.go @@ -48,3 +48,4 @@ func (c *clientErrorsConfig) Fatal() string { return derefOrDefault(c.c.Fatal) } func (c *clientErrorsConfig) ServiceUnavailable() string { return derefOrDefault(c.c.ServiceUnavailable) } +func (c *clientErrorsConfig) TooManyResults() string { return derefOrDefault(c.c.TooManyResults) } diff --git a/core/chains/evm/config/chain_scoped_node_pool.go b/core/chains/evm/config/chain_scoped_node_pool.go index a497436648..4b1d02d148 100644 --- a/core/chains/evm/config/chain_scoped_node_pool.go +++ b/core/chains/evm/config/chain_scoped_node_pool.go @@ -38,6 +38,10 @@ func (n *NodePoolConfig) FinalizedBlockPollInterval() time.Duration { return n.C.FinalizedBlockPollInterval.Duration() } +func (n *NodePoolConfig) NewHeadsPollInterval() time.Duration { + return n.C.NewHeadsPollInterval.Duration() +} + func (n *NodePoolConfig) Errors() ClientErrors { return &clientErrorsConfig{c: n.C.Errors} } func (n *NodePoolConfig) EnforceRepeatableRead() bool { diff --git a/core/chains/evm/config/chain_scoped_workflow.go b/core/chains/evm/config/chain_scoped_workflow.go index 36dcb3ea41..816270886a 100644 --- a/core/chains/evm/config/chain_scoped_workflow.go +++ b/core/chains/evm/config/chain_scoped_workflow.go @@ -16,3 +16,7 @@ func (b *workflowConfig) FromAddress() *types.EIP55Address { func (b *workflowConfig) ForwarderAddress() *types.EIP55Address { return b.c.ForwarderAddress } + +func (b *workflowConfig) GasLimitDefault() *uint64 { + return b.c.GasLimitDefault +} diff --git a/core/chains/evm/config/chaintype/chaintype.go b/core/chains/evm/config/chaintype/chaintype.go index 35dd214b1f..f6b84e4655 100644 --- a/core/chains/evm/config/chaintype/chaintype.go +++ b/core/chains/evm/config/chaintype/chaintype.go @@ -44,7 +44,7 @@ func (c ChainType) IsValid() bool { return false } -func ChainTypeFromSlug(slug string) ChainType { +func FromSlug(slug string) ChainType { switch slug { case "arbitrum": return ChainArbitrum @@ -79,53 +79,53 @@ func ChainTypeFromSlug(slug string) ChainType { } } -type ChainTypeConfig struct { +type Config struct { value ChainType slug string } -func NewChainTypeConfig(slug string) *ChainTypeConfig { - return &ChainTypeConfig{ - value: ChainTypeFromSlug(slug), +func NewConfig(slug string) *Config { + return &Config{ + value: FromSlug(slug), slug: slug, } } -func (c *ChainTypeConfig) MarshalText() ([]byte, error) { +func (c *Config) MarshalText() ([]byte, error) { if c == nil { return nil, nil } return []byte(c.slug), nil } -func (c *ChainTypeConfig) UnmarshalText(b []byte) error { +func (c *Config) UnmarshalText(b []byte) error { c.slug = string(b) - c.value = ChainTypeFromSlug(c.slug) + c.value = FromSlug(c.slug) return nil } -func (c *ChainTypeConfig) Slug() string { +func (c *Config) Slug() string { if c == nil { return "" } return c.slug } -func (c *ChainTypeConfig) ChainType() ChainType { +func (c *Config) ChainType() ChainType { if c == nil { return "" } return c.value } -func (c *ChainTypeConfig) String() string { +func (c *Config) String() string { if c == nil { return "" } return string(c.value) } -var ErrInvalidChainType = fmt.Errorf("must be one of %s or omitted", strings.Join([]string{ +var ErrInvalid = fmt.Errorf("must be one of %s or omitted", strings.Join([]string{ string(ChainArbitrum), string(ChainAstar), string(ChainCelo), diff --git a/core/chains/evm/config/config.go b/core/chains/evm/config/config.go index 3d00fe86a4..e5b806aa58 100644 --- a/core/chains/evm/config/config.go +++ b/core/chains/evm/config/config.go @@ -43,6 +43,7 @@ type EVM interface { MinIncomingConfirmations() uint32 NonceAutoSync() bool OperatorFactoryAddress() string + LogBroadcasterEnabled() bool RPCDefaultBatchSize() uint32 NodeNoNewHeadsThreshold() time.Duration FinalizedBlockOffset() uint32 @@ -96,6 +97,7 @@ type ClientErrors interface { TransactionAlreadyMined() string Fatal() string ServiceUnavailable() string + TooManyResults() string } type Transactions interface { @@ -166,6 +168,7 @@ type FeeHistory interface { type Workflow interface { FromAddress() *types.EIP55Address ForwarderAddress() *types.EIP55Address + GasLimitDefault() *uint64 } type NodePool interface { @@ -179,6 +182,7 @@ type NodePool interface { Errors() ClientErrors EnforceRepeatableRead() bool DeathDeclarationDelay() time.Duration + NewHeadsPollInterval() time.Duration } // TODO BCF-2509 does the chainscopedconfig really need the entire app config? diff --git a/core/chains/evm/config/config_test.go b/core/chains/evm/config/config_test.go index e0dec00e68..aec08f183d 100644 --- a/core/chains/evm/config/config_test.go +++ b/core/chains/evm/config/config_test.go @@ -205,6 +205,25 @@ func TestChainScopedConfig(t *testing.T) { assert.Equal(t, val.String(), cfg3.EVM().OperatorFactoryAddress()) }) }) + + t.Run("LogBroadcasterEnabled", func(t *testing.T) { + t.Run("turn on LogBroadcasterEnabled by default", func(t *testing.T) { + assert.Equal(t, true, cfg.EVM().LogBroadcasterEnabled()) + }) + + t.Run("verify LogBroadcasterEnabled is set correctly", func(t *testing.T) { + val := false + cfg3 := testutils.NewTestChainScopedConfig(t, func(c *toml.EVMConfig) { + c.LogBroadcasterEnabled = ptr(val) + }) + + assert.Equal(t, false, cfg3.EVM().LogBroadcasterEnabled()) + }) + + t.Run("use Noop logBroadcaster when LogBroadcaster is disabled", func(t *testing.T) { + + }) + }) } func TestChainScopedConfig_BlockHistory(t *testing.T) { @@ -361,6 +380,7 @@ func TestClientErrorsConfig(t *testing.T) { TransactionAlreadyMined: ptr("client error transaction already mined"), Fatal: ptr("client error fatal"), ServiceUnavailable: ptr("client error service unavailable"), + TooManyResults: ptr("client error too many results"), }, } }) @@ -380,6 +400,7 @@ func TestClientErrorsConfig(t *testing.T) { assert.Equal(t, "client error transaction already mined", errors.TransactionAlreadyMined()) assert.Equal(t, "client error fatal", errors.Fatal()) assert.Equal(t, "client error service unavailable", errors.ServiceUnavailable()) + assert.Equal(t, "client error too many results", errors.TooManyResults()) }) } diff --git a/core/chains/evm/config/toml/config.go b/core/chains/evm/config/toml/config.go index 90854d90cf..fd4039f5ea 100644 --- a/core/chains/evm/config/toml/config.go +++ b/core/chains/evm/config/toml/config.go @@ -341,7 +341,7 @@ type Chain struct { AutoCreateKey *bool BlockBackfillDepth *uint32 BlockBackfillSkip *bool - ChainType *chaintype.ChainTypeConfig + ChainType *chaintype.Config FinalityDepth *uint32 FinalityTagEnabled *bool FlagsContractAddress *types.EIP55Address @@ -356,6 +356,7 @@ type Chain struct { NonceAutoSync *bool NoNewHeadsThreshold *commonconfig.Duration OperatorFactoryAddress *types.EIP55Address + LogBroadcasterEnabled *bool RPCDefaultBatchSize *uint32 RPCBlockQueryDelay *uint16 FinalizedBlockOffset *uint32 @@ -375,7 +376,7 @@ type Chain struct { func (c *Chain) ValidateConfig() (err error) { if !c.ChainType.ChainType().IsValid() { err = multierr.Append(err, commonconfig.ErrInvalid{Name: "ChainType", Value: c.ChainType.ChainType(), - Msg: chaintype.ErrInvalidChainType.Error()}) + Msg: chaintype.ErrInvalid.Error()}) } if c.GasEstimator.BumpTxDepth != nil && *c.GasEstimator.BumpTxDepth > *c.Transactions.MaxInFlight { @@ -521,6 +522,7 @@ func (a *Automation) setFrom(f *Automation) { type Workflow struct { FromAddress *types.EIP55Address `toml:",omitempty"` ForwarderAddress *types.EIP55Address `toml:",omitempty"` + GasLimitDefault *uint64 } func (m *Workflow) setFrom(f *Workflow) { @@ -530,6 +532,10 @@ func (m *Workflow) setFrom(f *Workflow) { if v := f.ForwarderAddress; v != nil { m.ForwarderAddress = v } + + if v := f.GasLimitDefault; v != nil { + m.GasLimitDefault = v + } } type BalanceMonitor struct { @@ -816,6 +822,7 @@ type ClientErrors struct { TransactionAlreadyMined *string `toml:",omitempty"` Fatal *string `toml:",omitempty"` ServiceUnavailable *string `toml:",omitempty"` + TooManyResults *string `toml:",omitempty"` } func (r *ClientErrors) setFrom(f *ClientErrors) bool { @@ -861,6 +868,9 @@ func (r *ClientErrors) setFrom(f *ClientErrors) bool { if v := f.ServiceUnavailable; v != nil { r.ServiceUnavailable = v } + if v := f.TooManyResults; v != nil { + r.TooManyResults = v + } return true } @@ -875,6 +885,7 @@ type NodePool struct { Errors ClientErrors `toml:",omitempty"` EnforceRepeatableRead *bool DeathDeclarationDelay *commonconfig.Duration + NewHeadsPollInterval *commonconfig.Duration } func (p *NodePool) setFrom(f *NodePool) { @@ -907,6 +918,11 @@ func (p *NodePool) setFrom(f *NodePool) { if v := f.DeathDeclarationDelay; v != nil { p.DeathDeclarationDelay = v } + + if v := f.NewHeadsPollInterval; v != nil { + p.NewHeadsPollInterval = v + } + p.Errors.setFrom(&f.Errors) } diff --git a/core/chains/evm/config/toml/defaults.go b/core/chains/evm/config/toml/defaults.go index c3f087da8c..0885d94e6d 100644 --- a/core/chains/evm/config/toml/defaults.go +++ b/core/chains/evm/config/toml/defaults.go @@ -3,7 +3,10 @@ package toml import ( "bytes" "embed" + "fmt" + "io" "log" + "os" "path/filepath" "slices" "strings" @@ -12,37 +15,43 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/chaintype" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" + "github.com/smartcontractkit/chainlink/v2/core/config/env" ) var ( + //go:embed defaults/*.toml defaultsFS embed.FS fallback Chain defaults = map[string]Chain{} defaultNames = map[string]string{} + customDefaults = map[string]Chain{} + // DefaultIDs is the set of chain ids which have defaults. DefaultIDs []*big.Big ) func init() { + // read the defaults first + fes, err := defaultsFS.ReadDir("defaults") if err != nil { log.Fatalf("failed to read defaults/: %v", err) } for _, fe := range fes { path := filepath.Join("defaults", fe.Name()) - b, err := defaultsFS.ReadFile(path) - if err != nil { - log.Fatalf("failed to read %q: %v", path, err) + b, err2 := defaultsFS.ReadFile(path) + if err2 != nil { + log.Fatalf("failed to read %q: %v", path, err2) } var config = struct { ChainID *big.Big Chain }{} - if err := cconfig.DecodeTOML(bytes.NewReader(b), &config); err != nil { - log.Fatalf("failed to decode %q: %v", path, err) + if err3 := cconfig.DecodeTOML(bytes.NewReader(b), &config); err3 != nil { + log.Fatalf("failed to decode %q: %v", path, err3) } if fe.Name() == "fallback.toml" { if config.ChainID != nil { @@ -65,6 +74,63 @@ func init() { slices.SortFunc(DefaultIDs, func(a, b *big.Big) int { return a.Cmp(b) }) + + // read the custom defaults overrides + dir := env.CustomDefaults.Get() + if dir == "" { + // short-circuit; no default overrides provided + return + } + + // use evm overrides specifically + evmDir := fmt.Sprintf("%s/evm", dir) + + // Read directory contents for evm only + entries, err := os.ReadDir(evmDir) + if err != nil { + log.Fatalf("error reading evm custom defaults override directory: %v", err) + return + } + + for _, entry := range entries { + if entry.IsDir() { + // Skip directories + continue + } + + path := evmDir + "/" + entry.Name() + file, err := os.Open(path) + if err != nil { + log.Fatalf("error opening file (name: %v) in custom defaults override directory: %v", entry.Name(), err) + } + + // Read file contents + b, err := io.ReadAll(file) + file.Close() + if err != nil { + log.Fatalf("error reading file (name: %v) contents in custom defaults override directory: %v", entry.Name(), err) + } + + var config = struct { + ChainID *big.Big + Chain + }{} + + if err := cconfig.DecodeTOML(bytes.NewReader(b), &config); err != nil { + log.Fatalf("failed to decode %q in custom defaults override directory: %v", path, err) + } + + if config.ChainID == nil { + log.Fatalf("missing ChainID in: %s in custom defaults override directory. exiting", path) + } + + id := config.ChainID.String() + + if _, ok := customDefaults[id]; ok { + log.Fatalf("%q contains duplicate ChainID: %s", path, id) + } + customDefaults[id] = config.Chain + } } // DefaultsNamed returns the default Chain values, optionally for the given chainID, as well as a name if the chainID is known. @@ -78,6 +144,9 @@ func DefaultsNamed(chainID *big.Big) (c Chain, name string) { c.SetFrom(&d) name = defaultNames[s] } + if overrides, ok := customDefaults[s]; ok { + c.SetFrom(&overrides) + } return } @@ -155,6 +224,9 @@ func (c *Chain) SetFrom(f *Chain) { if v := f.OperatorFactoryAddress; v != nil { c.OperatorFactoryAddress = v } + if v := f.LogBroadcasterEnabled; v != nil { + c.LogBroadcasterEnabled = v + } if v := f.RPCDefaultBatchSize; v != nil { c.RPCDefaultBatchSize = v } diff --git a/core/chains/evm/config/toml/defaults/BSC_Mainnet.toml b/core/chains/evm/config/toml/defaults/BSC_Mainnet.toml index 10f4c570be..e95f0af1eb 100644 --- a/core/chains/evm/config/toml/defaults/BSC_Mainnet.toml +++ b/core/chains/evm/config/toml/defaults/BSC_Mainnet.toml @@ -13,6 +13,8 @@ NoNewFinalizedHeadsThreshold = '45s' [GasEstimator] PriceDefault = '5 gwei' +# Set to the BSC node's default Eth.Miner.GasPrice config +PriceMin = '3 gwei' # 15s delay since feeds update every minute in volatile situations BumpThreshold = 5 diff --git a/core/chains/evm/config/toml/defaults/Hedera_Mainnet.toml b/core/chains/evm/config/toml/defaults/Hedera_Mainnet.toml new file mode 100644 index 0000000000..4d5e48816f --- /dev/null +++ b/core/chains/evm/config/toml/defaults/Hedera_Mainnet.toml @@ -0,0 +1,31 @@ +ChainID = '295' +ChainType = 'hedera' +# Considering the 3-5 (6 including a buffer) seconds of finality and 2 seconds block production +# We set the depth to 6/2 = 3 blocks, setting to 10 for safety +FinalityDepth = 10 +# Hedera has high TPS, so polling less often +LogPollInterval = '10s' +MinIncomingConfirmations = 1 + +[BalanceMonitor] +Enabled = true + +[GasEstimator] +Mode = 'SuggestedPrice' +# Since Hedera dont have mempool and there's no way for a node to front run or a user to bribe a node to submit the transaction earlier than it's consensus timestamp, +# But they have automated congesting pricing throttling which would mean at high sustained level the gasPrice itself could be increased to prevent malicious behaviour. +# Disabling the Bumpthreshold as TXM now implicity handles the bumping after checking on-chain nonce & re-broadcast for Hedera chain type +BumpThreshold = 0 +BumpMin = '10 gwei' +BumpPercent = 20 + +[Transactions] +# To hit throttling you'd need to maintain 15 m gas /sec over a prolonged period of time. +# Because Hedera's block times are every 2 secs it's less less likely to happen as compared to other chains +# Setting this to little higher even though Hedera has High TPS, We have seen 10-12s to get the trasaction mined & 20-25s incase of failures +# Accounting for Node syncs & avoid re-sending txns before fetching the receipt, setting to 2m +ResendAfterThreshold = '2m' + + +[NodePool] +SyncThreshold = 10 \ No newline at end of file diff --git a/core/chains/evm/config/toml/defaults/Hedera_Testnet.toml b/core/chains/evm/config/toml/defaults/Hedera_Testnet.toml new file mode 100644 index 0000000000..6086a43af2 --- /dev/null +++ b/core/chains/evm/config/toml/defaults/Hedera_Testnet.toml @@ -0,0 +1,31 @@ +ChainID = '296' +ChainType = 'hedera' +# Considering the 3-5 (6 including a buffer) seconds of finality and 2 seconds block production +# We set the depth to 6/2 = 3 blocks, setting to 10 for safety +FinalityDepth = 10 +# Hedera has high TPS, so polling less often +LogPollInterval = '10s' +MinIncomingConfirmations = 1 + +[BalanceMonitor] +Enabled = true + +[GasEstimator] +Mode = 'SuggestedPrice' +# Since Hedera dont have mempool and there's no way for a node to front run or a user to bribe a node to submit the transaction earlier than it's consensus timestamp, +# But they have automated congesting pricing throttling which would mean at high sustained level the gasPrice itself could be increased to prevent malicious behaviour. +# Disabling the Bumpthreshold as TXM now implicity handles the bumping after checking on-chain nonce & re-broadcast for Hedera chain type +BumpThreshold = 0 +BumpMin = '10 gwei' +BumpPercent = 20 + +[Transactions] +# To hit throttling you'd need to maintain 15 m gas /sec over a prolonged period of time. +# Because Hedera's block times are every 2 secs it's less less likely to happen as compared to other chains +# Setting this to little higher even though Hedera has High TPS, We have seen 10-12s to get the trasaction mined & 20-25s incase of failures +# Accounting for Node syncs & avoid re-sending txns before fetching the receipt, setting to 2m +ResendAfterThreshold = '2m' + + +[NodePool] +SyncThreshold = 10 \ No newline at end of file diff --git a/core/chains/evm/config/toml/defaults/Polygon_Amoy.toml b/core/chains/evm/config/toml/defaults/Polygon_Amoy.toml index b05b3053a8..bca42d9b40 100644 --- a/core/chains/evm/config/toml/defaults/Polygon_Amoy.toml +++ b/core/chains/evm/config/toml/defaults/Polygon_Amoy.toml @@ -11,10 +11,10 @@ NoNewFinalizedHeadsThreshold = '12m' MaxQueued = 5000 [GasEstimator] -EIP1559DynamicFees = true -PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' PriceDefault = '25 gwei' +PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' PriceMin = '25 gwei' +EIP1559DynamicFees = true BumpMin = '20 gwei' BumpThreshold = 5 diff --git a/core/chains/evm/config/toml/defaults/Soneium_Sepolia.toml b/core/chains/evm/config/toml/defaults/Soneium_Sepolia.toml new file mode 100755 index 0000000000..9f4772dd9a --- /dev/null +++ b/core/chains/evm/config/toml/defaults/Soneium_Sepolia.toml @@ -0,0 +1,32 @@ +ChainID = '1946' +ChainType = 'optimismBedrock' +LinkContractAddress = '0x7ea13478Ea3961A0e8b538cb05a9DF0477c79Cd2' +FinalityDepth = 200 +LogPollInterval = '2s' +NoNewHeadsThreshold = '40s' +MinIncomingConfirmations = 1 +NoNewFinalizedHeadsThreshold = '120m' # Soneium can take upto 2Hours to finalize +FinalityTagEnabled = true + +[GasEstimator] +EIP1559DynamicFees = true +PriceMin = '1 wei' +BumpMin = '1 mwei' + +[GasEstimator.BlockHistory] +BlockHistorySize = 60 + +[Transactions] +ResendAfterThreshold = '30s' + +[HeadTracker] +HistoryDepth = 300 + +[NodePool] +SyncThreshold = 10 + +[OCR] +ContractConfirmations = 1 + +[OCR2.Automation] +GasLimit = 6500000 diff --git a/core/chains/evm/config/toml/defaults/fallback.toml b/core/chains/evm/config/toml/defaults/fallback.toml index fb8eed3949..c1f963a33f 100644 --- a/core/chains/evm/config/toml/defaults/fallback.toml +++ b/core/chains/evm/config/toml/defaults/fallback.toml @@ -17,6 +17,7 @@ RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 NoNewFinalizedHeadsThreshold = '0' +LogBroadcasterEnabled = true [Transactions] ForwardersEnabled = false @@ -77,6 +78,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -88,3 +90,6 @@ ObservationGracePeriod = '1s' [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400_000 diff --git a/core/chains/evm/forwarders/forwarder_manager.go b/core/chains/evm/forwarders/forwarder_manager.go index 6436988ba8..0e9f8781c3 100644 --- a/core/chains/evm/forwarders/forwarder_manager.go +++ b/core/chains/evm/forwarders/forwarder_manager.go @@ -33,7 +33,9 @@ type Config interface { } type FwdMgr struct { - services.StateMachine + services.Service + eng *services.Engine + ORM ORM evmClient evmclient.Client cfg Config @@ -48,61 +50,51 @@ type FwdMgr struct { authRcvr authorized_receiver.AuthorizedReceiverInterface offchainAgg offchain_aggregator_wrapper.OffchainAggregatorInterface - stopCh services.StopChan - cacheMu sync.RWMutex - wg sync.WaitGroup } -func NewFwdMgr(ds sqlutil.DataSource, client evmclient.Client, logpoller evmlogpoller.LogPoller, l logger.Logger, cfg Config) *FwdMgr { - lggr := logger.Sugared(logger.Named(l, "EVMForwarderManager")) - fwdMgr := FwdMgr{ - logger: lggr, +func NewFwdMgr(ds sqlutil.DataSource, client evmclient.Client, logpoller evmlogpoller.LogPoller, lggr logger.Logger, cfg Config) *FwdMgr { + fm := FwdMgr{ cfg: cfg, evmClient: client, ORM: NewORM(ds), logpoller: logpoller, sendersCache: make(map[common.Address][]common.Address), } - fwdMgr.stopCh = make(chan struct{}) - return &fwdMgr -} - -func (f *FwdMgr) Name() string { - return f.logger.Name() + fm.Service, fm.eng = services.Config{ + Name: "ForwarderManager", + Start: fm.start, + }.NewServiceEngine(lggr) + fm.logger = logger.Sugared(fm.eng) + return &fm } -// Start starts Forwarder Manager. -func (f *FwdMgr) Start(ctx context.Context) error { - return f.StartOnce("EVMForwarderManager", func() error { - f.logger.Debug("Initializing EVM forwarder manager") - chainId := f.evmClient.ConfiguredChainID() +func (f *FwdMgr) start(ctx context.Context) error { + chainId := f.evmClient.ConfiguredChainID() - fwdrs, err := f.ORM.FindForwardersByChain(ctx, big.Big(*chainId)) - if err != nil { - return pkgerrors.Wrapf(err, "Failed to retrieve forwarders for chain %d", chainId) - } - if len(fwdrs) != 0 { - f.initForwardersCache(ctx, fwdrs) - if err = f.subscribeForwardersLogs(ctx, fwdrs); err != nil { - return err - } + fwdrs, err := f.ORM.FindForwardersByChain(ctx, big.Big(*chainId)) + if err != nil { + return pkgerrors.Wrapf(err, "Failed to retrieve forwarders for chain %d", chainId) + } + if len(fwdrs) != 0 { + f.initForwardersCache(ctx, fwdrs) + if err = f.subscribeForwardersLogs(ctx, fwdrs); err != nil { + return err } + } - f.authRcvr, err = authorized_receiver.NewAuthorizedReceiver(common.Address{}, f.evmClient) - if err != nil { - return pkgerrors.Wrap(err, "Failed to init AuthorizedReceiver") - } + f.authRcvr, err = authorized_receiver.NewAuthorizedReceiver(common.Address{}, f.evmClient) + if err != nil { + return pkgerrors.Wrap(err, "Failed to init AuthorizedReceiver") + } - f.offchainAgg, err = offchain_aggregator_wrapper.NewOffchainAggregator(common.Address{}, f.evmClient) - if err != nil { - return pkgerrors.Wrap(err, "Failed to init OffchainAggregator") - } + f.offchainAgg, err = offchain_aggregator_wrapper.NewOffchainAggregator(common.Address{}, f.evmClient) + if err != nil { + return pkgerrors.Wrap(err, "Failed to init OffchainAggregator") + } - f.wg.Add(1) - go f.runLoop() - return nil - }) + f.eng.Go(f.runLoop) + return nil } func FilterName(addr common.Address) string { @@ -176,7 +168,7 @@ func (f *FwdMgr) ConvertPayload(dest common.Address, origPayload []byte) ([]byte if err != nil { f.logger.AssumptionViolationw("Forwarder encoding failed, this should never happen", "err", err, "to", dest, "payload", origPayload) - f.SvcErrBuffer.Append(err) + f.eng.EmitHealthErr(err) } } return databytes, nil @@ -269,11 +261,7 @@ func (f *FwdMgr) getCachedSenders(addr common.Address) ([]common.Address, bool) return addrs, ok } -func (f *FwdMgr) runLoop() { - defer f.wg.Done() - ctx, cancel := f.stopCh.NewCtx() - defer cancel() - +func (f *FwdMgr) runLoop(ctx context.Context) { ticker := services.NewTicker(time.Minute) defer ticker.Stop() @@ -353,16 +341,3 @@ func (f *FwdMgr) collectAddresses() (addrs []common.Address) { } return } - -// Stop cancels all outgoings calls and stops internal ticker loop. -func (f *FwdMgr) Close() error { - return f.StopOnce("EVMForwarderManager", func() (err error) { - close(f.stopCh) - f.wg.Wait() - return nil - }) -} - -func (f *FwdMgr) HealthReport() map[string]error { - return map[string]error{f.Name(): f.Healthy()} -} diff --git a/core/chains/evm/gas/block_history_estimator.go b/core/chains/evm/gas/block_history_estimator.go index 4075b46f90..0386d92a0d 100644 --- a/core/chains/evm/gas/block_history_estimator.go +++ b/core/chains/evm/gas/block_history_estimator.go @@ -276,6 +276,8 @@ func (b *BlockHistoryEstimator) setMaxPercentileGasPrice(gasPrice *assets.Wei) { } func (b *BlockHistoryEstimator) getBlockHistoryNumbers() (numsInHistory []int64) { + b.blocksMu.RLock() + defer b.blocksMu.RUnlock() for _, b := range b.blocks { numsInHistory = append(numsInHistory, b.Number) } @@ -653,12 +655,13 @@ func (b *BlockHistoryEstimator) FetchBlocks(ctx context.Context, head *evmtypes. } blocks := make(map[int64]evmtypes.Block) + earliestInChain := head.EarliestInChain() for _, block := range b.getBlocks() { // Make a best-effort to be re-org resistant using the head // chain, refetch blocks that got re-org'd out. // NOTE: Any blocks in the history that are older than the oldest block // in the provided chain will be assumed final. - if block.Number < head.EarliestInChain().BlockNumber() { + if block.Number < earliestInChain.BlockNumber() { blocks[block.Number] = block } else if head.IsInChain(block.Hash) { blocks[block.Number] = block diff --git a/core/chains/evm/gas/block_history_estimator_test.go b/core/chains/evm/gas/block_history_estimator_test.go index 95d966c1a3..19b30538e3 100644 --- a/core/chains/evm/gas/block_history_estimator_test.go +++ b/core/chains/evm/gas/block_history_estimator_test.go @@ -516,7 +516,7 @@ func TestBlockHistoryEstimator_FetchBlocks(t *testing.T) { head2 := evmtypes.NewHead(big.NewInt(2), b2.Hash, b1.Hash, uint64(time.Now().Unix()), ubig.New(testutils.FixtureChainID)) head3 := evmtypes.NewHead(big.NewInt(3), b3.Hash, b2.Hash, uint64(time.Now().Unix()), ubig.New(testutils.FixtureChainID)) - head3.Parent = &head2 + head3.Parent.Store(&head2) err := bhe.FetchBlocks(tests.Context(t), &head3) require.NoError(t, err) @@ -571,7 +571,7 @@ func TestBlockHistoryEstimator_FetchBlocks(t *testing.T) { // RE-ORG, head2 and head3 have different hash than saved b2 and b3 head2 := evmtypes.NewHead(big.NewInt(2), utils.NewHash(), b1.Hash, uint64(time.Now().Unix()), ubig.New(testutils.FixtureChainID)) head3 := evmtypes.NewHead(big.NewInt(3), utils.NewHash(), head2.Hash, uint64(time.Now().Unix()), ubig.New(testutils.FixtureChainID)) - head3.Parent = &head2 + head3.Parent.Store(&head2) ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { return len(b) == 2 && @@ -644,7 +644,7 @@ func TestBlockHistoryEstimator_FetchBlocks(t *testing.T) { // head2 and head3 have identical hash to saved blocks head2 := evmtypes.NewHead(big.NewInt(2), b2.Hash, b1.Hash, uint64(time.Now().Unix()), ubig.New(testutils.FixtureChainID)) head3 := evmtypes.NewHead(big.NewInt(3), b3.Hash, head2.Hash, uint64(time.Now().Unix()), ubig.New(testutils.FixtureChainID)) - head3.Parent = &head2 + head3.Parent.Store(&head2) err := bhe.FetchBlocks(tests.Context(t), &head3) require.NoError(t, err) diff --git a/core/chains/evm/gas/docs/FEE_HISTORY_ESTIMATOR.md b/core/chains/evm/gas/docs/FEE_HISTORY_ESTIMATOR.md new file mode 100644 index 0000000000..7f541344a3 --- /dev/null +++ b/core/chains/evm/gas/docs/FEE_HISTORY_ESTIMATOR.md @@ -0,0 +1,56 @@ +# Fee History Estimator + +## Overview +`Fee History` estimator is an EVM-based gas estimator that utilizes RPC calls to make gas price estimations. The estimator heavily relies on two RPC calls: `eth_gasPrice` and `eth_feeHistory`. It is built as a service and caches the calculated results in order to minimize overhead. While bumping, it prioritizes using the latest result from most recent blocks if it exceeds the bumped gas price. `Fee History` estimator supports both Legacy and Dynamic(EIP-1559) transactions. It can also handle chains that don't have a mempool and process transactions on an FCFS basis. + +## Configs +- `BumpPercent`: is the percentage by which to bump gas on a transaction. This is used when the estimator's bumping API gets called. +- `CacheTimeout`: is the time to wait to refresh the cached values. A small jitter is applied so the timeout won't be exactly the same each time. +- `EIP1559`: enables EIP-1559 mode and deactivates Legacy estimations. This means the estimator will refresh prices and make estimations only for Dynamic transactions. + +The rest of the configs are only applicable when `EIP1559` is enabled + +- `BlockHistorySize`: controls the number of past blocks to include during gas calculations. If set to 0, the estimator will skip any priority fee calculation and calculate the underlying base fee. This config should be set to 0 for chains that don't have a mempool. +- `RewardPercentile`: specifies which fee percentile to pick from for each processed past block. + +### Validations +During startup, the estimator will perform two config checks: +- `BumpPercent` is equal to or higher than *MinimumBumpPercentage*. *MinimumBumpPercentage* is fixed at 10% and it's the minimum percentage allowed by Geth when bumping a transaction, to prevent spam attacks. Replacing a transaction with a price less than 10% from the previous one will result in an error on the RPC side. Even for chains that don't enforce that rule, a 10% bump seems reasonable. +- `RewardPercentile` is equal or lower than *ConnectivityPercentile*, when `EIP1559` is enabled. *ConnectivityPercentile* is fixed at the 85th percentile and it's the maximum percentile we're willing to bump a transaction's price. This is used as a sanity check method in order to avoid excessive gas bumping when an RPC is not responding. + +## As a Service +`Fee History` estimator is built as a service. This means it will periodically poll the RPC for new prices, perform off-chain calculations, and cache the result for future use. For simplicity, only one type of gas estimation can be enabled at a time, Legacy or Dynamic. The poll interval is controlled by `CacheTimeout`. This value should be close to the block time. For slower chains, like Ethereum, you can set it to 12s, the same as the block time. For faster chains, you can skip a block or two, as prices will be refreshed more frequently. Ideally, 1s should be the absolute minimum. + + +## Legacy Gas Price Estimations +### Fetching +Periodically, `Fee History` estimator will call `eth_gasPrice` RPC method to fetch the gas price reported by the RPC. The parameters of this call can not be controlled by the user, meaning the result can sometimes be stale, especially during sudden gas spikes. It is advisable to use EIP-1559 if the chain supports it. + +### Bumping +During bumping, `Fee History` will refresh the cached value by making a call to the RPC. The bumped value of the original price will be compared with the market price and the highest value will be returned. + +## Dynamic Price Estimations +### Fetching +`Fee History` estimator periodically calls `eth_feeHistory` method to get the most up-to-date information from the RPC (more information about the call can be found [here](https://ethereum.github.io/execution-apis/api-documentation/)). It fetches three things: +- Base Fee of the next block +- The Yth priority fee percentiles of the past X blocks, where Y is controlled by `RewardPercentile` and X by `BlockHistorySize`. +- The 85th priority fee percentiles of the past X blocks. + +The above values are used to construct and cache the following: +- **MaxPriorityFeePerGas**: the average of Yth priority fee percentiles, excluding zero values. +- **MaxFeePerGas**: `baseFee * BaseFeeBufferPercentage + MaxPriorityFeePerGas`. *BaseFeeBufferPercentage* is used as a safety to catch any fluctuations in the Base Fee during the next blocks. +- **PriorityFeeThreshold**: the max out of every 85th priority fee percentile. This value is used to stop the estimator from bumping a price above that threshold and represents the maximum allowed value. + +*Note*: for chains that don't have a mempool (activated with `BlockHistorySize=0`) **MaxPriorityFeePerGas** and **PriorityFeeThreshold** are set to 0 since there is no concept of gas bumping. + +### Bumping +For bumping, `Fee History` estimator bumps both maxPriorityFeePerGas and maxFeePerGas of the original transaction attempt. This is required by Geth, along with the 10% minimum bumping threshold. The bumped price is compared to the cached market prices stored in the estimator and the highest of the two is picked. Finally, the resulting maxPriorityFeePerGas gets compared to the cached PriorityFeeThreshold value. If the bumped value is higher, this indicates a potential connection issue with the RPC, and bumping is skipped, returning an error. + +*Note*: for chains that don't have a mempool (activated with `BlockHistorySize=0`) bumping works differently. Instead, we force-fetch the most up-to-date Base Fee value and embed it in the MaxFeePerGas. MaxPriorityFeePerGas remains 0. + +### Metrics +The following prometheus metrics are exposed: +- **gas_price_updater**: latest Gas Price stored +- **base_fee_updater**: Base Fee of the next block +- **max_priority_fee_per_gas_updater**: latest MaxPriorityFeePerGas stored +- **max_fee_per_gas_updater**: latest MaxFeePerGas stored \ No newline at end of file diff --git a/core/chains/evm/gas/fee_history_estimator.go b/core/chains/evm/gas/fee_history_estimator.go index 53af03ac7a..2b2bc66fcb 100644 --- a/core/chains/evm/gas/fee_history_estimator.go +++ b/core/chains/evm/gas/fee_history_estimator.go @@ -277,7 +277,7 @@ func (f *FeeHistoryEstimator) RefreshDynamicPrice() error { priorityFeeThresholdWei = assets.NewWei(priorityFeeThreshold) maxPriorityFeePerGas = assets.NewWei(priorityFee.Div(priorityFee, big.NewInt(nonZeroRewardsLen))) } - // baseFeeBufferPercentage is added on top as a safety to catch fluctuations in the next blocks. + // BaseFeeBufferPercentage is used as a safety to catch any fluctuations in the Base Fee during the next blocks. maxFeePerGas := nextBaseFee.AddPercentage(BaseFeeBufferPercentage).Add(maxPriorityFeePerGas) promFeeHistoryEstimatorBaseFee.WithLabelValues(f.chainID.String()).Set(float64(nextBaseFee.Int64())) diff --git a/core/chains/evm/gas/rollups/op_l1_oracle.go b/core/chains/evm/gas/rollups/op_l1_oracle.go index 28071a268f..3c55844f40 100644 --- a/core/chains/evm/gas/rollups/op_l1_oracle.go +++ b/core/chains/evm/gas/rollups/op_l1_oracle.go @@ -26,6 +26,8 @@ import ( ) // Reads L2-specific precompiles and caches the l1GasPrice set by the L2. +// +//nolint:unused type optimismL1Oracle struct { services.StateMachine client l1OracleClient @@ -50,6 +52,7 @@ type optimismL1Oracle struct { blobBaseFeeCalldata []byte blobBaseFeeScalarCalldata []byte decimalsCalldata []byte + tokenRatioCalldata []byte isEcotoneCalldata []byte isEcotoneMethodAbi abi.ABI isFjordCalldata []byte diff --git a/core/chains/evm/headtracker/head_listener.go b/core/chains/evm/headtracker/head_listener.go deleted file mode 100644 index 04535a3486..0000000000 --- a/core/chains/evm/headtracker/head_listener.go +++ /dev/null @@ -1,28 +0,0 @@ -package headtracker - -import ( - "math/big" - - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/common" - - "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/smartcontractkit/chainlink/v2/common/headtracker" - - htrktypes "github.com/smartcontractkit/chainlink/v2/common/headtracker/types" - evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" - evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" -) - -type headListener = headtracker.HeadListener[*evmtypes.Head, common.Hash] - -func NewHeadListener( - lggr logger.Logger, - ethClient evmclient.Client, - config htrktypes.Config, chStop chan struct{}, -) headListener { - return headtracker.NewHeadListener[ - *evmtypes.Head, - ethereum.Subscription, *big.Int, common.Hash, - ](lggr, ethClient, config, chStop) -} diff --git a/core/chains/evm/headtracker/head_listener_test.go b/core/chains/evm/headtracker/head_listener_test.go index 29b090bbff..2e459af2a2 100644 --- a/core/chains/evm/headtracker/head_listener_test.go +++ b/core/chains/evm/headtracker/head_listener_test.go @@ -16,9 +16,9 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink/v2/common/headtracker" commonmocks "github.com/smartcontractkit/chainlink/v2/common/types/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/testutils" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" ) @@ -40,17 +40,10 @@ func Test_HeadListener_HappyPath(t *testing.T) { evmcfg := testutils.NewTestChainScopedConfig(t, func(c *toml.EVMConfig) { c.NoNewHeadsThreshold = &commonconfig.Duration{} }) - chStop := make(chan struct{}) - hl := headtracker.NewHeadListener(lggr, ethClient, evmcfg.EVM(), chStop) var headCount atomic.Int32 - handler := func(context.Context, *evmtypes.Head) error { - headCount.Add(1) - return nil - } - - subscribeAwaiter := testutils.NewAwaiter() unsubscribeAwaiter := testutils.NewAwaiter() + subscribeAwaiter := testutils.NewAwaiter() var chHeads chan<- *evmtypes.Head var chErr = make(chan error) var chSubErr <-chan error = chErr @@ -66,23 +59,23 @@ func Test_HeadListener_HappyPath(t *testing.T) { close(chErr) }) - doneAwaiter := testutils.NewAwaiter() - done := func() { - doneAwaiter.ItHappened() - } - go hl.ListenForNewHeads(func() {}, handler, done) - - subscribeAwaiter.AwaitOrFail(t, tests.WaitTimeout(t)) - require.Eventually(t, hl.Connected, tests.WaitTimeout(t), tests.TestInterval) + func() { + hl := headtracker.NewHeadListener(lggr, ethClient, evmcfg.EVM(), nil, func(context.Context, *evmtypes.Head) error { + headCount.Add(1) + return nil + }) + require.NoError(t, hl.Start(tests.Context(t))) + defer func() { assert.NoError(t, hl.Close()) }() - chHeads <- testutils.Head(0) - chHeads <- testutils.Head(1) - chHeads <- testutils.Head(2) + subscribeAwaiter.AwaitOrFail(t, tests.WaitTimeout(t)) + require.Eventually(t, hl.Connected, tests.WaitTimeout(t), tests.TestInterval) - require.True(t, hl.ReceivingHeads()) + chHeads <- testutils.Head(0) + chHeads <- testutils.Head(1) + chHeads <- testutils.Head(2) - close(chStop) - doneAwaiter.AwaitOrFail(t) + require.True(t, hl.ReceivingHeads()) + }() unsubscribeAwaiter.AwaitOrFail(t) require.Equal(t, int32(3), headCount.Load()) @@ -101,14 +94,8 @@ func Test_HeadListener_NotReceivingHeads(t *testing.T) { evmcfg := testutils.NewTestChainScopedConfig(t, func(c *toml.EVMConfig) { c.NoNewHeadsThreshold = commonconfig.MustNewDuration(time.Second) }) - chStop := make(chan struct{}) - hl := headtracker.NewHeadListener(lggr, ethClient, evmcfg.EVM(), chStop) firstHeadAwaiter := testutils.NewAwaiter() - handler := func(context.Context, *evmtypes.Head) error { - firstHeadAwaiter.ItHappened() - return nil - } subscribeAwaiter := testutils.NewAwaiter() var chHeads chan<- *evmtypes.Head @@ -125,25 +112,25 @@ func Test_HeadListener_NotReceivingHeads(t *testing.T) { close(chErr) }) - doneAwaiter := testutils.NewAwaiter() - done := func() { - doneAwaiter.ItHappened() - } - go hl.ListenForNewHeads(func() {}, handler, done) - - subscribeAwaiter.AwaitOrFail(t, tests.WaitTimeout(t)) + func() { + hl := headtracker.NewHeadListener(lggr, ethClient, evmcfg.EVM(), nil, func(context.Context, *evmtypes.Head) error { + firstHeadAwaiter.ItHappened() + return nil + }) + require.NoError(t, hl.Start(tests.Context(t))) + defer func() { assert.NoError(t, hl.Close()) }() - chHeads <- testutils.Head(0) - firstHeadAwaiter.AwaitOrFail(t) + subscribeAwaiter.AwaitOrFail(t, tests.WaitTimeout(t)) - require.True(t, hl.ReceivingHeads()) + chHeads <- testutils.Head(0) + firstHeadAwaiter.AwaitOrFail(t) - time.Sleep(time.Second * 2) + require.True(t, hl.ReceivingHeads()) - require.False(t, hl.ReceivingHeads()) + time.Sleep(time.Second * 2) - close(chStop) - doneAwaiter.AwaitOrFail(t) + require.False(t, hl.ReceivingHeads()) + }() } func Test_HeadListener_SubscriptionErr(t *testing.T) { @@ -161,19 +148,11 @@ func Test_HeadListener_SubscriptionErr(t *testing.T) { for _, test := range cases { test := test t.Run(test.name, func(t *testing.T) { - l := logger.Test(t) + lggr := logger.Test(t) ethClient := testutils.NewEthClientMockWithDefaultChain(t) evmcfg := testutils.NewTestChainScopedConfig(t, nil) - chStop := make(chan struct{}) - hl := headtracker.NewHeadListener(l, ethClient, evmcfg.EVM(), chStop) hnhCalled := make(chan *evmtypes.Head) - hnh := func(_ context.Context, header *evmtypes.Head) error { - hnhCalled <- header - return nil - } - doneAwaiter := testutils.NewAwaiter() - done := doneAwaiter.ItHappened chSubErrTest := make(chan error) var chSubErr <-chan error = chSubErrTest @@ -189,63 +168,66 @@ func Test_HeadListener_SubscriptionErr(t *testing.T) { headsCh = args.Get(1).(chan<- *evmtypes.Head) subscribeAwaiter.ItHappened() }) - go func() { - hl.ListenForNewHeads(func() {}, hnh, done) - }() - - // Put a head on the channel to ensure we test all code paths - subscribeAwaiter.AwaitOrFail(t, tests.WaitTimeout(t)) - head := testutils.Head(0) - headsCh <- head - - h := <-hnhCalled - assert.Equal(t, head, h) - - // Expect a call to unsubscribe on error - sub.On("Unsubscribe").Once().Run(func(_ mock.Arguments) { - close(headsCh) - // geth guarantees that Unsubscribe closes the errors channel - if !test.closeErr { + func() { + hl := headtracker.NewHeadListener(lggr, ethClient, evmcfg.EVM(), nil, func(_ context.Context, header *evmtypes.Head) error { + hnhCalled <- header + return nil + }) + require.NoError(t, hl.Start(tests.Context(t))) + defer func() { assert.NoError(t, hl.Close()) }() + + // Put a head on the channel to ensure we test all code paths + subscribeAwaiter.AwaitOrFail(t, tests.WaitTimeout(t)) + head := testutils.Head(0) + headsCh <- head + + h := <-hnhCalled + assert.Equal(t, head, h) + + // Expect a call to unsubscribe on error + sub.On("Unsubscribe").Once().Run(func(_ mock.Arguments) { + close(headsCh) + // geth guarantees that Unsubscribe closes the errors channel + if !test.closeErr { + close(chSubErrTest) + } + }) + // Expect a resubscribe + chSubErrTest2 := make(chan error) + var chSubErr2 <-chan error = chSubErrTest2 + sub2 := commonmocks.NewSubscription(t) + sub2.On("Err").Return(chSubErr2) + subscribeAwaiter2 := testutils.NewAwaiter() + + var headsCh2 chan<- *evmtypes.Head + ethClient.On("SubscribeNewHead", mock.Anything, mock.AnythingOfType("chan<- *types.Head")).Return(sub2, nil).Once().Run(func(args mock.Arguments) { + headsCh2 = args.Get(1).(chan<- *evmtypes.Head) + subscribeAwaiter2.ItHappened() + }) + + // Sending test error + if test.closeErr { close(chSubErrTest) + } else { + chSubErrTest <- test.err } - }) - // Expect a resubscribe - chSubErrTest2 := make(chan error) - var chSubErr2 <-chan error = chSubErrTest2 - sub2 := commonmocks.NewSubscription(t) - sub2.On("Err").Return(chSubErr2) - subscribeAwaiter2 := testutils.NewAwaiter() - - var headsCh2 chan<- *evmtypes.Head - ethClient.On("SubscribeNewHead", mock.Anything, mock.AnythingOfType("chan<- *types.Head")).Return(sub2, nil).Once().Run(func(args mock.Arguments) { - headsCh2 = args.Get(1).(chan<- *evmtypes.Head) - subscribeAwaiter2.ItHappened() - }) - - // Sending test error - if test.closeErr { - close(chSubErrTest) - } else { - chSubErrTest <- test.err - } - // Wait for it to resubscribe - subscribeAwaiter2.AwaitOrFail(t, tests.WaitTimeout(t)) + // Wait for it to resubscribe + subscribeAwaiter2.AwaitOrFail(t, tests.WaitTimeout(t)) - head2 := testutils.Head(1) - headsCh2 <- head2 + head2 := testutils.Head(1) + headsCh2 <- head2 - h2 := <-hnhCalled - assert.Equal(t, head2, h2) + h2 := <-hnhCalled + assert.Equal(t, head2, h2) - // Second call to unsubscribe on close - sub2.On("Unsubscribe").Once().Run(func(_ mock.Arguments) { - close(headsCh2) - // geth guarantees that Unsubscribe closes the errors channel - close(chSubErrTest2) - }) - close(chStop) - doneAwaiter.AwaitOrFail(t) + // Second call to unsubscribe on close + sub2.On("Unsubscribe").Once().Run(func(_ mock.Arguments) { + close(headsCh2) + // geth guarantees that Unsubscribe closes the errors channel + close(chSubErrTest2) + }) + }() }) } } diff --git a/core/chains/evm/headtracker/head_saver.go b/core/chains/evm/headtracker/head_saver.go index 320e88a19b..f2613334c4 100644 --- a/core/chains/evm/headtracker/head_saver.go +++ b/core/chains/evm/headtracker/head_saver.go @@ -35,13 +35,12 @@ func NewHeadSaver(lggr logger.Logger, orm ORM, config commontypes.Config, htConf } func (hs *headSaver) Save(ctx context.Context, head *evmtypes.Head) error { - if err := hs.orm.IdempotentInsertHead(ctx, head); err != nil { + // adding new head might form a cycle, so it's better to validate cached chain before persisting it + if err := hs.heads.AddHeads(head); err != nil { return err } - hs.heads.AddHeads(head) - - return nil + return hs.orm.IdempotentInsertHead(ctx, head) } func (hs *headSaver) Load(ctx context.Context, latestFinalized int64) (chain *evmtypes.Head, err error) { @@ -51,7 +50,10 @@ func (hs *headSaver) Load(ctx context.Context, latestFinalized int64) (chain *ev return nil, err } - hs.heads.AddHeads(heads...) + err = hs.heads.AddHeads(heads...) + if err != nil { + return nil, fmt.Errorf("failed to populate cache with loaded heads: %w", err) + } return hs.heads.LatestHead(), nil } diff --git a/core/chains/evm/headtracker/head_tracker.go b/core/chains/evm/headtracker/head_tracker.go index d6c2cdc64e..bb39b3b5c7 100644 --- a/core/chains/evm/headtracker/head_tracker.go +++ b/core/chains/evm/headtracker/head_tracker.go @@ -2,10 +2,8 @@ package headtracker import ( "context" - "math/big" "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/common" "go.uber.org/zap/zapcore" "github.com/smartcontractkit/chainlink-common/pkg/logger" @@ -13,21 +11,20 @@ import ( "github.com/smartcontractkit/chainlink/v2/common/headtracker" commontypes "github.com/smartcontractkit/chainlink/v2/common/headtracker/types" - evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" httypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker/types" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" ) func NewHeadTracker( lggr logger.Logger, - ethClient evmclient.Client, + ethClient httypes.Client, config commontypes.Config, htConfig commontypes.HeadTrackerConfig, headBroadcaster httypes.HeadBroadcaster, headSaver httypes.HeadSaver, mailMon *mailbox.Monitor, ) httypes.HeadTracker { - return headtracker.NewHeadTracker[*evmtypes.Head, ethereum.Subscription, *big.Int, common.Hash]( + return headtracker.NewHeadTracker[*evmtypes.Head, ethereum.Subscription]( lggr, ethClient, config, diff --git a/core/chains/evm/headtracker/head_tracker_test.go b/core/chains/evm/headtracker/head_tracker_test.go index 21ff1b1a92..de54f12ff9 100644 --- a/core/chains/evm/headtracker/head_tracker_test.go +++ b/core/chains/evm/headtracker/head_tracker_test.go @@ -12,7 +12,6 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" - gethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/onsi/gomega" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -21,21 +20,19 @@ import ( "go.uber.org/zap/zaptest/observer" "golang.org/x/exp/maps" + commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox/mailboxtest" "github.com/jmoiron/sqlx" - commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" - "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" htmocks "github.com/smartcontractkit/chainlink/v2/common/headtracker/mocks" commontypes "github.com/smartcontractkit/chainlink/v2/common/headtracker/types" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" - - evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmclimocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker" @@ -45,11 +42,13 @@ import ( evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" ubig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" ) -func firstHead(t *testing.T, db *sqlx.DB) (h evmtypes.Head) { - if err := db.Get(&h, `SELECT * FROM evm.heads ORDER BY number ASC LIMIT 1`); err != nil { +func firstHead(t *testing.T, db *sqlx.DB) *evmtypes.Head { + h := new(evmtypes.Head) + if err := db.Get(h, `SELECT * FROM evm.heads ORDER BY number ASC LIMIT 1`); err != nil { t.Fatal(err) } return h @@ -578,27 +577,10 @@ func TestHeadTracker_SwitchesToLongestChainWithHeadSamplingEnabled(t *testing.T) checker.On("OnNewLongestChain", mock.Anything, mock.Anything). Run(func(args mock.Arguments) { h := args.Get(1).(*evmtypes.Head) + // This is the new longest chain [0, 5], check that it came with its parents + assert.Equal(t, uint32(6), h.ChainLength()) + assertChainWithParents(t, blocksForked, 5, 1, h) - assert.Equal(t, int64(5), h.Number) - assert.Equal(t, blocksForked.Head(5).Hash, h.Hash) - - // This is the new longest chain, check that it came with its parents - if !assert.NotNil(t, h.Parent) { - return - } - assert.Equal(t, h.Parent.Hash, blocksForked.Head(4).Hash) - if !assert.NotNil(t, h.Parent.Parent) { - return - } - assert.Equal(t, h.Parent.Parent.Hash, blocksForked.Head(3).Hash) - if !assert.NotNil(t, h.Parent.Parent.Parent) { - return - } - assert.Equal(t, h.Parent.Parent.Parent.Hash, blocksForked.Head(2).Hash) - if !assert.NotNil(t, h.Parent.Parent.Parent.Parent) { - return - } - assert.Equal(t, h.Parent.Parent.Parent.Parent.Hash, blocksForked.Head(1).Hash) lastLongestChainAwaiter.ItHappened() }).Return().Once() @@ -639,6 +621,16 @@ func TestHeadTracker_SwitchesToLongestChainWithHeadSamplingEnabled(t *testing.T) } } +func assertChainWithParents(t testing.TB, blocks *blocks, startBN, endBN uint64, h *evmtypes.Head) { + for blockNumber := startBN; blockNumber >= endBN; blockNumber-- { + assert.NotNil(t, h) + assert.Equal(t, blockNumber, uint64(h.Number)) + assert.Equal(t, blocks.Head(blockNumber).Hash, h.Hash) + // move to parent + h = h.Parent.Load() + } +} + func TestHeadTracker_SwitchesToLongestChainWithHeadSamplingDisabled(t *testing.T) { t.Parallel() @@ -725,34 +717,13 @@ func TestHeadTracker_SwitchesToLongestChainWithHeadSamplingDisabled(t *testing.T checker.On("OnNewLongestChain", mock.Anything, mock.Anything). Run(func(args mock.Arguments) { h := args.Get(1).(*evmtypes.Head) - require.Equal(t, int64(4), h.Number) - require.Equal(t, blocks.Head(4).Hash, h.Hash) - - // Check that the block came with its parents - require.NotNil(t, h.Parent) - require.Equal(t, h.Parent.Hash, blocks.Head(3).Hash) - require.NotNil(t, h.Parent.Parent.Hash) - require.Equal(t, h.Parent.Parent.Hash, blocks.Head(2).Hash) - require.NotNil(t, h.Parent.Parent.Parent) - require.Equal(t, h.Parent.Parent.Parent.Hash, blocks.Head(1).Hash) + assertChainWithParents(t, blocks, 4, 1, h) }).Return().Once() checker.On("OnNewLongestChain", mock.Anything, mock.Anything). Run(func(args mock.Arguments) { h := args.Get(1).(*evmtypes.Head) - - require.Equal(t, int64(5), h.Number) - require.Equal(t, blocksForked.Head(5).Hash, h.Hash) - - // This is the new longest chain, check that it came with its parents - require.NotNil(t, h.Parent) - require.Equal(t, h.Parent.Hash, blocksForked.Head(4).Hash) - require.NotNil(t, h.Parent.Parent) - require.Equal(t, h.Parent.Parent.Hash, blocksForked.Head(3).Hash) - require.NotNil(t, h.Parent.Parent.Parent) - require.Equal(t, h.Parent.Parent.Parent.Hash, blocksForked.Head(2).Hash) - require.NotNil(t, h.Parent.Parent.Parent.Parent) - require.Equal(t, h.Parent.Parent.Parent.Parent.Hash, blocksForked.Head(1).Hash) + assertChainWithParents(t, blocksForked, 5, 1, h) lastLongestChainAwaiter.ItHappened() }).Return().Once() @@ -811,52 +782,37 @@ func TestHeadTracker_Backfill(t *testing.T) { now := uint64(time.Now().UTC().Unix()) - gethHead0 := &gethTypes.Header{ - Number: big.NewInt(0), - ParentHash: common.BigToHash(big.NewInt(0)), - Time: now, - } - head0 := evmtypes.NewHead(gethHead0.Number, utils.NewHash(), gethHead0.ParentHash, gethHead0.Time, ubig.New(testutils.FixtureChainID)) + head0 := evmtypes.NewHead(big.NewInt(0), utils.NewHash(), common.BigToHash(big.NewInt(0)), now, ubig.New(testutils.FixtureChainID)) - h1 := *testutils.Head(1) + h1 := testutils.Head(1) h1.ParentHash = head0.Hash - gethHead8 := &gethTypes.Header{ - Number: big.NewInt(8), - ParentHash: utils.NewHash(), - Time: now, - } - head8 := evmtypes.NewHead(gethHead8.Number, utils.NewHash(), gethHead8.ParentHash, gethHead8.Time, ubig.New(testutils.FixtureChainID)) + head8 := evmtypes.NewHead(big.NewInt(8), utils.NewHash(), utils.NewHash(), now, ubig.New(testutils.FixtureChainID)) - h9 := *testutils.Head(9) + h9 := testutils.Head(9) h9.ParentHash = head8.Hash - gethHead10 := &gethTypes.Header{ - Number: big.NewInt(10), - ParentHash: h9.Hash, - Time: now, - } - head10 := evmtypes.NewHead(gethHead10.Number, utils.NewHash(), gethHead10.ParentHash, gethHead10.Time, ubig.New(testutils.FixtureChainID)) + head10 := evmtypes.NewHead(big.NewInt(10), utils.NewHash(), h9.Hash, now, ubig.New(testutils.FixtureChainID)) - h11 := *testutils.Head(11) + h11 := testutils.Head(11) h11.ParentHash = head10.Hash - h12 := *testutils.Head(12) + h12 := testutils.Head(12) h12.ParentHash = h11.Hash - h13 := *testutils.Head(13) + h13 := testutils.Head(13) h13.ParentHash = h12.Hash - h14Orphaned := *testutils.Head(14) + h14Orphaned := testutils.Head(14) h14Orphaned.ParentHash = h13.Hash - h14 := *testutils.Head(14) + h14 := testutils.Head(14) h14.ParentHash = h13.Hash - h15 := *testutils.Head(15) + h15 := testutils.Head(15) h15.ParentHash = h14.Hash - heads := []evmtypes.Head{ + heads := []*evmtypes.Head{ h9, h11, h12, @@ -869,7 +825,7 @@ func TestHeadTracker_Backfill(t *testing.T) { ctx := tests.Context(t) type opts struct { - Heads []evmtypes.Head + Heads []*evmtypes.Head FinalityTagEnabled bool FinalizedBlockOffset uint32 FinalityDepth uint32 @@ -889,7 +845,7 @@ func TestHeadTracker_Backfill(t *testing.T) { db := pgtest.NewSqlxDB(t) orm := headtracker.NewORM(*testutils.FixtureChainID, db) for i := range opts.Heads { - require.NoError(t, orm.IdempotentInsertHead(tests.Context(t), &opts.Heads[i])) + require.NoError(t, orm.IdempotentInsertHead(tests.Context(t), opts.Heads[i])) } ethClient := testutils.NewEthClientMock(t) ethClient.On("ConfiguredChainID", mock.Anything).Return(evmcfg.EVM().ChainID(), nil) @@ -904,72 +860,70 @@ func TestHeadTracker_Backfill(t *testing.T) { const expectedError = "failed to fetch latest finalized block" htu.ethClient.On("LatestFinalizedBlock", mock.Anything).Return(nil, errors.New(expectedError)).Once() - err := htu.headTracker.Backfill(ctx, &h12) + err := htu.headTracker.Backfill(ctx, h12) require.ErrorContains(t, err, expectedError) }) t.Run("returns error if latestFinalized is not valid", func(t *testing.T) { htu := newHeadTrackerUniverse(t, opts{FinalityTagEnabled: true}) htu.ethClient.On("LatestFinalizedBlock", mock.Anything).Return(nil, nil).Once() - err := htu.headTracker.Backfill(ctx, &h12) + err := htu.headTracker.Backfill(ctx, h12) require.EqualError(t, err, "failed to calculate finalized block: failed to get valid latest finalized block") }) t.Run("Returns error if finality gap is too big", func(t *testing.T) { htu := newHeadTrackerUniverse(t, opts{FinalityTagEnabled: true, MaxAllowedFinalityDepth: 2}) - htu.ethClient.On("LatestFinalizedBlock", mock.Anything).Return(&h9, nil).Once() + htu.ethClient.On("LatestFinalizedBlock", mock.Anything).Return(h9, nil).Once() - err := htu.headTracker.Backfill(ctx, &h12) + err := htu.headTracker.Backfill(ctx, h12) require.EqualError(t, err, "gap between latest finalized block (9) and current head (12) is too large (> 2)") }) t.Run("Returns error if finalized head is ahead of canonical", func(t *testing.T) { htu := newHeadTrackerUniverse(t, opts{FinalityTagEnabled: true}) - htu.ethClient.On("LatestFinalizedBlock", mock.Anything).Return(&h14Orphaned, nil).Once() + htu.ethClient.On("LatestFinalizedBlock", mock.Anything).Return(h14Orphaned, nil).Once() - err := htu.headTracker.Backfill(ctx, &h12) + err := htu.headTracker.Backfill(ctx, h12) require.EqualError(t, err, "invariant violation: expected head of canonical chain to be ahead of the latestFinalized") }) t.Run("Returns error if finalizedHead is not present in the canonical chain", func(t *testing.T) { htu := newHeadTrackerUniverse(t, opts{Heads: heads, FinalityTagEnabled: true}) - htu.ethClient.On("LatestFinalizedBlock", mock.Anything).Return(&h14Orphaned, nil).Once() + htu.ethClient.On("LatestFinalizedBlock", mock.Anything).Return(h14Orphaned, nil).Once() - err := htu.headTracker.Backfill(ctx, &h15) + err := htu.headTracker.Backfill(ctx, h15) require.EqualError(t, err, "expected finalized block to be present in canonical chain") }) t.Run("Marks all blocks in chain that are older than finalized", func(t *testing.T) { htu := newHeadTrackerUniverse(t, opts{Heads: heads, FinalityTagEnabled: true}) - assertFinalized := func(expectedFinalized bool, msg string, heads ...evmtypes.Head) { + assertFinalized := func(expectedFinalized bool, msg string, heads ...*evmtypes.Head) { for _, h := range heads { storedHead := htu.headSaver.Chain(h.Hash) - assert.Equal(t, expectedFinalized, storedHead != nil && storedHead.IsFinalized, msg, "block_number", h.Number) + assert.Equal(t, expectedFinalized, storedHead != nil && storedHead.IsFinalized.Load(), msg, "block_number", h.Number) } } - htu.ethClient.On("LatestFinalizedBlock", mock.Anything).Return(&h14, nil).Once() - err := htu.headTracker.Backfill(ctx, &h15) + htu.ethClient.On("LatestFinalizedBlock", mock.Anything).Return(h14, nil).Once() + err := htu.headTracker.Backfill(ctx, h15) require.NoError(t, err) assertFinalized(true, "expected heads to be marked as finalized after backfill", h14, h13, h12, h11) - assertFinalized(false, "expected heads to remain unfinalized", h15, head10) + assertFinalized(false, "expected heads to remain unfinalized", h15, &head10) }) t.Run("fetches a missing head", func(t *testing.T) { htu := newHeadTrackerUniverse(t, opts{Heads: heads, FinalityTagEnabled: true}) - htu.ethClient.On("LatestFinalizedBlock", mock.Anything).Return(&h9, nil).Once() + htu.ethClient.On("LatestFinalizedBlock", mock.Anything).Return(h9, nil).Once() htu.ethClient.On("HeadByHash", mock.Anything, head10.Hash). Return(&head10, nil) - err := htu.headTracker.Backfill(ctx, &h12) + err := htu.headTracker.Backfill(ctx, h12) require.NoError(t, err) h := htu.headSaver.Chain(h12.Hash) - assert.Equal(t, int64(12), h.Number) - require.NotNil(t, h.Parent) - assert.Equal(t, int64(11), h.Parent.Number) - require.NotNil(t, h.Parent.Parent) - assert.Equal(t, int64(10), h.Parent.Parent.Number) - require.NotNil(t, h.Parent.Parent.Parent) - assert.Equal(t, int64(9), h.Parent.Parent.Parent.Number) + for expectedBlockNumber := int64(12); expectedBlockNumber >= 9; expectedBlockNumber-- { + require.NotNil(t, h) + assert.Equal(t, expectedBlockNumber, h.Number) + h = h.Parent.Load() + } writtenHead, err := htu.orm.HeadByHash(tests.Context(t), head10.Hash) require.NoError(t, err) @@ -984,7 +938,7 @@ func TestHeadTracker_Backfill(t *testing.T) { htu.ethClient.On("HeadByHash", mock.Anything, head8.Hash). Return(&head8, nil) - err := htu.headTracker.Backfill(ctx, &h15) + err := htu.headTracker.Backfill(ctx, h15) require.NoError(t, err) h := htu.headSaver.Chain(h15.Hash) @@ -1005,7 +959,7 @@ func TestHeadTracker_Backfill(t *testing.T) { Return(nil, ethereum.NotFound). Once() - err := htu.headTracker.Backfill(ctx, &h12) + err := htu.headTracker.Backfill(ctx, h12) require.Error(t, err) require.ErrorContains(t, err, "fetchAndSaveHead failed: not found") @@ -1027,7 +981,7 @@ func TestHeadTracker_Backfill(t *testing.T) { cancel() }) - err := htu.headTracker.Backfill(lctx, &h12) + err := htu.headTracker.Backfill(lctx, h12) require.Error(t, err) require.ErrorContains(t, err, "fetchAndSaveHead failed: context canceled") @@ -1039,12 +993,12 @@ func TestHeadTracker_Backfill(t *testing.T) { }) t.Run("abandons backfill and returns error when fetching a block by hash fails, indicating a reorg", func(t *testing.T) { htu := newHeadTrackerUniverse(t, opts{FinalityTagEnabled: true}) - htu.ethClient.On("LatestFinalizedBlock", mock.Anything).Return(&h11, nil).Once() - htu.ethClient.On("HeadByHash", mock.Anything, h14.Hash).Return(&h14, nil).Once() - htu.ethClient.On("HeadByHash", mock.Anything, h13.Hash).Return(&h13, nil).Once() + htu.ethClient.On("LatestFinalizedBlock", mock.Anything).Return(h11, nil).Once() + htu.ethClient.On("HeadByHash", mock.Anything, h14.Hash).Return(h14, nil).Once() + htu.ethClient.On("HeadByHash", mock.Anything, h13.Hash).Return(h13, nil).Once() htu.ethClient.On("HeadByHash", mock.Anything, h12.Hash).Return(nil, errors.New("not found")).Once() - err := htu.headTracker.Backfill(ctx, &h15) + err := htu.headTracker.Backfill(ctx, h15) require.Error(t, err) require.ErrorContains(t, err, "fetchAndSaveHead failed: not found") @@ -1056,83 +1010,83 @@ func TestHeadTracker_Backfill(t *testing.T) { assert.Equal(t, int64(13), h.EarliestInChain().BlockNumber()) }) t.Run("marks head as finalized, if latestHead = finalizedHead (0 finality depth)", func(t *testing.T) { - htu := newHeadTrackerUniverse(t, opts{Heads: []evmtypes.Head{h15}, FinalityTagEnabled: true}) + htu := newHeadTrackerUniverse(t, opts{Heads: []*evmtypes.Head{h15}, FinalityTagEnabled: true}) finalizedH15 := h15 // copy h15 to have different addresses - htu.ethClient.On("LatestFinalizedBlock", mock.Anything).Return(&finalizedH15, nil).Once() - err := htu.headTracker.Backfill(ctx, &h15) + htu.ethClient.On("LatestFinalizedBlock", mock.Anything).Return(finalizedH15, nil).Once() + err := htu.headTracker.Backfill(ctx, h15) require.NoError(t, err) h := htu.headSaver.LatestChain() // Should contain 14, 13 (15 was never added). When trying to get the parent of h13 by hash, a reorg happened and backfill exited. assert.Equal(t, 1, int(h.ChainLength())) - assert.True(t, h.IsFinalized) + assert.True(t, h.IsFinalized.Load()) assert.Equal(t, h15.BlockNumber(), h.BlockNumber()) assert.Equal(t, h15.Hash, h.Hash) }) t.Run("marks block as finalized according to FinalizedBlockOffset (finality tag)", func(t *testing.T) { - htu := newHeadTrackerUniverse(t, opts{Heads: []evmtypes.Head{h15}, FinalityTagEnabled: true, FinalizedBlockOffset: 2}) - htu.ethClient.On("LatestFinalizedBlock", mock.Anything).Return(&h14, nil).Once() + htu := newHeadTrackerUniverse(t, opts{Heads: []*evmtypes.Head{h15}, FinalityTagEnabled: true, FinalizedBlockOffset: 2}) + htu.ethClient.On("LatestFinalizedBlock", mock.Anything).Return(h14, nil).Once() // calculateLatestFinalizedBlock fetches blocks at LatestFinalized - FinalizedBlockOffset - htu.ethClient.On("HeadByNumber", mock.Anything, big.NewInt(h12.Number)).Return(&h12, nil).Once() + htu.ethClient.On("HeadByNumber", mock.Anything, big.NewInt(h12.Number)).Return(h12, nil).Once() // backfill from 15 to 12 - htu.ethClient.On("HeadByHash", mock.Anything, h12.Hash).Return(&h12, nil).Once() - htu.ethClient.On("HeadByHash", mock.Anything, h13.Hash).Return(&h13, nil).Once() - htu.ethClient.On("HeadByHash", mock.Anything, h14.Hash).Return(&h14, nil).Once() - err := htu.headTracker.Backfill(ctx, &h15) + htu.ethClient.On("HeadByHash", mock.Anything, h12.Hash).Return(h12, nil).Once() + htu.ethClient.On("HeadByHash", mock.Anything, h13.Hash).Return(h13, nil).Once() + htu.ethClient.On("HeadByHash", mock.Anything, h14.Hash).Return(h14, nil).Once() + err := htu.headTracker.Backfill(ctx, h15) require.NoError(t, err) h := htu.headSaver.LatestChain() // h - must contain 15, 14, 13, 12 and only 12 is finalized assert.Equal(t, 4, int(h.ChainLength())) - for ; h.Hash != h12.Hash; h = h.Parent { - assert.False(t, h.IsFinalized) + for ; h.Hash != h12.Hash; h = h.Parent.Load() { + assert.False(t, h.IsFinalized.Load()) } - assert.True(t, h.IsFinalized) + assert.True(t, h.IsFinalized.Load()) assert.Equal(t, h12.BlockNumber(), h.BlockNumber()) assert.Equal(t, h12.Hash, h.Hash) }) t.Run("marks block as finalized according to FinalizedBlockOffset (finality depth)", func(t *testing.T) { - htu := newHeadTrackerUniverse(t, opts{Heads: []evmtypes.Head{h15}, FinalityDepth: 1, FinalizedBlockOffset: 2}) - htu.ethClient.On("HeadByNumber", mock.Anything, big.NewInt(12)).Return(&h12, nil).Once() + htu := newHeadTrackerUniverse(t, opts{Heads: []*evmtypes.Head{h15}, FinalityDepth: 1, FinalizedBlockOffset: 2}) + htu.ethClient.On("HeadByNumber", mock.Anything, big.NewInt(12)).Return(h12, nil).Once() // backfill from 15 to 12 - htu.ethClient.On("HeadByHash", mock.Anything, h14.Hash).Return(&h14, nil).Once() - htu.ethClient.On("HeadByHash", mock.Anything, h13.Hash).Return(&h13, nil).Once() - htu.ethClient.On("HeadByHash", mock.Anything, h12.Hash).Return(&h12, nil).Once() - err := htu.headTracker.Backfill(ctx, &h15) + htu.ethClient.On("HeadByHash", mock.Anything, h14.Hash).Return(h14, nil).Once() + htu.ethClient.On("HeadByHash", mock.Anything, h13.Hash).Return(h13, nil).Once() + htu.ethClient.On("HeadByHash", mock.Anything, h12.Hash).Return(h12, nil).Once() + err := htu.headTracker.Backfill(ctx, h15) require.NoError(t, err) h := htu.headSaver.LatestChain() // h - must contain 15, 14, 13, 12 and only 12 is finalized assert.Equal(t, 4, int(h.ChainLength())) - for ; h.Hash != h12.Hash; h = h.Parent { - assert.False(t, h.IsFinalized) + for ; h.Hash != h12.Hash; h = h.Parent.Load() { + assert.False(t, h.IsFinalized.Load()) } - assert.True(t, h.IsFinalized) + assert.True(t, h.IsFinalized.Load()) assert.Equal(t, h12.BlockNumber(), h.BlockNumber()) assert.Equal(t, h12.Hash, h.Hash) }) t.Run("marks block as finalized according to FinalizedBlockOffset even with instant finality", func(t *testing.T) { - htu := newHeadTrackerUniverse(t, opts{Heads: []evmtypes.Head{h15}, FinalityDepth: 0, FinalizedBlockOffset: 2}) - htu.ethClient.On("HeadByNumber", mock.Anything, big.NewInt(13)).Return(&h13, nil).Once() + htu := newHeadTrackerUniverse(t, opts{Heads: []*evmtypes.Head{h15}, FinalityDepth: 0, FinalizedBlockOffset: 2}) + htu.ethClient.On("HeadByNumber", mock.Anything, big.NewInt(13)).Return(h13, nil).Once() // backfill from 15 to 13 - htu.ethClient.On("HeadByHash", mock.Anything, h14.Hash).Return(&h14, nil).Once() - htu.ethClient.On("HeadByHash", mock.Anything, h13.Hash).Return(&h13, nil).Once() - err := htu.headTracker.Backfill(ctx, &h15) + htu.ethClient.On("HeadByHash", mock.Anything, h14.Hash).Return(h14, nil).Once() + htu.ethClient.On("HeadByHash", mock.Anything, h13.Hash).Return(h13, nil).Once() + err := htu.headTracker.Backfill(ctx, h15) require.NoError(t, err) h := htu.headSaver.LatestChain() // h - must contain 15, 14, 13, only 13 is finalized assert.Equal(t, 3, int(h.ChainLength())) - for ; h.Hash != h13.Hash; h = h.Parent { - assert.False(t, h.IsFinalized) + for ; h.Hash != h13.Hash; h = h.Parent.Load() { + assert.False(t, h.IsFinalized.Load()) } - assert.True(t, h.IsFinalized) + assert.True(t, h.IsFinalized.Load()) assert.Equal(t, h13.BlockNumber(), h.BlockNumber()) assert.Equal(t, h13.Hash, h.Hash) }) @@ -1153,7 +1107,7 @@ func TestHeadTracker_LatestAndFinalizedBlock(t *testing.T) { h13.ParentHash = h12.Hash type opts struct { - Heads []evmtypes.Head + Heads []*evmtypes.Head FinalityTagEnabled bool FinalizedBlockOffset uint32 FinalityDepth uint32 @@ -1169,7 +1123,7 @@ func TestHeadTracker_LatestAndFinalizedBlock(t *testing.T) { db := pgtest.NewSqlxDB(t) orm := headtracker.NewORM(*testutils.FixtureChainID, db) for i := range opts.Heads { - require.NoError(t, orm.IdempotentInsertHead(tests.Context(t), &opts.Heads[i])) + require.NoError(t, orm.IdempotentInsertHead(tests.Context(t), opts.Heads[i])) } ethClient := evmtest.NewEthClientMock(t) ethClient.On("ConfiguredChainID", mock.Anything).Return(testutils.FixtureChainID, nil) @@ -1221,7 +1175,7 @@ func TestHeadTracker_LatestAndFinalizedBlock(t *testing.T) { assert.Equal(t, actualLF, h11) }) t.Run("returns latest finalized block with offset from cache (finality tag)", func(t *testing.T) { - htu := newHeadTrackerUniverse(t, opts{FinalityTagEnabled: true, FinalizedBlockOffset: 1, Heads: []evmtypes.Head{*h13, *h12, *h11}}) + htu := newHeadTrackerUniverse(t, opts{FinalityTagEnabled: true, FinalizedBlockOffset: 1, Heads: []*evmtypes.Head{h13, h12, h11}}) htu.ethClient.On("HeadByNumber", mock.Anything, (*big.Int)(nil)).Return(h13, nil).Once() htu.ethClient.On("LatestFinalizedBlock", mock.Anything).Return(h12, nil).Once() @@ -1231,7 +1185,7 @@ func TestHeadTracker_LatestAndFinalizedBlock(t *testing.T) { assert.Equal(t, actualLF.Number, h11.Number) }) t.Run("returns latest finalized block with offset from RPC (finality tag)", func(t *testing.T) { - htu := newHeadTrackerUniverse(t, opts{FinalityTagEnabled: true, FinalizedBlockOffset: 2, Heads: []evmtypes.Head{*h13, *h12, *h11}}) + htu := newHeadTrackerUniverse(t, opts{FinalityTagEnabled: true, FinalizedBlockOffset: 2, Heads: []*evmtypes.Head{h13, h12, h11}}) htu.ethClient.On("HeadByNumber", mock.Anything, (*big.Int)(nil)).Return(h13, nil).Once() htu.ethClient.On("LatestFinalizedBlock", mock.Anything).Return(h12, nil).Once() h10 := testutils.Head(10) @@ -1252,7 +1206,7 @@ func TestHeadTracker_LatestAndFinalizedBlock(t *testing.T) { assert.Equal(t, actualLF.Number, h13.Number) }) t.Run("returns latest finalized block with offset from cache (finality depth)", func(t *testing.T) { - htu := newHeadTrackerUniverse(t, opts{FinalityDepth: 1, FinalizedBlockOffset: 1, Heads: []evmtypes.Head{*h13, *h12, *h11}}) + htu := newHeadTrackerUniverse(t, opts{FinalityDepth: 1, FinalizedBlockOffset: 1, Heads: []*evmtypes.Head{h13, h12, h11}}) htu.ethClient.On("HeadByNumber", mock.Anything, (*big.Int)(nil)).Return(h13, nil).Once() actualL, actualLF, err := htu.headTracker.LatestAndFinalizedBlock(ctx) @@ -1261,7 +1215,7 @@ func TestHeadTracker_LatestAndFinalizedBlock(t *testing.T) { assert.Equal(t, actualLF.Number, h11.Number) }) t.Run("returns latest finalized block with offset from RPC (finality depth)", func(t *testing.T) { - htu := newHeadTrackerUniverse(t, opts{FinalityDepth: 1, FinalizedBlockOffset: 2, Heads: []evmtypes.Head{*h13, *h12, *h11}}) + htu := newHeadTrackerUniverse(t, opts{FinalityDepth: 1, FinalizedBlockOffset: 2, Heads: []*evmtypes.Head{h13, h12, h11}}) htu.ethClient.On("HeadByNumber", mock.Anything, (*big.Int)(nil)).Return(h13, nil).Once() h10 := testutils.Head(10) htu.ethClient.On("HeadByNumber", mock.Anything, big.NewInt(10)).Return(h10, nil).Once() @@ -1273,47 +1227,6 @@ func TestHeadTracker_LatestAndFinalizedBlock(t *testing.T) { }) } -// BenchmarkHeadTracker_Backfill - benchmarks HeadTracker's Backfill with focus on efficiency after initial -// backfill on start up -func BenchmarkHeadTracker_Backfill(b *testing.B) { - evmcfg := testutils.NewTestChainScopedConfig(b, func(c *toml.EVMConfig) { - c.FinalityTagEnabled = ptr(true) - }) - db := pgtest.NewSqlxDB(b) - chainID := big.NewInt(evmclient.NullClientChainID) - orm := headtracker.NewORM(*chainID, db) - ethClient := evmclimocks.NewClient(b) - ethClient.On("ConfiguredChainID").Return(chainID) - ht := createHeadTracker(b, ethClient, evmcfg.EVM(), evmcfg.EVM().HeadTracker(), orm) - ctx := tests.Context(b) - makeHash := func(n int64) common.Hash { - return common.BigToHash(big.NewInt(n)) - } - const finalityDepth = 12000 // observed value on Arbitrum - makeBlock := func(n int64) *evmtypes.Head { - return &evmtypes.Head{Number: n, Hash: makeHash(n), ParentHash: makeHash(n - 1)} - } - latest := makeBlock(finalityDepth) - finalized := makeBlock(1) - ethClient.On("HeadByHash", mock.Anything, mock.Anything).Return(func(_ context.Context, hash common.Hash) (*evmtypes.Head, error) { - number := hash.Big().Int64() - return makeBlock(number), nil - }) - ethClient.On("LatestFinalizedBlock", mock.Anything).Return(finalized, nil).Once() - // run initial backfill to populate the database - err := ht.headTracker.Backfill(ctx, latest) - require.NoError(b, err) - b.ResetTimer() - // focus benchmark on processing of a new latest block - for i := 0; i < b.N; i++ { - latest = makeBlock(int64(finalityDepth + i)) - finalized = makeBlock(int64(i + 1)) - ethClient.On("LatestFinalizedBlock", mock.Anything).Return(finalized, nil).Once() - err := ht.headTracker.Backfill(ctx, latest) - require.NoError(b, err) - } -} - func createHeadTracker(t testing.TB, ethClient *evmclimocks.Client, config commontypes.Config, htConfig commontypes.HeadTrackerConfig, orm headtracker.ORM) *headTrackerUniverse { lggr, ob := logger.TestObserved(t, zap.DebugLevel) hb := headtracker.NewHeadBroadcaster(lggr) @@ -1417,15 +1330,15 @@ func (hb *headBuffer) Append(head *evmtypes.Head) { Number: head.Number, Hash: head.Hash, ParentHash: head.ParentHash, - Parent: head.Parent, Timestamp: time.Unix(int64(len(hb.Heads)), 0), EVMChainID: head.EVMChainID, } + cloned.Parent.Store(head.Parent.Load()) hb.Heads = append(hb.Heads, cloned) } type blocks struct { - t *testing.T + t testing.TB Hashes []common.Hash mHashes map[int64]common.Hash Heads map[int64]*evmtypes.Head @@ -1435,7 +1348,7 @@ func (b *blocks) Head(number uint64) *evmtypes.Head { return b.Heads[int64(number)] } -func NewBlocks(t *testing.T, numHashes int) *blocks { +func NewBlocks(t testing.TB, numHashes int) *blocks { hashes := make([]common.Hash, 0) heads := make(map[int64]*evmtypes.Head) for i := int64(0); i < int64(numHashes); i++ { @@ -1445,7 +1358,7 @@ func NewBlocks(t *testing.T, numHashes int) *blocks { heads[i] = &evmtypes.Head{Hash: hash, Number: i, Timestamp: time.Unix(i, 0), EVMChainID: ubig.New(testutils.FixtureChainID)} if i > 0 { parent := heads[i-1] - heads[i].Parent = parent + heads[i].Parent.Store(parent) heads[i].ParentHash = parent.Hash } } @@ -1474,7 +1387,7 @@ func (b *blocks) ForkAt(t *testing.T, blockNum int64, numHashes int) *blocks { } forked.Heads[blockNum].ParentHash = b.Heads[blockNum].ParentHash - forked.Heads[blockNum].Parent = b.Heads[blockNum].Parent + forked.Heads[blockNum].Parent.Store(b.Heads[blockNum].Parent.Load()) return forked } @@ -1488,9 +1401,9 @@ func (b *blocks) NewHead(number uint64) *evmtypes.Head { Number: parent.Number + 1, Hash: testutils.NewHash(), ParentHash: parent.Hash, - Parent: parent, Timestamp: time.Unix(parent.Number+1, 0), EVMChainID: ubig.New(testutils.FixtureChainID), } + head.Parent.Store(parent) return head } diff --git a/core/chains/evm/headtracker/heads.go b/core/chains/evm/headtracker/heads.go index a61e55dcd2..c3492f9a59 100644 --- a/core/chains/evm/headtracker/heads.go +++ b/core/chains/evm/headtracker/heads.go @@ -1,7 +1,8 @@ package headtracker import ( - "sort" + "container/heap" + "fmt" "sync" "github.com/ethereum/go-ethereum/common" @@ -17,7 +18,7 @@ type Heads interface { HeadByHash(hash common.Hash) *evmtypes.Head // AddHeads adds newHeads to the collection, eliminates duplicates, // sorts by head number, fixes parents and cuts off old heads (historyDepth). - AddHeads(newHeads ...*evmtypes.Head) + AddHeads(newHeads ...*evmtypes.Head) error // Count returns number of heads in the collection. Count() int // MarkFinalized - finds `finalized` in the LatestHead and marks it and all direct ancestors as finalized. @@ -26,114 +27,158 @@ type Heads interface { } type heads struct { - heads []*evmtypes.Head - headsMap map[common.Hash]*evmtypes.Head - mu sync.RWMutex + highest *evmtypes.Head + headsAsc *headsHeap + headsByHash map[common.Hash]*evmtypes.Head + headsByParent map[common.Hash]map[common.Hash]*evmtypes.Head + mu sync.RWMutex } func NewHeads() Heads { - return &heads{} + return &heads{ + headsAsc: &headsHeap{}, + headsByHash: make(map[common.Hash]*evmtypes.Head), + headsByParent: map[common.Hash]map[common.Hash]*evmtypes.Head{}, + } } func (h *heads) LatestHead() *evmtypes.Head { h.mu.RLock() defer h.mu.RUnlock() - if len(h.heads) == 0 { - return nil - } - return h.heads[0] + return h.highest } func (h *heads) HeadByHash(hash common.Hash) *evmtypes.Head { h.mu.RLock() defer h.mu.RUnlock() - if h.headsMap == nil { + if h.headsByHash == nil { return nil } - return h.headsMap[hash] + return h.headsByHash[hash] } func (h *heads) Count() int { h.mu.RLock() defer h.mu.RUnlock() - return len(h.heads) + return h.headsAsc.Len() } -// MarkFinalized - marks block with has equal to finalized and all it's direct ancestors as finalized. +// MarkFinalized - marks block with hash equal to finalized and all it's direct ancestors as finalized. // Trims old blocks whose height is smaller than minBlockToKeep func (h *heads) MarkFinalized(finalized common.Hash, minBlockToKeep int64) bool { h.mu.Lock() defer h.mu.Unlock() - if len(h.heads) == 0 { + if len(h.headsByHash) == 0 { return false } - // deep copy to avoid race on head.Parent - h.heads, h.headsMap = deepCopy(h.heads, minBlockToKeep) - - finalizedHead, ok := h.headsMap[finalized] + finalizedHead, ok := h.headsByHash[finalized] if !ok { return false } - for finalizedHead != nil { - finalizedHead.IsFinalized = true - finalizedHead = finalizedHead.Parent + + markFinalized(finalizedHead) + + // remove all blocks that are older than minBlockToKeep + for h.headsAsc.Len() > 0 && h.headsAsc.Peek().Number < minBlockToKeep { + oldBlock := heap.Pop(h.headsAsc).(*evmtypes.Head) + delete(h.headsByHash, oldBlock.Hash) + // clear .Parent in oldBlock's children + for _, oldBlockChildren := range h.headsByParent[oldBlock.Hash] { + oldBlockChildren.Parent.Store(nil) + } + // headsByParent are expected to be of the same height, so we can remove them all at once + delete(h.headsByParent, oldBlock.ParentHash) + } + + if h.highest.Number < minBlockToKeep { + h.highest = nil } return true } -func deepCopy(oldHeads []*evmtypes.Head, minBlockToKeep int64) ([]*evmtypes.Head, map[common.Hash]*evmtypes.Head) { - headsMap := make(map[common.Hash]*evmtypes.Head, len(oldHeads)) - heads := make([]*evmtypes.Head, 0, len(headsMap)) - for _, head := range oldHeads { - if head.Hash == head.ParentHash { - // shouldn't happen but it is untrusted input - continue - } - if head.BlockNumber() < minBlockToKeep { - // trim redundant blocks - continue - } - // copy all head objects to avoid races when a previous head chain is used - // elsewhere (since we mutate Parent here) - headCopy := *head - headCopy.Parent = nil // always build it from scratch in case it points to a head too old to be included - // map eliminates duplicates - // prefer head that was already in heads as it might have been marked as finalized on previous run - if _, ok := headsMap[head.Hash]; !ok { - headsMap[head.Hash] = &headCopy - heads = append(heads, &headCopy) +func markFinalized(head *evmtypes.Head) { + // we can assume that if a head was previously marked as finalized all its ancestors were marked as finalized + for head != nil && !head.IsFinalized.Load() { + head.IsFinalized.Store(true) + head = head.Parent.Load() + } +} + +func (h *heads) ensureNoCycles(newHead *evmtypes.Head) error { + if newHead.ParentHash == newHead.Hash { + return fmt.Errorf("cycle detected: newHeads reference itself newHead(%s)", newHead.String()) + } + if parent, ok := h.headsByHash[newHead.ParentHash]; ok { + if parent.Number >= newHead.Number { + return fmt.Errorf("potential cycle detected while adding newHead as child: %w", newPotentialCycleError(parent, newHead)) } } - // sort the heads as original slice might be out of order - sort.SliceStable(heads, func(i, j int) bool { - // sorting from the highest number to lowest - return heads[i].Number > heads[j].Number - }) - - // assign parents - for i := 0; i < len(heads); i++ { - head := heads[i] - parent, exists := headsMap[head.ParentHash] - if exists { - head.Parent = parent + for _, child := range h.headsByParent[newHead.Hash] { + if newHead.Number >= child.Number { + return fmt.Errorf("potential cycle detected while adding newHead as parent: %w", newPotentialCycleError(newHead, child)) } } - return heads, headsMap + return nil } -func (h *heads) AddHeads(newHeads ...*evmtypes.Head) { +func (h *heads) AddHeads(newHeads ...*evmtypes.Head) error { h.mu.Lock() defer h.mu.Unlock() - // deep copy to avoid race on head.Parent - h.heads, h.headsMap = deepCopy(append(h.heads, newHeads...), 0) + for _, newHead := range newHeads { + // skip blocks that were previously added + if _, ok := h.headsByHash[newHead.Hash]; ok { + continue + } + + if err := h.ensureNoCycles(newHead); err != nil { + return err + } + + // heads now owns the newHead - reset values that are populated by heads + newHead.IsFinalized.Store(false) + newHead.Parent.Store(nil) + + // prefer newer head to set as highest + if h.highest == nil || h.highest.Number <= newHead.Number { + h.highest = newHead + } + + heap.Push(h.headsAsc, newHead) + h.headsByHash[newHead.Hash] = newHead + siblings, ok := h.headsByParent[newHead.ParentHash] + if !ok { + siblings = make(map[common.Hash]*evmtypes.Head) + h.headsByParent[newHead.ParentHash] = siblings + } + siblings[newHead.Hash] = newHead + // populate reference to parent + if parent, ok := h.headsByHash[newHead.ParentHash]; ok { + newHead.Parent.Store(parent) + } + for _, child := range h.headsByParent[newHead.Hash] { + // ensure all children have reference to newHead + child.Parent.Store(newHead) + if child.IsFinalized.Load() { + // mark newHead as finalized if any of its children is finalized + markFinalized(newHead) + } + } + } + + return nil +} + +func newPotentialCycleError(parent, child *evmtypes.Head) error { + return fmt.Errorf("expected head number to strictly decrease in 'child -> parent' relation: "+ + "child(%s), parent(%s)", child.String(), parent.String()) } diff --git a/core/chains/evm/headtracker/heads_test.go b/core/chains/evm/headtracker/heads_test.go index 6c02c528ba..92e4015d8c 100644 --- a/core/chains/evm/headtracker/heads_test.go +++ b/core/chains/evm/headtracker/heads_test.go @@ -20,21 +20,29 @@ func TestHeads_LatestHead(t *testing.T) { t.Parallel() heads := headtracker.NewHeads() - heads.AddHeads(testutils.Head(100), testutils.Head(200), testutils.Head(300)) + assert.NoError(t, heads.AddHeads(testutils.Head(100), testutils.Head(200), testutils.Head(300))) latest := heads.LatestHead() require.NotNil(t, latest) require.Equal(t, int64(300), latest.Number) - heads.AddHeads(testutils.Head(250)) + assert.NoError(t, heads.AddHeads(testutils.Head(250))) latest = heads.LatestHead() require.NotNil(t, latest) require.Equal(t, int64(300), latest.Number) - heads.AddHeads(testutils.Head(400)) + assert.NoError(t, heads.AddHeads(testutils.Head(400))) latest = heads.LatestHead() require.NotNil(t, latest) require.Equal(t, int64(400), latest.Number) + + // if heads have the same height, LatestHead prefers most recent + newerH400 := testutils.Head(400) + assert.NoError(t, heads.AddHeads(newerH400)) + latest = heads.LatestHead() + require.NotNil(t, latest) + require.Equal(t, int64(400), latest.Number) + require.Equal(t, newerH400.Hash, latest.Hash) } func TestHeads_HeadByHash(t *testing.T) { @@ -46,7 +54,7 @@ func TestHeads_HeadByHash(t *testing.T) { testutils.Head(300), } heads := headtracker.NewHeads() - heads.AddHeads(testHeads...) + assert.NoError(t, heads.AddHeads(testHeads...)) head := heads.HeadByHash(testHeads[1].Hash) require.NotNil(t, head) @@ -62,10 +70,10 @@ func TestHeads_Count(t *testing.T) { heads := headtracker.NewHeads() require.Zero(t, heads.Count()) - heads.AddHeads(testutils.Head(100), testutils.Head(200), testutils.Head(300)) + assert.NoError(t, heads.AddHeads(testutils.Head(100), testutils.Head(200), testutils.Head(300))) require.Equal(t, 3, heads.Count()) - heads.AddHeads(testutils.Head(400)) + assert.NoError(t, heads.AddHeads(testutils.Head(400))) require.Equal(t, 4, heads.Count()) } @@ -77,11 +85,11 @@ func TestHeads_AddHeads(t *testing.T) { var testHeads []*evmtypes.Head var parentHash common.Hash - for i := 0; i < 5; i++ { - hash := utils.NewHash() + for i := 1; i < 6; i++ { + hash := common.BigToHash(big.NewInt(int64(i))) h := evmtypes.NewHead(big.NewInt(int64(i)), hash, parentHash, uint64(time.Now().Unix()), ubig.NewI(0)) testHeads = append(testHeads, &h) - if i == 2 { + if i == 3 { // uncled block h := evmtypes.NewHead(big.NewInt(int64(i)), uncleHash, parentHash, uint64(time.Now().Unix()), ubig.NewI(0)) testHeads = append(testHeads, &h) @@ -89,10 +97,10 @@ func TestHeads_AddHeads(t *testing.T) { parentHash = hash } - heads.AddHeads(testHeads...) + assert.NoError(t, heads.AddHeads(testHeads...)) require.Equal(t, 6, heads.Count()) // Add duplicates (should be ignored) - heads.AddHeads(testHeads[2:5]...) + assert.NoError(t, heads.AddHeads(testHeads[2:5]...)) require.Equal(t, 6, heads.Count()) head := heads.LatestHead() @@ -102,6 +110,26 @@ func TestHeads_AddHeads(t *testing.T) { head = heads.HeadByHash(uncleHash) require.NotNil(t, head) require.Equal(t, 3, int(head.ChainLength())) + // returns an error, if newHead creates cycle + t.Run("Returns an error, if newHead create cycle", func(t *testing.T) { + cycleHead := &evmtypes.Head{ + Hash: heads.LatestHead().EarliestInChain().ParentHash, + ParentHash: heads.LatestHead().Hash, + } + // 1. try adding in front + cycleHead.Number = heads.LatestHead().Number + 1 + assert.EqualError(t, heads.AddHeads(cycleHead), "potential cycle detected while adding newHead as parent: expected head number to strictly decrease in 'child -> parent' relation: child(Head{Number: 1, Hash: 0x0000000000000000000000000000000000000000000000000000000000000001, ParentHash: 0x0000000000000000000000000000000000000000000000000000000000000000}), parent(Head{Number: 6, Hash: 0x0000000000000000000000000000000000000000000000000000000000000000, ParentHash: 0x0000000000000000000000000000000000000000000000000000000000000005})") + // 2. try adding to back + cycleHead.Number = heads.LatestHead().EarliestInChain().Number - 1 + assert.EqualError(t, heads.AddHeads(cycleHead), "potential cycle detected while adding newHead as child: expected head number to strictly decrease in 'child -> parent' relation: child(Head{Number: 0, Hash: 0x0000000000000000000000000000000000000000000000000000000000000000, ParentHash: 0x0000000000000000000000000000000000000000000000000000000000000005}), parent(Head{Number: 5, Hash: 0x0000000000000000000000000000000000000000000000000000000000000005, ParentHash: 0x0000000000000000000000000000000000000000000000000000000000000004})") + // 3. try adding to back with reference to self + cycleHead = &evmtypes.Head{ + Number: 1000, + Hash: common.BigToHash(big.NewInt(1000)), + ParentHash: common.BigToHash(big.NewInt(1000)), + } + assert.EqualError(t, heads.AddHeads(cycleHead), "cycle detected: newHeads reference itself newHead(Head{Number: 1000, Hash: 0x00000000000000000000000000000000000000000000000000000000000003e8, ParentHash: 0x00000000000000000000000000000000000000000000000000000000000003e8})") + }) } func TestHeads_MarkFinalized(t *testing.T) { @@ -110,7 +138,7 @@ func TestHeads_MarkFinalized(t *testing.T) { heads := headtracker.NewHeads() // create chain - // H0 <- H1 <- H2 <- H3 <- H4 <- H5 + // H0 <- H1 <- H2 <- H3 <- H4 <- H5 - Canonical // \ \ // H1Uncle H2Uncle // @@ -127,35 +155,80 @@ func TestHeads_MarkFinalized(t *testing.T) { h5 := newHead(5, h4.Hash) h2Uncle := newHead(2, h1.Hash) - allHeads := []*evmtypes.Head{h0, h1, h1Uncle, h2, h2Uncle, h3, h4, h5} - heads.AddHeads(allHeads...) + assert.NoError(t, heads.AddHeads(h0, h1, h1Uncle, h2, h2Uncle, h3, h4, h5)) // mark h3 and all ancestors as finalized require.True(t, heads.MarkFinalized(h3.Hash, h1.BlockNumber()), "expected MarkFinalized succeed") - // original heads remain unchanged - for _, h := range allHeads { - assert.False(t, h.IsFinalized, "expected original heads to remain unfinalized") - } - // h0 is too old. It should not be available directly or through its children assert.Nil(t, heads.HeadByHash(h0.Hash)) - assert.Nil(t, heads.HeadByHash(h1.Hash).Parent) - assert.Nil(t, heads.HeadByHash(h1Uncle.Hash).Parent) - assert.Nil(t, heads.HeadByHash(h2Uncle.Hash).Parent.Parent) + assert.Nil(t, heads.HeadByHash(h1.Hash).Parent.Load()) + assert.Nil(t, heads.HeadByHash(h1Uncle.Hash).Parent.Load()) + assert.Nil(t, heads.HeadByHash(h2Uncle.Hash).Parent.Load().Parent.Load()) require.False(t, heads.MarkFinalized(utils.NewHash(), 0), "expected false if finalized hash was not found in existing LatestHead chain") ensureProperFinalization := func(t *testing.T) { t.Helper() for _, head := range []*evmtypes.Head{h5, h4} { - require.False(t, heads.HeadByHash(head.Hash).IsFinalized, "expected h4-h5 not to be finalized", head.BlockNumber()) + require.False(t, heads.HeadByHash(head.Hash).IsFinalized.Load(), "expected h4-h5 not to be finalized", head.BlockNumber()) } for _, head := range []*evmtypes.Head{h3, h2, h1} { - require.True(t, heads.HeadByHash(head.Hash).IsFinalized, "expected h3 and all ancestors to be finalized", head.BlockNumber()) + require.True(t, heads.HeadByHash(head.Hash).IsFinalized.Load(), "expected h3 and all ancestors to be finalized", head.BlockNumber()) } - require.False(t, heads.HeadByHash(h2Uncle.Hash).IsFinalized, "expected uncle block not to be marked as finalized") + require.False(t, heads.HeadByHash(h2Uncle.Hash).IsFinalized.Load(), "expected uncle block not to be marked as finalized") } t.Run("blocks were correctly marked as finalized", ensureProperFinalization) - heads.AddHeads(h0, h1, h2, h2Uncle, h3, h4, h5) + assert.NoError(t, heads.AddHeads(h0, h1, h2, h2Uncle, h3, h4, h5)) t.Run("blocks remain finalized after re adding them to the Heads", ensureProperFinalization) + + // ensure that IsFinalized is propagated, when older blocks are added + // 1. remove all blocks older than 3 + heads.MarkFinalized(h3.Hash, 3) + // 2. ensure that h2 and h1 are no longer present + assert.Nil(t, heads.HeadByHash(h2.Hash)) + assert.Nil(t, heads.HeadByHash(h1.Hash)) + // 3. add blocks back, starting from older + assert.NoError(t, heads.AddHeads(h1)) + assert.False(t, heads.HeadByHash(h1.Hash).IsFinalized.Load(), "expected h1 to not be finalized as it was not explicitly marked and there no path to h3") + assert.NoError(t, heads.AddHeads(h2)) + // 4. now h2 and h1 must be marked as finalized + assert.True(t, heads.HeadByHash(h1.Hash).IsFinalized.Load()) + assert.True(t, heads.HeadByHash(h2.Hash).IsFinalized.Load()) +} + +func BenchmarkEarliestHeadInChain(b *testing.B) { + const latestBlockNum = 200_000 + blocks := NewBlocks(b, latestBlockNum+1) + b.ResetTimer() + for i := 0; i < b.N; i++ { + latest := blocks.Head(latestBlockNum) + earliest := latest.EarliestHeadInChain() + // perform sanity check + assert.NotEqual(b, latest.BlockNumber(), earliest.BlockNumber()) + assert.NotEqual(b, latest.BlockHash(), earliest.BlockHash()) + } +} + +// BenchmarkSimulated_Backfill - benchmarks AddHeads & MarkFinalized as if it was performed by HeadTracker's backfill +func BenchmarkHeads_SimulatedBackfill(b *testing.B) { + makeHash := func(n int64) common.Hash { + return common.BigToHash(big.NewInt(n)) + } + makeHead := func(n int64) *evmtypes.Head { + return &evmtypes.Head{Number: n, Hash: makeHash(n), ParentHash: makeHash(n - 1)} + } + + const finalityDepth = 16_000 // observed value on Arbitrum + // populate with initial values + heads := headtracker.NewHeads() + for i := int64(1); i <= finalityDepth; i++ { + assert.NoError(b, heads.AddHeads(makeHead(i))) + } + heads.MarkFinalized(makeHash(1), 1) + // focus benchmark on processing of a new latest block + b.ResetTimer() + for i := int64(1); i <= int64(b.N); i++ { + assert.NoError(b, heads.AddHeads(makeHead(finalityDepth+i))) + heads.MarkFinalized(makeHash(i), i) + } } diff --git a/core/chains/evm/headtracker/heap.go b/core/chains/evm/headtracker/heap.go new file mode 100644 index 0000000000..572ed541df --- /dev/null +++ b/core/chains/evm/headtracker/heap.go @@ -0,0 +1,35 @@ +package headtracker + +import evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + +type headsHeap struct { + values []*evmtypes.Head +} + +func (h *headsHeap) Len() int { + return len(h.values) +} + +func (h *headsHeap) Swap(i, j int) { + h.values[i], h.values[j] = h.values[j], h.values[i] +} + +func (h *headsHeap) Less(i, j int) bool { + return h.values[i].Number < h.values[j].Number +} + +func (h *headsHeap) Pop() any { + n := len(h.values) - 1 + old := h.values[n] + h.values[n] = nil + h.values = h.values[:n] + return old +} + +func (h *headsHeap) Push(v any) { + h.values = append(h.values, v.(*evmtypes.Head)) +} + +func (h *headsHeap) Peek() *evmtypes.Head { + return h.values[0] +} diff --git a/core/chains/evm/headtracker/simulated_head_tracker.go b/core/chains/evm/headtracker/simulated_head_tracker.go index e1e550de99..62bb4968c2 100644 --- a/core/chains/evm/headtracker/simulated_head_tracker.go +++ b/core/chains/evm/headtracker/simulated_head_tracker.go @@ -2,6 +2,7 @@ package headtracker import ( "context" + "errors" "fmt" "math/big" @@ -51,3 +52,31 @@ func (ht *simulatedHeadTracker) LatestAndFinalizedBlock(ctx context.Context) (*e return latest, finalizedBlock, nil } + +func (ht *simulatedHeadTracker) LatestChain() *evmtypes.Head { + return nil +} + +func (ht *simulatedHeadTracker) HealthReport() map[string]error { + return nil +} + +func (ht *simulatedHeadTracker) Start(_ context.Context) error { + return nil +} + +func (ht *simulatedHeadTracker) Close() error { + return nil +} + +func (ht *simulatedHeadTracker) Backfill(_ context.Context, _ *evmtypes.Head) error { + return errors.New("unimplemented") +} + +func (ht *simulatedHeadTracker) Name() string { + return "SimulatedHeadTracker" +} + +func (ht *simulatedHeadTracker) Ready() error { + return nil +} diff --git a/core/chains/evm/headtracker/types/types.go b/core/chains/evm/headtracker/types/types.go index 1a03f3cec6..ca5a79fc68 100644 --- a/core/chains/evm/headtracker/types/types.go +++ b/core/chains/evm/headtracker/types/types.go @@ -2,10 +2,13 @@ package types import ( "context" + "math/big" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/chainlink/v2/common/headtracker" + htrktypes "github.com/smartcontractkit/chainlink/v2/common/headtracker/types" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" ) @@ -22,4 +25,5 @@ type ( HeadTrackable = headtracker.HeadTrackable[*evmtypes.Head, common.Hash] HeadListener = headtracker.HeadListener[*evmtypes.Head, common.Hash] HeadBroadcaster = headtracker.HeadBroadcaster[*evmtypes.Head, common.Hash] + Client = htrktypes.Client[*evmtypes.Head, ethereum.Subscription, *big.Int, common.Hash] ) diff --git a/core/chains/evm/log/broadcaster.go b/core/chains/evm/log/broadcaster.go index e7f02d1199..3e37678bee 100644 --- a/core/chains/evm/log/broadcaster.go +++ b/core/chains/evm/log/broadcaster.go @@ -590,7 +590,7 @@ func (b *broadcaster) onNewHeads() { b.logger.Errorf("Failed to query for log broadcasts, %v", err) return } - b.registrations.sendLogs(ctx, logs, *latestHead, broadcasts, b.orm) + b.registrations.sendLogs(ctx, logs, latestHead, broadcasts, b.orm) if err := b.orm.SetPendingMinBlock(ctx, nil); err != nil { b.logger.Errorw("Failed to set pending broadcasts number null", "err", err) } @@ -605,7 +605,7 @@ func (b *broadcaster) onNewHeads() { return } - b.registrations.sendLogs(ctx, logs, *latestHead, broadcasts, b.orm) + b.registrations.sendLogs(ctx, logs, latestHead, broadcasts, b.orm) } newMin := b.logPool.deleteOlderLogs(keptDepth) if err := b.orm.SetPendingMinBlock(ctx, newMin); err != nil { diff --git a/core/chains/evm/log/registrations.go b/core/chains/evm/log/registrations.go index 68dd93b9d8..01104349a6 100644 --- a/core/chains/evm/log/registrations.go +++ b/core/chains/evm/log/registrations.go @@ -11,6 +11,7 @@ import ( pkgerrors "github.com/pkg/errors" "github.com/smartcontractkit/chainlink-common/pkg/logger" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated" @@ -215,7 +216,7 @@ func (r *registrations) isAddressRegistered(address common.Address) bool { return false } -func (r *registrations) sendLogs(ctx context.Context, logsToSend []logsOnBlock, latestHead evmtypes.Head, broadcasts []LogBroadcast, bc broadcastCreator) { +func (r *registrations) sendLogs(ctx context.Context, logsToSend []logsOnBlock, latestHead *evmtypes.Head, broadcasts []LogBroadcast, bc broadcastCreator) { broadcastsExisting := make(map[LogBroadcastAsKey]bool) for _, b := range broadcasts { broadcastsExisting[b.AsKey()] = b.Consumed @@ -387,7 +388,7 @@ type broadcastCreator interface { CreateBroadcast(ctx context.Context, blockHash common.Hash, blockNumber uint64, logIndex uint, jobID int32) error } -func (r *handler) sendLog(ctx context.Context, log types.Log, latestHead evmtypes.Head, +func (r *handler) sendLog(ctx context.Context, log types.Log, latestHead *evmtypes.Head, broadcasts map[LogBroadcastAsKey]bool, bc broadcastCreator, logger logger.Logger) { diff --git a/core/chains/evm/logpoller/disabled.go b/core/chains/evm/logpoller/disabled.go index c0882ff76c..a04b4fdb19 100644 --- a/core/chains/evm/logpoller/disabled.go +++ b/core/chains/evm/logpoller/disabled.go @@ -118,7 +118,7 @@ func (d disabled) LogsDataWordBetween(ctx context.Context, eventSig common.Hash, return nil, ErrDisabled } -func (d disabled) FilteredLogs(_ context.Context, _ query.KeyFilter, _ query.LimitAndSort, _ string) ([]Log, error) { +func (d disabled) FilteredLogs(_ context.Context, _ []query.Expression, _ query.LimitAndSort, _ string) ([]Log, error) { return nil, ErrDisabled } diff --git a/core/chains/evm/logpoller/log_poller.go b/core/chains/evm/logpoller/log_poller.go index dee5d1d1a5..dd7e0c5242 100644 --- a/core/chains/evm/logpoller/log_poller.go +++ b/core/chains/evm/logpoller/log_poller.go @@ -29,6 +29,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/utils/mathutil" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" ubig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" ) @@ -68,7 +69,7 @@ type LogPoller interface { LogsDataWordBetween(ctx context.Context, eventSig common.Hash, address common.Address, wordIndexMin, wordIndexMax int, wordValue common.Hash, confs evmtypes.Confirmations) ([]Log, error) // chainlink-common query filtering - FilteredLogs(ctx context.Context, filter query.KeyFilter, limitAndSort query.LimitAndSort, queryName string) ([]Log, error) + FilteredLogs(ctx context.Context, filter []query.Expression, limitAndSort query.LimitAndSort, queryName string) ([]Log, error) } type LogPollerTest interface { @@ -113,6 +114,7 @@ type logPoller struct { backfillBatchSize int64 // batch size to use when backfilling finalized logs rpcBatchSize int64 // batch size to use for fallback RPC calls made in GetBlocks logPrunePageSize int64 + clientErrors config.ClientErrors backupPollerNextBlock int64 // next block to be processed by Backup LogPoller backupPollerBlockDelay int64 // how far behind regular LogPoller should BackupLogPoller run. 0 = disabled @@ -143,6 +145,7 @@ type Opts struct { KeepFinalizedBlocksDepth int64 BackupPollerBlockDelay int64 LogPrunePageSize int64 + ClientErrors config.ClientErrors } // NewLogPoller creates a log poller. Note there is an assumption @@ -172,6 +175,7 @@ func NewLogPoller(orm ORM, ec Client, lggr logger.Logger, headTracker HeadTracke rpcBatchSize: opts.RpcBatchSize, keepFinalizedBlocksDepth: opts.KeepFinalizedBlocksDepth, logPrunePageSize: opts.LogPrunePageSize, + clientErrors: opts.ClientErrors, filters: make(map[string]Filter), filterDirty: true, // Always build Filter on first call to cache an empty filter if nothing registered yet. finalityViolated: new(atomic.Bool), @@ -594,18 +598,11 @@ func (lp *logPoller) run() { } // Otherwise this is the first poll _ever_ on a new chain. // Only safe thing to do is to start at the first finalized block. - latestBlock, latestFinalizedBlockNumber, err := lp.latestBlocks(ctx) + _, latestFinalizedBlockNumber, err := lp.latestBlocks(ctx) if err != nil { lp.lggr.Warnw("Unable to get latest for first poll", "err", err) continue } - // Do not support polling chains which don't even have finality depth worth of blocks. - // Could conceivably support this but not worth the effort. - // Need last finalized block number to be higher than 0 - if latestFinalizedBlockNumber <= 0 { - lp.lggr.Warnw("Insufficient number of blocks on chain, waiting for finality depth", "err", err, "latest", latestBlock.Number) - continue - } // Starting at the first finalized block. We do not backfill the first finalized block. start = latestFinalizedBlockNumber } else { @@ -794,8 +791,6 @@ func (lp *logPoller) blocksFromLogs(ctx context.Context, logs []types.Log, endBl return lp.GetBlocksRange(ctx, numbers) } -const jsonRpcLimitExceeded = -32005 // See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1474.md - // backfill will query FilterLogs in batches for logs in the // block range [start, end] and save them to the db. // Retries until ctx cancelled. Will return an error if cancelled @@ -807,13 +802,11 @@ func (lp *logPoller) backfill(ctx context.Context, start, end int64) error { gethLogs, err := lp.ec.FilterLogs(ctx, lp.Filter(big.NewInt(from), big.NewInt(to), nil)) if err != nil { - var rpcErr client.JsonError - if pkgerrors.As(err, &rpcErr) { - if rpcErr.Code != jsonRpcLimitExceeded { - lp.lggr.Errorw("Unable to query for logs", "err", err, "from", from, "to", to) - return err - } + if !client.IsTooManyResults(err, lp.clientErrors) { + lp.lggr.Errorw("Unable to query for logs", "err", err, "from", from, "to", to) + return err } + if batchSize == 1 { lp.lggr.Criticalw("Too many log results in a single block, failed to retrieve logs! Node may be running in a degraded state.", "err", err, "from", from, "to", to, "LogBackfillBatchSize", lp.backfillBatchSize) return err @@ -1023,8 +1016,16 @@ func (lp *logPoller) latestBlocks(ctx context.Context) (*evmtypes.Head, int64, e return nil, 0, fmt.Errorf("failed to get latest and latest finalized block from HeadTracker: %w", err) } - lp.lggr.Debugw("Latest blocks read from chain", "latest", latest.Number, "finalized", finalized.BlockNumber()) - return latest, finalized.BlockNumber(), nil + finalizedBN := finalized.BlockNumber() + // This is a dirty trick that allows LogPoller to function properly in tests where chain needs significant time to + // reach finality depth. An alternative to this one-liner is a database migration that drops restriction + // LogPollerBlock.FinalizedBlockNumber > 0 (which we actually want to keep to spot cases when FinalizedBlockNumber was simply not populated) + // and refactoring of queries that assume that restriction still holds. + if finalizedBN == 0 { + finalizedBN = 1 + } + lp.lggr.Debugw("Latest blocks read from chain", "latest", latest.Number, "finalized", finalizedBN) + return latest, finalizedBN, nil } // Find the first place where our chain and their chain have the same block, @@ -1036,7 +1037,7 @@ func (lp *logPoller) findBlockAfterLCA(ctx context.Context, current *evmtypes.He if err != nil { return nil, err } - blockAfterLCA := *current + blockAfterLCA := current // We expect reorgs up to the block after latestFinalizedBlock // We loop via parent instead of current so current always holds the LCA+1. // If the parent block number becomes < the first finalized block our reorg is too deep. @@ -1048,10 +1049,10 @@ func (lp *logPoller) findBlockAfterLCA(ctx context.Context, current *evmtypes.He } if parent.Hash == ourParentBlockHash.BlockHash { // If we do have the blockhash, return blockAfterLCA - return &blockAfterLCA, nil + return blockAfterLCA, nil } // Otherwise get a new parent and update blockAfterLCA. - blockAfterLCA = *parent + blockAfterLCA = parent parent, err = lp.ec.HeadByHash(ctx, parent.ParentHash) if err != nil { return nil, err @@ -1518,6 +1519,25 @@ func EvmWord(i uint64) common.Hash { return common.BytesToHash(b) } -func (lp *logPoller) FilteredLogs(ctx context.Context, queryFilter query.KeyFilter, limitAndSort query.LimitAndSort, queryName string) ([]Log, error) { +func (lp *logPoller) FilteredLogs(ctx context.Context, queryFilter []query.Expression, limitAndSort query.LimitAndSort, queryName string) ([]Log, error) { return lp.orm.FilteredLogs(ctx, queryFilter, limitAndSort, queryName) } + +// Where is a query.Where wrapper that ignores the Key and returns a slice of query.Expression rather than query.KeyFilter. +// If no expressions are provided, or an error occurs, an empty slice is returned. +func Where(expressions ...query.Expression) ([]query.Expression, error) { + filter, err := query.Where( + "", + expressions..., + ) + + if err != nil { + return []query.Expression{}, err + } + + if filter.Expressions == nil { + return []query.Expression{}, nil + } + + return filter.Expressions, nil +} diff --git a/core/chains/evm/logpoller/log_poller_internal_test.go b/core/chains/evm/logpoller/log_poller_internal_test.go index 448710b93f..620bbf14f4 100644 --- a/core/chains/evm/logpoller/log_poller_internal_test.go +++ b/core/chains/evm/logpoller/log_poller_internal_test.go @@ -7,6 +7,7 @@ import ( "math/big" "strings" "sync" + "sync/atomic" "testing" "time" @@ -287,12 +288,14 @@ func TestLogPoller_Replay(t *testing.T) { db := pgtest.NewSqlxDB(t) orm := NewORM(chainID, db, lggr) - head := evmtypes.Head{Number: 4} + var head atomic.Pointer[evmtypes.Head] + head.Store(&evmtypes.Head{Number: 4}) + events := []common.Hash{EmitterABI.Events["Log1"].ID} log1 := types.Log{ Index: 0, BlockHash: common.Hash{}, - BlockNumber: uint64(head.Number), + BlockNumber: uint64(head.Load().Number), Topics: events, Address: addr, TxHash: common.HexToHash("0x1234"), @@ -301,8 +304,7 @@ func TestLogPoller_Replay(t *testing.T) { ec := evmclimocks.NewClient(t) ec.On("HeadByNumber", mock.Anything, mock.Anything).Return(func(context.Context, *big.Int) (*evmtypes.Head, error) { - headCopy := head - return &headCopy, nil + return head.Load(), nil }) ec.On("FilterLogs", mock.Anything, mock.Anything).Return([]types.Log{log1}, nil).Once() ec.On("ConfiguredChainID").Return(chainID, nil) @@ -318,9 +320,9 @@ func TestLogPoller_Replay(t *testing.T) { headTracker := htMocks.NewHeadTracker[*evmtypes.Head, common.Hash](t) headTracker.On("LatestAndFinalizedBlock", mock.Anything).Return(func(ctx context.Context) (*evmtypes.Head, *evmtypes.Head, error) { - headCopy := head - finalized := &evmtypes.Head{Number: headCopy.Number - lpOpts.FinalityDepth} - return &headCopy, finalized, nil + h := head.Load() + finalized := &evmtypes.Head{Number: h.Number - lpOpts.FinalityDepth} + return h, finalized, nil }) lp := NewLogPoller(orm, ec, lggr, headTracker, lpOpts) @@ -394,7 +396,7 @@ func TestLogPoller_Replay(t *testing.T) { var wg sync.WaitGroup defer func() { wg.Wait() }() ec.On("FilterLogs", mock.Anything, mock.Anything).Once().Return([]types.Log{log1}, nil).Run(func(args mock.Arguments) { - head = evmtypes.Head{Number: 4} + head.Store(&evmtypes.Head{Number: 4}) wg.Add(1) go func() { defer wg.Done() @@ -421,7 +423,7 @@ func TestLogPoller_Replay(t *testing.T) { ec.On("FilterLogs", mock.Anything, mock.Anything).Return([]types.Log{log1}, nil).Maybe() // in case task gets delayed by >= 100ms - head = evmtypes.Head{Number: 5} + head.Store(&evmtypes.Head{Number: 5}) t.Cleanup(lp.reset) servicetest.Run(t, lp) @@ -448,7 +450,7 @@ func TestLogPoller_Replay(t *testing.T) { go func() { defer close(done) - head = evmtypes.Head{Number: 4} // Restore latest block to 4, so this matches the fromBlock requested + head.Store(&evmtypes.Head{Number: 4}) // Restore latest block to 4, so this matches the fromBlock requested select { case lp.replayStart <- 4: case <-ctx.Done(): @@ -469,7 +471,7 @@ func TestLogPoller_Replay(t *testing.T) { ec.On("FilterLogs", mock.Anything, mock.Anything).Return([]types.Log{log1}, nil) t.Cleanup(lp.reset) - head = evmtypes.Head{Number: 5} // Latest block must be > lastProcessed in order for SaveAndPollLogs() to call FilterLogs() + head.Store(&evmtypes.Head{Number: 5}) // Latest block must be > lastProcessed in order for SaveAndPollLogs() to call FilterLogs() servicetest.Run(t, lp) select { @@ -482,7 +484,8 @@ func TestLogPoller_Replay(t *testing.T) { // ReplayAsync should return as soon as replayStart is received t.Run("ReplayAsync success", func(t *testing.T) { t.Cleanup(lp.reset) - head = evmtypes.Head{Number: 5} + + head.Store(&evmtypes.Head{Number: 5}) ec.On("FilterLogs", mock.Anything, mock.Anything).Return([]types.Log{log1}, nil) mockBatchCallContext(t, ec) servicetest.Run(t, lp) @@ -496,7 +499,7 @@ func TestLogPoller_Replay(t *testing.T) { ctx := testutils.Context(t) t.Cleanup(lp.reset) servicetest.Run(t, lp) - head = evmtypes.Head{Number: 4} + head.Store(&evmtypes.Head{Number: 4}) anyErr := pkgerrors.New("async error") observedLogs.TakeAll() @@ -528,7 +531,8 @@ func TestLogPoller_Replay(t *testing.T) { err := lp.orm.DeleteLogsAndBlocksAfter(ctx, 0) require.NoError(t, err) - err = lp.orm.InsertBlock(ctx, head.Hash, head.Number, head.Timestamp, head.Number) + h := head.Load() + err = lp.orm.InsertBlock(ctx, h.Hash, h.Number, h.Timestamp, h.Number) require.NoError(t, err) ec.On("FilterLogs", mock.Anything, mock.Anything).Return([]types.Log{log1}, nil) @@ -565,7 +569,8 @@ func Test_latestBlockAndFinalityDepth(t *testing.T) { }) t.Run("headTracker returns valid chain", func(t *testing.T) { headTracker := htMocks.NewHeadTracker[*evmtypes.Head, common.Hash](t) - finalizedBlock := &evmtypes.Head{Number: 2, IsFinalized: true} + finalizedBlock := &evmtypes.Head{Number: 2} + finalizedBlock.IsFinalized.Store(true) head := &evmtypes.Head{Number: 10} headTracker.On("LatestAndFinalizedBlock", mock.Anything).Return(head, finalizedBlock, nil) diff --git a/core/chains/evm/logpoller/log_poller_test.go b/core/chains/evm/logpoller/log_poller_test.go index 548711c19b..1ab548063a 100644 --- a/core/chains/evm/logpoller/log_poller_test.go +++ b/core/chains/evm/logpoller/log_poller_test.go @@ -26,6 +26,8 @@ import ( "go.uber.org/zap/zapcore" "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/types/query" + "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" commonutils "github.com/smartcontractkit/chainlink-common/pkg/utils" htMocks "github.com/smartcontractkit/chainlink/v2/common/headtracker/mocks" @@ -1542,6 +1544,8 @@ type getLogErrData struct { } func TestTooManyLogResults(t *testing.T) { + t.Parallel() + ctx := testutils.Context(t) ec := evmtest.NewEthClientMockWithDefaultChain(t) lggr, obs := logger.TestObserved(t, zapcore.DebugLevel) @@ -1561,89 +1565,126 @@ func TestTooManyLogResults(t *testing.T) { lp := logpoller.NewLogPoller(o, ec, lggr, headTracker, lpOpts) expected := []int64{10, 5, 2, 1} - clientErr := client.JsonError{ + tooLargeErr := client.JsonError{ Code: -32005, Data: getLogErrData{"0x100E698", "0x100E6D4", 10000}, Message: "query returned more than 10000 results. Try with this block range [0x100E698, 0x100E6D4].", } - // Simulate currentBlock = 300 - head := &evmtypes.Head{Number: 300} - finalized := &evmtypes.Head{Number: head.Number - lpOpts.FinalityDepth} - headTracker.On("LatestAndFinalizedBlock", mock.Anything).Return(head, finalized, nil).Once() - call1 := ec.On("HeadByNumber", mock.Anything, mock.Anything).Return(func(ctx context.Context, blockNumber *big.Int) (*evmtypes.Head, error) { + var filterLogsCall *mock.Call + head := &evmtypes.Head{} + finalized := &evmtypes.Head{} + + ec.On("HeadByNumber", mock.Anything, mock.Anything).Return(func(ctx context.Context, blockNumber *big.Int) (*evmtypes.Head, error) { if blockNumber == nil { require.FailNow(t, "unexpected call to get current head") } return &evmtypes.Head{Number: blockNumber.Int64()}, nil }) - call2 := ec.On("FilterLogs", mock.Anything, mock.Anything).Return(func(ctx context.Context, fq ethereum.FilterQuery) (logs []types.Log, err error) { - if fq.BlockHash != nil { - return []types.Log{}, nil // succeed when single block requested - } - from := fq.FromBlock.Uint64() - to := fq.ToBlock.Uint64() - if to-from >= 4 { - return []types.Log{}, &clientErr // return "too many results" error if block range spans 4 or more blocks - } - return logs, err - }) + t.Run("halves size until small enough, then succeeds", func(t *testing.T) { + // Simulate currentBlock = 300 + head.Number = 300 + finalized.Number = head.Number - lpOpts.FinalityDepth + headTracker.On("LatestAndFinalizedBlock", mock.Anything).Return(head, finalized, nil).Once() - addr := testutils.NewAddress() - err := lp.RegisterFilter(ctx, logpoller.Filter{ - Name: "Integration test", - EventSigs: []common.Hash{EmitterABI.Events["Log1"].ID}, - Addresses: []common.Address{addr}, - }) - require.NoError(t, err) - lp.PollAndSaveLogs(ctx, 5) - block, err2 := o.SelectLatestBlock(ctx) - require.NoError(t, err2) - assert.Equal(t, int64(298), block.BlockNumber) - - logs := obs.FilterLevelExact(zapcore.WarnLevel).FilterMessageSnippet("halving block range batch size").FilterFieldKey("newBatchSize").All() - // Should have tried again 3 times--first reducing batch size to 10, then 5, then 2 - require.Len(t, logs, 3) - for i, s := range expected[:3] { - assert.Equal(t, s, logs[i].ContextMap()["newBatchSize"]) - } + filterLogsCall = ec.On("FilterLogs", mock.Anything, mock.Anything).Return(func(ctx context.Context, fq ethereum.FilterQuery) (logs []types.Log, err error) { + if fq.BlockHash != nil { + return []types.Log{}, nil // succeed when single block requested + } + from := fq.FromBlock.Uint64() + to := fq.ToBlock.Uint64() + if to-from >= 4 { + return []types.Log{}, tooLargeErr // return "too many results" error if block range spans 4 or more blocks + } + return logs, err + }) - obs.TakeAll() - call1.Unset() - call2.Unset() + addr := testutils.NewAddress() + err := lp.RegisterFilter(ctx, logpoller.Filter{ + Name: "Integration test", + EventSigs: []common.Hash{EmitterABI.Events["Log1"].ID}, + Addresses: []common.Address{addr}, + }) + require.NoError(t, err) + lp.PollAndSaveLogs(ctx, 5) + block, err2 := o.SelectLatestBlock(ctx) + require.NoError(t, err2) + assert.Equal(t, int64(298), block.BlockNumber) - // Now jump to block 500, but return error no matter how small the block range gets. - // Should exit the loop with a critical error instead of hanging. - head = &evmtypes.Head{Number: 500} - finalized = &evmtypes.Head{Number: head.Number - lpOpts.FinalityDepth} - headTracker.On("LatestAndFinalizedBlock", mock.Anything).Return(head, finalized, nil).Once() - call1.On("HeadByNumber", mock.Anything, mock.Anything).Return(func(ctx context.Context, blockNumber *big.Int) (*evmtypes.Head, error) { - if blockNumber == nil { - require.FailNow(t, "unexpected call to get current head") + logs := obs.FilterLevelExact(zapcore.WarnLevel).FilterMessageSnippet("halving block range batch size").FilterFieldKey("newBatchSize").All() + // Should have tried again 3 times--first reducing batch size to 10, then 5, then 2 + require.Len(t, logs, 3) + for i, s := range expected[:3] { + assert.Equal(t, s, logs[i].ContextMap()["newBatchSize"]) } - return &evmtypes.Head{Number: blockNumber.Int64()}, nil + filterLogsCall.Unset() }) - call2.On("FilterLogs", mock.Anything, mock.Anything).Return(func(ctx context.Context, fq ethereum.FilterQuery) (logs []types.Log, err error) { - if fq.BlockHash != nil { - return []types.Log{}, nil // succeed when single block requested + + t.Run("Halves size until single block, then reports critical error", func(t *testing.T) { + obs.TakeAll() + + // Now jump to block 500, but return error no matter how small the block range gets. + // Should exit the loop with a critical error instead of hanging. + head.Number = 500 + finalized.Number = head.Number - lpOpts.FinalityDepth + headTracker.On("LatestAndFinalizedBlock", mock.Anything).Return(head, finalized, nil).Once() + filterLogsCall = ec.On("FilterLogs", mock.Anything, mock.Anything).Return(func(ctx context.Context, fq ethereum.FilterQuery) (logs []types.Log, err error) { + if fq.BlockHash != nil { + return []types.Log{}, nil // succeed when single block requested + } + return []types.Log{}, tooLargeErr // return "too many results" error if block range spans 4 or more blocks + }) + + lp.PollAndSaveLogs(ctx, 298) + block, err := o.SelectLatestBlock(ctx) + if err != nil { + assert.ErrorContains(t, err, "no rows") // In case this subtest is run by itself + } else { + assert.Equal(t, int64(298), block.BlockNumber) + } + warns := obs.FilterMessageSnippet("halving block range").FilterLevelExact(zapcore.WarnLevel).All() + crit := obs.FilterMessageSnippet("failed to retrieve logs").FilterLevelExact(zapcore.DPanicLevel).All() + require.Len(t, warns, 4) + for i, s := range expected { + assert.Equal(t, s, warns[i].ContextMap()["newBatchSize"]) } - return []types.Log{}, &clientErr // return "too many results" error if block range spans 4 or more blocks + + require.Len(t, crit, 1) + assert.Contains(t, crit[0].Message, "Too many log results in a single block") + filterLogsCall.Unset() }) - lp.PollAndSaveLogs(ctx, 298) - block, err2 = o.SelectLatestBlock(ctx) - require.NoError(t, err2) - assert.Equal(t, int64(298), block.BlockNumber) - warns := obs.FilterMessageSnippet("halving block range").FilterLevelExact(zapcore.WarnLevel).All() - crit := obs.FilterMessageSnippet("failed to retrieve logs").FilterLevelExact(zapcore.DPanicLevel).All() - require.Len(t, warns, 4) - for i, s := range expected { - assert.Equal(t, s, warns[i].ContextMap()["newBatchSize"]) - } + t.Run("Unrelated error are retried without adjusting size", func(t *testing.T) { + unrelatedError := fmt.Errorf("Unrelated to the size of the request") + head.Number = 500 + finalized.Number = head.Number - lpOpts.FinalityDepth + + obs.TakeAll() + filterLogsCall = ec.On("FilterLogs", mock.Anything, mock.Anything).Return(func(ctx context.Context, fq ethereum.FilterQuery) (logs []types.Log, err error) { + if fq.BlockHash != nil { + return []types.Log{}, nil // succeed when single block requested + } + return []types.Log{}, unrelatedError // return an unrelated error that should just be retried with same size + }) + headTracker.On("LatestAndFinalizedBlock", mock.Anything).Return(head, finalized, nil).Once() - require.Len(t, crit, 1) - assert.Contains(t, crit[0].Message, "Too many log results in a single block") + lp.PollAndSaveLogs(ctx, 298) + block, err := o.SelectLatestBlock(ctx) + if err != nil { + assert.ErrorContains(t, err, "no rows") // In case this subtest is run by itself + } else { + assert.Equal(t, int64(298), block.BlockNumber) + } + crit := obs.FilterLevelExact(zapcore.DPanicLevel).All() + errors := obs.FilterLevelExact(zapcore.ErrorLevel).All() + warns := obs.FilterLevelExact(zapcore.WarnLevel).All() + assert.Len(t, crit, 0) + require.Len(t, errors, 1) + assert.Equal(t, errors[0].Message, "Unable to query for logs") + require.Len(t, warns, 1) + assert.Contains(t, warns[0].Message, "retrying later") + }) } func Test_PollAndQueryFinalizedBlocks(t *testing.T) { @@ -1733,7 +1774,7 @@ func Test_PollAndSavePersistsFinalityInBlocks(t *testing.T) { name: "setting last finalized block number to 0 if finality is too deep", useFinalityTag: false, finalityDepth: 20, - expectedFinalizedBlock: 0, + expectedFinalizedBlock: 1, }, { name: "using finality from chain", @@ -2052,3 +2093,39 @@ func TestFindLCA(t *testing.T) { }) } } + +func TestWhere(t *testing.T) { + address := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + eventSig := common.HexToHash("0xabcdef1234567890abcdef1234567890abcdef1234") + ts := time.Now() + + expr1 := logpoller.NewAddressFilter(address) + expr2 := logpoller.NewEventSigFilter(eventSig) + expr3 := query.Timestamp(uint64(ts.Unix()), primitives.Gte) + expr4 := logpoller.NewConfirmationsFilter(evmtypes.Confirmations(0)) + + t.Run("Valid combination of filters", func(t *testing.T) { + result, err := logpoller.Where(expr1, expr2, expr3, expr4) + assert.NoError(t, err) + assert.Equal(t, []query.Expression{expr1, expr2, expr3, expr4}, result) + }) + + t.Run("No expressions (should return empty slice)", func(t *testing.T) { + result, err := logpoller.Where() + assert.NoError(t, err) + assert.Equal(t, []query.Expression{}, result) + }) + + t.Run("Invalid boolean expression", func(t *testing.T) { + invalidExpr := query.Expression{ + BoolExpression: query.BoolExpression{ + Expressions: []query.Expression{}, + }, + } + + result, err := logpoller.Where(invalidExpr) + assert.Error(t, err) + assert.EqualError(t, err, "all boolean expressions should have at least 2 expressions") + assert.Equal(t, []query.Expression{}, result) + }) +} diff --git a/core/chains/evm/logpoller/mocks/log_poller.go b/core/chains/evm/logpoller/mocks/log_poller.go index 4ce68839d1..9ae4d9767c 100644 --- a/core/chains/evm/logpoller/mocks/log_poller.go +++ b/core/chains/evm/logpoller/mocks/log_poller.go @@ -124,7 +124,7 @@ func (_c *LogPoller_DeleteLogsAndBlocksAfter_Call) RunAndReturn(run func(context } // FilteredLogs provides a mock function with given fields: ctx, filter, limitAndSort, queryName -func (_m *LogPoller) FilteredLogs(ctx context.Context, filter query.KeyFilter, limitAndSort query.LimitAndSort, queryName string) ([]logpoller.Log, error) { +func (_m *LogPoller) FilteredLogs(ctx context.Context, filter []query.Expression, limitAndSort query.LimitAndSort, queryName string) ([]logpoller.Log, error) { ret := _m.Called(ctx, filter, limitAndSort, queryName) if len(ret) == 0 { @@ -133,10 +133,10 @@ func (_m *LogPoller) FilteredLogs(ctx context.Context, filter query.KeyFilter, l var r0 []logpoller.Log var r1 error - if rf, ok := ret.Get(0).(func(context.Context, query.KeyFilter, query.LimitAndSort, string) ([]logpoller.Log, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, []query.Expression, query.LimitAndSort, string) ([]logpoller.Log, error)); ok { return rf(ctx, filter, limitAndSort, queryName) } - if rf, ok := ret.Get(0).(func(context.Context, query.KeyFilter, query.LimitAndSort, string) []logpoller.Log); ok { + if rf, ok := ret.Get(0).(func(context.Context, []query.Expression, query.LimitAndSort, string) []logpoller.Log); ok { r0 = rf(ctx, filter, limitAndSort, queryName) } else { if ret.Get(0) != nil { @@ -144,7 +144,7 @@ func (_m *LogPoller) FilteredLogs(ctx context.Context, filter query.KeyFilter, l } } - if rf, ok := ret.Get(1).(func(context.Context, query.KeyFilter, query.LimitAndSort, string) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, []query.Expression, query.LimitAndSort, string) error); ok { r1 = rf(ctx, filter, limitAndSort, queryName) } else { r1 = ret.Error(1) @@ -160,16 +160,16 @@ type LogPoller_FilteredLogs_Call struct { // FilteredLogs is a helper method to define mock.On call // - ctx context.Context -// - filter query.KeyFilter +// - filter []query.Expression // - limitAndSort query.LimitAndSort // - queryName string func (_e *LogPoller_Expecter) FilteredLogs(ctx interface{}, filter interface{}, limitAndSort interface{}, queryName interface{}) *LogPoller_FilteredLogs_Call { return &LogPoller_FilteredLogs_Call{Call: _e.mock.On("FilteredLogs", ctx, filter, limitAndSort, queryName)} } -func (_c *LogPoller_FilteredLogs_Call) Run(run func(ctx context.Context, filter query.KeyFilter, limitAndSort query.LimitAndSort, queryName string)) *LogPoller_FilteredLogs_Call { +func (_c *LogPoller_FilteredLogs_Call) Run(run func(ctx context.Context, filter []query.Expression, limitAndSort query.LimitAndSort, queryName string)) *LogPoller_FilteredLogs_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(query.KeyFilter), args[2].(query.LimitAndSort), args[3].(string)) + run(args[0].(context.Context), args[1].([]query.Expression), args[2].(query.LimitAndSort), args[3].(string)) }) return _c } @@ -179,7 +179,7 @@ func (_c *LogPoller_FilteredLogs_Call) Return(_a0 []logpoller.Log, _a1 error) *L return _c } -func (_c *LogPoller_FilteredLogs_Call) RunAndReturn(run func(context.Context, query.KeyFilter, query.LimitAndSort, string) ([]logpoller.Log, error)) *LogPoller_FilteredLogs_Call { +func (_c *LogPoller_FilteredLogs_Call) RunAndReturn(run func(context.Context, []query.Expression, query.LimitAndSort, string) ([]logpoller.Log, error)) *LogPoller_FilteredLogs_Call { _c.Call.Return(run) return _c } diff --git a/core/chains/evm/logpoller/observability.go b/core/chains/evm/logpoller/observability.go index 782307e7d0..e0ed0cc478 100644 --- a/core/chains/evm/logpoller/observability.go +++ b/core/chains/evm/logpoller/observability.go @@ -262,7 +262,7 @@ func (o *ObservedORM) SelectIndexedLogsTopicRange(ctx context.Context, address c }) } -func (o *ObservedORM) FilteredLogs(ctx context.Context, filter query.KeyFilter, limitAndSort query.LimitAndSort, queryName string) ([]Log, error) { +func (o *ObservedORM) FilteredLogs(ctx context.Context, filter []query.Expression, limitAndSort query.LimitAndSort, queryName string) ([]Log, error) { return withObservedQueryAndResults(o, queryName, func() ([]Log, error) { return o.ORM.FilteredLogs(ctx, filter, limitAndSort, queryName) }) diff --git a/core/chains/evm/logpoller/observability_test.go b/core/chains/evm/logpoller/observability_test.go index 5e668a4ad1..2f502438bb 100644 --- a/core/chains/evm/logpoller/observability_test.go +++ b/core/chains/evm/logpoller/observability_test.go @@ -119,6 +119,16 @@ func TestCountersAreProperlyPopulatedForWrites(t *testing.T) { assert.Equal(t, float64(20), testutil.ToFloat64(orm.logsInserted.WithLabelValues("420"))) assert.Equal(t, float64(2), testutil.ToFloat64(orm.blocksInserted.WithLabelValues("420"))) + rowsAffected, err := orm.DeleteExpiredLogs(ctx, 3) + require.NoError(t, err) + require.Equal(t, int64(3), rowsAffected) + assert.Equal(t, 3, counterFromGaugeByLabels(orm.datasetSize, "420", "DeleteExpiredLogs", "delete")) + + rowsAffected, err = orm.DeleteBlocksBefore(ctx, 30, 0) + require.NoError(t, err) + require.Equal(t, int64(2), rowsAffected) + assert.Equal(t, 2, counterFromGaugeByLabels(orm.datasetSize, "420", "DeleteBlocksBefore", "delete")) + // Don't update counters in case of an error require.Error(t, orm.InsertLogsWithBlock(ctx, logs, NewLogPollerBlock(utils.RandomBytes32(), 0, time.Now(), 0))) assert.Equal(t, float64(20), testutil.ToFloat64(orm.logsInserted.WithLabelValues("420"))) diff --git a/core/chains/evm/logpoller/orm.go b/core/chains/evm/logpoller/orm.go index 9cbb21a606..0b5a8f4bd4 100644 --- a/core/chains/evm/logpoller/orm.go +++ b/core/chains/evm/logpoller/orm.go @@ -15,6 +15,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/types/query" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" @@ -63,7 +64,7 @@ type ORM interface { SelectLogsDataWordBetween(ctx context.Context, address common.Address, eventSig common.Hash, wordIndexMin int, wordIndexMax int, wordValue common.Hash, confs evmtypes.Confirmations) ([]Log, error) // FilteredLogs accepts chainlink-common filtering DSL. - FilteredLogs(ctx context.Context, filter query.KeyFilter, limitAndSort query.LimitAndSort, queryName string) ([]Log, error) + FilteredLogs(ctx context.Context, filter []query.Expression, limitAndSort query.LimitAndSort, queryName string) ([]Log, error) } type DSORM struct { @@ -313,34 +314,30 @@ type Exp struct { ShouldDelete bool } +// DeleteExpiredLogs removes any logs which either: +// - don't match any currently registered filters, or +// - have a timestamp older than any matching filter's retention, UNLESS there is at +// least one matching filter with retention=0 func (o *DSORM) DeleteExpiredLogs(ctx context.Context, limit int64) (int64, error) { var err error var result sql.Result - if limit > 0 { - result, err = o.ds.ExecContext(ctx, ` - DELETE FROM evm.logs + query := `DELETE FROM evm.logs WHERE (evm_chain_id, address, event_sig, block_number) IN ( SELECT l.evm_chain_id, l.address, l.event_sig, l.block_number FROM evm.logs l - INNER JOIN ( - SELECT address, event, MAX(retention) AS retention + LEFT JOIN ( + SELECT address, event, CASE WHEN MIN(retention) = 0 THEN 0 ELSE MAX(retention) END AS retention FROM evm.log_poller_filters WHERE evm_chain_id = $1 GROUP BY evm_chain_id, address, event - HAVING NOT 0 = ANY(ARRAY_AGG(retention)) - ) r ON l.evm_chain_id = $1 AND l.address = r.address AND l.event_sig = r.event - AND l.block_timestamp <= STATEMENT_TIMESTAMP() - (r.retention / 10^9 * interval '1 second') - LIMIT $2 - )`, ubig.New(o.chainID), limit) + ) r ON l.address = r.address AND l.event_sig = r.event + WHERE l.evm_chain_id = $1 AND -- Must be WHERE rather than ON due to LEFT JOIN + r.retention IS NULL OR (r.retention != 0 AND l.block_timestamp <= STATEMENT_TIMESTAMP() - (r.retention / 10^9 * interval '1 second')) %s)` + + if limit > 0 { + result, err = o.ds.ExecContext(ctx, fmt.Sprintf(query, "LIMIT $2"), ubig.New(o.chainID), limit) } else { - result, err = o.ds.ExecContext(ctx, `WITH r AS - ( SELECT address, event, MAX(retention) AS retention - FROM evm.log_poller_filters WHERE evm_chain_id=$1 - GROUP BY evm_chain_id,address, event HAVING NOT 0 = ANY(ARRAY_AGG(retention)) - ) DELETE FROM evm.logs l USING r - WHERE l.evm_chain_id = $1 AND l.address=r.address AND l.event_sig=r.event - AND l.block_timestamp <= STATEMENT_TIMESTAMP() - (r.retention / 10^9 * interval '1 second')`, // retention is in nanoseconds (time.Duration aka BIGINT) - ubig.New(o.chainID)) + result, err = o.ds.ExecContext(ctx, fmt.Sprintf(query, ""), ubig.New(o.chainID)) } if err != nil { @@ -393,7 +390,7 @@ func (o *DSORM) insertLogsWithinTx(ctx context.Context, logs []Log, tx sqlutil.D (:evm_chain_id, :log_index, :block_hash, :block_number, :block_timestamp, :address, :event_sig, :topics, :tx_hash, :data, NOW()) ON CONFLICT DO NOTHING` - _, err := o.ds.NamedExecContext(ctx, query, logs[start:end]) + _, err := tx.NamedExecContext(ctx, query, logs[start:end]) if err != nil { if pkgerrors.Is(err, context.DeadlineExceeded) && batchInsertSize > 500 { // In case of DB timeouts, try to insert again with a smaller batch upto a limit @@ -968,9 +965,8 @@ func (o *DSORM) SelectIndexedLogsWithSigsExcluding(ctx context.Context, sigA, si return logs, nil } -// TODO flaky BCF-3258 -func (o *DSORM) FilteredLogs(ctx context.Context, filter query.KeyFilter, limitAndSort query.LimitAndSort, _ string) ([]Log, error) { - qs, args, err := (&pgDSLParser{}).buildQuery(o.chainID, filter.Expressions, limitAndSort) +func (o *DSORM) FilteredLogs(ctx context.Context, filter []query.Expression, limitAndSort query.LimitAndSort, _ string) ([]Log, error) { + qs, args, err := (&pgDSLParser{}).buildQuery(o.chainID, filter, limitAndSort) if err != nil { return nil, err } diff --git a/core/chains/evm/logpoller/orm_test.go b/core/chains/evm/logpoller/orm_test.go index ed3f58504a..ba66e166eb 100644 --- a/core/chains/evm/logpoller/orm_test.go +++ b/core/chains/evm/logpoller/orm_test.go @@ -7,6 +7,7 @@ import ( "fmt" "math" "math/big" + "strconv" "testing" "time" @@ -186,6 +187,7 @@ func TestORM_GetBlocks_From_Range_Recent_Blocks(t *testing.T) { } func TestORM(t *testing.T) { + t.Parallel() th := SetupTH(t, lpOpts) o1 := th.ORM o2 := th.ORM2 @@ -333,6 +335,36 @@ func TestORM(t *testing.T) { }, })) + // Insert a couple logs on a different chain, to make sure + // these aren't affected by any operations on the chain LogPoller + // is managing. + require.NoError(t, o2.InsertLogs(ctx, []logpoller.Log{ + { + EvmChainId: ubig.New(th.ChainID2), + LogIndex: 8, + BlockHash: common.HexToHash("0x1238"), + BlockNumber: int64(17), + EventSig: topic2, + Topics: [][]byte{topic2[:]}, + Address: common.HexToAddress("0x1236"), + TxHash: common.HexToHash("0x1888"), + Data: []byte("same log on unrelated chain"), + BlockTimestamp: time.Now(), + }, + { + EvmChainId: ubig.New(th.ChainID2), + LogIndex: 9, + BlockHash: common.HexToHash("0x1999"), + BlockNumber: int64(18), + EventSig: topic, + Topics: [][]byte{topic[:], topic2[:]}, + Address: common.HexToAddress("0x5555"), + TxHash: common.HexToHash("0x1543"), + Data: []byte("different log on unrelated chain"), + BlockTimestamp: time.Now(), + }, + })) + t.Log(latest.BlockNumber) logs, err := o1.SelectLogsByBlockRange(ctx, 1, 17) require.NoError(t, err) @@ -453,24 +485,38 @@ func TestORM(t *testing.T) { require.NoError(t, err) require.Len(t, logs, 8) - // Delete expired logs + // Delete expired logs with page limit time.Sleep(2 * time.Millisecond) // just in case we haven't reached the end of the 1ms retention period - deleted, err := o1.DeleteExpiredLogs(ctx, 0) + deleted, err := o1.DeleteExpiredLogs(ctx, 2) require.NoError(t, err) - assert.Equal(t, int64(1), deleted) + assert.Equal(t, int64(2), deleted) + + // Delete expired logs without page limit + deleted, err = o1.DeleteExpiredLogs(ctx, 0) + require.NoError(t, err) + assert.Equal(t, int64(2), deleted) + + // Ensure that both of the logs from the second chain are still there + logs, err = o2.SelectLogs(ctx, 0, 100, common.HexToAddress("0x1236"), topic2) + require.NoError(t, err) + assert.Len(t, logs, 1) + logs, err = o2.SelectLogs(ctx, 0, 100, common.HexToAddress("0x5555"), topic) + require.NoError(t, err) + assert.Len(t, logs, 1) + logs, err = o1.SelectLogsByBlockRange(ctx, 1, latest.BlockNumber) require.NoError(t, err) - // The only log which should be deleted is the one which matches filter1 (ret=1ms) but not filter12 (ret=1 hour) - // Importantly, it shouldn't delete any logs matching only filter0 (ret=0 meaning permanent retention). Anything - // matching filter12 should be kept regardless of what other filters it matches. - assert.Len(t, logs, 7) + // It should have retained the log matching filter0 (due to ret=0 meaning permanent retention) as well as all + // 3 logs matching filter12 (ret=1 hour). It should have deleted 3 logs not matching any filter, as well as 1 + // of the 2 logs matching filter1 (ret=1ms)--the one that doesn't also match filter12. + assert.Len(t, logs, 4) // Delete logs after should delete all logs. err = o1.DeleteLogsAndBlocksAfter(ctx, 1) require.NoError(t, err) logs, err = o1.SelectLogsByBlockRange(ctx, 1, latest.BlockNumber) require.NoError(t, err) - require.Zero(t, len(logs)) + assert.Zero(t, len(logs)) } type PgxLogger struct { @@ -604,8 +650,8 @@ func TestORM_IndexedLogs(t *testing.T) { } for idx, value := range topicValues { - topicFilters.Expressions[idx] = logpoller.NewEventByTopicFilter(topicIdx, []primitives.ValueComparator{ - {Value: logpoller.EvmWord(value).Hex(), Operator: primitives.Eq}, + topicFilters.Expressions[idx] = logpoller.NewEventByTopicFilter(topicIdx, []logpoller.HashedValueComparator{ + {Value: logpoller.EvmWord(value), Operator: primitives.Eq}, }) } @@ -629,7 +675,7 @@ func TestORM_IndexedLogs(t *testing.T) { require.Equal(t, 1, len(lgs)) assert.Equal(t, logpoller.EvmWord(1).Bytes(), lgs[0].GetTopics()[1].Bytes()) - lgs, err = o1.FilteredLogs(ctx, standardFilter(1, []uint64{1}), limiter, "") + lgs, err = o1.FilteredLogs(ctx, standardFilter(1, []uint64{1}).Expressions, limiter, "") require.NoError(t, err) require.Equal(t, 1, len(lgs)) assert.Equal(t, logpoller.EvmWord(1).Bytes(), lgs[0].GetTopics()[1].Bytes()) @@ -638,19 +684,17 @@ func TestORM_IndexedLogs(t *testing.T) { require.NoError(t, err) assert.Equal(t, 2, len(lgs)) - lgs, err = o1.FilteredLogs(ctx, standardFilter(1, []uint64{1, 2}), limiter, "") + lgs, err = o1.FilteredLogs(ctx, standardFilter(1, []uint64{1, 2}).Expressions, limiter, "") require.NoError(t, err) assert.Equal(t, 2, len(lgs)) - blockRangeFilter := func(start, end uint64, topicIdx uint64, topicValues []uint64) query.KeyFilter { - return query.KeyFilter{ - Expressions: []query.Expression{ - logpoller.NewAddressFilter(addr), - logpoller.NewEventSigFilter(eventSig), - filtersForTopics(topicIdx, topicValues), - query.Block(start, primitives.Gte), - query.Block(end, primitives.Lte), - }, + blockRangeFilter := func(start, end string, topicIdx uint64, topicValues []uint64) []query.Expression { + return []query.Expression{ + logpoller.NewAddressFilter(addr), + logpoller.NewEventSigFilter(eventSig), + filtersForTopics(topicIdx, topicValues), + query.Block(start, primitives.Gte), + query.Block(end, primitives.Lte), } } @@ -658,7 +702,7 @@ func TestORM_IndexedLogs(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, len(lgs)) - lgs, err = o1.FilteredLogs(ctx, blockRangeFilter(1, 1, 1, []uint64{1}), limiter, "") + lgs, err = o1.FilteredLogs(ctx, blockRangeFilter("1", "1", 1, []uint64{1}), limiter, "") require.NoError(t, err) assert.Equal(t, 1, len(lgs)) @@ -666,7 +710,7 @@ func TestORM_IndexedLogs(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, len(lgs)) - lgs, err = o1.FilteredLogs(ctx, blockRangeFilter(1, 2, 1, []uint64{2}), limiter, "") + lgs, err = o1.FilteredLogs(ctx, blockRangeFilter("1", "2", 1, []uint64{2}), limiter, "") require.NoError(t, err) assert.Equal(t, 1, len(lgs)) @@ -674,7 +718,7 @@ func TestORM_IndexedLogs(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, len(lgs)) - lgs, err = o1.FilteredLogs(ctx, blockRangeFilter(1, 2, 1, []uint64{1}), limiter, "") + lgs, err = o1.FilteredLogs(ctx, blockRangeFilter("1", "2", 1, []uint64{1}), limiter, "") require.NoError(t, err) assert.Equal(t, 1, len(lgs)) @@ -682,7 +726,7 @@ func TestORM_IndexedLogs(t *testing.T) { require.Error(t, err) assert.Contains(t, err.Error(), "invalid index for topic: 0") - _, err = o1.FilteredLogs(ctx, blockRangeFilter(1, 2, 0, []uint64{1}), limiter, "") + _, err = o1.FilteredLogs(ctx, blockRangeFilter("1", "2", 0, []uint64{1}), limiter, "") require.Error(t, err) assert.Contains(t, err.Error(), "invalid index for topic: 0") @@ -690,7 +734,7 @@ func TestORM_IndexedLogs(t *testing.T) { require.Error(t, err) assert.Contains(t, err.Error(), "invalid index for topic: 4") - _, err = o1.FilteredLogs(ctx, blockRangeFilter(1, 2, 4, []uint64{1}), limiter, "") + _, err = o1.FilteredLogs(ctx, blockRangeFilter("1", "2", 4, []uint64{1}), limiter, "") require.Error(t, err) assert.Contains(t, err.Error(), "invalid index for topic: 4") @@ -702,30 +746,28 @@ func TestORM_IndexedLogs(t *testing.T) { Expressions: []query.Expression{ logpoller.NewAddressFilter(addr), logpoller.NewEventSigFilter(eventSig), - logpoller.NewEventByTopicFilter(1, []primitives.ValueComparator{ - {Value: logpoller.EvmWord(2).Hex(), Operator: primitives.Gte}, + logpoller.NewEventByTopicFilter(1, []logpoller.HashedValueComparator{ + {Value: logpoller.EvmWord(2), Operator: primitives.Gte}, }), query.Confidence(primitives.Unconfirmed), }, } - lgs, err = o1.FilteredLogs(ctx, filter, limiter, "") + lgs, err = o1.FilteredLogs(ctx, filter.Expressions, limiter, "") require.NoError(t, err) assert.Equal(t, 2, len(lgs)) - rangeFilter := func(topicIdx uint64, min, max uint64) query.KeyFilter { - return query.KeyFilter{ - Expressions: []query.Expression{ - logpoller.NewAddressFilter(addr), - logpoller.NewEventSigFilter(eventSig), - logpoller.NewEventByTopicFilter(topicIdx, []primitives.ValueComparator{ - {Value: logpoller.EvmWord(min).Hex(), Operator: primitives.Gte}, - }), - logpoller.NewEventByTopicFilter(topicIdx, []primitives.ValueComparator{ - {Value: logpoller.EvmWord(max).Hex(), Operator: primitives.Lte}, - }), - query.Confidence(primitives.Unconfirmed), - }, + rangeFilter := func(topicIdx uint64, min, max uint64) []query.Expression { + return []query.Expression{ + logpoller.NewAddressFilter(addr), + logpoller.NewEventSigFilter(eventSig), + logpoller.NewEventByTopicFilter(topicIdx, []logpoller.HashedValueComparator{ + {Value: logpoller.EvmWord(min), Operator: primitives.Gte}, + }), + logpoller.NewEventByTopicFilter(topicIdx, []logpoller.HashedValueComparator{ + {Value: logpoller.EvmWord(max), Operator: primitives.Lte}, + }), + query.Confidence(primitives.Unconfirmed), } } @@ -833,7 +875,7 @@ func TestORM_SelectIndexedLogsByTxHash(t *testing.T) { }, } - retrievedLogs, err = o1.FilteredLogs(ctx, filter, limiter, "") + retrievedLogs, err = o1.FilteredLogs(ctx, filter.Expressions, limiter, "") require.NoError(t, err) require.Equal(t, 2, len(retrievedLogs)) @@ -874,19 +916,17 @@ func TestORM_DataWords(t *testing.T) { }, })) - wordFilter := func(wordIdx uint8, word1, word2 uint64) query.KeyFilter { - return query.KeyFilter{ - Expressions: []query.Expression{ - logpoller.NewAddressFilter(addr), - logpoller.NewEventSigFilter(eventSig), - logpoller.NewEventByWordFilter(eventSig, wordIdx, []primitives.ValueComparator{ - {Value: logpoller.EvmWord(word1).Hex(), Operator: primitives.Gte}, - }), - logpoller.NewEventByWordFilter(eventSig, wordIdx, []primitives.ValueComparator{ - {Value: logpoller.EvmWord(word2).Hex(), Operator: primitives.Lte}, - }), - query.Confidence(primitives.Unconfirmed), - }, + wordFilter := func(wordIdx uint8, word1, word2 uint64) []query.Expression { + return []query.Expression{ + logpoller.NewAddressFilter(addr), + logpoller.NewEventSigFilter(eventSig), + logpoller.NewEventByWordFilter(wordIdx, []logpoller.HashedValueComparator{ + {Value: logpoller.EvmWord(word1), Operator: primitives.Gte}, + }), + logpoller.NewEventByWordFilter(wordIdx, []logpoller.HashedValueComparator{ + {Value: logpoller.EvmWord(word2), Operator: primitives.Lte}, + }), + query.Confidence(primitives.Unconfirmed), } } @@ -945,15 +985,13 @@ func TestORM_DataWords(t *testing.T) { require.NoError(t, err) assert.Equal(t, 2, len(lgs)) - filter := query.KeyFilter{ - Expressions: []query.Expression{ - logpoller.NewAddressFilter(addr), - logpoller.NewEventSigFilter(eventSig), - logpoller.NewEventByWordFilter(eventSig, 0, []primitives.ValueComparator{ - {Value: logpoller.EvmWord(1).Hex(), Operator: primitives.Gte}, - }), - query.Confidence(primitives.Unconfirmed), - }, + filter := []query.Expression{ + logpoller.NewAddressFilter(addr), + logpoller.NewEventSigFilter(eventSig), + logpoller.NewEventByWordFilter(0, []logpoller.HashedValueComparator{ + {Value: logpoller.EvmWord(1), Operator: primitives.Gte}, + }), + query.Confidence(primitives.Unconfirmed), } lgs, err = o1.FilteredLogs(ctx, filter, limiter, "") @@ -1042,7 +1080,7 @@ func TestORM_SelectLogsWithSigsByBlockRangeFilter(t *testing.T) { } require.NoError(t, o1.InsertLogs(ctx, inputLogs)) - filter := func(sigs []common.Hash, startBlock, endBlock int64) query.KeyFilter { + filter := func(sigs []common.Hash, startBlock, endBlock string) query.KeyFilter { filters := []query.Expression{ logpoller.NewAddressFilter(sourceAddr), } @@ -1064,8 +1102,8 @@ func TestORM_SelectLogsWithSigsByBlockRangeFilter(t *testing.T) { filters = append(filters, query.Expression{ BoolExpression: query.BoolExpression{ Expressions: []query.Expression{ - query.Block(uint64(startBlock), primitives.Gte), - query.Block(uint64(endBlock), primitives.Lte), + query.Block(startBlock, primitives.Gte), + query.Block(endBlock, primitives.Lte), }, BoolOperator: query.AND, }, @@ -1097,8 +1135,7 @@ func TestORM_SelectLogsWithSigsByBlockRangeFilter(t *testing.T) { }) assertion(t, logs, err, startBlock, endBlock) - - logs, err = th.ORM.FilteredLogs(ctx, filter([]common.Hash{topic, topic2}, startBlock, endBlock), limiter, "") + logs, err = th.ORM.FilteredLogs(ctx, filter([]common.Hash{topic, topic2}, strconv.Itoa(int(startBlock)), strconv.Itoa(int(endBlock))).Expressions, limiter, "") assertion(t, logs, err, startBlock, endBlock) } @@ -1160,14 +1197,12 @@ func TestLogPoller_Logs(t *testing.T) { assert.Equal(t, "0x0000000000000000000000000000000000000000000000000000000000000005", lgs[4].BlockHash.String()) assert.Equal(t, "0x0000000000000000000000000000000000000000000000000000000000000005", lgs[5].BlockHash.String()) - logFilter := func(start, end uint64, address common.Address) query.KeyFilter { - return query.KeyFilter{ - Expressions: []query.Expression{ - logpoller.NewAddressFilter(address), - logpoller.NewEventSigFilter(event1), - query.Block(start, primitives.Gte), - query.Block(end, primitives.Lte), - }, + logFilter := func(start, end string, address common.Address) []query.Expression { + return []query.Expression{ + logpoller.NewAddressFilter(address), + logpoller.NewEventSigFilter(event1), + query.Block(start, primitives.Gte), + query.Block(end, primitives.Lte), } } @@ -1181,7 +1216,7 @@ func TestLogPoller_Logs(t *testing.T) { assert.Equal(t, "0x0000000000000000000000000000000000000000000000000000000000000005", lgs[1].BlockHash.String()) assert.Equal(t, address1, lgs[1].Address) - lgs, err = th.ORM.FilteredLogs(ctx, logFilter(1, 3, address1), query.LimitAndSort{ + lgs, err = th.ORM.FilteredLogs(ctx, logFilter("1", "3", address1), query.LimitAndSort{ SortBy: []query.SortBy{query.NewSortBySequence(query.Asc)}, }, "") require.NoError(t, err) @@ -1201,7 +1236,7 @@ func TestLogPoller_Logs(t *testing.T) { assert.Equal(t, address2, lgs[0].Address) assert.Equal(t, event1.Bytes(), lgs[0].Topics[0]) - lgs, err = th.ORM.FilteredLogs(ctx, logFilter(2, 2, address2), query.LimitAndSort{ + lgs, err = th.ORM.FilteredLogs(ctx, logFilter("2", "2", address2), query.LimitAndSort{ SortBy: []query.SortBy{query.NewSortBySequence(query.Asc)}, }, "") require.NoError(t, err) @@ -1676,8 +1711,8 @@ func TestSelectLogsCreatedAfter(t *testing.T) { if len(topicVals) > 0 { exp := make([]query.Expression, len(topicVals)) for idx, val := range topicVals { - exp[idx] = logpoller.NewEventByTopicFilter(uint64(topicIdx), []primitives.ValueComparator{ - {Value: val.String(), Operator: primitives.Eq}, + exp[idx] = logpoller.NewEventByTopicFilter(uint64(topicIdx), []logpoller.HashedValueComparator{ + {Value: val, Operator: primitives.Eq}, }) } @@ -1721,7 +1756,7 @@ func TestSelectLogsCreatedAfter(t *testing.T) { assertion(t, logs, err, tt.expectedLogs) - logs, err = th.ORM.FilteredLogs(ctx, filter(tt.after, tt.confs, 0, nil), limiter, "") + logs, err = th.ORM.FilteredLogs(ctx, filter(tt.after, tt.confs, 0, nil).Expressions, limiter, "") assertion(t, logs, err, tt.expectedLogs) }) @@ -1734,7 +1769,7 @@ func TestSelectLogsCreatedAfter(t *testing.T) { assertion(t, logs, err, tt.expectedLogs) - logs, err = th.ORM.FilteredLogs(ctx, filter(tt.after, tt.confs, 1, []common.Hash{event}), limiter, "") + logs, err = th.ORM.FilteredLogs(ctx, filter(tt.after, tt.confs, 1, []common.Hash{event}).Expressions, limiter, "") assertion(t, logs, err, tt.expectedLogs) }) @@ -1964,11 +1999,11 @@ func TestSelectLogsDataWordBetween(t *testing.T) { Expressions: []query.Expression{ logpoller.NewAddressFilter(address), logpoller.NewEventSigFilter(eventSig), - logpoller.NewEventByWordFilter(eventSig, 0, []primitives.ValueComparator{ - {Value: logpoller.EvmWord(word).Hex(), Operator: primitives.Lte}, + logpoller.NewEventByWordFilter(0, []logpoller.HashedValueComparator{ + {Value: logpoller.EvmWord(word), Operator: primitives.Lte}, }), - logpoller.NewEventByWordFilter(eventSig, 1, []primitives.ValueComparator{ - {Value: logpoller.EvmWord(word).Hex(), Operator: primitives.Gte}, + logpoller.NewEventByWordFilter(1, []logpoller.HashedValueComparator{ + {Value: logpoller.EvmWord(word), Operator: primitives.Gte}, }), query.Confidence(primitives.Unconfirmed), }, @@ -1990,7 +2025,7 @@ func TestSelectLogsDataWordBetween(t *testing.T) { assertion(t, logs, err, tt.expectedLogs) - logs, err = th.ORM.FilteredLogs(ctx, wordFilter(tt.wordValue), limiter, "") + logs, err = th.ORM.FilteredLogs(ctx, wordFilter(tt.wordValue).Expressions, limiter, "") assertion(t, logs, err, tt.expectedLogs) }) diff --git a/core/chains/evm/logpoller/parser.go b/core/chains/evm/logpoller/parser.go index e08ea93da7..baa681e5ef 100644 --- a/core/chains/evm/logpoller/parser.go +++ b/core/chains/evm/logpoller/parser.go @@ -151,11 +151,11 @@ func (v *pgDSLParser) nestedConfQuery(finalized bool, confs uint64) string { } func (v *pgDSLParser) VisitEventByWordFilter(p *eventByWordFilter) { - if len(p.ValueComparers) > 0 { + if len(p.HashedValueComparers) > 0 { wordIdx := v.args.withIndexedField("word_index", p.WordIndex) - comps := make([]string, len(p.ValueComparers)) - for idx, comp := range p.ValueComparers { + comps := make([]string, len(p.HashedValueComparers)) + for idx, comp := range p.HashedValueComparers { comps[idx], v.err = makeComp(comp, v.args, "word_value", wordIdx, "substring(data from 32*:%s+1 for 32) %s :%s") if v.err != nil { return @@ -199,7 +199,7 @@ func (v *pgDSLParser) VisitConfirmationsFilter(p *confirmationsFilter) { } } -func makeComp(comp primitives.ValueComparator, args *queryArgs, field, subfield, pattern string) (string, error) { +func makeComp(comp HashedValueComparator, args *queryArgs, field, subfield, pattern string) (string, error) { cmp, err := cmpOpToString(comp.Operator) if err != nil { return "", err @@ -209,7 +209,7 @@ func makeComp(comp primitives.ValueComparator, args *queryArgs, field, subfield, pattern, subfield, cmp, - args.withIndexedField(field, common.HexToHash(comp.Value)), + args.withIndexedField(field, comp.Value), ), nil } @@ -492,17 +492,20 @@ func (f *eventSigFilter) Accept(visitor primitives.Visitor) { } } +type HashedValueComparator struct { + Value common.Hash + Operator primitives.ComparisonOperator +} + type eventByWordFilter struct { - EventSig common.Hash - WordIndex uint8 - ValueComparers []primitives.ValueComparator + WordIndex uint8 + HashedValueComparers []HashedValueComparator } -func NewEventByWordFilter(eventSig common.Hash, wordIndex uint8, valueComparers []primitives.ValueComparator) query.Expression { +func NewEventByWordFilter(wordIndex uint8, valueComparers []HashedValueComparator) query.Expression { return query.Expression{Primitive: &eventByWordFilter{ - EventSig: eventSig, - WordIndex: wordIndex, - ValueComparers: valueComparers, + WordIndex: wordIndex, + HashedValueComparers: valueComparers, }} } @@ -515,10 +518,10 @@ func (f *eventByWordFilter) Accept(visitor primitives.Visitor) { type eventByTopicFilter struct { Topic uint64 - ValueComparers []primitives.ValueComparator + ValueComparers []HashedValueComparator } -func NewEventByTopicFilter(topicIndex uint64, valueComparers []primitives.ValueComparator) query.Expression { +func NewEventByTopicFilter(topicIndex uint64, valueComparers []HashedValueComparator) query.Expression { return query.Expression{Primitive: &eventByTopicFilter{ Topic: topicIndex, ValueComparers: valueComparers, diff --git a/core/chains/evm/logpoller/parser_test.go b/core/chains/evm/logpoller/parser_test.go index 5e99ec7ba8..f37497600b 100644 --- a/core/chains/evm/logpoller/parser_test.go +++ b/core/chains/evm/logpoller/parser_test.go @@ -141,7 +141,7 @@ func TestDSLParser(t *testing.T) { expressions := []query.Expression{ query.Timestamp(10, primitives.Eq), query.TxHash(common.HexToHash("0x84").String()), - query.Block(99, primitives.Neq), + query.Block("99", primitives.Neq), query.Confidence(primitives.Finalized), } limiter := query.NewLimitAndSort(query.CursorLimit("10-20-0x42", query.CursorPrevious, 20)) @@ -233,8 +233,8 @@ func TestDSLParser(t *testing.T) { t.Run("query for event by word", func(t *testing.T) { t.Parallel() - wordFilter := NewEventByWordFilter(common.HexToHash("0x42"), 8, []primitives.ValueComparator{ - {Value: "", Operator: primitives.Gt}, + wordFilter := NewEventByWordFilter(8, []HashedValueComparator{ + {Value: common.HexToHash(""), Operator: primitives.Gt}, }) parser := &pgDSLParser{} @@ -257,9 +257,9 @@ func TestDSLParser(t *testing.T) { t.Run("query for event topic", func(t *testing.T) { t.Parallel() - topicFilter := NewEventByTopicFilter(2, []primitives.ValueComparator{ - {Value: "a", Operator: primitives.Gt}, - {Value: "b", Operator: primitives.Lt}, + topicFilter := NewEventByTopicFilter(2, []HashedValueComparator{ + {Value: common.HexToHash("a"), Operator: primitives.Gt}, + {Value: common.HexToHash("b"), Operator: primitives.Lt}, }) parser := &pgDSLParser{} @@ -321,9 +321,9 @@ func TestDSLParser(t *testing.T) { t.Run("nested query deep", func(t *testing.T) { t.Parallel() - wordFilter := NewEventByWordFilter(common.HexToHash("0x42"), 8, []primitives.ValueComparator{ - {Value: "a", Operator: primitives.Gt}, - {Value: "b", Operator: primitives.Lte}, + wordFilter := NewEventByWordFilter(8, []HashedValueComparator{ + {Value: common.HexToHash("a"), Operator: primitives.Gt}, + {Value: common.HexToHash("b"), Operator: primitives.Lte}, }) parser := &pgDSLParser{} diff --git a/core/chains/evm/monitor/balance.go b/core/chains/evm/monitor/balance.go index b8194a38af..1f5275c13f 100644 --- a/core/chains/evm/monitor/balance.go +++ b/core/chains/evm/monitor/balance.go @@ -33,14 +33,15 @@ type ( } balanceMonitor struct { - services.StateMachine - logger logger.Logger + services.Service + eng *services.Engine + ethClient evmclient.Client chainID *big.Int chainIDStr string ethKeyStore keystore.Eth ethBalances map[gethCommon.Address]*assets.Eth - ethBalancesMtx *sync.RWMutex + ethBalancesMtx sync.RWMutex sleeperTask *utils.SleeperTask } @@ -53,62 +54,41 @@ var _ BalanceMonitor = (*balanceMonitor)(nil) func NewBalanceMonitor(ethClient evmclient.Client, ethKeyStore keystore.Eth, lggr logger.Logger) *balanceMonitor { chainId := ethClient.ConfiguredChainID() bm := &balanceMonitor{ - services.StateMachine{}, - logger.Named(lggr, "BalanceMonitor"), - ethClient, - chainId, - chainId.String(), - ethKeyStore, - make(map[gethCommon.Address]*assets.Eth), - new(sync.RWMutex), - nil, + ethClient: ethClient, + chainID: chainId, + chainIDStr: chainId.String(), + ethKeyStore: ethKeyStore, + ethBalances: make(map[gethCommon.Address]*assets.Eth), } + bm.Service, bm.eng = services.Config{ + Name: "BalanceMonitor", + Start: bm.start, + Close: bm.close, + }.NewServiceEngine(lggr) bm.sleeperTask = utils.NewSleeperTask(&worker{bm: bm}) return bm } -func (bm *balanceMonitor) Start(ctx context.Context) error { - return bm.StartOnce("BalanceMonitor", func() error { - // Always query latest balance on start - (&worker{bm}).WorkCtx(ctx) - return nil - }) -} - -// Close shuts down the BalanceMonitor, should not be used after this -func (bm *balanceMonitor) Close() error { - return bm.StopOnce("BalanceMonitor", func() error { - return bm.sleeperTask.Stop() - }) -} - -func (bm *balanceMonitor) Ready() error { +func (bm *balanceMonitor) start(ctx context.Context) error { + // Always query latest balance on start + (&worker{bm}).WorkCtx(ctx) return nil } -func (bm *balanceMonitor) Name() string { - return bm.logger.Name() -} - -func (bm *balanceMonitor) HealthReport() map[string]error { - return map[string]error{bm.Name(): bm.Healthy()} +// Close shuts down the BalanceMonitor, should not be used after this +func (bm *balanceMonitor) close() error { + return bm.sleeperTask.Stop() } // OnNewLongestChain checks the balance for each key -func (bm *balanceMonitor) OnNewLongestChain(_ context.Context, head *evmtypes.Head) { - ok := bm.IfStarted(func() { - bm.checkBalance(head) - }) +func (bm *balanceMonitor) OnNewLongestChain(_ context.Context, _ *evmtypes.Head) { + bm.eng.Debugw("BalanceMonitor: signalling balance worker") + ok := bm.sleeperTask.WakeUpIfStarted() if !ok { - bm.logger.Debugw("BalanceMonitor: ignoring OnNewLongestChain call, balance monitor is not started", "state", bm.State()) + bm.eng.Debugw("BalanceMonitor: ignoring OnNewLongestChain call, balance monitor is not started", "state", bm.sleeperTask.State()) } } -func (bm *balanceMonitor) checkBalance(head *evmtypes.Head) { - bm.logger.Debugw("BalanceMonitor: signalling balance worker") - bm.sleeperTask.WakeUp() -} - func (bm *balanceMonitor) updateBalance(ethBal assets.Eth, address gethCommon.Address) { bm.promUpdateEthBalance(ðBal, address) @@ -117,7 +97,7 @@ func (bm *balanceMonitor) updateBalance(ethBal assets.Eth, address gethCommon.Ad bm.ethBalances[address] = ðBal bm.ethBalancesMtx.Unlock() - lgr := logger.Named(bm.logger, "BalanceLog") + lgr := logger.Named(bm.eng, "BalanceLog") lgr = logger.With(lgr, "address", address.Hex(), "ethBalance", ethBal.String(), @@ -151,7 +131,7 @@ func (bm *balanceMonitor) promUpdateEthBalance(balance *assets.Eth, from gethCom balanceFloat, err := ApproximateFloat64(balance) if err != nil { - bm.logger.Error(fmt.Errorf("updatePrometheusEthBalance: %v", err)) + bm.eng.Error(fmt.Errorf("updatePrometheusEthBalance: %v", err)) return } @@ -174,7 +154,7 @@ func (w *worker) Work() { func (w *worker) WorkCtx(ctx context.Context) { enabledAddresses, err := w.bm.ethKeyStore.EnabledAddressesForChain(ctx, w.bm.chainID) if err != nil { - w.bm.logger.Error("BalanceMonitor: error getting keys", err) + w.bm.eng.Error("BalanceMonitor: error getting keys", err) } var wg sync.WaitGroup @@ -198,12 +178,12 @@ func (w *worker) checkAccountBalance(ctx context.Context, address gethCommon.Add bal, err := w.bm.ethClient.BalanceAt(ctx, address, nil) if err != nil { - w.bm.logger.Errorw(fmt.Sprintf("BalanceMonitor: error getting balance for key %s", address.Hex()), + w.bm.eng.Errorw(fmt.Sprintf("BalanceMonitor: error getting balance for key %s", address.Hex()), "err", err, "address", address, ) } else if bal == nil { - w.bm.logger.Errorw(fmt.Sprintf("BalanceMonitor: error getting balance for key %s: invariant violation, bal may not be nil", address.Hex()), + w.bm.eng.Errorw(fmt.Sprintf("BalanceMonitor: error getting balance for key %s: invariant violation, bal may not be nil", address.Hex()), "err", err, "address", address, ) diff --git a/core/chains/evm/txmgr/attempts.go b/core/chains/evm/txmgr/attempts.go index c284ee77bd..2fc444071e 100644 --- a/core/chains/evm/txmgr/attempts.go +++ b/core/chains/evm/txmgr/attempts.go @@ -34,8 +34,6 @@ type evmTxAttemptBuilder struct { type evmTxAttemptBuilderFeeConfig interface { EIP1559DynamicFees() bool - TipCapMin() *assets.Wei - PriceMin() *assets.Wei PriceMaxKey(common.Address) *assets.Wei LimitDefault() uint64 } @@ -172,7 +170,7 @@ func (c *evmTxAttemptBuilder) NewEmptyTxAttempt(ctx context.Context, nonce evmty } func (c *evmTxAttemptBuilder) newDynamicFeeAttempt(ctx context.Context, etx Tx, fee gas.DynamicFee, gasLimit uint64) (attempt TxAttempt, err error) { - if err = validateDynamicFeeGas(c.feeConfig, c.feeConfig.TipCapMin(), fee, etx); err != nil { + if err = validateDynamicFeeGas(c.feeConfig, fee, etx); err != nil { return attempt, pkgerrors.Wrap(err, "error validating gas") } @@ -208,7 +206,7 @@ type keySpecificEstimator interface { // validateDynamicFeeGas is a sanity check - we have other checks elsewhere, but this // makes sure we _never_ create an invalid attempt -func validateDynamicFeeGas(kse keySpecificEstimator, tipCapMinimum *assets.Wei, fee gas.DynamicFee, etx Tx) error { +func validateDynamicFeeGas(kse keySpecificEstimator, fee gas.DynamicFee, etx Tx) error { gasTipCap, gasFeeCap := fee.TipCap, fee.FeeCap if gasTipCap == nil { @@ -235,11 +233,6 @@ func validateDynamicFeeGas(kse keySpecificEstimator, tipCapMinimum *assets.Wei, if gasFeeCap.Cmp(max) > 0 { return pkgerrors.Errorf("cannot create tx attempt: specified gas fee cap of %s would exceed max configured gas price of %s for key %s", gasFeeCap.String(), max.String(), etx.FromAddress.String()) } - // Tip must be above minimum - minTip := tipCapMinimum - if gasTipCap.Cmp(minTip) < 0 { - return pkgerrors.Errorf("cannot create tx attempt: specified gas tip cap of %s is below min configured gas tip of %s for key %s", gasTipCap.String(), minTip.String(), etx.FromAddress.String()) - } return nil } @@ -257,7 +250,7 @@ func newDynamicFeeTransaction(nonce uint64, to common.Address, value *big.Int, g } func (c *evmTxAttemptBuilder) newLegacyAttempt(ctx context.Context, etx Tx, gasPrice *assets.Wei, gasLimit uint64) (attempt TxAttempt, err error) { - if err = validateLegacyGas(c.feeConfig, c.feeConfig.PriceMin(), gasPrice, etx); err != nil { + if err = validateLegacyGas(c.feeConfig, gasPrice, etx); err != nil { return attempt, pkgerrors.Wrap(err, "error validating gas") } @@ -290,7 +283,7 @@ func (c *evmTxAttemptBuilder) newLegacyAttempt(ctx context.Context, etx Tx, gasP // validateLegacyGas is a sanity check - we have other checks elsewhere, but this // makes sure we _never_ create an invalid attempt -func validateLegacyGas(kse keySpecificEstimator, minGasPriceWei, gasPrice *assets.Wei, etx Tx) error { +func validateLegacyGas(kse keySpecificEstimator, gasPrice *assets.Wei, etx Tx) error { if gasPrice == nil { panic("gas price missing") } @@ -298,10 +291,6 @@ func validateLegacyGas(kse keySpecificEstimator, minGasPriceWei, gasPrice *asset if gasPrice.Cmp(max) > 0 { return pkgerrors.Errorf("cannot create tx attempt: specified gas price of %s would exceed max configured gas price of %s for key %s", gasPrice.String(), max.String(), etx.FromAddress.String()) } - min := minGasPriceWei - if gasPrice.Cmp(min) < 0 { - return pkgerrors.Errorf("cannot create tx attempt: specified gas price of %s is below min configured gas price of %s for key %s", gasPrice.String(), min.String(), etx.FromAddress.String()) - } return nil } diff --git a/core/chains/evm/txmgr/attempts_test.go b/core/chains/evm/txmgr/attempts_test.go index 5c43368fcc..df88835384 100644 --- a/core/chains/evm/txmgr/attempts_test.go +++ b/core/chains/evm/txmgr/attempts_test.go @@ -171,9 +171,6 @@ func TestTxm_NewDynamicFeeTx(t *testing.T) { {"ignores global min gas price", assets.GWei(5), assets.GWei(5), func(c *toml.EVMConfig) { c.GasEstimator.PriceMin = assets.GWei(6) }, ""}, - {"tip cap below min allowed", assets.GWei(5), assets.GWei(5), func(c *toml.EVMConfig) { - c.GasEstimator.TipCapMin = assets.GWei(6) - }, "specified gas tip cap of 5 gwei is below min configured gas tip of 6 gwei"}, } for _, tt := range cases { diff --git a/core/chains/evm/txmgr/broadcaster_test.go b/core/chains/evm/txmgr/broadcaster_test.go index 514d533159..439076cfa8 100644 --- a/core/chains/evm/txmgr/broadcaster_test.go +++ b/core/chains/evm/txmgr/broadcaster_test.go @@ -1524,7 +1524,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { pgtest.MustExec(t, db, `DELETE FROM evm.txes WHERE nonce = $1`, localNextNonce) }) - t.Run("eth tx is left in progress if eth node returns insufficient eth", func(t *testing.T) { + t.Run("tx is left in progress and its attempt gets replaced with a new re-estimated attempt if node returns insufficient eth", func(t *testing.T) { insufficientEthError := "insufficient funds for transfer" localNextNonce := getLocalNextNonce(t, nonceTracker, fromAddress) etx := mustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, testutils.FixtureChainID) @@ -1620,31 +1620,17 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { // configured for the transaction pool. // This is a configuration error by the node operator, since it means they set the base gas level too low. underpricedError := "transaction underpriced" - localNextNonce := getLocalNextNonce(t, nonceTracker, fromAddress) mustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, testutils.FixtureChainID) - // Check gas tip cap verification - evmcfg2 := evmtest.NewChainScopedConfig(t, configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { - c.EVM[0].GasEstimator.EIP1559DynamicFees = ptr(true) - c.EVM[0].GasEstimator.TipCapDefault = assets.NewWeiI(0) - })) - ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(localNextNonce, nil).Once() - eb2 := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, cfg, evmcfg2, &testCheckerFactory{}, false, nonceTracker) - - retryable, err := eb2.ProcessUnstartedTxs(ctx, fromAddress) - require.Error(t, err) - require.Contains(t, err.Error(), "specified gas tip cap of 0 is below min configured gas tip of 1 wei for key") - assert.True(t, retryable) - gasTipCapDefault := assets.NewWeiI(42) - evmcfg2 = evmtest.NewChainScopedConfig(t, configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + evmcfg2 := evmtest.NewChainScopedConfig(t, configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].GasEstimator.EIP1559DynamicFees = ptr(true) c.EVM[0].GasEstimator.TipCapDefault = gasTipCapDefault })) - localNextNonce = getLocalNextNonce(t, nonceTracker, fromAddress) + localNextNonce := getLocalNextNonce(t, nonceTracker, fromAddress) ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(localNextNonce, nil).Once() - eb2 = NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, cfg, evmcfg2, &testCheckerFactory{}, false, nonceTracker) + eb2 := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, cfg, evmcfg2, &testCheckerFactory{}, false, nonceTracker) // Second was underpriced but above minimum ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { @@ -1659,7 +1645,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { return tx.Nonce() == localNextNonce && tx.GasTipCap().Cmp(big.NewInt(0).Add(gasTipCapDefault.ToInt(), big.NewInt(0).Mul(evmcfg2.EVM().GasEstimator().BumpMin().ToInt(), big.NewInt(2)))) == 0 }), fromAddress).Return(commonclient.Successful, nil).Once() - retryable, err = eb2.ProcessUnstartedTxs(ctx, fromAddress) + retryable, err := eb2.ProcessUnstartedTxs(ctx, fromAddress) require.NoError(t, err) assert.False(t, retryable) diff --git a/core/chains/evm/txmgr/builder.go b/core/chains/evm/txmgr/builder.go index 8234d55b96..cbfb8775cf 100644 --- a/core/chains/evm/txmgr/builder.go +++ b/core/chains/evm/txmgr/builder.go @@ -1,6 +1,7 @@ package txmgr import ( + "context" "math/big" "time" @@ -18,6 +19,10 @@ import ( evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" ) +type latestAndFinalizedBlockHeadTracker interface { + LatestAndFinalizedBlock(ctx context.Context) (latest, finalized *evmtypes.Head, err error) +} + // NewTxm constructs the necessary dependencies for the EvmTxm (broadcaster, confirmer, etc) and returns a new EvmTxManager func NewTxm( ds sqlutil.DataSource, @@ -32,6 +37,7 @@ func NewTxm( logPoller logpoller.LogPoller, keyStore keystore.Eth, estimator gas.EvmFeeEstimator, + headTracker latestAndFinalizedBlockHeadTracker, ) (txm TxManager, err error, ) { @@ -53,12 +59,13 @@ func NewTxm( evmBroadcaster := NewEvmBroadcaster(txStore, txmClient, txmCfg, feeCfg, txConfig, listenerConfig, keyStore, txAttemptBuilder, lggr, checker, chainConfig.NonceAutoSync(), chainConfig.ChainType()) evmTracker := NewEvmTracker(txStore, keyStore, chainID, lggr) stuckTxDetector := NewStuckTxDetector(lggr, client.ConfiguredChainID(), chainConfig.ChainType(), fCfg.PriceMax(), txConfig.AutoPurge(), estimator, txStore, client) - evmConfirmer := NewEvmConfirmer(txStore, txmClient, txmCfg, feeCfg, txConfig, dbConfig, keyStore, txAttemptBuilder, lggr, stuckTxDetector) + evmConfirmer := NewEvmConfirmer(txStore, txmClient, txmCfg, feeCfg, txConfig, dbConfig, keyStore, txAttemptBuilder, lggr, stuckTxDetector, headTracker) + evmFinalizer := NewEvmFinalizer(lggr, client.ConfiguredChainID(), chainConfig.RPCDefaultBatchSize(), txStore, client, headTracker) var evmResender *Resender if txConfig.ResendAfterThreshold() > 0 { evmResender = NewEvmResender(lggr, txStore, txmClient, evmTracker, keyStore, txmgr.DefaultResenderPollInterval, chainConfig, txConfig) } - txm = NewEvmTxm(chainID, txmCfg, txConfig, keyStore, lggr, checker, fwdMgr, txAttemptBuilder, txStore, evmBroadcaster, evmConfirmer, evmResender, evmTracker) + txm = NewEvmTxm(chainID, txmCfg, txConfig, keyStore, lggr, checker, fwdMgr, txAttemptBuilder, txStore, evmBroadcaster, evmConfirmer, evmResender, evmTracker, evmFinalizer) return txm, nil } @@ -77,8 +84,9 @@ func NewEvmTxm( confirmer *Confirmer, resender *Resender, tracker *Tracker, + finalizer Finalizer, ) *Txm { - return txmgr.NewTxm(chainId, cfg, txCfg, keyStore, lggr, checkerFactory, fwdMgr, txAttemptBuilder, txStore, broadcaster, confirmer, resender, tracker, client.NewTxError) + return txmgr.NewTxm(chainId, cfg, txCfg, keyStore, lggr, checkerFactory, fwdMgr, txAttemptBuilder, txStore, broadcaster, confirmer, resender, tracker, finalizer, client.NewTxError) } // NewEvmResender creates a new concrete EvmResender @@ -96,8 +104,8 @@ func NewEvmResender( } // NewEvmReaper instantiates a new EVM-specific reaper object -func NewEvmReaper(lggr logger.Logger, store txmgrtypes.TxHistoryReaper[*big.Int], config EvmReaperConfig, txConfig txmgrtypes.ReaperTransactionsConfig, chainID *big.Int) *Reaper { - return txmgr.NewReaper(lggr, store, config, txConfig, chainID) +func NewEvmReaper(lggr logger.Logger, store txmgrtypes.TxHistoryReaper[*big.Int], txConfig txmgrtypes.ReaperTransactionsConfig, chainID *big.Int) *Reaper { + return txmgr.NewReaper(lggr, store, txConfig, chainID) } // NewEvmConfirmer instantiates a new EVM confirmer @@ -112,8 +120,9 @@ func NewEvmConfirmer( txAttemptBuilder TxAttemptBuilder, lggr logger.Logger, stuckTxDetector StuckTxDetector, + headTracker latestAndFinalizedBlockHeadTracker, ) *Confirmer { - return txmgr.NewConfirmer(txStore, client, chainConfig, feeConfig, txConfig, dbConfig, keystore, txAttemptBuilder, lggr, func(r *evmtypes.Receipt) bool { return r == nil }, stuckTxDetector) + return txmgr.NewConfirmer(txStore, client, chainConfig, feeConfig, txConfig, dbConfig, keystore, txAttemptBuilder, lggr, func(r *evmtypes.Receipt) bool { return r == nil }, stuckTxDetector, headTracker) } // NewEvmTracker instantiates a new EVM tracker for abandoned transactions diff --git a/core/chains/evm/txmgr/client.go b/core/chains/evm/txmgr/client.go index 7d3570ed77..05f442e1cc 100644 --- a/core/chains/evm/txmgr/client.go +++ b/core/chains/evm/txmgr/client.go @@ -184,3 +184,7 @@ func (c *evmTxmClient) CallContract(ctx context.Context, a TxAttempt, blockNumbe }, blockNumber) return client.ExtractRPCError(errCall) } + +func (c *evmTxmClient) HeadByHash(ctx context.Context, hash common.Hash) (*evmtypes.Head, error) { + return c.client.HeadByHash(ctx, hash) +} diff --git a/core/chains/evm/txmgr/config.go b/core/chains/evm/txmgr/config.go index b53f99840b..af20c9a590 100644 --- a/core/chains/evm/txmgr/config.go +++ b/core/chains/evm/txmgr/config.go @@ -48,7 +48,6 @@ type ( EvmBroadcasterConfig txmgrtypes.BroadcasterChainConfig EvmConfirmerConfig txmgrtypes.ConfirmerChainConfig EvmResenderConfig txmgrtypes.ResenderChainConfig - EvmReaperConfig txmgrtypes.ReaperChainConfig ) var _ EvmTxmConfig = (*evmTxmConfig)(nil) diff --git a/core/chains/evm/txmgr/confirmer_test.go b/core/chains/evm/txmgr/confirmer_test.go index 82b668f168..d63f0cf1de 100644 --- a/core/chains/evm/txmgr/confirmer_test.go +++ b/core/chains/evm/txmgr/confirmer_test.go @@ -34,6 +34,7 @@ import ( evmconfig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" gasmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/mocks" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/keystore" ksmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/keystore/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/testutils" @@ -131,7 +132,8 @@ func TestEthConfirmer_Lifecycle(t *testing.T) { feeEstimator := gas.NewEvmFeeEstimator(lggr, newEst, ge.EIP1559DynamicFees(), ge, ethClient) txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, ethKeyStore, feeEstimator) stuckTxDetector := txmgr.NewStuckTxDetector(lggr, testutils.FixtureChainID, "", assets.NewWei(assets.NewEth(100).ToInt()), config.EVM().Transactions().AutoPurge(), feeEstimator, txStore, ethClient) - ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient, nil), txmgr.NewEvmTxmConfig(config.EVM()), txmgr.NewEvmTxmFeeConfig(ge), config.EVM().Transactions(), gconfig.Database(), ethKeyStore, txBuilder, lggr, stuckTxDetector) + ht := headtracker.NewSimulatedHeadTracker(ethClient, true, 0) + ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient, nil), txmgr.NewEvmTxmConfig(config.EVM()), txmgr.NewEvmTxmFeeConfig(ge), config.EVM().Transactions(), gconfig.Database(), ethKeyStore, txBuilder, lggr, stuckTxDetector, ht) ctx := tests.Context(t) // Can't close unstarted instance @@ -145,20 +147,29 @@ func TestEthConfirmer_Lifecycle(t *testing.T) { // Can't start an already started instance err = ec.Start(ctx) require.Error(t, err) - head := evmtypes.Head{ + + latestFinalizedHead := &evmtypes.Head{ + Number: 8, + Hash: testutils.NewHash(), + } + // We are guaranteed to receive a latestFinalizedHead. + latestFinalizedHead.IsFinalized.Store(true) + + h9 := &evmtypes.Head{ + Hash: testutils.NewHash(), + Number: 9, + } + h9.Parent.Store(latestFinalizedHead) + head := &evmtypes.Head{ Hash: testutils.NewHash(), Number: 10, - Parent: &evmtypes.Head{ - Hash: testutils.NewHash(), - Number: 9, - Parent: &evmtypes.Head{ - Number: 8, - Hash: testutils.NewHash(), - Parent: nil, - }, - }, } - err = ec.ProcessHead(ctx, &head) + head.Parent.Store(h9) + + ethClient.On("HeadByNumber", mock.Anything, (*big.Int)(nil)).Return(head, nil).Once() + ethClient.On("LatestFinalizedBlock", mock.Anything).Return(latestFinalizedHead, nil).Once() + + err = ec.ProcessHead(ctx, head) require.NoError(t, err) // Can successfully close once err = ec.Close() @@ -199,6 +210,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { nonce := int64(0) ctx := tests.Context(t) blockNum := int64(0) + latestFinalizedBlockNum := int64(0) t.Run("only finds eth_txes in unconfirmed state with at least one broadcast attempt", func(t *testing.T) { mustInsertFatalErrorEthTx(t, txStore, fromAddress) @@ -211,7 +223,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { mustCreateUnstartedGeneratedTx(t, txStore, fromAddress, config.EVM().ChainID()) // Do the thing - require.NoError(t, ec.CheckForReceipts(ctx, blockNum)) + require.NoError(t, ec.CheckForReceipts(ctx, blockNum, latestFinalizedBlockNum)) }) etx1 := cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, txStore, nonce, fromAddress) @@ -232,7 +244,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { }).Once() // Do the thing - require.NoError(t, ec.CheckForReceipts(ctx, blockNum)) + require.NoError(t, ec.CheckForReceipts(ctx, blockNum, latestFinalizedBlockNum)) var err error etx1, err = txStore.FindTxWithAttempts(ctx, etx1.ID) @@ -261,7 +273,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { }).Once() // No error because it is merely logged - require.NoError(t, ec.CheckForReceipts(ctx, blockNum)) + require.NoError(t, ec.CheckForReceipts(ctx, blockNum, latestFinalizedBlockNum)) etx, err := txStore.FindTxWithAttempts(ctx, etx1.ID) require.NoError(t, err) @@ -289,7 +301,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { }).Once() // No error because it is merely logged - require.NoError(t, ec.CheckForReceipts(ctx, blockNum)) + require.NoError(t, ec.CheckForReceipts(ctx, blockNum, latestFinalizedBlockNum)) etx, err := txStore.FindTxWithAttempts(ctx, etx1.ID) require.NoError(t, err) @@ -326,7 +338,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { }).Once() // Do the thing - require.NoError(t, ec.CheckForReceipts(ctx, blockNum)) + require.NoError(t, ec.CheckForReceipts(ctx, blockNum, latestFinalizedBlockNum)) // Check that the receipt was saved etx, err := txStore.FindTxWithAttempts(ctx, etx1.ID) @@ -388,7 +400,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { }).Once() // Do the thing - require.NoError(t, ec.CheckForReceipts(ctx, blockNum)) + require.NoError(t, ec.CheckForReceipts(ctx, blockNum, latestFinalizedBlockNum)) // Check that the state was updated etx, err := txStore.FindTxWithAttempts(ctx, etx2.ID) @@ -416,7 +428,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { }).Once() // Do the thing - require.NoError(t, ec.CheckForReceipts(ctx, blockNum)) + require.NoError(t, ec.CheckForReceipts(ctx, blockNum, latestFinalizedBlockNum)) // No receipt, but no error either etx, err := txStore.FindTxWithAttempts(ctx, etx3.ID) @@ -443,7 +455,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { }).Once() // Do the thing - require.NoError(t, ec.CheckForReceipts(ctx, blockNum)) + require.NoError(t, ec.CheckForReceipts(ctx, blockNum, latestFinalizedBlockNum)) // No receipt, but no error either etx, err := txStore.FindTxWithAttempts(ctx, etx3.ID) @@ -472,7 +484,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { }).Once() // Do the thing - require.NoError(t, ec.CheckForReceipts(ctx, blockNum)) + require.NoError(t, ec.CheckForReceipts(ctx, blockNum, latestFinalizedBlockNum)) // Check that the receipt was unchanged etx, err := txStore.FindTxWithAttempts(ctx, etx3.ID) @@ -523,7 +535,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { }).Once() // Do the thing - require.NoError(t, ec.CheckForReceipts(ctx, blockNum)) + require.NoError(t, ec.CheckForReceipts(ctx, blockNum, latestFinalizedBlockNum)) // Check that the state was updated var err error @@ -576,7 +588,7 @@ func TestEthConfirmer_CheckForReceipts(t *testing.T) { }).Once() // Do the thing - require.NoError(t, ec.CheckForReceipts(ctx, blockNum)) + require.NoError(t, ec.CheckForReceipts(ctx, blockNum, latestFinalizedBlockNum)) // Check that the state was updated etx5, err = txStore.FindTxWithAttempts(ctx, etx5.ID) @@ -614,6 +626,7 @@ func TestEthConfirmer_CheckForReceipts_batching(t *testing.T) { etx := cltest.MustInsertUnconfirmedEthTx(t, txStore, 0, fromAddress) var attempts []txmgr.TxAttempt + latestFinalizedBlockNum := int64(0) // Total of 5 attempts should lead to 3 batched fetches (2, 2, 1) for i := 0; i < 5; i++ { @@ -650,7 +663,7 @@ func TestEthConfirmer_CheckForReceipts_batching(t *testing.T) { elems[0].Result = &evmtypes.Receipt{} }).Once() - require.NoError(t, ec.CheckForReceipts(ctx, 42)) + require.NoError(t, ec.CheckForReceipts(ctx, 42, latestFinalizedBlockNum)) } func TestEthConfirmer_CheckForReceipts_HandlesNonFwdTxsWithForwardingEnabled(t *testing.T) { @@ -671,6 +684,8 @@ func TestEthConfirmer_CheckForReceipts_HandlesNonFwdTxsWithForwardingEnabled(t * _, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) ec := newEthConfirmer(t, txStore, ethClient, cfg, evmcfg, ethKeyStore, nil) ctx := tests.Context(t) + latestFinalizedBlockNum := int64(0) + // tx is not forwarded and doesn't have meta set. EthConfirmer should handle nil meta values etx := cltest.MustInsertUnconfirmedEthTx(t, txStore, 0, fromAddress) attempt := newBroadcastLegacyEthTxAttempt(t, etx.ID, 2) @@ -697,7 +712,7 @@ func TestEthConfirmer_CheckForReceipts_HandlesNonFwdTxsWithForwardingEnabled(t * *(elems[0].Result.(*evmtypes.Receipt)) = txmReceipt // confirmed }).Once() - require.NoError(t, ec.CheckForReceipts(ctx, 42)) + require.NoError(t, ec.CheckForReceipts(ctx, 42, latestFinalizedBlockNum)) // Check receipt is inserted correctly. dbtx, err = txStore.FindTxWithAttempts(ctx, etx.ID) @@ -724,6 +739,7 @@ func TestEthConfirmer_CheckForReceipts_only_likely_confirmed(t *testing.T) { ec := newEthConfirmer(t, txStore, ethClient, cfg, evmcfg, ethKeyStore, nil) ctx := tests.Context(t) + latestFinalizedBlockNum := int64(0) var attempts []txmgr.TxAttempt // inserting in DESC nonce order to test DB ASC ordering @@ -755,7 +771,7 @@ func TestEthConfirmer_CheckForReceipts_only_likely_confirmed(t *testing.T) { elems[3].Result = &evmtypes.Receipt{} }).Once() - require.NoError(t, ec.CheckForReceipts(ctx, 42)) + require.NoError(t, ec.CheckForReceipts(ctx, 42, latestFinalizedBlockNum)) cltest.BatchElemMustMatchParams(t, captured[0], attempts[0].Hash, "eth_getTransactionReceipt") cltest.BatchElemMustMatchParams(t, captured[1], attempts[1].Hash, "eth_getTransactionReceipt") @@ -778,6 +794,7 @@ func TestEthConfirmer_CheckForReceipts_should_not_check_for_likely_unconfirmed(t ec := newEthConfirmer(t, txStore, ethClient, gconfig, config, ethKeyStore, nil) ctx := tests.Context(t) + latestFinalizedBlockNum := int64(0) etx := cltest.MustInsertUnconfirmedEthTx(t, txStore, 1, fromAddress) for i := 0; i < 4; i++ { @@ -788,7 +805,7 @@ func TestEthConfirmer_CheckForReceipts_should_not_check_for_likely_unconfirmed(t // latest nonce is lower that all attempts' nonces ethClient.On("SequenceAt", mock.Anything, mock.Anything, mock.Anything).Return(evmtypes.Nonce(0), nil) - require.NoError(t, ec.CheckForReceipts(ctx, 42)) + require.NoError(t, ec.CheckForReceipts(ctx, 42, latestFinalizedBlockNum)) } func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt_scoped_to_key(t *testing.T) { @@ -809,6 +826,7 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt_scoped_to_key(t ec := newEthConfirmer(t, txStore, ethClient, cfg, evmcfg, ethKeyStore, nil) ctx := tests.Context(t) + latestFinalizedBlockNum := int64(0) // STATE // key 1, tx with nonce 0 is unconfirmed @@ -832,7 +850,7 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt_scoped_to_key(t *(elems[0].Result.(*evmtypes.Receipt)) = txmReceipt2_9 }).Once() - require.NoError(t, ec.CheckForReceipts(ctx, 10)) + require.NoError(t, ec.CheckForReceipts(ctx, 10, latestFinalizedBlockNum)) mustTxBeInState(t, txStore, etx1_0, txmgrcommon.TxUnconfirmed) mustTxBeInState(t, txStore, etx1_1, txmgrcommon.TxUnconfirmed) @@ -850,7 +868,7 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt_scoped_to_key(t *(elems[0].Result.(*evmtypes.Receipt)) = txmReceipt1_1 }).Once() - require.NoError(t, ec.CheckForReceipts(ctx, 11)) + require.NoError(t, ec.CheckForReceipts(ctx, 11, latestFinalizedBlockNum)) mustTxBeInState(t, txStore, etx1_0, txmgrcommon.TxConfirmedMissingReceipt) mustTxBeInState(t, txStore, etx1_1, txmgrcommon.TxConfirmed) @@ -861,9 +879,7 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt(t *testing.T) { t.Parallel() db := pgtest.NewSqlxDB(t) - cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { - c.EVM[0].FinalityDepth = ptr[uint32](50) - }) + cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) {}) txStore := cltest.NewTestTxStore(t, db) ethKeyStore := cltest.NewKeyStore(t, db).Eth() @@ -876,6 +892,7 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt(t *testing.T) { ec := newEthConfirmer(t, txStore, ethClient, cfg, evmcfg, ethKeyStore, nil) ctx := tests.Context(t) + latestFinalizedBlockNum := int64(0) // STATE // eth_txes with nonce 0 has two attempts (broadcast before block 21 and 41) the first of which will get a receipt @@ -949,7 +966,7 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt(t *testing.T) { // PERFORM // Block num of 43 is one higher than the receipt (as would generally be expected) - require.NoError(t, ec.CheckForReceipts(ctx, 43)) + require.NoError(t, ec.CheckForReceipts(ctx, 43, latestFinalizedBlockNum)) // Expected state is that the "top" eth_tx is now confirmed, with the // two below it "confirmed_missing_receipt" and the "bottom" eth_tx also confirmed @@ -1009,7 +1026,7 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt(t *testing.T) { // PERFORM // Block num of 44 is one higher than the receipt (as would generally be expected) - require.NoError(t, ec.CheckForReceipts(ctx, 44)) + require.NoError(t, ec.CheckForReceipts(ctx, 44, latestFinalizedBlockNum)) // Expected state is that the "top" two eth_txes are now confirmed, with the // one below it still "confirmed_missing_receipt" and the bottom one remains confirmed @@ -1038,7 +1055,7 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt(t *testing.T) { // eth_txes with nonce 2 is confirmed // eth_txes with nonce 3 is confirmed - t.Run("continues to leave eth_txes with state 'confirmed_missing_receipt' unchanged if at least one attempt is above EVM.FinalityDepth", func(t *testing.T) { + t.Run("continues to leave eth_txes with state 'confirmed_missing_receipt' unchanged if at least one attempt is above LatestFinalizedBlockNum", func(t *testing.T) { ethClient.On("SequenceAt", mock.Anything, mock.Anything, mock.Anything).Return(evmtypes.Nonce(10), nil) ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { return len(b) == 2 && @@ -1051,9 +1068,11 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt(t *testing.T) { elems[1].Result = &evmtypes.Receipt{} }).Once() + latestFinalizedBlockNum = 30 + // PERFORM // Block num of 80 puts the first attempt (21) below threshold but second attempt (41) still above - require.NoError(t, ec.CheckForReceipts(ctx, 80)) + require.NoError(t, ec.CheckForReceipts(ctx, 80, latestFinalizedBlockNum)) // Expected state is that the "top" two eth_txes are now confirmed, with the // one below it still "confirmed_missing_receipt" and the bottom one remains confirmed @@ -1078,7 +1097,7 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt(t *testing.T) { // eth_txes with nonce 2 is confirmed // eth_txes with nonce 3 is confirmed - t.Run("marks eth_Txes with state 'confirmed_missing_receipt' as 'errored' if a receipt fails to show up and all attempts are buried deeper than EVM.FinalityDepth", func(t *testing.T) { + t.Run("marks eth_Txes with state 'confirmed_missing_receipt' as 'errored' if a receipt fails to show up and all attempts are buried deeper than LatestFinalizedBlockNum", func(t *testing.T) { ethClient.On("SequenceAt", mock.Anything, mock.Anything, mock.Anything).Return(evmtypes.Nonce(10), nil) ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { return len(b) == 2 && @@ -1091,9 +1110,11 @@ func TestEthConfirmer_CheckForReceipts_confirmed_missing_receipt(t *testing.T) { elems[1].Result = &evmtypes.Receipt{} }).Once() + latestFinalizedBlockNum = 50 + // PERFORM // Block num of 100 puts the first attempt (21) and second attempt (41) below threshold - require.NoError(t, ec.CheckForReceipts(ctx, 100)) + require.NoError(t, ec.CheckForReceipts(ctx, 100, latestFinalizedBlockNum)) // Expected state is that the "top" two eth_txes are now confirmed, with the // one below it marked as "fatal_error" and the bottom one remains confirmed @@ -1117,9 +1138,7 @@ func TestEthConfirmer_CheckConfirmedMissingReceipt(t *testing.T) { t.Parallel() db := pgtest.NewSqlxDB(t) - cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { - c.EVM[0].FinalityDepth = ptr[uint32](50) - }) + cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) {}) txStore := cltest.NewTestTxStore(t, db) ethKeyStore := cltest.NewKeyStore(t, db).Eth() @@ -1197,9 +1216,7 @@ func TestEthConfirmer_CheckConfirmedMissingReceipt_batchSendTransactions_fails(t t.Parallel() db := pgtest.NewSqlxDB(t) - cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { - c.EVM[0].FinalityDepth = ptr[uint32](50) - }) + cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) {}) txStore := cltest.NewTestTxStore(t, db) ethKeyStore := cltest.NewKeyStore(t, db).Eth() @@ -1262,7 +1279,6 @@ func TestEthConfirmer_CheckConfirmedMissingReceipt_smallEvmRPCBatchSize_middleBa db := pgtest.NewSqlxDB(t) cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { - c.EVM[0].FinalityDepth = ptr[uint32](50) c.EVM[0].RPCDefaultBatchSize = ptr[uint32](1) }) txStore := cltest.NewTestTxStore(t, db) @@ -1651,8 +1667,9 @@ func TestEthConfirmer_RebroadcastWhereNecessary_WithConnectivityCheck(t *testing addresses := []gethCommon.Address{fromAddress} kst.On("EnabledAddressesForChain", mock.Anything, &cltest.FixtureChainID).Return(addresses, nil).Maybe() stuckTxDetector := txmgr.NewStuckTxDetector(lggr, testutils.FixtureChainID, "", assets.NewWei(assets.NewEth(100).ToInt()), ccfg.EVM().Transactions().AutoPurge(), feeEstimator, txStore, ethClient) + ht := headtracker.NewSimulatedHeadTracker(ethClient, true, 0) // Create confirmer with necessary state - ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient, nil), ccfg.EVM(), txmgr.NewEvmTxmFeeConfig(ccfg.EVM().GasEstimator()), ccfg.EVM().Transactions(), cfg.Database(), kst, txBuilder, lggr, stuckTxDetector) + ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient, nil), ccfg.EVM(), txmgr.NewEvmTxmFeeConfig(ccfg.EVM().GasEstimator()), ccfg.EVM().Transactions(), cfg.Database(), kst, txBuilder, lggr, stuckTxDetector, ht) servicetest.Run(t, ec) currentHead := int64(30) oldEnough := int64(15) @@ -1700,7 +1717,8 @@ func TestEthConfirmer_RebroadcastWhereNecessary_WithConnectivityCheck(t *testing addresses := []gethCommon.Address{fromAddress} kst.On("EnabledAddressesForChain", mock.Anything, &cltest.FixtureChainID).Return(addresses, nil).Maybe() stuckTxDetector := txmgr.NewStuckTxDetector(lggr, testutils.FixtureChainID, "", assets.NewWei(assets.NewEth(100).ToInt()), ccfg.EVM().Transactions().AutoPurge(), feeEstimator, txStore, ethClient) - ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient, nil), ccfg.EVM(), txmgr.NewEvmTxmFeeConfig(ccfg.EVM().GasEstimator()), ccfg.EVM().Transactions(), cfg.Database(), kst, txBuilder, lggr, stuckTxDetector) + ht := headtracker.NewSimulatedHeadTracker(ethClient, true, 0) + ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient, nil), ccfg.EVM(), txmgr.NewEvmTxmFeeConfig(ccfg.EVM().GasEstimator()), ccfg.EVM().Transactions(), cfg.Database(), kst, txBuilder, lggr, stuckTxDetector, ht) servicetest.Run(t, ec) currentHead := int64(30) oldEnough := int64(15) @@ -2724,29 +2742,34 @@ func TestEthConfirmer_EnsureConfirmedTransactionsInLongestChain(t *testing.T) { gconfig, config := newTestChainScopedConfig(t) ec := newEthConfirmer(t, txStore, ethClient, gconfig, config, ethKeyStore, nil) - head := evmtypes.Head{ + latestFinalizedHead := evmtypes.Head{ + Number: 8, Hash: testutils.NewHash(), - Number: 10, - Parent: &evmtypes.Head{ - Hash: testutils.NewHash(), - Number: 9, - Parent: &evmtypes.Head{ - Number: 8, - Hash: testutils.NewHash(), - Parent: nil, - }, - }, } + h8 := &evmtypes.Head{ + Number: 8, + Hash: testutils.NewHash(), + } + h9 := &evmtypes.Head{ + Hash: testutils.NewHash(), + Number: 9, + } + h9.Parent.Store(h8) + head := &evmtypes.Head{ + Hash: testutils.NewHash(), + Number: 10, + } + head.Parent.Store(h9) t.Run("does nothing if there aren't any transactions", func(t *testing.T) { - require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(tests.Context(t), &head)) + require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(tests.Context(t), head, latestFinalizedHead.BlockNumber())) }) t.Run("does nothing to unconfirmed transactions", func(t *testing.T) { etx := cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, txStore, 0, fromAddress) // Do the thing - require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(tests.Context(t), &head)) + require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(tests.Context(t), head, latestFinalizedHead.BlockNumber())) etx, err := txStore.FindTxWithAttempts(ctx, etx.ID) require.NoError(t, err) @@ -2758,7 +2781,7 @@ func TestEthConfirmer_EnsureConfirmedTransactionsInLongestChain(t *testing.T) { mustInsertEthReceipt(t, txStore, head.Number, head.Hash, etx.TxAttempts[0].Hash) // Do the thing - require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(tests.Context(t), &head)) + require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(tests.Context(t), head, latestFinalizedHead.BlockNumber())) etx, err := txStore.FindTxWithAttempts(ctx, etx.ID) require.NoError(t, err) @@ -2768,10 +2791,10 @@ func TestEthConfirmer_EnsureConfirmedTransactionsInLongestChain(t *testing.T) { t.Run("does nothing to confirmed transactions that only have receipts older than the start of the chain", func(t *testing.T) { etx := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, txStore, 3, 1, fromAddress) // Add receipt that is older than the lowest block of the chain - mustInsertEthReceipt(t, txStore, head.Parent.Parent.Number-1, testutils.NewHash(), etx.TxAttempts[0].Hash) + mustInsertEthReceipt(t, txStore, h8.Number-1, testutils.NewHash(), etx.TxAttempts[0].Hash) // Do the thing - require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(tests.Context(t), &head)) + require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(tests.Context(t), head, latestFinalizedHead.BlockNumber())) etx, err := txStore.FindTxWithAttempts(ctx, etx.ID) require.NoError(t, err) @@ -2782,7 +2805,7 @@ func TestEthConfirmer_EnsureConfirmedTransactionsInLongestChain(t *testing.T) { etx := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, txStore, 4, 1, fromAddress) attempt := etx.TxAttempts[0] // Include one within head height but a different block hash - mustInsertEthReceipt(t, txStore, head.Parent.Number, testutils.NewHash(), attempt.Hash) + mustInsertEthReceipt(t, txStore, head.Parent.Load().Number, testutils.NewHash(), attempt.Hash) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { atx, err := txmgr.GetGethSignedTx(attempt.SignedRawTx) @@ -2792,7 +2815,7 @@ func TestEthConfirmer_EnsureConfirmedTransactionsInLongestChain(t *testing.T) { }), fromAddress).Return(commonclient.Successful, nil).Once() // Do the thing - require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(tests.Context(t), &head)) + require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(tests.Context(t), head, latestFinalizedHead.BlockNumber())) etx, err := txStore.FindTxWithAttempts(ctx, etx.ID) require.NoError(t, err) @@ -2807,15 +2830,15 @@ func TestEthConfirmer_EnsureConfirmedTransactionsInLongestChain(t *testing.T) { attempt := etx.TxAttempts[0] attemptHash := attempt.Hash // Add receipt that is older than the lowest block of the chain - mustInsertEthReceipt(t, txStore, head.Parent.Parent.Number-1, testutils.NewHash(), attemptHash) + mustInsertEthReceipt(t, txStore, h8.Number-1, testutils.NewHash(), attemptHash) // Include one within head height but a different block hash - mustInsertEthReceipt(t, txStore, head.Parent.Number, testutils.NewHash(), attemptHash) + mustInsertEthReceipt(t, txStore, head.Parent.Load().Number, testutils.NewHash(), attemptHash) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.Anything, fromAddress).Return( commonclient.Successful, nil).Once() // Do the thing - require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(tests.Context(t), &head)) + require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(tests.Context(t), head, latestFinalizedHead.BlockNumber())) etx, err := txStore.FindTxWithAttempts(ctx, etx.ID) require.NoError(t, err) @@ -2839,9 +2862,9 @@ func TestEthConfirmer_EnsureConfirmedTransactionsInLongestChain(t *testing.T) { require.NoError(t, txStore.InsertTxAttempt(ctx, &attempt3)) // Receipt is within head height but a different block hash - mustInsertEthReceipt(t, txStore, head.Parent.Number, testutils.NewHash(), attempt2.Hash) + mustInsertEthReceipt(t, txStore, head.Parent.Load().Number, testutils.NewHash(), attempt2.Hash) // Receipt is within head height but a different block hash - mustInsertEthReceipt(t, txStore, head.Parent.Number, testutils.NewHash(), attempt3.Hash) + mustInsertEthReceipt(t, txStore, head.Parent.Load().Number, testutils.NewHash(), attempt3.Hash) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *types.Transaction) bool { s, err := txmgr.GetGethSignedTx(attempt3.SignedRawTx) @@ -2850,7 +2873,7 @@ func TestEthConfirmer_EnsureConfirmedTransactionsInLongestChain(t *testing.T) { }), fromAddress).Return(commonclient.Successful, nil).Once() // Do the thing - require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(tests.Context(t), &head)) + require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(tests.Context(t), head, latestFinalizedHead.BlockNumber())) etx, err := txStore.FindTxWithAttempts(ctx, etx.ID) require.NoError(t, err) @@ -2870,7 +2893,7 @@ func TestEthConfirmer_EnsureConfirmedTransactionsInLongestChain(t *testing.T) { // Add receipt that is higher than head mustInsertEthReceipt(t, txStore, head.Number+1, testutils.NewHash(), attempt.Hash) - require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(tests.Context(t), &head)) + require.NoError(t, ec.EnsureConfirmedTransactionsInLongestChain(tests.Context(t), head, latestFinalizedHead.BlockNumber())) etx, err := txStore.FindTxWithAttempts(ctx, etx.ID) require.NoError(t, err) @@ -2997,19 +3020,20 @@ func TestEthConfirmer_ResumePendingRuns(t *testing.T) { evmcfg := evmtest.NewChainScopedConfig(t, config) + h8 := &evmtypes.Head{ + Number: 8, + Hash: testutils.NewHash(), + } + h9 := &evmtypes.Head{ + Hash: testutils.NewHash(), + Number: 9, + } + h9.Parent.Store(h8) head := evmtypes.Head{ Hash: testutils.NewHash(), Number: 10, - Parent: &evmtypes.Head{ - Hash: testutils.NewHash(), - Number: 9, - Parent: &evmtypes.Head{ - Number: 8, - Hash: testutils.NewHash(), - Parent: nil, - }, - }, } + head.Parent.Store(h9) minConfirmations := int64(2) @@ -3210,7 +3234,13 @@ func TestEthConfirmer_ProcessStuckTransactions(t *testing.T) { ge := evmcfg.EVM().GasEstimator() txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, ethKeyStore, feeEstimator) stuckTxDetector := txmgr.NewStuckTxDetector(lggr, testutils.FixtureChainID, "", assets.NewWei(assets.NewEth(100).ToInt()), evmcfg.EVM().Transactions().AutoPurge(), feeEstimator, txStore, ethClient) - ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient, nil), txmgr.NewEvmTxmConfig(evmcfg.EVM()), txmgr.NewEvmTxmFeeConfig(ge), evmcfg.EVM().Transactions(), cfg.Database(), ethKeyStore, txBuilder, lggr, stuckTxDetector) + ht := headtracker.NewSimulatedHeadTracker(ethClient, true, 0) + ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient, nil), txmgr.NewEvmTxmConfig(evmcfg.EVM()), txmgr.NewEvmTxmFeeConfig(ge), evmcfg.EVM().Transactions(), cfg.Database(), ethKeyStore, txBuilder, lggr, stuckTxDetector, ht) + fn := func(ctx context.Context, id uuid.UUID, result interface{}, err error) error { + require.ErrorContains(t, err, client.TerminallyStuckMsg) + return nil + } + ec.SetResumeCallback(fn) servicetest.Run(t, ec) ctx := tests.Context(t) @@ -3222,11 +3252,16 @@ func TestEthConfirmer_ProcessStuckTransactions(t *testing.T) { // Create autoPurgeMinAttempts number of attempts to ensure the broadcast attempt count check is not being triggered // Create attempts broadcasted autoPurgeThreshold block ago to ensure broadcast block num check is not being triggered tx := mustInsertUnconfirmedTxWithBroadcastAttempts(t, txStore, nonce, fromAddress, autoPurgeMinAttempts, blockNum-int64(autoPurgeThreshold), marketGasPrice.Add(oneGwei)) - + // Update tx to signal callback once it is identified as terminally stuck + pgtest.MustExec(t, db, `UPDATE evm.txes SET pipeline_task_run_id = $1, signal_callback = TRUE WHERE id = $2`, uuid.New(), tx.ID) head := evmtypes.Head{ Hash: testutils.NewHash(), Number: blockNum, } + head.IsFinalized.Store(true) + + ethClient.On("HeadByNumber", mock.Anything, (*big.Int)(nil)).Return(&head, nil).Once() + ethClient.On("LatestFinalizedBlock", mock.Anything).Return(&head, nil).Once() ethClient.On("SequenceAt", mock.Anything, mock.Anything, mock.Anything).Return(evmtypes.Nonce(0), nil).Once() ethClient.On("BatchCallContext", mock.Anything, mock.Anything).Return(nil).Once() @@ -3251,6 +3286,9 @@ func TestEthConfirmer_ProcessStuckTransactions(t *testing.T) { Hash: testutils.NewHash(), Number: blockNum + 1, } + head.IsFinalized.Store(true) + ethClient.On("HeadByNumber", mock.Anything, (*big.Int)(nil)).Return(&head, nil).Once() + ethClient.On("LatestFinalizedBlock", mock.Anything).Return(&head, nil).Once() ethClient.On("SequenceAt", mock.Anything, mock.Anything, mock.Anything).Return(evmtypes.Nonce(1), nil) ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { return len(b) == 4 && cltest.BatchElemMatchesParams(b[0], latestAttempt.Hash, "eth_getTransactionReceipt") @@ -3276,6 +3314,7 @@ func TestEthConfirmer_ProcessStuckTransactions(t *testing.T) { require.NotNil(t, dbTx) require.Equal(t, txmgrcommon.TxFatalError, dbTx.State) require.Equal(t, client.TerminallyStuckMsg, dbTx.Error.String) + require.Equal(t, true, dbTx.CallbackCompleted) }) } @@ -3289,7 +3328,8 @@ func newEthConfirmer(t testing.TB, txStore txmgr.EvmTxStore, ethClient client.Cl }, ge.EIP1559DynamicFees(), ge, ethClient) txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, ks, estimator) stuckTxDetector := txmgr.NewStuckTxDetector(lggr, testutils.FixtureChainID, "", assets.NewWei(assets.NewEth(100).ToInt()), config.EVM().Transactions().AutoPurge(), estimator, txStore, ethClient) - ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient, nil), txmgr.NewEvmTxmConfig(config.EVM()), txmgr.NewEvmTxmFeeConfig(ge), config.EVM().Transactions(), gconfig.Database(), ks, txBuilder, lggr, stuckTxDetector) + ht := headtracker.NewSimulatedHeadTracker(ethClient, true, 0) + ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient, nil), txmgr.NewEvmTxmConfig(config.EVM()), txmgr.NewEvmTxmFeeConfig(ge), config.EVM().Transactions(), gconfig.Database(), ks, txBuilder, lggr, stuckTxDetector, ht) ec.SetResumeCallback(fn) servicetest.Run(t, ec) return ec diff --git a/core/chains/evm/txmgr/evm_tx_store.go b/core/chains/evm/txmgr/evm_tx_store.go index e83a83907e..fa2251168d 100644 --- a/core/chains/evm/txmgr/evm_tx_store.go +++ b/core/chains/evm/txmgr/evm_tx_store.go @@ -34,8 +34,8 @@ import ( var ( ErrKeyNotUpdated = errors.New("evmTxStore: Key not updated") - // ErrCouldNotGetReceipt is the error string we save if we reach our finality depth for a confirmed transaction without ever getting a receipt - // This most likely happened because an external wallet used the account for this nonce + // ErrCouldNotGetReceipt is the error string we save if we reach our LatestFinalizedBlockNum for a confirmed transaction + // without ever getting a receipt. This most likely happened because an external wallet used the account for this nonce ErrCouldNotGetReceipt = "could not get receipt" ) @@ -44,6 +44,10 @@ type EvmTxStore interface { // redeclare TxStore for mockery txmgrtypes.TxStore[common.Address, *big.Int, common.Hash, common.Hash, *evmtypes.Receipt, evmtypes.Nonce, gas.EvmFee] TxStoreWebApi + + // methods used solely in EVM components + FindConfirmedTxesReceipts(ctx context.Context, finalizedBlockNum int64, chainID *big.Int) (receipts []Receipt, err error) + UpdateTxStatesToFinalizedUsingReceiptIds(ctx context.Context, etxIDs []int64, chainId *big.Int) error } // TxStoreWebApi encapsulates the methods that are not used by the txmgr and only used by the various web controllers, readers, or evm specific components @@ -87,7 +91,7 @@ var _ TestEvmTxStore = (*evmTxStore)(nil) // Directly maps to columns of database table "evm.receipts". // Do not modify type unless you // intend to modify the database schema -type dbReceipt struct { +type DbReceipt struct { ID int64 TxHash common.Hash BlockHash common.Hash @@ -97,8 +101,8 @@ type dbReceipt struct { CreatedAt time.Time } -func DbReceiptFromEvmReceipt(evmReceipt *evmtypes.Receipt) dbReceipt { - return dbReceipt{ +func DbReceiptFromEvmReceipt(evmReceipt *evmtypes.Receipt) DbReceipt { + return DbReceipt{ TxHash: evmReceipt.TxHash, BlockHash: evmReceipt.BlockHash, BlockNumber: evmReceipt.BlockNumber.Int64(), @@ -107,7 +111,7 @@ func DbReceiptFromEvmReceipt(evmReceipt *evmtypes.Receipt) dbReceipt { } } -func DbReceiptToEvmReceipt(receipt *dbReceipt) *evmtypes.Receipt { +func DbReceiptToEvmReceipt(receipt *DbReceipt) *evmtypes.Receipt { return &receipt.Receipt } @@ -131,7 +135,7 @@ type dbReceiptPlus struct { FailOnRevert bool `db:"FailOnRevert"` } -func fromDBReceipts(rs []dbReceipt) []*evmtypes.Receipt { +func fromDBReceipts(rs []DbReceipt) []*evmtypes.Receipt { receipts := make([]*evmtypes.Receipt, len(rs)) for i := 0; i < len(rs); i++ { receipts[i] = DbReceiptToEvmReceipt(&rs[i]) @@ -665,10 +669,8 @@ func (o *evmTxStore) loadEthTxAttemptsReceipts(ctx context.Context, etx *Tx) (er return o.loadEthTxesAttemptsReceipts(ctx, []*Tx{etx}) } -func (o *evmTxStore) loadEthTxesAttemptsReceipts(ctx context.Context, etxs []*Tx) (err error) { - if len(etxs) == 0 { - return nil - } +// initEthTxesAttempts takes an input txes slice, return an initialized attempt map and attemptHashes slice +func initEthTxesAttempts(etxs []*Tx) (map[common.Hash]*TxAttempt, [][]byte) { attemptHashM := make(map[common.Hash]*TxAttempt, len(etxs)) // len here is lower bound attemptHashes := make([][]byte, len(etxs)) // len here is lower bound for _, etx := range etxs { @@ -677,7 +679,17 @@ func (o *evmTxStore) loadEthTxesAttemptsReceipts(ctx context.Context, etxs []*Tx attemptHashes = append(attemptHashes, attempt.Hash.Bytes()) } } - var rs []dbReceipt + + return attemptHashM, attemptHashes +} + +func (o *evmTxStore) loadEthTxesAttemptsReceipts(ctx context.Context, etxs []*Tx) (err error) { + if len(etxs) == 0 { + return nil + } + + attemptHashM, attemptHashes := initEthTxesAttempts(etxs) + var rs []DbReceipt if err = o.q.SelectContext(ctx, &rs, `SELECT * FROM evm.receipts WHERE tx_hash = ANY($1)`, pq.Array(attemptHashes)); err != nil { return pkgerrors.Wrap(err, "loadEthTxesAttemptsReceipts failed to load evm.receipts") } @@ -693,6 +705,37 @@ func (o *evmTxStore) loadEthTxesAttemptsReceipts(ctx context.Context, etxs []*Tx return nil } +// loadEthTxesAttemptsWithPartialReceipts loads ethTxes with attempts and partial receipts values for optimization +func (o *evmTxStore) loadEthTxesAttemptsWithPartialReceipts(ctx context.Context, etxs []*Tx) (err error) { + if len(etxs) == 0 { + return nil + } + + attemptHashM, attemptHashes := initEthTxesAttempts(etxs) + var rs []DbReceipt + if err = o.q.SelectContext(ctx, &rs, `SELECT evm.receipts.block_hash, evm.receipts.block_number, evm.receipts.transaction_index, evm.receipts.tx_hash FROM evm.receipts WHERE tx_hash = ANY($1)`, pq.Array(attemptHashes)); err != nil { + return pkgerrors.Wrap(err, "loadEthTxesAttemptsReceipts failed to load evm.receipts") + } + + receipts := make([]*evmtypes.Receipt, len(rs)) + for i := 0; i < len(rs); i++ { + receipts[i] = &evmtypes.Receipt{ + BlockHash: rs[i].BlockHash, + BlockNumber: big.NewInt(rs[i].BlockNumber), + TransactionIndex: rs[i].TransactionIndex, + TxHash: rs[i].TxHash, + } + } + + for _, receipt := range receipts { + attempt := attemptHashM[receipt.TxHash] + // Although the attempts struct supports multiple receipts, the expectation for EVM is that there is only one receipt + // per tx and therefore attempt too. + attempt.Receipts = append(attempt.Receipts, receipt) + } + return nil +} + func loadConfirmedAttemptsReceipts(ctx context.Context, q sqlutil.DataSource, attempts []TxAttempt) error { byHash := make(map[string]*TxAttempt, len(attempts)) hashes := make([][]byte, len(attempts)) @@ -700,7 +743,7 @@ func loadConfirmedAttemptsReceipts(ctx context.Context, q sqlutil.DataSource, at byHash[attempt.Hash.String()] = &attempts[i] hashes = append(hashes, attempt.Hash.Bytes()) } - var rs []dbReceipt + var rs []DbReceipt if err := q.SelectContext(ctx, &rs, `SELECT * FROM evm.receipts WHERE tx_hash = ANY($1)`, pq.Array(hashes)); err != nil { return pkgerrors.Wrap(err, "loadConfirmedAttemptsReceipts failed to load evm.receipts") } @@ -955,11 +998,11 @@ func (o *evmTxStore) SaveFetchedReceipts(ctx context.Context, r []*evmtypes.Rece // NOTE: We continue to attempt to resend evm.txes in this state on // every head to guard against the extremely rare scenario of nonce gap due to // reorg that excludes the transaction (from another wallet) that had this -// nonce (until finality depth is reached, after which we make the explicit +// nonce (until LatestFinalizedBlockNum is reached, after which we make the explicit // decision to give up). This is done in the EthResender. // // We will continue to try to fetch a receipt for these attempts until all -// attempts are below the finality depth from current head. +// attempts are equal to or below the LatestFinalizedBlockNum from current head. func (o *evmTxStore) MarkAllConfirmedMissingReceipt(ctx context.Context, chainID *big.Int) (err error) { var cancel context.CancelFunc ctx, cancel = o.stopCh.Ctx(ctx) @@ -1116,7 +1159,7 @@ func updateEthTxAttemptUnbroadcast(ctx context.Context, orm *evmTxStore, attempt func updateEthTxUnconfirm(ctx context.Context, orm *evmTxStore, etx Tx) error { if etx.State != txmgr.TxConfirmed { - return errors.New("expected eth_tx state to be confirmed") + return errors.New("expected tx state to be confirmed") } _, err := orm.q.ExecContext(ctx, `UPDATE evm.txes SET state = 'unconfirmed' WHERE id = $1`, etx.ID) return pkgerrors.Wrap(err, "updateEthTxUnconfirm failed") @@ -1168,7 +1211,9 @@ ORDER BY nonce ASC if err = orm.LoadTxesAttempts(ctx, etxs); err != nil { return pkgerrors.Wrap(err, "FindTransactionsConfirmedInBlockRange failed to load evm.tx_attempts") } - err = orm.loadEthTxesAttemptsReceipts(ctx, etxs) + + // retrieve tx with attempts and partial receipt values for optimization purpose + err = orm.loadEthTxesAttemptsWithPartialReceipts(ctx, etxs) return pkgerrors.Wrap(err, "FindTransactionsConfirmedInBlockRange failed to load evm.receipts") }) return etxs, pkgerrors.Wrap(err, "FindTransactionsConfirmedInBlockRange failed") @@ -1205,24 +1250,6 @@ AND evm_chain_id = $1`, chainID.String()).Scan(&earliestUnconfirmedTxBlock) return earliestUnconfirmedTxBlock, err } -func (o *evmTxStore) IsTxFinalized(ctx context.Context, blockHeight int64, txID int64, chainID *big.Int) (finalized bool, err error) { - var cancel context.CancelFunc - ctx, cancel = o.stopCh.Ctx(ctx) - defer cancel() - - var count int32 - err = o.q.GetContext(ctx, &count, ` - SELECT COUNT(evm.receipts.receipt) FROM evm.txes - INNER JOIN evm.tx_attempts ON evm.txes.id = evm.tx_attempts.eth_tx_id - INNER JOIN evm.receipts ON evm.tx_attempts.hash = evm.receipts.tx_hash - WHERE evm.receipts.block_number <= ($1 - evm.txes.min_confirmations) - AND evm.txes.id = $2 AND evm.txes.evm_chain_id = $3`, blockHeight, txID, chainID.String()) - if err != nil { - return false, fmt.Errorf("failed to retrieve transaction reciepts: %w", err) - } - return count > 0, nil -} - func (o *evmTxStore) saveAttemptWithNewState(ctx context.Context, attempt TxAttempt, broadcastAt time.Time) error { var dbAttempt DbEthTxAttempt dbAttempt.FromTxAttempt(&attempt) @@ -1444,23 +1471,18 @@ ORDER BY nonce ASC // markOldTxesMissingReceiptAsErrored // -// Once eth_tx has all of its attempts broadcast before some cutoff threshold +// Once eth_tx has all of its attempts broadcast equal to or before latestFinalizedBlockNum // without receiving any receipts, we mark it as fatally errored (never sent). // // The job run will also be marked as errored in this case since we never got a // receipt and thus cannot pass on any transaction hash -func (o *evmTxStore) MarkOldTxesMissingReceiptAsErrored(ctx context.Context, blockNum int64, finalityDepth uint32, chainID *big.Int) error { +func (o *evmTxStore) MarkOldTxesMissingReceiptAsErrored(ctx context.Context, blockNum int64, latestFinalizedBlockNum int64, chainID *big.Int) error { var cancel context.CancelFunc ctx, cancel = o.stopCh.Ctx(ctx) defer cancel() - // cutoffBlockNum is a block height - // Any 'confirmed_missing_receipt' eth_tx with all attempts older than this block height will be marked as errored - // We will not try to query for receipts for this transaction any more - cutoff := blockNum - int64(finalityDepth) - if cutoff <= 0 { - return nil - } - if cutoff <= 0 { + // Any 'confirmed_missing_receipt' eth_tx with all attempts equal to or older than latestFinalizedBlockNum will be marked as errored + // We will not try to query for receipts for this transaction anymore + if latestFinalizedBlockNum <= 0 { return nil } // note: if QOpt passes in a sql.Tx this will reuse it @@ -1480,12 +1502,12 @@ FROM ( WHERE e2.state = 'confirmed_missing_receipt' AND e2.evm_chain_id = $3 GROUP BY e2.id - HAVING max(evm.tx_attempts.broadcast_before_block_num) < $2 + HAVING max(evm.tx_attempts.broadcast_before_block_num) <= $2 ) FOR UPDATE OF e1 ) e0 WHERE e0.id = evm.txes.id -RETURNING e0.id, e0.nonce`, ErrCouldNotGetReceipt, cutoff, chainID.String()) +RETURNING e0.id, e0.nonce`, ErrCouldNotGetReceipt, latestFinalizedBlockNum, chainID.String()) if err != nil { return pkgerrors.Wrap(err, "markOldTxesMissingReceiptAsErrored failed to query") @@ -1872,7 +1894,7 @@ id < ( return } -func (o *evmTxStore) ReapTxHistory(ctx context.Context, minBlockNumberToKeep int64, timeThreshold time.Time, chainID *big.Int) error { +func (o *evmTxStore) ReapTxHistory(ctx context.Context, timeThreshold time.Time, chainID *big.Int) error { var cancel context.CancelFunc ctx, cancel = o.stopCh.Ctx(ctx) defer cancel() @@ -1885,19 +1907,18 @@ func (o *evmTxStore) ReapTxHistory(ctx context.Context, minBlockNumberToKeep int res, err := o.q.ExecContext(ctx, ` WITH old_enough_receipts AS ( SELECT tx_hash FROM evm.receipts - WHERE block_number < $1 ORDER BY block_number ASC, id ASC - LIMIT $2 + LIMIT $1 ) DELETE FROM evm.txes USING old_enough_receipts, evm.tx_attempts WHERE evm.tx_attempts.eth_tx_id = evm.txes.id AND evm.tx_attempts.hash = old_enough_receipts.tx_hash -AND evm.txes.created_at < $3 -AND evm.txes.state = 'confirmed' -AND evm_chain_id = $4`, minBlockNumberToKeep, limit, timeThreshold, chainID.String()) +AND evm.txes.created_at < $2 +AND evm.txes.state = 'finalized' +AND evm_chain_id = $3`, limit, timeThreshold, chainID.String()) if err != nil { - return count, pkgerrors.Wrap(err, "ReapTxes failed to delete old confirmed evm.txes") + return count, pkgerrors.Wrap(err, "ReapTxes failed to delete old finalized evm.txes") } rowsAffected, err := res.RowsAffected() if err != nil { @@ -1906,7 +1927,7 @@ AND evm_chain_id = $4`, minBlockNumberToKeep, limit, timeThreshold, chainID.Stri return uint(rowsAffected), err }, batchSize) if err != nil { - return pkgerrors.Wrap(err, "TxmReaper#reapEthTxes batch delete of confirmed evm.txes failed") + return pkgerrors.Wrap(err, "TxmReaper#reapEthTxes batch delete of finalized evm.txes failed") } // Delete old 'fatal_error' evm.txes err = sqlutil.Batch(func(_, limit uint) (count uint, err error) { @@ -1927,6 +1948,38 @@ AND evm_chain_id = $2`, timeThreshold, chainID.String()) if err != nil { return pkgerrors.Wrap(err, "TxmReaper#reapEthTxes batch delete of fatally errored evm.txes failed") } + // Delete old 'confirmed' evm.txes that were never finalized + // This query should never result in changes but added just in case transactions slip through the cracks + // to avoid them building up in the DB + err = sqlutil.Batch(func(_, limit uint) (count uint, err error) { + res, err := o.q.ExecContext(ctx, ` +WITH old_enough_receipts AS ( + SELECT tx_hash FROM evm.receipts + ORDER BY block_number ASC, id ASC + LIMIT $1 +) +DELETE FROM evm.txes +USING old_enough_receipts, evm.tx_attempts +WHERE evm.tx_attempts.eth_tx_id = evm.txes.id +AND evm.tx_attempts.hash = old_enough_receipts.tx_hash +AND evm.txes.created_at < $2 +AND evm.txes.state = 'confirmed' +AND evm_chain_id = $3`, limit, timeThreshold, chainID.String()) + if err != nil { + return count, pkgerrors.Wrap(err, "ReapTxes failed to delete old confirmed evm.txes") + } + rowsAffected, err := res.RowsAffected() + if err != nil { + return count, pkgerrors.Wrap(err, "ReapTxes failed to get rows affected") + } + if rowsAffected > 0 { + o.logger.Errorf("%d confirmed transactions were reaped before being marked as finalized. This should never happen unless the threshold is set too low or the transactions were lost track of", rowsAffected) + } + return uint(rowsAffected), err + }, batchSize) + if err != nil { + return pkgerrors.Wrap(err, "TxmReaper#reapEthTxes batch delete of confirmed evm.txes failed") + } return nil } @@ -2055,3 +2108,36 @@ func (o *evmTxStore) UpdateTxAttemptBroadcastBeforeBlockNum(ctx context.Context, _, err := o.q.ExecContext(ctx, sql, blockNum, id) return err } + +// FindConfirmedTxesReceipts Returns all confirmed transactions with receipt block nums older than or equal to the finalized block number +func (o *evmTxStore) FindConfirmedTxesReceipts(ctx context.Context, finalizedBlockNum int64, chainID *big.Int) (receipts []Receipt, err error) { + var cancel context.CancelFunc + ctx, cancel = o.stopCh.Ctx(ctx) + defer cancel() + + // note the receipts are partially loaded for performance reason + query := `SELECT evm.receipts.id, evm.receipts.tx_hash, evm.receipts.block_hash, evm.receipts.block_number FROM evm.receipts + INNER JOIN evm.tx_attempts ON evm.tx_attempts.hash = evm.receipts.tx_hash + INNER JOIN evm.txes ON evm.txes.id = evm.tx_attempts.eth_tx_id + WHERE evm.txes.state = 'confirmed' AND evm.receipts.block_number <= $1 AND evm.txes.evm_chain_id = $2` + err = o.q.SelectContext(ctx, &receipts, query, finalizedBlockNum, chainID.String()) + return receipts, err +} + +// Mark transactions corresponding to receipt IDs as finalized +func (o *evmTxStore) UpdateTxStatesToFinalizedUsingReceiptIds(ctx context.Context, receiptIDs []int64, chainId *big.Int) error { + if len(receiptIDs) == 0 { + return nil + } + var cancel context.CancelFunc + ctx, cancel = o.stopCh.Ctx(ctx) + defer cancel() + sql := ` +UPDATE evm.txes SET state = 'finalized' WHERE evm.txes.evm_chain_id = $1 AND evm.txes.id IN (SELECT evm.txes.id FROM evm.txes + INNER JOIN evm.tx_attempts ON evm.tx_attempts.eth_tx_id = evm.txes.id + INNER JOIN evm.receipts ON evm.receipts.tx_hash = evm.tx_attempts.hash + WHERE evm.receipts.id = ANY($2)) +` + _, err := o.q.ExecContext(ctx, sql, chainId.String(), pq.Array(receiptIDs)) + return err +} diff --git a/core/chains/evm/txmgr/evm_tx_store_test.go b/core/chains/evm/txmgr/evm_tx_store_test.go index afb8de4ca5..e47387fb8d 100644 --- a/core/chains/evm/txmgr/evm_tx_store_test.go +++ b/core/chains/evm/txmgr/evm_tx_store_test.go @@ -628,19 +628,20 @@ func TestORM_FindTxesPendingCallback(t *testing.T) { pgtest.MustExec(t, db, `SET CONSTRAINTS fk_pipeline_runs_pruning_key DEFERRED`) pgtest.MustExec(t, db, `SET CONSTRAINTS pipeline_runs_pipeline_spec_id_fkey DEFERRED`) + h8 := &evmtypes.Head{ + Number: 8, + Hash: testutils.NewHash(), + } + h9 := &evmtypes.Head{ + Hash: testutils.NewHash(), + Number: 9, + } + h9.Parent.Store(h8) head := evmtypes.Head{ - Hash: utils.NewHash(), + Hash: testutils.NewHash(), Number: 10, - Parent: &evmtypes.Head{ - Hash: utils.NewHash(), - Number: 9, - Parent: &evmtypes.Head{ - Number: 8, - Hash: utils.NewHash(), - Parent: nil, - }, - }, } + head.Parent.Store(h9) minConfirmations := int64(2) @@ -783,30 +784,6 @@ func TestORM_UpdateTxForRebroadcast(t *testing.T) { }) } -func TestORM_IsTxFinalized(t *testing.T) { - t.Parallel() - - db := pgtest.NewSqlxDB(t) - txStore := cltest.NewTestTxStore(t, db) - ethClient := evmtest.NewEthClientMockWithDefaultChain(t) - - t.Run("confirmed tx not past finality_depth", func(t *testing.T) { - confirmedAddr := cltest.MustGenerateRandomKey(t).Address - tx := mustInsertConfirmedEthTxWithReceipt(t, txStore, confirmedAddr, 123, 1) - finalized, err := txStore.IsTxFinalized(tests.Context(t), 2, tx.ID, ethClient.ConfiguredChainID()) - require.NoError(t, err) - require.False(t, finalized) - }) - - t.Run("confirmed tx past finality_depth", func(t *testing.T) { - confirmedAddr := cltest.MustGenerateRandomKey(t).Address - tx := mustInsertConfirmedEthTxWithReceipt(t, txStore, confirmedAddr, 123, 1) - finalized, err := txStore.IsTxFinalized(tests.Context(t), 10, tx.ID, ethClient.ConfiguredChainID()) - require.NoError(t, err) - require.True(t, finalized) - }) -} - func TestORM_FindTransactionsConfirmedInBlockRange(t *testing.T) { t.Parallel() @@ -816,19 +793,20 @@ func TestORM_FindTransactionsConfirmedInBlockRange(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) _, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) + h8 := &evmtypes.Head{ + Number: 8, + Hash: testutils.NewHash(), + } + h9 := &evmtypes.Head{ + Hash: testutils.NewHash(), + Number: 9, + } + h9.Parent.Store(h8) head := evmtypes.Head{ - Hash: utils.NewHash(), + Hash: testutils.NewHash(), Number: 10, - Parent: &evmtypes.Head{ - Hash: utils.NewHash(), - Number: 9, - Parent: &evmtypes.Head{ - Number: 8, - Hash: utils.NewHash(), - Parent: nil, - }, - }, } + head.Parent.Store(h9) t.Run("find all transactions confirmed in range", func(t *testing.T) { etx_8 := mustInsertConfirmedEthTxWithReceipt(t, txStore, fromAddress, 700, 8) @@ -840,6 +818,12 @@ func TestORM_FindTransactionsConfirmedInBlockRange(t *testing.T) { assert.Equal(t, etxes[0].Sequence, etx_8.Sequence) assert.Equal(t, etxes[1].Sequence, etx_9.Sequence) }) + + t.Run("return empty txes when no transactions in range found", func(t *testing.T) { + etxes, err := txStore.FindTransactionsConfirmedInBlockRange(tests.Context(t), 0, 0, ethClient.ConfiguredChainID()) + require.NoError(t, err) + assert.Len(t, etxes, 0) + }) } func TestORM_FindEarliestUnconfirmedBroadcastTime(t *testing.T) { @@ -1141,13 +1125,14 @@ func TestORM_MarkOldTxesMissingReceiptAsErrored(t *testing.T) { ethKeyStore := cltest.NewKeyStore(t, db).Eth() ethClient := evmtest.NewEthClientMockWithDefaultChain(t) _, fromAddress := cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) + latestFinalizedBlockNum := int64(8) // tx state should be confirmed missing receipt - // attempt should be broadcast before cutoff time + // attempt should be before latestFinalizedBlockNum t.Run("successfully mark errored transactions", func(t *testing.T) { etx := mustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt(t, txStore, 1, 7, time.Now(), fromAddress) - err := txStore.MarkOldTxesMissingReceiptAsErrored(tests.Context(t), 10, 2, ethClient.ConfiguredChainID()) + err := txStore.MarkOldTxesMissingReceiptAsErrored(tests.Context(t), 10, latestFinalizedBlockNum, ethClient.ConfiguredChainID()) require.NoError(t, err) etx, err = txStore.FindTxWithAttempts(ctx, etx.ID) @@ -1157,7 +1142,7 @@ func TestORM_MarkOldTxesMissingReceiptAsErrored(t *testing.T) { t.Run("successfully mark errored transactions w/ qopt passing in sql.Tx", func(t *testing.T) { etx := mustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt(t, txStore, 1, 7, time.Now(), fromAddress) - err := txStore.MarkOldTxesMissingReceiptAsErrored(tests.Context(t), 10, 2, ethClient.ConfiguredChainID()) + err := txStore.MarkOldTxesMissingReceiptAsErrored(tests.Context(t), 10, latestFinalizedBlockNum, ethClient.ConfiguredChainID()) require.NoError(t, err) // must run other query outside of postgres transaction so changes are committed @@ -1382,7 +1367,7 @@ func TestORM_UpdateTxUnstartedToInProgress(t *testing.T) { evmTxmCfg := txmgr.NewEvmTxmConfig(ccfg.EVM()) ec := evmtest.NewEthClientMockWithDefaultChain(t) txMgr := txmgr.NewEvmTxm(ec.ConfiguredChainID(), evmTxmCfg, ccfg.EVM().Transactions(), nil, logger.Test(t), nil, nil, - nil, txStore, nil, nil, nil, nil) + nil, txStore, nil, nil, nil, nil, nil) err := txMgr.XXXTestAbandon(fromAddress) // mark transaction as abandoned require.NoError(t, err) @@ -1871,3 +1856,60 @@ func AssertCountPerSubject(t *testing.T, txStore txmgr.TestEvmTxStore, expected require.NoError(t, err) require.Equal(t, int(expected), count) } + +func TestORM_FindTransactionsByState(t *testing.T) { + t.Parallel() + + ctx := tests.Context(t) + db := pgtest.NewSqlxDB(t) + txStore := cltest.NewTestTxStore(t, db) + kst := cltest.NewKeyStore(t, db) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + finalizedBlockNum := int64(100) + + mustInsertUnstartedTx(t, txStore, fromAddress) + mustInsertInProgressEthTxWithAttempt(t, txStore, 0, fromAddress) + mustInsertUnconfirmedEthTxWithAttemptState(t, txStore, 1, fromAddress, txmgrtypes.TxAttemptBroadcast) + mustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt(t, txStore, 2, finalizedBlockNum, time.Now(), fromAddress) + mustInsertConfirmedEthTxWithReceipt(t, txStore, fromAddress, 3, finalizedBlockNum+1) + mustInsertConfirmedEthTxWithReceipt(t, txStore, fromAddress, 4, finalizedBlockNum) + mustInsertFatalErrorEthTx(t, txStore, fromAddress) + + receipts, err := txStore.FindConfirmedTxesReceipts(ctx, finalizedBlockNum, testutils.FixtureChainID) + require.NoError(t, err) + require.Len(t, receipts, 1) +} + +func TestORM_UpdateTxesFinalized(t *testing.T) { + t.Parallel() + + ctx := tests.Context(t) + db := pgtest.NewSqlxDB(t) + txStore := cltest.NewTestTxStore(t, db) + kst := cltest.NewKeyStore(t, db) + broadcast := time.Now() + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + t.Run("successfully finalizes a confirmed transaction", func(t *testing.T) { + nonce := evmtypes.Nonce(0) + tx := &txmgr.Tx{ + Sequence: &nonce, + FromAddress: fromAddress, + EncodedPayload: []byte{1, 2, 3}, + State: txmgrcommon.TxConfirmed, + BroadcastAt: &broadcast, + InitialBroadcastAt: &broadcast, + } + err := txStore.InsertTx(ctx, tx) + require.NoError(t, err) + attempt := newBroadcastLegacyEthTxAttempt(t, tx.ID) + err = txStore.InsertTxAttempt(ctx, &attempt) + require.NoError(t, err) + receipt := mustInsertEthReceipt(t, txStore, 100, testutils.NewHash(), attempt.Hash) + err = txStore.UpdateTxStatesToFinalizedUsingReceiptIds(ctx, []int64{receipt.ID}, testutils.FixtureChainID) + require.NoError(t, err) + etx, err := txStore.FindTxWithAttempts(ctx, tx.ID) + require.NoError(t, err) + require.Equal(t, txmgrcommon.TxFinalized, etx.State) + }) +} diff --git a/core/chains/evm/txmgr/finalizer.go b/core/chains/evm/txmgr/finalizer.go new file mode 100644 index 0000000000..6074463615 --- /dev/null +++ b/core/chains/evm/txmgr/finalizer.go @@ -0,0 +1,295 @@ +package txmgr + +import ( + "context" + "fmt" + "math/big" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rpc" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox" + + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" +) + +var _ Finalizer = (*evmFinalizer)(nil) + +// processHeadTimeout represents a sanity limit on how long ProcessHead should take to complete +const processHeadTimeout = 10 * time.Minute + +type finalizerTxStore interface { + FindConfirmedTxesReceipts(ctx context.Context, finalizedBlockNum int64, chainID *big.Int) ([]Receipt, error) + UpdateTxStatesToFinalizedUsingReceiptIds(ctx context.Context, txs []int64, chainId *big.Int) error +} + +type finalizerChainClient interface { + BatchCallContext(ctx context.Context, elems []rpc.BatchElem) error +} + +type finalizerHeadTracker interface { + LatestAndFinalizedBlock(ctx context.Context) (latest, finalized *evmtypes.Head, err error) +} + +// Finalizer handles processing new finalized blocks and marking transactions as finalized accordingly in the TXM DB +type evmFinalizer struct { + services.StateMachine + lggr logger.SugaredLogger + chainId *big.Int + rpcBatchSize int + + txStore finalizerTxStore + client finalizerChainClient + headTracker finalizerHeadTracker + + mb *mailbox.Mailbox[*evmtypes.Head] + stopCh services.StopChan + wg sync.WaitGroup + + lastProcessedFinalizedBlockNum int64 +} + +func NewEvmFinalizer( + lggr logger.Logger, + chainId *big.Int, + rpcBatchSize uint32, + txStore finalizerTxStore, + client finalizerChainClient, + headTracker finalizerHeadTracker, +) *evmFinalizer { + lggr = logger.Named(lggr, "Finalizer") + return &evmFinalizer{ + lggr: logger.Sugared(lggr), + chainId: chainId, + rpcBatchSize: int(rpcBatchSize), + txStore: txStore, + client: client, + headTracker: headTracker, + mb: mailbox.NewSingle[*evmtypes.Head](), + } +} + +// Start the finalizer +func (f *evmFinalizer) Start(ctx context.Context) error { + return f.StartOnce("Finalizer", func() error { + f.lggr.Debugf("started Finalizer with RPC batch size limit: %d", f.rpcBatchSize) + f.stopCh = make(chan struct{}) + f.wg.Add(1) + go f.runLoop() + return nil + }) +} + +// Close the finalizer +func (f *evmFinalizer) Close() error { + return f.StopOnce("Finalizer", func() error { + f.lggr.Debug("closing Finalizer") + close(f.stopCh) + f.wg.Wait() + return nil + }) +} + +func (f *evmFinalizer) Name() string { + return f.lggr.Name() +} + +func (f *evmFinalizer) HealthReport() map[string]error { + return map[string]error{f.Name(): f.Healthy()} +} + +func (f *evmFinalizer) runLoop() { + defer f.wg.Done() + ctx, cancel := f.stopCh.NewCtx() + defer cancel() + for { + select { + case <-f.mb.Notify(): + for { + if ctx.Err() != nil { + return + } + head, exists := f.mb.Retrieve() + if !exists { + break + } + if err := f.ProcessHead(ctx, head); err != nil { + f.lggr.Errorw("Error processing head", "err", err) + f.SvcErrBuffer.Append(err) + continue + } + } + case <-ctx.Done(): + return + } + } +} + +func (f *evmFinalizer) DeliverLatestHead(head *evmtypes.Head) bool { + return f.mb.Deliver(head) +} + +func (f *evmFinalizer) ProcessHead(ctx context.Context, head *evmtypes.Head) error { + ctx, cancel := context.WithTimeout(ctx, processHeadTimeout) + defer cancel() + _, latestFinalizedHead, err := f.headTracker.LatestAndFinalizedBlock(ctx) + if err != nil { + return fmt.Errorf("failed to retrieve latest finalized head: %w", err) + } + return f.processFinalizedHead(ctx, latestFinalizedHead) +} + +// Determines if any confirmed transactions can be marked as finalized by comparing their receipts against the latest finalized block +func (f *evmFinalizer) processFinalizedHead(ctx context.Context, latestFinalizedHead *evmtypes.Head) error { + // Cannot determine finality without a finalized head for comparison + if latestFinalizedHead == nil || !latestFinalizedHead.IsValid() { + return fmt.Errorf("invalid latestFinalizedHead") + } + // Only continue processing if the latestFinalizedHead has not already been processed + // Helps avoid unnecessary processing on every head if blocks are finalized in batches + if latestFinalizedHead.BlockNumber() == f.lastProcessedFinalizedBlockNum { + return nil + } + if latestFinalizedHead.BlockNumber() < f.lastProcessedFinalizedBlockNum { + f.lggr.Errorw("Received finalized block older than one already processed. This should never happen and could be an issue with RPCs.", "lastProcessedFinalizedBlockNum", f.lastProcessedFinalizedBlockNum, "retrievedFinalizedBlockNum", latestFinalizedHead.BlockNumber()) + return nil + } + + earliestBlockNumInChain := latestFinalizedHead.EarliestHeadInChain().BlockNumber() + f.lggr.Debugw("processing latest finalized head", "blockNum", latestFinalizedHead.BlockNumber(), "blockHash", latestFinalizedHead.BlockHash(), "earliestBlockNumInChain", earliestBlockNumInChain) + + // Retrieve all confirmed transactions with receipts older than or equal to the finalized block, loaded with attempts and receipts + unfinalizedReceipts, err := f.txStore.FindConfirmedTxesReceipts(ctx, latestFinalizedHead.BlockNumber(), f.chainId) + if err != nil { + return fmt.Errorf("failed to retrieve receipts for confirmed, unfinalized transactions: %w", err) + } + + var finalizedReceipts []Receipt + // Group by block hash transactions whose receipts cannot be validated using the cached heads + blockNumToReceiptsMap := make(map[int64][]Receipt) + // Find transactions with receipt block nums older than the latest finalized block num and block hashes still in chain + for _, receipt := range unfinalizedReceipts { + // The tx store query ensures transactions have receipts but leaving this check here for a belts and braces approach + if receipt.TxHash == utils.EmptyHash || receipt.BlockHash == utils.EmptyHash { + f.lggr.AssumptionViolationw("invalid receipt found for confirmed transaction", "receipt", receipt) + continue + } + // The tx store query only returns transactions with receipts older than or equal to the finalized block but leaving this check here for a belts and braces approach + if receipt.BlockNumber > latestFinalizedHead.BlockNumber() { + continue + } + // Receipt block num older than earliest head in chain. Validate hash using RPC call later + if receipt.BlockNumber < earliestBlockNumInChain { + blockNumToReceiptsMap[receipt.BlockNumber] = append(blockNumToReceiptsMap[receipt.BlockNumber], receipt) + continue + } + blockHashInChain := latestFinalizedHead.HashAtHeight(receipt.BlockNumber) + // Receipt block hash does not match the block hash in chain. Transaction has been re-org'd out but DB state has not been updated yet + if blockHashInChain.String() != receipt.BlockHash.String() { + // Log error if a transaction is marked as confirmed with a receipt older than the finalized block + // This scenario could potentially point to a re-org'd transaction the Confirmer has lost track of + f.lggr.Errorw("found confirmed transaction with re-org'd receipt older than finalized block", "receipt", receipt, "onchainBlockHash", blockHashInChain.String()) + continue + } + finalizedReceipts = append(finalizedReceipts, receipt) + } + + // Check if block hashes exist for receipts on-chain older than the earliest cached head + // Transactions are grouped by their receipt block hash to avoid repeat requests on the same hash in case transactions were confirmed in the same block + validatedReceipts := f.batchCheckReceiptHashesOnchain(ctx, blockNumToReceiptsMap) + finalizedReceipts = append(finalizedReceipts, validatedReceipts...) + + receiptIDs := f.buildReceiptIdList(finalizedReceipts) + + err = f.txStore.UpdateTxStatesToFinalizedUsingReceiptIds(ctx, receiptIDs, f.chainId) + if err != nil { + return fmt.Errorf("failed to update transactions as finalized: %w", err) + } + // Update lastProcessedFinalizedBlockNum after processing has completed to allow failed processing to retry on subsequent heads + // Does not need to be protected with mutex lock because the Finalizer only runs in a single loop + f.lastProcessedFinalizedBlockNum = latestFinalizedHead.BlockNumber() + return nil +} + +func (f *evmFinalizer) batchCheckReceiptHashesOnchain(ctx context.Context, blockNumToReceiptsMap map[int64][]Receipt) []Receipt { + if len(blockNumToReceiptsMap) == 0 { + return nil + } + // Group the RPC batch calls in groups of rpcBatchSize + var rpcBatchGroups [][]rpc.BatchElem + var rpcBatch []rpc.BatchElem + for blockNum := range blockNumToReceiptsMap { + elem := rpc.BatchElem{ + Method: "eth_getBlockByNumber", + Args: []any{ + hexutil.EncodeBig(big.NewInt(blockNum)), + false, + }, + Result: new(evmtypes.Head), + } + rpcBatch = append(rpcBatch, elem) + if len(rpcBatch) >= f.rpcBatchSize { + rpcBatchGroups = append(rpcBatchGroups, rpcBatch) + rpcBatch = []rpc.BatchElem{} + } + } + if len(rpcBatch) > 0 { + rpcBatchGroups = append(rpcBatchGroups, rpcBatch) + } + + var finalizedReceipts []Receipt + for _, rpcBatch := range rpcBatchGroups { + err := f.client.BatchCallContext(ctx, rpcBatch) + if err != nil { + // Continue if batch RPC call failed so other batches can still be considered for finalization + f.lggr.Errorw("failed to find blocks due to batch call failure", "error", err) + continue + } + for _, req := range rpcBatch { + if req.Error != nil { + // Continue if particular RPC call failed so other txs can still be considered for finalization + f.lggr.Errorw("failed to find block by number", "blockNum", req.Args[0], "error", req.Error) + continue + } + head, ok := req.Result.(*evmtypes.Head) + if !ok || !head.IsValid() { + // Continue if particular RPC call yielded a nil block so other txs can still be considered for finalization + f.lggr.Errorw("retrieved nil head for block number", "blockNum", req.Args[0]) + continue + } + receipts := blockNumToReceiptsMap[head.BlockNumber()] + // Check if transaction receipts match the block hash at the given block num + // If they do not, the transactions may have been re-org'd out + // The expectation is for the Confirmer to pick up on these re-orgs and get the transaction included + for _, receipt := range receipts { + if receipt.BlockHash.String() == head.BlockHash().String() { + finalizedReceipts = append(finalizedReceipts, receipt) + } else { + // Log error if a transaction is marked as confirmed with a receipt older than the finalized block + // This scenario could potentially point to a re-org'd transaction the Confirmer has lost track of + f.lggr.Errorw("found confirmed transaction with re-org'd receipt older than finalized block", "receipt", receipt, "onchainBlockHash", head.BlockHash().String()) + } + } + } + } + return finalizedReceipts +} + +// Build list of transaction IDs +func (f *evmFinalizer) buildReceiptIdList(finalizedReceipts []Receipt) []int64 { + receiptIds := make([]int64, len(finalizedReceipts)) + for i, receipt := range finalizedReceipts { + f.lggr.Debugw("transaction considered finalized", + "txHash", receipt.TxHash.String(), + "receiptBlockNum", receipt.BlockNumber, + "receiptBlockHash", receipt.BlockHash.String(), + ) + receiptIds[i] = receipt.ID + } + return receiptIds +} diff --git a/core/chains/evm/txmgr/finalizer_test.go b/core/chains/evm/txmgr/finalizer_test.go new file mode 100644 index 0000000000..b91121d773 --- /dev/null +++ b/core/chains/evm/txmgr/finalizer_test.go @@ -0,0 +1,241 @@ +package txmgr_test + +import ( + "errors" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rpc" + "github.com/google/uuid" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services/servicetest" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + + txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/testutils" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" +) + +func TestFinalizer_MarkTxFinalized(t *testing.T) { + t.Parallel() + ctx := tests.Context(t) + db := pgtest.NewSqlxDB(t) + txStore := cltest.NewTestTxStore(t, db) + ethKeyStore := cltest.NewKeyStore(t, db).Eth() + feeLimit := uint64(10_000) + ethClient := testutils.NewEthClientMockWithDefaultChain(t) + rpcBatchSize := uint32(1) + ht := headtracker.NewSimulatedHeadTracker(ethClient, true, 0) + + h99 := &evmtypes.Head{ + Hash: utils.NewHash(), + Number: 99, + } + h99.IsFinalized.Store(true) + head := &evmtypes.Head{ + Hash: utils.NewHash(), + Number: 100, + } + head.Parent.Store(h99) + + t.Run("returns not finalized for tx with receipt newer than finalized block", func(t *testing.T) { + finalizer := txmgr.NewEvmFinalizer(logger.Test(t), testutils.FixtureChainID, rpcBatchSize, txStore, ethClient, ht) + servicetest.Run(t, finalizer) + + idempotencyKey := uuid.New().String() + _, fromAddress := cltest.MustInsertRandomKey(t, ethKeyStore) + nonce := evmtypes.Nonce(0) + broadcast := time.Now() + tx := &txmgr.Tx{ + Sequence: &nonce, + IdempotencyKey: &idempotencyKey, + FromAddress: fromAddress, + EncodedPayload: []byte{1, 2, 3}, + FeeLimit: feeLimit, + State: txmgrcommon.TxConfirmed, + BroadcastAt: &broadcast, + InitialBroadcastAt: &broadcast, + } + attemptHash := insertTxAndAttemptWithIdempotencyKey(t, txStore, tx, idempotencyKey) + // Insert receipt for unfinalized block num + mustInsertEthReceipt(t, txStore, head.Number, head.Hash, attemptHash) + ethClient.On("HeadByNumber", mock.Anything, mock.Anything).Return(head, nil).Once() + ethClient.On("LatestFinalizedBlock", mock.Anything).Return(head.Parent.Load(), nil).Once() + err := finalizer.ProcessHead(ctx, head) + require.NoError(t, err) + tx, err = txStore.FindTxWithIdempotencyKey(ctx, idempotencyKey, testutils.FixtureChainID) + require.NoError(t, err) + require.Equal(t, txmgrcommon.TxConfirmed, tx.State) + }) + + t.Run("returns not finalized for tx with receipt re-org'd out", func(t *testing.T) { + finalizer := txmgr.NewEvmFinalizer(logger.Test(t), testutils.FixtureChainID, rpcBatchSize, txStore, ethClient, ht) + servicetest.Run(t, finalizer) + + idempotencyKey := uuid.New().String() + _, fromAddress := cltest.MustInsertRandomKey(t, ethKeyStore) + nonce := evmtypes.Nonce(0) + broadcast := time.Now() + tx := &txmgr.Tx{ + Sequence: &nonce, + IdempotencyKey: &idempotencyKey, + FromAddress: fromAddress, + EncodedPayload: []byte{1, 2, 3}, + FeeLimit: feeLimit, + State: txmgrcommon.TxConfirmed, + BroadcastAt: &broadcast, + InitialBroadcastAt: &broadcast, + } + attemptHash := insertTxAndAttemptWithIdempotencyKey(t, txStore, tx, idempotencyKey) + // Insert receipt for finalized block num + mustInsertEthReceipt(t, txStore, head.Parent.Load().Number, utils.NewHash(), attemptHash) + ethClient.On("HeadByNumber", mock.Anything, mock.Anything).Return(head, nil).Once() + ethClient.On("LatestFinalizedBlock", mock.Anything).Return(head.Parent.Load(), nil).Once() + err := finalizer.ProcessHead(ctx, head) + require.NoError(t, err) + tx, err = txStore.FindTxWithIdempotencyKey(ctx, idempotencyKey, testutils.FixtureChainID) + require.NoError(t, err) + require.Equal(t, txmgrcommon.TxConfirmed, tx.State) + }) + + t.Run("returns finalized for tx with receipt in a finalized block", func(t *testing.T) { + finalizer := txmgr.NewEvmFinalizer(logger.Test(t), testutils.FixtureChainID, rpcBatchSize, txStore, ethClient, ht) + servicetest.Run(t, finalizer) + + idempotencyKey := uuid.New().String() + _, fromAddress := cltest.MustInsertRandomKey(t, ethKeyStore) + nonce := evmtypes.Nonce(0) + broadcast := time.Now() + tx := &txmgr.Tx{ + Sequence: &nonce, + IdempotencyKey: &idempotencyKey, + FromAddress: fromAddress, + EncodedPayload: []byte{1, 2, 3}, + FeeLimit: feeLimit, + State: txmgrcommon.TxConfirmed, + BroadcastAt: &broadcast, + InitialBroadcastAt: &broadcast, + } + attemptHash := insertTxAndAttemptWithIdempotencyKey(t, txStore, tx, idempotencyKey) + // Insert receipt for finalized block num + mustInsertEthReceipt(t, txStore, head.Parent.Load().Number, head.Parent.Load().Hash, attemptHash) + ethClient.On("HeadByNumber", mock.Anything, mock.Anything).Return(head, nil).Once() + ethClient.On("LatestFinalizedBlock", mock.Anything).Return(head.Parent.Load(), nil).Once() + err := finalizer.ProcessHead(ctx, head) + require.NoError(t, err) + tx, err = txStore.FindTxWithIdempotencyKey(ctx, idempotencyKey, testutils.FixtureChainID) + require.NoError(t, err) + require.Equal(t, txmgrcommon.TxFinalized, tx.State) + }) + + t.Run("returns finalized for tx with receipt older than block history depth", func(t *testing.T) { + finalizer := txmgr.NewEvmFinalizer(logger.Test(t), testutils.FixtureChainID, rpcBatchSize, txStore, ethClient, ht) + servicetest.Run(t, finalizer) + + idempotencyKey := uuid.New().String() + _, fromAddress := cltest.MustInsertRandomKey(t, ethKeyStore) + nonce := evmtypes.Nonce(0) + broadcast := time.Now() + tx := &txmgr.Tx{ + Sequence: &nonce, + IdempotencyKey: &idempotencyKey, + FromAddress: fromAddress, + EncodedPayload: []byte{1, 2, 3}, + FeeLimit: feeLimit, + State: txmgrcommon.TxConfirmed, + BroadcastAt: &broadcast, + InitialBroadcastAt: &broadcast, + } + attemptHash := insertTxAndAttemptWithIdempotencyKey(t, txStore, tx, idempotencyKey) + // Insert receipt for finalized block num + receiptBlockHash1 := utils.NewHash() + mustInsertEthReceipt(t, txStore, head.Parent.Load().Number-2, receiptBlockHash1, attemptHash) + idempotencyKey = uuid.New().String() + nonce = evmtypes.Nonce(1) + tx = &txmgr.Tx{ + Sequence: &nonce, + IdempotencyKey: &idempotencyKey, + FromAddress: fromAddress, + EncodedPayload: []byte{1, 2, 3}, + FeeLimit: feeLimit, + State: txmgrcommon.TxConfirmed, + BroadcastAt: &broadcast, + InitialBroadcastAt: &broadcast, + } + attemptHash = insertTxAndAttemptWithIdempotencyKey(t, txStore, tx, idempotencyKey) + // Insert receipt for finalized block num + receiptBlockHash2 := utils.NewHash() + mustInsertEthReceipt(t, txStore, head.Parent.Load().Number-1, receiptBlockHash2, attemptHash) + // Separate batch calls will be made for each tx due to RPC batch size set to 1 when finalizer initialized above + ethClient.On("BatchCallContext", mock.Anything, mock.IsType([]rpc.BatchElem{})).Run(func(args mock.Arguments) { + rpcElements := args.Get(1).([]rpc.BatchElem) + require.Equal(t, 1, len(rpcElements)) + + require.Equal(t, "eth_getBlockByNumber", rpcElements[0].Method) + require.Equal(t, false, rpcElements[0].Args[1]) + + reqBlockNum := rpcElements[0].Args[0].(string) + req1BlockNum := hexutil.EncodeBig(big.NewInt(head.Parent.Load().Number - 2)) + req2BlockNum := hexutil.EncodeBig(big.NewInt(head.Parent.Load().Number - 1)) + var headResult evmtypes.Head + if req1BlockNum == reqBlockNum { + headResult = evmtypes.Head{Number: head.Parent.Load().Number - 2, Hash: receiptBlockHash1} + } else if req2BlockNum == reqBlockNum { + headResult = evmtypes.Head{Number: head.Parent.Load().Number - 1, Hash: receiptBlockHash2} + } else { + require.Fail(t, "unrecognized block hash") + } + rpcElements[0].Result = &headResult + }).Return(nil).Twice() + ethClient.On("HeadByNumber", mock.Anything, mock.Anything).Return(head, nil).Once() + ethClient.On("LatestFinalizedBlock", mock.Anything).Return(head.Parent.Load(), nil).Once() + err := finalizer.ProcessHead(ctx, head) + require.NoError(t, err) + tx, err = txStore.FindTxWithIdempotencyKey(ctx, idempotencyKey, testutils.FixtureChainID) + require.NoError(t, err) + require.Equal(t, txmgrcommon.TxFinalized, tx.State) + }) + + t.Run("returns error if failed to retrieve latest head in headtracker", func(t *testing.T) { + finalizer := txmgr.NewEvmFinalizer(logger.Test(t), testutils.FixtureChainID, rpcBatchSize, txStore, ethClient, ht) + servicetest.Run(t, finalizer) + + ethClient.On("HeadByNumber", mock.Anything, mock.Anything).Return(nil, errors.New("failed to get latest head")).Once() + err := finalizer.ProcessHead(ctx, head) + require.Error(t, err) + }) + + t.Run("returns error if failed to calculate latest finalized head in headtracker", func(t *testing.T) { + finalizer := txmgr.NewEvmFinalizer(logger.Test(t), testutils.FixtureChainID, rpcBatchSize, txStore, ethClient, ht) + servicetest.Run(t, finalizer) + + ethClient.On("HeadByNumber", mock.Anything, mock.Anything).Return(head, nil).Once() + ethClient.On("LatestFinalizedBlock", mock.Anything).Return(nil, errors.New("failed to calculate latest finalized head")).Once() + err := finalizer.ProcessHead(ctx, head) + require.Error(t, err) + }) +} + +func insertTxAndAttemptWithIdempotencyKey(t *testing.T, txStore txmgr.TestEvmTxStore, tx *txmgr.Tx, idempotencyKey string) common.Hash { + ctx := tests.Context(t) + err := txStore.InsertTx(ctx, tx) + require.NoError(t, err) + tx, err = txStore.FindTxWithIdempotencyKey(ctx, idempotencyKey, testutils.FixtureChainID) + require.NoError(t, err) + attempt := cltest.NewLegacyEthTxAttempt(t, tx.ID) + err = txStore.InsertTxAttempt(ctx, &attempt) + require.NoError(t, err) + return attempt.Hash +} diff --git a/core/chains/evm/txmgr/mocks/evm_tx_store.go b/core/chains/evm/txmgr/mocks/evm_tx_store.go index b28e55ec32..a9a175e3d9 100644 --- a/core/chains/evm/txmgr/mocks/evm_tx_store.go +++ b/core/chains/evm/txmgr/mocks/evm_tx_store.go @@ -18,6 +18,8 @@ import ( time "time" + txmgr "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" + types "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" uuid "github.com/google/uuid" @@ -444,6 +446,66 @@ func (_c *EvmTxStore_DeleteInProgressAttempt_Call) RunAndReturn(run func(context return _c } +// FindConfirmedTxesReceipts provides a mock function with given fields: ctx, finalizedBlockNum, chainID +func (_m *EvmTxStore) FindConfirmedTxesReceipts(ctx context.Context, finalizedBlockNum int64, chainID *big.Int) ([]txmgr.DbReceipt, error) { + ret := _m.Called(ctx, finalizedBlockNum, chainID) + + if len(ret) == 0 { + panic("no return value specified for FindConfirmedTxesReceipts") + } + + var r0 []txmgr.DbReceipt + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64, *big.Int) ([]txmgr.DbReceipt, error)); ok { + return rf(ctx, finalizedBlockNum, chainID) + } + if rf, ok := ret.Get(0).(func(context.Context, int64, *big.Int) []txmgr.DbReceipt); ok { + r0 = rf(ctx, finalizedBlockNum, chainID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]txmgr.DbReceipt) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, int64, *big.Int) error); ok { + r1 = rf(ctx, finalizedBlockNum, chainID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EvmTxStore_FindConfirmedTxesReceipts_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindConfirmedTxesReceipts' +type EvmTxStore_FindConfirmedTxesReceipts_Call struct { + *mock.Call +} + +// FindConfirmedTxesReceipts is a helper method to define mock.On call +// - ctx context.Context +// - finalizedBlockNum int64 +// - chainID *big.Int +func (_e *EvmTxStore_Expecter) FindConfirmedTxesReceipts(ctx interface{}, finalizedBlockNum interface{}, chainID interface{}) *EvmTxStore_FindConfirmedTxesReceipts_Call { + return &EvmTxStore_FindConfirmedTxesReceipts_Call{Call: _e.mock.On("FindConfirmedTxesReceipts", ctx, finalizedBlockNum, chainID)} +} + +func (_c *EvmTxStore_FindConfirmedTxesReceipts_Call) Run(run func(ctx context.Context, finalizedBlockNum int64, chainID *big.Int)) *EvmTxStore_FindConfirmedTxesReceipts_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(int64), args[2].(*big.Int)) + }) + return _c +} + +func (_c *EvmTxStore_FindConfirmedTxesReceipts_Call) Return(receipts []txmgr.DbReceipt, err error) *EvmTxStore_FindConfirmedTxesReceipts_Call { + _c.Call.Return(receipts, err) + return _c +} + +func (_c *EvmTxStore_FindConfirmedTxesReceipts_Call) RunAndReturn(run func(context.Context, int64, *big.Int) ([]txmgr.DbReceipt, error)) *EvmTxStore_FindConfirmedTxesReceipts_Call { + _c.Call.Return(run) + return _c +} + // FindEarliestUnconfirmedBroadcastTime provides a mock function with given fields: ctx, chainID func (_m *EvmTxStore) FindEarliestUnconfirmedBroadcastTime(ctx context.Context, chainID *big.Int) (null.Time, error) { ret := _m.Called(ctx, chainID) @@ -2058,65 +2120,6 @@ func (_c *EvmTxStore_HasInProgressTransaction_Call) RunAndReturn(run func(contex return _c } -// IsTxFinalized provides a mock function with given fields: ctx, blockHeight, txID, chainID -func (_m *EvmTxStore) IsTxFinalized(ctx context.Context, blockHeight int64, txID int64, chainID *big.Int) (bool, error) { - ret := _m.Called(ctx, blockHeight, txID, chainID) - - if len(ret) == 0 { - panic("no return value specified for IsTxFinalized") - } - - var r0 bool - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, int64, int64, *big.Int) (bool, error)); ok { - return rf(ctx, blockHeight, txID, chainID) - } - if rf, ok := ret.Get(0).(func(context.Context, int64, int64, *big.Int) bool); ok { - r0 = rf(ctx, blockHeight, txID, chainID) - } else { - r0 = ret.Get(0).(bool) - } - - if rf, ok := ret.Get(1).(func(context.Context, int64, int64, *big.Int) error); ok { - r1 = rf(ctx, blockHeight, txID, chainID) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// EvmTxStore_IsTxFinalized_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsTxFinalized' -type EvmTxStore_IsTxFinalized_Call struct { - *mock.Call -} - -// IsTxFinalized is a helper method to define mock.On call -// - ctx context.Context -// - blockHeight int64 -// - txID int64 -// - chainID *big.Int -func (_e *EvmTxStore_Expecter) IsTxFinalized(ctx interface{}, blockHeight interface{}, txID interface{}, chainID interface{}) *EvmTxStore_IsTxFinalized_Call { - return &EvmTxStore_IsTxFinalized_Call{Call: _e.mock.On("IsTxFinalized", ctx, blockHeight, txID, chainID)} -} - -func (_c *EvmTxStore_IsTxFinalized_Call) Run(run func(ctx context.Context, blockHeight int64, txID int64, chainID *big.Int)) *EvmTxStore_IsTxFinalized_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(int64), args[2].(int64), args[3].(*big.Int)) - }) - return _c -} - -func (_c *EvmTxStore_IsTxFinalized_Call) Return(finalized bool, err error) *EvmTxStore_IsTxFinalized_Call { - _c.Call.Return(finalized, err) - return _c -} - -func (_c *EvmTxStore_IsTxFinalized_Call) RunAndReturn(run func(context.Context, int64, int64, *big.Int) (bool, error)) *EvmTxStore_IsTxFinalized_Call { - _c.Call.Return(run) - return _c -} - // LoadTxAttempts provides a mock function with given fields: ctx, etx func (_m *EvmTxStore) LoadTxAttempts(ctx context.Context, etx *types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee]) error { ret := _m.Called(ctx, etx) @@ -2211,17 +2214,17 @@ func (_c *EvmTxStore_MarkAllConfirmedMissingReceipt_Call) RunAndReturn(run func( return _c } -// MarkOldTxesMissingReceiptAsErrored provides a mock function with given fields: ctx, blockNum, finalityDepth, chainID -func (_m *EvmTxStore) MarkOldTxesMissingReceiptAsErrored(ctx context.Context, blockNum int64, finalityDepth uint32, chainID *big.Int) error { - ret := _m.Called(ctx, blockNum, finalityDepth, chainID) +// MarkOldTxesMissingReceiptAsErrored provides a mock function with given fields: ctx, blockNum, latestFinalizedBlockNum, chainID +func (_m *EvmTxStore) MarkOldTxesMissingReceiptAsErrored(ctx context.Context, blockNum int64, latestFinalizedBlockNum int64, chainID *big.Int) error { + ret := _m.Called(ctx, blockNum, latestFinalizedBlockNum, chainID) if len(ret) == 0 { panic("no return value specified for MarkOldTxesMissingReceiptAsErrored") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, int64, uint32, *big.Int) error); ok { - r0 = rf(ctx, blockNum, finalityDepth, chainID) + if rf, ok := ret.Get(0).(func(context.Context, int64, int64, *big.Int) error); ok { + r0 = rf(ctx, blockNum, latestFinalizedBlockNum, chainID) } else { r0 = ret.Error(0) } @@ -2237,15 +2240,15 @@ type EvmTxStore_MarkOldTxesMissingReceiptAsErrored_Call struct { // MarkOldTxesMissingReceiptAsErrored is a helper method to define mock.On call // - ctx context.Context // - blockNum int64 -// - finalityDepth uint32 +// - latestFinalizedBlockNum int64 // - chainID *big.Int -func (_e *EvmTxStore_Expecter) MarkOldTxesMissingReceiptAsErrored(ctx interface{}, blockNum interface{}, finalityDepth interface{}, chainID interface{}) *EvmTxStore_MarkOldTxesMissingReceiptAsErrored_Call { - return &EvmTxStore_MarkOldTxesMissingReceiptAsErrored_Call{Call: _e.mock.On("MarkOldTxesMissingReceiptAsErrored", ctx, blockNum, finalityDepth, chainID)} +func (_e *EvmTxStore_Expecter) MarkOldTxesMissingReceiptAsErrored(ctx interface{}, blockNum interface{}, latestFinalizedBlockNum interface{}, chainID interface{}) *EvmTxStore_MarkOldTxesMissingReceiptAsErrored_Call { + return &EvmTxStore_MarkOldTxesMissingReceiptAsErrored_Call{Call: _e.mock.On("MarkOldTxesMissingReceiptAsErrored", ctx, blockNum, latestFinalizedBlockNum, chainID)} } -func (_c *EvmTxStore_MarkOldTxesMissingReceiptAsErrored_Call) Run(run func(ctx context.Context, blockNum int64, finalityDepth uint32, chainID *big.Int)) *EvmTxStore_MarkOldTxesMissingReceiptAsErrored_Call { +func (_c *EvmTxStore_MarkOldTxesMissingReceiptAsErrored_Call) Run(run func(ctx context.Context, blockNum int64, latestFinalizedBlockNum int64, chainID *big.Int)) *EvmTxStore_MarkOldTxesMissingReceiptAsErrored_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(int64), args[2].(uint32), args[3].(*big.Int)) + run(args[0].(context.Context), args[1].(int64), args[2].(int64), args[3].(*big.Int)) }) return _c } @@ -2255,7 +2258,7 @@ func (_c *EvmTxStore_MarkOldTxesMissingReceiptAsErrored_Call) Return(_a0 error) return _c } -func (_c *EvmTxStore_MarkOldTxesMissingReceiptAsErrored_Call) RunAndReturn(run func(context.Context, int64, uint32, *big.Int) error) *EvmTxStore_MarkOldTxesMissingReceiptAsErrored_Call { +func (_c *EvmTxStore_MarkOldTxesMissingReceiptAsErrored_Call) RunAndReturn(run func(context.Context, int64, int64, *big.Int) error) *EvmTxStore_MarkOldTxesMissingReceiptAsErrored_Call { _c.Call.Return(run) return _c } @@ -2367,17 +2370,17 @@ func (_c *EvmTxStore_PruneUnstartedTxQueue_Call) RunAndReturn(run func(context.C return _c } -// ReapTxHistory provides a mock function with given fields: ctx, minBlockNumberToKeep, timeThreshold, chainID -func (_m *EvmTxStore) ReapTxHistory(ctx context.Context, minBlockNumberToKeep int64, timeThreshold time.Time, chainID *big.Int) error { - ret := _m.Called(ctx, minBlockNumberToKeep, timeThreshold, chainID) +// ReapTxHistory provides a mock function with given fields: ctx, timeThreshold, chainID +func (_m *EvmTxStore) ReapTxHistory(ctx context.Context, timeThreshold time.Time, chainID *big.Int) error { + ret := _m.Called(ctx, timeThreshold, chainID) if len(ret) == 0 { panic("no return value specified for ReapTxHistory") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, int64, time.Time, *big.Int) error); ok { - r0 = rf(ctx, minBlockNumberToKeep, timeThreshold, chainID) + if rf, ok := ret.Get(0).(func(context.Context, time.Time, *big.Int) error); ok { + r0 = rf(ctx, timeThreshold, chainID) } else { r0 = ret.Error(0) } @@ -2392,16 +2395,15 @@ type EvmTxStore_ReapTxHistory_Call struct { // ReapTxHistory is a helper method to define mock.On call // - ctx context.Context -// - minBlockNumberToKeep int64 // - timeThreshold time.Time // - chainID *big.Int -func (_e *EvmTxStore_Expecter) ReapTxHistory(ctx interface{}, minBlockNumberToKeep interface{}, timeThreshold interface{}, chainID interface{}) *EvmTxStore_ReapTxHistory_Call { - return &EvmTxStore_ReapTxHistory_Call{Call: _e.mock.On("ReapTxHistory", ctx, minBlockNumberToKeep, timeThreshold, chainID)} +func (_e *EvmTxStore_Expecter) ReapTxHistory(ctx interface{}, timeThreshold interface{}, chainID interface{}) *EvmTxStore_ReapTxHistory_Call { + return &EvmTxStore_ReapTxHistory_Call{Call: _e.mock.On("ReapTxHistory", ctx, timeThreshold, chainID)} } -func (_c *EvmTxStore_ReapTxHistory_Call) Run(run func(ctx context.Context, minBlockNumberToKeep int64, timeThreshold time.Time, chainID *big.Int)) *EvmTxStore_ReapTxHistory_Call { +func (_c *EvmTxStore_ReapTxHistory_Call) Run(run func(ctx context.Context, timeThreshold time.Time, chainID *big.Int)) *EvmTxStore_ReapTxHistory_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(int64), args[2].(time.Time), args[3].(*big.Int)) + run(args[0].(context.Context), args[1].(time.Time), args[2].(*big.Int)) }) return _c } @@ -2411,7 +2413,7 @@ func (_c *EvmTxStore_ReapTxHistory_Call) Return(_a0 error) *EvmTxStore_ReapTxHis return _c } -func (_c *EvmTxStore_ReapTxHistory_Call) RunAndReturn(run func(context.Context, int64, time.Time, *big.Int) error) *EvmTxStore_ReapTxHistory_Call { +func (_c *EvmTxStore_ReapTxHistory_Call) RunAndReturn(run func(context.Context, time.Time, *big.Int) error) *EvmTxStore_ReapTxHistory_Call { _c.Call.Return(run) return _c } @@ -3197,6 +3199,54 @@ func (_c *EvmTxStore_UpdateTxForRebroadcast_Call) RunAndReturn(run func(context. return _c } +// UpdateTxStatesToFinalizedUsingReceiptIds provides a mock function with given fields: ctx, etxIDs, chainId +func (_m *EvmTxStore) UpdateTxStatesToFinalizedUsingReceiptIds(ctx context.Context, etxIDs []int64, chainId *big.Int) error { + ret := _m.Called(ctx, etxIDs, chainId) + + if len(ret) == 0 { + panic("no return value specified for UpdateTxStatesToFinalizedUsingReceiptIds") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, []int64, *big.Int) error); ok { + r0 = rf(ctx, etxIDs, chainId) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// EvmTxStore_UpdateTxStatesToFinalizedUsingReceiptIds_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateTxStatesToFinalizedUsingReceiptIds' +type EvmTxStore_UpdateTxStatesToFinalizedUsingReceiptIds_Call struct { + *mock.Call +} + +// UpdateTxStatesToFinalizedUsingReceiptIds is a helper method to define mock.On call +// - ctx context.Context +// - etxIDs []int64 +// - chainId *big.Int +func (_e *EvmTxStore_Expecter) UpdateTxStatesToFinalizedUsingReceiptIds(ctx interface{}, etxIDs interface{}, chainId interface{}) *EvmTxStore_UpdateTxStatesToFinalizedUsingReceiptIds_Call { + return &EvmTxStore_UpdateTxStatesToFinalizedUsingReceiptIds_Call{Call: _e.mock.On("UpdateTxStatesToFinalizedUsingReceiptIds", ctx, etxIDs, chainId)} +} + +func (_c *EvmTxStore_UpdateTxStatesToFinalizedUsingReceiptIds_Call) Run(run func(ctx context.Context, etxIDs []int64, chainId *big.Int)) *EvmTxStore_UpdateTxStatesToFinalizedUsingReceiptIds_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].([]int64), args[2].(*big.Int)) + }) + return _c +} + +func (_c *EvmTxStore_UpdateTxStatesToFinalizedUsingReceiptIds_Call) Return(_a0 error) *EvmTxStore_UpdateTxStatesToFinalizedUsingReceiptIds_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *EvmTxStore_UpdateTxStatesToFinalizedUsingReceiptIds_Call) RunAndReturn(run func(context.Context, []int64, *big.Int) error) *EvmTxStore_UpdateTxStatesToFinalizedUsingReceiptIds_Call { + _c.Call.Return(run) + return _c +} + // UpdateTxUnstartedToInProgress provides a mock function with given fields: ctx, etx, attempt func (_m *EvmTxStore) UpdateTxUnstartedToInProgress(ctx context.Context, etx *types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee], attempt *types.TxAttempt[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee]) error { ret := _m.Called(ctx, etx, attempt) diff --git a/core/chains/evm/txmgr/models.go b/core/chains/evm/txmgr/models.go index f8682ffd50..1ba3d193cb 100644 --- a/core/chains/evm/txmgr/models.go +++ b/core/chains/evm/txmgr/models.go @@ -36,12 +36,13 @@ type ( Tx = txmgrtypes.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee] TxMeta = txmgrtypes.TxMeta[common.Address, common.Hash] TxAttempt = txmgrtypes.TxAttempt[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee] - Receipt = dbReceipt // EvmReceipt is the exported DB table model for receipts + Receipt = DbReceipt // DbReceipt is the exported DB table model for receipts ReceiptPlus = txmgrtypes.ReceiptPlus[*evmtypes.Receipt] StuckTxDetector = txmgrtypes.StuckTxDetector[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee] TxmClient = txmgrtypes.TxmClient[*big.Int, common.Address, common.Hash, common.Hash, *evmtypes.Receipt, evmtypes.Nonce, gas.EvmFee] TransactionClient = txmgrtypes.TransactionClient[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee] ChainReceipt = txmgrtypes.ChainReceipt[common.Hash, common.Hash] + Finalizer = txmgrtypes.Finalizer[common.Hash, *evmtypes.Head] ) var _ KeyStore = (keystore.Eth)(nil) // check interface in txmgr to avoid circular import diff --git a/core/chains/evm/txmgr/reaper_test.go b/core/chains/evm/txmgr/reaper_test.go index b3ce48b702..cfaccdf04e 100644 --- a/core/chains/evm/txmgr/reaper_test.go +++ b/core/chains/evm/txmgr/reaper_test.go @@ -12,18 +12,17 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/utils" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" - txmgrmocks "github.com/smartcontractkit/chainlink/v2/common/txmgr/types/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" ) -func newReaperWithChainID(t *testing.T, db txmgrtypes.TxHistoryReaper[*big.Int], cfg txmgrtypes.ReaperChainConfig, txConfig txmgrtypes.ReaperTransactionsConfig, cid *big.Int) *txmgr.Reaper { - return txmgr.NewEvmReaper(logger.Test(t), db, cfg, txConfig, cid) +func newReaperWithChainID(t *testing.T, db txmgrtypes.TxHistoryReaper[*big.Int], txConfig txmgrtypes.ReaperTransactionsConfig, cid *big.Int) *txmgr.Reaper { + return txmgr.NewEvmReaper(logger.Test(t), db, txConfig, cid) } -func newReaper(t *testing.T, db txmgrtypes.TxHistoryReaper[*big.Int], cfg txmgrtypes.ReaperChainConfig, txConfig txmgrtypes.ReaperTransactionsConfig) *txmgr.Reaper { - return newReaperWithChainID(t, db, cfg, txConfig, &cltest.FixtureChainID) +func newReaper(t *testing.T, db txmgrtypes.TxHistoryReaper[*big.Int], txConfig txmgrtypes.ReaperTransactionsConfig) *txmgr.Reaper { + return newReaperWithChainID(t, db, txConfig, &cltest.FixtureChainID) } type reaperConfig struct { @@ -51,12 +50,9 @@ func TestReaper_ReapTxes(t *testing.T) { oneDayAgo := time.Now().Add(-24 * time.Hour) t.Run("with nothing in the database, doesn't error", func(t *testing.T) { - config := txmgrmocks.NewReaperConfig(t) - config.On("FinalityDepth").Return(uint32(10)) - tc := &reaperConfig{reaperThreshold: 1 * time.Hour} - r := newReaper(t, txStore, config, tc) + r := newReaper(t, txStore, tc) err := r.ReapTxes(42) assert.NoError(t, err) @@ -66,11 +62,9 @@ func TestReaper_ReapTxes(t *testing.T) { mustInsertConfirmedEthTxWithReceipt(t, txStore, from, nonce, 5) t.Run("skips if threshold=0", func(t *testing.T) { - config := txmgrmocks.NewReaperConfig(t) - tc := &reaperConfig{reaperThreshold: 0 * time.Second} - r := newReaper(t, txStore, config, tc) + r := newReaper(t, txStore, tc) err := r.ReapTxes(42) assert.NoError(t, err) @@ -79,12 +73,9 @@ func TestReaper_ReapTxes(t *testing.T) { }) t.Run("doesn't touch ethtxes with different chain ID", func(t *testing.T) { - config := txmgrmocks.NewReaperConfig(t) - config.On("FinalityDepth").Return(uint32(10)) - tc := &reaperConfig{reaperThreshold: 1 * time.Hour} - r := newReaperWithChainID(t, txStore, config, tc, big.NewInt(42)) + r := newReaperWithChainID(t, txStore, tc, big.NewInt(42)) err := r.ReapTxes(42) assert.NoError(t, err) @@ -92,41 +83,30 @@ func TestReaper_ReapTxes(t *testing.T) { cltest.AssertCount(t, db, "evm.txes", 1) }) - t.Run("deletes confirmed evm.txes that exceed the age threshold with at least EVM.FinalityDepth blocks above their receipt", func(t *testing.T) { - config := txmgrmocks.NewReaperConfig(t) - config.On("FinalityDepth").Return(uint32(10)) - + t.Run("deletes finalized evm.txes that exceed the age threshold", func(t *testing.T) { tc := &reaperConfig{reaperThreshold: 1 * time.Hour} - r := newReaper(t, txStore, config, tc) + r := newReaper(t, txStore, tc) err := r.ReapTxes(42) assert.NoError(t, err) // Didn't delete because eth_tx was not old enough cltest.AssertCount(t, db, "evm.txes", 1) - pgtest.MustExec(t, db, `UPDATE evm.txes SET created_at=$1`, oneDayAgo) - - err = r.ReapTxes(12) - assert.NoError(t, err) - // Didn't delete because eth_tx although old enough, was still within EVM.FinalityDepth of the current head - cltest.AssertCount(t, db, "evm.txes", 1) + pgtest.MustExec(t, db, `UPDATE evm.txes SET created_at=$1, state='finalized'`, oneDayAgo) err = r.ReapTxes(42) assert.NoError(t, err) - // Now it deleted because the eth_tx was past EVM.FinalityDepth + // Now it deleted because the eth_tx was past the age threshold cltest.AssertCount(t, db, "evm.txes", 0) }) mustInsertFatalErrorEthTx(t, txStore, from) t.Run("deletes errored evm.txes that exceed the age threshold", func(t *testing.T) { - config := txmgrmocks.NewReaperConfig(t) - config.On("FinalityDepth").Return(uint32(10)) - tc := &reaperConfig{reaperThreshold: 1 * time.Hour} - r := newReaper(t, txStore, config, tc) + r := newReaper(t, txStore, tc) err := r.ReapTxes(42) assert.NoError(t, err) @@ -140,4 +120,24 @@ func TestReaper_ReapTxes(t *testing.T) { // Deleted because it is old enough now cltest.AssertCount(t, db, "evm.txes", 0) }) + + mustInsertConfirmedEthTxWithReceipt(t, txStore, from, 0, 42) + + t.Run("deletes confirmed evm.txes that exceed the age threshold", func(t *testing.T) { + tc := &reaperConfig{reaperThreshold: 1 * time.Hour} + + r := newReaper(t, txStore, tc) + + err := r.ReapTxes(42) + assert.NoError(t, err) + // Didn't delete because eth_tx was not old enough + cltest.AssertCount(t, db, "evm.txes", 1) + + pgtest.MustExec(t, db, `UPDATE evm.txes SET created_at=$1`, oneDayAgo) + + err = r.ReapTxes(42) + assert.NoError(t, err) + // Now it deleted because the eth_tx was past the age threshold + cltest.AssertCount(t, db, "evm.txes", 0) + }) } diff --git a/core/chains/evm/txmgr/stuck_tx_detector.go b/core/chains/evm/txmgr/stuck_tx_detector.go index 362bb6c0a5..26d7643c15 100644 --- a/core/chains/evm/txmgr/stuck_tx_detector.go +++ b/core/chains/evm/txmgr/stuck_tx_detector.go @@ -409,7 +409,6 @@ func (d *stuckTxDetector) SetPurgeBlockNum(fromAddress common.Address, blockNum d.purgeBlockNumMap[fromAddress] = blockNum } -func (d *stuckTxDetector) StuckTxFatalError() *string { - errorMsg := client.TerminallyStuckMsg - return &errorMsg +func (d *stuckTxDetector) StuckTxFatalError() string { + return client.TerminallyStuckMsg } diff --git a/core/chains/evm/txmgr/test_helpers.go b/core/chains/evm/txmgr/test_helpers.go index b1317cb421..a66727ce13 100644 --- a/core/chains/evm/txmgr/test_helpers.go +++ b/core/chains/evm/txmgr/test_helpers.go @@ -53,6 +53,7 @@ type TestEvmConfig struct { Threshold uint32 MinAttempts uint32 DetectionApiUrl *url.URL + RpcDefaultBatchSize uint32 } func (e *TestEvmConfig) Transactions() evmconfig.Transactions { @@ -65,6 +66,8 @@ func (e *TestEvmConfig) FinalityDepth() uint32 { return 42 } func (e *TestEvmConfig) ChainType() chaintype.ChainType { return "" } +func (e *TestEvmConfig) RPCDefaultBatchSize() uint32 { return e.RpcDefaultBatchSize } + type TestGasEstimatorConfig struct { bumpThreshold uint64 } @@ -152,10 +155,9 @@ type autoPurgeConfig struct { func (a *autoPurgeConfig) Enabled() bool { return false } type MockConfig struct { - EvmConfig *TestEvmConfig - RpcDefaultBatchSize uint32 - finalityDepth uint32 - finalityTagEnabled bool + EvmConfig *TestEvmConfig + finalityDepth uint32 + finalityTagEnabled bool } func (c *MockConfig) EVM() evmconfig.EVM { @@ -167,11 +169,10 @@ func (c *MockConfig) ChainType() chaintype.ChainType { return "" } func (c *MockConfig) FinalityDepth() uint32 { return c.finalityDepth } func (c *MockConfig) SetFinalityDepth(fd uint32) { c.finalityDepth = fd } func (c *MockConfig) FinalityTagEnabled() bool { return c.finalityTagEnabled } -func (c *MockConfig) RPCDefaultBatchSize() uint32 { return c.RpcDefaultBatchSize } func MakeTestConfigs(t *testing.T) (*MockConfig, *TestDatabaseConfig, *TestEvmConfig) { db := &TestDatabaseConfig{defaultQueryTimeout: utils.DefaultQueryTimeout} - ec := &TestEvmConfig{BumpThreshold: 42, MaxInFlight: uint32(42), MaxQueued: uint64(0), ReaperInterval: time.Duration(0), ReaperThreshold: time.Duration(0)} + ec := &TestEvmConfig{BumpThreshold: 42, MaxInFlight: uint32(42), MaxQueued: uint64(0), ReaperInterval: time.Duration(0), ReaperThreshold: time.Duration(0), RpcDefaultBatchSize: uint32(250)} config := &MockConfig{EvmConfig: ec} return config, db, ec } diff --git a/core/chains/evm/txmgr/txmgr_test.go b/core/chains/evm/txmgr/txmgr_test.go index 2314225cb0..e943796031 100644 --- a/core/chains/evm/txmgr/txmgr_test.go +++ b/core/chains/evm/txmgr/txmgr_test.go @@ -85,7 +85,8 @@ func makeTestEvmTxm( lggr, lp, keyStore, - estimator) + estimator, + ht) } func TestTxm_SendNativeToken_DoesNotSendToZero(t *testing.T) { @@ -489,14 +490,20 @@ func TestTxm_Lifecycle(t *testing.T) { config, dbConfig, evmConfig := txmgr.MakeTestConfigs(t) config.SetFinalityDepth(uint32(42)) - config.RpcDefaultBatchSize = uint32(4) + evmConfig.RpcDefaultBatchSize = uint32(4) evmConfig.ResendAfterThreshold = 1 * time.Hour evmConfig.ReaperThreshold = 1 * time.Hour evmConfig.ReaperInterval = 1 * time.Hour kst.On("EnabledAddressesForChain", mock.Anything, &cltest.FixtureChainID).Return([]common.Address{}, nil) + head := cltest.Head(42) + finalizedHead := cltest.Head(0) + + ethClient.On("HeadByNumber", mock.Anything, mock.Anything).Return(head, nil).Once() + ethClient.On("HeadByNumber", mock.Anything, mock.Anything).Return(finalizedHead, nil).Once() + keyChangeCh := make(chan struct{}) unsub := cltest.NewAwaiter() kst.On("SubscribeToKeyChanges", mock.Anything).Return(keyChangeCh, unsub.ItHappened) @@ -505,7 +512,6 @@ func TestTxm_Lifecycle(t *testing.T) { txm, err := makeTestEvmTxm(t, db, ethClient, estimator, evmConfig, evmConfig.GasEstimator(), evmConfig.Transactions(), dbConfig, dbConfig.Listener(), kst) require.NoError(t, err) - head := cltest.Head(42) // It should not hang or panic txm.OnNewLongestChain(tests.Context(t), head) @@ -607,8 +613,22 @@ func TestTxm_GetTransactionStatus(t *testing.T) { gcfg := configtest.NewTestGeneralConfig(t) cfg := evmtest.NewChainScopedConfig(t, gcfg) + h99 := &evmtypes.Head{ + Hash: utils.NewHash(), + Number: 99, + } + h99.IsFinalized.Store(true) + head := &evmtypes.Head{ + Hash: utils.NewHash(), + Number: 100, + } + head.Parent.Store(h99) + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) ethClient.On("PendingNonceAt", mock.Anything, mock.Anything).Return(uint64(0), nil).Maybe() + ethClient.On("HeadByNumber", mock.Anything, mock.Anything).Return(head, nil).Once() + ethClient.On("HeadByNumber", mock.Anything, mock.Anything).Return(head.Parent.Load(), nil).Once() + ethClient.On("HeadByNumber", mock.Anything, mock.Anything).Return(head, nil) feeEstimator := gasmocks.NewEvmFeeEstimator(t) feeEstimator.On("Start", mock.Anything).Return(nil).Once() feeEstimator.On("Close", mock.Anything).Return(nil).Once() @@ -617,15 +637,6 @@ func TestTxm_GetTransactionStatus(t *testing.T) { require.NoError(t, err) servicetest.Run(t, txm) - head := &evmtypes.Head{ - Hash: utils.NewHash(), - Number: 100, - Parent: &evmtypes.Head{ - Hash: utils.NewHash(), - Number: 99, - IsFinalized: true, - }, - } txm.OnNewLongestChain(ctx, head) t.Run("returns error if transaction not found", func(t *testing.T) { @@ -671,7 +682,7 @@ func TestTxm_GetTransactionStatus(t *testing.T) { require.Equal(t, commontypes.Unknown, state) }) - t.Run("returns unconfirmed for unconfirmed state", func(t *testing.T) { + t.Run("returns pending for unconfirmed state", func(t *testing.T) { idempotencyKey := uuid.New().String() _, fromAddress := cltest.MustInsertRandomKey(t, ethKeyStore) nonce := evmtypes.Nonce(0) @@ -690,7 +701,7 @@ func TestTxm_GetTransactionStatus(t *testing.T) { require.NoError(t, err) state, err := txm.GetTransactionStatus(ctx, idempotencyKey) require.NoError(t, err) - require.Equal(t, commontypes.Unconfirmed, state) + require.Equal(t, commontypes.Pending, state) }) t.Run("returns unconfirmed for confirmed state", func(t *testing.T) { @@ -715,14 +726,43 @@ func TestTxm_GetTransactionStatus(t *testing.T) { attempt := cltest.NewLegacyEthTxAttempt(t, tx.ID) err = txStore.InsertTxAttempt(ctx, &attempt) require.NoError(t, err) - // Insert receipt for finalized block num - mustInsertEthReceipt(t, txStore, head.Parent.Number, head.ParentHash, attempt.Hash) + // Insert receipt for unfinalized block num + mustInsertEthReceipt(t, txStore, head.Number, head.Hash, attempt.Hash) state, err := txm.GetTransactionStatus(ctx, idempotencyKey) require.NoError(t, err) require.Equal(t, commontypes.Unconfirmed, state) }) - t.Run("returns unconfirmed for confirmed missing receipt state", func(t *testing.T) { + t.Run("returns finalized for finalized state", func(t *testing.T) { + idempotencyKey := uuid.New().String() + _, fromAddress := cltest.MustInsertRandomKey(t, ethKeyStore) + nonce := evmtypes.Nonce(0) + broadcast := time.Now() + tx := &txmgr.Tx{ + Sequence: &nonce, + IdempotencyKey: &idempotencyKey, + FromAddress: fromAddress, + EncodedPayload: []byte{1, 2, 3}, + FeeLimit: feeLimit, + State: txmgrcommon.TxFinalized, + BroadcastAt: &broadcast, + InitialBroadcastAt: &broadcast, + } + err := txStore.InsertTx(ctx, tx) + require.NoError(t, err) + tx, err = txStore.FindTxWithIdempotencyKey(ctx, idempotencyKey, testutils.FixtureChainID) + require.NoError(t, err) + attempt := cltest.NewLegacyEthTxAttempt(t, tx.ID) + err = txStore.InsertTxAttempt(ctx, &attempt) + require.NoError(t, err) + // Insert receipt for finalized block num + mustInsertEthReceipt(t, txStore, head.Parent.Load().Number, head.Parent.Load().Hash, attempt.Hash) + state, err := txm.GetTransactionStatus(ctx, idempotencyKey) + require.NoError(t, err) + require.Equal(t, commontypes.Finalized, state) + }) + + t.Run("returns pending for confirmed missing receipt state", func(t *testing.T) { idempotencyKey := uuid.New().String() _, fromAddress := cltest.MustInsertRandomKey(t, ethKeyStore) nonce := evmtypes.Nonce(0) @@ -741,7 +781,7 @@ func TestTxm_GetTransactionStatus(t *testing.T) { require.NoError(t, err) state, err := txm.GetTransactionStatus(ctx, idempotencyKey) require.NoError(t, err) - require.Equal(t, commontypes.Unconfirmed, state) + require.Equal(t, commontypes.Pending, state) }) t.Run("returns fatal for fatal error state with terminally stuck error", func(t *testing.T) { @@ -1042,6 +1082,12 @@ func mustCreateUnstartedTxFromEvmTxRequest(t testing.TB, txStore txmgr.EvmTxStor return tx } +func mustInsertUnstartedTx(t testing.TB, txStore txmgr.TestEvmTxStore, fromAddress common.Address) { + etx := cltest.NewEthTx(fromAddress) + ctx := tests.Context(t) + require.NoError(t, txStore.InsertTx(ctx, &etx)) +} + func txRequestWithStrategy(strategy txmgrtypes.TxStrategy) func(*txmgr.TxRequest) { return func(tx *txmgr.TxRequest) { tx.Strategy = strategy diff --git a/core/chains/evm/types/head_test.go b/core/chains/evm/types/head_test.go index 97c536a344..5d887c43c8 100644 --- a/core/chains/evm/types/head_test.go +++ b/core/chains/evm/types/head_test.go @@ -9,6 +9,11 @@ import ( func TestHead_LatestFinalizedHead(t *testing.T) { t.Parallel() + newFinalizedHead := func(num int64) *Head { + result := &Head{Number: num} + result.IsFinalized.Store(true) + return result + } cases := []struct { Name string Head *Head @@ -21,17 +26,17 @@ func TestHead_LatestFinalizedHead(t *testing.T) { }, { Name: "Chain without finalized returns nil", - Head: &Head{Parent: &Head{Parent: &Head{}}}, + Head: sliceToChain(&Head{}, &Head{}, &Head{}), Finalized: nil, }, { Name: "Returns head if it's finalized", - Head: &Head{Number: 2, IsFinalized: true, Parent: &Head{Number: 1, IsFinalized: true}}, + Head: sliceToChain(newFinalizedHead(2), newFinalizedHead(1)), Finalized: &Head{Number: 2}, }, { Name: "Returns first block in chain if it's finalized", - Head: &Head{Number: 3, IsFinalized: false, Parent: &Head{Number: 2, IsFinalized: true, Parent: &Head{Number: 1, IsFinalized: true}}}, + Head: sliceToChain(&Head{Number: 3}, newFinalizedHead(2), newFinalizedHead(1)), Finalized: &Head{Number: 2}, }, } @@ -48,3 +53,43 @@ func TestHead_LatestFinalizedHead(t *testing.T) { }) } } + +func TestHead_ChainString(t *testing.T) { + cases := []struct { + Name string + Chain *Head + ExpectedResult string + }{ + { + Name: "Empty chain", + ExpectedResult: "->nil", + }, + { + Name: "Single head", + Chain: &Head{Number: 1}, + ExpectedResult: "Head{Number: 1, Hash: 0x0000000000000000000000000000000000000000000000000000000000000000, ParentHash: 0x0000000000000000000000000000000000000000000000000000000000000000}->nil", + }, + { + Name: "Multiple heads", + Chain: sliceToChain(&Head{Number: 1}, &Head{Number: 2}, &Head{Number: 3}), + ExpectedResult: "Head{Number: 1, Hash: 0x0000000000000000000000000000000000000000000000000000000000000000, ParentHash: 0x0000000000000000000000000000000000000000000000000000000000000000}->Head{Number: 2, Hash: 0x0000000000000000000000000000000000000000000000000000000000000000, ParentHash: 0x0000000000000000000000000000000000000000000000000000000000000000}->Head{Number: 3, Hash: 0x0000000000000000000000000000000000000000000000000000000000000000, ParentHash: 0x0000000000000000000000000000000000000000000000000000000000000000}->nil", + }, + } + for _, testCase := range cases { + t.Run(testCase.Name, func(t *testing.T) { + assert.Equal(t, testCase.ExpectedResult, testCase.Chain.ChainString()) + }) + } +} + +func sliceToChain(heads ...*Head) *Head { + if len(heads) == 0 { + return nil + } + + for i := 1; i < len(heads); i++ { + heads[i-1].Parent.Store(heads[i]) + } + + return heads[0] +} diff --git a/core/chains/evm/types/models.go b/core/chains/evm/types/models.go index a9e5cd5841..1da8754cec 100644 --- a/core/chains/evm/types/models.go +++ b/core/chains/evm/types/models.go @@ -9,6 +9,7 @@ import ( "math/big" "regexp" "strings" + "sync/atomic" "time" "github.com/ethereum/go-ethereum/common" @@ -34,7 +35,7 @@ type Head struct { Number int64 L1BlockNumber sql.NullInt64 ParentHash common.Hash - Parent *Head + Parent atomic.Pointer[Head] EVMChainID *ubig.Big Timestamp time.Time CreatedAt time.Time @@ -44,7 +45,7 @@ type Head struct { StateRoot common.Hash Difficulty *big.Int TotalDifficulty *big.Int - IsFinalized bool + IsFinalized atomic.Bool } var _ commontypes.Head[common.Hash] = &Head{} @@ -74,10 +75,11 @@ func (h *Head) GetParentHash() common.Hash { } func (h *Head) GetParent() commontypes.Head[common.Hash] { - if h.Parent == nil { - return nil + if parent := h.Parent.Load(); parent != nil { + return parent } - return h.Parent + // explicitly return nil to avoid *Head(nil) + return nil } func (h *Head) GetTimestamp() time.Time { @@ -90,10 +92,11 @@ func (h *Head) BlockDifficulty() *big.Int { // EarliestInChain recurses through parents until it finds the earliest one func (h *Head) EarliestInChain() *Head { - for h.Parent != nil { - h = h.Parent + var earliestInChain *Head + for cur := h; cur != nil; cur = cur.Parent.Load() { + earliestInChain = cur } - return h + return earliestInChain } // EarliestHeadInChain recurses through parents until it finds the earliest one @@ -103,14 +106,10 @@ func (h *Head) EarliestHeadInChain() commontypes.Head[common.Hash] { // IsInChain returns true if the given hash matches the hash of a head in the chain func (h *Head) IsInChain(blockHash common.Hash) bool { - for { - if h.Hash == blockHash { + for cur := h; cur != nil; cur = cur.Parent.Load() { + if cur.Hash == blockHash { return true } - if h.Parent == nil { - break - } - h = h.Parent } return false } @@ -127,32 +126,19 @@ func (h *Head) HashAtHeight(blockNum int64) common.Hash { } func (h *Head) HeadAtHeight(blockNum int64) (commontypes.Head[common.Hash], error) { - for h != nil { - if h.Number == blockNum { - return h, nil + for cur := h; cur != nil; cur = cur.Parent.Load() { + if cur.Number == blockNum { + return cur, nil } - - h = h.Parent } return nil, fmt.Errorf("failed to find head at height %d", blockNum) } // ChainLength returns the length of the chain followed by recursively looking up parents func (h *Head) ChainLength() uint32 { - if h == nil { - return 0 - } - l := uint32(1) - - for { - if h.Parent == nil { - break - } + l := uint32(0) + for cur := h; cur != nil; cur = cur.Parent.Load() { l++ - if h == h.Parent { - panic("circular reference detected") - } - h = h.Parent } return l } @@ -160,29 +146,19 @@ func (h *Head) ChainLength() uint32 { // ChainHashes returns an array of block hashes by recursively looking up parents func (h *Head) ChainHashes() []common.Hash { var hashes []common.Hash - - for { - hashes = append(hashes, h.Hash) - if h.Parent == nil { - break - } - if h == h.Parent { - panic("circular reference detected") - } - h = h.Parent + for cur := h; cur != nil; cur = cur.Parent.Load() { + hashes = append(hashes, cur.Hash) } + return hashes } func (h *Head) LatestFinalizedHead() commontypes.Head[common.Hash] { - for h != nil { - if h.IsFinalized { - return h + for cur := h; cur != nil; cur = cur.Parent.Load() { + if cur.IsFinalized.Load() { + return cur } - - h = h.Parent } - return nil } @@ -200,18 +176,13 @@ func (h *Head) IsValid() bool { func (h *Head) ChainString() string { var sb strings.Builder - - for { - sb.WriteString(h.String()) - if h.Parent == nil { - break - } - if h == h.Parent { - panic("circular reference detected") + for cur := h; cur != nil; cur = cur.Parent.Load() { + if sb.Len() > 0 { + sb.WriteString("->") } - sb.WriteString("->") - h = h.Parent + sb.WriteString(cur.String()) } + sb.WriteString("->nil") return sb.String() } @@ -255,11 +226,11 @@ func (h *Head) AsSlice(k int) (heads []*Head) { if k < 1 || h == nil { return } - heads = make([]*Head, 1) - heads[0] = h - for len(heads) < k && h.Parent != nil { - h = h.Parent - heads = append(heads, h) + heads = make([]*Head, 0, k) + for cur := h; cur != nil; cur = cur.Parent.Load() { + if len(heads) < k { + heads = append(heads, cur) + } } return } diff --git a/core/chains/evm/types/models_test.go b/core/chains/evm/types/models_test.go index 01666d7bda..0b2f8b7141 100644 --- a/core/chains/evm/types/models_test.go +++ b/core/chains/evm/types/models_test.go @@ -116,11 +116,9 @@ func TestEthTxAttempt_GetSignedTx(t *testing.T) { } func TestHead_ChainLength(t *testing.T) { - head := evmtypes.Head{ - Parent: &evmtypes.Head{ - Parent: &evmtypes.Head{}, - }, - } + head := evmtypes.Head{} + head.Parent.Store(&evmtypes.Head{}) + head.Parent.Load().Parent.Store(&evmtypes.Head{}) assert.Equal(t, uint32(3), head.ChainLength()) @@ -134,12 +132,12 @@ func TestHead_AsSlice(t *testing.T) { } h2 := &evmtypes.Head{ Number: 2, - Parent: h1, } + h2.Parent.Store(h1) h3 := &evmtypes.Head{ Number: 3, - Parent: h2, } + h3.Parent.Store(h2) assert.Len(t, (*evmtypes.Head)(nil).AsSlice(0), 0) assert.Len(t, (*evmtypes.Head)(nil).AsSlice(1), 0) @@ -234,36 +232,35 @@ func TestSafeByteSlice_Error(t *testing.T) { } func TestHead_EarliestInChain(t *testing.T) { - head := evmtypes.Head{ + h3 := evmtypes.Head{ Number: 3, - Parent: &evmtypes.Head{ - Number: 2, - Parent: &evmtypes.Head{ - Number: 1, - }, - }, } + h2 := &evmtypes.Head{Number: 2} + h3.Parent.Store(h2) + h1 := &evmtypes.Head{Number: 1} + h2.Parent.Store(h1) - assert.Equal(t, int64(1), head.EarliestInChain().BlockNumber()) + assert.Equal(t, int64(1), h3.EarliestInChain().BlockNumber()) } func TestHead_HeadAtHeight(t *testing.T) { - expectedResult := &evmtypes.Head{ + h1 := &evmtypes.Head{ + Number: 1, + } + h2 := &evmtypes.Head{ Hash: common.BigToHash(big.NewInt(10)), Number: 2, - Parent: &evmtypes.Head{ - Number: 1, - }, } - head := evmtypes.Head{ + h2.Parent.Store(h1) + h3 := evmtypes.Head{ Number: 3, - Parent: expectedResult, } + h3.Parent.Store(h2) - headAtHeight, err := head.HeadAtHeight(2) + headAtHeight, err := h3.HeadAtHeight(2) require.NoError(t, err) - assert.Equal(t, expectedResult, headAtHeight) - _, err = head.HeadAtHeight(0) + assert.Equal(t, h2, headAtHeight) + _, err = h3.HeadAtHeight(0) assert.Error(t, err, "expected to get an error if head is not in the chain") } @@ -271,25 +268,27 @@ func TestHead_IsInChain(t *testing.T) { hash1 := utils.NewHash() hash2 := utils.NewHash() hash3 := utils.NewHash() - - head := evmtypes.Head{ - Number: 3, + h1 := &evmtypes.Head{ + Number: 1, + Hash: hash1, + } + h2 := &evmtypes.Head{ + Hash: hash2, + ParentHash: hash1, + Number: 2, + } + h2.Parent.Store(h1) + h3 := evmtypes.Head{ Hash: hash3, - Parent: &evmtypes.Head{ - Hash: hash2, - Number: 2, - Parent: &evmtypes.Head{ - Hash: hash1, - Number: 1, - }, - }, + Number: 3, } + h3.Parent.Store(h2) - assert.True(t, head.IsInChain(hash1)) - assert.True(t, head.IsInChain(hash2)) - assert.True(t, head.IsInChain(hash3)) - assert.False(t, head.IsInChain(utils.NewHash())) - assert.False(t, head.IsInChain(common.Hash{})) + assert.True(t, h3.IsInChain(hash1)) + assert.True(t, h3.IsInChain(hash2)) + assert.True(t, h3.IsInChain(hash3)) + assert.False(t, h3.IsInChain(utils.NewHash())) + assert.False(t, h3.IsInChain(common.Hash{})) } func TestTxReceipt_ReceiptIndicatesRunLogFulfillment(t *testing.T) { @@ -316,11 +315,11 @@ func TestHead_UnmarshalJSON(t *testing.T) { tests := []struct { name string json string - expected evmtypes.Head + expected *evmtypes.Head }{ {"geth", `{"difficulty":"0xf3a00","extraData":"0xd883010503846765746887676f312e372e318664617277696e","gasLimit":"0xffc001","gasUsed":"0x0","hash":"0x41800b5c3f1717687d85fc9018faac0a6e90b39deaa0b99e7fe4fe796ddeb26a","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0xd1aeb42885a43b72b518182ef893125814811048","mixHash":"0x0f98b15f1a4901a7e9204f3c500a7bd527b3fb2c3340e12176a44b83e414a69e","nonce":"0x0ece08ea8c49dfd9","number":"0x100","parentHash":"0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x218","stateRoot":"0xc7b01007a10da045eacb90385887dd0c38fcb5db7393006bdde24b93873c334b","timestamp":"0x58318da2","totalDifficulty":"0x1f3a00","transactions":[],"transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","uncles":[]}`, - evmtypes.Head{ + &evmtypes.Head{ Hash: common.HexToHash("0x41800b5c3f1717687d85fc9018faac0a6e90b39deaa0b99e7fe4fe796ddeb26a"), Number: 0x100, ParentHash: common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d"), @@ -332,7 +331,7 @@ func TestHead_UnmarshalJSON(t *testing.T) { }, {"parity", `{"author":"0xd1aeb42885a43b72b518182ef893125814811048","difficulty":"0xf3a00","extraData":"0xd883010503846765746887676f312e372e318664617277696e","gasLimit":"0xffc001","gasUsed":"0x0","hash":"0x41800b5c3f1717687d85fc9018faac0a6e90b39deaa0b99e7fe4fe796ddeb26a","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0xd1aeb42885a43b72b518182ef893125814811048","mixHash":"0x0f98b15f1a4901a7e9204f3c500a7bd527b3fb2c3340e12176a44b83e414a69e","nonce":"0x0ece08ea8c49dfd9","number":"0x100","parentHash":"0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","sealFields":["0xa00f98b15f1a4901a7e9204f3c500a7bd527b3fb2c3340e12176a44b83e414a69e","0x880ece08ea8c49dfd9"],"sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x218","stateRoot":"0xc7b01007a10da045eacb90385887dd0c38fcb5db7393006bdde24b93873c334b","timestamp":"0x58318da2","totalDifficulty":"0x1f3a00","transactions":[],"transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","uncles":[]}`, - evmtypes.Head{ + &evmtypes.Head{ Hash: common.HexToHash("0x41800b5c3f1717687d85fc9018faac0a6e90b39deaa0b99e7fe4fe796ddeb26a"), Number: 0x100, ParentHash: common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d"), @@ -344,7 +343,7 @@ func TestHead_UnmarshalJSON(t *testing.T) { }, {"arbitrum", `{"number":"0x15156","hash":"0x752dab43f7a2482db39227d46cd307623b26167841e2207e93e7566ab7ab7871","parentHash":"0x923ad1e27c1d43cb2d2fb09e26d2502ca4b4914a2e0599161d279c6c06117d34","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x71448077f5ce420a8e24db62d4d58e8d8e6ad2c7e76318868e089d41f7e0faf3","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x2c292672b8fc9d223647a2569e19721f0757c96a1421753a93e141f8e56cf504","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","size":"0x0","gasLimit":"0x11278208","gasUsed":"0x3d1fe9","timestamp":"0x60d0952d","transactions":["0xa1ea93556b93ed3b45cb24f21c8deb584e6a9049c35209242651bf3533c23b98","0xfc6593c45ba92351d17173aa1381e84734d252ab0169887783039212c4a41024","0x85ee9d04fd0ebb5f62191eeb53cb45d9c0945d43eba444c3548de2ac8421682f","0x50d120936473e5b75f6e04829ad4eeca7a1df7d3c5026ebb5d34af936a39b29c"],"uncles":[],"l1BlockNumber":"0x8652f9"}`, - evmtypes.Head{ + &evmtypes.Head{ Hash: common.HexToHash("0x752dab43f7a2482db39227d46cd307623b26167841e2207e93e7566ab7ab7871"), Number: 0x15156, ParentHash: common.HexToHash("0x923ad1e27c1d43cb2d2fb09e26d2502ca4b4914a2e0599161d279c6c06117d34"), @@ -357,7 +356,7 @@ func TestHead_UnmarshalJSON(t *testing.T) { }, {"arbitrum_empty_l1BlockNumber", `{"number":"0x15156","hash":"0x752dab43f7a2482db39227d46cd307623b26167841e2207e93e7566ab7ab7871","parentHash":"0x923ad1e27c1d43cb2d2fb09e26d2502ca4b4914a2e0599161d279c6c06117d34","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x71448077f5ce420a8e24db62d4d58e8d8e6ad2c7e76318868e089d41f7e0faf3","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x2c292672b8fc9d223647a2569e19721f0757c96a1421753a93e141f8e56cf504","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","size":"0x0","gasLimit":"0x11278208","gasUsed":"0x3d1fe9","timestamp":"0x60d0952d","transactions":["0xa1ea93556b93ed3b45cb24f21c8deb584e6a9049c35209242651bf3533c23b98","0xfc6593c45ba92351d17173aa1381e84734d252ab0169887783039212c4a41024","0x85ee9d04fd0ebb5f62191eeb53cb45d9c0945d43eba444c3548de2ac8421682f","0x50d120936473e5b75f6e04829ad4eeca7a1df7d3c5026ebb5d34af936a39b29c"],"uncles":[]}`, - evmtypes.Head{ + &evmtypes.Head{ Hash: common.HexToHash("0x752dab43f7a2482db39227d46cd307623b26167841e2207e93e7566ab7ab7871"), Number: 0x15156, ParentHash: common.HexToHash("0x923ad1e27c1d43cb2d2fb09e26d2502ca4b4914a2e0599161d279c6c06117d34"), @@ -370,7 +369,7 @@ func TestHead_UnmarshalJSON(t *testing.T) { }, {"not found", `null`, - evmtypes.Head{}, + &evmtypes.Head{}, }, } @@ -395,11 +394,11 @@ func TestHead_UnmarshalJSON(t *testing.T) { func TestHead_MarshalJSON(t *testing.T) { tests := []struct { name string - head evmtypes.Head + head *evmtypes.Head expected string }{ {"happy", - evmtypes.Head{ + &evmtypes.Head{ Hash: common.HexToHash("0x41800b5c3f1717687d85fc9018faac0a6e90b39deaa0b99e7fe4fe796ddeb26a"), Number: 0x100, ParentHash: common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d"), @@ -411,7 +410,7 @@ func TestHead_MarshalJSON(t *testing.T) { `{"hash":"0x41800b5c3f1717687d85fc9018faac0a6e90b39deaa0b99e7fe4fe796ddeb26a","number":"0x100","parentHash":"0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d","timestamp":"0x58318da2","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","stateRoot":"0xc7b01007a10da045eacb90385887dd0c38fcb5db7393006bdde24b93873c334b"}`, }, {"empty", - evmtypes.Head{}, + &evmtypes.Head{}, `{"number":"0x0"}`, }, } diff --git a/core/chains/evm/utils/big/big.go b/core/chains/evm/utils/big/big.go index 4bb51e2732..5706fda45b 100644 --- a/core/chains/evm/utils/big/big.go +++ b/core/chains/evm/utils/big/big.go @@ -188,3 +188,8 @@ func (b *Big) Sub(c *Big) *Big { func (b *Big) Mod(c *Big) *Big { return New(bigmath.Mod(b.ToInt(), c.ToInt())) } + +// IsZero returns true if b is zero +func (b *Big) IsZero() bool { + return b.ToInt().Sign() == 0 +} diff --git a/core/chains/legacyevm/chain.go b/core/chains/legacyevm/chain.go index 129c031882..f826e9576c 100644 --- a/core/chains/legacyevm/chain.go +++ b/core/chains/legacyevm/chain.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "math/big" + "strconv" gotoml "github.com/pelletier/go-toml/v2" "go.uber.org/multierr" @@ -241,13 +242,14 @@ func newChain(ctx context.Context, cfg *evmconfig.ChainScoped, nodes []*toml.Nod KeepFinalizedBlocksDepth: int64(cfg.EVM().LogKeepBlocksDepth()), LogPrunePageSize: int64(cfg.EVM().LogPrunePageSize()), BackupPollerBlockDelay: int64(cfg.EVM().BackupLogPollerBlockDelay()), + ClientErrors: cfg.EVM().NodePool().Errors(), } logPoller = logpoller.NewLogPoller(logpoller.NewObservedORM(chainID, opts.DS, l), client, l, headTracker, lpOpts) } } // note: gas estimator is started as a part of the txm - txm, gasEstimator, err := newEvmTxm(opts.DS, cfg.EVM(), opts.AppConfig.EVMRPCEnabled(), opts.AppConfig.Database(), opts.AppConfig.Database().Listener(), client, l, logPoller, opts) + txm, gasEstimator, err := newEvmTxm(opts.DS, cfg.EVM(), opts.AppConfig.EVMRPCEnabled(), opts.AppConfig.Database(), opts.AppConfig.Database().Listener(), client, l, logPoller, opts, headTracker) if err != nil { return nil, fmt.Errorf("failed to instantiate EvmTxm for chain with ID %s: %w", chainID.String(), err) } @@ -269,6 +271,8 @@ func newChain(ctx context.Context, cfg *evmconfig.ChainScoped, nodes []*toml.Nod var logBroadcaster log.Broadcaster if !opts.AppConfig.EVMRPCEnabled() { logBroadcaster = &log.NullBroadcaster{ErrMsg: fmt.Sprintf("Ethereum is disabled for chain %d", chainID)} + } else if !cfg.EVM().LogBroadcasterEnabled() { + logBroadcaster = &log.NullBroadcaster{ErrMsg: fmt.Sprintf("LogBroadcaster disabled for chain %d", chainID)} } else if opts.GenLogBroadcaster == nil { logORM := log.NewORM(opts.DS, *chainID) logBroadcaster = log.NewBroadcaster(logORM, client, cfg.EVM(), l, highestSeenHead, opts.MailMon) @@ -390,6 +394,19 @@ func (c *chain) SendTx(ctx context.Context, from, to string, amount *big.Int, ba return c.Transact(ctx, from, to, amount, balanceCheck) } +func (c *chain) LatestHead(_ context.Context) (types.Head, error) { + latestChain := c.headTracker.LatestChain() + if latestChain == nil { + return types.Head{}, errors.New("latest chain not found") + } + + return types.Head{ + Height: strconv.FormatInt(latestChain.BlockNumber(), 10), + Hash: latestChain.Hash.Bytes(), + Timestamp: uint64(latestChain.Timestamp.Unix()), + }, nil +} + func (c *chain) GetChainStatus(ctx context.Context) (types.ChainStatus, error) { toml, err := c.cfg.EVM().TOMLString() if err != nil { diff --git a/core/chains/legacyevm/evm_txm.go b/core/chains/legacyevm/evm_txm.go index cecfd4ffaf..ab11674966 100644 --- a/core/chains/legacyevm/evm_txm.go +++ b/core/chains/legacyevm/evm_txm.go @@ -7,6 +7,7 @@ import ( evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmconfig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" + httypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -22,6 +23,7 @@ func newEvmTxm( lggr logger.Logger, logPoller logpoller.LogPoller, opts ChainRelayExtenderConfig, + headTracker httypes.HeadTracker, ) (txm txmgr.TxManager, estimator gas.EvmFeeEstimator, err error, @@ -63,7 +65,8 @@ func newEvmTxm( lggr, logPoller, opts.KeyStore, - estimator) + estimator, + headTracker) } else { txm = opts.GenTxManager(chainID) } diff --git a/core/chains/legacyevm/mocks/chain.go b/core/chains/legacyevm/mocks/chain.go index 777212108c..3c6bd97d7a 100644 --- a/core/chains/legacyevm/mocks/chain.go +++ b/core/chains/legacyevm/mocks/chain.go @@ -523,6 +523,62 @@ func (_c *Chain_ID_Call) RunAndReturn(run func() *big.Int) *Chain_ID_Call { return _c } +// LatestHead provides a mock function with given fields: ctx +func (_m *Chain) LatestHead(ctx context.Context) (types.Head, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for LatestHead") + } + + var r0 types.Head + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (types.Head, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) types.Head); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(types.Head) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Chain_LatestHead_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LatestHead' +type Chain_LatestHead_Call struct { + *mock.Call +} + +// LatestHead is a helper method to define mock.On call +// - ctx context.Context +func (_e *Chain_Expecter) LatestHead(ctx interface{}) *Chain_LatestHead_Call { + return &Chain_LatestHead_Call{Call: _e.mock.On("LatestHead", ctx)} +} + +func (_c *Chain_LatestHead_Call) Run(run func(ctx context.Context)) *Chain_LatestHead_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *Chain_LatestHead_Call) Return(_a0 types.Head, _a1 error) *Chain_LatestHead_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Chain_LatestHead_Call) RunAndReturn(run func(context.Context) (types.Head, error)) *Chain_LatestHead_Call { + _c.Call.Return(run) + return _c +} + // ListNodeStatuses provides a mock function with given fields: ctx, pageSize, pageToken func (_m *Chain) ListNodeStatuses(ctx context.Context, pageSize int32, pageToken string) ([]types.NodeStatus, string, int, error) { ret := _m.Called(ctx, pageSize, pageToken) diff --git a/core/cmd/app.go b/core/cmd/app.go index 1ccb3da9a0..53c96980de 100644 --- a/core/cmd/app.go +++ b/core/cmd/app.go @@ -168,6 +168,10 @@ func NewApp(s *Shell) *cli.App { Usage: "Prints a health report", Action: s.Health, Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "failing, f", + Usage: "filter for failing services", + }, cli.BoolFlag{ Name: "json, j", Usage: "json output", diff --git a/core/cmd/forwarders_commands_test.go b/core/cmd/forwarders_commands_test.go index b5a96a73aa..dfd8272d8a 100644 --- a/core/cmd/forwarders_commands_test.go +++ b/core/cmd/forwarders_commands_test.go @@ -22,7 +22,7 @@ func TestEVMForwarderPresenter_RenderTable(t *testing.T) { t.Parallel() var ( - id = "1" + id = "ID:" address = utils.RandomAddress() evmChainID = big.NewI(4) createdAt = time.Now() diff --git a/core/cmd/shell.go b/core/cmd/shell.go index 3d055bb03a..c862b93614 100644 --- a/core/cmd/shell.go +++ b/core/cmd/shell.go @@ -23,14 +23,16 @@ import ( "github.com/Masterminds/semver/v3" "github.com/getsentry/sentry-go" "github.com/gin-gonic/gin" + "github.com/jmoiron/sqlx" "github.com/pkg/errors" "github.com/urfave/cli" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" "go.uber.org/multierr" "go.uber.org/zap/zapcore" "golang.org/x/sync/errgroup" - "github.com/jmoiron/sqlx" - + "github.com/smartcontractkit/chainlink-common/pkg/beholder" "github.com/smartcontractkit/chainlink-common/pkg/loop" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox" @@ -63,19 +65,59 @@ var ( grpcOpts loop.GRPCOpts ) -func initGlobals(cfgProm config.Prometheus, cfgTracing config.Tracing, logger logger.Logger) error { +func initGlobals(cfgProm config.Prometheus, cfgTracing config.Tracing, cfgTelemetry config.Telemetry, lggr logger.Logger) error { // Avoid double initializations, but does not prevent relay methods from being called multiple times. var err error initGlobalsOnce.Do(func() { - prometheus = ginprom.New(ginprom.Namespace("service"), ginprom.Token(cfgProm.AuthToken())) - grpcOpts = loop.NewGRPCOpts(nil) // default prometheus.Registerer - err = loop.SetupTracing(loop.TracingConfig{ - Enabled: cfgTracing.Enabled(), - CollectorTarget: cfgTracing.CollectorTarget(), - NodeAttributes: cfgTracing.Attributes(), - SamplingRatio: cfgTracing.SamplingRatio(), - OnDialError: func(error) { logger.Errorw("Failed to dial", "err", err) }, - }) + err = func() error { + prometheus = ginprom.New(ginprom.Namespace("service"), ginprom.Token(cfgProm.AuthToken())) + grpcOpts = loop.NewGRPCOpts(nil) // default prometheus.Registerer + + otel.SetErrorHandler(otel.ErrorHandlerFunc(func(err error) { + lggr.Errorw("Telemetry error", "err", err) + })) + + tracingCfg := loop.TracingConfig{ + Enabled: cfgTracing.Enabled(), + CollectorTarget: cfgTracing.CollectorTarget(), + NodeAttributes: cfgTracing.Attributes(), + SamplingRatio: cfgTracing.SamplingRatio(), + TLSCertPath: cfgTracing.TLSCertPath(), + OnDialError: func(error) { lggr.Errorw("Failed to dial", "err", err) }, + } + if !cfgTelemetry.Enabled() { + return loop.SetupTracing(tracingCfg) + } + + var attributes []attribute.KeyValue + if tracingCfg.Enabled { + attributes = tracingCfg.Attributes() + } + for k, v := range cfgTelemetry.ResourceAttributes() { + attributes = append(attributes, attribute.String(k, v)) + } + clientCfg := beholder.Config{ + InsecureConnection: cfgTelemetry.InsecureConnection(), + CACertFile: cfgTelemetry.CACertFile(), + OtelExporterGRPCEndpoint: cfgTelemetry.OtelExporterGRPCEndpoint(), + ResourceAttributes: attributes, + TraceSampleRatio: cfgTelemetry.TraceSampleRatio(), + } + if tracingCfg.Enabled { + clientCfg.TraceSpanExporter, err = tracingCfg.NewSpanExporter() + if err != nil { + return err + } + } + var beholderClient *beholder.Client + beholderClient, err = beholder.NewClient(clientCfg) + if err != nil { + return err + } + beholder.SetClient(beholderClient) + beholder.SetGlobalOtelProviders() + return nil + }() }) return err } @@ -138,7 +180,7 @@ type ChainlinkAppFactory struct{} // NewApplication returns a new instance of the node with the given config. func (n ChainlinkAppFactory) NewApplication(ctx context.Context, cfg chainlink.GeneralConfig, appLggr logger.Logger, db *sqlx.DB) (app chainlink.Application, err error) { - err = initGlobals(cfg.Prometheus(), cfg.Tracing(), appLggr) + err = initGlobals(cfg.Prometheus(), cfg.Tracing(), cfg.Telemetry(), appLggr) if err != nil { appLggr.Errorf("Failed to initialize globals: %v", err) } @@ -158,7 +200,7 @@ func (n ChainlinkAppFactory) NewApplication(ctx context.Context, cfg chainlink.G keyStore := keystore.New(ds, utils.GetScryptParams(cfg), appLggr) mailMon := mailbox.NewMonitor(cfg.AppID().String(), appLggr.Named("Mailbox")) - loopRegistry := plugins.NewLoopRegistry(appLggr, cfg.Tracing()) + loopRegistry := plugins.NewLoopRegistry(appLggr, cfg.Tracing(), cfg.Telemetry()) mercuryPool := wsrpc.NewPool(appLggr, cache.Config{ LatestReportTTL: cfg.Mercury().Cache().LatestReportTTL(), @@ -168,6 +210,7 @@ func (n ChainlinkAppFactory) NewApplication(ctx context.Context, cfg chainlink.G capabilitiesRegistry := capabilities.NewRegistry(appLggr) + unrestrictedClient := clhttp.NewUnrestrictedHTTPClient() // create the relayer-chain interoperators from application configuration relayerFactory := chainlink.RelayerFactory{ Logger: appLggr, @@ -175,6 +218,7 @@ func (n ChainlinkAppFactory) NewApplication(ctx context.Context, cfg chainlink.G GRPCOpts: grpcOpts, MercuryPool: mercuryPool, CapabilitiesRegistry: capabilitiesRegistry, + HTTPClient: unrestrictedClient, } evmFactoryCfg := chainlink.EVMFactoryConfig{ @@ -208,6 +252,13 @@ func (n ChainlinkAppFactory) NewApplication(ctx context.Context, cfg chainlink.G } initOps = append(initOps, chainlink.InitStarknet(ctx, relayerFactory, starkCfg)) } + if cfg.AptosEnabled() { + aptosCfg := chainlink.AptosFactoryConfig{ + Keystore: keyStore.Aptos(), + TOMLConfigs: cfg.AptosConfigs(), + } + initOps = append(initOps, chainlink.InitAptos(ctx, relayerFactory, aptosCfg)) + } relayChainInterops, err := chainlink.NewCoreRelayerChainInteroperators(initOps...) if err != nil { @@ -221,7 +272,6 @@ func (n ChainlinkAppFactory) NewApplication(ctx context.Context, cfg chainlink.G } restrictedClient := clhttp.NewRestrictedHTTPClient(cfg.Database(), appLggr) - unrestrictedClient := clhttp.NewUnrestrictedHTTPClient() externalInitiatorManager := webhook.NewExternalInitiatorManager(ds, unrestrictedClient) return chainlink.NewApplication(chainlink.ApplicationOpts{ Config: cfg, diff --git a/core/cmd/shell_local.go b/core/cmd/shell_local.go index e19cc485d8..fedd83dec8 100644 --- a/core/cmd/shell_local.go +++ b/core/cmd/shell_local.go @@ -22,9 +22,8 @@ import ( gethCommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/fatih/color" - "github.com/lib/pq" - "github.com/kylelemons/godebug/diff" + "github.com/lib/pq" "github.com/pkg/errors" "github.com/urfave/cli" "go.uber.org/multierr" @@ -435,6 +434,9 @@ func (s *Shell) runNode(c *cli.Context) error { if s.Config.StarkNetEnabled() { enabledChains = append(enabledChains, chaintype.StarkNet) } + if s.Config.AptosEnabled() { + enabledChains = append(enabledChains, chaintype.Aptos) + } err2 := app.GetKeyStore().OCR2().EnsureKeys(rootCtx, enabledChains...) if err2 != nil { return errors.Wrap(err2, "failed to ensure ocr key") @@ -464,6 +466,12 @@ func (s *Shell) runNode(c *cli.Context) error { return errors.Wrap(err2, "failed to ensure starknet key") } } + if s.Config.AptosEnabled() { + err2 := app.GetKeyStore().Aptos().EnsureKey(rootCtx) + if err2 != nil { + return errors.Wrap(err2, "failed to ensure aptos key") + } + } err2 := app.GetKeyStore().CSA().EnsureKey(rootCtx) if err2 != nil { @@ -669,7 +677,7 @@ func (s *Shell) RebroadcastTransactions(c *cli.Context) (err error) { feeCfg := txmgr.NewEvmTxmFeeConfig(chain.Config().EVM().GasEstimator()) stuckTxDetector := txmgr.NewStuckTxDetector(lggr, ethClient.ConfiguredChainID(), "", assets.NewWei(assets.NewEth(100).ToInt()), chain.Config().EVM().Transactions().AutoPurge(), nil, orm, ethClient) ec := txmgr.NewEvmConfirmer(orm, txmgr.NewEvmTxmClient(ethClient, chain.Config().EVM().NodePool().Errors()), - cfg, feeCfg, chain.Config().EVM().Transactions(), app.GetConfig().Database(), keyStore.Eth(), txBuilder, chain.Logger(), stuckTxDetector) + cfg, feeCfg, chain.Config().EVM().Transactions(), app.GetConfig().Database(), keyStore.Eth(), txBuilder, chain.Logger(), stuckTxDetector, chain.HeadTracker()) totalNonces := endingNonce - beginningNonce + 1 nonces := make([]evmtypes.Nonce, totalNonces) for i := int64(0); i < totalNonces; i++ { diff --git a/core/cmd/shell_local_test.go b/core/cmd/shell_local_test.go index 0bea1816a8..6f4907a5a6 100644 --- a/core/cmd/shell_local_test.go +++ b/core/cmd/shell_local_test.go @@ -11,9 +11,9 @@ import ( commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox" - "github.com/smartcontractkit/chainlink/v2/core/capabilities" "github.com/smartcontractkit/chainlink/v2/common/client" + "github.com/smartcontractkit/chainlink/v2/core/capabilities" "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/cmd" cmdMocks "github.com/smartcontractkit/chainlink/v2/core/cmd/mocks" @@ -46,7 +46,7 @@ import ( func genTestEVMRelayers(t *testing.T, opts legacyevm.ChainRelayExtenderConfig, ks evmrelayer.CSAETHKeystore) *chainlink.CoreRelayerChainInteroperators { f := chainlink.RelayerFactory{ Logger: opts.Logger, - LoopRegistry: plugins.NewLoopRegistry(opts.Logger, opts.AppConfig.Tracing()), + LoopRegistry: plugins.NewLoopRegistry(opts.Logger, opts.AppConfig.Tracing(), opts.AppConfig.Telemetry()), CapabilitiesRegistry: capabilities.NewRegistry(opts.Logger), } diff --git a/core/cmd/shell_remote.go b/core/cmd/shell_remote.go index aab4a94da6..0aa3f3837d 100644 --- a/core/cmd/shell_remote.go +++ b/core/cmd/shell_remote.go @@ -517,7 +517,11 @@ func (s *Shell) Health(c *cli.Context) error { if c.Bool("json") { mime = gin.MIMEJSON } - resp, err := s.HTTP.Get(s.ctx(), "/health", map[string]string{"Accept": mime}) + u := "/health" + if c.Bool("failing") { + u += "?failing" + } + resp, err := s.HTTP.Get(s.ctx(), u, map[string]string{"Accept": mime}) if err != nil { return s.errorOut(err) } diff --git a/core/cmd/shell_test.go b/core/cmd/shell_test.go index 6ecdc4a34d..a93be2fb9e 100644 --- a/core/cmd/shell_test.go +++ b/core/cmd/shell_test.go @@ -351,7 +351,7 @@ func TestNewUserCache(t *testing.T) { func TestSetupSolanaRelayer(t *testing.T) { lggr := logger.TestLogger(t) - reg := plugins.NewLoopRegistry(lggr, nil) + reg := plugins.NewLoopRegistry(lggr, nil, nil) ks := mocks.NewSolana(t) // config 3 chains but only enable 2 => should only be 2 relayer @@ -466,7 +466,7 @@ func TestSetupSolanaRelayer(t *testing.T) { func TestSetupStarkNetRelayer(t *testing.T) { lggr := logger.TestLogger(t) - reg := plugins.NewLoopRegistry(lggr, nil) + reg := plugins.NewLoopRegistry(lggr, nil, nil) ks := mocks.NewStarkNet(t) // config 3 chains but only enable 2 => should only be 2 relayer nEnabledChains := 2 diff --git a/core/config/app_config.go b/core/config/app_config.go index 112e242636..4cb7f1f610 100644 --- a/core/config/app_config.go +++ b/core/config/app_config.go @@ -56,6 +56,7 @@ type AppConfig interface { Threshold() Threshold WebServer() WebServer Tracing() Tracing + Telemetry() Telemetry } type DatabaseBackupMode string diff --git a/core/config/capabilities_config.go b/core/config/capabilities_config.go index ae542c062c..b7e5a3b86a 100644 --- a/core/config/capabilities_config.go +++ b/core/config/capabilities_config.go @@ -11,7 +11,24 @@ type CapabilitiesExternalRegistry interface { RelayID() types.RelayID } +type GatewayConnector interface { + ChainIDForNodeKey() string + NodeAddress() string + DonID() string + Gateways() []ConnectorGateway + WSHandshakeTimeoutMillis() uint32 + AuthMinChallengeLen() int + AuthTimestampToleranceSec() uint32 +} + +type ConnectorGateway interface { + ID() string + URL() string +} + type Capabilities interface { Peering() P2P + Dispatcher() Dispatcher ExternalRegistry() CapabilitiesExternalRegistry + GatewayConnector() GatewayConnector } diff --git a/core/config/dispatcher_config.go b/core/config/dispatcher_config.go new file mode 100644 index 0000000000..ec6f13e8f4 --- /dev/null +++ b/core/config/dispatcher_config.go @@ -0,0 +1,14 @@ +package config + +type DispatcherRateLimit interface { + GlobalRPS() float64 + GlobalBurst() int + PerSenderRPS() float64 + PerSenderBurst() int +} + +type Dispatcher interface { + SupportedVersion() int + ReceiverBufferSize() int + RateLimit() DispatcherRateLimit +} diff --git a/core/config/docs/chains-evm.toml b/core/config/docs/chains-evm.toml index bffc9ddf0c..fc53b0cc70 100644 --- a/core/config/docs/chains-evm.toml +++ b/core/config/docs/chains-evm.toml @@ -98,6 +98,8 @@ RPCBlockQueryDelay = 1 # Default # Block 64 will be treated as finalized by CL Node only when chain's latest finalized block is 65. As chain finalizes blocks in batches of 32, # CL Node has to wait for a whole new batch to be finalized to treat block 64 as finalized. FinalizedBlockOffset = 0 # Default +# LogBroadcasterEnabled is a feature flag for LogBroadcaster, by default it's true. +LogBroadcasterEnabled = true # Default # NoNewFinalizedHeadsThreshold controls how long to wait for new finalized block before `NodePool` marks rpc endpoints as # out-of-sync. Only applicable if `FinalityTagEnabled=true` # @@ -408,6 +410,10 @@ EnforceRepeatableRead = false # Default # trigger declaration of `FinalizedBlockOutOfSync` due to insignificant network delays in broadcasting of the finalized state among RPCs. # RPC will not be picked to handle a request even if this option is set to a nonzero value. DeathDeclarationDelay = '10s' # Default +# NewHeadsPollInterval define an interval for polling new block periodically using http client rather than subscribe to ws feed +# +# Set to 0 to disable. +NewHeadsPollInterval = '0s' # Default # **ADVANCED** # Errors enable the node to provide custom regex patterns to match against error messages from RPCs. [EVM.NodePool.Errors] @@ -439,6 +445,8 @@ TransactionAlreadyMined = '(: |^)transaction already mined' # Example Fatal = '(: |^)fatal' # Example # ServiceUnavailable is a regex pattern to match against service unavailable errors. ServiceUnavailable = '(: |^)service unavailable' # Example +# TooManyResults is a regex pattern to match an eth_getLogs error indicating the result set is too large to return +TooManyResults = '(: |^)too many results' # Example [EVM.OCR] # ContractConfirmations sets `OCR.ContractConfirmations` for this EVM chain. @@ -479,3 +487,5 @@ GasLimit = 5400000 # Default FromAddress = '0x2a3e23c6f242F5345320814aC8a1b4E58707D292' # Example # ForwarderAddress is the keystone forwarder contract address on chain. ForwarderAddress = '0x2a3e23c6f242F5345320814aC8a1b4E58707D292' # Example +# GasLimitDefault is the default gas limit for workflow transactions. +GasLimitDefault = 400_000 # Default diff --git a/core/config/docs/core.toml b/core/config/docs/core.toml index 91b3388949..b85dcf831a 100644 --- a/core/config/docs/core.toml +++ b/core/config/docs/core.toml @@ -14,7 +14,9 @@ LogPoller = false # Default # UICSAKeys enables CSA Keys in the UI. UICSAKeys = false # Default # CCIP enables the CCIP service. -CCIP = false # Default +CCIP = true # Default +# MultiFeedsManagers enables support for multiple feeds manager connections. +MultiFeedsManagers = false # Default [Database] # DefaultIdleInTxSessionTimeout is the maximum time allowed for a transaction to be open and idle before timing out. See Postgres `idle_in_transaction_session_timeout` for more details. @@ -87,7 +89,7 @@ LeaseRefreshInterval = '1s' # Default [TelemetryIngress] # UniConn toggles which ws connection style is used. -UniConn = true # Default +UniConn = false # Default # Logging toggles verbose logging of the raw telemetry messages being sent. Logging = false # Default # BufferSize is the number of telemetry messages to buffer before dropping new ones. @@ -192,7 +194,7 @@ StartTimeout = '15s' # Default ListenIP = '0.0.0.0' # Default # Optional LDAP config if WebServer.AuthenticationMethod is set to 'ldap' -# LDAP queries are all parameterized to support custom LDAP 'dn', 'cn', and attributes +# LDAP queries are all parameterized to support custom LDAP 'dn', 'cn', and attributes [WebServer.LDAP] # ServerTLS defines the option to require the secure ldaps ServerTLS = true # Default @@ -282,7 +284,7 @@ ReaperThreshold = '24h' # Default # **ADVANCED** # ResultWriteQueueDepth controls how many writes will be buffered before subsequent writes are dropped, for jobs that write results asynchronously for performance reasons, such as OCR. ResultWriteQueueDepth = 100 # Default -# VerboseLogging enables detailed logging of pipeline execution steps. +# VerboseLogging enables detailed logging of pipeline execution steps. # This can be useful for debugging failed runs without relying on the UI # or database. # @@ -421,16 +423,16 @@ TraceLogging = false # Default # Enabled enables P2P V2. # Note: V1.Enabled is true by default, so it must be set false in order to run V2 only. Enabled = true # Default -# AnnounceAddresses is the addresses the peer will advertise on the network in `host:port` form as accepted by the TCP version of Go’s `net.Dial`. -# The addresses should be reachable by other nodes on the network. When attempting to connect to another node, +# AnnounceAddresses is the addresses the peer will advertise on the network in `host:port` form as accepted by the TCP version of Go’s `net.Dial`. +# The addresses should be reachable by other nodes on the network. When attempting to connect to another node, # a node will attempt to dial all of the other node’s AnnounceAddresses in round-robin fashion. AnnounceAddresses = ['1.2.3.4:9999', '[a52d:0:a88:1274::abcd]:1337'] # Example # DefaultBootstrappers is the default bootstrapper peers for libocr's v2 networking stack. # -# Oracle nodes typically only know each other’s PeerIDs, but not their hostnames, IP addresses, or ports. -# DefaultBootstrappers are special nodes that help other nodes discover each other’s `AnnounceAddresses` so they can communicate. -# Nodes continuously attempt to connect to bootstrappers configured in here. When a node wants to connect to another node -# (which it knows only by PeerID, but not by address), it discovers the other node’s AnnounceAddresses from communications +# Oracle nodes typically only know each other’s PeerIDs, but not their hostnames, IP addresses, or ports. +# DefaultBootstrappers are special nodes that help other nodes discover each other’s `AnnounceAddresses` so they can communicate. +# Nodes continuously attempt to connect to bootstrappers configured in here. When a node wants to connect to another node +# (which it knows only by PeerID, but not by address), it discovers the other node’s AnnounceAddresses from communications # received from its DefaultBootstrappers or other discovered nodes. To facilitate discovery, # nodes will regularly broadcast signed announcements containing their PeerID and AnnounceAddresses. DefaultBootstrappers = ['12D3KooWMHMRLQkgPbFSYHwD3NBuwtS1AmxhvKVUrcfyaGDASR4U@1.2.3.4:9999', '12D3KooWM55u5Swtpw9r8aFLQHEtw7HR4t44GdNs654ej5gRs2Dh@example.com:1234'] # Example @@ -438,7 +440,7 @@ DefaultBootstrappers = ['12D3KooWMHMRLQkgPbFSYHwD3NBuwtS1AmxhvKVUrcfyaGDASR4U@1. DeltaDial = '15s' # Default # DeltaReconcile controls how often a Reconcile message is sent to every peer. DeltaReconcile = '1m' # Default -# ListenAddresses is the addresses the peer will listen to on the network in `host:port` form as accepted by `net.Listen()`, +# ListenAddresses is the addresses the peer will listen to on the network in `host:port` form as accepted by `net.Listen()`, # but the host and port must be fully specified and cannot be empty. You can specify `0.0.0.0` (IPv4) or `::` (IPv6) to listen on all interfaces, but that is not recommended. ListenAddresses = ['1.2.3.4:9999', '[a52d:0:a88:1274::abcd]:1337'] # Example @@ -450,6 +452,22 @@ NetworkID = 'evm' # Default # ChainID identifies the target chain id where the remote registry is located. ChainID = '1' # Default +[Capabilities.Dispatcher] +# SupportedVersion is the version of the version of message schema. +SupportedVersion = 1 # Default +# ReceiverBufferSize is the size of the buffer for incoming messages. +ReceiverBufferSize = 10000 # Default + +[Capabilities.Dispatcher.RateLimit] +# GlobalRPS is the global rate limit for the dispatcher. +GlobalRPS = 800 # Default +# GlobalBurst is the global burst limit for the dispatcher. +GlobalBurst = 1000 # Default +# PerSenderRPS is the per-sender rate limit for the dispatcher. +PerSenderRPS = 10 # Default +# PerSenderBurst is the per-sender burst limit for the dispatcher. +PerSenderBurst = 50 # Default + [Capabilities.Peering] # IncomingMessageBufferSize is the per-remote number of incoming # messages to buffer. Any additional messages received on top of those @@ -470,16 +488,16 @@ TraceLogging = false # Default [Capabilities.Peering.V2] # Enabled enables P2P V2. Enabled = false # Default -# AnnounceAddresses is the addresses the peer will advertise on the network in `host:port` form as accepted by the TCP version of Go’s `net.Dial`. -# The addresses should be reachable by other nodes on the network. When attempting to connect to another node, +# AnnounceAddresses is the addresses the peer will advertise on the network in `host:port` form as accepted by the TCP version of Go’s `net.Dial`. +# The addresses should be reachable by other nodes on the network. When attempting to connect to another node, # a node will attempt to dial all of the other node’s AnnounceAddresses in round-robin fashion. AnnounceAddresses = ['1.2.3.4:9999', '[a52d:0:a88:1274::abcd]:1337'] # Example # DefaultBootstrappers is the default bootstrapper peers for libocr's v2 networking stack. # -# Oracle nodes typically only know each other’s PeerIDs, but not their hostnames, IP addresses, or ports. -# DefaultBootstrappers are special nodes that help other nodes discover each other’s `AnnounceAddresses` so they can communicate. -# Nodes continuously attempt to connect to bootstrappers configured in here. When a node wants to connect to another node -# (which it knows only by PeerID, but not by address), it discovers the other node’s AnnounceAddresses from communications +# Oracle nodes typically only know each other’s PeerIDs, but not their hostnames, IP addresses, or ports. +# DefaultBootstrappers are special nodes that help other nodes discover each other’s `AnnounceAddresses` so they can communicate. +# Nodes continuously attempt to connect to bootstrappers configured in here. When a node wants to connect to another node +# (which it knows only by PeerID, but not by address), it discovers the other node’s AnnounceAddresses from communications # received from its DefaultBootstrappers or other discovered nodes. To facilitate discovery, # nodes will regularly broadcast signed announcements containing their PeerID and AnnounceAddresses. DefaultBootstrappers = ['12D3KooWMHMRLQkgPbFSYHwD3NBuwtS1AmxhvKVUrcfyaGDASR4U@1.2.3.4:9999', '12D3KooWM55u5Swtpw9r8aFLQHEtw7HR4t44GdNs654ej5gRs2Dh@example.com:1234'] # Example @@ -487,10 +505,30 @@ DefaultBootstrappers = ['12D3KooWMHMRLQkgPbFSYHwD3NBuwtS1AmxhvKVUrcfyaGDASR4U@1. DeltaDial = '15s' # Default # DeltaReconcile controls how often a Reconcile message is sent to every peer. DeltaReconcile = '1m' # Default -# ListenAddresses is the addresses the peer will listen to on the network in `host:port` form as accepted by `net.Listen()`, +# ListenAddresses is the addresses the peer will listen to on the network in `host:port` form as accepted by `net.Listen()`, # but the host and port must be fully specified and cannot be empty. You can specify `0.0.0.0` (IPv4) or `::` (IPv6) to listen on all interfaces, but that is not recommended. ListenAddresses = ['1.2.3.4:9999', '[a52d:0:a88:1274::abcd]:1337'] # Example +[Capabilities.GatewayConnector] +# ChainIDForNodeKey is the ChainID of the network associated with a private key to be used for authentication with Gateway nodes +ChainIDForNodeKey = '11155111' # Example +# NodeAddress is the address of the desired private key to be used for authentication with Gateway nodes +NodeAddress = '0x68902d681c28119f9b2531473a417088bf008e59' # Example +# DonID is the Id of the Don +DonID = 'example_don' # Example +# WSHandshakeTimeoutMillis is Websocket handshake timeout +WSHandshakeTimeoutMillis = 1000 # Example +# AuthMinChallengeLen is the minimum number of bytes in authentication challenge payload +AuthMinChallengeLen = 10 # Example +# AuthTimestampToleranceSec is Authentication timestamp tolerance +AuthTimestampToleranceSec = 10 # Example + +[[Capabilities.GatewayConnector.Gateways]] +# ID of the Gateway +ID = 'example_gateway' # Example +# URL of the Gateway +URL = 'wss://localhost:8081/node' # Example + [Keeper] # **ADVANCED** # DefaultTransactionQueueDepth controls the queue size for `DropOldestStrategy` in Keeper. Set to 0 to use `SendEvery` strategy instead. @@ -617,15 +655,15 @@ VerboseLogging = false # Default # LatestReportTTL controls how "stale" we will allow a price to be e.g. if # set to 1s, a new price will always be fetched if the last result was # from 1 second ago or older. -# +# # Another way of looking at it is such: the cache will _never_ return a # price that was queried from now-LatestReportTTL or before. -# +# # Setting to zero disables caching entirely. LatestReportTTL = "1s" # Default # MaxStaleAge is that maximum amount of time that a value can be stale # before it is deleted from the cache (a form of garbage collection). -# +# # This should generally be set to something much larger than # LatestReportTTL. Setting to zero disables garbage collection. MaxStaleAge = "1h" # Default @@ -651,3 +689,25 @@ TransmitQueueMaxSize = 10_000 # Default # when sending a message to the mercury server, before aborting and considering # the transmission to be failed. TransmitTimeout = "5s" # Default + +# Telemetry holds OTEL settings. +# This data includes open telemetry metrics, traces, & logs. +# It does not currently include prometheus metrics or standard out logs, but may in the future. +[Telemetry] +# Enabled turns telemetry collection on or off. +Enabled = false # Default +# Endpoint of the OTEL Collector. +Endpoint = 'example.com/collector' # Example +# CACertFile is the file path of the TLS certificate used for secure communication with the OTEL Collector. +# Required unless InescureConnection is true. +CACertFile = 'cert-file' # Example +# InsecureConnection bypasses the TLS CACertFile requirement and uses an insecure connection instead. +# Only available in dev mode. +InsecureConnection = false # Default +# TraceSampleRatio is the rate at which to sample traces. Must be between 0 and 1. +TraceSampleRatio = 0.01 # Default + +# ResourceAttributes are global metadata to include with all telemetry. +[Telemetry.ResourceAttributes] +# foo is an example resource attribute +foo = "bar" # Example diff --git a/core/config/docs/docs_test.go b/core/config/docs/docs_test.go index 8f46497cb5..13256a5764 100644 --- a/core/config/docs/docs_test.go +++ b/core/config/docs/docs_test.go @@ -83,8 +83,12 @@ func TestDoc(t *testing.T) { docDefaults.OperatorFactoryAddress = nil require.Empty(t, docDefaults.Workflow.FromAddress) require.Empty(t, docDefaults.Workflow.ForwarderAddress) + gasLimitDefault := uint64(400_000) + require.Equal(t, &gasLimitDefault, docDefaults.Workflow.GasLimitDefault) + docDefaults.Workflow.FromAddress = nil docDefaults.Workflow.ForwarderAddress = nil + docDefaults.Workflow.GasLimitDefault = &gasLimitDefault docDefaults.NodePool.Errors = evmcfg.ClientErrors{} // Transactions.AutoPurge configs are only set if the feature is enabled diff --git a/core/config/env/env.go b/core/config/env/env.go index 0ebfc357bf..c34cd7f4f5 100644 --- a/core/config/env/env.go +++ b/core/config/env/env.go @@ -20,6 +20,7 @@ var ( ThresholdKeyShare = Secret("CL_THRESHOLD_KEY_SHARE") // Migrations env vars EVMChainIDNotNullMigration0195 = "CL_EVM_CHAINID_NOT_NULL_MIGRATION_0195" + CustomDefaults = Var("CL_CHAIN_DEFAULTS") ) // LOOPP commands and vars diff --git a/core/config/feature_config.go b/core/config/feature_config.go index fbb3a4ea54..200a1fd8ed 100644 --- a/core/config/feature_config.go +++ b/core/config/feature_config.go @@ -4,4 +4,5 @@ type Feature interface { FeedsManager() bool UICSAKeys() bool LogPoller() bool + MultiFeedsManagers() bool } diff --git a/core/config/telemetry_config.go b/core/config/telemetry_config.go new file mode 100644 index 0000000000..5440e70b43 --- /dev/null +++ b/core/config/telemetry_config.go @@ -0,0 +1,10 @@ +package config + +type Telemetry interface { + Enabled() bool + InsecureConnection() bool + CACertFile() string + OtelExporterGRPCEndpoint() string + ResourceAttributes() map[string]string + TraceSampleRatio() float64 +} diff --git a/core/config/toml/types.go b/core/config/toml/types.go index 0c91ddd81a..497e33d5dd 100644 --- a/core/config/toml/types.go +++ b/core/config/toml/types.go @@ -5,6 +5,7 @@ import ( "fmt" "net" "net/url" + "reflect" "regexp" "strings" @@ -25,6 +26,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/store/dialects" "github.com/smartcontractkit/chainlink/v2/core/store/models" "github.com/smartcontractkit/chainlink/v2/core/utils" + configutils "github.com/smartcontractkit/chainlink/v2/core/utils/config" ) @@ -57,6 +59,7 @@ type Core struct { Tracing Tracing `toml:",omitempty"` Mercury Mercury `toml:",omitempty"` Capabilities Capabilities `toml:",omitempty"` + Telemetry Telemetry `toml:",omitempty"` } // SetFrom updates c with any non-nil values from f. (currently TOML field only!) @@ -93,11 +96,12 @@ func (c *Core) SetFrom(f *Core) { c.Sentry.setFrom(&f.Sentry) c.Insecure.setFrom(&f.Insecure) c.Tracing.setFrom(&f.Tracing) + c.Telemetry.setFrom(&f.Telemetry) } func (c *Core) ValidateConfig() (err error) { _, verr := parse.HomeDir(*c.RootDir) - if err != nil { + if verr != nil { err = multierr.Append(err, configutils.ErrInvalid{Name: "RootDir", Value: true, Msg: fmt.Sprintf("Failed to expand RootDir. Please use an explicit path: %s", verr)}) } @@ -105,6 +109,12 @@ func (c *Core) ValidateConfig() (err error) { err = multierr.Append(err, configutils.ErrInvalid{Name: "P2P.V2.Enabled", Value: false, Msg: "P2P required for OCR or OCR2. Please enable P2P or disable OCR/OCR2."}) } + if *c.Tracing.Enabled && *c.Telemetry.Enabled { + if c.Tracing.CollectorTarget == c.Telemetry.Endpoint { + err = multierr.Append(err, configutils.ErrInvalid{Name: "Tracing.CollectorTarget", Value: *c.Tracing.CollectorTarget, Msg: "Same as Telemetry.Endpoint. Must be different or disabled."}) + } + } + return err } @@ -300,10 +310,11 @@ func (p *PrometheusSecrets) validateMerge(f *PrometheusSecrets) (err error) { } type Feature struct { - FeedsManager *bool - LogPoller *bool - UICSAKeys *bool - CCIP *bool + FeedsManager *bool + LogPoller *bool + UICSAKeys *bool + CCIP *bool + MultiFeedsManagers *bool } func (f *Feature) setFrom(f2 *Feature) { @@ -319,6 +330,9 @@ func (f *Feature) setFrom(f2 *Feature) { if v := f2.CCIP; v != nil { f.CCIP = v } + if v := f2.MultiFeedsManagers; v != nil { + f.MultiFeedsManagers = v + } } type Database struct { @@ -1434,14 +1448,103 @@ func (r *ExternalRegistry) setFrom(f *ExternalRegistry) { } } +type Dispatcher struct { + SupportedVersion *int + ReceiverBufferSize *int + RateLimit DispatcherRateLimit +} + +func (d *Dispatcher) setFrom(f *Dispatcher) { + d.RateLimit.setFrom(&f.RateLimit) + + if f.ReceiverBufferSize != nil { + d.ReceiverBufferSize = f.ReceiverBufferSize + } + + if f.SupportedVersion != nil { + d.SupportedVersion = f.SupportedVersion + } +} + +type DispatcherRateLimit struct { + GlobalRPS *float64 + GlobalBurst *int + PerSenderRPS *float64 + PerSenderBurst *int +} + +func (drl *DispatcherRateLimit) setFrom(f *DispatcherRateLimit) { + if f.GlobalRPS != nil { + drl.GlobalRPS = f.GlobalRPS + } + if f.GlobalBurst != nil { + drl.GlobalBurst = f.GlobalBurst + } + if f.PerSenderRPS != nil { + drl.PerSenderRPS = f.PerSenderRPS + } + if f.PerSenderBurst != nil { + drl.PerSenderBurst = f.PerSenderBurst + } +} + +type GatewayConnector struct { + ChainIDForNodeKey *string + NodeAddress *string + DonID *string + Gateways []ConnectorGateway + WSHandshakeTimeoutMillis *uint32 + AuthMinChallengeLen *int + AuthTimestampToleranceSec *uint32 +} + +func (r *GatewayConnector) setFrom(f *GatewayConnector) { + if f.ChainIDForNodeKey != nil { + r.ChainIDForNodeKey = f.ChainIDForNodeKey + } + + if f.NodeAddress != nil { + r.NodeAddress = f.NodeAddress + } + + if f.DonID != nil { + r.DonID = f.DonID + } + + if f.Gateways != nil { + r.Gateways = f.Gateways + } + + if !reflect.ValueOf(f.WSHandshakeTimeoutMillis).IsZero() { + r.WSHandshakeTimeoutMillis = f.WSHandshakeTimeoutMillis + } + + if f.AuthMinChallengeLen != nil { + r.AuthMinChallengeLen = f.AuthMinChallengeLen + } + + if f.AuthTimestampToleranceSec != nil { + r.AuthTimestampToleranceSec = f.AuthTimestampToleranceSec + } +} + +type ConnectorGateway struct { + ID *string + URL *string +} + type Capabilities struct { Peering P2P `toml:",omitempty"` + Dispatcher Dispatcher `toml:",omitempty"` ExternalRegistry ExternalRegistry `toml:",omitempty"` + GatewayConnector GatewayConnector `toml:",omitempty"` } func (c *Capabilities) setFrom(f *Capabilities) { c.Peering.setFrom(&f.Peering) c.ExternalRegistry.setFrom(&f.ExternalRegistry) + c.Dispatcher.setFrom(&f.Dispatcher) + c.GatewayConnector.setFrom(&f.GatewayConnector) } type ThresholdKeyShareSecrets struct { @@ -1481,25 +1584,25 @@ type Tracing struct { func (t *Tracing) setFrom(f *Tracing) { if v := f.Enabled; v != nil { - t.Enabled = f.Enabled + t.Enabled = v } if v := f.CollectorTarget; v != nil { - t.CollectorTarget = f.CollectorTarget + t.CollectorTarget = v } if v := f.NodeID; v != nil { - t.NodeID = f.NodeID + t.NodeID = v } if v := f.Attributes; v != nil { - t.Attributes = f.Attributes + t.Attributes = v } if v := f.SamplingRatio; v != nil { - t.SamplingRatio = f.SamplingRatio + t.SamplingRatio = v } if v := f.Mode; v != nil { - t.Mode = f.Mode + t.Mode = v } if v := f.TLSCertPath; v != nil { - t.TLSCertPath = f.TLSCertPath + t.TLSCertPath = v } } @@ -1553,6 +1656,59 @@ func (t *Tracing) ValidateConfig() (err error) { return err } +type Telemetry struct { + Enabled *bool + CACertFile *string + Endpoint *string + InsecureConnection *bool + ResourceAttributes map[string]string `toml:",omitempty"` + TraceSampleRatio *float64 +} + +func (b *Telemetry) setFrom(f *Telemetry) { + if v := f.Enabled; v != nil { + b.Enabled = v + } + if v := f.CACertFile; v != nil { + b.CACertFile = v + } + if v := f.Endpoint; v != nil { + b.Endpoint = v + } + if v := f.InsecureConnection; v != nil { + b.InsecureConnection = v + } + if v := f.ResourceAttributes; v != nil { + b.ResourceAttributes = v + } + if v := f.TraceSampleRatio; v != nil { + b.TraceSampleRatio = v + } +} + +func (b *Telemetry) ValidateConfig() (err error) { + if b.Enabled == nil || !*b.Enabled { + return nil + } + if b.Endpoint == nil || *b.Endpoint == "" { + err = multierr.Append(err, configutils.ErrMissing{Name: "Endpoint", Msg: "must be set when Telemetry is enabled"}) + } + if b.InsecureConnection != nil && *b.InsecureConnection { + if build.IsProd() { + err = multierr.Append(err, configutils.ErrInvalid{Name: "InsecureConnection", Value: true, Msg: "cannot be used in production builds"}) + } + } else { + if b.CACertFile == nil || *b.CACertFile == "" { + err = multierr.Append(err, configutils.ErrMissing{Name: "CACertFile", Msg: "must be set, unless InsecureConnection is used"}) + } + } + if ratio := b.TraceSampleRatio; ratio != nil && (*ratio < 0 || *ratio > 1) { + err = multierr.Append(err, configutils.ErrInvalid{Name: "TraceSampleRatio", Value: *ratio, Msg: "must be between 0 and 1"}) + } + + return err +} + var hostnameRegex = regexp.MustCompile(`^[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*$`) // Validates uri is valid external or local URI diff --git a/core/gethwrappers/ccip/generated/ccip_reader_tester/ccip_reader_tester.go b/core/gethwrappers/ccip/generated/ccip_reader_tester/ccip_reader_tester.go index e5b59060a2..986f4c4c35 100644 --- a/core/gethwrappers/ccip/generated/ccip_reader_tester/ccip_reader_tester.go +++ b/core/gethwrappers/ccip/generated/ccip_reader_tester/ccip_reader_tester.go @@ -102,7 +102,7 @@ type OffRampSourceChainConfig struct { var CCIPReaderTesterMetaData = &bind.MetaData{ ABI: "[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"destChainSelector\",\"type\":\"uint64\"},{\"components\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"messageId\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"destChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"structInternal.RampMessageHeader\",\"name\":\"header\",\"type\":\"tuple\"},{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"receiver\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"extraArgs\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"feeToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"feeTokenAmount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"feeValueJuels\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"sourcePoolAddress\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"destTokenAddress\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"extraData\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"destExecData\",\"type\":\"bytes\"}],\"internalType\":\"structInternal.EVM2AnyTokenTransfer[]\",\"name\":\"tokenAmounts\",\"type\":\"tuple[]\"}],\"indexed\":false,\"internalType\":\"structInternal.EVM2AnyRampMessage\",\"name\":\"message\",\"type\":\"tuple\"}],\"name\":\"CCIPSendRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"components\":[{\"components\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"sourceToken\",\"type\":\"address\"},{\"internalType\":\"uint224\",\"name\":\"usdPerToken\",\"type\":\"uint224\"}],\"internalType\":\"structInternal.TokenPriceUpdate[]\",\"name\":\"tokenPriceUpdates\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"uint64\",\"name\":\"destChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint224\",\"name\":\"usdPerUnitGas\",\"type\":\"uint224\"}],\"internalType\":\"structInternal.GasPriceUpdate[]\",\"name\":\"gasPriceUpdates\",\"type\":\"tuple[]\"}],\"internalType\":\"structInternal.PriceUpdates\",\"name\":\"priceUpdates\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"onRampAddress\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"minSeqNr\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"maxSeqNr\",\"type\":\"uint64\"},{\"internalType\":\"bytes32\",\"name\":\"merkleRoot\",\"type\":\"bytes32\"}],\"internalType\":\"structInternal.MerkleRoot[]\",\"name\":\"merkleRoots\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"internalType\":\"structIRMNRemote.Signature[]\",\"name\":\"rmnSignatures\",\"type\":\"tuple[]\"},{\"internalType\":\"uint256\",\"name\":\"rmnRawVs\",\"type\":\"uint256\"}],\"indexed\":false,\"internalType\":\"structOffRamp.CommitReport\",\"name\":\"report\",\"type\":\"tuple\"}],\"name\":\"CommitReportAccepted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"messageId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"enumInternal.MessageExecutionState\",\"name\":\"state\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"returnData\",\"type\":\"bytes\"}],\"name\":\"ExecutionStateChanged\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"destChainSelector\",\"type\":\"uint64\"},{\"components\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"messageId\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"destChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"structInternal.RampMessageHeader\",\"name\":\"header\",\"type\":\"tuple\"},{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"receiver\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"extraArgs\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"feeToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"feeTokenAmount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"feeValueJuels\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"sourcePoolAddress\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"destTokenAddress\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"extraData\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"destExecData\",\"type\":\"bytes\"}],\"internalType\":\"structInternal.EVM2AnyTokenTransfer[]\",\"name\":\"tokenAmounts\",\"type\":\"tuple[]\"}],\"internalType\":\"structInternal.EVM2AnyRampMessage\",\"name\":\"message\",\"type\":\"tuple\"}],\"name\":\"emitCCIPSendRequested\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"components\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"sourceToken\",\"type\":\"address\"},{\"internalType\":\"uint224\",\"name\":\"usdPerToken\",\"type\":\"uint224\"}],\"internalType\":\"structInternal.TokenPriceUpdate[]\",\"name\":\"tokenPriceUpdates\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"uint64\",\"name\":\"destChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint224\",\"name\":\"usdPerUnitGas\",\"type\":\"uint224\"}],\"internalType\":\"structInternal.GasPriceUpdate[]\",\"name\":\"gasPriceUpdates\",\"type\":\"tuple[]\"}],\"internalType\":\"structInternal.PriceUpdates\",\"name\":\"priceUpdates\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"onRampAddress\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"minSeqNr\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"maxSeqNr\",\"type\":\"uint64\"},{\"internalType\":\"bytes32\",\"name\":\"merkleRoot\",\"type\":\"bytes32\"}],\"internalType\":\"structInternal.MerkleRoot[]\",\"name\":\"merkleRoots\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"internalType\":\"structIRMNRemote.Signature[]\",\"name\":\"rmnSignatures\",\"type\":\"tuple[]\"},{\"internalType\":\"uint256\",\"name\":\"rmnRawVs\",\"type\":\"uint256\"}],\"internalType\":\"structOffRamp.CommitReport\",\"name\":\"report\",\"type\":\"tuple\"}],\"name\":\"emitCommitReportAccepted\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"},{\"internalType\":\"bytes32\",\"name\":\"messageId\",\"type\":\"bytes32\"},{\"internalType\":\"enumInternal.MessageExecutionState\",\"name\":\"state\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"returnData\",\"type\":\"bytes\"}],\"name\":\"emitExecutionStateChanged\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"destChainSelector\",\"type\":\"uint64\"}],\"name\":\"getExpectedNextSequenceNumber\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"}],\"name\":\"getSourceChainConfig\",\"outputs\":[{\"components\":[{\"internalType\":\"contractIRouter\",\"name\":\"router\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"isEnabled\",\"type\":\"bool\"},{\"internalType\":\"uint64\",\"name\":\"minSeqNr\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"onRamp\",\"type\":\"bytes\"}],\"internalType\":\"structOffRamp.SourceChainConfig\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"destChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"}],\"name\":\"setDestChainSeqNr\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"components\":[{\"internalType\":\"contractIRouter\",\"name\":\"router\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"isEnabled\",\"type\":\"bool\"},{\"internalType\":\"uint64\",\"name\":\"minSeqNr\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"onRamp\",\"type\":\"bytes\"}],\"internalType\":\"structOffRamp.SourceChainConfig\",\"name\":\"sourceChainConfig\",\"type\":\"tuple\"}],\"name\":\"setSourceChainConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", - Bin: "0x608060405234801561001057600080fd5b506113e9806100206000396000f3fe608060405234801561001057600080fd5b506004361061006d5760003560e01c8063198b821c146100725780634cf66e36146100875780639041be3d1461009a578063c081a962146100ca578063c1a5a355146100dd578063e83eabba14610119578063e9d68a8e1461012c575b600080fd5b6100856100803660046107bd565b61014c565b005b610085610095366004610954565b610186565b6100ad6100a83660046109d2565b6101db565b6040516001600160401b0390911681526020015b60405180910390f35b6100856100d8366004610b82565b61020a565b6100856100eb366004610cbc565b6001600160401b03918216600090815260016020526040902080546001600160401b03191691909216179055565b610085610127366004610cef565b61024f565b61013f61013a3660046109d2565b6102db565b6040516100c19190610df0565b7f062172a2ae1a84d7c8d18965d6267b71d579aeca033bbd2e0548947798c3d4628160405161017b9190610f8c565b60405180910390a150565b82846001600160401b0316866001600160401b03167f8c324ce1367b83031769f6a813e3bb4c117aba2185789d66b98b791405be6df285856040516101cc92919061106d565b60405180910390a45050505050565b6001600160401b03808216600090815260016020819052604082205491926102049216906110a4565b92915050565b816001600160401b03167f273f5ec254c4821d9795f26fddd245b3658bf1126d45d9d1c13a1d18a4c77750826040516102439190611186565b60405180910390a25050565b6001600160401b0380831660009081526020818152604091829020845181549286015193860151909416600160a81b02600160a81b600160e81b0319931515600160a01b026001600160a81b03199093166001600160a01b03909516949094179190911791909116919091178155606082015182919060018201906102d4908261131d565b5050505050565b60408051608080820183526000808352602080840182905283850182905260608085018190526001600160401b038781168452838352928690208651948501875280546001600160a01b0381168652600160a01b810460ff16151593860193909352600160a81b90920490921694830194909452600184018054939492939184019161036690611292565b80601f016020809104026020016040519081016040528092919081815260200182805461039290611292565b80156103df5780601f106103b4576101008083540402835291602001916103df565b820191906000526020600020905b8154815290600101906020018083116103c257829003601f168201915b5050505050815250509050919050565b634e487b7160e01b600052604160045260246000fd5b604080519081016001600160401b0381118282101715610427576104276103ef565b60405290565b60405160a081016001600160401b0381118282101715610427576104276103ef565b604051608081016001600160401b0381118282101715610427576104276103ef565b60405161012081016001600160401b0381118282101715610427576104276103ef565b604051601f8201601f191681016001600160401b03811182821017156104bc576104bc6103ef565b604052919050565b60006001600160401b038211156104dd576104dd6103ef565b5060051b60200190565b6001600160a01b03811681146104fc57600080fd5b50565b803561050a816104e7565b919050565b80356001600160e01b038116811461050a57600080fd5b80356001600160401b038116811461050a57600080fd5b600082601f83011261054e57600080fd5b8135602061056361055e836104c4565b610494565b82815260069290921b8401810191818101908684111561058257600080fd5b8286015b848110156105cf576040818903121561059f5760008081fd5b6105a7610405565b6105b082610526565b81526105bd85830161050f565b81860152835291830191604001610586565b509695505050505050565b600082601f8301126105eb57600080fd5b81356001600160401b03811115610604576106046103ef565b610617601f8201601f1916602001610494565b81815284602083860101111561062c57600080fd5b816020850160208301376000918101602001919091529392505050565b600082601f83011261065a57600080fd5b8135602061066a61055e836104c4565b82815260059290921b8401810191818101908684111561068957600080fd5b8286015b848110156105cf5780356001600160401b03808211156106ad5760008081fd5b9088019060a0828b03601f19018113156106c75760008081fd5b6106cf61042d565b6106da888501610526565b8152604080850135848111156106f05760008081fd5b6106fe8e8b838901016105da565b8a8401525060609350610712848601610526565b908201526080610723858201610526565b9382019390935292013590820152835291830191830161068d565b600082601f83011261074f57600080fd5b8135602061075f61055e836104c4565b82815260069290921b8401810191818101908684111561077e57600080fd5b8286015b848110156105cf576040818903121561079b5760008081fd5b6107a3610405565b813581528482013585820152835291830191604001610782565b600060208083850312156107d057600080fd5b82356001600160401b03808211156107e757600080fd5b90840190608082870312156107fb57600080fd5b61080361044f565b82358281111561081257600080fd5b8301604081890381131561082557600080fd5b61082d610405565b82358581111561083c57600080fd5b8301601f81018b1361084d57600080fd5b803561085b61055e826104c4565b81815260069190911b8201890190898101908d83111561087a57600080fd5b928a01925b828410156108ca5785848f0312156108975760008081fd5b61089f610405565b84356108aa816104e7565b81526108b7858d0161050f565b818d0152825292850192908a019061087f565b8452505050828701359150848211156108e257600080fd5b6108ee8a83850161053d565b8188015283525050828401358281111561090757600080fd5b61091388828601610649565b8583015250604083013593508184111561092c57600080fd5b6109388785850161073e565b6040820152606083013560608201528094505050505092915050565b600080600080600060a0868803121561096c57600080fd5b61097586610526565b945061098360208701610526565b93506040860135925060608601356004811061099e57600080fd5b915060808601356001600160401b038111156109b957600080fd5b6109c5888289016105da565b9150509295509295909350565b6000602082840312156109e457600080fd5b6109ed82610526565b9392505050565b600060a08284031215610a0657600080fd5b610a0e61042d565b905081358152610a2060208301610526565b6020820152610a3160408301610526565b6040820152610a4260608301610526565b6060820152610a5360808301610526565b608082015292915050565b600082601f830112610a6f57600080fd5b81356020610a7f61055e836104c4565b82815260059290921b84018101918181019086841115610a9e57600080fd5b8286015b848110156105cf5780356001600160401b0380821115610ac25760008081fd5b9088019060a0828b03601f1901811315610adc5760008081fd5b610ae461042d565b610aef8885016104ff565b815260408085013584811115610b055760008081fd5b610b138e8b838901016105da565b8a8401525060608086013585811115610b2c5760008081fd5b610b3a8f8c838a01016105da565b83850152506080915081860135818401525082850135925083831115610b605760008081fd5b610b6e8d8a858801016105da565b908201528652505050918301918301610aa2565b60008060408385031215610b9557600080fd5b610b9e83610526565b915060208301356001600160401b0380821115610bba57600080fd5b908401906101a08287031215610bcf57600080fd5b610bd7610471565b610be187846109f4565b8152610bef60a084016104ff565b602082015260c083013582811115610c0657600080fd5b610c12888286016105da565b60408301525060e083013582811115610c2a57600080fd5b610c36888286016105da565b6060830152506101008084013583811115610c5057600080fd5b610c5c898287016105da565b608084015250610c6f61012085016104ff565b60a083015261014084013560c083015261016084013560e083015261018084013583811115610c9d57600080fd5b610ca989828701610a5e565b8284015250508093505050509250929050565b60008060408385031215610ccf57600080fd5b610cd883610526565b9150610ce660208401610526565b90509250929050565b60008060408385031215610d0257600080fd5b610d0b83610526565b915060208301356001600160401b0380821115610d2757600080fd5b9084019060808287031215610d3b57600080fd5b610d4361044f565b8235610d4e816104e7565b815260208301358015158114610d6357600080fd5b6020820152610d7460408401610526565b6040820152606083013582811115610d8b57600080fd5b610d97888286016105da565b6060830152508093505050509250929050565b6000815180845260005b81811015610dd057602081850181015186830182015201610db4565b506000602082860101526020601f19601f83011685010191505092915050565b602080825282516001600160a01b03168282015282015115156040808301919091528201516001600160401b0316606080830191909152820151608080830152600090610e4060a0840182610daa565b949350505050565b6001600160a01b03169052565b60008151808452602080850194506020840160005b83811015610ea357815180516001600160401b031688528301516001600160e01b03168388015260409096019590820190600101610e6a565b509495945050505050565b600082825180855260208086019550808260051b84010181860160005b84811015610f4357858303601f19018952815180516001600160401b0390811685528582015160a08787018190529190610f0783880182610daa565b60408581015184169089015260608086015190931692880192909252506080928301519290950191909152509783019790830190600101610ecb565b5090979650505050505050565b60008151808452602080850194506020840160005b83811015610ea3578151805188528301518388015260409096019590820190600101610f65565b602080825282516080838301528051604060a08501819052815160e08601819052600094939284019185916101008801905b80841015610ff957845180516001600160a01b031683528701516001600160e01b031687830152938601936001939093019290820190610fbe565b5093850151878503609f190160c0890152936110158186610e55565b945050505050818501519150601f19808583030160408601526110388284610eae565b92506040860151915080858403016060860152506110568282610f50565b915050606084015160808401528091505092915050565b60006004841061108d57634e487b7160e01b600052602160045260246000fd5b83825260406020830152610e406040830184610daa565b6001600160401b038181168382160190808211156110d257634e487b7160e01b600052601160045260246000fd5b5092915050565b600082825180855260208086019550808260051b84010181860160005b84811015610f4357858303601f19018952815180516001600160a01b031684528481015160a08686018190529061112f82870182610daa565b915050604080830151868303828801526111498382610daa565b925050506060808301518187015250608080830151925085820381870152506111728183610daa565b9a86019a94505050908301906001016110f6565b602081526111d3602082018351805182526020808201516001600160401b039081169184019190915260408083015182169084015260608083015182169084015260809182015116910152565b600060208301516111e760c0840182610e48565b5060408301516101a08060e08501526112046101c0850183610daa565b91506060850151601f196101008187860301818801526112248584610daa565b94506080880151925081878603016101208801526112428584610daa565b945060a08801519250611259610140880184610e48565b60c088015161016088015260e088015161018088015287015186850390910183870152905061128883826110d9565b9695505050505050565b600181811c908216806112a657607f821691505b6020821081036112c657634e487b7160e01b600052602260045260246000fd5b50919050565b601f821115611318576000816000526020600020601f850160051c810160208610156112f55750805b601f850160051c820191505b8181101561131457828155600101611301565b5050505b505050565b81516001600160401b03811115611336576113366103ef565b61134a816113448454611292565b846112cc565b602080601f83116001811461137f57600084156113675750858301515b600019600386901b1c1916600185901b178555611314565b600085815260208120601f198616915b828110156113ae5788860151825594840194600190910190840161138f565b50858210156113cc5787850151600019600388901b60f8161c191681555b5050505050600190811b0190555056fea164736f6c6343000818000a", + Bin: "0x608060405234801561001057600080fd5b50611730806100206000396000f3fe608060405234801561001057600080fd5b506004361061007d5760003560e01c8063c081a9621161005b578063c081a962146100db578063c1a5a355146100ee578063e83eabba14610143578063e9d68a8e1461015657600080fd5b8063198b821c146100825780634cf66e36146100975780639041be3d146100aa575b600080fd5b610095610090366004610919565b610176565b005b6100956100a5366004610ab1565b6101b0565b6100bd6100b8366004610b30565b610207565b60405167ffffffffffffffff90911681526020015b60405180910390f35b6100956100e9366004610d00565b610237565b6100956100fc366004610e3b565b67ffffffffffffffff918216600090815260016020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001691909216179055565b610095610151366004610e6e565b61027d565b610169610164366004610b30565b610367565b6040516100d29190610f8e565b7f062172a2ae1a84d7c8d18965d6267b71d579aeca033bbd2e0548947798c3d462816040516101a59190611159565b60405180910390a150565b828467ffffffffffffffff168667ffffffffffffffff167f8c324ce1367b83031769f6a813e3bb4c117aba2185789d66b98b791405be6df285856040516101f8929190611298565b60405180910390a45050505050565b67ffffffffffffffff808216600090815260016020819052604082205491926102319216906112e8565b92915050565b8167ffffffffffffffff167f273f5ec254c4821d9795f26fddd245b3658bf1126d45d9d1c13a1d18a4c7775082604051610271919061140d565b60405180910390a25050565b67ffffffffffffffff808316600090815260208181526040918290208451815492860151938601519094167501000000000000000000000000000000000000000000027fffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffff93151574010000000000000000000000000000000000000000027fffffffffffffffffffffff00000000000000000000000000000000000000000090931673ffffffffffffffffffffffffffffffffffffffff909516949094179190911791909116919091178155606082015182919060018201906103609082611609565b5050505050565b604080516080808201835260008083526020808401829052838501829052606080850181905267ffffffffffffffff87811684528383529286902086519485018752805473ffffffffffffffffffffffffffffffffffffffff8116865274010000000000000000000000000000000000000000810460ff16151593860193909352750100000000000000000000000000000000000000000090920490921694830194909452600184018054939492939184019161042390611565565b80601f016020809104026020016040519081016040528092919081815260200182805461044f90611565565b801561049c5780601f106104715761010080835404028352916020019161049c565b820191906000526020600020905b81548152906001019060200180831161047f57829003601f168201915b5050505050815250509050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040805190810167ffffffffffffffff811182821017156104fe576104fe6104ac565b60405290565b60405160a0810167ffffffffffffffff811182821017156104fe576104fe6104ac565b6040516080810167ffffffffffffffff811182821017156104fe576104fe6104ac565b604051610120810167ffffffffffffffff811182821017156104fe576104fe6104ac565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156105b5576105b56104ac565b604052919050565b600067ffffffffffffffff8211156105d7576105d76104ac565b5060051b60200190565b73ffffffffffffffffffffffffffffffffffffffff8116811461060357600080fd5b50565b8035610611816105e1565b919050565b80357bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8116811461061157600080fd5b803567ffffffffffffffff8116811461061157600080fd5b600082601f83011261066b57600080fd5b8135602061068061067b836105bd565b61056e565b82815260069290921b8401810191818101908684111561069f57600080fd5b8286015b848110156106ec57604081890312156106bc5760008081fd5b6106c46104db565b6106cd82610642565b81526106da858301610616565b818601528352918301916040016106a3565b509695505050505050565b600082601f83011261070857600080fd5b813567ffffffffffffffff811115610722576107226104ac565b61075360207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8401160161056e565b81815284602083860101111561076857600080fd5b816020850160208301376000918101602001919091529392505050565b600082601f83011261079657600080fd5b813560206107a661067b836105bd565b82815260059290921b840181019181810190868411156107c557600080fd5b8286015b848110156106ec57803567ffffffffffffffff808211156107ea5760008081fd5b818901915060a0807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0848d030112156108235760008081fd5b61082b610504565b610836888501610642565b81526040808501358481111561084c5760008081fd5b61085a8e8b838901016106f7565b8a840152506060935061086e848601610642565b90820152608061087f858201610642565b938201939093529201359082015283529183019183016107c9565b600082601f8301126108ab57600080fd5b813560206108bb61067b836105bd565b82815260069290921b840181019181810190868411156108da57600080fd5b8286015b848110156106ec57604081890312156108f75760008081fd5b6108ff6104db565b8135815284820135858201528352918301916040016108de565b6000602080838503121561092c57600080fd5b823567ffffffffffffffff8082111561094457600080fd5b908401906080828703121561095857600080fd5b610960610527565b82358281111561096f57600080fd5b8301604081890381131561098257600080fd5b61098a6104db565b82358581111561099957600080fd5b8301601f81018b136109aa57600080fd5b80356109b861067b826105bd565b81815260069190911b8201890190898101908d8311156109d757600080fd5b928a01925b82841015610a275785848f0312156109f45760008081fd5b6109fc6104db565b8435610a07816105e1565b8152610a14858d01610616565b818d0152825292850192908a01906109dc565b845250505082870135915084821115610a3f57600080fd5b610a4b8a83850161065a565b81880152835250508284013582811115610a6457600080fd5b610a7088828601610785565b85830152506040830135935081841115610a8957600080fd5b610a958785850161089a565b6040820152606083013560608201528094505050505092915050565b600080600080600060a08688031215610ac957600080fd5b610ad286610642565b9450610ae060208701610642565b935060408601359250606086013560048110610afb57600080fd5b9150608086013567ffffffffffffffff811115610b1757600080fd5b610b23888289016106f7565b9150509295509295909350565b600060208284031215610b4257600080fd5b610b4b82610642565b9392505050565b600060a08284031215610b6457600080fd5b610b6c610504565b905081358152610b7e60208301610642565b6020820152610b8f60408301610642565b6040820152610ba060608301610642565b6060820152610bb160808301610642565b608082015292915050565b600082601f830112610bcd57600080fd5b81356020610bdd61067b836105bd565b82815260059290921b84018101918181019086841115610bfc57600080fd5b8286015b848110156106ec57803567ffffffffffffffff80821115610c215760008081fd5b818901915060a0807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0848d03011215610c5a5760008081fd5b610c62610504565b610c6d888501610606565b815260408085013584811115610c835760008081fd5b610c918e8b838901016106f7565b8a8401525060608086013585811115610caa5760008081fd5b610cb88f8c838a01016106f7565b83850152506080915081860135818401525082850135925083831115610cde5760008081fd5b610cec8d8a858801016106f7565b908201528652505050918301918301610c00565b60008060408385031215610d1357600080fd5b610d1c83610642565b9150602083013567ffffffffffffffff80821115610d3957600080fd5b908401906101a08287031215610d4e57600080fd5b610d5661054a565b610d608784610b52565b8152610d6e60a08401610606565b602082015260c083013582811115610d8557600080fd5b610d91888286016106f7565b60408301525060e083013582811115610da957600080fd5b610db5888286016106f7565b6060830152506101008084013583811115610dcf57600080fd5b610ddb898287016106f7565b608084015250610dee6101208501610606565b60a083015261014084013560c083015261016084013560e083015261018084013583811115610e1c57600080fd5b610e2889828701610bbc565b8284015250508093505050509250929050565b60008060408385031215610e4e57600080fd5b610e5783610642565b9150610e6560208401610642565b90509250929050565b60008060408385031215610e8157600080fd5b610e8a83610642565b9150602083013567ffffffffffffffff80821115610ea757600080fd5b9084019060808287031215610ebb57600080fd5b610ec3610527565b8235610ece816105e1565b815260208301358015158114610ee357600080fd5b6020820152610ef460408401610642565b6040820152606083013582811115610f0b57600080fd5b610f17888286016106f7565b6060830152508093505050509250929050565b6000815180845260005b81811015610f5057602081850181015186830182015201610f34565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b6020815273ffffffffffffffffffffffffffffffffffffffff825116602082015260208201511515604082015267ffffffffffffffff604083015116606082015260006060830151608080840152610fe960a0840182610f2a565b949350505050565b60008151808452602080850194506020840160005b83811015611055578151805167ffffffffffffffff1688528301517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff168388015260409096019590820190600101611006565b509495945050505050565b600082825180855260208086019550808260051b84010181860160005b84811015611110577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0868403018952815160a067ffffffffffffffff8083511686528683015182888801526110d483880182610f2a565b6040858101518416908901526060808601519093169288019290925250608092830151929095019190915250978301979083019060010161107d565b5090979650505050505050565b60008151808452602080850194506020840160005b83811015611055578151805188528301518388015260409096019590820190600101611132565b602080825282516080838301528051604060a08501819052815160e08601819052600094939284019185916101008801905b808410156111e8578451805173ffffffffffffffffffffffffffffffffffffffff1683528701517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff168783015293860193600193909301929082019061118b565b50938501518785037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600160c0890152936112228186610ff1565b9450505050508185015191507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0808583030160408601526112638284611060565b9250604086015191508085840301606086015250611281828261111d565b915050606084015160808401528091505092915050565b6000600484106112d1577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b83825260406020830152610fe96040830184610f2a565b67ffffffffffffffff818116838216019080821115611330577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b5092915050565b600082825180855260208086019550808260051b84010181860160005b84811015611110577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0868403018952815160a073ffffffffffffffffffffffffffffffffffffffff82511685528582015181878701526113b682870182610f2a565b915050604080830151868303828801526113d08382610f2a565b925050506060808301518187015250608080830151925085820381870152506113f98183610f2a565b9a86019a9450505090830190600101611354565b6020815261145e60208201835180518252602081015167ffffffffffffffff808216602085015280604084015116604085015280606084015116606085015280608084015116608085015250505050565b6000602083015161148760c084018273ffffffffffffffffffffffffffffffffffffffff169052565b5060408301516101a08060e08501526114a46101c0850183610f2a565b915060608501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06101008187860301818801526114e28584610f2a565b94506080880151925081878603016101208801526115008584610f2a565b945060a0880151925061152c61014088018473ffffffffffffffffffffffffffffffffffffffff169052565b60c088015161016088015260e088015161018088015287015186850390910183870152905061155b8382611337565b9695505050505050565b600181811c9082168061157957607f821691505b6020821081036115b2577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b601f821115611604576000816000526020600020601f850160051c810160208610156115e15750805b601f850160051c820191505b81811015611600578281556001016115ed565b5050505b505050565b815167ffffffffffffffff811115611623576116236104ac565b611637816116318454611565565b846115b8565b602080601f83116001811461168a57600084156116545750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b178555611600565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b828110156116d7578886015182559484019460019091019084016116b8565b508582101561171357878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b0190555056fea164736f6c6343000818000a", } var CCIPReaderTesterABI = CCIPReaderTesterMetaData.ABI diff --git a/core/gethwrappers/ccip/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/core/gethwrappers/ccip/generation/generated-wrapper-dependency-versions-do-not-edit.txt index ed9f855b6f..55139ea87e 100644 --- a/core/gethwrappers/ccip/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/core/gethwrappers/ccip/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -7,7 +7,7 @@ burn_with_from_mint_token_pool: ../../../contracts/solc/v0.8.24/BurnWithFromMint burn_with_from_mint_token_pool_and_proxy: ../../../contracts/solc/v0.8.24/BurnWithFromMintTokenPoolAndProxy/BurnWithFromMintTokenPoolAndProxy.abi ../../../contracts/solc/v0.8.24/BurnWithFromMintTokenPoolAndProxy/BurnWithFromMintTokenPoolAndProxy.bin 1ed5c299f928529081dc01b7a46db2b5e6728001767863495a7675d8db99a9e2 ccip_encoding_utils: ../../../contracts/solc/v0.8.24/ICCIPEncodingUtils/ICCIPEncodingUtils.abi ../../../contracts/solc/v0.8.24/ICCIPEncodingUtils/ICCIPEncodingUtils.bin 2e6fa009821d30a24efdc9f9d14b2269fb9a51cb7d536ea8b2d29d97dd8b591c ccip_home: ../../../contracts/solc/v0.8.24/CCIPHome/CCIPHome.abi ../../../contracts/solc/v0.8.24/CCIPHome/CCIPHome.bin c570b86f8ae7697d3478e70625e06f0f682e75c1f0b7538f4b6f581b0d294b2b -ccip_reader_tester: ../../../contracts/solc/v0.8.24/CCIPReaderTester/CCIPReaderTester.abi ../../../contracts/solc/v0.8.24/CCIPReaderTester/CCIPReaderTester.bin f3b3c1df101be295808e6e7698cd9646e95c46a5a1c955459767e8189838c09e +ccip_reader_tester: ../../../contracts/solc/v0.8.24/CCIPReaderTester/CCIPReaderTester.abi ../../../contracts/solc/v0.8.24/CCIPReaderTester/CCIPReaderTester.bin 26a7e2add8a26174084a031f8957d9d9bd04618c56498df029aa549b4ab84d30 commit_store: ../../../contracts/solc/v0.8.24/CommitStore/CommitStore.abi ../../../contracts/solc/v0.8.24/CommitStore/CommitStore.bin 274d87db70b643e00ab0a7e7845bb4b791f3b613bfc87708d33fc5a8369e2a41 commit_store_helper: ../../../contracts/solc/v0.8.24/CommitStoreHelper/CommitStoreHelper.abi ../../../contracts/solc/v0.8.24/CommitStoreHelper/CommitStoreHelper.bin f7128dcc2ee6dbcbc976288abcc16970ffb19b59412c5202ef6b259d2007f801 ether_sender_receiver: ../../../contracts/solc/v0.8.24/EtherSenderReceiver/EtherSenderReceiver.abi ../../../contracts/solc/v0.8.24/EtherSenderReceiver/EtherSenderReceiver.bin 09510a3f773f108a3c231e8d202835c845ded862d071ec54c4f89c12d868b8de diff --git a/core/gethwrappers/generated/arbitrum_module/arbitrum_module.go b/core/gethwrappers/generated/arbitrum_module/arbitrum_module.go index 537fbe2154..ca9b77d563 100644 --- a/core/gethwrappers/generated/arbitrum_module/arbitrum_module.go +++ b/core/gethwrappers/generated/arbitrum_module/arbitrum_module.go @@ -29,8 +29,8 @@ var ( ) var ArbitrumModuleMetaData = &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\":[],\"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\"}]", - Bin: "0x608060405234801561001057600080fd5b5061041b806100206000396000f3fe608060405234801561001057600080fd5b50600436106100675760003560e01c806357e871e71161005057806357e871e71461009a57806385df51fd146100a2578063de9ee35e146100b557600080fd5b8063125441401461006c57806318b8f61314610092575b600080fd5b61007f61007a366004610333565b6100cb565b6040519081526020015b60405180910390f35b61007f610158565b61007f6101cf565b61007f6100b0366004610333565b61021d565b6040805161138881526000602082015201610089565b600080606c73ffffffffffffffffffffffffffffffffffffffff166341b247a86040518163ffffffff1660e01b815260040160c060405180830381865afa15801561011a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061013e919061034c565b50505050915050828161015191906103c5565b9392505050565b6000606c73ffffffffffffffffffffffffffffffffffffffff1663c6f7de0e6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101a6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101ca91906103e2565b905090565b6000606473ffffffffffffffffffffffffffffffffffffffff1663a3b1b31d6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101a6573d6000803e3d6000fd5b600080606473ffffffffffffffffffffffffffffffffffffffff1663a3b1b31d6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561026c573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061029091906103e2565b905080831015806102ab57506101006102a984836103fb565b115b156102b95750600092915050565b6040517f2b407a8200000000000000000000000000000000000000000000000000000000815260048101849052606490632b407a8290602401602060405180830381865afa15801561030f573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061015191906103e2565b60006020828403121561034557600080fd5b5035919050565b60008060008060008060c0878903121561036557600080fd5b865195506020870151945060408701519350606087015192506080870151915060a087015190509295509295509295565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b80820281158282048414176103dc576103dc610396565b92915050565b6000602082840312156103f457600080fd5b5051919050565b818103818111156103dc576103dc61039656fea164736f6c6343000813000a", + 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\":\"dataSize\",\"type\":\"uint256\"}],\"name\":\"getMaxL1Fee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", + Bin: "0x608060405234801561001057600080fd5b5061044a806100206000396000f3fe608060405234801561001057600080fd5b50600436106100675760003560e01c80637810d12a116100505780637810d12a1461009a57806385df51fd146100ad578063de9ee35e146100c057600080fd5b8063125441401461006c57806357e871e714610092575b600080fd5b61007f61007a366004610368565b6100d6565b6040519081526020015b60405180910390f35b61007f610163565b61007f6100a8366004610368565b6101da565b61007f6100bb366004610368565b610252565b6040805161138881526000602082015201610089565b600080606c73ffffffffffffffffffffffffffffffffffffffff166341b247a86040518163ffffffff1660e01b815260040160c060405180830381865afa158015610125573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101499190610381565b50505050915050828161015c91906103fa565b9392505050565b6000606473ffffffffffffffffffffffffffffffffffffffff1663a3b1b31d6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101b1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101d59190610411565b905090565b6000606c73ffffffffffffffffffffffffffffffffffffffff1663c6f7de0e6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610228573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061024c9190610411565b92915050565b600080606473ffffffffffffffffffffffffffffffffffffffff1663a3b1b31d6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156102a1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102c59190610411565b905080831015806102e057506101006102de848361042a565b115b156102ee5750600092915050565b6040517f2b407a8200000000000000000000000000000000000000000000000000000000815260048101849052606490632b407a8290602401602060405180830381865afa158015610344573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061015c9190610411565b60006020828403121561037a57600080fd5b5035919050565b60008060008060008060c0878903121561039a57600080fd5b865195506020870151945060408701519350606087015192506080870151915060a087015190509295509295509295565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b808202811582820484141761024c5761024c6103cb565b60006020828403121561042357600080fd5b5051919050565b8181038181111561024c5761024c6103cb56fea164736f6c6343000813000a", } var ArbitrumModuleABI = ArbitrumModuleMetaData.ABI @@ -213,9 +213,9 @@ func (_ArbitrumModule *ArbitrumModuleCallerSession) BlockNumber() (*big.Int, err return _ArbitrumModule.Contract.BlockNumber(&_ArbitrumModule.CallOpts) } -func (_ArbitrumModule *ArbitrumModuleCaller) GetCurrentL1Fee(opts *bind.CallOpts) (*big.Int, error) { +func (_ArbitrumModule *ArbitrumModuleCaller) GetCurrentL1Fee(opts *bind.CallOpts, arg0 *big.Int) (*big.Int, error) { var out []interface{} - err := _ArbitrumModule.contract.Call(opts, &out, "getCurrentL1Fee") + err := _ArbitrumModule.contract.Call(opts, &out, "getCurrentL1Fee", arg0) if err != nil { return *new(*big.Int), err @@ -227,12 +227,12 @@ func (_ArbitrumModule *ArbitrumModuleCaller) GetCurrentL1Fee(opts *bind.CallOpts } -func (_ArbitrumModule *ArbitrumModuleSession) GetCurrentL1Fee() (*big.Int, error) { - return _ArbitrumModule.Contract.GetCurrentL1Fee(&_ArbitrumModule.CallOpts) +func (_ArbitrumModule *ArbitrumModuleSession) GetCurrentL1Fee(arg0 *big.Int) (*big.Int, error) { + return _ArbitrumModule.Contract.GetCurrentL1Fee(&_ArbitrumModule.CallOpts, arg0) } -func (_ArbitrumModule *ArbitrumModuleCallerSession) GetCurrentL1Fee() (*big.Int, error) { - return _ArbitrumModule.Contract.GetCurrentL1Fee(&_ArbitrumModule.CallOpts) +func (_ArbitrumModule *ArbitrumModuleCallerSession) GetCurrentL1Fee(arg0 *big.Int) (*big.Int, error) { + return _ArbitrumModule.Contract.GetCurrentL1Fee(&_ArbitrumModule.CallOpts, arg0) } func (_ArbitrumModule *ArbitrumModuleCaller) GetGasOverhead(opts *bind.CallOpts) (GetGasOverhead, @@ -301,7 +301,7 @@ type ArbitrumModuleInterface interface { BlockNumber(opts *bind.CallOpts) (*big.Int, error) - GetCurrentL1Fee(opts *bind.CallOpts) (*big.Int, error) + GetCurrentL1Fee(opts *bind.CallOpts, arg0 *big.Int) (*big.Int, error) GetGasOverhead(opts *bind.CallOpts) (GetGasOverhead, diff --git a/core/gethwrappers/generated/automation_registry_logic_b_wrapper_2_3/automation_registry_logic_b_wrapper_2_3.go b/core/gethwrappers/generated/automation_registry_logic_b_wrapper_2_3/automation_registry_logic_b_wrapper_2_3.go index acbf11155d..c2a808d554 100644 --- a/core/gethwrappers/generated/automation_registry_logic_b_wrapper_2_3/automation_registry_logic_b_wrapper_2_3.go +++ b/core/gethwrappers/generated/automation_registry_logic_b_wrapper_2_3/automation_registry_logic_b_wrapper_2_3.go @@ -57,7 +57,7 @@ type AutomationRegistryBase23PaymentReceipt struct { var AutomationRegistryLogicBMetaData = &bind.MetaData{ ABI: "[{\"inputs\":[{\"internalType\":\"contractAutomationRegistryLogicC2_3\",\"name\":\"logicC\",\"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\":\"structAutomationRegistryBase2_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\":\"contractIERC20Metadata\",\"name\":\"token\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"uint32\",\"name\":\"gasFeePPB\",\"type\":\"uint32\"},{\"internalType\":\"uint24\",\"name\":\"flatFeeMilliCents\",\"type\":\"uint24\"},{\"internalType\":\"contractAggregatorV3Interface\",\"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\":\"structAutomationRegistryBase2_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\":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\":\"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\":\"contractIERC20Metadata\",\"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\":\"structAutomationRegistryBase2_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\":[{\"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\":\"enumAutomationRegistryBase2_3.UpkeepFailureReason\",\"name\":\"upkeepFailureReason\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"gasUsed\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"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\":\"enumAutomationRegistryBase2_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\":\"nonpayable\",\"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\":\"enumAutomationRegistryBase2_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\":\"nonpayable\",\"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\":\"enumAutomationRegistryBase2_3.UpkeepFailureReason\",\"name\":\"upkeepFailureReason\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"gasUsed\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"fallbackTo\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"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\":\"structAutomationRegistryBase2_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\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"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\":\"contractIERC20Metadata\",\"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\"}]", - Bin: "0x6101806040523480156200001257600080fd5b5060405162005d2238038062005d2283398101604081905262000035916200062f565b80816001600160a01b031663ca30e6036040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000075573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200009b91906200062f565b826001600160a01b031663226cf83c6040518163ffffffff1660e01b8152600401602060405180830381865afa158015620000da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200010091906200062f565b836001600160a01b031663614486af6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156200013f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200016591906200062f565b846001600160a01b0316636709d0e56040518163ffffffff1660e01b8152600401602060405180830381865afa158015620001a4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620001ca91906200062f565b856001600160a01b0316635425d8ac6040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000209573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200022f91906200062f565b866001600160a01b031663a08714c06040518163ffffffff1660e01b8152600401602060405180830381865afa1580156200026e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200029491906200062f565b876001600160a01b031663c5b964e06040518163ffffffff1660e01b8152600401602060405180830381865afa158015620002d3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620002f9919062000656565b886001600160a01b031663ac4dc59a6040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000338573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200035e91906200062f565b3380600081620003b55760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620003e857620003e8816200056b565b5050506001600160a01b0380891660805287811660a05286811660c05285811660e052848116610100528316610120526025805483919060ff19166001838181111562000439576200043962000679565b0217905550806001600160a01b0316610140816001600160a01b03168152505060c0516001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa1580156200049a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620004c091906200068f565b60ff1660a0516001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000504573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200052a91906200068f565b60ff16146200054c576040516301f86e1760e41b815260040160405180910390fd5b5050506001600160a01b039095166101605250620006b4945050505050565b336001600160a01b03821603620005c55760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401620003ac565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6001600160a01b03811681146200062c57600080fd5b50565b6000602082840312156200064257600080fd5b81516200064f8162000616565b9392505050565b6000602082840312156200066957600080fd5b8151600281106200064f57600080fd5b634e487b7160e01b600052602160045260246000fd5b600060208284031215620006a257600080fd5b815160ff811681146200064f57600080fd5b60805160a05160c05160e051610100516101205161014051610160516155e86200073a60003960008181610182015261022f0152600081816120e7015261228d01526000612bb30152600050506000612ee901526000613c5d01526000612fc3015260008181610c4701528181610d0801528181610dd30152612c7401526155e86000f3fe6080604052600436106101805760003560e01c80638765ecbe116100d6578063aed2e9291161007f578063ce7dc5b411610059578063ce7dc5b4146104b1578063f2fde38b146104d1578063f7d334ba146104f157610180565b8063aed2e9291461043a578063b148ab6b14610471578063cd7f71b51461049157610180565b8063948108f7116100b0578063948108f7146103e7578063a72aa27e146103fa578063a86e17811461041a57610180565b80638765ecbe1461037c5780638da5cb5b1461039c5780638dcf0fe7146103c757610180565b806354b7faae1161013857806371fae17f1161011257806371fae17f14610327578063744bfe611461034757806379ba50971461036757610180565b806354b7faae146102b457806368d369d8146102d457806371791aa0146102f457610180565b8063349e8cca11610169578063349e8cca146102205780634ee88d35146102745780635165f2f51461029457610180565b80631a2af011146101c757806329c5efad146101e7575b7f00000000000000000000000000000000000000000000000000000000000000003660008037600080366000845af43d6000803e8080156101c0573d6000f35b3d6000fd5b005b3480156101d357600080fd5b506101c56101e2366004614490565b610511565b3480156101f357600080fd5b50610207610202366004614604565b610617565b6040516102179493929190614723565b60405180910390f35b34801561022c57600080fd5b507f00000000000000000000000000000000000000000000000000000000000000005b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610217565b34801561028057600080fd5b506101c561028f3660046147a5565b6108f5565b3480156102a057600080fd5b506101c56102af3660046147f1565b610957565b3480156102c057600080fd5b506101c56102cf36600461480a565b610b09565b3480156102e057600080fd5b506101c56102ef366004614836565b610d7c565b34801561030057600080fd5b5061031461030f366004614604565b61102a565b6040516102179796959493929190614877565b34801561033357600080fd5b506101c56103423660046147f1565b61178e565b34801561035357600080fd5b506101c5610362366004614490565b611824565b34801561037357600080fd5b506101c5611c9c565b34801561038857600080fd5b506101c56103973660046147f1565b611d99565b3480156103a857600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff1661024f565b3480156103d357600080fd5b506101c56103e23660046147a5565b611f4e565b6101c56103f53660046148c5565b611fa3565b34801561040657600080fd5b506101c561041536600461490d565b612365565b34801561042657600080fd5b506101c5610435366004614932565b612464565b34801561044657600080fd5b5061045a6104553660046147a5565b612545565b604080519215158352602083019190915201610217565b34801561047d57600080fd5b506101c561048c3660046147f1565b6126f0565b34801561049d57600080fd5b506101c56104ac3660046147a5565b61291d565b3480156104bd57600080fd5b506102076104cc3660046149ac565b6129d4565b3480156104dd57600080fd5b506101c56104ec366004614a8e565b612a96565b3480156104fd57600080fd5b5061031461050c3660046147f1565b612aaa565b61051a82612ae6565b3373ffffffffffffffffffffffffffffffffffffffff821603610569576040517f8c8728c700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008281526006602052604090205473ffffffffffffffffffffffffffffffffffffffff8281169116146106135760008281526006602052604080822080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff851690811790915590519091339185917fb1cbb2c4b8480034c27e06da5f096b8233a8fd4497028593a41ff6df79726b3591a45b5050565b60006060600080610626612b9b565b600086815260046020908152604091829020825161012081018452815460ff8082161515835261010080830490911615159483019490945263ffffffff620100008204811695830195909552660100000000000081048516606083015273ffffffffffffffffffffffffffffffffffffffff6a01000000000000000000009091048116608083015260018301546fffffffffffffffffffffffffffffffff811660a08401526bffffffffffffffffffffffff70010000000000000000000000000000000082041660c08401527c0100000000000000000000000000000000000000000000000000000000900490941660e0820152600290910154909216908201525a9150600080826080015173ffffffffffffffffffffffffffffffffffffffff1663f00e6a2a6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561077c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107a09190614abb565b73ffffffffffffffffffffffffffffffffffffffff16601660000160149054906101000a900463ffffffff1663ffffffff16896040516107e09190614ad8565b60006040518083038160008787f1925050503d806000811461081e576040519150601f19603f3d011682016040523d82523d6000602084013e610823565b606091505b50915091505a6108339085614b23565b93508161085c5760006040518060200160405280600081525060079650965096505050506108ec565b808060200190518101906108709190614b8b565b90975095508661089c5760006040518060200160405280600081525060049650965096505050506108ec565b60185486517401000000000000000000000000000000000000000090910463ffffffff1610156108e85760006040518060200160405280600081525060059650965096505050506108ec565b5050505b92959194509250565b6108fe83612ae6565b6000838152601d60205260409020610917828483614c70565b50827f2b72ac786c97e68dbab71023ed6f2bdbfc80ad9bb7808941929229d71b7d5664838360405161094a929190614dd4565b60405180910390a2505050565b61096081612ae6565b600081815260046020908152604091829020825161012081018452815460ff808216151580845261010080840490921615159584019590955262010000820463ffffffff9081169684019690965266010000000000008204861660608401526a010000000000000000000090910473ffffffffffffffffffffffffffffffffffffffff908116608084015260018401546fffffffffffffffffffffffffffffffff811660a085015270010000000000000000000000000000000081046bffffffffffffffffffffffff1660c08501527c0100000000000000000000000000000000000000000000000000000000900490951660e083015260029092015490931690830152610a9a576040517f1b88a78400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600082815260046020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055610ad9600283612c0c565b5060405182907f7bada562044eb163f6b4003c4553e4e62825344c0418eea087bed5ee05a4745690600090a25050565b610b11612c21565b73ffffffffffffffffffffffffffffffffffffffff8216610b5e576040517f9c8d2cd200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000610b68612c72565b90506000811215610bb4576040517fcf47918100000000000000000000000000000000000000000000000000000000815260006004820152602481018390526044015b60405180910390fd5b80821115610bf8576040517fcf4791810000000000000000000000000000000000000000000000000000000081526004810182905260248101839052604401610bab565b6040517fa9059cbb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8481166004830152602482018490526000917f00000000000000000000000000000000000000000000000000000000000000009091169063a9059cbb906044016020604051808303816000875af1158015610c92573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610cb69190614de8565b905080610cef576040517f90b8ec1800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8373ffffffffffffffffffffffffffffffffffffffff167f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff167f5e110f8bc8a20b65dcc87f224bdf1cc039346e267118bae2739847f07321ffa885604051610d6e91815260200190565b60405180910390a350505050565b610d84612c21565b73ffffffffffffffffffffffffffffffffffffffff8216610dd1576040517f9c8d2cd200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610e56576040517fc1ab6dc100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000610e60612c72565b128015610e835750600060255460ff166001811115610e8157610e816146b9565b145b15610eba576040517f981bb6a000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff83166000818152602160205260408082205490517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152919290916370a0823190602401602060405180830381865afa158015610f36573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f5a9190614e03565b610f649190614b23565b905080821115610faa576040517fcf4791810000000000000000000000000000000000000000000000000000000081526004810182905260248101839052604401610bab565b610fcb73ffffffffffffffffffffffffffffffffffffffff85168484612d41565b8273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f5e110f8bc8a20b65dcc87f224bdf1cc039346e267118bae2739847f07321ffa884604051610d6e91815260200190565b60006060600080600080600061103e612b9b565b60006110498a612e1a565b905060006014604051806101200160405290816000820160009054906101000a90046bffffffffffffffffffffffff166bffffffffffffffffffffffff166bffffffffffffffffffffffff16815260200160008201600c9054906101000a900463ffffffff1663ffffffff1663ffffffff1681526020016000820160109054906101000a900462ffffff1662ffffff1662ffffff1681526020016000820160139054906101000a900461ffff1661ffff1661ffff1681526020016000820160159054906101000a900460ff1660ff1660ff1681526020016000820160169054906101000a900460ff161515151581526020016000820160179054906101000a900460ff161515151581526020016000820160189054906101000a900460ff161515151581526020016001820160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152505090506000600460008d8152602001908152602001600020604051806101200160405290816000820160009054906101000a900460ff161515151581526020016000820160019054906101000a900460ff161515151581526020016000820160029054906101000a900463ffffffff1663ffffffff1663ffffffff1681526020016000820160069054906101000a900463ffffffff1663ffffffff1663ffffffff16815260200160008201600a9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020016001820160009054906101000a90046fffffffffffffffffffffffffffffffff166fffffffffffffffffffffffffffffffff166fffffffffffffffffffffffffffffffff1681526020016001820160109054906101000a90046bffffffffffffffffffffffff166bffffffffffffffffffffffff166bffffffffffffffffffffffff16815260200160018201601c9054906101000a900463ffffffff1663ffffffff1663ffffffff1681526020016002820160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152505090506000808360a001511561140c5750506040805160208101825260008082529290910151919a5098506009975089965063ffffffff169450859350839250611782915050565b606083015163ffffffff908116146114565750506040805160208101825260008082529290910151919a5098506001975089965063ffffffff169450859350839250611782915050565b8251156114955750506040805160208101825260008082529290910151919a5098506002975089965063ffffffff169450859350839250611782915050565b61149e84612ec5565b8094508198508299505050506114c38e858786604001518b8b888a61010001516130b7565b9050806bffffffffffffffffffffffff168360c001516bffffffffffffffffffffffff1610156115255750506040805160208101825260008082529290910151919a5098506006975089965063ffffffff169450859350839250611782915050565b505060006115348d858e613427565b90505a9750600080836080015173ffffffffffffffffffffffffffffffffffffffff1663f00e6a2a6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561158b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115af9190614abb565b73ffffffffffffffffffffffffffffffffffffffff16601660000160149054906101000a900463ffffffff1663ffffffff16846040516115ef9190614ad8565b60006040518083038160008787f1925050503d806000811461162d576040519150601f19603f3d011682016040523d82523d6000602084013e611632565b606091505b50915091505a611642908b614b23565b9950816116c8576018548151780100000000000000000000000000000000000000000000000090910463ffffffff1610156116a75750506040805160208101825260008082529390910151929b509950600898505063ffffffff169450611782915050565b604090930151929a50600399505063ffffffff909116955061178292505050565b808060200190518101906116dc9190614b8b565b909d509b508c6117165750506040805160208101825260008082529390910151929b509950600498505063ffffffff169450611782915050565b6018548c517401000000000000000000000000000000000000000090910463ffffffff1610156117705750506040805160208101825260008082529390910151929b509950600598505063ffffffff169450611782915050565b5050506040015163ffffffff16945050505b92959891949750929550565b611796613607565b600081815260046020908152604080832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055602390915280822080547fffffffffffffffffffffffffffffffffffffffffffffffffff000000000000001690555182917f97d0ef3f46a56168af653f547bdb6f77ec2b1d7d9bc6ba0193c2b340ec68064a91a250565b60145477010000000000000000000000000000000000000000000000900460ff161561187c576040517f37ed32e800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601480547fffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffff167701000000000000000000000000000000000000000000000017905573ffffffffffffffffffffffffffffffffffffffff811661190b576040517f9c8d2cd200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000828152600460209081526040808320815161012081018352815460ff8082161515835261010080830490911615158387015263ffffffff620100008304811684870152660100000000000083048116606085015273ffffffffffffffffffffffffffffffffffffffff6a01000000000000000000009093048316608085015260018501546fffffffffffffffffffffffffffffffff811660a08601526bffffffffffffffffffffffff70010000000000000000000000000000000082041660c08601527c010000000000000000000000000000000000000000000000000000000090041660e084015260029093015481169282019290925286855260059093529220549091163314611a4b576040517fa47c170600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601554604080517f57e871e7000000000000000000000000000000000000000000000000000000008152905173ffffffffffffffffffffffffffffffffffffffff909216916357e871e7916004808201926020929091908290030181865afa158015611abb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611adf9190614e03565b816060015163ffffffff161115611b22576040517fff84e5dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008381526004602090815260408083206001015461010085015173ffffffffffffffffffffffffffffffffffffffff1684526021909252909120547001000000000000000000000000000000009091046bffffffffffffffffffffffff1690611b8d908290614b23565b6101008301805173ffffffffffffffffffffffffffffffffffffffff908116600090815260216020908152604080832095909555888252600490529290922060010180547fffffffff000000000000000000000000ffffffffffffffffffffffffffffffff16905551611c109116846bffffffffffffffffffffffff8416612d41565b604080516bffffffffffffffffffffffff8316815273ffffffffffffffffffffffffffffffffffffffff8516602082015285917ff3b5906e5672f3e524854103bcafbbdba80dbdfeca2c35e116127b1060a68318910160405180910390a25050601480547fffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffff1690555050565b60015473ffffffffffffffffffffffffffffffffffffffff163314611d1d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e6572000000000000000000006044820152606401610bab565b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b611da281612ae6565b600081815260046020908152604091829020825161012081018452815460ff808216158015845261010080840490921615159584019590955262010000820463ffffffff9081169684019690965266010000000000008204861660608401526a010000000000000000000090910473ffffffffffffffffffffffffffffffffffffffff908116608084015260018401546fffffffffffffffffffffffffffffffff811660a085015270010000000000000000000000000000000081046bffffffffffffffffffffffff1660c08501527c0100000000000000000000000000000000000000000000000000000000900490951660e083015260029092015490931690830152611edc576040517f514b6c2400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600082815260046020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055611f1e600283613658565b5060405182907f8ab10247ce168c27748e656ecf852b951fcaac790c18106b19aa0ae57a8b741f90600090a25050565b611f5783612ae6565b6000838152601e60205260409020611f70828483614c70565b50827f3e8740446213c8a77d40e08f79136ce3f347d13ed270a6ebdf57159e0faf4850838360405161094a929190614dd4565b600082815260046020908152604091829020825161012081018452815460ff8082161515835261010080830490911615159483019490945263ffffffff6201000082048116958301959095526601000000000000810485166060830181905273ffffffffffffffffffffffffffffffffffffffff6a01000000000000000000009092048216608084015260018401546fffffffffffffffffffffffffffffffff811660a08501526bffffffffffffffffffffffff70010000000000000000000000000000000082041660c08501527c01000000000000000000000000000000000000000000000000000000009004861660e084015260029093015416928101929092529091146120df576040517f9c0083a200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b341561217b577f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1681610100015173ffffffffffffffffffffffffffffffffffffffff161461216f576040517fc1ab6dc100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61217834613664565b91505b818160c0015161218b9190614e1c565b600084815260046020908152604080832060010180547fffffffff000000000000000000000000ffffffffffffffffffffffffffffffff167001000000000000000000000000000000006bffffffffffffffffffffffff9687160217905561010085015173ffffffffffffffffffffffffffffffffffffffff168352602190915290205461221b91841690614e41565b61010082015173ffffffffffffffffffffffffffffffffffffffff1660009081526021602052604081209190915534900361228b576101008101516122869073ffffffffffffffffffffffffffffffffffffffff1633306bffffffffffffffffffffffff8616613706565b61231b565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663d0e30db0836bffffffffffffffffffffffff166040518263ffffffff1660e01b81526004016000604051808303818588803b15801561230157600080fd5b505af1158015612315573d6000803e3d6000fd5b50505050505b6040516bffffffffffffffffffffffff83168152339084907fafd24114486da8ebfc32f3626dada8863652e187461aa74d4bfa7348915062039060200160405180910390a3505050565b6108fc8163ffffffff1610806123a2575060165463ffffffff78010000000000000000000000000000000000000000000000009091048116908216115b156123d9576040517f14c237fb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6123e282612ae6565b60008281526004602090815260409182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffff00000000ffff166201000063ffffffff861690810291909117909155915191825283917fc24c07e655ce79fba8a589778987d3c015bc6af1632bb20cf9182e02a65d972c91015b60405180910390a25050565b61246c613607565b6000828152600460205260409020546601000000000000900463ffffffff908116146124c4576040517f9c0083a200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600082815260046020908152604080832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790556023909152902081906125128282614e65565b905050817fd8a6d79d170a55968079d3a89b960d86b4442aef6aac1d01e644c32b9e38b340826040516124589190614eec565b600080612550612b9b565b601454760100000000000000000000000000000000000000000000900460ff16156125a7576040517f24522f3400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600085815260046020908152604091829020825161012081018452815460ff8082161515835261010080830490911615158386015262010000820463ffffffff90811684880181905266010000000000008404821660608601526a010000000000000000000090930473ffffffffffffffffffffffffffffffffffffffff9081166080860181905260018701546fffffffffffffffffffffffffffffffff811660a088015270010000000000000000000000000000000081046bffffffffffffffffffffffff1660c08801527c0100000000000000000000000000000000000000000000000000000000900490921660e0860152600290950154909416908301528451601f890185900485028101850190955287855290936126e393899089908190840183828082843760009201919091525061376a92505050565b9097909650945050505050565b600081815260046020908152604091829020825161012081018452815460ff8082161515835261010080830490911615159483019490945263ffffffff6201000082048116958301959095526601000000000000810485166060830181905273ffffffffffffffffffffffffffffffffffffffff6a01000000000000000000009092048216608084015260018401546fffffffffffffffffffffffffffffffff811660a08501526bffffffffffffffffffffffff70010000000000000000000000000000000082041660c08501527c01000000000000000000000000000000000000000000000000000000009004861660e0840152600290930154169281019290925290911461282c576040517f9c0083a200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008281526006602052604090205473ffffffffffffffffffffffffffffffffffffffff163314612889576040517f6352a85300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008281526005602090815260408083208054337fffffffffffffffffffffffff0000000000000000000000000000000000000000808316821790935560069094528285208054909216909155905173ffffffffffffffffffffffffffffffffffffffff90911692839186917f5cff4db96bef051785e999f44bfcd21c18823e034fb92dd376e3db4ce0feeb2c91a4505050565b61292683612ae6565b6017547c0100000000000000000000000000000000000000000000000000000000900463ffffffff16811115612988576040517fae7235df00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008381526007602052604090206129a1828483614c70565b50827fcba2d5723b2ee59e53a8e8a82a4a7caf4fdfe70e9f7c582950bf7e7a5c24e83d838360405161094a929190614dd4565b600060606000806000634b56a42e60e01b8888886040516024016129fa93929190614f23565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909316929092179091529050612a838982610617565b929c919b50995090975095505050505050565b612a9e613985565b612aa781613a06565b50565b600060606000806000806000612acf886040518060200160405280600081525061102a565b959e949d50929b5090995097509550909350915050565b60008181526005602052604090205473ffffffffffffffffffffffffffffffffffffffff163314612b43576040517fa47c170600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000818152600460205260409020546601000000000000900463ffffffff90811614612aa7576040517f9c0083a200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614612c0a576040517fb60ac5db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b6000612c188383613afb565b90505b92915050565b60185473ffffffffffffffffffffffffffffffffffffffff163314612c0a576040517fb6dfb7a600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166000818152602160205260408082205490517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152919290916370a0823190602401602060405180830381865afa158015612d0e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612d329190614e03565b612d3c9190614fb9565b905090565b60405173ffffffffffffffffffffffffffffffffffffffff8316602482015260448101829052612e159084907fa9059cbb00000000000000000000000000000000000000000000000000000000906064015b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090931692909217909152613b4a565b505050565b6000818160045b600f811015612ea7577fff000000000000000000000000000000000000000000000000000000000000008216838260208110612e5f57612e5f614fd9565b1a60f81b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614612e9557506000949350505050565b80612e9f81615008565b915050612e21565b5081600f1a6001811115612ebd57612ebd6146b9565b949350505050565b600080600080846040015162ffffff1690506000808263ffffffff161190506000807f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015612f52573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612f76919061505a565b5094509092505050600081131580612f8d57508142105b80612fae5750828015612fae5750612fa58242614b23565b8463ffffffff16105b15612fbd576019549650612fc1565b8096505b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa15801561302c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613050919061505a565b509450909250505060008113158061306757508142105b806130885750828015613088575061307f8242614b23565b8463ffffffff16105b1561309757601a54955061309b565b8095505b86866130a68a613c56565b965096509650505050509193909250565b60008080808960018111156130ce576130ce6146b9565b036130dd575062017f98613132565b60018960018111156130f1576130f16146b9565b0361310057506201de84613132565b6040517ff2b2d41200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008a60800151600161314591906150aa565b6131539060ff1660406150c3565b601854613181906103a49074010000000000000000000000000000000000000000900463ffffffff16614e41565b61318b9190614e41565b601554604080517fde9ee35e0000000000000000000000000000000000000000000000000000000081528151939450600093849373ffffffffffffffffffffffffffffffffffffffff169263de9ee35e92600480820193918290030181865afa1580156131fc573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061322091906150da565b90925090508183613232836018614e41565b61323c91906150c3565b60808f015161324c9060016150aa565b61325b9060ff166115e06150c3565b6132659190614e41565b61326f9190614e41565b6132799085614e41565b6101008e01516040517f125441400000000000000000000000000000000000000000000000000000000081526004810186905291955073ffffffffffffffffffffffffffffffffffffffff1690631254414090602401602060405180830381865afa1580156132ec573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906133109190614e03565b8d6060015161ffff1661332391906150c3565b945050505060006133348b86613d47565b60008d815260046020526040902054909150610100900460ff16156133995760008c81526023602090815260409182902082518084018452905463ffffffff811680835264010000000090910462ffffff908116928401928352928501525116908201525b60006134038c6040518061012001604052808d63ffffffff1681526020018681526020018781526020018c81526020018b81526020018a81526020018973ffffffffffffffffffffffffffffffffffffffff16815260200185815260200160001515815250613ec3565b6020810151815191925061341691614e1c565b9d9c50505050505050505050505050565b6060600083600181111561343d5761343d6146b9565b03613506576000848152600760205260409081902090517f6e04ff0d000000000000000000000000000000000000000000000000000000009161348291602401615199565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909316929092179091529050613600565b600183600181111561351a5761351a6146b9565b03613100576000828060200190518101906135359190615212565b6000868152600760205260409081902090519192507f40691db4000000000000000000000000000000000000000000000000000000009161357a918491602401615322565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009093169290921790915291506136009050565b9392505050565b60175473ffffffffffffffffffffffffffffffffffffffff163314612c0a576040517f77c3599200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000612c18838361420a565b60006bffffffffffffffffffffffff821115613702576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203960448201527f36206269747300000000000000000000000000000000000000000000000000006064820152608401610bab565b5090565b60405173ffffffffffffffffffffffffffffffffffffffff808516602483015283166044820152606481018290526137649085907f23b872dd0000000000000000000000000000000000000000000000000000000090608401612d93565b50505050565b601454600090819077010000000000000000000000000000000000000000000000900460ff16156137c7576040517f37ed32e800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601480547fffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffff16770100000000000000000000000000000000000000000000001790556040517f4585e33b000000000000000000000000000000000000000000000000000000009061383c9085906024016153ed565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931790925290517f79188d1600000000000000000000000000000000000000000000000000000000815290935073ffffffffffffffffffffffffffffffffffffffff8616906379188d169061390f9087908790600401615400565b60408051808303816000875af115801561392d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906139519190615419565b601480547fffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffff16905590969095509350505050565b60005473ffffffffffffffffffffffffffffffffffffffff163314612c0a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610bab565b3373ffffffffffffffffffffffffffffffffffffffff821603613a85576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610bab565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6000818152600183016020526040812054613b4257508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155612c1b565b506000612c1b565b6000613bac826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65648152508573ffffffffffffffffffffffffffffffffffffffff166142fd9092919063ffffffff16565b805190915015612e155780806020019051810190613bca9190614de8565b612e15576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f742073756363656564000000000000000000000000000000000000000000006064820152608401610bab565b60008060007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015613cc6573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613cea919061505a565b50935050925050600082131580613d0057508042105b80613d3057506000846040015162ffffff16118015613d305750613d248142614b23565b846040015162ffffff16105b15613d40575050601b5492915050565b5092915050565b60408051608081018252600080825260208083018281528385018381526060850184905273ffffffffffffffffffffffffffffffffffffffff878116855260229093528584208054640100000000810462ffffff1690925263ffffffff82169092527b01000000000000000000000000000000000000000000000000000000810460ff16855285517ffeaf968c00000000000000000000000000000000000000000000000000000000815295519495919484936701000000000000009092049091169163feaf968c9160048083019260a09291908290030181865afa158015613e34573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613e58919061505a565b50935050925050600082131580613e6e57508042105b80613e9e57506000866040015162ffffff16118015613e9e5750613e928142614b23565b866040015162ffffff16105b15613eb25760018301546060850152613eba565b606084018290525b50505092915050565b6040805161010081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e081019190915260008260e001516000015160ff1690506000846060015161ffff168460600151613f2e91906150c3565b90508361010001518015613f415750803a105b15613f4957503a5b600060128311613f5a576001613f70565b613f65601284614b23565b613f7090600a615565565b9050600060128410613f83576001613f99565b613f8e846012614b23565b613f9990600a615565565b905060008660a00151876040015188602001518960000151613fbb9190614e41565b613fc590876150c3565b613fcf9190614e41565b613fd991906150c3565b9050614035828860e0015160600151613ff291906150c3565b6001848a60e001516060015161400891906150c3565b6140129190614b23565b61401c86856150c3565b6140269190614e41565b6140309190615571565b613664565b6bffffffffffffffffffffffff1686526080870151614058906140309083615571565b6bffffffffffffffffffffffff1660408088019190915260e088015101516000906140919062ffffff16683635c9adc5dea000006150c3565b9050600081633b9aca008a60a001518b60e001516020015163ffffffff168c604001518d600001518b6140c491906150c3565b6140ce9190614e41565b6140d891906150c3565b6140e291906150c3565b6140ec9190615571565b6140f69190614e41565b9050614139848a60e001516060015161410f91906150c3565b6001868c60e001516060015161412591906150c3565b61412f9190614b23565b61401c88856150c3565b6bffffffffffffffffffffffff166020890152608089015161415f906140309083615571565b6bffffffffffffffffffffffff16606089015260c089015173ffffffffffffffffffffffffffffffffffffffff166080808a01919091528901516141a290613664565b6bffffffffffffffffffffffff1660a0808a01919091528901516141c590613664565b6bffffffffffffffffffffffff1660c089015260e0890151606001516141ea90613664565b6bffffffffffffffffffffffff1660e08901525050505050505092915050565b600081815260018301602052604081205480156142f357600061422e600183614b23565b855490915060009061424290600190614b23565b90508181146142a757600086600001828154811061426257614262614fd9565b906000526020600020015490508087600001848154811061428557614285614fd9565b6000918252602080832090910192909255918252600188019052604090208390555b85548690806142b8576142b86155ac565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050612c1b565b6000915050612c1b565b6060612ebd8484600085856000808673ffffffffffffffffffffffffffffffffffffffff1685876040516143319190614ad8565b60006040518083038185875af1925050503d806000811461436e576040519150601f19603f3d011682016040523d82523d6000602084013e614373565b606091505b50915091506143848783838761438f565b979650505050505050565b6060831561442557825160000361441e5773ffffffffffffffffffffffffffffffffffffffff85163b61441e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610bab565b5081612ebd565b612ebd838381511561443a5781518083602001fd5b806040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610bab91906153ed565b73ffffffffffffffffffffffffffffffffffffffff81168114612aa757600080fd5b600080604083850312156144a357600080fd5b8235915060208301356144b58161446e565b809150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051610100810167ffffffffffffffff81118282101715614513576145136144c0565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715614560576145606144c0565b604052919050565b600067ffffffffffffffff821115614582576145826144c0565b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b600082601f8301126145bf57600080fd5b81356145d26145cd82614568565b614519565b8181528460208386010111156145e757600080fd5b816020850160208301376000918101602001919091529392505050565b6000806040838503121561461757600080fd5b82359150602083013567ffffffffffffffff81111561463557600080fd5b614641858286016145ae565b9150509250929050565b60005b8381101561466657818101518382015260200161464e565b50506000910152565b6000815180845261468781602086016020860161464b565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b600a811061471f577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b9052565b841515815260806020820152600061473e608083018661466f565b905061474d60408301856146e8565b82606083015295945050505050565b60008083601f84011261476e57600080fd5b50813567ffffffffffffffff81111561478657600080fd5b60208301915083602082850101111561479e57600080fd5b9250929050565b6000806000604084860312156147ba57600080fd5b83359250602084013567ffffffffffffffff8111156147d857600080fd5b6147e48682870161475c565b9497909650939450505050565b60006020828403121561480357600080fd5b5035919050565b6000806040838503121561481d57600080fd5b82356148288161446e565b946020939093013593505050565b60008060006060848603121561484b57600080fd5b83356148568161446e565b925060208401356148668161446e565b929592945050506040919091013590565b871515815260e06020820152600061489260e083018961466f565b90506148a160408301886146e8565b8560608301528460808301528360a08301528260c083015298975050505050505050565b600080604083850312156148d857600080fd5b8235915060208301356bffffffffffffffffffffffff811681146144b557600080fd5b63ffffffff81168114612aa757600080fd5b6000806040838503121561492057600080fd5b8235915060208301356144b5816148fb565b600080828403606081121561494657600080fd5b8335925060407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08201121561497a57600080fd5b506020830190509250929050565b600067ffffffffffffffff8211156149a2576149a26144c0565b5060051b60200190565b600080600080606085870312156149c257600080fd5b8435935060208086013567ffffffffffffffff808211156149e257600080fd5b818801915088601f8301126149f657600080fd5b8135614a046145cd82614988565b81815260059190911b8301840190848101908b831115614a2357600080fd5b8585015b83811015614a5b57803585811115614a3f5760008081fd5b614a4d8e89838a01016145ae565b845250918601918601614a27565b50975050506040880135925080831115614a7457600080fd5b5050614a828782880161475c565b95989497509550505050565b600060208284031215614aa057600080fd5b81356136008161446e565b8051614ab68161446e565b919050565b600060208284031215614acd57600080fd5b81516136008161446e565b60008251614aea81846020870161464b565b9190910192915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b81810381811115612c1b57612c1b614af4565b80518015158114614ab657600080fd5b600082601f830112614b5757600080fd5b8151614b656145cd82614568565b818152846020838601011115614b7a57600080fd5b612ebd82602083016020870161464b565b60008060408385031215614b9e57600080fd5b614ba783614b36565b9150602083015167ffffffffffffffff811115614bc357600080fd5b61464185828601614b46565b600181811c90821680614be357607f821691505b602082108103614c1c577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b601f821115612e1557600081815260208120601f850160051c81016020861015614c495750805b601f850160051c820191505b81811015614c6857828155600101614c55565b505050505050565b67ffffffffffffffff831115614c8857614c886144c0565b614c9c83614c968354614bcf565b83614c22565b6000601f841160018114614cee5760008515614cb85750838201355b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600387901b1c1916600186901b178355614d84565b6000838152602090207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0861690835b82811015614d3d5786850135825560209485019460019092019101614d1d565b5086821015614d78577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88860031b161c19848701351681555b505060018560011b0183555b5050505050565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b602081526000612ebd602083018486614d8b565b600060208284031215614dfa57600080fd5b612c1882614b36565b600060208284031215614e1557600080fd5b5051919050565b6bffffffffffffffffffffffff818116838216019080821115613d4057613d40614af4565b80820180821115612c1b57612c1b614af4565b62ffffff81168114612aa757600080fd5b8135614e70816148fb565b63ffffffff811690508154817fffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000082161783556020840135614eb081614e54565b66ffffff000000008160201b16837fffffffffffffffffffffffffffffffffffffffffffffffffff000000000000008416171784555050505050565b604081018235614efb816148fb565b63ffffffff1682526020830135614f1181614e54565b62ffffff811660208401525092915050565b6000604082016040835280865180835260608501915060608160051b8601019250602080890160005b83811015614f98577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0888703018552614f8686835161466f565b95509382019390820190600101614f4c565b505085840381870152505050614faf818587614d8b565b9695505050505050565b8181036000831280158383131683831282161715613d4057613d40614af4565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361503957615039614af4565b5060010190565b805169ffffffffffffffffffff81168114614ab657600080fd5b600080600080600060a0868803121561507257600080fd5b61507b86615040565b945060208601519350604086015192506060860151915061509e60808701615040565b90509295509295909350565b60ff8181168382160190811115612c1b57612c1b614af4565b8082028115828204841417612c1b57612c1b614af4565b600080604083850312156150ed57600080fd5b505080516020909101519092909150565b6000815461510b81614bcf565b80855260206001838116801561512857600181146151605761518e565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff008516838901528284151560051b890101955061518e565b866000528260002060005b858110156151865781548a820186015290830190840161516b565b890184019650505b505050505092915050565b602081526000612c1860208301846150fe565b600082601f8301126151bd57600080fd5b815160206151cd6145cd83614988565b82815260059290921b840181019181810190868411156151ec57600080fd5b8286015b8481101561520757805183529183019183016151f0565b509695505050505050565b60006020828403121561522457600080fd5b815167ffffffffffffffff8082111561523c57600080fd5b90830190610100828603121561525157600080fd5b6152596144ef565b825181526020830151602082015260408301516040820152606083015160608201526080830151608082015261529160a08401614aab565b60a082015260c0830151828111156152a857600080fd5b6152b4878286016151ac565b60c08301525060e0830151828111156152cc57600080fd5b6152d887828601614b46565b60e08301525095945050505050565b600081518084526020808501945080840160005b83811015615317578151875295820195908201906001016152fb565b509495945050505050565b60408152825160408201526020830151606082015260408301516080820152606083015160a0820152608083015160c082015273ffffffffffffffffffffffffffffffffffffffff60a08401511660e0820152600060c08401516101008081850152506153936101408401826152e7565b905060e08501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0848303016101208501526153cf828261466f565b91505082810360208401526153e481856150fe565b95945050505050565b602081526000612c18602083018461466f565b828152604060208201526000612ebd604083018461466f565b6000806040838503121561542c57600080fd5b61543583614b36565b9150602083015190509250929050565b600181815b8085111561549e57817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0482111561548457615484614af4565b8085161561549157918102915b93841c939080029061544a565b509250929050565b6000826154b557506001612c1b565b816154c257506000612c1b565b81600181146154d857600281146154e2576154fe565b6001915050612c1b565b60ff8411156154f3576154f3614af4565b50506001821b612c1b565b5060208310610133831016604e8410600b8410161715615521575081810a612c1b565b61552b8383615445565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0482111561555d5761555d614af4565b029392505050565b6000612c1883836154a6565b6000826155a7577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fdfea164736f6c6343000813000a", + Bin: "0x6101806040523480156200001257600080fd5b5060405162005d2238038062005d2283398101604081905262000035916200062f565b80816001600160a01b031663ca30e6036040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000075573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200009b91906200062f565b826001600160a01b031663226cf83c6040518163ffffffff1660e01b8152600401602060405180830381865afa158015620000da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200010091906200062f565b836001600160a01b031663614486af6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156200013f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200016591906200062f565b846001600160a01b0316636709d0e56040518163ffffffff1660e01b8152600401602060405180830381865afa158015620001a4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620001ca91906200062f565b856001600160a01b0316635425d8ac6040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000209573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200022f91906200062f565b866001600160a01b031663a08714c06040518163ffffffff1660e01b8152600401602060405180830381865afa1580156200026e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200029491906200062f565b876001600160a01b031663c5b964e06040518163ffffffff1660e01b8152600401602060405180830381865afa158015620002d3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620002f9919062000656565b886001600160a01b031663ac4dc59a6040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000338573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200035e91906200062f565b3380600081620003b55760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620003e857620003e8816200056b565b5050506001600160a01b0380891660805287811660a05286811660c05285811660e052848116610100528316610120526025805483919060ff19166001838181111562000439576200043962000679565b0217905550806001600160a01b0316610140816001600160a01b03168152505060c0516001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa1580156200049a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620004c091906200068f565b60ff1660a0516001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000504573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200052a91906200068f565b60ff16146200054c576040516301f86e1760e41b815260040160405180910390fd5b5050506001600160a01b039095166101605250620006b4945050505050565b336001600160a01b03821603620005c55760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401620003ac565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6001600160a01b03811681146200062c57600080fd5b50565b6000602082840312156200064257600080fd5b81516200064f8162000616565b9392505050565b6000602082840312156200066957600080fd5b8151600281106200064f57600080fd5b634e487b7160e01b600052602160045260246000fd5b600060208284031215620006a257600080fd5b815160ff811681146200064f57600080fd5b60805160a05160c05160e051610100516101205161014051610160516155e86200073a60003960008181610182015261022f0152600081816120e7015261228d01526000612bb30152600050506000612ee901526000613c5d01526000612fc3015260008181610c4701528181610d0801528181610dd30152612c7401526155e86000f3fe6080604052600436106101805760003560e01c80638765ecbe116100d6578063aed2e9291161007f578063ce7dc5b411610059578063ce7dc5b4146104b1578063f2fde38b146104d1578063f7d334ba146104f157610180565b8063aed2e9291461043a578063b148ab6b14610471578063cd7f71b51461049157610180565b8063948108f7116100b0578063948108f7146103e7578063a72aa27e146103fa578063a86e17811461041a57610180565b80638765ecbe1461037c5780638da5cb5b1461039c5780638dcf0fe7146103c757610180565b806354b7faae1161013857806371fae17f1161011257806371fae17f14610327578063744bfe611461034757806379ba50971461036757610180565b806354b7faae146102b457806368d369d8146102d457806371791aa0146102f457610180565b8063349e8cca11610169578063349e8cca146102205780634ee88d35146102745780635165f2f51461029457610180565b80631a2af011146101c757806329c5efad146101e7575b7f00000000000000000000000000000000000000000000000000000000000000003660008037600080366000845af43d6000803e8080156101c0573d6000f35b3d6000fd5b005b3480156101d357600080fd5b506101c56101e2366004614490565b610511565b3480156101f357600080fd5b50610207610202366004614604565b610617565b6040516102179493929190614723565b60405180910390f35b34801561022c57600080fd5b507f00000000000000000000000000000000000000000000000000000000000000005b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610217565b34801561028057600080fd5b506101c561028f3660046147a5565b6108f5565b3480156102a057600080fd5b506101c56102af3660046147f1565b610957565b3480156102c057600080fd5b506101c56102cf36600461480a565b610b09565b3480156102e057600080fd5b506101c56102ef366004614836565b610d7c565b34801561030057600080fd5b5061031461030f366004614604565b61102a565b6040516102179796959493929190614877565b34801561033357600080fd5b506101c56103423660046147f1565b61178e565b34801561035357600080fd5b506101c5610362366004614490565b611824565b34801561037357600080fd5b506101c5611c9c565b34801561038857600080fd5b506101c56103973660046147f1565b611d99565b3480156103a857600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff1661024f565b3480156103d357600080fd5b506101c56103e23660046147a5565b611f4e565b6101c56103f53660046148c5565b611fa3565b34801561040657600080fd5b506101c561041536600461490d565b612365565b34801561042657600080fd5b506101c5610435366004614932565b612464565b34801561044657600080fd5b5061045a6104553660046147a5565b612545565b604080519215158352602083019190915201610217565b34801561047d57600080fd5b506101c561048c3660046147f1565b6126f0565b34801561049d57600080fd5b506101c56104ac3660046147a5565b61291d565b3480156104bd57600080fd5b506102076104cc3660046149ac565b6129d4565b3480156104dd57600080fd5b506101c56104ec366004614a8e565b612a96565b3480156104fd57600080fd5b5061031461050c3660046147f1565b612aaa565b61051a82612ae6565b3373ffffffffffffffffffffffffffffffffffffffff821603610569576040517f8c8728c700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008281526006602052604090205473ffffffffffffffffffffffffffffffffffffffff8281169116146106135760008281526006602052604080822080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff851690811790915590519091339185917fb1cbb2c4b8480034c27e06da5f096b8233a8fd4497028593a41ff6df79726b3591a45b5050565b60006060600080610626612b9b565b600086815260046020908152604091829020825161012081018452815460ff8082161515835261010080830490911615159483019490945263ffffffff620100008204811695830195909552660100000000000081048516606083015273ffffffffffffffffffffffffffffffffffffffff6a01000000000000000000009091048116608083015260018301546fffffffffffffffffffffffffffffffff811660a08401526bffffffffffffffffffffffff70010000000000000000000000000000000082041660c08401527c0100000000000000000000000000000000000000000000000000000000900490941660e0820152600290910154909216908201525a9150600080826080015173ffffffffffffffffffffffffffffffffffffffff1663f00e6a2a6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561077c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107a09190614abb565b73ffffffffffffffffffffffffffffffffffffffff16601660000160149054906101000a900463ffffffff1663ffffffff16896040516107e09190614ad8565b60006040518083038160008787f1925050503d806000811461081e576040519150601f19603f3d011682016040523d82523d6000602084013e610823565b606091505b50915091505a6108339085614b23565b93508161085c5760006040518060200160405280600081525060079650965096505050506108ec565b808060200190518101906108709190614b8b565b90975095508661089c5760006040518060200160405280600081525060049650965096505050506108ec565b60185486517401000000000000000000000000000000000000000090910463ffffffff1610156108e85760006040518060200160405280600081525060059650965096505050506108ec565b5050505b92959194509250565b6108fe83612ae6565b6000838152601d60205260409020610917828483614c70565b50827f2b72ac786c97e68dbab71023ed6f2bdbfc80ad9bb7808941929229d71b7d5664838360405161094a929190614dd4565b60405180910390a2505050565b61096081612ae6565b600081815260046020908152604091829020825161012081018452815460ff808216151580845261010080840490921615159584019590955262010000820463ffffffff9081169684019690965266010000000000008204861660608401526a010000000000000000000090910473ffffffffffffffffffffffffffffffffffffffff908116608084015260018401546fffffffffffffffffffffffffffffffff811660a085015270010000000000000000000000000000000081046bffffffffffffffffffffffff1660c08501527c0100000000000000000000000000000000000000000000000000000000900490951660e083015260029092015490931690830152610a9a576040517f1b88a78400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600082815260046020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055610ad9600283612c0c565b5060405182907f7bada562044eb163f6b4003c4553e4e62825344c0418eea087bed5ee05a4745690600090a25050565b610b11612c21565b73ffffffffffffffffffffffffffffffffffffffff8216610b5e576040517f9c8d2cd200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000610b68612c72565b90506000811215610bb4576040517fcf47918100000000000000000000000000000000000000000000000000000000815260006004820152602481018390526044015b60405180910390fd5b80821115610bf8576040517fcf4791810000000000000000000000000000000000000000000000000000000081526004810182905260248101839052604401610bab565b6040517fa9059cbb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8481166004830152602482018490526000917f00000000000000000000000000000000000000000000000000000000000000009091169063a9059cbb906044016020604051808303816000875af1158015610c92573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610cb69190614de8565b905080610cef576040517f90b8ec1800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8373ffffffffffffffffffffffffffffffffffffffff167f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff167f5e110f8bc8a20b65dcc87f224bdf1cc039346e267118bae2739847f07321ffa885604051610d6e91815260200190565b60405180910390a350505050565b610d84612c21565b73ffffffffffffffffffffffffffffffffffffffff8216610dd1576040517f9c8d2cd200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610e56576040517fc1ab6dc100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000610e60612c72565b128015610e835750600060255460ff166001811115610e8157610e816146b9565b145b15610eba576040517f981bb6a000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff83166000818152602160205260408082205490517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152919290916370a0823190602401602060405180830381865afa158015610f36573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f5a9190614e03565b610f649190614b23565b905080821115610faa576040517fcf4791810000000000000000000000000000000000000000000000000000000081526004810182905260248101839052604401610bab565b610fcb73ffffffffffffffffffffffffffffffffffffffff85168484612d41565b8273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f5e110f8bc8a20b65dcc87f224bdf1cc039346e267118bae2739847f07321ffa884604051610d6e91815260200190565b60006060600080600080600061103e612b9b565b60006110498a612e1a565b905060006014604051806101200160405290816000820160009054906101000a90046bffffffffffffffffffffffff166bffffffffffffffffffffffff166bffffffffffffffffffffffff16815260200160008201600c9054906101000a900463ffffffff1663ffffffff1663ffffffff1681526020016000820160109054906101000a900462ffffff1662ffffff1662ffffff1681526020016000820160139054906101000a900461ffff1661ffff1661ffff1681526020016000820160159054906101000a900460ff1660ff1660ff1681526020016000820160169054906101000a900460ff161515151581526020016000820160179054906101000a900460ff161515151581526020016000820160189054906101000a900460ff161515151581526020016001820160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152505090506000600460008d8152602001908152602001600020604051806101200160405290816000820160009054906101000a900460ff161515151581526020016000820160019054906101000a900460ff161515151581526020016000820160029054906101000a900463ffffffff1663ffffffff1663ffffffff1681526020016000820160069054906101000a900463ffffffff1663ffffffff1663ffffffff16815260200160008201600a9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020016001820160009054906101000a90046fffffffffffffffffffffffffffffffff166fffffffffffffffffffffffffffffffff166fffffffffffffffffffffffffffffffff1681526020016001820160109054906101000a90046bffffffffffffffffffffffff166bffffffffffffffffffffffff166bffffffffffffffffffffffff16815260200160018201601c9054906101000a900463ffffffff1663ffffffff1663ffffffff1681526020016002820160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152505090506000808360a001511561140c5750506040805160208101825260008082529290910151919a5098506009975089965063ffffffff169450859350839250611782915050565b606083015163ffffffff908116146114565750506040805160208101825260008082529290910151919a5098506001975089965063ffffffff169450859350839250611782915050565b8251156114955750506040805160208101825260008082529290910151919a5098506002975089965063ffffffff169450859350839250611782915050565b61149e84612ec5565b8094508198508299505050506114c38e858786604001518b8b888a61010001516130b7565b9050806bffffffffffffffffffffffff168360c001516bffffffffffffffffffffffff1610156115255750506040805160208101825260008082529290910151919a5098506006975089965063ffffffff169450859350839250611782915050565b505060006115348d858e613427565b90505a9750600080836080015173ffffffffffffffffffffffffffffffffffffffff1663f00e6a2a6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561158b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115af9190614abb565b73ffffffffffffffffffffffffffffffffffffffff16601660000160149054906101000a900463ffffffff1663ffffffff16846040516115ef9190614ad8565b60006040518083038160008787f1925050503d806000811461162d576040519150601f19603f3d011682016040523d82523d6000602084013e611632565b606091505b50915091505a611642908b614b23565b9950816116c8576018548151780100000000000000000000000000000000000000000000000090910463ffffffff1610156116a75750506040805160208101825260008082529390910151929b509950600898505063ffffffff169450611782915050565b604090930151929a50600399505063ffffffff909116955061178292505050565b808060200190518101906116dc9190614b8b565b909d509b508c6117165750506040805160208101825260008082529390910151929b509950600498505063ffffffff169450611782915050565b6018548c517401000000000000000000000000000000000000000090910463ffffffff1610156117705750506040805160208101825260008082529390910151929b509950600598505063ffffffff169450611782915050565b5050506040015163ffffffff16945050505b92959891949750929550565b611796613607565b600081815260046020908152604080832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055602390915280822080547fffffffffffffffffffffffffffffffffffffffffffffffffff000000000000001690555182917f97d0ef3f46a56168af653f547bdb6f77ec2b1d7d9bc6ba0193c2b340ec68064a91a250565b60145477010000000000000000000000000000000000000000000000900460ff161561187c576040517f37ed32e800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601480547fffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffff167701000000000000000000000000000000000000000000000017905573ffffffffffffffffffffffffffffffffffffffff811661190b576040517f9c8d2cd200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000828152600460209081526040808320815161012081018352815460ff8082161515835261010080830490911615158387015263ffffffff620100008304811684870152660100000000000083048116606085015273ffffffffffffffffffffffffffffffffffffffff6a01000000000000000000009093048316608085015260018501546fffffffffffffffffffffffffffffffff811660a08601526bffffffffffffffffffffffff70010000000000000000000000000000000082041660c08601527c010000000000000000000000000000000000000000000000000000000090041660e084015260029093015481169282019290925286855260059093529220549091163314611a4b576040517fa47c170600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601554604080517f57e871e7000000000000000000000000000000000000000000000000000000008152905173ffffffffffffffffffffffffffffffffffffffff909216916357e871e7916004808201926020929091908290030181865afa158015611abb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611adf9190614e03565b816060015163ffffffff161115611b22576040517fff84e5dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008381526004602090815260408083206001015461010085015173ffffffffffffffffffffffffffffffffffffffff1684526021909252909120547001000000000000000000000000000000009091046bffffffffffffffffffffffff1690611b8d908290614b23565b6101008301805173ffffffffffffffffffffffffffffffffffffffff908116600090815260216020908152604080832095909555888252600490529290922060010180547fffffffff000000000000000000000000ffffffffffffffffffffffffffffffff16905551611c109116846bffffffffffffffffffffffff8416612d41565b604080516bffffffffffffffffffffffff8316815273ffffffffffffffffffffffffffffffffffffffff8516602082015285917ff3b5906e5672f3e524854103bcafbbdba80dbdfeca2c35e116127b1060a68318910160405180910390a25050601480547fffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffff1690555050565b60015473ffffffffffffffffffffffffffffffffffffffff163314611d1d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e6572000000000000000000006044820152606401610bab565b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b611da281612ae6565b600081815260046020908152604091829020825161012081018452815460ff808216158015845261010080840490921615159584019590955262010000820463ffffffff9081169684019690965266010000000000008204861660608401526a010000000000000000000090910473ffffffffffffffffffffffffffffffffffffffff908116608084015260018401546fffffffffffffffffffffffffffffffff811660a085015270010000000000000000000000000000000081046bffffffffffffffffffffffff1660c08501527c0100000000000000000000000000000000000000000000000000000000900490951660e083015260029092015490931690830152611edc576040517f514b6c2400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600082815260046020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055611f1e600283613658565b5060405182907f8ab10247ce168c27748e656ecf852b951fcaac790c18106b19aa0ae57a8b741f90600090a25050565b611f5783612ae6565b6000838152601e60205260409020611f70828483614c70565b50827f3e8740446213c8a77d40e08f79136ce3f347d13ed270a6ebdf57159e0faf4850838360405161094a929190614dd4565b600082815260046020908152604091829020825161012081018452815460ff8082161515835261010080830490911615159483019490945263ffffffff6201000082048116958301959095526601000000000000810485166060830181905273ffffffffffffffffffffffffffffffffffffffff6a01000000000000000000009092048216608084015260018401546fffffffffffffffffffffffffffffffff811660a08501526bffffffffffffffffffffffff70010000000000000000000000000000000082041660c08501527c01000000000000000000000000000000000000000000000000000000009004861660e084015260029093015416928101929092529091146120df576040517f9c0083a200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b341561217b577f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1681610100015173ffffffffffffffffffffffffffffffffffffffff161461216f576040517fc1ab6dc100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61217834613664565b91505b818160c0015161218b9190614e1c565b600084815260046020908152604080832060010180547fffffffff000000000000000000000000ffffffffffffffffffffffffffffffff167001000000000000000000000000000000006bffffffffffffffffffffffff9687160217905561010085015173ffffffffffffffffffffffffffffffffffffffff168352602190915290205461221b91841690614e41565b61010082015173ffffffffffffffffffffffffffffffffffffffff1660009081526021602052604081209190915534900361228b576101008101516122869073ffffffffffffffffffffffffffffffffffffffff1633306bffffffffffffffffffffffff8616613706565b61231b565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663d0e30db0836bffffffffffffffffffffffff166040518263ffffffff1660e01b81526004016000604051808303818588803b15801561230157600080fd5b505af1158015612315573d6000803e3d6000fd5b50505050505b6040516bffffffffffffffffffffffff83168152339084907fafd24114486da8ebfc32f3626dada8863652e187461aa74d4bfa7348915062039060200160405180910390a3505050565b6108fc8163ffffffff1610806123a2575060165463ffffffff78010000000000000000000000000000000000000000000000009091048116908216115b156123d9576040517f14c237fb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6123e282612ae6565b60008281526004602090815260409182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffff00000000ffff166201000063ffffffff861690810291909117909155915191825283917fc24c07e655ce79fba8a589778987d3c015bc6af1632bb20cf9182e02a65d972c91015b60405180910390a25050565b61246c613607565b6000828152600460205260409020546601000000000000900463ffffffff908116146124c4576040517f9c0083a200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600082815260046020908152604080832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790556023909152902081906125128282614e65565b905050817fd8a6d79d170a55968079d3a89b960d86b4442aef6aac1d01e644c32b9e38b340826040516124589190614eec565b600080612550612b9b565b601454760100000000000000000000000000000000000000000000900460ff16156125a7576040517f24522f3400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600085815260046020908152604091829020825161012081018452815460ff8082161515835261010080830490911615158386015262010000820463ffffffff90811684880181905266010000000000008404821660608601526a010000000000000000000090930473ffffffffffffffffffffffffffffffffffffffff9081166080860181905260018701546fffffffffffffffffffffffffffffffff811660a088015270010000000000000000000000000000000081046bffffffffffffffffffffffff1660c08801527c0100000000000000000000000000000000000000000000000000000000900490921660e0860152600290950154909416908301528451601f890185900485028101850190955287855290936126e393899089908190840183828082843760009201919091525061376a92505050565b9097909650945050505050565b600081815260046020908152604091829020825161012081018452815460ff8082161515835261010080830490911615159483019490945263ffffffff6201000082048116958301959095526601000000000000810485166060830181905273ffffffffffffffffffffffffffffffffffffffff6a01000000000000000000009092048216608084015260018401546fffffffffffffffffffffffffffffffff811660a08501526bffffffffffffffffffffffff70010000000000000000000000000000000082041660c08501527c01000000000000000000000000000000000000000000000000000000009004861660e0840152600290930154169281019290925290911461282c576040517f9c0083a200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008281526006602052604090205473ffffffffffffffffffffffffffffffffffffffff163314612889576040517f6352a85300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008281526005602090815260408083208054337fffffffffffffffffffffffff0000000000000000000000000000000000000000808316821790935560069094528285208054909216909155905173ffffffffffffffffffffffffffffffffffffffff90911692839186917f5cff4db96bef051785e999f44bfcd21c18823e034fb92dd376e3db4ce0feeb2c91a4505050565b61292683612ae6565b6017547c0100000000000000000000000000000000000000000000000000000000900463ffffffff16811115612988576040517fae7235df00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008381526007602052604090206129a1828483614c70565b50827fcba2d5723b2ee59e53a8e8a82a4a7caf4fdfe70e9f7c582950bf7e7a5c24e83d838360405161094a929190614dd4565b600060606000806000634b56a42e60e01b8888886040516024016129fa93929190614f23565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909316929092179091529050612a838982610617565b929c919b50995090975095505050505050565b612a9e613985565b612aa781613a06565b50565b600060606000806000806000612acf886040518060200160405280600081525061102a565b959e949d50929b5090995097509550909350915050565b60008181526005602052604090205473ffffffffffffffffffffffffffffffffffffffff163314612b43576040517fa47c170600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000818152600460205260409020546601000000000000900463ffffffff90811614612aa7576040517f9c0083a200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614612c0a576040517fb60ac5db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b6000612c188383613afb565b90505b92915050565b60185473ffffffffffffffffffffffffffffffffffffffff163314612c0a576040517fb6dfb7a600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166000818152602160205260408082205490517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152919290916370a0823190602401602060405180830381865afa158015612d0e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612d329190614e03565b612d3c9190614fb9565b905090565b60405173ffffffffffffffffffffffffffffffffffffffff8316602482015260448101829052612e159084907fa9059cbb00000000000000000000000000000000000000000000000000000000906064015b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090931692909217909152613b4a565b505050565b6000818160045b600f811015612ea7577fff000000000000000000000000000000000000000000000000000000000000008216838260208110612e5f57612e5f614fd9565b1a60f81b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614612e9557506000949350505050565b80612e9f81615008565b915050612e21565b5081600f1a6001811115612ebd57612ebd6146b9565b949350505050565b600080600080846040015162ffffff1690506000808263ffffffff161190506000807f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015612f52573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612f76919061505a565b5094509092505050600081131580612f8d57508142105b80612fae5750828015612fae5750612fa58242614b23565b8463ffffffff16105b15612fbd576019549650612fc1565b8096505b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa15801561302c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613050919061505a565b509450909250505060008113158061306757508142105b806130885750828015613088575061307f8242614b23565b8463ffffffff16105b1561309757601a54955061309b565b8095505b86866130a68a613c56565b965096509650505050509193909250565b60008080808960018111156130ce576130ce6146b9565b036130dd575062017f98613132565b60018960018111156130f1576130f16146b9565b0361310057506201e26c613132565b6040517ff2b2d41200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008a60800151600161314591906150aa565b6131539060ff1660406150c3565b601854613181906103a49074010000000000000000000000000000000000000000900463ffffffff16614e41565b61318b9190614e41565b601554604080517fde9ee35e0000000000000000000000000000000000000000000000000000000081528151939450600093849373ffffffffffffffffffffffffffffffffffffffff169263de9ee35e92600480820193918290030181865afa1580156131fc573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061322091906150da565b90925090508183613232836018614e41565b61323c91906150c3565b60808f015161324c9060016150aa565b61325b9060ff166115e06150c3565b6132659190614e41565b61326f9190614e41565b6132799085614e41565b6101008e01516040517f125441400000000000000000000000000000000000000000000000000000000081526004810186905291955073ffffffffffffffffffffffffffffffffffffffff1690631254414090602401602060405180830381865afa1580156132ec573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906133109190614e03565b8d6060015161ffff1661332391906150c3565b945050505060006133348b86613d47565b60008d815260046020526040902054909150610100900460ff16156133995760008c81526023602090815260409182902082518084018452905463ffffffff811680835264010000000090910462ffffff908116928401928352928501525116908201525b60006134038c6040518061012001604052808d63ffffffff1681526020018681526020018781526020018c81526020018b81526020018a81526020018973ffffffffffffffffffffffffffffffffffffffff16815260200185815260200160001515815250613ec3565b6020810151815191925061341691614e1c565b9d9c50505050505050505050505050565b6060600083600181111561343d5761343d6146b9565b03613506576000848152600760205260409081902090517f6e04ff0d000000000000000000000000000000000000000000000000000000009161348291602401615199565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909316929092179091529050613600565b600183600181111561351a5761351a6146b9565b03613100576000828060200190518101906135359190615212565b6000868152600760205260409081902090519192507f40691db4000000000000000000000000000000000000000000000000000000009161357a918491602401615322565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009093169290921790915291506136009050565b9392505050565b60175473ffffffffffffffffffffffffffffffffffffffff163314612c0a576040517f77c3599200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000612c18838361420a565b60006bffffffffffffffffffffffff821115613702576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203960448201527f36206269747300000000000000000000000000000000000000000000000000006064820152608401610bab565b5090565b60405173ffffffffffffffffffffffffffffffffffffffff808516602483015283166044820152606481018290526137649085907f23b872dd0000000000000000000000000000000000000000000000000000000090608401612d93565b50505050565b601454600090819077010000000000000000000000000000000000000000000000900460ff16156137c7576040517f37ed32e800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601480547fffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffff16770100000000000000000000000000000000000000000000001790556040517f4585e33b000000000000000000000000000000000000000000000000000000009061383c9085906024016153ed565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931790925290517f79188d1600000000000000000000000000000000000000000000000000000000815290935073ffffffffffffffffffffffffffffffffffffffff8616906379188d169061390f9087908790600401615400565b60408051808303816000875af115801561392d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906139519190615419565b601480547fffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffff16905590969095509350505050565b60005473ffffffffffffffffffffffffffffffffffffffff163314612c0a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610bab565b3373ffffffffffffffffffffffffffffffffffffffff821603613a85576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610bab565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6000818152600183016020526040812054613b4257508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155612c1b565b506000612c1b565b6000613bac826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65648152508573ffffffffffffffffffffffffffffffffffffffff166142fd9092919063ffffffff16565b805190915015612e155780806020019051810190613bca9190614de8565b612e15576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f742073756363656564000000000000000000000000000000000000000000006064820152608401610bab565b60008060007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015613cc6573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613cea919061505a565b50935050925050600082131580613d0057508042105b80613d3057506000846040015162ffffff16118015613d305750613d248142614b23565b846040015162ffffff16105b15613d40575050601b5492915050565b5092915050565b60408051608081018252600080825260208083018281528385018381526060850184905273ffffffffffffffffffffffffffffffffffffffff878116855260229093528584208054640100000000810462ffffff1690925263ffffffff82169092527b01000000000000000000000000000000000000000000000000000000810460ff16855285517ffeaf968c00000000000000000000000000000000000000000000000000000000815295519495919484936701000000000000009092049091169163feaf968c9160048083019260a09291908290030181865afa158015613e34573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613e58919061505a565b50935050925050600082131580613e6e57508042105b80613e9e57506000866040015162ffffff16118015613e9e5750613e928142614b23565b866040015162ffffff16105b15613eb25760018301546060850152613eba565b606084018290525b50505092915050565b6040805161010081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e081019190915260008260e001516000015160ff1690506000846060015161ffff168460600151613f2e91906150c3565b90508361010001518015613f415750803a105b15613f4957503a5b600060128311613f5a576001613f70565b613f65601284614b23565b613f7090600a615565565b9050600060128410613f83576001613f99565b613f8e846012614b23565b613f9990600a615565565b905060008660a00151876040015188602001518960000151613fbb9190614e41565b613fc590876150c3565b613fcf9190614e41565b613fd991906150c3565b9050614035828860e0015160600151613ff291906150c3565b6001848a60e001516060015161400891906150c3565b6140129190614b23565b61401c86856150c3565b6140269190614e41565b6140309190615571565b613664565b6bffffffffffffffffffffffff1686526080870151614058906140309083615571565b6bffffffffffffffffffffffff1660408088019190915260e088015101516000906140919062ffffff16683635c9adc5dea000006150c3565b9050600081633b9aca008a60a001518b60e001516020015163ffffffff168c604001518d600001518b6140c491906150c3565b6140ce9190614e41565b6140d891906150c3565b6140e291906150c3565b6140ec9190615571565b6140f69190614e41565b9050614139848a60e001516060015161410f91906150c3565b6001868c60e001516060015161412591906150c3565b61412f9190614b23565b61401c88856150c3565b6bffffffffffffffffffffffff166020890152608089015161415f906140309083615571565b6bffffffffffffffffffffffff16606089015260c089015173ffffffffffffffffffffffffffffffffffffffff166080808a01919091528901516141a290613664565b6bffffffffffffffffffffffff1660a0808a01919091528901516141c590613664565b6bffffffffffffffffffffffff1660c089015260e0890151606001516141ea90613664565b6bffffffffffffffffffffffff1660e08901525050505050505092915050565b600081815260018301602052604081205480156142f357600061422e600183614b23565b855490915060009061424290600190614b23565b90508181146142a757600086600001828154811061426257614262614fd9565b906000526020600020015490508087600001848154811061428557614285614fd9565b6000918252602080832090910192909255918252600188019052604090208390555b85548690806142b8576142b86155ac565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050612c1b565b6000915050612c1b565b6060612ebd8484600085856000808673ffffffffffffffffffffffffffffffffffffffff1685876040516143319190614ad8565b60006040518083038185875af1925050503d806000811461436e576040519150601f19603f3d011682016040523d82523d6000602084013e614373565b606091505b50915091506143848783838761438f565b979650505050505050565b6060831561442557825160000361441e5773ffffffffffffffffffffffffffffffffffffffff85163b61441e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610bab565b5081612ebd565b612ebd838381511561443a5781518083602001fd5b806040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610bab91906153ed565b73ffffffffffffffffffffffffffffffffffffffff81168114612aa757600080fd5b600080604083850312156144a357600080fd5b8235915060208301356144b58161446e565b809150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051610100810167ffffffffffffffff81118282101715614513576145136144c0565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715614560576145606144c0565b604052919050565b600067ffffffffffffffff821115614582576145826144c0565b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b600082601f8301126145bf57600080fd5b81356145d26145cd82614568565b614519565b8181528460208386010111156145e757600080fd5b816020850160208301376000918101602001919091529392505050565b6000806040838503121561461757600080fd5b82359150602083013567ffffffffffffffff81111561463557600080fd5b614641858286016145ae565b9150509250929050565b60005b8381101561466657818101518382015260200161464e565b50506000910152565b6000815180845261468781602086016020860161464b565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b600a811061471f577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b9052565b841515815260806020820152600061473e608083018661466f565b905061474d60408301856146e8565b82606083015295945050505050565b60008083601f84011261476e57600080fd5b50813567ffffffffffffffff81111561478657600080fd5b60208301915083602082850101111561479e57600080fd5b9250929050565b6000806000604084860312156147ba57600080fd5b83359250602084013567ffffffffffffffff8111156147d857600080fd5b6147e48682870161475c565b9497909650939450505050565b60006020828403121561480357600080fd5b5035919050565b6000806040838503121561481d57600080fd5b82356148288161446e565b946020939093013593505050565b60008060006060848603121561484b57600080fd5b83356148568161446e565b925060208401356148668161446e565b929592945050506040919091013590565b871515815260e06020820152600061489260e083018961466f565b90506148a160408301886146e8565b8560608301528460808301528360a08301528260c083015298975050505050505050565b600080604083850312156148d857600080fd5b8235915060208301356bffffffffffffffffffffffff811681146144b557600080fd5b63ffffffff81168114612aa757600080fd5b6000806040838503121561492057600080fd5b8235915060208301356144b5816148fb565b600080828403606081121561494657600080fd5b8335925060407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08201121561497a57600080fd5b506020830190509250929050565b600067ffffffffffffffff8211156149a2576149a26144c0565b5060051b60200190565b600080600080606085870312156149c257600080fd5b8435935060208086013567ffffffffffffffff808211156149e257600080fd5b818801915088601f8301126149f657600080fd5b8135614a046145cd82614988565b81815260059190911b8301840190848101908b831115614a2357600080fd5b8585015b83811015614a5b57803585811115614a3f5760008081fd5b614a4d8e89838a01016145ae565b845250918601918601614a27565b50975050506040880135925080831115614a7457600080fd5b5050614a828782880161475c565b95989497509550505050565b600060208284031215614aa057600080fd5b81356136008161446e565b8051614ab68161446e565b919050565b600060208284031215614acd57600080fd5b81516136008161446e565b60008251614aea81846020870161464b565b9190910192915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b81810381811115612c1b57612c1b614af4565b80518015158114614ab657600080fd5b600082601f830112614b5757600080fd5b8151614b656145cd82614568565b818152846020838601011115614b7a57600080fd5b612ebd82602083016020870161464b565b60008060408385031215614b9e57600080fd5b614ba783614b36565b9150602083015167ffffffffffffffff811115614bc357600080fd5b61464185828601614b46565b600181811c90821680614be357607f821691505b602082108103614c1c577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b601f821115612e1557600081815260208120601f850160051c81016020861015614c495750805b601f850160051c820191505b81811015614c6857828155600101614c55565b505050505050565b67ffffffffffffffff831115614c8857614c886144c0565b614c9c83614c968354614bcf565b83614c22565b6000601f841160018114614cee5760008515614cb85750838201355b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600387901b1c1916600186901b178355614d84565b6000838152602090207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0861690835b82811015614d3d5786850135825560209485019460019092019101614d1d565b5086821015614d78577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88860031b161c19848701351681555b505060018560011b0183555b5050505050565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b602081526000612ebd602083018486614d8b565b600060208284031215614dfa57600080fd5b612c1882614b36565b600060208284031215614e1557600080fd5b5051919050565b6bffffffffffffffffffffffff818116838216019080821115613d4057613d40614af4565b80820180821115612c1b57612c1b614af4565b62ffffff81168114612aa757600080fd5b8135614e70816148fb565b63ffffffff811690508154817fffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000082161783556020840135614eb081614e54565b66ffffff000000008160201b16837fffffffffffffffffffffffffffffffffffffffffffffffffff000000000000008416171784555050505050565b604081018235614efb816148fb565b63ffffffff1682526020830135614f1181614e54565b62ffffff811660208401525092915050565b6000604082016040835280865180835260608501915060608160051b8601019250602080890160005b83811015614f98577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0888703018552614f8686835161466f565b95509382019390820190600101614f4c565b505085840381870152505050614faf818587614d8b565b9695505050505050565b8181036000831280158383131683831282161715613d4057613d40614af4565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361503957615039614af4565b5060010190565b805169ffffffffffffffffffff81168114614ab657600080fd5b600080600080600060a0868803121561507257600080fd5b61507b86615040565b945060208601519350604086015192506060860151915061509e60808701615040565b90509295509295909350565b60ff8181168382160190811115612c1b57612c1b614af4565b8082028115828204841417612c1b57612c1b614af4565b600080604083850312156150ed57600080fd5b505080516020909101519092909150565b6000815461510b81614bcf565b80855260206001838116801561512857600181146151605761518e565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff008516838901528284151560051b890101955061518e565b866000528260002060005b858110156151865781548a820186015290830190840161516b565b890184019650505b505050505092915050565b602081526000612c1860208301846150fe565b600082601f8301126151bd57600080fd5b815160206151cd6145cd83614988565b82815260059290921b840181019181810190868411156151ec57600080fd5b8286015b8481101561520757805183529183019183016151f0565b509695505050505050565b60006020828403121561522457600080fd5b815167ffffffffffffffff8082111561523c57600080fd5b90830190610100828603121561525157600080fd5b6152596144ef565b825181526020830151602082015260408301516040820152606083015160608201526080830151608082015261529160a08401614aab565b60a082015260c0830151828111156152a857600080fd5b6152b4878286016151ac565b60c08301525060e0830151828111156152cc57600080fd5b6152d887828601614b46565b60e08301525095945050505050565b600081518084526020808501945080840160005b83811015615317578151875295820195908201906001016152fb565b509495945050505050565b60408152825160408201526020830151606082015260408301516080820152606083015160a0820152608083015160c082015273ffffffffffffffffffffffffffffffffffffffff60a08401511660e0820152600060c08401516101008081850152506153936101408401826152e7565b905060e08501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0848303016101208501526153cf828261466f565b91505082810360208401526153e481856150fe565b95945050505050565b602081526000612c18602083018461466f565b828152604060208201526000612ebd604083018461466f565b6000806040838503121561542c57600080fd5b61543583614b36565b9150602083015190509250929050565b600181815b8085111561549e57817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0482111561548457615484614af4565b8085161561549157918102915b93841c939080029061544a565b509250929050565b6000826154b557506001612c1b565b816154c257506000612c1b565b81600181146154d857600281146154e2576154fe565b6001915050612c1b565b60ff8411156154f3576154f3614af4565b50506001821b612c1b565b5060208310610133831016604e8410600b8410161715615521575081810a612c1b565b61552b8383615445565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0482111561555d5761555d614af4565b029392505050565b6000612c1883836154a6565b6000826155a7577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fdfea164736f6c6343000813000a", } var AutomationRegistryLogicBABI = AutomationRegistryLogicBMetaData.ABI diff --git a/core/gethwrappers/generated/automation_registry_logic_c_wrapper_2_3/automation_registry_logic_c_wrapper_2_3.go b/core/gethwrappers/generated/automation_registry_logic_c_wrapper_2_3/automation_registry_logic_c_wrapper_2_3.go index e32d6890dd..d5e6d9d64f 100644 --- a/core/gethwrappers/generated/automation_registry_logic_c_wrapper_2_3/automation_registry_logic_c_wrapper_2_3.go +++ b/core/gethwrappers/generated/automation_registry_logic_c_wrapper_2_3/automation_registry_logic_c_wrapper_2_3.go @@ -151,7 +151,7 @@ type IAutomationV21PlusCommonUpkeepInfoLegacy struct { var AutomationRegistryLogicCMetaData = &bind.MetaData{ ABI: "[{\"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\":\"enumAutomationRegistryBase2_3.PayoutMode\",\"name\":\"payoutMode\",\"type\":\"uint8\"},{\"internalType\":\"address\",\"name\":\"wrappedNativeTokenAddress\",\"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\":\"structAutomationRegistryBase2_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\":\"contractIERC20Metadata\",\"name\":\"token\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"uint32\",\"name\":\"gasFeePPB\",\"type\":\"uint32\"},{\"internalType\":\"uint24\",\"name\":\"flatFeeMilliCents\",\"type\":\"uint24\"},{\"internalType\":\"contractAggregatorV3Interface\",\"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\":\"structAutomationRegistryBase2_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\":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\":\"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\":\"contractIERC20Metadata\",\"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\":\"structAutomationRegistryBase2_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\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"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\":\"contractIERC20Metadata\",\"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\":\"contractIERC20Metadata\",\"name\":\"billingToken\",\"type\":\"address\"}],\"name\":\"getBillingConfig\",\"outputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"gasFeePPB\",\"type\":\"uint32\"},{\"internalType\":\"uint24\",\"name\":\"flatFeeMilliCents\",\"type\":\"uint24\"},{\"internalType\":\"contractAggregatorV3Interface\",\"name\":\"priceFeed\",\"type\":\"address\"},{\"internalType\":\"uint8\",\"name\":\"decimals\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"fallbackPrice\",\"type\":\"uint256\"},{\"internalType\":\"uint96\",\"name\":\"minSpend\",\"type\":\"uint96\"}],\"internalType\":\"structAutomationRegistryBase2_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\":\"structAutomationRegistryBase2_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\":\"contractIERC20Metadata\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contractIERC20Metadata\",\"name\":\"token\",\"type\":\"address\"}],\"name\":\"getBillingTokenConfig\",\"outputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"gasFeePPB\",\"type\":\"uint32\"},{\"internalType\":\"uint24\",\"name\":\"flatFeeMilliCents\",\"type\":\"uint24\"},{\"internalType\":\"contractAggregatorV3Interface\",\"name\":\"priceFeed\",\"type\":\"address\"},{\"internalType\":\"uint8\",\"name\":\"decimals\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"fallbackPrice\",\"type\":\"uint256\"},{\"internalType\":\"uint96\",\"name\":\"minSpend\",\"type\":\"uint96\"}],\"internalType\":\"structAutomationRegistryBase2_3.BillingConfig\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getBillingTokens\",\"outputs\":[{\"internalType\":\"contractIERC20Metadata[]\",\"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\":\"contractIChainModule\",\"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\":\"contractIChainModule\",\"name\":\"chainModule\",\"type\":\"address\"}],\"internalType\":\"structAutomationRegistryBase2_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\":\"contractIAutomationForwarder\",\"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\":\"contractIChainModule\",\"name\":\"chainModule\",\"type\":\"address\"}],\"internalType\":\"structAutomationRegistryBase2_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\":\"enumAutomationRegistryBase2_3.Trigger\",\"name\":\"triggerType\",\"type\":\"uint8\"},{\"internalType\":\"uint32\",\"name\":\"gasLimit\",\"type\":\"uint32\"},{\"internalType\":\"contractIERC20Metadata\",\"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\":\"enumAutomationRegistryBase2_3.PayoutMode\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"peer\",\"type\":\"address\"}],\"name\":\"getPeerRegistryMigrationPermission\",\"outputs\":[{\"internalType\":\"enumAutomationRegistryBase2_3.MigrationPermission\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getPerPerformByteGasOverhead\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"pure\",\"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\":\"contractIERC20Metadata\",\"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\":\"structIAutomationV21PlusCommon.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\":\"structIAutomationV21PlusCommon.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\":\"structAutomationRegistryBase2_3.Storage\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getTransmitCalldataFixedBytesOverhead\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getTransmitCalldataPerSignerBytesOverhead\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"pure\",\"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\":\"structAutomationRegistryBase2_3.TransmitterPayeeInfo[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"upkeepId\",\"type\":\"uint256\"}],\"name\":\"getTriggerType\",\"outputs\":[{\"internalType\":\"enumAutomationRegistryBase2_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\":\"structIAutomationV21PlusCommon.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\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"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\":\"enumAutomationRegistryBase2_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\":\"contractIERC20Metadata\",\"name\":\"token\",\"type\":\"address\"}],\"name\":\"supportsBillingToken\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"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\"}]", - Bin: "0x6101606040523480156200001257600080fd5b5060405162005c8738038062005c87833981016040819052620000359162000309565b87878787878787873380600081620000945760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620000c757620000c78162000241565b5050506001600160a01b0380891660805287811660a05286811660c05285811660e052848116610100528316610120526025805483919060ff191660018381811115620001185762000118620003b6565b0217905550806001600160a01b0316610140816001600160a01b03168152505060c0516001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000179573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200019f9190620003cc565b60ff1660a0516001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015620001e3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620002099190620003cc565b60ff16146200022b576040516301f86e1760e41b815260040160405180910390fd5b50505050505050505050505050505050620003f8565b336001600160a01b038216036200029b5760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c6600000000000000000060448201526064016200008b565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b80516001600160a01b03811681146200030457600080fd5b919050565b600080600080600080600080610100898b0312156200032757600080fd5b6200033289620002ec565b97506200034260208a01620002ec565b96506200035260408a01620002ec565b95506200036260608a01620002ec565b94506200037260808a01620002ec565b93506200038260a08a01620002ec565b925060c0890151600281106200039757600080fd5b9150620003a760e08a01620002ec565b90509295985092959890939650565b634e487b7160e01b600052602160045260246000fd5b600060208284031215620003df57600080fd5b815160ff81168114620003f157600080fd5b9392505050565b60805160a05160c05160e051610100516101205161014051615803620004846000396000610b7001526000610ad2015260006107be0152600081816109a001526135b60152600081816109540152613e3201526000818161051d0152613690015260008181610c1801528181611df00152818161256b015281816125c80152613af601526158036000f3fe608060405234801561001057600080fd5b50600436106103d05760003560e01c80638ed02bab116101ff578063c3f909d41161011a578063eb5dcd6c116100ad578063f5a418461161007c578063f5a4184614610eb4578063f777ff0614610f35578063faa3e99614610f3c578063ffd242bd14610f8257600080fd5b8063eb5dcd6c14610de5578063ec4de4ba14610df8578063ed56b3e114610e2e578063f2fde38b14610ea157600080fd5b8063d09dc339116100e9578063d09dc33914610c3c578063d763264814610c44578063d85aa07c14610c57578063e80a3d4414610c5f57600080fd5b8063c3f909d414610bd6578063c5b964e014610beb578063c7c3a19a14610bf6578063ca30e60314610c1657600080fd5b8063aab9edd611610192578063b3596c2311610161578063b3596c23146107e2578063b6511a2a14610ba7578063b657bc9c14610bae578063ba87666814610bc157600080fd5b8063aab9edd614610b57578063abc76ae014610b66578063ac4dc59a14610b6e578063b121e14714610b9457600080fd5b8063a08714c0116101ce578063a08714c014610ad0578063a538b2eb14610af6578063a710b22114610b3c578063a87f45fe14610b4f57600080fd5b80638ed02bab14610a5c5780639089daa414610a7a57806393f6ebcf14610a8f5780639e0a99ed14610ac857600080fd5b806343cc055c116102ef5780636209e1e91161028257806379ba50971161025157806379ba5097146109ea57806379ea9943146109f25780638456cb5914610a365780638da5cb5b14610a3e57600080fd5b80636209e1e91461098b5780636709d0e51461099e578063671d36ed146109c45780636eec02a2146109d757600080fd5b80635425d8ac116102be5780635425d8ac146107bc57806357359584146107e2578063614486af146109525780636181d82d1461097857600080fd5b806343cc055c1461074957806344cb70b8146107705780634ca16c52146107935780635147cd591461079c57600080fd5b8063207b6516116103675780633b9cce59116103365780633b9cce59146106c05780633f4ba83a146106d3578063421d183b146106db57806343b46e5f1461074157600080fd5b8063207b651614610508578063226cf83c1461051b578063232c1cc5146105625780633408f73a1461056957600080fd5b8063187256e8116103a3578063187256e81461043b57806319d97a941461044e5780631e0104391461046e5780631efcf646146104d057600080fd5b8063050ee65d146103d557806306e3b632146103ed5780630b7d33e61461040d5780631865c57d14610422575b600080fd5b6201de845b6040519081526020015b60405180910390f35b6104006103fb36600461447a565b610f8a565b6040516103e491906144d7565b61042061041b366004614533565b6110a7565b005b61042a611108565b6040516103e495949392919061472b565b61042061044936600461486c565b611508565b61046161045c3660046148a9565b611579565b6040516103e49190614926565b6104b361047c3660046148a9565b60009081526004602052604090206001015470010000000000000000000000000000000090046bffffffffffffffffffffffff1690565b6040516bffffffffffffffffffffffff90911681526020016103e4565b6104f86104de3660046148a9565b600090815260046020526040902054610100900460ff1690565b60405190151581526020016103e4565b6104616105163660046148a9565b61161b565b7f00000000000000000000000000000000000000000000000000000000000000005b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016103e4565b60186103da565b6106b36040805161016081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e081018290526101008101829052610120810182905261014081019190915250604080516101608101825260165473ffffffffffffffffffffffffffffffffffffffff808216835263ffffffff740100000000000000000000000000000000000000008084048216602086015278010000000000000000000000000000000000000000000000008085048316968601969096527c010000000000000000000000000000000000000000000000000000000093849004821660608601526017548084166080870152818104831660a0870152868104831660c087015293909304811660e085015260185491821661010085015291810482166101208401529290920490911661014082015290565b6040516103e49190614939565b6104206106ce366004614a63565b611638565b61042061188e565b6106ee6106e9366004614ad8565b6118f4565b60408051951515865260ff90941660208601526bffffffffffffffffffffffff9283169385019390935216606083015273ffffffffffffffffffffffffffffffffffffffff16608082015260a0016103e4565b610420611a13565b6014547801000000000000000000000000000000000000000000000000900460ff166104f8565b6104f861077e3660046148a9565b60009081526008602052604090205460ff1690565b62017f986103da565b6107af6107aa3660046148a9565b611eaa565b6040516103e49190614b34565b7f000000000000000000000000000000000000000000000000000000000000000061053d565b6108d46107f0366004614ad8565b6040805160c081018252600080825260208201819052918101829052606081018290526080810182905260a08101919091525073ffffffffffffffffffffffffffffffffffffffff908116600090815260226020908152604091829020825160c081018452815463ffffffff81168252640100000000810462ffffff16938201939093526701000000000000008304909416928401929092527b01000000000000000000000000000000000000000000000000000000900460ff16606083015260018101546080830152600201546bffffffffffffffffffffffff1660a082015290565b6040516103e49190600060c08201905063ffffffff835116825262ffffff602084015116602083015273ffffffffffffffffffffffffffffffffffffffff604084015116604083015260ff6060840151166060830152608083015160808301526bffffffffffffffffffffffff60a08401511660a083015292915050565b7f000000000000000000000000000000000000000000000000000000000000000061053d565b6104b3610986366004614b47565b611eb5565b610461610999366004614ad8565b612010565b7f000000000000000000000000000000000000000000000000000000000000000061053d565b6104206109d2366004614ba7565b612043565b6103da6109e5366004614ad8565b6120c3565b61042061216d565b61053d610a003660046148a9565b6000908152600460205260409020546a0100000000000000000000900473ffffffffffffffffffffffffffffffffffffffff1690565b61042061226f565b60005473ffffffffffffffffffffffffffffffffffffffff1661053d565b60155473ffffffffffffffffffffffffffffffffffffffff1661053d565b610a826122e8565b6040516103e49190614be3565b61053d610a9d3660046148a9565b60009081526004602052604090206002015473ffffffffffffffffffffffffffffffffffffffff1690565b6103a46103da565b7f000000000000000000000000000000000000000000000000000000000000000061053d565b6104f8610b04366004614ad8565b73ffffffffffffffffffffffffffffffffffffffff908116600090815260226020526040902054670100000000000000900416151590565b610420610b4a366004614c4c565b6123fa565b610420612728565b604051600481526020016103e4565b6115e06103da565b7f000000000000000000000000000000000000000000000000000000000000000061053d565b610420610ba2366004614ad8565b61275a565b60326103da565b6104b3610bbc3660046148a9565b612852565b610bc9612979565b6040516103e49190614c7a565b610bde6129e8565b6040516103e49190614cd4565b60255460ff166107af565b610c09610c043660046148a9565b612bd0565b6040516103e49190614e5b565b7f000000000000000000000000000000000000000000000000000000000000000061053d565b6103da612fe0565b6104b3610c523660046148a9565b612fef565b601b546103da565b610dd86040805161012081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e081018290526101008101919091525060408051610120810182526014546bffffffffffffffffffffffff8116825263ffffffff6c01000000000000000000000000820416602083015262ffffff7001000000000000000000000000000000008204169282019290925261ffff730100000000000000000000000000000000000000830416606082015260ff750100000000000000000000000000000000000000000083048116608083015276010000000000000000000000000000000000000000000083048116151560a08301527701000000000000000000000000000000000000000000000083048116151560c08301527801000000000000000000000000000000000000000000000000909204909116151560e082015260155473ffffffffffffffffffffffffffffffffffffffff1661010082015290565b6040516103e49190614f92565b610420610df3366004614c4c565b612ffa565b6103da610e06366004614ad8565b73ffffffffffffffffffffffffffffffffffffffff1660009081526021602052604090205490565b610e88610e3c366004614ad8565b73ffffffffffffffffffffffffffffffffffffffff166000908152600c602090815260409182902082518084019093525460ff8082161515808552610100909204169290910182905291565b60408051921515835260ff9091166020830152016103e4565b610420610eaf366004614ad8565b613159565b610f0f610ec23660046148a9565b60408051808201909152600080825260208201525060009081526023602090815260409182902082518084019093525463ffffffff81168352640100000000900462ffffff169082015290565b60408051825163ffffffff16815260209283015162ffffff1692810192909252016103e4565b60406103da565b610f75610f4a366004614ad8565b73ffffffffffffffffffffffffffffffffffffffff166000908152601c602052604090205460ff1690565b6040516103e49190615062565b6103da61316d565b60606000610f986002613175565b9050808410610fd3576040517f1390f2a100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000610fdf84866150a5565b905081811180610fed575083155b610ff75780610ff9565b815b9050600061100786836150b8565b67ffffffffffffffff81111561101f5761101f6150cb565b604051908082528060200260200182016040528015611048578160200160208202803683370190505b50905060005b815181101561109b5761106c61106488836150a5565b60029061317f565b82828151811061107e5761107e6150fa565b60209081029190910101528061109381615129565b91505061104e565b50925050505b92915050565b6110af61318b565b6000838152601f602052604090206110c8828483615203565b50827f2fd8d70753a007014349d4591843cc031c2dd7a260d7dd82eca8253686ae776983836040516110fb92919061531e565b60405180910390a2505050565b6040805161014081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e081018290526101008101829052610120810191909152604080516101e08101825260008082526020820181905291810182905260608082018390526080820183905260a0820183905260c0820183905260e08201839052610100820183905261012082018390526101408201839052610160820183905261018082018390526101a08201526101c081019190915260408051610140810182526016547c0100000000000000000000000000000000000000000000000000000000900463ffffffff1681526000602082018190529181018290526014546bffffffffffffffffffffffff16606080830191909152918291608081016112416002613175565b81526017547401000000000000000000000000000000000000000080820463ffffffff908116602080860191909152780100000000000000000000000000000000000000000000000080850483166040808801919091526013546060808901919091526014546c01000000000000000000000000810486166080808b0191909152760100000000000000000000000000000000000000000000820460ff16151560a09a8b015283516101e0810185526000808252968101879052601654898104891695820195909552700100000000000000000000000000000000830462ffffff169381019390935273010000000000000000000000000000000000000090910461ffff169082015296870192909252808204831660c08701527c0100000000000000000000000000000000000000000000000000000000909404821660e086015260185492830482166101008601529290910416610120830152601954610140830152601a5461016083015273ffffffffffffffffffffffffffffffffffffffff166101808201529095506101a081016113dc60096131de565b815260175473ffffffffffffffffffffffffffffffffffffffff16602091820152601454600d80546040805182860281018601909152818152949850899489949293600e93750100000000000000000000000000000000000000000090910460ff1692859183018282801561148757602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff16815260019091019060200180831161145c575b50505050509250818054806020026020016040519081016040528092919081815260200182805480156114f057602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff1681526001909101906020018083116114c5575b50505050509150945094509450945094509091929394565b6115106131eb565b73ffffffffffffffffffffffffffffffffffffffff82166000908152601c6020526040902080548291907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600183600381111561157057611570614af5565b02179055505050565b6000818152601f6020526040902080546060919061159690615161565b80601f01602080910402602001604051908101604052809291908181526020018280546115c290615161565b801561160f5780601f106115e45761010080835404028352916020019161160f565b820191906000526020600020905b8154815290600101906020018083116115f257829003601f168201915b50505050509050919050565b6000818152601d6020526040902080546060919061159690615161565b6116406131eb565b600e54811461167b576040517fcf54c06a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b600e5481101561184d576000600e828154811061169d5761169d6150fa565b600091825260208083209091015473ffffffffffffffffffffffffffffffffffffffff9081168084526011909252604083205491935016908585858181106116e7576116e76150fa565b90506020020160208101906116fc9190614ad8565b905073ffffffffffffffffffffffffffffffffffffffff8116158061178f575073ffffffffffffffffffffffffffffffffffffffff82161580159061176d57508073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b801561178f575073ffffffffffffffffffffffffffffffffffffffff81811614155b156117c6576040517fb387a23800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff818116146118375773ffffffffffffffffffffffffffffffffffffffff838116600090815260116020526040902080547fffffffffffffffffffffffff0000000000000000000000000000000000000000169183169190911790555b505050808061184590615129565b91505061167e565b507fa46de38886467c59be07a0675f14781206a5477d871628af46c2443822fcb725600e83836040516118829392919061536b565b60405180910390a15050565b6118966131eb565b601480547fffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffff1690556040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa906020015b60405180910390a1565b73ffffffffffffffffffffffffffffffffffffffff81166000908152600b602090815260408083208151608081018352905460ff80821615801584526101008304909116948301949094526bffffffffffffffffffffffff6201000082048116938301939093526e0100000000000000000000000000009004909116606082015282918291829182919082906119ba5760608201516014546000916119a6916bffffffffffffffffffffffff1661541d565b600e549091506119b69082615471565b9150505b8151602083015160408401516119d190849061549c565b6060949094015173ffffffffffffffffffffffffffffffffffffffff9a8b16600090815260116020526040902054929b919a9499509750921694509092505050565b611a1b61326c565b600060255460ff166001811115611a3457611a34614af5565b03611a6b576040517fe0262d7400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601454600e546bffffffffffffffffffffffff909116906000611a8e600f613175565b90506000611a9c82846150a5565b905060008167ffffffffffffffff811115611ab957611ab96150cb565b604051908082528060200260200182016040528015611ae2578160200160208202803683370190505b50905060008267ffffffffffffffff811115611b0057611b006150cb565b604051908082528060200260200182016040528015611b29578160200160208202803683370190505b50905060005b85811015611c5b576000600e8281548110611b4c57611b4c6150fa565b600091825260208220015473ffffffffffffffffffffffffffffffffffffffff169150611b7a828a8a6132bd565b9050806bffffffffffffffffffffffff16858481518110611b9d57611b9d6150fa565b60209081029190910181019190915273ffffffffffffffffffffffffffffffffffffffff808416600090815260119092526040909120548551911690859085908110611beb57611beb6150fa565b73ffffffffffffffffffffffffffffffffffffffff92831660209182029290920181019190915292166000908152600b909252506040902080547fffffffffffffffffffffffffffffffffffff000000000000000000000000ffff16905580611c5381615129565b915050611b2f565b5060005b84811015611dd8576000611c74600f8361317f565b73ffffffffffffffffffffffffffffffffffffffff8082166000818152600b602090815260408083208151608081018352905460ff80821615158352610100820416828501526bffffffffffffffffffffffff6201000082048116838501526e0100000000000000000000000000009091041660608201529383526011909152902054929350911684611d078a866150a5565b81518110611d1757611d176150fa565b73ffffffffffffffffffffffffffffffffffffffff9092166020928302919091019091015260408101516bffffffffffffffffffffffff1685611d5a8a866150a5565b81518110611d6a57611d6a6150fa565b60209081029190910181019190915273ffffffffffffffffffffffffffffffffffffffff9092166000908152600b909252506040902080547fffffffffffffffffffffffffffffffffffff000000000000000000000000ffff16905580611dd081615129565b915050611c5f565b5073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000166000908152602160205260408120819055611e2b600f613175565b90505b8015611e6857611e55611e4d611e456001846150b8565b600f9061317f565b600f906134c5565b5080611e60816154c1565b915050611e2e565b507f5af23b715253628d12b660b27a4f3fc626562ea8a55040aa99ab3dc178989fad8183604051611e9a9291906154f6565b60405180910390a1505050505050565b60006110a1826134e7565b60408051610120810182526014546bffffffffffffffffffffffff8116825263ffffffff6c01000000000000000000000000820416602083015262ffffff7001000000000000000000000000000000008204169282019290925261ffff730100000000000000000000000000000000000000830416606082015260ff750100000000000000000000000000000000000000000083048116608083015276010000000000000000000000000000000000000000000083048116151560a08301527701000000000000000000000000000000000000000000000083048116151560c08301527801000000000000000000000000000000000000000000000000909204909116151560e082015260155473ffffffffffffffffffffffffffffffffffffffff16610100820152600090818080611fed84613592565b92509250925061200389858a8a8787878d613784565b9998505050505050505050565b73ffffffffffffffffffffffffffffffffffffffff81166000908152602080526040902080546060919061159690615161565b61204b61318b565b73ffffffffffffffffffffffffffffffffffffffff83166000908152602080526040902061207a828483615203565b508273ffffffffffffffffffffffffffffffffffffffff167f7c44b4eb59ee7873514e7e43e7718c269d872965938b288aa143befca62f99d283836040516110fb92919061531e565b73ffffffffffffffffffffffffffffffffffffffff81166000818152602160205260408082205490517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152919290916370a0823190602401602060405180830381865afa15801561213f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906121639190615524565b6110a191906150b8565b60015473ffffffffffffffffffffffffffffffffffffffff1633146121f3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b6122776131eb565b601480547fffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffff167601000000000000000000000000000000000000000000001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258906020016118ea565b600e5460609060008167ffffffffffffffff811115612309576123096150cb565b60405190808252806020026020018201604052801561234e57816020015b60408051808201909152600080825260208201528152602001906001900390816123275790505b50905060005b828110156123f3576000600e8281548110612371576123716150fa565b600091825260208083209091015473ffffffffffffffffffffffffffffffffffffffff9081168084526011835260409384902054845180860190955281855290911691830182905285519093509091908590859081106123d3576123d36150fa565b6020026020010181905250505080806123eb90615129565b915050612354565b5092915050565b73ffffffffffffffffffffffffffffffffffffffff8116612447576040517f9c8d2cd200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600160255460ff16600181111561246057612460614af5565b03612497576040517f4a3578fb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8281166000908152601160205260409020541633146124f7576040517fcebf515b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601454600e5460009161251a9185916bffffffffffffffffffffffff16906132bd565b73ffffffffffffffffffffffffffffffffffffffff8085166000908152600b6020908152604080832080547fffffffffffffffffffffffffffffffffffff000000000000000000000000ffff1690557f000000000000000000000000000000000000000000000000000000000000000090931682526021905220549091506125b1906bffffffffffffffffffffffff8316906150b8565b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000081166000818152602160205260408082209490945592517fa9059cbb00000000000000000000000000000000000000000000000000000000815291851660048301526bffffffffffffffffffffffff841660248301529063a9059cbb906044016020604051808303816000875af1158015612666573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061268a919061553d565b9050806126c3576040517f90b8ec1800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405133815273ffffffffffffffffffffffffffffffffffffffff808516916bffffffffffffffffffffffff8516918716907f9819093176a1851202c7bcfa46845809b4e47c261866550e94ed3775d2f406989060200160405180910390a450505050565b6127306131eb565b602580547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055565b73ffffffffffffffffffffffffffffffffffffffff8181166000908152601260205260409020541633146127ba576040517f6752e7aa00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff81811660008181526011602090815260408083208054337fffffffffffffffffffffffff000000000000000000000000000000000000000080831682179093556012909452828520805490921690915590519416939092849290917f78af32efdcad432315431e9b03d27e6cd98fb79c405fdc5af7c1714d9c0f75b39190a45050565b6000818152600460209081526040808320815161012081018352815460ff8082161515835261010080830490911615159583019590955263ffffffff620100008204811694830194909452660100000000000081048416606083015273ffffffffffffffffffffffffffffffffffffffff6a01000000000000000000009091048116608083015260018301546fffffffffffffffffffffffffffffffff811660a08401526bffffffffffffffffffffffff70010000000000000000000000000000000082041660c08401527c0100000000000000000000000000000000000000000000000000000000900490931660e08201526002909101549091169181019190915261297283612962816134e7565b8360400151846101000151611eb5565b9392505050565b606060248054806020026020016040519081016040528092919081815260200182805480156129de57602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff1681526001909101906020018083116129b3575b5050505050905090565b604080516102008101825260008082526020820181905291810182905260608082018390526080820183905260a0820183905260c0820183905260e08201839052610100820183905261012082018390526101408201839052610160820183905261018082018390526101a082018390526101c08201526101e0810191909152604080516102008101825260165463ffffffff74010000000000000000000000000000000000000000808304821684527801000000000000000000000000000000000000000000000000808404831660208601526017547c0100000000000000000000000000000000000000000000000000000000810484169686019690965273ffffffffffffffffffffffffffffffffffffffff938416606086015260145460ff828204161515608087015262ffffff70010000000000000000000000000000000082041660a0870152601854928304841660c087015290820490921660e085015293821661010084015261ffff7301000000000000000000000000000000000000009091041661012083015291909116610140820152601954610160820152601a54610180820152601b546101a08201526101c08101612baa60096131de565b815260155473ffffffffffffffffffffffffffffffffffffffff16602090910152919050565b604080516101408101825260008082526020820181905260609282018390528282018190526080820181905260a0820181905260c0820181905260e082018190526101008201526101208101919091526000828152600460209081526040808320815161012081018352815460ff8082161515835261010080830490911615159583019590955263ffffffff620100008204811694830194909452660100000000000081048416606083015273ffffffffffffffffffffffffffffffffffffffff6a010000000000000000000090910481166080830181905260018401546fffffffffffffffffffffffffffffffff811660a08501526bffffffffffffffffffffffff70010000000000000000000000000000000082041660c08501527c0100000000000000000000000000000000000000000000000000000000900490941660e08301526002909201549091169281019290925290919015612da557816080015173ffffffffffffffffffffffffffffffffffffffff1663f00e6a2a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612d7c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612da0919061555f565b612da8565b60005b90506040518061014001604052808273ffffffffffffffffffffffffffffffffffffffff168152602001836040015163ffffffff168152602001600760008781526020019081526020016000208054612e0090615161565b80601f0160208091040260200160405190810160405280929190818152602001828054612e2c90615161565b8015612e795780601f10612e4e57610100808354040283529160200191612e79565b820191906000526020600020905b815481529060010190602001808311612e5c57829003601f168201915b505050505081526020018360c001516bffffffffffffffffffffffff1681526020016005600087815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001836060015163ffffffff1667ffffffffffffffff1681526020018360e0015163ffffffff1681526020018360a001516bffffffffffffffffffffffff168152602001836000015115158152602001601e60008781526020019081526020016000208054612f5690615161565b80601f0160208091040260200160405190810160405280929190818152602001828054612f8290615161565b8015612fcf5780601f10612fa457610100808354040283529160200191612fcf565b820191906000526020600020905b815481529060010190602001808311612fb257829003601f168201915b505050505081525092505050919050565b6000612fea613af4565b905090565b60006110a182612852565b73ffffffffffffffffffffffffffffffffffffffff82811660009081526011602052604090205416331461305a576040517fcebf515b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff8216036130a9576040517f8c8728c700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8281166000908152601260205260409020548116908216146131555773ffffffffffffffffffffffffffffffffffffffff82811660008181526012602052604080822080547fffffffffffffffffffffffff0000000000000000000000000000000000000000169486169485179055513392917f84f7c7c80bb8ed2279b4aab5f61cd05e6374073d38f46d7f32de8c30e9e3836791a45b5050565b6131616131eb565b61316a81613bbe565b50565b6000612fea60025b60006110a1825490565b60006129728383613cb3565b60175473ffffffffffffffffffffffffffffffffffffffff1633146131dc576040517f77c3599200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b6060600061297283613cdd565b60005473ffffffffffffffffffffffffffffffffffffffff1633146131dc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e65720000000000000000000060448201526064016121ea565b60185473ffffffffffffffffffffffffffffffffffffffff1633146131dc576040517fb6dfb7a600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff83166000908152600b602090815260408083208151608081018352905460ff80821615801584526101008304909116948301949094526bffffffffffffffffffffffff6201000082048116938301939093526e01000000000000000000000000000090049091166060820152906134b9576000816060015185613355919061541d565b905060006133638583615471565b90508083604001818151613377919061549c565b6bffffffffffffffffffffffff16905250613392858261557c565b836060018181516133a3919061549c565b6bffffffffffffffffffffffff90811690915273ffffffffffffffffffffffffffffffffffffffff89166000908152600b602090815260409182902087518154928901519389015160608a015186166e010000000000000000000000000000027fffffffffffff000000000000000000000000ffffffffffffffffffffffffffff919096166201000002167fffffffffffff000000000000000000000000000000000000000000000000ffff60ff95909516610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff921515929092167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000909416939093171792909216179190911790555050505b60400151949350505050565b60006129728373ffffffffffffffffffffffffffffffffffffffff8416613d38565b6000818160045b600f811015613574577fff00000000000000000000000000000000000000000000000000000000000000821683826020811061352c5761352c6150fa565b1a60f81b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161461356257506000949350505050565b8061356c81615129565b9150506134ee565b5081600f1a600181111561358a5761358a614af5565b949350505050565b600080600080846040015162ffffff1690506000808263ffffffff161190506000807f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa15801561361f573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061364391906155c3565b509450909250505060008113158061365a57508142105b8061367b575082801561367b575061367282426150b8565b8463ffffffff16105b1561368a57601954965061368e565b8096505b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa1580156136f9573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061371d91906155c3565b509450909250505060008113158061373457508142105b806137555750828015613755575061374c82426150b8565b8463ffffffff16105b1561376457601a549550613768565b8095505b86866137738a613e2b565b965096509650505050509193909250565b600080808089600181111561379b5761379b614af5565b036137aa575062017f986137ff565b60018960018111156137be576137be614af5565b036137cd57506201de846137ff565b6040517ff2b2d41200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008a6080015160016138129190615613565b6138209060ff16604061562c565b60185461384e906103a49074010000000000000000000000000000000000000000900463ffffffff166150a5565b61385891906150a5565b601554604080517fde9ee35e0000000000000000000000000000000000000000000000000000000081528151939450600093849373ffffffffffffffffffffffffffffffffffffffff169263de9ee35e92600480820193918290030181865afa1580156138c9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906138ed9190615643565b909250905081836138ff8360186150a5565b613909919061562c565b60808f0151613919906001615613565b6139289060ff166115e061562c565b61393291906150a5565b61393c91906150a5565b61394690856150a5565b6101008e01516040517f125441400000000000000000000000000000000000000000000000000000000081526004810186905291955073ffffffffffffffffffffffffffffffffffffffff1690631254414090602401602060405180830381865afa1580156139b9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906139dd9190615524565b8d6060015161ffff166139f0919061562c565b94505050506000613a018b86613f15565b60008d815260046020526040902054909150610100900460ff1615613a665760008c81526023602090815260409182902082518084018452905463ffffffff811680835264010000000090910462ffffff908116928401928352928501525116908201525b6000613ad08c6040518061012001604052808d63ffffffff1681526020018681526020018781526020018c81526020018b81526020018a81526020018973ffffffffffffffffffffffffffffffffffffffff16815260200185815260200160001515815250614091565b60208101518151919250613ae39161549c565b9d9c50505050505050505050505050565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166000818152602160205260408082205490517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152919290916370a0823190602401602060405180830381865afa158015613b90573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613bb49190615524565b612fea9190615667565b3373ffffffffffffffffffffffffffffffffffffffff821603613c3d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c6600000000000000000060448201526064016121ea565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6000826000018281548110613cca57613cca6150fa565b9060005260206000200154905092915050565b60608160000180548060200260200160405190810160405280929190818152602001828054801561160f57602002820191906000526020600020905b815481526020019060010190808311613d195750505050509050919050565b60008181526001830160205260408120548015613e21576000613d5c6001836150b8565b8554909150600090613d70906001906150b8565b9050818114613dd5576000866000018281548110613d9057613d906150fa565b9060005260206000200154905080876000018481548110613db357613db36150fa565b6000918252602080832090910192909255918252600188019052604090208390555b8554869080613de657613de6615687565b6001900381819060005260206000200160009055905585600101600086815260200190815260200160002060009055600193505050506110a1565b60009150506110a1565b60008060007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015613e9b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613ebf91906155c3565b50935050925050600082131580613ed557508042105b80613f0557506000846040015162ffffff16118015613f055750613ef981426150b8565b846040015162ffffff16105b156123f3575050601b5492915050565b60408051608081018252600080825260208083018281528385018381526060850184905273ffffffffffffffffffffffffffffffffffffffff878116855260229093528584208054640100000000810462ffffff1690925263ffffffff82169092527b01000000000000000000000000000000000000000000000000000000810460ff16855285517ffeaf968c00000000000000000000000000000000000000000000000000000000815295519495919484936701000000000000009092049091169163feaf968c9160048083019260a09291908290030181865afa158015614002573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061402691906155c3565b5093505092505060008213158061403c57508042105b8061406c57506000866040015162ffffff1611801561406c575061406081426150b8565b866040015162ffffff16105b156140805760018301546060850152614088565b606084018290525b50505092915050565b6040805161010081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e081019190915260008260e001516000015160ff1690506000846060015161ffff1684606001516140fc919061562c565b9050836101000151801561410f5750803a105b1561411757503a5b60006012831161412857600161413e565b6141336012846150b8565b61413e90600a6157d6565b9050600060128410614151576001614167565b61415c8460126150b8565b61416790600a6157d6565b905060008660a0015187604001518860200151896000015161418991906150a5565b614193908761562c565b61419d91906150a5565b6141a7919061562c565b9050614203828860e00151606001516141c0919061562c565b6001848a60e00151606001516141d6919061562c565b6141e091906150b8565b6141ea868561562c565b6141f491906150a5565b6141fe91906157e2565b6143d8565b6bffffffffffffffffffffffff1686526080870151614226906141fe90836157e2565b6bffffffffffffffffffffffff1660408088019190915260e0880151015160009061425f9062ffffff16683635c9adc5dea0000061562c565b9050600081633b9aca008a60a001518b60e001516020015163ffffffff168c604001518d600001518b614292919061562c565b61429c91906150a5565b6142a6919061562c565b6142b0919061562c565b6142ba91906157e2565b6142c491906150a5565b9050614307848a60e00151606001516142dd919061562c565b6001868c60e00151606001516142f3919061562c565b6142fd91906150b8565b6141ea888561562c565b6bffffffffffffffffffffffff166020890152608089015161432d906141fe90836157e2565b6bffffffffffffffffffffffff16606089015260c089015173ffffffffffffffffffffffffffffffffffffffff166080808a0191909152890151614370906143d8565b6bffffffffffffffffffffffff1660a0808a0191909152890151614393906143d8565b6bffffffffffffffffffffffff1660c089015260e0890151606001516143b8906143d8565b6bffffffffffffffffffffffff1660e08901525050505050505092915050565b60006bffffffffffffffffffffffff821115614476576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203960448201527f362062697473000000000000000000000000000000000000000000000000000060648201526084016121ea565b5090565b6000806040838503121561448d57600080fd5b50508035926020909101359150565b600081518084526020808501945080840160005b838110156144cc578151875295820195908201906001016144b0565b509495945050505050565b602081526000612972602083018461449c565b60008083601f8401126144fc57600080fd5b50813567ffffffffffffffff81111561451457600080fd5b60208301915083602082850101111561452c57600080fd5b9250929050565b60008060006040848603121561454857600080fd5b83359250602084013567ffffffffffffffff81111561456657600080fd5b614572868287016144ea565b9497909650939450505050565b600081518084526020808501945080840160005b838110156144cc57815173ffffffffffffffffffffffffffffffffffffffff1687529582019590820190600101614593565b805163ffffffff16825260006101e060208301516145eb602086018263ffffffff169052565b506040830151614603604086018263ffffffff169052565b50606083015161461a606086018262ffffff169052565b506080830151614630608086018261ffff169052565b5060a083015161465060a08601826bffffffffffffffffffffffff169052565b5060c083015161466860c086018263ffffffff169052565b5060e083015161468060e086018263ffffffff169052565b506101008381015163ffffffff908116918601919091526101208085015190911690850152610140808401519085015261016080840151908501526101808084015173ffffffffffffffffffffffffffffffffffffffff16908501526101a0808401518186018390526146f58387018261457f565b925050506101c0808401516147218287018273ffffffffffffffffffffffffffffffffffffffff169052565b5090949350505050565b855163ffffffff16815260006101c0602088015161475960208501826bffffffffffffffffffffffff169052565b5060408801516040840152606088015161478360608501826bffffffffffffffffffffffff169052565b506080880151608084015260a08801516147a560a085018263ffffffff169052565b5060c08801516147bd60c085018263ffffffff169052565b5060e088015160e0840152610100808901516147e08286018263ffffffff169052565b5050610120888101511515908401526101408301819052614803818401886145c5565b9050828103610160840152614818818761457f565b905082810361018084015261482d818661457f565b9150506148406101a083018460ff169052565b9695505050505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461316a57600080fd5b6000806040838503121561487f57600080fd5b823561488a8161484a565b915060208301356004811061489e57600080fd5b809150509250929050565b6000602082840312156148bb57600080fd5b5035919050565b6000815180845260005b818110156148e8576020818501810151868301820152016148cc565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b60208152600061297260208301846148c2565b815173ffffffffffffffffffffffffffffffffffffffff1681526101608101602083015161496f602084018263ffffffff169052565b506040830151614987604084018263ffffffff169052565b50606083015161499f606084018263ffffffff169052565b5060808301516149c7608084018273ffffffffffffffffffffffffffffffffffffffff169052565b5060a08301516149df60a084018263ffffffff169052565b5060c08301516149f760c084018263ffffffff169052565b5060e0830151614a0f60e084018263ffffffff169052565b506101008381015173ffffffffffffffffffffffffffffffffffffffff81168483015250506101208381015163ffffffff81168483015250506101408381015163ffffffff8116848301525b505092915050565b60008060208385031215614a7657600080fd5b823567ffffffffffffffff80821115614a8e57600080fd5b818501915085601f830112614aa257600080fd5b813581811115614ab157600080fd5b8660208260051b8501011115614ac657600080fd5b60209290920196919550909350505050565b600060208284031215614aea57600080fd5b81356129728161484a565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b6002811061316a5761316a614af5565b60208101614b4183614b24565b91905290565b60008060008060808587031215614b5d57600080fd5b84359350602085013560028110614b7357600080fd5b9250604085013563ffffffff81168114614b8c57600080fd5b91506060850135614b9c8161484a565b939692955090935050565b600080600060408486031215614bbc57600080fd5b8335614bc78161484a565b9250602084013567ffffffffffffffff81111561456657600080fd5b602080825282518282018190526000919060409081850190868401855b82811015614c3f578151805173ffffffffffffffffffffffffffffffffffffffff90811686529087015116868501529284019290850190600101614c00565b5091979650505050505050565b60008060408385031215614c5f57600080fd5b8235614c6a8161484a565b9150602083013561489e8161484a565b6020808252825182820181905260009190848201906040850190845b81811015614cc857835173ffffffffffffffffffffffffffffffffffffffff1683529284019291840191600101614c96565b50909695505050505050565b60208152614ceb60208201835163ffffffff169052565b60006020830151614d04604084018263ffffffff169052565b50604083015163ffffffff8116606084015250606083015173ffffffffffffffffffffffffffffffffffffffff8116608084015250608083015180151560a08401525060a083015162ffffff811660c08401525060c083015163ffffffff811660e08401525060e0830151610100614d838185018363ffffffff169052565b8401519050610120614dac8482018373ffffffffffffffffffffffffffffffffffffffff169052565b8401519050610140614dc38482018361ffff169052565b8401519050610160614dec8482018373ffffffffffffffffffffffffffffffffffffffff169052565b840151610180848101919091528401516101a0808501919091528401516101c0808501919091528401516102006101e080860182905291925090614e3461022086018461457f565b9086015173ffffffffffffffffffffffffffffffffffffffff811683870152909250614721565b60208152614e8260208201835173ffffffffffffffffffffffffffffffffffffffff169052565b60006020830151614e9b604084018263ffffffff169052565b506040830151610140806060850152614eb86101608501836148c2565b91506060850151614ed960808601826bffffffffffffffffffffffff169052565b50608085015173ffffffffffffffffffffffffffffffffffffffff811660a08601525060a085015167ffffffffffffffff811660c08601525060c085015163ffffffff811660e08601525060e0850151610100614f45818701836bffffffffffffffffffffffff169052565b8601519050610120614f5a8682018315159052565b8601518584037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00183870152905061484083826148c2565b6000610120820190506bffffffffffffffffffffffff835116825263ffffffff60208401511660208301526040830151614fd3604084018262ffffff169052565b506060830151614fe9606084018261ffff169052565b506080830151614ffe608084018260ff169052565b5060a083015161501260a084018215159052565b5060c083015161502660c084018215159052565b5060e083015161503a60e084018215159052565b506101008381015173ffffffffffffffffffffffffffffffffffffffff811684830152614a5b565b6020810160048310614b4157614b41614af5565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b808201808211156110a1576110a1615076565b818103818111156110a1576110a1615076565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361515a5761515a615076565b5060010190565b600181811c9082168061517557607f821691505b6020821081036151ae577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b601f8211156151fe57600081815260208120601f850160051c810160208610156151db5750805b601f850160051c820191505b818110156151fa578281556001016151e7565b5050505b505050565b67ffffffffffffffff83111561521b5761521b6150cb565b61522f836152298354615161565b836151b4565b6000601f841160018114615281576000851561524b5750838201355b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600387901b1c1916600186901b178355615317565b6000838152602090207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0861690835b828110156152d057868501358255602094850194600190920191016152b0565b508682101561530b577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88860031b161c19848701351681555b505060018560011b0183555b5050505050565b60208152816020820152818360408301376000818301604090810191909152601f9092017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0160101919050565b6000604082016040835280865480835260608501915087600052602092508260002060005b828110156153c257815473ffffffffffffffffffffffffffffffffffffffff1684529284019260019182019101615390565b505050838103828501528481528590820160005b868110156154115782356153e98161484a565b73ffffffffffffffffffffffffffffffffffffffff16825291830191908301906001016153d6565b50979650505050505050565b6bffffffffffffffffffffffff8281168282160390808211156123f3576123f3615076565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b60006bffffffffffffffffffffffff8084168061549057615490615442565b92169190910492915050565b6bffffffffffffffffffffffff8181168382160190808211156123f3576123f3615076565b6000816154d0576154d0615076565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190565b604081526000615509604083018561457f565b828103602084015261551b818561449c565b95945050505050565b60006020828403121561553657600080fd5b5051919050565b60006020828403121561554f57600080fd5b8151801515811461297257600080fd5b60006020828403121561557157600080fd5b81516129728161484a565b6bffffffffffffffffffffffff818116838216028082169190828114614a5b57614a5b615076565b805169ffffffffffffffffffff811681146155be57600080fd5b919050565b600080600080600060a086880312156155db57600080fd5b6155e4866155a4565b9450602086015193506040860151925060608601519150615607608087016155a4565b90509295509295909350565b60ff81811683821601908111156110a1576110a1615076565b80820281158282048414176110a1576110a1615076565b6000806040838503121561565657600080fd5b505080516020909101519092909150565b81810360008312801583831316838312821617156123f3576123f3615076565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b600181815b8085111561570f57817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048211156156f5576156f5615076565b8085161561570257918102915b93841c93908002906156bb565b509250929050565b600082615726575060016110a1565b81615733575060006110a1565b816001811461574957600281146157535761576f565b60019150506110a1565b60ff84111561576457615764615076565b50506001821b6110a1565b5060208310610133831016604e8410600b8410161715615792575081810a6110a1565b61579c83836156b6565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048211156157ce576157ce615076565b029392505050565b60006129728383615717565b6000826157f1576157f1615442565b50049056fea164736f6c6343000813000a", + Bin: "0x6101606040523480156200001257600080fd5b5060405162005c8738038062005c87833981016040819052620000359162000309565b87878787878787873380600081620000945760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620000c757620000c78162000241565b5050506001600160a01b0380891660805287811660a05286811660c05285811660e052848116610100528316610120526025805483919060ff191660018381811115620001185762000118620003b6565b0217905550806001600160a01b0316610140816001600160a01b03168152505060c0516001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000179573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200019f9190620003cc565b60ff1660a0516001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015620001e3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620002099190620003cc565b60ff16146200022b576040516301f86e1760e41b815260040160405180910390fd5b50505050505050505050505050505050620003f8565b336001600160a01b038216036200029b5760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c6600000000000000000060448201526064016200008b565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b80516001600160a01b03811681146200030457600080fd5b919050565b600080600080600080600080610100898b0312156200032757600080fd5b6200033289620002ec565b97506200034260208a01620002ec565b96506200035260408a01620002ec565b95506200036260608a01620002ec565b94506200037260808a01620002ec565b93506200038260a08a01620002ec565b925060c0890151600281106200039757600080fd5b9150620003a760e08a01620002ec565b90509295985092959890939650565b634e487b7160e01b600052602160045260246000fd5b600060208284031215620003df57600080fd5b815160ff81168114620003f157600080fd5b9392505050565b60805160a05160c05160e051610100516101205161014051615803620004846000396000610b7001526000610ad2015260006107be0152600081816109a001526135b60152600081816109540152613e3201526000818161051d0152613690015260008181610c1801528181611df00152818161256b015281816125c80152613af601526158036000f3fe608060405234801561001057600080fd5b50600436106103d05760003560e01c80638ed02bab116101ff578063c3f909d41161011a578063eb5dcd6c116100ad578063f5a418461161007c578063f5a4184614610eb4578063f777ff0614610f35578063faa3e99614610f3c578063ffd242bd14610f8257600080fd5b8063eb5dcd6c14610de5578063ec4de4ba14610df8578063ed56b3e114610e2e578063f2fde38b14610ea157600080fd5b8063d09dc339116100e9578063d09dc33914610c3c578063d763264814610c44578063d85aa07c14610c57578063e80a3d4414610c5f57600080fd5b8063c3f909d414610bd6578063c5b964e014610beb578063c7c3a19a14610bf6578063ca30e60314610c1657600080fd5b8063aab9edd611610192578063b3596c2311610161578063b3596c23146107e2578063b6511a2a14610ba7578063b657bc9c14610bae578063ba87666814610bc157600080fd5b8063aab9edd614610b57578063abc76ae014610b66578063ac4dc59a14610b6e578063b121e14714610b9457600080fd5b8063a08714c0116101ce578063a08714c014610ad0578063a538b2eb14610af6578063a710b22114610b3c578063a87f45fe14610b4f57600080fd5b80638ed02bab14610a5c5780639089daa414610a7a57806393f6ebcf14610a8f5780639e0a99ed14610ac857600080fd5b806343cc055c116102ef5780636209e1e91161028257806379ba50971161025157806379ba5097146109ea57806379ea9943146109f25780638456cb5914610a365780638da5cb5b14610a3e57600080fd5b80636209e1e91461098b5780636709d0e51461099e578063671d36ed146109c45780636eec02a2146109d757600080fd5b80635425d8ac116102be5780635425d8ac146107bc57806357359584146107e2578063614486af146109525780636181d82d1461097857600080fd5b806343cc055c1461074957806344cb70b8146107705780634ca16c52146107935780635147cd591461079c57600080fd5b8063207b6516116103675780633b9cce59116103365780633b9cce59146106c05780633f4ba83a146106d3578063421d183b146106db57806343b46e5f1461074157600080fd5b8063207b651614610508578063226cf83c1461051b578063232c1cc5146105625780633408f73a1461056957600080fd5b8063187256e8116103a3578063187256e81461043b57806319d97a941461044e5780631e0104391461046e5780631efcf646146104d057600080fd5b8063050ee65d146103d557806306e3b632146103ed5780630b7d33e61461040d5780631865c57d14610422575b600080fd5b6201e26c5b6040519081526020015b60405180910390f35b6104006103fb36600461447a565b610f8a565b6040516103e491906144d7565b61042061041b366004614533565b6110a7565b005b61042a611108565b6040516103e495949392919061472b565b61042061044936600461486c565b611508565b61046161045c3660046148a9565b611579565b6040516103e49190614926565b6104b361047c3660046148a9565b60009081526004602052604090206001015470010000000000000000000000000000000090046bffffffffffffffffffffffff1690565b6040516bffffffffffffffffffffffff90911681526020016103e4565b6104f86104de3660046148a9565b600090815260046020526040902054610100900460ff1690565b60405190151581526020016103e4565b6104616105163660046148a9565b61161b565b7f00000000000000000000000000000000000000000000000000000000000000005b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016103e4565b60186103da565b6106b36040805161016081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e081018290526101008101829052610120810182905261014081019190915250604080516101608101825260165473ffffffffffffffffffffffffffffffffffffffff808216835263ffffffff740100000000000000000000000000000000000000008084048216602086015278010000000000000000000000000000000000000000000000008085048316968601969096527c010000000000000000000000000000000000000000000000000000000093849004821660608601526017548084166080870152818104831660a0870152868104831660c087015293909304811660e085015260185491821661010085015291810482166101208401529290920490911661014082015290565b6040516103e49190614939565b6104206106ce366004614a63565b611638565b61042061188e565b6106ee6106e9366004614ad8565b6118f4565b60408051951515865260ff90941660208601526bffffffffffffffffffffffff9283169385019390935216606083015273ffffffffffffffffffffffffffffffffffffffff16608082015260a0016103e4565b610420611a13565b6014547801000000000000000000000000000000000000000000000000900460ff166104f8565b6104f861077e3660046148a9565b60009081526008602052604090205460ff1690565b62017f986103da565b6107af6107aa3660046148a9565b611eaa565b6040516103e49190614b34565b7f000000000000000000000000000000000000000000000000000000000000000061053d565b6108d46107f0366004614ad8565b6040805160c081018252600080825260208201819052918101829052606081018290526080810182905260a08101919091525073ffffffffffffffffffffffffffffffffffffffff908116600090815260226020908152604091829020825160c081018452815463ffffffff81168252640100000000810462ffffff16938201939093526701000000000000008304909416928401929092527b01000000000000000000000000000000000000000000000000000000900460ff16606083015260018101546080830152600201546bffffffffffffffffffffffff1660a082015290565b6040516103e49190600060c08201905063ffffffff835116825262ffffff602084015116602083015273ffffffffffffffffffffffffffffffffffffffff604084015116604083015260ff6060840151166060830152608083015160808301526bffffffffffffffffffffffff60a08401511660a083015292915050565b7f000000000000000000000000000000000000000000000000000000000000000061053d565b6104b3610986366004614b47565b611eb5565b610461610999366004614ad8565b612010565b7f000000000000000000000000000000000000000000000000000000000000000061053d565b6104206109d2366004614ba7565b612043565b6103da6109e5366004614ad8565b6120c3565b61042061216d565b61053d610a003660046148a9565b6000908152600460205260409020546a0100000000000000000000900473ffffffffffffffffffffffffffffffffffffffff1690565b61042061226f565b60005473ffffffffffffffffffffffffffffffffffffffff1661053d565b60155473ffffffffffffffffffffffffffffffffffffffff1661053d565b610a826122e8565b6040516103e49190614be3565b61053d610a9d3660046148a9565b60009081526004602052604090206002015473ffffffffffffffffffffffffffffffffffffffff1690565b6103a46103da565b7f000000000000000000000000000000000000000000000000000000000000000061053d565b6104f8610b04366004614ad8565b73ffffffffffffffffffffffffffffffffffffffff908116600090815260226020526040902054670100000000000000900416151590565b610420610b4a366004614c4c565b6123fa565b610420612728565b604051600481526020016103e4565b6115e06103da565b7f000000000000000000000000000000000000000000000000000000000000000061053d565b610420610ba2366004614ad8565b61275a565b60326103da565b6104b3610bbc3660046148a9565b612852565b610bc9612979565b6040516103e49190614c7a565b610bde6129e8565b6040516103e49190614cd4565b60255460ff166107af565b610c09610c043660046148a9565b612bd0565b6040516103e49190614e5b565b7f000000000000000000000000000000000000000000000000000000000000000061053d565b6103da612fe0565b6104b3610c523660046148a9565b612fef565b601b546103da565b610dd86040805161012081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e081018290526101008101919091525060408051610120810182526014546bffffffffffffffffffffffff8116825263ffffffff6c01000000000000000000000000820416602083015262ffffff7001000000000000000000000000000000008204169282019290925261ffff730100000000000000000000000000000000000000830416606082015260ff750100000000000000000000000000000000000000000083048116608083015276010000000000000000000000000000000000000000000083048116151560a08301527701000000000000000000000000000000000000000000000083048116151560c08301527801000000000000000000000000000000000000000000000000909204909116151560e082015260155473ffffffffffffffffffffffffffffffffffffffff1661010082015290565b6040516103e49190614f92565b610420610df3366004614c4c565b612ffa565b6103da610e06366004614ad8565b73ffffffffffffffffffffffffffffffffffffffff1660009081526021602052604090205490565b610e88610e3c366004614ad8565b73ffffffffffffffffffffffffffffffffffffffff166000908152600c602090815260409182902082518084019093525460ff8082161515808552610100909204169290910182905291565b60408051921515835260ff9091166020830152016103e4565b610420610eaf366004614ad8565b613159565b610f0f610ec23660046148a9565b60408051808201909152600080825260208201525060009081526023602090815260409182902082518084019093525463ffffffff81168352640100000000900462ffffff169082015290565b60408051825163ffffffff16815260209283015162ffffff1692810192909252016103e4565b60406103da565b610f75610f4a366004614ad8565b73ffffffffffffffffffffffffffffffffffffffff166000908152601c602052604090205460ff1690565b6040516103e49190615062565b6103da61316d565b60606000610f986002613175565b9050808410610fd3576040517f1390f2a100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000610fdf84866150a5565b905081811180610fed575083155b610ff75780610ff9565b815b9050600061100786836150b8565b67ffffffffffffffff81111561101f5761101f6150cb565b604051908082528060200260200182016040528015611048578160200160208202803683370190505b50905060005b815181101561109b5761106c61106488836150a5565b60029061317f565b82828151811061107e5761107e6150fa565b60209081029190910101528061109381615129565b91505061104e565b50925050505b92915050565b6110af61318b565b6000838152601f602052604090206110c8828483615203565b50827f2fd8d70753a007014349d4591843cc031c2dd7a260d7dd82eca8253686ae776983836040516110fb92919061531e565b60405180910390a2505050565b6040805161014081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e081018290526101008101829052610120810191909152604080516101e08101825260008082526020820181905291810182905260608082018390526080820183905260a0820183905260c0820183905260e08201839052610100820183905261012082018390526101408201839052610160820183905261018082018390526101a08201526101c081019190915260408051610140810182526016547c0100000000000000000000000000000000000000000000000000000000900463ffffffff1681526000602082018190529181018290526014546bffffffffffffffffffffffff16606080830191909152918291608081016112416002613175565b81526017547401000000000000000000000000000000000000000080820463ffffffff908116602080860191909152780100000000000000000000000000000000000000000000000080850483166040808801919091526013546060808901919091526014546c01000000000000000000000000810486166080808b0191909152760100000000000000000000000000000000000000000000820460ff16151560a09a8b015283516101e0810185526000808252968101879052601654898104891695820195909552700100000000000000000000000000000000830462ffffff169381019390935273010000000000000000000000000000000000000090910461ffff169082015296870192909252808204831660c08701527c0100000000000000000000000000000000000000000000000000000000909404821660e086015260185492830482166101008601529290910416610120830152601954610140830152601a5461016083015273ffffffffffffffffffffffffffffffffffffffff166101808201529095506101a081016113dc60096131de565b815260175473ffffffffffffffffffffffffffffffffffffffff16602091820152601454600d80546040805182860281018601909152818152949850899489949293600e93750100000000000000000000000000000000000000000090910460ff1692859183018282801561148757602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff16815260019091019060200180831161145c575b50505050509250818054806020026020016040519081016040528092919081815260200182805480156114f057602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff1681526001909101906020018083116114c5575b50505050509150945094509450945094509091929394565b6115106131eb565b73ffffffffffffffffffffffffffffffffffffffff82166000908152601c6020526040902080548291907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600183600381111561157057611570614af5565b02179055505050565b6000818152601f6020526040902080546060919061159690615161565b80601f01602080910402602001604051908101604052809291908181526020018280546115c290615161565b801561160f5780601f106115e45761010080835404028352916020019161160f565b820191906000526020600020905b8154815290600101906020018083116115f257829003601f168201915b50505050509050919050565b6000818152601d6020526040902080546060919061159690615161565b6116406131eb565b600e54811461167b576040517fcf54c06a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b600e5481101561184d576000600e828154811061169d5761169d6150fa565b600091825260208083209091015473ffffffffffffffffffffffffffffffffffffffff9081168084526011909252604083205491935016908585858181106116e7576116e76150fa565b90506020020160208101906116fc9190614ad8565b905073ffffffffffffffffffffffffffffffffffffffff8116158061178f575073ffffffffffffffffffffffffffffffffffffffff82161580159061176d57508073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b801561178f575073ffffffffffffffffffffffffffffffffffffffff81811614155b156117c6576040517fb387a23800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff818116146118375773ffffffffffffffffffffffffffffffffffffffff838116600090815260116020526040902080547fffffffffffffffffffffffff0000000000000000000000000000000000000000169183169190911790555b505050808061184590615129565b91505061167e565b507fa46de38886467c59be07a0675f14781206a5477d871628af46c2443822fcb725600e83836040516118829392919061536b565b60405180910390a15050565b6118966131eb565b601480547fffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffff1690556040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa906020015b60405180910390a1565b73ffffffffffffffffffffffffffffffffffffffff81166000908152600b602090815260408083208151608081018352905460ff80821615801584526101008304909116948301949094526bffffffffffffffffffffffff6201000082048116938301939093526e0100000000000000000000000000009004909116606082015282918291829182919082906119ba5760608201516014546000916119a6916bffffffffffffffffffffffff1661541d565b600e549091506119b69082615471565b9150505b8151602083015160408401516119d190849061549c565b6060949094015173ffffffffffffffffffffffffffffffffffffffff9a8b16600090815260116020526040902054929b919a9499509750921694509092505050565b611a1b61326c565b600060255460ff166001811115611a3457611a34614af5565b03611a6b576040517fe0262d7400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601454600e546bffffffffffffffffffffffff909116906000611a8e600f613175565b90506000611a9c82846150a5565b905060008167ffffffffffffffff811115611ab957611ab96150cb565b604051908082528060200260200182016040528015611ae2578160200160208202803683370190505b50905060008267ffffffffffffffff811115611b0057611b006150cb565b604051908082528060200260200182016040528015611b29578160200160208202803683370190505b50905060005b85811015611c5b576000600e8281548110611b4c57611b4c6150fa565b600091825260208220015473ffffffffffffffffffffffffffffffffffffffff169150611b7a828a8a6132bd565b9050806bffffffffffffffffffffffff16858481518110611b9d57611b9d6150fa565b60209081029190910181019190915273ffffffffffffffffffffffffffffffffffffffff808416600090815260119092526040909120548551911690859085908110611beb57611beb6150fa565b73ffffffffffffffffffffffffffffffffffffffff92831660209182029290920181019190915292166000908152600b909252506040902080547fffffffffffffffffffffffffffffffffffff000000000000000000000000ffff16905580611c5381615129565b915050611b2f565b5060005b84811015611dd8576000611c74600f8361317f565b73ffffffffffffffffffffffffffffffffffffffff8082166000818152600b602090815260408083208151608081018352905460ff80821615158352610100820416828501526bffffffffffffffffffffffff6201000082048116838501526e0100000000000000000000000000009091041660608201529383526011909152902054929350911684611d078a866150a5565b81518110611d1757611d176150fa565b73ffffffffffffffffffffffffffffffffffffffff9092166020928302919091019091015260408101516bffffffffffffffffffffffff1685611d5a8a866150a5565b81518110611d6a57611d6a6150fa565b60209081029190910181019190915273ffffffffffffffffffffffffffffffffffffffff9092166000908152600b909252506040902080547fffffffffffffffffffffffffffffffffffff000000000000000000000000ffff16905580611dd081615129565b915050611c5f565b5073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000166000908152602160205260408120819055611e2b600f613175565b90505b8015611e6857611e55611e4d611e456001846150b8565b600f9061317f565b600f906134c5565b5080611e60816154c1565b915050611e2e565b507f5af23b715253628d12b660b27a4f3fc626562ea8a55040aa99ab3dc178989fad8183604051611e9a9291906154f6565b60405180910390a1505050505050565b60006110a1826134e7565b60408051610120810182526014546bffffffffffffffffffffffff8116825263ffffffff6c01000000000000000000000000820416602083015262ffffff7001000000000000000000000000000000008204169282019290925261ffff730100000000000000000000000000000000000000830416606082015260ff750100000000000000000000000000000000000000000083048116608083015276010000000000000000000000000000000000000000000083048116151560a08301527701000000000000000000000000000000000000000000000083048116151560c08301527801000000000000000000000000000000000000000000000000909204909116151560e082015260155473ffffffffffffffffffffffffffffffffffffffff16610100820152600090818080611fed84613592565b92509250925061200389858a8a8787878d613784565b9998505050505050505050565b73ffffffffffffffffffffffffffffffffffffffff81166000908152602080526040902080546060919061159690615161565b61204b61318b565b73ffffffffffffffffffffffffffffffffffffffff83166000908152602080526040902061207a828483615203565b508273ffffffffffffffffffffffffffffffffffffffff167f7c44b4eb59ee7873514e7e43e7718c269d872965938b288aa143befca62f99d283836040516110fb92919061531e565b73ffffffffffffffffffffffffffffffffffffffff81166000818152602160205260408082205490517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152919290916370a0823190602401602060405180830381865afa15801561213f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906121639190615524565b6110a191906150b8565b60015473ffffffffffffffffffffffffffffffffffffffff1633146121f3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b6122776131eb565b601480547fffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffff167601000000000000000000000000000000000000000000001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258906020016118ea565b600e5460609060008167ffffffffffffffff811115612309576123096150cb565b60405190808252806020026020018201604052801561234e57816020015b60408051808201909152600080825260208201528152602001906001900390816123275790505b50905060005b828110156123f3576000600e8281548110612371576123716150fa565b600091825260208083209091015473ffffffffffffffffffffffffffffffffffffffff9081168084526011835260409384902054845180860190955281855290911691830182905285519093509091908590859081106123d3576123d36150fa565b6020026020010181905250505080806123eb90615129565b915050612354565b5092915050565b73ffffffffffffffffffffffffffffffffffffffff8116612447576040517f9c8d2cd200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600160255460ff16600181111561246057612460614af5565b03612497576040517f4a3578fb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8281166000908152601160205260409020541633146124f7576040517fcebf515b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601454600e5460009161251a9185916bffffffffffffffffffffffff16906132bd565b73ffffffffffffffffffffffffffffffffffffffff8085166000908152600b6020908152604080832080547fffffffffffffffffffffffffffffffffffff000000000000000000000000ffff1690557f000000000000000000000000000000000000000000000000000000000000000090931682526021905220549091506125b1906bffffffffffffffffffffffff8316906150b8565b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000081166000818152602160205260408082209490945592517fa9059cbb00000000000000000000000000000000000000000000000000000000815291851660048301526bffffffffffffffffffffffff841660248301529063a9059cbb906044016020604051808303816000875af1158015612666573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061268a919061553d565b9050806126c3576040517f90b8ec1800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405133815273ffffffffffffffffffffffffffffffffffffffff808516916bffffffffffffffffffffffff8516918716907f9819093176a1851202c7bcfa46845809b4e47c261866550e94ed3775d2f406989060200160405180910390a450505050565b6127306131eb565b602580547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055565b73ffffffffffffffffffffffffffffffffffffffff8181166000908152601260205260409020541633146127ba576040517f6752e7aa00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff81811660008181526011602090815260408083208054337fffffffffffffffffffffffff000000000000000000000000000000000000000080831682179093556012909452828520805490921690915590519416939092849290917f78af32efdcad432315431e9b03d27e6cd98fb79c405fdc5af7c1714d9c0f75b39190a45050565b6000818152600460209081526040808320815161012081018352815460ff8082161515835261010080830490911615159583019590955263ffffffff620100008204811694830194909452660100000000000081048416606083015273ffffffffffffffffffffffffffffffffffffffff6a01000000000000000000009091048116608083015260018301546fffffffffffffffffffffffffffffffff811660a08401526bffffffffffffffffffffffff70010000000000000000000000000000000082041660c08401527c0100000000000000000000000000000000000000000000000000000000900490931660e08201526002909101549091169181019190915261297283612962816134e7565b8360400151846101000151611eb5565b9392505050565b606060248054806020026020016040519081016040528092919081815260200182805480156129de57602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff1681526001909101906020018083116129b3575b5050505050905090565b604080516102008101825260008082526020820181905291810182905260608082018390526080820183905260a0820183905260c0820183905260e08201839052610100820183905261012082018390526101408201839052610160820183905261018082018390526101a082018390526101c08201526101e0810191909152604080516102008101825260165463ffffffff74010000000000000000000000000000000000000000808304821684527801000000000000000000000000000000000000000000000000808404831660208601526017547c0100000000000000000000000000000000000000000000000000000000810484169686019690965273ffffffffffffffffffffffffffffffffffffffff938416606086015260145460ff828204161515608087015262ffffff70010000000000000000000000000000000082041660a0870152601854928304841660c087015290820490921660e085015293821661010084015261ffff7301000000000000000000000000000000000000009091041661012083015291909116610140820152601954610160820152601a54610180820152601b546101a08201526101c08101612baa60096131de565b815260155473ffffffffffffffffffffffffffffffffffffffff16602090910152919050565b604080516101408101825260008082526020820181905260609282018390528282018190526080820181905260a0820181905260c0820181905260e082018190526101008201526101208101919091526000828152600460209081526040808320815161012081018352815460ff8082161515835261010080830490911615159583019590955263ffffffff620100008204811694830194909452660100000000000081048416606083015273ffffffffffffffffffffffffffffffffffffffff6a010000000000000000000090910481166080830181905260018401546fffffffffffffffffffffffffffffffff811660a08501526bffffffffffffffffffffffff70010000000000000000000000000000000082041660c08501527c0100000000000000000000000000000000000000000000000000000000900490941660e08301526002909201549091169281019290925290919015612da557816080015173ffffffffffffffffffffffffffffffffffffffff1663f00e6a2a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612d7c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612da0919061555f565b612da8565b60005b90506040518061014001604052808273ffffffffffffffffffffffffffffffffffffffff168152602001836040015163ffffffff168152602001600760008781526020019081526020016000208054612e0090615161565b80601f0160208091040260200160405190810160405280929190818152602001828054612e2c90615161565b8015612e795780601f10612e4e57610100808354040283529160200191612e79565b820191906000526020600020905b815481529060010190602001808311612e5c57829003601f168201915b505050505081526020018360c001516bffffffffffffffffffffffff1681526020016005600087815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001836060015163ffffffff1667ffffffffffffffff1681526020018360e0015163ffffffff1681526020018360a001516bffffffffffffffffffffffff168152602001836000015115158152602001601e60008781526020019081526020016000208054612f5690615161565b80601f0160208091040260200160405190810160405280929190818152602001828054612f8290615161565b8015612fcf5780601f10612fa457610100808354040283529160200191612fcf565b820191906000526020600020905b815481529060010190602001808311612fb257829003601f168201915b505050505081525092505050919050565b6000612fea613af4565b905090565b60006110a182612852565b73ffffffffffffffffffffffffffffffffffffffff82811660009081526011602052604090205416331461305a576040517fcebf515b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff8216036130a9576040517f8c8728c700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8281166000908152601260205260409020548116908216146131555773ffffffffffffffffffffffffffffffffffffffff82811660008181526012602052604080822080547fffffffffffffffffffffffff0000000000000000000000000000000000000000169486169485179055513392917f84f7c7c80bb8ed2279b4aab5f61cd05e6374073d38f46d7f32de8c30e9e3836791a45b5050565b6131616131eb565b61316a81613bbe565b50565b6000612fea60025b60006110a1825490565b60006129728383613cb3565b60175473ffffffffffffffffffffffffffffffffffffffff1633146131dc576040517f77c3599200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b6060600061297283613cdd565b60005473ffffffffffffffffffffffffffffffffffffffff1633146131dc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e65720000000000000000000060448201526064016121ea565b60185473ffffffffffffffffffffffffffffffffffffffff1633146131dc576040517fb6dfb7a600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff83166000908152600b602090815260408083208151608081018352905460ff80821615801584526101008304909116948301949094526bffffffffffffffffffffffff6201000082048116938301939093526e01000000000000000000000000000090049091166060820152906134b9576000816060015185613355919061541d565b905060006133638583615471565b90508083604001818151613377919061549c565b6bffffffffffffffffffffffff16905250613392858261557c565b836060018181516133a3919061549c565b6bffffffffffffffffffffffff90811690915273ffffffffffffffffffffffffffffffffffffffff89166000908152600b602090815260409182902087518154928901519389015160608a015186166e010000000000000000000000000000027fffffffffffff000000000000000000000000ffffffffffffffffffffffffffff919096166201000002167fffffffffffff000000000000000000000000000000000000000000000000ffff60ff95909516610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff921515929092167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000909416939093171792909216179190911790555050505b60400151949350505050565b60006129728373ffffffffffffffffffffffffffffffffffffffff8416613d38565b6000818160045b600f811015613574577fff00000000000000000000000000000000000000000000000000000000000000821683826020811061352c5761352c6150fa565b1a60f81b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161461356257506000949350505050565b8061356c81615129565b9150506134ee565b5081600f1a600181111561358a5761358a614af5565b949350505050565b600080600080846040015162ffffff1690506000808263ffffffff161190506000807f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa15801561361f573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061364391906155c3565b509450909250505060008113158061365a57508142105b8061367b575082801561367b575061367282426150b8565b8463ffffffff16105b1561368a57601954965061368e565b8096505b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa1580156136f9573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061371d91906155c3565b509450909250505060008113158061373457508142105b806137555750828015613755575061374c82426150b8565b8463ffffffff16105b1561376457601a549550613768565b8095505b86866137738a613e2b565b965096509650505050509193909250565b600080808089600181111561379b5761379b614af5565b036137aa575062017f986137ff565b60018960018111156137be576137be614af5565b036137cd57506201e26c6137ff565b6040517ff2b2d41200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008a6080015160016138129190615613565b6138209060ff16604061562c565b60185461384e906103a49074010000000000000000000000000000000000000000900463ffffffff166150a5565b61385891906150a5565b601554604080517fde9ee35e0000000000000000000000000000000000000000000000000000000081528151939450600093849373ffffffffffffffffffffffffffffffffffffffff169263de9ee35e92600480820193918290030181865afa1580156138c9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906138ed9190615643565b909250905081836138ff8360186150a5565b613909919061562c565b60808f0151613919906001615613565b6139289060ff166115e061562c565b61393291906150a5565b61393c91906150a5565b61394690856150a5565b6101008e01516040517f125441400000000000000000000000000000000000000000000000000000000081526004810186905291955073ffffffffffffffffffffffffffffffffffffffff1690631254414090602401602060405180830381865afa1580156139b9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906139dd9190615524565b8d6060015161ffff166139f0919061562c565b94505050506000613a018b86613f15565b60008d815260046020526040902054909150610100900460ff1615613a665760008c81526023602090815260409182902082518084018452905463ffffffff811680835264010000000090910462ffffff908116928401928352928501525116908201525b6000613ad08c6040518061012001604052808d63ffffffff1681526020018681526020018781526020018c81526020018b81526020018a81526020018973ffffffffffffffffffffffffffffffffffffffff16815260200185815260200160001515815250614091565b60208101518151919250613ae39161549c565b9d9c50505050505050505050505050565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166000818152602160205260408082205490517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152919290916370a0823190602401602060405180830381865afa158015613b90573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613bb49190615524565b612fea9190615667565b3373ffffffffffffffffffffffffffffffffffffffff821603613c3d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c6600000000000000000060448201526064016121ea565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6000826000018281548110613cca57613cca6150fa565b9060005260206000200154905092915050565b60608160000180548060200260200160405190810160405280929190818152602001828054801561160f57602002820191906000526020600020905b815481526020019060010190808311613d195750505050509050919050565b60008181526001830160205260408120548015613e21576000613d5c6001836150b8565b8554909150600090613d70906001906150b8565b9050818114613dd5576000866000018281548110613d9057613d906150fa565b9060005260206000200154905080876000018481548110613db357613db36150fa565b6000918252602080832090910192909255918252600188019052604090208390555b8554869080613de657613de6615687565b6001900381819060005260206000200160009055905585600101600086815260200190815260200160002060009055600193505050506110a1565b60009150506110a1565b60008060007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015613e9b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613ebf91906155c3565b50935050925050600082131580613ed557508042105b80613f0557506000846040015162ffffff16118015613f055750613ef981426150b8565b846040015162ffffff16105b156123f3575050601b5492915050565b60408051608081018252600080825260208083018281528385018381526060850184905273ffffffffffffffffffffffffffffffffffffffff878116855260229093528584208054640100000000810462ffffff1690925263ffffffff82169092527b01000000000000000000000000000000000000000000000000000000810460ff16855285517ffeaf968c00000000000000000000000000000000000000000000000000000000815295519495919484936701000000000000009092049091169163feaf968c9160048083019260a09291908290030181865afa158015614002573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061402691906155c3565b5093505092505060008213158061403c57508042105b8061406c57506000866040015162ffffff1611801561406c575061406081426150b8565b866040015162ffffff16105b156140805760018301546060850152614088565b606084018290525b50505092915050565b6040805161010081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e081019190915260008260e001516000015160ff1690506000846060015161ffff1684606001516140fc919061562c565b9050836101000151801561410f5750803a105b1561411757503a5b60006012831161412857600161413e565b6141336012846150b8565b61413e90600a6157d6565b9050600060128410614151576001614167565b61415c8460126150b8565b61416790600a6157d6565b905060008660a0015187604001518860200151896000015161418991906150a5565b614193908761562c565b61419d91906150a5565b6141a7919061562c565b9050614203828860e00151606001516141c0919061562c565b6001848a60e00151606001516141d6919061562c565b6141e091906150b8565b6141ea868561562c565b6141f491906150a5565b6141fe91906157e2565b6143d8565b6bffffffffffffffffffffffff1686526080870151614226906141fe90836157e2565b6bffffffffffffffffffffffff1660408088019190915260e0880151015160009061425f9062ffffff16683635c9adc5dea0000061562c565b9050600081633b9aca008a60a001518b60e001516020015163ffffffff168c604001518d600001518b614292919061562c565b61429c91906150a5565b6142a6919061562c565b6142b0919061562c565b6142ba91906157e2565b6142c491906150a5565b9050614307848a60e00151606001516142dd919061562c565b6001868c60e00151606001516142f3919061562c565b6142fd91906150b8565b6141ea888561562c565b6bffffffffffffffffffffffff166020890152608089015161432d906141fe90836157e2565b6bffffffffffffffffffffffff16606089015260c089015173ffffffffffffffffffffffffffffffffffffffff166080808a0191909152890151614370906143d8565b6bffffffffffffffffffffffff1660a0808a0191909152890151614393906143d8565b6bffffffffffffffffffffffff1660c089015260e0890151606001516143b8906143d8565b6bffffffffffffffffffffffff1660e08901525050505050505092915050565b60006bffffffffffffffffffffffff821115614476576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203960448201527f362062697473000000000000000000000000000000000000000000000000000060648201526084016121ea565b5090565b6000806040838503121561448d57600080fd5b50508035926020909101359150565b600081518084526020808501945080840160005b838110156144cc578151875295820195908201906001016144b0565b509495945050505050565b602081526000612972602083018461449c565b60008083601f8401126144fc57600080fd5b50813567ffffffffffffffff81111561451457600080fd5b60208301915083602082850101111561452c57600080fd5b9250929050565b60008060006040848603121561454857600080fd5b83359250602084013567ffffffffffffffff81111561456657600080fd5b614572868287016144ea565b9497909650939450505050565b600081518084526020808501945080840160005b838110156144cc57815173ffffffffffffffffffffffffffffffffffffffff1687529582019590820190600101614593565b805163ffffffff16825260006101e060208301516145eb602086018263ffffffff169052565b506040830151614603604086018263ffffffff169052565b50606083015161461a606086018262ffffff169052565b506080830151614630608086018261ffff169052565b5060a083015161465060a08601826bffffffffffffffffffffffff169052565b5060c083015161466860c086018263ffffffff169052565b5060e083015161468060e086018263ffffffff169052565b506101008381015163ffffffff908116918601919091526101208085015190911690850152610140808401519085015261016080840151908501526101808084015173ffffffffffffffffffffffffffffffffffffffff16908501526101a0808401518186018390526146f58387018261457f565b925050506101c0808401516147218287018273ffffffffffffffffffffffffffffffffffffffff169052565b5090949350505050565b855163ffffffff16815260006101c0602088015161475960208501826bffffffffffffffffffffffff169052565b5060408801516040840152606088015161478360608501826bffffffffffffffffffffffff169052565b506080880151608084015260a08801516147a560a085018263ffffffff169052565b5060c08801516147bd60c085018263ffffffff169052565b5060e088015160e0840152610100808901516147e08286018263ffffffff169052565b5050610120888101511515908401526101408301819052614803818401886145c5565b9050828103610160840152614818818761457f565b905082810361018084015261482d818661457f565b9150506148406101a083018460ff169052565b9695505050505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461316a57600080fd5b6000806040838503121561487f57600080fd5b823561488a8161484a565b915060208301356004811061489e57600080fd5b809150509250929050565b6000602082840312156148bb57600080fd5b5035919050565b6000815180845260005b818110156148e8576020818501810151868301820152016148cc565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b60208152600061297260208301846148c2565b815173ffffffffffffffffffffffffffffffffffffffff1681526101608101602083015161496f602084018263ffffffff169052565b506040830151614987604084018263ffffffff169052565b50606083015161499f606084018263ffffffff169052565b5060808301516149c7608084018273ffffffffffffffffffffffffffffffffffffffff169052565b5060a08301516149df60a084018263ffffffff169052565b5060c08301516149f760c084018263ffffffff169052565b5060e0830151614a0f60e084018263ffffffff169052565b506101008381015173ffffffffffffffffffffffffffffffffffffffff81168483015250506101208381015163ffffffff81168483015250506101408381015163ffffffff8116848301525b505092915050565b60008060208385031215614a7657600080fd5b823567ffffffffffffffff80821115614a8e57600080fd5b818501915085601f830112614aa257600080fd5b813581811115614ab157600080fd5b8660208260051b8501011115614ac657600080fd5b60209290920196919550909350505050565b600060208284031215614aea57600080fd5b81356129728161484a565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b6002811061316a5761316a614af5565b60208101614b4183614b24565b91905290565b60008060008060808587031215614b5d57600080fd5b84359350602085013560028110614b7357600080fd5b9250604085013563ffffffff81168114614b8c57600080fd5b91506060850135614b9c8161484a565b939692955090935050565b600080600060408486031215614bbc57600080fd5b8335614bc78161484a565b9250602084013567ffffffffffffffff81111561456657600080fd5b602080825282518282018190526000919060409081850190868401855b82811015614c3f578151805173ffffffffffffffffffffffffffffffffffffffff90811686529087015116868501529284019290850190600101614c00565b5091979650505050505050565b60008060408385031215614c5f57600080fd5b8235614c6a8161484a565b9150602083013561489e8161484a565b6020808252825182820181905260009190848201906040850190845b81811015614cc857835173ffffffffffffffffffffffffffffffffffffffff1683529284019291840191600101614c96565b50909695505050505050565b60208152614ceb60208201835163ffffffff169052565b60006020830151614d04604084018263ffffffff169052565b50604083015163ffffffff8116606084015250606083015173ffffffffffffffffffffffffffffffffffffffff8116608084015250608083015180151560a08401525060a083015162ffffff811660c08401525060c083015163ffffffff811660e08401525060e0830151610100614d838185018363ffffffff169052565b8401519050610120614dac8482018373ffffffffffffffffffffffffffffffffffffffff169052565b8401519050610140614dc38482018361ffff169052565b8401519050610160614dec8482018373ffffffffffffffffffffffffffffffffffffffff169052565b840151610180848101919091528401516101a0808501919091528401516101c0808501919091528401516102006101e080860182905291925090614e3461022086018461457f565b9086015173ffffffffffffffffffffffffffffffffffffffff811683870152909250614721565b60208152614e8260208201835173ffffffffffffffffffffffffffffffffffffffff169052565b60006020830151614e9b604084018263ffffffff169052565b506040830151610140806060850152614eb86101608501836148c2565b91506060850151614ed960808601826bffffffffffffffffffffffff169052565b50608085015173ffffffffffffffffffffffffffffffffffffffff811660a08601525060a085015167ffffffffffffffff811660c08601525060c085015163ffffffff811660e08601525060e0850151610100614f45818701836bffffffffffffffffffffffff169052565b8601519050610120614f5a8682018315159052565b8601518584037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00183870152905061484083826148c2565b6000610120820190506bffffffffffffffffffffffff835116825263ffffffff60208401511660208301526040830151614fd3604084018262ffffff169052565b506060830151614fe9606084018261ffff169052565b506080830151614ffe608084018260ff169052565b5060a083015161501260a084018215159052565b5060c083015161502660c084018215159052565b5060e083015161503a60e084018215159052565b506101008381015173ffffffffffffffffffffffffffffffffffffffff811684830152614a5b565b6020810160048310614b4157614b41614af5565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b808201808211156110a1576110a1615076565b818103818111156110a1576110a1615076565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361515a5761515a615076565b5060010190565b600181811c9082168061517557607f821691505b6020821081036151ae577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b601f8211156151fe57600081815260208120601f850160051c810160208610156151db5750805b601f850160051c820191505b818110156151fa578281556001016151e7565b5050505b505050565b67ffffffffffffffff83111561521b5761521b6150cb565b61522f836152298354615161565b836151b4565b6000601f841160018114615281576000851561524b5750838201355b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600387901b1c1916600186901b178355615317565b6000838152602090207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0861690835b828110156152d057868501358255602094850194600190920191016152b0565b508682101561530b577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88860031b161c19848701351681555b505060018560011b0183555b5050505050565b60208152816020820152818360408301376000818301604090810191909152601f9092017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0160101919050565b6000604082016040835280865480835260608501915087600052602092508260002060005b828110156153c257815473ffffffffffffffffffffffffffffffffffffffff1684529284019260019182019101615390565b505050838103828501528481528590820160005b868110156154115782356153e98161484a565b73ffffffffffffffffffffffffffffffffffffffff16825291830191908301906001016153d6565b50979650505050505050565b6bffffffffffffffffffffffff8281168282160390808211156123f3576123f3615076565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b60006bffffffffffffffffffffffff8084168061549057615490615442565b92169190910492915050565b6bffffffffffffffffffffffff8181168382160190808211156123f3576123f3615076565b6000816154d0576154d0615076565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190565b604081526000615509604083018561457f565b828103602084015261551b818561449c565b95945050505050565b60006020828403121561553657600080fd5b5051919050565b60006020828403121561554f57600080fd5b8151801515811461297257600080fd5b60006020828403121561557157600080fd5b81516129728161484a565b6bffffffffffffffffffffffff818116838216028082169190828114614a5b57614a5b615076565b805169ffffffffffffffffffff811681146155be57600080fd5b919050565b600080600080600060a086880312156155db57600080fd5b6155e4866155a4565b9450602086015193506040860151925060608601519150615607608087016155a4565b90509295509295909350565b60ff81811683821601908111156110a1576110a1615076565b80820281158282048414176110a1576110a1615076565b6000806040838503121561565657600080fd5b505080516020909101519092909150565b81810360008312801583831316838312821617156123f3576123f3615076565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b600181815b8085111561570f57817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048211156156f5576156f5615076565b8085161561570257918102915b93841c93908002906156bb565b509250929050565b600082615726575060016110a1565b81615733575060006110a1565b816001811461574957600281146157535761576f565b60019150506110a1565b60ff84111561576457615764615076565b50506001821b6110a1565b5060208310610133831016604e8410600b8410161715615792575081810a6110a1565b61579c83836156b6565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048211156157ce576157ce615076565b029392505050565b60006129728383615717565b6000826157f1576157f1615442565b50049056fea164736f6c6343000813000a", } var AutomationRegistryLogicCABI = AutomationRegistryLogicCMetaData.ABI diff --git a/core/gethwrappers/generated/automation_registry_wrapper_2_2/automation_registry_wrapper_2_2.go b/core/gethwrappers/generated/automation_registry_wrapper_2_2/automation_registry_wrapper_2_2.go index eb385cf7b0..a7fcf430b3 100644 --- a/core/gethwrappers/generated/automation_registry_wrapper_2_2/automation_registry_wrapper_2_2.go +++ b/core/gethwrappers/generated/automation_registry_wrapper_2_2/automation_registry_wrapper_2_2.go @@ -52,7 +52,7 @@ type AutomationRegistryBase22OnchainConfig struct { var AutomationRegistryMetaData = &bind.MetaData{ ABI: "[{\"inputs\":[{\"internalType\":\"contractAutomationRegistryLogicB2_2\",\"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\":[],\"name\":\"InvalidDataLength\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidPayee\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidRecipient\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidReport\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidSigner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidTransmitter\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidTrigger\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidTriggerType\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MaxCheckDataSizeCanOnlyIncrease\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MaxPerformDataSizeCanOnlyIncrease\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MigrationNotPermitted\",\"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\":\"OnlyPausedUpkeep\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlySimulatedBackend\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyUnpausedUpkeep\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ParameterLengthError\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"PaymentGreaterThanAllLINK\",\"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\":\"UpkeepAlreadyExists\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UpkeepCancelled\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UpkeepNotCanceled\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UpkeepNotNeeded\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ValueNotChanged\",\"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\"},{\"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\":\"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\":\"uint96\",\"name\":\"amount\",\"type\":\"uint96\"}],\"name\":\"OwnerFundsWithdrawn\",\"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\"},{\"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\":[{\"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\":[],\"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\":\"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\":\"contractIChainModule\",\"name\":\"chainModule\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"reorgProtectionEnabled\",\"type\":\"bool\"}],\"internalType\":\"structAutomationRegistryBase2_2.OnchainConfig\",\"name\":\"onchainConfig\",\"type\":\"tuple\"},{\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"}],\"name\":\"setConfigTypeSafe\",\"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\":\"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\"}]", - Bin: "0x6101406040523480156200001257600080fd5b506040516200524a3803806200524a8339810160408190526200003591620003b1565b80816001600160a01b031663ca30e6036040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000075573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200009b9190620003b1565b826001600160a01b031663b10b673c6040518163ffffffff1660e01b8152600401602060405180830381865afa158015620000da573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620001009190620003b1565b836001600160a01b0316636709d0e56040518163ffffffff1660e01b8152600401602060405180830381865afa1580156200013f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620001659190620003b1565b846001600160a01b0316635425d8ac6040518163ffffffff1660e01b8152600401602060405180830381865afa158015620001a4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620001ca9190620003b1565b856001600160a01b031663a08714c06040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000209573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200022f9190620003b1565b3380600081620002865760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620002b957620002b981620002ed565b5050506001600160a01b0394851660805292841660a05290831660c052821660e052811661010052166101205250620003d8565b336001600160a01b03821603620003475760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c6600000000000000000060448201526064016200027d565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6001600160a01b0381168114620003ae57600080fd5b50565b600060208284031215620003c457600080fd5b8151620003d18162000398565b9392505050565b60805160a05160c05160e0516101005161012051614e25620004256000396000818160c9015261017c01526000611bb8015260005050600050506000505060006104c20152614e256000f3fe6080604052600436106100c75760003560e01c8063aed2e92911610074578063e3d0e7121161004e578063e3d0e71214610348578063f2fde38b14610368578063f75f6b1114610388576100c7565b8063aed2e929146102a3578063afcb95d7146102da578063b1dc65a414610328576100c7565b806381ff7048116100a557806381ff7048146101d65780638da5cb5b14610258578063a4c0ed3614610283576100c7565b8063181f5a771461010e578063349e8cca1461016d57806379ba5097146101c1575b7f00000000000000000000000000000000000000000000000000000000000000003660008037600080366000845af43d6000803e808015610107573d6000f35b3d6000fd5b005b34801561011a57600080fd5b506101576040518060400160405280601881526020017f4175746f6d6174696f6e526567697374727920322e322e30000000000000000081525081565b6040516101649190613b21565b60405180910390f35b34801561017957600080fd5b507f00000000000000000000000000000000000000000000000000000000000000005b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610164565b3480156101cd57600080fd5b5061010c6103a8565b3480156101e257600080fd5b5061023560155460115463ffffffff780100000000000000000000000000000000000000000000000083048116937c01000000000000000000000000000000000000000000000000000000009093041691565b6040805163ffffffff948516815293909216602084015290820152606001610164565b34801561026457600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff1661019c565b34801561028f57600080fd5b5061010c61029e366004613baf565b6104aa565b3480156102af57600080fd5b506102c36102be366004613c0b565b6106c6565b604080519215158352602083019190915201610164565b3480156102e657600080fd5b50601154601254604080516000815260208101939093527401000000000000000000000000000000000000000090910463ffffffff1690820152606001610164565b34801561033457600080fd5b5061010c610343366004613c9c565b61083c565b34801561035457600080fd5b5061010c610363366004613f6d565b610b77565b34801561037457600080fd5b5061010c61038336600461403a565b610ba0565b34801561039457600080fd5b5061010c6103a336600461423e565b610bb4565b60015473ffffffffffffffffffffffffffffffffffffffff16331461042e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610519576040517fc8bad78d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60208114610553576040517fdfe9309000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000610561828401846142cd565b60008181526004602052604090205490915065010000000000900463ffffffff908116146105bb576040517f9c0083a200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000818152600460205260409020600101546105f69085906c0100000000000000000000000090046bffffffffffffffffffffffff16614315565b600082815260046020526040902060010180546bffffffffffffffffffffffff929092166c01000000000000000000000000027fffffffffffffffff000000000000000000000000ffffffffffffffffffffffff90921691909117905560195461066190859061433a565b6019556040516bffffffffffffffffffffffff8516815273ffffffffffffffffffffffffffffffffffffffff86169082907fafd24114486da8ebfc32f3626dada8863652e187461aa74d4bfa7348915062039060200160405180910390a35050505050565b6000806106d1611ba0565b6012547e01000000000000000000000000000000000000000000000000000000000000900460ff1615610730576040517f24522f3400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600085815260046020908152604091829020825160e081018452815460ff811615158252610100810463ffffffff908116838601819052650100000000008304821684880152690100000000000000000090920473ffffffffffffffffffffffffffffffffffffffff16606084018190526001909401546bffffffffffffffffffffffff80821660808601526c0100000000000000000000000082041660a0850152780100000000000000000000000000000000000000000000000090041660c08301528451601f8901859004850281018501909552878552909361082f938990899081908401838280828437600092019190915250611c1192505050565b9097909650945050505050565b60005a60408051610160810182526012546bffffffffffffffffffffffff8116825263ffffffff6c010000000000000000000000008204811660208401527001000000000000000000000000000000008204811693830193909352740100000000000000000000000000000000000000008104909216606082015262ffffff7801000000000000000000000000000000000000000000000000830416608082015261ffff7b0100000000000000000000000000000000000000000000000000000083041660a082015260ff7d0100000000000000000000000000000000000000000000000000000000008304811660c08301527e010000000000000000000000000000000000000000000000000000000000008304811615801560e08401527f01000000000000000000000000000000000000000000000000000000000000009093048116151561010080840191909152601354918216151561012084015273ffffffffffffffffffffffffffffffffffffffff9104166101408201529192506109f2576040517f24522f3400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b336000908152600b602052604090205460ff16610a3b576040517f1099ed7500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6011548a3514610a77576040517fdfdcf8e700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60c0810151610a8790600161437c565b60ff1686141580610a985750858414155b15610acf576040517f0244f71a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610adf8a8a8a8a8a8a8a8a611e3a565b6000610aeb8a8a6120a3565b905060208b0135600881901c63ffffffff16610b0884848761215e565b836060015163ffffffff168163ffffffff161115610b6857601280547fffffffffffffffff00000000ffffffffffffffffffffffffffffffffffffffff167401000000000000000000000000000000000000000063ffffffff8416021790555b50505050505050505050505050565b610b9886868686806020019051810190610b91919061443b565b8686610bb4565b505050505050565b610ba8612add565b610bb181612b5e565b50565b610bbc612add565b601f86511115610bf8576040517f25d0209c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8360ff16600003610c35576040517fe77dba5600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b84518651141580610c545750610c4c8460036145bd565b60ff16865111155b15610c8b576040517f1d2d1c5800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601254600e546bffffffffffffffffffffffff9091169060005b816bffffffffffffffffffffffff16811015610d0d57610cfa600e8281548110610cd157610cd161434d565b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff168484612c53565b5080610d05816145d9565b915050610ca5565b5060008060005b836bffffffffffffffffffffffff16811015610e1657600d8181548110610d3d57610d3d61434d565b600091825260209091200154600e805473ffffffffffffffffffffffffffffffffffffffff90921694509082908110610d7857610d7861434d565b600091825260208083209091015473ffffffffffffffffffffffffffffffffffffffff8681168452600c8352604080852080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00001690559116808452600b90925290912080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055915080610e0e816145d9565b915050610d14565b50610e23600d6000613a00565b610e2f600e6000613a00565b604080516080810182526000808252602082018190529181018290526060810182905290805b8c5181101561129857600c60008e8381518110610e7457610e7461434d565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff1682528101919091526040016000205460ff1615610edf576040517f77cea0fa00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168d8281518110610f0957610f0961434d565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1603610f5e576040517f815e1d6400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405180604001604052806001151581526020018260ff16815250600c60008f8481518110610f8f57610f8f61434d565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff1682528181019290925260400160002082518154939092015160ff16610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff921515929092167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000909316929092171790558b518c90829081106110375761103761434d565b60200260200101519150600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036110a7576040517f58a70a0a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff82166000908152600b60209081526040918290208251608081018452905460ff80821615801584526101008304909116938301939093526bffffffffffffffffffffffff6201000082048116948301949094526e01000000000000000000000000000090049092166060830152909350611162576040517f6a7281ad00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001835260ff80821660208086019182526bffffffffffffffffffffffff808b166060880190815273ffffffffffffffffffffffffffffffffffffffff87166000908152600b909352604092839020885181549551948a0151925184166e010000000000000000000000000000027fffffffffffff000000000000000000000000ffffffffffffffffffffffffffff939094166201000002929092167fffffffffffff000000000000000000000000000000000000000000000000ffff94909616610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff921515929092167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00009095169490941717919091169290921791909117905580611290816145d9565b915050610e55565b50508a516112ae9150600d9060208d0190613a1e565b5088516112c290600e9060208c0190613a1e565b50604051806101600160405280856bffffffffffffffffffffffff168152602001886000015163ffffffff168152602001886020015163ffffffff168152602001600063ffffffff168152602001886060015162ffffff168152602001886080015161ffff1681526020018960ff1681526020016012600001601e9054906101000a900460ff16151581526020016012600001601f9054906101000a900460ff161515815260200188610200015115158152602001886101e0015173ffffffffffffffffffffffffffffffffffffffff16815250601260008201518160000160006101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550602082015181600001600c6101000a81548163ffffffff021916908363ffffffff16021790555060408201518160000160106101000a81548163ffffffff021916908363ffffffff16021790555060608201518160000160146101000a81548163ffffffff021916908363ffffffff16021790555060808201518160000160186101000a81548162ffffff021916908362ffffff16021790555060a082015181600001601b6101000a81548161ffff021916908361ffff16021790555060c082015181600001601d6101000a81548160ff021916908360ff16021790555060e082015181600001601e6101000a81548160ff02191690831515021790555061010082015181600001601f6101000a81548160ff0219169083151502179055506101208201518160010160006101000a81548160ff0219169083151502179055506101408201518160010160016101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055509050506040518061018001604052808860a001516bffffffffffffffffffffffff16815260200188610180015173ffffffffffffffffffffffffffffffffffffffff168152602001601460010160009054906101000a90046bffffffffffffffffffffffff166bffffffffffffffffffffffff168152602001886040015163ffffffff1681526020018860c0015163ffffffff168152602001601460010160149054906101000a900463ffffffff1663ffffffff168152602001601460010160189054906101000a900463ffffffff1663ffffffff1681526020016014600101601c9054906101000a900463ffffffff1663ffffffff1681526020018860e0015163ffffffff16815260200188610100015163ffffffff16815260200188610120015163ffffffff168152602001886101c0015173ffffffffffffffffffffffffffffffffffffffff16815250601460008201518160000160006101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550602082015181600001600c6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060408201518160010160006101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550606082015181600101600c6101000a81548163ffffffff021916908363ffffffff16021790555060808201518160010160106101000a81548163ffffffff021916908363ffffffff16021790555060a08201518160010160146101000a81548163ffffffff021916908363ffffffff16021790555060c08201518160010160186101000a81548163ffffffff021916908363ffffffff16021790555060e082015181600101601c6101000a81548163ffffffff021916908363ffffffff1602179055506101008201518160020160006101000a81548163ffffffff021916908363ffffffff1602179055506101208201518160020160046101000a81548163ffffffff021916908363ffffffff1602179055506101408201518160020160086101000a81548163ffffffff021916908363ffffffff16021790555061016082015181600201600c6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555090505086610140015160178190555086610160015160188190555060006014600101601c9054906101000a900463ffffffff169050876101e0015173ffffffffffffffffffffffffffffffffffffffff166357e871e76040518163ffffffff1660e01b8152600401602060405180830381865afa158015611963573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119879190614611565b601580547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167c010000000000000000000000000000000000000000000000000000000063ffffffff9384160217808255600192601891611a02918591780100000000000000000000000000000000000000000000000090041661462a565b92506101000a81548163ffffffff021916908363ffffffff160217905550600088604051602001611a339190614698565b604080518083037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0018152919052601554909150611a9c90469030907801000000000000000000000000000000000000000000000000900463ffffffff168f8f8f878f8f612e5b565b60115560005b611aac6009612f05565b811015611adc57611ac9611ac1600983612f0f565b600990612f22565b5080611ad4816145d9565b915050611aa2565b5060005b896101a0015151811015611b3357611b208a6101a001518281518110611b0857611b0861434d565b60200260200101516009612f4490919063ffffffff16565b5080611b2b816145d9565b915050611ae0565b507f1591690b8638f5fb2dbec82ac741805ac5da8b45dc5263f4875b0496fdce4e0582601154601460010160189054906101000a900463ffffffff168f8f8f878f8f604051611b8a9998979695949392919061483c565b60405180910390a1505050505050505050505050565b3273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614611c0f576040517fb60ac5db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b60125460009081907f0100000000000000000000000000000000000000000000000000000000000000900460ff1615611c76576040517f37ed32e800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601280547effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f01000000000000000000000000000000000000000000000000000000000000001790556040517f4585e33b0000000000000000000000000000000000000000000000000000000090611cf2908590602401613b21565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931790925290517f79188d1600000000000000000000000000000000000000000000000000000000815290935073ffffffffffffffffffffffffffffffffffffffff8616906379188d1690611dc590879087906004016148d2565b60408051808303816000875af1158015611de3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e0791906148eb565b601280547effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16905590969095509350505050565b60008787604051611e4c929190614919565b604051908190038120611e63918b90602001614929565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815282825280516020918201208383019092526000808452908301819052909250906000805b8881101561203a57600185878360208110611ecf57611ecf61434d565b611edc91901a601b61437c565b8c8c85818110611eee57611eee61434d565b905060200201358b8b86818110611f0757611f0761434d565b9050602002013560405160008152602001604052604051611f44949392919093845260ff9290921660208401526040830152606082015260800190565b6020604051602081039080840390855afa158015611f66573d6000803e3d6000fd5b5050604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081015173ffffffffffffffffffffffffffffffffffffffff81166000908152600c602090815290849020838501909452925460ff8082161515808552610100909204169383019390935290955093509050612014576040517f0f4c073700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b826020015160080260ff166001901b840193508080612032906145d9565b915050611eb2565b50827e01010101010101010101010101010101010101010101010101010101010101841614612095576040517fc103be2e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505050505050505050505050565b6120dc6040518060c001604052806000815260200160008152602001606081526020016060815260200160608152602001606081525090565b60006120ea83850185614a1a565b604081015151606082015151919250908114158061210d57508082608001515114155b8061211d5750808260a001515114155b15612154576040517fb55ac75400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5090505b92915050565b600082604001515167ffffffffffffffff81111561217e5761217e613d53565b60405190808252806020026020018201604052801561223a57816020015b604080516101c081018252600060e08201818152610100830182905261012083018290526101408301829052610160830182905261018083018290526101a0830182905282526020808301829052928201819052606082018190526080820181905260a0820181905260c082015282527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90920191018161219c5790505b50905060006040518060800160405280600061ffff1681526020016000815260200160006bffffffffffffffffffffffff16815260200160006bffffffffffffffffffffffff168152509050600085610140015173ffffffffffffffffffffffffffffffffffffffff166357e871e76040518163ffffffff1660e01b8152600401602060405180830381865afa1580156122d8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906122fc9190614611565b9050600086610140015173ffffffffffffffffffffffffffffffffffffffff166318b8f6136040518163ffffffff1660e01b8152600401602060405180830381865afa158015612350573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123749190614611565b905060005b8660400151518110156127c35760046000886040015183815181106123a0576123a061434d565b6020908102919091018101518252818101929092526040908101600020815160e081018352815460ff811615158252610100810463ffffffff90811695830195909552650100000000008104851693820193909352690100000000000000000090920473ffffffffffffffffffffffffffffffffffffffff166060830152600101546bffffffffffffffffffffffff80821660808401526c0100000000000000000000000082041660a08301527801000000000000000000000000000000000000000000000000900490911660c082015285518690839081106124855761248561434d565b6020026020010151600001819052506124ba876040015182815181106124ad576124ad61434d565b6020026020010151612f66565b8582815181106124cc576124cc61434d565b60200260200101516060019060018111156124e9576124e9614b07565b908160018111156124fc576124fc614b07565b81525050612560876040015182815181106125195761251961434d565b602002602001015184896080015184815181106125385761253861434d565b60200260200101518885815181106125525761255261434d565b60200260200101518c613011565b8683815181106125725761257261434d565b602002602001015160200187848151811061258f5761258f61434d565b602002602001015160c00182815250821515151581525050508481815181106125ba576125ba61434d565b602002602001015160200151156125ea576001846000018181516125de9190614b36565b61ffff169052506125ef565b6127b1565b6126558582815181106126045761260461434d565b602002602001015160000151606001518860600151838151811061262a5761262a61434d565b60200260200101518960a0015184815181106126485761264861434d565b6020026020010151611c11565b8683815181106126675761266761434d565b60200260200101516040018784815181106126845761268461434d565b602090810291909101015160800191909152901515905260c08801516126ab90600161437c565b6126b99060ff166040614b51565b6103a48860a0015183815181106126d2576126d261434d565b6020026020010151516126e5919061433a565b6126ef919061433a565b8582815181106127015761270161434d565b602002602001015160a00181815250508481815181106127235761272361434d565b602002602001015160a0015184602001818151612740919061433a565b90525084518590829081106127575761275761434d565b6020026020010151608001518661276e9190614b68565b95506127b1876040015182815181106127895761278961434d565b6020026020010151848784815181106127a4576127a461434d565b6020026020010151613130565b806127bb816145d9565b915050612379565b50825161ffff166000036127da5750505050505050565b6155f06127e8366010614b51565b5a6127f39088614b68565b6127fd919061433a565b612807919061433a565b8351909550611b589061281e9061ffff1687614baa565b612828919061433a565b945060008060005b886040015151811015612a0c5786818151811061284f5761284f61434d565b602002602001015160200151156129fa576128e88a8a60400151838151811061287a5761287a61434d565b60200260200101518984815181106128945761289461434d565b6020026020010151608001518c600001518d602001518d8c602001518e89815181106128c2576128c261434d565b602002602001015160a001518c6128d99190614b51565b6128e39190614baa565b613235565b6060880180519295509093508391612901908390614315565b6bffffffffffffffffffffffff16905250604086018051849190612926908390614315565b6bffffffffffffffffffffffff16905250865187908290811061294b5761294b61434d565b60200260200101516040015115158960400151828151811061296f5761296f61434d565b60200260200101517fad8cc9579b21dfe2c2f6ea35ba15b656e46b4f5b0cb424f52739b8ce5cac9c5b84866129a49190614315565b8a85815181106129b6576129b661434d565b6020026020010151608001518c8e6080015187815181106129d9576129d961434d565b60200260200101516040516129f19493929190614bbe565b60405180910390a35b80612a04816145d9565b915050612830565b505050604083810151336000908152600b602052919091208054600290612a489084906201000090046bffffffffffffffffffffffff16614315565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055508260600151601260000160008282829054906101000a90046bffffffffffffffffffffffff16612aa69190614315565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff16021790555050505050505050565b60005473ffffffffffffffffffffffffffffffffffffffff163314611c0f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610425565b3373ffffffffffffffffffffffffffffffffffffffff821603612bdd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610425565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b73ffffffffffffffffffffffffffffffffffffffff83166000908152600b602090815260408083208151608081018352905460ff80821615801584526101008304909116948301949094526bffffffffffffffffffffffff6201000082048116938301939093526e0100000000000000000000000000009004909116606082015290612e4f576000816060015185612ceb9190614bfb565b90506000612cf98583614c20565b90508083604001818151612d0d9190614315565b6bffffffffffffffffffffffff16905250612d288582614c4b565b83606001818151612d399190614315565b6bffffffffffffffffffffffff90811690915273ffffffffffffffffffffffffffffffffffffffff89166000908152600b602090815260409182902087518154928901519389015160608a015186166e010000000000000000000000000000027fffffffffffff000000000000000000000000ffffffffffffffffffffffffffff919096166201000002167fffffffffffff000000000000000000000000000000000000000000000000ffff60ff95909516610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff921515929092167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000909416939093171792909216179190911790555050505b60400151949350505050565b6000808a8a8a8a8a8a8a8a8a604051602001612e7f99989796959493929190614c7b565b604080518083037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001815291905280516020909101207dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e01000000000000000000000000000000000000000000000000000000000000179b9a5050505050505050505050565b6000612158825490565b6000612f1b83836133b8565b9392505050565b6000612f1b8373ffffffffffffffffffffffffffffffffffffffff84166133e2565b6000612f1b8373ffffffffffffffffffffffffffffffffffffffff84166134dc565b6000818160045b600f811015612ff3577fff000000000000000000000000000000000000000000000000000000000000008216838260208110612fab57612fab61434d565b1a60f81b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614612fe157506000949350505050565b80612feb816145d9565b915050612f6d565b5081600f1a600181111561300957613009614b07565b949350505050565b60008080808560600151600181111561302c5761302c614b07565b036130525761303e888888888861352b565b61304d57600092509050613126565b6130ca565b60018560600151600181111561306a5761306a614b07565b0361309857600061307d898989886136b6565b92509050806130925750600092509050613126565b506130ca565b6040517ff2b2d41200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b84516040015163ffffffff16871061311f57877fc3237c8807c467c1b39b8d0395eff077313e691bf0a7388106792564ebfd56368760405161310c9190613b21565b60405180910390a2600092509050613126565b6001925090505b9550959350505050565b60008160600151600181111561314857613148614b07565b036131ac57600083815260046020526040902060010180547fffffffff00000000ffffffffffffffffffffffffffffffffffffffffffffffff16780100000000000000000000000000000000000000000000000063ffffffff851602179055505050565b6001816060015160018111156131c4576131c4614b07565b036132305760c08101805160009081526008602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055915191517fa4a4e334c0e330143f9437484fe516c13bc560b86b5b0daf58e7084aaac228f29190a25b505050565b600080613248898886868a8a60016138c4565b60008a8152600460205260408120600101549294509092506c010000000000000000000000009091046bffffffffffffffffffffffff169061328a8385614315565b9050836bffffffffffffffffffffffff16826bffffffffffffffffffffffff1610156132be575091506000905081806132f1565b806bffffffffffffffffffffffff16826bffffffffffffffffffffffff1610156132f15750806132ee8482614bfb565b92505b60008a81526004602052604090206001018054829190600c906133339084906c0100000000000000000000000090046bffffffffffffffffffffffff16614bfb565b82546101009290920a6bffffffffffffffffffffffff81810219909316918316021790915560008c81526004602052604081206001018054859450909261337c91859116614315565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550505097509795505050505050565b60008260000182815481106133cf576133cf61434d565b9060005260206000200154905092915050565b600081815260018301602052604081205480156134cb576000613406600183614b68565b855490915060009061341a90600190614b68565b905081811461347f57600086600001828154811061343a5761343a61434d565b906000526020600020015490508087600001848154811061345d5761345d61434d565b6000918252602080832090910192909255918252600188019052604090208390555b855486908061349057613490614d10565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050612158565b6000915050612158565b5092915050565b600081815260018301602052604081205461352357508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155612158565b506000612158565b600080848060200190518101906135429190614d3f565b845160c00151815191925063ffffffff9081169116101561359f57867f405288ea7be309e16cfdf481367f90a413e1d4634fcdaf8966546db9b93012e88660405161358d9190613b21565b60405180910390a260009150506136ad565b826101200151801561366057506020810151158015906136605750602081015161014084015182516040517f85df51fd00000000000000000000000000000000000000000000000000000000815263ffffffff909116600482015273ffffffffffffffffffffffffffffffffffffffff909116906385df51fd90602401602060405180830381865afa158015613639573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061365d9190614611565b14155b806136725750805163ffffffff168611155b156136a757867f6aa7f60c176da7af894b384daea2249497448137f5943c1237ada8bc92bdc3018660405161358d9190613b21565b60019150505b95945050505050565b6000806000848060200190518101906136cf9190614d97565b905060008782600001518360200151846040015160405160200161373194939291909384526020840192909252604083015260e01b7fffffffff0000000000000000000000000000000000000000000000000000000016606082015260640190565b604051602081830303815290604052805190602001209050846101200151801561380d575060808201511580159061380d5750608082015161014086015160608401516040517f85df51fd00000000000000000000000000000000000000000000000000000000815263ffffffff909116600482015273ffffffffffffffffffffffffffffffffffffffff909116906385df51fd90602401602060405180830381865afa1580156137e6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061380a9190614611565b14155b80613822575086826060015163ffffffff1610155b1561386c57877f6aa7f60c176da7af894b384daea2249497448137f5943c1237ada8bc92bdc301876040516138579190613b21565b60405180910390a26000935091506138bb9050565b60008181526008602052604090205460ff16156138b357877f405288ea7be309e16cfdf481367f90a413e1d4634fcdaf8966546db9b93012e8876040516138579190613b21565b600193509150505b94509492505050565b60008060008960a0015161ffff16866138dd9190614b51565b90508380156138eb5750803a105b156138f357503a5b600085886139018b8d61433a565b61390b9085614b51565b613915919061433a565b61392790670de0b6b3a7640000614b51565b6139319190614baa565b905060008b6040015163ffffffff1664e8d4a510006139509190614b51565b60208d0151889063ffffffff168b6139688f88614b51565b613972919061433a565b61398090633b9aca00614b51565b61398a9190614b51565b6139949190614baa565b61399e919061433a565b90506b033b2e3c9fd0803ce80000006139b7828461433a565b11156139ef576040517f2ad7547a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b909b909a5098505050505050505050565b5080546000825590600052602060002090810190610bb19190613aa8565b828054828255906000526020600020908101928215613a98579160200282015b82811115613a9857825182547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff909116178255602090920191600190910190613a3e565b50613aa4929150613aa8565b5090565b5b80821115613aa45760008155600101613aa9565b6000815180845260005b81811015613ae357602081850181015186830182015201613ac7565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b602081526000612f1b6020830184613abd565b73ffffffffffffffffffffffffffffffffffffffff81168114610bb157600080fd5b8035613b6181613b34565b919050565b60008083601f840112613b7857600080fd5b50813567ffffffffffffffff811115613b9057600080fd5b602083019150836020828501011115613ba857600080fd5b9250929050565b60008060008060608587031215613bc557600080fd5b8435613bd081613b34565b935060208501359250604085013567ffffffffffffffff811115613bf357600080fd5b613bff87828801613b66565b95989497509550505050565b600080600060408486031215613c2057600080fd5b83359250602084013567ffffffffffffffff811115613c3e57600080fd5b613c4a86828701613b66565b9497909650939450505050565b60008083601f840112613c6957600080fd5b50813567ffffffffffffffff811115613c8157600080fd5b6020830191508360208260051b8501011115613ba857600080fd5b60008060008060008060008060e0898b031215613cb857600080fd5b606089018a811115613cc957600080fd5b8998503567ffffffffffffffff80821115613ce357600080fd5b613cef8c838d01613b66565b909950975060808b0135915080821115613d0857600080fd5b613d148c838d01613c57565b909750955060a08b0135915080821115613d2d57600080fd5b50613d3a8b828c01613c57565b999c989b50969995989497949560c00135949350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051610220810167ffffffffffffffff81118282101715613da657613da6613d53565b60405290565b60405160c0810167ffffffffffffffff81118282101715613da657613da6613d53565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715613e1657613e16613d53565b604052919050565b600067ffffffffffffffff821115613e3857613e38613d53565b5060051b60200190565b600082601f830112613e5357600080fd5b81356020613e68613e6383613e1e565b613dcf565b82815260059290921b84018101918181019086841115613e8757600080fd5b8286015b84811015613eab578035613e9e81613b34565b8352918301918301613e8b565b509695505050505050565b803560ff81168114613b6157600080fd5b600082601f830112613ed857600080fd5b813567ffffffffffffffff811115613ef257613ef2613d53565b613f2360207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601613dcf565b818152846020838601011115613f3857600080fd5b816020850160208301376000918101602001919091529392505050565b803567ffffffffffffffff81168114613b6157600080fd5b60008060008060008060c08789031215613f8657600080fd5b863567ffffffffffffffff80821115613f9e57600080fd5b613faa8a838b01613e42565b97506020890135915080821115613fc057600080fd5b613fcc8a838b01613e42565b9650613fda60408a01613eb6565b95506060890135915080821115613ff057600080fd5b613ffc8a838b01613ec7565b945061400a60808a01613f55565b935060a089013591508082111561402057600080fd5b5061402d89828a01613ec7565b9150509295509295509295565b60006020828403121561404c57600080fd5b8135612f1b81613b34565b63ffffffff81168114610bb157600080fd5b8035613b6181614057565b62ffffff81168114610bb157600080fd5b8035613b6181614074565b61ffff81168114610bb157600080fd5b8035613b6181614090565b6bffffffffffffffffffffffff81168114610bb157600080fd5b8035613b61816140ab565b8015158114610bb157600080fd5b8035613b61816140d0565b600061022082840312156140fc57600080fd5b614104613d82565b905061410f82614069565b815261411d60208301614069565b602082015261412e60408301614069565b604082015261413f60608301614085565b6060820152614150608083016140a0565b608082015261416160a083016140c5565b60a082015261417260c08301614069565b60c082015261418360e08301614069565b60e0820152610100614196818401614069565b908201526101206141a8838201614069565b90820152610140828101359082015261016080830135908201526101806141d0818401613b56565b908201526101a08281013567ffffffffffffffff8111156141f057600080fd5b6141fc85828601613e42565b8284015250506101c0614210818401613b56565b908201526101e0614222838201613b56565b908201526102006142348382016140de565b9082015292915050565b60008060008060008060c0878903121561425757600080fd5b863567ffffffffffffffff8082111561426f57600080fd5b61427b8a838b01613e42565b9750602089013591508082111561429157600080fd5b61429d8a838b01613e42565b96506142ab60408a01613eb6565b955060608901359150808211156142c157600080fd5b613ffc8a838b016140e9565b6000602082840312156142df57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6bffffffffffffffffffffffff8181168382160190808211156134d5576134d56142e6565b80820180821115612158576121586142e6565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60ff8181168382160190811115612158576121586142e6565b8051613b6181614057565b8051613b6181614074565b8051613b6181614090565b8051613b61816140ab565b8051613b6181613b34565b600082601f8301126143dd57600080fd5b815160206143ed613e6383613e1e565b82815260059290921b8401810191818101908684111561440c57600080fd5b8286015b84811015613eab57805161442381613b34565b8352918301918301614410565b8051613b61816140d0565b60006020828403121561444d57600080fd5b815167ffffffffffffffff8082111561446557600080fd5b90830190610220828603121561447a57600080fd5b614482613d82565b61448b83614395565b815261449960208401614395565b60208201526144aa60408401614395565b60408201526144bb606084016143a0565b60608201526144cc608084016143ab565b60808201526144dd60a084016143b6565b60a08201526144ee60c08401614395565b60c08201526144ff60e08401614395565b60e0820152610100614512818501614395565b90820152610120614524848201614395565b908201526101408381015190820152610160808401519082015261018061454c8185016143c1565b908201526101a0838101518381111561456457600080fd5b614570888287016143cc565b8284015250506101c091506145868284016143c1565b828201526101e0915061459a8284016143c1565b8282015261020091506145ae828401614430565b91810191909152949350505050565b60ff81811683821602908116908181146134d5576134d56142e6565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361460a5761460a6142e6565b5060010190565b60006020828403121561462357600080fd5b5051919050565b63ffffffff8181168382160190808211156134d5576134d56142e6565b600081518084526020808501945080840160005b8381101561468d57815173ffffffffffffffffffffffffffffffffffffffff168752958201959082019060010161465b565b509495945050505050565b602081526146af60208201835163ffffffff169052565b600060208301516146c8604084018263ffffffff169052565b50604083015163ffffffff8116606084015250606083015162ffffff8116608084015250608083015161ffff811660a08401525060a08301516bffffffffffffffffffffffff811660c08401525060c083015163ffffffff811660e08401525060e08301516101006147418185018363ffffffff169052565b840151905061012061475a8482018363ffffffff169052565b84015190506101406147738482018363ffffffff169052565b840151610160848101919091528401516101808085019190915284015190506101a06147b68185018373ffffffffffffffffffffffffffffffffffffffff169052565b808501519150506102206101c081818601526147d6610240860184614647565b908601519092506101e06148018682018373ffffffffffffffffffffffffffffffffffffffff169052565b860151905061020061482a8682018373ffffffffffffffffffffffffffffffffffffffff169052565b90950151151593019290925250919050565b600061012063ffffffff808d1684528b6020850152808b1660408501525080606084015261486c8184018a614647565b905082810360808401526148808189614647565b905060ff871660a084015282810360c084015261489d8187613abd565b905067ffffffffffffffff851660e08401528281036101008401526148c28185613abd565b9c9b505050505050505050505050565b8281526040602082015260006130096040830184613abd565b600080604083850312156148fe57600080fd5b8251614909816140d0565b6020939093015192949293505050565b8183823760009101908152919050565b8281526080810160608360208401379392505050565b600082601f83011261495057600080fd5b81356020614960613e6383613e1e565b82815260059290921b8401810191818101908684111561497f57600080fd5b8286015b84811015613eab5780358352918301918301614983565b600082601f8301126149ab57600080fd5b813560206149bb613e6383613e1e565b82815260059290921b840181019181810190868411156149da57600080fd5b8286015b84811015613eab57803567ffffffffffffffff8111156149fe5760008081fd5b614a0c8986838b0101613ec7565b8452509183019183016149de565b600060208284031215614a2c57600080fd5b813567ffffffffffffffff80821115614a4457600080fd5b9083019060c08286031215614a5857600080fd5b614a60613dac565b8235815260208301356020820152604083013582811115614a8057600080fd5b614a8c8782860161493f565b604083015250606083013582811115614aa457600080fd5b614ab08782860161493f565b606083015250608083013582811115614ac857600080fd5b614ad48782860161499a565b60808301525060a083013582811115614aec57600080fd5b614af88782860161499a565b60a08301525095945050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b61ffff8181168382160190808211156134d5576134d56142e6565b8082028115828204841417612158576121586142e6565b81810381811115612158576121586142e6565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600082614bb957614bb9614b7b565b500490565b6bffffffffffffffffffffffff85168152836020820152826040820152608060608201526000614bf16080830184613abd565b9695505050505050565b6bffffffffffffffffffffffff8281168282160390808211156134d5576134d56142e6565b60006bffffffffffffffffffffffff80841680614c3f57614c3f614b7b565b92169190910492915050565b6bffffffffffffffffffffffff818116838216028082169190828114614c7357614c736142e6565b505092915050565b60006101208b835273ffffffffffffffffffffffffffffffffffffffff8b16602084015267ffffffffffffffff808b166040850152816060850152614cc28285018b614647565b91508382036080850152614cd6828a614647565b915060ff881660a085015283820360c0850152614cf38288613abd565b90861660e085015283810361010085015290506148c28185613abd565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b600060408284031215614d5157600080fd5b6040516040810181811067ffffffffffffffff82111715614d7457614d74613d53565b6040528251614d8281614057565b81526020928301519281019290925250919050565b600060a08284031215614da957600080fd5b60405160a0810181811067ffffffffffffffff82111715614dcc57614dcc613d53565b806040525082518152602083015160208201526040830151614ded81614057565b60408201526060830151614e0081614057565b6060820152608092830151928101929092525091905056fea164736f6c6343000813000a", + Bin: "0x6101406040523480156200001257600080fd5b506040516200526d3803806200526d8339810160408190526200003591620003b1565b80816001600160a01b031663ca30e6036040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000075573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200009b9190620003b1565b826001600160a01b031663b10b673c6040518163ffffffff1660e01b8152600401602060405180830381865afa158015620000da573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620001009190620003b1565b836001600160a01b0316636709d0e56040518163ffffffff1660e01b8152600401602060405180830381865afa1580156200013f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620001659190620003b1565b846001600160a01b0316635425d8ac6040518163ffffffff1660e01b8152600401602060405180830381865afa158015620001a4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620001ca9190620003b1565b856001600160a01b031663a08714c06040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000209573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200022f9190620003b1565b3380600081620002865760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620002b957620002b981620002ed565b5050506001600160a01b0394851660805292841660a05290831660c052821660e052811661010052166101205250620003d8565b336001600160a01b03821603620003475760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c6600000000000000000060448201526064016200027d565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6001600160a01b0381168114620003ae57600080fd5b50565b600060208284031215620003c457600080fd5b8151620003d18162000398565b9392505050565b60805160a05160c05160e0516101005161012051614e48620004256000396000818160c9015261017c01526000611bb8015260005050600050506000505060006104c20152614e486000f3fe6080604052600436106100c75760003560e01c8063aed2e92911610074578063e3d0e7121161004e578063e3d0e71214610348578063f2fde38b14610368578063f75f6b1114610388576100c7565b8063aed2e929146102a3578063afcb95d7146102da578063b1dc65a414610328576100c7565b806381ff7048116100a557806381ff7048146101d65780638da5cb5b14610258578063a4c0ed3614610283576100c7565b8063181f5a771461010e578063349e8cca1461016d57806379ba5097146101c1575b7f00000000000000000000000000000000000000000000000000000000000000003660008037600080366000845af43d6000803e808015610107573d6000f35b3d6000fd5b005b34801561011a57600080fd5b506101576040518060400160405280601881526020017f4175746f6d6174696f6e526567697374727920322e322e30000000000000000081525081565b6040516101649190613b44565b60405180910390f35b34801561017957600080fd5b507f00000000000000000000000000000000000000000000000000000000000000005b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610164565b3480156101cd57600080fd5b5061010c6103a8565b3480156101e257600080fd5b5061023560155460115463ffffffff780100000000000000000000000000000000000000000000000083048116937c01000000000000000000000000000000000000000000000000000000009093041691565b6040805163ffffffff948516815293909216602084015290820152606001610164565b34801561026457600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff1661019c565b34801561028f57600080fd5b5061010c61029e366004613bd2565b6104aa565b3480156102af57600080fd5b506102c36102be366004613c2e565b6106c6565b604080519215158352602083019190915201610164565b3480156102e657600080fd5b50601154601254604080516000815260208101939093527401000000000000000000000000000000000000000090910463ffffffff1690820152606001610164565b34801561033457600080fd5b5061010c610343366004613cbf565b61083c565b34801561035457600080fd5b5061010c610363366004613f90565b610b77565b34801561037457600080fd5b5061010c61038336600461405d565b610ba0565b34801561039457600080fd5b5061010c6103a3366004614261565b610bb4565b60015473ffffffffffffffffffffffffffffffffffffffff16331461042e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610519576040517fc8bad78d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60208114610553576040517fdfe9309000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000610561828401846142f0565b60008181526004602052604090205490915065010000000000900463ffffffff908116146105bb576040517f9c0083a200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000818152600460205260409020600101546105f69085906c0100000000000000000000000090046bffffffffffffffffffffffff16614338565b600082815260046020526040902060010180546bffffffffffffffffffffffff929092166c01000000000000000000000000027fffffffffffffffff000000000000000000000000ffffffffffffffffffffffff90921691909117905560195461066190859061435d565b6019556040516bffffffffffffffffffffffff8516815273ffffffffffffffffffffffffffffffffffffffff86169082907fafd24114486da8ebfc32f3626dada8863652e187461aa74d4bfa7348915062039060200160405180910390a35050505050565b6000806106d1611ba0565b6012547e01000000000000000000000000000000000000000000000000000000000000900460ff1615610730576040517f24522f3400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600085815260046020908152604091829020825160e081018452815460ff811615158252610100810463ffffffff908116838601819052650100000000008304821684880152690100000000000000000090920473ffffffffffffffffffffffffffffffffffffffff16606084018190526001909401546bffffffffffffffffffffffff80821660808601526c0100000000000000000000000082041660a0850152780100000000000000000000000000000000000000000000000090041660c08301528451601f8901859004850281018501909552878552909361082f938990899081908401838280828437600092019190915250611c1192505050565b9097909650945050505050565b60005a60408051610160810182526012546bffffffffffffffffffffffff8116825263ffffffff6c010000000000000000000000008204811660208401527001000000000000000000000000000000008204811693830193909352740100000000000000000000000000000000000000008104909216606082015262ffffff7801000000000000000000000000000000000000000000000000830416608082015261ffff7b0100000000000000000000000000000000000000000000000000000083041660a082015260ff7d0100000000000000000000000000000000000000000000000000000000008304811660c08301527e010000000000000000000000000000000000000000000000000000000000008304811615801560e08401527f01000000000000000000000000000000000000000000000000000000000000009093048116151561010080840191909152601354918216151561012084015273ffffffffffffffffffffffffffffffffffffffff9104166101408201529192506109f2576040517f24522f3400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b336000908152600b602052604090205460ff16610a3b576040517f1099ed7500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6011548a3514610a77576040517fdfdcf8e700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60c0810151610a8790600161439f565b60ff1686141580610a985750858414155b15610acf576040517f0244f71a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610adf8a8a8a8a8a8a8a8a611e3a565b6000610aeb8a8a6120a3565b905060208b0135600881901c63ffffffff16610b0884848761215e565b836060015163ffffffff168163ffffffff161115610b6857601280547fffffffffffffffff00000000ffffffffffffffffffffffffffffffffffffffff167401000000000000000000000000000000000000000063ffffffff8416021790555b50505050505050505050505050565b610b9886868686806020019051810190610b91919061445e565b8686610bb4565b505050505050565b610ba8612b00565b610bb181612b81565b50565b610bbc612b00565b601f86511115610bf8576040517f25d0209c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8360ff16600003610c35576040517fe77dba5600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b84518651141580610c545750610c4c8460036145e0565b60ff16865111155b15610c8b576040517f1d2d1c5800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601254600e546bffffffffffffffffffffffff9091169060005b816bffffffffffffffffffffffff16811015610d0d57610cfa600e8281548110610cd157610cd1614370565b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff168484612c76565b5080610d05816145fc565b915050610ca5565b5060008060005b836bffffffffffffffffffffffff16811015610e1657600d8181548110610d3d57610d3d614370565b600091825260209091200154600e805473ffffffffffffffffffffffffffffffffffffffff90921694509082908110610d7857610d78614370565b600091825260208083209091015473ffffffffffffffffffffffffffffffffffffffff8681168452600c8352604080852080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00001690559116808452600b90925290912080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055915080610e0e816145fc565b915050610d14565b50610e23600d6000613a23565b610e2f600e6000613a23565b604080516080810182526000808252602082018190529181018290526060810182905290805b8c5181101561129857600c60008e8381518110610e7457610e74614370565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff1682528101919091526040016000205460ff1615610edf576040517f77cea0fa00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168d8281518110610f0957610f09614370565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1603610f5e576040517f815e1d6400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405180604001604052806001151581526020018260ff16815250600c60008f8481518110610f8f57610f8f614370565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff1682528181019290925260400160002082518154939092015160ff16610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff921515929092167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000909316929092171790558b518c908290811061103757611037614370565b60200260200101519150600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036110a7576040517f58a70a0a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff82166000908152600b60209081526040918290208251608081018452905460ff80821615801584526101008304909116938301939093526bffffffffffffffffffffffff6201000082048116948301949094526e01000000000000000000000000000090049092166060830152909350611162576040517f6a7281ad00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001835260ff80821660208086019182526bffffffffffffffffffffffff808b166060880190815273ffffffffffffffffffffffffffffffffffffffff87166000908152600b909352604092839020885181549551948a0151925184166e010000000000000000000000000000027fffffffffffff000000000000000000000000ffffffffffffffffffffffffffff939094166201000002929092167fffffffffffff000000000000000000000000000000000000000000000000ffff94909616610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff921515929092167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00009095169490941717919091169290921791909117905580611290816145fc565b915050610e55565b50508a516112ae9150600d9060208d0190613a41565b5088516112c290600e9060208c0190613a41565b50604051806101600160405280856bffffffffffffffffffffffff168152602001886000015163ffffffff168152602001886020015163ffffffff168152602001600063ffffffff168152602001886060015162ffffff168152602001886080015161ffff1681526020018960ff1681526020016012600001601e9054906101000a900460ff16151581526020016012600001601f9054906101000a900460ff161515815260200188610200015115158152602001886101e0015173ffffffffffffffffffffffffffffffffffffffff16815250601260008201518160000160006101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550602082015181600001600c6101000a81548163ffffffff021916908363ffffffff16021790555060408201518160000160106101000a81548163ffffffff021916908363ffffffff16021790555060608201518160000160146101000a81548163ffffffff021916908363ffffffff16021790555060808201518160000160186101000a81548162ffffff021916908362ffffff16021790555060a082015181600001601b6101000a81548161ffff021916908361ffff16021790555060c082015181600001601d6101000a81548160ff021916908360ff16021790555060e082015181600001601e6101000a81548160ff02191690831515021790555061010082015181600001601f6101000a81548160ff0219169083151502179055506101208201518160010160006101000a81548160ff0219169083151502179055506101408201518160010160016101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055509050506040518061018001604052808860a001516bffffffffffffffffffffffff16815260200188610180015173ffffffffffffffffffffffffffffffffffffffff168152602001601460010160009054906101000a90046bffffffffffffffffffffffff166bffffffffffffffffffffffff168152602001886040015163ffffffff1681526020018860c0015163ffffffff168152602001601460010160149054906101000a900463ffffffff1663ffffffff168152602001601460010160189054906101000a900463ffffffff1663ffffffff1681526020016014600101601c9054906101000a900463ffffffff1663ffffffff1681526020018860e0015163ffffffff16815260200188610100015163ffffffff16815260200188610120015163ffffffff168152602001886101c0015173ffffffffffffffffffffffffffffffffffffffff16815250601460008201518160000160006101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550602082015181600001600c6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060408201518160010160006101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550606082015181600101600c6101000a81548163ffffffff021916908363ffffffff16021790555060808201518160010160106101000a81548163ffffffff021916908363ffffffff16021790555060a08201518160010160146101000a81548163ffffffff021916908363ffffffff16021790555060c08201518160010160186101000a81548163ffffffff021916908363ffffffff16021790555060e082015181600101601c6101000a81548163ffffffff021916908363ffffffff1602179055506101008201518160020160006101000a81548163ffffffff021916908363ffffffff1602179055506101208201518160020160046101000a81548163ffffffff021916908363ffffffff1602179055506101408201518160020160086101000a81548163ffffffff021916908363ffffffff16021790555061016082015181600201600c6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555090505086610140015160178190555086610160015160188190555060006014600101601c9054906101000a900463ffffffff169050876101e0015173ffffffffffffffffffffffffffffffffffffffff166357e871e76040518163ffffffff1660e01b8152600401602060405180830381865afa158015611963573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119879190614634565b601580547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167c010000000000000000000000000000000000000000000000000000000063ffffffff9384160217808255600192601891611a02918591780100000000000000000000000000000000000000000000000090041661464d565b92506101000a81548163ffffffff021916908363ffffffff160217905550600088604051602001611a3391906146bb565b604080518083037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0018152919052601554909150611a9c90469030907801000000000000000000000000000000000000000000000000900463ffffffff168f8f8f878f8f612e7e565b60115560005b611aac6009612f28565b811015611adc57611ac9611ac1600983612f32565b600990612f45565b5080611ad4816145fc565b915050611aa2565b5060005b896101a0015151811015611b3357611b208a6101a001518281518110611b0857611b08614370565b60200260200101516009612f6790919063ffffffff16565b5080611b2b816145fc565b915050611ae0565b507f1591690b8638f5fb2dbec82ac741805ac5da8b45dc5263f4875b0496fdce4e0582601154601460010160189054906101000a900463ffffffff168f8f8f878f8f604051611b8a9998979695949392919061485f565b60405180910390a1505050505050505050505050565b3273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614611c0f576040517fb60ac5db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b60125460009081907f0100000000000000000000000000000000000000000000000000000000000000900460ff1615611c76576040517f37ed32e800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601280547effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f01000000000000000000000000000000000000000000000000000000000000001790556040517f4585e33b0000000000000000000000000000000000000000000000000000000090611cf2908590602401613b44565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931790925290517f79188d1600000000000000000000000000000000000000000000000000000000815290935073ffffffffffffffffffffffffffffffffffffffff8616906379188d1690611dc590879087906004016148f5565b60408051808303816000875af1158015611de3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e07919061490e565b601280547effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16905590969095509350505050565b60008787604051611e4c92919061493c565b604051908190038120611e63918b9060200161494c565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815282825280516020918201208383019092526000808452908301819052909250906000805b8881101561203a57600185878360208110611ecf57611ecf614370565b611edc91901a601b61439f565b8c8c85818110611eee57611eee614370565b905060200201358b8b86818110611f0757611f07614370565b9050602002013560405160008152602001604052604051611f44949392919093845260ff9290921660208401526040830152606082015260800190565b6020604051602081039080840390855afa158015611f66573d6000803e3d6000fd5b5050604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081015173ffffffffffffffffffffffffffffffffffffffff81166000908152600c602090815290849020838501909452925460ff8082161515808552610100909204169383019390935290955093509050612014576040517f0f4c073700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b826020015160080260ff166001901b840193508080612032906145fc565b915050611eb2565b50827e01010101010101010101010101010101010101010101010101010101010101841614612095576040517fc103be2e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505050505050505050505050565b6120dc6040518060c001604052806000815260200160008152602001606081526020016060815260200160608152602001606081525090565b60006120ea83850185614a3d565b604081015151606082015151919250908114158061210d57508082608001515114155b8061211d5750808260a001515114155b15612154576040517fb55ac75400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5090505b92915050565b600082604001515167ffffffffffffffff81111561217e5761217e613d76565b60405190808252806020026020018201604052801561223a57816020015b604080516101c081018252600060e08201818152610100830182905261012083018290526101408301829052610160830182905261018083018290526101a0830182905282526020808301829052928201819052606082018190526080820181905260a0820181905260c082015282527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90920191018161219c5790505b50905060006040518060800160405280600061ffff1681526020016000815260200160006bffffffffffffffffffffffff16815260200160006bffffffffffffffffffffffff168152509050600085610140015173ffffffffffffffffffffffffffffffffffffffff166357e871e76040518163ffffffff1660e01b8152600401602060405180830381865afa1580156122d8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906122fc9190614634565b6101408701516040517f7810d12a00000000000000000000000000000000000000000000000000000000815236600482015291925060009173ffffffffffffffffffffffffffffffffffffffff90911690637810d12a90602401602060405180830381865afa158015612373573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123979190614634565b905060005b8660400151518110156127e65760046000886040015183815181106123c3576123c3614370565b6020908102919091018101518252818101929092526040908101600020815160e081018352815460ff811615158252610100810463ffffffff90811695830195909552650100000000008104851693820193909352690100000000000000000090920473ffffffffffffffffffffffffffffffffffffffff166060830152600101546bffffffffffffffffffffffff80821660808401526c0100000000000000000000000082041660a08301527801000000000000000000000000000000000000000000000000900490911660c082015285518690839081106124a8576124a8614370565b6020026020010151600001819052506124dd876040015182815181106124d0576124d0614370565b6020026020010151612f89565b8582815181106124ef576124ef614370565b602002602001015160600190600181111561250c5761250c614b2a565b9081600181111561251f5761251f614b2a565b815250506125838760400151828151811061253c5761253c614370565b6020026020010151848960800151848151811061255b5761255b614370565b602002602001015188858151811061257557612575614370565b60200260200101518c613034565b86838151811061259557612595614370565b60200260200101516020018784815181106125b2576125b2614370565b602002602001015160c00182815250821515151581525050508481815181106125dd576125dd614370565b6020026020010151602001511561260d576001846000018181516126019190614b59565b61ffff16905250612612565b6127d4565b61267885828151811061262757612627614370565b602002602001015160000151606001518860600151838151811061264d5761264d614370565b60200260200101518960a00151848151811061266b5761266b614370565b6020026020010151611c11565b86838151811061268a5761268a614370565b60200260200101516040018784815181106126a7576126a7614370565b602090810291909101015160800191909152901515905260c08801516126ce90600161439f565b6126dc9060ff166040614b74565b6103a48860a0015183815181106126f5576126f5614370565b602002602001015151612708919061435d565b612712919061435d565b85828151811061272457612724614370565b602002602001015160a001818152505084818151811061274657612746614370565b602002602001015160a0015184602001818151612763919061435d565b905250845185908290811061277a5761277a614370565b602002602001015160800151866127919190614b8b565b95506127d4876040015182815181106127ac576127ac614370565b6020026020010151848784815181106127c7576127c7614370565b6020026020010151613153565b806127de816145fc565b91505061239c565b50825161ffff166000036127fd5750505050505050565b6155f061280b366010614b74565b5a6128169088614b8b565b612820919061435d565b61282a919061435d565b8351909550611b58906128419061ffff1687614bcd565b61284b919061435d565b945060008060005b886040015151811015612a2f5786818151811061287257612872614370565b60200260200101516020015115612a1d5761290b8a8a60400151838151811061289d5761289d614370565b60200260200101518984815181106128b7576128b7614370565b6020026020010151608001518c600001518d602001518d8c602001518e89815181106128e5576128e5614370565b602002602001015160a001518c6128fc9190614b74565b6129069190614bcd565b613258565b6060880180519295509093508391612924908390614338565b6bffffffffffffffffffffffff16905250604086018051849190612949908390614338565b6bffffffffffffffffffffffff16905250865187908290811061296e5761296e614370565b60200260200101516040015115158960400151828151811061299257612992614370565b60200260200101517fad8cc9579b21dfe2c2f6ea35ba15b656e46b4f5b0cb424f52739b8ce5cac9c5b84866129c79190614338565b8a85815181106129d9576129d9614370565b6020026020010151608001518c8e6080015187815181106129fc576129fc614370565b6020026020010151604051612a149493929190614be1565b60405180910390a35b80612a27816145fc565b915050612853565b505050604083810151336000908152600b602052919091208054600290612a6b9084906201000090046bffffffffffffffffffffffff16614338565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055508260600151601260000160008282829054906101000a90046bffffffffffffffffffffffff16612ac99190614338565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff16021790555050505050505050565b60005473ffffffffffffffffffffffffffffffffffffffff163314611c0f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610425565b3373ffffffffffffffffffffffffffffffffffffffff821603612c00576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610425565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b73ffffffffffffffffffffffffffffffffffffffff83166000908152600b602090815260408083208151608081018352905460ff80821615801584526101008304909116948301949094526bffffffffffffffffffffffff6201000082048116938301939093526e0100000000000000000000000000009004909116606082015290612e72576000816060015185612d0e9190614c1e565b90506000612d1c8583614c43565b90508083604001818151612d309190614338565b6bffffffffffffffffffffffff16905250612d4b8582614c6e565b83606001818151612d5c9190614338565b6bffffffffffffffffffffffff90811690915273ffffffffffffffffffffffffffffffffffffffff89166000908152600b602090815260409182902087518154928901519389015160608a015186166e010000000000000000000000000000027fffffffffffff000000000000000000000000ffffffffffffffffffffffffffff919096166201000002167fffffffffffff000000000000000000000000000000000000000000000000ffff60ff95909516610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff921515929092167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000909416939093171792909216179190911790555050505b60400151949350505050565b6000808a8a8a8a8a8a8a8a8a604051602001612ea299989796959493929190614c9e565b604080518083037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001815291905280516020909101207dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e01000000000000000000000000000000000000000000000000000000000000179b9a5050505050505050505050565b6000612158825490565b6000612f3e83836133db565b9392505050565b6000612f3e8373ffffffffffffffffffffffffffffffffffffffff8416613405565b6000612f3e8373ffffffffffffffffffffffffffffffffffffffff84166134ff565b6000818160045b600f811015613016577fff000000000000000000000000000000000000000000000000000000000000008216838260208110612fce57612fce614370565b1a60f81b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161461300457506000949350505050565b8061300e816145fc565b915050612f90565b5081600f1a600181111561302c5761302c614b2a565b949350505050565b60008080808560600151600181111561304f5761304f614b2a565b0361307557613061888888888861354e565b61307057600092509050613149565b6130ed565b60018560600151600181111561308d5761308d614b2a565b036130bb5760006130a0898989886136d9565b92509050806130b55750600092509050613149565b506130ed565b6040517ff2b2d41200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b84516040015163ffffffff16871061314257877fc3237c8807c467c1b39b8d0395eff077313e691bf0a7388106792564ebfd56368760405161312f9190613b44565b60405180910390a2600092509050613149565b6001925090505b9550959350505050565b60008160600151600181111561316b5761316b614b2a565b036131cf57600083815260046020526040902060010180547fffffffff00000000ffffffffffffffffffffffffffffffffffffffffffffffff16780100000000000000000000000000000000000000000000000063ffffffff851602179055505050565b6001816060015160018111156131e7576131e7614b2a565b036132535760c08101805160009081526008602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055915191517fa4a4e334c0e330143f9437484fe516c13bc560b86b5b0daf58e7084aaac228f29190a25b505050565b60008061326b898886868a8a60016138e7565b60008a8152600460205260408120600101549294509092506c010000000000000000000000009091046bffffffffffffffffffffffff16906132ad8385614338565b9050836bffffffffffffffffffffffff16826bffffffffffffffffffffffff1610156132e157509150600090508180613314565b806bffffffffffffffffffffffff16826bffffffffffffffffffffffff1610156133145750806133118482614c1e565b92505b60008a81526004602052604090206001018054829190600c906133569084906c0100000000000000000000000090046bffffffffffffffffffffffff16614c1e565b82546101009290920a6bffffffffffffffffffffffff81810219909316918316021790915560008c81526004602052604081206001018054859450909261339f91859116614338565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550505097509795505050505050565b60008260000182815481106133f2576133f2614370565b9060005260206000200154905092915050565b600081815260018301602052604081205480156134ee576000613429600183614b8b565b855490915060009061343d90600190614b8b565b90508181146134a257600086600001828154811061345d5761345d614370565b906000526020600020015490508087600001848154811061348057613480614370565b6000918252602080832090910192909255918252600188019052604090208390555b85548690806134b3576134b3614d33565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050612158565b6000915050612158565b5092915050565b600081815260018301602052604081205461354657508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155612158565b506000612158565b600080848060200190518101906135659190614d62565b845160c00151815191925063ffffffff908116911610156135c257867f405288ea7be309e16cfdf481367f90a413e1d4634fcdaf8966546db9b93012e8866040516135b09190613b44565b60405180910390a260009150506136d0565b826101200151801561368357506020810151158015906136835750602081015161014084015182516040517f85df51fd00000000000000000000000000000000000000000000000000000000815263ffffffff909116600482015273ffffffffffffffffffffffffffffffffffffffff909116906385df51fd90602401602060405180830381865afa15801561365c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906136809190614634565b14155b806136955750805163ffffffff168611155b156136ca57867f6aa7f60c176da7af894b384daea2249497448137f5943c1237ada8bc92bdc301866040516135b09190613b44565b60019150505b95945050505050565b6000806000848060200190518101906136f29190614dba565b905060008782600001518360200151846040015160405160200161375494939291909384526020840192909252604083015260e01b7fffffffff0000000000000000000000000000000000000000000000000000000016606082015260640190565b604051602081830303815290604052805190602001209050846101200151801561383057506080820151158015906138305750608082015161014086015160608401516040517f85df51fd00000000000000000000000000000000000000000000000000000000815263ffffffff909116600482015273ffffffffffffffffffffffffffffffffffffffff909116906385df51fd90602401602060405180830381865afa158015613809573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061382d9190614634565b14155b80613845575086826060015163ffffffff1610155b1561388f57877f6aa7f60c176da7af894b384daea2249497448137f5943c1237ada8bc92bdc3018760405161387a9190613b44565b60405180910390a26000935091506138de9050565b60008181526008602052604090205460ff16156138d657877f405288ea7be309e16cfdf481367f90a413e1d4634fcdaf8966546db9b93012e88760405161387a9190613b44565b600193509150505b94509492505050565b60008060008960a0015161ffff16866139009190614b74565b905083801561390e5750803a105b1561391657503a5b600085886139248b8d61435d565b61392e9085614b74565b613938919061435d565b61394a90670de0b6b3a7640000614b74565b6139549190614bcd565b905060008b6040015163ffffffff1664e8d4a510006139739190614b74565b60208d0151889063ffffffff168b61398b8f88614b74565b613995919061435d565b6139a390633b9aca00614b74565b6139ad9190614b74565b6139b79190614bcd565b6139c1919061435d565b90506b033b2e3c9fd0803ce80000006139da828461435d565b1115613a12576040517f2ad7547a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b909b909a5098505050505050505050565b5080546000825590600052602060002090810190610bb19190613acb565b828054828255906000526020600020908101928215613abb579160200282015b82811115613abb57825182547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff909116178255602090920191600190910190613a61565b50613ac7929150613acb565b5090565b5b80821115613ac75760008155600101613acc565b6000815180845260005b81811015613b0657602081850181015186830182015201613aea565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b602081526000612f3e6020830184613ae0565b73ffffffffffffffffffffffffffffffffffffffff81168114610bb157600080fd5b8035613b8481613b57565b919050565b60008083601f840112613b9b57600080fd5b50813567ffffffffffffffff811115613bb357600080fd5b602083019150836020828501011115613bcb57600080fd5b9250929050565b60008060008060608587031215613be857600080fd5b8435613bf381613b57565b935060208501359250604085013567ffffffffffffffff811115613c1657600080fd5b613c2287828801613b89565b95989497509550505050565b600080600060408486031215613c4357600080fd5b83359250602084013567ffffffffffffffff811115613c6157600080fd5b613c6d86828701613b89565b9497909650939450505050565b60008083601f840112613c8c57600080fd5b50813567ffffffffffffffff811115613ca457600080fd5b6020830191508360208260051b8501011115613bcb57600080fd5b60008060008060008060008060e0898b031215613cdb57600080fd5b606089018a811115613cec57600080fd5b8998503567ffffffffffffffff80821115613d0657600080fd5b613d128c838d01613b89565b909950975060808b0135915080821115613d2b57600080fd5b613d378c838d01613c7a565b909750955060a08b0135915080821115613d5057600080fd5b50613d5d8b828c01613c7a565b999c989b50969995989497949560c00135949350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051610220810167ffffffffffffffff81118282101715613dc957613dc9613d76565b60405290565b60405160c0810167ffffffffffffffff81118282101715613dc957613dc9613d76565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715613e3957613e39613d76565b604052919050565b600067ffffffffffffffff821115613e5b57613e5b613d76565b5060051b60200190565b600082601f830112613e7657600080fd5b81356020613e8b613e8683613e41565b613df2565b82815260059290921b84018101918181019086841115613eaa57600080fd5b8286015b84811015613ece578035613ec181613b57565b8352918301918301613eae565b509695505050505050565b803560ff81168114613b8457600080fd5b600082601f830112613efb57600080fd5b813567ffffffffffffffff811115613f1557613f15613d76565b613f4660207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601613df2565b818152846020838601011115613f5b57600080fd5b816020850160208301376000918101602001919091529392505050565b803567ffffffffffffffff81168114613b8457600080fd5b60008060008060008060c08789031215613fa957600080fd5b863567ffffffffffffffff80821115613fc157600080fd5b613fcd8a838b01613e65565b97506020890135915080821115613fe357600080fd5b613fef8a838b01613e65565b9650613ffd60408a01613ed9565b9550606089013591508082111561401357600080fd5b61401f8a838b01613eea565b945061402d60808a01613f78565b935060a089013591508082111561404357600080fd5b5061405089828a01613eea565b9150509295509295509295565b60006020828403121561406f57600080fd5b8135612f3e81613b57565b63ffffffff81168114610bb157600080fd5b8035613b848161407a565b62ffffff81168114610bb157600080fd5b8035613b8481614097565b61ffff81168114610bb157600080fd5b8035613b84816140b3565b6bffffffffffffffffffffffff81168114610bb157600080fd5b8035613b84816140ce565b8015158114610bb157600080fd5b8035613b84816140f3565b6000610220828403121561411f57600080fd5b614127613da5565b90506141328261408c565b81526141406020830161408c565b60208201526141516040830161408c565b6040820152614162606083016140a8565b6060820152614173608083016140c3565b608082015261418460a083016140e8565b60a082015261419560c0830161408c565b60c08201526141a660e0830161408c565b60e08201526101006141b981840161408c565b908201526101206141cb83820161408c565b90820152610140828101359082015261016080830135908201526101806141f3818401613b79565b908201526101a08281013567ffffffffffffffff81111561421357600080fd5b61421f85828601613e65565b8284015250506101c0614233818401613b79565b908201526101e0614245838201613b79565b90820152610200614257838201614101565b9082015292915050565b60008060008060008060c0878903121561427a57600080fd5b863567ffffffffffffffff8082111561429257600080fd5b61429e8a838b01613e65565b975060208901359150808211156142b457600080fd5b6142c08a838b01613e65565b96506142ce60408a01613ed9565b955060608901359150808211156142e457600080fd5b61401f8a838b0161410c565b60006020828403121561430257600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6bffffffffffffffffffffffff8181168382160190808211156134f8576134f8614309565b8082018082111561215857612158614309565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60ff818116838216019081111561215857612158614309565b8051613b848161407a565b8051613b8481614097565b8051613b84816140b3565b8051613b84816140ce565b8051613b8481613b57565b600082601f83011261440057600080fd5b81516020614410613e8683613e41565b82815260059290921b8401810191818101908684111561442f57600080fd5b8286015b84811015613ece57805161444681613b57565b8352918301918301614433565b8051613b84816140f3565b60006020828403121561447057600080fd5b815167ffffffffffffffff8082111561448857600080fd5b90830190610220828603121561449d57600080fd5b6144a5613da5565b6144ae836143b8565b81526144bc602084016143b8565b60208201526144cd604084016143b8565b60408201526144de606084016143c3565b60608201526144ef608084016143ce565b608082015261450060a084016143d9565b60a082015261451160c084016143b8565b60c082015261452260e084016143b8565b60e08201526101006145358185016143b8565b908201526101206145478482016143b8565b908201526101408381015190820152610160808401519082015261018061456f8185016143e4565b908201526101a0838101518381111561458757600080fd5b614593888287016143ef565b8284015250506101c091506145a98284016143e4565b828201526101e091506145bd8284016143e4565b8282015261020091506145d1828401614453565b91810191909152949350505050565b60ff81811683821602908116908181146134f8576134f8614309565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361462d5761462d614309565b5060010190565b60006020828403121561464657600080fd5b5051919050565b63ffffffff8181168382160190808211156134f8576134f8614309565b600081518084526020808501945080840160005b838110156146b057815173ffffffffffffffffffffffffffffffffffffffff168752958201959082019060010161467e565b509495945050505050565b602081526146d260208201835163ffffffff169052565b600060208301516146eb604084018263ffffffff169052565b50604083015163ffffffff8116606084015250606083015162ffffff8116608084015250608083015161ffff811660a08401525060a08301516bffffffffffffffffffffffff811660c08401525060c083015163ffffffff811660e08401525060e08301516101006147648185018363ffffffff169052565b840151905061012061477d8482018363ffffffff169052565b84015190506101406147968482018363ffffffff169052565b840151610160848101919091528401516101808085019190915284015190506101a06147d98185018373ffffffffffffffffffffffffffffffffffffffff169052565b808501519150506102206101c081818601526147f961024086018461466a565b908601519092506101e06148248682018373ffffffffffffffffffffffffffffffffffffffff169052565b860151905061020061484d8682018373ffffffffffffffffffffffffffffffffffffffff169052565b90950151151593019290925250919050565b600061012063ffffffff808d1684528b6020850152808b1660408501525080606084015261488f8184018a61466a565b905082810360808401526148a3818961466a565b905060ff871660a084015282810360c08401526148c08187613ae0565b905067ffffffffffffffff851660e08401528281036101008401526148e58185613ae0565b9c9b505050505050505050505050565b82815260406020820152600061302c6040830184613ae0565b6000806040838503121561492157600080fd5b825161492c816140f3565b6020939093015192949293505050565b8183823760009101908152919050565b8281526080810160608360208401379392505050565b600082601f83011261497357600080fd5b81356020614983613e8683613e41565b82815260059290921b840181019181810190868411156149a257600080fd5b8286015b84811015613ece57803583529183019183016149a6565b600082601f8301126149ce57600080fd5b813560206149de613e8683613e41565b82815260059290921b840181019181810190868411156149fd57600080fd5b8286015b84811015613ece57803567ffffffffffffffff811115614a215760008081fd5b614a2f8986838b0101613eea565b845250918301918301614a01565b600060208284031215614a4f57600080fd5b813567ffffffffffffffff80821115614a6757600080fd5b9083019060c08286031215614a7b57600080fd5b614a83613dcf565b8235815260208301356020820152604083013582811115614aa357600080fd5b614aaf87828601614962565b604083015250606083013582811115614ac757600080fd5b614ad387828601614962565b606083015250608083013582811115614aeb57600080fd5b614af7878286016149bd565b60808301525060a083013582811115614b0f57600080fd5b614b1b878286016149bd565b60a08301525095945050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b61ffff8181168382160190808211156134f8576134f8614309565b808202811582820484141761215857612158614309565b8181038181111561215857612158614309565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600082614bdc57614bdc614b9e565b500490565b6bffffffffffffffffffffffff85168152836020820152826040820152608060608201526000614c146080830184613ae0565b9695505050505050565b6bffffffffffffffffffffffff8281168282160390808211156134f8576134f8614309565b60006bffffffffffffffffffffffff80841680614c6257614c62614b9e565b92169190910492915050565b6bffffffffffffffffffffffff818116838216028082169190828114614c9657614c96614309565b505092915050565b60006101208b835273ffffffffffffffffffffffffffffffffffffffff8b16602084015267ffffffffffffffff808b166040850152816060850152614ce58285018b61466a565b91508382036080850152614cf9828a61466a565b915060ff881660a085015283820360c0850152614d168288613ae0565b90861660e085015283810361010085015290506148e58185613ae0565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b600060408284031215614d7457600080fd5b6040516040810181811067ffffffffffffffff82111715614d9757614d97613d76565b6040528251614da58161407a565b81526020928301519281019290925250919050565b600060a08284031215614dcc57600080fd5b60405160a0810181811067ffffffffffffffff82111715614def57614def613d76565b806040525082518152602083015160208201526040830151614e108161407a565b60408201526060830151614e238161407a565b6060820152608092830151928101929092525091905056fea164736f6c6343000813000a", } var AutomationRegistryABI = AutomationRegistryMetaData.ABI diff --git a/core/gethwrappers/generated/automation_registry_wrapper_2_3/automation_registry_wrapper_2_3.go b/core/gethwrappers/generated/automation_registry_wrapper_2_3/automation_registry_wrapper_2_3.go index 1d460036fa..97f0cfb113 100644 --- a/core/gethwrappers/generated/automation_registry_wrapper_2_3/automation_registry_wrapper_2_3.go +++ b/core/gethwrappers/generated/automation_registry_wrapper_2_3/automation_registry_wrapper_2_3.go @@ -76,7 +76,7 @@ type AutomationRegistryBase23PaymentReceipt struct { var AutomationRegistryMetaData = &bind.MetaData{ ABI: "[{\"inputs\":[{\"internalType\":\"contractAutomationRegistryLogicA2_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\":\"structAutomationRegistryBase2_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\":\"contractIERC20Metadata\",\"name\":\"token\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"uint32\",\"name\":\"gasFeePPB\",\"type\":\"uint32\"},{\"internalType\":\"uint24\",\"name\":\"flatFeeMilliCents\",\"type\":\"uint24\"},{\"internalType\":\"contractAggregatorV3Interface\",\"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\":\"structAutomationRegistryBase2_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\":\"contractIERC20Metadata\",\"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\":\"structAutomationRegistryBase2_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\":\"contractIChainModule\",\"name\":\"chainModule\",\"type\":\"address\"}],\"internalType\":\"structAutomationRegistryBase2_3.OnchainConfig\",\"name\":\"onchainConfig\",\"type\":\"tuple\"},{\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"},{\"internalType\":\"contractIERC20Metadata[]\",\"name\":\"billingTokens\",\"type\":\"address[]\"},{\"components\":[{\"internalType\":\"uint32\",\"name\":\"gasFeePPB\",\"type\":\"uint32\"},{\"internalType\":\"uint24\",\"name\":\"flatFeeMilliCents\",\"type\":\"uint24\"},{\"internalType\":\"contractAggregatorV3Interface\",\"name\":\"priceFeed\",\"type\":\"address\"},{\"internalType\":\"uint8\",\"name\":\"decimals\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"fallbackPrice\",\"type\":\"uint256\"},{\"internalType\":\"uint96\",\"name\":\"minSpend\",\"type\":\"uint96\"}],\"internalType\":\"structAutomationRegistryBase2_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\"}]", - Bin: "0x6101806040523480156200001257600080fd5b506040516200651f3803806200651f83398101604081905262000035916200062f565b80816001600160a01b031663ca30e6036040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000075573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200009b91906200062f565b826001600160a01b031663226cf83c6040518163ffffffff1660e01b8152600401602060405180830381865afa158015620000da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200010091906200062f565b836001600160a01b031663614486af6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156200013f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200016591906200062f565b846001600160a01b0316636709d0e56040518163ffffffff1660e01b8152600401602060405180830381865afa158015620001a4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620001ca91906200062f565b856001600160a01b0316635425d8ac6040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000209573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200022f91906200062f565b866001600160a01b031663a08714c06040518163ffffffff1660e01b8152600401602060405180830381865afa1580156200026e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200029491906200062f565b876001600160a01b031663c5b964e06040518163ffffffff1660e01b8152600401602060405180830381865afa158015620002d3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620002f9919062000656565b886001600160a01b031663ac4dc59a6040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000338573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200035e91906200062f565b3380600081620003b55760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620003e857620003e8816200056b565b5050506001600160a01b0380891660805287811660a05286811660c05285811660e052848116610100528316610120526025805483919060ff19166001838181111562000439576200043962000679565b0217905550806001600160a01b0316610140816001600160a01b03168152505060c0516001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa1580156200049a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620004c091906200068f565b60ff1660a0516001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000504573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200052a91906200068f565b60ff16146200054c576040516301f86e1760e41b815260040160405180910390fd5b5050506001600160a01b039095166101605250620006b4945050505050565b336001600160a01b03821603620005c55760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401620003ac565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6001600160a01b03811681146200062c57600080fd5b50565b6000602082840312156200064257600080fd5b81516200064f8162000616565b9392505050565b6000602082840312156200066957600080fd5b8151600281106200064f57600080fd5b634e487b7160e01b600052602160045260246000fd5b600060208284031215620006a257600080fd5b815160ff811681146200064f57600080fd5b60805160a05160c05160e05161010051610120516101405161016051615e07620007186000396000818160b30152610186015260005050600050506000505060005050600061379a015260005050600081816113710152612dc30152615e076000f3fe6080604052600436106100b15760003560e01c80638da5cb5b11610069578063b1dc65a41161004e578063b1dc65a4146102cb578063e3d0e712146102eb578063f2fde38b1461030b576100b1565b80638da5cb5b1461025a578063afcb95d714610285576100b1565b8063349e8cca1161009a578063349e8cca1461017757806379ba5097146101cb57806381ff7048146101e0576100b1565b80630870d3a1146100f8578063181f5a7714610118575b7f00000000000000000000000000000000000000000000000000000000000000003660008037600080366000845af43d6000803e8080156100f1573d6000f35b3d6000fd5b005b34801561010457600080fd5b506100f6610113366004614b7b565b61032b565b34801561012457600080fd5b506101616040518060400160405280601881526020017f4175746f6d6174696f6e526567697374727920322e332e30000000000000000081525081565b60405161016e9190614cf7565b60405180910390f35b34801561018357600080fd5b507f00000000000000000000000000000000000000000000000000000000000000005b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200161016e565b3480156101d757600080fd5b506100f6610c0f565b3480156101ec57600080fd5b5061023760175460135463ffffffff74010000000000000000000000000000000000000000830481169378010000000000000000000000000000000000000000000000009093041691565b6040805163ffffffff94851681529390921660208401529082015260600161016e565b34801561026657600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff166101a6565b34801561029157600080fd5b50601354601454604080516000815260208101939093526c0100000000000000000000000090910463ffffffff169082015260600161016e565b3480156102d757600080fd5b506100f66102e6366004614d56565b610d11565b3480156102f757600080fd5b506100f6610306366004614e3b565b611051565b34801561031757600080fd5b506100f6610326366004614f08565b61108b565b61033361109f565b601f8851111561036f576040517f25d0209c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8560ff166000036103ac576040517fe77dba5600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b865188511415806103cb57506103c3866003614f54565b60ff16885111155b15610402576040517f1d2d1c5800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b805182511461043d576040517fcf54c06a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6104478282611122565b610451888861175a565b604051806101200160405280601460000160009054906101000a90046bffffffffffffffffffffffff166bffffffffffffffffffffffff168152602001600063ffffffff1681526020018660a0015162ffffff16815260200186610120015161ffff1681526020018760ff168152602001601460000160169054906101000a900460ff1615158152602001601460000160179054906101000a900460ff1615158152602001866080015115158152602001866101e0015173ffffffffffffffffffffffffffffffffffffffff16815250601460008201518160000160006101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550602082015181600001600c6101000a81548163ffffffff021916908363ffffffff16021790555060408201518160000160106101000a81548162ffffff021916908362ffffff16021790555060608201518160000160136101000a81548161ffff021916908361ffff16021790555060808201518160000160156101000a81548160ff021916908360ff16021790555060a08201518160000160166101000a81548160ff02191690831515021790555060c08201518160000160176101000a81548160ff02191690831515021790555060e08201518160000160186101000a81548160ff0219169083151502179055506101008201518160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055509050506000601660010160189054906101000a900463ffffffff1690506000866101e0015173ffffffffffffffffffffffffffffffffffffffff166357e871e76040518163ffffffff1660e01b8152600401602060405180830381865afa158015610701573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107259190614f70565b6017549091506000906107579074010000000000000000000000000000000000000000900463ffffffff166001614f89565b9050604051806101600160405280896060015173ffffffffffffffffffffffffffffffffffffffff168152602001896000015163ffffffff168152602001896020015163ffffffff1681526020016016600001601c9054906101000a900463ffffffff1663ffffffff16815260200189610100015173ffffffffffffffffffffffffffffffffffffffff1681526020018263ffffffff1681526020018363ffffffff168152602001896040015163ffffffff16815260200189610140015173ffffffffffffffffffffffffffffffffffffffff1681526020018960c0015163ffffffff1681526020018960e0015163ffffffff16815250601660008201518160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060208201518160000160146101000a81548163ffffffff021916908363ffffffff16021790555060408201518160000160186101000a81548163ffffffff021916908363ffffffff160217905550606082015181600001601c6101000a81548163ffffffff021916908363ffffffff16021790555060808201518160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060a08201518160010160146101000a81548163ffffffff021916908363ffffffff16021790555060c08201518160010160186101000a81548163ffffffff021916908363ffffffff16021790555060e082015181600101601c6101000a81548163ffffffff021916908363ffffffff1602179055506101008201518160020160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506101208201518160020160146101000a81548163ffffffff021916908363ffffffff1602179055506101408201518160020160186101000a81548163ffffffff021916908363ffffffff160217905550905050876101600151601981905550876101800151601a81905550876101a00151601b81905550600088604051602001610a9a9190614ff7565b604080518083037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0018152919052601754909150610aff904690309074010000000000000000000000000000000000000000900463ffffffff168f8f8f878f8f611e19565b6013556000610b0e6009611ec3565b90505b8015610b4b57610b38610b30610b2860018461517d565b600990611ed3565b600990611ee6565b5080610b4381615190565b915050610b11565b5060005b896101c0015151811015610ba257610b8f8a6101c001518281518110610b7757610b776151c5565b60200260200101516009611f0890919063ffffffff16565b5080610b9a816151f4565b915050610b4f565b507f1591690b8638f5fb2dbec82ac741805ac5da8b45dc5263f4875b0496fdce4e0584601354601660010160149054906101000a900463ffffffff168f8f8f878f8f604051610bf99998979695949392919061522c565b60405180910390a1505050505050505050505050565b60015473ffffffffffffffffffffffffffffffffffffffff163314610c95576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b60005a90506000610d238660406152c2565b610d2f896101446152d9565b610d3991906152d9565b9050368114610d74576040517fdfe9309000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60408051610120810182526014546bffffffffffffffffffffffff8116825263ffffffff6c01000000000000000000000000820416602083015262ffffff7001000000000000000000000000000000008204169282019290925261ffff730100000000000000000000000000000000000000830416606082015260ff75010000000000000000000000000000000000000000008304811660808301527601000000000000000000000000000000000000000000008304811615801560a08401527701000000000000000000000000000000000000000000000084048216151560c0840152780100000000000000000000000000000000000000000000000090930416151560e082015260155473ffffffffffffffffffffffffffffffffffffffff1661010082015290610ed3576040517f24522f3400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b336000908152600b602052604090205460ff16610f1c576040517f1099ed7500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6013548b3514610f58576040517fdfdcf8e700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6080810151610f689060016152ec565b60ff1687141580610f795750868514155b15610fb0576040517f0244f71a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610fc08b8b8b8b8b8b8b8b611f2a565b6000610fcc8b8b612193565b905060208c0135600881901c63ffffffff16610fe984848861224c565b836020015163ffffffff168163ffffffff16111561104157601480547fffffffffffffffffffffffffffffffff00000000ffffffffffffffffffffffff166c0100000000000000000000000063ffffffff8416021790555b5050505050505050505050505050565b60008060008580602001905181019061106a9190615472565b925092509250611080898989868989888861032b565b505050505050505050565b61109361109f565b61109c81612e39565b50565b60005473ffffffffffffffffffffffffffffffffffffffff163314611120576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610c8c565b565b60005b6024548110156111e0576022600060248381548110611146576111466151c5565b600091825260208083209091015473ffffffffffffffffffffffffffffffffffffffff168352820192909252604001812080547fffffffff00000000000000000000000000000000000000000000000000000000168155600181019190915560020180547fffffffffffffffffffffffffffffffffffffffff000000000000000000000000169055806111d8816151f4565b915050611125565b506111ed602460006145cf565b60255460ff1660005b8351811015611754576000848281518110611213576112136151c5565b602002602001015190506000848381518110611231576112316151c5565b602002602001015190508173ffffffffffffffffffffffffffffffffffffffff1663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015611286573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112aa919061561d565b60ff16816060015160ff161415806113385750806040015173ffffffffffffffffffffffffffffffffffffffff1663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa15801561130c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611330919061561d565b60ff16600814155b1561136f576040517fc1ab6dc100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161480156113db575060018460018111156113d9576113d961563a565b145b15611412576040517fc1ab6dc100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8216158061144d5750604081015173ffffffffffffffffffffffffffffffffffffffff16155b15611484576040517f8579befe00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff828116600090815260226020526040902054670100000000000000900416156114ee576040517f357d0cc400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6024805460018181019092557f7cd332d19b93bcabe3cce7ca0c18a052f57e5fd03b4758a09f30f5ddc4b22ec401805473ffffffffffffffffffffffffffffffffffffffff8086167fffffffffffffffffffffffff0000000000000000000000000000000000000000909216821790925560008181526022602090815260409182902086518154928801518489015160608a015160ff167b01000000000000000000000000000000000000000000000000000000027fffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffff9190981667010000000000000002167fffffffff000000000000000000000000000000000000000000ffffffffffffff62ffffff909216640100000000027fffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000090951663ffffffff9093169290921793909317929092169190911793909317835560808501519383019390935560a0840151600290920180546bffffffffffffffffffffffff9093167fffffffffffffffffffffffffffffffffffffffff0000000000000000000000009093169290921790915590517fca93cbe727c73163ec538f71be6c0a64877d7f1f6dd35d5ca7cbaef3a3e34ba390611737908490600060c08201905063ffffffff835116825262ffffff602084015116602083015273ffffffffffffffffffffffffffffffffffffffff604084015116604083015260ff6060840151166060830152608083015160808301526bffffffffffffffffffffffff60a08401511660a083015292915050565b60405180910390a25050808061174c906151f4565b9150506111f6565b50505050565b600e546014546bffffffffffffffffffffffff1660005b600e548110156117cd576117ba600e8281548110611791576117916151c5565b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff168385612f2e565b50806117c5816151f4565b915050611771565b5060255460009060ff16815b600e5481101561193e57600e81815481106117f6576117f66151c5565b6000918252602082200154600d805473ffffffffffffffffffffffffffffffffffffffff9092169550600c929184908110611833576118336151c5565b600091825260208083209091015473ffffffffffffffffffffffffffffffffffffffff9081168452838201949094526040928301822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00001690559286168152600b909252902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016905560018260018111156118d5576118d561563a565b14801561191a575073ffffffffffffffffffffffffffffffffffffffff83166000908152600b60205260409020546201000090046bffffffffffffffffffffffff1615155b1561192c5761192a600f84611f08565b505b80611936816151f4565b9150506117d9565b5061194b600d60006145cf565b611957600e60006145cf565b6040805160808101825260008082526020820181905291810182905260608101829052905b8751811015611de757600c600089838151811061199b5761199b6151c5565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff1682528101919091526040016000205460ff1615611a06576040517f77cea0fa00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16888281518110611a3057611a306151c5565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1603611a85576040517f815e1d6400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405180604001604052806001151581526020018260ff16815250600c60008a8481518110611ab657611ab66151c5565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff1682528181019290925260400160002082518154939092015160ff16610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff921515929092167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000909316929092171790558651879082908110611b5e57611b5e6151c5565b60200260200101519350600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603611bce576040517f58a70a0a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff84166000908152600b60209081526040918290208251608081018452905460ff80821615801584526101008304909116938301939093526bffffffffffffffffffffffff6201000082048116948301949094526e01000000000000000000000000000090049092166060830152909250611c89576040517f6a7281ad00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600180835260ff80831660208086019182526014546bffffffffffffffffffffffff9081166060880190815273ffffffffffffffffffffffffffffffffffffffff8a166000908152600b909352604092839020885181549551948a0151925184166e010000000000000000000000000000027fffffffffffff000000000000000000000000ffffffffffffffffffffffffffff939094166201000002929092167fffffffffffff000000000000000000000000000000000000000000000000ffff94909616610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff921515929092167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000090951694909417179190911692909217919091179055836001811115611dc357611dc361563a565b03611dd557611dd3600f85611ee6565b505b80611ddf816151f4565b91505061197c565b508651611dfb90600d9060208a01906145ed565b508551611e0f90600e9060208901906145ed565b5050505050505050565b6000808a8a8a8a8a8a8a8a8a604051602001611e3d99989796959493929190615669565b604080518083037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001815291905280516020909101207dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e01000000000000000000000000000000000000000000000000000000000000179b9a5050505050505050505050565b6000611ecd825490565b92915050565b6000611edf8383613136565b9392505050565b6000611edf8373ffffffffffffffffffffffffffffffffffffffff8416613160565b6000611edf8373ffffffffffffffffffffffffffffffffffffffff841661325a565b60008787604051611f3c9291906156fe565b604051908190038120611f53918b9060200161570e565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815282825280516020918201208383019092526000808452908301819052909250906000805b8881101561212a57600185878360208110611fbf57611fbf6151c5565b611fcc91901a601b6152ec565b8c8c85818110611fde57611fde6151c5565b905060200201358b8b86818110611ff757611ff76151c5565b9050602002013560405160008152602001604052604051612034949392919093845260ff9290921660208401526040830152606082015260800190565b6020604051602081039080840390855afa158015612056573d6000803e3d6000fd5b5050604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081015173ffffffffffffffffffffffffffffffffffffffff81166000908152600c602090815290849020838501909452925460ff8082161515808552610100909204169383019390935290955093509050612104576040517f0f4c073700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b826020015160080260ff166001901b840193508080612122906151f4565b915050611fa2565b50827e01010101010101010101010101010101010101010101010101010101010101841614612185576040517fc103be2e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505050505050505050505050565b6121cc6040518060c001604052806000815260200160008152602001606081526020016060815260200160608152602001606081525090565b60006121da838501856157ff565b60408101515160608201515191925090811415806121fd57508082608001515114155b8061220d5750808260a001515114155b15612244576040517fb55ac75400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b509392505050565b600082604001515167ffffffffffffffff81111561226c5761226c614684565b60405190808252806020026020018201604052801561233857816020015b6040805161020081018252600060e08201818152610100830182905261012083018290526101408301829052610160830182905261018083018290526101a083018290526101c083018290526101e0830182905282526020808301829052928201819052606082018190526080820181905260a0820181905260c082015282527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90920191018161228a5790505b50905060006040518060800160405280600061ffff16815260200160006bffffffffffffffffffffffff16815260200160006bffffffffffffffffffffffff16815260200160008152509050600085610100015173ffffffffffffffffffffffffffffffffffffffff166357e871e76040518163ffffffff1660e01b8152600401602060405180830381865afa1580156123d6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123fa9190614f70565b9050600086610100015173ffffffffffffffffffffffffffffffffffffffff166318b8f6136040518163ffffffff1660e01b8152600401602060405180830381865afa15801561244e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124729190614f70565b905060005b86604001515181101561290257600460008860400151838151811061249e5761249e6151c5565b6020908102919091018101518252818101929092526040908101600020815161012081018352815460ff8082161515835261010080830490911615159583019590955263ffffffff620100008204811694830194909452660100000000000081048416606083015273ffffffffffffffffffffffffffffffffffffffff6a01000000000000000000009091048116608083015260018301546fffffffffffffffffffffffffffffffff811660a08401526bffffffffffffffffffffffff70010000000000000000000000000000000082041660c08401527c0100000000000000000000000000000000000000000000000000000000900490931660e08201526002909101549091169181019190915285518690839081106125c1576125c16151c5565b6020026020010151600001819052506125f6876040015182815181106125e9576125e96151c5565b60200260200101516132a9565b858281518110612608576126086151c5565b60200260200101516060019060018111156126255761262561563a565b908160018111156126385761263861563a565b8152505061269c87604001518281518110612655576126556151c5565b60200260200101518489608001518481518110612674576126746151c5565b602002602001015188858151811061268e5761268e6151c5565b60200260200101518c613354565b8683815181106126ae576126ae6151c5565b60200260200101516020018784815181106126cb576126cb6151c5565b602002602001015160c00182815250821515151581525050508481815181106126f6576126f66151c5565b602002602001015160200151156127265760018460000181815161271a91906158ec565b61ffff1690525061272b565b6128f0565b612791858281518110612740576127406151c5565b6020026020010151600001516080015188606001518381518110612766576127666151c5565b60200260200101518960a001518481518110612784576127846151c5565b6020026020010151613473565b8683815181106127a3576127a36151c5565b60200260200101516040018784815181106127c0576127c06151c5565b60200260200101516080018281525082151515158152505050876080015160016127ea91906152ec565b6127f89060ff1660406152c2565b6103a48860a001518381518110612811576128116151c5565b60200260200101515161282491906152d9565b61282e91906152d9565b858281518110612840576128406151c5565b602002602001015160a0018181525050848181518110612862576128626151c5565b602002602001015160a001518460600181815161287f91906152d9565b9052508451859082908110612896576128966151c5565b602002602001015160800151866128ad919061517d565b95506128f0876040015182815181106128c8576128c86151c5565b6020026020010151848784815181106128e3576128e36151c5565b602002602001015161368e565b806128fa816151f4565b915050612477565b50825161ffff166000036129195750505050505050565b61c8006129273660106152c2565b5a612932908861517d565b61293c91906152d9565b61294691906152d9565b83519095506137789061295d9061ffff1687615936565b61296791906152d9565b604080516080810182526000808252602082018190529181018290526060810182905291965061299689613793565b905060005b886040015151811015612cd2578681815181106129ba576129ba6151c5565b60200260200101516020015115612cc057801580612a525750866129df60018361517d565b815181106129ef576129ef6151c5565b602002602001015160000151610100015173ffffffffffffffffffffffffffffffffffffffff16878281518110612a2857612a286151c5565b602002602001015160000151610100015173ffffffffffffffffffffffffffffffffffffffff1614155b15612a8657612a838a888381518110612a6d57612a6d6151c5565b602002602001015160000151610100015161387d565b92505b6000612ba48b6040518061012001604052808b8681518110612aaa57612aaa6151c5565b60200260200101516080015181526020018c81526020018a606001518c8781518110612ad857612ad86151c5565b602002602001015160a001518a612aef91906152c2565b612af99190615936565b81526020018d6000015181526020018d6020015181526020018681526020018b8681518110612b2a57612b2a6151c5565b602002602001015160000151610100015173ffffffffffffffffffffffffffffffffffffffff168152602001878152602001600115158152508c604001518581518110612b7957612b796151c5565b60200260200101518b8681518110612b9357612b936151c5565b6020026020010151600001516139f9565b9050806060015187604001818151612bbc919061594a565b6bffffffffffffffffffffffff169052506040810151602088018051612be390839061594a565b6bffffffffffffffffffffffff169052508751889083908110612c0857612c086151c5565b60200260200101516040015115158a604001518381518110612c2c57612c2c6151c5565b60200260200101517fad8cc9579b21dfe2c2f6ea35ba15b656e46b4f5b0cb424f52739b8ce5cac9c5b83602001518460000151612c69919061594a565b8b8681518110612c7b57612c7b6151c5565b6020026020010151608001518d8f608001518881518110612c9e57612c9e6151c5565b6020026020010151604051612cb6949392919061596f565b60405180910390a3505b80612cca816151f4565b91505061299b565b505050602083810151336000908152600b90925260409091208054600290612d0f9084906201000090046bffffffffffffffffffffffff1661594a565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055508260400151601460000160008282829054906101000a90046bffffffffffffffffffffffff16612d6d919061594a565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff16021790555082604001518360200151612daf919061594a565b6bffffffffffffffffffffffff16602160007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254612e2b91906152d9565b909155505050505050505050565b3373ffffffffffffffffffffffffffffffffffffffff821603612eb8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610c8c565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b73ffffffffffffffffffffffffffffffffffffffff83166000908152600b602090815260408083208151608081018352905460ff80821615801584526101008304909116948301949094526bffffffffffffffffffffffff6201000082048116938301939093526e010000000000000000000000000000900490911660608201529061312a576000816060015185612fc691906159ac565b90506000612fd485836159d1565b90508083604001818151612fe8919061594a565b6bffffffffffffffffffffffff1690525061300385826159fc565b83606001818151613014919061594a565b6bffffffffffffffffffffffff90811690915273ffffffffffffffffffffffffffffffffffffffff89166000908152600b602090815260409182902087518154928901519389015160608a015186166e010000000000000000000000000000027fffffffffffff000000000000000000000000ffffffffffffffffffffffffffff919096166201000002167fffffffffffff000000000000000000000000000000000000000000000000ffff60ff95909516610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff921515929092167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000909416939093171792909216179190911790555050505b60400151949350505050565b600082600001828154811061314d5761314d6151c5565b9060005260206000200154905092915050565b6000818152600183016020526040812054801561324957600061318460018361517d565b85549091506000906131989060019061517d565b90508181146131fd5760008660000182815481106131b8576131b86151c5565b90600052602060002001549050808760000184815481106131db576131db6151c5565b6000918252602080832090910192909255918252600188019052604090208390555b855486908061320e5761320e615a2c565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050611ecd565b6000915050611ecd565b5092915050565b60008181526001830160205260408120546132a157508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155611ecd565b506000611ecd565b6000818160045b600f811015613336577fff0000000000000000000000000000000000000000000000000000000000000082168382602081106132ee576132ee6151c5565b1a60f81b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161461332457506000949350505050565b8061332e816151f4565b9150506132b0565b5081600f1a600181111561334c5761334c61563a565b949350505050565b60008080808560600151600181111561336f5761336f61563a565b03613395576133818888888888613e68565b61339057600092509050613469565b61340d565b6001856060015160018111156133ad576133ad61563a565b036133db5760006133c089898988613ff2565b92509050806133d55750600092509050613469565b5061340d565b6040517ff2b2d41200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b84516060015163ffffffff16871061346257877fc3237c8807c467c1b39b8d0395eff077313e691bf0a7388106792564ebfd56368760405161344f9190614cf7565b60405180910390a2600092509050613469565b6001925090505b9550959350505050565b601454600090819077010000000000000000000000000000000000000000000000900460ff16156134d0576040517f37ed32e800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601480547fffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffff16770100000000000000000000000000000000000000000000001790556040517f4585e33b0000000000000000000000000000000000000000000000000000000090613545908590602401614cf7565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931790925290517f79188d1600000000000000000000000000000000000000000000000000000000815290935073ffffffffffffffffffffffffffffffffffffffff8616906379188d16906136189087908790600401615a5b565b60408051808303816000875af1158015613636573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061365a9190615a74565b601480547fffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffff16905590969095509350505050565b6000816060015160018111156136a6576136a661563a565b0361370a57600083815260046020526040902060010180547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167c010000000000000000000000000000000000000000000000000000000063ffffffff851602179055505050565b6001816060015160018111156137225761372261563a565b0361378e5760c08101805160009081526008602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055915191517fa4a4e334c0e330143f9437484fe516c13bc560b86b5b0daf58e7084aaac228f29190a25b505050565b60008060007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015613803573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906138279190615abc565b5093505092505060008213158061383d57508042105b8061386d57506000846040015162ffffff1611801561386d5750613861814261517d565b846040015162ffffff16105b15613253575050601b5492915050565b60408051608081018252600080825260208083018281528385018381526060850184905273ffffffffffffffffffffffffffffffffffffffff878116855260229093528584208054640100000000810462ffffff1690925263ffffffff82169092527b01000000000000000000000000000000000000000000000000000000810460ff16855285517ffeaf968c00000000000000000000000000000000000000000000000000000000815295519495919484936701000000000000009092049091169163feaf968c9160048083019260a09291908290030181865afa15801561396a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061398e9190615abc565b509350509250506000821315806139a457508042105b806139d457506000866040015162ffffff161180156139d457506139c8814261517d565b866040015162ffffff16105b156139e857600183015460608501526139f0565b606084018290525b50505092915050565b604080516101008101825260008082526020808301829052928201819052606082018190526080820181905260a0820181905260c0820181905260e08201529082015115613a8f5760008381526023602090815260409182902082518084018452905463ffffffff811680835262ffffff640100000000909204821692840192835260e089018051909401529051915191169101525b6000613a9b86866141ff565b60c0840151602082015182519293509091600091613ab89161594a565b60e08801515190915060ff16600060128210613ad5576001613aeb565b613ae082601261517d565b613aeb90600a615c2c565b9050600060128311613afe576001613b14565b613b0960128461517d565b613b1490600a615c2c565b905085600001516bffffffffffffffffffffffff16856bffffffffffffffffffffffff161015613bbc57849350613b90818b60800151613b5491906152c2565b838c60e0015160600151886bffffffffffffffffffffffff16613b7791906152c2565b613b8191906152c2565b613b8b9190615936565b61452d565b6bffffffffffffffffffffffff9081166040880152600060608801819052602088015285168652613ce2565b836bffffffffffffffffffffffff16856bffffffffffffffffffffffff161015613ce257849350613c4a86604001516bffffffffffffffffffffffff16828c60800151613c0991906152c2565b848d60e0015160600151896bffffffffffffffffffffffff16613c2c91906152c2565b613c3691906152c2565b613c409190615936565b613b8b919061517d565b6bffffffffffffffffffffffff1660608088019190915260e08b01510151613cce90613c779084906152c2565b6001848d60e0015160600151613c8d91906152c2565b613c97919061517d565b838d608001518a606001516bffffffffffffffffffffffff16613cba91906152c2565b613cc491906152c2565b613b8191906152d9565b6bffffffffffffffffffffffff1660208701525b60008981526004602052604090206001018054859190601090613d2890849070010000000000000000000000000000000090046bffffffffffffffffffffffff166159ac565b82546101009290920a6bffffffffffffffffffffffff81810219909316918316021790915560008b81526004602052604081206001018054928816935091613d839084906fffffffffffffffffffffffffffffffff16615c38565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff160217905550836bffffffffffffffffffffffff16602160008c60c0015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254613e1a919061517d565b92505081905550887f801ba6ed51146ffe3e99d1dbd9dd0f4de6292e78a9a34c39c0183de17b3f40fc87604051613e519190615c61565b60405180910390a250939998505050505050505050565b60008084806020019051810190613e7f9190615d21565b845160e00151815191925063ffffffff90811691161015613edc57867f405288ea7be309e16cfdf481367f90a413e1d4634fcdaf8966546db9b93012e886604051613eca9190614cf7565b60405180910390a26000915050613fe9565b8260e001518015613f9c5750602081015115801590613f9c5750602081015161010084015182516040517f85df51fd00000000000000000000000000000000000000000000000000000000815263ffffffff909116600482015273ffffffffffffffffffffffffffffffffffffffff909116906385df51fd90602401602060405180830381865afa158015613f75573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613f999190614f70565b14155b80613fae5750805163ffffffff168611155b15613fe357867f6aa7f60c176da7af894b384daea2249497448137f5943c1237ada8bc92bdc30186604051613eca9190614cf7565b60019150505b95945050505050565b60008060008480602001905181019061400b9190615d79565b905060008782600001518360200151846040015160405160200161406d94939291909384526020840192909252604083015260e01b7fffffffff0000000000000000000000000000000000000000000000000000000016606082015260640190565b6040516020818303038152906040528051906020012090508460e00151801561414857506080820151158015906141485750608082015161010086015160608401516040517f85df51fd00000000000000000000000000000000000000000000000000000000815263ffffffff909116600482015273ffffffffffffffffffffffffffffffffffffffff909116906385df51fd90602401602060405180830381865afa158015614121573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906141459190614f70565b14155b8061415d575086826060015163ffffffff1610155b156141a757877f6aa7f60c176da7af894b384daea2249497448137f5943c1237ada8bc92bdc301876040516141929190614cf7565b60405180910390a26000935091506141f69050565b60008181526008602052604090205460ff16156141ee57877f405288ea7be309e16cfdf481367f90a413e1d4634fcdaf8966546db9b93012e8876040516141929190614cf7565b600193509150505b94509492505050565b6040805161010081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e081019190915260008260e001516000015160ff1690506000846060015161ffff16846060015161426a91906152c2565b9050836101000151801561427d5750803a105b1561428557503a5b6000601283116142965760016142ac565b6142a160128461517d565b6142ac90600a615c2c565b90506000601284106142bf5760016142d5565b6142ca84601261517d565b6142d590600a615c2c565b905060008660a001518760400151886020015189600001516142f791906152d9565b61430190876152c2565b61430b91906152d9565b61431591906152c2565b9050614358828860e001516060015161432e91906152c2565b6001848a60e001516060015161434491906152c2565b61434e919061517d565b613cc486856152c2565b6bffffffffffffffffffffffff168652608087015161437b90613b8b9083615936565b6bffffffffffffffffffffffff1660408088019190915260e088015101516000906143b49062ffffff16683635c9adc5dea000006152c2565b9050600081633b9aca008a60a001518b60e001516020015163ffffffff168c604001518d600001518b6143e791906152c2565b6143f191906152d9565b6143fb91906152c2565b61440591906152c2565b61440f9190615936565b61441991906152d9565b905061445c848a60e001516060015161443291906152c2565b6001868c60e001516060015161444891906152c2565b614452919061517d565b613cc488856152c2565b6bffffffffffffffffffffffff166020890152608089015161448290613b8b9083615936565b6bffffffffffffffffffffffff16606089015260c089015173ffffffffffffffffffffffffffffffffffffffff166080808a01919091528901516144c59061452d565b6bffffffffffffffffffffffff1660a0808a01919091528901516144e89061452d565b6bffffffffffffffffffffffff1660c089015260e08901516060015161450d9061452d565b6bffffffffffffffffffffffff1660e08901525050505050505092915050565b60006bffffffffffffffffffffffff8211156145cb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203960448201527f36206269747300000000000000000000000000000000000000000000000000006064820152608401610c8c565b5090565b508054600082559060005260206000209081019061109c919061466f565b828054828255906000526020600020908101928215614667579160200282015b8281111561466757825182547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff90911617825560209092019160019091019061460d565b506145cb9291505b5b808211156145cb5760008155600101614670565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051610200810167ffffffffffffffff811182821017156146d7576146d7614684565b60405290565b60405160c0810167ffffffffffffffff811182821017156146d7576146d7614684565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff8111828210171561474757614747614684565b604052919050565b600067ffffffffffffffff82111561476957614769614684565b5060051b60200190565b73ffffffffffffffffffffffffffffffffffffffff8116811461109c57600080fd5b80356147a081614773565b919050565b600082601f8301126147b657600080fd5b813560206147cb6147c68361474f565b614700565b82815260059290921b840181019181810190868411156147ea57600080fd5b8286015b8481101561480e57803561480181614773565b83529183019183016147ee565b509695505050505050565b60ff8116811461109c57600080fd5b80356147a081614819565b63ffffffff8116811461109c57600080fd5b80356147a081614833565b801515811461109c57600080fd5b80356147a081614850565b62ffffff8116811461109c57600080fd5b80356147a081614869565b61ffff8116811461109c57600080fd5b80356147a081614885565b600061020082840312156148b357600080fd5b6148bb6146b3565b90506148c682614845565b81526148d460208301614845565b60208201526148e560408301614845565b60408201526148f660608301614795565b60608201526149076080830161485e565b608082015261491860a0830161487a565b60a082015261492960c08301614845565b60c082015261493a60e08301614845565b60e082015261010061494d818401614795565b9082015261012061495f838201614895565b90820152610140614971838201614795565b90820152610160828101359082015261018080830135908201526101a080830135908201526101c08083013567ffffffffffffffff8111156149b257600080fd5b6149be858286016147a5565b8284015250506101e06149d2818401614795565b9082015292915050565b803567ffffffffffffffff811681146147a057600080fd5b600082601f830112614a0557600080fd5b813567ffffffffffffffff811115614a1f57614a1f614684565b614a5060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601614700565b818152846020838601011115614a6557600080fd5b816020850160208301376000918101602001919091529392505050565b6bffffffffffffffffffffffff8116811461109c57600080fd5b600082601f830112614aad57600080fd5b81356020614abd6147c68361474f565b82815260c09283028501820192828201919087851115614adc57600080fd5b8387015b85811015614b6e5781818a031215614af85760008081fd5b614b006146dd565b8135614b0b81614833565b815281860135614b1a81614869565b81870152604082810135614b2d81614773565b90820152606082810135614b4081614819565b908201526080828101359082015260a080830135614b5d81614a82565b908201528452928401928101614ae0565b5090979650505050505050565b600080600080600080600080610100898b031215614b9857600080fd5b883567ffffffffffffffff80821115614bb057600080fd5b614bbc8c838d016147a5565b995060208b0135915080821115614bd257600080fd5b614bde8c838d016147a5565b9850614bec60408c01614828565b975060608b0135915080821115614c0257600080fd5b614c0e8c838d016148a0565b9650614c1c60808c016149dc565b955060a08b0135915080821115614c3257600080fd5b614c3e8c838d016149f4565b945060c08b0135915080821115614c5457600080fd5b614c608c838d016147a5565b935060e08b0135915080821115614c7657600080fd5b50614c838b828c01614a9c565b9150509295985092959890939650565b6000815180845260005b81811015614cb957602081850181015186830182015201614c9d565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b602081526000611edf6020830184614c93565b60008083601f840112614d1c57600080fd5b50813567ffffffffffffffff811115614d3457600080fd5b6020830191508360208260051b8501011115614d4f57600080fd5b9250929050565b60008060008060008060008060e0898b031215614d7257600080fd5b606089018a811115614d8357600080fd5b8998503567ffffffffffffffff80821115614d9d57600080fd5b818b0191508b601f830112614db157600080fd5b813581811115614dc057600080fd5b8c6020828501011115614dd257600080fd5b6020830199508098505060808b0135915080821115614df057600080fd5b614dfc8c838d01614d0a565b909750955060a08b0135915080821115614e1557600080fd5b50614e228b828c01614d0a565b999c989b50969995989497949560c00135949350505050565b60008060008060008060c08789031215614e5457600080fd5b863567ffffffffffffffff80821115614e6c57600080fd5b614e788a838b016147a5565b97506020890135915080821115614e8e57600080fd5b614e9a8a838b016147a5565b9650614ea860408a01614828565b95506060890135915080821115614ebe57600080fd5b614eca8a838b016149f4565b9450614ed860808a016149dc565b935060a0890135915080821115614eee57600080fd5b50614efb89828a016149f4565b9150509295509295509295565b600060208284031215614f1a57600080fd5b8135611edf81614773565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60ff818116838216029081169081811461325357613253614f25565b600060208284031215614f8257600080fd5b5051919050565b63ffffffff81811683821601908082111561325357613253614f25565b600081518084526020808501945080840160005b83811015614fec57815173ffffffffffffffffffffffffffffffffffffffff1687529582019590820190600101614fba565b509495945050505050565b6020815261500e60208201835163ffffffff169052565b60006020830151615027604084018263ffffffff169052565b50604083015163ffffffff8116606084015250606083015173ffffffffffffffffffffffffffffffffffffffff8116608084015250608083015180151560a08401525060a083015162ffffff811660c08401525060c083015163ffffffff811660e08401525060e08301516101006150a68185018363ffffffff169052565b84015190506101206150cf8482018373ffffffffffffffffffffffffffffffffffffffff169052565b84015190506101406150e68482018361ffff169052565b840151905061016061510f8482018373ffffffffffffffffffffffffffffffffffffffff169052565b840151610180848101919091528401516101a0808501919091528401516101c0808501919091528401516102006101e080860182905291925090615157610220860184614fa6565b95015173ffffffffffffffffffffffffffffffffffffffff169301929092525090919050565b81810381811115611ecd57611ecd614f25565b60008161519f5761519f614f25565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361522557615225614f25565b5060010190565b600061012063ffffffff808d1684528b6020850152808b1660408501525080606084015261525c8184018a614fa6565b905082810360808401526152708189614fa6565b905060ff871660a084015282810360c084015261528d8187614c93565b905067ffffffffffffffff851660e08401528281036101008401526152b28185614c93565b9c9b505050505050505050505050565b8082028115828204841417611ecd57611ecd614f25565b80820180821115611ecd57611ecd614f25565b60ff8181168382160190811115611ecd57611ecd614f25565b80516147a081614833565b80516147a081614773565b80516147a081614850565b80516147a081614869565b80516147a081614885565b600082601f83011261534d57600080fd5b8151602061535d6147c68361474f565b82815260059290921b8401810191818101908684111561537c57600080fd5b8286015b8481101561480e57805161539381614773565b8352918301918301615380565b600082601f8301126153b157600080fd5b815160206153c16147c68361474f565b82815260c092830285018201928282019190878511156153e057600080fd5b8387015b85811015614b6e5781818a0312156153fc5760008081fd5b6154046146dd565b815161540f81614833565b81528186015161541e81614869565b8187015260408281015161543181614773565b9082015260608281015161544481614819565b908201526080828101519082015260a08083015161546181614a82565b9082015284529284019281016153e4565b60008060006060848603121561548757600080fd5b835167ffffffffffffffff8082111561549f57600080fd5b9085019061020082880312156154b457600080fd5b6154bc6146b3565b6154c583615305565b81526154d360208401615305565b60208201526154e460408401615305565b60408201526154f560608401615310565b60608201526155066080840161531b565b608082015261551760a08401615326565b60a082015261552860c08401615305565b60c082015261553960e08401615305565b60e082015261010061554c818501615310565b9082015261012061555e848201615331565b90820152610140615570848201615310565b90820152610160838101519082015261018080840151908201526101a080840151908201526101c080840151838111156155a957600080fd5b6155b58a82870161533c565b8284015250506101e06155c9818501615310565b9082015260208701519095509150808211156155e457600080fd5b6155f08783880161533c565b9350604086015191508082111561560657600080fd5b50615613868287016153a0565b9150509250925092565b60006020828403121561562f57600080fd5b8151611edf81614819565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b60006101208b835273ffffffffffffffffffffffffffffffffffffffff8b16602084015267ffffffffffffffff808b1660408501528160608501526156b08285018b614fa6565b915083820360808501526156c4828a614fa6565b915060ff881660a085015283820360c08501526156e18288614c93565b90861660e085015283810361010085015290506152b28185614c93565b8183823760009101908152919050565b8281526080810160608360208401379392505050565b600082601f83011261573557600080fd5b813560206157456147c68361474f565b82815260059290921b8401810191818101908684111561576457600080fd5b8286015b8481101561480e5780358352918301918301615768565b600082601f83011261579057600080fd5b813560206157a06147c68361474f565b82815260059290921b840181019181810190868411156157bf57600080fd5b8286015b8481101561480e57803567ffffffffffffffff8111156157e35760008081fd5b6157f18986838b01016149f4565b8452509183019183016157c3565b60006020828403121561581157600080fd5b813567ffffffffffffffff8082111561582957600080fd5b9083019060c0828603121561583d57600080fd5b6158456146dd565b823581526020830135602082015260408301358281111561586557600080fd5b61587187828601615724565b60408301525060608301358281111561588957600080fd5b61589587828601615724565b6060830152506080830135828111156158ad57600080fd5b6158b98782860161577f565b60808301525060a0830135828111156158d157600080fd5b6158dd8782860161577f565b60a08301525095945050505050565b61ffff81811683821601908082111561325357613253614f25565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b60008261594557615945615907565b500490565b6bffffffffffffffffffffffff81811683821601908082111561325357613253614f25565b6bffffffffffffffffffffffff851681528360208201528260408201526080606082015260006159a26080830184614c93565b9695505050505050565b6bffffffffffffffffffffffff82811682821603908082111561325357613253614f25565b60006bffffffffffffffffffffffff808416806159f0576159f0615907565b92169190910492915050565b6bffffffffffffffffffffffff818116838216028082169190828114615a2457615a24614f25565b505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b82815260406020820152600061334c6040830184614c93565b60008060408385031215615a8757600080fd5b8251615a9281614850565b6020939093015192949293505050565b805169ffffffffffffffffffff811681146147a057600080fd5b600080600080600060a08688031215615ad457600080fd5b615add86615aa2565b9450602086015193506040860151925060608601519150615b0060808701615aa2565b90509295509295909350565b600181815b80851115615b6557817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04821115615b4b57615b4b614f25565b80851615615b5857918102915b93841c9390800290615b11565b509250929050565b600082615b7c57506001611ecd565b81615b8957506000611ecd565b8160018114615b9f5760028114615ba957615bc5565b6001915050611ecd565b60ff841115615bba57615bba614f25565b50506001821b611ecd565b5060208310610133831016604e8410600b8410161715615be8575081810a611ecd565b615bf28383615b0c565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04821115615c2457615c24614f25565b029392505050565b6000611edf8383615b6d565b6fffffffffffffffffffffffffffffffff81811683821601908082111561325357613253614f25565b6000610100820190506bffffffffffffffffffffffff8084511683528060208501511660208401528060408501511660408401528060608501511660608401525073ffffffffffffffffffffffffffffffffffffffff608084015116608083015260a0830151615ce160a08401826bffffffffffffffffffffffff169052565b5060c0830151615d0160c08401826bffffffffffffffffffffffff169052565b5060e083015161325360e08401826bffffffffffffffffffffffff169052565b600060408284031215615d3357600080fd5b6040516040810181811067ffffffffffffffff82111715615d5657615d56614684565b6040528251615d6481614833565b81526020928301519281019290925250919050565b600060a08284031215615d8b57600080fd5b60405160a0810181811067ffffffffffffffff82111715615dae57615dae614684565b806040525082518152602083015160208201526040830151615dcf81614833565b60408201526060830151615de281614833565b6060820152608092830151928101929092525091905056fea164736f6c6343000813000a", + Bin: "0x6101806040523480156200001257600080fd5b50604051620065423803806200654283398101604081905262000035916200062f565b80816001600160a01b031663ca30e6036040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000075573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200009b91906200062f565b826001600160a01b031663226cf83c6040518163ffffffff1660e01b8152600401602060405180830381865afa158015620000da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200010091906200062f565b836001600160a01b031663614486af6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156200013f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200016591906200062f565b846001600160a01b0316636709d0e56040518163ffffffff1660e01b8152600401602060405180830381865afa158015620001a4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620001ca91906200062f565b856001600160a01b0316635425d8ac6040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000209573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200022f91906200062f565b866001600160a01b031663a08714c06040518163ffffffff1660e01b8152600401602060405180830381865afa1580156200026e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200029491906200062f565b876001600160a01b031663c5b964e06040518163ffffffff1660e01b8152600401602060405180830381865afa158015620002d3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620002f9919062000656565b886001600160a01b031663ac4dc59a6040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000338573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200035e91906200062f565b3380600081620003b55760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620003e857620003e8816200056b565b5050506001600160a01b0380891660805287811660a05286811660c05285811660e052848116610100528316610120526025805483919060ff19166001838181111562000439576200043962000679565b0217905550806001600160a01b0316610140816001600160a01b03168152505060c0516001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa1580156200049a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620004c091906200068f565b60ff1660a0516001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000504573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200052a91906200068f565b60ff16146200054c576040516301f86e1760e41b815260040160405180910390fd5b5050506001600160a01b039095166101605250620006b4945050505050565b336001600160a01b03821603620005c55760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401620003ac565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6001600160a01b03811681146200062c57600080fd5b50565b6000602082840312156200064257600080fd5b81516200064f8162000616565b9392505050565b6000602082840312156200066957600080fd5b8151600281106200064f57600080fd5b634e487b7160e01b600052602160045260246000fd5b600060208284031215620006a257600080fd5b815160ff811681146200064f57600080fd5b60805160a05160c05160e05161010051610120516101405161016051615e2a620007186000396000818160b3015261018601526000505060005050600050506000505060006137bd015260005050600081816113710152612de60152615e2a6000f3fe6080604052600436106100b15760003560e01c80638da5cb5b11610069578063b1dc65a41161004e578063b1dc65a4146102cb578063e3d0e712146102eb578063f2fde38b1461030b576100b1565b80638da5cb5b1461025a578063afcb95d714610285576100b1565b8063349e8cca1161009a578063349e8cca1461017757806379ba5097146101cb57806381ff7048146101e0576100b1565b80630870d3a1146100f8578063181f5a7714610118575b7f00000000000000000000000000000000000000000000000000000000000000003660008037600080366000845af43d6000803e8080156100f1573d6000f35b3d6000fd5b005b34801561010457600080fd5b506100f6610113366004614b9e565b61032b565b34801561012457600080fd5b506101616040518060400160405280601881526020017f4175746f6d6174696f6e526567697374727920322e332e30000000000000000081525081565b60405161016e9190614d1a565b60405180910390f35b34801561018357600080fd5b507f00000000000000000000000000000000000000000000000000000000000000005b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200161016e565b3480156101d757600080fd5b506100f6610c0f565b3480156101ec57600080fd5b5061023760175460135463ffffffff74010000000000000000000000000000000000000000830481169378010000000000000000000000000000000000000000000000009093041691565b6040805163ffffffff94851681529390921660208401529082015260600161016e565b34801561026657600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff166101a6565b34801561029157600080fd5b50601354601454604080516000815260208101939093526c0100000000000000000000000090910463ffffffff169082015260600161016e565b3480156102d757600080fd5b506100f66102e6366004614d79565b610d11565b3480156102f757600080fd5b506100f6610306366004614e5e565b611051565b34801561031757600080fd5b506100f6610326366004614f2b565b61108b565b61033361109f565b601f8851111561036f576040517f25d0209c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8560ff166000036103ac576040517fe77dba5600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b865188511415806103cb57506103c3866003614f77565b60ff16885111155b15610402576040517f1d2d1c5800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b805182511461043d576040517fcf54c06a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6104478282611122565b610451888861175a565b604051806101200160405280601460000160009054906101000a90046bffffffffffffffffffffffff166bffffffffffffffffffffffff168152602001600063ffffffff1681526020018660a0015162ffffff16815260200186610120015161ffff1681526020018760ff168152602001601460000160169054906101000a900460ff1615158152602001601460000160179054906101000a900460ff1615158152602001866080015115158152602001866101e0015173ffffffffffffffffffffffffffffffffffffffff16815250601460008201518160000160006101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550602082015181600001600c6101000a81548163ffffffff021916908363ffffffff16021790555060408201518160000160106101000a81548162ffffff021916908362ffffff16021790555060608201518160000160136101000a81548161ffff021916908361ffff16021790555060808201518160000160156101000a81548160ff021916908360ff16021790555060a08201518160000160166101000a81548160ff02191690831515021790555060c08201518160000160176101000a81548160ff02191690831515021790555060e08201518160000160186101000a81548160ff0219169083151502179055506101008201518160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055509050506000601660010160189054906101000a900463ffffffff1690506000866101e0015173ffffffffffffffffffffffffffffffffffffffff166357e871e76040518163ffffffff1660e01b8152600401602060405180830381865afa158015610701573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107259190614f93565b6017549091506000906107579074010000000000000000000000000000000000000000900463ffffffff166001614fac565b9050604051806101600160405280896060015173ffffffffffffffffffffffffffffffffffffffff168152602001896000015163ffffffff168152602001896020015163ffffffff1681526020016016600001601c9054906101000a900463ffffffff1663ffffffff16815260200189610100015173ffffffffffffffffffffffffffffffffffffffff1681526020018263ffffffff1681526020018363ffffffff168152602001896040015163ffffffff16815260200189610140015173ffffffffffffffffffffffffffffffffffffffff1681526020018960c0015163ffffffff1681526020018960e0015163ffffffff16815250601660008201518160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060208201518160000160146101000a81548163ffffffff021916908363ffffffff16021790555060408201518160000160186101000a81548163ffffffff021916908363ffffffff160217905550606082015181600001601c6101000a81548163ffffffff021916908363ffffffff16021790555060808201518160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060a08201518160010160146101000a81548163ffffffff021916908363ffffffff16021790555060c08201518160010160186101000a81548163ffffffff021916908363ffffffff16021790555060e082015181600101601c6101000a81548163ffffffff021916908363ffffffff1602179055506101008201518160020160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506101208201518160020160146101000a81548163ffffffff021916908363ffffffff1602179055506101408201518160020160186101000a81548163ffffffff021916908363ffffffff160217905550905050876101600151601981905550876101800151601a81905550876101a00151601b81905550600088604051602001610a9a919061501a565b604080518083037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0018152919052601754909150610aff904690309074010000000000000000000000000000000000000000900463ffffffff168f8f8f878f8f611e19565b6013556000610b0e6009611ec3565b90505b8015610b4b57610b38610b30610b286001846151a0565b600990611ed3565b600990611ee6565b5080610b43816151b3565b915050610b11565b5060005b896101c0015151811015610ba257610b8f8a6101c001518281518110610b7757610b776151e8565b60200260200101516009611f0890919063ffffffff16565b5080610b9a81615217565b915050610b4f565b507f1591690b8638f5fb2dbec82ac741805ac5da8b45dc5263f4875b0496fdce4e0584601354601660010160149054906101000a900463ffffffff168f8f8f878f8f604051610bf99998979695949392919061524f565b60405180910390a1505050505050505050505050565b60015473ffffffffffffffffffffffffffffffffffffffff163314610c95576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b60005a90506000610d238660406152e5565b610d2f896101446152fc565b610d3991906152fc565b9050368114610d74576040517fdfe9309000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60408051610120810182526014546bffffffffffffffffffffffff8116825263ffffffff6c01000000000000000000000000820416602083015262ffffff7001000000000000000000000000000000008204169282019290925261ffff730100000000000000000000000000000000000000830416606082015260ff75010000000000000000000000000000000000000000008304811660808301527601000000000000000000000000000000000000000000008304811615801560a08401527701000000000000000000000000000000000000000000000084048216151560c0840152780100000000000000000000000000000000000000000000000090930416151560e082015260155473ffffffffffffffffffffffffffffffffffffffff1661010082015290610ed3576040517f24522f3400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b336000908152600b602052604090205460ff16610f1c576040517f1099ed7500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6013548b3514610f58576040517fdfdcf8e700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6080810151610f6890600161530f565b60ff1687141580610f795750868514155b15610fb0576040517f0244f71a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610fc08b8b8b8b8b8b8b8b611f2a565b6000610fcc8b8b612193565b905060208c0135600881901c63ffffffff16610fe984848861224c565b836020015163ffffffff168163ffffffff16111561104157601480547fffffffffffffffffffffffffffffffff00000000ffffffffffffffffffffffff166c0100000000000000000000000063ffffffff8416021790555b5050505050505050505050505050565b60008060008580602001905181019061106a9190615495565b925092509250611080898989868989888861032b565b505050505050505050565b61109361109f565b61109c81612e5c565b50565b60005473ffffffffffffffffffffffffffffffffffffffff163314611120576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610c8c565b565b60005b6024548110156111e0576022600060248381548110611146576111466151e8565b600091825260208083209091015473ffffffffffffffffffffffffffffffffffffffff168352820192909252604001812080547fffffffff00000000000000000000000000000000000000000000000000000000168155600181019190915560020180547fffffffffffffffffffffffffffffffffffffffff000000000000000000000000169055806111d881615217565b915050611125565b506111ed602460006145f2565b60255460ff1660005b8351811015611754576000848281518110611213576112136151e8565b602002602001015190506000848381518110611231576112316151e8565b602002602001015190508173ffffffffffffffffffffffffffffffffffffffff1663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015611286573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112aa9190615640565b60ff16816060015160ff161415806113385750806040015173ffffffffffffffffffffffffffffffffffffffff1663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa15801561130c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113309190615640565b60ff16600814155b1561136f576040517fc1ab6dc100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161480156113db575060018460018111156113d9576113d961565d565b145b15611412576040517fc1ab6dc100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8216158061144d5750604081015173ffffffffffffffffffffffffffffffffffffffff16155b15611484576040517f8579befe00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff828116600090815260226020526040902054670100000000000000900416156114ee576040517f357d0cc400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6024805460018181019092557f7cd332d19b93bcabe3cce7ca0c18a052f57e5fd03b4758a09f30f5ddc4b22ec401805473ffffffffffffffffffffffffffffffffffffffff8086167fffffffffffffffffffffffff0000000000000000000000000000000000000000909216821790925560008181526022602090815260409182902086518154928801518489015160608a015160ff167b01000000000000000000000000000000000000000000000000000000027fffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffff9190981667010000000000000002167fffffffff000000000000000000000000000000000000000000ffffffffffffff62ffffff909216640100000000027fffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000090951663ffffffff9093169290921793909317929092169190911793909317835560808501519383019390935560a0840151600290920180546bffffffffffffffffffffffff9093167fffffffffffffffffffffffffffffffffffffffff0000000000000000000000009093169290921790915590517fca93cbe727c73163ec538f71be6c0a64877d7f1f6dd35d5ca7cbaef3a3e34ba390611737908490600060c08201905063ffffffff835116825262ffffff602084015116602083015273ffffffffffffffffffffffffffffffffffffffff604084015116604083015260ff6060840151166060830152608083015160808301526bffffffffffffffffffffffff60a08401511660a083015292915050565b60405180910390a25050808061174c90615217565b9150506111f6565b50505050565b600e546014546bffffffffffffffffffffffff1660005b600e548110156117cd576117ba600e8281548110611791576117916151e8565b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff168385612f51565b50806117c581615217565b915050611771565b5060255460009060ff16815b600e5481101561193e57600e81815481106117f6576117f66151e8565b6000918252602082200154600d805473ffffffffffffffffffffffffffffffffffffffff9092169550600c929184908110611833576118336151e8565b600091825260208083209091015473ffffffffffffffffffffffffffffffffffffffff9081168452838201949094526040928301822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00001690559286168152600b909252902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016905560018260018111156118d5576118d561565d565b14801561191a575073ffffffffffffffffffffffffffffffffffffffff83166000908152600b60205260409020546201000090046bffffffffffffffffffffffff1615155b1561192c5761192a600f84611f08565b505b8061193681615217565b9150506117d9565b5061194b600d60006145f2565b611957600e60006145f2565b6040805160808101825260008082526020820181905291810182905260608101829052905b8751811015611de757600c600089838151811061199b5761199b6151e8565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff1682528101919091526040016000205460ff1615611a06576040517f77cea0fa00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16888281518110611a3057611a306151e8565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1603611a85576040517f815e1d6400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405180604001604052806001151581526020018260ff16815250600c60008a8481518110611ab657611ab66151e8565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff1682528181019290925260400160002082518154939092015160ff16610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff921515929092167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000909316929092171790558651879082908110611b5e57611b5e6151e8565b60200260200101519350600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603611bce576040517f58a70a0a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff84166000908152600b60209081526040918290208251608081018452905460ff80821615801584526101008304909116938301939093526bffffffffffffffffffffffff6201000082048116948301949094526e01000000000000000000000000000090049092166060830152909250611c89576040517f6a7281ad00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600180835260ff80831660208086019182526014546bffffffffffffffffffffffff9081166060880190815273ffffffffffffffffffffffffffffffffffffffff8a166000908152600b909352604092839020885181549551948a0151925184166e010000000000000000000000000000027fffffffffffff000000000000000000000000ffffffffffffffffffffffffffff939094166201000002929092167fffffffffffff000000000000000000000000000000000000000000000000ffff94909616610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff921515929092167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000090951694909417179190911692909217919091179055836001811115611dc357611dc361565d565b03611dd557611dd3600f85611ee6565b505b80611ddf81615217565b91505061197c565b508651611dfb90600d9060208a0190614610565b508551611e0f90600e906020890190614610565b5050505050505050565b6000808a8a8a8a8a8a8a8a8a604051602001611e3d9998979695949392919061568c565b604080518083037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001815291905280516020909101207dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e01000000000000000000000000000000000000000000000000000000000000179b9a5050505050505050505050565b6000611ecd825490565b92915050565b6000611edf8383613159565b9392505050565b6000611edf8373ffffffffffffffffffffffffffffffffffffffff8416613183565b6000611edf8373ffffffffffffffffffffffffffffffffffffffff841661327d565b60008787604051611f3c929190615721565b604051908190038120611f53918b90602001615731565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815282825280516020918201208383019092526000808452908301819052909250906000805b8881101561212a57600185878360208110611fbf57611fbf6151e8565b611fcc91901a601b61530f565b8c8c85818110611fde57611fde6151e8565b905060200201358b8b86818110611ff757611ff76151e8565b9050602002013560405160008152602001604052604051612034949392919093845260ff9290921660208401526040830152606082015260800190565b6020604051602081039080840390855afa158015612056573d6000803e3d6000fd5b5050604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081015173ffffffffffffffffffffffffffffffffffffffff81166000908152600c602090815290849020838501909452925460ff8082161515808552610100909204169383019390935290955093509050612104576040517f0f4c073700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b826020015160080260ff166001901b84019350808061212290615217565b915050611fa2565b50827e01010101010101010101010101010101010101010101010101010101010101841614612185576040517fc103be2e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505050505050505050505050565b6121cc6040518060c001604052806000815260200160008152602001606081526020016060815260200160608152602001606081525090565b60006121da83850185615822565b60408101515160608201515191925090811415806121fd57508082608001515114155b8061220d5750808260a001515114155b15612244576040517fb55ac75400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b509392505050565b600082604001515167ffffffffffffffff81111561226c5761226c6146a7565b60405190808252806020026020018201604052801561233857816020015b6040805161020081018252600060e08201818152610100830182905261012083018290526101408301829052610160830182905261018083018290526101a083018290526101c083018290526101e0830182905282526020808301829052928201819052606082018190526080820181905260a0820181905260c082015282527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90920191018161228a5790505b50905060006040518060800160405280600061ffff16815260200160006bffffffffffffffffffffffff16815260200160006bffffffffffffffffffffffff16815260200160008152509050600085610100015173ffffffffffffffffffffffffffffffffffffffff166357e871e76040518163ffffffff1660e01b8152600401602060405180830381865afa1580156123d6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123fa9190614f93565b6101008701516040517f7810d12a00000000000000000000000000000000000000000000000000000000815236600482015291925060009173ffffffffffffffffffffffffffffffffffffffff90911690637810d12a90602401602060405180830381865afa158015612471573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124959190614f93565b905060005b8660400151518110156129255760046000886040015183815181106124c1576124c16151e8565b6020908102919091018101518252818101929092526040908101600020815161012081018352815460ff8082161515835261010080830490911615159583019590955263ffffffff620100008204811694830194909452660100000000000081048416606083015273ffffffffffffffffffffffffffffffffffffffff6a01000000000000000000009091048116608083015260018301546fffffffffffffffffffffffffffffffff811660a08401526bffffffffffffffffffffffff70010000000000000000000000000000000082041660c08401527c0100000000000000000000000000000000000000000000000000000000900490931660e08201526002909101549091169181019190915285518690839081106125e4576125e46151e8565b6020026020010151600001819052506126198760400151828151811061260c5761260c6151e8565b60200260200101516132cc565b85828151811061262b5761262b6151e8565b60200260200101516060019060018111156126485761264861565d565b9081600181111561265b5761265b61565d565b815250506126bf87604001518281518110612678576126786151e8565b60200260200101518489608001518481518110612697576126976151e8565b60200260200101518885815181106126b1576126b16151e8565b60200260200101518c613377565b8683815181106126d1576126d16151e8565b60200260200101516020018784815181106126ee576126ee6151e8565b602002602001015160c0018281525082151515158152505050848181518110612719576127196151e8565b602002602001015160200151156127495760018460000181815161273d919061590f565b61ffff1690525061274e565b612913565b6127b4858281518110612763576127636151e8565b6020026020010151600001516080015188606001518381518110612789576127896151e8565b60200260200101518960a0015184815181106127a7576127a76151e8565b6020026020010151613496565b8683815181106127c6576127c66151e8565b60200260200101516040018784815181106127e3576127e36151e8565b602002602001015160800182815250821515151581525050508760800151600161280d919061530f565b61281b9060ff1660406152e5565b6103a48860a001518381518110612834576128346151e8565b60200260200101515161284791906152fc565b61285191906152fc565b858281518110612863576128636151e8565b602002602001015160a0018181525050848181518110612885576128856151e8565b602002602001015160a00151846060018181516128a291906152fc565b90525084518590829081106128b9576128b96151e8565b602002602001015160800151866128d091906151a0565b9550612913876040015182815181106128eb576128eb6151e8565b602002602001015184878481518110612906576129066151e8565b60200260200101516136b1565b8061291d81615217565b91505061249a565b50825161ffff1660000361293c5750505050505050565b61c80061294a3660106152e5565b5a61295590886151a0565b61295f91906152fc565b61296991906152fc565b8351909550613778906129809061ffff1687615959565b61298a91906152fc565b60408051608081018252600080825260208201819052918101829052606081018290529196506129b9896137b6565b905060005b886040015151811015612cf5578681815181106129dd576129dd6151e8565b60200260200101516020015115612ce357801580612a75575086612a026001836151a0565b81518110612a1257612a126151e8565b602002602001015160000151610100015173ffffffffffffffffffffffffffffffffffffffff16878281518110612a4b57612a4b6151e8565b602002602001015160000151610100015173ffffffffffffffffffffffffffffffffffffffff1614155b15612aa957612aa68a888381518110612a9057612a906151e8565b60200260200101516000015161010001516138a0565b92505b6000612bc78b6040518061012001604052808b8681518110612acd57612acd6151e8565b60200260200101516080015181526020018c81526020018a606001518c8781518110612afb57612afb6151e8565b602002602001015160a001518a612b1291906152e5565b612b1c9190615959565b81526020018d6000015181526020018d6020015181526020018681526020018b8681518110612b4d57612b4d6151e8565b602002602001015160000151610100015173ffffffffffffffffffffffffffffffffffffffff168152602001878152602001600115158152508c604001518581518110612b9c57612b9c6151e8565b60200260200101518b8681518110612bb657612bb66151e8565b602002602001015160000151613a1c565b9050806060015187604001818151612bdf919061596d565b6bffffffffffffffffffffffff169052506040810151602088018051612c0690839061596d565b6bffffffffffffffffffffffff169052508751889083908110612c2b57612c2b6151e8565b60200260200101516040015115158a604001518381518110612c4f57612c4f6151e8565b60200260200101517fad8cc9579b21dfe2c2f6ea35ba15b656e46b4f5b0cb424f52739b8ce5cac9c5b83602001518460000151612c8c919061596d565b8b8681518110612c9e57612c9e6151e8565b6020026020010151608001518d8f608001518881518110612cc157612cc16151e8565b6020026020010151604051612cd99493929190615992565b60405180910390a3505b80612ced81615217565b9150506129be565b505050602083810151336000908152600b90925260409091208054600290612d329084906201000090046bffffffffffffffffffffffff1661596d565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055508260400151601460000160008282829054906101000a90046bffffffffffffffffffffffff16612d90919061596d565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff16021790555082604001518360200151612dd2919061596d565b6bffffffffffffffffffffffff16602160007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254612e4e91906152fc565b909155505050505050505050565b3373ffffffffffffffffffffffffffffffffffffffff821603612edb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610c8c565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b73ffffffffffffffffffffffffffffffffffffffff83166000908152600b602090815260408083208151608081018352905460ff80821615801584526101008304909116948301949094526bffffffffffffffffffffffff6201000082048116938301939093526e010000000000000000000000000000900490911660608201529061314d576000816060015185612fe991906159cf565b90506000612ff785836159f4565b9050808360400181815161300b919061596d565b6bffffffffffffffffffffffff169052506130268582615a1f565b83606001818151613037919061596d565b6bffffffffffffffffffffffff90811690915273ffffffffffffffffffffffffffffffffffffffff89166000908152600b602090815260409182902087518154928901519389015160608a015186166e010000000000000000000000000000027fffffffffffff000000000000000000000000ffffffffffffffffffffffffffff919096166201000002167fffffffffffff000000000000000000000000000000000000000000000000ffff60ff95909516610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff921515929092167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000909416939093171792909216179190911790555050505b60400151949350505050565b6000826000018281548110613170576131706151e8565b9060005260206000200154905092915050565b6000818152600183016020526040812054801561326c5760006131a76001836151a0565b85549091506000906131bb906001906151a0565b90508181146132205760008660000182815481106131db576131db6151e8565b90600052602060002001549050808760000184815481106131fe576131fe6151e8565b6000918252602080832090910192909255918252600188019052604090208390555b855486908061323157613231615a4f565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050611ecd565b6000915050611ecd565b5092915050565b60008181526001830160205260408120546132c457508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155611ecd565b506000611ecd565b6000818160045b600f811015613359577fff000000000000000000000000000000000000000000000000000000000000008216838260208110613311576133116151e8565b1a60f81b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161461334757506000949350505050565b8061335181615217565b9150506132d3565b5081600f1a600181111561336f5761336f61565d565b949350505050565b6000808080856060015160018111156133925761339261565d565b036133b8576133a48888888888613e8b565b6133b35760009250905061348c565b613430565b6001856060015160018111156133d0576133d061565d565b036133fe5760006133e389898988614015565b92509050806133f8575060009250905061348c565b50613430565b6040517ff2b2d41200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b84516060015163ffffffff16871061348557877fc3237c8807c467c1b39b8d0395eff077313e691bf0a7388106792564ebfd5636876040516134729190614d1a565b60405180910390a260009250905061348c565b6001925090505b9550959350505050565b601454600090819077010000000000000000000000000000000000000000000000900460ff16156134f3576040517f37ed32e800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601480547fffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffff16770100000000000000000000000000000000000000000000001790556040517f4585e33b0000000000000000000000000000000000000000000000000000000090613568908590602401614d1a565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931790925290517f79188d1600000000000000000000000000000000000000000000000000000000815290935073ffffffffffffffffffffffffffffffffffffffff8616906379188d169061363b9087908790600401615a7e565b60408051808303816000875af1158015613659573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061367d9190615a97565b601480547fffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffff16905590969095509350505050565b6000816060015160018111156136c9576136c961565d565b0361372d57600083815260046020526040902060010180547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167c010000000000000000000000000000000000000000000000000000000063ffffffff851602179055505050565b6001816060015160018111156137455761374561565d565b036137b15760c08101805160009081526008602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055915191517fa4a4e334c0e330143f9437484fe516c13bc560b86b5b0daf58e7084aaac228f29190a25b505050565b60008060007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015613826573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061384a9190615adf565b5093505092505060008213158061386057508042105b8061389057506000846040015162ffffff16118015613890575061388481426151a0565b846040015162ffffff16105b15613276575050601b5492915050565b60408051608081018252600080825260208083018281528385018381526060850184905273ffffffffffffffffffffffffffffffffffffffff878116855260229093528584208054640100000000810462ffffff1690925263ffffffff82169092527b01000000000000000000000000000000000000000000000000000000810460ff16855285517ffeaf968c00000000000000000000000000000000000000000000000000000000815295519495919484936701000000000000009092049091169163feaf968c9160048083019260a09291908290030181865afa15801561398d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906139b19190615adf565b509350509250506000821315806139c757508042105b806139f757506000866040015162ffffff161180156139f757506139eb81426151a0565b866040015162ffffff16105b15613a0b5760018301546060850152613a13565b606084018290525b50505092915050565b604080516101008101825260008082526020808301829052928201819052606082018190526080820181905260a0820181905260c0820181905260e08201529082015115613ab25760008381526023602090815260409182902082518084018452905463ffffffff811680835262ffffff640100000000909204821692840192835260e089018051909401529051915191169101525b6000613abe8686614222565b60c0840151602082015182519293509091600091613adb9161596d565b60e08801515190915060ff16600060128210613af8576001613b0e565b613b038260126151a0565b613b0e90600a615c4f565b9050600060128311613b21576001613b37565b613b2c6012846151a0565b613b3790600a615c4f565b905085600001516bffffffffffffffffffffffff16856bffffffffffffffffffffffff161015613bdf57849350613bb3818b60800151613b7791906152e5565b838c60e0015160600151886bffffffffffffffffffffffff16613b9a91906152e5565b613ba491906152e5565b613bae9190615959565b614550565b6bffffffffffffffffffffffff9081166040880152600060608801819052602088015285168652613d05565b836bffffffffffffffffffffffff16856bffffffffffffffffffffffff161015613d0557849350613c6d86604001516bffffffffffffffffffffffff16828c60800151613c2c91906152e5565b848d60e0015160600151896bffffffffffffffffffffffff16613c4f91906152e5565b613c5991906152e5565b613c639190615959565b613bae91906151a0565b6bffffffffffffffffffffffff1660608088019190915260e08b01510151613cf190613c9a9084906152e5565b6001848d60e0015160600151613cb091906152e5565b613cba91906151a0565b838d608001518a606001516bffffffffffffffffffffffff16613cdd91906152e5565b613ce791906152e5565b613ba491906152fc565b6bffffffffffffffffffffffff1660208701525b60008981526004602052604090206001018054859190601090613d4b90849070010000000000000000000000000000000090046bffffffffffffffffffffffff166159cf565b82546101009290920a6bffffffffffffffffffffffff81810219909316918316021790915560008b81526004602052604081206001018054928816935091613da69084906fffffffffffffffffffffffffffffffff16615c5b565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff160217905550836bffffffffffffffffffffffff16602160008c60c0015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254613e3d91906151a0565b92505081905550887f801ba6ed51146ffe3e99d1dbd9dd0f4de6292e78a9a34c39c0183de17b3f40fc87604051613e749190615c84565b60405180910390a250939998505050505050505050565b60008084806020019051810190613ea29190615d44565b845160e00151815191925063ffffffff90811691161015613eff57867f405288ea7be309e16cfdf481367f90a413e1d4634fcdaf8966546db9b93012e886604051613eed9190614d1a565b60405180910390a2600091505061400c565b8260e001518015613fbf5750602081015115801590613fbf5750602081015161010084015182516040517f85df51fd00000000000000000000000000000000000000000000000000000000815263ffffffff909116600482015273ffffffffffffffffffffffffffffffffffffffff909116906385df51fd90602401602060405180830381865afa158015613f98573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613fbc9190614f93565b14155b80613fd15750805163ffffffff168611155b1561400657867f6aa7f60c176da7af894b384daea2249497448137f5943c1237ada8bc92bdc30186604051613eed9190614d1a565b60019150505b95945050505050565b60008060008480602001905181019061402e9190615d9c565b905060008782600001518360200151846040015160405160200161409094939291909384526020840192909252604083015260e01b7fffffffff0000000000000000000000000000000000000000000000000000000016606082015260640190565b6040516020818303038152906040528051906020012090508460e00151801561416b575060808201511580159061416b5750608082015161010086015160608401516040517f85df51fd00000000000000000000000000000000000000000000000000000000815263ffffffff909116600482015273ffffffffffffffffffffffffffffffffffffffff909116906385df51fd90602401602060405180830381865afa158015614144573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906141689190614f93565b14155b80614180575086826060015163ffffffff1610155b156141ca57877f6aa7f60c176da7af894b384daea2249497448137f5943c1237ada8bc92bdc301876040516141b59190614d1a565b60405180910390a26000935091506142199050565b60008181526008602052604090205460ff161561421157877f405288ea7be309e16cfdf481367f90a413e1d4634fcdaf8966546db9b93012e8876040516141b59190614d1a565b600193509150505b94509492505050565b6040805161010081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e081019190915260008260e001516000015160ff1690506000846060015161ffff16846060015161428d91906152e5565b905083610100015180156142a05750803a105b156142a857503a5b6000601283116142b95760016142cf565b6142c46012846151a0565b6142cf90600a615c4f565b90506000601284106142e25760016142f8565b6142ed8460126151a0565b6142f890600a615c4f565b905060008660a0015187604001518860200151896000015161431a91906152fc565b61432490876152e5565b61432e91906152fc565b61433891906152e5565b905061437b828860e001516060015161435191906152e5565b6001848a60e001516060015161436791906152e5565b61437191906151a0565b613ce786856152e5565b6bffffffffffffffffffffffff168652608087015161439e90613bae9083615959565b6bffffffffffffffffffffffff1660408088019190915260e088015101516000906143d79062ffffff16683635c9adc5dea000006152e5565b9050600081633b9aca008a60a001518b60e001516020015163ffffffff168c604001518d600001518b61440a91906152e5565b61441491906152fc565b61441e91906152e5565b61442891906152e5565b6144329190615959565b61443c91906152fc565b905061447f848a60e001516060015161445591906152e5565b6001868c60e001516060015161446b91906152e5565b61447591906151a0565b613ce788856152e5565b6bffffffffffffffffffffffff16602089015260808901516144a590613bae9083615959565b6bffffffffffffffffffffffff16606089015260c089015173ffffffffffffffffffffffffffffffffffffffff166080808a01919091528901516144e890614550565b6bffffffffffffffffffffffff1660a0808a019190915289015161450b90614550565b6bffffffffffffffffffffffff1660c089015260e08901516060015161453090614550565b6bffffffffffffffffffffffff1660e08901525050505050505092915050565b60006bffffffffffffffffffffffff8211156145ee576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203960448201527f36206269747300000000000000000000000000000000000000000000000000006064820152608401610c8c565b5090565b508054600082559060005260206000209081019061109c9190614692565b82805482825590600052602060002090810192821561468a579160200282015b8281111561468a57825182547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff909116178255602090920191600190910190614630565b506145ee9291505b5b808211156145ee5760008155600101614693565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051610200810167ffffffffffffffff811182821017156146fa576146fa6146a7565b60405290565b60405160c0810167ffffffffffffffff811182821017156146fa576146fa6146a7565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff8111828210171561476a5761476a6146a7565b604052919050565b600067ffffffffffffffff82111561478c5761478c6146a7565b5060051b60200190565b73ffffffffffffffffffffffffffffffffffffffff8116811461109c57600080fd5b80356147c381614796565b919050565b600082601f8301126147d957600080fd5b813560206147ee6147e983614772565b614723565b82815260059290921b8401810191818101908684111561480d57600080fd5b8286015b8481101561483157803561482481614796565b8352918301918301614811565b509695505050505050565b60ff8116811461109c57600080fd5b80356147c38161483c565b63ffffffff8116811461109c57600080fd5b80356147c381614856565b801515811461109c57600080fd5b80356147c381614873565b62ffffff8116811461109c57600080fd5b80356147c38161488c565b61ffff8116811461109c57600080fd5b80356147c3816148a8565b600061020082840312156148d657600080fd5b6148de6146d6565b90506148e982614868565b81526148f760208301614868565b602082015261490860408301614868565b6040820152614919606083016147b8565b606082015261492a60808301614881565b608082015261493b60a0830161489d565b60a082015261494c60c08301614868565b60c082015261495d60e08301614868565b60e08201526101006149708184016147b8565b908201526101206149828382016148b8565b908201526101406149948382016147b8565b90820152610160828101359082015261018080830135908201526101a080830135908201526101c08083013567ffffffffffffffff8111156149d557600080fd5b6149e1858286016147c8565b8284015250506101e06149f58184016147b8565b9082015292915050565b803567ffffffffffffffff811681146147c357600080fd5b600082601f830112614a2857600080fd5b813567ffffffffffffffff811115614a4257614a426146a7565b614a7360207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601614723565b818152846020838601011115614a8857600080fd5b816020850160208301376000918101602001919091529392505050565b6bffffffffffffffffffffffff8116811461109c57600080fd5b600082601f830112614ad057600080fd5b81356020614ae06147e983614772565b82815260c09283028501820192828201919087851115614aff57600080fd5b8387015b85811015614b915781818a031215614b1b5760008081fd5b614b23614700565b8135614b2e81614856565b815281860135614b3d8161488c565b81870152604082810135614b5081614796565b90820152606082810135614b638161483c565b908201526080828101359082015260a080830135614b8081614aa5565b908201528452928401928101614b03565b5090979650505050505050565b600080600080600080600080610100898b031215614bbb57600080fd5b883567ffffffffffffffff80821115614bd357600080fd5b614bdf8c838d016147c8565b995060208b0135915080821115614bf557600080fd5b614c018c838d016147c8565b9850614c0f60408c0161484b565b975060608b0135915080821115614c2557600080fd5b614c318c838d016148c3565b9650614c3f60808c016149ff565b955060a08b0135915080821115614c5557600080fd5b614c618c838d01614a17565b945060c08b0135915080821115614c7757600080fd5b614c838c838d016147c8565b935060e08b0135915080821115614c9957600080fd5b50614ca68b828c01614abf565b9150509295985092959890939650565b6000815180845260005b81811015614cdc57602081850181015186830182015201614cc0565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b602081526000611edf6020830184614cb6565b60008083601f840112614d3f57600080fd5b50813567ffffffffffffffff811115614d5757600080fd5b6020830191508360208260051b8501011115614d7257600080fd5b9250929050565b60008060008060008060008060e0898b031215614d9557600080fd5b606089018a811115614da657600080fd5b8998503567ffffffffffffffff80821115614dc057600080fd5b818b0191508b601f830112614dd457600080fd5b813581811115614de357600080fd5b8c6020828501011115614df557600080fd5b6020830199508098505060808b0135915080821115614e1357600080fd5b614e1f8c838d01614d2d565b909750955060a08b0135915080821115614e3857600080fd5b50614e458b828c01614d2d565b999c989b50969995989497949560c00135949350505050565b60008060008060008060c08789031215614e7757600080fd5b863567ffffffffffffffff80821115614e8f57600080fd5b614e9b8a838b016147c8565b97506020890135915080821115614eb157600080fd5b614ebd8a838b016147c8565b9650614ecb60408a0161484b565b95506060890135915080821115614ee157600080fd5b614eed8a838b01614a17565b9450614efb60808a016149ff565b935060a0890135915080821115614f1157600080fd5b50614f1e89828a01614a17565b9150509295509295509295565b600060208284031215614f3d57600080fd5b8135611edf81614796565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60ff818116838216029081169081811461327657613276614f48565b600060208284031215614fa557600080fd5b5051919050565b63ffffffff81811683821601908082111561327657613276614f48565b600081518084526020808501945080840160005b8381101561500f57815173ffffffffffffffffffffffffffffffffffffffff1687529582019590820190600101614fdd565b509495945050505050565b6020815261503160208201835163ffffffff169052565b6000602083015161504a604084018263ffffffff169052565b50604083015163ffffffff8116606084015250606083015173ffffffffffffffffffffffffffffffffffffffff8116608084015250608083015180151560a08401525060a083015162ffffff811660c08401525060c083015163ffffffff811660e08401525060e08301516101006150c98185018363ffffffff169052565b84015190506101206150f28482018373ffffffffffffffffffffffffffffffffffffffff169052565b84015190506101406151098482018361ffff169052565b84015190506101606151328482018373ffffffffffffffffffffffffffffffffffffffff169052565b840151610180848101919091528401516101a0808501919091528401516101c0808501919091528401516102006101e08086018290529192509061517a610220860184614fc9565b95015173ffffffffffffffffffffffffffffffffffffffff169301929092525090919050565b81810381811115611ecd57611ecd614f48565b6000816151c2576151c2614f48565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361524857615248614f48565b5060010190565b600061012063ffffffff808d1684528b6020850152808b1660408501525080606084015261527f8184018a614fc9565b905082810360808401526152938189614fc9565b905060ff871660a084015282810360c08401526152b08187614cb6565b905067ffffffffffffffff851660e08401528281036101008401526152d58185614cb6565b9c9b505050505050505050505050565b8082028115828204841417611ecd57611ecd614f48565b80820180821115611ecd57611ecd614f48565b60ff8181168382160190811115611ecd57611ecd614f48565b80516147c381614856565b80516147c381614796565b80516147c381614873565b80516147c38161488c565b80516147c3816148a8565b600082601f83011261537057600080fd5b815160206153806147e983614772565b82815260059290921b8401810191818101908684111561539f57600080fd5b8286015b848110156148315780516153b681614796565b83529183019183016153a3565b600082601f8301126153d457600080fd5b815160206153e46147e983614772565b82815260c0928302850182019282820191908785111561540357600080fd5b8387015b85811015614b915781818a03121561541f5760008081fd5b615427614700565b815161543281614856565b8152818601516154418161488c565b8187015260408281015161545481614796565b908201526060828101516154678161483c565b908201526080828101519082015260a08083015161548481614aa5565b908201528452928401928101615407565b6000806000606084860312156154aa57600080fd5b835167ffffffffffffffff808211156154c257600080fd5b9085019061020082880312156154d757600080fd5b6154df6146d6565b6154e883615328565b81526154f660208401615328565b602082015261550760408401615328565b604082015261551860608401615333565b60608201526155296080840161533e565b608082015261553a60a08401615349565b60a082015261554b60c08401615328565b60c082015261555c60e08401615328565b60e082015261010061556f818501615333565b90820152610120615581848201615354565b90820152610140615593848201615333565b90820152610160838101519082015261018080840151908201526101a080840151908201526101c080840151838111156155cc57600080fd5b6155d88a82870161535f565b8284015250506101e06155ec818501615333565b90820152602087015190955091508082111561560757600080fd5b6156138783880161535f565b9350604086015191508082111561562957600080fd5b50615636868287016153c3565b9150509250925092565b60006020828403121561565257600080fd5b8151611edf8161483c565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b60006101208b835273ffffffffffffffffffffffffffffffffffffffff8b16602084015267ffffffffffffffff808b1660408501528160608501526156d38285018b614fc9565b915083820360808501526156e7828a614fc9565b915060ff881660a085015283820360c08501526157048288614cb6565b90861660e085015283810361010085015290506152d58185614cb6565b8183823760009101908152919050565b8281526080810160608360208401379392505050565b600082601f83011261575857600080fd5b813560206157686147e983614772565b82815260059290921b8401810191818101908684111561578757600080fd5b8286015b84811015614831578035835291830191830161578b565b600082601f8301126157b357600080fd5b813560206157c36147e983614772565b82815260059290921b840181019181810190868411156157e257600080fd5b8286015b8481101561483157803567ffffffffffffffff8111156158065760008081fd5b6158148986838b0101614a17565b8452509183019183016157e6565b60006020828403121561583457600080fd5b813567ffffffffffffffff8082111561584c57600080fd5b9083019060c0828603121561586057600080fd5b615868614700565b823581526020830135602082015260408301358281111561588857600080fd5b61589487828601615747565b6040830152506060830135828111156158ac57600080fd5b6158b887828601615747565b6060830152506080830135828111156158d057600080fd5b6158dc878286016157a2565b60808301525060a0830135828111156158f457600080fd5b615900878286016157a2565b60a08301525095945050505050565b61ffff81811683821601908082111561327657613276614f48565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b6000826159685761596861592a565b500490565b6bffffffffffffffffffffffff81811683821601908082111561327657613276614f48565b6bffffffffffffffffffffffff851681528360208201528260408201526080606082015260006159c56080830184614cb6565b9695505050505050565b6bffffffffffffffffffffffff82811682821603908082111561327657613276614f48565b60006bffffffffffffffffffffffff80841680615a1357615a1361592a565b92169190910492915050565b6bffffffffffffffffffffffff818116838216028082169190828114615a4757615a47614f48565b505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b82815260406020820152600061336f6040830184614cb6565b60008060408385031215615aaa57600080fd5b8251615ab581614873565b6020939093015192949293505050565b805169ffffffffffffffffffff811681146147c357600080fd5b600080600080600060a08688031215615af757600080fd5b615b0086615ac5565b9450602086015193506040860151925060608601519150615b2360808701615ac5565b90509295509295909350565b600181815b80851115615b8857817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04821115615b6e57615b6e614f48565b80851615615b7b57918102915b93841c9390800290615b34565b509250929050565b600082615b9f57506001611ecd565b81615bac57506000611ecd565b8160018114615bc25760028114615bcc57615be8565b6001915050611ecd565b60ff841115615bdd57615bdd614f48565b50506001821b611ecd565b5060208310610133831016604e8410600b8410161715615c0b575081810a611ecd565b615c158383615b2f565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04821115615c4757615c47614f48565b029392505050565b6000611edf8383615b90565b6fffffffffffffffffffffffffffffffff81811683821601908082111561327657613276614f48565b6000610100820190506bffffffffffffffffffffffff8084511683528060208501511660208401528060408501511660408401528060608501511660608401525073ffffffffffffffffffffffffffffffffffffffff608084015116608083015260a0830151615d0460a08401826bffffffffffffffffffffffff169052565b5060c0830151615d2460c08401826bffffffffffffffffffffffff169052565b5060e083015161327660e08401826bffffffffffffffffffffffff169052565b600060408284031215615d5657600080fd5b6040516040810181811067ffffffffffffffff82111715615d7957615d796146a7565b6040528251615d8781614856565b81526020928301519281019290925250919050565b600060a08284031215615dae57600080fd5b60405160a0810181811067ffffffffffffffff82111715615dd157615dd16146a7565b806040525082518152602083015160208201526040830151615df281614856565b60408201526060830151615e0581614856565b6060820152608092830151928101929092525091905056fea164736f6c6343000813000a", } var AutomationRegistryABI = AutomationRegistryMetaData.ABI 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 bf98608aab..19bc49298f 100644 --- a/core/gethwrappers/generated/chain_module_base/chain_module_base.go +++ b/core/gethwrappers/generated/chain_module_base/chain_module_base.go @@ -29,8 +29,8 @@ 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\":[],\"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\"}]", - Bin: "0x608060405234801561001057600080fd5b5061015c806100206000396000f3fe608060405234801561001057600080fd5b50600436106100675760003560e01c806357e871e71161005057806357e871e71461009a57806385df51fd146100a0578063de9ee35e146100b357600080fd5b8063125441401461006c57806318b8f61314610093575b600080fd5b61008061007a3660046100f6565b50600090565b6040519081526020015b60405180910390f35b6000610080565b43610080565b6100806100ae3660046100f6565b6100c9565b6040805161012c8152600060208201520161008a565b600043821015806100e457506101006100e2834361010f565b115b156100f157506000919050565b504090565b60006020828403121561010857600080fd5b5035919050565b81810381811115610149577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b9291505056fea164736f6c6343000813000a", + 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", } var ChainModuleBaseABI = ChainModuleBaseMetaData.ABI @@ -213,9 +213,9 @@ func (_ChainModuleBase *ChainModuleBaseCallerSession) BlockNumber() (*big.Int, e return _ChainModuleBase.Contract.BlockNumber(&_ChainModuleBase.CallOpts) } -func (_ChainModuleBase *ChainModuleBaseCaller) GetCurrentL1Fee(opts *bind.CallOpts) (*big.Int, error) { +func (_ChainModuleBase *ChainModuleBaseCaller) GetCurrentL1Fee(opts *bind.CallOpts, arg0 *big.Int) (*big.Int, error) { var out []interface{} - err := _ChainModuleBase.contract.Call(opts, &out, "getCurrentL1Fee") + err := _ChainModuleBase.contract.Call(opts, &out, "getCurrentL1Fee", arg0) if err != nil { return *new(*big.Int), err @@ -227,12 +227,12 @@ func (_ChainModuleBase *ChainModuleBaseCaller) GetCurrentL1Fee(opts *bind.CallOp } -func (_ChainModuleBase *ChainModuleBaseSession) GetCurrentL1Fee() (*big.Int, error) { - return _ChainModuleBase.Contract.GetCurrentL1Fee(&_ChainModuleBase.CallOpts) +func (_ChainModuleBase *ChainModuleBaseSession) GetCurrentL1Fee(arg0 *big.Int) (*big.Int, error) { + return _ChainModuleBase.Contract.GetCurrentL1Fee(&_ChainModuleBase.CallOpts, arg0) } -func (_ChainModuleBase *ChainModuleBaseCallerSession) GetCurrentL1Fee() (*big.Int, error) { - return _ChainModuleBase.Contract.GetCurrentL1Fee(&_ChainModuleBase.CallOpts) +func (_ChainModuleBase *ChainModuleBaseCallerSession) GetCurrentL1Fee(arg0 *big.Int) (*big.Int, error) { + return _ChainModuleBase.Contract.GetCurrentL1Fee(&_ChainModuleBase.CallOpts, arg0) } func (_ChainModuleBase *ChainModuleBaseCaller) GetGasOverhead(opts *bind.CallOpts) (GetGasOverhead, @@ -301,7 +301,7 @@ type ChainModuleBaseInterface interface { BlockNumber(opts *bind.CallOpts) (*big.Int, error) - GetCurrentL1Fee(opts *bind.CallOpts) (*big.Int, error) + GetCurrentL1Fee(opts *bind.CallOpts, arg0 *big.Int) (*big.Int, error) GetGasOverhead(opts *bind.CallOpts) (GetGasOverhead, diff --git a/core/gethwrappers/generated/chain_reader_tester/chain_reader_tester.go b/core/gethwrappers/generated/chain_reader_tester/chain_reader_tester.go index 751df82269..7adf4d8530 100644 --- a/core/gethwrappers/generated/chain_reader_tester/chain_reader_tester.go +++ b/core/gethwrappers/generated/chain_reader_tester/chain_reader_tester.go @@ -52,8 +52,8 @@ type TestStruct struct { } var ChainReaderTesterMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"int32\",\"name\":\"field\",\"type\":\"int32\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"differentField\",\"type\":\"string\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"oracleId\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8[32]\",\"name\":\"oracleIds\",\"type\":\"uint8[32]\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"Account\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"Accounts\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"int192\",\"name\":\"bigField\",\"type\":\"int192\"},{\"components\":[{\"internalType\":\"bytes2\",\"name\":\"FixedBytes\",\"type\":\"bytes2\"},{\"components\":[{\"internalType\":\"int64\",\"name\":\"IntVal\",\"type\":\"int64\"},{\"internalType\":\"string\",\"name\":\"S\",\"type\":\"string\"}],\"internalType\":\"structInnerTestStruct\",\"name\":\"Inner\",\"type\":\"tuple\"}],\"indexed\":false,\"internalType\":\"structMidLevelTestStruct\",\"name\":\"nestedStruct\",\"type\":\"tuple\"}],\"name\":\"Triggered\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"string\",\"name\":\"fieldHash\",\"type\":\"string\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"field\",\"type\":\"string\"}],\"name\":\"TriggeredEventWithDynamicTopic\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"int32\",\"name\":\"field1\",\"type\":\"int32\"},{\"indexed\":true,\"internalType\":\"int32\",\"name\":\"field2\",\"type\":\"int32\"},{\"indexed\":true,\"internalType\":\"int32\",\"name\":\"field3\",\"type\":\"int32\"}],\"name\":\"TriggeredWithFourTopics\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"int32\",\"name\":\"field\",\"type\":\"int32\"},{\"internalType\":\"string\",\"name\":\"differentField\",\"type\":\"string\"},{\"internalType\":\"uint8\",\"name\":\"oracleId\",\"type\":\"uint8\"},{\"internalType\":\"uint8[32]\",\"name\":\"oracleIds\",\"type\":\"uint8[32]\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"address[]\",\"name\":\"accounts\",\"type\":\"address[]\"},{\"internalType\":\"int192\",\"name\":\"bigField\",\"type\":\"int192\"},{\"components\":[{\"internalType\":\"bytes2\",\"name\":\"FixedBytes\",\"type\":\"bytes2\"},{\"components\":[{\"internalType\":\"int64\",\"name\":\"IntVal\",\"type\":\"int64\"},{\"internalType\":\"string\",\"name\":\"S\",\"type\":\"string\"}],\"internalType\":\"structInnerTestStruct\",\"name\":\"Inner\",\"type\":\"tuple\"}],\"internalType\":\"structMidLevelTestStruct\",\"name\":\"nestedStruct\",\"type\":\"tuple\"}],\"name\":\"addTestStruct\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAlterablePrimitiveValue\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getDifferentPrimitiveValue\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"i\",\"type\":\"uint256\"}],\"name\":\"getElementAtIndex\",\"outputs\":[{\"components\":[{\"internalType\":\"int32\",\"name\":\"Field\",\"type\":\"int32\"},{\"internalType\":\"string\",\"name\":\"DifferentField\",\"type\":\"string\"},{\"internalType\":\"uint8\",\"name\":\"OracleId\",\"type\":\"uint8\"},{\"internalType\":\"uint8[32]\",\"name\":\"OracleIds\",\"type\":\"uint8[32]\"},{\"internalType\":\"address\",\"name\":\"Account\",\"type\":\"address\"},{\"internalType\":\"address[]\",\"name\":\"Accounts\",\"type\":\"address[]\"},{\"internalType\":\"int192\",\"name\":\"BigField\",\"type\":\"int192\"},{\"components\":[{\"internalType\":\"bytes2\",\"name\":\"FixedBytes\",\"type\":\"bytes2\"},{\"components\":[{\"internalType\":\"int64\",\"name\":\"IntVal\",\"type\":\"int64\"},{\"internalType\":\"string\",\"name\":\"S\",\"type\":\"string\"}],\"internalType\":\"structInnerTestStruct\",\"name\":\"Inner\",\"type\":\"tuple\"}],\"internalType\":\"structMidLevelTestStruct\",\"name\":\"NestedStruct\",\"type\":\"tuple\"}],\"internalType\":\"structTestStruct\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getPrimitiveValue\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getSliceValue\",\"outputs\":[{\"internalType\":\"uint64[]\",\"name\":\"\",\"type\":\"uint64[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int32\",\"name\":\"field\",\"type\":\"int32\"},{\"internalType\":\"string\",\"name\":\"differentField\",\"type\":\"string\"},{\"internalType\":\"uint8\",\"name\":\"oracleId\",\"type\":\"uint8\"},{\"internalType\":\"uint8[32]\",\"name\":\"oracleIds\",\"type\":\"uint8[32]\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"address[]\",\"name\":\"accounts\",\"type\":\"address[]\"},{\"internalType\":\"int192\",\"name\":\"bigField\",\"type\":\"int192\"},{\"components\":[{\"internalType\":\"bytes2\",\"name\":\"FixedBytes\",\"type\":\"bytes2\"},{\"components\":[{\"internalType\":\"int64\",\"name\":\"IntVal\",\"type\":\"int64\"},{\"internalType\":\"string\",\"name\":\"S\",\"type\":\"string\"}],\"internalType\":\"structInnerTestStruct\",\"name\":\"Inner\",\"type\":\"tuple\"}],\"internalType\":\"structMidLevelTestStruct\",\"name\":\"nestedStruct\",\"type\":\"tuple\"}],\"name\":\"returnSeen\",\"outputs\":[{\"components\":[{\"internalType\":\"int32\",\"name\":\"Field\",\"type\":\"int32\"},{\"internalType\":\"string\",\"name\":\"DifferentField\",\"type\":\"string\"},{\"internalType\":\"uint8\",\"name\":\"OracleId\",\"type\":\"uint8\"},{\"internalType\":\"uint8[32]\",\"name\":\"OracleIds\",\"type\":\"uint8[32]\"},{\"internalType\":\"address\",\"name\":\"Account\",\"type\":\"address\"},{\"internalType\":\"address[]\",\"name\":\"Accounts\",\"type\":\"address[]\"},{\"internalType\":\"int192\",\"name\":\"BigField\",\"type\":\"int192\"},{\"components\":[{\"internalType\":\"bytes2\",\"name\":\"FixedBytes\",\"type\":\"bytes2\"},{\"components\":[{\"internalType\":\"int64\",\"name\":\"IntVal\",\"type\":\"int64\"},{\"internalType\":\"string\",\"name\":\"S\",\"type\":\"string\"}],\"internalType\":\"structInnerTestStruct\",\"name\":\"Inner\",\"type\":\"tuple\"}],\"internalType\":\"structMidLevelTestStruct\",\"name\":\"NestedStruct\",\"type\":\"tuple\"}],\"internalType\":\"structTestStruct\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"name\":\"setAlterablePrimitiveValue\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int32\",\"name\":\"field\",\"type\":\"int32\"},{\"internalType\":\"string\",\"name\":\"differentField\",\"type\":\"string\"},{\"internalType\":\"uint8\",\"name\":\"oracleId\",\"type\":\"uint8\"},{\"internalType\":\"uint8[32]\",\"name\":\"oracleIds\",\"type\":\"uint8[32]\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"address[]\",\"name\":\"accounts\",\"type\":\"address[]\"},{\"internalType\":\"int192\",\"name\":\"bigField\",\"type\":\"int192\"},{\"components\":[{\"internalType\":\"bytes2\",\"name\":\"FixedBytes\",\"type\":\"bytes2\"},{\"components\":[{\"internalType\":\"int64\",\"name\":\"IntVal\",\"type\":\"int64\"},{\"internalType\":\"string\",\"name\":\"S\",\"type\":\"string\"}],\"internalType\":\"structInnerTestStruct\",\"name\":\"Inner\",\"type\":\"tuple\"}],\"internalType\":\"structMidLevelTestStruct\",\"name\":\"nestedStruct\",\"type\":\"tuple\"}],\"name\":\"triggerEvent\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"field\",\"type\":\"string\"}],\"name\":\"triggerEventWithDynamicTopic\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int32\",\"name\":\"field1\",\"type\":\"int32\"},{\"internalType\":\"int32\",\"name\":\"field2\",\"type\":\"int32\"},{\"internalType\":\"int32\",\"name\":\"field3\",\"type\":\"int32\"}],\"name\":\"triggerWithFourTopics\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", - Bin: "0x608060405234801561001057600080fd5b50600180548082018255600082905260048082047fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6908101805460086003958616810261010090810a8088026001600160401b0391820219909416939093179093558654808801909755848704909301805496909516909202900a91820291021990921691909117905561181e806100a96000396000f3fe608060405234801561001057600080fd5b50600436106100c95760003560e01c80637f002d6711610081578063ef4e1ced1161005b578063ef4e1ced146101c0578063f6f871c8146101c7578063fbe9fbf6146101da57600080fd5b80637f002d671461017d578063ab5e0b3814610190578063dbfd7332146101ad57600080fd5b806349eac2ac116100b257806349eac2ac1461010c578063679004a41461011f5780636c9a43b61461013457600080fd5b80632c45576f146100ce5780633272b66c146100f7575b600080fd5b6100e16100dc366004610c2b565b6101ec565b6040516100ee9190610d8a565b60405180910390f35b61010a610105366004610ec9565b6104c7565b005b61010a61011a366004610fde565b61051c565b61012761081f565b6040516100ee91906110d0565b61010a61014236600461111e565b600280547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001667ffffffffffffffff92909216919091179055565b61010a61018b366004610fde565b6108ab565b6107c65b60405167ffffffffffffffff90911681526020016100ee565b61010a6101bb36600461114f565b610902565b6003610194565b6100e16101d5366004610fde565b61093f565b60025467ffffffffffffffff16610194565b6101f4610a48565b6000610201600184611192565b81548110610211576102116111cc565b6000918252602091829020604080516101008101909152600a90920201805460030b8252600181018054929391929184019161024c906111fb565b80601f0160208091040260200160405190810160405280929190818152602001828054610278906111fb565b80156102c55780601f1061029a576101008083540402835291602001916102c5565b820191906000526020600020905b8154815290600101906020018083116102a857829003601f168201915b5050509183525050600282015460ff166020808301919091526040805161040081018083529190930192916003850191826000855b825461010083900a900460ff168152602060019283018181049485019490930390920291018084116102fa57505050928452505050600482015473ffffffffffffffffffffffffffffffffffffffff1660208083019190915260058301805460408051828502810185018252828152940193928301828280156103b357602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff168152600190910190602001808311610388575b5050509183525050600682015460170b6020808301919091526040805180820182526007808601805460f01b7fffff0000000000000000000000000000000000000000000000000000000000001683528351808501855260088801805490930b81526009880180549590970196939591948683019491939284019190610438906111fb565b80601f0160208091040260200160405190810160405280929190818152602001828054610464906111fb565b80156104b15780601f10610486576101008083540402835291602001916104b1565b820191906000526020600020905b81548152906001019060200180831161049457829003601f168201915b5050509190925250505090525090525092915050565b81816040516104d7929190611248565b60405180910390207f3d969732b1bbbb9f1d7eb9f3f14e4cb50a74d950b3ef916a397b85dfbab93c6783836040516105109291906112a1565b60405180910390a25050565b60006040518061010001604052808c60030b81526020018b8b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050509082525060ff8a166020808301919091526040805161040081810183529190930192918b9183908390808284376000920191909152505050815273ffffffffffffffffffffffffffffffffffffffff8816602080830191909152604080518883028181018401835289825291909301929189918991829190850190849080828437600092019190915250505090825250601785900b602082015260400161060e8461139e565b905281546001808201845560009384526020938490208351600a9093020180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000001663ffffffff90931692909217825592820151919290919082019061067490826114f8565b5060408201516002820180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660ff90921691909117905560608201516106c29060038301906020610a97565b5060808201516004820180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff90921691909117905560a08201518051610729916005840191602090910190610b2a565b5060c08201516006820180547fffffffffffffffff0000000000000000000000000000000000000000000000001677ffffffffffffffffffffffffffffffffffffffffffffffff90921691909117905560e082015180516007830180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00001660f09290921c91909117815560208083015180516008860180547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001667ffffffffffffffff90921691909117815591810151909190600986019061080c90826114f8565b5050505050505050505050505050505050565b606060018054806020026020016040519081016040528092919081815260200182805480156108a157602002820191906000526020600020906000905b82829054906101000a900467ffffffffffffffff1667ffffffffffffffff168152602001906008019060208260070104928301926001038202915080841161085c5790505b5050505050905090565b8960030b7f7188419dcd8b51877b71766f075f3626586c0ff190e7d056aa65ce9acb649a3d8a8a8a8a8a8a8a8a8a6040516108ee99989796959493929190611757565b60405180910390a250505050505050505050565b8060030b8260030b8460030b7f91c80dc390f3d041b3a04b0099b19634499541ea26972250986ee4b24a12fac560405160405180910390a4505050565b610947610a48565b6040518061010001604052808c60030b81526020018b8b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050509082525060ff8a166020808301919091526040805161040081810183529190930192918b9183908390808284376000920191909152505050815273ffffffffffffffffffffffffffffffffffffffff8816602080830191909152604080518883028181018401835289825291909301929189918991829190850190849080828437600092019190915250505090825250601785900b6020820152604001610a378461139e565b90529b9a5050505050505050505050565b6040805161010081018252600080825260606020830181905292820152908101610a70610ba4565b8152600060208201819052606060408301819052820152608001610a92610bc3565b905290565b600183019183908215610b1a5791602002820160005b83821115610aeb57835183826101000a81548160ff021916908360ff1602179055509260200192600101602081600001049283019260010302610aad565b8015610b185782816101000a81549060ff0219169055600101602081600001049283019260010302610aeb565b505b50610b26929150610c16565b5090565b828054828255906000526020600020908101928215610b1a579160200282015b82811115610b1a57825182547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff909116178255602090920191600190910190610b4a565b6040518061040001604052806020906020820280368337509192915050565b604051806040016040528060007dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152602001610a926040518060400160405280600060070b8152602001606081525090565b5b80821115610b265760008155600101610c17565b600060208284031215610c3d57600080fd5b5035919050565b6000815180845260005b81811015610c6a57602081850181015186830182015201610c4e565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b8060005b6020808210610cbb5750610cd2565b825160ff1685529384019390910190600101610cac565b50505050565b600081518084526020808501945080840160005b83811015610d1e57815173ffffffffffffffffffffffffffffffffffffffff1687529582019590820190600101610cec565b509495945050505050565b7fffff00000000000000000000000000000000000000000000000000000000000081511682526000602082015160406020850152805160070b60408501526020810151905060406060850152610d826080850182610c44565b949350505050565b60208152610d9e60208201835160030b9052565b600060208301516104e0806040850152610dbc610500850183610c44565b91506040850151610dd2606086018260ff169052565b506060850151610de56080860182610ca8565b50608085015173ffffffffffffffffffffffffffffffffffffffff1661048085015260a08501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe085840381016104a0870152610e428483610cd8565b935060c08701519150610e5b6104c087018360170b9052565b60e0870151915080868503018387015250610e768382610d29565b9695505050505050565b60008083601f840112610e9257600080fd5b50813567ffffffffffffffff811115610eaa57600080fd5b602083019150836020828501011115610ec257600080fd5b9250929050565b60008060208385031215610edc57600080fd5b823567ffffffffffffffff811115610ef357600080fd5b610eff85828601610e80565b90969095509350505050565b8035600381900b8114610f1d57600080fd5b919050565b803560ff81168114610f1d57600080fd5b806104008101831015610f4557600080fd5b92915050565b803573ffffffffffffffffffffffffffffffffffffffff81168114610f1d57600080fd5b60008083601f840112610f8157600080fd5b50813567ffffffffffffffff811115610f9957600080fd5b6020830191508360208260051b8501011115610ec257600080fd5b8035601781900b8114610f1d57600080fd5b600060408284031215610fd857600080fd5b50919050565b6000806000806000806000806000806104e08b8d031215610ffe57600080fd5b6110078b610f0b565b995060208b013567ffffffffffffffff8082111561102457600080fd5b6110308e838f01610e80565b909b50995089915061104460408e01610f22565b98506110538e60608f01610f33565b97506110626104608e01610f4b565b96506104808d013591508082111561107957600080fd5b6110858e838f01610f6f565b909650945084915061109a6104a08e01610fb4565b93506104c08d01359150808211156110b157600080fd5b506110be8d828e01610fc6565b9150509295989b9194979a5092959850565b6020808252825182820181905260009190848201906040850190845b8181101561111257835167ffffffffffffffff16835292840192918401916001016110ec565b50909695505050505050565b60006020828403121561113057600080fd5b813567ffffffffffffffff8116811461114857600080fd5b9392505050565b60008060006060848603121561116457600080fd5b61116d84610f0b565b925061117b60208501610f0b565b915061118960408501610f0b565b90509250925092565b81810381811115610f45577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600181811c9082168061120f57607f821691505b602082108103610fd8577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b8183823760009101908152919050565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b602081526000610d82602083018486611258565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040805190810167ffffffffffffffff81118282101715611307576113076112b5565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715611354576113546112b5565b604052919050565b80357fffff00000000000000000000000000000000000000000000000000000000000081168114610f1d57600080fd5b8035600781900b8114610f1d57600080fd5b6000604082360312156113b057600080fd5b6113b86112e4565b6113c18361135c565b815260208084013567ffffffffffffffff808211156113df57600080fd5b8186019150604082360312156113f457600080fd5b6113fc6112e4565b6114058361138c565b8152838301358281111561141857600080fd5b929092019136601f84011261142c57600080fd5b82358281111561143e5761143e6112b5565b61146e857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8401160161130d565b9250808352368582860101111561148457600080fd5b8085850186850137600090830185015280840191909152918301919091525092915050565b601f8211156114f357600081815260208120601f850160051c810160208610156114d05750805b601f850160051c820191505b818110156114ef578281556001016114dc565b5050505b505050565b815167ffffffffffffffff811115611512576115126112b5565b6115268161152084546111fb565b846114a9565b602080601f83116001811461157957600084156115435750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b1785556114ef565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b828110156115c6578886015182559484019460019091019084016115a7565b508582101561160257878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b8183526000602080850194508260005b85811015610d1e5773ffffffffffffffffffffffffffffffffffffffff61164883610f4b565b1687529582019590820190600101611622565b7fffff0000000000000000000000000000000000000000000000000000000000006116858261135c565b168252600060208201357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc18336030181126116bf57600080fd5b6040602085015282016116d18161138c565b60070b604085015260208101357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe182360301811261170e57600080fd5b0160208101903567ffffffffffffffff81111561172a57600080fd5b80360382131561173957600080fd5b6040606086015261174e608086018284611258565b95945050505050565b60006104c080835261176c8184018c8e611258565b9050602060ff808c1682860152604085018b60005b848110156117a6578361179383610f22565b1683529184019190840190600101611781565b505050505073ffffffffffffffffffffffffffffffffffffffff88166104408401528281036104608401526117dc818789611612565b90506117ee61048084018660170b9052565b8281036104a0840152611801818561165b565b9c9b50505050505050505050505056fea164736f6c6343000813000a", + ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"int32\",\"name\":\"field\",\"type\":\"int32\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"oracleId\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8[32]\",\"name\":\"oracleIds\",\"type\":\"uint8[32]\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"Account\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"Accounts\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"differentField\",\"type\":\"string\"},{\"indexed\":false,\"internalType\":\"int192\",\"name\":\"bigField\",\"type\":\"int192\"},{\"components\":[{\"internalType\":\"bytes2\",\"name\":\"FixedBytes\",\"type\":\"bytes2\"},{\"components\":[{\"internalType\":\"int64\",\"name\":\"IntVal\",\"type\":\"int64\"},{\"internalType\":\"string\",\"name\":\"S\",\"type\":\"string\"}],\"internalType\":\"structInnerTestStruct\",\"name\":\"Inner\",\"type\":\"tuple\"}],\"indexed\":false,\"internalType\":\"structMidLevelTestStruct\",\"name\":\"nestedStruct\",\"type\":\"tuple\"}],\"name\":\"Triggered\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"string\",\"name\":\"fieldHash\",\"type\":\"string\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"field\",\"type\":\"string\"}],\"name\":\"TriggeredEventWithDynamicTopic\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"int32\",\"name\":\"field1\",\"type\":\"int32\"},{\"indexed\":true,\"internalType\":\"int32\",\"name\":\"field2\",\"type\":\"int32\"},{\"indexed\":true,\"internalType\":\"int32\",\"name\":\"field3\",\"type\":\"int32\"}],\"name\":\"TriggeredWithFourTopics\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"string\",\"name\":\"field1\",\"type\":\"string\"},{\"indexed\":true,\"internalType\":\"uint8[32]\",\"name\":\"field2\",\"type\":\"uint8[32]\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"field3\",\"type\":\"bytes32\"}],\"name\":\"TriggeredWithFourTopicsWithHashed\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"int32\",\"name\":\"field\",\"type\":\"int32\"},{\"internalType\":\"string\",\"name\":\"differentField\",\"type\":\"string\"},{\"internalType\":\"uint8\",\"name\":\"oracleId\",\"type\":\"uint8\"},{\"internalType\":\"uint8[32]\",\"name\":\"oracleIds\",\"type\":\"uint8[32]\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"address[]\",\"name\":\"accounts\",\"type\":\"address[]\"},{\"internalType\":\"int192\",\"name\":\"bigField\",\"type\":\"int192\"},{\"components\":[{\"internalType\":\"bytes2\",\"name\":\"FixedBytes\",\"type\":\"bytes2\"},{\"components\":[{\"internalType\":\"int64\",\"name\":\"IntVal\",\"type\":\"int64\"},{\"internalType\":\"string\",\"name\":\"S\",\"type\":\"string\"}],\"internalType\":\"structInnerTestStruct\",\"name\":\"Inner\",\"type\":\"tuple\"}],\"internalType\":\"structMidLevelTestStruct\",\"name\":\"nestedStruct\",\"type\":\"tuple\"}],\"name\":\"addTestStruct\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAlterablePrimitiveValue\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getDifferentPrimitiveValue\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"i\",\"type\":\"uint256\"}],\"name\":\"getElementAtIndex\",\"outputs\":[{\"components\":[{\"internalType\":\"int32\",\"name\":\"Field\",\"type\":\"int32\"},{\"internalType\":\"string\",\"name\":\"DifferentField\",\"type\":\"string\"},{\"internalType\":\"uint8\",\"name\":\"OracleId\",\"type\":\"uint8\"},{\"internalType\":\"uint8[32]\",\"name\":\"OracleIds\",\"type\":\"uint8[32]\"},{\"internalType\":\"address\",\"name\":\"Account\",\"type\":\"address\"},{\"internalType\":\"address[]\",\"name\":\"Accounts\",\"type\":\"address[]\"},{\"internalType\":\"int192\",\"name\":\"BigField\",\"type\":\"int192\"},{\"components\":[{\"internalType\":\"bytes2\",\"name\":\"FixedBytes\",\"type\":\"bytes2\"},{\"components\":[{\"internalType\":\"int64\",\"name\":\"IntVal\",\"type\":\"int64\"},{\"internalType\":\"string\",\"name\":\"S\",\"type\":\"string\"}],\"internalType\":\"structInnerTestStruct\",\"name\":\"Inner\",\"type\":\"tuple\"}],\"internalType\":\"structMidLevelTestStruct\",\"name\":\"NestedStruct\",\"type\":\"tuple\"}],\"internalType\":\"structTestStruct\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getPrimitiveValue\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getSliceValue\",\"outputs\":[{\"internalType\":\"uint64[]\",\"name\":\"\",\"type\":\"uint64[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int32\",\"name\":\"field\",\"type\":\"int32\"},{\"internalType\":\"string\",\"name\":\"differentField\",\"type\":\"string\"},{\"internalType\":\"uint8\",\"name\":\"oracleId\",\"type\":\"uint8\"},{\"internalType\":\"uint8[32]\",\"name\":\"oracleIds\",\"type\":\"uint8[32]\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"address[]\",\"name\":\"accounts\",\"type\":\"address[]\"},{\"internalType\":\"int192\",\"name\":\"bigField\",\"type\":\"int192\"},{\"components\":[{\"internalType\":\"bytes2\",\"name\":\"FixedBytes\",\"type\":\"bytes2\"},{\"components\":[{\"internalType\":\"int64\",\"name\":\"IntVal\",\"type\":\"int64\"},{\"internalType\":\"string\",\"name\":\"S\",\"type\":\"string\"}],\"internalType\":\"structInnerTestStruct\",\"name\":\"Inner\",\"type\":\"tuple\"}],\"internalType\":\"structMidLevelTestStruct\",\"name\":\"nestedStruct\",\"type\":\"tuple\"}],\"name\":\"returnSeen\",\"outputs\":[{\"components\":[{\"internalType\":\"int32\",\"name\":\"Field\",\"type\":\"int32\"},{\"internalType\":\"string\",\"name\":\"DifferentField\",\"type\":\"string\"},{\"internalType\":\"uint8\",\"name\":\"OracleId\",\"type\":\"uint8\"},{\"internalType\":\"uint8[32]\",\"name\":\"OracleIds\",\"type\":\"uint8[32]\"},{\"internalType\":\"address\",\"name\":\"Account\",\"type\":\"address\"},{\"internalType\":\"address[]\",\"name\":\"Accounts\",\"type\":\"address[]\"},{\"internalType\":\"int192\",\"name\":\"BigField\",\"type\":\"int192\"},{\"components\":[{\"internalType\":\"bytes2\",\"name\":\"FixedBytes\",\"type\":\"bytes2\"},{\"components\":[{\"internalType\":\"int64\",\"name\":\"IntVal\",\"type\":\"int64\"},{\"internalType\":\"string\",\"name\":\"S\",\"type\":\"string\"}],\"internalType\":\"structInnerTestStruct\",\"name\":\"Inner\",\"type\":\"tuple\"}],\"internalType\":\"structMidLevelTestStruct\",\"name\":\"NestedStruct\",\"type\":\"tuple\"}],\"internalType\":\"structTestStruct\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"name\":\"setAlterablePrimitiveValue\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int32\",\"name\":\"field\",\"type\":\"int32\"},{\"internalType\":\"uint8\",\"name\":\"oracleId\",\"type\":\"uint8\"},{\"internalType\":\"uint8[32]\",\"name\":\"oracleIds\",\"type\":\"uint8[32]\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"address[]\",\"name\":\"accounts\",\"type\":\"address[]\"},{\"internalType\":\"string\",\"name\":\"differentField\",\"type\":\"string\"},{\"internalType\":\"int192\",\"name\":\"bigField\",\"type\":\"int192\"},{\"components\":[{\"internalType\":\"bytes2\",\"name\":\"FixedBytes\",\"type\":\"bytes2\"},{\"components\":[{\"internalType\":\"int64\",\"name\":\"IntVal\",\"type\":\"int64\"},{\"internalType\":\"string\",\"name\":\"S\",\"type\":\"string\"}],\"internalType\":\"structInnerTestStruct\",\"name\":\"Inner\",\"type\":\"tuple\"}],\"internalType\":\"structMidLevelTestStruct\",\"name\":\"nestedStruct\",\"type\":\"tuple\"}],\"name\":\"triggerEvent\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"field\",\"type\":\"string\"}],\"name\":\"triggerEventWithDynamicTopic\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int32\",\"name\":\"field1\",\"type\":\"int32\"},{\"internalType\":\"int32\",\"name\":\"field2\",\"type\":\"int32\"},{\"internalType\":\"int32\",\"name\":\"field3\",\"type\":\"int32\"}],\"name\":\"triggerWithFourTopics\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"field1\",\"type\":\"string\"},{\"internalType\":\"uint8[32]\",\"name\":\"field2\",\"type\":\"uint8[32]\"},{\"internalType\":\"bytes32\",\"name\":\"field3\",\"type\":\"bytes32\"}],\"name\":\"triggerWithFourTopicsWithHashed\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + Bin: "0x608060405234801561001057600080fd5b50600180548082018255600082905260048082047fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6908101805460086003958616810261010090810a8088026001600160401b0391820219909416939093179093558654808801909755848704909301805496909516909202900a918202910219909216919091179055611a42806100a96000396000f3fe608060405234801561001057600080fd5b50600436106100d45760003560e01c8063ab5e0b3811610081578063ef4e1ced1161005b578063ef4e1ced146101de578063f6f871c8146101e5578063fbe9fbf6146101f857600080fd5b8063ab5e0b381461019b578063dbfd7332146101b8578063dd1319b3146101cb57600080fd5b8063679004a4116100b2578063679004a41461012a5780636c9a43b61461013f578063a90e19981461018857600080fd5b80632c45576f146100d95780633272b66c1461010257806349eac2ac14610117575b600080fd5b6100ec6100e7366004610ca3565b61020a565b6040516100f99190610e0c565b60405180910390f35b610115610110366004610f4b565b6104e5565b005b610115610125366004611060565b61053a565b61013261083d565b6040516100f99190611152565b61011561014d3660046111a0565b600280547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001667ffffffffffffffff92909216919091179055565b6101156101963660046112d4565b6108c9565b6107c65b60405167ffffffffffffffff90911681526020016100f9565b6101156101c6366004611389565b610923565b6101156101d93660046113cc565b610960565b600361019f565b6100ec6101f3366004611060565b6109b7565b60025467ffffffffffffffff1661019f565b610212610ac0565b600061021f600184611471565b8154811061022f5761022f6114ab565b6000918252602091829020604080516101008101909152600a90920201805460030b8252600181018054929391929184019161026a906114da565b80601f0160208091040260200160405190810160405280929190818152602001828054610296906114da565b80156102e35780601f106102b8576101008083540402835291602001916102e3565b820191906000526020600020905b8154815290600101906020018083116102c657829003601f168201915b5050509183525050600282015460ff166020808301919091526040805161040081018083529190930192916003850191826000855b825461010083900a900460ff1681526020600192830181810494850194909303909202910180841161031857505050928452505050600482015473ffffffffffffffffffffffffffffffffffffffff1660208083019190915260058301805460408051828502810185018252828152940193928301828280156103d157602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff1681526001909101906020018083116103a6575b5050509183525050600682015460170b6020808301919091526040805180820182526007808601805460f01b7fffff0000000000000000000000000000000000000000000000000000000000001683528351808501855260088801805490930b81526009880180549590970196939591948683019491939284019190610456906114da565b80601f0160208091040260200160405190810160405280929190818152602001828054610482906114da565b80156104cf5780601f106104a4576101008083540402835291602001916104cf565b820191906000526020600020905b8154815290600101906020018083116104b257829003601f168201915b5050509190925250505090525090525092915050565b81816040516104f5929190611527565b60405180910390207f3d969732b1bbbb9f1d7eb9f3f14e4cb50a74d950b3ef916a397b85dfbab93c67838360405161052e929190611580565b60405180910390a25050565b60006040518061010001604052808c60030b81526020018b8b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050509082525060ff8a166020808301919091526040805161040081810183529190930192918b9183908390808284376000920191909152505050815273ffffffffffffffffffffffffffffffffffffffff8816602080830191909152604080518883028181018401835289825291909301929189918991829190850190849080828437600092019190915250505090825250601785900b602082015260400161062c846115d6565b905281546001808201845560009384526020938490208351600a9093020180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000001663ffffffff90931692909217825592820151919290919082019061069290826116c3565b5060408201516002820180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660ff90921691909117905560608201516106e09060038301906020610b0f565b5060808201516004820180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff90921691909117905560a08201518051610747916005840191602090910190610ba2565b5060c08201516006820180547fffffffffffffffff0000000000000000000000000000000000000000000000001677ffffffffffffffffffffffffffffffffffffffffffffffff90921691909117905560e082015180516007830180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00001660f09290921c91909117815560208083015180516008860180547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001667ffffffffffffffff90921691909117815591810151909190600986019061082a90826116c3565b5050505050505050505050505050505050565b606060018054806020026020016040519081016040528092919081815260200182805480156108bf57602002820191906000526020600020906000905b82829054906101000a900467ffffffffffffffff1667ffffffffffffffff168152602001906008019060208260070104928301926001038202915080841161087a5790505b5050505050905090565b80826040516108d891906117dd565b6040518091039020846040516108ee9190611819565b604051908190038120907f7220e4dbe4e9d0ed5f71acd022bc89c26748ac6784f2c548bc17bb8e52af34b090600090a4505050565b8060030b8260030b8460030b7f91c80dc390f3d041b3a04b0099b19634499541ea26972250986ee4b24a12fac560405160405180910390a4505050565b8960030b7f4f01af651956288a9505cdb2c7565e925522cd4bb7e31f693f331357ec7b1b5f8a8a8a8a8a8a8a8a8a6040516109a39998979695949392919061197a565b60405180910390a250505050505050505050565b6109bf610ac0565b6040518061010001604052808c60030b81526020018b8b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050509082525060ff8a166020808301919091526040805161040081810183529190930192918b9183908390808284376000920191909152505050815273ffffffffffffffffffffffffffffffffffffffff8816602080830191909152604080518883028181018401835289825291909301929189918991829190850190849080828437600092019190915250505090825250601785900b6020820152604001610aaf846115d6565b90529b9a5050505050505050505050565b6040805161010081018252600080825260606020830181905292820152908101610ae8610c1c565b8152600060208201819052606060408301819052820152608001610b0a610c3b565b905290565b600183019183908215610b925791602002820160005b83821115610b6357835183826101000a81548160ff021916908360ff1602179055509260200192600101602081600001049283019260010302610b25565b8015610b905782816101000a81549060ff0219169055600101602081600001049283019260010302610b63565b505b50610b9e929150610c8e565b5090565b828054828255906000526020600020908101928215610b92579160200282015b82811115610b9257825182547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff909116178255602090920191600190910190610bc2565b6040518061040001604052806020906020820280368337509192915050565b604051806040016040528060007dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152602001610b0a6040518060400160405280600060070b8152602001606081525090565b5b80821115610b9e5760008155600101610c8f565b600060208284031215610cb557600080fd5b5035919050565b60005b83811015610cd7578181015183820152602001610cbf565b50506000910152565b60008151808452610cf8816020860160208601610cbc565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b8060005b6020808210610d3d5750610d54565b825160ff1685529384019390910190600101610d2e565b50505050565b600081518084526020808501945080840160005b83811015610da057815173ffffffffffffffffffffffffffffffffffffffff1687529582019590820190600101610d6e565b509495945050505050565b7fffff00000000000000000000000000000000000000000000000000000000000081511682526000602082015160406020850152805160070b60408501526020810151905060406060850152610e046080850182610ce0565b949350505050565b60208152610e2060208201835160030b9052565b600060208301516104e0806040850152610e3e610500850183610ce0565b91506040850151610e54606086018260ff169052565b506060850151610e676080860182610d2a565b50608085015173ffffffffffffffffffffffffffffffffffffffff1661048085015260a08501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe085840381016104a0870152610ec48483610d5a565b935060c08701519150610edd6104c087018360170b9052565b60e0870151915080868503018387015250610ef88382610dab565b9695505050505050565b60008083601f840112610f1457600080fd5b50813567ffffffffffffffff811115610f2c57600080fd5b602083019150836020828501011115610f4457600080fd5b9250929050565b60008060208385031215610f5e57600080fd5b823567ffffffffffffffff811115610f7557600080fd5b610f8185828601610f02565b90969095509350505050565b8035600381900b8114610f9f57600080fd5b919050565b803560ff81168114610f9f57600080fd5b806104008101831015610fc757600080fd5b92915050565b803573ffffffffffffffffffffffffffffffffffffffff81168114610f9f57600080fd5b60008083601f84011261100357600080fd5b50813567ffffffffffffffff81111561101b57600080fd5b6020830191508360208260051b8501011115610f4457600080fd5b8035601781900b8114610f9f57600080fd5b60006040828403121561105a57600080fd5b50919050565b6000806000806000806000806000806104e08b8d03121561108057600080fd5b6110898b610f8d565b995060208b013567ffffffffffffffff808211156110a657600080fd5b6110b28e838f01610f02565b909b5099508991506110c660408e01610fa4565b98506110d58e60608f01610fb5565b97506110e46104608e01610fcd565b96506104808d01359150808211156110fb57600080fd5b6111078e838f01610ff1565b909650945084915061111c6104a08e01611036565b93506104c08d013591508082111561113357600080fd5b506111408d828e01611048565b9150509295989b9194979a5092959850565b6020808252825182820181905260009190848201906040850190845b8181101561119457835167ffffffffffffffff168352928401929184019160010161116e565b50909695505050505050565b6000602082840312156111b257600080fd5b813567ffffffffffffffff811681146111ca57600080fd5b9392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040805190810167ffffffffffffffff81118282101715611223576112236111d1565b60405290565b600082601f83011261123a57600080fd5b813567ffffffffffffffff80821115611255576112556111d1565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190828211818310171561129b5761129b6111d1565b816040528381528660208588010111156112b457600080fd5b836020870160208301376000602085830101528094505050505092915050565b600080600061044084860312156112ea57600080fd5b833567ffffffffffffffff8082111561130257600080fd5b61130e87838801611229565b94506020915086603f87011261132357600080fd5b6040516104008101818110838211171561133f5761133f6111d1565b60405290508061042087018881111561135757600080fd5b8388015b818110156113795761136c81610fa4565b845292840192840161135b565b5095989097509435955050505050565b60008060006060848603121561139e57600080fd5b6113a784610f8d565b92506113b560208501610f8d565b91506113c360408501610f8d565b90509250925092565b6000806000806000806000806000806104e08b8d0312156113ec57600080fd5b6113f58b610f8d565b995061140360208c01610fa4565b98506114128c60408d01610fb5565b97506114216104408c01610fcd565b96506104608b013567ffffffffffffffff8082111561143f57600080fd5b61144b8e838f01610ff1565b90985096506104808d013591508082111561146557600080fd5b6111078e838f01610f02565b81810381811115610fc7577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600181811c908216806114ee57607f821691505b60208210810361105a577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b8183823760009101908152919050565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b602081526000610e04602083018486611537565b80357fffff00000000000000000000000000000000000000000000000000000000000081168114610f9f57600080fd5b8035600781900b8114610f9f57600080fd5b6000604082360312156115e857600080fd5b6115f0611200565b6115f983611594565b8152602083013567ffffffffffffffff8082111561161657600080fd5b81850191506040823603121561162b57600080fd5b611633611200565b61163c836115c4565b815260208301358281111561165057600080fd5b61165c36828601611229565b60208301525080602085015250505080915050919050565b601f8211156116be57600081815260208120601f850160051c8101602086101561169b5750805b601f850160051c820191505b818110156116ba578281556001016116a7565b5050505b505050565b815167ffffffffffffffff8111156116dd576116dd6111d1565b6116f1816116eb84546114da565b84611674565b602080601f831160018114611744576000841561170e5750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b1785556116ba565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b8281101561179157888601518255948401946001909101908401611772565b50858210156117cd57878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b60008183825b60208082106117f25750611809565b825160ff16845292830192909101906001016117e3565b5050506104008201905092915050565b6000825161182b818460208701610cbc565b9190910192915050565b8183526000602080850194508260005b85811015610da05773ffffffffffffffffffffffffffffffffffffffff61186b83610fcd565b1687529582019590820190600101611845565b7fffff0000000000000000000000000000000000000000000000000000000000006118a882611594565b168252600060208201357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc18336030181126118e257600080fd5b6040602085015282016118f4816115c4565b60070b604085015260208101357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe182360301811261193157600080fd5b0160208101903567ffffffffffffffff81111561194d57600080fd5b80360382131561195c57600080fd5b60406060860152611971608086018284611537565b95945050505050565b60006104c060ff808d16845260208085018d60005b838110156119b457846119a183610fa4565b168352918301919083019060010161198f565b505050505073ffffffffffffffffffffffffffffffffffffffff8a16610420840152806104408401526119ea818401898b611835565b9050828103610460840152611a00818789611537565b9050611a1261048084018660170b9052565b8281036104a0840152611a25818561187e565b9c9b50505050505050505050505056fea164736f6c6343000813000a", } var ChainReaderTesterABI = ChainReaderTesterMetaData.ABI @@ -348,16 +348,16 @@ func (_ChainReaderTester *ChainReaderTesterTransactorSession) SetAlterablePrimit return _ChainReaderTester.Contract.SetAlterablePrimitiveValue(&_ChainReaderTester.TransactOpts, value) } -func (_ChainReaderTester *ChainReaderTesterTransactor) TriggerEvent(opts *bind.TransactOpts, field int32, differentField string, oracleId uint8, oracleIds [32]uint8, account common.Address, accounts []common.Address, bigField *big.Int, nestedStruct MidLevelTestStruct) (*types.Transaction, error) { - return _ChainReaderTester.contract.Transact(opts, "triggerEvent", field, differentField, oracleId, oracleIds, account, accounts, bigField, nestedStruct) +func (_ChainReaderTester *ChainReaderTesterTransactor) TriggerEvent(opts *bind.TransactOpts, field int32, oracleId uint8, oracleIds [32]uint8, account common.Address, accounts []common.Address, differentField string, bigField *big.Int, nestedStruct MidLevelTestStruct) (*types.Transaction, error) { + return _ChainReaderTester.contract.Transact(opts, "triggerEvent", field, oracleId, oracleIds, account, accounts, differentField, bigField, nestedStruct) } -func (_ChainReaderTester *ChainReaderTesterSession) TriggerEvent(field int32, differentField string, oracleId uint8, oracleIds [32]uint8, account common.Address, accounts []common.Address, bigField *big.Int, nestedStruct MidLevelTestStruct) (*types.Transaction, error) { - return _ChainReaderTester.Contract.TriggerEvent(&_ChainReaderTester.TransactOpts, field, differentField, oracleId, oracleIds, account, accounts, bigField, nestedStruct) +func (_ChainReaderTester *ChainReaderTesterSession) TriggerEvent(field int32, oracleId uint8, oracleIds [32]uint8, account common.Address, accounts []common.Address, differentField string, bigField *big.Int, nestedStruct MidLevelTestStruct) (*types.Transaction, error) { + return _ChainReaderTester.Contract.TriggerEvent(&_ChainReaderTester.TransactOpts, field, oracleId, oracleIds, account, accounts, differentField, bigField, nestedStruct) } -func (_ChainReaderTester *ChainReaderTesterTransactorSession) TriggerEvent(field int32, differentField string, oracleId uint8, oracleIds [32]uint8, account common.Address, accounts []common.Address, bigField *big.Int, nestedStruct MidLevelTestStruct) (*types.Transaction, error) { - return _ChainReaderTester.Contract.TriggerEvent(&_ChainReaderTester.TransactOpts, field, differentField, oracleId, oracleIds, account, accounts, bigField, nestedStruct) +func (_ChainReaderTester *ChainReaderTesterTransactorSession) TriggerEvent(field int32, oracleId uint8, oracleIds [32]uint8, account common.Address, accounts []common.Address, differentField string, bigField *big.Int, nestedStruct MidLevelTestStruct) (*types.Transaction, error) { + return _ChainReaderTester.Contract.TriggerEvent(&_ChainReaderTester.TransactOpts, field, oracleId, oracleIds, account, accounts, differentField, bigField, nestedStruct) } func (_ChainReaderTester *ChainReaderTesterTransactor) TriggerEventWithDynamicTopic(opts *bind.TransactOpts, field string) (*types.Transaction, error) { @@ -384,6 +384,18 @@ func (_ChainReaderTester *ChainReaderTesterTransactorSession) TriggerWithFourTop return _ChainReaderTester.Contract.TriggerWithFourTopics(&_ChainReaderTester.TransactOpts, field1, field2, field3) } +func (_ChainReaderTester *ChainReaderTesterTransactor) TriggerWithFourTopicsWithHashed(opts *bind.TransactOpts, field1 string, field2 [32]uint8, field3 [32]byte) (*types.Transaction, error) { + return _ChainReaderTester.contract.Transact(opts, "triggerWithFourTopicsWithHashed", field1, field2, field3) +} + +func (_ChainReaderTester *ChainReaderTesterSession) TriggerWithFourTopicsWithHashed(field1 string, field2 [32]uint8, field3 [32]byte) (*types.Transaction, error) { + return _ChainReaderTester.Contract.TriggerWithFourTopicsWithHashed(&_ChainReaderTester.TransactOpts, field1, field2, field3) +} + +func (_ChainReaderTester *ChainReaderTesterTransactorSession) TriggerWithFourTopicsWithHashed(field1 string, field2 [32]uint8, field3 [32]byte) (*types.Transaction, error) { + return _ChainReaderTester.Contract.TriggerWithFourTopicsWithHashed(&_ChainReaderTester.TransactOpts, field1, field2, field3) +} + type ChainReaderTesterTriggeredIterator struct { Event *ChainReaderTesterTriggered @@ -446,11 +458,11 @@ func (it *ChainReaderTesterTriggeredIterator) Close() error { type ChainReaderTesterTriggered struct { Field int32 - DifferentField string OracleId uint8 OracleIds [32]uint8 Account common.Address Accounts []common.Address + DifferentField string BigField *big.Int NestedStruct MidLevelTestStruct Raw types.Log @@ -791,6 +803,151 @@ func (_ChainReaderTester *ChainReaderTesterFilterer) ParseTriggeredWithFourTopic return event, nil } +type ChainReaderTesterTriggeredWithFourTopicsWithHashedIterator struct { + Event *ChainReaderTesterTriggeredWithFourTopicsWithHashed + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *ChainReaderTesterTriggeredWithFourTopicsWithHashedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(ChainReaderTesterTriggeredWithFourTopicsWithHashed) + 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(ChainReaderTesterTriggeredWithFourTopicsWithHashed) + 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 *ChainReaderTesterTriggeredWithFourTopicsWithHashedIterator) Error() error { + return it.fail +} + +func (it *ChainReaderTesterTriggeredWithFourTopicsWithHashedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type ChainReaderTesterTriggeredWithFourTopicsWithHashed struct { + Field1 common.Hash + Field2 [32]uint8 + Field3 [32]byte + Raw types.Log +} + +func (_ChainReaderTester *ChainReaderTesterFilterer) FilterTriggeredWithFourTopicsWithHashed(opts *bind.FilterOpts, field1 []string, field2 [][32]uint8, field3 [][32]byte) (*ChainReaderTesterTriggeredWithFourTopicsWithHashedIterator, error) { + + var field1Rule []interface{} + for _, field1Item := range field1 { + field1Rule = append(field1Rule, field1Item) + } + var field2Rule []interface{} + for _, field2Item := range field2 { + field2Rule = append(field2Rule, field2Item) + } + var field3Rule []interface{} + for _, field3Item := range field3 { + field3Rule = append(field3Rule, field3Item) + } + + logs, sub, err := _ChainReaderTester.contract.FilterLogs(opts, "TriggeredWithFourTopicsWithHashed", field1Rule, field2Rule, field3Rule) + if err != nil { + return nil, err + } + return &ChainReaderTesterTriggeredWithFourTopicsWithHashedIterator{contract: _ChainReaderTester.contract, event: "TriggeredWithFourTopicsWithHashed", logs: logs, sub: sub}, nil +} + +func (_ChainReaderTester *ChainReaderTesterFilterer) WatchTriggeredWithFourTopicsWithHashed(opts *bind.WatchOpts, sink chan<- *ChainReaderTesterTriggeredWithFourTopicsWithHashed, field1 []string, field2 [][32]uint8, field3 [][32]byte) (event.Subscription, error) { + + var field1Rule []interface{} + for _, field1Item := range field1 { + field1Rule = append(field1Rule, field1Item) + } + var field2Rule []interface{} + for _, field2Item := range field2 { + field2Rule = append(field2Rule, field2Item) + } + var field3Rule []interface{} + for _, field3Item := range field3 { + field3Rule = append(field3Rule, field3Item) + } + + logs, sub, err := _ChainReaderTester.contract.WatchLogs(opts, "TriggeredWithFourTopicsWithHashed", field1Rule, field2Rule, field3Rule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(ChainReaderTesterTriggeredWithFourTopicsWithHashed) + if err := _ChainReaderTester.contract.UnpackLog(event, "TriggeredWithFourTopicsWithHashed", 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 (_ChainReaderTester *ChainReaderTesterFilterer) ParseTriggeredWithFourTopicsWithHashed(log types.Log) (*ChainReaderTesterTriggeredWithFourTopicsWithHashed, error) { + event := new(ChainReaderTesterTriggeredWithFourTopicsWithHashed) + if err := _ChainReaderTester.contract.UnpackLog(event, "TriggeredWithFourTopicsWithHashed", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + func (_ChainReaderTester *ChainReaderTester) ParseLog(log types.Log) (generated.AbigenLog, error) { switch log.Topics[0] { case _ChainReaderTester.abi.Events["Triggered"].ID: @@ -799,6 +956,8 @@ func (_ChainReaderTester *ChainReaderTester) ParseLog(log types.Log) (generated. return _ChainReaderTester.ParseTriggeredEventWithDynamicTopic(log) case _ChainReaderTester.abi.Events["TriggeredWithFourTopics"].ID: return _ChainReaderTester.ParseTriggeredWithFourTopics(log) + case _ChainReaderTester.abi.Events["TriggeredWithFourTopicsWithHashed"].ID: + return _ChainReaderTester.ParseTriggeredWithFourTopicsWithHashed(log) default: return nil, fmt.Errorf("abigen wrapper received unknown log topic: %v", log.Topics[0]) @@ -806,7 +965,7 @@ func (_ChainReaderTester *ChainReaderTester) ParseLog(log types.Log) (generated. } func (ChainReaderTesterTriggered) Topic() common.Hash { - return common.HexToHash("0x7188419dcd8b51877b71766f075f3626586c0ff190e7d056aa65ce9acb649a3d") + return common.HexToHash("0x4f01af651956288a9505cdb2c7565e925522cd4bb7e31f693f331357ec7b1b5f") } func (ChainReaderTesterTriggeredEventWithDynamicTopic) Topic() common.Hash { @@ -817,6 +976,10 @@ func (ChainReaderTesterTriggeredWithFourTopics) Topic() common.Hash { return common.HexToHash("0x91c80dc390f3d041b3a04b0099b19634499541ea26972250986ee4b24a12fac5") } +func (ChainReaderTesterTriggeredWithFourTopicsWithHashed) Topic() common.Hash { + return common.HexToHash("0x7220e4dbe4e9d0ed5f71acd022bc89c26748ac6784f2c548bc17bb8e52af34b0") +} + func (_ChainReaderTester *ChainReaderTester) Address() common.Address { return _ChainReaderTester.address } @@ -838,12 +1001,14 @@ type ChainReaderTesterInterface interface { SetAlterablePrimitiveValue(opts *bind.TransactOpts, value uint64) (*types.Transaction, error) - TriggerEvent(opts *bind.TransactOpts, field int32, differentField string, oracleId uint8, oracleIds [32]uint8, account common.Address, accounts []common.Address, bigField *big.Int, nestedStruct MidLevelTestStruct) (*types.Transaction, error) + TriggerEvent(opts *bind.TransactOpts, field int32, oracleId uint8, oracleIds [32]uint8, account common.Address, accounts []common.Address, differentField string, bigField *big.Int, nestedStruct MidLevelTestStruct) (*types.Transaction, error) TriggerEventWithDynamicTopic(opts *bind.TransactOpts, field string) (*types.Transaction, error) TriggerWithFourTopics(opts *bind.TransactOpts, field1 int32, field2 int32, field3 int32) (*types.Transaction, error) + TriggerWithFourTopicsWithHashed(opts *bind.TransactOpts, field1 string, field2 [32]uint8, field3 [32]byte) (*types.Transaction, error) + FilterTriggered(opts *bind.FilterOpts, field []int32) (*ChainReaderTesterTriggeredIterator, error) WatchTriggered(opts *bind.WatchOpts, sink chan<- *ChainReaderTesterTriggered, field []int32) (event.Subscription, error) @@ -862,6 +1027,12 @@ type ChainReaderTesterInterface interface { ParseTriggeredWithFourTopics(log types.Log) (*ChainReaderTesterTriggeredWithFourTopics, error) + FilterTriggeredWithFourTopicsWithHashed(opts *bind.FilterOpts, field1 []string, field2 [][32]uint8, field3 [][32]byte) (*ChainReaderTesterTriggeredWithFourTopicsWithHashedIterator, error) + + WatchTriggeredWithFourTopicsWithHashed(opts *bind.WatchOpts, sink chan<- *ChainReaderTesterTriggeredWithFourTopicsWithHashed, field1 []string, field2 [][32]uint8, field3 [][32]byte) (event.Subscription, error) + + ParseTriggeredWithFourTopicsWithHashed(log types.Log) (*ChainReaderTesterTriggeredWithFourTopicsWithHashed, error) + ParseLog(log types.Log) (generated.AbigenLog, error) Address() common.Address 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 23cec8fb9c..e432d912cc 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\":[],\"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) { @@ -194,9 +194,9 @@ func (_IChainModule *IChainModuleCallerSession) BlockNumber() (*big.Int, error) return _IChainModule.Contract.BlockNumber(&_IChainModule.CallOpts) } -func (_IChainModule *IChainModuleCaller) GetCurrentL1Fee(opts *bind.CallOpts) (*big.Int, error) { +func (_IChainModule *IChainModuleCaller) GetCurrentL1Fee(opts *bind.CallOpts, dataSize *big.Int) (*big.Int, error) { var out []interface{} - err := _IChainModule.contract.Call(opts, &out, "getCurrentL1Fee") + err := _IChainModule.contract.Call(opts, &out, "getCurrentL1Fee", dataSize) if err != nil { return *new(*big.Int), err @@ -208,12 +208,12 @@ func (_IChainModule *IChainModuleCaller) GetCurrentL1Fee(opts *bind.CallOpts) (* } -func (_IChainModule *IChainModuleSession) GetCurrentL1Fee() (*big.Int, error) { - return _IChainModule.Contract.GetCurrentL1Fee(&_IChainModule.CallOpts) +func (_IChainModule *IChainModuleSession) GetCurrentL1Fee(dataSize *big.Int) (*big.Int, error) { + return _IChainModule.Contract.GetCurrentL1Fee(&_IChainModule.CallOpts, dataSize) } -func (_IChainModule *IChainModuleCallerSession) GetCurrentL1Fee() (*big.Int, error) { - return _IChainModule.Contract.GetCurrentL1Fee(&_IChainModule.CallOpts) +func (_IChainModule *IChainModuleCallerSession) GetCurrentL1Fee(dataSize *big.Int) (*big.Int, error) { + return _IChainModule.Contract.GetCurrentL1Fee(&_IChainModule.CallOpts, dataSize) } func (_IChainModule *IChainModuleCaller) GetGasOverhead(opts *bind.CallOpts) (GetGasOverhead, @@ -278,11 +278,11 @@ 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) - GetCurrentL1Fee(opts *bind.CallOpts) (*big.Int, error) + GetCurrentL1Fee(opts *bind.CallOpts, dataSize *big.Int) (*big.Int, error) GetGasOverhead(opts *bind.CallOpts) (GetGasOverhead, diff --git a/core/gethwrappers/generated/optimism_module/optimism_module.go b/core/gethwrappers/generated/optimism_module/optimism_module.go index 009ab6d8cf..89b6a758fc 100644 --- a/core/gethwrappers/generated/optimism_module/optimism_module.go +++ b/core/gethwrappers/generated/optimism_module/optimism_module.go @@ -29,8 +29,8 @@ var ( ) var OptimismModuleMetaData = &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\":[],\"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\"}]", - Bin: "0x608060405234801561001057600080fd5b506104d1806100206000396000f3fe608060405234801561001057600080fd5b50600436106100675760003560e01c806357e871e71161005057806357e871e71461009a57806385df51fd146100a0578063de9ee35e146100b357600080fd5b8063125441401461006c57806318b8f61314610092575b600080fd5b61007f61007a3660046102e9565b6100ca565b6040519081526020015b60405180910390f35b61007f6101eb565b4361007f565b61007f6100ae3660046102e9565b6102bc565b6040805161ea60815261010e602082015201610089565b6000806100d8836004610331565b67ffffffffffffffff8111156100f0576100f061034e565b6040519080825280601f01601f19166020018201604052801561011a576020820181803683370190505b50905073420000000000000000000000000000000000000f73ffffffffffffffffffffffffffffffffffffffff166349948e0e82604051806080016040528060508152602001610475605091396040516020016101789291906103a1565b6040516020818303038152906040526040518263ffffffff1660e01b81526004016101a391906103d0565b602060405180830381865afa1580156101c0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101e49190610421565b9392505050565b600073420000000000000000000000000000000000000f73ffffffffffffffffffffffffffffffffffffffff166349948e0e6000366040518060800160405280605081526020016104756050913960405160200161024b9392919061043a565b6040516020818303038152906040526040518263ffffffff1660e01b815260040161027691906103d0565b602060405180830381865afa158015610293573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102b79190610421565b905090565b600043821015806102d757506101006102d58343610461565b115b156102e457506000919050565b504090565b6000602082840312156102fb57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b808202811582820484141761034857610348610302565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60005b83811015610398578181015183820152602001610380565b50506000910152565b600083516103b381846020880161037d565b8351908301906103c781836020880161037d565b01949350505050565b60208152600082518060208401526103ef81604085016020870161037d565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169190910160400192915050565b60006020828403121561043357600080fd5b5051919050565b82848237600083820160008152835161045781836020880161037d565b0195945050505050565b818103818111156103485761034861030256feffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa164736f6c6343000813000a", + 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\":\"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\"}]", + Bin: "0x608060405234801561001057600080fd5b506103dc806100206000396000f3fe608060405234801561001057600080fd5b50600436106100675760003560e01c80637810d12a116100505780637810d12a1461006c57806385df51fd14610098578063de9ee35e146100ab57600080fd5b8063125441401461006c57806357e871e714610092575b600080fd5b61007f61007a366004610221565b6100c2565b6040519081526020015b60405180910390f35b4361007f565b61007f6100a6366004610221565b6100d3565b6040805161ea60815261010e602082015201610089565b60006100cd82610100565b92915050565b600043821015806100ee57506101006100ec8343610269565b115b156100fb57506000919050565b504090565b60008061010e83600461027c565b67ffffffffffffffff81111561012657610126610293565b6040519080825280601f01601f191660200182016040528015610150576020820181803683370190505b50905073420000000000000000000000000000000000000f73ffffffffffffffffffffffffffffffffffffffff166349948e0e82604051806080016040528060508152602001610380605091396040516020016101ae9291906102e6565b6040516020818303038152906040526040518263ffffffff1660e01b81526004016101d99190610315565b602060405180830381865afa1580156101f6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061021a9190610366565b9392505050565b60006020828403121561023357600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b818103818111156100cd576100cd61023a565b80820281158282048414176100cd576100cd61023a565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60005b838110156102dd5781810151838201526020016102c5565b50506000910152565b600083516102f88184602088016102c2565b83519083019061030c8183602088016102c2565b01949350505050565b60208152600082518060208401526103348160408501602087016102c2565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169190910160400192915050565b60006020828403121561037857600080fd5b505191905056feffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa164736f6c6343000813000a", } var OptimismModuleABI = OptimismModuleMetaData.ABI @@ -213,9 +213,9 @@ func (_OptimismModule *OptimismModuleCallerSession) BlockNumber() (*big.Int, err return _OptimismModule.Contract.BlockNumber(&_OptimismModule.CallOpts) } -func (_OptimismModule *OptimismModuleCaller) GetCurrentL1Fee(opts *bind.CallOpts) (*big.Int, error) { +func (_OptimismModule *OptimismModuleCaller) GetCurrentL1Fee(opts *bind.CallOpts, dataSize *big.Int) (*big.Int, error) { var out []interface{} - err := _OptimismModule.contract.Call(opts, &out, "getCurrentL1Fee") + err := _OptimismModule.contract.Call(opts, &out, "getCurrentL1Fee", dataSize) if err != nil { return *new(*big.Int), err @@ -227,12 +227,12 @@ func (_OptimismModule *OptimismModuleCaller) GetCurrentL1Fee(opts *bind.CallOpts } -func (_OptimismModule *OptimismModuleSession) GetCurrentL1Fee() (*big.Int, error) { - return _OptimismModule.Contract.GetCurrentL1Fee(&_OptimismModule.CallOpts) +func (_OptimismModule *OptimismModuleSession) GetCurrentL1Fee(dataSize *big.Int) (*big.Int, error) { + return _OptimismModule.Contract.GetCurrentL1Fee(&_OptimismModule.CallOpts, dataSize) } -func (_OptimismModule *OptimismModuleCallerSession) GetCurrentL1Fee() (*big.Int, error) { - return _OptimismModule.Contract.GetCurrentL1Fee(&_OptimismModule.CallOpts) +func (_OptimismModule *OptimismModuleCallerSession) GetCurrentL1Fee(dataSize *big.Int) (*big.Int, error) { + return _OptimismModule.Contract.GetCurrentL1Fee(&_OptimismModule.CallOpts, dataSize) } func (_OptimismModule *OptimismModuleCaller) GetGasOverhead(opts *bind.CallOpts) (GetGasOverhead, @@ -301,7 +301,7 @@ type OptimismModuleInterface interface { BlockNumber(opts *bind.CallOpts) (*big.Int, error) - GetCurrentL1Fee(opts *bind.CallOpts) (*big.Int, error) + GetCurrentL1Fee(opts *bind.CallOpts, dataSize *big.Int) (*big.Int, error) GetGasOverhead(opts *bind.CallOpts) (GetGasOverhead, 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 0000000000..abad079caa --- /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 702cc39a7a..e8f48da0d0 100644 --- a/core/gethwrappers/generated/scroll_module/scroll_module.go +++ b/core/gethwrappers/generated/scroll_module/scroll_module.go @@ -5,6 +5,7 @@ package scroll_module import ( "errors" + "fmt" "math/big" "strings" @@ -14,6 +15,7 @@ import ( "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 ( @@ -29,8 +31,8 @@ var ( ) var ScrollModuleMetaData = &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\":[],\"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\"}]", - Bin: "0x608060405234801561001057600080fd5b5061050c806100206000396000f3fe608060405234801561001057600080fd5b50600436106100675760003560e01c806357e871e71161005057806357e871e71461009a57806385df51fd146100a0578063de9ee35e146100b357600080fd5b8063125441401461006c57806318b8f61314610092575b600080fd5b61007f61007a3660046102e8565b6100c9565b6040519081526020015b60405180910390f35b61007f6101ea565b4361007f565b61007f6100ae3660046102e8565b6102bb565b6040805161afc8815260aa602082015201610089565b6000806100d7836004610330565b67ffffffffffffffff8111156100ef576100ef61034d565b6040519080825280601f01601f191660200182016040528015610119576020820181803683370190505b50905073530000000000000000000000000000000000000273ffffffffffffffffffffffffffffffffffffffff166349948e0e826040518060c00160405280608c8152602001610474608c91396040516020016101779291906103a0565b6040516020818303038152906040526040518263ffffffff1660e01b81526004016101a291906103cf565b602060405180830381865afa1580156101bf573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101e39190610420565b9392505050565b600073530000000000000000000000000000000000000273ffffffffffffffffffffffffffffffffffffffff166349948e0e6000366040518060c00160405280608c8152602001610474608c913960405160200161024a93929190610439565b6040516020818303038152906040526040518263ffffffff1660e01b815260040161027591906103cf565b602060405180830381865afa158015610292573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102b69190610420565b905090565b600043821015806102d657506101006102d48343610460565b115b156102e357506000919050565b504090565b6000602082840312156102fa57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b808202811582820484141761034757610347610301565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60005b8381101561039757818101518382015260200161037f565b50506000910152565b600083516103b281846020880161037c565b8351908301906103c681836020880161037c565b01949350505050565b60208152600082518060208401526103ee81604085016020870161037c565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169190910160400192915050565b60006020828403121561043257600080fd5b5051919050565b82848237600083820160008152835161045681836020880161037c565b0195945050505050565b818103818111156103475761034761030156feffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa164736f6c6343000813000a", + 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 @@ -213,9 +215,9 @@ func (_ScrollModule *ScrollModuleCallerSession) BlockNumber() (*big.Int, error) return _ScrollModule.Contract.BlockNumber(&_ScrollModule.CallOpts) } -func (_ScrollModule *ScrollModuleCaller) GetCurrentL1Fee(opts *bind.CallOpts) (*big.Int, error) { +func (_ScrollModule *ScrollModuleCaller) GetCurrentL1Fee(opts *bind.CallOpts, dataSize *big.Int) (*big.Int, error) { var out []interface{} - err := _ScrollModule.contract.Call(opts, &out, "getCurrentL1Fee") + err := _ScrollModule.contract.Call(opts, &out, "getCurrentL1Fee", dataSize) if err != nil { return *new(*big.Int), err @@ -227,12 +229,12 @@ func (_ScrollModule *ScrollModuleCaller) GetCurrentL1Fee(opts *bind.CallOpts) (* } -func (_ScrollModule *ScrollModuleSession) GetCurrentL1Fee() (*big.Int, error) { - return _ScrollModule.Contract.GetCurrentL1Fee(&_ScrollModule.CallOpts) +func (_ScrollModule *ScrollModuleSession) GetCurrentL1Fee(dataSize *big.Int) (*big.Int, error) { + return _ScrollModule.Contract.GetCurrentL1Fee(&_ScrollModule.CallOpts, dataSize) } -func (_ScrollModule *ScrollModuleCallerSession) GetCurrentL1Fee() (*big.Int, error) { - return _ScrollModule.Contract.GetCurrentL1Fee(&_ScrollModule.CallOpts) +func (_ScrollModule *ScrollModuleCallerSession) GetCurrentL1Fee(dataSize *big.Int) (*big.Int, error) { + return _ScrollModule.Contract.GetCurrentL1Fee(&_ScrollModule.CallOpts, dataSize) } func (_ScrollModule *ScrollModuleCaller) GetGasOverhead(opts *bind.CallOpts) (GetGasOverhead, @@ -265,6 +267,28 @@ func (_ScrollModule *ScrollModuleCallerSession) GetGasOverhead() (GetGasOverhead return _ScrollModule.Contract.GetGasOverhead(&_ScrollModule.CallOpts) } +func (_ScrollModule *ScrollModuleCaller) GetL1FeeCoefficient(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _ScrollModule.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 (_ScrollModule *ScrollModuleSession) GetL1FeeCoefficient() (*big.Int, error) { + return _ScrollModule.Contract.GetL1FeeCoefficient(&_ScrollModule.CallOpts) +} + +func (_ScrollModule *ScrollModuleCallerSession) GetL1FeeCoefficient() (*big.Int, error) { + return _ScrollModule.Contract.GetL1FeeCoefficient(&_ScrollModule.CallOpts) +} + func (_ScrollModule *ScrollModuleCaller) GetMaxL1Fee(opts *bind.CallOpts, dataSize *big.Int) (*big.Int, error) { var out []interface{} err := _ScrollModule.contract.Call(opts, &out, "getMaxL1Fee", dataSize) @@ -287,11 +311,484 @@ func (_ScrollModule *ScrollModuleCallerSession) GetMaxL1Fee(dataSize *big.Int) ( return _ScrollModule.Contract.GetMaxL1Fee(&_ScrollModule.CallOpts, dataSize) } +func (_ScrollModule *ScrollModuleCaller) Owner(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _ScrollModule.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 (_ScrollModule *ScrollModuleSession) Owner() (common.Address, error) { + return _ScrollModule.Contract.Owner(&_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) { + return _ScrollModule.contract.Transact(opts, "acceptOwnership") +} + +func (_ScrollModule *ScrollModuleSession) AcceptOwnership() (*types.Transaction, error) { + return _ScrollModule.Contract.AcceptOwnership(&_ScrollModule.TransactOpts) +} + +func (_ScrollModule *ScrollModuleTransactorSession) AcceptOwnership() (*types.Transaction, error) { + return _ScrollModule.Contract.AcceptOwnership(&_ScrollModule.TransactOpts) +} + +func (_ScrollModule *ScrollModuleTransactor) SetL1FeeCalculation(opts *bind.TransactOpts, coefficient uint8) (*types.Transaction, error) { + return _ScrollModule.contract.Transact(opts, "setL1FeeCalculation", coefficient) +} + +func (_ScrollModule *ScrollModuleSession) SetL1FeeCalculation(coefficient uint8) (*types.Transaction, error) { + return _ScrollModule.Contract.SetL1FeeCalculation(&_ScrollModule.TransactOpts, coefficient) +} + +func (_ScrollModule *ScrollModuleTransactorSession) SetL1FeeCalculation(coefficient uint8) (*types.Transaction, error) { + return _ScrollModule.Contract.SetL1FeeCalculation(&_ScrollModule.TransactOpts, coefficient) +} + +func (_ScrollModule *ScrollModuleTransactor) TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) { + return _ScrollModule.contract.Transact(opts, "transferOwnership", to) +} + +func (_ScrollModule *ScrollModuleSession) TransferOwnership(to common.Address) (*types.Transaction, error) { + return _ScrollModule.Contract.TransferOwnership(&_ScrollModule.TransactOpts, to) +} + +func (_ScrollModule *ScrollModuleTransactorSession) TransferOwnership(to common.Address) (*types.Transaction, error) { + return _ScrollModule.Contract.TransferOwnership(&_ScrollModule.TransactOpts, to) +} + +type ScrollModuleL1FeeCoefficientSetIterator struct { + Event *ScrollModuleL1FeeCoefficientSet + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *ScrollModuleL1FeeCoefficientSetIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(ScrollModuleL1FeeCoefficientSet) + 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(ScrollModuleL1FeeCoefficientSet) + 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 *ScrollModuleL1FeeCoefficientSetIterator) Error() error { + return it.fail +} + +func (it *ScrollModuleL1FeeCoefficientSetIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type ScrollModuleL1FeeCoefficientSet struct { + Coefficient uint8 + Raw types.Log +} + +func (_ScrollModule *ScrollModuleFilterer) FilterL1FeeCoefficientSet(opts *bind.FilterOpts) (*ScrollModuleL1FeeCoefficientSetIterator, error) { + + logs, sub, err := _ScrollModule.contract.FilterLogs(opts, "L1FeeCoefficientSet") + if err != nil { + return nil, err + } + return &ScrollModuleL1FeeCoefficientSetIterator{contract: _ScrollModule.contract, event: "L1FeeCoefficientSet", logs: logs, sub: sub}, nil +} + +func (_ScrollModule *ScrollModuleFilterer) WatchL1FeeCoefficientSet(opts *bind.WatchOpts, sink chan<- *ScrollModuleL1FeeCoefficientSet) (event.Subscription, error) { + + logs, sub, err := _ScrollModule.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(ScrollModuleL1FeeCoefficientSet) + if err := _ScrollModule.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 (_ScrollModule *ScrollModuleFilterer) ParseL1FeeCoefficientSet(log types.Log) (*ScrollModuleL1FeeCoefficientSet, error) { + event := new(ScrollModuleL1FeeCoefficientSet) + if err := _ScrollModule.contract.UnpackLog(event, "L1FeeCoefficientSet", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type ScrollModuleOwnershipTransferRequestedIterator struct { + Event *ScrollModuleOwnershipTransferRequested + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *ScrollModuleOwnershipTransferRequestedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(ScrollModuleOwnershipTransferRequested) + 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(ScrollModuleOwnershipTransferRequested) + 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 *ScrollModuleOwnershipTransferRequestedIterator) Error() error { + return it.fail +} + +func (it *ScrollModuleOwnershipTransferRequestedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type ScrollModuleOwnershipTransferRequested struct { + From common.Address + To common.Address + Raw types.Log +} + +func (_ScrollModule *ScrollModuleFilterer) FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*ScrollModuleOwnershipTransferRequestedIterator, 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 := _ScrollModule.contract.FilterLogs(opts, "OwnershipTransferRequested", fromRule, toRule) + if err != nil { + return nil, err + } + return &ScrollModuleOwnershipTransferRequestedIterator{contract: _ScrollModule.contract, event: "OwnershipTransferRequested", logs: logs, sub: sub}, nil +} + +func (_ScrollModule *ScrollModuleFilterer) WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *ScrollModuleOwnershipTransferRequested, 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 := _ScrollModule.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(ScrollModuleOwnershipTransferRequested) + if err := _ScrollModule.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 (_ScrollModule *ScrollModuleFilterer) ParseOwnershipTransferRequested(log types.Log) (*ScrollModuleOwnershipTransferRequested, error) { + event := new(ScrollModuleOwnershipTransferRequested) + if err := _ScrollModule.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type ScrollModuleOwnershipTransferredIterator struct { + Event *ScrollModuleOwnershipTransferred + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *ScrollModuleOwnershipTransferredIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(ScrollModuleOwnershipTransferred) + 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(ScrollModuleOwnershipTransferred) + 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 *ScrollModuleOwnershipTransferredIterator) Error() error { + return it.fail +} + +func (it *ScrollModuleOwnershipTransferredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type ScrollModuleOwnershipTransferred struct { + From common.Address + To common.Address + Raw types.Log +} + +func (_ScrollModule *ScrollModuleFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*ScrollModuleOwnershipTransferredIterator, 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 := _ScrollModule.contract.FilterLogs(opts, "OwnershipTransferred", fromRule, toRule) + if err != nil { + return nil, err + } + return &ScrollModuleOwnershipTransferredIterator{contract: _ScrollModule.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil +} + +func (_ScrollModule *ScrollModuleFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *ScrollModuleOwnershipTransferred, 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 := _ScrollModule.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(ScrollModuleOwnershipTransferred) + if err := _ScrollModule.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 (_ScrollModule *ScrollModuleFilterer) ParseOwnershipTransferred(log types.Log) (*ScrollModuleOwnershipTransferred, error) { + event := new(ScrollModuleOwnershipTransferred) + if err := _ScrollModule.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 (_ScrollModule *ScrollModule) ParseLog(log types.Log) (generated.AbigenLog, error) { + switch log.Topics[0] { + case _ScrollModule.abi.Events["L1FeeCoefficientSet"].ID: + return _ScrollModule.ParseL1FeeCoefficientSet(log) + case _ScrollModule.abi.Events["OwnershipTransferRequested"].ID: + return _ScrollModule.ParseOwnershipTransferRequested(log) + case _ScrollModule.abi.Events["OwnershipTransferred"].ID: + return _ScrollModule.ParseOwnershipTransferred(log) + + default: + return nil, fmt.Errorf("abigen wrapper received unknown log topic: %v", log.Topics[0]) + } +} + +func (ScrollModuleL1FeeCoefficientSet) Topic() common.Hash { + return common.HexToHash("0x29ec9e31de0d3fe0208a7ccb792bbc26a854f123146110daa3a77219cb74a554") +} + +func (ScrollModuleOwnershipTransferRequested) Topic() common.Hash { + return common.HexToHash("0xed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278") +} + +func (ScrollModuleOwnershipTransferred) Topic() common.Hash { + return common.HexToHash("0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0") +} + func (_ScrollModule *ScrollModule) Address() common.Address { return _ScrollModule.address } @@ -301,13 +798,43 @@ type ScrollModuleInterface interface { BlockNumber(opts *bind.CallOpts) (*big.Int, error) - GetCurrentL1Fee(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) (*ScrollModuleL1FeeCoefficientSetIterator, error) + + WatchL1FeeCoefficientSet(opts *bind.WatchOpts, sink chan<- *ScrollModuleL1FeeCoefficientSet) (event.Subscription, error) + + ParseL1FeeCoefficientSet(log types.Log) (*ScrollModuleL1FeeCoefficientSet, error) + + FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*ScrollModuleOwnershipTransferRequestedIterator, error) + + WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *ScrollModuleOwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error) + + ParseOwnershipTransferRequested(log types.Log) (*ScrollModuleOwnershipTransferRequested, error) + + FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*ScrollModuleOwnershipTransferredIterator, error) + + WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *ScrollModuleOwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error) + + ParseOwnershipTransferred(log types.Log) (*ScrollModuleOwnershipTransferred, error) + + ParseLog(log types.Log) (generated.AbigenLog, error) + Address() common.Address } diff --git a/core/gethwrappers/generated/vrf_coordinator_test_v2_5/vrf_coordinator_test_v2_5.go b/core/gethwrappers/generated/vrf_coordinator_test_v2_5/vrf_coordinator_test_v2_5.go new file mode 100644 index 0000000000..6d6d38ef38 --- /dev/null +++ b/core/gethwrappers/generated/vrf_coordinator_test_v2_5/vrf_coordinator_test_v2_5.go @@ -0,0 +1,3994 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package vrf_coordinator_test_v2_5 + +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 +) + +type VRFOldProof struct { + Pk [2]*big.Int + Gamma [2]*big.Int + C *big.Int + S *big.Int + Seed *big.Int + UWitness common.Address + CGammaWitness [2]*big.Int + SHashWitness [2]*big.Int + ZInv *big.Int +} + +type VRFTypesRequestCommitmentV2Plus struct { + BlockNum uint64 + SubId *big.Int + CallbackGasLimit uint32 + NumWords uint32 + Sender common.Address + ExtraArgs []byte +} + +type VRFV2PlusClientRandomWordsRequest struct { + KeyHash [32]byte + SubId *big.Int + RequestConfirmations uint16 + CallbackGasLimit uint32 + NumWords uint32 + ExtraArgs []byte +} + +var VRFCoordinatorTestV25MetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"blockhashStore\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"internalBalance\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"externalBalance\",\"type\":\"uint256\"}],\"name\":\"BalanceInvariantViolated\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNum\",\"type\":\"uint256\"}],\"name\":\"BlockhashNotInStore\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"coordinatorAddress\",\"type\":\"address\"}],\"name\":\"CoordinatorAlreadyRegistered\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"coordinatorAddress\",\"type\":\"address\"}],\"name\":\"CoordinatorNotRegistered\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FailedToSendNative\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FailedToTransferLink\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"have\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"want\",\"type\":\"uint32\"}],\"name\":\"GasLimitTooBig\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"gasPrice\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxGas\",\"type\":\"uint256\"}],\"name\":\"GasPriceExceeded\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"IncorrectCommitment\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"IndexOutOfRange\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InsufficientBalance\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidCalldata\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"subId\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"consumer\",\"type\":\"address\"}],\"name\":\"InvalidConsumer\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidExtraArgsTag\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"int256\",\"name\":\"linkWei\",\"type\":\"int256\"}],\"name\":\"InvalidLinkWeiPrice\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"premiumPercentage\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"max\",\"type\":\"uint8\"}],\"name\":\"InvalidPremiumPercentage\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint16\",\"name\":\"have\",\"type\":\"uint16\"},{\"internalType\":\"uint16\",\"name\":\"min\",\"type\":\"uint16\"},{\"internalType\":\"uint16\",\"name\":\"max\",\"type\":\"uint16\"}],\"name\":\"InvalidRequestConfirmations\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidSubscription\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"LinkAlreadySet\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"flatFeeLinkDiscountPPM\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"flatFeeNativePPM\",\"type\":\"uint32\"}],\"name\":\"LinkDiscountTooHigh\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"LinkNotSet\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"have\",\"type\":\"uint256\"},{\"internalType\":\"uint32\",\"name\":\"max\",\"type\":\"uint32\"}],\"name\":\"MsgDataTooBig\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"proposedOwner\",\"type\":\"address\"}],\"name\":\"MustBeRequestedOwner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"MustBeSubOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NoCorrespondingRequest\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"keyHash\",\"type\":\"bytes32\"}],\"name\":\"NoSuchProvingKey\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"have\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"want\",\"type\":\"uint32\"}],\"name\":\"NumWordsTooBig\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyCallableFromLink\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"PaymentTooLarge\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"PendingRequestExists\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"keyHash\",\"type\":\"bytes32\"}],\"name\":\"ProvingKeyAlreadyRegistered\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"Reentrant\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"TooManyConsumers\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"minimumRequestConfirmations\",\"type\":\"uint16\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"maxGasLimit\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"stalenessSeconds\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"gasAfterPaymentCalculation\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"fallbackWeiPerUnitLink\",\"type\":\"int256\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeNativePPM\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkDiscountPPM\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"nativePremiumPercentage\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"linkPremiumPercentage\",\"type\":\"uint8\"}],\"name\":\"ConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"coordinatorAddress\",\"type\":\"address\"}],\"name\":\"CoordinatorDeregistered\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"coordinatorAddress\",\"type\":\"address\"}],\"name\":\"CoordinatorRegistered\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"requestId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"fallbackWeiPerUnitLink\",\"type\":\"int256\"}],\"name\":\"FallbackWeiPerUnitLinkUsed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"FundsRecovered\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newCoordinator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"subId\",\"type\":\"uint256\"}],\"name\":\"MigrationCompleted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"NativeFundsRecovered\",\"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\":\"bytes32\",\"name\":\"keyHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"maxGas\",\"type\":\"uint64\"}],\"name\":\"ProvingKeyDeregistered\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"keyHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"maxGas\",\"type\":\"uint64\"}],\"name\":\"ProvingKeyRegistered\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"requestId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"outputSeed\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"subId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint96\",\"name\":\"payment\",\"type\":\"uint96\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"nativePayment\",\"type\":\"bool\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"onlyPremium\",\"type\":\"bool\"}],\"name\":\"RandomWordsFulfilled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"keyHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"requestId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"preSeed\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"subId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"minimumRequestConfirmations\",\"type\":\"uint16\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"numWords\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"extraArgs\",\"type\":\"bytes\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RandomWordsRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"subId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amountLink\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amountNative\",\"type\":\"uint256\"}],\"name\":\"SubscriptionCanceled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"subId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"consumer\",\"type\":\"address\"}],\"name\":\"SubscriptionConsumerAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"subId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"consumer\",\"type\":\"address\"}],\"name\":\"SubscriptionConsumerRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"subId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"SubscriptionCreated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"subId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldBalance\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newBalance\",\"type\":\"uint256\"}],\"name\":\"SubscriptionFunded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"subId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldNativeBalance\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newNativeBalance\",\"type\":\"uint256\"}],\"name\":\"SubscriptionFundedWithNative\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"subId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"SubscriptionOwnerTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"subId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"SubscriptionOwnerTransferred\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"BLOCKHASH_STORE\",\"outputs\":[{\"internalType\":\"contractBlockhashStoreInterface\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"LINK\",\"outputs\":[{\"internalType\":\"contractLinkTokenInterface\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"LINK_NATIVE_FEED\",\"outputs\":[{\"internalType\":\"contractAggregatorV3Interface\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MAX_CONSUMERS\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"\",\"type\":\"uint16\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MAX_NUM_WORDS\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MAX_REQUEST_CONFIRMATIONS\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"\",\"type\":\"uint16\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"subId\",\"type\":\"uint256\"}],\"name\":\"acceptSubscriptionOwnerTransfer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"subId\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"consumer\",\"type\":\"address\"}],\"name\":\"addConsumer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"subId\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"cancelSubscription\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"createSubscription\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"subId\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"}],\"name\":\"deregisterMigratableCoordinator\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256[2]\",\"name\":\"publicProvingKey\",\"type\":\"uint256[2]\"}],\"name\":\"deregisterProvingKey\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint256[2]\",\"name\":\"pk\",\"type\":\"uint256[2]\"},{\"internalType\":\"uint256[2]\",\"name\":\"gamma\",\"type\":\"uint256[2]\"},{\"internalType\":\"uint256\",\"name\":\"c\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"s\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"seed\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"uWitness\",\"type\":\"address\"},{\"internalType\":\"uint256[2]\",\"name\":\"cGammaWitness\",\"type\":\"uint256[2]\"},{\"internalType\":\"uint256[2]\",\"name\":\"sHashWitness\",\"type\":\"uint256[2]\"},{\"internalType\":\"uint256\",\"name\":\"zInv\",\"type\":\"uint256\"}],\"internalType\":\"structVRFOld.Proof\",\"name\":\"proof\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint64\",\"name\":\"blockNum\",\"type\":\"uint64\"},{\"internalType\":\"uint256\",\"name\":\"subId\",\"type\":\"uint256\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"numWords\",\"type\":\"uint32\"},{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"extraArgs\",\"type\":\"bytes\"}],\"internalType\":\"structVRFTypes.RequestCommitmentV2Plus\",\"name\":\"rc\",\"type\":\"tuple\"},{\"internalType\":\"bool\",\"name\":\"onlyPremium\",\"type\":\"bool\"}],\"name\":\"fulfillRandomWords\",\"outputs\":[{\"internalType\":\"uint96\",\"name\":\"payment\",\"type\":\"uint96\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"subId\",\"type\":\"uint256\"}],\"name\":\"fundSubscriptionWithNative\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"startIndex\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxCount\",\"type\":\"uint256\"}],\"name\":\"getActiveSubscriptionIds\",\"outputs\":[{\"internalType\":\"uint256[]\",\"name\":\"ids\",\"type\":\"uint256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"subId\",\"type\":\"uint256\"}],\"name\":\"getSubscription\",\"outputs\":[{\"internalType\":\"uint96\",\"name\":\"balance\",\"type\":\"uint96\"},{\"internalType\":\"uint96\",\"name\":\"nativeBalance\",\"type\":\"uint96\"},{\"internalType\":\"uint64\",\"name\":\"reqCount\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"subOwner\",\"type\":\"address\"},{\"internalType\":\"address[]\",\"name\":\"consumers\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256[2]\",\"name\":\"publicKey\",\"type\":\"uint256[2]\"}],\"name\":\"hashOfKey\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"subId\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"newCoordinator\",\"type\":\"address\"}],\"name\":\"migrate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"onTokenTransfer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"subId\",\"type\":\"uint256\"}],\"name\":\"ownerCancelSubscription\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"subId\",\"type\":\"uint256\"}],\"name\":\"pendingRequestExists\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"recoverFunds\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"addresspayable\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"recoverNativeFunds\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"}],\"name\":\"registerMigratableCoordinator\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256[2]\",\"name\":\"publicProvingKey\",\"type\":\"uint256[2]\"},{\"internalType\":\"uint64\",\"name\":\"maxGas\",\"type\":\"uint64\"}],\"name\":\"registerProvingKey\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"subId\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"consumer\",\"type\":\"address\"}],\"name\":\"removeConsumer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"keyHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"subId\",\"type\":\"uint256\"},{\"internalType\":\"uint16\",\"name\":\"requestConfirmations\",\"type\":\"uint16\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"numWords\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"extraArgs\",\"type\":\"bytes\"}],\"internalType\":\"structVRFV2PlusClient.RandomWordsRequest\",\"name\":\"req\",\"type\":\"tuple\"}],\"name\":\"requestRandomWords\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"requestId\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"subId\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"requestSubscriptionOwnerTransfer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_config\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"minimumRequestConfirmations\",\"type\":\"uint16\"},{\"internalType\":\"uint32\",\"name\":\"maxGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"bool\",\"name\":\"reentrancyLock\",\"type\":\"bool\"},{\"internalType\":\"uint32\",\"name\":\"stalenessSeconds\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasAfterPaymentCalculation\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeNativePPM\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkDiscountPPM\",\"type\":\"uint32\"},{\"internalType\":\"uint8\",\"name\":\"nativePremiumPercentage\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"linkPremiumPercentage\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_currentSubNonce\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_fallbackWeiPerUnitLink\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"s_provingKeyHashes\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"s_provingKeys\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"exists\",\"type\":\"bool\"},{\"internalType\":\"uint64\",\"name\":\"maxGas\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"s_requestCommitments\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_totalBalance\",\"outputs\":[{\"internalType\":\"uint96\",\"name\":\"\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_totalNativeBalance\",\"outputs\":[{\"internalType\":\"uint96\",\"name\":\"\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint16\",\"name\":\"minimumRequestConfirmations\",\"type\":\"uint16\"},{\"internalType\":\"uint32\",\"name\":\"maxGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"stalenessSeconds\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasAfterPaymentCalculation\",\"type\":\"uint32\"},{\"internalType\":\"int256\",\"name\":\"fallbackWeiPerUnitLink\",\"type\":\"int256\"},{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeNativePPM\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"fulfillmentFlatFeeLinkDiscountPPM\",\"type\":\"uint32\"},{\"internalType\":\"uint8\",\"name\":\"nativePremiumPercentage\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"linkPremiumPercentage\",\"type\":\"uint8\"}],\"name\":\"setConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"link\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"linkNativeFeed\",\"type\":\"address\"}],\"name\":\"setLINKAndLINKNativeFeed\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"}],\"name\":\"withdraw\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"addresspayable\",\"name\":\"recipient\",\"type\":\"address\"}],\"name\":\"withdrawNative\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + Bin: "0x60a06040523480156200001157600080fd5b5060405162005e6338038062005e6383398101604081905262000034916200017e565b33806000816200008b5760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620000be57620000be81620000d3565b5050506001600160a01b0316608052620001b0565b336001600160a01b038216036200012d5760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640162000082565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6000602082840312156200019157600080fd5b81516001600160a01b0381168114620001a957600080fd5b9392505050565b608051615c90620001d3600039600081816105d2015261331e0152615c906000f3fe60806040526004361061028c5760003560e01c80638402595e11610164578063b2a7cac5116100c6578063da2f26101161008a578063e72f6e3011610064578063e72f6e3014610904578063ee9d2d3814610924578063f2fde38b1461095157600080fd5b8063da2f261014610854578063dac83d29146108b3578063dc311dd3146108d357600080fd5b8063b2a7cac5146107b4578063bec4c08c146107d4578063caf70c4a146107f4578063cb63179714610814578063d98e620e1461083457600080fd5b80639d40a6fd11610128578063a63e0bfb11610102578063a63e0bfb14610747578063aa433aff14610767578063aefb212f1461078757600080fd5b80639d40a6fd146106da578063a21a23e414610712578063a4c0ed361461072757600080fd5b80638402595e1461064957806386fe91c7146106695780638da5cb5b1461068957806395b55cfc146106a75780639b1c385e146106ba57600080fd5b8063405b84fa1161020d57806364d51a2a116101d157806372e9d565116101ab57806372e9d565146105f457806379ba5097146106145780637a5a2aef1461062957600080fd5b806364d51a2a1461058b57806365982744146105a0578063689c4517146105c057600080fd5b8063405b84fa146104d057806340d6bb82146104f057806341af6c871461051b57806351cff8d91461054b5780635d06b4ab1461056b57600080fd5b806315c48b841161025457806315c48b84146103f157806318e3dd27146104195780631b6b6d23146104585780632f622e6b14610490578063301f42e9146104b057600080fd5b806304104edb14610291578063043bd6ae146102b3578063088070f5146102dc57806308821d58146103b15780630ae09540146103d1575b600080fd5b34801561029d57600080fd5b506102b16102ac366004614f21565b610971565b005b3480156102bf57600080fd5b506102c960105481565b6040519081526020015b60405180910390f35b3480156102e857600080fd5b50600c546103549061ffff81169063ffffffff62010000820481169160ff660100000000000082048116926701000000000000008304811692600160581b8104821692600160781b8204831692600160981b83041691600160b81b8104821691600160c01b9091041689565b6040805161ffff909a168a5263ffffffff98891660208b01529615159689019690965293861660608801529185166080870152841660a08601529290921660c084015260ff91821660e084015216610100820152610120016102d3565b3480156103bd57600080fd5b506102b16103cc366004614f4f565b610aea565b3480156103dd57600080fd5b506102b16103ec366004614f6b565b610ca7565b3480156103fd57600080fd5b5061040660c881565b60405161ffff90911681526020016102d3565b34801561042557600080fd5b50600a5461044090600160601b90046001600160601b031681565b6040516001600160601b0390911681526020016102d3565b34801561046457600080fd5b50600254610478906001600160a01b031681565b6040516001600160a01b0390911681526020016102d3565b34801561049c57600080fd5b506102b16104ab366004614f21565b610cef565b3480156104bc57600080fd5b506104406104cb3660046151cd565b610d95565b3480156104dc57600080fd5b506102b16104eb366004614f6b565b6110ab565b3480156104fc57600080fd5b506105066101f481565b60405163ffffffff90911681526020016102d3565b34801561052757600080fd5b5061053b6105363660046152bb565b61148d565b60405190151581526020016102d3565b34801561055757600080fd5b506102b1610566366004614f21565b611541565b34801561057757600080fd5b506102b1610586366004614f21565b611666565b34801561059757600080fd5b50610406606481565b3480156105ac57600080fd5b506102b16105bb3660046152d4565b611724565b3480156105cc57600080fd5b506104787f000000000000000000000000000000000000000000000000000000000000000081565b34801561060057600080fd5b50600354610478906001600160a01b031681565b34801561062057600080fd5b506102b1611784565b34801561063557600080fd5b506102b1610644366004615302565b611835565b34801561065557600080fd5b506102b1610664366004614f21565b611969565b34801561067557600080fd5b50600a54610440906001600160601b031681565b34801561069557600080fd5b506000546001600160a01b0316610478565b6102b16106b53660046152bb565b611a84565b3480156106c657600080fd5b506102c96106d5366004615336565b611b94565b3480156106e657600080fd5b506007546106fa906001600160401b031681565b6040516001600160401b0390911681526020016102d3565b34801561071e57600080fd5b506102c9611fda565b34801561073357600080fd5b506102b1610742366004615370565b6121c1565b34801561075357600080fd5b506102b161076236600461541b565b612329565b34801561077357600080fd5b506102b16107823660046152bb565b612610565b34801561079357600080fd5b506107a76107a23660046154bc565b612643565b6040516102d39190615519565b3480156107c057600080fd5b506102b16107cf3660046152bb565b612745565b3480156107e057600080fd5b506102b16107ef366004614f6b565b612834565b34801561080057600080fd5b506102c961080f36600461552c565b612927565b34801561082057600080fd5b506102b161082f366004614f6b565b612957565b34801561084057600080fd5b506102c961084f3660046152bb565b612bc5565b34801561086057600080fd5b5061089461086f3660046152bb565b600d6020526000908152604090205460ff81169061010090046001600160401b031682565b6040805192151583526001600160401b039091166020830152016102d3565b3480156108bf57600080fd5b506102b16108ce366004614f6b565b612be6565b3480156108df57600080fd5b506108f36108ee3660046152bb565b612c81565b6040516102d3959493929190615581565b34801561091057600080fd5b506102b161091f366004614f21565b612d5a565b34801561093057600080fd5b506102c961093f3660046152bb565b600f6020526000908152604090205481565b34801561095d57600080fd5b506102b161096c366004614f21565b612f1b565b610979612f2c565b60115460005b81811015610abd57826001600160a01b0316601182815481106109a4576109a46155d6565b6000918252602090912001546001600160a01b031603610aad5760116109cb600184615602565b815481106109db576109db6155d6565b600091825260209091200154601180546001600160a01b039092169183908110610a0757610a076155d6565b9060005260206000200160006101000a8154816001600160a01b0302191690836001600160a01b031602179055506011805480610a4657610a46615615565b6000828152602090819020600019908301810180546001600160a01b03191690559091019091556040516001600160a01b03851681527ff80a1a97fd42251f3c33cda98635e7399253033a6774fe37cd3f650b5282af3791015b60405180910390a1505050565b610ab68161562b565b905061097f565b50604051635428d44960e01b81526001600160a01b03831660048201526024015b60405180910390fd5b50565b610af2612f2c565b604080518082018252600091610b21919084906002908390839080828437600092019190915250612927915050565b6000818152600d602090815260409182902082518084019093525460ff811615158084526101009091046001600160401b03169183019190915291925090610b7f57604051631dfd6e1360e21b815260048101839052602401610ade565b6000828152600d60205260408120805468ffffffffffffffffff19169055600e54905b81811015610c515783600e8281548110610bbe57610bbe6155d6565b906000526020600020015403610c4157600e610bdb600184615602565b81548110610beb57610beb6155d6565b9060005260206000200154600e8281548110610c0957610c096155d6565b600091825260209091200155600e805480610c2657610c26615615565b60019003818190600052602060002001600090559055610c51565b610c4a8161562b565b9050610ba2565b507f9b6868e0eb737bcd72205360baa6bfd0ba4e4819a33ade2db384e8a8025639a5838360200151604051610c999291909182526001600160401b0316602082015260400190565b60405180910390a150505050565b81610cb181612f88565b610cb9612fdd565b610cc28361148d565b15610ce057604051631685ecdd60e31b815260040160405180910390fd5b610cea838361300b565b505050565b610cf7612fdd565b610cff612f2c565b600b54600160601b90046001600160601b0316610d1d8115156130ee565b600b80546bffffffffffffffffffffffff60601b19169055600a8054829190600c90610d5a908490600160601b90046001600160601b0316615644565b92506101000a8154816001600160601b0302191690836001600160601b03160217905550610d9182826001600160601b031661310c565b5050565b6000610d9f612fdd565b60005a9050610324361115610dd157604051630f28961b60e01b81523660048201526103246024820152604401610ade565b6000610ddd8686613180565b90506000610df385836000015160200151613431565b60408301516060888101519293509163ffffffff16806001600160401b03811115610e2057610e20614f9b565b604051908082528060200260200182016040528015610e49578160200160208202803683370190505b50925060005b81811015610eb15760408051602081018590529081018290526060016040516020818303038152906040528051906020012060001c848281518110610e9657610e966155d6565b6020908102919091010152610eaa8161562b565b9050610e4f565b5050602080850180516000908152600f9092526040822082905551610ed7908a8561348c565b60208a8101516000908152600690915260409020805491925090601890610f0d90600160c01b90046001600160401b0316615664565b82546101009290920a6001600160401b0381810219909316918316021790915560808a01516001600160a01b03166000908152600460209081526040808320828e01518452909152902080549091600991610f7091600160481b9091041661568a565b91906101000a8154816001600160401b0302191690836001600160401b0316021790555060008960a0015160018b60a0015151610fad9190615602565b81518110610fbd57610fbd6155d6565b60209101015160f81c60011490506000610fd98887848d613530565b909950905080156110245760208088015160105460408051928352928201527f6ca648a381f22ead7e37773d934e64885dcf861fbfbb26c40354cbf0c4662d1a910160405180910390a15b5061103488828c60200151613568565b6020808b015187820151604080518781526001600160601b038d16948101949094528415159084015284151560608401528b1515608084015290917faeb4b4786571e184246d39587f659abf0e26f41f6a3358692250382c0cdb47b79060a00160405180910390a3505050505050505b9392505050565b6110b3612fdd565b6110bc816136c5565b6110e457604051635428d44960e01b81526001600160a01b0382166004820152602401610ade565b6000806000806110f386612c81565b945094505093509350336001600160a01b0316826001600160a01b03161461115d5760405162461bcd60e51b815260206004820152601660248201527f4e6f7420737562736372697074696f6e206f776e6572000000000000000000006044820152606401610ade565b6111668661148d565b156111b35760405162461bcd60e51b815260206004820152601660248201527f50656e64696e67207265717565737420657869737473000000000000000000006044820152606401610ade565b6040805160c0810182526001815260208082018990526001600160a01b03851682840152606082018490526001600160601b038088166080840152861660a083015291519091600091611208918491016156ad565b604051602081830303815290604052905061122288613730565b505060405163ce3f471960e01b81526001600160a01b0388169063ce3f4719906001600160601b0388169061125b908590600401615772565b6000604051808303818588803b15801561127457600080fd5b505af1158015611288573d6000803e3d6000fd5b50506002546001600160a01b0316158015935091506112b1905057506001600160601b03861615155b156113815760025460405163a9059cbb60e01b81526001600160a01b0389811660048301526001600160601b03891660248301529091169063a9059cbb906044016020604051808303816000875af1158015611311573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113359190615785565b6113815760405162461bcd60e51b815260206004820152601260248201527f696e73756666696369656e742066756e647300000000000000000000000000006044820152606401610ade565b600c805466ff0000000000001916660100000000000017905560005b8351811015611430578381815181106113b8576113b86155d6565b6020908102919091010151604051638ea9811760e01b81526001600160a01b038a8116600483015290911690638ea9811790602401600060405180830381600087803b15801561140757600080fd5b505af115801561141b573d6000803e3d6000fd5b50505050806114299061562b565b905061139d565b50600c805466ff00000000000019169055604080516001600160a01b0389168152602081018a90527fd63ca8cb945956747ee69bfdc3ea754c24a4caf7418db70e46052f7850be4187910160405180910390a15050505050505050565b600081815260056020526040812060020180548083036114b1575060009392505050565b60005b81811015611536576000600460008584815481106114d4576114d46155d6565b60009182526020808320909101546001600160a01b0316835282810193909352604091820181208982529092529020546001600160401b03600160481b90910416111561152657506001949350505050565b61152f8161562b565b90506114b4565b506000949350505050565b611549612fdd565b611551612f2c565b6002546001600160a01b031661157a5760405163c1f0c0a160e01b815260040160405180910390fd5b600b546001600160601b03166115918115156130ee565b600b80546bffffffffffffffffffffffff19169055600a80548291906000906115c49084906001600160601b0316615644565b82546101009290920a6001600160601b0381810219909316918316021790915560025460405163a9059cbb60e01b81526001600160a01b0386811660048301529285166024820152610d91935091169063a9059cbb906044015b6020604051808303816000875af115801561163d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116619190615785565b6130ee565b61166e612f2c565b611677816136c5565b156116a05760405163ac8a27ef60e01b81526001600160a01b0382166004820152602401610ade565b601180546001810182556000919091527f31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c680180546001600160a01b0319166001600160a01b0383169081179091556040519081527fb7cabbfc11e66731fc77de0444614282023bcbd41d16781c753a431d0af016259060200160405180910390a150565b61172c612f2c565b6002546001600160a01b03161561175657604051631688c53760e11b815260040160405180910390fd5b600280546001600160a01b039384166001600160a01b03199182161790915560038054929093169116179055565b6001546001600160a01b031633146117de5760405162461bcd60e51b815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e6572000000000000000000006044820152606401610ade565b60008054336001600160a01b0319808316821784556001805490911690556040516001600160a01b0390921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b61183d612f2c565b60408051808201825260009161186c919085906002908390839080828437600092019190915250612927915050565b6000818152600d602052604090205490915060ff16156118a257604051634a0b8fa760e01b815260048101829052602401610ade565b60408051808201825260018082526001600160401b0385811660208085018281526000888152600d835287812096518754925168ffffffffffffffffff1990931690151568ffffffffffffffff00191617610100929095169190910293909317909455600e805493840181559091527fbb7b4a454dc3493923482f07822329ed19e8244eff582cc204f8554c3620c3fd9091018490558251848152918201527f9b911b2c240bfbef3b6a8f7ed6ee321d1258bb2a3fe6becab52ac1cd3210afd39101610aa0565b611971612f2c565b600a544790600160601b90046001600160601b0316818111156119b1576040516354ced18160e11b81526004810182905260248101839052604401610ade565b81811015610cea5760006119c58284615602565b90506000846001600160a01b03168260405160006040518083038185875af1925050503d8060008114611a14576040519150601f19603f3d011682016040523d82523d6000602084013e611a19565b606091505b5050905080611a3b5760405163950b247960e01b815260040160405180910390fd5b604080516001600160a01b0387168152602081018490527f4aed7c8eed0496c8c19ea2681fcca25741c1602342e38b045d9f1e8e905d2e9c910160405180910390a15050505050565b611a8c612fdd565b600081815260056020526040902054611aad906001600160a01b03166138e2565b60008181526006602052604090208054600160601b90046001600160601b0316903490600c611adc83856157a2565b92506101000a8154816001600160601b0302191690836001600160601b0316021790555034600a600c8282829054906101000a90046001600160601b0316611b2491906157a2565b92506101000a8154816001600160601b0302191690836001600160601b03160217905550817f7603b205d03651ee812f803fccde89f1012e545a9c99f0abfea9cedd0fd8e902823484611b7791906157c2565b604080519283526020830191909152015b60405180910390a25050565b6000611b9e612fdd565b602080830135600081815260059092526040909120546001600160a01b0316611bda57604051630fb532db60e11b815260040160405180910390fd5b336000908152600460209081526040808320848452808352928190208151606081018352905460ff811615158083526001600160401b036101008304811695840195909552600160481b9091049093169181019190915290611c58576040516379bfd40160e01b815260048101849052336024820152604401610ade565b600c5461ffff16611c6f60608701604088016157d5565b61ffff161080611c92575060c8611c8c60608701604088016157d5565b61ffff16115b15611cd857611ca760608601604087016157d5565b600c5460405163539c34bb60e11b815261ffff92831660048201529116602482015260c86044820152606401610ade565b600c5462010000900463ffffffff16611cf760808701606088016157f0565b63ffffffff161115611d4757611d1360808601606087016157f0565b600c54604051637aebf00f60e11b815263ffffffff9283166004820152620100009091049091166024820152604401610ade565b6101f4611d5a60a08701608088016157f0565b63ffffffff161115611da057611d7660a08601608087016157f0565b6040516311ce1afb60e21b815263ffffffff90911660048201526101f46024820152604401610ade565b806020018051611daf90615664565b6001600160401b03169052604081018051611dc990615664565b6001600160401b03908116909152602082810151604080518935818501819052338284015260608201899052929094166080808601919091528151808603909101815260a08501825280519084012060c085019290925260e08085018390528151808603909101815261010090940190528251929091019190912060009190955090506000611e6b611e66611e6160a08a018a61580b565b613909565b61398a565b905085611e766139fb565b86611e8760808b0160608c016157f0565b611e9760a08c0160808d016157f0565b3386604051602001611eaf9796959493929190615858565b60405160208183030381529060405280519060200120600f600088815260200190815260200160002081905550336001600160a01b03168588600001357feb0e3652e0f44f417695e6e90f2f42c99b65cd7169074c5a654b16b9748c3a4e89868c6040016020810190611f2291906157d5565b8d6060016020810190611f3591906157f0565b8e6080016020810190611f4891906157f0565b89604051611f5b969594939291906158af565b60405180910390a45050600092835260209182526040928390208151815493830151929094015168ffffffffffffffffff1990931693151568ffffffffffffffff001916939093176101006001600160401b03928316021770ffffffffffffffff0000000000000000001916600160481b91909216021790555b919050565b6000611fe4612fdd565b6007546001600160401b031633611ffc600143615602565b6040516bffffffffffffffffffffffff19606093841b81166020830152914060348201523090921b1660548201526001600160c01b031960c083901b16606882015260700160408051601f19818403018152919052805160209091012091506120668160016158ee565b6007805467ffffffffffffffff19166001600160401b03928316179055604080516000808252608082018352602080830182815283850183815260608086018581528a86526006855287862093518454935191516001600160601b039182166001600160c01b031990951694909417600160601b91909216021777ffffffffffffffffffffffffffffffffffffffffffffffff16600160c01b9290981691909102969096179055835194850184523385528481018281528585018481528884526005835294909220855181546001600160a01b03199081166001600160a01b0392831617835593516001830180549095169116179092559251805192949391926121769260028501920190614e0f565b5061218691506008905084613a7c565b5060405133815283907f1d3015d7ba850fa198dc7b1a3f5d42779313a681035f77c8c03764c61005518d9060200160405180910390a2505090565b6121c9612fdd565b6002546001600160a01b031633146121f4576040516344b0e3c360e01b815260040160405180910390fd5b6020811461221557604051638129bbcd60e01b815260040160405180910390fd5b6000612223828401846152bb565b600081815260056020526040902054909150612247906001600160a01b03166138e2565b600081815260066020526040812080546001600160601b03169186919061226e83856157a2565b92506101000a8154816001600160601b0302191690836001600160601b0316021790555084600a60008282829054906101000a90046001600160601b03166122b691906157a2565b92506101000a8154816001600160601b0302191690836001600160601b03160217905550817f1ced9348ff549fceab2ac57cd3a9de38edaaab274b725ee82c23e8fc8c4eec7a82878461230991906157c2565b6040805192835260208301919091520160405180910390a2505050505050565b612331612f2c565b60c861ffff8a16111561236b5760405163539c34bb60e11b815261ffff8a1660048201819052602482015260c86044820152606401610ade565b6000851361238f576040516321ea67b360e11b815260048101869052602401610ade565b8363ffffffff168363ffffffff1611156123cc576040516313c06e5960e11b815263ffffffff808516600483015285166024820152604401610ade565b609b60ff831611156123fd57604051631d66288d60e11b815260ff83166004820152609b6024820152604401610ade565b609b60ff8216111561242e57604051631d66288d60e11b815260ff82166004820152609b6024820152604401610ade565b604080516101208101825261ffff8b1680825263ffffffff808c16602084018190526000848601528b8216606085018190528b8316608086018190528a841660a08701819052938a1660c0870181905260ff808b1660e08901819052908a16610100909801889052600c8054600160c01b90990260ff60c01b19600160b81b9093029290921661ffff60b81b19600160981b90940263ffffffff60981b19600160781b9099029890981676ffffffffffffffff00000000000000000000000000000019600160581b9096026effffffff000000000000000000000019670100000000000000909802979097166effffffffffffffffff000000000000196201000090990265ffffffffffff19909c16909a179a909a1796909616979097179390931791909116959095179290921793909316929092179190911790556010869055517f2c6b6b12413678366b05b145c5f00745bdd00e739131ab5de82484a50c9d78b6906125fd908b908b908b908b908b908b908b908b908b9061ffff99909916895263ffffffff97881660208a0152958716604089015293861660608801526080870192909252841660a086015290921660c084015260ff91821660e0840152166101008201526101200190565b60405180910390a1505050505050505050565b612618612f2c565b6000818152600560205260409020546001600160a01b0316612639816138e2565b610d91828261300b565b606060006126516008613a88565b905080841061267357604051631390f2a160e01b815260040160405180910390fd5b600061267f84866157c2565b90508181118061268d575083155b6126975780612699565b815b905060006126a78683615602565b9050806001600160401b038111156126c1576126c1614f9b565b6040519080825280602002602001820160405280156126ea578160200160208202803683370190505b50935060005b8181101561273a5761270d61270588836157c2565b600890613a92565b85828151811061271f5761271f6155d6565b60209081029190910101526127338161562b565b90506126f0565b505050505b92915050565b61274d612fdd565b6000818152600560205260409020546001600160a01b031661276e816138e2565b6000828152600560205260409020600101546001600160a01b031633146127c7576000828152600560205260409081902060010154905163d084e97560e01b81526001600160a01b039091166004820152602401610ade565b6000828152600560209081526040918290208054336001600160a01b03199182168117835560019092018054909116905582516001600160a01b03851681529182015283917fd4114ab6e9af9f597c52041f32d62dc57c5c4e4c0d4427006069635e216c93869101611b88565b8161283e81612f88565b612846612fdd565b6001600160a01b03821660009081526004602090815260408083208684529091529020805460ff16156128795750505050565b60008481526005602052604090206002018054606319016128ad576040516305a48e0f60e01b815260040160405180910390fd5b8154600160ff199091168117835581549081018255600082815260209081902090910180546001600160a01b0319166001600160a01b03871690811790915560405190815286917f1e980d04aa7648e205713e5e8ea3808672ac163d10936d36f91b2c88ac1575e191015b60405180910390a25050505050565b60008160405160200161293a9190615931565b604051602081830303815290604052805190602001209050919050565b8161296181612f88565b612969612fdd565b6129728361148d565b1561299057604051631685ecdd60e31b815260040160405180910390fd5b6001600160a01b038216600090815260046020908152604080832086845290915290205460ff166129e6576040516379bfd40160e01b8152600481018490526001600160a01b0383166024820152604401610ade565b600083815260056020908152604080832060020180548251818502810185019093528083529192909190830182828015612a4957602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311612a2b575b50505050509050600060018251612a609190615602565b905060005b8251811015612b6957846001600160a01b0316838281518110612a8a57612a8a6155d6565b60200260200101516001600160a01b031603612b59576000838381518110612ab457612ab46155d6565b6020026020010151905080600560008981526020019081526020016000206002018381548110612ae657612ae66155d6565b600091825260208083209190910180546001600160a01b0319166001600160a01b039490941693909317909255888152600590915260409020600201805480612b3157612b31615615565b600082815260209020810160001990810180546001600160a01b031916905501905550612b69565b612b628161562b565b9050612a65565b506001600160a01b0384166000818152600460209081526040808320898452825291829020805460ff19169055905191825286917f32158c6058347c1601b2d12bc696ac6901d8a9a9aa3ba10c27ab0a983e8425a79101612918565b600e8181548110612bd557600080fd5b600091825260209091200154905081565b81612bf081612f88565b612bf8612fdd565b600083815260056020526040902060018101546001600160a01b03848116911614612c7b576001810180546001600160a01b0319166001600160a01b03851690811790915560408051338152602081019290925285917f21a4dad170a6bf476c31bbcf4a16628295b0e450672eec25d7c93308e05344a191015b60405180910390a25b50505050565b600081815260056020526040812054819081906001600160a01b03166060612ca8826138e2565b600086815260066020908152604080832054600583529281902060020180548251818502810185019093528083526001600160601b0380861695600160601b810490911694600160c01b9091046001600160401b0316938893929091839190830182828015612d4057602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311612d22575b505050505090509450945094509450945091939590929450565b612d62612f2c565b6002546001600160a01b0316612d8b5760405163c1f0c0a160e01b815260040160405180910390fd5b6002546040516370a0823160e01b81523060048201526000916001600160a01b0316906370a0823190602401602060405180830381865afa158015612dd4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612df8919061593f565b600a549091506001600160601b031681811115612e32576040516354ced18160e11b81526004810182905260248101839052604401610ade565b81811015610cea576000612e468284615602565b60025460405163a9059cbb60e01b81526001600160a01b0387811660048301526024820184905292935091169063a9059cbb906044016020604051808303816000875af1158015612e9b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612ebf9190615785565b612edc57604051631f01ff1360e21b815260040160405180910390fd5b604080516001600160a01b0386168152602081018390527f59bfc682b673f8cbf945f1e454df9334834abf7dfe7f92237ca29ecb9b4366009101610c99565b612f23612f2c565b610ae781613a9e565b6000546001600160a01b03163314612f865760405162461bcd60e51b815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610ade565b565b6000818152600560205260409020546001600160a01b0316612fa9816138e2565b336001600160a01b03821614610d9157604051636c51fda960e11b81526001600160a01b0382166004820152602401610ade565b600c546601000000000000900460ff1615612f865760405163769dd35360e11b815260040160405180910390fd5b60008061301784613730565b60025491935091506001600160a01b03161580159061303e57506001600160601b03821615155b156130865760025460405163a9059cbb60e01b81526001600160a01b0385811660048301526001600160601b038516602483015261308692169063a9059cbb9060440161161e565b61309983826001600160601b031661310c565b604080516001600160a01b03851681526001600160601b03808516602083015283169181019190915284907f8c74ce8b8cf87f5eb001275c8be27eb34ea2b62bfab6814fcc62192bb63e81c490606001612c72565b80610ae757604051631e9acf1760e31b815260040160405180910390fd5b6000826001600160a01b03168260405160006040518083038185875af1925050503d8060008114613159576040519150601f19603f3d011682016040523d82523d6000602084013e61315e565b606091505b5050905080610cea5760405163950b247960e01b815260040160405180910390fd5b6040805160a081018252600060608201818152608083018290528252602082018190529181019190915260006131b98460000151612927565b6000818152600d602090815260409182902082518084019093525460ff811615158084526101009091046001600160401b0316918301919091529192509061321757604051631dfd6e1360e21b815260048101839052602401610ade565b6000828660800151604051602001613239929190918252602082015260400190565b60408051601f1981840301815291815281516020928301206000818152600f909352908220549092509081900361328357604051631b44092560e11b815260040160405180910390fd5b85516020808801516040808a015160608b015160808c015160a08d015193516132b2978a979096959101615958565b6040516020818303038152906040528051906020012081146132e75760405163354a450b60e21b815260040160405180910390fd5b60006132f68760000151613b47565b9050806133bf578651604051631d2827a760e31b81526001600160401b0390911660048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063e9413d3890602401602060405180830381865afa15801561336d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613391919061593f565b9050806133bf57865160405163175dadad60e01b81526001600160401b039091166004820152602401610ade565b60008860800151826040516020016133e1929190918252602082015260400190565b6040516020818303038152906040528051906020012060001c905060006134088a83613c1a565b604080516060810182529788526020880196909652948601949094525092979650505050505050565b6000816001600160401b03163a111561348457821561345a57506001600160401b03811661273f565b60405163435e532d60e11b81523a60048201526001600160401b0383166024820152604401610ade565b503a92915050565b6000806000631fe543e360e01b86856040516024016134ac9291906159ab565b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b031990941693909317909252600c805466ff000000000000191666010000000000001790559086015160808701519192506135169163ffffffff9091169083613c85565b600c805466ff000000000000191690559695505050505050565b600080831561354f57613544868685613cd1565b60009150915061355f565b61355a868685613de2565b915091505b94509492505050565b6000818152600660205260409020821561362c5780546001600160601b03600160601b90910481169085168110156135b357604051631e9acf1760e31b815260040160405180910390fd5b6135bd8582615644565b82546bffffffffffffffffffffffff60601b1916600160601b6001600160601b039283168102919091178455600b805488939192600c926136029286929004166157a2565b92506101000a8154816001600160601b0302191690836001600160601b0316021790555050612c7b565b80546001600160601b0390811690851681101561365c57604051631e9acf1760e31b815260040160405180910390fd5b6136668582615644565b82546bffffffffffffffffffffffff19166001600160601b03918216178355600b8054879260009161369a918591166157a2565b92506101000a8154816001600160601b0302191690836001600160601b031602179055505050505050565b601154600090815b8181101561372657836001600160a01b0316601182815481106136f2576136f26155d6565b6000918252602090912001546001600160a01b031603613716575060019392505050565b61371f8161562b565b90506136cd565b5060009392505050565b60008181526005602090815260408083206006909252822054600290910180546001600160601b0380841694600160601b90940416925b818110156137dc5760046000848381548110613785576137856155d6565b60009182526020808320909101546001600160a01b0316835282810193909352604091820181208982529092529020805470ffffffffffffffffffffffffffffffffff191690556137d58161562b565b9050613767565b50600085815260056020526040812080546001600160a01b031990811682556001820180549091169055906138146002830182614e74565b5050600085815260066020526040812055613830600886613fd4565b506001600160601b0384161561388357600a805485919060009061385e9084906001600160601b0316615644565b92506101000a8154816001600160601b0302191690836001600160601b031602179055505b6001600160601b038316156138db5782600a600c8282829054906101000a90046001600160601b03166138b69190615644565b92506101000a8154816001600160601b0302191690836001600160601b031602179055505b5050915091565b6001600160a01b038116610ae757604051630fb532db60e11b815260040160405180910390fd5b6040805160208101909152600081526000829003613936575060408051602081019091526000815261273f565b63125fa26760e31b61394883856159cc565b6001600160e01b0319161461397057604051632923fee760e11b815260040160405180910390fd5b61397d82600481866159fc565b8101906110a49190615a26565b60607f92fd13387c7fe7befbc38d303d6468778fb9731bc4583f17d92989c6fcfdeaaa826040516024016139c391511515815260200190565b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b03199093169290921790915292915050565b600046613a0781613fe0565b15613a755760646001600160a01b031663a3b1b31d6040518163ffffffff1660e01b8152600401602060405180830381865afa158015613a4b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613a6f919061593f565b91505090565b4391505090565b60006110a48383614003565b600061273f825490565b60006110a48383614052565b336001600160a01b03821603613af65760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610ade565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b600046613b5381613fe0565b15613c0b57610100836001600160401b0316613b6d6139fb565b613b779190615602565b1180613b935750613b866139fb565b836001600160401b031610155b15613ba15750600092915050565b6040516315a03d4160e11b81526001600160401b0384166004820152606490632b407a82906024015b602060405180830381865afa158015613be7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110a4919061593f565b50506001600160401b03164090565b6000613c4e8360000151846020015185604001518660600151868860a001518960c001518a60e001518b610100015161407c565b60038360200151604051602001613c66929190615a71565b60408051601f1981840301815291905280516020909101209392505050565b60005a611388811015613c9757600080fd5b611388810390508460408204820311613caf57600080fd5b50823b613cbb57600080fd5b60008083516020850160008789f1949350505050565b600080613d146000368080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506142a792505050565b905060005a600c54613d34908890600160581b900463ffffffff166157c2565b613d3e9190615602565b613d489086615a85565b600c54909150600090613d6d90600160781b900463ffffffff1664e8d4a51000615a85565b90508415613db957600c548190606490600160b81b900460ff16613d9185876157c2565b613d9b9190615a85565b613da59190615ab2565b613daf91906157c2565b93505050506110a4565b600c548190606490613dd590600160b81b900460ff1682615ac6565b60ff16613d9185876157c2565b600080600080613df0614387565b9150915060008213613e18576040516321ea67b360e11b815260048101839052602401610ade565b6000613e5a6000368080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506142a792505050565b9050600083825a600c54613e7c908d90600160581b900463ffffffff166157c2565b613e869190615602565b613e90908b615a85565b613e9a91906157c2565b613eac90670de0b6b3a7640000615a85565b613eb69190615ab2565b600c54909150600090613edf9063ffffffff600160981b8204811691600160781b900416615adf565b613ef49063ffffffff1664e8d4a51000615a85565b9050600085613f0b83670de0b6b3a7640000615a85565b613f159190615ab2565b905060008915613f5657600c548290606490613f3b90600160c01b900460ff1687615a85565b613f459190615ab2565b613f4f91906157c2565b9050613f96565b600c548290606490613f7290600160c01b900460ff1682615ac6565b613f7f9060ff1687615a85565b613f899190615ab2565b613f9391906157c2565b90505b6b033b2e3c9fd0803ce8000000811115613fc35760405163e80fa38160e01b815260040160405180910390fd5b9b949a509398505050505050505050565b60006110a48383614452565b600061a4b1821480613ff4575062066eed82145b8061273f57505062066eee1490565b600081815260018301602052604081205461404a5750815460018181018455600084815260208082209093018490558454848252828601909352604090209190915561273f565b50600061273f565b6000826000018281548110614069576140696155d6565b9060005260206000200154905092915050565b6140858961454c565b6140d15760405162461bcd60e51b815260206004820152601a60248201527f7075626c6963206b6579206973206e6f74206f6e2063757276650000000000006044820152606401610ade565b6140da8861454c565b6141265760405162461bcd60e51b815260206004820152601560248201527f67616d6d61206973206e6f74206f6e20637572766500000000000000000000006044820152606401610ade565b61412f8361454c565b61417b5760405162461bcd60e51b815260206004820152601d60248201527f6347616d6d615769746e657373206973206e6f74206f6e2063757276650000006044820152606401610ade565b6141848261454c565b6141d05760405162461bcd60e51b815260206004820152601c60248201527f73486173685769746e657373206973206e6f74206f6e206375727665000000006044820152606401610ade565b6141dc878a8887614625565b6142285760405162461bcd60e51b815260206004820152601960248201527f6164647228632a706b2b732a6729213d5f755769746e657373000000000000006044820152606401610ade565b60006142348a87614748565b90506000614247898b878b8689896147ac565b90506000614258838d8d8a866148d8565b9050808a146142995760405162461bcd60e51b815260206004820152600d60248201526c34b73b30b634b210383937b7b360991b6044820152606401610ade565b505050505050505050505050565b6000466142b381613fe0565b156142f757606c6001600160a01b031663c6f7de0e6040518163ffffffff1660e01b8152600401602060405180830381865afa158015613be7573d6000803e3d6000fd5b61430081614918565b1561437e5773420000000000000000000000000000000000000f6001600160a01b03166349948e0e84604051806080016040528060488152602001615c3c60489139604051602001614353929190615afc565b6040516020818303038152906040526040518263ffffffff1660e01b8152600401613bca9190615772565b50600092915050565b600c5460035460408051633fabe5a360e21b81529051600093849367010000000000000090910463ffffffff169284926001600160a01b039092169163feaf968c9160048082019260a0929091908290030181865afa1580156143ee573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906144129190615b45565b50919650909250505063ffffffff82161580159061443e57506144358142615602565b8263ffffffff16105b9250821561444c5760105493505b50509091565b6000818152600183016020526040812054801561453b576000614476600183615602565b855490915060009061448a90600190615602565b90508181146144ef5760008660000182815481106144aa576144aa6155d6565b90600052602060002001549050808760000184815481106144cd576144cd6155d6565b6000918252602080832090910192909255918252600188019052604090208390555b855486908061450057614500615615565b60019003818190600052602060002001600090559055856001016000868152602001908152602001600020600090556001935050505061273f565b600091505061273f565b5092915050565b80516000906401000003d019116145a55760405162461bcd60e51b815260206004820152601260248201527f696e76616c696420782d6f7264696e61746500000000000000000000000000006044820152606401610ade565b60208201516401000003d019116145fe5760405162461bcd60e51b815260206004820152601260248201527f696e76616c696420792d6f7264696e61746500000000000000000000000000006044820152606401610ade565b60208201516401000003d01990800961461e8360005b602002015161495f565b1492915050565b60006001600160a01b03821661466b5760405162461bcd60e51b815260206004820152600b60248201526a626164207769746e65737360a81b6044820152606401610ade565b60208401516000906001161561468257601c614685565b601b5b9050600070014551231950b75fc4402da1732fc9bebe1985876000602002015109865170014551231950b75fc4402da1732fc9bebe19918203925060009190890987516040805160008082526020820180845287905260ff88169282019290925260608101929092526080820183905291925060019060a0016020604051602081039080840390855afa158015614720573d6000803e3d6000fd5b5050604051601f1901516001600160a01b039081169088161495505050505050949350505050565b614750614e92565b61477d6001848460405160200161476993929190615b95565b604051602081830303815290604052614983565b90505b6147898161454c565b61273f5780516040805160208101929092526147a59101614769565b9050614780565b6147b4614e92565b825186516401000003d01991829006919006036148135760405162461bcd60e51b815260206004820152601e60248201527f706f696e747320696e2073756d206d7573742062652064697374696e637400006044820152606401610ade565b61481e8789886149d0565b61486a5760405162461bcd60e51b815260206004820152601660248201527f4669727374206d756c20636865636b206661696c6564000000000000000000006044820152606401610ade565b6148758486856149d0565b6148c15760405162461bcd60e51b815260206004820152601760248201527f5365636f6e64206d756c20636865636b206661696c65640000000000000000006044820152606401610ade565b6148cc868484614afb565b98975050505050505050565b6000600286868685876040516020016148f696959493929190615bb6565b60408051601f1981840301815291905280516020909101209695505050505050565b6000600a82148061492a57506101a482145b80614937575062aa37dc82145b80614943575061210582145b80614950575062014a3382145b8061273f57505062014a341490565b6000806401000003d01980848509840990506401000003d019600782089392505050565b61498b614e92565b61499482614bc2565b81526149a96149a4826000614614565b614bfd565b6020820181905260029006600103611fd5576020810180516401000003d019039052919050565b600082600003614a105760405162461bcd60e51b815260206004820152600b60248201526a3d32b9379039b1b0b630b960a91b6044820152606401610ade565b83516020850151600090614a2690600290615c15565b15614a3257601c614a35565b601b5b9050600070014551231950b75fc4402da1732fc9bebe198387096040805160008082526020820180845281905260ff86169282019290925260608101869052608081018390529192509060019060a0016020604051602081039080840390855afa158015614aa7573d6000803e3d6000fd5b505050602060405103519050600086604051602001614ac69190615c29565b60408051601f1981840301815291905280516020909101206001600160a01b0392831692169190911498975050505050505050565b614b03614e92565b835160208086015185519186015160009384938493614b2493909190614c1d565b919450925090506401000003d019858209600114614b845760405162461bcd60e51b815260206004820152601960248201527f696e765a206d75737420626520696e7665727365206f66207a000000000000006044820152606401610ade565b60405180604001604052806401000003d01980614ba357614ba3615a9c565b87860981526020016401000003d0198785099052979650505050505050565b805160208201205b6401000003d0198110611fd557604080516020808201939093528151808203840181529082019091528051910120614bca565b600061273f826002614c166401000003d01960016157c2565b901c614cfd565b60008080600180826401000003d019896401000003d019038808905060006401000003d0198b6401000003d019038a0890506000614c5d83838585614da2565b9098509050614c6e88828e88614dc6565b9098509050614c7f88828c87614dc6565b90985090506000614c928d878b85614dc6565b9098509050614ca388828686614da2565b9098509050614cb488828e89614dc6565b9098509050818114614ce9576401000003d019818a0998506401000003d01982890997506401000003d0198183099650614ced565b8196505b5050505050509450945094915050565b600080614d08614eb0565b6020808252818101819052604082015260608101859052608081018490526401000003d01960a0820152614d3a614ece565b60208160c0846005600019fa925082600003614d985760405162461bcd60e51b815260206004820152601260248201527f6269674d6f64457870206661696c7572652100000000000000000000000000006044820152606401610ade565b5195945050505050565b6000806401000003d0198487096401000003d0198487099097909650945050505050565b600080806401000003d019878509905060006401000003d01987876401000003d019030990506401000003d0198183086401000003d01986890990999098509650505050505050565b828054828255906000526020600020908101928215614e64579160200282015b82811115614e6457825182546001600160a01b0319166001600160a01b03909116178255602090920191600190910190614e2f565b50614e70929150614eec565b5090565b5080546000825590600052602060002090810190610ae79190614eec565b60405180604001604052806002906020820280368337509192915050565b6040518060c001604052806006906020820280368337509192915050565b60405180602001604052806001906020820280368337509192915050565b5b80821115614e705760008155600101614eed565b6001600160a01b0381168114610ae757600080fd5b8035611fd581614f01565b600060208284031215614f3357600080fd5b81356110a481614f01565b806040810183101561273f57600080fd5b600060408284031215614f6157600080fd5b6110a48383614f3e565b60008060408385031215614f7e57600080fd5b823591506020830135614f9081614f01565b809150509250929050565b634e487b7160e01b600052604160045260246000fd5b60405160c081016001600160401b0381118282101715614fd357614fd3614f9b565b60405290565b60405161012081016001600160401b0381118282101715614fd357614fd3614f9b565b604051601f8201601f191681016001600160401b038111828210171561502457615024614f9b565b604052919050565b600082601f83011261503d57600080fd5b604051604081018181106001600160401b038211171561505f5761505f614f9b565b806040525080604084018581111561507657600080fd5b845b81811015615090578035835260209283019201615078565b509195945050505050565b80356001600160401b0381168114611fd557600080fd5b803563ffffffff81168114611fd557600080fd5b600060c082840312156150d857600080fd5b6150e0614fb1565b90506150eb8261509b565b815260208083013581830152615103604084016150b2565b6040830152615114606084016150b2565b6060830152608083013561512781614f01565b608083015260a08301356001600160401b038082111561514657600080fd5b818501915085601f83011261515a57600080fd5b81358181111561516c5761516c614f9b565b61517e601f8201601f19168501614ffc565b9150808252868482850101111561519457600080fd5b80848401858401376000848284010152508060a085015250505092915050565b8015158114610ae757600080fd5b8035611fd5816151b4565b60008060008385036101e08112156151e457600080fd5b6101a0808212156151f457600080fd5b6151fc614fd9565b9150615208878761502c565b8252615217876040880161502c565b60208301526080860135604083015260a0860135606083015260c0860135608083015261524660e08701614f16565b60a083015261010061525a8882890161502c565b60c084015261526d88610140890161502c565b60e0840152610180870135908301529093508401356001600160401b0381111561529657600080fd5b6152a2868287016150c6565b9250506152b26101c085016151c2565b90509250925092565b6000602082840312156152cd57600080fd5b5035919050565b600080604083850312156152e757600080fd5b82356152f281614f01565b91506020830135614f9081614f01565b6000806060838503121561531557600080fd5b61531f8484614f3e565b915061532d6040840161509b565b90509250929050565b60006020828403121561534857600080fd5b81356001600160401b0381111561535e57600080fd5b820160c081850312156110a457600080fd5b6000806000806060858703121561538657600080fd5b843561539181614f01565b93506020850135925060408501356001600160401b03808211156153b457600080fd5b818701915087601f8301126153c857600080fd5b8135818111156153d757600080fd5b8860208285010111156153e957600080fd5b95989497505060200194505050565b803561ffff81168114611fd557600080fd5b803560ff81168114611fd557600080fd5b60008060008060008060008060006101208a8c03121561543a57600080fd5b6154438a6153f8565b985061545160208b016150b2565b975061545f60408b016150b2565b965061546d60608b016150b2565b955060808a0135945061548260a08b016150b2565b935061549060c08b016150b2565b925061549e60e08b0161540a565b91506154ad6101008b0161540a565b90509295985092959850929598565b600080604083850312156154cf57600080fd5b50508035926020909101359150565b600081518084526020808501945080840160005b8381101561550e578151875295820195908201906001016154f2565b509495945050505050565b6020815260006110a460208301846154de565b60006040828403121561553e57600080fd5b6110a4838361502c565b600081518084526020808501945080840160005b8381101561550e5781516001600160a01b03168752958201959082019060010161555c565b60006001600160601b0380881683528087166020840152506001600160401b03851660408301526001600160a01b038416606083015260a060808301526155cb60a0830184615548565b979650505050505050565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b8181038181111561273f5761273f6155ec565b634e487b7160e01b600052603160045260246000fd5b60006001820161563d5761563d6155ec565b5060010190565b6001600160601b03828116828216039080821115614545576145456155ec565b60006001600160401b03808316818103615680576156806155ec565b6001019392505050565b60006001600160401b038216806156a3576156a36155ec565b6000190192915050565b6020815260ff8251166020820152602082015160408201526001600160a01b0360408301511660608201526000606083015160c060808401526156f360e0840182615548565b905060808401516001600160601b0380821660a08601528060a08701511660c086015250508091505092915050565b60005b8381101561573d578181015183820152602001615725565b50506000910152565b6000815180845261575e816020860160208601615722565b601f01601f19169290920160200192915050565b6020815260006110a46020830184615746565b60006020828403121561579757600080fd5b81516110a4816151b4565b6001600160601b03818116838216019080821115614545576145456155ec565b8082018082111561273f5761273f6155ec565b6000602082840312156157e757600080fd5b6110a4826153f8565b60006020828403121561580257600080fd5b6110a4826150b2565b6000808335601e1984360301811261582257600080fd5b8301803591506001600160401b0382111561583c57600080fd5b60200191503681900382131561585157600080fd5b9250929050565b878152866020820152856040820152600063ffffffff80871660608401528086166080840152506001600160a01b03841660a083015260e060c08301526158a260e0830184615746565b9998505050505050505050565b86815285602082015261ffff85166040820152600063ffffffff808616606084015280851660808401525060c060a08301526148cc60c0830184615746565b6001600160401b03818116838216019080821115614545576145456155ec565b8060005b6002811015612c7b578151845260209384019390910190600101615912565b6040810161273f828461590e565b60006020828403121561595157600080fd5b5051919050565b8781526001600160401b0387166020820152856040820152600063ffffffff80871660608401528086166080840152506001600160a01b03841660a083015260e060c08301526158a260e0830184615746565b8281526040602082015260006159c460408301846154de565b949350505050565b6001600160e01b031981358181169160048510156159f45780818660040360031b1b83161692505b505092915050565b60008085851115615a0c57600080fd5b83861115615a1957600080fd5b5050820193919092039150565b600060208284031215615a3857600080fd5b604051602081018181106001600160401b0382111715615a5a57615a5a614f9b565b6040528235615a68816151b4565b81529392505050565b828152606081016110a4602083018461590e565b808202811582820484141761273f5761273f6155ec565b634e487b7160e01b600052601260045260246000fd5b600082615ac157615ac1615a9c565b500490565b60ff818116838216019081111561273f5761273f6155ec565b63ffffffff828116828216039080821115614545576145456155ec565b60008351615b0e818460208801615722565b835190830190615b22818360208801615722565b01949350505050565b805169ffffffffffffffffffff81168114611fd557600080fd5b600080600080600060a08688031215615b5d57600080fd5b615b6686615b2b565b9450602086015193506040860151925060608601519150615b8960808701615b2b565b90509295509295909350565b838152615ba5602082018461590e565b606081019190915260800192915050565b868152615bc6602082018761590e565b615bd3606082018661590e565b615be060a082018561590e565b615bed60e082018461590e565b60609190911b6bffffffffffffffffffffffff19166101208201526101340195945050505050565b600082615c2457615c24615a9c565b500690565b615c33818361590e565b60400191905056fe307866666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666a164736f6c6343000813000a", +} + +var VRFCoordinatorTestV25ABI = VRFCoordinatorTestV25MetaData.ABI + +var VRFCoordinatorTestV25Bin = VRFCoordinatorTestV25MetaData.Bin + +func DeployVRFCoordinatorTestV25(auth *bind.TransactOpts, backend bind.ContractBackend, blockhashStore common.Address) (common.Address, *types.Transaction, *VRFCoordinatorTestV25, error) { + parsed, err := VRFCoordinatorTestV25MetaData.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(VRFCoordinatorTestV25Bin), backend, blockhashStore) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &VRFCoordinatorTestV25{address: address, abi: *parsed, VRFCoordinatorTestV25Caller: VRFCoordinatorTestV25Caller{contract: contract}, VRFCoordinatorTestV25Transactor: VRFCoordinatorTestV25Transactor{contract: contract}, VRFCoordinatorTestV25Filterer: VRFCoordinatorTestV25Filterer{contract: contract}}, nil +} + +type VRFCoordinatorTestV25 struct { + address common.Address + abi abi.ABI + VRFCoordinatorTestV25Caller + VRFCoordinatorTestV25Transactor + VRFCoordinatorTestV25Filterer +} + +type VRFCoordinatorTestV25Caller struct { + contract *bind.BoundContract +} + +type VRFCoordinatorTestV25Transactor struct { + contract *bind.BoundContract +} + +type VRFCoordinatorTestV25Filterer struct { + contract *bind.BoundContract +} + +type VRFCoordinatorTestV25Session struct { + Contract *VRFCoordinatorTestV25 + CallOpts bind.CallOpts + TransactOpts bind.TransactOpts +} + +type VRFCoordinatorTestV25CallerSession struct { + Contract *VRFCoordinatorTestV25Caller + CallOpts bind.CallOpts +} + +type VRFCoordinatorTestV25TransactorSession struct { + Contract *VRFCoordinatorTestV25Transactor + TransactOpts bind.TransactOpts +} + +type VRFCoordinatorTestV25Raw struct { + Contract *VRFCoordinatorTestV25 +} + +type VRFCoordinatorTestV25CallerRaw struct { + Contract *VRFCoordinatorTestV25Caller +} + +type VRFCoordinatorTestV25TransactorRaw struct { + Contract *VRFCoordinatorTestV25Transactor +} + +func NewVRFCoordinatorTestV25(address common.Address, backend bind.ContractBackend) (*VRFCoordinatorTestV25, error) { + abi, err := abi.JSON(strings.NewReader(VRFCoordinatorTestV25ABI)) + if err != nil { + return nil, err + } + contract, err := bindVRFCoordinatorTestV25(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &VRFCoordinatorTestV25{address: address, abi: abi, VRFCoordinatorTestV25Caller: VRFCoordinatorTestV25Caller{contract: contract}, VRFCoordinatorTestV25Transactor: VRFCoordinatorTestV25Transactor{contract: contract}, VRFCoordinatorTestV25Filterer: VRFCoordinatorTestV25Filterer{contract: contract}}, nil +} + +func NewVRFCoordinatorTestV25Caller(address common.Address, caller bind.ContractCaller) (*VRFCoordinatorTestV25Caller, error) { + contract, err := bindVRFCoordinatorTestV25(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &VRFCoordinatorTestV25Caller{contract: contract}, nil +} + +func NewVRFCoordinatorTestV25Transactor(address common.Address, transactor bind.ContractTransactor) (*VRFCoordinatorTestV25Transactor, error) { + contract, err := bindVRFCoordinatorTestV25(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &VRFCoordinatorTestV25Transactor{contract: contract}, nil +} + +func NewVRFCoordinatorTestV25Filterer(address common.Address, filterer bind.ContractFilterer) (*VRFCoordinatorTestV25Filterer, error) { + contract, err := bindVRFCoordinatorTestV25(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &VRFCoordinatorTestV25Filterer{contract: contract}, nil +} + +func bindVRFCoordinatorTestV25(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := VRFCoordinatorTestV25MetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Raw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _VRFCoordinatorTestV25.Contract.VRFCoordinatorTestV25Caller.contract.Call(opts, result, method, params...) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Raw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.VRFCoordinatorTestV25Transactor.contract.Transfer(opts) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Raw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.VRFCoordinatorTestV25Transactor.contract.Transact(opts, method, params...) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25CallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _VRFCoordinatorTestV25.Contract.contract.Call(opts, result, method, params...) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25TransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.contract.Transfer(opts) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25TransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.contract.Transact(opts, method, params...) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Caller) BLOCKHASHSTORE(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _VRFCoordinatorTestV25.contract.Call(opts, &out, "BLOCKHASH_STORE") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) BLOCKHASHSTORE() (common.Address, error) { + return _VRFCoordinatorTestV25.Contract.BLOCKHASHSTORE(&_VRFCoordinatorTestV25.CallOpts) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25CallerSession) BLOCKHASHSTORE() (common.Address, error) { + return _VRFCoordinatorTestV25.Contract.BLOCKHASHSTORE(&_VRFCoordinatorTestV25.CallOpts) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Caller) LINK(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _VRFCoordinatorTestV25.contract.Call(opts, &out, "LINK") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) LINK() (common.Address, error) { + return _VRFCoordinatorTestV25.Contract.LINK(&_VRFCoordinatorTestV25.CallOpts) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25CallerSession) LINK() (common.Address, error) { + return _VRFCoordinatorTestV25.Contract.LINK(&_VRFCoordinatorTestV25.CallOpts) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Caller) LINKNATIVEFEED(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _VRFCoordinatorTestV25.contract.Call(opts, &out, "LINK_NATIVE_FEED") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) LINKNATIVEFEED() (common.Address, error) { + return _VRFCoordinatorTestV25.Contract.LINKNATIVEFEED(&_VRFCoordinatorTestV25.CallOpts) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25CallerSession) LINKNATIVEFEED() (common.Address, error) { + return _VRFCoordinatorTestV25.Contract.LINKNATIVEFEED(&_VRFCoordinatorTestV25.CallOpts) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Caller) MAXCONSUMERS(opts *bind.CallOpts) (uint16, error) { + var out []interface{} + err := _VRFCoordinatorTestV25.contract.Call(opts, &out, "MAX_CONSUMERS") + + if err != nil { + return *new(uint16), err + } + + out0 := *abi.ConvertType(out[0], new(uint16)).(*uint16) + + return out0, err + +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) MAXCONSUMERS() (uint16, error) { + return _VRFCoordinatorTestV25.Contract.MAXCONSUMERS(&_VRFCoordinatorTestV25.CallOpts) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25CallerSession) MAXCONSUMERS() (uint16, error) { + return _VRFCoordinatorTestV25.Contract.MAXCONSUMERS(&_VRFCoordinatorTestV25.CallOpts) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Caller) MAXNUMWORDS(opts *bind.CallOpts) (uint32, error) { + var out []interface{} + err := _VRFCoordinatorTestV25.contract.Call(opts, &out, "MAX_NUM_WORDS") + + if err != nil { + return *new(uint32), err + } + + out0 := *abi.ConvertType(out[0], new(uint32)).(*uint32) + + return out0, err + +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) MAXNUMWORDS() (uint32, error) { + return _VRFCoordinatorTestV25.Contract.MAXNUMWORDS(&_VRFCoordinatorTestV25.CallOpts) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25CallerSession) MAXNUMWORDS() (uint32, error) { + return _VRFCoordinatorTestV25.Contract.MAXNUMWORDS(&_VRFCoordinatorTestV25.CallOpts) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Caller) MAXREQUESTCONFIRMATIONS(opts *bind.CallOpts) (uint16, error) { + var out []interface{} + err := _VRFCoordinatorTestV25.contract.Call(opts, &out, "MAX_REQUEST_CONFIRMATIONS") + + if err != nil { + return *new(uint16), err + } + + out0 := *abi.ConvertType(out[0], new(uint16)).(*uint16) + + return out0, err + +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) MAXREQUESTCONFIRMATIONS() (uint16, error) { + return _VRFCoordinatorTestV25.Contract.MAXREQUESTCONFIRMATIONS(&_VRFCoordinatorTestV25.CallOpts) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25CallerSession) MAXREQUESTCONFIRMATIONS() (uint16, error) { + return _VRFCoordinatorTestV25.Contract.MAXREQUESTCONFIRMATIONS(&_VRFCoordinatorTestV25.CallOpts) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Caller) GetActiveSubscriptionIds(opts *bind.CallOpts, startIndex *big.Int, maxCount *big.Int) ([]*big.Int, error) { + var out []interface{} + err := _VRFCoordinatorTestV25.contract.Call(opts, &out, "getActiveSubscriptionIds", startIndex, maxCount) + + if err != nil { + return *new([]*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new([]*big.Int)).(*[]*big.Int) + + return out0, err + +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) GetActiveSubscriptionIds(startIndex *big.Int, maxCount *big.Int) ([]*big.Int, error) { + return _VRFCoordinatorTestV25.Contract.GetActiveSubscriptionIds(&_VRFCoordinatorTestV25.CallOpts, startIndex, maxCount) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25CallerSession) GetActiveSubscriptionIds(startIndex *big.Int, maxCount *big.Int) ([]*big.Int, error) { + return _VRFCoordinatorTestV25.Contract.GetActiveSubscriptionIds(&_VRFCoordinatorTestV25.CallOpts, startIndex, maxCount) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Caller) GetSubscription(opts *bind.CallOpts, subId *big.Int) (GetSubscription, + + error) { + var out []interface{} + err := _VRFCoordinatorTestV25.contract.Call(opts, &out, "getSubscription", subId) + + outstruct := new(GetSubscription) + if err != nil { + return *outstruct, err + } + + outstruct.Balance = *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + outstruct.NativeBalance = *abi.ConvertType(out[1], new(*big.Int)).(**big.Int) + outstruct.ReqCount = *abi.ConvertType(out[2], new(uint64)).(*uint64) + outstruct.SubOwner = *abi.ConvertType(out[3], new(common.Address)).(*common.Address) + outstruct.Consumers = *abi.ConvertType(out[4], new([]common.Address)).(*[]common.Address) + + return *outstruct, err + +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) GetSubscription(subId *big.Int) (GetSubscription, + + error) { + return _VRFCoordinatorTestV25.Contract.GetSubscription(&_VRFCoordinatorTestV25.CallOpts, subId) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25CallerSession) GetSubscription(subId *big.Int) (GetSubscription, + + error) { + return _VRFCoordinatorTestV25.Contract.GetSubscription(&_VRFCoordinatorTestV25.CallOpts, subId) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Caller) HashOfKey(opts *bind.CallOpts, publicKey [2]*big.Int) ([32]byte, error) { + var out []interface{} + err := _VRFCoordinatorTestV25.contract.Call(opts, &out, "hashOfKey", publicKey) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) HashOfKey(publicKey [2]*big.Int) ([32]byte, error) { + return _VRFCoordinatorTestV25.Contract.HashOfKey(&_VRFCoordinatorTestV25.CallOpts, publicKey) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25CallerSession) HashOfKey(publicKey [2]*big.Int) ([32]byte, error) { + return _VRFCoordinatorTestV25.Contract.HashOfKey(&_VRFCoordinatorTestV25.CallOpts, publicKey) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Caller) Owner(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _VRFCoordinatorTestV25.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 (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) Owner() (common.Address, error) { + return _VRFCoordinatorTestV25.Contract.Owner(&_VRFCoordinatorTestV25.CallOpts) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25CallerSession) Owner() (common.Address, error) { + return _VRFCoordinatorTestV25.Contract.Owner(&_VRFCoordinatorTestV25.CallOpts) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Caller) PendingRequestExists(opts *bind.CallOpts, subId *big.Int) (bool, error) { + var out []interface{} + err := _VRFCoordinatorTestV25.contract.Call(opts, &out, "pendingRequestExists", subId) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) PendingRequestExists(subId *big.Int) (bool, error) { + return _VRFCoordinatorTestV25.Contract.PendingRequestExists(&_VRFCoordinatorTestV25.CallOpts, subId) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25CallerSession) PendingRequestExists(subId *big.Int) (bool, error) { + return _VRFCoordinatorTestV25.Contract.PendingRequestExists(&_VRFCoordinatorTestV25.CallOpts, subId) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Caller) SConfig(opts *bind.CallOpts) (SConfig, + + error) { + var out []interface{} + err := _VRFCoordinatorTestV25.contract.Call(opts, &out, "s_config") + + outstruct := new(SConfig) + if err != nil { + return *outstruct, err + } + + outstruct.MinimumRequestConfirmations = *abi.ConvertType(out[0], new(uint16)).(*uint16) + outstruct.MaxGasLimit = *abi.ConvertType(out[1], new(uint32)).(*uint32) + outstruct.ReentrancyLock = *abi.ConvertType(out[2], new(bool)).(*bool) + outstruct.StalenessSeconds = *abi.ConvertType(out[3], new(uint32)).(*uint32) + outstruct.GasAfterPaymentCalculation = *abi.ConvertType(out[4], new(uint32)).(*uint32) + outstruct.FulfillmentFlatFeeNativePPM = *abi.ConvertType(out[5], new(uint32)).(*uint32) + outstruct.FulfillmentFlatFeeLinkDiscountPPM = *abi.ConvertType(out[6], new(uint32)).(*uint32) + outstruct.NativePremiumPercentage = *abi.ConvertType(out[7], new(uint8)).(*uint8) + outstruct.LinkPremiumPercentage = *abi.ConvertType(out[8], new(uint8)).(*uint8) + + return *outstruct, err + +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) SConfig() (SConfig, + + error) { + return _VRFCoordinatorTestV25.Contract.SConfig(&_VRFCoordinatorTestV25.CallOpts) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25CallerSession) SConfig() (SConfig, + + error) { + return _VRFCoordinatorTestV25.Contract.SConfig(&_VRFCoordinatorTestV25.CallOpts) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Caller) SCurrentSubNonce(opts *bind.CallOpts) (uint64, error) { + var out []interface{} + err := _VRFCoordinatorTestV25.contract.Call(opts, &out, "s_currentSubNonce") + + if err != nil { + return *new(uint64), err + } + + out0 := *abi.ConvertType(out[0], new(uint64)).(*uint64) + + return out0, err + +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) SCurrentSubNonce() (uint64, error) { + return _VRFCoordinatorTestV25.Contract.SCurrentSubNonce(&_VRFCoordinatorTestV25.CallOpts) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25CallerSession) SCurrentSubNonce() (uint64, error) { + return _VRFCoordinatorTestV25.Contract.SCurrentSubNonce(&_VRFCoordinatorTestV25.CallOpts) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Caller) SFallbackWeiPerUnitLink(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _VRFCoordinatorTestV25.contract.Call(opts, &out, "s_fallbackWeiPerUnitLink") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) SFallbackWeiPerUnitLink() (*big.Int, error) { + return _VRFCoordinatorTestV25.Contract.SFallbackWeiPerUnitLink(&_VRFCoordinatorTestV25.CallOpts) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25CallerSession) SFallbackWeiPerUnitLink() (*big.Int, error) { + return _VRFCoordinatorTestV25.Contract.SFallbackWeiPerUnitLink(&_VRFCoordinatorTestV25.CallOpts) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Caller) SProvingKeyHashes(opts *bind.CallOpts, arg0 *big.Int) ([32]byte, error) { + var out []interface{} + err := _VRFCoordinatorTestV25.contract.Call(opts, &out, "s_provingKeyHashes", arg0) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) SProvingKeyHashes(arg0 *big.Int) ([32]byte, error) { + return _VRFCoordinatorTestV25.Contract.SProvingKeyHashes(&_VRFCoordinatorTestV25.CallOpts, arg0) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25CallerSession) SProvingKeyHashes(arg0 *big.Int) ([32]byte, error) { + return _VRFCoordinatorTestV25.Contract.SProvingKeyHashes(&_VRFCoordinatorTestV25.CallOpts, arg0) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Caller) SProvingKeys(opts *bind.CallOpts, arg0 [32]byte) (SProvingKeys, + + error) { + var out []interface{} + err := _VRFCoordinatorTestV25.contract.Call(opts, &out, "s_provingKeys", arg0) + + outstruct := new(SProvingKeys) + if err != nil { + return *outstruct, err + } + + outstruct.Exists = *abi.ConvertType(out[0], new(bool)).(*bool) + outstruct.MaxGas = *abi.ConvertType(out[1], new(uint64)).(*uint64) + + return *outstruct, err + +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) SProvingKeys(arg0 [32]byte) (SProvingKeys, + + error) { + return _VRFCoordinatorTestV25.Contract.SProvingKeys(&_VRFCoordinatorTestV25.CallOpts, arg0) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25CallerSession) SProvingKeys(arg0 [32]byte) (SProvingKeys, + + error) { + return _VRFCoordinatorTestV25.Contract.SProvingKeys(&_VRFCoordinatorTestV25.CallOpts, arg0) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Caller) SRequestCommitments(opts *bind.CallOpts, arg0 *big.Int) ([32]byte, error) { + var out []interface{} + err := _VRFCoordinatorTestV25.contract.Call(opts, &out, "s_requestCommitments", arg0) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) SRequestCommitments(arg0 *big.Int) ([32]byte, error) { + return _VRFCoordinatorTestV25.Contract.SRequestCommitments(&_VRFCoordinatorTestV25.CallOpts, arg0) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25CallerSession) SRequestCommitments(arg0 *big.Int) ([32]byte, error) { + return _VRFCoordinatorTestV25.Contract.SRequestCommitments(&_VRFCoordinatorTestV25.CallOpts, arg0) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Caller) STotalBalance(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _VRFCoordinatorTestV25.contract.Call(opts, &out, "s_totalBalance") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) STotalBalance() (*big.Int, error) { + return _VRFCoordinatorTestV25.Contract.STotalBalance(&_VRFCoordinatorTestV25.CallOpts) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25CallerSession) STotalBalance() (*big.Int, error) { + return _VRFCoordinatorTestV25.Contract.STotalBalance(&_VRFCoordinatorTestV25.CallOpts) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Caller) STotalNativeBalance(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _VRFCoordinatorTestV25.contract.Call(opts, &out, "s_totalNativeBalance") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) STotalNativeBalance() (*big.Int, error) { + return _VRFCoordinatorTestV25.Contract.STotalNativeBalance(&_VRFCoordinatorTestV25.CallOpts) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25CallerSession) STotalNativeBalance() (*big.Int, error) { + return _VRFCoordinatorTestV25.Contract.STotalNativeBalance(&_VRFCoordinatorTestV25.CallOpts) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Transactor) AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.contract.Transact(opts, "acceptOwnership") +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) AcceptOwnership() (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.AcceptOwnership(&_VRFCoordinatorTestV25.TransactOpts) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25TransactorSession) AcceptOwnership() (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.AcceptOwnership(&_VRFCoordinatorTestV25.TransactOpts) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Transactor) AcceptSubscriptionOwnerTransfer(opts *bind.TransactOpts, subId *big.Int) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.contract.Transact(opts, "acceptSubscriptionOwnerTransfer", subId) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) AcceptSubscriptionOwnerTransfer(subId *big.Int) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.AcceptSubscriptionOwnerTransfer(&_VRFCoordinatorTestV25.TransactOpts, subId) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25TransactorSession) AcceptSubscriptionOwnerTransfer(subId *big.Int) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.AcceptSubscriptionOwnerTransfer(&_VRFCoordinatorTestV25.TransactOpts, subId) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Transactor) AddConsumer(opts *bind.TransactOpts, subId *big.Int, consumer common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.contract.Transact(opts, "addConsumer", subId, consumer) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) AddConsumer(subId *big.Int, consumer common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.AddConsumer(&_VRFCoordinatorTestV25.TransactOpts, subId, consumer) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25TransactorSession) AddConsumer(subId *big.Int, consumer common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.AddConsumer(&_VRFCoordinatorTestV25.TransactOpts, subId, consumer) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Transactor) CancelSubscription(opts *bind.TransactOpts, subId *big.Int, to common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.contract.Transact(opts, "cancelSubscription", subId, to) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) CancelSubscription(subId *big.Int, to common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.CancelSubscription(&_VRFCoordinatorTestV25.TransactOpts, subId, to) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25TransactorSession) CancelSubscription(subId *big.Int, to common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.CancelSubscription(&_VRFCoordinatorTestV25.TransactOpts, subId, to) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Transactor) CreateSubscription(opts *bind.TransactOpts) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.contract.Transact(opts, "createSubscription") +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) CreateSubscription() (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.CreateSubscription(&_VRFCoordinatorTestV25.TransactOpts) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25TransactorSession) CreateSubscription() (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.CreateSubscription(&_VRFCoordinatorTestV25.TransactOpts) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Transactor) DeregisterMigratableCoordinator(opts *bind.TransactOpts, target common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.contract.Transact(opts, "deregisterMigratableCoordinator", target) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) DeregisterMigratableCoordinator(target common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.DeregisterMigratableCoordinator(&_VRFCoordinatorTestV25.TransactOpts, target) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25TransactorSession) DeregisterMigratableCoordinator(target common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.DeregisterMigratableCoordinator(&_VRFCoordinatorTestV25.TransactOpts, target) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Transactor) DeregisterProvingKey(opts *bind.TransactOpts, publicProvingKey [2]*big.Int) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.contract.Transact(opts, "deregisterProvingKey", publicProvingKey) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) DeregisterProvingKey(publicProvingKey [2]*big.Int) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.DeregisterProvingKey(&_VRFCoordinatorTestV25.TransactOpts, publicProvingKey) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25TransactorSession) DeregisterProvingKey(publicProvingKey [2]*big.Int) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.DeregisterProvingKey(&_VRFCoordinatorTestV25.TransactOpts, publicProvingKey) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Transactor) FulfillRandomWords(opts *bind.TransactOpts, proof VRFOldProof, rc VRFTypesRequestCommitmentV2Plus, onlyPremium bool) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.contract.Transact(opts, "fulfillRandomWords", proof, rc, onlyPremium) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) FulfillRandomWords(proof VRFOldProof, rc VRFTypesRequestCommitmentV2Plus, onlyPremium bool) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.FulfillRandomWords(&_VRFCoordinatorTestV25.TransactOpts, proof, rc, onlyPremium) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25TransactorSession) FulfillRandomWords(proof VRFOldProof, rc VRFTypesRequestCommitmentV2Plus, onlyPremium bool) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.FulfillRandomWords(&_VRFCoordinatorTestV25.TransactOpts, proof, rc, onlyPremium) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Transactor) FundSubscriptionWithNative(opts *bind.TransactOpts, subId *big.Int) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.contract.Transact(opts, "fundSubscriptionWithNative", subId) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) FundSubscriptionWithNative(subId *big.Int) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.FundSubscriptionWithNative(&_VRFCoordinatorTestV25.TransactOpts, subId) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25TransactorSession) FundSubscriptionWithNative(subId *big.Int) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.FundSubscriptionWithNative(&_VRFCoordinatorTestV25.TransactOpts, subId) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Transactor) Migrate(opts *bind.TransactOpts, subId *big.Int, newCoordinator common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.contract.Transact(opts, "migrate", subId, newCoordinator) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) Migrate(subId *big.Int, newCoordinator common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.Migrate(&_VRFCoordinatorTestV25.TransactOpts, subId, newCoordinator) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25TransactorSession) Migrate(subId *big.Int, newCoordinator common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.Migrate(&_VRFCoordinatorTestV25.TransactOpts, subId, newCoordinator) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Transactor) OnTokenTransfer(opts *bind.TransactOpts, arg0 common.Address, amount *big.Int, data []byte) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.contract.Transact(opts, "onTokenTransfer", arg0, amount, data) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) OnTokenTransfer(arg0 common.Address, amount *big.Int, data []byte) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.OnTokenTransfer(&_VRFCoordinatorTestV25.TransactOpts, arg0, amount, data) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25TransactorSession) OnTokenTransfer(arg0 common.Address, amount *big.Int, data []byte) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.OnTokenTransfer(&_VRFCoordinatorTestV25.TransactOpts, arg0, amount, data) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Transactor) OwnerCancelSubscription(opts *bind.TransactOpts, subId *big.Int) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.contract.Transact(opts, "ownerCancelSubscription", subId) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) OwnerCancelSubscription(subId *big.Int) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.OwnerCancelSubscription(&_VRFCoordinatorTestV25.TransactOpts, subId) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25TransactorSession) OwnerCancelSubscription(subId *big.Int) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.OwnerCancelSubscription(&_VRFCoordinatorTestV25.TransactOpts, subId) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Transactor) RecoverFunds(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.contract.Transact(opts, "recoverFunds", to) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) RecoverFunds(to common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.RecoverFunds(&_VRFCoordinatorTestV25.TransactOpts, to) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25TransactorSession) RecoverFunds(to common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.RecoverFunds(&_VRFCoordinatorTestV25.TransactOpts, to) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Transactor) RecoverNativeFunds(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.contract.Transact(opts, "recoverNativeFunds", to) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) RecoverNativeFunds(to common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.RecoverNativeFunds(&_VRFCoordinatorTestV25.TransactOpts, to) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25TransactorSession) RecoverNativeFunds(to common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.RecoverNativeFunds(&_VRFCoordinatorTestV25.TransactOpts, to) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Transactor) RegisterMigratableCoordinator(opts *bind.TransactOpts, target common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.contract.Transact(opts, "registerMigratableCoordinator", target) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) RegisterMigratableCoordinator(target common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.RegisterMigratableCoordinator(&_VRFCoordinatorTestV25.TransactOpts, target) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25TransactorSession) RegisterMigratableCoordinator(target common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.RegisterMigratableCoordinator(&_VRFCoordinatorTestV25.TransactOpts, target) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Transactor) RegisterProvingKey(opts *bind.TransactOpts, publicProvingKey [2]*big.Int, maxGas uint64) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.contract.Transact(opts, "registerProvingKey", publicProvingKey, maxGas) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) RegisterProvingKey(publicProvingKey [2]*big.Int, maxGas uint64) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.RegisterProvingKey(&_VRFCoordinatorTestV25.TransactOpts, publicProvingKey, maxGas) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25TransactorSession) RegisterProvingKey(publicProvingKey [2]*big.Int, maxGas uint64) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.RegisterProvingKey(&_VRFCoordinatorTestV25.TransactOpts, publicProvingKey, maxGas) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Transactor) RemoveConsumer(opts *bind.TransactOpts, subId *big.Int, consumer common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.contract.Transact(opts, "removeConsumer", subId, consumer) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) RemoveConsumer(subId *big.Int, consumer common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.RemoveConsumer(&_VRFCoordinatorTestV25.TransactOpts, subId, consumer) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25TransactorSession) RemoveConsumer(subId *big.Int, consumer common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.RemoveConsumer(&_VRFCoordinatorTestV25.TransactOpts, subId, consumer) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Transactor) RequestRandomWords(opts *bind.TransactOpts, req VRFV2PlusClientRandomWordsRequest) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.contract.Transact(opts, "requestRandomWords", req) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) RequestRandomWords(req VRFV2PlusClientRandomWordsRequest) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.RequestRandomWords(&_VRFCoordinatorTestV25.TransactOpts, req) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25TransactorSession) RequestRandomWords(req VRFV2PlusClientRandomWordsRequest) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.RequestRandomWords(&_VRFCoordinatorTestV25.TransactOpts, req) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Transactor) RequestSubscriptionOwnerTransfer(opts *bind.TransactOpts, subId *big.Int, newOwner common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.contract.Transact(opts, "requestSubscriptionOwnerTransfer", subId, newOwner) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) RequestSubscriptionOwnerTransfer(subId *big.Int, newOwner common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.RequestSubscriptionOwnerTransfer(&_VRFCoordinatorTestV25.TransactOpts, subId, newOwner) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25TransactorSession) RequestSubscriptionOwnerTransfer(subId *big.Int, newOwner common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.RequestSubscriptionOwnerTransfer(&_VRFCoordinatorTestV25.TransactOpts, subId, newOwner) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Transactor) SetConfig(opts *bind.TransactOpts, minimumRequestConfirmations uint16, maxGasLimit uint32, stalenessSeconds uint32, gasAfterPaymentCalculation uint32, fallbackWeiPerUnitLink *big.Int, fulfillmentFlatFeeNativePPM uint32, fulfillmentFlatFeeLinkDiscountPPM uint32, nativePremiumPercentage uint8, linkPremiumPercentage uint8) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.contract.Transact(opts, "setConfig", minimumRequestConfirmations, maxGasLimit, stalenessSeconds, gasAfterPaymentCalculation, fallbackWeiPerUnitLink, fulfillmentFlatFeeNativePPM, fulfillmentFlatFeeLinkDiscountPPM, nativePremiumPercentage, linkPremiumPercentage) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) SetConfig(minimumRequestConfirmations uint16, maxGasLimit uint32, stalenessSeconds uint32, gasAfterPaymentCalculation uint32, fallbackWeiPerUnitLink *big.Int, fulfillmentFlatFeeNativePPM uint32, fulfillmentFlatFeeLinkDiscountPPM uint32, nativePremiumPercentage uint8, linkPremiumPercentage uint8) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.SetConfig(&_VRFCoordinatorTestV25.TransactOpts, minimumRequestConfirmations, maxGasLimit, stalenessSeconds, gasAfterPaymentCalculation, fallbackWeiPerUnitLink, fulfillmentFlatFeeNativePPM, fulfillmentFlatFeeLinkDiscountPPM, nativePremiumPercentage, linkPremiumPercentage) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25TransactorSession) SetConfig(minimumRequestConfirmations uint16, maxGasLimit uint32, stalenessSeconds uint32, gasAfterPaymentCalculation uint32, fallbackWeiPerUnitLink *big.Int, fulfillmentFlatFeeNativePPM uint32, fulfillmentFlatFeeLinkDiscountPPM uint32, nativePremiumPercentage uint8, linkPremiumPercentage uint8) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.SetConfig(&_VRFCoordinatorTestV25.TransactOpts, minimumRequestConfirmations, maxGasLimit, stalenessSeconds, gasAfterPaymentCalculation, fallbackWeiPerUnitLink, fulfillmentFlatFeeNativePPM, fulfillmentFlatFeeLinkDiscountPPM, nativePremiumPercentage, linkPremiumPercentage) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Transactor) SetLINKAndLINKNativeFeed(opts *bind.TransactOpts, link common.Address, linkNativeFeed common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.contract.Transact(opts, "setLINKAndLINKNativeFeed", link, linkNativeFeed) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) SetLINKAndLINKNativeFeed(link common.Address, linkNativeFeed common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.SetLINKAndLINKNativeFeed(&_VRFCoordinatorTestV25.TransactOpts, link, linkNativeFeed) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25TransactorSession) SetLINKAndLINKNativeFeed(link common.Address, linkNativeFeed common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.SetLINKAndLINKNativeFeed(&_VRFCoordinatorTestV25.TransactOpts, link, linkNativeFeed) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Transactor) TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.contract.Transact(opts, "transferOwnership", to) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) TransferOwnership(to common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.TransferOwnership(&_VRFCoordinatorTestV25.TransactOpts, to) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25TransactorSession) TransferOwnership(to common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.TransferOwnership(&_VRFCoordinatorTestV25.TransactOpts, to) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Transactor) Withdraw(opts *bind.TransactOpts, recipient common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.contract.Transact(opts, "withdraw", recipient) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) Withdraw(recipient common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.Withdraw(&_VRFCoordinatorTestV25.TransactOpts, recipient) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25TransactorSession) Withdraw(recipient common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.Withdraw(&_VRFCoordinatorTestV25.TransactOpts, recipient) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Transactor) WithdrawNative(opts *bind.TransactOpts, recipient common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.contract.Transact(opts, "withdrawNative", recipient) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Session) WithdrawNative(recipient common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.WithdrawNative(&_VRFCoordinatorTestV25.TransactOpts, recipient) +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25TransactorSession) WithdrawNative(recipient common.Address) (*types.Transaction, error) { + return _VRFCoordinatorTestV25.Contract.WithdrawNative(&_VRFCoordinatorTestV25.TransactOpts, recipient) +} + +type VRFCoordinatorTestV25ConfigSetIterator struct { + Event *VRFCoordinatorTestV25ConfigSet + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *VRFCoordinatorTestV25ConfigSetIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(VRFCoordinatorTestV25ConfigSet) + 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(VRFCoordinatorTestV25ConfigSet) + 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 *VRFCoordinatorTestV25ConfigSetIterator) Error() error { + return it.fail +} + +func (it *VRFCoordinatorTestV25ConfigSetIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type VRFCoordinatorTestV25ConfigSet struct { + MinimumRequestConfirmations uint16 + MaxGasLimit uint32 + StalenessSeconds uint32 + GasAfterPaymentCalculation uint32 + FallbackWeiPerUnitLink *big.Int + FulfillmentFlatFeeNativePPM uint32 + FulfillmentFlatFeeLinkDiscountPPM uint32 + NativePremiumPercentage uint8 + LinkPremiumPercentage uint8 + Raw types.Log +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) FilterConfigSet(opts *bind.FilterOpts) (*VRFCoordinatorTestV25ConfigSetIterator, error) { + + logs, sub, err := _VRFCoordinatorTestV25.contract.FilterLogs(opts, "ConfigSet") + if err != nil { + return nil, err + } + return &VRFCoordinatorTestV25ConfigSetIterator{contract: _VRFCoordinatorTestV25.contract, event: "ConfigSet", logs: logs, sub: sub}, nil +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) WatchConfigSet(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25ConfigSet) (event.Subscription, error) { + + logs, sub, err := _VRFCoordinatorTestV25.contract.WatchLogs(opts, "ConfigSet") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(VRFCoordinatorTestV25ConfigSet) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "ConfigSet", 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 (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) ParseConfigSet(log types.Log) (*VRFCoordinatorTestV25ConfigSet, error) { + event := new(VRFCoordinatorTestV25ConfigSet) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "ConfigSet", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type VRFCoordinatorTestV25CoordinatorDeregisteredIterator struct { + Event *VRFCoordinatorTestV25CoordinatorDeregistered + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *VRFCoordinatorTestV25CoordinatorDeregisteredIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(VRFCoordinatorTestV25CoordinatorDeregistered) + 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(VRFCoordinatorTestV25CoordinatorDeregistered) + 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 *VRFCoordinatorTestV25CoordinatorDeregisteredIterator) Error() error { + return it.fail +} + +func (it *VRFCoordinatorTestV25CoordinatorDeregisteredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type VRFCoordinatorTestV25CoordinatorDeregistered struct { + CoordinatorAddress common.Address + Raw types.Log +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) FilterCoordinatorDeregistered(opts *bind.FilterOpts) (*VRFCoordinatorTestV25CoordinatorDeregisteredIterator, error) { + + logs, sub, err := _VRFCoordinatorTestV25.contract.FilterLogs(opts, "CoordinatorDeregistered") + if err != nil { + return nil, err + } + return &VRFCoordinatorTestV25CoordinatorDeregisteredIterator{contract: _VRFCoordinatorTestV25.contract, event: "CoordinatorDeregistered", logs: logs, sub: sub}, nil +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) WatchCoordinatorDeregistered(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25CoordinatorDeregistered) (event.Subscription, error) { + + logs, sub, err := _VRFCoordinatorTestV25.contract.WatchLogs(opts, "CoordinatorDeregistered") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(VRFCoordinatorTestV25CoordinatorDeregistered) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "CoordinatorDeregistered", 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 (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) ParseCoordinatorDeregistered(log types.Log) (*VRFCoordinatorTestV25CoordinatorDeregistered, error) { + event := new(VRFCoordinatorTestV25CoordinatorDeregistered) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "CoordinatorDeregistered", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type VRFCoordinatorTestV25CoordinatorRegisteredIterator struct { + Event *VRFCoordinatorTestV25CoordinatorRegistered + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *VRFCoordinatorTestV25CoordinatorRegisteredIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(VRFCoordinatorTestV25CoordinatorRegistered) + 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(VRFCoordinatorTestV25CoordinatorRegistered) + 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 *VRFCoordinatorTestV25CoordinatorRegisteredIterator) Error() error { + return it.fail +} + +func (it *VRFCoordinatorTestV25CoordinatorRegisteredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type VRFCoordinatorTestV25CoordinatorRegistered struct { + CoordinatorAddress common.Address + Raw types.Log +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) FilterCoordinatorRegistered(opts *bind.FilterOpts) (*VRFCoordinatorTestV25CoordinatorRegisteredIterator, error) { + + logs, sub, err := _VRFCoordinatorTestV25.contract.FilterLogs(opts, "CoordinatorRegistered") + if err != nil { + return nil, err + } + return &VRFCoordinatorTestV25CoordinatorRegisteredIterator{contract: _VRFCoordinatorTestV25.contract, event: "CoordinatorRegistered", logs: logs, sub: sub}, nil +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) WatchCoordinatorRegistered(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25CoordinatorRegistered) (event.Subscription, error) { + + logs, sub, err := _VRFCoordinatorTestV25.contract.WatchLogs(opts, "CoordinatorRegistered") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(VRFCoordinatorTestV25CoordinatorRegistered) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "CoordinatorRegistered", 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 (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) ParseCoordinatorRegistered(log types.Log) (*VRFCoordinatorTestV25CoordinatorRegistered, error) { + event := new(VRFCoordinatorTestV25CoordinatorRegistered) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "CoordinatorRegistered", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type VRFCoordinatorTestV25FallbackWeiPerUnitLinkUsedIterator struct { + Event *VRFCoordinatorTestV25FallbackWeiPerUnitLinkUsed + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *VRFCoordinatorTestV25FallbackWeiPerUnitLinkUsedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(VRFCoordinatorTestV25FallbackWeiPerUnitLinkUsed) + 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(VRFCoordinatorTestV25FallbackWeiPerUnitLinkUsed) + 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 *VRFCoordinatorTestV25FallbackWeiPerUnitLinkUsedIterator) Error() error { + return it.fail +} + +func (it *VRFCoordinatorTestV25FallbackWeiPerUnitLinkUsedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type VRFCoordinatorTestV25FallbackWeiPerUnitLinkUsed struct { + RequestId *big.Int + FallbackWeiPerUnitLink *big.Int + Raw types.Log +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) FilterFallbackWeiPerUnitLinkUsed(opts *bind.FilterOpts) (*VRFCoordinatorTestV25FallbackWeiPerUnitLinkUsedIterator, error) { + + logs, sub, err := _VRFCoordinatorTestV25.contract.FilterLogs(opts, "FallbackWeiPerUnitLinkUsed") + if err != nil { + return nil, err + } + return &VRFCoordinatorTestV25FallbackWeiPerUnitLinkUsedIterator{contract: _VRFCoordinatorTestV25.contract, event: "FallbackWeiPerUnitLinkUsed", logs: logs, sub: sub}, nil +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) WatchFallbackWeiPerUnitLinkUsed(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25FallbackWeiPerUnitLinkUsed) (event.Subscription, error) { + + logs, sub, err := _VRFCoordinatorTestV25.contract.WatchLogs(opts, "FallbackWeiPerUnitLinkUsed") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(VRFCoordinatorTestV25FallbackWeiPerUnitLinkUsed) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "FallbackWeiPerUnitLinkUsed", 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 (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) ParseFallbackWeiPerUnitLinkUsed(log types.Log) (*VRFCoordinatorTestV25FallbackWeiPerUnitLinkUsed, error) { + event := new(VRFCoordinatorTestV25FallbackWeiPerUnitLinkUsed) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "FallbackWeiPerUnitLinkUsed", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type VRFCoordinatorTestV25FundsRecoveredIterator struct { + Event *VRFCoordinatorTestV25FundsRecovered + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *VRFCoordinatorTestV25FundsRecoveredIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(VRFCoordinatorTestV25FundsRecovered) + 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(VRFCoordinatorTestV25FundsRecovered) + 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 *VRFCoordinatorTestV25FundsRecoveredIterator) Error() error { + return it.fail +} + +func (it *VRFCoordinatorTestV25FundsRecoveredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type VRFCoordinatorTestV25FundsRecovered struct { + To common.Address + Amount *big.Int + Raw types.Log +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) FilterFundsRecovered(opts *bind.FilterOpts) (*VRFCoordinatorTestV25FundsRecoveredIterator, error) { + + logs, sub, err := _VRFCoordinatorTestV25.contract.FilterLogs(opts, "FundsRecovered") + if err != nil { + return nil, err + } + return &VRFCoordinatorTestV25FundsRecoveredIterator{contract: _VRFCoordinatorTestV25.contract, event: "FundsRecovered", logs: logs, sub: sub}, nil +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) WatchFundsRecovered(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25FundsRecovered) (event.Subscription, error) { + + logs, sub, err := _VRFCoordinatorTestV25.contract.WatchLogs(opts, "FundsRecovered") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(VRFCoordinatorTestV25FundsRecovered) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "FundsRecovered", 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 (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) ParseFundsRecovered(log types.Log) (*VRFCoordinatorTestV25FundsRecovered, error) { + event := new(VRFCoordinatorTestV25FundsRecovered) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "FundsRecovered", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type VRFCoordinatorTestV25MigrationCompletedIterator struct { + Event *VRFCoordinatorTestV25MigrationCompleted + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *VRFCoordinatorTestV25MigrationCompletedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(VRFCoordinatorTestV25MigrationCompleted) + 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(VRFCoordinatorTestV25MigrationCompleted) + 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 *VRFCoordinatorTestV25MigrationCompletedIterator) Error() error { + return it.fail +} + +func (it *VRFCoordinatorTestV25MigrationCompletedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type VRFCoordinatorTestV25MigrationCompleted struct { + NewCoordinator common.Address + SubId *big.Int + Raw types.Log +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) FilterMigrationCompleted(opts *bind.FilterOpts) (*VRFCoordinatorTestV25MigrationCompletedIterator, error) { + + logs, sub, err := _VRFCoordinatorTestV25.contract.FilterLogs(opts, "MigrationCompleted") + if err != nil { + return nil, err + } + return &VRFCoordinatorTestV25MigrationCompletedIterator{contract: _VRFCoordinatorTestV25.contract, event: "MigrationCompleted", logs: logs, sub: sub}, nil +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) WatchMigrationCompleted(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25MigrationCompleted) (event.Subscription, error) { + + logs, sub, err := _VRFCoordinatorTestV25.contract.WatchLogs(opts, "MigrationCompleted") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(VRFCoordinatorTestV25MigrationCompleted) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "MigrationCompleted", 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 (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) ParseMigrationCompleted(log types.Log) (*VRFCoordinatorTestV25MigrationCompleted, error) { + event := new(VRFCoordinatorTestV25MigrationCompleted) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "MigrationCompleted", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type VRFCoordinatorTestV25NativeFundsRecoveredIterator struct { + Event *VRFCoordinatorTestV25NativeFundsRecovered + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *VRFCoordinatorTestV25NativeFundsRecoveredIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(VRFCoordinatorTestV25NativeFundsRecovered) + 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(VRFCoordinatorTestV25NativeFundsRecovered) + 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 *VRFCoordinatorTestV25NativeFundsRecoveredIterator) Error() error { + return it.fail +} + +func (it *VRFCoordinatorTestV25NativeFundsRecoveredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type VRFCoordinatorTestV25NativeFundsRecovered struct { + To common.Address + Amount *big.Int + Raw types.Log +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) FilterNativeFundsRecovered(opts *bind.FilterOpts) (*VRFCoordinatorTestV25NativeFundsRecoveredIterator, error) { + + logs, sub, err := _VRFCoordinatorTestV25.contract.FilterLogs(opts, "NativeFundsRecovered") + if err != nil { + return nil, err + } + return &VRFCoordinatorTestV25NativeFundsRecoveredIterator{contract: _VRFCoordinatorTestV25.contract, event: "NativeFundsRecovered", logs: logs, sub: sub}, nil +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) WatchNativeFundsRecovered(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25NativeFundsRecovered) (event.Subscription, error) { + + logs, sub, err := _VRFCoordinatorTestV25.contract.WatchLogs(opts, "NativeFundsRecovered") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(VRFCoordinatorTestV25NativeFundsRecovered) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "NativeFundsRecovered", 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 (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) ParseNativeFundsRecovered(log types.Log) (*VRFCoordinatorTestV25NativeFundsRecovered, error) { + event := new(VRFCoordinatorTestV25NativeFundsRecovered) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "NativeFundsRecovered", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type VRFCoordinatorTestV25OwnershipTransferRequestedIterator struct { + Event *VRFCoordinatorTestV25OwnershipTransferRequested + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *VRFCoordinatorTestV25OwnershipTransferRequestedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(VRFCoordinatorTestV25OwnershipTransferRequested) + 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(VRFCoordinatorTestV25OwnershipTransferRequested) + 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 *VRFCoordinatorTestV25OwnershipTransferRequestedIterator) Error() error { + return it.fail +} + +func (it *VRFCoordinatorTestV25OwnershipTransferRequestedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type VRFCoordinatorTestV25OwnershipTransferRequested struct { + From common.Address + To common.Address + Raw types.Log +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*VRFCoordinatorTestV25OwnershipTransferRequestedIterator, 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 := _VRFCoordinatorTestV25.contract.FilterLogs(opts, "OwnershipTransferRequested", fromRule, toRule) + if err != nil { + return nil, err + } + return &VRFCoordinatorTestV25OwnershipTransferRequestedIterator{contract: _VRFCoordinatorTestV25.contract, event: "OwnershipTransferRequested", logs: logs, sub: sub}, nil +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25OwnershipTransferRequested, 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 := _VRFCoordinatorTestV25.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(VRFCoordinatorTestV25OwnershipTransferRequested) + if err := _VRFCoordinatorTestV25.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 (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) ParseOwnershipTransferRequested(log types.Log) (*VRFCoordinatorTestV25OwnershipTransferRequested, error) { + event := new(VRFCoordinatorTestV25OwnershipTransferRequested) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type VRFCoordinatorTestV25OwnershipTransferredIterator struct { + Event *VRFCoordinatorTestV25OwnershipTransferred + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *VRFCoordinatorTestV25OwnershipTransferredIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(VRFCoordinatorTestV25OwnershipTransferred) + 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(VRFCoordinatorTestV25OwnershipTransferred) + 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 *VRFCoordinatorTestV25OwnershipTransferredIterator) Error() error { + return it.fail +} + +func (it *VRFCoordinatorTestV25OwnershipTransferredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type VRFCoordinatorTestV25OwnershipTransferred struct { + From common.Address + To common.Address + Raw types.Log +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*VRFCoordinatorTestV25OwnershipTransferredIterator, 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 := _VRFCoordinatorTestV25.contract.FilterLogs(opts, "OwnershipTransferred", fromRule, toRule) + if err != nil { + return nil, err + } + return &VRFCoordinatorTestV25OwnershipTransferredIterator{contract: _VRFCoordinatorTestV25.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25OwnershipTransferred, 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 := _VRFCoordinatorTestV25.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(VRFCoordinatorTestV25OwnershipTransferred) + if err := _VRFCoordinatorTestV25.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 (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) ParseOwnershipTransferred(log types.Log) (*VRFCoordinatorTestV25OwnershipTransferred, error) { + event := new(VRFCoordinatorTestV25OwnershipTransferred) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type VRFCoordinatorTestV25ProvingKeyDeregisteredIterator struct { + Event *VRFCoordinatorTestV25ProvingKeyDeregistered + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *VRFCoordinatorTestV25ProvingKeyDeregisteredIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(VRFCoordinatorTestV25ProvingKeyDeregistered) + 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(VRFCoordinatorTestV25ProvingKeyDeregistered) + 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 *VRFCoordinatorTestV25ProvingKeyDeregisteredIterator) Error() error { + return it.fail +} + +func (it *VRFCoordinatorTestV25ProvingKeyDeregisteredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type VRFCoordinatorTestV25ProvingKeyDeregistered struct { + KeyHash [32]byte + MaxGas uint64 + Raw types.Log +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) FilterProvingKeyDeregistered(opts *bind.FilterOpts) (*VRFCoordinatorTestV25ProvingKeyDeregisteredIterator, error) { + + logs, sub, err := _VRFCoordinatorTestV25.contract.FilterLogs(opts, "ProvingKeyDeregistered") + if err != nil { + return nil, err + } + return &VRFCoordinatorTestV25ProvingKeyDeregisteredIterator{contract: _VRFCoordinatorTestV25.contract, event: "ProvingKeyDeregistered", logs: logs, sub: sub}, nil +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) WatchProvingKeyDeregistered(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25ProvingKeyDeregistered) (event.Subscription, error) { + + logs, sub, err := _VRFCoordinatorTestV25.contract.WatchLogs(opts, "ProvingKeyDeregistered") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(VRFCoordinatorTestV25ProvingKeyDeregistered) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "ProvingKeyDeregistered", 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 (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) ParseProvingKeyDeregistered(log types.Log) (*VRFCoordinatorTestV25ProvingKeyDeregistered, error) { + event := new(VRFCoordinatorTestV25ProvingKeyDeregistered) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "ProvingKeyDeregistered", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type VRFCoordinatorTestV25ProvingKeyRegisteredIterator struct { + Event *VRFCoordinatorTestV25ProvingKeyRegistered + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *VRFCoordinatorTestV25ProvingKeyRegisteredIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(VRFCoordinatorTestV25ProvingKeyRegistered) + 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(VRFCoordinatorTestV25ProvingKeyRegistered) + 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 *VRFCoordinatorTestV25ProvingKeyRegisteredIterator) Error() error { + return it.fail +} + +func (it *VRFCoordinatorTestV25ProvingKeyRegisteredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type VRFCoordinatorTestV25ProvingKeyRegistered struct { + KeyHash [32]byte + MaxGas uint64 + Raw types.Log +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) FilterProvingKeyRegistered(opts *bind.FilterOpts) (*VRFCoordinatorTestV25ProvingKeyRegisteredIterator, error) { + + logs, sub, err := _VRFCoordinatorTestV25.contract.FilterLogs(opts, "ProvingKeyRegistered") + if err != nil { + return nil, err + } + return &VRFCoordinatorTestV25ProvingKeyRegisteredIterator{contract: _VRFCoordinatorTestV25.contract, event: "ProvingKeyRegistered", logs: logs, sub: sub}, nil +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) WatchProvingKeyRegistered(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25ProvingKeyRegistered) (event.Subscription, error) { + + logs, sub, err := _VRFCoordinatorTestV25.contract.WatchLogs(opts, "ProvingKeyRegistered") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(VRFCoordinatorTestV25ProvingKeyRegistered) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "ProvingKeyRegistered", 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 (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) ParseProvingKeyRegistered(log types.Log) (*VRFCoordinatorTestV25ProvingKeyRegistered, error) { + event := new(VRFCoordinatorTestV25ProvingKeyRegistered) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "ProvingKeyRegistered", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type VRFCoordinatorTestV25RandomWordsFulfilledIterator struct { + Event *VRFCoordinatorTestV25RandomWordsFulfilled + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *VRFCoordinatorTestV25RandomWordsFulfilledIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(VRFCoordinatorTestV25RandomWordsFulfilled) + 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(VRFCoordinatorTestV25RandomWordsFulfilled) + 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 *VRFCoordinatorTestV25RandomWordsFulfilledIterator) Error() error { + return it.fail +} + +func (it *VRFCoordinatorTestV25RandomWordsFulfilledIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type VRFCoordinatorTestV25RandomWordsFulfilled struct { + RequestId *big.Int + OutputSeed *big.Int + SubId *big.Int + Payment *big.Int + NativePayment bool + Success bool + OnlyPremium bool + Raw types.Log +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) FilterRandomWordsFulfilled(opts *bind.FilterOpts, requestId []*big.Int, subId []*big.Int) (*VRFCoordinatorTestV25RandomWordsFulfilledIterator, error) { + + var requestIdRule []interface{} + for _, requestIdItem := range requestId { + requestIdRule = append(requestIdRule, requestIdItem) + } + + var subIdRule []interface{} + for _, subIdItem := range subId { + subIdRule = append(subIdRule, subIdItem) + } + + logs, sub, err := _VRFCoordinatorTestV25.contract.FilterLogs(opts, "RandomWordsFulfilled", requestIdRule, subIdRule) + if err != nil { + return nil, err + } + return &VRFCoordinatorTestV25RandomWordsFulfilledIterator{contract: _VRFCoordinatorTestV25.contract, event: "RandomWordsFulfilled", logs: logs, sub: sub}, nil +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) WatchRandomWordsFulfilled(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25RandomWordsFulfilled, requestId []*big.Int, subId []*big.Int) (event.Subscription, error) { + + var requestIdRule []interface{} + for _, requestIdItem := range requestId { + requestIdRule = append(requestIdRule, requestIdItem) + } + + var subIdRule []interface{} + for _, subIdItem := range subId { + subIdRule = append(subIdRule, subIdItem) + } + + logs, sub, err := _VRFCoordinatorTestV25.contract.WatchLogs(opts, "RandomWordsFulfilled", requestIdRule, subIdRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(VRFCoordinatorTestV25RandomWordsFulfilled) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "RandomWordsFulfilled", 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 (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) ParseRandomWordsFulfilled(log types.Log) (*VRFCoordinatorTestV25RandomWordsFulfilled, error) { + event := new(VRFCoordinatorTestV25RandomWordsFulfilled) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "RandomWordsFulfilled", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type VRFCoordinatorTestV25RandomWordsRequestedIterator struct { + Event *VRFCoordinatorTestV25RandomWordsRequested + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *VRFCoordinatorTestV25RandomWordsRequestedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(VRFCoordinatorTestV25RandomWordsRequested) + 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(VRFCoordinatorTestV25RandomWordsRequested) + 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 *VRFCoordinatorTestV25RandomWordsRequestedIterator) Error() error { + return it.fail +} + +func (it *VRFCoordinatorTestV25RandomWordsRequestedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type VRFCoordinatorTestV25RandomWordsRequested struct { + KeyHash [32]byte + RequestId *big.Int + PreSeed *big.Int + SubId *big.Int + MinimumRequestConfirmations uint16 + CallbackGasLimit uint32 + NumWords uint32 + ExtraArgs []byte + Sender common.Address + Raw types.Log +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) FilterRandomWordsRequested(opts *bind.FilterOpts, keyHash [][32]byte, subId []*big.Int, sender []common.Address) (*VRFCoordinatorTestV25RandomWordsRequestedIterator, error) { + + var keyHashRule []interface{} + for _, keyHashItem := range keyHash { + keyHashRule = append(keyHashRule, keyHashItem) + } + + var subIdRule []interface{} + for _, subIdItem := range subId { + subIdRule = append(subIdRule, subIdItem) + } + + var senderRule []interface{} + for _, senderItem := range sender { + senderRule = append(senderRule, senderItem) + } + + logs, sub, err := _VRFCoordinatorTestV25.contract.FilterLogs(opts, "RandomWordsRequested", keyHashRule, subIdRule, senderRule) + if err != nil { + return nil, err + } + return &VRFCoordinatorTestV25RandomWordsRequestedIterator{contract: _VRFCoordinatorTestV25.contract, event: "RandomWordsRequested", logs: logs, sub: sub}, nil +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) WatchRandomWordsRequested(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25RandomWordsRequested, keyHash [][32]byte, subId []*big.Int, sender []common.Address) (event.Subscription, error) { + + var keyHashRule []interface{} + for _, keyHashItem := range keyHash { + keyHashRule = append(keyHashRule, keyHashItem) + } + + var subIdRule []interface{} + for _, subIdItem := range subId { + subIdRule = append(subIdRule, subIdItem) + } + + var senderRule []interface{} + for _, senderItem := range sender { + senderRule = append(senderRule, senderItem) + } + + logs, sub, err := _VRFCoordinatorTestV25.contract.WatchLogs(opts, "RandomWordsRequested", keyHashRule, subIdRule, senderRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(VRFCoordinatorTestV25RandomWordsRequested) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "RandomWordsRequested", 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 (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) ParseRandomWordsRequested(log types.Log) (*VRFCoordinatorTestV25RandomWordsRequested, error) { + event := new(VRFCoordinatorTestV25RandomWordsRequested) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "RandomWordsRequested", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type VRFCoordinatorTestV25SubscriptionCanceledIterator struct { + Event *VRFCoordinatorTestV25SubscriptionCanceled + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *VRFCoordinatorTestV25SubscriptionCanceledIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(VRFCoordinatorTestV25SubscriptionCanceled) + 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(VRFCoordinatorTestV25SubscriptionCanceled) + 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 *VRFCoordinatorTestV25SubscriptionCanceledIterator) Error() error { + return it.fail +} + +func (it *VRFCoordinatorTestV25SubscriptionCanceledIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type VRFCoordinatorTestV25SubscriptionCanceled struct { + SubId *big.Int + To common.Address + AmountLink *big.Int + AmountNative *big.Int + Raw types.Log +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) FilterSubscriptionCanceled(opts *bind.FilterOpts, subId []*big.Int) (*VRFCoordinatorTestV25SubscriptionCanceledIterator, error) { + + var subIdRule []interface{} + for _, subIdItem := range subId { + subIdRule = append(subIdRule, subIdItem) + } + + logs, sub, err := _VRFCoordinatorTestV25.contract.FilterLogs(opts, "SubscriptionCanceled", subIdRule) + if err != nil { + return nil, err + } + return &VRFCoordinatorTestV25SubscriptionCanceledIterator{contract: _VRFCoordinatorTestV25.contract, event: "SubscriptionCanceled", logs: logs, sub: sub}, nil +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) WatchSubscriptionCanceled(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25SubscriptionCanceled, subId []*big.Int) (event.Subscription, error) { + + var subIdRule []interface{} + for _, subIdItem := range subId { + subIdRule = append(subIdRule, subIdItem) + } + + logs, sub, err := _VRFCoordinatorTestV25.contract.WatchLogs(opts, "SubscriptionCanceled", subIdRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(VRFCoordinatorTestV25SubscriptionCanceled) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "SubscriptionCanceled", 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 (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) ParseSubscriptionCanceled(log types.Log) (*VRFCoordinatorTestV25SubscriptionCanceled, error) { + event := new(VRFCoordinatorTestV25SubscriptionCanceled) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "SubscriptionCanceled", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type VRFCoordinatorTestV25SubscriptionConsumerAddedIterator struct { + Event *VRFCoordinatorTestV25SubscriptionConsumerAdded + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *VRFCoordinatorTestV25SubscriptionConsumerAddedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(VRFCoordinatorTestV25SubscriptionConsumerAdded) + 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(VRFCoordinatorTestV25SubscriptionConsumerAdded) + 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 *VRFCoordinatorTestV25SubscriptionConsumerAddedIterator) Error() error { + return it.fail +} + +func (it *VRFCoordinatorTestV25SubscriptionConsumerAddedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type VRFCoordinatorTestV25SubscriptionConsumerAdded struct { + SubId *big.Int + Consumer common.Address + Raw types.Log +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) FilterSubscriptionConsumerAdded(opts *bind.FilterOpts, subId []*big.Int) (*VRFCoordinatorTestV25SubscriptionConsumerAddedIterator, error) { + + var subIdRule []interface{} + for _, subIdItem := range subId { + subIdRule = append(subIdRule, subIdItem) + } + + logs, sub, err := _VRFCoordinatorTestV25.contract.FilterLogs(opts, "SubscriptionConsumerAdded", subIdRule) + if err != nil { + return nil, err + } + return &VRFCoordinatorTestV25SubscriptionConsumerAddedIterator{contract: _VRFCoordinatorTestV25.contract, event: "SubscriptionConsumerAdded", logs: logs, sub: sub}, nil +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) WatchSubscriptionConsumerAdded(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25SubscriptionConsumerAdded, subId []*big.Int) (event.Subscription, error) { + + var subIdRule []interface{} + for _, subIdItem := range subId { + subIdRule = append(subIdRule, subIdItem) + } + + logs, sub, err := _VRFCoordinatorTestV25.contract.WatchLogs(opts, "SubscriptionConsumerAdded", subIdRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(VRFCoordinatorTestV25SubscriptionConsumerAdded) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "SubscriptionConsumerAdded", 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 (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) ParseSubscriptionConsumerAdded(log types.Log) (*VRFCoordinatorTestV25SubscriptionConsumerAdded, error) { + event := new(VRFCoordinatorTestV25SubscriptionConsumerAdded) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "SubscriptionConsumerAdded", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type VRFCoordinatorTestV25SubscriptionConsumerRemovedIterator struct { + Event *VRFCoordinatorTestV25SubscriptionConsumerRemoved + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *VRFCoordinatorTestV25SubscriptionConsumerRemovedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(VRFCoordinatorTestV25SubscriptionConsumerRemoved) + 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(VRFCoordinatorTestV25SubscriptionConsumerRemoved) + 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 *VRFCoordinatorTestV25SubscriptionConsumerRemovedIterator) Error() error { + return it.fail +} + +func (it *VRFCoordinatorTestV25SubscriptionConsumerRemovedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type VRFCoordinatorTestV25SubscriptionConsumerRemoved struct { + SubId *big.Int + Consumer common.Address + Raw types.Log +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) FilterSubscriptionConsumerRemoved(opts *bind.FilterOpts, subId []*big.Int) (*VRFCoordinatorTestV25SubscriptionConsumerRemovedIterator, error) { + + var subIdRule []interface{} + for _, subIdItem := range subId { + subIdRule = append(subIdRule, subIdItem) + } + + logs, sub, err := _VRFCoordinatorTestV25.contract.FilterLogs(opts, "SubscriptionConsumerRemoved", subIdRule) + if err != nil { + return nil, err + } + return &VRFCoordinatorTestV25SubscriptionConsumerRemovedIterator{contract: _VRFCoordinatorTestV25.contract, event: "SubscriptionConsumerRemoved", logs: logs, sub: sub}, nil +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) WatchSubscriptionConsumerRemoved(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25SubscriptionConsumerRemoved, subId []*big.Int) (event.Subscription, error) { + + var subIdRule []interface{} + for _, subIdItem := range subId { + subIdRule = append(subIdRule, subIdItem) + } + + logs, sub, err := _VRFCoordinatorTestV25.contract.WatchLogs(opts, "SubscriptionConsumerRemoved", subIdRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(VRFCoordinatorTestV25SubscriptionConsumerRemoved) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "SubscriptionConsumerRemoved", 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 (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) ParseSubscriptionConsumerRemoved(log types.Log) (*VRFCoordinatorTestV25SubscriptionConsumerRemoved, error) { + event := new(VRFCoordinatorTestV25SubscriptionConsumerRemoved) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "SubscriptionConsumerRemoved", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type VRFCoordinatorTestV25SubscriptionCreatedIterator struct { + Event *VRFCoordinatorTestV25SubscriptionCreated + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *VRFCoordinatorTestV25SubscriptionCreatedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(VRFCoordinatorTestV25SubscriptionCreated) + 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(VRFCoordinatorTestV25SubscriptionCreated) + 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 *VRFCoordinatorTestV25SubscriptionCreatedIterator) Error() error { + return it.fail +} + +func (it *VRFCoordinatorTestV25SubscriptionCreatedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type VRFCoordinatorTestV25SubscriptionCreated struct { + SubId *big.Int + Owner common.Address + Raw types.Log +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) FilterSubscriptionCreated(opts *bind.FilterOpts, subId []*big.Int) (*VRFCoordinatorTestV25SubscriptionCreatedIterator, error) { + + var subIdRule []interface{} + for _, subIdItem := range subId { + subIdRule = append(subIdRule, subIdItem) + } + + logs, sub, err := _VRFCoordinatorTestV25.contract.FilterLogs(opts, "SubscriptionCreated", subIdRule) + if err != nil { + return nil, err + } + return &VRFCoordinatorTestV25SubscriptionCreatedIterator{contract: _VRFCoordinatorTestV25.contract, event: "SubscriptionCreated", logs: logs, sub: sub}, nil +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) WatchSubscriptionCreated(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25SubscriptionCreated, subId []*big.Int) (event.Subscription, error) { + + var subIdRule []interface{} + for _, subIdItem := range subId { + subIdRule = append(subIdRule, subIdItem) + } + + logs, sub, err := _VRFCoordinatorTestV25.contract.WatchLogs(opts, "SubscriptionCreated", subIdRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(VRFCoordinatorTestV25SubscriptionCreated) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "SubscriptionCreated", 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 (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) ParseSubscriptionCreated(log types.Log) (*VRFCoordinatorTestV25SubscriptionCreated, error) { + event := new(VRFCoordinatorTestV25SubscriptionCreated) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "SubscriptionCreated", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type VRFCoordinatorTestV25SubscriptionFundedIterator struct { + Event *VRFCoordinatorTestV25SubscriptionFunded + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *VRFCoordinatorTestV25SubscriptionFundedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(VRFCoordinatorTestV25SubscriptionFunded) + 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(VRFCoordinatorTestV25SubscriptionFunded) + 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 *VRFCoordinatorTestV25SubscriptionFundedIterator) Error() error { + return it.fail +} + +func (it *VRFCoordinatorTestV25SubscriptionFundedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type VRFCoordinatorTestV25SubscriptionFunded struct { + SubId *big.Int + OldBalance *big.Int + NewBalance *big.Int + Raw types.Log +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) FilterSubscriptionFunded(opts *bind.FilterOpts, subId []*big.Int) (*VRFCoordinatorTestV25SubscriptionFundedIterator, error) { + + var subIdRule []interface{} + for _, subIdItem := range subId { + subIdRule = append(subIdRule, subIdItem) + } + + logs, sub, err := _VRFCoordinatorTestV25.contract.FilterLogs(opts, "SubscriptionFunded", subIdRule) + if err != nil { + return nil, err + } + return &VRFCoordinatorTestV25SubscriptionFundedIterator{contract: _VRFCoordinatorTestV25.contract, event: "SubscriptionFunded", logs: logs, sub: sub}, nil +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) WatchSubscriptionFunded(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25SubscriptionFunded, subId []*big.Int) (event.Subscription, error) { + + var subIdRule []interface{} + for _, subIdItem := range subId { + subIdRule = append(subIdRule, subIdItem) + } + + logs, sub, err := _VRFCoordinatorTestV25.contract.WatchLogs(opts, "SubscriptionFunded", subIdRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(VRFCoordinatorTestV25SubscriptionFunded) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "SubscriptionFunded", 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 (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) ParseSubscriptionFunded(log types.Log) (*VRFCoordinatorTestV25SubscriptionFunded, error) { + event := new(VRFCoordinatorTestV25SubscriptionFunded) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "SubscriptionFunded", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type VRFCoordinatorTestV25SubscriptionFundedWithNativeIterator struct { + Event *VRFCoordinatorTestV25SubscriptionFundedWithNative + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *VRFCoordinatorTestV25SubscriptionFundedWithNativeIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(VRFCoordinatorTestV25SubscriptionFundedWithNative) + 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(VRFCoordinatorTestV25SubscriptionFundedWithNative) + 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 *VRFCoordinatorTestV25SubscriptionFundedWithNativeIterator) Error() error { + return it.fail +} + +func (it *VRFCoordinatorTestV25SubscriptionFundedWithNativeIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type VRFCoordinatorTestV25SubscriptionFundedWithNative struct { + SubId *big.Int + OldNativeBalance *big.Int + NewNativeBalance *big.Int + Raw types.Log +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) FilterSubscriptionFundedWithNative(opts *bind.FilterOpts, subId []*big.Int) (*VRFCoordinatorTestV25SubscriptionFundedWithNativeIterator, error) { + + var subIdRule []interface{} + for _, subIdItem := range subId { + subIdRule = append(subIdRule, subIdItem) + } + + logs, sub, err := _VRFCoordinatorTestV25.contract.FilterLogs(opts, "SubscriptionFundedWithNative", subIdRule) + if err != nil { + return nil, err + } + return &VRFCoordinatorTestV25SubscriptionFundedWithNativeIterator{contract: _VRFCoordinatorTestV25.contract, event: "SubscriptionFundedWithNative", logs: logs, sub: sub}, nil +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) WatchSubscriptionFundedWithNative(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25SubscriptionFundedWithNative, subId []*big.Int) (event.Subscription, error) { + + var subIdRule []interface{} + for _, subIdItem := range subId { + subIdRule = append(subIdRule, subIdItem) + } + + logs, sub, err := _VRFCoordinatorTestV25.contract.WatchLogs(opts, "SubscriptionFundedWithNative", subIdRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(VRFCoordinatorTestV25SubscriptionFundedWithNative) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "SubscriptionFundedWithNative", 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 (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) ParseSubscriptionFundedWithNative(log types.Log) (*VRFCoordinatorTestV25SubscriptionFundedWithNative, error) { + event := new(VRFCoordinatorTestV25SubscriptionFundedWithNative) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "SubscriptionFundedWithNative", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type VRFCoordinatorTestV25SubscriptionOwnerTransferRequestedIterator struct { + Event *VRFCoordinatorTestV25SubscriptionOwnerTransferRequested + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *VRFCoordinatorTestV25SubscriptionOwnerTransferRequestedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(VRFCoordinatorTestV25SubscriptionOwnerTransferRequested) + 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(VRFCoordinatorTestV25SubscriptionOwnerTransferRequested) + 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 *VRFCoordinatorTestV25SubscriptionOwnerTransferRequestedIterator) Error() error { + return it.fail +} + +func (it *VRFCoordinatorTestV25SubscriptionOwnerTransferRequestedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type VRFCoordinatorTestV25SubscriptionOwnerTransferRequested struct { + SubId *big.Int + From common.Address + To common.Address + Raw types.Log +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) FilterSubscriptionOwnerTransferRequested(opts *bind.FilterOpts, subId []*big.Int) (*VRFCoordinatorTestV25SubscriptionOwnerTransferRequestedIterator, error) { + + var subIdRule []interface{} + for _, subIdItem := range subId { + subIdRule = append(subIdRule, subIdItem) + } + + logs, sub, err := _VRFCoordinatorTestV25.contract.FilterLogs(opts, "SubscriptionOwnerTransferRequested", subIdRule) + if err != nil { + return nil, err + } + return &VRFCoordinatorTestV25SubscriptionOwnerTransferRequestedIterator{contract: _VRFCoordinatorTestV25.contract, event: "SubscriptionOwnerTransferRequested", logs: logs, sub: sub}, nil +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) WatchSubscriptionOwnerTransferRequested(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25SubscriptionOwnerTransferRequested, subId []*big.Int) (event.Subscription, error) { + + var subIdRule []interface{} + for _, subIdItem := range subId { + subIdRule = append(subIdRule, subIdItem) + } + + logs, sub, err := _VRFCoordinatorTestV25.contract.WatchLogs(opts, "SubscriptionOwnerTransferRequested", subIdRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(VRFCoordinatorTestV25SubscriptionOwnerTransferRequested) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "SubscriptionOwnerTransferRequested", 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 (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) ParseSubscriptionOwnerTransferRequested(log types.Log) (*VRFCoordinatorTestV25SubscriptionOwnerTransferRequested, error) { + event := new(VRFCoordinatorTestV25SubscriptionOwnerTransferRequested) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "SubscriptionOwnerTransferRequested", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type VRFCoordinatorTestV25SubscriptionOwnerTransferredIterator struct { + Event *VRFCoordinatorTestV25SubscriptionOwnerTransferred + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *VRFCoordinatorTestV25SubscriptionOwnerTransferredIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(VRFCoordinatorTestV25SubscriptionOwnerTransferred) + 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(VRFCoordinatorTestV25SubscriptionOwnerTransferred) + 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 *VRFCoordinatorTestV25SubscriptionOwnerTransferredIterator) Error() error { + return it.fail +} + +func (it *VRFCoordinatorTestV25SubscriptionOwnerTransferredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type VRFCoordinatorTestV25SubscriptionOwnerTransferred struct { + SubId *big.Int + From common.Address + To common.Address + Raw types.Log +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) FilterSubscriptionOwnerTransferred(opts *bind.FilterOpts, subId []*big.Int) (*VRFCoordinatorTestV25SubscriptionOwnerTransferredIterator, error) { + + var subIdRule []interface{} + for _, subIdItem := range subId { + subIdRule = append(subIdRule, subIdItem) + } + + logs, sub, err := _VRFCoordinatorTestV25.contract.FilterLogs(opts, "SubscriptionOwnerTransferred", subIdRule) + if err != nil { + return nil, err + } + return &VRFCoordinatorTestV25SubscriptionOwnerTransferredIterator{contract: _VRFCoordinatorTestV25.contract, event: "SubscriptionOwnerTransferred", logs: logs, sub: sub}, nil +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) WatchSubscriptionOwnerTransferred(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25SubscriptionOwnerTransferred, subId []*big.Int) (event.Subscription, error) { + + var subIdRule []interface{} + for _, subIdItem := range subId { + subIdRule = append(subIdRule, subIdItem) + } + + logs, sub, err := _VRFCoordinatorTestV25.contract.WatchLogs(opts, "SubscriptionOwnerTransferred", subIdRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(VRFCoordinatorTestV25SubscriptionOwnerTransferred) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "SubscriptionOwnerTransferred", 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 (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25Filterer) ParseSubscriptionOwnerTransferred(log types.Log) (*VRFCoordinatorTestV25SubscriptionOwnerTransferred, error) { + event := new(VRFCoordinatorTestV25SubscriptionOwnerTransferred) + if err := _VRFCoordinatorTestV25.contract.UnpackLog(event, "SubscriptionOwnerTransferred", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type GetSubscription struct { + Balance *big.Int + NativeBalance *big.Int + ReqCount uint64 + SubOwner common.Address + Consumers []common.Address +} +type SConfig struct { + MinimumRequestConfirmations uint16 + MaxGasLimit uint32 + ReentrancyLock bool + StalenessSeconds uint32 + GasAfterPaymentCalculation uint32 + FulfillmentFlatFeeNativePPM uint32 + FulfillmentFlatFeeLinkDiscountPPM uint32 + NativePremiumPercentage uint8 + LinkPremiumPercentage uint8 +} +type SProvingKeys struct { + Exists bool + MaxGas uint64 +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25) ParseLog(log types.Log) (generated.AbigenLog, error) { + switch log.Topics[0] { + case _VRFCoordinatorTestV25.abi.Events["ConfigSet"].ID: + return _VRFCoordinatorTestV25.ParseConfigSet(log) + case _VRFCoordinatorTestV25.abi.Events["CoordinatorDeregistered"].ID: + return _VRFCoordinatorTestV25.ParseCoordinatorDeregistered(log) + case _VRFCoordinatorTestV25.abi.Events["CoordinatorRegistered"].ID: + return _VRFCoordinatorTestV25.ParseCoordinatorRegistered(log) + case _VRFCoordinatorTestV25.abi.Events["FallbackWeiPerUnitLinkUsed"].ID: + return _VRFCoordinatorTestV25.ParseFallbackWeiPerUnitLinkUsed(log) + case _VRFCoordinatorTestV25.abi.Events["FundsRecovered"].ID: + return _VRFCoordinatorTestV25.ParseFundsRecovered(log) + case _VRFCoordinatorTestV25.abi.Events["MigrationCompleted"].ID: + return _VRFCoordinatorTestV25.ParseMigrationCompleted(log) + case _VRFCoordinatorTestV25.abi.Events["NativeFundsRecovered"].ID: + return _VRFCoordinatorTestV25.ParseNativeFundsRecovered(log) + case _VRFCoordinatorTestV25.abi.Events["OwnershipTransferRequested"].ID: + return _VRFCoordinatorTestV25.ParseOwnershipTransferRequested(log) + case _VRFCoordinatorTestV25.abi.Events["OwnershipTransferred"].ID: + return _VRFCoordinatorTestV25.ParseOwnershipTransferred(log) + case _VRFCoordinatorTestV25.abi.Events["ProvingKeyDeregistered"].ID: + return _VRFCoordinatorTestV25.ParseProvingKeyDeregistered(log) + case _VRFCoordinatorTestV25.abi.Events["ProvingKeyRegistered"].ID: + return _VRFCoordinatorTestV25.ParseProvingKeyRegistered(log) + case _VRFCoordinatorTestV25.abi.Events["RandomWordsFulfilled"].ID: + return _VRFCoordinatorTestV25.ParseRandomWordsFulfilled(log) + case _VRFCoordinatorTestV25.abi.Events["RandomWordsRequested"].ID: + return _VRFCoordinatorTestV25.ParseRandomWordsRequested(log) + case _VRFCoordinatorTestV25.abi.Events["SubscriptionCanceled"].ID: + return _VRFCoordinatorTestV25.ParseSubscriptionCanceled(log) + case _VRFCoordinatorTestV25.abi.Events["SubscriptionConsumerAdded"].ID: + return _VRFCoordinatorTestV25.ParseSubscriptionConsumerAdded(log) + case _VRFCoordinatorTestV25.abi.Events["SubscriptionConsumerRemoved"].ID: + return _VRFCoordinatorTestV25.ParseSubscriptionConsumerRemoved(log) + case _VRFCoordinatorTestV25.abi.Events["SubscriptionCreated"].ID: + return _VRFCoordinatorTestV25.ParseSubscriptionCreated(log) + case _VRFCoordinatorTestV25.abi.Events["SubscriptionFunded"].ID: + return _VRFCoordinatorTestV25.ParseSubscriptionFunded(log) + case _VRFCoordinatorTestV25.abi.Events["SubscriptionFundedWithNative"].ID: + return _VRFCoordinatorTestV25.ParseSubscriptionFundedWithNative(log) + case _VRFCoordinatorTestV25.abi.Events["SubscriptionOwnerTransferRequested"].ID: + return _VRFCoordinatorTestV25.ParseSubscriptionOwnerTransferRequested(log) + case _VRFCoordinatorTestV25.abi.Events["SubscriptionOwnerTransferred"].ID: + return _VRFCoordinatorTestV25.ParseSubscriptionOwnerTransferred(log) + + default: + return nil, fmt.Errorf("abigen wrapper received unknown log topic: %v", log.Topics[0]) + } +} + +func (VRFCoordinatorTestV25ConfigSet) Topic() common.Hash { + return common.HexToHash("0x2c6b6b12413678366b05b145c5f00745bdd00e739131ab5de82484a50c9d78b6") +} + +func (VRFCoordinatorTestV25CoordinatorDeregistered) Topic() common.Hash { + return common.HexToHash("0xf80a1a97fd42251f3c33cda98635e7399253033a6774fe37cd3f650b5282af37") +} + +func (VRFCoordinatorTestV25CoordinatorRegistered) Topic() common.Hash { + return common.HexToHash("0xb7cabbfc11e66731fc77de0444614282023bcbd41d16781c753a431d0af01625") +} + +func (VRFCoordinatorTestV25FallbackWeiPerUnitLinkUsed) Topic() common.Hash { + return common.HexToHash("0x6ca648a381f22ead7e37773d934e64885dcf861fbfbb26c40354cbf0c4662d1a") +} + +func (VRFCoordinatorTestV25FundsRecovered) Topic() common.Hash { + return common.HexToHash("0x59bfc682b673f8cbf945f1e454df9334834abf7dfe7f92237ca29ecb9b436600") +} + +func (VRFCoordinatorTestV25MigrationCompleted) Topic() common.Hash { + return common.HexToHash("0xd63ca8cb945956747ee69bfdc3ea754c24a4caf7418db70e46052f7850be4187") +} + +func (VRFCoordinatorTestV25NativeFundsRecovered) Topic() common.Hash { + return common.HexToHash("0x4aed7c8eed0496c8c19ea2681fcca25741c1602342e38b045d9f1e8e905d2e9c") +} + +func (VRFCoordinatorTestV25OwnershipTransferRequested) Topic() common.Hash { + return common.HexToHash("0xed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278") +} + +func (VRFCoordinatorTestV25OwnershipTransferred) Topic() common.Hash { + return common.HexToHash("0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0") +} + +func (VRFCoordinatorTestV25ProvingKeyDeregistered) Topic() common.Hash { + return common.HexToHash("0x9b6868e0eb737bcd72205360baa6bfd0ba4e4819a33ade2db384e8a8025639a5") +} + +func (VRFCoordinatorTestV25ProvingKeyRegistered) Topic() common.Hash { + return common.HexToHash("0x9b911b2c240bfbef3b6a8f7ed6ee321d1258bb2a3fe6becab52ac1cd3210afd3") +} + +func (VRFCoordinatorTestV25RandomWordsFulfilled) Topic() common.Hash { + return common.HexToHash("0xaeb4b4786571e184246d39587f659abf0e26f41f6a3358692250382c0cdb47b7") +} + +func (VRFCoordinatorTestV25RandomWordsRequested) Topic() common.Hash { + return common.HexToHash("0xeb0e3652e0f44f417695e6e90f2f42c99b65cd7169074c5a654b16b9748c3a4e") +} + +func (VRFCoordinatorTestV25SubscriptionCanceled) Topic() common.Hash { + return common.HexToHash("0x8c74ce8b8cf87f5eb001275c8be27eb34ea2b62bfab6814fcc62192bb63e81c4") +} + +func (VRFCoordinatorTestV25SubscriptionConsumerAdded) Topic() common.Hash { + return common.HexToHash("0x1e980d04aa7648e205713e5e8ea3808672ac163d10936d36f91b2c88ac1575e1") +} + +func (VRFCoordinatorTestV25SubscriptionConsumerRemoved) Topic() common.Hash { + return common.HexToHash("0x32158c6058347c1601b2d12bc696ac6901d8a9a9aa3ba10c27ab0a983e8425a7") +} + +func (VRFCoordinatorTestV25SubscriptionCreated) Topic() common.Hash { + return common.HexToHash("0x1d3015d7ba850fa198dc7b1a3f5d42779313a681035f77c8c03764c61005518d") +} + +func (VRFCoordinatorTestV25SubscriptionFunded) Topic() common.Hash { + return common.HexToHash("0x1ced9348ff549fceab2ac57cd3a9de38edaaab274b725ee82c23e8fc8c4eec7a") +} + +func (VRFCoordinatorTestV25SubscriptionFundedWithNative) Topic() common.Hash { + return common.HexToHash("0x7603b205d03651ee812f803fccde89f1012e545a9c99f0abfea9cedd0fd8e902") +} + +func (VRFCoordinatorTestV25SubscriptionOwnerTransferRequested) Topic() common.Hash { + return common.HexToHash("0x21a4dad170a6bf476c31bbcf4a16628295b0e450672eec25d7c93308e05344a1") +} + +func (VRFCoordinatorTestV25SubscriptionOwnerTransferred) Topic() common.Hash { + return common.HexToHash("0xd4114ab6e9af9f597c52041f32d62dc57c5c4e4c0d4427006069635e216c9386") +} + +func (_VRFCoordinatorTestV25 *VRFCoordinatorTestV25) Address() common.Address { + return _VRFCoordinatorTestV25.address +} + +type VRFCoordinatorTestV25Interface interface { + BLOCKHASHSTORE(opts *bind.CallOpts) (common.Address, error) + + LINK(opts *bind.CallOpts) (common.Address, error) + + LINKNATIVEFEED(opts *bind.CallOpts) (common.Address, error) + + MAXCONSUMERS(opts *bind.CallOpts) (uint16, error) + + MAXNUMWORDS(opts *bind.CallOpts) (uint32, error) + + MAXREQUESTCONFIRMATIONS(opts *bind.CallOpts) (uint16, error) + + GetActiveSubscriptionIds(opts *bind.CallOpts, startIndex *big.Int, maxCount *big.Int) ([]*big.Int, error) + + GetSubscription(opts *bind.CallOpts, subId *big.Int) (GetSubscription, + + error) + + HashOfKey(opts *bind.CallOpts, publicKey [2]*big.Int) ([32]byte, error) + + Owner(opts *bind.CallOpts) (common.Address, error) + + PendingRequestExists(opts *bind.CallOpts, subId *big.Int) (bool, error) + + SConfig(opts *bind.CallOpts) (SConfig, + + error) + + SCurrentSubNonce(opts *bind.CallOpts) (uint64, error) + + SFallbackWeiPerUnitLink(opts *bind.CallOpts) (*big.Int, error) + + SProvingKeyHashes(opts *bind.CallOpts, arg0 *big.Int) ([32]byte, error) + + SProvingKeys(opts *bind.CallOpts, arg0 [32]byte) (SProvingKeys, + + error) + + SRequestCommitments(opts *bind.CallOpts, arg0 *big.Int) ([32]byte, error) + + STotalBalance(opts *bind.CallOpts) (*big.Int, error) + + STotalNativeBalance(opts *bind.CallOpts) (*big.Int, error) + + AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) + + AcceptSubscriptionOwnerTransfer(opts *bind.TransactOpts, subId *big.Int) (*types.Transaction, error) + + AddConsumer(opts *bind.TransactOpts, subId *big.Int, consumer common.Address) (*types.Transaction, error) + + CancelSubscription(opts *bind.TransactOpts, subId *big.Int, to common.Address) (*types.Transaction, error) + + CreateSubscription(opts *bind.TransactOpts) (*types.Transaction, error) + + DeregisterMigratableCoordinator(opts *bind.TransactOpts, target common.Address) (*types.Transaction, error) + + DeregisterProvingKey(opts *bind.TransactOpts, publicProvingKey [2]*big.Int) (*types.Transaction, error) + + FulfillRandomWords(opts *bind.TransactOpts, proof VRFOldProof, rc VRFTypesRequestCommitmentV2Plus, onlyPremium bool) (*types.Transaction, error) + + FundSubscriptionWithNative(opts *bind.TransactOpts, subId *big.Int) (*types.Transaction, error) + + Migrate(opts *bind.TransactOpts, subId *big.Int, newCoordinator common.Address) (*types.Transaction, error) + + OnTokenTransfer(opts *bind.TransactOpts, arg0 common.Address, amount *big.Int, data []byte) (*types.Transaction, error) + + OwnerCancelSubscription(opts *bind.TransactOpts, subId *big.Int) (*types.Transaction, error) + + RecoverFunds(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) + + RecoverNativeFunds(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) + + RegisterMigratableCoordinator(opts *bind.TransactOpts, target common.Address) (*types.Transaction, error) + + RegisterProvingKey(opts *bind.TransactOpts, publicProvingKey [2]*big.Int, maxGas uint64) (*types.Transaction, error) + + RemoveConsumer(opts *bind.TransactOpts, subId *big.Int, consumer common.Address) (*types.Transaction, error) + + RequestRandomWords(opts *bind.TransactOpts, req VRFV2PlusClientRandomWordsRequest) (*types.Transaction, error) + + RequestSubscriptionOwnerTransfer(opts *bind.TransactOpts, subId *big.Int, newOwner common.Address) (*types.Transaction, error) + + SetConfig(opts *bind.TransactOpts, minimumRequestConfirmations uint16, maxGasLimit uint32, stalenessSeconds uint32, gasAfterPaymentCalculation uint32, fallbackWeiPerUnitLink *big.Int, fulfillmentFlatFeeNativePPM uint32, fulfillmentFlatFeeLinkDiscountPPM uint32, nativePremiumPercentage uint8, linkPremiumPercentage uint8) (*types.Transaction, error) + + SetLINKAndLINKNativeFeed(opts *bind.TransactOpts, link common.Address, linkNativeFeed common.Address) (*types.Transaction, error) + + TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) + + Withdraw(opts *bind.TransactOpts, recipient common.Address) (*types.Transaction, error) + + WithdrawNative(opts *bind.TransactOpts, recipient common.Address) (*types.Transaction, error) + + FilterConfigSet(opts *bind.FilterOpts) (*VRFCoordinatorTestV25ConfigSetIterator, error) + + WatchConfigSet(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25ConfigSet) (event.Subscription, error) + + ParseConfigSet(log types.Log) (*VRFCoordinatorTestV25ConfigSet, error) + + FilterCoordinatorDeregistered(opts *bind.FilterOpts) (*VRFCoordinatorTestV25CoordinatorDeregisteredIterator, error) + + WatchCoordinatorDeregistered(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25CoordinatorDeregistered) (event.Subscription, error) + + ParseCoordinatorDeregistered(log types.Log) (*VRFCoordinatorTestV25CoordinatorDeregistered, error) + + FilterCoordinatorRegistered(opts *bind.FilterOpts) (*VRFCoordinatorTestV25CoordinatorRegisteredIterator, error) + + WatchCoordinatorRegistered(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25CoordinatorRegistered) (event.Subscription, error) + + ParseCoordinatorRegistered(log types.Log) (*VRFCoordinatorTestV25CoordinatorRegistered, error) + + FilterFallbackWeiPerUnitLinkUsed(opts *bind.FilterOpts) (*VRFCoordinatorTestV25FallbackWeiPerUnitLinkUsedIterator, error) + + WatchFallbackWeiPerUnitLinkUsed(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25FallbackWeiPerUnitLinkUsed) (event.Subscription, error) + + ParseFallbackWeiPerUnitLinkUsed(log types.Log) (*VRFCoordinatorTestV25FallbackWeiPerUnitLinkUsed, error) + + FilterFundsRecovered(opts *bind.FilterOpts) (*VRFCoordinatorTestV25FundsRecoveredIterator, error) + + WatchFundsRecovered(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25FundsRecovered) (event.Subscription, error) + + ParseFundsRecovered(log types.Log) (*VRFCoordinatorTestV25FundsRecovered, error) + + FilterMigrationCompleted(opts *bind.FilterOpts) (*VRFCoordinatorTestV25MigrationCompletedIterator, error) + + WatchMigrationCompleted(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25MigrationCompleted) (event.Subscription, error) + + ParseMigrationCompleted(log types.Log) (*VRFCoordinatorTestV25MigrationCompleted, error) + + FilterNativeFundsRecovered(opts *bind.FilterOpts) (*VRFCoordinatorTestV25NativeFundsRecoveredIterator, error) + + WatchNativeFundsRecovered(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25NativeFundsRecovered) (event.Subscription, error) + + ParseNativeFundsRecovered(log types.Log) (*VRFCoordinatorTestV25NativeFundsRecovered, error) + + FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*VRFCoordinatorTestV25OwnershipTransferRequestedIterator, error) + + WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25OwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error) + + ParseOwnershipTransferRequested(log types.Log) (*VRFCoordinatorTestV25OwnershipTransferRequested, error) + + FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*VRFCoordinatorTestV25OwnershipTransferredIterator, error) + + WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25OwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error) + + ParseOwnershipTransferred(log types.Log) (*VRFCoordinatorTestV25OwnershipTransferred, error) + + FilterProvingKeyDeregistered(opts *bind.FilterOpts) (*VRFCoordinatorTestV25ProvingKeyDeregisteredIterator, error) + + WatchProvingKeyDeregistered(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25ProvingKeyDeregistered) (event.Subscription, error) + + ParseProvingKeyDeregistered(log types.Log) (*VRFCoordinatorTestV25ProvingKeyDeregistered, error) + + FilterProvingKeyRegistered(opts *bind.FilterOpts) (*VRFCoordinatorTestV25ProvingKeyRegisteredIterator, error) + + WatchProvingKeyRegistered(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25ProvingKeyRegistered) (event.Subscription, error) + + ParseProvingKeyRegistered(log types.Log) (*VRFCoordinatorTestV25ProvingKeyRegistered, error) + + FilterRandomWordsFulfilled(opts *bind.FilterOpts, requestId []*big.Int, subId []*big.Int) (*VRFCoordinatorTestV25RandomWordsFulfilledIterator, error) + + WatchRandomWordsFulfilled(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25RandomWordsFulfilled, requestId []*big.Int, subId []*big.Int) (event.Subscription, error) + + ParseRandomWordsFulfilled(log types.Log) (*VRFCoordinatorTestV25RandomWordsFulfilled, error) + + FilterRandomWordsRequested(opts *bind.FilterOpts, keyHash [][32]byte, subId []*big.Int, sender []common.Address) (*VRFCoordinatorTestV25RandomWordsRequestedIterator, error) + + WatchRandomWordsRequested(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25RandomWordsRequested, keyHash [][32]byte, subId []*big.Int, sender []common.Address) (event.Subscription, error) + + ParseRandomWordsRequested(log types.Log) (*VRFCoordinatorTestV25RandomWordsRequested, error) + + FilterSubscriptionCanceled(opts *bind.FilterOpts, subId []*big.Int) (*VRFCoordinatorTestV25SubscriptionCanceledIterator, error) + + WatchSubscriptionCanceled(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25SubscriptionCanceled, subId []*big.Int) (event.Subscription, error) + + ParseSubscriptionCanceled(log types.Log) (*VRFCoordinatorTestV25SubscriptionCanceled, error) + + FilterSubscriptionConsumerAdded(opts *bind.FilterOpts, subId []*big.Int) (*VRFCoordinatorTestV25SubscriptionConsumerAddedIterator, error) + + WatchSubscriptionConsumerAdded(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25SubscriptionConsumerAdded, subId []*big.Int) (event.Subscription, error) + + ParseSubscriptionConsumerAdded(log types.Log) (*VRFCoordinatorTestV25SubscriptionConsumerAdded, error) + + FilterSubscriptionConsumerRemoved(opts *bind.FilterOpts, subId []*big.Int) (*VRFCoordinatorTestV25SubscriptionConsumerRemovedIterator, error) + + WatchSubscriptionConsumerRemoved(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25SubscriptionConsumerRemoved, subId []*big.Int) (event.Subscription, error) + + ParseSubscriptionConsumerRemoved(log types.Log) (*VRFCoordinatorTestV25SubscriptionConsumerRemoved, error) + + FilterSubscriptionCreated(opts *bind.FilterOpts, subId []*big.Int) (*VRFCoordinatorTestV25SubscriptionCreatedIterator, error) + + WatchSubscriptionCreated(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25SubscriptionCreated, subId []*big.Int) (event.Subscription, error) + + ParseSubscriptionCreated(log types.Log) (*VRFCoordinatorTestV25SubscriptionCreated, error) + + FilterSubscriptionFunded(opts *bind.FilterOpts, subId []*big.Int) (*VRFCoordinatorTestV25SubscriptionFundedIterator, error) + + WatchSubscriptionFunded(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25SubscriptionFunded, subId []*big.Int) (event.Subscription, error) + + ParseSubscriptionFunded(log types.Log) (*VRFCoordinatorTestV25SubscriptionFunded, error) + + FilterSubscriptionFundedWithNative(opts *bind.FilterOpts, subId []*big.Int) (*VRFCoordinatorTestV25SubscriptionFundedWithNativeIterator, error) + + WatchSubscriptionFundedWithNative(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25SubscriptionFundedWithNative, subId []*big.Int) (event.Subscription, error) + + ParseSubscriptionFundedWithNative(log types.Log) (*VRFCoordinatorTestV25SubscriptionFundedWithNative, error) + + FilterSubscriptionOwnerTransferRequested(opts *bind.FilterOpts, subId []*big.Int) (*VRFCoordinatorTestV25SubscriptionOwnerTransferRequestedIterator, error) + + WatchSubscriptionOwnerTransferRequested(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25SubscriptionOwnerTransferRequested, subId []*big.Int) (event.Subscription, error) + + ParseSubscriptionOwnerTransferRequested(log types.Log) (*VRFCoordinatorTestV25SubscriptionOwnerTransferRequested, error) + + FilterSubscriptionOwnerTransferred(opts *bind.FilterOpts, subId []*big.Int) (*VRFCoordinatorTestV25SubscriptionOwnerTransferredIterator, error) + + WatchSubscriptionOwnerTransferred(opts *bind.WatchOpts, sink chan<- *VRFCoordinatorTestV25SubscriptionOwnerTransferred, subId []*big.Int) (event.Subscription, error) + + ParseSubscriptionOwnerTransferred(log types.Log) (*VRFCoordinatorTestV25SubscriptionOwnerTransferred, error) + + ParseLog(log types.Log) (generated.AbigenLog, error) + + Address() common.Address +} diff --git a/core/gethwrappers/generated/weth9_wrapper/weth9_wrapper.go b/core/gethwrappers/generated/weth9_wrapper/weth9_wrapper.go index f38435143b..87849c786f 100644 --- a/core/gethwrappers/generated/weth9_wrapper/weth9_wrapper.go +++ b/core/gethwrappers/generated/weth9_wrapper/weth9_wrapper.go @@ -32,7 +32,7 @@ var ( var WETH9MetaData = &bind.MetaData{ ABI: "[{\"inputs\":[],\"name\":\"InsufficientBalance\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"src\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"guy\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"dst\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"Deposit\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"src\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"dst\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"src\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"Withdrawal\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"guy\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"deposit\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"mint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"dst\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"src\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"dst\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"withdraw\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}]", - Bin: "0x60c0604052600d60809081526c2bb930b83832b21022ba3432b960991b60a05260009061002c9082610114565b506040805180820190915260048152630ae8aa8960e31b60208201526001906100559082610114565b506002805460ff1916601217905534801561006f57600080fd5b506101d3565b634e487b7160e01b600052604160045260246000fd5b600181811c9082168061009f57607f821691505b6020821081036100bf57634e487b7160e01b600052602260045260246000fd5b50919050565b601f82111561010f57600081815260208120601f850160051c810160208610156100ec5750805b601f850160051c820191505b8181101561010b578281556001016100f8565b5050505b505050565b81516001600160401b0381111561012d5761012d610075565b6101418161013b845461008b565b846100c5565b602080601f831160018114610176576000841561015e5750858301515b600019600386901b1c1916600185901b17855561010b565b600085815260208120601f198616915b828110156101a557888601518255948401946001909101908401610186565b50858210156101c35787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b61099c80620001e36000396000f3fe6080604052600436106100cb5760003560e01c806340c10f1911610074578063a9059cbb1161004e578063a9059cbb14610225578063d0e30db014610245578063dd62ed3e1461024d57600080fd5b806340c10f19146101c357806370a08231146101e357806395d89b411461021057600080fd5b806323b872dd116100a557806323b872dd146101575780632e1a7d4d14610177578063313ce5671461019757600080fd5b806306fdde03146100df578063095ea7b31461010a57806318160ddd1461013a57600080fd5b366100da576100d8610285565b005b600080fd5b3480156100eb57600080fd5b506100f46102e0565b6040516101019190610785565b60405180910390f35b34801561011657600080fd5b5061012a61012536600461081a565b61036e565b6040519015158152602001610101565b34801561014657600080fd5b50475b604051908152602001610101565b34801561016357600080fd5b5061012a610172366004610844565b6103e8565b34801561018357600080fd5b506100d8610192366004610880565b610649565b3480156101a357600080fd5b506002546101b19060ff1681565b60405160ff9091168152602001610101565b3480156101cf57600080fd5b506100d86101de36600461081a565b61071c565b3480156101ef57600080fd5b506101496101fe366004610899565b60036020526000908152604090205481565b34801561021c57600080fd5b506100f461075a565b34801561023157600080fd5b5061012a61024036600461081a565b610767565b6100d861077b565b34801561025957600080fd5b506101496102683660046108b4565b600460209081526000928352604080842090915290825290205481565b33600090815260036020526040812080543492906102a4908490610916565b909155505060405134815233907fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c9060200160405180910390a2565b600080546102ed90610929565b80601f016020809104026020016040519081016040528092919081815260200182805461031990610929565b80156103665780601f1061033b57610100808354040283529160200191610366565b820191906000526020600020905b81548152906001019060200180831161034957829003601f168201915b505050505081565b33600081815260046020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925906103d69086815260200190565b60405180910390a35060015b92915050565b73ffffffffffffffffffffffffffffffffffffffff8316600090815260036020526040812054821115610447576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff841633148015906104ad575073ffffffffffffffffffffffffffffffffffffffff841660009081526004602090815260408083203384529091529020546fffffffffffffffffffffffffffffffff14155b156105625773ffffffffffffffffffffffffffffffffffffffff8416600090815260046020908152604080832033845290915290205482111561051c576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff841660009081526004602090815260408083203384529091528120805484929061055c90849061097c565b90915550505b73ffffffffffffffffffffffffffffffffffffffff84166000908152600360205260408120805484929061059790849061097c565b909155505073ffffffffffffffffffffffffffffffffffffffff8316600090815260036020526040812080548492906105d1908490610916565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161063791815260200190565b60405180910390a35060019392505050565b33600090815260036020526040902054811115610692576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b33600090815260036020526040812080548392906106b190849061097c565b9091555050604051339082156108fc029083906000818181858888f193505050501580156106e3573d6000803e3d6000fd5b5060405181815233907f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b659060200160405180910390a250565b73ffffffffffffffffffffffffffffffffffffffff821660009081526003602052604081208054839290610751908490610916565b90915550505050565b600180546102ed90610929565b60006107743384846103e8565b9392505050565b610783610285565b565b600060208083528351808285015260005b818110156107b257858101830151858201604001528201610796565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461081557600080fd5b919050565b6000806040838503121561082d57600080fd5b610836836107f1565b946020939093013593505050565b60008060006060848603121561085957600080fd5b610862846107f1565b9250610870602085016107f1565b9150604084013590509250925092565b60006020828403121561089257600080fd5b5035919050565b6000602082840312156108ab57600080fd5b610774826107f1565b600080604083850312156108c757600080fd5b6108d0836107f1565b91506108de602084016107f1565b90509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b808201808211156103e2576103e26108e7565b600181811c9082168061093d57607f821691505b602082108103610976577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b818103818111156103e2576103e26108e756fea164736f6c6343000813000a", + Bin: "0x60c0604052600d60809081526c2bb930b83832b21022ba3432b960991b60a05260009061002c9082610114565b506040805180820190915260048152630ae8aa8960e31b60208201526001906100559082610114565b506002805460ff1916601217905534801561006f57600080fd5b506101d3565b634e487b7160e01b600052604160045260246000fd5b600181811c9082168061009f57607f821691505b6020821081036100bf57634e487b7160e01b600052602260045260246000fd5b50919050565b601f82111561010f57600081815260208120601f850160051c810160208610156100ec5750805b601f850160051c820191505b8181101561010b578281556001016100f8565b5050505b505050565b81516001600160401b0381111561012d5761012d610075565b6101418161013b845461008b565b846100c5565b602080601f831160018114610176576000841561015e5750858301515b600019600386901b1c1916600185901b17855561010b565b600085815260208120601f198616915b828110156101a557888601518255948401946001909101908401610186565b50858210156101c35787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b6109b680620001e36000396000f3fe6080604052600436106100cb5760003560e01c806340c10f1911610074578063a9059cbb1161004e578063a9059cbb14610225578063d0e30db014610245578063dd62ed3e1461024d57600080fd5b806340c10f19146101c357806370a08231146101e357806395d89b411461021057600080fd5b806323b872dd116100a557806323b872dd146101575780632e1a7d4d14610177578063313ce5671461019757600080fd5b806306fdde03146100df578063095ea7b31461010a57806318160ddd1461013a57600080fd5b366100da576100d8610285565b005b600080fd5b3480156100eb57600080fd5b506100f46102e0565b604051610101919061079f565b60405180910390f35b34801561011657600080fd5b5061012a610125366004610834565b61036e565b6040519015158152602001610101565b34801561014657600080fd5b50475b604051908152602001610101565b34801561016357600080fd5b5061012a61017236600461085e565b6103e8565b34801561018357600080fd5b506100d861019236600461089a565b610649565b3480156101a357600080fd5b506002546101b19060ff1681565b60405160ff9091168152602001610101565b3480156101cf57600080fd5b506100d86101de366004610834565b610736565b3480156101ef57600080fd5b506101496101fe3660046108b3565b60036020526000908152604090205481565b34801561021c57600080fd5b506100f4610774565b34801561023157600080fd5b5061012a610240366004610834565b610781565b6100d8610795565b34801561025957600080fd5b506101496102683660046108ce565b600460209081526000928352604080842090915290825290205481565b33600090815260036020526040812080543492906102a4908490610930565b909155505060405134815233907fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c9060200160405180910390a2565b600080546102ed90610943565b80601f016020809104026020016040519081016040528092919081815260200182805461031990610943565b80156103665780601f1061033b57610100808354040283529160200191610366565b820191906000526020600020905b81548152906001019060200180831161034957829003601f168201915b505050505081565b33600081815260046020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925906103d69086815260200190565b60405180910390a35060015b92915050565b73ffffffffffffffffffffffffffffffffffffffff8316600090815260036020526040812054821115610447576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff841633148015906104ad575073ffffffffffffffffffffffffffffffffffffffff841660009081526004602090815260408083203384529091529020546fffffffffffffffffffffffffffffffff14155b156105625773ffffffffffffffffffffffffffffffffffffffff8416600090815260046020908152604080832033845290915290205482111561051c576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff841660009081526004602090815260408083203384529091528120805484929061055c908490610996565b90915550505b73ffffffffffffffffffffffffffffffffffffffff841660009081526003602052604081208054849290610597908490610996565b909155505073ffffffffffffffffffffffffffffffffffffffff8316600090815260036020526040812080548492906105d1908490610930565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161063791815260200190565b60405180910390a35060019392505050565b33600090815260036020526040902054811115610692576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b33600090815260036020526040812080548392906106b1908490610996565b909155505060405133908290600081818185875af1925050503d80600081146106f6576040519150601f19603f3d011682016040523d82523d6000602084013e6106fb565b606091505b50506040518281523391507f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b659060200160405180910390a250565b73ffffffffffffffffffffffffffffffffffffffff82166000908152600360205260408120805483929061076b908490610930565b90915550505050565b600180546102ed90610943565b600061078e3384846103e8565b9392505050565b61079d610285565b565b600060208083528351808285015260005b818110156107cc578581018301518582016040015282016107b0565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461082f57600080fd5b919050565b6000806040838503121561084757600080fd5b6108508361080b565b946020939093013593505050565b60008060006060848603121561087357600080fd5b61087c8461080b565b925061088a6020850161080b565b9150604084013590509250925092565b6000602082840312156108ac57600080fd5b5035919050565b6000602082840312156108c557600080fd5b61078e8261080b565b600080604083850312156108e157600080fd5b6108ea8361080b565b91506108f86020840161080b565b90509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b808201808211156103e2576103e2610901565b600181811c9082168061095757607f821691505b602082108103610990577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b818103818111156103e2576103e261090156fea164736f6c6343000813000a", } var WETH9ABI = WETH9MetaData.ABI 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 41c270d61c..18acf5fc30 100644 --- a/core/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/core/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -1,7 +1,7 @@ GETH_VERSION: 1.13.8 aggregator_v2v3_interface: ../../contracts/solc/v0.8.6/AggregatorV2V3Interface/AggregatorV2V3Interface.abi ../../contracts/solc/v0.8.6/AggregatorV2V3Interface/AggregatorV2V3Interface.bin 95e8814b408bb05bf21742ef580d98698b7db6a9bac6a35c3de12b23aec4ee28 aggregator_v3_interface: ../../contracts/solc/v0.8.6/AggregatorV2V3Interface/AggregatorV3Interface.abi ../../contracts/solc/v0.8.6/AggregatorV2V3Interface/AggregatorV3Interface.bin 351b55d3b0f04af67db6dfb5c92f1c64479400ca1fec77afc20bc0ce65cb49ab -arbitrum_module: ../../contracts/solc/v0.8.19/ArbitrumModule/ArbitrumModule.abi ../../contracts/solc/v0.8.19/ArbitrumModule/ArbitrumModule.bin b76cf77e3e8200c5f292e93af3f620f68f207f83634aacaaee43d682701dfea3 +arbitrum_module: ../../contracts/solc/v0.8.19/ArbitrumModule/ArbitrumModule.abi ../../contracts/solc/v0.8.19/ArbitrumModule/ArbitrumModule.bin 12a7bad1f887d832d101a73ae279a91a90c93fd72befea9983e85eff493f62f4 authorized_forwarder: ../../contracts/solc/v0.8.19/AuthorizedForwarder/AuthorizedForwarder.abi ../../contracts/solc/v0.8.19/AuthorizedForwarder/AuthorizedForwarder.bin 8ea76c883d460f8353a45a493f2aebeb5a2d9a7b4619d1bc4fff5fb590bb3e10 authorized_receiver: ../../contracts/solc/v0.8.19/AuthorizedReceiver/AuthorizedReceiver.abi ../../contracts/solc/v0.8.19/AuthorizedReceiver/AuthorizedReceiver.bin 18e8969ba3234b027e1b16c11a783aca58d0ea5c2361010ec597f134b7bf1c4f automation_compatible_utils: ../../contracts/solc/v0.8.19/AutomationCompatibleUtils/AutomationCompatibleUtils.abi ../../contracts/solc/v0.8.19/AutomationCompatibleUtils/AutomationCompatibleUtils.bin dfe88f4f40d124b8cb5f36a7e9f9328008ca57f7ec5d07a28d949d569d5f2834 @@ -12,10 +12,10 @@ automation_registrar_wrapper2_3: ../../contracts/solc/v0.8.19/AutomationRegistra automation_registry_logic_a_wrapper_2_2: ../../contracts/solc/v0.8.19/AutomationRegistryLogicA2_2/AutomationRegistryLogicA2_2.abi ../../contracts/solc/v0.8.19/AutomationRegistryLogicA2_2/AutomationRegistryLogicA2_2.bin 2f267fb8467a15c587ce4586ac56069f7229344ad3936430d7c7624c0528a171 automation_registry_logic_a_wrapper_2_3: ../../contracts/solc/v0.8.19/AutomationRegistryLogicA2_3/AutomationRegistryLogicA2_3.abi ../../contracts/solc/v0.8.19/AutomationRegistryLogicA2_3/AutomationRegistryLogicA2_3.bin 73b5cc3ece642abbf6f2a4c9188335b71404f4dd0ad10b761390b6397af6f1c8 automation_registry_logic_b_wrapper_2_2: ../../contracts/solc/v0.8.19/AutomationRegistryLogicB2_2/AutomationRegistryLogicB2_2.abi ../../contracts/solc/v0.8.19/AutomationRegistryLogicB2_2/AutomationRegistryLogicB2_2.bin a6d33dfbbfb0ff253eb59a51f4f6d6d4c22ea5ec95aae52d25d49a312b37a22f -automation_registry_logic_b_wrapper_2_3: ../../contracts/solc/v0.8.19/AutomationRegistryLogicB2_3/AutomationRegistryLogicB2_3.abi ../../contracts/solc/v0.8.19/AutomationRegistryLogicB2_3/AutomationRegistryLogicB2_3.bin fbf6f6cf4e6858855ff5da847c3baa4859dd997cfae51f2fa0651e4fa15b92c9 -automation_registry_logic_c_wrapper_2_3: ../../contracts/solc/v0.8.19/AutomationRegistryLogicC2_3/AutomationRegistryLogicC2_3.abi ../../contracts/solc/v0.8.19/AutomationRegistryLogicC2_3/AutomationRegistryLogicC2_3.bin 6bfe0f54fa7a587a83b6981ffdef28b3cb5e24cae1c95becdf59eed21147d289 -automation_registry_wrapper_2_2: ../../contracts/solc/v0.8.19/AutomationRegistry2_2/AutomationRegistry2_2.abi ../../contracts/solc/v0.8.19/AutomationRegistry2_2/AutomationRegistry2_2.bin de60f69878e9b32a291a001c91fc8636544c2cfbd9b507c8c1a4873b602bfb62 -automation_registry_wrapper_2_3: ../../contracts/solc/v0.8.19/AutomationRegistry2_3/AutomationRegistry2_3.abi ../../contracts/solc/v0.8.19/AutomationRegistry2_3/AutomationRegistry2_3.bin f8f920a225fdb1e36948dd95bae3aa46ecc2b01fd113480e111960b5e5f95624 +automation_registry_logic_b_wrapper_2_3: ../../contracts/solc/v0.8.19/AutomationRegistryLogicB2_3/AutomationRegistryLogicB2_3.abi ../../contracts/solc/v0.8.19/AutomationRegistryLogicB2_3/AutomationRegistryLogicB2_3.bin e628b4ba1ca8bf45c2b08c6b80f0b14efbd2dff13b85e5a9ebf643df32335ed2 +automation_registry_logic_c_wrapper_2_3: ../../contracts/solc/v0.8.19/AutomationRegistryLogicC2_3/AutomationRegistryLogicC2_3.abi ../../contracts/solc/v0.8.19/AutomationRegistryLogicC2_3/AutomationRegistryLogicC2_3.bin 19d59318e42f28777756eff60d5c5e52563a2fffb8e3f0f0b07b6d36d82b2c55 +automation_registry_wrapper_2_2: ../../contracts/solc/v0.8.19/AutomationRegistry2_2/AutomationRegistry2_2.abi ../../contracts/solc/v0.8.19/AutomationRegistry2_2/AutomationRegistry2_2.bin 7072ba90159d84572f427ec816e78aa032cf907b39bf228185e0c446842f7c11 +automation_registry_wrapper_2_3: ../../contracts/solc/v0.8.19/AutomationRegistry2_3/AutomationRegistry2_3.abi ../../contracts/solc/v0.8.19/AutomationRegistry2_3/AutomationRegistry2_3.bin b6163402434b84e3b66bc078f6efac121c1e1240dca0e8ea89c43db46b4e308b automation_utils_2_1: ../../contracts/solc/v0.8.16/AutomationUtils2_1/AutomationUtils2_1.abi ../../contracts/solc/v0.8.16/AutomationUtils2_1/AutomationUtils2_1.bin 815b17b63f15d26a0274b962eefad98cdee4ec897ead58688bbb8e2470e585f5 automation_utils_2_2: ../../contracts/solc/v0.8.19/AutomationUtils2_2/AutomationUtils2_2.abi ../../contracts/solc/v0.8.19/AutomationUtils2_2/AutomationUtils2_2.bin 8743f6231aaefa3f2a0b2d484258070d506e2d0860690e66890dccc3949edb2e automation_utils_2_3: ../../contracts/solc/v0.8.19/AutomationUtils2_3/AutomationUtils2_3.abi ../../contracts/solc/v0.8.19/AutomationUtils2_3/AutomationUtils2_3.bin 11e2b481dc9a4d936e3443345d45d2cc571164459d214917b42a8054b295393b @@ -23,8 +23,8 @@ 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 39dfce79330e921e5c169051b11c6e5ea15cd4db5a7b09c06aabbe9658148915 -chain_reader_tester: ../../contracts/solc/v0.8.19/ChainReaderTester/ChainReaderTester.abi ../../contracts/solc/v0.8.19/ChainReaderTester/ChainReaderTester.bin b3718dad488f54de97d124221d96b867c81e11210084a1fad379cb8385d37ffe +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 84c4223c4dbd51aafd77a6787f4b84ce80f661ce86a907c1431c5b82d633f2ad 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 cron_upkeep_factory_wrapper: ../../contracts/solc/v0.8.6/CronUpkeepFactory/CronUpkeepFactory.abi - dacb0f8cdf54ae9d2781c5e720fc314b32ed5e58eddccff512c75d6067292cd7 @@ -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 383611981c86c70522f41b8750719faacc7d7933a22849d5004799ebef3371fa +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 a1f8ee97e12b1b2311db03b94dc52b91f3c2e9a2f8d554031a9c7b41e4432280 +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 8de157cb7e5bc78146548212803d60926c8483aca7e912d802b7c66dc5d2ab11 +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 @@ -82,6 +82,7 @@ vrf_consumer_v2_plus_upgradeable_example: ../../contracts/solc/v0.8.19/VRFConsum vrf_consumer_v2_upgradeable_example: ../../contracts/solc/v0.8.6/VRFConsumerV2UpgradeableExample/VRFConsumerV2UpgradeableExample.abi ../../contracts/solc/v0.8.6/VRFConsumerV2UpgradeableExample/VRFConsumerV2UpgradeableExample.bin f1790a9a2f2a04c730593e483459709cb89e897f8a19d7a3ac0cfe6a97265e6e vrf_coordinator_mock: ../../contracts/solc/v0.8.6/VRFCoordinatorMock/VRFCoordinatorMock.abi ../../contracts/solc/v0.8.6/VRFCoordinatorMock/VRFCoordinatorMock.bin 5c495cf8df1f46d8736b9150cdf174cce358cb8352f60f0d5bb9581e23920501 vrf_coordinator_test_v2: ../../contracts/solc/v0.8.6/VRFCoordinatorTestV2/VRFCoordinatorTestV2.abi ../../contracts/solc/v0.8.6/VRFCoordinatorTestV2/VRFCoordinatorTestV2.bin ff6c0056c6181ea75f667beed21ff4610f417dd50ceabf2dec8fa42e84851f50 +vrf_coordinator_test_v2_5: ../../contracts/solc/v0.8.19/VRFCoordinatorTestV2_5/VRFCoordinatorTestV2_5.abi ../../contracts/solc/v0.8.19/VRFCoordinatorTestV2_5/VRFCoordinatorTestV2_5.bin ab793e7d72b2d10d5c80b5358ca98caf5ff8a8686700735b198ed811272d7910 vrf_coordinator_v2: ../../contracts/solc/v0.8.6/VRFCoordinatorV2/VRFCoordinatorV2.abi ../../contracts/solc/v0.8.6/VRFCoordinatorV2/VRFCoordinatorV2.bin 156fbbc19489383901087c2076648ccd343bcd9a332f1ad25974da834c5be961 vrf_coordinator_v2_5: ../../contracts/solc/v0.8.19/VRFCoordinatorV2_5/VRFCoordinatorV2_5.abi ../../contracts/solc/v0.8.19/VRFCoordinatorV2_5/VRFCoordinatorV2_5.bin 3c766dbdefcc895ad475de96c65b6c48c868b8dc889ee750bba6711b1e5ec41d vrf_coordinator_v2_5_arbitrum: ../../contracts/solc/v0.8.19/VRFCoordinatorV2_5_Arbitrum/VRFCoordinatorV2_5_Arbitrum.abi ../../contracts/solc/v0.8.19/VRFCoordinatorV2_5_Arbitrum/VRFCoordinatorV2_5_Arbitrum.bin 1a2431ee76e307b45f683c439d08b9096a08f08aaf9ca132ea5b36b409962abe @@ -121,4 +122,4 @@ vrfv2plus_wrapper_arbitrum: ../../contracts/solc/v0.8.19/VRFV2PlusWrapper_Arbitr vrfv2plus_wrapper_consumer_example: ../../contracts/solc/v0.8.19/VRFV2PlusWrapperConsumerExample/VRFV2PlusWrapperConsumerExample.abi ../../contracts/solc/v0.8.19/VRFV2PlusWrapperConsumerExample/VRFV2PlusWrapperConsumerExample.bin aeb0c681fa264f90971f65cba1e8d41064948070b217c8204a80ac95e1fa2294 vrfv2plus_wrapper_load_test_consumer: ../../contracts/solc/v0.8.19/VRFV2PlusWrapperLoadTestConsumer/VRFV2PlusWrapperLoadTestConsumer.abi ../../contracts/solc/v0.8.19/VRFV2PlusWrapperLoadTestConsumer/VRFV2PlusWrapperLoadTestConsumer.bin 5ca0223d3f6f6073ddfee4f9ddca13ea5f87297eb5f800359d7a1c41d04b6776 vrfv2plus_wrapper_optimism: ../../contracts/solc/v0.8.19/VRFV2PlusWrapper_Optimism/VRFV2PlusWrapper_Optimism.abi ../../contracts/solc/v0.8.19/VRFV2PlusWrapper_Optimism/VRFV2PlusWrapper_Optimism.bin 12a8c7a96716a5472a8ca712b10ab631085d4f5eb17bd5f7e0d2412556058ce9 -weth9_wrapper: ../../contracts/solc/v0.8.19/WETH9/WETH9.abi ../../contracts/solc/v0.8.19/WETH9/WETH9.bin 7f600a1de0c02a071cb13bcf9eb1dbf11c3e3eccd1e78ed4b4ecb2960f2bb020 +weth9_wrapper: ../../contracts/solc/v0.8.19/WETH9/WETH9.abi ../../contracts/solc/v0.8.19/WETH9/WETH9.bin 393b7b1ea2d1dc5a520a60cc6736dc489726cb0bd1481ea8b22d2872d4a510b1 diff --git a/core/gethwrappers/go_generate.go b/core/gethwrappers/go_generate.go index 9d1a5194e8..1fadd651cc 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 @@ -143,6 +143,7 @@ package gethwrappers //go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.19/VRFCoordinatorV2_5_Optimism/VRFCoordinatorV2_5_Optimism.abi ../../contracts/solc/v0.8.19/VRFCoordinatorV2_5_Optimism/VRFCoordinatorV2_5_Optimism.bin VRFCoordinatorV2_5_Optimism vrf_coordinator_v2_5_optimism //go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.19/VRFV2PlusWrapper_Arbitrum/VRFV2PlusWrapper_Arbitrum.abi ../../contracts/solc/v0.8.19/VRFV2PlusWrapper_Arbitrum/VRFV2PlusWrapper_Arbitrum.bin VRFV2PlusWrapper_Arbitrum vrfv2plus_wrapper_arbitrum //go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.19/VRFV2PlusWrapper_Optimism/VRFV2PlusWrapper_Optimism.abi ../../contracts/solc/v0.8.19/VRFV2PlusWrapper_Optimism/VRFV2PlusWrapper_Optimism.bin VRFV2PlusWrapper_Optimism vrfv2plus_wrapper_optimism +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.19/VRFCoordinatorTestV2_5/VRFCoordinatorTestV2_5.abi ../../contracts/solc/v0.8.19/VRFCoordinatorTestV2_5/VRFCoordinatorTestV2_5.bin VRFCoordinatorTestV2_5 vrf_coordinator_test_v2_5 // Aggregators //go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/AggregatorV2V3Interface/AggregatorV2V3Interface.abi ../../contracts/solc/v0.8.6/AggregatorV2V3Interface/AggregatorV2V3Interface.bin AggregatorV2V3Interface aggregator_v2v3_interface @@ -154,20 +155,14 @@ package gethwrappers // ChainReader test contract //go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.19/ChainReaderTester/ChainReaderTester.abi ../../contracts/solc/v0.8.19/ChainReaderTester/ChainReaderTester.bin ChainReaderTester chain_reader_tester -// Chainlink Functions //go:generate go generate ./functions - -// Chainlink Keystone //go:generate go generate ./keystone - -// Mercury //go:generate go generate ./llo-feeds - -// Operator Forwarder //go:generate go generate ./operatorforwarder - -// Shared //go:generate go generate ./shared +//go:generate go generate ./transmission +//go:generate go generate ./ccip +//go:generate go generate ./liquiditymanager // CCIP //go:generate go generate ./ccip @@ -180,6 +175,3 @@ package gethwrappers // 3. Compile events mock contracts. ./generation/compile_event_mock_contract.sh calls contracts/scripts/native_solc_compile_all_events_mock to compile events mock contracts. // 4. Generate wrappers for events mock contracts. //go:generate ./generation/compile_event_mock_contract.sh - -// Transmission -//go:generate go generate ./transmission diff --git a/core/gethwrappers/keystone/generated/capabilities_registry/capabilities_registry.go b/core/gethwrappers/keystone/generated/capabilities_registry/capabilities_registry.go index c345a86569..d9c7dbe69f 100644 --- a/core/gethwrappers/keystone/generated/capabilities_registry/capabilities_registry.go +++ b/core/gethwrappers/keystone/generated/capabilities_registry/capabilities_registry.go @@ -86,8 +86,8 @@ type CapabilitiesRegistryNodeParams struct { } var CapabilitiesRegistryMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"AccessForbidden\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"hashedCapabilityId\",\"type\":\"bytes32\"}],\"name\":\"CapabilityAlreadyExists\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"hashedCapabilityId\",\"type\":\"bytes32\"}],\"name\":\"CapabilityDoesNotExist\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"hashedCapabilityId\",\"type\":\"bytes32\"}],\"name\":\"CapabilityIsDeprecated\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"hashedCapabilityId\",\"type\":\"bytes32\"},{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"}],\"name\":\"CapabilityRequiredByDON\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"}],\"name\":\"DONDoesNotExist\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"capabilityId\",\"type\":\"bytes32\"}],\"name\":\"DuplicateDONCapability\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"nodeP2PId\",\"type\":\"bytes32\"}],\"name\":\"DuplicateDONNode\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"proposedConfigurationContract\",\"type\":\"address\"}],\"name\":\"InvalidCapabilityConfigurationContractInterface\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"nodeCount\",\"type\":\"uint256\"}],\"name\":\"InvalidFaultTolerance\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32[]\",\"name\":\"hashedCapabilityIds\",\"type\":\"bytes32[]\"}],\"name\":\"InvalidNodeCapabilities\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidNodeOperatorAdmin\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"}],\"name\":\"InvalidNodeP2PId\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidNodeSigner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"lengthOne\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"lengthTwo\",\"type\":\"uint256\"}],\"name\":\"LengthMismatch\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"nodeP2PId\",\"type\":\"bytes32\"}],\"name\":\"NodeAlreadyExists\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"nodeP2PId\",\"type\":\"bytes32\"}],\"name\":\"NodeDoesNotExist\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"nodeP2PId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"capabilityId\",\"type\":\"bytes32\"}],\"name\":\"NodeDoesNotSupportCapability\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"nodeOperatorId\",\"type\":\"uint32\"}],\"name\":\"NodeOperatorDoesNotExist\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"nodeP2PId\",\"type\":\"bytes32\"}],\"name\":\"NodePartOfCapabilitiesDON\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"nodeP2PId\",\"type\":\"bytes32\"}],\"name\":\"NodePartOfWorkflowDON\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"hashedCapabilityId\",\"type\":\"bytes32\"}],\"name\":\"CapabilityConfigured\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"hashedCapabilityId\",\"type\":\"bytes32\"}],\"name\":\"CapabilityDeprecated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"configCount\",\"type\":\"uint32\"}],\"name\":\"ConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"nodeOperatorId\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"signer\",\"type\":\"bytes32\"}],\"name\":\"NodeAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"nodeOperatorId\",\"type\":\"uint32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"admin\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"}],\"name\":\"NodeOperatorAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"nodeOperatorId\",\"type\":\"uint32\"}],\"name\":\"NodeOperatorRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"nodeOperatorId\",\"type\":\"uint32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"admin\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"}],\"name\":\"NodeOperatorUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"}],\"name\":\"NodeRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"nodeOperatorId\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"signer\",\"type\":\"bytes32\"}],\"name\":\"NodeUpdated\",\"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\":[{\"components\":[{\"internalType\":\"string\",\"name\":\"labelledName\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"version\",\"type\":\"string\"},{\"internalType\":\"enumCapabilitiesRegistry.CapabilityType\",\"name\":\"capabilityType\",\"type\":\"uint8\"},{\"internalType\":\"enumCapabilitiesRegistry.CapabilityResponseType\",\"name\":\"responseType\",\"type\":\"uint8\"},{\"internalType\":\"address\",\"name\":\"configurationContract\",\"type\":\"address\"}],\"internalType\":\"structCapabilitiesRegistry.Capability[]\",\"name\":\"capabilities\",\"type\":\"tuple[]\"}],\"name\":\"addCapabilities\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32[]\",\"name\":\"nodes\",\"type\":\"bytes32[]\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"capabilityId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"config\",\"type\":\"bytes\"}],\"internalType\":\"structCapabilitiesRegistry.CapabilityConfiguration[]\",\"name\":\"capabilityConfigurations\",\"type\":\"tuple[]\"},{\"internalType\":\"bool\",\"name\":\"isPublic\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"acceptsWorkflows\",\"type\":\"bool\"},{\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"}],\"name\":\"addDON\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"admin\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"}],\"internalType\":\"structCapabilitiesRegistry.NodeOperator[]\",\"name\":\"nodeOperators\",\"type\":\"tuple[]\"}],\"name\":\"addNodeOperators\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"nodeOperatorId\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"signer\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32[]\",\"name\":\"hashedCapabilityIds\",\"type\":\"bytes32[]\"}],\"internalType\":\"structCapabilitiesRegistry.NodeParams[]\",\"name\":\"nodes\",\"type\":\"tuple[]\"}],\"name\":\"addNodes\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32[]\",\"name\":\"hashedCapabilityIds\",\"type\":\"bytes32[]\"}],\"name\":\"deprecateCapabilities\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCapabilities\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"hashedId\",\"type\":\"bytes32\"},{\"internalType\":\"string\",\"name\":\"labelledName\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"version\",\"type\":\"string\"},{\"internalType\":\"enumCapabilitiesRegistry.CapabilityType\",\"name\":\"capabilityType\",\"type\":\"uint8\"},{\"internalType\":\"enumCapabilitiesRegistry.CapabilityResponseType\",\"name\":\"responseType\",\"type\":\"uint8\"},{\"internalType\":\"address\",\"name\":\"configurationContract\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"isDeprecated\",\"type\":\"bool\"}],\"internalType\":\"structCapabilitiesRegistry.CapabilityInfo[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"hashedId\",\"type\":\"bytes32\"}],\"name\":\"getCapability\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"hashedId\",\"type\":\"bytes32\"},{\"internalType\":\"string\",\"name\":\"labelledName\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"version\",\"type\":\"string\"},{\"internalType\":\"enumCapabilitiesRegistry.CapabilityType\",\"name\":\"capabilityType\",\"type\":\"uint8\"},{\"internalType\":\"enumCapabilitiesRegistry.CapabilityResponseType\",\"name\":\"responseType\",\"type\":\"uint8\"},{\"internalType\":\"address\",\"name\":\"configurationContract\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"isDeprecated\",\"type\":\"bool\"}],\"internalType\":\"structCapabilitiesRegistry.CapabilityInfo\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"capabilityId\",\"type\":\"bytes32\"}],\"name\":\"getCapabilityConfigs\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"}],\"name\":\"getDON\",\"outputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"id\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"configCount\",\"type\":\"uint32\"},{\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"internalType\":\"bool\",\"name\":\"isPublic\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"acceptsWorkflows\",\"type\":\"bool\"},{\"internalType\":\"bytes32[]\",\"name\":\"nodeP2PIds\",\"type\":\"bytes32[]\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"capabilityId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"config\",\"type\":\"bytes\"}],\"internalType\":\"structCapabilitiesRegistry.CapabilityConfiguration[]\",\"name\":\"capabilityConfigurations\",\"type\":\"tuple[]\"}],\"internalType\":\"structCapabilitiesRegistry.DONInfo\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getDONs\",\"outputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"id\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"configCount\",\"type\":\"uint32\"},{\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"internalType\":\"bool\",\"name\":\"isPublic\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"acceptsWorkflows\",\"type\":\"bool\"},{\"internalType\":\"bytes32[]\",\"name\":\"nodeP2PIds\",\"type\":\"bytes32[]\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"capabilityId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"config\",\"type\":\"bytes\"}],\"internalType\":\"structCapabilitiesRegistry.CapabilityConfiguration[]\",\"name\":\"capabilityConfigurations\",\"type\":\"tuple[]\"}],\"internalType\":\"structCapabilitiesRegistry.DONInfo[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"labelledName\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"version\",\"type\":\"string\"}],\"name\":\"getHashedCapabilityId\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"}],\"name\":\"getNode\",\"outputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"nodeOperatorId\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"configCount\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"workflowDONId\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"signer\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32[]\",\"name\":\"hashedCapabilityIds\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint256[]\",\"name\":\"capabilitiesDONIds\",\"type\":\"uint256[]\"}],\"internalType\":\"structCapabilitiesRegistry.NodeInfo\",\"name\":\"nodeInfo\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"nodeOperatorId\",\"type\":\"uint32\"}],\"name\":\"getNodeOperator\",\"outputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"admin\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"}],\"internalType\":\"structCapabilitiesRegistry.NodeOperator\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getNodeOperators\",\"outputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"admin\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"}],\"internalType\":\"structCapabilitiesRegistry.NodeOperator[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getNodes\",\"outputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"nodeOperatorId\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"configCount\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"workflowDONId\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"signer\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32[]\",\"name\":\"hashedCapabilityIds\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint256[]\",\"name\":\"capabilitiesDONIds\",\"type\":\"uint256[]\"}],\"internalType\":\"structCapabilitiesRegistry.NodeInfo[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"hashedCapabilityId\",\"type\":\"bytes32\"}],\"name\":\"isCapabilityDeprecated\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32[]\",\"name\":\"donIds\",\"type\":\"uint32[]\"}],\"name\":\"removeDONs\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32[]\",\"name\":\"nodeOperatorIds\",\"type\":\"uint32[]\"}],\"name\":\"removeNodeOperators\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32[]\",\"name\":\"removedNodeP2PIds\",\"type\":\"bytes32[]\"}],\"name\":\"removeNodes\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"bytes32[]\",\"name\":\"nodes\",\"type\":\"bytes32[]\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"capabilityId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"config\",\"type\":\"bytes\"}],\"internalType\":\"structCapabilitiesRegistry.CapabilityConfiguration[]\",\"name\":\"capabilityConfigurations\",\"type\":\"tuple[]\"},{\"internalType\":\"bool\",\"name\":\"isPublic\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"acceptsWorkflows\",\"type\":\"bool\"},{\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"}],\"name\":\"updateDON\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32[]\",\"name\":\"nodeOperatorIds\",\"type\":\"uint32[]\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"admin\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"}],\"internalType\":\"structCapabilitiesRegistry.NodeOperator[]\",\"name\":\"nodeOperators\",\"type\":\"tuple[]\"}],\"name\":\"updateNodeOperators\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"nodeOperatorId\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"signer\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32[]\",\"name\":\"hashedCapabilityIds\",\"type\":\"bytes32[]\"}],\"internalType\":\"structCapabilitiesRegistry.NodeParams[]\",\"name\":\"nodes\",\"type\":\"tuple[]\"}],\"name\":\"updateNodes\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", - Bin: "0x6080604052600e80546001600160401b0319166401000000011790553480156200002857600080fd5b503380600081620000805760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620000b357620000b381620000bc565b50505062000167565b336001600160a01b03821603620001165760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640162000077565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6150f680620001776000396000f3fe608060405234801561001057600080fd5b50600436106101ae5760003560e01c80635e65e309116100ee5780638da5cb5b11610097578063d8bc7b6811610071578063d8bc7b68146103f6578063ddbe4f8214610409578063e29581aa1461041e578063f2fde38b1461043357600080fd5b80638da5cb5b1461039b5780639cb7c5f4146103c3578063d59a79f6146103e357600080fd5b806373ac22b4116100c857806373ac22b41461036d57806379ba50971461038057806386fa42461461038857600080fd5b80635e65e3091461033257806366acaa3314610345578063715f52951461035a57600080fd5b8063235374051161015b578063398f377311610135578063398f3773146102cb5780633f2a13c9146102de57806350c946fe146102ff5780635d83d9671461031f57600080fd5b80632353740514610285578063275459f2146102a55780632c01a1e8146102b857600080fd5b80631d05394c1161018c5780631d05394c1461023b578063214502431461025057806322bdbcbc1461026557600080fd5b80630fe5800a146101b357806312570011146101d9578063181f5a77146101fc575b600080fd5b6101c66101c1366004613e8b565b610446565b6040519081526020015b60405180910390f35b6101ec6101e7366004613eef565b61047a565b60405190151581526020016101d0565b604080518082018252601a81527f4361706162696c6974696573526567697374727920312e302e30000000000000602082015290516101d09190613f76565b61024e610249366004613fce565b610487565b005b61025861069c565b6040516101d09190614150565b6102786102733660046141eb565b6107f9565b6040516101d09190614243565b6102986102933660046141eb565b6108e6565b6040516101d09190614256565b61024e6102b3366004613fce565b61092a565b61024e6102c6366004613fce565b610a01565b61024e6102d9366004613fce565b610c9d565b6102f16102ec366004614269565b610e5c565b6040516101d0929190614293565b61031261030d366004613eef565b611048565b6040516101d09190614358565b61024e61032d366004613fce565b611122565b61024e610340366004613fce565b611217565b61034d61193f565b6040516101d0919061436b565b61024e610368366004613fce565b611b22565b61024e61037b366004613fce565b611bd4565b61024e6120a2565b61024e6103963660046143e0565b61219f565b60005460405173ffffffffffffffffffffffffffffffffffffffff90911681526020016101d0565b6103d66103d1366004613eef565b6124df565b6040516101d0919061452f565b61024e6103f1366004614561565b61271a565b61024e610404366004614616565b6127e3565b6104116128ad565b6040516101d091906146bb565b6104266129a1565b6040516101d09190614730565b61024e6104413660046147c9565b612aaa565b6000828260405160200161045b929190614293565b6040516020818303038152906040528051906020012090505b92915050565b6000610474600583612abe565b61048f612ad9565b60005b818110156106975760008383838181106104ae576104ae6147e4565b90506020020160208101906104c391906141eb565b63ffffffff8181166000908152600d60209081526040808320805464010000000081049095168085526001820190935290832094955093909290916a010000000000000000000090910460ff16905b61051b83612b5c565b8110156105bb57811561057157600c60006105368584612b66565b8152602081019190915260400160002080547fffffffffffffffffffffffffffffffffffffffff00000000ffffffffffffffff1690556105b3565b6105b18663ffffffff16600c60006105928588612b6690919063ffffffff16565b8152602001908152602001600020600401612b7290919063ffffffff16565b505b600101610512565b508354640100000000900463ffffffff16600003610612576040517f2b62be9b00000000000000000000000000000000000000000000000000000000815263ffffffff861660048201526024015b60405180910390fd5b63ffffffff85166000818152600d6020908152604080832080547fffffffffffffffffffffffffffffffffffffffffff00000000000000000000001690558051938452908301919091527ff264aae70bf6a9d90e68e0f9b393f4e7fbea67b063b0f336e0b36c1581703651910160405180910390a15050505050806001019050610492565b505050565b600e54606090640100000000900463ffffffff1660006106bd600183614842565b63ffffffff1667ffffffffffffffff8111156106db576106db613d25565b60405190808252806020026020018201604052801561076257816020015b6040805160e081018252600080825260208083018290529282018190526060808301829052608083019190915260a0820181905260c082015282527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9092019101816106f95790505b509050600060015b8363ffffffff168163ffffffff1610156107d65763ffffffff8082166000908152600d602052604090205416156107ce576107a481612b7e565b8383815181106107b6576107b66147e4565b6020026020010181905250816107cb9061485f565b91505b60010161076a565b506107e2600184614842565b63ffffffff1681146107f2578082525b5092915050565b60408051808201909152600081526060602082015263ffffffff82166000908152600b60209081526040918290208251808401909352805473ffffffffffffffffffffffffffffffffffffffff168352600181018054919284019161085d90614897565b80601f016020809104026020016040519081016040528092919081815260200182805461088990614897565b80156108d65780601f106108ab576101008083540402835291602001916108d6565b820191906000526020600020905b8154815290600101906020018083116108b957829003601f168201915b5050505050815250509050919050565b6040805160e0810182526000808252602082018190529181018290526060808201839052608082019290925260a0810182905260c081019190915261047482612b7e565b610932612ad9565b60005b63ffffffff811682111561069757600083838363ffffffff1681811061095d5761095d6147e4565b905060200201602081019061097291906141eb565b63ffffffff81166000908152600b6020526040812080547fffffffffffffffffffffffff00000000000000000000000000000000000000001681559192506109bd6001830182613cb8565b505060405163ffffffff8216907fa59268ca81d40429e65ccea5385b59cf2d3fc6519371dee92f8eb1dae5107a7a90600090a2506109fa816148ea565b9050610935565b6000805473ffffffffffffffffffffffffffffffffffffffff163314905b82811015610c97576000848483818110610a3b57610a3b6147e4565b602090810292909201356000818152600c90935260409092206001810154929350919050610a98576040517fd82f6adb00000000000000000000000000000000000000000000000000000000815260048101839052602401610609565b6000610aa682600401612b5c565b1115610afb57610ab96004820184612b66565b6040517f60a6d89800000000000000000000000000000000000000000000000000000000815263ffffffff909116600482015260248101839052604401610609565b805468010000000000000000900463ffffffff1615610b635780546040517f60b9df730000000000000000000000000000000000000000000000000000000081526801000000000000000090910463ffffffff16600482015260248101839052604401610609565b83158015610b9d5750805463ffffffff166000908152600b602052604090205473ffffffffffffffffffffffffffffffffffffffff163314155b15610bd6576040517f9473075d000000000000000000000000000000000000000000000000000000008152336004820152602401610609565b6001810154610be790600790612b72565b506002810154610bf990600990612b72565b506000828152600c6020526040812080547fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016815560018101829055600281018290559060048201818181610c4e8282613cf2565b5050505050507f5254e609a97bab37b7cc79fe128f85c097bd6015c6e1624ae0ba392eb975320582604051610c8591815260200190565b60405180910390a15050600101610a1f565b50505050565b610ca5612ad9565b60005b81811015610697576000838383818110610cc457610cc46147e4565b9050602002810190610cd6919061490d565b610cdf9061494b565b805190915073ffffffffffffffffffffffffffffffffffffffff16610d30576040517feeacd93900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600e54604080518082018252835173ffffffffffffffffffffffffffffffffffffffff908116825260208086015181840190815263ffffffff9095166000818152600b909252939020825181547fffffffffffffffffffffffff00000000000000000000000000000000000000001692169190911781559251919290916001820190610dbc9082614a05565b5050600e8054909150600090610dd79063ffffffff166148ea565b91906101000a81548163ffffffff021916908363ffffffff160217905550816000015173ffffffffffffffffffffffffffffffffffffffff168163ffffffff167f78e94ca80be2c30abc061b99e7eb8583b1254781734b1e3ce339abb57da2fe8e8460200151604051610e4a9190613f76565b60405180910390a35050600101610ca8565b63ffffffff8083166000908152600d60209081526040808320805464010000000090049094168084526001909401825280832085845260030190915281208054606093849390929091610eae90614897565b80601f0160208091040260200160405190810160405280929190818152602001828054610eda90614897565b8015610f275780601f10610efc57610100808354040283529160200191610f27565b820191906000526020600020905b815481529060010190602001808311610f0a57829003601f168201915b5050506000888152600260208190526040909120015492935060609262010000900473ffffffffffffffffffffffffffffffffffffffff1615915061103a905057600086815260026020819052604091829020015490517f8318ed5d00000000000000000000000000000000000000000000000000000000815263ffffffff891660048201526201000090910473ffffffffffffffffffffffffffffffffffffffff1690638318ed5d90602401600060405180830381865afa158015610ff1573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526110379190810190614b1f565b90505b9093509150505b9250929050565b6040805160e0810182526000808252602082018190529181018290526060808201839052608082019290925260a0810182905260c08101919091526040805160e0810182526000848152600c6020908152838220805463ffffffff8082168652640100000000820481168487018190526801000000000000000090920416858701526001820154606086015260028201546080860152835260030190529190912060a08201906110f790612e49565b815260200161111a600c6000868152602001908152602001600020600401612e49565b905292915050565b61112a612ad9565b60005b81811015610697576000838383818110611149576111496147e4565b905060200201359050611166816003612abe90919063ffffffff16565b61119f576040517fe181733f00000000000000000000000000000000000000000000000000000000815260048101829052602401610609565b6111aa600582612e56565b6111e3576040517ff7d7a29400000000000000000000000000000000000000000000000000000000815260048101829052602401610609565b60405181907fdcea1b78b6ddc31592a94607d537543fcaafda6cc52d6d5cc7bbfca1422baf2190600090a25060010161112d565b6000805473ffffffffffffffffffffffffffffffffffffffff163314905b82811015610c97576000848483818110611251576112516147e4565b90506020028101906112639190614b8d565b61126c90614bc1565b6040808201516000908152600c6020908152828220805463ffffffff168352600b82528383208451808601909552805473ffffffffffffffffffffffffffffffffffffffff16855260018101805496975091959394939092840191906112d190614897565b80601f01602080910402602001604051908101604052809291908181526020018280546112fd90614897565b801561134a5780601f1061131f5761010080835404028352916020019161134a565b820191906000526020600020905b81548152906001019060200180831161132d57829003601f168201915b50505091909252505050600183015490915061139a5782604001516040517fd82f6adb00000000000000000000000000000000000000000000000000000000815260040161060991815260200190565b841580156113bf5750805173ffffffffffffffffffffffffffffffffffffffff163314155b156113f8576040517f9473075d000000000000000000000000000000000000000000000000000000008152336004820152602401610609565b6020830151611433576040517f8377314600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001820154602084015181146114b457602084015161145490600790612abe565b1561148b576040517f8377314600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b602084015160018401556114a0600782612b72565b5060208401516114b290600790612e56565b505b606084015180516000036114f657806040517f3748d4c60000000000000000000000000000000000000000000000000000000081526004016106099190614c94565b8354600090859060049061151790640100000000900463ffffffff166148ea565b91906101000a81548163ffffffff021916908363ffffffff1602179055905060005b82518110156115fc5761156f838281518110611557576115576147e4565b60200260200101516003612abe90919063ffffffff16565b6115a757826040517f3748d4c60000000000000000000000000000000000000000000000000000000081526004016106099190614c94565b6115f38382815181106115bc576115bc6147e4565b60200260200101518760030160008563ffffffff1663ffffffff168152602001908152602001600020612e5690919063ffffffff16565b50600101611539565b50845468010000000000000000900463ffffffff16801561175d5763ffffffff8082166000908152600d60209081526040808320805464010000000090049094168352600190930181528282206002018054845181840281018401909552808552929392909183018282801561169157602002820191906000526020600020905b81548152602001906001019080831161167d575b5050505050905060005b815181101561175a576116f08282815181106116b9576116b96147e4565b60200260200101518960030160008763ffffffff1663ffffffff168152602001908152602001600020612abe90919063ffffffff16565b61175257818181518110611706576117066147e4565b6020026020010151836040517f03dcd86200000000000000000000000000000000000000000000000000000000815260040161060992919091825263ffffffff16602082015260400190565b60010161169b565b50505b600061176b87600401612e49565b905060005b81518163ffffffff1610156118b1576000828263ffffffff1681518110611799576117996147e4565b60209081029190910181015163ffffffff8082166000908152600d8452604080822080546401000000009004909316825260019092018452818120600201805483518187028101870190945280845293955090939192909183018282801561182057602002820191906000526020600020905b81548152602001906001019080831161180c575b5050505050905060005b815181101561189d5761187f828281518110611848576118486147e4565b60200260200101518c60030160008a63ffffffff1663ffffffff168152602001908152602001600020612abe90919063ffffffff16565b61189557818181518110611706576117066147e4565b60010161182a565b505050806118aa906148ea565b9050611770565b50875187547fffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000001663ffffffff90911690811788556040808a015160028a018190556020808c01518351928352908201527f4b5b465e22eea0c3d40c30e936643245b80d19b2dcf75788c0699fe8d8db645b910160405180910390a25050505050505050806001019050611235565b600e5460609063ffffffff166000611958600183614842565b63ffffffff1667ffffffffffffffff81111561197657611976613d25565b6040519080825280602002602001820160405280156119bc57816020015b6040805180820190915260008152606060208201528152602001906001900390816119945790505b509050600060015b8363ffffffff168163ffffffff161015611b0c5763ffffffff81166000908152600b602052604090205473ffffffffffffffffffffffffffffffffffffffff1615611b045763ffffffff81166000908152600b60209081526040918290208251808401909352805473ffffffffffffffffffffffffffffffffffffffff1683526001810180549192840191611a5890614897565b80601f0160208091040260200160405190810160405280929190818152602001828054611a8490614897565b8015611ad15780601f10611aa657610100808354040283529160200191611ad1565b820191906000526020600020905b815481529060010190602001808311611ab457829003601f168201915b505050505081525050838381518110611aec57611aec6147e4565b602002602001018190525081611b019061485f565b91505b6001016119c4565b50600e546107e29060019063ffffffff16614842565b611b2a612ad9565b60005b81811015610697576000838383818110611b4957611b496147e4565b9050602002810190611b5b9190614cd8565b611b6490614d1b565b90506000611b7a82600001518360200151610446565b9050611b87600382612e56565b611bc0576040517febf5255100000000000000000000000000000000000000000000000000000000815260048101829052602401610609565b611bca8183612e62565b5050600101611b2d565b6000805473ffffffffffffffffffffffffffffffffffffffff163314905b82811015610c97576000848483818110611c0e57611c0e6147e4565b9050602002810190611c209190614b8d565b611c2990614bc1565b805163ffffffff166000908152600b602090815260408083208151808301909252805473ffffffffffffffffffffffffffffffffffffffff168252600181018054959650939491939092840191611c7f90614897565b80601f0160208091040260200160405190810160405280929190818152602001828054611cab90614897565b8015611cf85780601f10611ccd57610100808354040283529160200191611cf8565b820191906000526020600020905b815481529060010190602001808311611cdb57829003601f168201915b50505091909252505081519192505073ffffffffffffffffffffffffffffffffffffffff16611d5e5781516040517fadd9ae1e00000000000000000000000000000000000000000000000000000000815263ffffffff9091166004820152602401610609565b83158015611d835750805173ffffffffffffffffffffffffffffffffffffffff163314155b15611dbc576040517f9473075d000000000000000000000000000000000000000000000000000000008152336004820152602401610609565b6040808301516000908152600c60205220600181015415611e115782604001516040517f5461848300000000000000000000000000000000000000000000000000000000815260040161060991815260200190565b6040830151611e545782604001516040517f64e2ee9200000000000000000000000000000000000000000000000000000000815260040161060991815260200190565b60208301511580611e7157506020830151611e7190600790612abe565b15611ea8576040517f8377314600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60608301518051600003611eea57806040517f3748d4c60000000000000000000000000000000000000000000000000000000081526004016106099190614c94565b81548290600490611f0890640100000000900463ffffffff166148ea565b82546101009290920a63ffffffff818102199093169183160217909155825464010000000090041660005b8251811015611fde57611f51838281518110611557576115576147e4565b611f8957826040517f3748d4c60000000000000000000000000000000000000000000000000000000081526004016106099190614c94565b611fd5838281518110611f9e57611f9e6147e4565b60200260200101518560030160008563ffffffff1663ffffffff168152602001908152602001600020612e5690919063ffffffff16565b50600101611f33565b50845183547fffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000001663ffffffff918216178455604086015160028501556020860151600185018190556120349160079190612e5616565b50604085015161204690600990612e56565b50845160408087015160208089015183519283529082015263ffffffff909216917f74becb12a5e8fd0e98077d02dfba8f647c9670c9df177e42c2418cf17a636f05910160405180910390a25050505050806001019050611bf2565b60015473ffffffffffffffffffffffffffffffffffffffff163314612123576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e6572000000000000000000006044820152606401610609565b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b8281146121e2576040517fab8b67c60000000000000000000000000000000000000000000000000000000081526004810184905260248101829052604401610609565b6000805473ffffffffffffffffffffffffffffffffffffffff16905b848110156124d757600086868381811061221a5761221a6147e4565b905060200201602081019061222f91906141eb565b63ffffffff81166000908152600b6020526040902080549192509073ffffffffffffffffffffffffffffffffffffffff1661229e576040517fadd9ae1e00000000000000000000000000000000000000000000000000000000815263ffffffff83166004820152602401610609565b60008686858181106122b2576122b26147e4565b90506020028101906122c4919061490d565b6122cd9061494b565b805190915073ffffffffffffffffffffffffffffffffffffffff1661231e576040517feeacd93900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b815473ffffffffffffffffffffffffffffffffffffffff16331480159061235b57503373ffffffffffffffffffffffffffffffffffffffff861614155b15612394576040517f9473075d000000000000000000000000000000000000000000000000000000008152336004820152602401610609565b8051825473ffffffffffffffffffffffffffffffffffffffff908116911614158061241057506020808201516040516123cd9201613f76565b60405160208183030381529060405280519060200120826001016040516020016123f79190614dc1565b6040516020818303038152906040528051906020012014155b156124c957805182547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff9091161782556020810151600183019061246a9082614a05565b50806000015173ffffffffffffffffffffffffffffffffffffffff168363ffffffff167f86f41145bde5dd7f523305452e4aad3685508c181432ec733d5f345009358a2883602001516040516124c09190613f76565b60405180910390a35b5050508060010190506121fe565b505050505050565b6125206040805160e0810182526000808252606060208301819052928201839052909182019081526020016000815260006020820181905260409091015290565b6040805160e0810182528381526000848152600260209081529290208054919283019161254c90614897565b80601f016020809104026020016040519081016040528092919081815260200182805461257890614897565b80156125c55780601f1061259a576101008083540402835291602001916125c5565b820191906000526020600020905b8154815290600101906020018083116125a857829003601f168201915b505050505081526020016002600085815260200190815260200160002060010180546125f090614897565b80601f016020809104026020016040519081016040528092919081815260200182805461261c90614897565b80156126695780601f1061263e57610100808354040283529160200191612669565b820191906000526020600020905b81548152906001019060200180831161264c57829003601f168201915b50505091835250506000848152600260208181526040909220015491019060ff16600381111561269b5761269b61444c565b815260008481526002602081815260409092200154910190610100900460ff1660018111156126cc576126cc61444c565b81526000848152600260208181526040928390209091015462010000900473ffffffffffffffffffffffffffffffffffffffff169083015201612710600585612abe565b1515905292915050565b612722612ad9565b63ffffffff8089166000908152600d6020526040812054640100000000900490911690819003612786576040517f2b62be9b00000000000000000000000000000000000000000000000000000000815263ffffffff8a166004820152602401610609565b6127d8888888886040518060a001604052808f63ffffffff168152602001876127ae906148ea565b97508763ffffffff1681526020018a1515815260200189151581526020018860ff168152506130f6565b505050505050505050565b6127eb612ad9565b600e805460009164010000000090910463ffffffff1690600461280d836148ea565b82546101009290920a63ffffffff81810219909316918316021790915581166000818152600d602090815260409182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000001684179055815160a08101835292835260019083015286151590820152841515606082015260ff841660808201529091506128a39089908990899089906130f6565b5050505050505050565b606060006128bb6003612e49565b90506000815167ffffffffffffffff8111156128d9576128d9613d25565b60405190808252806020026020018201604052801561294b57816020015b6129386040805160e0810182526000808252606060208301819052928201839052909182019081526020016000815260006020820181905260409091015290565b8152602001906001900390816128f75790505b50905060005b82518110156107f25761297c83828151811061296f5761296f6147e4565b60200260200101516124df565b82828151811061298e5761298e6147e4565b6020908102919091010152600101612951565b606060006129af6009612e49565b90506000815167ffffffffffffffff8111156129cd576129cd613d25565b604051908082528060200260200182016040528015612a5457816020015b6040805160e081018252600080825260208083018290529282018190526060808301829052608083019190915260a0820181905260c082015282527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9092019101816129eb5790505b50905060005b82518110156107f257612a85838281518110612a7857612a786147e4565b6020026020010151611048565b828281518110612a9757612a976147e4565b6020908102919091010152600101612a5a565b612ab2612ad9565b612abb8161391a565b50565b600081815260018301602052604081205415155b9392505050565b60005473ffffffffffffffffffffffffffffffffffffffff163314612b5a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610609565b565b6000610474825490565b6000612ad28383613a0f565b6000612ad28383613a39565b6040805160e0810182526000808252602080830182905282840182905260608084018390526080840183905260a0840181905260c084015263ffffffff8581168352600d8252848320805464010000000090049091168084526001909101825284832060028101805487518186028101860190985280885295969295919493909190830182828015612c2f57602002820191906000526020600020905b815481526020019060010190808311612c1b575b505050505090506000815167ffffffffffffffff811115612c5257612c52613d25565b604051908082528060200260200182016040528015612c9857816020015b604080518082019091526000815260606020820152815260200190600190039081612c705790505b50905060005b8151811015612db0576040518060400160405280848381518110612cc457612cc46147e4565b60200260200101518152602001856003016000868581518110612ce957612ce96147e4565b602002602001015181526020019081526020016000208054612d0a90614897565b80601f0160208091040260200160405190810160405280929190818152602001828054612d3690614897565b8015612d835780601f10612d5857610100808354040283529160200191612d83565b820191906000526020600020905b815481529060010190602001808311612d6657829003601f168201915b5050505050815250828281518110612d9d57612d9d6147e4565b6020908102919091010152600101612c9e565b506040805160e08101825263ffffffff8089166000818152600d6020818152868320548086168752948b168187015260ff680100000000000000008604811697870197909752690100000000000000000085048716151560608701529290915290526a010000000000000000000090049091161515608082015260a08101612e3785612e49565b81526020019190915295945050505050565b60606000612ad283613b2c565b6000612ad28383613b88565b608081015173ffffffffffffffffffffffffffffffffffffffff1615612fb057608081015173ffffffffffffffffffffffffffffffffffffffff163b1580612f5b575060808101516040517f01ffc9a70000000000000000000000000000000000000000000000000000000081527f78bea72100000000000000000000000000000000000000000000000000000000600482015273ffffffffffffffffffffffffffffffffffffffff909116906301ffc9a790602401602060405180830381865afa158015612f35573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612f599190614e6f565b155b15612fb05760808101516040517fabb5e3fd00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091166004820152602401610609565b600082815260026020526040902081518291908190612fcf9082614a05565b5060208201516001820190612fe49082614a05565b5060408201516002820180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660018360038111156130265761302661444c565b021790555060608201516002820180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff1661010083600181111561306d5761306d61444c565b0217905550608091909101516002909101805473ffffffffffffffffffffffffffffffffffffffff90921662010000027fffffffffffffffffffff0000000000000000000000000000000000000000ffff90921691909117905560405182907f04f0a9bcf3f3a3b42a4d7ca081119755f82ebe43e0d30c8f7292c4fe0dc4a2ae90600090a25050565b805163ffffffff9081166000908152600d602090815260408083208286015190941683526001909301905220608082015160ff161580613148575060808201518590613143906001614e8c565b60ff16115b156131915760808201516040517f25b4d61800000000000000000000000000000000000000000000000000000000815260ff909116600482015260248101869052604401610609565b6001826020015163ffffffff16111561327957815163ffffffff166000908152600d6020908152604082209084015160019182019183916131d29190614842565b63ffffffff1663ffffffff168152602001908152602001600020905060005b6131fa82612b5c565b81101561327657613229846000015163ffffffff16600c60006105928587600001612b6690919063ffffffff16565b50600c60006132388484612b66565b8152602081019190915260400160002080547fffffffffffffffffffffffffffffffffffffffff00000000ffffffffffffffff1690556001016131f1565b50505b60005b858110156134b3576132a9878783818110613299576132996147e4565b8592602090910201359050612e56565b61330a5782518787838181106132c1576132c16147e4565b6040517f636e405700000000000000000000000000000000000000000000000000000000815263ffffffff90941660048501526020029190910135602483015250604401610609565b82606001511561346157825163ffffffff16600c6000898985818110613332576133326147e4565b602090810292909201358352508101919091526040016000205468010000000000000000900463ffffffff16148015906133ac5750600c600088888481811061337d5761337d6147e4565b602090810292909201358352508101919091526040016000205468010000000000000000900463ffffffff1615155b1561340e5782518787838181106133c5576133c56147e4565b6040517f60b9df7300000000000000000000000000000000000000000000000000000000815263ffffffff90941660048501526020029190910135602483015250604401610609565b8251600c6000898985818110613426576134266147e4565b90506020020135815260200190815260200160002060000160086101000a81548163ffffffff021916908363ffffffff1602179055506134ab565b82516134a99063ffffffff16600c60008a8a86818110613483576134836147e4565b905060200201358152602001908152602001600020600401612e5690919063ffffffff16565b505b60010161327c565b5060005b8381101561378f57368585838181106134d2576134d26147e4565b90506020028101906134e4919061490d565b90506134f260038235612abe565b61352b576040517fe181733f00000000000000000000000000000000000000000000000000000000815281356004820152602401610609565b61353760058235612abe565b15613571576040517ff7d7a29400000000000000000000000000000000000000000000000000000000815281356004820152602401610609565b803560009081526003840160205260408120805461358e90614897565b905011156135da5783516040517f3927d08000000000000000000000000000000000000000000000000000000000815263ffffffff909116600482015281356024820152604401610609565b60005b878110156136e4576136818235600c60008c8c86818110613600576136006147e4565b9050602002013581526020019081526020016000206003016000600c60008e8e88818110613630576136306147e4565b90506020020135815260200190815260200160002060000160049054906101000a900463ffffffff1663ffffffff1663ffffffff168152602001908152602001600020612abe90919063ffffffff16565b6136dc57888882818110613697576136976147e4565b6040517fa7e792500000000000000000000000000000000000000000000000000000000081526020909102929092013560048301525082356024820152604401610609565b6001016135dd565b506002830180546001810182556000918252602091829020833591015561370d90820182614ea5565b8235600090815260038601602052604090209161372b919083614f0a565b50835160208086015161378692918435908c908c9061374c90880188614ea5565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250613bd792505050565b506001016134b7565b50604080830151835163ffffffff9081166000908152600d602090815284822080549415156901000000000000000000027fffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffff90951694909417909355606086015186518316825284822080549115156a0100000000000000000000027fffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffff9092169190911790556080860151865183168252848220805460ff9290921668010000000000000000027fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff909216919091179055918501805186518316845292849020805493909216640100000000027fffffffffffffffffffffffffffffffffffffffffffffffff00000000ffffffff9093169290921790558351905191517ff264aae70bf6a9d90e68e0f9b393f4e7fbea67b063b0f336e0b36c15817036519261390a929163ffffffff92831681529116602082015260400190565b60405180910390a1505050505050565b3373ffffffffffffffffffffffffffffffffffffffff821603613999576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610609565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6000826000018281548110613a2657613a266147e4565b9060005260206000200154905092915050565b60008181526001830160205260408120548015613b22576000613a5d600183615025565b8554909150600090613a7190600190615025565b9050818114613ad6576000866000018281548110613a9157613a916147e4565b9060005260206000200154905080876000018481548110613ab457613ab46147e4565b6000918252602080832090910192909255918252600188019052604090208390555b8554869080613ae757613ae7615038565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050610474565b6000915050610474565b606081600001805480602002602001604051908101604052809291908181526020018280548015613b7c57602002820191906000526020600020905b815481526020019060010190808311613b68575b50505050509050919050565b6000818152600183016020526040812054613bcf57508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610474565b506000610474565b6000848152600260208190526040909120015462010000900473ffffffffffffffffffffffffffffffffffffffff16156124d757600084815260026020819052604091829020015490517ffba64a7c0000000000000000000000000000000000000000000000000000000081526201000090910473ffffffffffffffffffffffffffffffffffffffff169063fba64a7c90613c7e908690869086908b908d90600401615067565b600060405180830381600087803b158015613c9857600080fd5b505af1158015613cac573d6000803e3d6000fd5b50505050505050505050565b508054613cc490614897565b6000825580601f10613cd4575050565b601f016020900490600052602060002090810190612abb9190613d0c565b5080546000825590600052602060002090810190612abb91905b5b80821115613d215760008155600101613d0d565b5090565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040516080810167ffffffffffffffff81118282101715613d7757613d77613d25565b60405290565b60405160a0810167ffffffffffffffff81118282101715613d7757613d77613d25565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715613de757613de7613d25565b604052919050565b600067ffffffffffffffff821115613e0957613e09613d25565b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b600082601f830112613e4657600080fd5b8135613e59613e5482613def565b613da0565b818152846020838601011115613e6e57600080fd5b816020850160208301376000918101602001919091529392505050565b60008060408385031215613e9e57600080fd5b823567ffffffffffffffff80821115613eb657600080fd5b613ec286838701613e35565b93506020850135915080821115613ed857600080fd5b50613ee585828601613e35565b9150509250929050565b600060208284031215613f0157600080fd5b5035919050565b60005b83811015613f23578181015183820152602001613f0b565b50506000910152565b60008151808452613f44816020860160208601613f08565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b602081526000612ad26020830184613f2c565b60008083601f840112613f9b57600080fd5b50813567ffffffffffffffff811115613fb357600080fd5b6020830191508360208260051b850101111561104157600080fd5b60008060208385031215613fe157600080fd5b823567ffffffffffffffff811115613ff857600080fd5b61400485828601613f89565b90969095509350505050565b60008151808452602080850194506020840160005b8381101561404157815187529582019590820190600101614025565b509495945050505050565b600082825180855260208086019550808260051b84010181860160005b848110156140c9578583037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001895281518051845284015160408585018190526140b581860183613f2c565b9a86019a9450505090830190600101614069565b5090979650505050505050565b600063ffffffff8083511684528060208401511660208501525060ff604083015116604084015260608201511515606084015260808201511515608084015260a082015160e060a085015261412e60e0850182614010565b905060c083015184820360c0860152614147828261404c565b95945050505050565b600060208083016020845280855180835260408601915060408160051b87010192506020870160005b828110156141c5577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc08886030184526141b38583516140d6565b94509285019290850190600101614179565b5092979650505050505050565b803563ffffffff811681146141e657600080fd5b919050565b6000602082840312156141fd57600080fd5b612ad2826141d2565b73ffffffffffffffffffffffffffffffffffffffff8151168252600060208201516040602085015261423b6040850182613f2c565b949350505050565b602081526000612ad26020830184614206565b602081526000612ad260208301846140d6565b6000806040838503121561427c57600080fd5b614285836141d2565b946020939093013593505050565b6040815260006142a66040830185613f2c565b82810360208401526141478185613f2c565b600063ffffffff808351168452602081818501511681860152816040850151166040860152606084015160608601526080840151608086015260a0840151915060e060a086015261430c60e0860183614010565b60c08581015187830391880191909152805180835290830193506000918301905b8083101561434d578451825293830193600192909201919083019061432d565b509695505050505050565b602081526000612ad260208301846142b8565b600060208083016020845280855180835260408601915060408160051b87010192506020870160005b828110156141c5577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc08886030184526143ce858351614206565b94509285019290850190600101614394565b600080600080604085870312156143f657600080fd5b843567ffffffffffffffff8082111561440e57600080fd5b61441a88838901613f89565b9096509450602087013591508082111561443357600080fd5b5061444087828801613f89565b95989497509550505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b805182526000602082015160e0602085015261449a60e0850182613f2c565b9050604083015184820360408601526144b38282613f2c565b9150506060830151600481106144cb576144cb61444c565b60608501526080830151600281106144e5576144e561444c565b8060808601525060a083015161451360a086018273ffffffffffffffffffffffffffffffffffffffff169052565b5060c083015161452760c086018215159052565b509392505050565b602081526000612ad2602083018461447b565b8015158114612abb57600080fd5b803560ff811681146141e657600080fd5b60008060008060008060008060c0898b03121561457d57600080fd5b614586896141d2565b9750602089013567ffffffffffffffff808211156145a357600080fd5b6145af8c838d01613f89565b909950975060408b01359150808211156145c857600080fd5b506145d58b828c01613f89565b90965094505060608901356145e981614542565b925060808901356145f981614542565b915061460760a08a01614550565b90509295985092959890939650565b600080600080600080600060a0888a03121561463157600080fd5b873567ffffffffffffffff8082111561464957600080fd5b6146558b838c01613f89565b909950975060208a013591508082111561466e57600080fd5b5061467b8a828b01613f89565b909650945050604088013561468f81614542565b9250606088013561469f81614542565b91506146ad60808901614550565b905092959891949750929550565b600060208083016020845280855180835260408601915060408160051b87010192506020870160005b828110156141c5577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc088860301845261471e85835161447b565b945092850192908501906001016146e4565b600060208083016020845280855180835260408601915060408160051b87010192506020870160005b828110156141c5577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc08886030184526147938583516142b8565b94509285019290850190600101614759565b803573ffffffffffffffffffffffffffffffffffffffff811681146141e657600080fd5b6000602082840312156147db57600080fd5b612ad2826147a5565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b63ffffffff8281168282160390808211156107f2576107f2614813565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361489057614890614813565b5060010190565b600181811c908216806148ab57607f821691505b6020821081036148e4577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b600063ffffffff80831681810361490357614903614813565b6001019392505050565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc183360301811261494157600080fd5b9190910192915050565b60006040823603121561495d57600080fd5b6040516040810167ffffffffffffffff828210818311171561498157614981613d25565b8160405261498e856147a5565b835260208501359150808211156149a457600080fd5b506149b136828601613e35565b60208301525092915050565b601f821115610697576000816000526020600020601f850160051c810160208610156149e65750805b601f850160051c820191505b818110156124d7578281556001016149f2565b815167ffffffffffffffff811115614a1f57614a1f613d25565b614a3381614a2d8454614897565b846149bd565b602080601f831160018114614a865760008415614a505750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b1785556124d7565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b82811015614ad357888601518255948401946001909101908401614ab4565b5085821015614b0f57878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b600060208284031215614b3157600080fd5b815167ffffffffffffffff811115614b4857600080fd5b8201601f81018413614b5957600080fd5b8051614b67613e5482613def565b818152856020838501011115614b7c57600080fd5b614147826020830160208601613f08565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8183360301811261494157600080fd5b600060808236031215614bd357600080fd5b614bdb613d54565b614be4836141d2565b81526020808401358183015260408401356040830152606084013567ffffffffffffffff80821115614c1557600080fd5b9085019036601f830112614c2857600080fd5b813581811115614c3a57614c3a613d25565b8060051b9150614c4b848301613da0565b8181529183018401918481019036841115614c6557600080fd5b938501935b83851015614c8357843582529385019390850190614c6a565b606087015250939695505050505050565b6020808252825182820181905260009190848201906040850190845b81811015614ccc57835183529284019291840191600101614cb0565b50909695505050505050565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6183360301811261494157600080fd5b8035600281106141e657600080fd5b600060a08236031215614d2d57600080fd5b614d35613d7d565b823567ffffffffffffffff80821115614d4d57600080fd5b614d5936838701613e35565b83526020850135915080821115614d6f57600080fd5b50614d7c36828601613e35565b602083015250604083013560048110614d9457600080fd5b6040820152614da560608401614d0c565b6060820152614db6608084016147a5565b608082015292915050565b6000602080835260008454614dd581614897565b8060208701526040600180841660008114614df75760018114614e3157614e61565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00851660408a0152604084151560051b8a01019550614e61565b89600052602060002060005b85811015614e585781548b8201860152908301908801614e3d565b8a016040019650505b509398975050505050505050565b600060208284031215614e8157600080fd5b8151612ad281614542565b60ff818116838216019081111561047457610474614813565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112614eda57600080fd5b83018035915067ffffffffffffffff821115614ef557600080fd5b60200191503681900382131561104157600080fd5b67ffffffffffffffff831115614f2257614f22613d25565b614f3683614f308354614897565b836149bd565b6000601f841160018114614f885760008515614f525750838201355b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600387901b1c1916600186901b17835561501e565b6000838152602090207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0861690835b82811015614fd75786850135825560209485019460019092019101614fb7565b5086821015615012577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88860031b161c19848701351681555b505060018560011b0183555b5050505050565b8181038181111561047457610474614813565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b6080815284608082015260007f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8611156150a057600080fd5b8560051b808860a0850137820182810360a090810160208501526150c690820187613f2c565b91505063ffffffff8085166040840152808416606084015250969550505050505056fea164736f6c6343000818000a", + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"AccessForbidden\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"hashedCapabilityId\",\"type\":\"bytes32\"}],\"name\":\"CapabilityAlreadyExists\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"hashedCapabilityId\",\"type\":\"bytes32\"}],\"name\":\"CapabilityDoesNotExist\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"hashedCapabilityId\",\"type\":\"bytes32\"}],\"name\":\"CapabilityIsDeprecated\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"hashedCapabilityId\",\"type\":\"bytes32\"},{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"}],\"name\":\"CapabilityRequiredByDON\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"}],\"name\":\"DONDoesNotExist\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"capabilityId\",\"type\":\"bytes32\"}],\"name\":\"DuplicateDONCapability\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"nodeP2PId\",\"type\":\"bytes32\"}],\"name\":\"DuplicateDONNode\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"proposedConfigurationContract\",\"type\":\"address\"}],\"name\":\"InvalidCapabilityConfigurationContractInterface\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"nodeCount\",\"type\":\"uint256\"}],\"name\":\"InvalidFaultTolerance\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32[]\",\"name\":\"hashedCapabilityIds\",\"type\":\"bytes32[]\"}],\"name\":\"InvalidNodeCapabilities\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidNodeOperatorAdmin\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"}],\"name\":\"InvalidNodeP2PId\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidNodeSigner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"lengthOne\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"lengthTwo\",\"type\":\"uint256\"}],\"name\":\"LengthMismatch\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"nodeP2PId\",\"type\":\"bytes32\"}],\"name\":\"NodeAlreadyExists\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"nodeP2PId\",\"type\":\"bytes32\"}],\"name\":\"NodeDoesNotExist\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"nodeP2PId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"capabilityId\",\"type\":\"bytes32\"}],\"name\":\"NodeDoesNotSupportCapability\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"nodeOperatorId\",\"type\":\"uint32\"}],\"name\":\"NodeOperatorDoesNotExist\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"nodeP2PId\",\"type\":\"bytes32\"}],\"name\":\"NodePartOfCapabilitiesDON\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"nodeP2PId\",\"type\":\"bytes32\"}],\"name\":\"NodePartOfWorkflowDON\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"hashedCapabilityId\",\"type\":\"bytes32\"}],\"name\":\"CapabilityConfigured\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"hashedCapabilityId\",\"type\":\"bytes32\"}],\"name\":\"CapabilityDeprecated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"configCount\",\"type\":\"uint32\"}],\"name\":\"ConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"nodeOperatorId\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"signer\",\"type\":\"bytes32\"}],\"name\":\"NodeAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"nodeOperatorId\",\"type\":\"uint32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"admin\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"}],\"name\":\"NodeOperatorAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"nodeOperatorId\",\"type\":\"uint32\"}],\"name\":\"NodeOperatorRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"nodeOperatorId\",\"type\":\"uint32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"admin\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"}],\"name\":\"NodeOperatorUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"}],\"name\":\"NodeRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"nodeOperatorId\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"signer\",\"type\":\"bytes32\"}],\"name\":\"NodeUpdated\",\"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\":[{\"components\":[{\"internalType\":\"string\",\"name\":\"labelledName\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"version\",\"type\":\"string\"},{\"internalType\":\"enumCapabilitiesRegistry.CapabilityType\",\"name\":\"capabilityType\",\"type\":\"uint8\"},{\"internalType\":\"enumCapabilitiesRegistry.CapabilityResponseType\",\"name\":\"responseType\",\"type\":\"uint8\"},{\"internalType\":\"address\",\"name\":\"configurationContract\",\"type\":\"address\"}],\"internalType\":\"structCapabilitiesRegistry.Capability[]\",\"name\":\"capabilities\",\"type\":\"tuple[]\"}],\"name\":\"addCapabilities\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32[]\",\"name\":\"nodes\",\"type\":\"bytes32[]\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"capabilityId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"config\",\"type\":\"bytes\"}],\"internalType\":\"structCapabilitiesRegistry.CapabilityConfiguration[]\",\"name\":\"capabilityConfigurations\",\"type\":\"tuple[]\"},{\"internalType\":\"bool\",\"name\":\"isPublic\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"acceptsWorkflows\",\"type\":\"bool\"},{\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"}],\"name\":\"addDON\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"admin\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"}],\"internalType\":\"structCapabilitiesRegistry.NodeOperator[]\",\"name\":\"nodeOperators\",\"type\":\"tuple[]\"}],\"name\":\"addNodeOperators\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"nodeOperatorId\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"signer\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32[]\",\"name\":\"hashedCapabilityIds\",\"type\":\"bytes32[]\"}],\"internalType\":\"structCapabilitiesRegistry.NodeParams[]\",\"name\":\"nodes\",\"type\":\"tuple[]\"}],\"name\":\"addNodes\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32[]\",\"name\":\"hashedCapabilityIds\",\"type\":\"bytes32[]\"}],\"name\":\"deprecateCapabilities\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCapabilities\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"hashedId\",\"type\":\"bytes32\"},{\"internalType\":\"string\",\"name\":\"labelledName\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"version\",\"type\":\"string\"},{\"internalType\":\"enumCapabilitiesRegistry.CapabilityType\",\"name\":\"capabilityType\",\"type\":\"uint8\"},{\"internalType\":\"enumCapabilitiesRegistry.CapabilityResponseType\",\"name\":\"responseType\",\"type\":\"uint8\"},{\"internalType\":\"address\",\"name\":\"configurationContract\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"isDeprecated\",\"type\":\"bool\"}],\"internalType\":\"structCapabilitiesRegistry.CapabilityInfo[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"hashedId\",\"type\":\"bytes32\"}],\"name\":\"getCapability\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"hashedId\",\"type\":\"bytes32\"},{\"internalType\":\"string\",\"name\":\"labelledName\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"version\",\"type\":\"string\"},{\"internalType\":\"enumCapabilitiesRegistry.CapabilityType\",\"name\":\"capabilityType\",\"type\":\"uint8\"},{\"internalType\":\"enumCapabilitiesRegistry.CapabilityResponseType\",\"name\":\"responseType\",\"type\":\"uint8\"},{\"internalType\":\"address\",\"name\":\"configurationContract\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"isDeprecated\",\"type\":\"bool\"}],\"internalType\":\"structCapabilitiesRegistry.CapabilityInfo\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"capabilityId\",\"type\":\"bytes32\"}],\"name\":\"getCapabilityConfigs\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"}],\"name\":\"getDON\",\"outputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"id\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"configCount\",\"type\":\"uint32\"},{\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"internalType\":\"bool\",\"name\":\"isPublic\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"acceptsWorkflows\",\"type\":\"bool\"},{\"internalType\":\"bytes32[]\",\"name\":\"nodeP2PIds\",\"type\":\"bytes32[]\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"capabilityId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"config\",\"type\":\"bytes\"}],\"internalType\":\"structCapabilitiesRegistry.CapabilityConfiguration[]\",\"name\":\"capabilityConfigurations\",\"type\":\"tuple[]\"}],\"internalType\":\"structCapabilitiesRegistry.DONInfo\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getDONs\",\"outputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"id\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"configCount\",\"type\":\"uint32\"},{\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"internalType\":\"bool\",\"name\":\"isPublic\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"acceptsWorkflows\",\"type\":\"bool\"},{\"internalType\":\"bytes32[]\",\"name\":\"nodeP2PIds\",\"type\":\"bytes32[]\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"capabilityId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"config\",\"type\":\"bytes\"}],\"internalType\":\"structCapabilitiesRegistry.CapabilityConfiguration[]\",\"name\":\"capabilityConfigurations\",\"type\":\"tuple[]\"}],\"internalType\":\"structCapabilitiesRegistry.DONInfo[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"labelledName\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"version\",\"type\":\"string\"}],\"name\":\"getHashedCapabilityId\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"}],\"name\":\"getNode\",\"outputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"nodeOperatorId\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"configCount\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"workflowDONId\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"signer\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32[]\",\"name\":\"hashedCapabilityIds\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint256[]\",\"name\":\"capabilitiesDONIds\",\"type\":\"uint256[]\"}],\"internalType\":\"structCapabilitiesRegistry.NodeInfo\",\"name\":\"nodeInfo\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"nodeOperatorId\",\"type\":\"uint32\"}],\"name\":\"getNodeOperator\",\"outputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"admin\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"}],\"internalType\":\"structCapabilitiesRegistry.NodeOperator\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getNodeOperators\",\"outputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"admin\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"}],\"internalType\":\"structCapabilitiesRegistry.NodeOperator[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getNodes\",\"outputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"nodeOperatorId\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"configCount\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"workflowDONId\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"signer\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32[]\",\"name\":\"hashedCapabilityIds\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint256[]\",\"name\":\"capabilitiesDONIds\",\"type\":\"uint256[]\"}],\"internalType\":\"structCapabilitiesRegistry.NodeInfo[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"hashedCapabilityId\",\"type\":\"bytes32\"}],\"name\":\"isCapabilityDeprecated\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32[]\",\"name\":\"donIds\",\"type\":\"uint32[]\"}],\"name\":\"removeDONs\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32[]\",\"name\":\"nodeOperatorIds\",\"type\":\"uint32[]\"}],\"name\":\"removeNodeOperators\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32[]\",\"name\":\"removedNodeP2PIds\",\"type\":\"bytes32[]\"}],\"name\":\"removeNodes\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"bytes32[]\",\"name\":\"nodes\",\"type\":\"bytes32[]\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"capabilityId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"config\",\"type\":\"bytes\"}],\"internalType\":\"structCapabilitiesRegistry.CapabilityConfiguration[]\",\"name\":\"capabilityConfigurations\",\"type\":\"tuple[]\"},{\"internalType\":\"bool\",\"name\":\"isPublic\",\"type\":\"bool\"},{\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"}],\"name\":\"updateDON\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32[]\",\"name\":\"nodeOperatorIds\",\"type\":\"uint32[]\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"admin\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"}],\"internalType\":\"structCapabilitiesRegistry.NodeOperator[]\",\"name\":\"nodeOperators\",\"type\":\"tuple[]\"}],\"name\":\"updateNodeOperators\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"nodeOperatorId\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"signer\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"p2pId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32[]\",\"name\":\"hashedCapabilityIds\",\"type\":\"bytes32[]\"}],\"internalType\":\"structCapabilitiesRegistry.NodeParams[]\",\"name\":\"nodes\",\"type\":\"tuple[]\"}],\"name\":\"updateNodes\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + Bin: "0x6080604052600e80546001600160401b0319166401000000011790553480156200002857600080fd5b503380600081620000805760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620000b357620000b381620000bc565b50505062000167565b336001600160a01b03821603620001165760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640162000077565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b61514e80620001776000396000f3fe608060405234801561001057600080fd5b50600436106101ae5760003560e01c80635d83d967116100ee57806386fa424611610097578063d8bc7b6811610071578063d8bc7b68146103f6578063ddbe4f8214610409578063e29581aa1461041e578063f2fde38b1461043357600080fd5b806386fa42461461039b5780638da5cb5b146103ae5780639cb7c5f4146103d657600080fd5b8063715f5295116100c8578063715f52951461036d57806373ac22b41461038057806379ba50971461039357600080fd5b80635d83d967146103325780635e65e3091461034557806366acaa331461035857600080fd5b8063235374051161015b5780632c01a1e8116101355780632c01a1e8146102cb578063398f3773146102de5780633f2a13c9146102f157806350c946fe1461031257600080fd5b80632353740514610285578063275459f2146102a55780632a852933146102b857600080fd5b80631d05394c1161018c5780631d05394c1461023b578063214502431461025057806322bdbcbc1461026557600080fd5b80630fe5800a146101b357806312570011146101d9578063181f5a77146101fc575b600080fd5b6101c66101c1366004613f30565b610446565b6040519081526020015b60405180910390f35b6101ec6101e7366004613f94565b61047a565b60405190151581526020016101d0565b604080518082018252601a81527f4361706162696c6974696573526567697374727920312e302e30000000000000602082015290516101d0919061401b565b61024e610249366004614073565b610487565b005b610258610694565b6040516101d091906141f5565b610278610273366004614290565b6107f1565b6040516101d091906142e8565b610298610293366004614290565b6108de565b6040516101d091906142fb565b61024e6102b3366004614073565b610922565b61024e6102c636600461432f565b6109f9565b61024e6102d9366004614073565b610ad9565b61024e6102ec366004614073565b610d75565b6103046102ff3660046143d1565b610f34565b6040516101d09291906143fb565b610325610320366004613f94565b611120565b6040516101d091906144c0565b61024e610340366004614073565b6111fa565b61024e610353366004614073565b6112ef565b610360611a17565b6040516101d091906144d3565b61024e61037b366004614073565b611bfa565b61024e61038e366004614073565b611cac565b61024e61217a565b61024e6103a9366004614548565b612277565b60005460405173ffffffffffffffffffffffffffffffffffffffff90911681526020016101d0565b6103e96103e4366004613f94565b6125b7565b6040516101d09190614697565b61024e6104043660046146aa565b6127f2565b6104116128bc565b6040516101d09190614730565b6104266129b0565b6040516101d091906147a5565b61024e61044136600461483e565b612ab9565b6000828260405160200161045b9291906143fb565b6040516020818303038152906040528051906020012090505b92915050565b6000610474600583612acd565b61048f612ae8565b60005b8181101561068f5760008383838181106104ae576104ae614859565b90506020020160208101906104c39190614290565b63ffffffff8181166000908152600d60209081526040808320805464010000000081049095168085526001820190935290832094955093909290916a010000000000000000000090910460ff16905b61051b83612b6b565b8110156105bb57811561057157600c60006105368584612b75565b8152602081019190915260400160002080547fffffffffffffffffffffffffffffffffffffffff00000000ffffffffffffffff1690556105b3565b6105b18663ffffffff16600c60006105928588612b7590919063ffffffff16565b8152602001908152602001600020600401612b8190919063ffffffff16565b505b600101610512565b508354640100000000900463ffffffff16600003610612576040517f2b62be9b00000000000000000000000000000000000000000000000000000000815263ffffffff861660048201526024015b60405180910390fd5b63ffffffff85166000818152600d6020908152604080832080547fffffffffffffffffffffffffffffffffffffffffff0000000000000000000000169055519182527ff264aae70bf6a9d90e68e0f9b393f4e7fbea67b063b0f336e0b36c1581703651910160405180910390a25050505050806001019050610492565b505050565b600e54606090640100000000900463ffffffff1660006106b56001836148b7565b63ffffffff1667ffffffffffffffff8111156106d3576106d3613dca565b60405190808252806020026020018201604052801561075a57816020015b6040805160e081018252600080825260208083018290529282018190526060808301829052608083019190915260a0820181905260c082015282527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9092019101816106f15790505b509050600060015b8363ffffffff168163ffffffff1610156107ce5763ffffffff8082166000908152600d602052604090205416156107c65761079c81612b8d565b8383815181106107ae576107ae614859565b6020026020010181905250816107c3906148d4565b91505b600101610762565b506107da6001846148b7565b63ffffffff1681146107ea578082525b5092915050565b60408051808201909152600081526060602082015263ffffffff82166000908152600b60209081526040918290208251808401909352805473ffffffffffffffffffffffffffffffffffffffff16835260018101805491928401916108559061490c565b80601f01602080910402602001604051908101604052809291908181526020018280546108819061490c565b80156108ce5780601f106108a3576101008083540402835291602001916108ce565b820191906000526020600020905b8154815290600101906020018083116108b157829003601f168201915b5050505050815250509050919050565b6040805160e0810182526000808252602082018190529181018290526060808201839052608082019290925260a0810182905260c081019190915261047482612b8d565b61092a612ae8565b60005b63ffffffff811682111561068f57600083838363ffffffff1681811061095557610955614859565b905060200201602081019061096a9190614290565b63ffffffff81166000908152600b6020526040812080547fffffffffffffffffffffffff00000000000000000000000000000000000000001681559192506109b56001830182613d5d565b505060405163ffffffff8216907fa59268ca81d40429e65ccea5385b59cf2d3fc6519371dee92f8eb1dae5107a7a90600090a2506109f28161495f565b905061092d565b610a01612ae8565b63ffffffff8088166000908152600d60205260408120805490926401000000009091041690819003610a67576040517f2b62be9b00000000000000000000000000000000000000000000000000000000815263ffffffff8a166004820152602401610609565b610ace888888886040518060a001604052808f63ffffffff16815260200187610a8f9061495f565b63ffffffff811682528b15156020830152895460ff6a01000000000000000000009091048116151560408401528b166060909201919091529650612e58565b505050505050505050565b6000805473ffffffffffffffffffffffffffffffffffffffff163314905b82811015610d6f576000848483818110610b1357610b13614859565b602090810292909201356000818152600c90935260409092206001810154929350919050610b70576040517fd82f6adb00000000000000000000000000000000000000000000000000000000815260048101839052602401610609565b6000610b7e82600401612b6b565b1115610bd357610b916004820184612b75565b6040517f60a6d89800000000000000000000000000000000000000000000000000000000815263ffffffff909116600482015260248101839052604401610609565b805468010000000000000000900463ffffffff1615610c3b5780546040517f60b9df730000000000000000000000000000000000000000000000000000000081526801000000000000000090910463ffffffff16600482015260248101839052604401610609565b83158015610c755750805463ffffffff166000908152600b602052604090205473ffffffffffffffffffffffffffffffffffffffff163314155b15610cae576040517f9473075d000000000000000000000000000000000000000000000000000000008152336004820152602401610609565b6001810154610cbf90600790612b81565b506002810154610cd190600990612b81565b506000828152600c6020526040812080547fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016815560018101829055600281018290559060048201818181610d268282613d97565b5050505050507f5254e609a97bab37b7cc79fe128f85c097bd6015c6e1624ae0ba392eb975320582604051610d5d91815260200190565b60405180910390a15050600101610af7565b50505050565b610d7d612ae8565b60005b8181101561068f576000838383818110610d9c57610d9c614859565b9050602002810190610dae9190614982565b610db7906149c0565b805190915073ffffffffffffffffffffffffffffffffffffffff16610e08576040517feeacd93900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600e54604080518082018252835173ffffffffffffffffffffffffffffffffffffffff908116825260208086015181840190815263ffffffff9095166000818152600b909252939020825181547fffffffffffffffffffffffff00000000000000000000000000000000000000001692169190911781559251919290916001820190610e949082614a7a565b5050600e8054909150600090610eaf9063ffffffff1661495f565b91906101000a81548163ffffffff021916908363ffffffff160217905550816000015173ffffffffffffffffffffffffffffffffffffffff168163ffffffff167f78e94ca80be2c30abc061b99e7eb8583b1254781734b1e3ce339abb57da2fe8e8460200151604051610f22919061401b565b60405180910390a35050600101610d80565b63ffffffff8083166000908152600d60209081526040808320805464010000000090049094168084526001909401825280832085845260030190915281208054606093849390929091610f869061490c565b80601f0160208091040260200160405190810160405280929190818152602001828054610fb29061490c565b8015610fff5780601f10610fd457610100808354040283529160200191610fff565b820191906000526020600020905b815481529060010190602001808311610fe257829003601f168201915b5050506000888152600260208190526040909120015492935060609262010000900473ffffffffffffffffffffffffffffffffffffffff16159150611112905057600086815260026020819052604091829020015490517f8318ed5d00000000000000000000000000000000000000000000000000000000815263ffffffff891660048201526201000090910473ffffffffffffffffffffffffffffffffffffffff1690638318ed5d90602401600060405180830381865afa1580156110c9573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820160405261110f9190810190614b94565b90505b9093509150505b9250929050565b6040805160e0810182526000808252602082018190529181018290526060808201839052608082019290925260a0810182905260c08101919091526040805160e0810182526000848152600c6020908152838220805463ffffffff8082168652640100000000820481168487018190526801000000000000000090920416858701526001820154606086015260028201546080860152835260030190529190912060a08201906111cf9061366f565b81526020016111f2600c600086815260200190815260200160002060040161366f565b905292915050565b611202612ae8565b60005b8181101561068f57600083838381811061122157611221614859565b90506020020135905061123e816003612acd90919063ffffffff16565b611277576040517fe181733f00000000000000000000000000000000000000000000000000000000815260048101829052602401610609565b61128260058261367c565b6112bb576040517ff7d7a29400000000000000000000000000000000000000000000000000000000815260048101829052602401610609565b60405181907fdcea1b78b6ddc31592a94607d537543fcaafda6cc52d6d5cc7bbfca1422baf2190600090a250600101611205565b6000805473ffffffffffffffffffffffffffffffffffffffff163314905b82811015610d6f57600084848381811061132957611329614859565b905060200281019061133b9190614c02565b61134490614c36565b6040808201516000908152600c6020908152828220805463ffffffff168352600b82528383208451808601909552805473ffffffffffffffffffffffffffffffffffffffff16855260018101805496975091959394939092840191906113a99061490c565b80601f01602080910402602001604051908101604052809291908181526020018280546113d59061490c565b80156114225780601f106113f757610100808354040283529160200191611422565b820191906000526020600020905b81548152906001019060200180831161140557829003601f168201915b5050509190925250505060018301549091506114725782604001516040517fd82f6adb00000000000000000000000000000000000000000000000000000000815260040161060991815260200190565b841580156114975750805173ffffffffffffffffffffffffffffffffffffffff163314155b156114d0576040517f9473075d000000000000000000000000000000000000000000000000000000008152336004820152602401610609565b602083015161150b576040517f8377314600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60018201546020840151811461158c57602084015161152c90600790612acd565b15611563576040517f8377314600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60208401516001840155611578600782612b81565b50602084015161158a9060079061367c565b505b606084015180516000036115ce57806040517f3748d4c60000000000000000000000000000000000000000000000000000000081526004016106099190614d09565b835460009085906004906115ef90640100000000900463ffffffff1661495f565b91906101000a81548163ffffffff021916908363ffffffff1602179055905060005b82518110156116d45761164783828151811061162f5761162f614859565b60200260200101516003612acd90919063ffffffff16565b61167f57826040517f3748d4c60000000000000000000000000000000000000000000000000000000081526004016106099190614d09565b6116cb83828151811061169457611694614859565b60200260200101518760030160008563ffffffff1663ffffffff16815260200190815260200160002061367c90919063ffffffff16565b50600101611611565b50845468010000000000000000900463ffffffff1680156118355763ffffffff8082166000908152600d60209081526040808320805464010000000090049094168352600190930181528282206002018054845181840281018401909552808552929392909183018282801561176957602002820191906000526020600020905b815481526020019060010190808311611755575b5050505050905060005b8151811015611832576117c882828151811061179157611791614859565b60200260200101518960030160008763ffffffff1663ffffffff168152602001908152602001600020612acd90919063ffffffff16565b61182a578181815181106117de576117de614859565b6020026020010151836040517f03dcd86200000000000000000000000000000000000000000000000000000000815260040161060992919091825263ffffffff16602082015260400190565b600101611773565b50505b60006118438760040161366f565b905060005b81518163ffffffff161015611989576000828263ffffffff168151811061187157611871614859565b60209081029190910181015163ffffffff8082166000908152600d845260408082208054640100000000900490931682526001909201845281812060020180548351818702810187019094528084529395509093919290918301828280156118f857602002820191906000526020600020905b8154815260200190600101908083116118e4575b5050505050905060005b81518110156119755761195782828151811061192057611920614859565b60200260200101518c60030160008a63ffffffff1663ffffffff168152602001908152602001600020612acd90919063ffffffff16565b61196d578181815181106117de576117de614859565b600101611902565b505050806119829061495f565b9050611848565b50875187547fffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000001663ffffffff90911690811788556040808a015160028a018190556020808c01518351928352908201527f4b5b465e22eea0c3d40c30e936643245b80d19b2dcf75788c0699fe8d8db645b910160405180910390a2505050505050505080600101905061130d565b600e5460609063ffffffff166000611a306001836148b7565b63ffffffff1667ffffffffffffffff811115611a4e57611a4e613dca565b604051908082528060200260200182016040528015611a9457816020015b604080518082019091526000815260606020820152815260200190600190039081611a6c5790505b509050600060015b8363ffffffff168163ffffffff161015611be45763ffffffff81166000908152600b602052604090205473ffffffffffffffffffffffffffffffffffffffff1615611bdc5763ffffffff81166000908152600b60209081526040918290208251808401909352805473ffffffffffffffffffffffffffffffffffffffff1683526001810180549192840191611b309061490c565b80601f0160208091040260200160405190810160405280929190818152602001828054611b5c9061490c565b8015611ba95780601f10611b7e57610100808354040283529160200191611ba9565b820191906000526020600020905b815481529060010190602001808311611b8c57829003601f168201915b505050505081525050838381518110611bc457611bc4614859565b602002602001018190525081611bd9906148d4565b91505b600101611a9c565b50600e546107da9060019063ffffffff166148b7565b611c02612ae8565b60005b8181101561068f576000838383818110611c2157611c21614859565b9050602002810190611c339190614d4d565b611c3c90614d90565b90506000611c5282600001518360200151610446565b9050611c5f60038261367c565b611c98576040517febf5255100000000000000000000000000000000000000000000000000000000815260048101829052602401610609565b611ca28183613688565b5050600101611c05565b6000805473ffffffffffffffffffffffffffffffffffffffff163314905b82811015610d6f576000848483818110611ce657611ce6614859565b9050602002810190611cf89190614c02565b611d0190614c36565b805163ffffffff166000908152600b602090815260408083208151808301909252805473ffffffffffffffffffffffffffffffffffffffff168252600181018054959650939491939092840191611d579061490c565b80601f0160208091040260200160405190810160405280929190818152602001828054611d839061490c565b8015611dd05780601f10611da557610100808354040283529160200191611dd0565b820191906000526020600020905b815481529060010190602001808311611db357829003601f168201915b50505091909252505081519192505073ffffffffffffffffffffffffffffffffffffffff16611e365781516040517fadd9ae1e00000000000000000000000000000000000000000000000000000000815263ffffffff9091166004820152602401610609565b83158015611e5b5750805173ffffffffffffffffffffffffffffffffffffffff163314155b15611e94576040517f9473075d000000000000000000000000000000000000000000000000000000008152336004820152602401610609565b6040808301516000908152600c60205220600181015415611ee95782604001516040517f5461848300000000000000000000000000000000000000000000000000000000815260040161060991815260200190565b6040830151611f2c5782604001516040517f64e2ee9200000000000000000000000000000000000000000000000000000000815260040161060991815260200190565b60208301511580611f4957506020830151611f4990600790612acd565b15611f80576040517f8377314600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60608301518051600003611fc257806040517f3748d4c60000000000000000000000000000000000000000000000000000000081526004016106099190614d09565b81548290600490611fe090640100000000900463ffffffff1661495f565b82546101009290920a63ffffffff818102199093169183160217909155825464010000000090041660005b82518110156120b65761202983828151811061162f5761162f614859565b61206157826040517f3748d4c60000000000000000000000000000000000000000000000000000000081526004016106099190614d09565b6120ad83828151811061207657612076614859565b60200260200101518560030160008563ffffffff1663ffffffff16815260200190815260200160002061367c90919063ffffffff16565b5060010161200b565b50845183547fffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000001663ffffffff9182161784556040860151600285015560208601516001850181905561210c916007919061367c16565b50604085015161211e9060099061367c565b50845160408087015160208089015183519283529082015263ffffffff909216917f74becb12a5e8fd0e98077d02dfba8f647c9670c9df177e42c2418cf17a636f05910160405180910390a25050505050806001019050611cca565b60015473ffffffffffffffffffffffffffffffffffffffff1633146121fb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e6572000000000000000000006044820152606401610609565b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b8281146122ba576040517fab8b67c60000000000000000000000000000000000000000000000000000000081526004810184905260248101829052604401610609565b6000805473ffffffffffffffffffffffffffffffffffffffff16905b848110156125af5760008686838181106122f2576122f2614859565b90506020020160208101906123079190614290565b63ffffffff81166000908152600b6020526040902080549192509073ffffffffffffffffffffffffffffffffffffffff16612376576040517fadd9ae1e00000000000000000000000000000000000000000000000000000000815263ffffffff83166004820152602401610609565b600086868581811061238a5761238a614859565b905060200281019061239c9190614982565b6123a5906149c0565b805190915073ffffffffffffffffffffffffffffffffffffffff166123f6576040517feeacd93900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b815473ffffffffffffffffffffffffffffffffffffffff16331480159061243357503373ffffffffffffffffffffffffffffffffffffffff861614155b1561246c576040517f9473075d000000000000000000000000000000000000000000000000000000008152336004820152602401610609565b8051825473ffffffffffffffffffffffffffffffffffffffff90811691161415806124e857506020808201516040516124a5920161401b565b60405160208183030381529060405280519060200120826001016040516020016124cf9190614e36565b6040516020818303038152906040528051906020012014155b156125a157805182547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff909116178255602081015160018301906125429082614a7a565b50806000015173ffffffffffffffffffffffffffffffffffffffff168363ffffffff167f86f41145bde5dd7f523305452e4aad3685508c181432ec733d5f345009358a288360200151604051612598919061401b565b60405180910390a35b5050508060010190506122d6565b505050505050565b6125f86040805160e0810182526000808252606060208301819052928201839052909182019081526020016000815260006020820181905260409091015290565b6040805160e081018252838152600084815260026020908152929020805491928301916126249061490c565b80601f01602080910402602001604051908101604052809291908181526020018280546126509061490c565b801561269d5780601f106126725761010080835404028352916020019161269d565b820191906000526020600020905b81548152906001019060200180831161268057829003601f168201915b505050505081526020016002600085815260200190815260200160002060010180546126c89061490c565b80601f01602080910402602001604051908101604052809291908181526020018280546126f49061490c565b80156127415780601f1061271657610100808354040283529160200191612741565b820191906000526020600020905b81548152906001019060200180831161272457829003601f168201915b50505091835250506000848152600260208181526040909220015491019060ff166003811115612773576127736145b4565b815260008481526002602081815260409092200154910190610100900460ff1660018111156127a4576127a46145b4565b81526000848152600260208181526040928390209091015462010000900473ffffffffffffffffffffffffffffffffffffffff1690830152016127e8600585612acd565b1515905292915050565b6127fa612ae8565b600e805460009164010000000090910463ffffffff1690600461281c8361495f565b82546101009290920a63ffffffff81810219909316918316021790915581166000818152600d602090815260409182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000001684179055815160a08101835292835260019083015286151590820152841515606082015260ff841660808201529091506128b2908990899089908990612e58565b5050505050505050565b606060006128ca600361366f565b90506000815167ffffffffffffffff8111156128e8576128e8613dca565b60405190808252806020026020018201604052801561295a57816020015b6129476040805160e0810182526000808252606060208301819052928201839052909182019081526020016000815260006020820181905260409091015290565b8152602001906001900390816129065790505b50905060005b82518110156107ea5761298b83828151811061297e5761297e614859565b60200260200101516125b7565b82828151811061299d5761299d614859565b6020908102919091010152600101612960565b606060006129be600961366f565b90506000815167ffffffffffffffff8111156129dc576129dc613dca565b604051908082528060200260200182016040528015612a6357816020015b6040805160e081018252600080825260208083018290529282018190526060808301829052608083019190915260a0820181905260c082015282527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9092019101816129fa5790505b50905060005b82518110156107ea57612a94838281518110612a8757612a87614859565b6020026020010151611120565b828281518110612aa657612aa6614859565b6020908102919091010152600101612a69565b612ac1612ae8565b612aca81613870565b50565b600081815260018301602052604081205415155b9392505050565b60005473ffffffffffffffffffffffffffffffffffffffff163314612b69576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610609565b565b6000610474825490565b6000612ae18383613965565b6000612ae1838361398f565b6040805160e0810182526000808252602080830182905282840182905260608084018390526080840183905260a0840181905260c084015263ffffffff8581168352600d8252848320805464010000000090049091168084526001909101825284832060028101805487518186028101860190985280885295969295919493909190830182828015612c3e57602002820191906000526020600020905b815481526020019060010190808311612c2a575b505050505090506000815167ffffffffffffffff811115612c6157612c61613dca565b604051908082528060200260200182016040528015612ca757816020015b604080518082019091526000815260606020820152815260200190600190039081612c7f5790505b50905060005b8151811015612dbf576040518060400160405280848381518110612cd357612cd3614859565b60200260200101518152602001856003016000868581518110612cf857612cf8614859565b602002602001015181526020019081526020016000208054612d199061490c565b80601f0160208091040260200160405190810160405280929190818152602001828054612d459061490c565b8015612d925780601f10612d6757610100808354040283529160200191612d92565b820191906000526020600020905b815481529060010190602001808311612d7557829003601f168201915b5050505050815250828281518110612dac57612dac614859565b6020908102919091010152600101612cad565b506040805160e08101825263ffffffff8089166000818152600d6020818152868320548086168752948b168187015260ff680100000000000000008604811697870197909752690100000000000000000085048716151560608701529290915290526a010000000000000000000090049091161515608082015260a08101612e468561366f565b81526020019190915295945050505050565b805163ffffffff9081166000908152600d602090815260408083208286015190941683526001909301905220608082015160ff161580612eaa575060808201518590612ea5906001614ee4565b60ff16115b15612ef35760808201516040517f25b4d61800000000000000000000000000000000000000000000000000000000815260ff909116600482015260248101869052604401610609565b6001826020015163ffffffff161115612fdb57815163ffffffff166000908152600d602090815260408220908401516001918201918391612f3491906148b7565b63ffffffff1663ffffffff168152602001908152602001600020905060005b612f5c82612b6b565b811015612fd857612f8b846000015163ffffffff16600c60006105928587600001612b7590919063ffffffff16565b50600c6000612f9a8484612b75565b8152602081019190915260400160002080547fffffffffffffffffffffffffffffffffffffffff00000000ffffffffffffffff169055600101612f53565b50505b60005b858110156132155761300b878783818110612ffb57612ffb614859565b859260209091020135905061367c565b61306c57825187878381811061302357613023614859565b6040517f636e405700000000000000000000000000000000000000000000000000000000815263ffffffff90941660048501526020029190910135602483015250604401610609565b8260600151156131c357825163ffffffff16600c600089898581811061309457613094614859565b602090810292909201358352508101919091526040016000205468010000000000000000900463ffffffff161480159061310e5750600c60008888848181106130df576130df614859565b602090810292909201358352508101919091526040016000205468010000000000000000900463ffffffff1615155b1561317057825187878381811061312757613127614859565b6040517f60b9df7300000000000000000000000000000000000000000000000000000000815263ffffffff90941660048501526020029190910135602483015250604401610609565b8251600c600089898581811061318857613188614859565b90506020020135815260200190815260200160002060000160086101000a81548163ffffffff021916908363ffffffff16021790555061320d565b825161320b9063ffffffff16600c60008a8a868181106131e5576131e5614859565b90506020020135815260200190815260200160002060040161367c90919063ffffffff16565b505b600101612fde565b5060005b83811015613623573685858381811061323457613234614859565b90506020028101906132469190614982565b905061325460038235612acd565b61328d576040517fe181733f00000000000000000000000000000000000000000000000000000000815281356004820152602401610609565b61329960058235612acd565b156132d3576040517ff7d7a29400000000000000000000000000000000000000000000000000000000815281356004820152602401610609565b80356000908152600384016020526040812080546132f09061490c565b9050111561333c5783516040517f3927d08000000000000000000000000000000000000000000000000000000000815263ffffffff909116600482015281356024820152604401610609565b60005b87811015613446576133e38235600c60008c8c8681811061336257613362614859565b9050602002013581526020019081526020016000206003016000600c60008e8e8881811061339257613392614859565b90506020020135815260200190815260200160002060000160049054906101000a900463ffffffff1663ffffffff1663ffffffff168152602001908152602001600020612acd90919063ffffffff16565b61343e578888828181106133f9576133f9614859565b6040517fa7e792500000000000000000000000000000000000000000000000000000000081526020909102929092013560048301525082356024820152604401610609565b60010161333f565b506002830180546001810182556000918252602091829020833591015561346f90820182614efd565b8235600090815260038601602052604090209161348d919083614f62565b50604080850151855163ffffffff9081166000908152600d602090815284822080549415156901000000000000000000027fffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffff90951694909417909355606088015188518316825284822080549115156a0100000000000000000000027fffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffff9092169190911790556080880151885183168252848220805460ff9290921668010000000000000000027fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff909216919091179055828801805189518416835294909120805494909216640100000000027fffffffffffffffffffffffffffffffffffffffffffffffff00000000ffffffff9094169390931790558551915161361a92918435908c908c906135e090880188614efd565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250613a8292505050565b50600101613219565b50815160208084015160405163ffffffff91821681529216917ff264aae70bf6a9d90e68e0f9b393f4e7fbea67b063b0f336e0b36c1581703651910160405180910390a2505050505050565b60606000612ae183613b63565b6000612ae18383613bbf565b608081015173ffffffffffffffffffffffffffffffffffffffff161561372a576136d681608001517f78bea72100000000000000000000000000000000000000000000000000000000613c0e565b61372a5760808101516040517fabb5e3fd00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091166004820152602401610609565b6000828152600260205260409020815182919081906137499082614a7a565b506020820151600182019061375e9082614a7a565b5060408201516002820180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660018360038111156137a0576137a06145b4565b021790555060608201516002820180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101008360018111156137e7576137e76145b4565b0217905550608091909101516002909101805473ffffffffffffffffffffffffffffffffffffffff90921662010000027fffffffffffffffffffff0000000000000000000000000000000000000000ffff90921691909117905560405182907f04f0a9bcf3f3a3b42a4d7ca081119755f82ebe43e0d30c8f7292c4fe0dc4a2ae90600090a25050565b3373ffffffffffffffffffffffffffffffffffffffff8216036138ef576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610609565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b600082600001828154811061397c5761397c614859565b9060005260206000200154905092915050565b60008181526001830160205260408120548015613a785760006139b360018361507d565b85549091506000906139c79060019061507d565b9050818114613a2c5760008660000182815481106139e7576139e7614859565b9060005260206000200154905080876000018481548110613a0a57613a0a614859565b6000918252602080832090910192909255918252600188019052604090208390555b8554869080613a3d57613a3d615090565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050610474565b6000915050610474565b6000848152600260208190526040909120015462010000900473ffffffffffffffffffffffffffffffffffffffff16156125af57600084815260026020819052604091829020015490517ffba64a7c0000000000000000000000000000000000000000000000000000000081526201000090910473ffffffffffffffffffffffffffffffffffffffff169063fba64a7c90613b29908690869086908b908d906004016150bf565b600060405180830381600087803b158015613b4357600080fd5b505af1158015613b57573d6000803e3d6000fd5b50505050505050505050565b606081600001805480602002602001604051908101604052809291908181526020018280548015613bb357602002820191906000526020600020905b815481526020019060010190808311613b9f575b50505050509050919050565b6000818152600183016020526040812054613c0657508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610474565b506000610474565b6000613c1983613c2a565b8015612ae15750612ae18383613c8e565b6000613c56827f01ffc9a700000000000000000000000000000000000000000000000000000000613c8e565b80156104745750613c87827fffffffff00000000000000000000000000000000000000000000000000000000613c8e565b1592915050565b604080517fffffffff000000000000000000000000000000000000000000000000000000008316602480830191909152825180830390910181526044909101909152602080820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f01ffc9a700000000000000000000000000000000000000000000000000000000178152825160009392849283928392918391908a617530fa92503d91506000519050828015613d46575060208210155b8015613d525750600081115b979650505050505050565b508054613d699061490c565b6000825580601f10613d79575050565b601f016020900490600052602060002090810190612aca9190613db1565b5080546000825590600052602060002090810190612aca91905b5b80821115613dc65760008155600101613db2565b5090565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040516080810167ffffffffffffffff81118282101715613e1c57613e1c613dca565b60405290565b60405160a0810167ffffffffffffffff81118282101715613e1c57613e1c613dca565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715613e8c57613e8c613dca565b604052919050565b600067ffffffffffffffff821115613eae57613eae613dca565b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b600082601f830112613eeb57600080fd5b8135613efe613ef982613e94565b613e45565b818152846020838601011115613f1357600080fd5b816020850160208301376000918101602001919091529392505050565b60008060408385031215613f4357600080fd5b823567ffffffffffffffff80821115613f5b57600080fd5b613f6786838701613eda565b93506020850135915080821115613f7d57600080fd5b50613f8a85828601613eda565b9150509250929050565b600060208284031215613fa657600080fd5b5035919050565b60005b83811015613fc8578181015183820152602001613fb0565b50506000910152565b60008151808452613fe9816020860160208601613fad565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b602081526000612ae16020830184613fd1565b60008083601f84011261404057600080fd5b50813567ffffffffffffffff81111561405857600080fd5b6020830191508360208260051b850101111561111957600080fd5b6000806020838503121561408657600080fd5b823567ffffffffffffffff81111561409d57600080fd5b6140a98582860161402e565b90969095509350505050565b60008151808452602080850194506020840160005b838110156140e6578151875295820195908201906001016140ca565b509495945050505050565b600082825180855260208086019550808260051b84010181860160005b8481101561416e578583037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0018952815180518452840151604085850181905261415a81860183613fd1565b9a86019a945050509083019060010161410e565b5090979650505050505050565b600063ffffffff8083511684528060208401511660208501525060ff604083015116604084015260608201511515606084015260808201511515608084015260a082015160e060a08501526141d360e08501826140b5565b905060c083015184820360c08601526141ec82826140f1565b95945050505050565b600060208083016020845280855180835260408601915060408160051b87010192506020870160005b8281101561426a577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc088860301845261425885835161417b565b9450928501929085019060010161421e565b5092979650505050505050565b803563ffffffff8116811461428b57600080fd5b919050565b6000602082840312156142a257600080fd5b612ae182614277565b73ffffffffffffffffffffffffffffffffffffffff815116825260006020820151604060208501526142e06040850182613fd1565b949350505050565b602081526000612ae160208301846142ab565b602081526000612ae1602083018461417b565b8035801515811461428b57600080fd5b803560ff8116811461428b57600080fd5b600080600080600080600060a0888a03121561434a57600080fd5b61435388614277565b9650602088013567ffffffffffffffff8082111561437057600080fd5b61437c8b838c0161402e565b909850965060408a013591508082111561439557600080fd5b506143a28a828b0161402e565b90955093506143b590506060890161430e565b91506143c36080890161431e565b905092959891949750929550565b600080604083850312156143e457600080fd5b6143ed83614277565b946020939093013593505050565b60408152600061440e6040830185613fd1565b82810360208401526141ec8185613fd1565b600063ffffffff808351168452602081818501511681860152816040850151166040860152606084015160608601526080840151608086015260a0840151915060e060a086015261447460e08601836140b5565b60c08581015187830391880191909152805180835290830193506000918301905b808310156144b55784518252938301936001929092019190830190614495565b509695505050505050565b602081526000612ae16020830184614420565b600060208083016020845280855180835260408601915060408160051b87010192506020870160005b8281101561426a577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc08886030184526145368583516142ab565b945092850192908501906001016144fc565b6000806000806040858703121561455e57600080fd5b843567ffffffffffffffff8082111561457657600080fd5b6145828883890161402e565b9096509450602087013591508082111561459b57600080fd5b506145a88782880161402e565b95989497509550505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b805182526000602082015160e0602085015261460260e0850182613fd1565b90506040830151848203604086015261461b8282613fd1565b915050606083015160048110614633576146336145b4565b606085015260808301516002811061464d5761464d6145b4565b8060808601525060a083015161467b60a086018273ffffffffffffffffffffffffffffffffffffffff169052565b5060c083015161468f60c086018215159052565b509392505050565b602081526000612ae160208301846145e3565b600080600080600080600060a0888a0312156146c557600080fd5b873567ffffffffffffffff808211156146dd57600080fd5b6146e98b838c0161402e565b909950975060208a013591508082111561470257600080fd5b5061470f8a828b0161402e565b909650945061472290506040890161430e565b92506143b56060890161430e565b600060208083016020845280855180835260408601915060408160051b87010192506020870160005b8281101561426a577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc08886030184526147938583516145e3565b94509285019290850190600101614759565b600060208083016020845280855180835260408601915060408160051b87010192506020870160005b8281101561426a577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0888603018452614808858351614420565b945092850192908501906001016147ce565b803573ffffffffffffffffffffffffffffffffffffffff8116811461428b57600080fd5b60006020828403121561485057600080fd5b612ae18261481a565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b63ffffffff8281168282160390808211156107ea576107ea614888565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361490557614905614888565b5060010190565b600181811c9082168061492057607f821691505b602082108103614959577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b600063ffffffff80831681810361497857614978614888565b6001019392505050565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc18336030181126149b657600080fd5b9190910192915050565b6000604082360312156149d257600080fd5b6040516040810167ffffffffffffffff82821081831117156149f6576149f6613dca565b81604052614a038561481a565b83526020850135915080821115614a1957600080fd5b50614a2636828601613eda565b60208301525092915050565b601f82111561068f576000816000526020600020601f850160051c81016020861015614a5b5750805b601f850160051c820191505b818110156125af57828155600101614a67565b815167ffffffffffffffff811115614a9457614a94613dca565b614aa881614aa2845461490c565b84614a32565b602080601f831160018114614afb5760008415614ac55750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b1785556125af565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b82811015614b4857888601518255948401946001909101908401614b29565b5085821015614b8457878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b600060208284031215614ba657600080fd5b815167ffffffffffffffff811115614bbd57600080fd5b8201601f81018413614bce57600080fd5b8051614bdc613ef982613e94565b818152856020838501011115614bf157600080fd5b6141ec826020830160208601613fad565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff818336030181126149b657600080fd5b600060808236031215614c4857600080fd5b614c50613df9565b614c5983614277565b81526020808401358183015260408401356040830152606084013567ffffffffffffffff80821115614c8a57600080fd5b9085019036601f830112614c9d57600080fd5b813581811115614caf57614caf613dca565b8060051b9150614cc0848301613e45565b8181529183018401918481019036841115614cda57600080fd5b938501935b83851015614cf857843582529385019390850190614cdf565b606087015250939695505050505050565b6020808252825182820181905260009190848201906040850190845b81811015614d4157835183529284019291840191600101614d25565b50909695505050505050565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff618336030181126149b657600080fd5b80356002811061428b57600080fd5b600060a08236031215614da257600080fd5b614daa613e22565b823567ffffffffffffffff80821115614dc257600080fd5b614dce36838701613eda565b83526020850135915080821115614de457600080fd5b50614df136828601613eda565b602083015250604083013560048110614e0957600080fd5b6040820152614e1a60608401614d81565b6060820152614e2b6080840161481a565b608082015292915050565b6000602080835260008454614e4a8161490c565b8060208701526040600180841660008114614e6c5760018114614ea657614ed6565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00851660408a0152604084151560051b8a01019550614ed6565b89600052602060002060005b85811015614ecd5781548b8201860152908301908801614eb2565b8a016040019650505b509398975050505050505050565b60ff818116838216019081111561047457610474614888565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112614f3257600080fd5b83018035915067ffffffffffffffff821115614f4d57600080fd5b60200191503681900382131561111957600080fd5b67ffffffffffffffff831115614f7a57614f7a613dca565b614f8e83614f88835461490c565b83614a32565b6000601f841160018114614fe05760008515614faa5750838201355b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600387901b1c1916600186901b178355615076565b6000838152602090207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0861690835b8281101561502f578685013582556020948501946001909201910161500f565b508682101561506a577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88860031b161c19848701351681555b505060018560011b0183555b5050505050565b8181038181111561047457610474614888565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b6080815284608082015260007f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8611156150f857600080fd5b8560051b808860a0850137820182810360a0908101602085015261511e90820187613fd1565b91505063ffffffff8085166040840152808416606084015250969550505050505056fea164736f6c6343000818000a", } var CapabilitiesRegistryABI = CapabilitiesRegistryMetaData.ABI @@ -633,16 +633,16 @@ func (_CapabilitiesRegistry *CapabilitiesRegistryTransactorSession) TransferOwne return _CapabilitiesRegistry.Contract.TransferOwnership(&_CapabilitiesRegistry.TransactOpts, to) } -func (_CapabilitiesRegistry *CapabilitiesRegistryTransactor) UpdateDON(opts *bind.TransactOpts, donId uint32, nodes [][32]byte, capabilityConfigurations []CapabilitiesRegistryCapabilityConfiguration, isPublic bool, acceptsWorkflows bool, f uint8) (*types.Transaction, error) { - return _CapabilitiesRegistry.contract.Transact(opts, "updateDON", donId, nodes, capabilityConfigurations, isPublic, acceptsWorkflows, f) +func (_CapabilitiesRegistry *CapabilitiesRegistryTransactor) UpdateDON(opts *bind.TransactOpts, donId uint32, nodes [][32]byte, capabilityConfigurations []CapabilitiesRegistryCapabilityConfiguration, isPublic bool, f uint8) (*types.Transaction, error) { + return _CapabilitiesRegistry.contract.Transact(opts, "updateDON", donId, nodes, capabilityConfigurations, isPublic, f) } -func (_CapabilitiesRegistry *CapabilitiesRegistrySession) UpdateDON(donId uint32, nodes [][32]byte, capabilityConfigurations []CapabilitiesRegistryCapabilityConfiguration, isPublic bool, acceptsWorkflows bool, f uint8) (*types.Transaction, error) { - return _CapabilitiesRegistry.Contract.UpdateDON(&_CapabilitiesRegistry.TransactOpts, donId, nodes, capabilityConfigurations, isPublic, acceptsWorkflows, f) +func (_CapabilitiesRegistry *CapabilitiesRegistrySession) UpdateDON(donId uint32, nodes [][32]byte, capabilityConfigurations []CapabilitiesRegistryCapabilityConfiguration, isPublic bool, f uint8) (*types.Transaction, error) { + return _CapabilitiesRegistry.Contract.UpdateDON(&_CapabilitiesRegistry.TransactOpts, donId, nodes, capabilityConfigurations, isPublic, f) } -func (_CapabilitiesRegistry *CapabilitiesRegistryTransactorSession) UpdateDON(donId uint32, nodes [][32]byte, capabilityConfigurations []CapabilitiesRegistryCapabilityConfiguration, isPublic bool, acceptsWorkflows bool, f uint8) (*types.Transaction, error) { - return _CapabilitiesRegistry.Contract.UpdateDON(&_CapabilitiesRegistry.TransactOpts, donId, nodes, capabilityConfigurations, isPublic, acceptsWorkflows, f) +func (_CapabilitiesRegistry *CapabilitiesRegistryTransactorSession) UpdateDON(donId uint32, nodes [][32]byte, capabilityConfigurations []CapabilitiesRegistryCapabilityConfiguration, isPublic bool, f uint8) (*types.Transaction, error) { + return _CapabilitiesRegistry.Contract.UpdateDON(&_CapabilitiesRegistry.TransactOpts, donId, nodes, capabilityConfigurations, isPublic, f) } func (_CapabilitiesRegistry *CapabilitiesRegistryTransactor) UpdateNodeOperators(opts *bind.TransactOpts, nodeOperatorIds []uint32, nodeOperators []CapabilitiesRegistryNodeOperator) (*types.Transaction, error) { @@ -989,18 +989,28 @@ type CapabilitiesRegistryConfigSet struct { Raw types.Log } -func (_CapabilitiesRegistry *CapabilitiesRegistryFilterer) FilterConfigSet(opts *bind.FilterOpts) (*CapabilitiesRegistryConfigSetIterator, error) { +func (_CapabilitiesRegistry *CapabilitiesRegistryFilterer) FilterConfigSet(opts *bind.FilterOpts, donId []uint32) (*CapabilitiesRegistryConfigSetIterator, error) { - logs, sub, err := _CapabilitiesRegistry.contract.FilterLogs(opts, "ConfigSet") + var donIdRule []interface{} + for _, donIdItem := range donId { + donIdRule = append(donIdRule, donIdItem) + } + + logs, sub, err := _CapabilitiesRegistry.contract.FilterLogs(opts, "ConfigSet", donIdRule) if err != nil { return nil, err } return &CapabilitiesRegistryConfigSetIterator{contract: _CapabilitiesRegistry.contract, event: "ConfigSet", logs: logs, sub: sub}, nil } -func (_CapabilitiesRegistry *CapabilitiesRegistryFilterer) WatchConfigSet(opts *bind.WatchOpts, sink chan<- *CapabilitiesRegistryConfigSet) (event.Subscription, error) { +func (_CapabilitiesRegistry *CapabilitiesRegistryFilterer) WatchConfigSet(opts *bind.WatchOpts, sink chan<- *CapabilitiesRegistryConfigSet, donId []uint32) (event.Subscription, error) { + + var donIdRule []interface{} + for _, donIdItem := range donId { + donIdRule = append(donIdRule, donIdItem) + } - logs, sub, err := _CapabilitiesRegistry.contract.WatchLogs(opts, "ConfigSet") + logs, sub, err := _CapabilitiesRegistry.contract.WatchLogs(opts, "ConfigSet", donIdRule) if err != nil { return nil, err } @@ -2214,7 +2224,7 @@ type CapabilitiesRegistryInterface interface { TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) - UpdateDON(opts *bind.TransactOpts, donId uint32, nodes [][32]byte, capabilityConfigurations []CapabilitiesRegistryCapabilityConfiguration, isPublic bool, acceptsWorkflows bool, f uint8) (*types.Transaction, error) + UpdateDON(opts *bind.TransactOpts, donId uint32, nodes [][32]byte, capabilityConfigurations []CapabilitiesRegistryCapabilityConfiguration, isPublic bool, f uint8) (*types.Transaction, error) UpdateNodeOperators(opts *bind.TransactOpts, nodeOperatorIds []uint32, nodeOperators []CapabilitiesRegistryNodeOperator) (*types.Transaction, error) @@ -2232,9 +2242,9 @@ type CapabilitiesRegistryInterface interface { ParseCapabilityDeprecated(log types.Log) (*CapabilitiesRegistryCapabilityDeprecated, error) - FilterConfigSet(opts *bind.FilterOpts) (*CapabilitiesRegistryConfigSetIterator, error) + FilterConfigSet(opts *bind.FilterOpts, donId []uint32) (*CapabilitiesRegistryConfigSetIterator, error) - WatchConfigSet(opts *bind.WatchOpts, sink chan<- *CapabilitiesRegistryConfigSet) (event.Subscription, error) + WatchConfigSet(opts *bind.WatchOpts, sink chan<- *CapabilitiesRegistryConfigSet, donId []uint32) (event.Subscription, error) ParseConfigSet(log types.Log) (*CapabilitiesRegistryConfigSet, error) diff --git a/core/gethwrappers/keystone/generated/feeds_consumer/feeds_consumer.go b/core/gethwrappers/keystone/generated/feeds_consumer/feeds_consumer.go index 2951835c8d..8b618fbddb 100644 --- a/core/gethwrappers/keystone/generated/feeds_consumer/feeds_consumer.go +++ b/core/gethwrappers/keystone/generated/feeds_consumer/feeds_consumer.go @@ -32,7 +32,7 @@ var ( var KeystoneFeedsConsumerMetaData = &bind.MetaData{ ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"UnauthorizedSender\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes10\",\"name\":\"workflowName\",\"type\":\"bytes10\"}],\"name\":\"UnauthorizedWorkflowName\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"workflowOwner\",\"type\":\"address\"}],\"name\":\"UnauthorizedWorkflowOwner\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"feedId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint224\",\"name\":\"price\",\"type\":\"uint224\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"timestamp\",\"type\":\"uint32\"}],\"name\":\"FeedReceived\",\"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\":\"bytes32\",\"name\":\"feedId\",\"type\":\"bytes32\"}],\"name\":\"getPrice\",\"outputs\":[{\"internalType\":\"uint224\",\"name\":\"\",\"type\":\"uint224\"},{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"metadata\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"rawReport\",\"type\":\"bytes\"}],\"name\":\"onReport\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_allowedSendersList\",\"type\":\"address[]\"},{\"internalType\":\"address[]\",\"name\":\"_allowedWorkflowOwnersList\",\"type\":\"address[]\"},{\"internalType\":\"bytes10[]\",\"name\":\"_allowedWorkflowNamesList\",\"type\":\"bytes10[]\"}],\"name\":\"setConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", - Bin: "0x608060405234801561001057600080fd5b5033806000816100675760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615610097576100978161009f565b505050610148565b336001600160a01b038216036100f75760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640161005e565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b611281806101576000396000f3fe608060405234801561001057600080fd5b506004361061007d5760003560e01c8063805f21321161005b578063805f2132146101ab5780638da5cb5b146101be578063e3401711146101e6578063f2fde38b146101f957600080fd5b806301ffc9a71461008257806331d98b3f146100ec57806379ba5097146101a1575b600080fd5b6100d7610090366004610dd4565b7fffffffff00000000000000000000000000000000000000000000000000000000167f805f2132000000000000000000000000000000000000000000000000000000001490565b60405190151581526020015b60405180910390f35b6101686100fa366004610e1d565b6000908152600260209081526040918290208251808401909352547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff81168084527c010000000000000000000000000000000000000000000000000000000090910463ffffffff169290910182905291565b604080517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff909316835263ffffffff9091166020830152016100e3565b6101a961020c565b005b6101a96101b9366004610e7f565b61030e565b60005460405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100e3565b6101a96101f4366004610f30565b61068d565b6101a9610207366004610fca565b610acd565b60015473ffffffffffffffffffffffffffffffffffffffff163314610292576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b3360009081526004602052604090205460ff16610359576040517f3fcc3f17000000000000000000000000000000000000000000000000000000008152336004820152602401610289565b60008061039b86868080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610ae192505050565b7fffffffffffffffffffff000000000000000000000000000000000000000000008216600090815260086020526040902054919350915060ff1661042f576040517f4b942f800000000000000000000000000000000000000000000000000000000081527fffffffffffffffffffff0000000000000000000000000000000000000000000083166004820152602401610289565b73ffffffffffffffffffffffffffffffffffffffff811660009081526006602052604090205460ff166104a6576040517fbf24162300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82166004820152602401610289565b60006104b4848601866110a7565b905060005b81518110156106835760405180604001604052808383815181106104df576104df6111b9565b6020026020010151602001517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff168152602001838381518110610520576105206111b9565b60200260200101516040015163ffffffff168152506002600084848151811061054b5761054b6111b9565b602090810291909101810151518252818101929092526040016000208251929091015163ffffffff167c0100000000000000000000000000000000000000000000000000000000027bffffffffffffffffffffffffffffffffffffffffffffffffffffffff90921691909117905581518290829081106105cd576105cd6111b9565b6020026020010151600001517f2c30f5cb3caf4239d0f994ce539d7ef24817fa550169c388e3a110f02e40197d83838151811061060c5761060c6111b9565b60200260200101516020015184848151811061062a5761062a6111b9565b6020026020010151604001516040516106739291907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff92909216825263ffffffff16602082015260400190565b60405180910390a26001016104b9565b5050505050505050565b610695610af7565b60005b60035463ffffffff821610156107365760006004600060038463ffffffff16815481106106c7576106c76111b9565b60009182526020808320919091015473ffffffffffffffffffffffffffffffffffffffff168352820192909252604001902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001691151591909117905561072f816111e8565b9050610698565b5060005b63ffffffff81168611156107de5760016004600089898563ffffffff16818110610766576107666111b9565b905060200201602081019061077b9190610fca565b73ffffffffffffffffffffffffffffffffffffffff168152602081019190915260400160002080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169115159190911790556107d7816111e8565b905061073a565b506107eb60038787610c6f565b5060005b60055463ffffffff8216101561088d5760006006600060058463ffffffff168154811061081e5761081e6111b9565b60009182526020808320919091015473ffffffffffffffffffffffffffffffffffffffff168352820192909252604001902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016911515919091179055610886816111e8565b90506107ef565b5060005b63ffffffff81168411156109355760016006600087878563ffffffff168181106108bd576108bd6111b9565b90506020020160208101906108d29190610fca565b73ffffffffffffffffffffffffffffffffffffffff168152602081019190915260400160002080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001691151591909117905561092e816111e8565b9050610891565b5061094260058585610c6f565b5060005b60075463ffffffff82161015610a035760006008600060078463ffffffff1681548110610975576109756111b9565b600091825260208083206003808404909101549206600a026101000a90910460b01b7fffffffffffffffffffff00000000000000000000000000000000000000000000168352820192909252604001902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169115159190911790556109fc816111e8565b9050610946565b5060005b63ffffffff8116821115610ab75760016008600085858563ffffffff16818110610a3357610a336111b9565b9050602002016020810190610a489190611232565b7fffffffffffffffffffff00000000000000000000000000000000000000000000168152602081019190915260400160002080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016911515919091179055610ab0816111e8565b9050610a07565b50610ac460078383610cf7565b50505050505050565b610ad5610af7565b610ade81610b7a565b50565b6040810151604a90910151909160609190911c90565b60005473ffffffffffffffffffffffffffffffffffffffff163314610b78576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610289565b565b3373ffffffffffffffffffffffffffffffffffffffff821603610bf9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610289565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b828054828255906000526020600020908101928215610ce7579160200282015b82811115610ce75781547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff843516178255602090920191600190910190610c8f565b50610cf3929150610dbf565b5090565b82805482825590600052602060002090600201600390048101928215610ce75791602002820160005b83821115610d8057833575ffffffffffffffffffffffffffffffffffffffffffff191683826101000a81548169ffffffffffffffffffff021916908360b01c02179055509260200192600a01602081600901049283019260010302610d20565b8015610db65782816101000a81549069ffffffffffffffffffff0219169055600a01602081600901049283019260010302610d80565b5050610cf39291505b5b80821115610cf35760008155600101610dc0565b600060208284031215610de657600080fd5b81357fffffffff0000000000000000000000000000000000000000000000000000000081168114610e1657600080fd5b9392505050565b600060208284031215610e2f57600080fd5b5035919050565b60008083601f840112610e4857600080fd5b50813567ffffffffffffffff811115610e6057600080fd5b602083019150836020828501011115610e7857600080fd5b9250929050565b60008060008060408587031215610e9557600080fd5b843567ffffffffffffffff80821115610ead57600080fd5b610eb988838901610e36565b90965094506020870135915080821115610ed257600080fd5b50610edf87828801610e36565b95989497509550505050565b60008083601f840112610efd57600080fd5b50813567ffffffffffffffff811115610f1557600080fd5b6020830191508360208260051b8501011115610e7857600080fd5b60008060008060008060608789031215610f4957600080fd5b863567ffffffffffffffff80821115610f6157600080fd5b610f6d8a838b01610eeb565b90985096506020890135915080821115610f8657600080fd5b610f928a838b01610eeb565b90965094506040890135915080821115610fab57600080fd5b50610fb889828a01610eeb565b979a9699509497509295939492505050565b600060208284031215610fdc57600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610e1657600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040516060810167ffffffffffffffff8111828210171561105257611052611000565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff8111828210171561109f5761109f611000565b604052919050565b600060208083850312156110ba57600080fd5b823567ffffffffffffffff808211156110d257600080fd5b818501915085601f8301126110e657600080fd5b8135818111156110f8576110f8611000565b611106848260051b01611058565b8181528481019250606091820284018501918883111561112557600080fd5b938501935b828510156111ad5780858a0312156111425760008081fd5b61114a61102f565b85358152868601357bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8116811461117d5760008081fd5b8188015260408681013563ffffffff8116811461119a5760008081fd5b908201528452938401939285019261112a565b50979650505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600063ffffffff808316818103611228577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6001019392505050565b60006020828403121561124457600080fd5b81357fffffffffffffffffffff0000000000000000000000000000000000000000000081168114610e1657600080fdfea164736f6c6343000818000a", + Bin: "0x608060405234801561001057600080fd5b5033806000816100675760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615610097576100978161009f565b505050610148565b336001600160a01b038216036100f75760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640161005e565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6112d8806101576000396000f3fe608060405234801561001057600080fd5b506004361061007d5760003560e01c8063805f21321161005b578063805f2132146101695780638da5cb5b1461017c578063e3401711146101a4578063f2fde38b146101b757600080fd5b806301ffc9a71461008257806331d98b3f146100aa57806379ba50971461015f575b600080fd5b610095610090366004610e2b565b6101ca565b60405190151581526020015b60405180910390f35b6101266100b8366004610e74565b6000908152600260209081526040918290208251808401909352547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff81168084527c010000000000000000000000000000000000000000000000000000000090910463ffffffff169290910182905291565b604080517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff909316835263ffffffff9091166020830152016100a1565b610167610263565b005b610167610177366004610ed6565b610365565b60005460405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100a1565b6101676101b2366004610f87565b6106e4565b6101676101c5366004611021565b610b24565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f805f213200000000000000000000000000000000000000000000000000000000148061025d57507fffffffff0000000000000000000000000000000000000000000000000000000082167f01ffc9a700000000000000000000000000000000000000000000000000000000145b92915050565b60015473ffffffffffffffffffffffffffffffffffffffff1633146102e9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b3360009081526004602052604090205460ff166103b0576040517f3fcc3f170000000000000000000000000000000000000000000000000000000081523360048201526024016102e0565b6000806103f286868080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610b3892505050565b7fffffffffffffffffffff000000000000000000000000000000000000000000008216600090815260086020526040902054919350915060ff16610486576040517f4b942f800000000000000000000000000000000000000000000000000000000081527fffffffffffffffffffff00000000000000000000000000000000000000000000831660048201526024016102e0565b73ffffffffffffffffffffffffffffffffffffffff811660009081526006602052604090205460ff166104fd576040517fbf24162300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff821660048201526024016102e0565b600061050b848601866110fe565b905060005b81518110156106da57604051806040016040528083838151811061053657610536611210565b6020026020010151602001517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff16815260200183838151811061057757610577611210565b60200260200101516040015163ffffffff16815250600260008484815181106105a2576105a2611210565b602090810291909101810151518252818101929092526040016000208251929091015163ffffffff167c0100000000000000000000000000000000000000000000000000000000027bffffffffffffffffffffffffffffffffffffffffffffffffffffffff909216919091179055815182908290811061062457610624611210565b6020026020010151600001517f2c30f5cb3caf4239d0f994ce539d7ef24817fa550169c388e3a110f02e40197d83838151811061066357610663611210565b60200260200101516020015184848151811061068157610681611210565b6020026020010151604001516040516106ca9291907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff92909216825263ffffffff16602082015260400190565b60405180910390a2600101610510565b5050505050505050565b6106ec610b4e565b60005b60035463ffffffff8216101561078d5760006004600060038463ffffffff168154811061071e5761071e611210565b60009182526020808320919091015473ffffffffffffffffffffffffffffffffffffffff168352820192909252604001902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169115159190911790556107868161123f565b90506106ef565b5060005b63ffffffff81168611156108355760016004600089898563ffffffff168181106107bd576107bd611210565b90506020020160208101906107d29190611021565b73ffffffffffffffffffffffffffffffffffffffff168152602081019190915260400160002080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001691151591909117905561082e8161123f565b9050610791565b5061084260038787610cc6565b5060005b60055463ffffffff821610156108e45760006006600060058463ffffffff168154811061087557610875611210565b60009182526020808320919091015473ffffffffffffffffffffffffffffffffffffffff168352820192909252604001902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169115159190911790556108dd8161123f565b9050610846565b5060005b63ffffffff811684111561098c5760016006600087878563ffffffff1681811061091457610914611210565b90506020020160208101906109299190611021565b73ffffffffffffffffffffffffffffffffffffffff168152602081019190915260400160002080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169115159190911790556109858161123f565b90506108e8565b5061099960058585610cc6565b5060005b60075463ffffffff82161015610a5a5760006008600060078463ffffffff16815481106109cc576109cc611210565b600091825260208083206003808404909101549206600a026101000a90910460b01b7fffffffffffffffffffff00000000000000000000000000000000000000000000168352820192909252604001902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016911515919091179055610a538161123f565b905061099d565b5060005b63ffffffff8116821115610b0e5760016008600085858563ffffffff16818110610a8a57610a8a611210565b9050602002016020810190610a9f9190611289565b7fffffffffffffffffffff00000000000000000000000000000000000000000000168152602081019190915260400160002080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016911515919091179055610b078161123f565b9050610a5e565b50610b1b60078383610d4e565b50505050505050565b610b2c610b4e565b610b3581610bd1565b50565b6040810151604a90910151909160609190911c90565b60005473ffffffffffffffffffffffffffffffffffffffff163314610bcf576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e65720000000000000000000060448201526064016102e0565b565b3373ffffffffffffffffffffffffffffffffffffffff821603610c50576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c6600000000000000000060448201526064016102e0565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b828054828255906000526020600020908101928215610d3e579160200282015b82811115610d3e5781547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff843516178255602090920191600190910190610ce6565b50610d4a929150610e16565b5090565b82805482825590600052602060002090600201600390048101928215610d3e5791602002820160005b83821115610dd757833575ffffffffffffffffffffffffffffffffffffffffffff191683826101000a81548169ffffffffffffffffffff021916908360b01c02179055509260200192600a01602081600901049283019260010302610d77565b8015610e0d5782816101000a81549069ffffffffffffffffffff0219169055600a01602081600901049283019260010302610dd7565b5050610d4a9291505b5b80821115610d4a5760008155600101610e17565b600060208284031215610e3d57600080fd5b81357fffffffff0000000000000000000000000000000000000000000000000000000081168114610e6d57600080fd5b9392505050565b600060208284031215610e8657600080fd5b5035919050565b60008083601f840112610e9f57600080fd5b50813567ffffffffffffffff811115610eb757600080fd5b602083019150836020828501011115610ecf57600080fd5b9250929050565b60008060008060408587031215610eec57600080fd5b843567ffffffffffffffff80821115610f0457600080fd5b610f1088838901610e8d565b90965094506020870135915080821115610f2957600080fd5b50610f3687828801610e8d565b95989497509550505050565b60008083601f840112610f5457600080fd5b50813567ffffffffffffffff811115610f6c57600080fd5b6020830191508360208260051b8501011115610ecf57600080fd5b60008060008060008060608789031215610fa057600080fd5b863567ffffffffffffffff80821115610fb857600080fd5b610fc48a838b01610f42565b90985096506020890135915080821115610fdd57600080fd5b610fe98a838b01610f42565b9096509450604089013591508082111561100257600080fd5b5061100f89828a01610f42565b979a9699509497509295939492505050565b60006020828403121561103357600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610e6d57600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040516060810167ffffffffffffffff811182821017156110a9576110a9611057565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156110f6576110f6611057565b604052919050565b6000602080838503121561111157600080fd5b823567ffffffffffffffff8082111561112957600080fd5b818501915085601f83011261113d57600080fd5b81358181111561114f5761114f611057565b61115d848260051b016110af565b8181528481019250606091820284018501918883111561117c57600080fd5b938501935b828510156112045780858a0312156111995760008081fd5b6111a1611086565b85358152868601357bffffffffffffffffffffffffffffffffffffffffffffffffffffffff811681146111d45760008081fd5b8188015260408681013563ffffffff811681146111f15760008081fd5b9082015284529384019392850192611181565b50979650505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600063ffffffff80831681810361127f577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6001019392505050565b60006020828403121561129b57600080fd5b81357fffffffffffffffffffff0000000000000000000000000000000000000000000081168114610e6d57600080fdfea164736f6c6343000818000a", } var KeystoneFeedsConsumerABI = KeystoneFeedsConsumerMetaData.ABI diff --git a/core/gethwrappers/keystone/generated/forwarder/forwarder.go b/core/gethwrappers/keystone/generated/forwarder/forwarder.go index 0412241cf7..5c95cfef2b 100644 --- a/core/gethwrappers/keystone/generated/forwarder/forwarder.go +++ b/core/gethwrappers/keystone/generated/forwarder/forwarder.go @@ -30,9 +30,18 @@ var ( _ = abi.ConvertType ) +type IRouterTransmissionInfo struct { + TransmissionId [32]byte + State uint8 + Transmitter common.Address + InvalidReceiver bool + Success bool + GasLimit *big.Int +} + var KeystoneForwarderMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"transmissionId\",\"type\":\"bytes32\"}],\"name\":\"AlreadyAttempted\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"signer\",\"type\":\"address\"}],\"name\":\"DuplicateSigner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"numSigners\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxSigners\",\"type\":\"uint256\"}],\"name\":\"ExcessSigners\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FaultToleranceMustBePositive\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"numSigners\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minSigners\",\"type\":\"uint256\"}],\"name\":\"InsufficientSigners\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"configId\",\"type\":\"uint64\"}],\"name\":\"InvalidConfig\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidReport\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"signature\",\"type\":\"bytes\"}],\"name\":\"InvalidSignature\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"expected\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"received\",\"type\":\"uint256\"}],\"name\":\"InvalidSignatureCount\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"signer\",\"type\":\"address\"}],\"name\":\"InvalidSigner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnauthorizedForwarder\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"configVersion\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"}],\"name\":\"ConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"forwarder\",\"type\":\"address\"}],\"name\":\"ForwarderAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"forwarder\",\"type\":\"address\"}],\"name\":\"ForwarderRemoved\",\"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\":true,\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"workflowExecutionId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes2\",\"name\":\"reportId\",\"type\":\"bytes2\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"result\",\"type\":\"bool\"}],\"name\":\"ReportProcessed\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"forwarder\",\"type\":\"address\"}],\"name\":\"addForwarder\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"configVersion\",\"type\":\"uint32\"}],\"name\":\"clearConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"workflowExecutionId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes2\",\"name\":\"reportId\",\"type\":\"bytes2\"}],\"name\":\"getTransmissionId\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"workflowExecutionId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes2\",\"name\":\"reportId\",\"type\":\"bytes2\"}],\"name\":\"getTransmissionState\",\"outputs\":[{\"internalType\":\"enumIRouter.TransmissionState\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"workflowExecutionId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes2\",\"name\":\"reportId\",\"type\":\"bytes2\"}],\"name\":\"getTransmitter\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"forwarder\",\"type\":\"address\"}],\"name\":\"isForwarder\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"forwarder\",\"type\":\"address\"}],\"name\":\"removeForwarder\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"rawReport\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"reportContext\",\"type\":\"bytes\"},{\"internalType\":\"bytes[]\",\"name\":\"signatures\",\"type\":\"bytes[]\"}],\"name\":\"report\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"transmissionId\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"transmitter\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"metadata\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"validatedReport\",\"type\":\"bytes\"}],\"name\":\"route\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"configVersion\",\"type\":\"uint32\"},{\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"}],\"name\":\"setConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", - Bin: "0x608060405234801561001057600080fd5b5033806000816100675760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b038481169190911790915581161561009757610097816100b9565b5050306000908152600360205260409020805460ff1916600117905550610162565b336001600160a01b038216036101115760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640161005e565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b611b2d80620001726000396000f3fe608060405234801561001057600080fd5b50600436106100ea5760003560e01c806379ba50971161008c578063abcef55411610066578063abcef5541461023e578063ee59d26c14610277578063ef6e17a01461028a578063f2fde38b1461029d57600080fd5b806379ba5097146101e05780638864b864146101e85780638da5cb5b1461022057600080fd5b8063354bdd66116100c8578063354bdd661461017957806343c164671461019a5780634d93172d146101ba5780635c41d2fe146101cd57600080fd5b806311289565146100ef578063181f5a7714610104578063233fd52d14610156575b600080fd5b6101026100fd366004611474565b6102b0565b005b6101406040518060400160405280601a81526020017f466f7277617264657220616e6420526f7574657220312e302e3000000000000081525081565b60405161014d919061151f565b60405180910390f35b61016961016436600461158c565b61080d565b604051901515815260200161014d565b61018c610187366004611614565b610a00565b60405190815260200161014d565b6101ad6101a8366004611614565b610a84565b60405161014d9190611679565b6101026101c83660046116ba565b610b09565b6101026101db3660046116ba565b610b85565b610102610c04565b6101fb6101f6366004611614565b610d01565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200161014d565b60005473ffffffffffffffffffffffffffffffffffffffff166101fb565b61016961024c3660046116ba565b73ffffffffffffffffffffffffffffffffffffffff1660009081526003602052604090205460ff1690565b6101026102853660046116e9565b610d41565b610102610298366004611767565b6110ba565b6101026102ab3660046116ba565b61115a565b606d8510156102eb576040517fb55ac75400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600080600061032f89898080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061116e92505050565b67ffffffffffffffff8216600090815260026020526040812080549497509195509193509160ff16908190036103a2576040517fdf3b81ea00000000000000000000000000000000000000000000000000000000815267ffffffffffffffff841660048201526024015b60405180910390fd5b856103ae8260016117c9565b60ff1614610400576103c18160016117c9565b6040517fd6022e8e00000000000000000000000000000000000000000000000000000000815260ff909116600482015260248101879052604401610399565b60008b8b6040516104129291906117e8565b60405190819003812061042b918c908c906020016117f8565b60405160208183030381529060405280519060200120905061044b611301565b60005b888110156106cd573660008b8b8481811061046b5761046b611812565b905060200281019061047d9190611841565b9092509050604181146104c05781816040517f2adfdc300000000000000000000000000000000000000000000000000000000081526004016103999291906118ef565b6000600186848460408181106104d8576104d8611812565b6104ea92013560f81c9050601b6117c9565b6104f860206000878961190b565b61050191611935565b61050f60406020888a61190b565b61051891611935565b6040805160008152602081018083529590955260ff909316928401929092526060830152608082015260a0016020604051602081039080840390855afa158015610566573d6000803e3d6000fd5b5050604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015173ffffffffffffffffffffffffffffffffffffffff8116600090815260028c0160205291822054909350915081900361060c576040517fbf18af4300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff83166004820152602401610399565b600086826020811061062057610620611812565b602002015173ffffffffffffffffffffffffffffffffffffffff161461068a576040517fe021c4f200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff83166004820152602401610399565b8186826020811061069d5761069d611812565b73ffffffffffffffffffffffffffffffffffffffff909216602092909202015250506001909201915061044e9050565b50505050505060003073ffffffffffffffffffffffffffffffffffffffff1663233fd52d6106fc8c8686610a00565b338d8d8d602d90606d926107129392919061190b565b8f8f606d9080926107259392919061190b565b6040518863ffffffff1660e01b81526004016107479796959493929190611971565b6020604051808303816000875af1158015610766573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061078a91906119d2565b9050817dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916838b73ffffffffffffffffffffffffffffffffffffffff167f3617b009e9785c42daebadb6d3fb553243a4bf586d07ea72d65d80013ce116b5846040516107f9911515815260200190565b60405180910390a450505050505050505050565b3360009081526003602052604081205460ff16610856576040517fd79e123d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008881526004602052604090205473ffffffffffffffffffffffffffffffffffffffff16156108b5576040517fa53dc8ca00000000000000000000000000000000000000000000000000000000815260048101899052602401610399565b600088815260046020526040812080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff8a81169190911790915587163b9003610917575060006109f5565b6040517f805f213200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff87169063805f21329061096f9088908890889088906004016119f4565b600060405180830381600087803b15801561098957600080fd5b505af192505050801561099a575060015b6109a6575060006109f5565b50600087815260046020526040902080547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff167401000000000000000000000000000000000000000017905560015b979650505050505050565b6040517fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606085901b166020820152603481018390527fffff000000000000000000000000000000000000000000000000000000000000821660548201526000906056016040516020818303038152906040528051906020012090505b9392505050565b600080610a92858585610a00565b60008181526004602052604090205490915073ffffffffffffffffffffffffffffffffffffffff16610ac8576000915050610a7d565b60008181526004602052604090205474010000000000000000000000000000000000000000900460ff16610afd576002610b00565b60015b95945050505050565b610b11611189565b73ffffffffffffffffffffffffffffffffffffffff811660008181526003602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055517fb96d15bf9258c7b8df062753a6a262864611fc7b060a5ee2e57e79b85f898d389190a250565b610b8d611189565b73ffffffffffffffffffffffffffffffffffffffff811660008181526003602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055517f0ea0ce2c048ff45a4a95f2947879de3fb94abec2f152190400cab2d1272a68e79190a250565b60015473ffffffffffffffffffffffffffffffffffffffff163314610c85576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e6572000000000000000000006044820152606401610399565b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b600060046000610d12868686610a00565b815260208101919091526040016000205473ffffffffffffffffffffffffffffffffffffffff16949350505050565b610d49611189565b8260ff16600003610d86576040517f0743bae600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601f811115610dcb576040517f61750f4000000000000000000000000000000000000000000000000000000000815260048101829052601f6024820152604401610399565b610dd6836003611a1b565b60ff168111610e345780610deb846003611a1b565b610df69060016117c9565b6040517f9dd9e6d8000000000000000000000000000000000000000000000000000000008152600481019290925260ff166024820152604401610399565b67ffffffff00000000602086901b1663ffffffff85161760005b67ffffffffffffffff8216600090815260026020526040902060010154811015610ee45767ffffffffffffffff8216600090815260026020819052604082206001810180549190920192919084908110610eaa57610eaa611812565b600091825260208083209091015473ffffffffffffffffffffffffffffffffffffffff168352820192909252604001812055600101610e4e565b5060005b82811015610ffc576000848483818110610f0457610f04611812565b9050602002016020810190610f1991906116ba565b67ffffffffffffffff8416600090815260026020818152604080842073ffffffffffffffffffffffffffffffffffffffff86168552909201905290205490915015610fa8576040517fe021c4f200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82166004820152602401610399565b610fb3826001611a3e565b67ffffffffffffffff8416600090815260026020818152604080842073ffffffffffffffffffffffffffffffffffffffff90961684529490910190529190912055600101610ee8565b5067ffffffffffffffff81166000908152600260205260409020611024906001018484611320565b5067ffffffffffffffff81166000908152600260205260409081902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660ff87161790555163ffffffff86811691908816907f4120bd3b23957dd423555817d55654d4481b438aa15485c21b4180c784f1a455906110aa90889088908890611a51565b60405180910390a3505050505050565b6110c2611189565b63ffffffff818116602084811b67ffffffff00000000168217600090815260028252604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001690558051828152928301905291928516917f4120bd3b23957dd423555817d55654d4481b438aa15485c21b4180c784f1a4559160405161114e929190611ab7565b60405180910390a35050565b611162611189565b61116b8161120c565b50565b60218101516045820151608b90920151909260c09290921c91565b60005473ffffffffffffffffffffffffffffffffffffffff16331461120a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610399565b565b3373ffffffffffffffffffffffffffffffffffffffff82160361128b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610399565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6040518061040001604052806020906020820280368337509192915050565b828054828255906000526020600020908101928215611398579160200282015b828111156113985781547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff843516178255602090920191600190910190611340565b506113a49291506113a8565b5090565b5b808211156113a457600081556001016113a9565b803573ffffffffffffffffffffffffffffffffffffffff811681146113e157600080fd5b919050565b60008083601f8401126113f857600080fd5b50813567ffffffffffffffff81111561141057600080fd5b60208301915083602082850101111561142857600080fd5b9250929050565b60008083601f84011261144157600080fd5b50813567ffffffffffffffff81111561145957600080fd5b6020830191508360208260051b850101111561142857600080fd5b60008060008060008060006080888a03121561148f57600080fd5b611498886113bd565b9650602088013567ffffffffffffffff808211156114b557600080fd5b6114c18b838c016113e6565b909850965060408a01359150808211156114da57600080fd5b6114e68b838c016113e6565b909650945060608a01359150808211156114ff57600080fd5b5061150c8a828b0161142f565b989b979a50959850939692959293505050565b60006020808352835180602085015260005b8181101561154d57858101830151858201604001528201611531565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b600080600080600080600060a0888a0312156115a757600080fd5b873596506115b7602089016113bd565b95506115c5604089016113bd565b9450606088013567ffffffffffffffff808211156115e257600080fd5b6115ee8b838c016113e6565b909650945060808a013591508082111561160757600080fd5b5061150c8a828b016113e6565b60008060006060848603121561162957600080fd5b611632846113bd565b92506020840135915060408401357fffff0000000000000000000000000000000000000000000000000000000000008116811461166e57600080fd5b809150509250925092565b60208101600383106116b4577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b91905290565b6000602082840312156116cc57600080fd5b610a7d826113bd565b803563ffffffff811681146113e157600080fd5b60008060008060006080868803121561170157600080fd5b61170a866116d5565b9450611718602087016116d5565b9350604086013560ff8116811461172e57600080fd5b9250606086013567ffffffffffffffff81111561174a57600080fd5b6117568882890161142f565b969995985093965092949392505050565b6000806040838503121561177a57600080fd5b611783836116d5565b9150611791602084016116d5565b90509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60ff81811683821601908111156117e2576117e261179a565b92915050565b8183823760009101908152919050565b838152818360208301376000910160200190815292915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe184360301811261187657600080fd5b83018035915067ffffffffffffffff82111561189157600080fd5b60200191503681900382131561142857600080fd5b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b6020815260006119036020830184866118a6565b949350505050565b6000808585111561191b57600080fd5b8386111561192857600080fd5b5050820193919092039150565b803560208310156117e2577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff602084900360031b1b1692915050565b878152600073ffffffffffffffffffffffffffffffffffffffff808916602084015280881660408401525060a060608301526119b160a0830186886118a6565b82810360808401526119c48185876118a6565b9a9950505050505050505050565b6000602082840312156119e457600080fd5b81518015158114610a7d57600080fd5b604081526000611a086040830186886118a6565b82810360208401526109f58185876118a6565b60ff8181168382160290811690818114611a3757611a3761179a565b5092915050565b808201808211156117e2576117e261179a565b60ff8416815260406020808301829052908201839052600090849060608401835b86811015611aab5773ffffffffffffffffffffffffffffffffffffffff611a98856113bd565b1682529282019290820190600101611a72565b50979650505050505050565b60006040820160ff8516835260206040602085015281855180845260608601915060208701935060005b81811015611b1357845173ffffffffffffffffffffffffffffffffffffffff1683529383019391830191600101611ae1565b509097965050505050505056fea164736f6c6343000818000a", + ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"transmissionId\",\"type\":\"bytes32\"}],\"name\":\"AlreadyAttempted\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"signer\",\"type\":\"address\"}],\"name\":\"DuplicateSigner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"numSigners\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxSigners\",\"type\":\"uint256\"}],\"name\":\"ExcessSigners\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FaultToleranceMustBePositive\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"transmissionId\",\"type\":\"bytes32\"}],\"name\":\"InsufficientGasForRouting\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"numSigners\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minSigners\",\"type\":\"uint256\"}],\"name\":\"InsufficientSigners\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"configId\",\"type\":\"uint64\"}],\"name\":\"InvalidConfig\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidReport\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"signature\",\"type\":\"bytes\"}],\"name\":\"InvalidSignature\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"expected\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"received\",\"type\":\"uint256\"}],\"name\":\"InvalidSignatureCount\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"signer\",\"type\":\"address\"}],\"name\":\"InvalidSigner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnauthorizedForwarder\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"configVersion\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"}],\"name\":\"ConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"forwarder\",\"type\":\"address\"}],\"name\":\"ForwarderAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"forwarder\",\"type\":\"address\"}],\"name\":\"ForwarderRemoved\",\"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\":true,\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"workflowExecutionId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes2\",\"name\":\"reportId\",\"type\":\"bytes2\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"result\",\"type\":\"bool\"}],\"name\":\"ReportProcessed\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"forwarder\",\"type\":\"address\"}],\"name\":\"addForwarder\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"configVersion\",\"type\":\"uint32\"}],\"name\":\"clearConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"workflowExecutionId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes2\",\"name\":\"reportId\",\"type\":\"bytes2\"}],\"name\":\"getTransmissionId\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"workflowExecutionId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes2\",\"name\":\"reportId\",\"type\":\"bytes2\"}],\"name\":\"getTransmissionInfo\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"transmissionId\",\"type\":\"bytes32\"},{\"internalType\":\"enumIRouter.TransmissionState\",\"name\":\"state\",\"type\":\"uint8\"},{\"internalType\":\"address\",\"name\":\"transmitter\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"invalidReceiver\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"},{\"internalType\":\"uint80\",\"name\":\"gasLimit\",\"type\":\"uint80\"}],\"internalType\":\"structIRouter.TransmissionInfo\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"workflowExecutionId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes2\",\"name\":\"reportId\",\"type\":\"bytes2\"}],\"name\":\"getTransmitter\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"forwarder\",\"type\":\"address\"}],\"name\":\"isForwarder\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"forwarder\",\"type\":\"address\"}],\"name\":\"removeForwarder\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"rawReport\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"reportContext\",\"type\":\"bytes\"},{\"internalType\":\"bytes[]\",\"name\":\"signatures\",\"type\":\"bytes[]\"}],\"name\":\"report\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"transmissionId\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"transmitter\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"metadata\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"validatedReport\",\"type\":\"bytes\"}],\"name\":\"route\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"configVersion\",\"type\":\"uint32\"},{\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"}],\"name\":\"setConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", + Bin: "0x60806040523480156200001157600080fd5b503380600081620000695760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b03848116919091179091558116156200009c576200009c81620000bf565b5050306000908152600360205260409020805460ff19166001179055506200016a565b336001600160a01b03821603620001195760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640162000060565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b61218f806200017a6000396000f3fe608060405234801561001057600080fd5b50600436106100ea5760003560e01c806379ba50971161008c578063abcef55411610066578063abcef5541461035d578063ee59d26c14610396578063ef6e17a0146103a9578063f2fde38b146103bc57600080fd5b806379ba50971461025e5780638864b864146102665780638da5cb5b1461033f57600080fd5b8063272cbd93116100c8578063272cbd9314610179578063354bdd66146101995780634d93172d146102385780635c41d2fe1461024b57600080fd5b806311289565146100ef578063181f5a7714610104578063233fd52d14610156575b600080fd5b6101026100fd366004611a33565b6103cf565b005b6101406040518060400160405280601a81526020017f466f7277617264657220616e6420526f7574657220312e302e3000000000000081525081565b60405161014d9190611ade565b60405180910390f35b610169610164366004611b4b565b610989565b604051901515815260200161014d565b61018c610187366004611bd3565b610d4a565b60405161014d9190611c67565b61022a6101a7366004611bd3565b6040517fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606085901b166020820152603481018390527fffff000000000000000000000000000000000000000000000000000000000000821660548201526000906056016040516020818303038152906040528051906020012090509392505050565b60405190815260200161014d565b610102610246366004611d0f565b610f50565b610102610259366004611d0f565b610fcc565b61010261104b565b61031a610274366004611bd3565b6040805160609490941b7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000001660208086019190915260348501939093527fffff000000000000000000000000000000000000000000000000000000000000919091166054840152805160368185030181526056909301815282519282019290922060009081526004909152205473ffffffffffffffffffffffffffffffffffffffff1690565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200161014d565b60005473ffffffffffffffffffffffffffffffffffffffff1661031a565b61016961036b366004611d0f565b73ffffffffffffffffffffffffffffffffffffffff1660009081526003602052604090205460ff1690565b6101026103a4366004611d3e565b611148565b6101026103b7366004611dbc565b611525565b6101026103ca366004611d0f565b6115c5565b606d85101561040a576040517fb55ac75400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600080600061044e89898080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506115d992505050565b67ffffffffffffffff8216600090815260026020526040812080549497509195509193509160ff16908190036104c1576040517fdf3b81ea00000000000000000000000000000000000000000000000000000000815267ffffffffffffffff841660048201526024015b60405180910390fd5b856104cd826001611e1e565b60ff161461051f576104e0816001611e1e565b6040517fd6022e8e00000000000000000000000000000000000000000000000000000000815260ff9091166004820152602481018790526044016104b8565b60008b8b604051610531929190611e37565b60405190819003812061054a918c908c90602001611e47565b60405160208183030381529060405280519060200120905061056a6118c0565b60005b888110156107ec573660008b8b8481811061058a5761058a611e61565b905060200281019061059c9190611e90565b9092509050604181146105df5781816040517f2adfdc300000000000000000000000000000000000000000000000000000000081526004016104b8929190611f3e565b6000600186848460408181106105f7576105f7611e61565b61060992013560f81c9050601b611e1e565b610617602060008789611f5a565b61062091611f84565b61062e60406020888a611f5a565b61063791611f84565b6040805160008152602081018083529590955260ff909316928401929092526060830152608082015260a0016020604051602081039080840390855afa158015610685573d6000803e3d6000fd5b5050604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015173ffffffffffffffffffffffffffffffffffffffff8116600090815260028c0160205291822054909350915081900361072b576040517fbf18af4300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff831660048201526024016104b8565b600086826020811061073f5761073f611e61565b602002015173ffffffffffffffffffffffffffffffffffffffff16146107a9576040517fe021c4f200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff831660048201526024016104b8565b818682602081106107bc576107bc611e61565b73ffffffffffffffffffffffffffffffffffffffff909216602092909202015250506001909201915061056d9050565b50506040805160608f901b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016602080830191909152603482018990527fffff0000000000000000000000000000000000000000000000000000000000008816605483015282516036818403018152605690920190925280519101206000945030935063233fd52d92509050338d8d8d602d90606d9261088e93929190611f5a565b8f8f606d9080926108a193929190611f5a565b6040518863ffffffff1660e01b81526004016108c39796959493929190611fc0565b6020604051808303816000875af11580156108e2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109069190612021565b9050817dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916838b73ffffffffffffffffffffffffffffffffffffffff167f3617b009e9785c42daebadb6d3fb553243a4bf586d07ea72d65d80013ce116b584604051610975911515815260200190565b60405180910390a450505050505050505050565b3360009081526003602052604081205460ff166109d2576040517fd79e123d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006109e26113886161a861204a565b5a6109ed919061205d565b90506109fd6113886161a861204a565b610a0a9062015f9061204a565b610a169061271061204a565b811015610a52576040517f0bfecd63000000000000000000000000000000000000000000000000000000008152600481018a90526024016104b8565b6000898152600460209081526040918290208251608081018452905473ffffffffffffffffffffffffffffffffffffffff8116825274010000000000000000000000000000000000000000810460ff90811615159383019390935275010000000000000000000000000000000000000000008104909216151592810183905276010000000000000000000000000000000000000000000090910469ffffffffffffffffffff1660608201529080610b0a575080602001515b15610b44576040517fa53dc8ca000000000000000000000000000000000000000000000000000000008152600481018b90526024016104b8565b60008a8152600460205260409020805469ffffffffffffffffffff84167601000000000000000000000000000000000000000000000275ffff000000000000000000000000000000000000000090911673ffffffffffffffffffffffffffffffffffffffff8c1617179055610bd9887f805f2132000000000000000000000000000000000000000000000000000000006115f4565b610c3057505050600087815260046020526040812080547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1674010000000000000000000000000000000000000000179055610d3f565b60008088888888604051602401610c4a9493929190612070565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f805f213200000000000000000000000000000000000000000000000000000000179052905060006113885a610cd2919061205d565b905060008083516020850160008f86f192508215610d375760008d815260046020526040902080547fffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffff1675010000000000000000000000000000000000000000001790555b509093505050505b979650505050505050565b6040805160c0810182526000808252602080830182905282840182905260608084018390526080840183905260a0840183905284519088901b7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000001681830152603481018790527fffff000000000000000000000000000000000000000000000000000000000000861660548201528451603681830301815260568201808752815191840191909120808552600490935285842060d68301909652945473ffffffffffffffffffffffffffffffffffffffff811680875274010000000000000000000000000000000000000000820460ff9081161515607685015275010000000000000000000000000000000000000000008304161515609684015276010000000000000000000000000000000000000000000090910469ffffffffffffffffffff1660b69092019190915292939092909190610ea857506000610ed0565b816020015115610eba57506002610ed0565b8160400151610eca576003610ecd565b60015b90505b6040518060c00160405280848152602001826003811115610ef357610ef3611c38565b8152602001836000015173ffffffffffffffffffffffffffffffffffffffff168152602001836020015115158152602001836040015115158152602001836060015169ffffffffffffffffffff1681525093505050509392505050565b610f58611619565b73ffffffffffffffffffffffffffffffffffffffff811660008181526003602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055517fb96d15bf9258c7b8df062753a6a262864611fc7b060a5ee2e57e79b85f898d389190a250565b610fd4611619565b73ffffffffffffffffffffffffffffffffffffffff811660008181526003602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055517f0ea0ce2c048ff45a4a95f2947879de3fb94abec2f152190400cab2d1272a68e79190a250565b60015473ffffffffffffffffffffffffffffffffffffffff1633146110cc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064016104b8565b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b611150611619565b8260ff1660000361118d576040517f0743bae600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601f8111156111d2576040517f61750f4000000000000000000000000000000000000000000000000000000000815260048101829052601f60248201526044016104b8565b6111dd836003612097565b60ff16811161123b57806111f2846003612097565b6111fd906001611e1e565b6040517f9dd9e6d8000000000000000000000000000000000000000000000000000000008152600481019290925260ff1660248201526044016104b8565b67ffffffff00000000602086901b1663ffffffff85161760005b67ffffffffffffffff82166000908152600260205260409020600101548110156112eb5767ffffffffffffffff82166000908152600260208190526040822060018101805491909201929190849081106112b1576112b1611e61565b600091825260208083209091015473ffffffffffffffffffffffffffffffffffffffff168352820192909252604001812055600101611255565b5060005b8281101561146757600084848381811061130b5761130b611e61565b90506020020160208101906113209190611d0f565b905073ffffffffffffffffffffffffffffffffffffffff8116611387576040517fbf18af4300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff821660048201526024016104b8565b67ffffffffffffffff8316600090815260026020818152604080842073ffffffffffffffffffffffffffffffffffffffff86168552909201905290205415611413576040517fe021c4f200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff821660048201526024016104b8565b61141e82600161204a565b67ffffffffffffffff8416600090815260026020818152604080842073ffffffffffffffffffffffffffffffffffffffff909616845294909101905291909120556001016112ef565b5067ffffffffffffffff8116600090815260026020526040902061148f9060010184846118df565b5067ffffffffffffffff81166000908152600260205260409081902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660ff87161790555163ffffffff86811691908816907f4120bd3b23957dd423555817d55654d4481b438aa15485c21b4180c784f1a45590611515908890889088906120b3565b60405180910390a3505050505050565b61152d611619565b63ffffffff818116602084811b67ffffffff00000000168217600090815260028252604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001690558051828152928301905291928516917f4120bd3b23957dd423555817d55654d4481b438aa15485c21b4180c784f1a455916040516115b9929190612119565b60405180910390a35050565b6115cd611619565b6115d68161169c565b50565b60218101516045820151608b90920151909260c09290921c91565b60006115ff83611791565b8015611610575061161083836117f5565b90505b92915050565b60005473ffffffffffffffffffffffffffffffffffffffff16331461169a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e65720000000000000000000060448201526064016104b8565b565b3373ffffffffffffffffffffffffffffffffffffffff82160361171b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c6600000000000000000060448201526064016104b8565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b60006117bd827f01ffc9a7000000000000000000000000000000000000000000000000000000006117f5565b801561161357506117ee827fffffffff000000000000000000000000000000000000000000000000000000006117f5565b1592915050565b604080517fffffffff000000000000000000000000000000000000000000000000000000008316602480830191909152825180830390910181526044909101909152602080820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f01ffc9a700000000000000000000000000000000000000000000000000000000178152825160009392849283928392918391908a617530fa92503d915060005190508280156118ad575060208210155b8015610d3f575015159695505050505050565b6040518061040001604052806020906020820280368337509192915050565b828054828255906000526020600020908101928215611957579160200282015b828111156119575781547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff8435161782556020909201916001909101906118ff565b50611963929150611967565b5090565b5b808211156119635760008155600101611968565b803573ffffffffffffffffffffffffffffffffffffffff811681146119a057600080fd5b919050565b60008083601f8401126119b757600080fd5b50813567ffffffffffffffff8111156119cf57600080fd5b6020830191508360208285010111156119e757600080fd5b9250929050565b60008083601f840112611a0057600080fd5b50813567ffffffffffffffff811115611a1857600080fd5b6020830191508360208260051b85010111156119e757600080fd5b60008060008060008060006080888a031215611a4e57600080fd5b611a578861197c565b9650602088013567ffffffffffffffff80821115611a7457600080fd5b611a808b838c016119a5565b909850965060408a0135915080821115611a9957600080fd5b611aa58b838c016119a5565b909650945060608a0135915080821115611abe57600080fd5b50611acb8a828b016119ee565b989b979a50959850939692959293505050565b60006020808352835180602085015260005b81811015611b0c57858101830151858201604001528201611af0565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b600080600080600080600060a0888a031215611b6657600080fd5b87359650611b766020890161197c565b9550611b846040890161197c565b9450606088013567ffffffffffffffff80821115611ba157600080fd5b611bad8b838c016119a5565b909650945060808a0135915080821115611bc657600080fd5b50611acb8a828b016119a5565b600080600060608486031215611be857600080fd5b611bf18461197c565b92506020840135915060408401357fffff00000000000000000000000000000000000000000000000000000000000081168114611c2d57600080fd5b809150509250925092565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b81518152602082015160c082019060048110611cac577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b8060208401525073ffffffffffffffffffffffffffffffffffffffff604084015116604083015260608301511515606083015260808301511515608083015260a0830151611d0860a084018269ffffffffffffffffffff169052565b5092915050565b600060208284031215611d2157600080fd5b6116108261197c565b803563ffffffff811681146119a057600080fd5b600080600080600060808688031215611d5657600080fd5b611d5f86611d2a565b9450611d6d60208701611d2a565b9350604086013560ff81168114611d8357600080fd5b9250606086013567ffffffffffffffff811115611d9f57600080fd5b611dab888289016119ee565b969995985093965092949392505050565b60008060408385031215611dcf57600080fd5b611dd883611d2a565b9150611de660208401611d2a565b90509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60ff818116838216019081111561161357611613611def565b8183823760009101908152919050565b838152818360208301376000910160200190815292915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112611ec557600080fd5b83018035915067ffffffffffffffff821115611ee057600080fd5b6020019150368190038213156119e757600080fd5b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b602081526000611f52602083018486611ef5565b949350505050565b60008085851115611f6a57600080fd5b83861115611f7757600080fd5b5050820193919092039150565b80356020831015611613577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff602084900360031b1b1692915050565b878152600073ffffffffffffffffffffffffffffffffffffffff808916602084015280881660408401525060a0606083015261200060a083018688611ef5565b8281036080840152612013818587611ef5565b9a9950505050505050505050565b60006020828403121561203357600080fd5b8151801515811461204357600080fd5b9392505050565b8082018082111561161357611613611def565b8181038181111561161357611613611def565b604081526000612084604083018688611ef5565b8281036020840152610d3f818587611ef5565b60ff8181168382160290811690818114611d0857611d08611def565b60ff8416815260406020808301829052908201839052600090849060608401835b8681101561210d5773ffffffffffffffffffffffffffffffffffffffff6120fa8561197c565b16825292820192908201906001016120d4565b50979650505050505050565b60006040820160ff8516835260206040602085015281855180845260608601915060208701935060005b8181101561217557845173ffffffffffffffffffffffffffffffffffffffff1683529383019391830191600101612143565b509097965050505050505056fea164736f6c6343000818000a", } var KeystoneForwarderABI = KeystoneForwarderMetaData.ABI @@ -193,26 +202,26 @@ func (_KeystoneForwarder *KeystoneForwarderCallerSession) GetTransmissionId(rece return _KeystoneForwarder.Contract.GetTransmissionId(&_KeystoneForwarder.CallOpts, receiver, workflowExecutionId, reportId) } -func (_KeystoneForwarder *KeystoneForwarderCaller) GetTransmissionState(opts *bind.CallOpts, receiver common.Address, workflowExecutionId [32]byte, reportId [2]byte) (uint8, error) { +func (_KeystoneForwarder *KeystoneForwarderCaller) GetTransmissionInfo(opts *bind.CallOpts, receiver common.Address, workflowExecutionId [32]byte, reportId [2]byte) (IRouterTransmissionInfo, error) { var out []interface{} - err := _KeystoneForwarder.contract.Call(opts, &out, "getTransmissionState", receiver, workflowExecutionId, reportId) + err := _KeystoneForwarder.contract.Call(opts, &out, "getTransmissionInfo", receiver, workflowExecutionId, reportId) if err != nil { - return *new(uint8), err + return *new(IRouterTransmissionInfo), err } - out0 := *abi.ConvertType(out[0], new(uint8)).(*uint8) + out0 := *abi.ConvertType(out[0], new(IRouterTransmissionInfo)).(*IRouterTransmissionInfo) return out0, err } -func (_KeystoneForwarder *KeystoneForwarderSession) GetTransmissionState(receiver common.Address, workflowExecutionId [32]byte, reportId [2]byte) (uint8, error) { - return _KeystoneForwarder.Contract.GetTransmissionState(&_KeystoneForwarder.CallOpts, receiver, workflowExecutionId, reportId) +func (_KeystoneForwarder *KeystoneForwarderSession) GetTransmissionInfo(receiver common.Address, workflowExecutionId [32]byte, reportId [2]byte) (IRouterTransmissionInfo, error) { + return _KeystoneForwarder.Contract.GetTransmissionInfo(&_KeystoneForwarder.CallOpts, receiver, workflowExecutionId, reportId) } -func (_KeystoneForwarder *KeystoneForwarderCallerSession) GetTransmissionState(receiver common.Address, workflowExecutionId [32]byte, reportId [2]byte) (uint8, error) { - return _KeystoneForwarder.Contract.GetTransmissionState(&_KeystoneForwarder.CallOpts, receiver, workflowExecutionId, reportId) +func (_KeystoneForwarder *KeystoneForwarderCallerSession) GetTransmissionInfo(receiver common.Address, workflowExecutionId [32]byte, reportId [2]byte) (IRouterTransmissionInfo, error) { + return _KeystoneForwarder.Contract.GetTransmissionInfo(&_KeystoneForwarder.CallOpts, receiver, workflowExecutionId, reportId) } func (_KeystoneForwarder *KeystoneForwarderCaller) GetTransmitter(opts *bind.CallOpts, receiver common.Address, workflowExecutionId [32]byte, reportId [2]byte) (common.Address, error) { @@ -1260,7 +1269,7 @@ func (_KeystoneForwarder *KeystoneForwarder) Address() common.Address { type KeystoneForwarderInterface interface { GetTransmissionId(opts *bind.CallOpts, receiver common.Address, workflowExecutionId [32]byte, reportId [2]byte) ([32]byte, error) - GetTransmissionState(opts *bind.CallOpts, receiver common.Address, workflowExecutionId [32]byte, reportId [2]byte) (uint8, error) + GetTransmissionInfo(opts *bind.CallOpts, receiver common.Address, workflowExecutionId [32]byte, reportId [2]byte) (IRouterTransmissionInfo, error) GetTransmitter(opts *bind.CallOpts, receiver common.Address, workflowExecutionId [32]byte, reportId [2]byte) (common.Address, error) diff --git a/core/gethwrappers/keystone/generated/ocr3_capability/ocr3_capability.go b/core/gethwrappers/keystone/generated/ocr3_capability/ocr3_capability.go index e51f4762d8..21aeb4975c 100644 --- a/core/gethwrappers/keystone/generated/ocr3_capability/ocr3_capability.go +++ b/core/gethwrappers/keystone/generated/ocr3_capability/ocr3_capability.go @@ -31,8 +31,8 @@ var ( ) var OCR3CapabilityMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"message\",\"type\":\"string\"}],\"name\":\"InvalidConfig\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"message\",\"type\":\"string\"}],\"name\":\"ReportInvalid\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ReportingUnsupported\",\"type\":\"error\"},{\"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\":\"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\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"epoch\",\"type\":\"uint32\"}],\"name\":\"Transmitted\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"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\":\"_onchainConfig\",\"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\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32[3]\",\"name\":\"reportContext\",\"type\":\"bytes32[3]\"},{\"internalType\":\"bytes\",\"name\":\"report\",\"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\":\"transmitters\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}]", - Bin: "0x60806040523480156200001157600080fd5b503380600081620000695760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b03848116919091179091558116156200009c576200009c81620000a5565b50505062000150565b336001600160a01b03821603620000ff5760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640162000060565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b611fe180620001606000396000f3fe608060405234801561001057600080fd5b50600436106100a35760003560e01c80638da5cb5b11610076578063b1dc65a41161005b578063b1dc65a4146101c4578063e3d0e712146101d7578063f2fde38b146101ea57600080fd5b80638da5cb5b1461017c578063afcb95d7146101a457600080fd5b8063181f5a77146100a857806379ba5097146100f057806381411834146100fa57806381ff70481461010f575b600080fd5b604080518082018252600e81527f4b657973746f6e6520312e302e30000000000000000000000000000000000000602082015290516100e79190611883565b60405180910390f35b6100f86101fd565b005b6101026102ff565b6040516100e791906118ef565b61015960015460025463ffffffff74010000000000000000000000000000000000000000830481169378010000000000000000000000000000000000000000000000009093041691565b6040805163ffffffff9485168152939092166020840152908201526060016100e7565b60005460405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100e7565b6040805160018152600060208201819052918101919091526060016100e7565b6100f86101d236600461194e565b61036e565b6100f86101e5366004611c18565b610975565b6100f86101f8366004611ce5565b6114e0565b60015473ffffffffffffffffffffffffffffffffffffffff163314610283576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b6060600680548060200260200160405190810160405280929190818152602001828054801561036457602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff168152600190910190602001808311610339575b5050505050905090565b60005a604080518b3580825262ffffff6020808f0135600881901c929092169084015293945092917fb04e63db38c49950639fa09d29872f21f5d49d614f3a969d8adf3d4b52e41a62910160405180910390a16103cf8a8a8a8a8a8a6114f4565b6003546000906002906103ed9060ff80821691610100900416611d5e565b6103f79190611d7d565b610402906001611d5e565b60ff169050878114610470576040517f660bd4ba00000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f77726f6e67206e756d626572206f66207369676e617475726573000000000000604482015260640161027a565b8786146104ff576040517f660bd4ba00000000000000000000000000000000000000000000000000000000815260206004820152602860248201527f7265706f727420727320616e64207373206d757374206265206f66206571756160448201527f6c206c656e677468000000000000000000000000000000000000000000000000606482015260840161027a565b3360009081526004602090815260408083208151808301909252805460ff8082168452929391929184019161010090910416600281111561054257610542611dc6565b600281111561055357610553611dc6565b905250905060028160200151600281111561057057610570611dc6565b141580156105b957506006816000015160ff168154811061059357610593611d00565b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff163314155b15610620576040517f660bd4ba00000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f756e617574686f72697a6564207472616e736d69747465720000000000000000604482015260640161027a565b5050505061062c611800565b6000808a8a60405161063f929190611df5565b604051908190038120610656918e90602001611e05565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181528282528051602091820120838301909252600080845290830152915060005b898110156109575760006001848984602081106106bf576106bf611d00565b6106cc91901a601b611d5e565b8e8e868181106106de576106de611d00565b905060200201358d8d878181106106f7576106f7611d00565b9050602002013560405160008152602001604052604051610734949392919093845260ff9290921660208401526040830152606082015260800190565b6020604051602081039080840390855afa158015610756573d6000803e3d6000fd5b5050604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081015173ffffffffffffffffffffffffffffffffffffffff811660009081526004602090815290849020838501909452835460ff808216855292965092945084019161010090041660028111156107d6576107d6611dc6565b60028111156107e7576107e7611dc6565b905250925060018360200151600281111561080457610804611dc6565b1461086b576040517f660bd4ba00000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f61646472657373206e6f7420617574686f72697a656420746f207369676e0000604482015260640161027a565b8251600090879060ff16601f811061088557610885611d00565b602002015173ffffffffffffffffffffffffffffffffffffffff1614610907576040517f660bd4ba00000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f6e6f6e2d756e69717565207369676e6174757265000000000000000000000000604482015260640161027a565b8086846000015160ff16601f811061092157610921611d00565b73ffffffffffffffffffffffffffffffffffffffff909216602092909202015261094c600186611d5e565b9450506001016106a0565b505050610968833383858e8e6115ab565b5050505050505050505050565b855185518560ff16601f8311156109e8576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f746f6f206d616e79207369676e65727300000000000000000000000000000000604482015260640161027a565b80600003610a52576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f66206d75737420626520706f7369746976650000000000000000000000000000604482015260640161027a565b818314610ae0576040517f89a61989000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f6f7261636c6520616464726573736573206f7574206f6620726567697374726160448201527f74696f6e00000000000000000000000000000000000000000000000000000000606482015260840161027a565b610aeb816003611e19565b8311610b53576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f6661756c74792d6f7261636c65206620746f6f20686967680000000000000000604482015260640161027a565b610b5b6115dd565b6040805160c0810182528a8152602081018a905260ff8916918101919091526060810187905267ffffffffffffffff8616608082015260a081018590525b60055415610d4e57600554600090610bb390600190611e30565b9050600060058281548110610bca57610bca611d00565b60009182526020822001546006805473ffffffffffffffffffffffffffffffffffffffff90921693509084908110610c0457610c04611d00565b600091825260208083209091015473ffffffffffffffffffffffffffffffffffffffff85811684526004909252604080842080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000090811690915592909116808452922080549091169055600580549192509080610c8457610c84611e43565b60008281526020902081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90810180547fffffffffffffffffffffffff00000000000000000000000000000000000000001690550190556006805480610ced57610ced611e43565b60008281526020902081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90810180547fffffffffffffffffffffffff000000000000000000000000000000000000000016905501905550610b99915050565b60005b8151518110156112fd57815180516000919083908110610d7357610d73611d00565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1603610df8576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f7369676e6572206d757374206e6f7420626520656d7074790000000000000000604482015260640161027a565b600073ffffffffffffffffffffffffffffffffffffffff1682602001518281518110610e2657610e26611d00565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1603610eab576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f7472616e736d6974746572206d757374206e6f7420626520656d707479000000604482015260640161027a565b60006004600084600001518481518110610ec757610ec7611d00565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff16825281019190915260400160002054610100900460ff166002811115610f1157610f11611dc6565b14610f78576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f7265706561746564207369676e65722061646472657373000000000000000000604482015260640161027a565b6040805180820190915260ff82168152600160208201528251805160049160009185908110610fa957610fa9611d00565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff168252818101929092526040016000208251815460ff9091167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082168117835592840151919283917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000161761010083600281111561104a5761104a611dc6565b02179055506000915061105a9050565b600460008460200151848151811061107457611074611d00565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff16825281019190915260400160002054610100900460ff1660028111156110be576110be611dc6565b14611125576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f7265706561746564207472616e736d6974746572206164647265737300000000604482015260640161027a565b6040805180820190915260ff82168152602081016002815250600460008460200151848151811061115857611158611d00565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff168252818101929092526040016000208251815460ff9091167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082168117835592840151919283917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000016176101008360028111156111f9576111f9611dc6565b02179055505082518051600592508390811061121757611217611d00565b602090810291909101810151825460018101845560009384529282902090920180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff909316929092179091558201518051600691908390811061129357611293611d00565b60209081029190910181015182546001808201855560009485529290932090920180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff9093169290921790915501610d51565b506040810151600380547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660ff909216919091179055600180547fffffffff00000000ffffffffffffffffffffffffffffffffffffffffffffffff8116780100000000000000000000000000000000000000000000000063ffffffff43811682029290921780855592048116929182916014916113b591849174010000000000000000000000000000000000000000900416611e72565b92506101000a81548163ffffffff021916908363ffffffff1602179055506114144630600160149054906101000a900463ffffffff1663ffffffff16856000015186602001518760400151886060015189608001518a60a00151611660565b600281905582518051600380547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff1661010060ff9093169290920291909117905560015460208501516040808701516060880151608089015160a08a015193517f1591690b8638f5fb2dbec82ac741805ac5da8b45dc5263f4875b0496fdce4e05986114cb988b9891977401000000000000000000000000000000000000000090920463ffffffff16969095919491939192611e96565b60405180910390a15050505050505050505050565b6114e86115dd565b6114f18161170b565b50565b6000611501826020611e19565b61150c856020611e19565b61151888610144611f2c565b6115229190611f2c565b61152c9190611f2c565b611537906000611f2c565b90503681146115a2576040517f660bd4ba00000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f63616c6c64617461206c656e677468206d69736d617463680000000000000000604482015260640161027a565b50505050505050565b6040517f0750181900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005473ffffffffffffffffffffffffffffffffffffffff16331461165e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e657200000000000000000000604482015260640161027a565b565b6000808a8a8a8a8a8a8a8a8a60405160200161168499989796959493929190611f3f565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291905280516020909101207dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e01000000000000000000000000000000000000000000000000000000000000179150509998505050505050505050565b3373ffffffffffffffffffffffffffffffffffffffff82160361178a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640161027a565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b604051806103e00160405280601f906020820280368337509192915050565b6000815180845260005b8181101561184557602081850181015186830182015201611829565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b602081526000611896602083018461181f565b9392505050565b60008151808452602080850194506020840160005b838110156118e457815173ffffffffffffffffffffffffffffffffffffffff16875295820195908201906001016118b2565b509495945050505050565b602081526000611896602083018461189d565b60008083601f84011261191457600080fd5b50813567ffffffffffffffff81111561192c57600080fd5b6020830191508360208260051b850101111561194757600080fd5b9250929050565b60008060008060008060008060e0898b03121561196a57600080fd5b606089018a81111561197b57600080fd5b8998503567ffffffffffffffff8082111561199557600080fd5b818b0191508b601f8301126119a957600080fd5b8135818111156119b857600080fd5b8c60208285010111156119ca57600080fd5b6020830199508098505060808b01359150808211156119e857600080fd5b6119f48c838d01611902565b909750955060a08b0135915080821115611a0d57600080fd5b50611a1a8b828c01611902565b999c989b50969995989497949560c00135949350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715611aa957611aa9611a33565b604052919050565b803573ffffffffffffffffffffffffffffffffffffffff81168114611ad557600080fd5b919050565b600082601f830112611aeb57600080fd5b8135602067ffffffffffffffff821115611b0757611b07611a33565b8160051b611b16828201611a62565b9283528481018201928281019087851115611b3057600080fd5b83870192505b84831015611b5657611b4783611ab1565b82529183019190830190611b36565b979650505050505050565b803560ff81168114611ad557600080fd5b600082601f830112611b8357600080fd5b813567ffffffffffffffff811115611b9d57611b9d611a33565b611bce60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601611a62565b818152846020838601011115611be357600080fd5b816020850160208301376000918101602001919091529392505050565b803567ffffffffffffffff81168114611ad557600080fd5b60008060008060008060c08789031215611c3157600080fd5b863567ffffffffffffffff80821115611c4957600080fd5b611c558a838b01611ada565b97506020890135915080821115611c6b57600080fd5b611c778a838b01611ada565b9650611c8560408a01611b61565b95506060890135915080821115611c9b57600080fd5b611ca78a838b01611b72565b9450611cb560808a01611c00565b935060a0890135915080821115611ccb57600080fd5b50611cd889828a01611b72565b9150509295509295509295565b600060208284031215611cf757600080fd5b61189682611ab1565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60ff8181168382160190811115611d7757611d77611d2f565b92915050565b600060ff831680611db7577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b8060ff84160491505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b8183823760009101908152919050565b828152606082602083013760800192915050565b8082028115828204841417611d7757611d77611d2f565b81810381811115611d7757611d77611d2f565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b63ffffffff818116838216019080821115611e8f57611e8f611d2f565b5092915050565b600061012063ffffffff808d1684528b6020850152808b16604085015250806060840152611ec68184018a61189d565b90508281036080840152611eda818961189d565b905060ff871660a084015282810360c0840152611ef7818761181f565b905067ffffffffffffffff851660e0840152828103610100840152611f1c818561181f565b9c9b505050505050505050505050565b80820180821115611d7757611d77611d2f565b60006101208b835273ffffffffffffffffffffffffffffffffffffffff8b16602084015267ffffffffffffffff808b166040850152816060850152611f868285018b61189d565b91508382036080850152611f9a828a61189d565b915060ff881660a085015283820360c0850152611fb7828861181f565b90861660e08501528381036101008501529050611f1c818561181f56fea164736f6c6343000818000a", + ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"message\",\"type\":\"string\"}],\"name\":\"InvalidConfig\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ReportingUnsupported\",\"type\":\"error\"},{\"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\":\"bytes[]\",\"name\":\"signers\",\"type\":\"bytes[]\"},{\"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\":\"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\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"epoch\",\"type\":\"uint32\"}],\"name\":\"Transmitted\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"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\":\"bytes[]\",\"name\":\"_signers\",\"type\":\"bytes[]\"},{\"internalType\":\"address[]\",\"name\":\"_transmitters\",\"type\":\"address[]\"},{\"internalType\":\"uint8\",\"name\":\"_f\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"_onchainConfig\",\"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\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32[3]\",\"name\":\"\",\"type\":\"bytes32[3]\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"bytes32[]\",\"name\":\"\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"transmit\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}]", + Bin: "0x608060405234801561001057600080fd5b5033806000816100675760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615610097576100978161009f565b505050610148565b336001600160a01b038216036100f75760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640161005e565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b611266806101576000396000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c80638da5cb5b1161005b5780638da5cb5b1461015f578063afcb95d714610187578063b1dc65a4146101a7578063f2fde38b146101ba57600080fd5b8063181f5a771461008d57806379ba5097146100d55780637f3c87d3146100df57806381ff7048146100f2575b600080fd5b604080518082018252600e81527f4b657973746f6e6520312e302e30000000000000000000000000000000000000602082015290516100cc9190610b02565b60405180910390f35b6100dd6101cd565b005b6100dd6100ed366004610c70565b6102cf565b61013c60015460025463ffffffff74010000000000000000000000000000000000000000830481169378010000000000000000000000000000000000000000000000009093041691565b6040805163ffffffff9485168152939092166020840152908201526060016100cc565b60005460405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100cc565b6040805160018152600060208201819052918101919091526060016100cc565b6100dd6101b5366004610d4c565b61082f565b6100dd6101c8366004610e55565b610861565b60015473ffffffffffffffffffffffffffffffffffffffff163314610253576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b868560ff8616601f831115610340576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f746f6f206d616e79207369676e65727300000000000000000000000000000000604482015260640161024a565b806000036103aa576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f66206d75737420626520706f7369746976650000000000000000000000000000604482015260640161024a565b818314610438576040517f89a61989000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f6f7261636c6520616464726573736573206f7574206f6620726567697374726160448201527f74696f6e00000000000000000000000000000000000000000000000000000000606482015260840161024a565b610443816003610e9f565b83116104ab576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f6661756c74792d6f7261636c65206620746f6f20686967680000000000000000604482015260640161024a565b6104b3610875565b60005b8a81101561069d5760008a8a838181106104d2576104d2610ebc565b90506020020160208101906104e79190610e55565b73ffffffffffffffffffffffffffffffffffffffff1603610564576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f7472616e736d6974746572206d757374206e6f7420626520656d707479000000604482015260640161024a565b3660008d8d8481811061057957610579610ebc565b905060200281019061058b9190610eeb565b90925090506000815b8061ffff168261ffff16101561068d57600084848461ffff168181106105bc576105bc610ebc565b919091013560f81c915060009050600886866105d9876002610f50565b61ffff168181106105ec576105ec610ebc565b919091013560f81c90911b90508686610606876001610f50565b61ffff1681811061061957610619610ebc565b61062a9392013560f81c9050610f50565b9050366000878761063c886003610f50565b61ffff16908561064d8a6003610f50565b6106579190610f50565b61ffff169261066893929190610f72565b9092509050610678836003610f50565b6106829087610f50565b955050505050610594565b5050600190920191506104b69050565b50600380547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660ff8916179055600180547fffffffff00000000ffffffffffffffffffffffffffffffffffffffffffffffff8116780100000000000000000000000000000000000000000000000063ffffffff438116820292909217808555920481169291829160149161074c91849174010000000000000000000000000000000000000000900416610f9c565b92506101000a81548163ffffffff021916908363ffffffff1602179055506107954630600160149054906101000a900463ffffffff1663ffffffff168f8f8f8f8f8f8f8f6108f8565b6002600001819055508b8b9050600260010160016101000a81548160ff021916908360ff1602179055507f36257c6e8d535293ad661e377c0baac536289be6707b8a488ac175ddaa4055c881600260000154600160149054906101000a900463ffffffff168f8f8f8f8f8f8f8f6040516108199b9a99989796959493929190611128565b60405180910390a1505050505050505050505050565b6040517f0750181900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610869610875565b610872816109a9565b50565b60005473ffffffffffffffffffffffffffffffffffffffff1633146108f6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e657200000000000000000000604482015260640161024a565b565b6000808c8c8c8c8c8c8c8c8c8c8c6040516020016109209b9a999897969594939291906111c2565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291905280516020909101207dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e0e000000000000000000000000000000000000000000000000000000000000179150509b9a5050505050505050505050565b3373ffffffffffffffffffffffffffffffffffffffff821603610a28576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640161024a565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6000815180845260005b81811015610ac457602081850181015186830182015201610aa8565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b602081526000610b156020830184610a9e565b9392505050565b60008083601f840112610b2e57600080fd5b50813567ffffffffffffffff811115610b4657600080fd5b6020830191508360208260051b8501011115610b6157600080fd5b9250929050565b803560ff81168114610b7957600080fd5b919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600082601f830112610bbe57600080fd5b813567ffffffffffffffff80821115610bd957610bd9610b7e565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f01168101908282118183101715610c1f57610c1f610b7e565b81604052838152866020858801011115610c3857600080fd5b836020870160208301376000602085830101528094505050505092915050565b803567ffffffffffffffff81168114610b7957600080fd5b60008060008060008060008060c0898b031215610c8c57600080fd5b883567ffffffffffffffff80821115610ca457600080fd5b610cb08c838d01610b1c565b909a50985060208b0135915080821115610cc957600080fd5b610cd58c838d01610b1c565b9098509650869150610ce960408c01610b68565b955060608b0135915080821115610cff57600080fd5b610d0b8c838d01610bad565b9450610d1960808c01610c58565b935060a08b0135915080821115610d2f57600080fd5b50610d3c8b828c01610bad565b9150509295985092959890939650565b60008060008060008060008060e0898b031215610d6857600080fd5b606089018a811115610d7957600080fd5b8998503567ffffffffffffffff80821115610d9357600080fd5b818b0191508b601f830112610da757600080fd5b813581811115610db657600080fd5b8c6020828501011115610dc857600080fd5b6020830199508098505060808b0135915080821115610de657600080fd5b610df28c838d01610b1c565b909750955060a08b0135915080821115610e0b57600080fd5b50610e188b828c01610b1c565b999c989b50969995989497949560c00135949350505050565b803573ffffffffffffffffffffffffffffffffffffffff81168114610b7957600080fd5b600060208284031215610e6757600080fd5b610b1582610e31565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8082028115828204841417610eb657610eb6610e70565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112610f2057600080fd5b83018035915067ffffffffffffffff821115610f3b57600080fd5b602001915036819003821315610b6157600080fd5b61ffff818116838216019080821115610f6b57610f6b610e70565b5092915050565b60008085851115610f8257600080fd5b83861115610f8f57600080fd5b5050820193919092039150565b63ffffffff818116838216019080821115610f6b57610f6b610e70565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b6000838385526020808601955060208560051b8301018460005b878110156110c7577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe085840301895281357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe188360301811261107d57600080fd5b8701848101903567ffffffffffffffff81111561109957600080fd5b8036038213156110a857600080fd5b6110b3858284610fb9565b9a86019a945050509083019060010161101c565b5090979650505050505050565b8183526000602080850194508260005b8581101561111d5773ffffffffffffffffffffffffffffffffffffffff61110a83610e31565b16875295820195908201906001016110e4565b509495945050505050565b600061012063ffffffff808f1684528d6020850152808d166040850152508060608401526111598184018b8d611002565b9050828103608084015261116e81898b6110d4565b905060ff871660a084015282810360c084015261118b8187610a9e565b905067ffffffffffffffff851660e08401528281036101008401526111b08185610a9e565b9e9d5050505050505050505050505050565b60006101208d835273ffffffffffffffffffffffffffffffffffffffff8d16602084015267ffffffffffffffff808d16604085015281606085015261120a8285018c8e611002565b9150838203608085015261121f828a8c6110d4565b915060ff881660a085015283820360c085015261123c8288610a9e565b90861660e085015283810361010085015290506111b08185610a9e56fea164736f6c6343000818000a", } var OCR3CapabilityABI = OCR3CapabilityMetaData.ABI @@ -255,26 +255,24 @@ func (_OCR3Capability *OCR3CapabilityCallerSession) Owner() (common.Address, err return _OCR3Capability.Contract.Owner(&_OCR3Capability.CallOpts) } -func (_OCR3Capability *OCR3CapabilityCaller) Transmitters(opts *bind.CallOpts) ([]common.Address, error) { +func (_OCR3Capability *OCR3CapabilityCaller) Transmit(opts *bind.CallOpts, arg0 [3][32]byte, arg1 []byte, arg2 [][32]byte, arg3 [][32]byte, arg4 [32]byte) error { var out []interface{} - err := _OCR3Capability.contract.Call(opts, &out, "transmitters") + err := _OCR3Capability.contract.Call(opts, &out, "transmit", arg0, arg1, arg2, arg3, arg4) if err != nil { - return *new([]common.Address), err + return err } - out0 := *abi.ConvertType(out[0], new([]common.Address)).(*[]common.Address) - - return out0, err + return err } -func (_OCR3Capability *OCR3CapabilitySession) Transmitters() ([]common.Address, error) { - return _OCR3Capability.Contract.Transmitters(&_OCR3Capability.CallOpts) +func (_OCR3Capability *OCR3CapabilitySession) Transmit(arg0 [3][32]byte, arg1 []byte, arg2 [][32]byte, arg3 [][32]byte, arg4 [32]byte) error { + return _OCR3Capability.Contract.Transmit(&_OCR3Capability.CallOpts, arg0, arg1, arg2, arg3, arg4) } -func (_OCR3Capability *OCR3CapabilityCallerSession) Transmitters() ([]common.Address, error) { - return _OCR3Capability.Contract.Transmitters(&_OCR3Capability.CallOpts) +func (_OCR3Capability *OCR3CapabilityCallerSession) Transmit(arg0 [3][32]byte, arg1 []byte, arg2 [][32]byte, arg3 [][32]byte, arg4 [32]byte) error { + return _OCR3Capability.Contract.Transmit(&_OCR3Capability.CallOpts, arg0, arg1, arg2, arg3, arg4) } func (_OCR3Capability *OCR3CapabilityCaller) TypeAndVersion(opts *bind.CallOpts) (string, error) { @@ -311,15 +309,15 @@ func (_OCR3Capability *OCR3CapabilityTransactorSession) AcceptOwnership() (*type return _OCR3Capability.Contract.AcceptOwnership(&_OCR3Capability.TransactOpts) } -func (_OCR3Capability *OCR3CapabilityTransactor) SetConfig(opts *bind.TransactOpts, _signers []common.Address, _transmitters []common.Address, _f uint8, _onchainConfig []byte, _offchainConfigVersion uint64, _offchainConfig []byte) (*types.Transaction, error) { +func (_OCR3Capability *OCR3CapabilityTransactor) SetConfig(opts *bind.TransactOpts, _signers [][]byte, _transmitters []common.Address, _f uint8, _onchainConfig []byte, _offchainConfigVersion uint64, _offchainConfig []byte) (*types.Transaction, error) { return _OCR3Capability.contract.Transact(opts, "setConfig", _signers, _transmitters, _f, _onchainConfig, _offchainConfigVersion, _offchainConfig) } -func (_OCR3Capability *OCR3CapabilitySession) SetConfig(_signers []common.Address, _transmitters []common.Address, _f uint8, _onchainConfig []byte, _offchainConfigVersion uint64, _offchainConfig []byte) (*types.Transaction, error) { +func (_OCR3Capability *OCR3CapabilitySession) SetConfig(_signers [][]byte, _transmitters []common.Address, _f uint8, _onchainConfig []byte, _offchainConfigVersion uint64, _offchainConfig []byte) (*types.Transaction, error) { return _OCR3Capability.Contract.SetConfig(&_OCR3Capability.TransactOpts, _signers, _transmitters, _f, _onchainConfig, _offchainConfigVersion, _offchainConfig) } -func (_OCR3Capability *OCR3CapabilityTransactorSession) SetConfig(_signers []common.Address, _transmitters []common.Address, _f uint8, _onchainConfig []byte, _offchainConfigVersion uint64, _offchainConfig []byte) (*types.Transaction, error) { +func (_OCR3Capability *OCR3CapabilityTransactorSession) SetConfig(_signers [][]byte, _transmitters []common.Address, _f uint8, _onchainConfig []byte, _offchainConfigVersion uint64, _offchainConfig []byte) (*types.Transaction, error) { return _OCR3Capability.Contract.SetConfig(&_OCR3Capability.TransactOpts, _signers, _transmitters, _f, _onchainConfig, _offchainConfigVersion, _offchainConfig) } @@ -335,18 +333,6 @@ func (_OCR3Capability *OCR3CapabilityTransactorSession) TransferOwnership(to com return _OCR3Capability.Contract.TransferOwnership(&_OCR3Capability.TransactOpts, to) } -func (_OCR3Capability *OCR3CapabilityTransactor) Transmit(opts *bind.TransactOpts, reportContext [3][32]byte, report []byte, rs [][32]byte, ss [][32]byte, rawVs [32]byte) (*types.Transaction, error) { - return _OCR3Capability.contract.Transact(opts, "transmit", reportContext, report, rs, ss, rawVs) -} - -func (_OCR3Capability *OCR3CapabilitySession) Transmit(reportContext [3][32]byte, report []byte, rs [][32]byte, ss [][32]byte, rawVs [32]byte) (*types.Transaction, error) { - return _OCR3Capability.Contract.Transmit(&_OCR3Capability.TransactOpts, reportContext, report, rs, ss, rawVs) -} - -func (_OCR3Capability *OCR3CapabilityTransactorSession) Transmit(reportContext [3][32]byte, report []byte, rs [][32]byte, ss [][32]byte, rawVs [32]byte) (*types.Transaction, error) { - return _OCR3Capability.Contract.Transmit(&_OCR3Capability.TransactOpts, reportContext, report, rs, ss, rawVs) -} - type OCR3CapabilityConfigSetIterator struct { Event *OCR3CapabilityConfigSet @@ -411,7 +397,7 @@ type OCR3CapabilityConfigSet struct { PreviousConfigBlockNumber uint32 ConfigDigest [32]byte ConfigCount uint64 - Signers []common.Address + Signers [][]byte Transmitters []common.Address F uint8 OnchainConfig []byte @@ -890,7 +876,7 @@ func (_OCR3Capability *OCR3Capability) ParseLog(log types.Log) (generated.Abigen } func (OCR3CapabilityConfigSet) Topic() common.Hash { - return common.HexToHash("0x1591690b8638f5fb2dbec82ac741805ac5da8b45dc5263f4875b0496fdce4e05") + return common.HexToHash("0x36257c6e8d535293ad661e377c0baac536289be6707b8a488ac175ddaa4055c8") } func (OCR3CapabilityOwnershipTransferRequested) Topic() common.Hash { @@ -920,18 +906,16 @@ type OCR3CapabilityInterface interface { Owner(opts *bind.CallOpts) (common.Address, error) - Transmitters(opts *bind.CallOpts) ([]common.Address, error) + Transmit(opts *bind.CallOpts, arg0 [3][32]byte, arg1 []byte, arg2 [][32]byte, arg3 [][32]byte, arg4 [32]byte) error TypeAndVersion(opts *bind.CallOpts) (string, error) AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) - SetConfig(opts *bind.TransactOpts, _signers []common.Address, _transmitters []common.Address, _f uint8, _onchainConfig []byte, _offchainConfigVersion uint64, _offchainConfig []byte) (*types.Transaction, error) + SetConfig(opts *bind.TransactOpts, _signers [][]byte, _transmitters []common.Address, _f uint8, _onchainConfig []byte, _offchainConfigVersion uint64, _offchainConfig []byte) (*types.Transaction, error) TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) - Transmit(opts *bind.TransactOpts, reportContext [3][32]byte, report []byte, rs [][32]byte, ss [][32]byte, rawVs [32]byte) (*types.Transaction, error) - FilterConfigSet(opts *bind.FilterOpts) (*OCR3CapabilityConfigSetIterator, error) WatchConfigSet(opts *bind.WatchOpts, sink chan<- *OCR3CapabilityConfigSet) (event.Subscription, error) diff --git a/core/gethwrappers/keystone/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/core/gethwrappers/keystone/generation/generated-wrapper-dependency-versions-do-not-edit.txt index 40cf539277..74d3b73e6a 100644 --- a/core/gethwrappers/keystone/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/core/gethwrappers/keystone/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -1,5 +1,5 @@ GETH_VERSION: 1.13.8 -capabilities_registry: ../../../contracts/solc/v0.8.24/CapabilitiesRegistry/CapabilitiesRegistry.abi ../../../contracts/solc/v0.8.24/CapabilitiesRegistry/CapabilitiesRegistry.bin 6d2e3aa3a6f3aed2cf24b613743bb9ae4b9558f48a6864dc03b8b0ebb37235e3 -feeds_consumer: ../../../contracts/solc/v0.8.24/KeystoneFeedsConsumer/KeystoneFeedsConsumer.abi ../../../contracts/solc/v0.8.24/KeystoneFeedsConsumer/KeystoneFeedsConsumer.bin 8c3a2b18a80be41e7c40d2bc3a4c8d1b5e18d55c1fd20ad5af68cebb66109fc5 -forwarder: ../../../contracts/solc/v0.8.24/KeystoneForwarder/KeystoneForwarder.abi ../../../contracts/solc/v0.8.24/KeystoneForwarder/KeystoneForwarder.bin dc98a86a3775ead987b79d5b6079ee0e26f31c0626032bdd6508f986e2423227 -ocr3_capability: ../../../contracts/solc/v0.8.24/OCR3Capability/OCR3Capability.abi ../../../contracts/solc/v0.8.24/OCR3Capability/OCR3Capability.bin 8bf0f53f222efce7143dea6134552eb26ea1eef845407b4475a0d79b7d7ba9f8 +capabilities_registry: ../../../contracts/solc/v0.8.24/CapabilitiesRegistry/CapabilitiesRegistry.abi ../../../contracts/solc/v0.8.24/CapabilitiesRegistry/CapabilitiesRegistry.bin cb3e79280a928979bc37de154b12b876996bdbe10f1827e683ee2bfa7a429a6c +feeds_consumer: ../../../contracts/solc/v0.8.24/KeystoneFeedsConsumer/KeystoneFeedsConsumer.abi ../../../contracts/solc/v0.8.24/KeystoneFeedsConsumer/KeystoneFeedsConsumer.bin 6ac5b12eff3b022a35c3c40d5ed0285bf9bfec0e3669a4b12307332a216048ca +forwarder: ../../../contracts/solc/v0.8.24/KeystoneForwarder/KeystoneForwarder.abi ../../../contracts/solc/v0.8.24/KeystoneForwarder/KeystoneForwarder.bin 03911334d0c88f8ee8ee2d9832fd312bc8a48c824fcda5c807585af2d0e6a148 +ocr3_capability: ../../../contracts/solc/v0.8.24/OCR3Capability/OCR3Capability.abi ../../../contracts/solc/v0.8.24/OCR3Capability/OCR3Capability.bin 2a6bfae30ccf38327fc7e78605a226839dfa0ce5a1a22e0414b91d24c35b3a53 diff --git a/core/gethwrappers/llo-feeds/generated/channel_config_store/channel_config_store.go b/core/gethwrappers/llo-feeds/generated/channel_config_store/channel_config_store.go index 3c67b64227..7dd4407b3a 100644 --- a/core/gethwrappers/llo-feeds/generated/channel_config_store/channel_config_store.go +++ b/core/gethwrappers/llo-feeds/generated/channel_config_store/channel_config_store.go @@ -30,15 +30,9 @@ var ( _ = abi.ConvertType ) -type IChannelConfigStoreChannelDefinition struct { - ReportFormat uint32 - ChainSelector uint64 - StreamIDs []uint32 -} - var ChannelConfigStoreMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"ChannelDefinitionNotFound\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"EmptyStreamIDs\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyCallableByEOA\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"StagingConfigAlreadyPromoted\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroChainSelector\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroReportFormat\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"channelId\",\"type\":\"uint32\"}],\"name\":\"ChannelDefinitionRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"channelId\",\"type\":\"uint32\"},{\"components\":[{\"internalType\":\"uint32\",\"name\":\"reportFormat\",\"type\":\"uint32\"},{\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint32[]\",\"name\":\"streamIDs\",\"type\":\"uint32[]\"}],\"indexed\":false,\"internalType\":\"structIChannelConfigStore.ChannelDefinition\",\"name\":\"channelDefinition\",\"type\":\"tuple\"}],\"name\":\"NewChannelDefinition\",\"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\":\"uint32\",\"name\":\"channelId\",\"type\":\"uint32\"}],\"name\":\"PromoteStagingConfig\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"channelId\",\"type\":\"uint32\"},{\"components\":[{\"internalType\":\"uint32\",\"name\":\"reportFormat\",\"type\":\"uint32\"},{\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint32[]\",\"name\":\"streamIDs\",\"type\":\"uint32[]\"}],\"internalType\":\"structIChannelConfigStore.ChannelDefinition\",\"name\":\"channelDefinition\",\"type\":\"tuple\"}],\"name\":\"addChannel\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"channelId\",\"type\":\"uint32\"}],\"name\":\"getChannelDefinitions\",\"outputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"reportFormat\",\"type\":\"uint32\"},{\"internalType\":\"uint64\",\"name\":\"chainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint32[]\",\"name\":\"streamIDs\",\"type\":\"uint32[]\"}],\"internalType\":\"structIChannelConfigStore.ChannelDefinition\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"channelId\",\"type\":\"uint32\"}],\"name\":\"removeChannel\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}]", - Bin: "0x608060405234801561001057600080fd5b5033806000816100675760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615610097576100978161009f565b505050610148565b336001600160a01b038216036100f75760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640161005e565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b610e4f806101576000396000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c80638da5cb5b1161005b5780638da5cb5b146101535780639682a4501461017b578063f2fde38b1461018e578063f5810719146101a157600080fd5b806301ffc9a71461008d578063181f5a77146100f757806379ba5097146101365780637e37e71914610140575b600080fd5b6100e261009b3660046107d1565b7fffffffff00000000000000000000000000000000000000000000000000000000167f1d344450000000000000000000000000000000000000000000000000000000001490565b60405190151581526020015b60405180910390f35b604080518082018252601881527f4368616e6e656c436f6e66696753746f726520302e302e300000000000000000602082015290516100ee919061081a565b61013e6101c1565b005b61013e61014e366004610898565b6102c3565b60005460405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100ee565b61013e6101893660046108b5565b6103a3565b61013e61019c36600461090c565b6104f3565b6101b46101af366004610898565b610507565b6040516100ee9190610942565b60015473ffffffffffffffffffffffffffffffffffffffff163314610247576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b6102cb610620565b63ffffffff8116600090815260026020526040812060010154900361031c576040517fd1a751e200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b63ffffffff8116600090815260026020526040812080547fffffffffffffffffffffffffffffffffffffffff000000000000000000000000168155906103656001830182610798565b505060405163ffffffff821681527f334e877e9691ecae0660510061973bebaa8b4fb37332ed6090052e630c9798619060200160405180910390a150565b6103ab610620565b6103b860408201826109bc565b90506000036103f3576040517f4b620e2400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6104036040820160208301610a41565b67ffffffffffffffff16600003610446576040517ff89d762900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6104536020820182610898565b63ffffffff16600003610492576040517febd3ef0200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b63ffffffff8216600090815260026020526040902081906104b38282610c37565b9050507f35d63e43dd8abd374a4c4e0b5b02c8294dd20e1f493e7344a1751123d11ecc1482826040516104e7929190610d7f565b60405180910390a15050565b6104fb610620565b610504816106a3565b50565b6040805160608082018352600080835260208301529181019190915233321461055c576040517f74e2cd5100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b63ffffffff82811660009081526002602090815260409182902082516060810184528154948516815264010000000090940467ffffffffffffffff16848301526001810180548451818502810185018652818152929486019383018282801561061057602002820191906000526020600020906000905b82829054906101000a900463ffffffff1663ffffffff16815260200190600401906020826003010492830192600103820291508084116105d35790505b5050505050815250509050919050565b60005473ffffffffffffffffffffffffffffffffffffffff1633146106a1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e657200000000000000000000604482015260640161023e565b565b3373ffffffffffffffffffffffffffffffffffffffff821603610722576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640161023e565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b50805460008255600701600890049060005260206000209081019061050491905b808211156107cd57600081556001016107b9565b5090565b6000602082840312156107e357600080fd5b81357fffffffff000000000000000000000000000000000000000000000000000000008116811461081357600080fd5b9392505050565b600060208083528351808285015260005b818110156108475785810183015185820160400152820161082b565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b63ffffffff8116811461050457600080fd5b6000602082840312156108aa57600080fd5b813561081381610886565b600080604083850312156108c857600080fd5b82356108d381610886565b9150602083013567ffffffffffffffff8111156108ef57600080fd5b83016060818603121561090157600080fd5b809150509250929050565b60006020828403121561091e57600080fd5b813573ffffffffffffffffffffffffffffffffffffffff8116811461081357600080fd5b600060208083526080830163ffffffff808651168386015267ffffffffffffffff83870151166040860152604086015160608087015282815180855260a0880191508583019450600092505b808310156109b05784518416825293850193600192909201919085019061098e565b50979650505050505050565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18436030181126109f157600080fd5b83018035915067ffffffffffffffff821115610a0c57600080fd5b6020019150600581901b3603821315610a2457600080fd5b9250929050565b67ffffffffffffffff8116811461050457600080fd5b600060208284031215610a5357600080fd5b813561081381610a2b565b60008135610a6b81610886565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b68010000000000000000821115610ab957610ab9610a71565b805482825580831015610b3e576000828152602081206007850160031c81016007840160031c82019150601c8660021b168015610b25577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8083018054828460200360031b1c16815550505b505b81811015610b3a57828155600101610b27565b5050505b505050565b67ffffffffffffffff831115610b5b57610b5b610a71565b610b658382610aa0565b60008181526020902082908460031c60005b81811015610bd0576000805b6008811015610bc357610bb2610b9887610a5e565b63ffffffff908116600584901b90811b91901b1984161790565b602096909601959150600101610b83565b5083820155600101610b77565b507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff88616808703818814610c2d576000805b82811015610c2757610c16610b9888610a5e565b602097909701969150600101610c02565b50848401555b5050505050505050565b8135610c4281610886565b63ffffffff811690508154817fffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000082161783556020840135610c8281610a2b565b6bffffffffffffffff000000008160201b16837fffffffffffffffffffffffffffffffffffffffff00000000000000000000000084161717845550505060408201357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1833603018112610cf457600080fd5b8201803567ffffffffffffffff811115610d0d57600080fd5b6020820191508060051b3603821315610d2557600080fd5b610d33818360018601610b43565b50505050565b8183526000602080850194508260005b85811015610d74578135610d5c81610886565b63ffffffff1687529582019590820190600101610d49565b509495945050505050565b600063ffffffff8085168352604060208401528335610d9d81610886565b1660408301526020830135610db181610a2b565b67ffffffffffffffff8082166060850152604085013591507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1853603018212610df957600080fd5b6020918501918201913581811115610e1057600080fd5b8060051b3603831315610e2257600080fd5b60606080860152610e3760a086018285610d39565b97965050505050505056fea164736f6c6343000813000a", + ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"donId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"version\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"url\",\"type\":\"string\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"sha\",\"type\":\"bytes32\"}],\"name\":\"NewChannelDefinition\",\"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\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"string\",\"name\":\"url\",\"type\":\"string\"},{\"internalType\":\"bytes32\",\"name\":\"sha\",\"type\":\"bytes32\"}],\"name\":\"setChannelDefinitions\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}]", + Bin: "0x608060405234801561001057600080fd5b5033806000816100675760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615610097576100978161009f565b505050610148565b336001600160a01b038216036100f75760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640161005e565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6106d2806101576000396000f3fe608060405234801561001057600080fd5b50600436106100725760003560e01c806379ba50971161005057806379ba5097146101355780638da5cb5b1461013d578063f2fde38b1461016557600080fd5b806301ffc9a714610077578063181f5a77146100e15780635ba5bac214610120575b600080fd5b6100cc610085366004610483565b7fffffffff00000000000000000000000000000000000000000000000000000000167f5ba5bac2000000000000000000000000000000000000000000000000000000001490565b60405190151581526020015b60405180910390f35b604080518082018252601881527f4368616e6e656c436f6e66696753746f726520302e302e310000000000000000602082015290516100d891906104cc565b61013361012e366004610538565b610178565b005b6101336101f5565b60005460405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100d8565b6101336101733660046105cc565b6102f7565b61018061030b565b63ffffffff84166000908152600260205260408120805482906101a290610602565b91905081905590508463ffffffff167fe5b641a7879fb491e4e5a35a1ce950f0237b2537ee9b1b1e4fb65e29aff1f5e8828686866040516101e69493929190610661565b60405180910390a25050505050565b60015473ffffffffffffffffffffffffffffffffffffffff16331461027b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b6102ff61030b565b6103088161038e565b50565b60005473ffffffffffffffffffffffffffffffffffffffff16331461038c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610272565b565b3373ffffffffffffffffffffffffffffffffffffffff82160361040d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610272565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b60006020828403121561049557600080fd5b81357fffffffff00000000000000000000000000000000000000000000000000000000811681146104c557600080fd5b9392505050565b600060208083528351808285015260005b818110156104f9578581018301518582016040015282016104dd565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b6000806000806060858703121561054e57600080fd5b843563ffffffff8116811461056257600080fd5b9350602085013567ffffffffffffffff8082111561057f57600080fd5b818701915087601f83011261059357600080fd5b8135818111156105a257600080fd5b8860208285010111156105b457600080fd5b95986020929092019750949560400135945092505050565b6000602082840312156105de57600080fd5b813573ffffffffffffffffffffffffffffffffffffffff811681146104c557600080fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361065a577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b5060010190565b63ffffffff851681526060602082015282606082015282846080830137600060808483010152600060807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f86011683010190508260408301529594505050505056fea164736f6c6343000813000a", } var ChannelConfigStoreABI = ChannelConfigStoreMetaData.ABI @@ -177,28 +171,6 @@ func (_ChannelConfigStore *ChannelConfigStoreTransactorRaw) Transact(opts *bind. return _ChannelConfigStore.Contract.contract.Transact(opts, method, params...) } -func (_ChannelConfigStore *ChannelConfigStoreCaller) GetChannelDefinitions(opts *bind.CallOpts, channelId uint32) (IChannelConfigStoreChannelDefinition, error) { - var out []interface{} - err := _ChannelConfigStore.contract.Call(opts, &out, "getChannelDefinitions", channelId) - - if err != nil { - return *new(IChannelConfigStoreChannelDefinition), err - } - - out0 := *abi.ConvertType(out[0], new(IChannelConfigStoreChannelDefinition)).(*IChannelConfigStoreChannelDefinition) - - return out0, err - -} - -func (_ChannelConfigStore *ChannelConfigStoreSession) GetChannelDefinitions(channelId uint32) (IChannelConfigStoreChannelDefinition, error) { - return _ChannelConfigStore.Contract.GetChannelDefinitions(&_ChannelConfigStore.CallOpts, channelId) -} - -func (_ChannelConfigStore *ChannelConfigStoreCallerSession) GetChannelDefinitions(channelId uint32) (IChannelConfigStoreChannelDefinition, error) { - return _ChannelConfigStore.Contract.GetChannelDefinitions(&_ChannelConfigStore.CallOpts, channelId) -} - func (_ChannelConfigStore *ChannelConfigStoreCaller) Owner(opts *bind.CallOpts) (common.Address, error) { var out []interface{} err := _ChannelConfigStore.contract.Call(opts, &out, "owner") @@ -277,28 +249,16 @@ func (_ChannelConfigStore *ChannelConfigStoreTransactorSession) AcceptOwnership( return _ChannelConfigStore.Contract.AcceptOwnership(&_ChannelConfigStore.TransactOpts) } -func (_ChannelConfigStore *ChannelConfigStoreTransactor) AddChannel(opts *bind.TransactOpts, channelId uint32, channelDefinition IChannelConfigStoreChannelDefinition) (*types.Transaction, error) { - return _ChannelConfigStore.contract.Transact(opts, "addChannel", channelId, channelDefinition) -} - -func (_ChannelConfigStore *ChannelConfigStoreSession) AddChannel(channelId uint32, channelDefinition IChannelConfigStoreChannelDefinition) (*types.Transaction, error) { - return _ChannelConfigStore.Contract.AddChannel(&_ChannelConfigStore.TransactOpts, channelId, channelDefinition) -} - -func (_ChannelConfigStore *ChannelConfigStoreTransactorSession) AddChannel(channelId uint32, channelDefinition IChannelConfigStoreChannelDefinition) (*types.Transaction, error) { - return _ChannelConfigStore.Contract.AddChannel(&_ChannelConfigStore.TransactOpts, channelId, channelDefinition) -} - -func (_ChannelConfigStore *ChannelConfigStoreTransactor) RemoveChannel(opts *bind.TransactOpts, channelId uint32) (*types.Transaction, error) { - return _ChannelConfigStore.contract.Transact(opts, "removeChannel", channelId) +func (_ChannelConfigStore *ChannelConfigStoreTransactor) SetChannelDefinitions(opts *bind.TransactOpts, donId uint32, url string, sha [32]byte) (*types.Transaction, error) { + return _ChannelConfigStore.contract.Transact(opts, "setChannelDefinitions", donId, url, sha) } -func (_ChannelConfigStore *ChannelConfigStoreSession) RemoveChannel(channelId uint32) (*types.Transaction, error) { - return _ChannelConfigStore.Contract.RemoveChannel(&_ChannelConfigStore.TransactOpts, channelId) +func (_ChannelConfigStore *ChannelConfigStoreSession) SetChannelDefinitions(donId uint32, url string, sha [32]byte) (*types.Transaction, error) { + return _ChannelConfigStore.Contract.SetChannelDefinitions(&_ChannelConfigStore.TransactOpts, donId, url, sha) } -func (_ChannelConfigStore *ChannelConfigStoreTransactorSession) RemoveChannel(channelId uint32) (*types.Transaction, error) { - return _ChannelConfigStore.Contract.RemoveChannel(&_ChannelConfigStore.TransactOpts, channelId) +func (_ChannelConfigStore *ChannelConfigStoreTransactorSession) SetChannelDefinitions(donId uint32, url string, sha [32]byte) (*types.Transaction, error) { + return _ChannelConfigStore.Contract.SetChannelDefinitions(&_ChannelConfigStore.TransactOpts, donId, url, sha) } func (_ChannelConfigStore *ChannelConfigStoreTransactor) TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) { @@ -313,123 +273,6 @@ func (_ChannelConfigStore *ChannelConfigStoreTransactorSession) TransferOwnershi return _ChannelConfigStore.Contract.TransferOwnership(&_ChannelConfigStore.TransactOpts, to) } -type ChannelConfigStoreChannelDefinitionRemovedIterator struct { - Event *ChannelConfigStoreChannelDefinitionRemoved - - contract *bind.BoundContract - event string - - logs chan types.Log - sub ethereum.Subscription - done bool - fail error -} - -func (it *ChannelConfigStoreChannelDefinitionRemovedIterator) Next() bool { - - if it.fail != nil { - return false - } - - if it.done { - select { - case log := <-it.logs: - it.Event = new(ChannelConfigStoreChannelDefinitionRemoved) - 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(ChannelConfigStoreChannelDefinitionRemoved) - 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 *ChannelConfigStoreChannelDefinitionRemovedIterator) Error() error { - return it.fail -} - -func (it *ChannelConfigStoreChannelDefinitionRemovedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -type ChannelConfigStoreChannelDefinitionRemoved struct { - ChannelId uint32 - Raw types.Log -} - -func (_ChannelConfigStore *ChannelConfigStoreFilterer) FilterChannelDefinitionRemoved(opts *bind.FilterOpts) (*ChannelConfigStoreChannelDefinitionRemovedIterator, error) { - - logs, sub, err := _ChannelConfigStore.contract.FilterLogs(opts, "ChannelDefinitionRemoved") - if err != nil { - return nil, err - } - return &ChannelConfigStoreChannelDefinitionRemovedIterator{contract: _ChannelConfigStore.contract, event: "ChannelDefinitionRemoved", logs: logs, sub: sub}, nil -} - -func (_ChannelConfigStore *ChannelConfigStoreFilterer) WatchChannelDefinitionRemoved(opts *bind.WatchOpts, sink chan<- *ChannelConfigStoreChannelDefinitionRemoved) (event.Subscription, error) { - - logs, sub, err := _ChannelConfigStore.contract.WatchLogs(opts, "ChannelDefinitionRemoved") - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - - event := new(ChannelConfigStoreChannelDefinitionRemoved) - if err := _ChannelConfigStore.contract.UnpackLog(event, "ChannelDefinitionRemoved", 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 (_ChannelConfigStore *ChannelConfigStoreFilterer) ParseChannelDefinitionRemoved(log types.Log) (*ChannelConfigStoreChannelDefinitionRemoved, error) { - event := new(ChannelConfigStoreChannelDefinitionRemoved) - if err := _ChannelConfigStore.contract.UnpackLog(event, "ChannelDefinitionRemoved", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - type ChannelConfigStoreNewChannelDefinitionIterator struct { Event *ChannelConfigStoreNewChannelDefinition @@ -491,23 +334,35 @@ func (it *ChannelConfigStoreNewChannelDefinitionIterator) Close() error { } type ChannelConfigStoreNewChannelDefinition struct { - ChannelId uint32 - ChannelDefinition IChannelConfigStoreChannelDefinition - Raw types.Log + DonId *big.Int + Version uint32 + Url string + Sha [32]byte + Raw types.Log } -func (_ChannelConfigStore *ChannelConfigStoreFilterer) FilterNewChannelDefinition(opts *bind.FilterOpts) (*ChannelConfigStoreNewChannelDefinitionIterator, error) { +func (_ChannelConfigStore *ChannelConfigStoreFilterer) FilterNewChannelDefinition(opts *bind.FilterOpts, donId []*big.Int) (*ChannelConfigStoreNewChannelDefinitionIterator, error) { + + var donIdRule []interface{} + for _, donIdItem := range donId { + donIdRule = append(donIdRule, donIdItem) + } - logs, sub, err := _ChannelConfigStore.contract.FilterLogs(opts, "NewChannelDefinition") + logs, sub, err := _ChannelConfigStore.contract.FilterLogs(opts, "NewChannelDefinition", donIdRule) if err != nil { return nil, err } return &ChannelConfigStoreNewChannelDefinitionIterator{contract: _ChannelConfigStore.contract, event: "NewChannelDefinition", logs: logs, sub: sub}, nil } -func (_ChannelConfigStore *ChannelConfigStoreFilterer) WatchNewChannelDefinition(opts *bind.WatchOpts, sink chan<- *ChannelConfigStoreNewChannelDefinition) (event.Subscription, error) { +func (_ChannelConfigStore *ChannelConfigStoreFilterer) WatchNewChannelDefinition(opts *bind.WatchOpts, sink chan<- *ChannelConfigStoreNewChannelDefinition, donId []*big.Int) (event.Subscription, error) { + + var donIdRule []interface{} + for _, donIdItem := range donId { + donIdRule = append(donIdRule, donIdItem) + } - logs, sub, err := _ChannelConfigStore.contract.WatchLogs(opts, "NewChannelDefinition") + logs, sub, err := _ChannelConfigStore.contract.WatchLogs(opts, "NewChannelDefinition", donIdRule) if err != nil { return nil, err } @@ -820,147 +675,22 @@ func (_ChannelConfigStore *ChannelConfigStoreFilterer) ParseOwnershipTransferred return event, nil } -type ChannelConfigStorePromoteStagingConfigIterator struct { - Event *ChannelConfigStorePromoteStagingConfig - - contract *bind.BoundContract - event string - - logs chan types.Log - sub ethereum.Subscription - done bool - fail error -} - -func (it *ChannelConfigStorePromoteStagingConfigIterator) Next() bool { - - if it.fail != nil { - return false - } - - if it.done { - select { - case log := <-it.logs: - it.Event = new(ChannelConfigStorePromoteStagingConfig) - 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(ChannelConfigStorePromoteStagingConfig) - 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 *ChannelConfigStorePromoteStagingConfigIterator) Error() error { - return it.fail -} - -func (it *ChannelConfigStorePromoteStagingConfigIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -type ChannelConfigStorePromoteStagingConfig struct { - ChannelId uint32 - Raw types.Log -} - -func (_ChannelConfigStore *ChannelConfigStoreFilterer) FilterPromoteStagingConfig(opts *bind.FilterOpts) (*ChannelConfigStorePromoteStagingConfigIterator, error) { - - logs, sub, err := _ChannelConfigStore.contract.FilterLogs(opts, "PromoteStagingConfig") - if err != nil { - return nil, err - } - return &ChannelConfigStorePromoteStagingConfigIterator{contract: _ChannelConfigStore.contract, event: "PromoteStagingConfig", logs: logs, sub: sub}, nil -} - -func (_ChannelConfigStore *ChannelConfigStoreFilterer) WatchPromoteStagingConfig(opts *bind.WatchOpts, sink chan<- *ChannelConfigStorePromoteStagingConfig) (event.Subscription, error) { - - logs, sub, err := _ChannelConfigStore.contract.WatchLogs(opts, "PromoteStagingConfig") - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - - event := new(ChannelConfigStorePromoteStagingConfig) - if err := _ChannelConfigStore.contract.UnpackLog(event, "PromoteStagingConfig", 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 (_ChannelConfigStore *ChannelConfigStoreFilterer) ParsePromoteStagingConfig(log types.Log) (*ChannelConfigStorePromoteStagingConfig, error) { - event := new(ChannelConfigStorePromoteStagingConfig) - if err := _ChannelConfigStore.contract.UnpackLog(event, "PromoteStagingConfig", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - func (_ChannelConfigStore *ChannelConfigStore) ParseLog(log types.Log) (generated.AbigenLog, error) { switch log.Topics[0] { - case _ChannelConfigStore.abi.Events["ChannelDefinitionRemoved"].ID: - return _ChannelConfigStore.ParseChannelDefinitionRemoved(log) case _ChannelConfigStore.abi.Events["NewChannelDefinition"].ID: return _ChannelConfigStore.ParseNewChannelDefinition(log) case _ChannelConfigStore.abi.Events["OwnershipTransferRequested"].ID: return _ChannelConfigStore.ParseOwnershipTransferRequested(log) case _ChannelConfigStore.abi.Events["OwnershipTransferred"].ID: return _ChannelConfigStore.ParseOwnershipTransferred(log) - case _ChannelConfigStore.abi.Events["PromoteStagingConfig"].ID: - return _ChannelConfigStore.ParsePromoteStagingConfig(log) default: return nil, fmt.Errorf("abigen wrapper received unknown log topic: %v", log.Topics[0]) } } -func (ChannelConfigStoreChannelDefinitionRemoved) Topic() common.Hash { - return common.HexToHash("0x334e877e9691ecae0660510061973bebaa8b4fb37332ed6090052e630c979861") -} - func (ChannelConfigStoreNewChannelDefinition) Topic() common.Hash { - return common.HexToHash("0x35d63e43dd8abd374a4c4e0b5b02c8294dd20e1f493e7344a1751123d11ecc14") + return common.HexToHash("0xe5b641a7879fb491e4e5a35a1ce950f0237b2537ee9b1b1e4fb65e29aff1f5e8") } func (ChannelConfigStoreOwnershipTransferRequested) Topic() common.Hash { @@ -971,17 +701,11 @@ func (ChannelConfigStoreOwnershipTransferred) Topic() common.Hash { return common.HexToHash("0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0") } -func (ChannelConfigStorePromoteStagingConfig) Topic() common.Hash { - return common.HexToHash("0xbdd8ee023f9979bf23e8af6fd7241f484024e83fb0fabd11bb7fd5e9bed7308a") -} - func (_ChannelConfigStore *ChannelConfigStore) Address() common.Address { return _ChannelConfigStore.address } type ChannelConfigStoreInterface interface { - GetChannelDefinitions(opts *bind.CallOpts, channelId uint32) (IChannelConfigStoreChannelDefinition, error) - Owner(opts *bind.CallOpts) (common.Address, error) SupportsInterface(opts *bind.CallOpts, interfaceId [4]byte) (bool, error) @@ -990,21 +714,13 @@ type ChannelConfigStoreInterface interface { AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) - AddChannel(opts *bind.TransactOpts, channelId uint32, channelDefinition IChannelConfigStoreChannelDefinition) (*types.Transaction, error) - - RemoveChannel(opts *bind.TransactOpts, channelId uint32) (*types.Transaction, error) + SetChannelDefinitions(opts *bind.TransactOpts, donId uint32, url string, sha [32]byte) (*types.Transaction, error) TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) - FilterChannelDefinitionRemoved(opts *bind.FilterOpts) (*ChannelConfigStoreChannelDefinitionRemovedIterator, error) - - WatchChannelDefinitionRemoved(opts *bind.WatchOpts, sink chan<- *ChannelConfigStoreChannelDefinitionRemoved) (event.Subscription, error) - - ParseChannelDefinitionRemoved(log types.Log) (*ChannelConfigStoreChannelDefinitionRemoved, error) - - FilterNewChannelDefinition(opts *bind.FilterOpts) (*ChannelConfigStoreNewChannelDefinitionIterator, error) + FilterNewChannelDefinition(opts *bind.FilterOpts, donId []*big.Int) (*ChannelConfigStoreNewChannelDefinitionIterator, error) - WatchNewChannelDefinition(opts *bind.WatchOpts, sink chan<- *ChannelConfigStoreNewChannelDefinition) (event.Subscription, error) + WatchNewChannelDefinition(opts *bind.WatchOpts, sink chan<- *ChannelConfigStoreNewChannelDefinition, donId []*big.Int) (event.Subscription, error) ParseNewChannelDefinition(log types.Log) (*ChannelConfigStoreNewChannelDefinition, error) @@ -1020,12 +736,6 @@ type ChannelConfigStoreInterface interface { ParseOwnershipTransferred(log types.Log) (*ChannelConfigStoreOwnershipTransferred, error) - FilterPromoteStagingConfig(opts *bind.FilterOpts) (*ChannelConfigStorePromoteStagingConfigIterator, error) - - WatchPromoteStagingConfig(opts *bind.WatchOpts, sink chan<- *ChannelConfigStorePromoteStagingConfig) (event.Subscription, error) - - ParsePromoteStagingConfig(log types.Log) (*ChannelConfigStorePromoteStagingConfig, error) - ParseLog(log types.Log) (generated.AbigenLog, error) Address() common.Address diff --git a/core/gethwrappers/llo-feeds/generated/channel_verifier/channel_verifier.go b/core/gethwrappers/llo-feeds/generated/channel_verifier/channel_verifier.go deleted file mode 100644 index 104cab6104..0000000000 --- a/core/gethwrappers/llo-feeds/generated/channel_verifier/channel_verifier.go +++ /dev/null @@ -1,1583 +0,0 @@ -// Code generated - DO NOT EDIT. -// This file is a generated binding and any manual changes will be lost. - -package channel_verifier - -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 -) - -type CommonAddressAndWeight struct { - Addr common.Address - Weight uint64 -} - -var ChannelVerifierMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"verifierProxyAddr\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"AccessForbidden\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"BadVerification\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"}],\"name\":\"CannotDeactivateLatestConfig\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"DigestEmpty\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"}],\"name\":\"DigestInactive\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"}],\"name\":\"DigestNotSet\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"numSigners\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxSigners\",\"type\":\"uint256\"}],\"name\":\"ExcessSigners\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FaultToleranceMustBePositive\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FeedIdEmpty\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"feedId\",\"type\":\"bytes32\"}],\"name\":\"InactiveFeed\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"numSigners\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"expectedNumSigners\",\"type\":\"uint256\"}],\"name\":\"IncorrectSignatureCount\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"numSigners\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minSigners\",\"type\":\"uint256\"}],\"name\":\"InsufficientSigners\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"feedId\",\"type\":\"bytes32\"}],\"name\":\"InvalidFeed\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"rsLength\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"ssLength\",\"type\":\"uint256\"}],\"name\":\"MismatchedSignatures\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NonUniqueSignatures\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroAddress\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"}],\"name\":\"ConfigActivated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"}],\"name\":\"ConfigDeactivated\",\"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\":\"bytes32[]\",\"name\":\"offchainTransmitters\",\"type\":\"bytes32[]\"},{\"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\":\"feedId\",\"type\":\"bytes32\"}],\"name\":\"FeedActivated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"feedId\",\"type\":\"bytes32\"}],\"name\":\"FeedDeactivated\",\"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\":true,\"internalType\":\"bytes32\",\"name\":\"feedId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"requester\",\"type\":\"address\"}],\"name\":\"ReportVerified\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"}],\"name\":\"activateConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"feedId\",\"type\":\"bytes32\"}],\"name\":\"activateFeed\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"}],\"name\":\"deactivateConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"feedId\",\"type\":\"bytes32\"}],\"name\":\"deactivateFeed\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"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\":\"bytes32[]\",\"name\":\"offchainTransmitters\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"onchainConfig\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"weight\",\"type\":\"uint64\"}],\"internalType\":\"structCommon.AddressAndWeight[]\",\"name\":\"recipientAddressesAndWeights\",\"type\":\"tuple[]\"}],\"name\":\"setConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"sourceChainId\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"sourceAddress\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"newConfigCount\",\"type\":\"uint32\"},{\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"offchainTransmitters\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"onchainConfig\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"weight\",\"type\":\"uint64\"}],\"internalType\":\"structCommon.AddressAndWeight[]\",\"name\":\"recipientAddressesAndWeights\",\"type\":\"tuple[]\"}],\"name\":\"setConfigFromSource\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"isVerifier\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"signedReport\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"verify\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"verifierResponse\",\"type\":\"bytes\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", - Bin: "0x60a06040523480156200001157600080fd5b50604051620020ed380380620020ed8339810160408190526200003491620001a6565b33806000816200008b5760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620000be57620000be81620000fb565b5050506001600160a01b038116620000e95760405163d92e233d60e01b815260040160405180910390fd5b6001600160a01b0316608052620001d8565b336001600160a01b03821603620001555760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640162000082565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b600060208284031215620001b957600080fd5b81516001600160a01b0381168114620001d157600080fd5b9392505050565b608051611ef9620001f4600039600061051a0152611ef96000f3fe608060405234801561001057600080fd5b50600436106100ea5760003560e01c8063564a0a7a1161008c5780638da5cb5b116100665780638da5cb5b14610247578063afcb95d71461026f578063eb1dc803146102a4578063f2fde38b146102b757600080fd5b8063564a0a7a146101fc57806379ba50971461020f57806381ff70481461021757600080fd5b8063181f5a77116100c8578063181f5a77146101815780633d3ac1b5146101c35780633dd86430146101d657806354e68a81146101e957600080fd5b806301ffc9a7146100ef5780630d1d79af146101595780630f672ef41461016e575b600080fd5b6101446100fd3660046113d0565b7fffffffff00000000000000000000000000000000000000000000000000000000167f3d3ac1b5000000000000000000000000000000000000000000000000000000001490565b60405190151581526020015b60405180910390f35b61016c610167366004611419565b6102ca565b005b61016c61017c366004611419565b6103d0565b60408051808201909152601581527f4368616e6e656c566572696669657220302e302e30000000000000000000000060208201525b6040516101509190611496565b6101b66101d13660046114d2565b610500565b61016c6101e4366004611419565b610680565b61016c6101f7366004611866565b6106fd565b61016c61020a366004611419565b61080d565b61016c61088d565b6002546003546040805163ffffffff80851682526401000000009094049093166020840152820152606001610150565b60005460405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610150565b600354600254604080516000815260208101939093526801000000000000000090910463ffffffff1690820152606001610150565b61016c6102b2366004611986565b61098a565b61016c6102c5366004611a79565b610a52565b6102d2610a63565b80610309576040517fe332262700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008181526005602052604081205460ff16900361035b576040517f74eb4b93000000000000000000000000000000000000000000000000000000008152600481018290526024015b60405180910390fd5b6000818152600560205260409081902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff16610100179055517fa543797a0501218bba8a3daf75a71c8df8d1a7f791f4e44d40e43b6450183cea906103c59083815260200190565b60405180910390a150565b6103d8610a63565b8061040f576040517fe332262700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008181526005602052604081205460ff16900361045c576040517f74eb4b9300000000000000000000000000000000000000000000000000000000815260048101829052602401610352565b600354810361049a576040517f67863f4400000000000000000000000000000000000000000000000000000000815260048101829052602401610352565b6000818152600560205260409081902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055517f5bfaab86edc1b932e3c334327a591c9ded067cb521abae19b95ca927d6076579906103c59083815260200190565b60603373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610571576040517fef67f5d800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600080808080610583888a018a611a94565b9450945094509450945060008461059990611b6f565b60008181526004602052604090205490915060ff16156105e8576040517f36dbe74800000000000000000000000000000000000000000000000000000000815260048101829052602401610352565b8551600081815260056020526040902061060482878784610ae6565b61060f886002610bda565b86516020880120610624818a89898987610c42565b60405173ffffffffffffffffffffffffffffffffffffffff8c16815284907f58ca9502e98a536e06e72d680fcc251e5d10b72291a281665a2c2dc0ac30fcc59060200160405180910390a250959b9a5050505050505050505050565b610688610a63565b60008181526004602052604090205460ff166106fa5760008181526004602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001690555182917ff438564f793525caa89c6e3a26d41e16aa39d1e589747595751e3f3df75cb2b491a25b50565b86518560ff168060000361073d576040517f0743bae600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601f821115610782576040517f61750f4000000000000000000000000000000000000000000000000000000000815260048101839052601f6024820152604401610352565b61078d816003611c12565b82116107e5578161079f826003611c12565b6107aa906001611c2f565b6040517f9dd9e6d800000000000000000000000000000000000000000000000000000000815260048101929092526024820152604401610352565b6107ed610a63565b6107ff8c8c8c8c8c8c8c8c8c8c610ebe565b505050505050505050505050565b610815610a63565b60008181526004602052604090205460ff16156106fa5760008181526004602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790555182917ffc4f79b8c65b6be1773063461984c0974400d1e99654c79477a092ace83fd06191a250565b60015473ffffffffffffffffffffffffffffffffffffffff16331461090e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e6572000000000000000000006044820152606401610352565b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b86518560ff16806000036109ca576040517f0743bae600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601f821115610a0f576040517f61750f4000000000000000000000000000000000000000000000000000000000815260048101839052601f6024820152604401610352565b610a1a816003611c12565b8211610a2c578161079f826003611c12565b610a34610a63565b610a47463060008c8c8c8c8c8c8c610ebe565b505050505050505050565b610a5a610a63565b6106fa81611230565b60005473ffffffffffffffffffffffffffffffffffffffff163314610ae4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610352565b565b8054600090610af99060ff166001611c42565b8254909150610100900460ff16610b3f576040517fd990d62100000000000000000000000000000000000000000000000000000000815260048101869052602401610352565b8060ff16845114610b8b5783516040517f5348a282000000000000000000000000000000000000000000000000000000008152600481019190915260ff82166024820152604401610352565b8251845114610bd357835183516040517ff0d3140800000000000000000000000000000000000000000000000000000000815260048101929092526024820152604401610352565b5050505050565b6020820151815463ffffffff600883901c81169168010000000000000000900416811115610c3c5782547fffffffffffffffffffffffffffffffffffffffff00000000ffffffffffffffff166801000000000000000063ffffffff8316021783555b50505050565b60008686604051602001610c57929190611c5b565b6040516020818303038152906040528051906020012090506000610c8b604080518082019091526000808252602082015290565b8651600090815b81811015610e5657600186898360208110610caf57610caf611bb4565b610cbc91901a601b611c42565b8c8481518110610cce57610cce611bb4565b60200260200101518c8581518110610ce857610ce8611bb4565b602002602001015160405160008152602001604052604051610d26949392919093845260ff9290921660208401526040830152606082015260800190565b6020604051602081039080840390855afa158015610d48573d6000803e3d6000fd5b5050604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081015173ffffffffffffffffffffffffffffffffffffffff811660009081526001808d01602090815291859020848601909552845460ff808216865293995093955090850192610100900490911690811115610dcd57610dcd611c97565b6001811115610dde57610dde611c97565b9052509350600184602001516001811115610dfb57610dfb611c97565b14610e32576040517f4df18f0700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b836000015160080260ff166001901b8501945080610e4f90611cc6565b9050610c92565b50837e01010101010101010101010101010101010101010101010101010101010101851614610eb1576040517f4df18f0700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5050505050505050505050565b63ffffffff881615610eff57600280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000001663ffffffff8a16179055610f35565b6002805463ffffffff16906000610f1583611cfe565b91906101000a81548163ffffffff021916908363ffffffff160217905550505b600254600090610f54908c908c9063ffffffff168b8b8b8b8b8b611325565b600081815260056020526040812080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00001660ff8a16176101001790559091505b88518160ff16101561118f576000898260ff1681518110610fb857610fb8611bb4565b60200260200101519050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603611028576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600080600085815260056020908152604080832073ffffffffffffffffffffffffffffffffffffffff87168452600190810190925290912054610100900460ff169081111561107957611079611c97565b14801591506110b4576040517ff67bc7c400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040805180820190915260ff841681526020810160019052600085815260056020908152604080832073ffffffffffffffffffffffffffffffffffffffff871684526001908101835292208351815460ff9091167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00821681178355928501519193919284927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000909216179061010090849081111561117457611174611c97565b021790555090505050508061118890611d21565b9050610f95565b506002546040517f1074b4b9a073f79bd1f7f5c808348125ce0f25c27188df7efcaa7a08276051b3916111e29163ffffffff640100000000830481169286929116908d908d908d908d908d908d90611dc1565b60405180910390a1600280547fffffffffffffffffffffffffffffffffffffffff0000000000000000ffffffff1664010000000063ffffffff43160217905560035550505050505050505050565b3373ffffffffffffffffffffffffffffffffffffffff8216036112af576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610352565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6000808a8a8a8a8a8a8a8a8a60405160200161134999989796959493929190611e57565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291905280516020909101207dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e09000000000000000000000000000000000000000000000000000000000000179150509998505050505050505050565b6000602082840312156113e257600080fd5b81357fffffffff000000000000000000000000000000000000000000000000000000008116811461141257600080fd5b9392505050565b60006020828403121561142b57600080fd5b5035919050565b6000815180845260005b818110156114585760208185018101518683018201520161143c565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b6020815260006114126020830184611432565b803573ffffffffffffffffffffffffffffffffffffffff811681146114cd57600080fd5b919050565b6000806000604084860312156114e757600080fd5b833567ffffffffffffffff808211156114ff57600080fd5b818601915086601f83011261151357600080fd5b81358181111561152257600080fd5b87602082850101111561153457600080fd5b60209283019550935061154a91860190506114a9565b90509250925092565b803563ffffffff811681146114cd57600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040805190810167ffffffffffffffff811182821017156115b9576115b9611567565b60405290565b6040516060810167ffffffffffffffff811182821017156115b9576115b9611567565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff8111828210171561162957611629611567565b604052919050565b600067ffffffffffffffff82111561164b5761164b611567565b5060051b60200190565b600082601f83011261166657600080fd5b8135602061167b61167683611631565b6115e2565b82815260059290921b8401810191818101908684111561169a57600080fd5b8286015b848110156116bc576116af816114a9565b835291830191830161169e565b509695505050505050565b600082601f8301126116d857600080fd5b813560206116e861167683611631565b82815260059290921b8401810191818101908684111561170757600080fd5b8286015b848110156116bc578035835291830191830161170b565b803560ff811681146114cd57600080fd5b600082601f83011261174457600080fd5b813567ffffffffffffffff81111561175e5761175e611567565b61178f60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116016115e2565b8181528460208386010111156117a457600080fd5b816020850160208301376000918101602001919091529392505050565b803567ffffffffffffffff811681146114cd57600080fd5b600082601f8301126117ea57600080fd5b813560206117fa61167683611631565b82815260069290921b8401810191818101908684111561181957600080fd5b8286015b848110156116bc57604081890312156118365760008081fd5b61183e611596565b611847826114a9565b81526118548583016117c1565b8186015283529183019160400161181d565b6000806000806000806000806000806101408b8d03121561188657600080fd5b8a35995061189660208c016114a9565b98506118a460408c01611553565b975060608b013567ffffffffffffffff808211156118c157600080fd5b6118cd8e838f01611655565b985060808d01359150808211156118e357600080fd5b6118ef8e838f016116c7565b97506118fd60a08e01611722565b965060c08d013591508082111561191357600080fd5b61191f8e838f01611733565b955061192d60e08e016117c1565b94506101008d013591508082111561194457600080fd5b6119508e838f01611733565b93506101208d013591508082111561196757600080fd5b506119748d828e016117d9565b9150509295989b9194979a5092959850565b600080600080600080600060e0888a0312156119a157600080fd5b873567ffffffffffffffff808211156119b957600080fd5b6119c58b838c01611655565b985060208a01359150808211156119db57600080fd5b6119e78b838c016116c7565b97506119f560408b01611722565b965060608a0135915080821115611a0b57600080fd5b611a178b838c01611733565b9550611a2560808b016117c1565b945060a08a0135915080821115611a3b57600080fd5b611a478b838c01611733565b935060c08a0135915080821115611a5d57600080fd5b50611a6a8a828b016117d9565b91505092959891949750929550565b600060208284031215611a8b57600080fd5b611412826114a9565b600080600080600060e08688031215611aac57600080fd5b86601f870112611abb57600080fd5b611ac36115bf565b806060880189811115611ad557600080fd5b885b81811015611aef578035845260209384019301611ad7565b5090965035905067ffffffffffffffff80821115611b0c57600080fd5b611b1889838a01611733565b95506080880135915080821115611b2e57600080fd5b611b3a89838a016116c7565b945060a0880135915080821115611b5057600080fd5b50611b5d888289016116c7565b9598949750929560c001359392505050565b80516020808301519190811015611bae577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8160200360031b1b821691505b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8082028115828204841417611c2957611c29611be3565b92915050565b80820180821115611c2957611c29611be3565b60ff8181168382160190811115611c2957611c29611be3565b828152600060208083018460005b6003811015611c8657815183529183019190830190600101611c69565b505050506080820190509392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203611cf757611cf7611be3565b5060010190565b600063ffffffff808316818103611d1757611d17611be3565b6001019392505050565b600060ff821660ff8103611d3757611d37611be3565b60010192915050565b600081518084526020808501945080840160005b83811015611d8657815173ffffffffffffffffffffffffffffffffffffffff1687529582019590820190600101611d54565b509495945050505050565b600081518084526020808501945080840160005b83811015611d8657815187529582019590820190600101611da5565b600061012063ffffffff808d1684528b6020850152808b16604085015250806060840152611df18184018a611d40565b90508281036080840152611e058189611d91565b905060ff871660a084015282810360c0840152611e228187611432565b905067ffffffffffffffff851660e0840152828103610100840152611e478185611432565b9c9b505050505050505050505050565b60006101208b835273ffffffffffffffffffffffffffffffffffffffff8b16602084015267ffffffffffffffff808b166040850152816060850152611e9e8285018b611d40565b91508382036080850152611eb2828a611d91565b915060ff881660a085015283820360c0850152611ecf8288611432565b90861660e08501528381036101008501529050611e47818561143256fea164736f6c6343000813000a", -} - -var ChannelVerifierABI = ChannelVerifierMetaData.ABI - -var ChannelVerifierBin = ChannelVerifierMetaData.Bin - -func DeployChannelVerifier(auth *bind.TransactOpts, backend bind.ContractBackend, verifierProxyAddr common.Address) (common.Address, *types.Transaction, *ChannelVerifier, error) { - parsed, err := ChannelVerifierMetaData.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(ChannelVerifierBin), backend, verifierProxyAddr) - if err != nil { - return common.Address{}, nil, nil, err - } - return address, tx, &ChannelVerifier{address: address, abi: *parsed, ChannelVerifierCaller: ChannelVerifierCaller{contract: contract}, ChannelVerifierTransactor: ChannelVerifierTransactor{contract: contract}, ChannelVerifierFilterer: ChannelVerifierFilterer{contract: contract}}, nil -} - -type ChannelVerifier struct { - address common.Address - abi abi.ABI - ChannelVerifierCaller - ChannelVerifierTransactor - ChannelVerifierFilterer -} - -type ChannelVerifierCaller struct { - contract *bind.BoundContract -} - -type ChannelVerifierTransactor struct { - contract *bind.BoundContract -} - -type ChannelVerifierFilterer struct { - contract *bind.BoundContract -} - -type ChannelVerifierSession struct { - Contract *ChannelVerifier - CallOpts bind.CallOpts - TransactOpts bind.TransactOpts -} - -type ChannelVerifierCallerSession struct { - Contract *ChannelVerifierCaller - CallOpts bind.CallOpts -} - -type ChannelVerifierTransactorSession struct { - Contract *ChannelVerifierTransactor - TransactOpts bind.TransactOpts -} - -type ChannelVerifierRaw struct { - Contract *ChannelVerifier -} - -type ChannelVerifierCallerRaw struct { - Contract *ChannelVerifierCaller -} - -type ChannelVerifierTransactorRaw struct { - Contract *ChannelVerifierTransactor -} - -func NewChannelVerifier(address common.Address, backend bind.ContractBackend) (*ChannelVerifier, error) { - abi, err := abi.JSON(strings.NewReader(ChannelVerifierABI)) - if err != nil { - return nil, err - } - contract, err := bindChannelVerifier(address, backend, backend, backend) - if err != nil { - return nil, err - } - return &ChannelVerifier{address: address, abi: abi, ChannelVerifierCaller: ChannelVerifierCaller{contract: contract}, ChannelVerifierTransactor: ChannelVerifierTransactor{contract: contract}, ChannelVerifierFilterer: ChannelVerifierFilterer{contract: contract}}, nil -} - -func NewChannelVerifierCaller(address common.Address, caller bind.ContractCaller) (*ChannelVerifierCaller, error) { - contract, err := bindChannelVerifier(address, caller, nil, nil) - if err != nil { - return nil, err - } - return &ChannelVerifierCaller{contract: contract}, nil -} - -func NewChannelVerifierTransactor(address common.Address, transactor bind.ContractTransactor) (*ChannelVerifierTransactor, error) { - contract, err := bindChannelVerifier(address, nil, transactor, nil) - if err != nil { - return nil, err - } - return &ChannelVerifierTransactor{contract: contract}, nil -} - -func NewChannelVerifierFilterer(address common.Address, filterer bind.ContractFilterer) (*ChannelVerifierFilterer, error) { - contract, err := bindChannelVerifier(address, nil, nil, filterer) - if err != nil { - return nil, err - } - return &ChannelVerifierFilterer{contract: contract}, nil -} - -func bindChannelVerifier(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { - parsed, err := ChannelVerifierMetaData.GetAbi() - if err != nil { - return nil, err - } - return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil -} - -func (_ChannelVerifier *ChannelVerifierRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { - return _ChannelVerifier.Contract.ChannelVerifierCaller.contract.Call(opts, result, method, params...) -} - -func (_ChannelVerifier *ChannelVerifierRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _ChannelVerifier.Contract.ChannelVerifierTransactor.contract.Transfer(opts) -} - -func (_ChannelVerifier *ChannelVerifierRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _ChannelVerifier.Contract.ChannelVerifierTransactor.contract.Transact(opts, method, params...) -} - -func (_ChannelVerifier *ChannelVerifierCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { - return _ChannelVerifier.Contract.contract.Call(opts, result, method, params...) -} - -func (_ChannelVerifier *ChannelVerifierTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _ChannelVerifier.Contract.contract.Transfer(opts) -} - -func (_ChannelVerifier *ChannelVerifierTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _ChannelVerifier.Contract.contract.Transact(opts, method, params...) -} - -func (_ChannelVerifier *ChannelVerifierCaller) LatestConfigDetails(opts *bind.CallOpts) (LatestConfigDetails, - - error) { - var out []interface{} - err := _ChannelVerifier.contract.Call(opts, &out, "latestConfigDetails") - - outstruct := new(LatestConfigDetails) - if err != nil { - return *outstruct, err - } - - outstruct.ConfigCount = *abi.ConvertType(out[0], new(uint32)).(*uint32) - outstruct.BlockNumber = *abi.ConvertType(out[1], new(uint32)).(*uint32) - outstruct.ConfigDigest = *abi.ConvertType(out[2], new([32]byte)).(*[32]byte) - - return *outstruct, err - -} - -func (_ChannelVerifier *ChannelVerifierSession) LatestConfigDetails() (LatestConfigDetails, - - error) { - return _ChannelVerifier.Contract.LatestConfigDetails(&_ChannelVerifier.CallOpts) -} - -func (_ChannelVerifier *ChannelVerifierCallerSession) LatestConfigDetails() (LatestConfigDetails, - - error) { - return _ChannelVerifier.Contract.LatestConfigDetails(&_ChannelVerifier.CallOpts) -} - -func (_ChannelVerifier *ChannelVerifierCaller) LatestConfigDigestAndEpoch(opts *bind.CallOpts) (LatestConfigDigestAndEpoch, - - error) { - var out []interface{} - err := _ChannelVerifier.contract.Call(opts, &out, "latestConfigDigestAndEpoch") - - outstruct := new(LatestConfigDigestAndEpoch) - if err != nil { - return *outstruct, err - } - - outstruct.ScanLogs = *abi.ConvertType(out[0], new(bool)).(*bool) - outstruct.ConfigDigest = *abi.ConvertType(out[1], new([32]byte)).(*[32]byte) - outstruct.Epoch = *abi.ConvertType(out[2], new(uint32)).(*uint32) - - return *outstruct, err - -} - -func (_ChannelVerifier *ChannelVerifierSession) LatestConfigDigestAndEpoch() (LatestConfigDigestAndEpoch, - - error) { - return _ChannelVerifier.Contract.LatestConfigDigestAndEpoch(&_ChannelVerifier.CallOpts) -} - -func (_ChannelVerifier *ChannelVerifierCallerSession) LatestConfigDigestAndEpoch() (LatestConfigDigestAndEpoch, - - error) { - return _ChannelVerifier.Contract.LatestConfigDigestAndEpoch(&_ChannelVerifier.CallOpts) -} - -func (_ChannelVerifier *ChannelVerifierCaller) Owner(opts *bind.CallOpts) (common.Address, error) { - var out []interface{} - err := _ChannelVerifier.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 (_ChannelVerifier *ChannelVerifierSession) Owner() (common.Address, error) { - return _ChannelVerifier.Contract.Owner(&_ChannelVerifier.CallOpts) -} - -func (_ChannelVerifier *ChannelVerifierCallerSession) Owner() (common.Address, error) { - return _ChannelVerifier.Contract.Owner(&_ChannelVerifier.CallOpts) -} - -func (_ChannelVerifier *ChannelVerifierCaller) SupportsInterface(opts *bind.CallOpts, interfaceId [4]byte) (bool, error) { - var out []interface{} - err := _ChannelVerifier.contract.Call(opts, &out, "supportsInterface", interfaceId) - - if err != nil { - return *new(bool), err - } - - out0 := *abi.ConvertType(out[0], new(bool)).(*bool) - - return out0, err - -} - -func (_ChannelVerifier *ChannelVerifierSession) SupportsInterface(interfaceId [4]byte) (bool, error) { - return _ChannelVerifier.Contract.SupportsInterface(&_ChannelVerifier.CallOpts, interfaceId) -} - -func (_ChannelVerifier *ChannelVerifierCallerSession) SupportsInterface(interfaceId [4]byte) (bool, error) { - return _ChannelVerifier.Contract.SupportsInterface(&_ChannelVerifier.CallOpts, interfaceId) -} - -func (_ChannelVerifier *ChannelVerifierCaller) TypeAndVersion(opts *bind.CallOpts) (string, error) { - var out []interface{} - err := _ChannelVerifier.contract.Call(opts, &out, "typeAndVersion") - - if err != nil { - return *new(string), err - } - - out0 := *abi.ConvertType(out[0], new(string)).(*string) - - return out0, err - -} - -func (_ChannelVerifier *ChannelVerifierSession) TypeAndVersion() (string, error) { - return _ChannelVerifier.Contract.TypeAndVersion(&_ChannelVerifier.CallOpts) -} - -func (_ChannelVerifier *ChannelVerifierCallerSession) TypeAndVersion() (string, error) { - return _ChannelVerifier.Contract.TypeAndVersion(&_ChannelVerifier.CallOpts) -} - -func (_ChannelVerifier *ChannelVerifierTransactor) AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) { - return _ChannelVerifier.contract.Transact(opts, "acceptOwnership") -} - -func (_ChannelVerifier *ChannelVerifierSession) AcceptOwnership() (*types.Transaction, error) { - return _ChannelVerifier.Contract.AcceptOwnership(&_ChannelVerifier.TransactOpts) -} - -func (_ChannelVerifier *ChannelVerifierTransactorSession) AcceptOwnership() (*types.Transaction, error) { - return _ChannelVerifier.Contract.AcceptOwnership(&_ChannelVerifier.TransactOpts) -} - -func (_ChannelVerifier *ChannelVerifierTransactor) ActivateConfig(opts *bind.TransactOpts, configDigest [32]byte) (*types.Transaction, error) { - return _ChannelVerifier.contract.Transact(opts, "activateConfig", configDigest) -} - -func (_ChannelVerifier *ChannelVerifierSession) ActivateConfig(configDigest [32]byte) (*types.Transaction, error) { - return _ChannelVerifier.Contract.ActivateConfig(&_ChannelVerifier.TransactOpts, configDigest) -} - -func (_ChannelVerifier *ChannelVerifierTransactorSession) ActivateConfig(configDigest [32]byte) (*types.Transaction, error) { - return _ChannelVerifier.Contract.ActivateConfig(&_ChannelVerifier.TransactOpts, configDigest) -} - -func (_ChannelVerifier *ChannelVerifierTransactor) ActivateFeed(opts *bind.TransactOpts, feedId [32]byte) (*types.Transaction, error) { - return _ChannelVerifier.contract.Transact(opts, "activateFeed", feedId) -} - -func (_ChannelVerifier *ChannelVerifierSession) ActivateFeed(feedId [32]byte) (*types.Transaction, error) { - return _ChannelVerifier.Contract.ActivateFeed(&_ChannelVerifier.TransactOpts, feedId) -} - -func (_ChannelVerifier *ChannelVerifierTransactorSession) ActivateFeed(feedId [32]byte) (*types.Transaction, error) { - return _ChannelVerifier.Contract.ActivateFeed(&_ChannelVerifier.TransactOpts, feedId) -} - -func (_ChannelVerifier *ChannelVerifierTransactor) DeactivateConfig(opts *bind.TransactOpts, configDigest [32]byte) (*types.Transaction, error) { - return _ChannelVerifier.contract.Transact(opts, "deactivateConfig", configDigest) -} - -func (_ChannelVerifier *ChannelVerifierSession) DeactivateConfig(configDigest [32]byte) (*types.Transaction, error) { - return _ChannelVerifier.Contract.DeactivateConfig(&_ChannelVerifier.TransactOpts, configDigest) -} - -func (_ChannelVerifier *ChannelVerifierTransactorSession) DeactivateConfig(configDigest [32]byte) (*types.Transaction, error) { - return _ChannelVerifier.Contract.DeactivateConfig(&_ChannelVerifier.TransactOpts, configDigest) -} - -func (_ChannelVerifier *ChannelVerifierTransactor) DeactivateFeed(opts *bind.TransactOpts, feedId [32]byte) (*types.Transaction, error) { - return _ChannelVerifier.contract.Transact(opts, "deactivateFeed", feedId) -} - -func (_ChannelVerifier *ChannelVerifierSession) DeactivateFeed(feedId [32]byte) (*types.Transaction, error) { - return _ChannelVerifier.Contract.DeactivateFeed(&_ChannelVerifier.TransactOpts, feedId) -} - -func (_ChannelVerifier *ChannelVerifierTransactorSession) DeactivateFeed(feedId [32]byte) (*types.Transaction, error) { - return _ChannelVerifier.Contract.DeactivateFeed(&_ChannelVerifier.TransactOpts, feedId) -} - -func (_ChannelVerifier *ChannelVerifierTransactor) SetConfig(opts *bind.TransactOpts, signers []common.Address, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte, recipientAddressesAndWeights []CommonAddressAndWeight) (*types.Transaction, error) { - return _ChannelVerifier.contract.Transact(opts, "setConfig", signers, offchainTransmitters, f, onchainConfig, offchainConfigVersion, offchainConfig, recipientAddressesAndWeights) -} - -func (_ChannelVerifier *ChannelVerifierSession) SetConfig(signers []common.Address, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte, recipientAddressesAndWeights []CommonAddressAndWeight) (*types.Transaction, error) { - return _ChannelVerifier.Contract.SetConfig(&_ChannelVerifier.TransactOpts, signers, offchainTransmitters, f, onchainConfig, offchainConfigVersion, offchainConfig, recipientAddressesAndWeights) -} - -func (_ChannelVerifier *ChannelVerifierTransactorSession) SetConfig(signers []common.Address, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte, recipientAddressesAndWeights []CommonAddressAndWeight) (*types.Transaction, error) { - return _ChannelVerifier.Contract.SetConfig(&_ChannelVerifier.TransactOpts, signers, offchainTransmitters, f, onchainConfig, offchainConfigVersion, offchainConfig, recipientAddressesAndWeights) -} - -func (_ChannelVerifier *ChannelVerifierTransactor) SetConfigFromSource(opts *bind.TransactOpts, sourceChainId *big.Int, sourceAddress common.Address, newConfigCount uint32, signers []common.Address, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte, recipientAddressesAndWeights []CommonAddressAndWeight) (*types.Transaction, error) { - return _ChannelVerifier.contract.Transact(opts, "setConfigFromSource", sourceChainId, sourceAddress, newConfigCount, signers, offchainTransmitters, f, onchainConfig, offchainConfigVersion, offchainConfig, recipientAddressesAndWeights) -} - -func (_ChannelVerifier *ChannelVerifierSession) SetConfigFromSource(sourceChainId *big.Int, sourceAddress common.Address, newConfigCount uint32, signers []common.Address, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte, recipientAddressesAndWeights []CommonAddressAndWeight) (*types.Transaction, error) { - return _ChannelVerifier.Contract.SetConfigFromSource(&_ChannelVerifier.TransactOpts, sourceChainId, sourceAddress, newConfigCount, signers, offchainTransmitters, f, onchainConfig, offchainConfigVersion, offchainConfig, recipientAddressesAndWeights) -} - -func (_ChannelVerifier *ChannelVerifierTransactorSession) SetConfigFromSource(sourceChainId *big.Int, sourceAddress common.Address, newConfigCount uint32, signers []common.Address, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte, recipientAddressesAndWeights []CommonAddressAndWeight) (*types.Transaction, error) { - return _ChannelVerifier.Contract.SetConfigFromSource(&_ChannelVerifier.TransactOpts, sourceChainId, sourceAddress, newConfigCount, signers, offchainTransmitters, f, onchainConfig, offchainConfigVersion, offchainConfig, recipientAddressesAndWeights) -} - -func (_ChannelVerifier *ChannelVerifierTransactor) TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) { - return _ChannelVerifier.contract.Transact(opts, "transferOwnership", to) -} - -func (_ChannelVerifier *ChannelVerifierSession) TransferOwnership(to common.Address) (*types.Transaction, error) { - return _ChannelVerifier.Contract.TransferOwnership(&_ChannelVerifier.TransactOpts, to) -} - -func (_ChannelVerifier *ChannelVerifierTransactorSession) TransferOwnership(to common.Address) (*types.Transaction, error) { - return _ChannelVerifier.Contract.TransferOwnership(&_ChannelVerifier.TransactOpts, to) -} - -func (_ChannelVerifier *ChannelVerifierTransactor) Verify(opts *bind.TransactOpts, signedReport []byte, sender common.Address) (*types.Transaction, error) { - return _ChannelVerifier.contract.Transact(opts, "verify", signedReport, sender) -} - -func (_ChannelVerifier *ChannelVerifierSession) Verify(signedReport []byte, sender common.Address) (*types.Transaction, error) { - return _ChannelVerifier.Contract.Verify(&_ChannelVerifier.TransactOpts, signedReport, sender) -} - -func (_ChannelVerifier *ChannelVerifierTransactorSession) Verify(signedReport []byte, sender common.Address) (*types.Transaction, error) { - return _ChannelVerifier.Contract.Verify(&_ChannelVerifier.TransactOpts, signedReport, sender) -} - -type ChannelVerifierConfigActivatedIterator struct { - Event *ChannelVerifierConfigActivated - - contract *bind.BoundContract - event string - - logs chan types.Log - sub ethereum.Subscription - done bool - fail error -} - -func (it *ChannelVerifierConfigActivatedIterator) Next() bool { - - if it.fail != nil { - return false - } - - if it.done { - select { - case log := <-it.logs: - it.Event = new(ChannelVerifierConfigActivated) - 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(ChannelVerifierConfigActivated) - 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 *ChannelVerifierConfigActivatedIterator) Error() error { - return it.fail -} - -func (it *ChannelVerifierConfigActivatedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -type ChannelVerifierConfigActivated struct { - ConfigDigest [32]byte - Raw types.Log -} - -func (_ChannelVerifier *ChannelVerifierFilterer) FilterConfigActivated(opts *bind.FilterOpts) (*ChannelVerifierConfigActivatedIterator, error) { - - logs, sub, err := _ChannelVerifier.contract.FilterLogs(opts, "ConfigActivated") - if err != nil { - return nil, err - } - return &ChannelVerifierConfigActivatedIterator{contract: _ChannelVerifier.contract, event: "ConfigActivated", logs: logs, sub: sub}, nil -} - -func (_ChannelVerifier *ChannelVerifierFilterer) WatchConfigActivated(opts *bind.WatchOpts, sink chan<- *ChannelVerifierConfigActivated) (event.Subscription, error) { - - logs, sub, err := _ChannelVerifier.contract.WatchLogs(opts, "ConfigActivated") - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - - event := new(ChannelVerifierConfigActivated) - if err := _ChannelVerifier.contract.UnpackLog(event, "ConfigActivated", 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 (_ChannelVerifier *ChannelVerifierFilterer) ParseConfigActivated(log types.Log) (*ChannelVerifierConfigActivated, error) { - event := new(ChannelVerifierConfigActivated) - if err := _ChannelVerifier.contract.UnpackLog(event, "ConfigActivated", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -type ChannelVerifierConfigDeactivatedIterator struct { - Event *ChannelVerifierConfigDeactivated - - contract *bind.BoundContract - event string - - logs chan types.Log - sub ethereum.Subscription - done bool - fail error -} - -func (it *ChannelVerifierConfigDeactivatedIterator) Next() bool { - - if it.fail != nil { - return false - } - - if it.done { - select { - case log := <-it.logs: - it.Event = new(ChannelVerifierConfigDeactivated) - 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(ChannelVerifierConfigDeactivated) - 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 *ChannelVerifierConfigDeactivatedIterator) Error() error { - return it.fail -} - -func (it *ChannelVerifierConfigDeactivatedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -type ChannelVerifierConfigDeactivated struct { - ConfigDigest [32]byte - Raw types.Log -} - -func (_ChannelVerifier *ChannelVerifierFilterer) FilterConfigDeactivated(opts *bind.FilterOpts) (*ChannelVerifierConfigDeactivatedIterator, error) { - - logs, sub, err := _ChannelVerifier.contract.FilterLogs(opts, "ConfigDeactivated") - if err != nil { - return nil, err - } - return &ChannelVerifierConfigDeactivatedIterator{contract: _ChannelVerifier.contract, event: "ConfigDeactivated", logs: logs, sub: sub}, nil -} - -func (_ChannelVerifier *ChannelVerifierFilterer) WatchConfigDeactivated(opts *bind.WatchOpts, sink chan<- *ChannelVerifierConfigDeactivated) (event.Subscription, error) { - - logs, sub, err := _ChannelVerifier.contract.WatchLogs(opts, "ConfigDeactivated") - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - - event := new(ChannelVerifierConfigDeactivated) - if err := _ChannelVerifier.contract.UnpackLog(event, "ConfigDeactivated", 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 (_ChannelVerifier *ChannelVerifierFilterer) ParseConfigDeactivated(log types.Log) (*ChannelVerifierConfigDeactivated, error) { - event := new(ChannelVerifierConfigDeactivated) - if err := _ChannelVerifier.contract.UnpackLog(event, "ConfigDeactivated", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -type ChannelVerifierConfigSetIterator struct { - Event *ChannelVerifierConfigSet - - contract *bind.BoundContract - event string - - logs chan types.Log - sub ethereum.Subscription - done bool - fail error -} - -func (it *ChannelVerifierConfigSetIterator) Next() bool { - - if it.fail != nil { - return false - } - - if it.done { - select { - case log := <-it.logs: - it.Event = new(ChannelVerifierConfigSet) - 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(ChannelVerifierConfigSet) - 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 *ChannelVerifierConfigSetIterator) Error() error { - return it.fail -} - -func (it *ChannelVerifierConfigSetIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -type ChannelVerifierConfigSet struct { - PreviousConfigBlockNumber uint32 - ConfigDigest [32]byte - ConfigCount uint64 - Signers []common.Address - OffchainTransmitters [][32]byte - F uint8 - OnchainConfig []byte - OffchainConfigVersion uint64 - OffchainConfig []byte - Raw types.Log -} - -func (_ChannelVerifier *ChannelVerifierFilterer) FilterConfigSet(opts *bind.FilterOpts) (*ChannelVerifierConfigSetIterator, error) { - - logs, sub, err := _ChannelVerifier.contract.FilterLogs(opts, "ConfigSet") - if err != nil { - return nil, err - } - return &ChannelVerifierConfigSetIterator{contract: _ChannelVerifier.contract, event: "ConfigSet", logs: logs, sub: sub}, nil -} - -func (_ChannelVerifier *ChannelVerifierFilterer) WatchConfigSet(opts *bind.WatchOpts, sink chan<- *ChannelVerifierConfigSet) (event.Subscription, error) { - - logs, sub, err := _ChannelVerifier.contract.WatchLogs(opts, "ConfigSet") - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - - event := new(ChannelVerifierConfigSet) - if err := _ChannelVerifier.contract.UnpackLog(event, "ConfigSet", 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 (_ChannelVerifier *ChannelVerifierFilterer) ParseConfigSet(log types.Log) (*ChannelVerifierConfigSet, error) { - event := new(ChannelVerifierConfigSet) - if err := _ChannelVerifier.contract.UnpackLog(event, "ConfigSet", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -type ChannelVerifierFeedActivatedIterator struct { - Event *ChannelVerifierFeedActivated - - contract *bind.BoundContract - event string - - logs chan types.Log - sub ethereum.Subscription - done bool - fail error -} - -func (it *ChannelVerifierFeedActivatedIterator) Next() bool { - - if it.fail != nil { - return false - } - - if it.done { - select { - case log := <-it.logs: - it.Event = new(ChannelVerifierFeedActivated) - 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(ChannelVerifierFeedActivated) - 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 *ChannelVerifierFeedActivatedIterator) Error() error { - return it.fail -} - -func (it *ChannelVerifierFeedActivatedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -type ChannelVerifierFeedActivated struct { - FeedId [32]byte - Raw types.Log -} - -func (_ChannelVerifier *ChannelVerifierFilterer) FilterFeedActivated(opts *bind.FilterOpts, feedId [][32]byte) (*ChannelVerifierFeedActivatedIterator, error) { - - var feedIdRule []interface{} - for _, feedIdItem := range feedId { - feedIdRule = append(feedIdRule, feedIdItem) - } - - logs, sub, err := _ChannelVerifier.contract.FilterLogs(opts, "FeedActivated", feedIdRule) - if err != nil { - return nil, err - } - return &ChannelVerifierFeedActivatedIterator{contract: _ChannelVerifier.contract, event: "FeedActivated", logs: logs, sub: sub}, nil -} - -func (_ChannelVerifier *ChannelVerifierFilterer) WatchFeedActivated(opts *bind.WatchOpts, sink chan<- *ChannelVerifierFeedActivated, feedId [][32]byte) (event.Subscription, error) { - - var feedIdRule []interface{} - for _, feedIdItem := range feedId { - feedIdRule = append(feedIdRule, feedIdItem) - } - - logs, sub, err := _ChannelVerifier.contract.WatchLogs(opts, "FeedActivated", feedIdRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - - event := new(ChannelVerifierFeedActivated) - if err := _ChannelVerifier.contract.UnpackLog(event, "FeedActivated", 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 (_ChannelVerifier *ChannelVerifierFilterer) ParseFeedActivated(log types.Log) (*ChannelVerifierFeedActivated, error) { - event := new(ChannelVerifierFeedActivated) - if err := _ChannelVerifier.contract.UnpackLog(event, "FeedActivated", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -type ChannelVerifierFeedDeactivatedIterator struct { - Event *ChannelVerifierFeedDeactivated - - contract *bind.BoundContract - event string - - logs chan types.Log - sub ethereum.Subscription - done bool - fail error -} - -func (it *ChannelVerifierFeedDeactivatedIterator) Next() bool { - - if it.fail != nil { - return false - } - - if it.done { - select { - case log := <-it.logs: - it.Event = new(ChannelVerifierFeedDeactivated) - 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(ChannelVerifierFeedDeactivated) - 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 *ChannelVerifierFeedDeactivatedIterator) Error() error { - return it.fail -} - -func (it *ChannelVerifierFeedDeactivatedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -type ChannelVerifierFeedDeactivated struct { - FeedId [32]byte - Raw types.Log -} - -func (_ChannelVerifier *ChannelVerifierFilterer) FilterFeedDeactivated(opts *bind.FilterOpts, feedId [][32]byte) (*ChannelVerifierFeedDeactivatedIterator, error) { - - var feedIdRule []interface{} - for _, feedIdItem := range feedId { - feedIdRule = append(feedIdRule, feedIdItem) - } - - logs, sub, err := _ChannelVerifier.contract.FilterLogs(opts, "FeedDeactivated", feedIdRule) - if err != nil { - return nil, err - } - return &ChannelVerifierFeedDeactivatedIterator{contract: _ChannelVerifier.contract, event: "FeedDeactivated", logs: logs, sub: sub}, nil -} - -func (_ChannelVerifier *ChannelVerifierFilterer) WatchFeedDeactivated(opts *bind.WatchOpts, sink chan<- *ChannelVerifierFeedDeactivated, feedId [][32]byte) (event.Subscription, error) { - - var feedIdRule []interface{} - for _, feedIdItem := range feedId { - feedIdRule = append(feedIdRule, feedIdItem) - } - - logs, sub, err := _ChannelVerifier.contract.WatchLogs(opts, "FeedDeactivated", feedIdRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - - event := new(ChannelVerifierFeedDeactivated) - if err := _ChannelVerifier.contract.UnpackLog(event, "FeedDeactivated", 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 (_ChannelVerifier *ChannelVerifierFilterer) ParseFeedDeactivated(log types.Log) (*ChannelVerifierFeedDeactivated, error) { - event := new(ChannelVerifierFeedDeactivated) - if err := _ChannelVerifier.contract.UnpackLog(event, "FeedDeactivated", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -type ChannelVerifierOwnershipTransferRequestedIterator struct { - Event *ChannelVerifierOwnershipTransferRequested - - contract *bind.BoundContract - event string - - logs chan types.Log - sub ethereum.Subscription - done bool - fail error -} - -func (it *ChannelVerifierOwnershipTransferRequestedIterator) Next() bool { - - if it.fail != nil { - return false - } - - if it.done { - select { - case log := <-it.logs: - it.Event = new(ChannelVerifierOwnershipTransferRequested) - 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(ChannelVerifierOwnershipTransferRequested) - 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 *ChannelVerifierOwnershipTransferRequestedIterator) Error() error { - return it.fail -} - -func (it *ChannelVerifierOwnershipTransferRequestedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -type ChannelVerifierOwnershipTransferRequested struct { - From common.Address - To common.Address - Raw types.Log -} - -func (_ChannelVerifier *ChannelVerifierFilterer) FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*ChannelVerifierOwnershipTransferRequestedIterator, 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 := _ChannelVerifier.contract.FilterLogs(opts, "OwnershipTransferRequested", fromRule, toRule) - if err != nil { - return nil, err - } - return &ChannelVerifierOwnershipTransferRequestedIterator{contract: _ChannelVerifier.contract, event: "OwnershipTransferRequested", logs: logs, sub: sub}, nil -} - -func (_ChannelVerifier *ChannelVerifierFilterer) WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *ChannelVerifierOwnershipTransferRequested, 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 := _ChannelVerifier.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(ChannelVerifierOwnershipTransferRequested) - if err := _ChannelVerifier.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 (_ChannelVerifier *ChannelVerifierFilterer) ParseOwnershipTransferRequested(log types.Log) (*ChannelVerifierOwnershipTransferRequested, error) { - event := new(ChannelVerifierOwnershipTransferRequested) - if err := _ChannelVerifier.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -type ChannelVerifierOwnershipTransferredIterator struct { - Event *ChannelVerifierOwnershipTransferred - - contract *bind.BoundContract - event string - - logs chan types.Log - sub ethereum.Subscription - done bool - fail error -} - -func (it *ChannelVerifierOwnershipTransferredIterator) Next() bool { - - if it.fail != nil { - return false - } - - if it.done { - select { - case log := <-it.logs: - it.Event = new(ChannelVerifierOwnershipTransferred) - 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(ChannelVerifierOwnershipTransferred) - 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 *ChannelVerifierOwnershipTransferredIterator) Error() error { - return it.fail -} - -func (it *ChannelVerifierOwnershipTransferredIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -type ChannelVerifierOwnershipTransferred struct { - From common.Address - To common.Address - Raw types.Log -} - -func (_ChannelVerifier *ChannelVerifierFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*ChannelVerifierOwnershipTransferredIterator, 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 := _ChannelVerifier.contract.FilterLogs(opts, "OwnershipTransferred", fromRule, toRule) - if err != nil { - return nil, err - } - return &ChannelVerifierOwnershipTransferredIterator{contract: _ChannelVerifier.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil -} - -func (_ChannelVerifier *ChannelVerifierFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *ChannelVerifierOwnershipTransferred, 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 := _ChannelVerifier.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(ChannelVerifierOwnershipTransferred) - if err := _ChannelVerifier.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 (_ChannelVerifier *ChannelVerifierFilterer) ParseOwnershipTransferred(log types.Log) (*ChannelVerifierOwnershipTransferred, error) { - event := new(ChannelVerifierOwnershipTransferred) - if err := _ChannelVerifier.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -type ChannelVerifierReportVerifiedIterator struct { - Event *ChannelVerifierReportVerified - - contract *bind.BoundContract - event string - - logs chan types.Log - sub ethereum.Subscription - done bool - fail error -} - -func (it *ChannelVerifierReportVerifiedIterator) Next() bool { - - if it.fail != nil { - return false - } - - if it.done { - select { - case log := <-it.logs: - it.Event = new(ChannelVerifierReportVerified) - 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(ChannelVerifierReportVerified) - 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 *ChannelVerifierReportVerifiedIterator) Error() error { - return it.fail -} - -func (it *ChannelVerifierReportVerifiedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -type ChannelVerifierReportVerified struct { - FeedId [32]byte - Requester common.Address - Raw types.Log -} - -func (_ChannelVerifier *ChannelVerifierFilterer) FilterReportVerified(opts *bind.FilterOpts, feedId [][32]byte) (*ChannelVerifierReportVerifiedIterator, error) { - - var feedIdRule []interface{} - for _, feedIdItem := range feedId { - feedIdRule = append(feedIdRule, feedIdItem) - } - - logs, sub, err := _ChannelVerifier.contract.FilterLogs(opts, "ReportVerified", feedIdRule) - if err != nil { - return nil, err - } - return &ChannelVerifierReportVerifiedIterator{contract: _ChannelVerifier.contract, event: "ReportVerified", logs: logs, sub: sub}, nil -} - -func (_ChannelVerifier *ChannelVerifierFilterer) WatchReportVerified(opts *bind.WatchOpts, sink chan<- *ChannelVerifierReportVerified, feedId [][32]byte) (event.Subscription, error) { - - var feedIdRule []interface{} - for _, feedIdItem := range feedId { - feedIdRule = append(feedIdRule, feedIdItem) - } - - logs, sub, err := _ChannelVerifier.contract.WatchLogs(opts, "ReportVerified", feedIdRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - - event := new(ChannelVerifierReportVerified) - if err := _ChannelVerifier.contract.UnpackLog(event, "ReportVerified", 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 (_ChannelVerifier *ChannelVerifierFilterer) ParseReportVerified(log types.Log) (*ChannelVerifierReportVerified, error) { - event := new(ChannelVerifierReportVerified) - if err := _ChannelVerifier.contract.UnpackLog(event, "ReportVerified", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -type LatestConfigDetails struct { - ConfigCount uint32 - BlockNumber uint32 - ConfigDigest [32]byte -} -type LatestConfigDigestAndEpoch struct { - ScanLogs bool - ConfigDigest [32]byte - Epoch uint32 -} - -func (_ChannelVerifier *ChannelVerifier) ParseLog(log types.Log) (generated.AbigenLog, error) { - switch log.Topics[0] { - case _ChannelVerifier.abi.Events["ConfigActivated"].ID: - return _ChannelVerifier.ParseConfigActivated(log) - case _ChannelVerifier.abi.Events["ConfigDeactivated"].ID: - return _ChannelVerifier.ParseConfigDeactivated(log) - case _ChannelVerifier.abi.Events["ConfigSet"].ID: - return _ChannelVerifier.ParseConfigSet(log) - case _ChannelVerifier.abi.Events["FeedActivated"].ID: - return _ChannelVerifier.ParseFeedActivated(log) - case _ChannelVerifier.abi.Events["FeedDeactivated"].ID: - return _ChannelVerifier.ParseFeedDeactivated(log) - case _ChannelVerifier.abi.Events["OwnershipTransferRequested"].ID: - return _ChannelVerifier.ParseOwnershipTransferRequested(log) - case _ChannelVerifier.abi.Events["OwnershipTransferred"].ID: - return _ChannelVerifier.ParseOwnershipTransferred(log) - case _ChannelVerifier.abi.Events["ReportVerified"].ID: - return _ChannelVerifier.ParseReportVerified(log) - - default: - return nil, fmt.Errorf("abigen wrapper received unknown log topic: %v", log.Topics[0]) - } -} - -func (ChannelVerifierConfigActivated) Topic() common.Hash { - return common.HexToHash("0xa543797a0501218bba8a3daf75a71c8df8d1a7f791f4e44d40e43b6450183cea") -} - -func (ChannelVerifierConfigDeactivated) Topic() common.Hash { - return common.HexToHash("0x5bfaab86edc1b932e3c334327a591c9ded067cb521abae19b95ca927d6076579") -} - -func (ChannelVerifierConfigSet) Topic() common.Hash { - return common.HexToHash("0x1074b4b9a073f79bd1f7f5c808348125ce0f25c27188df7efcaa7a08276051b3") -} - -func (ChannelVerifierFeedActivated) Topic() common.Hash { - return common.HexToHash("0xf438564f793525caa89c6e3a26d41e16aa39d1e589747595751e3f3df75cb2b4") -} - -func (ChannelVerifierFeedDeactivated) Topic() common.Hash { - return common.HexToHash("0xfc4f79b8c65b6be1773063461984c0974400d1e99654c79477a092ace83fd061") -} - -func (ChannelVerifierOwnershipTransferRequested) Topic() common.Hash { - return common.HexToHash("0xed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278") -} - -func (ChannelVerifierOwnershipTransferred) Topic() common.Hash { - return common.HexToHash("0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0") -} - -func (ChannelVerifierReportVerified) Topic() common.Hash { - return common.HexToHash("0x58ca9502e98a536e06e72d680fcc251e5d10b72291a281665a2c2dc0ac30fcc5") -} - -func (_ChannelVerifier *ChannelVerifier) Address() common.Address { - return _ChannelVerifier.address -} - -type ChannelVerifierInterface interface { - LatestConfigDetails(opts *bind.CallOpts) (LatestConfigDetails, - - error) - - LatestConfigDigestAndEpoch(opts *bind.CallOpts) (LatestConfigDigestAndEpoch, - - error) - - Owner(opts *bind.CallOpts) (common.Address, error) - - SupportsInterface(opts *bind.CallOpts, interfaceId [4]byte) (bool, error) - - TypeAndVersion(opts *bind.CallOpts) (string, error) - - AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) - - ActivateConfig(opts *bind.TransactOpts, configDigest [32]byte) (*types.Transaction, error) - - ActivateFeed(opts *bind.TransactOpts, feedId [32]byte) (*types.Transaction, error) - - DeactivateConfig(opts *bind.TransactOpts, configDigest [32]byte) (*types.Transaction, error) - - DeactivateFeed(opts *bind.TransactOpts, feedId [32]byte) (*types.Transaction, error) - - SetConfig(opts *bind.TransactOpts, signers []common.Address, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte, recipientAddressesAndWeights []CommonAddressAndWeight) (*types.Transaction, error) - - SetConfigFromSource(opts *bind.TransactOpts, sourceChainId *big.Int, sourceAddress common.Address, newConfigCount uint32, signers []common.Address, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte, recipientAddressesAndWeights []CommonAddressAndWeight) (*types.Transaction, error) - - TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) - - Verify(opts *bind.TransactOpts, signedReport []byte, sender common.Address) (*types.Transaction, error) - - FilterConfigActivated(opts *bind.FilterOpts) (*ChannelVerifierConfigActivatedIterator, error) - - WatchConfigActivated(opts *bind.WatchOpts, sink chan<- *ChannelVerifierConfigActivated) (event.Subscription, error) - - ParseConfigActivated(log types.Log) (*ChannelVerifierConfigActivated, error) - - FilterConfigDeactivated(opts *bind.FilterOpts) (*ChannelVerifierConfigDeactivatedIterator, error) - - WatchConfigDeactivated(opts *bind.WatchOpts, sink chan<- *ChannelVerifierConfigDeactivated) (event.Subscription, error) - - ParseConfigDeactivated(log types.Log) (*ChannelVerifierConfigDeactivated, error) - - FilterConfigSet(opts *bind.FilterOpts) (*ChannelVerifierConfigSetIterator, error) - - WatchConfigSet(opts *bind.WatchOpts, sink chan<- *ChannelVerifierConfigSet) (event.Subscription, error) - - ParseConfigSet(log types.Log) (*ChannelVerifierConfigSet, error) - - FilterFeedActivated(opts *bind.FilterOpts, feedId [][32]byte) (*ChannelVerifierFeedActivatedIterator, error) - - WatchFeedActivated(opts *bind.WatchOpts, sink chan<- *ChannelVerifierFeedActivated, feedId [][32]byte) (event.Subscription, error) - - ParseFeedActivated(log types.Log) (*ChannelVerifierFeedActivated, error) - - FilterFeedDeactivated(opts *bind.FilterOpts, feedId [][32]byte) (*ChannelVerifierFeedDeactivatedIterator, error) - - WatchFeedDeactivated(opts *bind.WatchOpts, sink chan<- *ChannelVerifierFeedDeactivated, feedId [][32]byte) (event.Subscription, error) - - ParseFeedDeactivated(log types.Log) (*ChannelVerifierFeedDeactivated, error) - - FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*ChannelVerifierOwnershipTransferRequestedIterator, error) - - WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *ChannelVerifierOwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error) - - ParseOwnershipTransferRequested(log types.Log) (*ChannelVerifierOwnershipTransferRequested, error) - - FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*ChannelVerifierOwnershipTransferredIterator, error) - - WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *ChannelVerifierOwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error) - - ParseOwnershipTransferred(log types.Log) (*ChannelVerifierOwnershipTransferred, error) - - FilterReportVerified(opts *bind.FilterOpts, feedId [][32]byte) (*ChannelVerifierReportVerifiedIterator, error) - - WatchReportVerified(opts *bind.WatchOpts, sink chan<- *ChannelVerifierReportVerified, feedId [][32]byte) (event.Subscription, error) - - ParseReportVerified(log types.Log) (*ChannelVerifierReportVerified, error) - - ParseLog(log types.Log) (generated.AbigenLog, error) - - Address() common.Address -} diff --git a/core/gethwrappers/llo-feeds/generated/configurator/configurator.go b/core/gethwrappers/llo-feeds/generated/configurator/configurator.go new file mode 100644 index 0000000000..3e6f1c7e2d --- /dev/null +++ b/core/gethwrappers/llo-feeds/generated/configurator/configurator.go @@ -0,0 +1,748 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package configurator + +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 ConfiguratorMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"numSigners\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxSigners\",\"type\":\"uint256\"}],\"name\":\"ExcessSigners\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FaultToleranceMustBePositive\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"numSigners\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minSigners\",\"type\":\"uint256\"}],\"name\":\"InsufficientSigners\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"configId\",\"type\":\"bytes32\"},{\"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\":\"bytes32[]\",\"name\":\"offchainTransmitters\",\"type\":\"bytes32[]\"},{\"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\":\"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\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"donId\",\"type\":\"bytes32\"},{\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"offchainTransmitters\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"onchainConfig\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"}],\"name\":\"setConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"isVerifier\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}]", + Bin: "0x608060405234801561001057600080fd5b5033806000816100675760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615610097576100978161009f565b505050610148565b336001600160a01b038216036100f75760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640161005e565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b610d21806101576000396000f3fe608060405234801561001057600080fd5b50600436106100725760003560e01c806379ba50971161005057806379ba5097146101355780638da5cb5b1461013d578063f2fde38b1461016557600080fd5b806301ffc9a714610077578063181f5a77146100e157806344a0b2ad14610120575b600080fd5b6100cc6100853660046106c9565b7fffffffff00000000000000000000000000000000000000000000000000000000167f44a0b2ad000000000000000000000000000000000000000000000000000000001490565b60405190151581526020015b60405180910390f35b604080518082018252601281527f436f6e666967757261746f7220302e342e300000000000000000000000000000602082015290516100d89190610776565b61013361012e3660046109d8565b610178565b005b610133610289565b60005460405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100d8565b610133610173366004610ab0565b610386565b85518460ff16806000036101b8576040517f0743bae600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601f821115610202576040517f61750f4000000000000000000000000000000000000000000000000000000000815260048101839052601f60248201526044015b60405180910390fd5b61020d816003610afa565b8211610265578161021f826003610afa565b61022a906001610b17565b6040517f9dd9e6d8000000000000000000000000000000000000000000000000000000008152600481019290925260248201526044016101f9565b61026d61039a565b61027e8946308b8b8b8b8b8b61041d565b505050505050505050565b60015473ffffffffffffffffffffffffffffffffffffffff16331461030a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064016101f9565b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b61038e61039a565b61039781610526565b50565b60005473ffffffffffffffffffffffffffffffffffffffff16331461041b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e65720000000000000000000060448201526064016101f9565b565b60008981526002602052604081208054909190829082906104479067ffffffffffffffff16610b2a565b91906101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055905060006104828c8c8c858d8d8d8d8d8d61061b565b90508b7fa23a88453230b183877098801ff5a8f771a120e2573eea559ce6c4c2e305a4da8460000160089054906101000a900463ffffffff1683858d8d8d8d8d8d6040516104d899989796959493929190610bd2565b60405180910390a2505080547fffffffffffffffffffffffffffffffffffffffff00000000ffffffffffffffff16680100000000000000004363ffffffff1602179055505050505050505050565b3373ffffffffffffffffffffffffffffffffffffffff8216036105a5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c6600000000000000000060448201526064016101f9565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6000808b8b8b8b8b8b8b8b8b8b6040516020016106419a99989796959493929190610c67565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291905280516020909101207dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e09000000000000000000000000000000000000000000000000000000000000179150509a9950505050505050505050565b6000602082840312156106db57600080fd5b81357fffffffff000000000000000000000000000000000000000000000000000000008116811461070b57600080fd5b9392505050565b6000815180845260005b818110156107385760208185018101518683018201520161071c565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b60208152600061070b6020830184610712565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156107ff576107ff610789565b604052919050565b600067ffffffffffffffff82111561082157610821610789565b5060051b60200190565b803573ffffffffffffffffffffffffffffffffffffffff8116811461084f57600080fd5b919050565b600082601f83011261086557600080fd5b8135602061087a61087583610807565b6107b8565b82815260059290921b8401810191818101908684111561089957600080fd5b8286015b848110156108bb576108ae8161082b565b835291830191830161089d565b509695505050505050565b600082601f8301126108d757600080fd5b813560206108e761087583610807565b82815260059290921b8401810191818101908684111561090657600080fd5b8286015b848110156108bb578035835291830191830161090a565b803560ff8116811461084f57600080fd5b600082601f83011261094357600080fd5b813567ffffffffffffffff81111561095d5761095d610789565b61098e60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116016107b8565b8181528460208386010111156109a357600080fd5b816020850160208301376000918101602001919091529392505050565b803567ffffffffffffffff8116811461084f57600080fd5b600080600080600080600060e0888a0312156109f357600080fd5b87359650602088013567ffffffffffffffff80821115610a1257600080fd5b610a1e8b838c01610854565b975060408a0135915080821115610a3457600080fd5b610a408b838c016108c6565b9650610a4e60608b01610921565b955060808a0135915080821115610a6457600080fd5b610a708b838c01610932565b9450610a7e60a08b016109c0565b935060c08a0135915080821115610a9457600080fd5b50610aa18a828b01610932565b91505092959891949750929550565b600060208284031215610ac257600080fd5b61070b8261082b565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8082028115828204841417610b1157610b11610acb565b92915050565b80820180821115610b1157610b11610acb565b600067ffffffffffffffff808316818103610b4757610b47610acb565b6001019392505050565b600081518084526020808501945080840160005b83811015610b9757815173ffffffffffffffffffffffffffffffffffffffff1687529582019590820190600101610b65565b509495945050505050565b600081518084526020808501945080840160005b83811015610b9757815187529582019590820190600101610bb6565b600061012063ffffffff8c1683528a602084015267ffffffffffffffff808b166040850152816060850152610c098285018b610b51565b91508382036080850152610c1d828a610ba2565b915060ff881660a085015283820360c0850152610c3a8288610712565b90861660e08501528381036101008501529050610c578185610712565b9c9b505050505050505050505050565b60006101408c83528b602084015273ffffffffffffffffffffffffffffffffffffffff8b16604084015267ffffffffffffffff808b166060850152816080850152610cb48285018b610b51565b915083820360a0850152610cc8828a610ba2565b915060ff881660c085015283820360e0850152610ce58288610712565b9086166101008501528381036101208501529050610d038185610712565b9d9c5050505050505050505050505056fea164736f6c6343000813000a", +} + +var ConfiguratorABI = ConfiguratorMetaData.ABI + +var ConfiguratorBin = ConfiguratorMetaData.Bin + +func DeployConfigurator(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *Configurator, error) { + parsed, err := ConfiguratorMetaData.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(ConfiguratorBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &Configurator{address: address, abi: *parsed, ConfiguratorCaller: ConfiguratorCaller{contract: contract}, ConfiguratorTransactor: ConfiguratorTransactor{contract: contract}, ConfiguratorFilterer: ConfiguratorFilterer{contract: contract}}, nil +} + +type Configurator struct { + address common.Address + abi abi.ABI + ConfiguratorCaller + ConfiguratorTransactor + ConfiguratorFilterer +} + +type ConfiguratorCaller struct { + contract *bind.BoundContract +} + +type ConfiguratorTransactor struct { + contract *bind.BoundContract +} + +type ConfiguratorFilterer struct { + contract *bind.BoundContract +} + +type ConfiguratorSession struct { + Contract *Configurator + CallOpts bind.CallOpts + TransactOpts bind.TransactOpts +} + +type ConfiguratorCallerSession struct { + Contract *ConfiguratorCaller + CallOpts bind.CallOpts +} + +type ConfiguratorTransactorSession struct { + Contract *ConfiguratorTransactor + TransactOpts bind.TransactOpts +} + +type ConfiguratorRaw struct { + Contract *Configurator +} + +type ConfiguratorCallerRaw struct { + Contract *ConfiguratorCaller +} + +type ConfiguratorTransactorRaw struct { + Contract *ConfiguratorTransactor +} + +func NewConfigurator(address common.Address, backend bind.ContractBackend) (*Configurator, error) { + abi, err := abi.JSON(strings.NewReader(ConfiguratorABI)) + if err != nil { + return nil, err + } + contract, err := bindConfigurator(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &Configurator{address: address, abi: abi, ConfiguratorCaller: ConfiguratorCaller{contract: contract}, ConfiguratorTransactor: ConfiguratorTransactor{contract: contract}, ConfiguratorFilterer: ConfiguratorFilterer{contract: contract}}, nil +} + +func NewConfiguratorCaller(address common.Address, caller bind.ContractCaller) (*ConfiguratorCaller, error) { + contract, err := bindConfigurator(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &ConfiguratorCaller{contract: contract}, nil +} + +func NewConfiguratorTransactor(address common.Address, transactor bind.ContractTransactor) (*ConfiguratorTransactor, error) { + contract, err := bindConfigurator(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &ConfiguratorTransactor{contract: contract}, nil +} + +func NewConfiguratorFilterer(address common.Address, filterer bind.ContractFilterer) (*ConfiguratorFilterer, error) { + contract, err := bindConfigurator(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &ConfiguratorFilterer{contract: contract}, nil +} + +func bindConfigurator(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := ConfiguratorMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +func (_Configurator *ConfiguratorRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Configurator.Contract.ConfiguratorCaller.contract.Call(opts, result, method, params...) +} + +func (_Configurator *ConfiguratorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Configurator.Contract.ConfiguratorTransactor.contract.Transfer(opts) +} + +func (_Configurator *ConfiguratorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Configurator.Contract.ConfiguratorTransactor.contract.Transact(opts, method, params...) +} + +func (_Configurator *ConfiguratorCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Configurator.Contract.contract.Call(opts, result, method, params...) +} + +func (_Configurator *ConfiguratorTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Configurator.Contract.contract.Transfer(opts) +} + +func (_Configurator *ConfiguratorTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Configurator.Contract.contract.Transact(opts, method, params...) +} + +func (_Configurator *ConfiguratorCaller) Owner(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _Configurator.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 (_Configurator *ConfiguratorSession) Owner() (common.Address, error) { + return _Configurator.Contract.Owner(&_Configurator.CallOpts) +} + +func (_Configurator *ConfiguratorCallerSession) Owner() (common.Address, error) { + return _Configurator.Contract.Owner(&_Configurator.CallOpts) +} + +func (_Configurator *ConfiguratorCaller) SupportsInterface(opts *bind.CallOpts, interfaceId [4]byte) (bool, error) { + var out []interface{} + err := _Configurator.contract.Call(opts, &out, "supportsInterface", interfaceId) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +func (_Configurator *ConfiguratorSession) SupportsInterface(interfaceId [4]byte) (bool, error) { + return _Configurator.Contract.SupportsInterface(&_Configurator.CallOpts, interfaceId) +} + +func (_Configurator *ConfiguratorCallerSession) SupportsInterface(interfaceId [4]byte) (bool, error) { + return _Configurator.Contract.SupportsInterface(&_Configurator.CallOpts, interfaceId) +} + +func (_Configurator *ConfiguratorCaller) TypeAndVersion(opts *bind.CallOpts) (string, error) { + var out []interface{} + err := _Configurator.contract.Call(opts, &out, "typeAndVersion") + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + +} + +func (_Configurator *ConfiguratorSession) TypeAndVersion() (string, error) { + return _Configurator.Contract.TypeAndVersion(&_Configurator.CallOpts) +} + +func (_Configurator *ConfiguratorCallerSession) TypeAndVersion() (string, error) { + return _Configurator.Contract.TypeAndVersion(&_Configurator.CallOpts) +} + +func (_Configurator *ConfiguratorTransactor) AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Configurator.contract.Transact(opts, "acceptOwnership") +} + +func (_Configurator *ConfiguratorSession) AcceptOwnership() (*types.Transaction, error) { + return _Configurator.Contract.AcceptOwnership(&_Configurator.TransactOpts) +} + +func (_Configurator *ConfiguratorTransactorSession) AcceptOwnership() (*types.Transaction, error) { + return _Configurator.Contract.AcceptOwnership(&_Configurator.TransactOpts) +} + +func (_Configurator *ConfiguratorTransactor) SetConfig(opts *bind.TransactOpts, donId [32]byte, signers []common.Address, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte) (*types.Transaction, error) { + return _Configurator.contract.Transact(opts, "setConfig", donId, signers, offchainTransmitters, f, onchainConfig, offchainConfigVersion, offchainConfig) +} + +func (_Configurator *ConfiguratorSession) SetConfig(donId [32]byte, signers []common.Address, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte) (*types.Transaction, error) { + return _Configurator.Contract.SetConfig(&_Configurator.TransactOpts, donId, signers, offchainTransmitters, f, onchainConfig, offchainConfigVersion, offchainConfig) +} + +func (_Configurator *ConfiguratorTransactorSession) SetConfig(donId [32]byte, signers []common.Address, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte) (*types.Transaction, error) { + return _Configurator.Contract.SetConfig(&_Configurator.TransactOpts, donId, signers, offchainTransmitters, f, onchainConfig, offchainConfigVersion, offchainConfig) +} + +func (_Configurator *ConfiguratorTransactor) TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) { + return _Configurator.contract.Transact(opts, "transferOwnership", to) +} + +func (_Configurator *ConfiguratorSession) TransferOwnership(to common.Address) (*types.Transaction, error) { + return _Configurator.Contract.TransferOwnership(&_Configurator.TransactOpts, to) +} + +func (_Configurator *ConfiguratorTransactorSession) TransferOwnership(to common.Address) (*types.Transaction, error) { + return _Configurator.Contract.TransferOwnership(&_Configurator.TransactOpts, to) +} + +type ConfiguratorConfigSetIterator struct { + Event *ConfiguratorConfigSet + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *ConfiguratorConfigSetIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(ConfiguratorConfigSet) + 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(ConfiguratorConfigSet) + 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 *ConfiguratorConfigSetIterator) Error() error { + return it.fail +} + +func (it *ConfiguratorConfigSetIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type ConfiguratorConfigSet struct { + ConfigId [32]byte + PreviousConfigBlockNumber uint32 + ConfigDigest [32]byte + ConfigCount uint64 + Signers []common.Address + OffchainTransmitters [][32]byte + F uint8 + OnchainConfig []byte + OffchainConfigVersion uint64 + OffchainConfig []byte + Raw types.Log +} + +func (_Configurator *ConfiguratorFilterer) FilterConfigSet(opts *bind.FilterOpts, configId [][32]byte) (*ConfiguratorConfigSetIterator, error) { + + var configIdRule []interface{} + for _, configIdItem := range configId { + configIdRule = append(configIdRule, configIdItem) + } + + logs, sub, err := _Configurator.contract.FilterLogs(opts, "ConfigSet", configIdRule) + if err != nil { + return nil, err + } + return &ConfiguratorConfigSetIterator{contract: _Configurator.contract, event: "ConfigSet", logs: logs, sub: sub}, nil +} + +func (_Configurator *ConfiguratorFilterer) WatchConfigSet(opts *bind.WatchOpts, sink chan<- *ConfiguratorConfigSet, configId [][32]byte) (event.Subscription, error) { + + var configIdRule []interface{} + for _, configIdItem := range configId { + configIdRule = append(configIdRule, configIdItem) + } + + logs, sub, err := _Configurator.contract.WatchLogs(opts, "ConfigSet", configIdRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(ConfiguratorConfigSet) + if err := _Configurator.contract.UnpackLog(event, "ConfigSet", 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 (_Configurator *ConfiguratorFilterer) ParseConfigSet(log types.Log) (*ConfiguratorConfigSet, error) { + event := new(ConfiguratorConfigSet) + if err := _Configurator.contract.UnpackLog(event, "ConfigSet", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type ConfiguratorOwnershipTransferRequestedIterator struct { + Event *ConfiguratorOwnershipTransferRequested + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *ConfiguratorOwnershipTransferRequestedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(ConfiguratorOwnershipTransferRequested) + 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(ConfiguratorOwnershipTransferRequested) + 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 *ConfiguratorOwnershipTransferRequestedIterator) Error() error { + return it.fail +} + +func (it *ConfiguratorOwnershipTransferRequestedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type ConfiguratorOwnershipTransferRequested struct { + From common.Address + To common.Address + Raw types.Log +} + +func (_Configurator *ConfiguratorFilterer) FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*ConfiguratorOwnershipTransferRequestedIterator, 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 := _Configurator.contract.FilterLogs(opts, "OwnershipTransferRequested", fromRule, toRule) + if err != nil { + return nil, err + } + return &ConfiguratorOwnershipTransferRequestedIterator{contract: _Configurator.contract, event: "OwnershipTransferRequested", logs: logs, sub: sub}, nil +} + +func (_Configurator *ConfiguratorFilterer) WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *ConfiguratorOwnershipTransferRequested, 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 := _Configurator.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(ConfiguratorOwnershipTransferRequested) + if err := _Configurator.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 (_Configurator *ConfiguratorFilterer) ParseOwnershipTransferRequested(log types.Log) (*ConfiguratorOwnershipTransferRequested, error) { + event := new(ConfiguratorOwnershipTransferRequested) + if err := _Configurator.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type ConfiguratorOwnershipTransferredIterator struct { + Event *ConfiguratorOwnershipTransferred + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *ConfiguratorOwnershipTransferredIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(ConfiguratorOwnershipTransferred) + 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(ConfiguratorOwnershipTransferred) + 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 *ConfiguratorOwnershipTransferredIterator) Error() error { + return it.fail +} + +func (it *ConfiguratorOwnershipTransferredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type ConfiguratorOwnershipTransferred struct { + From common.Address + To common.Address + Raw types.Log +} + +func (_Configurator *ConfiguratorFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*ConfiguratorOwnershipTransferredIterator, 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 := _Configurator.contract.FilterLogs(opts, "OwnershipTransferred", fromRule, toRule) + if err != nil { + return nil, err + } + return &ConfiguratorOwnershipTransferredIterator{contract: _Configurator.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil +} + +func (_Configurator *ConfiguratorFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *ConfiguratorOwnershipTransferred, 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 := _Configurator.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(ConfiguratorOwnershipTransferred) + if err := _Configurator.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 (_Configurator *ConfiguratorFilterer) ParseOwnershipTransferred(log types.Log) (*ConfiguratorOwnershipTransferred, error) { + event := new(ConfiguratorOwnershipTransferred) + if err := _Configurator.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +func (_Configurator *Configurator) ParseLog(log types.Log) (generated.AbigenLog, error) { + switch log.Topics[0] { + case _Configurator.abi.Events["ConfigSet"].ID: + return _Configurator.ParseConfigSet(log) + case _Configurator.abi.Events["OwnershipTransferRequested"].ID: + return _Configurator.ParseOwnershipTransferRequested(log) + case _Configurator.abi.Events["OwnershipTransferred"].ID: + return _Configurator.ParseOwnershipTransferred(log) + + default: + return nil, fmt.Errorf("abigen wrapper received unknown log topic: %v", log.Topics[0]) + } +} + +func (ConfiguratorConfigSet) Topic() common.Hash { + return common.HexToHash("0xa23a88453230b183877098801ff5a8f771a120e2573eea559ce6c4c2e305a4da") +} + +func (ConfiguratorOwnershipTransferRequested) Topic() common.Hash { + return common.HexToHash("0xed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278") +} + +func (ConfiguratorOwnershipTransferred) Topic() common.Hash { + return common.HexToHash("0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0") +} + +func (_Configurator *Configurator) Address() common.Address { + return _Configurator.address +} + +type ConfiguratorInterface interface { + Owner(opts *bind.CallOpts) (common.Address, error) + + SupportsInterface(opts *bind.CallOpts, interfaceId [4]byte) (bool, error) + + TypeAndVersion(opts *bind.CallOpts) (string, error) + + AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) + + SetConfig(opts *bind.TransactOpts, donId [32]byte, signers []common.Address, offchainTransmitters [][32]byte, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte) (*types.Transaction, error) + + TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) + + FilterConfigSet(opts *bind.FilterOpts, configId [][32]byte) (*ConfiguratorConfigSetIterator, error) + + WatchConfigSet(opts *bind.WatchOpts, sink chan<- *ConfiguratorConfigSet, configId [][32]byte) (event.Subscription, error) + + ParseConfigSet(log types.Log) (*ConfiguratorConfigSet, error) + + FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*ConfiguratorOwnershipTransferRequestedIterator, error) + + WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *ConfiguratorOwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error) + + ParseOwnershipTransferRequested(log types.Log) (*ConfiguratorOwnershipTransferRequested, error) + + FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*ConfiguratorOwnershipTransferredIterator, error) + + WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *ConfiguratorOwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error) + + ParseOwnershipTransferred(log types.Log) (*ConfiguratorOwnershipTransferred, error) + + ParseLog(log types.Log) (generated.AbigenLog, error) + + Address() common.Address +} diff --git a/core/gethwrappers/llo-feeds/generated/destination_fee_manager/destination_fee_manager.go b/core/gethwrappers/llo-feeds/generated/destination_fee_manager/destination_fee_manager.go new file mode 100644 index 0000000000..fc9cda0b3d --- /dev/null +++ b/core/gethwrappers/llo-feeds/generated/destination_fee_manager/destination_fee_manager.go @@ -0,0 +1,1828 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package destination_fee_manager + +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 +) + +type CommonAddressAndWeight struct { + Addr common.Address + Weight uint64 +} + +type CommonAsset struct { + AssetAddress common.Address + Amount *big.Int +} + +type IDestinationRewardManagerFeePayment struct { + PoolId [32]byte + Amount *big.Int +} + +var DestinationFeeManagerMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_linkAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_nativeAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_verifierAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_rewardManagerAddress\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"ExpiredReport\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidAddress\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidDeposit\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidDiscount\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidQuote\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidReceivingAddress\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidSurcharge\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"PoolIdMismatch\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"Unauthorized\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroDeficit\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"subscriber\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"assetAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"indexed\":false,\"internalType\":\"structCommon.Asset\",\"name\":\"fee\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"assetAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"indexed\":false,\"internalType\":\"structCommon.Asset\",\"name\":\"reward\",\"type\":\"tuple\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"appliedDiscount\",\"type\":\"uint256\"}],\"name\":\"DiscountApplied\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"poolId\",\"type\":\"bytes32\"},{\"internalType\":\"uint192\",\"name\":\"amount\",\"type\":\"uint192\"}],\"indexed\":false,\"internalType\":\"structIDestinationRewardManager.FeePayment[]\",\"name\":\"rewards\",\"type\":\"tuple[]\"}],\"name\":\"InsufficientLink\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"linkQuantity\",\"type\":\"uint256\"}],\"name\":\"LinkDeficitCleared\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"newSurcharge\",\"type\":\"uint64\"}],\"name\":\"NativeSurchargeUpdated\",\"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\":true,\"internalType\":\"address\",\"name\":\"subscriber\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"feedId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"discount\",\"type\":\"uint64\"}],\"name\":\"SubscriberDiscountUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"adminAddress\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"assetAddress\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint192\",\"name\":\"quantity\",\"type\":\"uint192\"}],\"name\":\"Withdraw\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"verifierAddress\",\"type\":\"address\"}],\"name\":\"addVerifier\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"subscriber\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"report\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"quoteAddress\",\"type\":\"address\"}],\"name\":\"getFeeAndReward\",\"outputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"assetAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structCommon.Asset\",\"name\":\"\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"assetAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structCommon.Asset\",\"name\":\"\",\"type\":\"tuple\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"i_linkAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"i_nativeAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"i_rewardManager\",\"outputs\":[{\"internalType\":\"contractIDestinationRewardManager\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"linkAvailableForPayment\",\"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\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"}],\"name\":\"payLinkDeficit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"recipient\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"payload\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"parameterPayload\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"subscriber\",\"type\":\"address\"}],\"name\":\"processFee\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32[]\",\"name\":\"poolIds\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes[]\",\"name\":\"payloads\",\"type\":\"bytes[]\"},{\"internalType\":\"bytes\",\"name\":\"parameterPayload\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"subscriber\",\"type\":\"address\"}],\"name\":\"processFeeBulk\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"verifierAddress\",\"type\":\"address\"}],\"name\":\"removeVerifier\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"s_globalDiscounts\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"s_linkDeficit\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_nativeSurcharge\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"s_subscriberDiscounts\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"s_verifierAddressList\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"weight\",\"type\":\"uint64\"}],\"internalType\":\"structCommon.AddressAndWeight[]\",\"name\":\"rewardRecipientAndWeights\",\"type\":\"tuple[]\"}],\"name\":\"setFeeRecipients\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"surcharge\",\"type\":\"uint64\"}],\"name\":\"setNativeSurcharge\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"rewardManagerAddress\",\"type\":\"address\"}],\"name\":\"setRewardManager\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"subscriber\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"feedId\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"discount\",\"type\":\"uint64\"}],\"name\":\"updateSubscriberDiscount\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"subscriber\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"discount\",\"type\":\"uint64\"}],\"name\":\"updateSubscriberGlobalDiscount\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"assetAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint192\",\"name\":\"quantity\",\"type\":\"uint192\"}],\"name\":\"withdraw\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + Bin: "0x60c06040523480156200001157600080fd5b5060405162003d4538038062003d458339810160408190526200003491620002ae565b33806000816200008b5760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620000be57620000be81620001e6565b5050506001600160a01b0384161580620000df57506001600160a01b038316155b80620000f257506001600160a01b038216155b806200010557506001600160a01b038116155b15620001245760405163e6c4247b60e01b815260040160405180910390fd5b6001600160a01b03848116608081905284821660a0528382166000818152600560205260409081902080546001600160a01b03199081169093179055600680549092169385169384179091555163095ea7b360e01b8152600481019290925260001960248301529063095ea7b3906044016020604051808303816000875af1158015620001b5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620001db91906200030b565b505050505062000336565b336001600160a01b03821603620002405760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640162000082565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b80516001600160a01b0381168114620002a957600080fd5b919050565b60008060008060808587031215620002c557600080fd5b620002d08562000291565b9350620002e06020860162000291565b9250620002f06040860162000291565b9150620003006060860162000291565b905092959194509250565b6000602082840312156200031e57600080fd5b815180151581146200032f57600080fd5b9392505050565b60805160a051613956620003ef6000396000818161038901528181610f3d01528181611577015281816117d50152818161182c01528181611b2a0152818161251401526125bd0152600081816105b1015281816108340152818161093e01528181610d4101528181610ee60152818161124f01528181611520015281816116b7015281816117fa0152818161188301528181611a1b01528181611a8601528181611ac60152818161218f01526126b101526139566000f3fe6080604052600436106101a15760003560e01c806379ba5097116100e1578063ce7817d11161008a578063e389d9a411610064578063e389d9a41461057f578063ea4b861b1461059f578063f2fde38b146105d3578063f65df962146105f357600080fd5b8063ce7817d1146104df578063d09dc339146104ff578063e03dab1a1461051457600080fd5b80638da5cb5b116100bb5780638da5cb5b146104745780639000b3d61461049f578063ca2dfd0a146104bf57600080fd5b806379ba50971461040e57806386968cfd1461042357806387d6d8431461043657600080fd5b806332f5f7461161014e57806350538094116101285780635053809414610357578063638786681461037757806376cf3187146103ab5780637700feeb146103cb57600080fd5b806332f5f746146102dc57806336907509146102f25780633aa5ac071461030557600080fd5b8063181f5a771161017f578063181f5a77146102385780631cc7f2d8146102845780631d4d84a2146102bc57600080fd5b8063013f542b146101a657806301ffc9a7146101e6578063153ee55414610216575b600080fd5b3480156101b257600080fd5b506101d36101c1366004612e20565b60046020526000908152604090205481565b6040519081526020015b60405180910390f35b3480156101f257600080fd5b50610206610201366004612e39565b610613565b60405190151581526020016101dd565b34801561022257600080fd5b50610236610231366004612ead565b6106ac565b005b34801561024457600080fd5b50604080518082018252601b81527f44657374696e6174696f6e4665654d616e6167657220302e342e300000000000602082015290516101dd9190612eee565b34801561029057600080fd5b506101d361029f366004612f3f565b600360209081526000928352604080842090915290825290205481565b3480156102c857600080fd5b506102366102d7366004612f9e565b6109b1565b3480156102e857600080fd5b506101d360075481565b6102366103003660046130f5565b610b45565b34801561031157600080fd5b506006546103329073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016101dd565b34801561036357600080fd5b50610236610372366004613215565b610df6565b34801561038357600080fd5b506103327f000000000000000000000000000000000000000000000000000000000000000081565b3480156103b757600080fd5b506102366103c6366004613230565b610e90565b3480156103d757600080fd5b506103326103e6366004612ead565b60056020526000908152604090205473ffffffffffffffffffffffffffffffffffffffff1681565b34801561041a57600080fd5b50610236611045565b610236610431366004613277565b611147565b34801561044257600080fd5b506101d3610451366004613303565b600260209081526000938452604080852082529284528284209052825290205481565b34801561048057600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff16610332565b3480156104ab57600080fd5b506102366104ba366004612ead565b6112c8565b3480156104cb57600080fd5b506102366104da366004612ead565b6113cb565b3480156104eb57600080fd5b506102366104fa36600461333a565b6114ca565b34801561050b57600080fd5b506101d3611686565b34801561052057600080fd5b5061053461052f366004613419565b61173c565b60408051845173ffffffffffffffffffffffffffffffffffffffff9081168252602095860151868301528451169181019190915292909101516060830152608082015260a0016101dd565b34801561058b57600080fd5b5061023661059a366004612e20565b611b8c565b3480156105ab57600080fd5b506103327f000000000000000000000000000000000000000000000000000000000000000081565b3480156105df57600080fd5b506102366105ee366004612ead565b611d41565b3480156105ff57600080fd5b5061023661060e366004613472565b611d55565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f6993386f0000000000000000000000000000000000000000000000000000000014806106a657507fffffffff0000000000000000000000000000000000000000000000000000000082167f465b009600000000000000000000000000000000000000000000000000000000145b92915050565b6106b4611e43565b73ffffffffffffffffffffffffffffffffffffffff8116610701576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f01ffc9a70000000000000000000000000000000000000000000000000000000081527f768ffd3a00000000000000000000000000000000000000000000000000000000600482015273ffffffffffffffffffffffffffffffffffffffff8216906301ffc9a790602401602060405180830381865afa15801561078b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107af91906134f1565b6107e5576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6006546040517f095ea7b300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9182166004820152600060248201527f00000000000000000000000000000000000000000000000000000000000000009091169063095ea7b3906044016020604051808303816000875af115801561087f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108a391906134f1565b50600680547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff8381169182179092556040517f095ea7b300000000000000000000000000000000000000000000000000000000815260048101919091527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60248201527f00000000000000000000000000000000000000000000000000000000000000009091169063095ea7b3906044016020604051808303816000875af1158015610989573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109ad91906134f1565b5050565b6109b9611e43565b73ffffffffffffffffffffffffffffffffffffffff8316610a8e5760008273ffffffffffffffffffffffffffffffffffffffff168277ffffffffffffffffffffffffffffffffffffffffffffffff1660405160006040518083038185875af1925050503d8060008114610a48576040519150601f19603f3d011682016040523d82523d6000602084013e610a4d565b606091505b5050905080610a88576040517fef2af20100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50505050565b610ac973ffffffffffffffffffffffffffffffffffffffff84168377ffffffffffffffffffffffffffffffffffffffffffffffff8416611ec6565b6040805133815273ffffffffffffffffffffffffffffffffffffffff848116602083015285168183015277ffffffffffffffffffffffffffffffffffffffffffffffff8316606082015290517f7ff78a71698bdb18dcca96f52ab25e0a1b146fb6a49adf8e6845299e49021f299181900360800190a15b505050565b3360008181526005602052604090205473ffffffffffffffffffffffffffffffffffffffff1614610ba2576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b85518414610bdb576040517e154a0200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008467ffffffffffffffff811115610bf657610bf6612fe9565b604051908082528060200260200182016040528015610c2f57816020015b610c1c612d93565b815260200190600190039081610c145790505b5090506000806000805b88811015610dbc576000801b8b8281518110610c5757610c57613513565b602002602001015103610c96576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000806000610cca8d8d86818110610cb057610cb0613513565b9050602002810190610cc29190613542565b8d8d8d611f9a565b9250925092508260200151600014610da85760405180608001604052808f8681518110610cf957610cf9613513565b6020026020010151815260200184815260200183815260200182815250888680610d22906135d6565b975081518110610d3457610d34613513565b60200260200101819052507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16836000015173ffffffffffffffffffffffffffffffffffffffff1603610da157866001019650610da8565b8560010195505b50505080610db5906135d6565b9050610c39565b5082151580610dca57508115155b15610de057610ddb858585856120aa565b610dea565b610dea85346128a4565b50505050505050505050565b610dfe611e43565b670de0b6b3a764000067ffffffffffffffff82161115610e4a576040517f05e8ac2900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b67ffffffffffffffff811660078190556040519081527f08f7c0d17932ddb8523bc06754d42ff19ebc77d76a8b9bfde02c28ab1ed3d6399060200160405180910390a150565b610e98611e43565b670de0b6b3a764000067ffffffffffffffff82161115610ee4576040517f997ea36000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614158015610f8c57507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b15610fc3576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff838116600081815260036020908152604080832094871680845294825280832067ffffffffffffffff87169081905581519586529185019190915290927f5eba5a8afa39780f0f99b6cbeb95f3da6a7040ca00abd46bdc91a0a060134139910160405180910390a3505050565b60015473ffffffffffffffffffffffffffffffffffffffff1633146110cb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b3360008181526005602052604090205473ffffffffffffffffffffffffffffffffffffffff16146111a4576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008060006111b68888888888611f9a565b92509250925082602001516000036111da576111d284346128a4565b5050506112c0565b604080516001808252818301909252600091816020015b6111f9612d93565b8152602001906001900390816111f157905050905060405180608001604052808b8152602001858152602001848152602001838152508160008151811061124257611242613513565b60200260200101819052507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16846000015173ffffffffffffffffffffffffffffffffffffffff16036112b257610ddb8582600160006120aa565b610dea8582600060016120aa565b505050505050565b6112d0611e43565b73ffffffffffffffffffffffffffffffffffffffff811661131d576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff818116600090815260056020526040902054161561137c576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff16600081815260056020526040902080547fffffffffffffffffffffffff0000000000000000000000000000000000000000169091179055565b6113d3611e43565b73ffffffffffffffffffffffffffffffffffffffff8116611420576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8181166000908152600560205260409020541661147e576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff16600090815260056020526040902080547fffffffffffffffffffffffff0000000000000000000000000000000000000000169055565b6114d2611e43565b670de0b6b3a764000067ffffffffffffffff8216111561151e576040517f997ea36000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141580156115c657507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b156115fd576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff848116600081815260026020908152604080832088845282528083209487168084529482529182902067ffffffffffffffff86169081905582519485529084015285927f5eba5a8afa39780f0f99b6cbeb95f3da6a7040ca00abd46bdc91a0a060134139910160405180910390a350505050565b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201526000907f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16906370a0823190602401602060405180830381865afa158015611713573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611737919061360e565b905090565b604080518082018252600080825260208083018290528351808501855282815280820183905284518086018652838152808301849052855180870190965283865291850183905292938261178f88613627565b90507fffff00000000000000000000000000000000000000000000000000000000000080821690810161182a57505073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000811683527f0000000000000000000000000000000000000000000000000000000000000000168152909350915060009050611b83565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff16141580156118d257507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff1614155b15611909576040517ff861803000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008060008b8060200190518101906119229190613680565b77ffffffffffffffffffffffffffffffffffffffffffffffff91821698509116955063ffffffff169350505042821015905061198a576040517fb6c405f500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff808e1660009081526002602090815260408083208984528252808320938f1683529290529081205490819003611a04575073ffffffffffffffffffffffffffffffffffffffff808e166000908152600360209081526040808320938f16835292905220545b73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168752611a6a611a5282670de0b6b3a76400006136e6565b611a5c90866136f9565b670de0b6b3a76400006128ed565b602088015273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000008116908d1603611af75773ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016885260208088015190890152611b74565b600754600090611b1390611a5290670de0b6b3a7640000613710565b73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168a529050611b6d611b6383670de0b6b3a76400006136e6565b611a5c90836136f9565b60208a0152505b96995094975094955050505050505b93509350939050565b611b94611e43565b60008181526004602052604081205490819003611bdd576040517f03aad31200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000828152600460205260408082208290558051600180825281830190925290816020015b6040805180820190915260008082526020820152815260200190600190039081611c0257905050905060405180604001604052808481526020018377ffffffffffffffffffffffffffffffffffffffffffffffff1681525081600081518110611c6d57611c6d613513565b60209081029190910101526006546040517fb0d9fa1900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091169063b0d9fa1990611cd09084903090600401613783565b600060405180830381600087803b158015611cea57600080fd5b505af1158015611cfe573d6000803e3d6000fd5b50505050827f843f0b103e50b42b08f9d30f12f961845a6d02623730872e24644899c0dd989583604051611d3491815260200190565b60405180910390a2505050565b611d49611e43565b611d5281612925565b50565b3360008181526005602052604090205473ffffffffffffffffffffffffffffffffffffffff1614611db2576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6006546040517f14060f2300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff909116906314060f2390611e0c908690869086906004016137bb565b600060405180830381600087803b158015611e2657600080fd5b505af1158015611e3a573d6000803e3d6000fd5b50505050505050565b60005473ffffffffffffffffffffffffffffffffffffffff163314611ec4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e65720000000000000000000060448201526064016110c2565b565b60405173ffffffffffffffffffffffffffffffffffffffff8316602482015260448101829052610b409084907fa9059cbb00000000000000000000000000000000000000000000000000000000906064015b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090931692909217909152612a1a565b6040805180820190915260008082526020820152604080518082019091526000808252602082015260003073ffffffffffffffffffffffffffffffffffffffff851603612013576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000612021888a018a61383b565b91505060008161203090613627565b905060007e010000000000000000000000000000000000000000000000000000000000007fffff00000000000000000000000000000000000000000000000000000000000083161461208b57612088888a018a612ead565b90505b61209687848361173c565b955095509550505050955095509592505050565b60008267ffffffffffffffff8111156120c5576120c5612fe9565b60405190808252806020026020018201604052801561210a57816020015b60408051808201909152600080825260208201528152602001906001900390816120e35790505b50905060008267ffffffffffffffff81111561212857612128612fe9565b60405190808252806020026020018201604052801561216d57816020015b60408051808201909152600080825260208201528152602001906001900390816121465790505b509050600080808080612180888a613710565b905060005b818110156124cf577f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff168b82815181106121d6576121d6613513565b6020026020010151602001516000015173ffffffffffffffffffffffffffffffffffffffff160361229c5760405180604001604052808c838151811061221e5761221e613513565b60200260200101516000015181526020018c838151811061224157612241613513565b6020026020010151604001516020015177ffffffffffffffffffffffffffffffffffffffffffffffff1681525088858061227a906135d6565b96508151811061228c5761228c613513565b6020026020010181905250612391565b60405180604001604052808c83815181106122b9576122b9613513565b60200260200101516000015181526020018c83815181106122dc576122dc613513565b6020026020010151604001516020015177ffffffffffffffffffffffffffffffffffffffffffffffff16815250878480612315906135d6565b95508151811061232757612327613513565b60200260200101819052508a818151811061234457612344613513565b602002602001015160200151602001518661235f9190613710565b95508a818151811061237357612373613513565b602002602001015160400151602001518561238e9190613710565b94505b8a81815181106123a3576123a3613513565b6020026020010151606001516000146124bf578b73ffffffffffffffffffffffffffffffffffffffff168b82815181106123df576123df613513565b6020026020010151600001517f88b15eb682210089cddf967648e2cb2a4535aeadc8f8f36050922e33c04e71258d848151811061241e5761241e613513565b6020026020010151602001518e858151811061243c5761243c613513565b6020026020010151604001518f868151811061245a5761245a613513565b6020026020010151606001516040516124b693929190835173ffffffffffffffffffffffffffffffffffffffff908116825260209485015185830152835116604082015291909201516060820152608081019190915260a00190565b60405180910390a35b6124c8816135d6565b9050612185565b506000341561259d5734861115612512576040517fb2e532de00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663d0e30db0876040518263ffffffff1660e01b81526004016000604051808303818588803b15801561257a57600080fd5b505af115801561258e573d6000803e3d6000fd5b505050505085340390506125e5565b85156125e5576125e573ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168d3089612b26565b87511561267c57600660009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663b0d9fa19898e6040518363ffffffff1660e01b8152600401612649929190613783565b600060405180830381600087803b15801561266357600080fd5b505af1158015612677573d6000803e3d6000fd5b505050505b86511561288c576040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201527f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16906370a0823190602401602060405180830381865afa15801561270d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612731919061360e565b8511156128015760005b87518110156127c45787818151811061275657612756613513565b60200260200101516020015177ffffffffffffffffffffffffffffffffffffffffffffffff16600460008a848151811061279257612792613513565b602090810291909101810151518252810191909152604001600020805490910190556127bd816135d6565b905061273b565b507ff52e5907b69d97c33392936c12d78b494463b78c5b72df50b4c497eee5720b67876040516127f491906138df565b60405180910390a161288c565b6006546040517fb0d9fa1900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091169063b0d9fa1990612859908a903090600401613783565b600060405180830381600087803b15801561287357600080fd5b505af1158015612887573d6000803e3d6000fd5b505050505b6128968c826128a4565b505050505050505050505050565b80156109ad5760405173ffffffffffffffffffffffffffffffffffffffff83169082156108fc029083906000818181858888f19350505050158015610b40573d6000803e3d6000fd5b6000821561291b57816129016001856136e6565b61290b91906138f2565b612916906001613710565b61291e565b60005b9392505050565b3373ffffffffffffffffffffffffffffffffffffffff8216036129a4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c6600000000000000000060448201526064016110c2565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6000612a7c826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65648152508573ffffffffffffffffffffffffffffffffffffffff16612b849092919063ffffffff16565b805190915015610b405780806020019051810190612a9a91906134f1565b610b40576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f7420737563636565640000000000000000000000000000000000000000000060648201526084016110c2565b60405173ffffffffffffffffffffffffffffffffffffffff80851660248301528316604482015260648101829052610a889085907f23b872dd0000000000000000000000000000000000000000000000000000000090608401611f18565b6060612b938484600085612b9b565b949350505050565b606082471015612c2d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c000000000000000000000000000000000000000000000000000060648201526084016110c2565b6000808673ffffffffffffffffffffffffffffffffffffffff168587604051612c56919061392d565b60006040518083038185875af1925050503d8060008114612c93576040519150601f19603f3d011682016040523d82523d6000602084013e612c98565b606091505b5091509150612ca987838387612cb4565b979650505050505050565b60608315612d4a578251600003612d435773ffffffffffffffffffffffffffffffffffffffff85163b612d43576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016110c2565b5081612b93565b612b938383815115612d5f5781518083602001fd5b806040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016110c29190612eee565b604051806080016040528060008019168152602001612ddb6040518060400160405280600073ffffffffffffffffffffffffffffffffffffffff168152602001600081525090565b8152602001612e136040518060400160405280600073ffffffffffffffffffffffffffffffffffffffff168152602001600081525090565b8152602001600081525090565b600060208284031215612e3257600080fd5b5035919050565b600060208284031215612e4b57600080fd5b81357fffffffff000000000000000000000000000000000000000000000000000000008116811461291e57600080fd5b73ffffffffffffffffffffffffffffffffffffffff81168114611d5257600080fd5b8035612ea881612e7b565b919050565b600060208284031215612ebf57600080fd5b813561291e81612e7b565b60005b83811015612ee5578181015183820152602001612ecd565b50506000910152565b6020815260008251806020840152612f0d816040850160208701612eca565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169190910160400192915050565b60008060408385031215612f5257600080fd5b8235612f5d81612e7b565b91506020830135612f6d81612e7b565b809150509250929050565b77ffffffffffffffffffffffffffffffffffffffffffffffff81168114611d5257600080fd5b600080600060608486031215612fb357600080fd5b8335612fbe81612e7b565b92506020840135612fce81612e7b565b91506040840135612fde81612f78565b809150509250925092565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff8111828210171561305f5761305f612fe9565b604052919050565b60008083601f84011261307957600080fd5b50813567ffffffffffffffff81111561309157600080fd5b6020830191508360208260051b85010111156130ac57600080fd5b9250929050565b60008083601f8401126130c557600080fd5b50813567ffffffffffffffff8111156130dd57600080fd5b6020830191508360208285010111156130ac57600080fd5b6000806000806000806080878903121561310e57600080fd5b863567ffffffffffffffff8082111561312657600080fd5b818901915089601f83011261313a57600080fd5b813560208282111561314e5761314e612fe9565b8160051b61315d828201613018565b928352848101820192828101908e85111561317757600080fd5b958301955b848710156131955786358252958301959083019061317c565b9b5050508a0135925050808211156131ac57600080fd5b6131b88a838b01613067565b909750955060408901359150808211156131d157600080fd5b506131de89828a016130b3565b90945092506131f1905060608801612e9d565b90509295509295509295565b803567ffffffffffffffff81168114612ea857600080fd5b60006020828403121561322757600080fd5b61291e826131fd565b60008060006060848603121561324557600080fd5b833561325081612e7b565b9250602084013561326081612e7b565b915061326e604085016131fd565b90509250925092565b6000806000806000806080878903121561329057600080fd5b86359550602087013567ffffffffffffffff808211156132af57600080fd5b6132bb8a838b016130b3565b909750955060408901359150808211156132d457600080fd5b506132e189828a016130b3565b90945092505060608701356132f581612e7b565b809150509295509295509295565b60008060006060848603121561331857600080fd5b833561332381612e7b565b9250602084013591506040840135612fde81612e7b565b6000806000806080858703121561335057600080fd5b843561335b81612e7b565b935060208501359250604085013561337281612e7b565b9150613380606086016131fd565b905092959194509250565b600082601f83011261339c57600080fd5b813567ffffffffffffffff8111156133b6576133b6612fe9565b6133e760207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601613018565b8181528460208386010111156133fc57600080fd5b816020850160208301376000918101602001919091529392505050565b60008060006060848603121561342e57600080fd5b833561343981612e7b565b9250602084013567ffffffffffffffff81111561345557600080fd5b6134618682870161338b565b9250506040840135612fde81612e7b565b60008060006040848603121561348757600080fd5b83359250602084013567ffffffffffffffff808211156134a657600080fd5b818601915086601f8301126134ba57600080fd5b8135818111156134c957600080fd5b8760208260061b85010111156134de57600080fd5b6020830194508093505050509250925092565b60006020828403121561350357600080fd5b8151801515811461291e57600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe184360301811261357757600080fd5b83018035915067ffffffffffffffff82111561359257600080fd5b6020019150368190038213156130ac57600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203613607576136076135a7565b5060010190565b60006020828403121561362057600080fd5b5051919050565b80516020808301519190811015613666577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8160200360031b1b821691505b50919050565b805163ffffffff81168114612ea857600080fd5b60008060008060008060c0878903121561369957600080fd5b865195506136a96020880161366c565b94506136b76040880161366c565b935060608701516136c781612f78565b60808801519093506136d881612f78565b91506131f160a0880161366c565b818103818111156106a6576106a66135a7565b80820281158282048414176106a6576106a66135a7565b808201808211156106a6576106a66135a7565b600081518084526020808501945080840160005b838110156137785781518051885283015177ffffffffffffffffffffffffffffffffffffffffffffffff168388015260409096019590820190600101613737565b509495945050505050565b6040815260006137966040830185613723565b905073ffffffffffffffffffffffffffffffffffffffff831660208301529392505050565b8381526040602080830182905282820184905260009190859060608501845b8781101561382e5783356137ed81612e7b565b73ffffffffffffffffffffffffffffffffffffffff16825267ffffffffffffffff6138198585016131fd565b168284015292840192908401906001016137da565b5098975050505050505050565b6000806080838503121561384e57600080fd5b83601f84011261385d57600080fd5b6040516060810167ffffffffffffffff828210818311171561388157613881612fe9565b81604052829150606086018781111561389957600080fd5b865b818110156138b357803584526020938401930161389b565b50929450913591808311156138c757600080fd5b50506138d58582860161338b565b9150509250929050565b60208152600061291e6020830184613723565b600082613928577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b6000825161393f818460208701612eca565b919091019291505056fea164736f6c6343000813000a", +} + +var DestinationFeeManagerABI = DestinationFeeManagerMetaData.ABI + +var DestinationFeeManagerBin = DestinationFeeManagerMetaData.Bin + +func DeployDestinationFeeManager(auth *bind.TransactOpts, backend bind.ContractBackend, _linkAddress common.Address, _nativeAddress common.Address, _verifierAddress common.Address, _rewardManagerAddress common.Address) (common.Address, *types.Transaction, *DestinationFeeManager, error) { + parsed, err := DestinationFeeManagerMetaData.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(DestinationFeeManagerBin), backend, _linkAddress, _nativeAddress, _verifierAddress, _rewardManagerAddress) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &DestinationFeeManager{address: address, abi: *parsed, DestinationFeeManagerCaller: DestinationFeeManagerCaller{contract: contract}, DestinationFeeManagerTransactor: DestinationFeeManagerTransactor{contract: contract}, DestinationFeeManagerFilterer: DestinationFeeManagerFilterer{contract: contract}}, nil +} + +type DestinationFeeManager struct { + address common.Address + abi abi.ABI + DestinationFeeManagerCaller + DestinationFeeManagerTransactor + DestinationFeeManagerFilterer +} + +type DestinationFeeManagerCaller struct { + contract *bind.BoundContract +} + +type DestinationFeeManagerTransactor struct { + contract *bind.BoundContract +} + +type DestinationFeeManagerFilterer struct { + contract *bind.BoundContract +} + +type DestinationFeeManagerSession struct { + Contract *DestinationFeeManager + CallOpts bind.CallOpts + TransactOpts bind.TransactOpts +} + +type DestinationFeeManagerCallerSession struct { + Contract *DestinationFeeManagerCaller + CallOpts bind.CallOpts +} + +type DestinationFeeManagerTransactorSession struct { + Contract *DestinationFeeManagerTransactor + TransactOpts bind.TransactOpts +} + +type DestinationFeeManagerRaw struct { + Contract *DestinationFeeManager +} + +type DestinationFeeManagerCallerRaw struct { + Contract *DestinationFeeManagerCaller +} + +type DestinationFeeManagerTransactorRaw struct { + Contract *DestinationFeeManagerTransactor +} + +func NewDestinationFeeManager(address common.Address, backend bind.ContractBackend) (*DestinationFeeManager, error) { + abi, err := abi.JSON(strings.NewReader(DestinationFeeManagerABI)) + if err != nil { + return nil, err + } + contract, err := bindDestinationFeeManager(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &DestinationFeeManager{address: address, abi: abi, DestinationFeeManagerCaller: DestinationFeeManagerCaller{contract: contract}, DestinationFeeManagerTransactor: DestinationFeeManagerTransactor{contract: contract}, DestinationFeeManagerFilterer: DestinationFeeManagerFilterer{contract: contract}}, nil +} + +func NewDestinationFeeManagerCaller(address common.Address, caller bind.ContractCaller) (*DestinationFeeManagerCaller, error) { + contract, err := bindDestinationFeeManager(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &DestinationFeeManagerCaller{contract: contract}, nil +} + +func NewDestinationFeeManagerTransactor(address common.Address, transactor bind.ContractTransactor) (*DestinationFeeManagerTransactor, error) { + contract, err := bindDestinationFeeManager(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &DestinationFeeManagerTransactor{contract: contract}, nil +} + +func NewDestinationFeeManagerFilterer(address common.Address, filterer bind.ContractFilterer) (*DestinationFeeManagerFilterer, error) { + contract, err := bindDestinationFeeManager(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &DestinationFeeManagerFilterer{contract: contract}, nil +} + +func bindDestinationFeeManager(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := DestinationFeeManagerMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +func (_DestinationFeeManager *DestinationFeeManagerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _DestinationFeeManager.Contract.DestinationFeeManagerCaller.contract.Call(opts, result, method, params...) +} + +func (_DestinationFeeManager *DestinationFeeManagerRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _DestinationFeeManager.Contract.DestinationFeeManagerTransactor.contract.Transfer(opts) +} + +func (_DestinationFeeManager *DestinationFeeManagerRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _DestinationFeeManager.Contract.DestinationFeeManagerTransactor.contract.Transact(opts, method, params...) +} + +func (_DestinationFeeManager *DestinationFeeManagerCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _DestinationFeeManager.Contract.contract.Call(opts, result, method, params...) +} + +func (_DestinationFeeManager *DestinationFeeManagerTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _DestinationFeeManager.Contract.contract.Transfer(opts) +} + +func (_DestinationFeeManager *DestinationFeeManagerTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _DestinationFeeManager.Contract.contract.Transact(opts, method, params...) +} + +func (_DestinationFeeManager *DestinationFeeManagerCaller) GetFeeAndReward(opts *bind.CallOpts, subscriber common.Address, report []byte, quoteAddress common.Address) (CommonAsset, CommonAsset, *big.Int, error) { + var out []interface{} + err := _DestinationFeeManager.contract.Call(opts, &out, "getFeeAndReward", subscriber, report, quoteAddress) + + if err != nil { + return *new(CommonAsset), *new(CommonAsset), *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(CommonAsset)).(*CommonAsset) + out1 := *abi.ConvertType(out[1], new(CommonAsset)).(*CommonAsset) + out2 := *abi.ConvertType(out[2], new(*big.Int)).(**big.Int) + + return out0, out1, out2, err + +} + +func (_DestinationFeeManager *DestinationFeeManagerSession) GetFeeAndReward(subscriber common.Address, report []byte, quoteAddress common.Address) (CommonAsset, CommonAsset, *big.Int, error) { + return _DestinationFeeManager.Contract.GetFeeAndReward(&_DestinationFeeManager.CallOpts, subscriber, report, quoteAddress) +} + +func (_DestinationFeeManager *DestinationFeeManagerCallerSession) GetFeeAndReward(subscriber common.Address, report []byte, quoteAddress common.Address) (CommonAsset, CommonAsset, *big.Int, error) { + return _DestinationFeeManager.Contract.GetFeeAndReward(&_DestinationFeeManager.CallOpts, subscriber, report, quoteAddress) +} + +func (_DestinationFeeManager *DestinationFeeManagerCaller) ILinkAddress(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _DestinationFeeManager.contract.Call(opts, &out, "i_linkAddress") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +func (_DestinationFeeManager *DestinationFeeManagerSession) ILinkAddress() (common.Address, error) { + return _DestinationFeeManager.Contract.ILinkAddress(&_DestinationFeeManager.CallOpts) +} + +func (_DestinationFeeManager *DestinationFeeManagerCallerSession) ILinkAddress() (common.Address, error) { + return _DestinationFeeManager.Contract.ILinkAddress(&_DestinationFeeManager.CallOpts) +} + +func (_DestinationFeeManager *DestinationFeeManagerCaller) INativeAddress(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _DestinationFeeManager.contract.Call(opts, &out, "i_nativeAddress") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +func (_DestinationFeeManager *DestinationFeeManagerSession) INativeAddress() (common.Address, error) { + return _DestinationFeeManager.Contract.INativeAddress(&_DestinationFeeManager.CallOpts) +} + +func (_DestinationFeeManager *DestinationFeeManagerCallerSession) INativeAddress() (common.Address, error) { + return _DestinationFeeManager.Contract.INativeAddress(&_DestinationFeeManager.CallOpts) +} + +func (_DestinationFeeManager *DestinationFeeManagerCaller) IRewardManager(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _DestinationFeeManager.contract.Call(opts, &out, "i_rewardManager") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +func (_DestinationFeeManager *DestinationFeeManagerSession) IRewardManager() (common.Address, error) { + return _DestinationFeeManager.Contract.IRewardManager(&_DestinationFeeManager.CallOpts) +} + +func (_DestinationFeeManager *DestinationFeeManagerCallerSession) IRewardManager() (common.Address, error) { + return _DestinationFeeManager.Contract.IRewardManager(&_DestinationFeeManager.CallOpts) +} + +func (_DestinationFeeManager *DestinationFeeManagerCaller) LinkAvailableForPayment(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _DestinationFeeManager.contract.Call(opts, &out, "linkAvailableForPayment") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_DestinationFeeManager *DestinationFeeManagerSession) LinkAvailableForPayment() (*big.Int, error) { + return _DestinationFeeManager.Contract.LinkAvailableForPayment(&_DestinationFeeManager.CallOpts) +} + +func (_DestinationFeeManager *DestinationFeeManagerCallerSession) LinkAvailableForPayment() (*big.Int, error) { + return _DestinationFeeManager.Contract.LinkAvailableForPayment(&_DestinationFeeManager.CallOpts) +} + +func (_DestinationFeeManager *DestinationFeeManagerCaller) Owner(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _DestinationFeeManager.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 (_DestinationFeeManager *DestinationFeeManagerSession) Owner() (common.Address, error) { + return _DestinationFeeManager.Contract.Owner(&_DestinationFeeManager.CallOpts) +} + +func (_DestinationFeeManager *DestinationFeeManagerCallerSession) Owner() (common.Address, error) { + return _DestinationFeeManager.Contract.Owner(&_DestinationFeeManager.CallOpts) +} + +func (_DestinationFeeManager *DestinationFeeManagerCaller) SGlobalDiscounts(opts *bind.CallOpts, arg0 common.Address, arg1 common.Address) (*big.Int, error) { + var out []interface{} + err := _DestinationFeeManager.contract.Call(opts, &out, "s_globalDiscounts", arg0, arg1) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_DestinationFeeManager *DestinationFeeManagerSession) SGlobalDiscounts(arg0 common.Address, arg1 common.Address) (*big.Int, error) { + return _DestinationFeeManager.Contract.SGlobalDiscounts(&_DestinationFeeManager.CallOpts, arg0, arg1) +} + +func (_DestinationFeeManager *DestinationFeeManagerCallerSession) SGlobalDiscounts(arg0 common.Address, arg1 common.Address) (*big.Int, error) { + return _DestinationFeeManager.Contract.SGlobalDiscounts(&_DestinationFeeManager.CallOpts, arg0, arg1) +} + +func (_DestinationFeeManager *DestinationFeeManagerCaller) SLinkDeficit(opts *bind.CallOpts, arg0 [32]byte) (*big.Int, error) { + var out []interface{} + err := _DestinationFeeManager.contract.Call(opts, &out, "s_linkDeficit", arg0) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_DestinationFeeManager *DestinationFeeManagerSession) SLinkDeficit(arg0 [32]byte) (*big.Int, error) { + return _DestinationFeeManager.Contract.SLinkDeficit(&_DestinationFeeManager.CallOpts, arg0) +} + +func (_DestinationFeeManager *DestinationFeeManagerCallerSession) SLinkDeficit(arg0 [32]byte) (*big.Int, error) { + return _DestinationFeeManager.Contract.SLinkDeficit(&_DestinationFeeManager.CallOpts, arg0) +} + +func (_DestinationFeeManager *DestinationFeeManagerCaller) SNativeSurcharge(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _DestinationFeeManager.contract.Call(opts, &out, "s_nativeSurcharge") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_DestinationFeeManager *DestinationFeeManagerSession) SNativeSurcharge() (*big.Int, error) { + return _DestinationFeeManager.Contract.SNativeSurcharge(&_DestinationFeeManager.CallOpts) +} + +func (_DestinationFeeManager *DestinationFeeManagerCallerSession) SNativeSurcharge() (*big.Int, error) { + return _DestinationFeeManager.Contract.SNativeSurcharge(&_DestinationFeeManager.CallOpts) +} + +func (_DestinationFeeManager *DestinationFeeManagerCaller) SSubscriberDiscounts(opts *bind.CallOpts, arg0 common.Address, arg1 [32]byte, arg2 common.Address) (*big.Int, error) { + var out []interface{} + err := _DestinationFeeManager.contract.Call(opts, &out, "s_subscriberDiscounts", arg0, arg1, arg2) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_DestinationFeeManager *DestinationFeeManagerSession) SSubscriberDiscounts(arg0 common.Address, arg1 [32]byte, arg2 common.Address) (*big.Int, error) { + return _DestinationFeeManager.Contract.SSubscriberDiscounts(&_DestinationFeeManager.CallOpts, arg0, arg1, arg2) +} + +func (_DestinationFeeManager *DestinationFeeManagerCallerSession) SSubscriberDiscounts(arg0 common.Address, arg1 [32]byte, arg2 common.Address) (*big.Int, error) { + return _DestinationFeeManager.Contract.SSubscriberDiscounts(&_DestinationFeeManager.CallOpts, arg0, arg1, arg2) +} + +func (_DestinationFeeManager *DestinationFeeManagerCaller) SVerifierAddressList(opts *bind.CallOpts, arg0 common.Address) (common.Address, error) { + var out []interface{} + err := _DestinationFeeManager.contract.Call(opts, &out, "s_verifierAddressList", arg0) + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +func (_DestinationFeeManager *DestinationFeeManagerSession) SVerifierAddressList(arg0 common.Address) (common.Address, error) { + return _DestinationFeeManager.Contract.SVerifierAddressList(&_DestinationFeeManager.CallOpts, arg0) +} + +func (_DestinationFeeManager *DestinationFeeManagerCallerSession) SVerifierAddressList(arg0 common.Address) (common.Address, error) { + return _DestinationFeeManager.Contract.SVerifierAddressList(&_DestinationFeeManager.CallOpts, arg0) +} + +func (_DestinationFeeManager *DestinationFeeManagerCaller) SupportsInterface(opts *bind.CallOpts, interfaceId [4]byte) (bool, error) { + var out []interface{} + err := _DestinationFeeManager.contract.Call(opts, &out, "supportsInterface", interfaceId) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +func (_DestinationFeeManager *DestinationFeeManagerSession) SupportsInterface(interfaceId [4]byte) (bool, error) { + return _DestinationFeeManager.Contract.SupportsInterface(&_DestinationFeeManager.CallOpts, interfaceId) +} + +func (_DestinationFeeManager *DestinationFeeManagerCallerSession) SupportsInterface(interfaceId [4]byte) (bool, error) { + return _DestinationFeeManager.Contract.SupportsInterface(&_DestinationFeeManager.CallOpts, interfaceId) +} + +func (_DestinationFeeManager *DestinationFeeManagerCaller) TypeAndVersion(opts *bind.CallOpts) (string, error) { + var out []interface{} + err := _DestinationFeeManager.contract.Call(opts, &out, "typeAndVersion") + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + +} + +func (_DestinationFeeManager *DestinationFeeManagerSession) TypeAndVersion() (string, error) { + return _DestinationFeeManager.Contract.TypeAndVersion(&_DestinationFeeManager.CallOpts) +} + +func (_DestinationFeeManager *DestinationFeeManagerCallerSession) TypeAndVersion() (string, error) { + return _DestinationFeeManager.Contract.TypeAndVersion(&_DestinationFeeManager.CallOpts) +} + +func (_DestinationFeeManager *DestinationFeeManagerTransactor) AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) { + return _DestinationFeeManager.contract.Transact(opts, "acceptOwnership") +} + +func (_DestinationFeeManager *DestinationFeeManagerSession) AcceptOwnership() (*types.Transaction, error) { + return _DestinationFeeManager.Contract.AcceptOwnership(&_DestinationFeeManager.TransactOpts) +} + +func (_DestinationFeeManager *DestinationFeeManagerTransactorSession) AcceptOwnership() (*types.Transaction, error) { + return _DestinationFeeManager.Contract.AcceptOwnership(&_DestinationFeeManager.TransactOpts) +} + +func (_DestinationFeeManager *DestinationFeeManagerTransactor) AddVerifier(opts *bind.TransactOpts, verifierAddress common.Address) (*types.Transaction, error) { + return _DestinationFeeManager.contract.Transact(opts, "addVerifier", verifierAddress) +} + +func (_DestinationFeeManager *DestinationFeeManagerSession) AddVerifier(verifierAddress common.Address) (*types.Transaction, error) { + return _DestinationFeeManager.Contract.AddVerifier(&_DestinationFeeManager.TransactOpts, verifierAddress) +} + +func (_DestinationFeeManager *DestinationFeeManagerTransactorSession) AddVerifier(verifierAddress common.Address) (*types.Transaction, error) { + return _DestinationFeeManager.Contract.AddVerifier(&_DestinationFeeManager.TransactOpts, verifierAddress) +} + +func (_DestinationFeeManager *DestinationFeeManagerTransactor) PayLinkDeficit(opts *bind.TransactOpts, configDigest [32]byte) (*types.Transaction, error) { + return _DestinationFeeManager.contract.Transact(opts, "payLinkDeficit", configDigest) +} + +func (_DestinationFeeManager *DestinationFeeManagerSession) PayLinkDeficit(configDigest [32]byte) (*types.Transaction, error) { + return _DestinationFeeManager.Contract.PayLinkDeficit(&_DestinationFeeManager.TransactOpts, configDigest) +} + +func (_DestinationFeeManager *DestinationFeeManagerTransactorSession) PayLinkDeficit(configDigest [32]byte) (*types.Transaction, error) { + return _DestinationFeeManager.Contract.PayLinkDeficit(&_DestinationFeeManager.TransactOpts, configDigest) +} + +func (_DestinationFeeManager *DestinationFeeManagerTransactor) ProcessFee(opts *bind.TransactOpts, recipient [32]byte, payload []byte, parameterPayload []byte, subscriber common.Address) (*types.Transaction, error) { + return _DestinationFeeManager.contract.Transact(opts, "processFee", recipient, payload, parameterPayload, subscriber) +} + +func (_DestinationFeeManager *DestinationFeeManagerSession) ProcessFee(recipient [32]byte, payload []byte, parameterPayload []byte, subscriber common.Address) (*types.Transaction, error) { + return _DestinationFeeManager.Contract.ProcessFee(&_DestinationFeeManager.TransactOpts, recipient, payload, parameterPayload, subscriber) +} + +func (_DestinationFeeManager *DestinationFeeManagerTransactorSession) ProcessFee(recipient [32]byte, payload []byte, parameterPayload []byte, subscriber common.Address) (*types.Transaction, error) { + return _DestinationFeeManager.Contract.ProcessFee(&_DestinationFeeManager.TransactOpts, recipient, payload, parameterPayload, subscriber) +} + +func (_DestinationFeeManager *DestinationFeeManagerTransactor) ProcessFeeBulk(opts *bind.TransactOpts, poolIds [][32]byte, payloads [][]byte, parameterPayload []byte, subscriber common.Address) (*types.Transaction, error) { + return _DestinationFeeManager.contract.Transact(opts, "processFeeBulk", poolIds, payloads, parameterPayload, subscriber) +} + +func (_DestinationFeeManager *DestinationFeeManagerSession) ProcessFeeBulk(poolIds [][32]byte, payloads [][]byte, parameterPayload []byte, subscriber common.Address) (*types.Transaction, error) { + return _DestinationFeeManager.Contract.ProcessFeeBulk(&_DestinationFeeManager.TransactOpts, poolIds, payloads, parameterPayload, subscriber) +} + +func (_DestinationFeeManager *DestinationFeeManagerTransactorSession) ProcessFeeBulk(poolIds [][32]byte, payloads [][]byte, parameterPayload []byte, subscriber common.Address) (*types.Transaction, error) { + return _DestinationFeeManager.Contract.ProcessFeeBulk(&_DestinationFeeManager.TransactOpts, poolIds, payloads, parameterPayload, subscriber) +} + +func (_DestinationFeeManager *DestinationFeeManagerTransactor) RemoveVerifier(opts *bind.TransactOpts, verifierAddress common.Address) (*types.Transaction, error) { + return _DestinationFeeManager.contract.Transact(opts, "removeVerifier", verifierAddress) +} + +func (_DestinationFeeManager *DestinationFeeManagerSession) RemoveVerifier(verifierAddress common.Address) (*types.Transaction, error) { + return _DestinationFeeManager.Contract.RemoveVerifier(&_DestinationFeeManager.TransactOpts, verifierAddress) +} + +func (_DestinationFeeManager *DestinationFeeManagerTransactorSession) RemoveVerifier(verifierAddress common.Address) (*types.Transaction, error) { + return _DestinationFeeManager.Contract.RemoveVerifier(&_DestinationFeeManager.TransactOpts, verifierAddress) +} + +func (_DestinationFeeManager *DestinationFeeManagerTransactor) SetFeeRecipients(opts *bind.TransactOpts, configDigest [32]byte, rewardRecipientAndWeights []CommonAddressAndWeight) (*types.Transaction, error) { + return _DestinationFeeManager.contract.Transact(opts, "setFeeRecipients", configDigest, rewardRecipientAndWeights) +} + +func (_DestinationFeeManager *DestinationFeeManagerSession) SetFeeRecipients(configDigest [32]byte, rewardRecipientAndWeights []CommonAddressAndWeight) (*types.Transaction, error) { + return _DestinationFeeManager.Contract.SetFeeRecipients(&_DestinationFeeManager.TransactOpts, configDigest, rewardRecipientAndWeights) +} + +func (_DestinationFeeManager *DestinationFeeManagerTransactorSession) SetFeeRecipients(configDigest [32]byte, rewardRecipientAndWeights []CommonAddressAndWeight) (*types.Transaction, error) { + return _DestinationFeeManager.Contract.SetFeeRecipients(&_DestinationFeeManager.TransactOpts, configDigest, rewardRecipientAndWeights) +} + +func (_DestinationFeeManager *DestinationFeeManagerTransactor) SetNativeSurcharge(opts *bind.TransactOpts, surcharge uint64) (*types.Transaction, error) { + return _DestinationFeeManager.contract.Transact(opts, "setNativeSurcharge", surcharge) +} + +func (_DestinationFeeManager *DestinationFeeManagerSession) SetNativeSurcharge(surcharge uint64) (*types.Transaction, error) { + return _DestinationFeeManager.Contract.SetNativeSurcharge(&_DestinationFeeManager.TransactOpts, surcharge) +} + +func (_DestinationFeeManager *DestinationFeeManagerTransactorSession) SetNativeSurcharge(surcharge uint64) (*types.Transaction, error) { + return _DestinationFeeManager.Contract.SetNativeSurcharge(&_DestinationFeeManager.TransactOpts, surcharge) +} + +func (_DestinationFeeManager *DestinationFeeManagerTransactor) SetRewardManager(opts *bind.TransactOpts, rewardManagerAddress common.Address) (*types.Transaction, error) { + return _DestinationFeeManager.contract.Transact(opts, "setRewardManager", rewardManagerAddress) +} + +func (_DestinationFeeManager *DestinationFeeManagerSession) SetRewardManager(rewardManagerAddress common.Address) (*types.Transaction, error) { + return _DestinationFeeManager.Contract.SetRewardManager(&_DestinationFeeManager.TransactOpts, rewardManagerAddress) +} + +func (_DestinationFeeManager *DestinationFeeManagerTransactorSession) SetRewardManager(rewardManagerAddress common.Address) (*types.Transaction, error) { + return _DestinationFeeManager.Contract.SetRewardManager(&_DestinationFeeManager.TransactOpts, rewardManagerAddress) +} + +func (_DestinationFeeManager *DestinationFeeManagerTransactor) TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) { + return _DestinationFeeManager.contract.Transact(opts, "transferOwnership", to) +} + +func (_DestinationFeeManager *DestinationFeeManagerSession) TransferOwnership(to common.Address) (*types.Transaction, error) { + return _DestinationFeeManager.Contract.TransferOwnership(&_DestinationFeeManager.TransactOpts, to) +} + +func (_DestinationFeeManager *DestinationFeeManagerTransactorSession) TransferOwnership(to common.Address) (*types.Transaction, error) { + return _DestinationFeeManager.Contract.TransferOwnership(&_DestinationFeeManager.TransactOpts, to) +} + +func (_DestinationFeeManager *DestinationFeeManagerTransactor) UpdateSubscriberDiscount(opts *bind.TransactOpts, subscriber common.Address, feedId [32]byte, token common.Address, discount uint64) (*types.Transaction, error) { + return _DestinationFeeManager.contract.Transact(opts, "updateSubscriberDiscount", subscriber, feedId, token, discount) +} + +func (_DestinationFeeManager *DestinationFeeManagerSession) UpdateSubscriberDiscount(subscriber common.Address, feedId [32]byte, token common.Address, discount uint64) (*types.Transaction, error) { + return _DestinationFeeManager.Contract.UpdateSubscriberDiscount(&_DestinationFeeManager.TransactOpts, subscriber, feedId, token, discount) +} + +func (_DestinationFeeManager *DestinationFeeManagerTransactorSession) UpdateSubscriberDiscount(subscriber common.Address, feedId [32]byte, token common.Address, discount uint64) (*types.Transaction, error) { + return _DestinationFeeManager.Contract.UpdateSubscriberDiscount(&_DestinationFeeManager.TransactOpts, subscriber, feedId, token, discount) +} + +func (_DestinationFeeManager *DestinationFeeManagerTransactor) UpdateSubscriberGlobalDiscount(opts *bind.TransactOpts, subscriber common.Address, token common.Address, discount uint64) (*types.Transaction, error) { + return _DestinationFeeManager.contract.Transact(opts, "updateSubscriberGlobalDiscount", subscriber, token, discount) +} + +func (_DestinationFeeManager *DestinationFeeManagerSession) UpdateSubscriberGlobalDiscount(subscriber common.Address, token common.Address, discount uint64) (*types.Transaction, error) { + return _DestinationFeeManager.Contract.UpdateSubscriberGlobalDiscount(&_DestinationFeeManager.TransactOpts, subscriber, token, discount) +} + +func (_DestinationFeeManager *DestinationFeeManagerTransactorSession) UpdateSubscriberGlobalDiscount(subscriber common.Address, token common.Address, discount uint64) (*types.Transaction, error) { + return _DestinationFeeManager.Contract.UpdateSubscriberGlobalDiscount(&_DestinationFeeManager.TransactOpts, subscriber, token, discount) +} + +func (_DestinationFeeManager *DestinationFeeManagerTransactor) Withdraw(opts *bind.TransactOpts, assetAddress common.Address, recipient common.Address, quantity *big.Int) (*types.Transaction, error) { + return _DestinationFeeManager.contract.Transact(opts, "withdraw", assetAddress, recipient, quantity) +} + +func (_DestinationFeeManager *DestinationFeeManagerSession) Withdraw(assetAddress common.Address, recipient common.Address, quantity *big.Int) (*types.Transaction, error) { + return _DestinationFeeManager.Contract.Withdraw(&_DestinationFeeManager.TransactOpts, assetAddress, recipient, quantity) +} + +func (_DestinationFeeManager *DestinationFeeManagerTransactorSession) Withdraw(assetAddress common.Address, recipient common.Address, quantity *big.Int) (*types.Transaction, error) { + return _DestinationFeeManager.Contract.Withdraw(&_DestinationFeeManager.TransactOpts, assetAddress, recipient, quantity) +} + +type DestinationFeeManagerDiscountAppliedIterator struct { + Event *DestinationFeeManagerDiscountApplied + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *DestinationFeeManagerDiscountAppliedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(DestinationFeeManagerDiscountApplied) + 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(DestinationFeeManagerDiscountApplied) + 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 *DestinationFeeManagerDiscountAppliedIterator) Error() error { + return it.fail +} + +func (it *DestinationFeeManagerDiscountAppliedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type DestinationFeeManagerDiscountApplied struct { + ConfigDigest [32]byte + Subscriber common.Address + Fee CommonAsset + Reward CommonAsset + AppliedDiscount *big.Int + Raw types.Log +} + +func (_DestinationFeeManager *DestinationFeeManagerFilterer) FilterDiscountApplied(opts *bind.FilterOpts, configDigest [][32]byte, subscriber []common.Address) (*DestinationFeeManagerDiscountAppliedIterator, error) { + + var configDigestRule []interface{} + for _, configDigestItem := range configDigest { + configDigestRule = append(configDigestRule, configDigestItem) + } + var subscriberRule []interface{} + for _, subscriberItem := range subscriber { + subscriberRule = append(subscriberRule, subscriberItem) + } + + logs, sub, err := _DestinationFeeManager.contract.FilterLogs(opts, "DiscountApplied", configDigestRule, subscriberRule) + if err != nil { + return nil, err + } + return &DestinationFeeManagerDiscountAppliedIterator{contract: _DestinationFeeManager.contract, event: "DiscountApplied", logs: logs, sub: sub}, nil +} + +func (_DestinationFeeManager *DestinationFeeManagerFilterer) WatchDiscountApplied(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerDiscountApplied, configDigest [][32]byte, subscriber []common.Address) (event.Subscription, error) { + + var configDigestRule []interface{} + for _, configDigestItem := range configDigest { + configDigestRule = append(configDigestRule, configDigestItem) + } + var subscriberRule []interface{} + for _, subscriberItem := range subscriber { + subscriberRule = append(subscriberRule, subscriberItem) + } + + logs, sub, err := _DestinationFeeManager.contract.WatchLogs(opts, "DiscountApplied", configDigestRule, subscriberRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(DestinationFeeManagerDiscountApplied) + if err := _DestinationFeeManager.contract.UnpackLog(event, "DiscountApplied", 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 (_DestinationFeeManager *DestinationFeeManagerFilterer) ParseDiscountApplied(log types.Log) (*DestinationFeeManagerDiscountApplied, error) { + event := new(DestinationFeeManagerDiscountApplied) + if err := _DestinationFeeManager.contract.UnpackLog(event, "DiscountApplied", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type DestinationFeeManagerInsufficientLinkIterator struct { + Event *DestinationFeeManagerInsufficientLink + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *DestinationFeeManagerInsufficientLinkIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(DestinationFeeManagerInsufficientLink) + 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(DestinationFeeManagerInsufficientLink) + 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 *DestinationFeeManagerInsufficientLinkIterator) Error() error { + return it.fail +} + +func (it *DestinationFeeManagerInsufficientLinkIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type DestinationFeeManagerInsufficientLink struct { + Rewards []IDestinationRewardManagerFeePayment + Raw types.Log +} + +func (_DestinationFeeManager *DestinationFeeManagerFilterer) FilterInsufficientLink(opts *bind.FilterOpts) (*DestinationFeeManagerInsufficientLinkIterator, error) { + + logs, sub, err := _DestinationFeeManager.contract.FilterLogs(opts, "InsufficientLink") + if err != nil { + return nil, err + } + return &DestinationFeeManagerInsufficientLinkIterator{contract: _DestinationFeeManager.contract, event: "InsufficientLink", logs: logs, sub: sub}, nil +} + +func (_DestinationFeeManager *DestinationFeeManagerFilterer) WatchInsufficientLink(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerInsufficientLink) (event.Subscription, error) { + + logs, sub, err := _DestinationFeeManager.contract.WatchLogs(opts, "InsufficientLink") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(DestinationFeeManagerInsufficientLink) + if err := _DestinationFeeManager.contract.UnpackLog(event, "InsufficientLink", 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 (_DestinationFeeManager *DestinationFeeManagerFilterer) ParseInsufficientLink(log types.Log) (*DestinationFeeManagerInsufficientLink, error) { + event := new(DestinationFeeManagerInsufficientLink) + if err := _DestinationFeeManager.contract.UnpackLog(event, "InsufficientLink", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type DestinationFeeManagerLinkDeficitClearedIterator struct { + Event *DestinationFeeManagerLinkDeficitCleared + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *DestinationFeeManagerLinkDeficitClearedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(DestinationFeeManagerLinkDeficitCleared) + 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(DestinationFeeManagerLinkDeficitCleared) + 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 *DestinationFeeManagerLinkDeficitClearedIterator) Error() error { + return it.fail +} + +func (it *DestinationFeeManagerLinkDeficitClearedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type DestinationFeeManagerLinkDeficitCleared struct { + ConfigDigest [32]byte + LinkQuantity *big.Int + Raw types.Log +} + +func (_DestinationFeeManager *DestinationFeeManagerFilterer) FilterLinkDeficitCleared(opts *bind.FilterOpts, configDigest [][32]byte) (*DestinationFeeManagerLinkDeficitClearedIterator, error) { + + var configDigestRule []interface{} + for _, configDigestItem := range configDigest { + configDigestRule = append(configDigestRule, configDigestItem) + } + + logs, sub, err := _DestinationFeeManager.contract.FilterLogs(opts, "LinkDeficitCleared", configDigestRule) + if err != nil { + return nil, err + } + return &DestinationFeeManagerLinkDeficitClearedIterator{contract: _DestinationFeeManager.contract, event: "LinkDeficitCleared", logs: logs, sub: sub}, nil +} + +func (_DestinationFeeManager *DestinationFeeManagerFilterer) WatchLinkDeficitCleared(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerLinkDeficitCleared, configDigest [][32]byte) (event.Subscription, error) { + + var configDigestRule []interface{} + for _, configDigestItem := range configDigest { + configDigestRule = append(configDigestRule, configDigestItem) + } + + logs, sub, err := _DestinationFeeManager.contract.WatchLogs(opts, "LinkDeficitCleared", configDigestRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(DestinationFeeManagerLinkDeficitCleared) + if err := _DestinationFeeManager.contract.UnpackLog(event, "LinkDeficitCleared", 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 (_DestinationFeeManager *DestinationFeeManagerFilterer) ParseLinkDeficitCleared(log types.Log) (*DestinationFeeManagerLinkDeficitCleared, error) { + event := new(DestinationFeeManagerLinkDeficitCleared) + if err := _DestinationFeeManager.contract.UnpackLog(event, "LinkDeficitCleared", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type DestinationFeeManagerNativeSurchargeUpdatedIterator struct { + Event *DestinationFeeManagerNativeSurchargeUpdated + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *DestinationFeeManagerNativeSurchargeUpdatedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(DestinationFeeManagerNativeSurchargeUpdated) + 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(DestinationFeeManagerNativeSurchargeUpdated) + 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 *DestinationFeeManagerNativeSurchargeUpdatedIterator) Error() error { + return it.fail +} + +func (it *DestinationFeeManagerNativeSurchargeUpdatedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type DestinationFeeManagerNativeSurchargeUpdated struct { + NewSurcharge uint64 + Raw types.Log +} + +func (_DestinationFeeManager *DestinationFeeManagerFilterer) FilterNativeSurchargeUpdated(opts *bind.FilterOpts) (*DestinationFeeManagerNativeSurchargeUpdatedIterator, error) { + + logs, sub, err := _DestinationFeeManager.contract.FilterLogs(opts, "NativeSurchargeUpdated") + if err != nil { + return nil, err + } + return &DestinationFeeManagerNativeSurchargeUpdatedIterator{contract: _DestinationFeeManager.contract, event: "NativeSurchargeUpdated", logs: logs, sub: sub}, nil +} + +func (_DestinationFeeManager *DestinationFeeManagerFilterer) WatchNativeSurchargeUpdated(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerNativeSurchargeUpdated) (event.Subscription, error) { + + logs, sub, err := _DestinationFeeManager.contract.WatchLogs(opts, "NativeSurchargeUpdated") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(DestinationFeeManagerNativeSurchargeUpdated) + if err := _DestinationFeeManager.contract.UnpackLog(event, "NativeSurchargeUpdated", 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 (_DestinationFeeManager *DestinationFeeManagerFilterer) ParseNativeSurchargeUpdated(log types.Log) (*DestinationFeeManagerNativeSurchargeUpdated, error) { + event := new(DestinationFeeManagerNativeSurchargeUpdated) + if err := _DestinationFeeManager.contract.UnpackLog(event, "NativeSurchargeUpdated", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type DestinationFeeManagerOwnershipTransferRequestedIterator struct { + Event *DestinationFeeManagerOwnershipTransferRequested + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *DestinationFeeManagerOwnershipTransferRequestedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(DestinationFeeManagerOwnershipTransferRequested) + 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(DestinationFeeManagerOwnershipTransferRequested) + 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 *DestinationFeeManagerOwnershipTransferRequestedIterator) Error() error { + return it.fail +} + +func (it *DestinationFeeManagerOwnershipTransferRequestedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type DestinationFeeManagerOwnershipTransferRequested struct { + From common.Address + To common.Address + Raw types.Log +} + +func (_DestinationFeeManager *DestinationFeeManagerFilterer) FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationFeeManagerOwnershipTransferRequestedIterator, 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 := _DestinationFeeManager.contract.FilterLogs(opts, "OwnershipTransferRequested", fromRule, toRule) + if err != nil { + return nil, err + } + return &DestinationFeeManagerOwnershipTransferRequestedIterator{contract: _DestinationFeeManager.contract, event: "OwnershipTransferRequested", logs: logs, sub: sub}, nil +} + +func (_DestinationFeeManager *DestinationFeeManagerFilterer) WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerOwnershipTransferRequested, 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 := _DestinationFeeManager.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(DestinationFeeManagerOwnershipTransferRequested) + if err := _DestinationFeeManager.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 (_DestinationFeeManager *DestinationFeeManagerFilterer) ParseOwnershipTransferRequested(log types.Log) (*DestinationFeeManagerOwnershipTransferRequested, error) { + event := new(DestinationFeeManagerOwnershipTransferRequested) + if err := _DestinationFeeManager.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type DestinationFeeManagerOwnershipTransferredIterator struct { + Event *DestinationFeeManagerOwnershipTransferred + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *DestinationFeeManagerOwnershipTransferredIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(DestinationFeeManagerOwnershipTransferred) + 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(DestinationFeeManagerOwnershipTransferred) + 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 *DestinationFeeManagerOwnershipTransferredIterator) Error() error { + return it.fail +} + +func (it *DestinationFeeManagerOwnershipTransferredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type DestinationFeeManagerOwnershipTransferred struct { + From common.Address + To common.Address + Raw types.Log +} + +func (_DestinationFeeManager *DestinationFeeManagerFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationFeeManagerOwnershipTransferredIterator, 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 := _DestinationFeeManager.contract.FilterLogs(opts, "OwnershipTransferred", fromRule, toRule) + if err != nil { + return nil, err + } + return &DestinationFeeManagerOwnershipTransferredIterator{contract: _DestinationFeeManager.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil +} + +func (_DestinationFeeManager *DestinationFeeManagerFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerOwnershipTransferred, 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 := _DestinationFeeManager.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(DestinationFeeManagerOwnershipTransferred) + if err := _DestinationFeeManager.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 (_DestinationFeeManager *DestinationFeeManagerFilterer) ParseOwnershipTransferred(log types.Log) (*DestinationFeeManagerOwnershipTransferred, error) { + event := new(DestinationFeeManagerOwnershipTransferred) + if err := _DestinationFeeManager.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type DestinationFeeManagerSubscriberDiscountUpdatedIterator struct { + Event *DestinationFeeManagerSubscriberDiscountUpdated + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *DestinationFeeManagerSubscriberDiscountUpdatedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(DestinationFeeManagerSubscriberDiscountUpdated) + 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(DestinationFeeManagerSubscriberDiscountUpdated) + 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 *DestinationFeeManagerSubscriberDiscountUpdatedIterator) Error() error { + return it.fail +} + +func (it *DestinationFeeManagerSubscriberDiscountUpdatedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type DestinationFeeManagerSubscriberDiscountUpdated struct { + Subscriber common.Address + FeedId [32]byte + Token common.Address + Discount uint64 + Raw types.Log +} + +func (_DestinationFeeManager *DestinationFeeManagerFilterer) FilterSubscriberDiscountUpdated(opts *bind.FilterOpts, subscriber []common.Address, feedId [][32]byte) (*DestinationFeeManagerSubscriberDiscountUpdatedIterator, error) { + + var subscriberRule []interface{} + for _, subscriberItem := range subscriber { + subscriberRule = append(subscriberRule, subscriberItem) + } + var feedIdRule []interface{} + for _, feedIdItem := range feedId { + feedIdRule = append(feedIdRule, feedIdItem) + } + + logs, sub, err := _DestinationFeeManager.contract.FilterLogs(opts, "SubscriberDiscountUpdated", subscriberRule, feedIdRule) + if err != nil { + return nil, err + } + return &DestinationFeeManagerSubscriberDiscountUpdatedIterator{contract: _DestinationFeeManager.contract, event: "SubscriberDiscountUpdated", logs: logs, sub: sub}, nil +} + +func (_DestinationFeeManager *DestinationFeeManagerFilterer) WatchSubscriberDiscountUpdated(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerSubscriberDiscountUpdated, subscriber []common.Address, feedId [][32]byte) (event.Subscription, error) { + + var subscriberRule []interface{} + for _, subscriberItem := range subscriber { + subscriberRule = append(subscriberRule, subscriberItem) + } + var feedIdRule []interface{} + for _, feedIdItem := range feedId { + feedIdRule = append(feedIdRule, feedIdItem) + } + + logs, sub, err := _DestinationFeeManager.contract.WatchLogs(opts, "SubscriberDiscountUpdated", subscriberRule, feedIdRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(DestinationFeeManagerSubscriberDiscountUpdated) + if err := _DestinationFeeManager.contract.UnpackLog(event, "SubscriberDiscountUpdated", 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 (_DestinationFeeManager *DestinationFeeManagerFilterer) ParseSubscriberDiscountUpdated(log types.Log) (*DestinationFeeManagerSubscriberDiscountUpdated, error) { + event := new(DestinationFeeManagerSubscriberDiscountUpdated) + if err := _DestinationFeeManager.contract.UnpackLog(event, "SubscriberDiscountUpdated", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type DestinationFeeManagerWithdrawIterator struct { + Event *DestinationFeeManagerWithdraw + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *DestinationFeeManagerWithdrawIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(DestinationFeeManagerWithdraw) + 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(DestinationFeeManagerWithdraw) + 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 *DestinationFeeManagerWithdrawIterator) Error() error { + return it.fail +} + +func (it *DestinationFeeManagerWithdrawIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type DestinationFeeManagerWithdraw struct { + AdminAddress common.Address + Recipient common.Address + AssetAddress common.Address + Quantity *big.Int + Raw types.Log +} + +func (_DestinationFeeManager *DestinationFeeManagerFilterer) FilterWithdraw(opts *bind.FilterOpts) (*DestinationFeeManagerWithdrawIterator, error) { + + logs, sub, err := _DestinationFeeManager.contract.FilterLogs(opts, "Withdraw") + if err != nil { + return nil, err + } + return &DestinationFeeManagerWithdrawIterator{contract: _DestinationFeeManager.contract, event: "Withdraw", logs: logs, sub: sub}, nil +} + +func (_DestinationFeeManager *DestinationFeeManagerFilterer) WatchWithdraw(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerWithdraw) (event.Subscription, error) { + + logs, sub, err := _DestinationFeeManager.contract.WatchLogs(opts, "Withdraw") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(DestinationFeeManagerWithdraw) + if err := _DestinationFeeManager.contract.UnpackLog(event, "Withdraw", 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 (_DestinationFeeManager *DestinationFeeManagerFilterer) ParseWithdraw(log types.Log) (*DestinationFeeManagerWithdraw, error) { + event := new(DestinationFeeManagerWithdraw) + if err := _DestinationFeeManager.contract.UnpackLog(event, "Withdraw", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +func (_DestinationFeeManager *DestinationFeeManager) ParseLog(log types.Log) (generated.AbigenLog, error) { + switch log.Topics[0] { + case _DestinationFeeManager.abi.Events["DiscountApplied"].ID: + return _DestinationFeeManager.ParseDiscountApplied(log) + case _DestinationFeeManager.abi.Events["InsufficientLink"].ID: + return _DestinationFeeManager.ParseInsufficientLink(log) + case _DestinationFeeManager.abi.Events["LinkDeficitCleared"].ID: + return _DestinationFeeManager.ParseLinkDeficitCleared(log) + case _DestinationFeeManager.abi.Events["NativeSurchargeUpdated"].ID: + return _DestinationFeeManager.ParseNativeSurchargeUpdated(log) + case _DestinationFeeManager.abi.Events["OwnershipTransferRequested"].ID: + return _DestinationFeeManager.ParseOwnershipTransferRequested(log) + case _DestinationFeeManager.abi.Events["OwnershipTransferred"].ID: + return _DestinationFeeManager.ParseOwnershipTransferred(log) + case _DestinationFeeManager.abi.Events["SubscriberDiscountUpdated"].ID: + return _DestinationFeeManager.ParseSubscriberDiscountUpdated(log) + case _DestinationFeeManager.abi.Events["Withdraw"].ID: + return _DestinationFeeManager.ParseWithdraw(log) + + default: + return nil, fmt.Errorf("abigen wrapper received unknown log topic: %v", log.Topics[0]) + } +} + +func (DestinationFeeManagerDiscountApplied) Topic() common.Hash { + return common.HexToHash("0x88b15eb682210089cddf967648e2cb2a4535aeadc8f8f36050922e33c04e7125") +} + +func (DestinationFeeManagerInsufficientLink) Topic() common.Hash { + return common.HexToHash("0xf52e5907b69d97c33392936c12d78b494463b78c5b72df50b4c497eee5720b67") +} + +func (DestinationFeeManagerLinkDeficitCleared) Topic() common.Hash { + return common.HexToHash("0x843f0b103e50b42b08f9d30f12f961845a6d02623730872e24644899c0dd9895") +} + +func (DestinationFeeManagerNativeSurchargeUpdated) Topic() common.Hash { + return common.HexToHash("0x08f7c0d17932ddb8523bc06754d42ff19ebc77d76a8b9bfde02c28ab1ed3d639") +} + +func (DestinationFeeManagerOwnershipTransferRequested) Topic() common.Hash { + return common.HexToHash("0xed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278") +} + +func (DestinationFeeManagerOwnershipTransferred) Topic() common.Hash { + return common.HexToHash("0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0") +} + +func (DestinationFeeManagerSubscriberDiscountUpdated) Topic() common.Hash { + return common.HexToHash("0x5eba5a8afa39780f0f99b6cbeb95f3da6a7040ca00abd46bdc91a0a060134139") +} + +func (DestinationFeeManagerWithdraw) Topic() common.Hash { + return common.HexToHash("0x7ff78a71698bdb18dcca96f52ab25e0a1b146fb6a49adf8e6845299e49021f29") +} + +func (_DestinationFeeManager *DestinationFeeManager) Address() common.Address { + return _DestinationFeeManager.address +} + +type DestinationFeeManagerInterface interface { + GetFeeAndReward(opts *bind.CallOpts, subscriber common.Address, report []byte, quoteAddress common.Address) (CommonAsset, CommonAsset, *big.Int, error) + + ILinkAddress(opts *bind.CallOpts) (common.Address, error) + + INativeAddress(opts *bind.CallOpts) (common.Address, error) + + IRewardManager(opts *bind.CallOpts) (common.Address, error) + + LinkAvailableForPayment(opts *bind.CallOpts) (*big.Int, error) + + Owner(opts *bind.CallOpts) (common.Address, error) + + SGlobalDiscounts(opts *bind.CallOpts, arg0 common.Address, arg1 common.Address) (*big.Int, error) + + SLinkDeficit(opts *bind.CallOpts, arg0 [32]byte) (*big.Int, error) + + SNativeSurcharge(opts *bind.CallOpts) (*big.Int, error) + + SSubscriberDiscounts(opts *bind.CallOpts, arg0 common.Address, arg1 [32]byte, arg2 common.Address) (*big.Int, error) + + SVerifierAddressList(opts *bind.CallOpts, arg0 common.Address) (common.Address, error) + + SupportsInterface(opts *bind.CallOpts, interfaceId [4]byte) (bool, error) + + TypeAndVersion(opts *bind.CallOpts) (string, error) + + AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) + + AddVerifier(opts *bind.TransactOpts, verifierAddress common.Address) (*types.Transaction, error) + + PayLinkDeficit(opts *bind.TransactOpts, configDigest [32]byte) (*types.Transaction, error) + + ProcessFee(opts *bind.TransactOpts, recipient [32]byte, payload []byte, parameterPayload []byte, subscriber common.Address) (*types.Transaction, error) + + ProcessFeeBulk(opts *bind.TransactOpts, poolIds [][32]byte, payloads [][]byte, parameterPayload []byte, subscriber common.Address) (*types.Transaction, error) + + RemoveVerifier(opts *bind.TransactOpts, verifierAddress common.Address) (*types.Transaction, error) + + SetFeeRecipients(opts *bind.TransactOpts, configDigest [32]byte, rewardRecipientAndWeights []CommonAddressAndWeight) (*types.Transaction, error) + + SetNativeSurcharge(opts *bind.TransactOpts, surcharge uint64) (*types.Transaction, error) + + SetRewardManager(opts *bind.TransactOpts, rewardManagerAddress common.Address) (*types.Transaction, error) + + TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) + + UpdateSubscriberDiscount(opts *bind.TransactOpts, subscriber common.Address, feedId [32]byte, token common.Address, discount uint64) (*types.Transaction, error) + + UpdateSubscriberGlobalDiscount(opts *bind.TransactOpts, subscriber common.Address, token common.Address, discount uint64) (*types.Transaction, error) + + Withdraw(opts *bind.TransactOpts, assetAddress common.Address, recipient common.Address, quantity *big.Int) (*types.Transaction, error) + + FilterDiscountApplied(opts *bind.FilterOpts, configDigest [][32]byte, subscriber []common.Address) (*DestinationFeeManagerDiscountAppliedIterator, error) + + WatchDiscountApplied(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerDiscountApplied, configDigest [][32]byte, subscriber []common.Address) (event.Subscription, error) + + ParseDiscountApplied(log types.Log) (*DestinationFeeManagerDiscountApplied, error) + + FilterInsufficientLink(opts *bind.FilterOpts) (*DestinationFeeManagerInsufficientLinkIterator, error) + + WatchInsufficientLink(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerInsufficientLink) (event.Subscription, error) + + ParseInsufficientLink(log types.Log) (*DestinationFeeManagerInsufficientLink, error) + + FilterLinkDeficitCleared(opts *bind.FilterOpts, configDigest [][32]byte) (*DestinationFeeManagerLinkDeficitClearedIterator, error) + + WatchLinkDeficitCleared(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerLinkDeficitCleared, configDigest [][32]byte) (event.Subscription, error) + + ParseLinkDeficitCleared(log types.Log) (*DestinationFeeManagerLinkDeficitCleared, error) + + FilterNativeSurchargeUpdated(opts *bind.FilterOpts) (*DestinationFeeManagerNativeSurchargeUpdatedIterator, error) + + WatchNativeSurchargeUpdated(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerNativeSurchargeUpdated) (event.Subscription, error) + + ParseNativeSurchargeUpdated(log types.Log) (*DestinationFeeManagerNativeSurchargeUpdated, error) + + FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationFeeManagerOwnershipTransferRequestedIterator, error) + + WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerOwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error) + + ParseOwnershipTransferRequested(log types.Log) (*DestinationFeeManagerOwnershipTransferRequested, error) + + FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationFeeManagerOwnershipTransferredIterator, error) + + WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerOwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error) + + ParseOwnershipTransferred(log types.Log) (*DestinationFeeManagerOwnershipTransferred, error) + + FilterSubscriberDiscountUpdated(opts *bind.FilterOpts, subscriber []common.Address, feedId [][32]byte) (*DestinationFeeManagerSubscriberDiscountUpdatedIterator, error) + + WatchSubscriberDiscountUpdated(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerSubscriberDiscountUpdated, subscriber []common.Address, feedId [][32]byte) (event.Subscription, error) + + ParseSubscriberDiscountUpdated(log types.Log) (*DestinationFeeManagerSubscriberDiscountUpdated, error) + + FilterWithdraw(opts *bind.FilterOpts) (*DestinationFeeManagerWithdrawIterator, error) + + WatchWithdraw(opts *bind.WatchOpts, sink chan<- *DestinationFeeManagerWithdraw) (event.Subscription, error) + + ParseWithdraw(log types.Log) (*DestinationFeeManagerWithdraw, error) + + ParseLog(log types.Log) (generated.AbigenLog, error) + + Address() common.Address +} diff --git a/core/gethwrappers/llo-feeds/generated/destination_reward_manager/destination_reward_manager.go b/core/gethwrappers/llo-feeds/generated/destination_reward_manager/destination_reward_manager.go new file mode 100644 index 0000000000..75ff7abe7e --- /dev/null +++ b/core/gethwrappers/llo-feeds/generated/destination_reward_manager/destination_reward_manager.go @@ -0,0 +1,1434 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package destination_reward_manager + +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 +) + +type CommonAddressAndWeight struct { + Addr common.Address + Weight uint64 +} + +type IDestinationRewardManagerFeePayment struct { + PoolId [32]byte + Amount *big.Int +} + +var DestinationRewardManagerMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"linkAddress\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"InvalidAddress\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidPoolId\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidPoolLength\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidWeights\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"Unauthorized\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newFeeManagerAddress\",\"type\":\"address\"}],\"name\":\"FeeManagerUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"poolId\",\"type\":\"bytes32\"},{\"internalType\":\"uint192\",\"name\":\"amount\",\"type\":\"uint192\"}],\"indexed\":false,\"internalType\":\"structIDestinationRewardManager.FeePayment[]\",\"name\":\"payments\",\"type\":\"tuple[]\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"payer\",\"type\":\"address\"}],\"name\":\"FeePaid\",\"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\":true,\"internalType\":\"bytes32\",\"name\":\"poolId\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"weight\",\"type\":\"uint64\"}],\"indexed\":false,\"internalType\":\"structCommon.AddressAndWeight[]\",\"name\":\"newRewardRecipients\",\"type\":\"tuple[]\"}],\"name\":\"RewardRecipientsUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"poolId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint192\",\"name\":\"quantity\",\"type\":\"uint192\"}],\"name\":\"RewardsClaimed\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newFeeManagerAddress\",\"type\":\"address\"}],\"name\":\"addFeeManager\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32[]\",\"name\":\"poolIds\",\"type\":\"bytes32[]\"}],\"name\":\"claimRewards\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"startIndex\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"endIndex\",\"type\":\"uint256\"}],\"name\":\"getAvailableRewardPoolIds\",\"outputs\":[{\"internalType\":\"bytes32[]\",\"name\":\"\",\"type\":\"bytes32[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"i_linkAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"poolId\",\"type\":\"bytes32\"},{\"internalType\":\"uint192\",\"name\":\"amount\",\"type\":\"uint192\"}],\"internalType\":\"structIDestinationRewardManager.FeePayment[]\",\"name\":\"payments\",\"type\":\"tuple[]\"},{\"internalType\":\"address\",\"name\":\"payer\",\"type\":\"address\"}],\"name\":\"onFeePaid\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"poolId\",\"type\":\"bytes32\"},{\"internalType\":\"address[]\",\"name\":\"recipients\",\"type\":\"address[]\"}],\"name\":\"payRecipients\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"feeManagerAddress\",\"type\":\"address\"}],\"name\":\"removeFeeManager\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"s_feeManagerAddressList\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"s_registeredPoolIds\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"s_rewardRecipientWeights\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"s_rewardRecipientWeightsSet\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"s_totalRewardRecipientFees\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"s_totalRewardRecipientFeesLastClaimedAmounts\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"poolId\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"weight\",\"type\":\"uint64\"}],\"internalType\":\"structCommon.AddressAndWeight[]\",\"name\":\"rewardRecipientAndWeights\",\"type\":\"tuple[]\"}],\"name\":\"setRewardRecipients\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"poolId\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"weight\",\"type\":\"uint64\"}],\"internalType\":\"structCommon.AddressAndWeight[]\",\"name\":\"newRewardRecipients\",\"type\":\"tuple[]\"}],\"name\":\"updateRewardRecipients\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + Bin: "0x60a06040523480156200001157600080fd5b5060405162002205380380620022058339810160408190526200003491620001a6565b33806000816200008b5760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620000be57620000be81620000fb565b5050506001600160a01b038116620000e95760405163e6c4247b60e01b815260040160405180910390fd5b6001600160a01b0316608052620001d8565b336001600160a01b03821603620001555760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640162000082565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b600060208284031215620001b957600080fd5b81516001600160a01b0381168114620001d157600080fd5b9392505050565b60805161200362000202600039600081816103fd01528181610e26015261106101526120036000f3fe608060405234801561001057600080fd5b506004361061016c5760003560e01c806360122608116100cd5780639604d05f11610081578063cd5f729211610066578063cd5f7292146103e5578063ea4b861b146103f8578063f2fde38b1461041f57600080fd5b80639604d05f1461039c578063b0d9fa19146103d257600080fd5b80638115c9cc116100b25780638115c9cc1461031f5780638ac85a5c146103325780638da5cb5b1461035d57600080fd5b806360122608146102ec57806379ba50971461031757600080fd5b806339ee81e1116101245780634944832f116101095780634944832f146102a35780634d322084146102b657806359256201146102c957600080fd5b806339ee81e114610255578063472264751461028357600080fd5b806314060f231161015557806314060f23146101f0578063181f5a77146102035780631f2d32c31461024257600080fd5b806301ffc9a7146101715780630f3c34d1146101db575b600080fd5b6101c661017f3660046118ef565b7fffffffff00000000000000000000000000000000000000000000000000000000167f768ffd3a000000000000000000000000000000000000000000000000000000001490565b60405190151581526020015b60405180910390f35b6101ee6101e93660046119af565b610432565b005b6101ee6101fe366004611aa1565b610440565b604080518082018252601e81527f44657374696e6174696f6e5265776172644d616e6167657220302e342e300000602082015290516101d29190611b11565b6101ee610250366004611b8b565b610602565b610275610263366004611bad565b60026020526000908152604090205481565b6040519081526020016101d2565b610296610291366004611bc6565b61073a565b6040516101d29190611bf9565b6101ee6102b1366004611aa1565b6108c4565b6101ee6102c4366004611c3d565b610a0d565b6101c66102d7366004611bad565b60056020526000908152604090205460ff1681565b6102756102fa366004611cbc565b600360209081526000928352604080842090915290825290205481565b6101ee610b1b565b6101ee61032d366004611b8b565b610c1d565b610275610340366004611cbc565b600460209081526000928352604080842090915290825290205481565b60005473ffffffffffffffffffffffffffffffffffffffff165b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016101d2565b6103776103aa366004611b8b565b60076020526000908152604090205473ffffffffffffffffffffffffffffffffffffffff1681565b6101ee6103e0366004611ce8565b610ccf565b6102756103f3366004611bad565b610e8f565b6103777f000000000000000000000000000000000000000000000000000000000000000081565b6101ee61042d366004611b8b565b610eb0565b61043c3382610ec4565b5050565b3360008181526007602052604090205473ffffffffffffffffffffffffffffffffffffffff161480159061048c575060005473ffffffffffffffffffffffffffffffffffffffff163314155b156104c3576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008190036104fe576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008381526005602052604090205460ff1615610547576040517f0afa7ee800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6006805460018181019092557ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f01849055600084815260056020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001690911790556105c3838383670de0b6b3a7640000611091565b827f8f668d6090683f98b3373a8b83d214da45737f7486cb7de554cc07b54e61cfe683836040516105f5929190611d54565b60405180910390a2505050565b61060a611268565b73ffffffffffffffffffffffffffffffffffffffff8116610657576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff81811660009081526007602052604090205416156106b6576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff811660008181526007602090815260409182902080547fffffffffffffffffffffffff0000000000000000000000000000000000000000168417905590519182527fe45f5e140399b0a7e12971ab020724b828fbed8ac408c420884dc7d1bbe506b4910160405180910390a150565b600654606090600081841161074f5783610751565b815b90508085111561078d576040517fa22caccc00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006107998683611deb565b67ffffffffffffffff8111156107b1576107b1611931565b6040519080825280602002602001820160405280156107da578160200160208202803683370190505b5090506000865b838110156108b7576000600682815481106107fe576107fe611dfe565b600091825260208083209091015480835260048252604080842073ffffffffffffffffffffffffffffffffffffffff8f168552909252912054909150156108a6576000818152600260209081526040808320546003835281842073ffffffffffffffffffffffffffffffffffffffff8f1685529092529091205481146108a4578185858060010196508151811061089757610897611dfe565b6020026020010181815250505b505b506108b081611e2d565b90506107e1565b5090979650505050505050565b6108cc611268565b60408051600180825281830190925260009160208083019080368337019050509050838160008151811061090257610902611dfe565b6020026020010181815250506000805b838110156109bf57600085858381811061092e5761092e611dfe565b6109449260206040909202019081019150611b8b565b600088815260046020908152604080832073ffffffffffffffffffffffffffffffffffffffff851684529091529020549091506109a887878581811061098c5761098c611dfe565b6109a29260206040909202019081019150611b8b565b86610ec4565b509290920191506109b881611e2d565b9050610912565b506109cc85858584611091565b847f8f668d6090683f98b3373a8b83d214da45737f7486cb7de554cc07b54e61cfe685856040516109fe929190611d54565b60405180910390a25050505050565b60008381526004602090815260408083203384529091529020548390158015610a4e575060005473ffffffffffffffffffffffffffffffffffffffff163314155b15610a85576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604080516001808252818301909252600091602080830190803683370190505090508481600081518110610abb57610abb611dfe565b60200260200101818152505060005b83811015610b1357610b02858583818110610ae757610ae7611dfe565b9050602002016020810190610afc9190611b8b565b83610ec4565b50610b0c81611e2d565b9050610aca565b505050505050565b60015473ffffffffffffffffffffffffffffffffffffffff163314610ba1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b610c25611268565b73ffffffffffffffffffffffffffffffffffffffff81811660009081526007602052604090205416610c83576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff16600090815260076020526040902080547fffffffffffffffffffffffff0000000000000000000000000000000000000000169055565b3360008181526007602052604090205473ffffffffffffffffffffffffffffffffffffffff1614610d2c576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000805b83811015610e0b57848482818110610d4a57610d4a611dfe565b9050604002016020016020810190610d629190611e8d565b77ffffffffffffffffffffffffffffffffffffffffffffffff1660026000878785818110610d9257610d92611dfe565b6040908102929092013583525060208201929092520160002080549091019055848482818110610dc457610dc4611dfe565b9050604002016020016020810190610ddc9190611e8d565b77ffffffffffffffffffffffffffffffffffffffffffffffff168201915080610e0490611e2d565b9050610d30565b50610e4e73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168330846112eb565b7fa1cc025ea76bacce5d740ee4bc331899375dc2c5f2ab33933aaacbd9ba001b66848484604051610e8193929190611ea8565b60405180910390a150505050565b60068181548110610e9f57600080fd5b600091825260209091200154905081565b610eb8611268565b610ec1816113cd565b50565b60008060005b8351811015611040576000848281518110610ee757610ee7611dfe565b6020026020010151905060006002600083815260200190815260200160002054905080600003610f18575050611030565b600082815260036020908152604080832073ffffffffffffffffffffffffffffffffffffffff8b16808552908352818420548685526004845282852091855292528220549083039190670de0b6b3a764000090830204905080600003610f815750505050611030565b600084815260036020908152604080832073ffffffffffffffffffffffffffffffffffffffff8d168085529252909120849055885196820196899087908110610fcc57610fcc611dfe565b60200260200101517f989969655bc1d593922527fe85d71347bb8e12fa423cc71f362dd8ef7cb10ef283604051611023919077ffffffffffffffffffffffffffffffffffffffffffffffff91909116815260200190565b60405180910390a3505050505b61103981611e2d565b9050610eca565b5080156110885761108873ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001685836114c2565b90505b92915050565b6110ec8383808060200260200160405190810160405280939291908181526020016000905b828210156110e2576110d360408302860136819003810190611f2f565b815260200190600101906110b6565b505050505061151d565b15611123576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000805b8381101561122757600085858381811061114357611143611dfe565b905060400201602001602081019061115b9190611f8a565b67ffffffffffffffff169050600086868481811061117b5761117b611dfe565b6111919260206040909202019081019150611b8b565b905073ffffffffffffffffffffffffffffffffffffffff81166111e0576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600088815260046020908152604080832073ffffffffffffffffffffffffffffffffffffffff909416835292905220819055919091019061122081611e2d565b9050611127565b50818114611261576040517f84677ce800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5050505050565b60005473ffffffffffffffffffffffffffffffffffffffff1633146112e9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610b98565b565b60405173ffffffffffffffffffffffffffffffffffffffff808516602483015283166044820152606481018290526113c79085907f23b872dd00000000000000000000000000000000000000000000000000000000906084015b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909316929092179091526115d4565b50505050565b3373ffffffffffffffffffffffffffffffffffffffff82160361144c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610b98565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b60405173ffffffffffffffffffffffffffffffffffffffff83166024820152604481018290526115189084907fa9059cbb0000000000000000000000000000000000000000000000000000000090606401611345565b505050565b6000805b82518110156115cb576000611537826001611fa5565b90505b83518110156115c25783818151811061155557611555611dfe565b60200260200101516000015173ffffffffffffffffffffffffffffffffffffffff1684838151811061158957611589611dfe565b60200260200101516000015173ffffffffffffffffffffffffffffffffffffffff16036115ba575060019392505050565b60010161153a565b50600101611521565b50600092915050565b6000611636826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65648152508573ffffffffffffffffffffffffffffffffffffffff166116e09092919063ffffffff16565b80519091501561151857808060200190518101906116549190611fb8565b611518576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f742073756363656564000000000000000000000000000000000000000000006064820152608401610b98565b60606116ef84846000856116f7565b949350505050565b606082471015611789576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c00000000000000000000000000000000000000000000000000006064820152608401610b98565b6000808673ffffffffffffffffffffffffffffffffffffffff1685876040516117b29190611fda565b60006040518083038185875af1925050503d80600081146117ef576040519150601f19603f3d011682016040523d82523d6000602084013e6117f4565b606091505b509150915061180587838387611810565b979650505050505050565b606083156118a657825160000361189f5773ffffffffffffffffffffffffffffffffffffffff85163b61189f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610b98565b50816116ef565b6116ef83838151156118bb5781518083602001fd5b806040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b989190611b11565b60006020828403121561190157600080fd5b81357fffffffff000000000000000000000000000000000000000000000000000000008116811461108857600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156119a7576119a7611931565b604052919050565b600060208083850312156119c257600080fd5b823567ffffffffffffffff808211156119da57600080fd5b818501915085601f8301126119ee57600080fd5b813581811115611a0057611a00611931565b8060051b9150611a11848301611960565b8181529183018401918481019088841115611a2b57600080fd5b938501935b83851015611a4957843582529385019390850190611a30565b98975050505050505050565b60008083601f840112611a6757600080fd5b50813567ffffffffffffffff811115611a7f57600080fd5b6020830191508360208260061b8501011115611a9a57600080fd5b9250929050565b600080600060408486031215611ab657600080fd5b83359250602084013567ffffffffffffffff811115611ad457600080fd5b611ae086828701611a55565b9497909650939450505050565b60005b83811015611b08578181015183820152602001611af0565b50506000910152565b6020815260008251806020840152611b30816040850160208701611aed565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169190910160400192915050565b803573ffffffffffffffffffffffffffffffffffffffff81168114611b8657600080fd5b919050565b600060208284031215611b9d57600080fd5b611ba682611b62565b9392505050565b600060208284031215611bbf57600080fd5b5035919050565b600080600060608486031215611bdb57600080fd5b611be484611b62565b95602085013595506040909401359392505050565b6020808252825182820181905260009190848201906040850190845b81811015611c3157835183529284019291840191600101611c15565b50909695505050505050565b600080600060408486031215611c5257600080fd5b83359250602084013567ffffffffffffffff80821115611c7157600080fd5b818601915086601f830112611c8557600080fd5b813581811115611c9457600080fd5b8760208260051b8501011115611ca957600080fd5b6020830194508093505050509250925092565b60008060408385031215611ccf57600080fd5b82359150611cdf60208401611b62565b90509250929050565b600080600060408486031215611cfd57600080fd5b833567ffffffffffffffff811115611d1457600080fd5b611d2086828701611a55565b9094509250611d33905060208501611b62565b90509250925092565b803567ffffffffffffffff81168114611b8657600080fd5b6020808252818101839052600090604080840186845b878110156108b75773ffffffffffffffffffffffffffffffffffffffff611d9083611b62565b16835267ffffffffffffffff611da7868401611d3c565b16838601529183019190830190600101611d6a565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8181038181111561108b5761108b611dbc565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203611e5e57611e5e611dbc565b5060010190565b803577ffffffffffffffffffffffffffffffffffffffffffffffff81168114611b8657600080fd5b600060208284031215611e9f57600080fd5b611ba682611e65565b60408082528181018490526000908560608401835b87811015611f045782358252602077ffffffffffffffffffffffffffffffffffffffffffffffff611eef828601611e65565b16908301529183019190830190600101611ebd565b5080935050505073ffffffffffffffffffffffffffffffffffffffff83166020830152949350505050565b600060408284031215611f4157600080fd5b6040516040810181811067ffffffffffffffff82111715611f6457611f64611931565b604052611f7083611b62565b8152611f7e60208401611d3c565b60208201529392505050565b600060208284031215611f9c57600080fd5b611ba682611d3c565b8082018082111561108b5761108b611dbc565b600060208284031215611fca57600080fd5b8151801515811461108857600080fd5b60008251611fec818460208701611aed565b919091019291505056fea164736f6c6343000813000a", +} + +var DestinationRewardManagerABI = DestinationRewardManagerMetaData.ABI + +var DestinationRewardManagerBin = DestinationRewardManagerMetaData.Bin + +func DeployDestinationRewardManager(auth *bind.TransactOpts, backend bind.ContractBackend, linkAddress common.Address) (common.Address, *types.Transaction, *DestinationRewardManager, error) { + parsed, err := DestinationRewardManagerMetaData.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(DestinationRewardManagerBin), backend, linkAddress) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &DestinationRewardManager{address: address, abi: *parsed, DestinationRewardManagerCaller: DestinationRewardManagerCaller{contract: contract}, DestinationRewardManagerTransactor: DestinationRewardManagerTransactor{contract: contract}, DestinationRewardManagerFilterer: DestinationRewardManagerFilterer{contract: contract}}, nil +} + +type DestinationRewardManager struct { + address common.Address + abi abi.ABI + DestinationRewardManagerCaller + DestinationRewardManagerTransactor + DestinationRewardManagerFilterer +} + +type DestinationRewardManagerCaller struct { + contract *bind.BoundContract +} + +type DestinationRewardManagerTransactor struct { + contract *bind.BoundContract +} + +type DestinationRewardManagerFilterer struct { + contract *bind.BoundContract +} + +type DestinationRewardManagerSession struct { + Contract *DestinationRewardManager + CallOpts bind.CallOpts + TransactOpts bind.TransactOpts +} + +type DestinationRewardManagerCallerSession struct { + Contract *DestinationRewardManagerCaller + CallOpts bind.CallOpts +} + +type DestinationRewardManagerTransactorSession struct { + Contract *DestinationRewardManagerTransactor + TransactOpts bind.TransactOpts +} + +type DestinationRewardManagerRaw struct { + Contract *DestinationRewardManager +} + +type DestinationRewardManagerCallerRaw struct { + Contract *DestinationRewardManagerCaller +} + +type DestinationRewardManagerTransactorRaw struct { + Contract *DestinationRewardManagerTransactor +} + +func NewDestinationRewardManager(address common.Address, backend bind.ContractBackend) (*DestinationRewardManager, error) { + abi, err := abi.JSON(strings.NewReader(DestinationRewardManagerABI)) + if err != nil { + return nil, err + } + contract, err := bindDestinationRewardManager(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &DestinationRewardManager{address: address, abi: abi, DestinationRewardManagerCaller: DestinationRewardManagerCaller{contract: contract}, DestinationRewardManagerTransactor: DestinationRewardManagerTransactor{contract: contract}, DestinationRewardManagerFilterer: DestinationRewardManagerFilterer{contract: contract}}, nil +} + +func NewDestinationRewardManagerCaller(address common.Address, caller bind.ContractCaller) (*DestinationRewardManagerCaller, error) { + contract, err := bindDestinationRewardManager(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &DestinationRewardManagerCaller{contract: contract}, nil +} + +func NewDestinationRewardManagerTransactor(address common.Address, transactor bind.ContractTransactor) (*DestinationRewardManagerTransactor, error) { + contract, err := bindDestinationRewardManager(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &DestinationRewardManagerTransactor{contract: contract}, nil +} + +func NewDestinationRewardManagerFilterer(address common.Address, filterer bind.ContractFilterer) (*DestinationRewardManagerFilterer, error) { + contract, err := bindDestinationRewardManager(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &DestinationRewardManagerFilterer{contract: contract}, nil +} + +func bindDestinationRewardManager(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := DestinationRewardManagerMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +func (_DestinationRewardManager *DestinationRewardManagerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _DestinationRewardManager.Contract.DestinationRewardManagerCaller.contract.Call(opts, result, method, params...) +} + +func (_DestinationRewardManager *DestinationRewardManagerRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _DestinationRewardManager.Contract.DestinationRewardManagerTransactor.contract.Transfer(opts) +} + +func (_DestinationRewardManager *DestinationRewardManagerRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _DestinationRewardManager.Contract.DestinationRewardManagerTransactor.contract.Transact(opts, method, params...) +} + +func (_DestinationRewardManager *DestinationRewardManagerCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _DestinationRewardManager.Contract.contract.Call(opts, result, method, params...) +} + +func (_DestinationRewardManager *DestinationRewardManagerTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _DestinationRewardManager.Contract.contract.Transfer(opts) +} + +func (_DestinationRewardManager *DestinationRewardManagerTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _DestinationRewardManager.Contract.contract.Transact(opts, method, params...) +} + +func (_DestinationRewardManager *DestinationRewardManagerCaller) GetAvailableRewardPoolIds(opts *bind.CallOpts, recipient common.Address, startIndex *big.Int, endIndex *big.Int) ([][32]byte, error) { + var out []interface{} + err := _DestinationRewardManager.contract.Call(opts, &out, "getAvailableRewardPoolIds", recipient, startIndex, endIndex) + + if err != nil { + return *new([][32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([][32]byte)).(*[][32]byte) + + return out0, err + +} + +func (_DestinationRewardManager *DestinationRewardManagerSession) GetAvailableRewardPoolIds(recipient common.Address, startIndex *big.Int, endIndex *big.Int) ([][32]byte, error) { + return _DestinationRewardManager.Contract.GetAvailableRewardPoolIds(&_DestinationRewardManager.CallOpts, recipient, startIndex, endIndex) +} + +func (_DestinationRewardManager *DestinationRewardManagerCallerSession) GetAvailableRewardPoolIds(recipient common.Address, startIndex *big.Int, endIndex *big.Int) ([][32]byte, error) { + return _DestinationRewardManager.Contract.GetAvailableRewardPoolIds(&_DestinationRewardManager.CallOpts, recipient, startIndex, endIndex) +} + +func (_DestinationRewardManager *DestinationRewardManagerCaller) ILinkAddress(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _DestinationRewardManager.contract.Call(opts, &out, "i_linkAddress") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +func (_DestinationRewardManager *DestinationRewardManagerSession) ILinkAddress() (common.Address, error) { + return _DestinationRewardManager.Contract.ILinkAddress(&_DestinationRewardManager.CallOpts) +} + +func (_DestinationRewardManager *DestinationRewardManagerCallerSession) ILinkAddress() (common.Address, error) { + return _DestinationRewardManager.Contract.ILinkAddress(&_DestinationRewardManager.CallOpts) +} + +func (_DestinationRewardManager *DestinationRewardManagerCaller) Owner(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _DestinationRewardManager.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 (_DestinationRewardManager *DestinationRewardManagerSession) Owner() (common.Address, error) { + return _DestinationRewardManager.Contract.Owner(&_DestinationRewardManager.CallOpts) +} + +func (_DestinationRewardManager *DestinationRewardManagerCallerSession) Owner() (common.Address, error) { + return _DestinationRewardManager.Contract.Owner(&_DestinationRewardManager.CallOpts) +} + +func (_DestinationRewardManager *DestinationRewardManagerCaller) SFeeManagerAddressList(opts *bind.CallOpts, arg0 common.Address) (common.Address, error) { + var out []interface{} + err := _DestinationRewardManager.contract.Call(opts, &out, "s_feeManagerAddressList", arg0) + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +func (_DestinationRewardManager *DestinationRewardManagerSession) SFeeManagerAddressList(arg0 common.Address) (common.Address, error) { + return _DestinationRewardManager.Contract.SFeeManagerAddressList(&_DestinationRewardManager.CallOpts, arg0) +} + +func (_DestinationRewardManager *DestinationRewardManagerCallerSession) SFeeManagerAddressList(arg0 common.Address) (common.Address, error) { + return _DestinationRewardManager.Contract.SFeeManagerAddressList(&_DestinationRewardManager.CallOpts, arg0) +} + +func (_DestinationRewardManager *DestinationRewardManagerCaller) SRegisteredPoolIds(opts *bind.CallOpts, arg0 *big.Int) ([32]byte, error) { + var out []interface{} + err := _DestinationRewardManager.contract.Call(opts, &out, "s_registeredPoolIds", arg0) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +func (_DestinationRewardManager *DestinationRewardManagerSession) SRegisteredPoolIds(arg0 *big.Int) ([32]byte, error) { + return _DestinationRewardManager.Contract.SRegisteredPoolIds(&_DestinationRewardManager.CallOpts, arg0) +} + +func (_DestinationRewardManager *DestinationRewardManagerCallerSession) SRegisteredPoolIds(arg0 *big.Int) ([32]byte, error) { + return _DestinationRewardManager.Contract.SRegisteredPoolIds(&_DestinationRewardManager.CallOpts, arg0) +} + +func (_DestinationRewardManager *DestinationRewardManagerCaller) SRewardRecipientWeights(opts *bind.CallOpts, arg0 [32]byte, arg1 common.Address) (*big.Int, error) { + var out []interface{} + err := _DestinationRewardManager.contract.Call(opts, &out, "s_rewardRecipientWeights", arg0, arg1) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_DestinationRewardManager *DestinationRewardManagerSession) SRewardRecipientWeights(arg0 [32]byte, arg1 common.Address) (*big.Int, error) { + return _DestinationRewardManager.Contract.SRewardRecipientWeights(&_DestinationRewardManager.CallOpts, arg0, arg1) +} + +func (_DestinationRewardManager *DestinationRewardManagerCallerSession) SRewardRecipientWeights(arg0 [32]byte, arg1 common.Address) (*big.Int, error) { + return _DestinationRewardManager.Contract.SRewardRecipientWeights(&_DestinationRewardManager.CallOpts, arg0, arg1) +} + +func (_DestinationRewardManager *DestinationRewardManagerCaller) SRewardRecipientWeightsSet(opts *bind.CallOpts, arg0 [32]byte) (bool, error) { + var out []interface{} + err := _DestinationRewardManager.contract.Call(opts, &out, "s_rewardRecipientWeightsSet", arg0) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +func (_DestinationRewardManager *DestinationRewardManagerSession) SRewardRecipientWeightsSet(arg0 [32]byte) (bool, error) { + return _DestinationRewardManager.Contract.SRewardRecipientWeightsSet(&_DestinationRewardManager.CallOpts, arg0) +} + +func (_DestinationRewardManager *DestinationRewardManagerCallerSession) SRewardRecipientWeightsSet(arg0 [32]byte) (bool, error) { + return _DestinationRewardManager.Contract.SRewardRecipientWeightsSet(&_DestinationRewardManager.CallOpts, arg0) +} + +func (_DestinationRewardManager *DestinationRewardManagerCaller) STotalRewardRecipientFees(opts *bind.CallOpts, arg0 [32]byte) (*big.Int, error) { + var out []interface{} + err := _DestinationRewardManager.contract.Call(opts, &out, "s_totalRewardRecipientFees", arg0) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_DestinationRewardManager *DestinationRewardManagerSession) STotalRewardRecipientFees(arg0 [32]byte) (*big.Int, error) { + return _DestinationRewardManager.Contract.STotalRewardRecipientFees(&_DestinationRewardManager.CallOpts, arg0) +} + +func (_DestinationRewardManager *DestinationRewardManagerCallerSession) STotalRewardRecipientFees(arg0 [32]byte) (*big.Int, error) { + return _DestinationRewardManager.Contract.STotalRewardRecipientFees(&_DestinationRewardManager.CallOpts, arg0) +} + +func (_DestinationRewardManager *DestinationRewardManagerCaller) STotalRewardRecipientFeesLastClaimedAmounts(opts *bind.CallOpts, arg0 [32]byte, arg1 common.Address) (*big.Int, error) { + var out []interface{} + err := _DestinationRewardManager.contract.Call(opts, &out, "s_totalRewardRecipientFeesLastClaimedAmounts", arg0, arg1) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_DestinationRewardManager *DestinationRewardManagerSession) STotalRewardRecipientFeesLastClaimedAmounts(arg0 [32]byte, arg1 common.Address) (*big.Int, error) { + return _DestinationRewardManager.Contract.STotalRewardRecipientFeesLastClaimedAmounts(&_DestinationRewardManager.CallOpts, arg0, arg1) +} + +func (_DestinationRewardManager *DestinationRewardManagerCallerSession) STotalRewardRecipientFeesLastClaimedAmounts(arg0 [32]byte, arg1 common.Address) (*big.Int, error) { + return _DestinationRewardManager.Contract.STotalRewardRecipientFeesLastClaimedAmounts(&_DestinationRewardManager.CallOpts, arg0, arg1) +} + +func (_DestinationRewardManager *DestinationRewardManagerCaller) SupportsInterface(opts *bind.CallOpts, interfaceId [4]byte) (bool, error) { + var out []interface{} + err := _DestinationRewardManager.contract.Call(opts, &out, "supportsInterface", interfaceId) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +func (_DestinationRewardManager *DestinationRewardManagerSession) SupportsInterface(interfaceId [4]byte) (bool, error) { + return _DestinationRewardManager.Contract.SupportsInterface(&_DestinationRewardManager.CallOpts, interfaceId) +} + +func (_DestinationRewardManager *DestinationRewardManagerCallerSession) SupportsInterface(interfaceId [4]byte) (bool, error) { + return _DestinationRewardManager.Contract.SupportsInterface(&_DestinationRewardManager.CallOpts, interfaceId) +} + +func (_DestinationRewardManager *DestinationRewardManagerCaller) TypeAndVersion(opts *bind.CallOpts) (string, error) { + var out []interface{} + err := _DestinationRewardManager.contract.Call(opts, &out, "typeAndVersion") + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + +} + +func (_DestinationRewardManager *DestinationRewardManagerSession) TypeAndVersion() (string, error) { + return _DestinationRewardManager.Contract.TypeAndVersion(&_DestinationRewardManager.CallOpts) +} + +func (_DestinationRewardManager *DestinationRewardManagerCallerSession) TypeAndVersion() (string, error) { + return _DestinationRewardManager.Contract.TypeAndVersion(&_DestinationRewardManager.CallOpts) +} + +func (_DestinationRewardManager *DestinationRewardManagerTransactor) AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) { + return _DestinationRewardManager.contract.Transact(opts, "acceptOwnership") +} + +func (_DestinationRewardManager *DestinationRewardManagerSession) AcceptOwnership() (*types.Transaction, error) { + return _DestinationRewardManager.Contract.AcceptOwnership(&_DestinationRewardManager.TransactOpts) +} + +func (_DestinationRewardManager *DestinationRewardManagerTransactorSession) AcceptOwnership() (*types.Transaction, error) { + return _DestinationRewardManager.Contract.AcceptOwnership(&_DestinationRewardManager.TransactOpts) +} + +func (_DestinationRewardManager *DestinationRewardManagerTransactor) AddFeeManager(opts *bind.TransactOpts, newFeeManagerAddress common.Address) (*types.Transaction, error) { + return _DestinationRewardManager.contract.Transact(opts, "addFeeManager", newFeeManagerAddress) +} + +func (_DestinationRewardManager *DestinationRewardManagerSession) AddFeeManager(newFeeManagerAddress common.Address) (*types.Transaction, error) { + return _DestinationRewardManager.Contract.AddFeeManager(&_DestinationRewardManager.TransactOpts, newFeeManagerAddress) +} + +func (_DestinationRewardManager *DestinationRewardManagerTransactorSession) AddFeeManager(newFeeManagerAddress common.Address) (*types.Transaction, error) { + return _DestinationRewardManager.Contract.AddFeeManager(&_DestinationRewardManager.TransactOpts, newFeeManagerAddress) +} + +func (_DestinationRewardManager *DestinationRewardManagerTransactor) ClaimRewards(opts *bind.TransactOpts, poolIds [][32]byte) (*types.Transaction, error) { + return _DestinationRewardManager.contract.Transact(opts, "claimRewards", poolIds) +} + +func (_DestinationRewardManager *DestinationRewardManagerSession) ClaimRewards(poolIds [][32]byte) (*types.Transaction, error) { + return _DestinationRewardManager.Contract.ClaimRewards(&_DestinationRewardManager.TransactOpts, poolIds) +} + +func (_DestinationRewardManager *DestinationRewardManagerTransactorSession) ClaimRewards(poolIds [][32]byte) (*types.Transaction, error) { + return _DestinationRewardManager.Contract.ClaimRewards(&_DestinationRewardManager.TransactOpts, poolIds) +} + +func (_DestinationRewardManager *DestinationRewardManagerTransactor) OnFeePaid(opts *bind.TransactOpts, payments []IDestinationRewardManagerFeePayment, payer common.Address) (*types.Transaction, error) { + return _DestinationRewardManager.contract.Transact(opts, "onFeePaid", payments, payer) +} + +func (_DestinationRewardManager *DestinationRewardManagerSession) OnFeePaid(payments []IDestinationRewardManagerFeePayment, payer common.Address) (*types.Transaction, error) { + return _DestinationRewardManager.Contract.OnFeePaid(&_DestinationRewardManager.TransactOpts, payments, payer) +} + +func (_DestinationRewardManager *DestinationRewardManagerTransactorSession) OnFeePaid(payments []IDestinationRewardManagerFeePayment, payer common.Address) (*types.Transaction, error) { + return _DestinationRewardManager.Contract.OnFeePaid(&_DestinationRewardManager.TransactOpts, payments, payer) +} + +func (_DestinationRewardManager *DestinationRewardManagerTransactor) PayRecipients(opts *bind.TransactOpts, poolId [32]byte, recipients []common.Address) (*types.Transaction, error) { + return _DestinationRewardManager.contract.Transact(opts, "payRecipients", poolId, recipients) +} + +func (_DestinationRewardManager *DestinationRewardManagerSession) PayRecipients(poolId [32]byte, recipients []common.Address) (*types.Transaction, error) { + return _DestinationRewardManager.Contract.PayRecipients(&_DestinationRewardManager.TransactOpts, poolId, recipients) +} + +func (_DestinationRewardManager *DestinationRewardManagerTransactorSession) PayRecipients(poolId [32]byte, recipients []common.Address) (*types.Transaction, error) { + return _DestinationRewardManager.Contract.PayRecipients(&_DestinationRewardManager.TransactOpts, poolId, recipients) +} + +func (_DestinationRewardManager *DestinationRewardManagerTransactor) RemoveFeeManager(opts *bind.TransactOpts, feeManagerAddress common.Address) (*types.Transaction, error) { + return _DestinationRewardManager.contract.Transact(opts, "removeFeeManager", feeManagerAddress) +} + +func (_DestinationRewardManager *DestinationRewardManagerSession) RemoveFeeManager(feeManagerAddress common.Address) (*types.Transaction, error) { + return _DestinationRewardManager.Contract.RemoveFeeManager(&_DestinationRewardManager.TransactOpts, feeManagerAddress) +} + +func (_DestinationRewardManager *DestinationRewardManagerTransactorSession) RemoveFeeManager(feeManagerAddress common.Address) (*types.Transaction, error) { + return _DestinationRewardManager.Contract.RemoveFeeManager(&_DestinationRewardManager.TransactOpts, feeManagerAddress) +} + +func (_DestinationRewardManager *DestinationRewardManagerTransactor) SetRewardRecipients(opts *bind.TransactOpts, poolId [32]byte, rewardRecipientAndWeights []CommonAddressAndWeight) (*types.Transaction, error) { + return _DestinationRewardManager.contract.Transact(opts, "setRewardRecipients", poolId, rewardRecipientAndWeights) +} + +func (_DestinationRewardManager *DestinationRewardManagerSession) SetRewardRecipients(poolId [32]byte, rewardRecipientAndWeights []CommonAddressAndWeight) (*types.Transaction, error) { + return _DestinationRewardManager.Contract.SetRewardRecipients(&_DestinationRewardManager.TransactOpts, poolId, rewardRecipientAndWeights) +} + +func (_DestinationRewardManager *DestinationRewardManagerTransactorSession) SetRewardRecipients(poolId [32]byte, rewardRecipientAndWeights []CommonAddressAndWeight) (*types.Transaction, error) { + return _DestinationRewardManager.Contract.SetRewardRecipients(&_DestinationRewardManager.TransactOpts, poolId, rewardRecipientAndWeights) +} + +func (_DestinationRewardManager *DestinationRewardManagerTransactor) TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) { + return _DestinationRewardManager.contract.Transact(opts, "transferOwnership", to) +} + +func (_DestinationRewardManager *DestinationRewardManagerSession) TransferOwnership(to common.Address) (*types.Transaction, error) { + return _DestinationRewardManager.Contract.TransferOwnership(&_DestinationRewardManager.TransactOpts, to) +} + +func (_DestinationRewardManager *DestinationRewardManagerTransactorSession) TransferOwnership(to common.Address) (*types.Transaction, error) { + return _DestinationRewardManager.Contract.TransferOwnership(&_DestinationRewardManager.TransactOpts, to) +} + +func (_DestinationRewardManager *DestinationRewardManagerTransactor) UpdateRewardRecipients(opts *bind.TransactOpts, poolId [32]byte, newRewardRecipients []CommonAddressAndWeight) (*types.Transaction, error) { + return _DestinationRewardManager.contract.Transact(opts, "updateRewardRecipients", poolId, newRewardRecipients) +} + +func (_DestinationRewardManager *DestinationRewardManagerSession) UpdateRewardRecipients(poolId [32]byte, newRewardRecipients []CommonAddressAndWeight) (*types.Transaction, error) { + return _DestinationRewardManager.Contract.UpdateRewardRecipients(&_DestinationRewardManager.TransactOpts, poolId, newRewardRecipients) +} + +func (_DestinationRewardManager *DestinationRewardManagerTransactorSession) UpdateRewardRecipients(poolId [32]byte, newRewardRecipients []CommonAddressAndWeight) (*types.Transaction, error) { + return _DestinationRewardManager.Contract.UpdateRewardRecipients(&_DestinationRewardManager.TransactOpts, poolId, newRewardRecipients) +} + +type DestinationRewardManagerFeeManagerUpdatedIterator struct { + Event *DestinationRewardManagerFeeManagerUpdated + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *DestinationRewardManagerFeeManagerUpdatedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(DestinationRewardManagerFeeManagerUpdated) + 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(DestinationRewardManagerFeeManagerUpdated) + 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 *DestinationRewardManagerFeeManagerUpdatedIterator) Error() error { + return it.fail +} + +func (it *DestinationRewardManagerFeeManagerUpdatedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type DestinationRewardManagerFeeManagerUpdated struct { + NewFeeManagerAddress common.Address + Raw types.Log +} + +func (_DestinationRewardManager *DestinationRewardManagerFilterer) FilterFeeManagerUpdated(opts *bind.FilterOpts) (*DestinationRewardManagerFeeManagerUpdatedIterator, error) { + + logs, sub, err := _DestinationRewardManager.contract.FilterLogs(opts, "FeeManagerUpdated") + if err != nil { + return nil, err + } + return &DestinationRewardManagerFeeManagerUpdatedIterator{contract: _DestinationRewardManager.contract, event: "FeeManagerUpdated", logs: logs, sub: sub}, nil +} + +func (_DestinationRewardManager *DestinationRewardManagerFilterer) WatchFeeManagerUpdated(opts *bind.WatchOpts, sink chan<- *DestinationRewardManagerFeeManagerUpdated) (event.Subscription, error) { + + logs, sub, err := _DestinationRewardManager.contract.WatchLogs(opts, "FeeManagerUpdated") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(DestinationRewardManagerFeeManagerUpdated) + if err := _DestinationRewardManager.contract.UnpackLog(event, "FeeManagerUpdated", 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 (_DestinationRewardManager *DestinationRewardManagerFilterer) ParseFeeManagerUpdated(log types.Log) (*DestinationRewardManagerFeeManagerUpdated, error) { + event := new(DestinationRewardManagerFeeManagerUpdated) + if err := _DestinationRewardManager.contract.UnpackLog(event, "FeeManagerUpdated", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type DestinationRewardManagerFeePaidIterator struct { + Event *DestinationRewardManagerFeePaid + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *DestinationRewardManagerFeePaidIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(DestinationRewardManagerFeePaid) + 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(DestinationRewardManagerFeePaid) + 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 *DestinationRewardManagerFeePaidIterator) Error() error { + return it.fail +} + +func (it *DestinationRewardManagerFeePaidIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type DestinationRewardManagerFeePaid struct { + Payments []IDestinationRewardManagerFeePayment + Payer common.Address + Raw types.Log +} + +func (_DestinationRewardManager *DestinationRewardManagerFilterer) FilterFeePaid(opts *bind.FilterOpts) (*DestinationRewardManagerFeePaidIterator, error) { + + logs, sub, err := _DestinationRewardManager.contract.FilterLogs(opts, "FeePaid") + if err != nil { + return nil, err + } + return &DestinationRewardManagerFeePaidIterator{contract: _DestinationRewardManager.contract, event: "FeePaid", logs: logs, sub: sub}, nil +} + +func (_DestinationRewardManager *DestinationRewardManagerFilterer) WatchFeePaid(opts *bind.WatchOpts, sink chan<- *DestinationRewardManagerFeePaid) (event.Subscription, error) { + + logs, sub, err := _DestinationRewardManager.contract.WatchLogs(opts, "FeePaid") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(DestinationRewardManagerFeePaid) + if err := _DestinationRewardManager.contract.UnpackLog(event, "FeePaid", 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 (_DestinationRewardManager *DestinationRewardManagerFilterer) ParseFeePaid(log types.Log) (*DestinationRewardManagerFeePaid, error) { + event := new(DestinationRewardManagerFeePaid) + if err := _DestinationRewardManager.contract.UnpackLog(event, "FeePaid", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type DestinationRewardManagerOwnershipTransferRequestedIterator struct { + Event *DestinationRewardManagerOwnershipTransferRequested + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *DestinationRewardManagerOwnershipTransferRequestedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(DestinationRewardManagerOwnershipTransferRequested) + 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(DestinationRewardManagerOwnershipTransferRequested) + 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 *DestinationRewardManagerOwnershipTransferRequestedIterator) Error() error { + return it.fail +} + +func (it *DestinationRewardManagerOwnershipTransferRequestedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type DestinationRewardManagerOwnershipTransferRequested struct { + From common.Address + To common.Address + Raw types.Log +} + +func (_DestinationRewardManager *DestinationRewardManagerFilterer) FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationRewardManagerOwnershipTransferRequestedIterator, 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 := _DestinationRewardManager.contract.FilterLogs(opts, "OwnershipTransferRequested", fromRule, toRule) + if err != nil { + return nil, err + } + return &DestinationRewardManagerOwnershipTransferRequestedIterator{contract: _DestinationRewardManager.contract, event: "OwnershipTransferRequested", logs: logs, sub: sub}, nil +} + +func (_DestinationRewardManager *DestinationRewardManagerFilterer) WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *DestinationRewardManagerOwnershipTransferRequested, 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 := _DestinationRewardManager.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(DestinationRewardManagerOwnershipTransferRequested) + if err := _DestinationRewardManager.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 (_DestinationRewardManager *DestinationRewardManagerFilterer) ParseOwnershipTransferRequested(log types.Log) (*DestinationRewardManagerOwnershipTransferRequested, error) { + event := new(DestinationRewardManagerOwnershipTransferRequested) + if err := _DestinationRewardManager.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type DestinationRewardManagerOwnershipTransferredIterator struct { + Event *DestinationRewardManagerOwnershipTransferred + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *DestinationRewardManagerOwnershipTransferredIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(DestinationRewardManagerOwnershipTransferred) + 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(DestinationRewardManagerOwnershipTransferred) + 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 *DestinationRewardManagerOwnershipTransferredIterator) Error() error { + return it.fail +} + +func (it *DestinationRewardManagerOwnershipTransferredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type DestinationRewardManagerOwnershipTransferred struct { + From common.Address + To common.Address + Raw types.Log +} + +func (_DestinationRewardManager *DestinationRewardManagerFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationRewardManagerOwnershipTransferredIterator, 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 := _DestinationRewardManager.contract.FilterLogs(opts, "OwnershipTransferred", fromRule, toRule) + if err != nil { + return nil, err + } + return &DestinationRewardManagerOwnershipTransferredIterator{contract: _DestinationRewardManager.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil +} + +func (_DestinationRewardManager *DestinationRewardManagerFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *DestinationRewardManagerOwnershipTransferred, 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 := _DestinationRewardManager.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(DestinationRewardManagerOwnershipTransferred) + if err := _DestinationRewardManager.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 (_DestinationRewardManager *DestinationRewardManagerFilterer) ParseOwnershipTransferred(log types.Log) (*DestinationRewardManagerOwnershipTransferred, error) { + event := new(DestinationRewardManagerOwnershipTransferred) + if err := _DestinationRewardManager.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type DestinationRewardManagerRewardRecipientsUpdatedIterator struct { + Event *DestinationRewardManagerRewardRecipientsUpdated + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *DestinationRewardManagerRewardRecipientsUpdatedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(DestinationRewardManagerRewardRecipientsUpdated) + 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(DestinationRewardManagerRewardRecipientsUpdated) + 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 *DestinationRewardManagerRewardRecipientsUpdatedIterator) Error() error { + return it.fail +} + +func (it *DestinationRewardManagerRewardRecipientsUpdatedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type DestinationRewardManagerRewardRecipientsUpdated struct { + PoolId [32]byte + NewRewardRecipients []CommonAddressAndWeight + Raw types.Log +} + +func (_DestinationRewardManager *DestinationRewardManagerFilterer) FilterRewardRecipientsUpdated(opts *bind.FilterOpts, poolId [][32]byte) (*DestinationRewardManagerRewardRecipientsUpdatedIterator, error) { + + var poolIdRule []interface{} + for _, poolIdItem := range poolId { + poolIdRule = append(poolIdRule, poolIdItem) + } + + logs, sub, err := _DestinationRewardManager.contract.FilterLogs(opts, "RewardRecipientsUpdated", poolIdRule) + if err != nil { + return nil, err + } + return &DestinationRewardManagerRewardRecipientsUpdatedIterator{contract: _DestinationRewardManager.contract, event: "RewardRecipientsUpdated", logs: logs, sub: sub}, nil +} + +func (_DestinationRewardManager *DestinationRewardManagerFilterer) WatchRewardRecipientsUpdated(opts *bind.WatchOpts, sink chan<- *DestinationRewardManagerRewardRecipientsUpdated, poolId [][32]byte) (event.Subscription, error) { + + var poolIdRule []interface{} + for _, poolIdItem := range poolId { + poolIdRule = append(poolIdRule, poolIdItem) + } + + logs, sub, err := _DestinationRewardManager.contract.WatchLogs(opts, "RewardRecipientsUpdated", poolIdRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(DestinationRewardManagerRewardRecipientsUpdated) + if err := _DestinationRewardManager.contract.UnpackLog(event, "RewardRecipientsUpdated", 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 (_DestinationRewardManager *DestinationRewardManagerFilterer) ParseRewardRecipientsUpdated(log types.Log) (*DestinationRewardManagerRewardRecipientsUpdated, error) { + event := new(DestinationRewardManagerRewardRecipientsUpdated) + if err := _DestinationRewardManager.contract.UnpackLog(event, "RewardRecipientsUpdated", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type DestinationRewardManagerRewardsClaimedIterator struct { + Event *DestinationRewardManagerRewardsClaimed + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *DestinationRewardManagerRewardsClaimedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(DestinationRewardManagerRewardsClaimed) + 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(DestinationRewardManagerRewardsClaimed) + 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 *DestinationRewardManagerRewardsClaimedIterator) Error() error { + return it.fail +} + +func (it *DestinationRewardManagerRewardsClaimedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type DestinationRewardManagerRewardsClaimed struct { + PoolId [32]byte + Recipient common.Address + Quantity *big.Int + Raw types.Log +} + +func (_DestinationRewardManager *DestinationRewardManagerFilterer) FilterRewardsClaimed(opts *bind.FilterOpts, poolId [][32]byte, recipient []common.Address) (*DestinationRewardManagerRewardsClaimedIterator, error) { + + var poolIdRule []interface{} + for _, poolIdItem := range poolId { + poolIdRule = append(poolIdRule, poolIdItem) + } + var recipientRule []interface{} + for _, recipientItem := range recipient { + recipientRule = append(recipientRule, recipientItem) + } + + logs, sub, err := _DestinationRewardManager.contract.FilterLogs(opts, "RewardsClaimed", poolIdRule, recipientRule) + if err != nil { + return nil, err + } + return &DestinationRewardManagerRewardsClaimedIterator{contract: _DestinationRewardManager.contract, event: "RewardsClaimed", logs: logs, sub: sub}, nil +} + +func (_DestinationRewardManager *DestinationRewardManagerFilterer) WatchRewardsClaimed(opts *bind.WatchOpts, sink chan<- *DestinationRewardManagerRewardsClaimed, poolId [][32]byte, recipient []common.Address) (event.Subscription, error) { + + var poolIdRule []interface{} + for _, poolIdItem := range poolId { + poolIdRule = append(poolIdRule, poolIdItem) + } + var recipientRule []interface{} + for _, recipientItem := range recipient { + recipientRule = append(recipientRule, recipientItem) + } + + logs, sub, err := _DestinationRewardManager.contract.WatchLogs(opts, "RewardsClaimed", poolIdRule, recipientRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(DestinationRewardManagerRewardsClaimed) + if err := _DestinationRewardManager.contract.UnpackLog(event, "RewardsClaimed", 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 (_DestinationRewardManager *DestinationRewardManagerFilterer) ParseRewardsClaimed(log types.Log) (*DestinationRewardManagerRewardsClaimed, error) { + event := new(DestinationRewardManagerRewardsClaimed) + if err := _DestinationRewardManager.contract.UnpackLog(event, "RewardsClaimed", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +func (_DestinationRewardManager *DestinationRewardManager) ParseLog(log types.Log) (generated.AbigenLog, error) { + switch log.Topics[0] { + case _DestinationRewardManager.abi.Events["FeeManagerUpdated"].ID: + return _DestinationRewardManager.ParseFeeManagerUpdated(log) + case _DestinationRewardManager.abi.Events["FeePaid"].ID: + return _DestinationRewardManager.ParseFeePaid(log) + case _DestinationRewardManager.abi.Events["OwnershipTransferRequested"].ID: + return _DestinationRewardManager.ParseOwnershipTransferRequested(log) + case _DestinationRewardManager.abi.Events["OwnershipTransferred"].ID: + return _DestinationRewardManager.ParseOwnershipTransferred(log) + case _DestinationRewardManager.abi.Events["RewardRecipientsUpdated"].ID: + return _DestinationRewardManager.ParseRewardRecipientsUpdated(log) + case _DestinationRewardManager.abi.Events["RewardsClaimed"].ID: + return _DestinationRewardManager.ParseRewardsClaimed(log) + + default: + return nil, fmt.Errorf("abigen wrapper received unknown log topic: %v", log.Topics[0]) + } +} + +func (DestinationRewardManagerFeeManagerUpdated) Topic() common.Hash { + return common.HexToHash("0xe45f5e140399b0a7e12971ab020724b828fbed8ac408c420884dc7d1bbe506b4") +} + +func (DestinationRewardManagerFeePaid) Topic() common.Hash { + return common.HexToHash("0xa1cc025ea76bacce5d740ee4bc331899375dc2c5f2ab33933aaacbd9ba001b66") +} + +func (DestinationRewardManagerOwnershipTransferRequested) Topic() common.Hash { + return common.HexToHash("0xed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278") +} + +func (DestinationRewardManagerOwnershipTransferred) Topic() common.Hash { + return common.HexToHash("0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0") +} + +func (DestinationRewardManagerRewardRecipientsUpdated) Topic() common.Hash { + return common.HexToHash("0x8f668d6090683f98b3373a8b83d214da45737f7486cb7de554cc07b54e61cfe6") +} + +func (DestinationRewardManagerRewardsClaimed) Topic() common.Hash { + return common.HexToHash("0x989969655bc1d593922527fe85d71347bb8e12fa423cc71f362dd8ef7cb10ef2") +} + +func (_DestinationRewardManager *DestinationRewardManager) Address() common.Address { + return _DestinationRewardManager.address +} + +type DestinationRewardManagerInterface interface { + GetAvailableRewardPoolIds(opts *bind.CallOpts, recipient common.Address, startIndex *big.Int, endIndex *big.Int) ([][32]byte, error) + + ILinkAddress(opts *bind.CallOpts) (common.Address, error) + + Owner(opts *bind.CallOpts) (common.Address, error) + + SFeeManagerAddressList(opts *bind.CallOpts, arg0 common.Address) (common.Address, error) + + SRegisteredPoolIds(opts *bind.CallOpts, arg0 *big.Int) ([32]byte, error) + + SRewardRecipientWeights(opts *bind.CallOpts, arg0 [32]byte, arg1 common.Address) (*big.Int, error) + + SRewardRecipientWeightsSet(opts *bind.CallOpts, arg0 [32]byte) (bool, error) + + STotalRewardRecipientFees(opts *bind.CallOpts, arg0 [32]byte) (*big.Int, error) + + STotalRewardRecipientFeesLastClaimedAmounts(opts *bind.CallOpts, arg0 [32]byte, arg1 common.Address) (*big.Int, error) + + SupportsInterface(opts *bind.CallOpts, interfaceId [4]byte) (bool, error) + + TypeAndVersion(opts *bind.CallOpts) (string, error) + + AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) + + AddFeeManager(opts *bind.TransactOpts, newFeeManagerAddress common.Address) (*types.Transaction, error) + + ClaimRewards(opts *bind.TransactOpts, poolIds [][32]byte) (*types.Transaction, error) + + OnFeePaid(opts *bind.TransactOpts, payments []IDestinationRewardManagerFeePayment, payer common.Address) (*types.Transaction, error) + + PayRecipients(opts *bind.TransactOpts, poolId [32]byte, recipients []common.Address) (*types.Transaction, error) + + RemoveFeeManager(opts *bind.TransactOpts, feeManagerAddress common.Address) (*types.Transaction, error) + + SetRewardRecipients(opts *bind.TransactOpts, poolId [32]byte, rewardRecipientAndWeights []CommonAddressAndWeight) (*types.Transaction, error) + + TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) + + UpdateRewardRecipients(opts *bind.TransactOpts, poolId [32]byte, newRewardRecipients []CommonAddressAndWeight) (*types.Transaction, error) + + FilterFeeManagerUpdated(opts *bind.FilterOpts) (*DestinationRewardManagerFeeManagerUpdatedIterator, error) + + WatchFeeManagerUpdated(opts *bind.WatchOpts, sink chan<- *DestinationRewardManagerFeeManagerUpdated) (event.Subscription, error) + + ParseFeeManagerUpdated(log types.Log) (*DestinationRewardManagerFeeManagerUpdated, error) + + FilterFeePaid(opts *bind.FilterOpts) (*DestinationRewardManagerFeePaidIterator, error) + + WatchFeePaid(opts *bind.WatchOpts, sink chan<- *DestinationRewardManagerFeePaid) (event.Subscription, error) + + ParseFeePaid(log types.Log) (*DestinationRewardManagerFeePaid, error) + + FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationRewardManagerOwnershipTransferRequestedIterator, error) + + WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *DestinationRewardManagerOwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error) + + ParseOwnershipTransferRequested(log types.Log) (*DestinationRewardManagerOwnershipTransferRequested, error) + + FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationRewardManagerOwnershipTransferredIterator, error) + + WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *DestinationRewardManagerOwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error) + + ParseOwnershipTransferred(log types.Log) (*DestinationRewardManagerOwnershipTransferred, error) + + FilterRewardRecipientsUpdated(opts *bind.FilterOpts, poolId [][32]byte) (*DestinationRewardManagerRewardRecipientsUpdatedIterator, error) + + WatchRewardRecipientsUpdated(opts *bind.WatchOpts, sink chan<- *DestinationRewardManagerRewardRecipientsUpdated, poolId [][32]byte) (event.Subscription, error) + + ParseRewardRecipientsUpdated(log types.Log) (*DestinationRewardManagerRewardRecipientsUpdated, error) + + FilterRewardsClaimed(opts *bind.FilterOpts, poolId [][32]byte, recipient []common.Address) (*DestinationRewardManagerRewardsClaimedIterator, error) + + WatchRewardsClaimed(opts *bind.WatchOpts, sink chan<- *DestinationRewardManagerRewardsClaimed, poolId [][32]byte, recipient []common.Address) (event.Subscription, error) + + ParseRewardsClaimed(log types.Log) (*DestinationRewardManagerRewardsClaimed, error) + + ParseLog(log types.Log) (generated.AbigenLog, error) + + Address() common.Address +} diff --git a/core/gethwrappers/llo-feeds/generated/destination_verifier/destination_verifier.go b/core/gethwrappers/llo-feeds/generated/destination_verifier/destination_verifier.go new file mode 100644 index 0000000000..41ae715fe9 --- /dev/null +++ b/core/gethwrappers/llo-feeds/generated/destination_verifier/destination_verifier.go @@ -0,0 +1,1577 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package destination_verifier + +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 +) + +type CommonAddressAndWeight struct { + Addr common.Address + Weight uint64 +} + +var DestinationVerifierMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"verifierProxy\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"AccessForbidden\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"BadActivationTime\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"BadVerification\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes24\",\"name\":\"donConfigId\",\"type\":\"bytes24\"}],\"name\":\"DonConfigAlreadyExists\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"DonConfigDoesNotExist\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"numSigners\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxSigners\",\"type\":\"uint256\"}],\"name\":\"ExcessSigners\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FaultToleranceMustBePositive\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FeeManagerInvalid\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"numSigners\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minSigners\",\"type\":\"uint256\"}],\"name\":\"InsufficientSigners\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"rsLength\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"ssLength\",\"type\":\"uint256\"}],\"name\":\"MismatchedSignatures\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NoSigners\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NonUniqueSignatures\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroAddress\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"oldAccessController\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newAccessController\",\"type\":\"address\"}],\"name\":\"AccessControllerSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes24\",\"name\":\"donConfigId\",\"type\":\"bytes24\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isActive\",\"type\":\"bool\"}],\"name\":\"ConfigActivated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes24\",\"name\":\"donConfigId\",\"type\":\"bytes24\"}],\"name\":\"ConfigRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes24\",\"name\":\"donConfigId\",\"type\":\"bytes24\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"weight\",\"type\":\"uint64\"}],\"indexed\":false,\"internalType\":\"structCommon.AddressAndWeight[]\",\"name\":\"recipientAddressesAndWeights\",\"type\":\"tuple[]\"},{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"donConfigIndex\",\"type\":\"uint16\"}],\"name\":\"ConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"oldFeeManager\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newFeeManager\",\"type\":\"address\"}],\"name\":\"FeeManagerSet\",\"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\":true,\"internalType\":\"bytes32\",\"name\":\"feedId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"requester\",\"type\":\"address\"}],\"name\":\"ReportVerified\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"i_verifierProxy\",\"outputs\":[{\"internalType\":\"contractIDestinationVerifierProxy\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"removeLatestConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_accessController\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_feeManager\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"accessController\",\"type\":\"address\"}],\"name\":\"setAccessController\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"},{\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"weight\",\"type\":\"uint64\"}],\"internalType\":\"structCommon.AddressAndWeight[]\",\"name\":\"recipientAddressesAndWeights\",\"type\":\"tuple[]\"}],\"name\":\"setConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"donConfigIndex\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"isActive\",\"type\":\"bool\"}],\"name\":\"setConfigActive\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"},{\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"weight\",\"type\":\"uint64\"}],\"internalType\":\"structCommon.AddressAndWeight[]\",\"name\":\"recipientAddressesAndWeights\",\"type\":\"tuple[]\"},{\"internalType\":\"uint32\",\"name\":\"activationTime\",\"type\":\"uint32\"}],\"name\":\"setConfigWithActivationTime\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"feeManager\",\"type\":\"address\"}],\"name\":\"setFeeManager\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"signedReport\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"parameterPayload\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"verify\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes[]\",\"name\":\"signedReports\",\"type\":\"bytes[]\"},{\"internalType\":\"bytes\",\"name\":\"parameterPayload\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"verifyBulk\",\"outputs\":[{\"internalType\":\"bytes[]\",\"name\":\"\",\"type\":\"bytes[]\"}],\"stateMutability\":\"payable\",\"type\":\"function\"}]", + Bin: "0x60a06040523480156200001157600080fd5b50604051620033d8380380620033d88339810160408190526200003491620001a6565b33806000816200008b5760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620000be57620000be81620000fb565b5050506001600160a01b038116620000e95760405163d92e233d60e01b815260040160405180910390fd5b6001600160a01b0316608052620001d8565b336001600160a01b03821603620001555760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640162000082565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b600060208284031215620001b957600080fd5b81516001600160a01b0381168114620001d157600080fd5b9392505050565b6080516131d662000202600039600081816102d7015281816104160152610ce201526131d66000f3fe6080604052600436106100f35760003560e01c80638da5cb5b1161008a578063d7c72e4e11610059578063d7c72e4e146102f9578063f08391d814610319578063f2fde38b14610339578063f9c7bf771461035957600080fd5b80638da5cb5b1461025857806394ba284614610283578063af4fed24146102b0578063b97455c7146102c557600080fd5b8063453ec61b116100c6578063453ec61b146101e1578063472d35b9146102035780635ad72fae1461022357806379ba50971461024357600080fd5b806301ffc9a7146100f8578063181f5a771461012d578063294d2bb11461017c57806338416b5b1461018f575b600080fd5b34801561010457600080fd5b50610118610113366004612246565b610379565b60405190151581526020015b60405180910390f35b34801561013957600080fd5b5060408051808201909152601981527f44657374696e6174696f6e566572696669657220302e342e300000000000000060208201525b60405161012491906122f3565b61016f61018a366004612378565b610412565b34801561019b57600080fd5b506004546101bc9073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610124565b3480156101ed57600080fd5b506102016101fc366004612607565b61065f565b005b34801561020f57600080fd5b5061020161021e36600461267b565b610767565b34801561022f57600080fd5b5061020161023e3660046126a4565b6108da565b34801561024f57600080fd5b506102016109f0565b34801561026457600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff166101bc565b34801561028f57600080fd5b506005546101bc9073ffffffffffffffffffffffffffffffffffffffff1681565b3480156102bc57600080fd5b50610201610aed565b3480156102d157600080fd5b506101bc7f000000000000000000000000000000000000000000000000000000000000000081565b61030c6103073660046126d4565b610cde565b6040516101249190612757565b34801561032557600080fd5b5061020161033436600461267b565b610fd0565b34801561034557600080fd5b5061020161035436600461267b565b611057565b34801561036557600080fd5b506102016103743660046127e9565b61106b565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167ffecf1f8700000000000000000000000000000000000000000000000000000000148061040c57507fffffffff0000000000000000000000000000000000000000000000000000000082167f527146e200000000000000000000000000000000000000000000000000000000145b92915050565b60607f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163314610483576040517fef67f5d800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600554829073ffffffffffffffffffffffffffffffffffffffff16801580159061054257506040517f6b14daf800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff821690636b14daf8906104ff90859060009036906004016128b9565b602060405180830381865afa15801561051c573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061054091906128f2565b155b15610579576040517fef67f5d800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000806105878a8a88611129565b600454919350915073ffffffffffffffffffffffffffffffffffffffff168015610650578073ffffffffffffffffffffffffffffffffffffffff166386968cfd34848e8e8e8e8e6040518863ffffffff1660e01b81526004016105ef9695949392919061290f565b6000604051808303818588803b15801561060857600080fd5b505af19350505050801561061a575060015b610650576040517f4df18f0700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50909998505050505050505050565b82518260ff168060000361069f576040517f0743bae600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601f8211156106e9576040517f61750f4000000000000000000000000000000000000000000000000000000000815260048101839052601f60248201526044015b60405180910390fd5b6106f4816003612995565b821161074c5781610706826003612995565b6107119060016129ac565b6040517f9dd9e6d8000000000000000000000000000000000000000000000000000000008152600481019290925260248201526044016106e0565b61075461162e565b610760858585426116b1565b5050505050565b61076f61162e565b6040517f01ffc9a70000000000000000000000000000000000000000000000000000000081527f465b009600000000000000000000000000000000000000000000000000000000600482015273ffffffffffffffffffffffffffffffffffffffff8216906301ffc9a790602401602060405180830381865afa1580156107f9573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061081d91906128f2565b610853576040517f8238941900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6004805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff000000000000000000000000000000000000000083168117909355604080519190921680825260208201939093527f04628abcaa6b1674651352125cb94b65b289145bc2bc4d67720bb7d966372f0391015b60405180910390a15050565b6108e261162e565b600354821061091d576040517f5a0d6fe200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060038381548110610932576109326129bf565b600091825260209182902001805484151579010000000000000000000000000000000000000000000000000081027fffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffff909216919091178083556040805191811b7fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000168252938101919091529092507f90186a1e77b498ec417ea88bd026cae00d7043c357cc45221777623bda582dd4910160405180910390a1505050565b60015473ffffffffffffffffffffffffffffffffffffffff163314610a71576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064016106e0565b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b610af561162e565b600354600003610b31576040517f5a0d6fe200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6003805460009190610b45906001906129ee565b81548110610b5557610b556129bf565b600091825260209182902060408051608081018252929091015480821b7fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001683527801000000000000000000000000000000000000000000000000810460ff9081169484019490945279010000000000000000000000000000000000000000000000000081049093161515908201527a01000000000000000000000000000000000000000000000000000090910463ffffffff166060820152600380549192509080610c2357610c23612a01565b60008281526020902081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90810180547fffff00000000000000000000000000000000000000000000000000000000000016905501905580516040517f970fd8f3ebdd9a271080aacf9807a5c709be0b448e4047a6fc212b8cc165368d91610cd3917fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000091909116815260200190565b60405180910390a150565b60607f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163314610d4f576040517fef67f5d800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600554829073ffffffffffffffffffffffffffffffffffffffff168015801590610e0e57506040517f6b14daf800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff821690636b14daf890610dcb90859060009036906004016128b9565b602060405180830381865afa158015610de8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e0c91906128f2565b155b15610e45576040517fef67f5d800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008767ffffffffffffffff811115610e6057610e606123f9565b604051908082528060200260200182016040528015610e9357816020015b6060815260200190600190039081610e7e5790505b50905060008867ffffffffffffffff811115610eb157610eb16123f9565b604051908082528060200260200182016040528015610eda578160200160208202803683370190505b50905060005b89811015610f6c57600080610f188d8d85818110610f0057610f006129bf565b9050602002810190610f129190612a30565b8b611129565b9150915081858481518110610f2f57610f2f6129bf565b602002602001018190525080848481518110610f4d57610f4d6129bf565b602002602001018181525050505080610f6590612a95565b9050610ee0565b5060045473ffffffffffffffffffffffffffffffffffffffff168015610650578073ffffffffffffffffffffffffffffffffffffffff16633690750934848e8e8e8e8e6040518863ffffffff1660e01b81526004016105ef96959493929190612acd565b610fd861162e565b6005805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff000000000000000000000000000000000000000083168117909355604080519190921680825260208201939093527f953e92b1a6442e9c3242531154a3f6f6eb00b4e9c719ba8118fa6235e4ce89b691016108ce565b61105f61162e565b61106881611d0f565b50565b83518360ff16806000036110ab576040517f0743bae600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601f8211156110f0576040517f61750f4000000000000000000000000000000000000000000000000000000000815260048101839052601f60248201526044016106e0565b6110fb816003612995565b821161110d5781610706826003612995565b61111561162e565b611121868686866116b1565b505050505050565b60606000808080808061113e898b018b612cff565b94509450945094509450815183511461119057825182516040517ff0d31408000000000000000000000000000000000000000000000000000000008152600481019290925260248201526044016106e0565b82516000036111cb576040517fc7af40f000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008480519060200120866040516020016111e7929190612dda565b6040516020818303038152906040528051906020012090506000845167ffffffffffffffff81111561121b5761121b6123f9565b604051908082528060200260200182016040528015611244578160200160208202803683370190505b50905060005b855181101561135257600183858360208110611268576112686129bf565b61127591901a601b612e16565b888481518110611287576112876129bf565b60200260200101518885815181106112a1576112a16129bf565b6020026020010151604051600081526020016040526040516112df949392919093845260ff9290921660208401526040830152606082015260800190565b6020604051602081039080840390855afa158015611301573d6000803e3d6000fd5b5050506020604051035182828151811061131d5761131d6129bf565b73ffffffffffffffffffffffffffffffffffffffff9092166020928302919091019091015261134b81612a95565b905061124a565b5061135c81611e04565b15611393576040517f4df18f0700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061139e87611eb3565b905060006113ab82611ed9565b80519091507fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000016611408576040517f4df18f0700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060400151611443576040517f4df18f0700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b806020015160ff16835111611484576040517f4df18f0700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000805b84518110156115a2578481815181106114a3576114a36129bf565b6020026020010151836000015160405160200161151392919060609290921b7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000001682527fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000166014820152602c0190565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291815281516020928301206000818152600290935291205490925060ff16611592576040517f4df18f0700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61159b81612a95565b9050611488565b506115ac89612e2f565b60405173ffffffffffffffffffffffffffffffffffffffff8f1681527f58ca9502e98a536e06e72d680fcc251e5d10b72291a281665a2c2dc0ac30fcc59060200160405180910390a25051969d7fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000009097169c50959a5050505050505050505050565b60005473ffffffffffffffffffffffffffffffffffffffff1633146116af576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e65720000000000000000000060448201526064016106e0565b565b6116ba84611e04565b156116f1576040517ff67bc7c400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b428163ffffffff161115611731576040517f0114c7e600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61174a8460006001875161174591906129ee565b61205e565b6000848460405160200161175f929190612e74565b60405160208183030381529060405280519060200120905060005b855181101561190657600073ffffffffffffffffffffffffffffffffffffffff168682815181106117ad576117ad6129bf565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1603611802576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60016002600088848151811061181a5761181a6129bf565b60200260200101518560405160200161188692919060609290921b7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000001682527fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000166014820152602c0190565b604080518083037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001815291815281516020928301208352908201929092520160002080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169115159190911790556118ff81612a95565b905061177a565b50600354801580159061196b575063ffffffff831660036119286001846129ee565b81548110611938576119386129bf565b6000918252602090912001547a010000000000000000000000000000000000000000000000000000900463ffffffff1610155b156119a2576040517f0114c7e600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600081118015611a2057507fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000821660036119dd6001846129ee565b815481106119ed576119ed6129bf565b60009182526020909120015460401b7fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000016145b15611a7b576040517fc0178c860000000000000000000000000000000000000000000000000000000081527fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000831660048201526024016106e0565b835115611b5b5760045473ffffffffffffffffffffffffffffffffffffffff16611ad1576040517f8238941900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600480546040517ff65df96200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091169163f65df96291611b28918691899101612f52565b600060405180830381600087803b158015611b4257600080fd5b505af1158015611b56573d6000803e3d6000fd5b505050505b604080516080810182527fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000841680825260ff80891660208401908152600184860181815263ffffffff808b166060880190815260038054948501815560005296517fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b90930180549451925197519091167a010000000000000000000000000000000000000000000000000000027fffff00000000ffffffffffffffffffffffffffffffffffffffffffffffffffff97151579010000000000000000000000000000000000000000000000000002979097167fffff0000000000ffffffffffffffffffffffffffffffffffffffffffffffffff929095167801000000000000000000000000000000000000000000000000027fffffffffffffff0000000000000000000000000000000000000000000000000090941692881c929092179290921791909116919091179290921790915590517fa7d03f81dd1c1d8a55355fd71e1221d5959b7d9fb171fbb6389f207f63598fa590611cff908990899089908790612f95565b60405180910390a2505050505050565b3373ffffffffffffffffffffffffffffffffffffffff821603611d8e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c6600000000000000000060448201526064016106e0565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6000805b8251811015611eaa576000611e1e8260016129ac565b90505b8351811015611ea157838181518110611e3c57611e3c6129bf565b602002602001015173ffffffffffffffffffffffffffffffffffffffff16848381518110611e6c57611e6c6129bf565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1603611e99575060019392505050565b600101611e21565b50600101611e08565b50600092915050565b60008082806020019051810190611eca9190613018565b63ffffffff1695945050505050565b6040805160808101825260008082526020820181905291810182905260608101919091526040805160808101825260008082526020820181905291810182905260608101919091526003545b801561205757611f348161305b565b90508360038281548110611f4a57611f4a6129bf565b6000918252602090912001547a010000000000000000000000000000000000000000000000000000900463ffffffff16116120525760038181548110611f9257611f926129bf565b600091825260209182902060408051608081018252929091015480821b7fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001683527801000000000000000000000000000000000000000000000000810460ff9081169484019490945279010000000000000000000000000000000000000000000000000081049093161515908201527a01000000000000000000000000000000000000000000000000000090910463ffffffff1660608201529150612057565b611f25565b5092915050565b818180820361206e575050505050565b600085600261207d8787613090565b61208791906130b0565b612091908761313f565b815181106120a1576120a16129bf565b602002602001015190505b818313612220575b8073ffffffffffffffffffffffffffffffffffffffff168684815181106120dd576120dd6129bf565b602002602001015173ffffffffffffffffffffffffffffffffffffffff161015612113578261210b81613167565b9350506120b4565b858281518110612125576121256129bf565b602002602001015173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161015612172578161216a81613198565b925050612113565b81831361221b5785828151811061218b5761218b6129bf565b60200260200101518684815181106121a5576121a56129bf565b60200260200101518785815181106121bf576121bf6129bf565b602002602001018885815181106121d8576121d86129bf565b73ffffffffffffffffffffffffffffffffffffffff9384166020918202929092010152911690528261220981613167565b935050818061221790613198565b9250505b6120ac565b818512156122335761223386868461205e565b838312156111215761112186848661205e565b60006020828403121561225857600080fd5b81357fffffffff000000000000000000000000000000000000000000000000000000008116811461228857600080fd5b9392505050565b6000815180845260005b818110156122b557602081850181015186830182015201612299565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b602081526000612288602083018461228f565b60008083601f84011261231857600080fd5b50813567ffffffffffffffff81111561233057600080fd5b60208301915083602082850101111561234857600080fd5b9250929050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461237357600080fd5b919050565b60008060008060006060868803121561239057600080fd5b853567ffffffffffffffff808211156123a857600080fd5b6123b489838a01612306565b909750955060208801359150808211156123cd57600080fd5b506123da88828901612306565b90945092506123ed90506040870161234f565b90509295509295909350565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040805190810167ffffffffffffffff8111828210171561244b5761244b6123f9565b60405290565b6040516060810167ffffffffffffffff8111828210171561244b5761244b6123f9565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156124bb576124bb6123f9565b604052919050565b600067ffffffffffffffff8211156124dd576124dd6123f9565b5060051b60200190565b600082601f8301126124f857600080fd5b8135602061250d612508836124c3565b612474565b82815260059290921b8401810191818101908684111561252c57600080fd5b8286015b8481101561254e576125418161234f565b8352918301918301612530565b509695505050505050565b803560ff8116811461237357600080fd5b600082601f83011261257b57600080fd5b8135602061258b612508836124c3565b82815260069290921b840181019181810190868411156125aa57600080fd5b8286015b8481101561254e57604081890312156125c75760008081fd5b6125cf612428565b6125d88261234f565b81528482013567ffffffffffffffff811681146125f55760008081fd5b818601528352918301916040016125ae565b60008060006060848603121561261c57600080fd5b833567ffffffffffffffff8082111561263457600080fd5b612640878388016124e7565b945061264e60208701612559565b9350604086013591508082111561266457600080fd5b506126718682870161256a565b9150509250925092565b60006020828403121561268d57600080fd5b6122888261234f565b801515811461106857600080fd5b600080604083850312156126b757600080fd5b8235915060208301356126c981612696565b809150509250929050565b6000806000806000606086880312156126ec57600080fd5b853567ffffffffffffffff8082111561270457600080fd5b818801915088601f83011261271857600080fd5b81358181111561272757600080fd5b8960208260051b850101111561273c57600080fd5b6020928301975095509087013590808211156123cd57600080fd5b6000602080830181845280855180835260408601915060408160051b870101925083870160005b828110156127ca577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc08886030184526127b885835161228f565b9450928501929085019060010161277e565b5092979650505050505050565b63ffffffff8116811461106857600080fd5b600080600080608085870312156127ff57600080fd5b843567ffffffffffffffff8082111561281757600080fd5b612823888389016124e7565b955061283160208801612559565b9450604087013591508082111561284757600080fd5b506128548782880161256a565b9250506060850135612865816127d7565b939692955090935050565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b73ffffffffffffffffffffffffffffffffffffffff841681526040602082015260006128e9604083018486612870565b95945050505050565b60006020828403121561290457600080fd5b815161228881612696565b868152608060208201526000612929608083018789612870565b828103604084015261293c818688612870565b91505073ffffffffffffffffffffffffffffffffffffffff83166060830152979650505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b808202811582820484141761040c5761040c612966565b8082018082111561040c5761040c612966565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b8181038181111561040c5761040c612966565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112612a6557600080fd5b83018035915067ffffffffffffffff821115612a8057600080fd5b60200191503681900382131561234857600080fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203612ac657612ac6612966565b5060010190565b6080808252875190820181905260009060209060a0840190828b01845b82811015612b0657815184529284019290840190600101612aea565b50505083810382850152878152818101600589901b820183018a60005b8b811015612bce577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe085840301845281357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18e3603018112612b8457600080fd5b8d01868101903567ffffffffffffffff811115612ba057600080fd5b803603821315612baf57600080fd5b612bba858284612870565b958801959450505090850190600101612b23565b50508581036040870152612be381898b612870565b945050505050612c0b606083018473ffffffffffffffffffffffffffffffffffffffff169052565b979650505050505050565b600082601f830112612c2757600080fd5b813567ffffffffffffffff811115612c4157612c416123f9565b612c7260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601612474565b818152846020838601011115612c8757600080fd5b816020850160208301376000918101602001919091529392505050565b600082601f830112612cb557600080fd5b81356020612cc5612508836124c3565b82815260059290921b84018101918181019086841115612ce457600080fd5b8286015b8481101561254e5780358352918301918301612ce8565b600080600080600060e08688031215612d1757600080fd5b86601f870112612d2657600080fd5b612d2e612451565b806060880189811115612d4057600080fd5b885b81811015612d5a578035845260209384019301612d42565b5090965035905067ffffffffffffffff80821115612d7757600080fd5b612d8389838a01612c16565b95506080880135915080821115612d9957600080fd5b612da589838a01612ca4565b945060a0880135915080821115612dbb57600080fd5b50612dc888828901612ca4565b9598949750929560c001359392505050565b828152600060208083018460005b6003811015612e0557815183529183019190830190600101612de8565b505050506080820190509392505050565b60ff818116838216019081111561040c5761040c612966565b80516020808301519190811015612e6e577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8160200360031b1b821691505b50919050565b825160009082906020808701845b83811015612eb457815173ffffffffffffffffffffffffffffffffffffffff1685529382019390820190600101612e82565b5050505060f89390931b7fff000000000000000000000000000000000000000000000000000000000000001683525050600101919050565b600081518084526020808501945080840160005b83811015612f47578151805173ffffffffffffffffffffffffffffffffffffffff16885283015167ffffffffffffffff168388015260409096019590820190600101612f00565b509495945050505050565b7fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000083168152604060208201526000612f8d6040830184612eec565b949350505050565b6080808252855190820181905260009060209060a0840190828901845b82811015612fe457815173ffffffffffffffffffffffffffffffffffffffff1684529284019290840190600101612fb2565b50505060ff87168285015283810360408501526130018187612eec565b9250505061ffff8316606083015295945050505050565b60008060006060848603121561302d57600080fd5b83519250602084015161303f816127d7565b6040850151909250613050816127d7565b809150509250925092565b60008161306a5761306a612966565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190565b818103600083128015838313168383128216171561205757612057612966565b6000826130e6577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83147f80000000000000000000000000000000000000000000000000000000000000008314161561313a5761313a612966565b500590565b808201828112600083128015821682158216171561315f5761315f612966565b505092915050565b60007f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203612ac657612ac6612966565b60007f8000000000000000000000000000000000000000000000000000000000000000820361306a5761306a61296656fea164736f6c6343000813000a", +} + +var DestinationVerifierABI = DestinationVerifierMetaData.ABI + +var DestinationVerifierBin = DestinationVerifierMetaData.Bin + +func DeployDestinationVerifier(auth *bind.TransactOpts, backend bind.ContractBackend, verifierProxy common.Address) (common.Address, *types.Transaction, *DestinationVerifier, error) { + parsed, err := DestinationVerifierMetaData.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(DestinationVerifierBin), backend, verifierProxy) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &DestinationVerifier{address: address, abi: *parsed, DestinationVerifierCaller: DestinationVerifierCaller{contract: contract}, DestinationVerifierTransactor: DestinationVerifierTransactor{contract: contract}, DestinationVerifierFilterer: DestinationVerifierFilterer{contract: contract}}, nil +} + +type DestinationVerifier struct { + address common.Address + abi abi.ABI + DestinationVerifierCaller + DestinationVerifierTransactor + DestinationVerifierFilterer +} + +type DestinationVerifierCaller struct { + contract *bind.BoundContract +} + +type DestinationVerifierTransactor struct { + contract *bind.BoundContract +} + +type DestinationVerifierFilterer struct { + contract *bind.BoundContract +} + +type DestinationVerifierSession struct { + Contract *DestinationVerifier + CallOpts bind.CallOpts + TransactOpts bind.TransactOpts +} + +type DestinationVerifierCallerSession struct { + Contract *DestinationVerifierCaller + CallOpts bind.CallOpts +} + +type DestinationVerifierTransactorSession struct { + Contract *DestinationVerifierTransactor + TransactOpts bind.TransactOpts +} + +type DestinationVerifierRaw struct { + Contract *DestinationVerifier +} + +type DestinationVerifierCallerRaw struct { + Contract *DestinationVerifierCaller +} + +type DestinationVerifierTransactorRaw struct { + Contract *DestinationVerifierTransactor +} + +func NewDestinationVerifier(address common.Address, backend bind.ContractBackend) (*DestinationVerifier, error) { + abi, err := abi.JSON(strings.NewReader(DestinationVerifierABI)) + if err != nil { + return nil, err + } + contract, err := bindDestinationVerifier(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &DestinationVerifier{address: address, abi: abi, DestinationVerifierCaller: DestinationVerifierCaller{contract: contract}, DestinationVerifierTransactor: DestinationVerifierTransactor{contract: contract}, DestinationVerifierFilterer: DestinationVerifierFilterer{contract: contract}}, nil +} + +func NewDestinationVerifierCaller(address common.Address, caller bind.ContractCaller) (*DestinationVerifierCaller, error) { + contract, err := bindDestinationVerifier(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &DestinationVerifierCaller{contract: contract}, nil +} + +func NewDestinationVerifierTransactor(address common.Address, transactor bind.ContractTransactor) (*DestinationVerifierTransactor, error) { + contract, err := bindDestinationVerifier(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &DestinationVerifierTransactor{contract: contract}, nil +} + +func NewDestinationVerifierFilterer(address common.Address, filterer bind.ContractFilterer) (*DestinationVerifierFilterer, error) { + contract, err := bindDestinationVerifier(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &DestinationVerifierFilterer{contract: contract}, nil +} + +func bindDestinationVerifier(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := DestinationVerifierMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +func (_DestinationVerifier *DestinationVerifierRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _DestinationVerifier.Contract.DestinationVerifierCaller.contract.Call(opts, result, method, params...) +} + +func (_DestinationVerifier *DestinationVerifierRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _DestinationVerifier.Contract.DestinationVerifierTransactor.contract.Transfer(opts) +} + +func (_DestinationVerifier *DestinationVerifierRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _DestinationVerifier.Contract.DestinationVerifierTransactor.contract.Transact(opts, method, params...) +} + +func (_DestinationVerifier *DestinationVerifierCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _DestinationVerifier.Contract.contract.Call(opts, result, method, params...) +} + +func (_DestinationVerifier *DestinationVerifierTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _DestinationVerifier.Contract.contract.Transfer(opts) +} + +func (_DestinationVerifier *DestinationVerifierTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _DestinationVerifier.Contract.contract.Transact(opts, method, params...) +} + +func (_DestinationVerifier *DestinationVerifierCaller) IVerifierProxy(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _DestinationVerifier.contract.Call(opts, &out, "i_verifierProxy") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +func (_DestinationVerifier *DestinationVerifierSession) IVerifierProxy() (common.Address, error) { + return _DestinationVerifier.Contract.IVerifierProxy(&_DestinationVerifier.CallOpts) +} + +func (_DestinationVerifier *DestinationVerifierCallerSession) IVerifierProxy() (common.Address, error) { + return _DestinationVerifier.Contract.IVerifierProxy(&_DestinationVerifier.CallOpts) +} + +func (_DestinationVerifier *DestinationVerifierCaller) Owner(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _DestinationVerifier.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 (_DestinationVerifier *DestinationVerifierSession) Owner() (common.Address, error) { + return _DestinationVerifier.Contract.Owner(&_DestinationVerifier.CallOpts) +} + +func (_DestinationVerifier *DestinationVerifierCallerSession) Owner() (common.Address, error) { + return _DestinationVerifier.Contract.Owner(&_DestinationVerifier.CallOpts) +} + +func (_DestinationVerifier *DestinationVerifierCaller) SAccessController(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _DestinationVerifier.contract.Call(opts, &out, "s_accessController") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +func (_DestinationVerifier *DestinationVerifierSession) SAccessController() (common.Address, error) { + return _DestinationVerifier.Contract.SAccessController(&_DestinationVerifier.CallOpts) +} + +func (_DestinationVerifier *DestinationVerifierCallerSession) SAccessController() (common.Address, error) { + return _DestinationVerifier.Contract.SAccessController(&_DestinationVerifier.CallOpts) +} + +func (_DestinationVerifier *DestinationVerifierCaller) SFeeManager(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _DestinationVerifier.contract.Call(opts, &out, "s_feeManager") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +func (_DestinationVerifier *DestinationVerifierSession) SFeeManager() (common.Address, error) { + return _DestinationVerifier.Contract.SFeeManager(&_DestinationVerifier.CallOpts) +} + +func (_DestinationVerifier *DestinationVerifierCallerSession) SFeeManager() (common.Address, error) { + return _DestinationVerifier.Contract.SFeeManager(&_DestinationVerifier.CallOpts) +} + +func (_DestinationVerifier *DestinationVerifierCaller) SupportsInterface(opts *bind.CallOpts, interfaceId [4]byte) (bool, error) { + var out []interface{} + err := _DestinationVerifier.contract.Call(opts, &out, "supportsInterface", interfaceId) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +func (_DestinationVerifier *DestinationVerifierSession) SupportsInterface(interfaceId [4]byte) (bool, error) { + return _DestinationVerifier.Contract.SupportsInterface(&_DestinationVerifier.CallOpts, interfaceId) +} + +func (_DestinationVerifier *DestinationVerifierCallerSession) SupportsInterface(interfaceId [4]byte) (bool, error) { + return _DestinationVerifier.Contract.SupportsInterface(&_DestinationVerifier.CallOpts, interfaceId) +} + +func (_DestinationVerifier *DestinationVerifierCaller) TypeAndVersion(opts *bind.CallOpts) (string, error) { + var out []interface{} + err := _DestinationVerifier.contract.Call(opts, &out, "typeAndVersion") + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + +} + +func (_DestinationVerifier *DestinationVerifierSession) TypeAndVersion() (string, error) { + return _DestinationVerifier.Contract.TypeAndVersion(&_DestinationVerifier.CallOpts) +} + +func (_DestinationVerifier *DestinationVerifierCallerSession) TypeAndVersion() (string, error) { + return _DestinationVerifier.Contract.TypeAndVersion(&_DestinationVerifier.CallOpts) +} + +func (_DestinationVerifier *DestinationVerifierTransactor) AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) { + return _DestinationVerifier.contract.Transact(opts, "acceptOwnership") +} + +func (_DestinationVerifier *DestinationVerifierSession) AcceptOwnership() (*types.Transaction, error) { + return _DestinationVerifier.Contract.AcceptOwnership(&_DestinationVerifier.TransactOpts) +} + +func (_DestinationVerifier *DestinationVerifierTransactorSession) AcceptOwnership() (*types.Transaction, error) { + return _DestinationVerifier.Contract.AcceptOwnership(&_DestinationVerifier.TransactOpts) +} + +func (_DestinationVerifier *DestinationVerifierTransactor) RemoveLatestConfig(opts *bind.TransactOpts) (*types.Transaction, error) { + return _DestinationVerifier.contract.Transact(opts, "removeLatestConfig") +} + +func (_DestinationVerifier *DestinationVerifierSession) RemoveLatestConfig() (*types.Transaction, error) { + return _DestinationVerifier.Contract.RemoveLatestConfig(&_DestinationVerifier.TransactOpts) +} + +func (_DestinationVerifier *DestinationVerifierTransactorSession) RemoveLatestConfig() (*types.Transaction, error) { + return _DestinationVerifier.Contract.RemoveLatestConfig(&_DestinationVerifier.TransactOpts) +} + +func (_DestinationVerifier *DestinationVerifierTransactor) SetAccessController(opts *bind.TransactOpts, accessController common.Address) (*types.Transaction, error) { + return _DestinationVerifier.contract.Transact(opts, "setAccessController", accessController) +} + +func (_DestinationVerifier *DestinationVerifierSession) SetAccessController(accessController common.Address) (*types.Transaction, error) { + return _DestinationVerifier.Contract.SetAccessController(&_DestinationVerifier.TransactOpts, accessController) +} + +func (_DestinationVerifier *DestinationVerifierTransactorSession) SetAccessController(accessController common.Address) (*types.Transaction, error) { + return _DestinationVerifier.Contract.SetAccessController(&_DestinationVerifier.TransactOpts, accessController) +} + +func (_DestinationVerifier *DestinationVerifierTransactor) SetConfig(opts *bind.TransactOpts, signers []common.Address, f uint8, recipientAddressesAndWeights []CommonAddressAndWeight) (*types.Transaction, error) { + return _DestinationVerifier.contract.Transact(opts, "setConfig", signers, f, recipientAddressesAndWeights) +} + +func (_DestinationVerifier *DestinationVerifierSession) SetConfig(signers []common.Address, f uint8, recipientAddressesAndWeights []CommonAddressAndWeight) (*types.Transaction, error) { + return _DestinationVerifier.Contract.SetConfig(&_DestinationVerifier.TransactOpts, signers, f, recipientAddressesAndWeights) +} + +func (_DestinationVerifier *DestinationVerifierTransactorSession) SetConfig(signers []common.Address, f uint8, recipientAddressesAndWeights []CommonAddressAndWeight) (*types.Transaction, error) { + return _DestinationVerifier.Contract.SetConfig(&_DestinationVerifier.TransactOpts, signers, f, recipientAddressesAndWeights) +} + +func (_DestinationVerifier *DestinationVerifierTransactor) SetConfigActive(opts *bind.TransactOpts, donConfigIndex *big.Int, isActive bool) (*types.Transaction, error) { + return _DestinationVerifier.contract.Transact(opts, "setConfigActive", donConfigIndex, isActive) +} + +func (_DestinationVerifier *DestinationVerifierSession) SetConfigActive(donConfigIndex *big.Int, isActive bool) (*types.Transaction, error) { + return _DestinationVerifier.Contract.SetConfigActive(&_DestinationVerifier.TransactOpts, donConfigIndex, isActive) +} + +func (_DestinationVerifier *DestinationVerifierTransactorSession) SetConfigActive(donConfigIndex *big.Int, isActive bool) (*types.Transaction, error) { + return _DestinationVerifier.Contract.SetConfigActive(&_DestinationVerifier.TransactOpts, donConfigIndex, isActive) +} + +func (_DestinationVerifier *DestinationVerifierTransactor) SetConfigWithActivationTime(opts *bind.TransactOpts, signers []common.Address, f uint8, recipientAddressesAndWeights []CommonAddressAndWeight, activationTime uint32) (*types.Transaction, error) { + return _DestinationVerifier.contract.Transact(opts, "setConfigWithActivationTime", signers, f, recipientAddressesAndWeights, activationTime) +} + +func (_DestinationVerifier *DestinationVerifierSession) SetConfigWithActivationTime(signers []common.Address, f uint8, recipientAddressesAndWeights []CommonAddressAndWeight, activationTime uint32) (*types.Transaction, error) { + return _DestinationVerifier.Contract.SetConfigWithActivationTime(&_DestinationVerifier.TransactOpts, signers, f, recipientAddressesAndWeights, activationTime) +} + +func (_DestinationVerifier *DestinationVerifierTransactorSession) SetConfigWithActivationTime(signers []common.Address, f uint8, recipientAddressesAndWeights []CommonAddressAndWeight, activationTime uint32) (*types.Transaction, error) { + return _DestinationVerifier.Contract.SetConfigWithActivationTime(&_DestinationVerifier.TransactOpts, signers, f, recipientAddressesAndWeights, activationTime) +} + +func (_DestinationVerifier *DestinationVerifierTransactor) SetFeeManager(opts *bind.TransactOpts, feeManager common.Address) (*types.Transaction, error) { + return _DestinationVerifier.contract.Transact(opts, "setFeeManager", feeManager) +} + +func (_DestinationVerifier *DestinationVerifierSession) SetFeeManager(feeManager common.Address) (*types.Transaction, error) { + return _DestinationVerifier.Contract.SetFeeManager(&_DestinationVerifier.TransactOpts, feeManager) +} + +func (_DestinationVerifier *DestinationVerifierTransactorSession) SetFeeManager(feeManager common.Address) (*types.Transaction, error) { + return _DestinationVerifier.Contract.SetFeeManager(&_DestinationVerifier.TransactOpts, feeManager) +} + +func (_DestinationVerifier *DestinationVerifierTransactor) TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) { + return _DestinationVerifier.contract.Transact(opts, "transferOwnership", to) +} + +func (_DestinationVerifier *DestinationVerifierSession) TransferOwnership(to common.Address) (*types.Transaction, error) { + return _DestinationVerifier.Contract.TransferOwnership(&_DestinationVerifier.TransactOpts, to) +} + +func (_DestinationVerifier *DestinationVerifierTransactorSession) TransferOwnership(to common.Address) (*types.Transaction, error) { + return _DestinationVerifier.Contract.TransferOwnership(&_DestinationVerifier.TransactOpts, to) +} + +func (_DestinationVerifier *DestinationVerifierTransactor) Verify(opts *bind.TransactOpts, signedReport []byte, parameterPayload []byte, sender common.Address) (*types.Transaction, error) { + return _DestinationVerifier.contract.Transact(opts, "verify", signedReport, parameterPayload, sender) +} + +func (_DestinationVerifier *DestinationVerifierSession) Verify(signedReport []byte, parameterPayload []byte, sender common.Address) (*types.Transaction, error) { + return _DestinationVerifier.Contract.Verify(&_DestinationVerifier.TransactOpts, signedReport, parameterPayload, sender) +} + +func (_DestinationVerifier *DestinationVerifierTransactorSession) Verify(signedReport []byte, parameterPayload []byte, sender common.Address) (*types.Transaction, error) { + return _DestinationVerifier.Contract.Verify(&_DestinationVerifier.TransactOpts, signedReport, parameterPayload, sender) +} + +func (_DestinationVerifier *DestinationVerifierTransactor) VerifyBulk(opts *bind.TransactOpts, signedReports [][]byte, parameterPayload []byte, sender common.Address) (*types.Transaction, error) { + return _DestinationVerifier.contract.Transact(opts, "verifyBulk", signedReports, parameterPayload, sender) +} + +func (_DestinationVerifier *DestinationVerifierSession) VerifyBulk(signedReports [][]byte, parameterPayload []byte, sender common.Address) (*types.Transaction, error) { + return _DestinationVerifier.Contract.VerifyBulk(&_DestinationVerifier.TransactOpts, signedReports, parameterPayload, sender) +} + +func (_DestinationVerifier *DestinationVerifierTransactorSession) VerifyBulk(signedReports [][]byte, parameterPayload []byte, sender common.Address) (*types.Transaction, error) { + return _DestinationVerifier.Contract.VerifyBulk(&_DestinationVerifier.TransactOpts, signedReports, parameterPayload, sender) +} + +type DestinationVerifierAccessControllerSetIterator struct { + Event *DestinationVerifierAccessControllerSet + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *DestinationVerifierAccessControllerSetIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(DestinationVerifierAccessControllerSet) + 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(DestinationVerifierAccessControllerSet) + 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 *DestinationVerifierAccessControllerSetIterator) Error() error { + return it.fail +} + +func (it *DestinationVerifierAccessControllerSetIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type DestinationVerifierAccessControllerSet struct { + OldAccessController common.Address + NewAccessController common.Address + Raw types.Log +} + +func (_DestinationVerifier *DestinationVerifierFilterer) FilterAccessControllerSet(opts *bind.FilterOpts) (*DestinationVerifierAccessControllerSetIterator, error) { + + logs, sub, err := _DestinationVerifier.contract.FilterLogs(opts, "AccessControllerSet") + if err != nil { + return nil, err + } + return &DestinationVerifierAccessControllerSetIterator{contract: _DestinationVerifier.contract, event: "AccessControllerSet", logs: logs, sub: sub}, nil +} + +func (_DestinationVerifier *DestinationVerifierFilterer) WatchAccessControllerSet(opts *bind.WatchOpts, sink chan<- *DestinationVerifierAccessControllerSet) (event.Subscription, error) { + + logs, sub, err := _DestinationVerifier.contract.WatchLogs(opts, "AccessControllerSet") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(DestinationVerifierAccessControllerSet) + if err := _DestinationVerifier.contract.UnpackLog(event, "AccessControllerSet", 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 (_DestinationVerifier *DestinationVerifierFilterer) ParseAccessControllerSet(log types.Log) (*DestinationVerifierAccessControllerSet, error) { + event := new(DestinationVerifierAccessControllerSet) + if err := _DestinationVerifier.contract.UnpackLog(event, "AccessControllerSet", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type DestinationVerifierConfigActivatedIterator struct { + Event *DestinationVerifierConfigActivated + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *DestinationVerifierConfigActivatedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(DestinationVerifierConfigActivated) + 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(DestinationVerifierConfigActivated) + 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 *DestinationVerifierConfigActivatedIterator) Error() error { + return it.fail +} + +func (it *DestinationVerifierConfigActivatedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type DestinationVerifierConfigActivated struct { + DonConfigId [24]byte + IsActive bool + Raw types.Log +} + +func (_DestinationVerifier *DestinationVerifierFilterer) FilterConfigActivated(opts *bind.FilterOpts) (*DestinationVerifierConfigActivatedIterator, error) { + + logs, sub, err := _DestinationVerifier.contract.FilterLogs(opts, "ConfigActivated") + if err != nil { + return nil, err + } + return &DestinationVerifierConfigActivatedIterator{contract: _DestinationVerifier.contract, event: "ConfigActivated", logs: logs, sub: sub}, nil +} + +func (_DestinationVerifier *DestinationVerifierFilterer) WatchConfigActivated(opts *bind.WatchOpts, sink chan<- *DestinationVerifierConfigActivated) (event.Subscription, error) { + + logs, sub, err := _DestinationVerifier.contract.WatchLogs(opts, "ConfigActivated") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(DestinationVerifierConfigActivated) + if err := _DestinationVerifier.contract.UnpackLog(event, "ConfigActivated", 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 (_DestinationVerifier *DestinationVerifierFilterer) ParseConfigActivated(log types.Log) (*DestinationVerifierConfigActivated, error) { + event := new(DestinationVerifierConfigActivated) + if err := _DestinationVerifier.contract.UnpackLog(event, "ConfigActivated", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type DestinationVerifierConfigRemovedIterator struct { + Event *DestinationVerifierConfigRemoved + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *DestinationVerifierConfigRemovedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(DestinationVerifierConfigRemoved) + 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(DestinationVerifierConfigRemoved) + 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 *DestinationVerifierConfigRemovedIterator) Error() error { + return it.fail +} + +func (it *DestinationVerifierConfigRemovedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type DestinationVerifierConfigRemoved struct { + DonConfigId [24]byte + Raw types.Log +} + +func (_DestinationVerifier *DestinationVerifierFilterer) FilterConfigRemoved(opts *bind.FilterOpts) (*DestinationVerifierConfigRemovedIterator, error) { + + logs, sub, err := _DestinationVerifier.contract.FilterLogs(opts, "ConfigRemoved") + if err != nil { + return nil, err + } + return &DestinationVerifierConfigRemovedIterator{contract: _DestinationVerifier.contract, event: "ConfigRemoved", logs: logs, sub: sub}, nil +} + +func (_DestinationVerifier *DestinationVerifierFilterer) WatchConfigRemoved(opts *bind.WatchOpts, sink chan<- *DestinationVerifierConfigRemoved) (event.Subscription, error) { + + logs, sub, err := _DestinationVerifier.contract.WatchLogs(opts, "ConfigRemoved") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(DestinationVerifierConfigRemoved) + if err := _DestinationVerifier.contract.UnpackLog(event, "ConfigRemoved", 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 (_DestinationVerifier *DestinationVerifierFilterer) ParseConfigRemoved(log types.Log) (*DestinationVerifierConfigRemoved, error) { + event := new(DestinationVerifierConfigRemoved) + if err := _DestinationVerifier.contract.UnpackLog(event, "ConfigRemoved", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type DestinationVerifierConfigSetIterator struct { + Event *DestinationVerifierConfigSet + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *DestinationVerifierConfigSetIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(DestinationVerifierConfigSet) + 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(DestinationVerifierConfigSet) + 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 *DestinationVerifierConfigSetIterator) Error() error { + return it.fail +} + +func (it *DestinationVerifierConfigSetIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type DestinationVerifierConfigSet struct { + DonConfigId [24]byte + Signers []common.Address + F uint8 + RecipientAddressesAndWeights []CommonAddressAndWeight + DonConfigIndex uint16 + Raw types.Log +} + +func (_DestinationVerifier *DestinationVerifierFilterer) FilterConfigSet(opts *bind.FilterOpts, donConfigId [][24]byte) (*DestinationVerifierConfigSetIterator, error) { + + var donConfigIdRule []interface{} + for _, donConfigIdItem := range donConfigId { + donConfigIdRule = append(donConfigIdRule, donConfigIdItem) + } + + logs, sub, err := _DestinationVerifier.contract.FilterLogs(opts, "ConfigSet", donConfigIdRule) + if err != nil { + return nil, err + } + return &DestinationVerifierConfigSetIterator{contract: _DestinationVerifier.contract, event: "ConfigSet", logs: logs, sub: sub}, nil +} + +func (_DestinationVerifier *DestinationVerifierFilterer) WatchConfigSet(opts *bind.WatchOpts, sink chan<- *DestinationVerifierConfigSet, donConfigId [][24]byte) (event.Subscription, error) { + + var donConfigIdRule []interface{} + for _, donConfigIdItem := range donConfigId { + donConfigIdRule = append(donConfigIdRule, donConfigIdItem) + } + + logs, sub, err := _DestinationVerifier.contract.WatchLogs(opts, "ConfigSet", donConfigIdRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(DestinationVerifierConfigSet) + if err := _DestinationVerifier.contract.UnpackLog(event, "ConfigSet", 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 (_DestinationVerifier *DestinationVerifierFilterer) ParseConfigSet(log types.Log) (*DestinationVerifierConfigSet, error) { + event := new(DestinationVerifierConfigSet) + if err := _DestinationVerifier.contract.UnpackLog(event, "ConfigSet", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type DestinationVerifierFeeManagerSetIterator struct { + Event *DestinationVerifierFeeManagerSet + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *DestinationVerifierFeeManagerSetIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(DestinationVerifierFeeManagerSet) + 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(DestinationVerifierFeeManagerSet) + 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 *DestinationVerifierFeeManagerSetIterator) Error() error { + return it.fail +} + +func (it *DestinationVerifierFeeManagerSetIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type DestinationVerifierFeeManagerSet struct { + OldFeeManager common.Address + NewFeeManager common.Address + Raw types.Log +} + +func (_DestinationVerifier *DestinationVerifierFilterer) FilterFeeManagerSet(opts *bind.FilterOpts) (*DestinationVerifierFeeManagerSetIterator, error) { + + logs, sub, err := _DestinationVerifier.contract.FilterLogs(opts, "FeeManagerSet") + if err != nil { + return nil, err + } + return &DestinationVerifierFeeManagerSetIterator{contract: _DestinationVerifier.contract, event: "FeeManagerSet", logs: logs, sub: sub}, nil +} + +func (_DestinationVerifier *DestinationVerifierFilterer) WatchFeeManagerSet(opts *bind.WatchOpts, sink chan<- *DestinationVerifierFeeManagerSet) (event.Subscription, error) { + + logs, sub, err := _DestinationVerifier.contract.WatchLogs(opts, "FeeManagerSet") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(DestinationVerifierFeeManagerSet) + if err := _DestinationVerifier.contract.UnpackLog(event, "FeeManagerSet", 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 (_DestinationVerifier *DestinationVerifierFilterer) ParseFeeManagerSet(log types.Log) (*DestinationVerifierFeeManagerSet, error) { + event := new(DestinationVerifierFeeManagerSet) + if err := _DestinationVerifier.contract.UnpackLog(event, "FeeManagerSet", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type DestinationVerifierOwnershipTransferRequestedIterator struct { + Event *DestinationVerifierOwnershipTransferRequested + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *DestinationVerifierOwnershipTransferRequestedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(DestinationVerifierOwnershipTransferRequested) + 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(DestinationVerifierOwnershipTransferRequested) + 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 *DestinationVerifierOwnershipTransferRequestedIterator) Error() error { + return it.fail +} + +func (it *DestinationVerifierOwnershipTransferRequestedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type DestinationVerifierOwnershipTransferRequested struct { + From common.Address + To common.Address + Raw types.Log +} + +func (_DestinationVerifier *DestinationVerifierFilterer) FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationVerifierOwnershipTransferRequestedIterator, 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 := _DestinationVerifier.contract.FilterLogs(opts, "OwnershipTransferRequested", fromRule, toRule) + if err != nil { + return nil, err + } + return &DestinationVerifierOwnershipTransferRequestedIterator{contract: _DestinationVerifier.contract, event: "OwnershipTransferRequested", logs: logs, sub: sub}, nil +} + +func (_DestinationVerifier *DestinationVerifierFilterer) WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *DestinationVerifierOwnershipTransferRequested, 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 := _DestinationVerifier.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(DestinationVerifierOwnershipTransferRequested) + if err := _DestinationVerifier.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 (_DestinationVerifier *DestinationVerifierFilterer) ParseOwnershipTransferRequested(log types.Log) (*DestinationVerifierOwnershipTransferRequested, error) { + event := new(DestinationVerifierOwnershipTransferRequested) + if err := _DestinationVerifier.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type DestinationVerifierOwnershipTransferredIterator struct { + Event *DestinationVerifierOwnershipTransferred + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *DestinationVerifierOwnershipTransferredIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(DestinationVerifierOwnershipTransferred) + 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(DestinationVerifierOwnershipTransferred) + 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 *DestinationVerifierOwnershipTransferredIterator) Error() error { + return it.fail +} + +func (it *DestinationVerifierOwnershipTransferredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type DestinationVerifierOwnershipTransferred struct { + From common.Address + To common.Address + Raw types.Log +} + +func (_DestinationVerifier *DestinationVerifierFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationVerifierOwnershipTransferredIterator, 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 := _DestinationVerifier.contract.FilterLogs(opts, "OwnershipTransferred", fromRule, toRule) + if err != nil { + return nil, err + } + return &DestinationVerifierOwnershipTransferredIterator{contract: _DestinationVerifier.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil +} + +func (_DestinationVerifier *DestinationVerifierFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *DestinationVerifierOwnershipTransferred, 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 := _DestinationVerifier.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(DestinationVerifierOwnershipTransferred) + if err := _DestinationVerifier.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 (_DestinationVerifier *DestinationVerifierFilterer) ParseOwnershipTransferred(log types.Log) (*DestinationVerifierOwnershipTransferred, error) { + event := new(DestinationVerifierOwnershipTransferred) + if err := _DestinationVerifier.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type DestinationVerifierReportVerifiedIterator struct { + Event *DestinationVerifierReportVerified + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *DestinationVerifierReportVerifiedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(DestinationVerifierReportVerified) + 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(DestinationVerifierReportVerified) + 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 *DestinationVerifierReportVerifiedIterator) Error() error { + return it.fail +} + +func (it *DestinationVerifierReportVerifiedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type DestinationVerifierReportVerified struct { + FeedId [32]byte + Requester common.Address + Raw types.Log +} + +func (_DestinationVerifier *DestinationVerifierFilterer) FilterReportVerified(opts *bind.FilterOpts, feedId [][32]byte) (*DestinationVerifierReportVerifiedIterator, error) { + + var feedIdRule []interface{} + for _, feedIdItem := range feedId { + feedIdRule = append(feedIdRule, feedIdItem) + } + + logs, sub, err := _DestinationVerifier.contract.FilterLogs(opts, "ReportVerified", feedIdRule) + if err != nil { + return nil, err + } + return &DestinationVerifierReportVerifiedIterator{contract: _DestinationVerifier.contract, event: "ReportVerified", logs: logs, sub: sub}, nil +} + +func (_DestinationVerifier *DestinationVerifierFilterer) WatchReportVerified(opts *bind.WatchOpts, sink chan<- *DestinationVerifierReportVerified, feedId [][32]byte) (event.Subscription, error) { + + var feedIdRule []interface{} + for _, feedIdItem := range feedId { + feedIdRule = append(feedIdRule, feedIdItem) + } + + logs, sub, err := _DestinationVerifier.contract.WatchLogs(opts, "ReportVerified", feedIdRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(DestinationVerifierReportVerified) + if err := _DestinationVerifier.contract.UnpackLog(event, "ReportVerified", 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 (_DestinationVerifier *DestinationVerifierFilterer) ParseReportVerified(log types.Log) (*DestinationVerifierReportVerified, error) { + event := new(DestinationVerifierReportVerified) + if err := _DestinationVerifier.contract.UnpackLog(event, "ReportVerified", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +func (_DestinationVerifier *DestinationVerifier) ParseLog(log types.Log) (generated.AbigenLog, error) { + switch log.Topics[0] { + case _DestinationVerifier.abi.Events["AccessControllerSet"].ID: + return _DestinationVerifier.ParseAccessControllerSet(log) + case _DestinationVerifier.abi.Events["ConfigActivated"].ID: + return _DestinationVerifier.ParseConfigActivated(log) + case _DestinationVerifier.abi.Events["ConfigRemoved"].ID: + return _DestinationVerifier.ParseConfigRemoved(log) + case _DestinationVerifier.abi.Events["ConfigSet"].ID: + return _DestinationVerifier.ParseConfigSet(log) + case _DestinationVerifier.abi.Events["FeeManagerSet"].ID: + return _DestinationVerifier.ParseFeeManagerSet(log) + case _DestinationVerifier.abi.Events["OwnershipTransferRequested"].ID: + return _DestinationVerifier.ParseOwnershipTransferRequested(log) + case _DestinationVerifier.abi.Events["OwnershipTransferred"].ID: + return _DestinationVerifier.ParseOwnershipTransferred(log) + case _DestinationVerifier.abi.Events["ReportVerified"].ID: + return _DestinationVerifier.ParseReportVerified(log) + + default: + return nil, fmt.Errorf("abigen wrapper received unknown log topic: %v", log.Topics[0]) + } +} + +func (DestinationVerifierAccessControllerSet) Topic() common.Hash { + return common.HexToHash("0x953e92b1a6442e9c3242531154a3f6f6eb00b4e9c719ba8118fa6235e4ce89b6") +} + +func (DestinationVerifierConfigActivated) Topic() common.Hash { + return common.HexToHash("0x90186a1e77b498ec417ea88bd026cae00d7043c357cc45221777623bda582dd4") +} + +func (DestinationVerifierConfigRemoved) Topic() common.Hash { + return common.HexToHash("0x970fd8f3ebdd9a271080aacf9807a5c709be0b448e4047a6fc212b8cc165368d") +} + +func (DestinationVerifierConfigSet) Topic() common.Hash { + return common.HexToHash("0xa7d03f81dd1c1d8a55355fd71e1221d5959b7d9fb171fbb6389f207f63598fa5") +} + +func (DestinationVerifierFeeManagerSet) Topic() common.Hash { + return common.HexToHash("0x04628abcaa6b1674651352125cb94b65b289145bc2bc4d67720bb7d966372f03") +} + +func (DestinationVerifierOwnershipTransferRequested) Topic() common.Hash { + return common.HexToHash("0xed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278") +} + +func (DestinationVerifierOwnershipTransferred) Topic() common.Hash { + return common.HexToHash("0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0") +} + +func (DestinationVerifierReportVerified) Topic() common.Hash { + return common.HexToHash("0x58ca9502e98a536e06e72d680fcc251e5d10b72291a281665a2c2dc0ac30fcc5") +} + +func (_DestinationVerifier *DestinationVerifier) Address() common.Address { + return _DestinationVerifier.address +} + +type DestinationVerifierInterface interface { + IVerifierProxy(opts *bind.CallOpts) (common.Address, error) + + Owner(opts *bind.CallOpts) (common.Address, error) + + SAccessController(opts *bind.CallOpts) (common.Address, error) + + SFeeManager(opts *bind.CallOpts) (common.Address, error) + + SupportsInterface(opts *bind.CallOpts, interfaceId [4]byte) (bool, error) + + TypeAndVersion(opts *bind.CallOpts) (string, error) + + AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) + + RemoveLatestConfig(opts *bind.TransactOpts) (*types.Transaction, error) + + SetAccessController(opts *bind.TransactOpts, accessController common.Address) (*types.Transaction, error) + + SetConfig(opts *bind.TransactOpts, signers []common.Address, f uint8, recipientAddressesAndWeights []CommonAddressAndWeight) (*types.Transaction, error) + + SetConfigActive(opts *bind.TransactOpts, donConfigIndex *big.Int, isActive bool) (*types.Transaction, error) + + SetConfigWithActivationTime(opts *bind.TransactOpts, signers []common.Address, f uint8, recipientAddressesAndWeights []CommonAddressAndWeight, activationTime uint32) (*types.Transaction, error) + + SetFeeManager(opts *bind.TransactOpts, feeManager common.Address) (*types.Transaction, error) + + TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) + + Verify(opts *bind.TransactOpts, signedReport []byte, parameterPayload []byte, sender common.Address) (*types.Transaction, error) + + VerifyBulk(opts *bind.TransactOpts, signedReports [][]byte, parameterPayload []byte, sender common.Address) (*types.Transaction, error) + + FilterAccessControllerSet(opts *bind.FilterOpts) (*DestinationVerifierAccessControllerSetIterator, error) + + WatchAccessControllerSet(opts *bind.WatchOpts, sink chan<- *DestinationVerifierAccessControllerSet) (event.Subscription, error) + + ParseAccessControllerSet(log types.Log) (*DestinationVerifierAccessControllerSet, error) + + FilterConfigActivated(opts *bind.FilterOpts) (*DestinationVerifierConfigActivatedIterator, error) + + WatchConfigActivated(opts *bind.WatchOpts, sink chan<- *DestinationVerifierConfigActivated) (event.Subscription, error) + + ParseConfigActivated(log types.Log) (*DestinationVerifierConfigActivated, error) + + FilterConfigRemoved(opts *bind.FilterOpts) (*DestinationVerifierConfigRemovedIterator, error) + + WatchConfigRemoved(opts *bind.WatchOpts, sink chan<- *DestinationVerifierConfigRemoved) (event.Subscription, error) + + ParseConfigRemoved(log types.Log) (*DestinationVerifierConfigRemoved, error) + + FilterConfigSet(opts *bind.FilterOpts, donConfigId [][24]byte) (*DestinationVerifierConfigSetIterator, error) + + WatchConfigSet(opts *bind.WatchOpts, sink chan<- *DestinationVerifierConfigSet, donConfigId [][24]byte) (event.Subscription, error) + + ParseConfigSet(log types.Log) (*DestinationVerifierConfigSet, error) + + FilterFeeManagerSet(opts *bind.FilterOpts) (*DestinationVerifierFeeManagerSetIterator, error) + + WatchFeeManagerSet(opts *bind.WatchOpts, sink chan<- *DestinationVerifierFeeManagerSet) (event.Subscription, error) + + ParseFeeManagerSet(log types.Log) (*DestinationVerifierFeeManagerSet, error) + + FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationVerifierOwnershipTransferRequestedIterator, error) + + WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *DestinationVerifierOwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error) + + ParseOwnershipTransferRequested(log types.Log) (*DestinationVerifierOwnershipTransferRequested, error) + + FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationVerifierOwnershipTransferredIterator, error) + + WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *DestinationVerifierOwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error) + + ParseOwnershipTransferred(log types.Log) (*DestinationVerifierOwnershipTransferred, error) + + FilterReportVerified(opts *bind.FilterOpts, feedId [][32]byte) (*DestinationVerifierReportVerifiedIterator, error) + + WatchReportVerified(opts *bind.WatchOpts, sink chan<- *DestinationVerifierReportVerified, feedId [][32]byte) (event.Subscription, error) + + ParseReportVerified(log types.Log) (*DestinationVerifierReportVerified, error) + + ParseLog(log types.Log) (generated.AbigenLog, error) + + Address() common.Address +} diff --git a/core/gethwrappers/llo-feeds/generated/destination_verifier_proxy/destination_verifier_proxy.go b/core/gethwrappers/llo-feeds/generated/destination_verifier_proxy/destination_verifier_proxy.go new file mode 100644 index 0000000000..7111fc7763 --- /dev/null +++ b/core/gethwrappers/llo-feeds/generated/destination_verifier_proxy/destination_verifier_proxy.go @@ -0,0 +1,676 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package destination_verifier_proxy + +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 DestinationVerifierProxyMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"verifierAddress\",\"type\":\"address\"}],\"name\":\"VerifierInvalid\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroAddress\",\"type\":\"error\"},{\"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\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_accessController\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_feeManager\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"verifierAddress\",\"type\":\"address\"}],\"name\":\"setVerifier\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"payload\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"parameterPayload\",\"type\":\"bytes\"}],\"name\":\"verify\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes[]\",\"name\":\"payloads\",\"type\":\"bytes[]\"},{\"internalType\":\"bytes\",\"name\":\"parameterPayload\",\"type\":\"bytes\"}],\"name\":\"verifyBulk\",\"outputs\":[{\"internalType\":\"bytes[]\",\"name\":\"verifiedReports\",\"type\":\"bytes[]\"}],\"stateMutability\":\"payable\",\"type\":\"function\"}]", + Bin: "0x608060405234801561001057600080fd5b5033806000816100675760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615610097576100978161009f565b505050610148565b336001600160a01b038216036100f75760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640161005e565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b610fac806101576000396000f3fe6080604052600436106100b15760003560e01c80638da5cb5b11610069578063f2fde38b1161004e578063f2fde38b1461022d578063f7e83aee1461024d578063f873a61c1461026057600080fd5b80638da5cb5b146101ed57806394ba28461461021857600080fd5b806338416b5b1161009a57806338416b5b1461017c5780635437988d146101b657806379ba5097146101d857600080fd5b806301ffc9a7146100b6578063181f5a771461012d575b600080fd5b3480156100c257600080fd5b506101186100d13660046108f7565b7fffffffff00000000000000000000000000000000000000000000000000000000167ff7574762000000000000000000000000000000000000000000000000000000001490565b60405190151581526020015b60405180910390f35b34801561013957600080fd5b5060408051808201909152601e81527f44657374696e6174696f6e566572696669657250726f787920302e342e30000060208201525b60405161012491906109ae565b34801561018857600080fd5b50610191610280565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610124565b3480156101c257600080fd5b506101d66101d13660046109e3565b610319565b005b3480156101e457600080fd5b506101d6610469565b3480156101f957600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff16610191565b34801561022457600080fd5b50610191610566565b34801561023957600080fd5b506101d66102483660046109e3565b6105d6565b61016f61025b366004610a49565b6105ea565b61027361026e366004610ab5565b6106b9565b6040516101249190610b36565b600254604080517f38416b5b000000000000000000000000000000000000000000000000000000008152905160009273ffffffffffffffffffffffffffffffffffffffff16916338416b5b9160048083019260209291908290030181865afa1580156102f0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103149190610bb6565b905090565b61032161077f565b6040517f01ffc9a70000000000000000000000000000000000000000000000000000000081527f527146e200000000000000000000000000000000000000000000000000000000600482015273ffffffffffffffffffffffffffffffffffffffff8216906301ffc9a790602401602060405180830381865afa1580156103ab573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103cf9190610bd3565b610422576040517f96ac86f300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff821660048201526024015b60405180910390fd5b600280547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b60015473ffffffffffffffffffffffffffffffffffffffff1633146104ea576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e6572000000000000000000006044820152606401610419565b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b600254604080517f94ba2846000000000000000000000000000000000000000000000000000000008152905160009273ffffffffffffffffffffffffffffffffffffffff16916394ba28469160048083019260209291908290030181865afa1580156102f0573d6000803e3d6000fd5b6105de61077f565b6105e781610802565b50565b6002546040517f294d2bb100000000000000000000000000000000000000000000000000000000815260609173ffffffffffffffffffffffffffffffffffffffff169063294d2bb190349061064b9089908990899089903390600401610c3e565b60006040518083038185885af1158015610669573d6000803e3d6000fd5b50505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526106b09190810190610d96565b95945050505050565b6002546040517fd7c72e4e00000000000000000000000000000000000000000000000000000000815260609173ffffffffffffffffffffffffffffffffffffffff169063d7c72e4e90349061071a9089908990899089903390600401610dcb565b60006040518083038185885af1158015610738573d6000803e3d6000fd5b50505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526106b09190810190610edc565b60005473ffffffffffffffffffffffffffffffffffffffff163314610800576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610419565b565b3373ffffffffffffffffffffffffffffffffffffffff821603610881576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610419565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b60006020828403121561090957600080fd5b81357fffffffff000000000000000000000000000000000000000000000000000000008116811461093957600080fd5b9392505050565b60005b8381101561095b578181015183820152602001610943565b50506000910152565b6000815180845261097c816020860160208601610940565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006109396020830184610964565b73ffffffffffffffffffffffffffffffffffffffff811681146105e757600080fd5b6000602082840312156109f557600080fd5b8135610939816109c1565b60008083601f840112610a1257600080fd5b50813567ffffffffffffffff811115610a2a57600080fd5b602083019150836020828501011115610a4257600080fd5b9250929050565b60008060008060408587031215610a5f57600080fd5b843567ffffffffffffffff80821115610a7757600080fd5b610a8388838901610a00565b90965094506020870135915080821115610a9c57600080fd5b50610aa987828801610a00565b95989497509550505050565b60008060008060408587031215610acb57600080fd5b843567ffffffffffffffff80821115610ae357600080fd5b818701915087601f830112610af757600080fd5b813581811115610b0657600080fd5b8860208260051b8501011115610b1b57600080fd5b602092830196509450908601359080821115610a9c57600080fd5b6000602080830181845280855180835260408601915060408160051b870101925083870160005b82811015610ba9577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0888603018452610b97858351610964565b94509285019290850190600101610b5d565b5092979650505050505050565b600060208284031215610bc857600080fd5b8151610939816109c1565b600060208284031215610be557600080fd5b8151801515811461093957600080fd5b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b606081526000610c52606083018789610bf5565b8281036020840152610c65818688610bf5565b91505073ffffffffffffffffffffffffffffffffffffffff831660408301529695505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715610d0457610d04610c8e565b604052919050565b600082601f830112610d1d57600080fd5b815167ffffffffffffffff811115610d3757610d37610c8e565b610d6860207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601610cbd565b818152846020838601011115610d7d57600080fd5b610d8e826020830160208701610940565b949350505050565b600060208284031215610da857600080fd5b815167ffffffffffffffff811115610dbf57600080fd5b610d8e84828501610d0c565b6060808252810185905260006080600587901b8301810190830188835b89811015610e97577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8086850301835281357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18c3603018112610e4957600080fd5b8b01602081810191359067ffffffffffffffff821115610e6857600080fd5b813603831315610e7757600080fd5b610e82878385610bf5565b96509485019493909301925050600101610de8565b5050508281036020840152610ead818688610bf5565b915050610ed2604083018473ffffffffffffffffffffffffffffffffffffffff169052565b9695505050505050565b60006020808385031215610eef57600080fd5b825167ffffffffffffffff80821115610f0757600080fd5b818501915085601f830112610f1b57600080fd5b815181811115610f2d57610f2d610c8e565b8060051b610f3c858201610cbd565b9182528381018501918581019089841115610f5657600080fd5b86860192505b83831015610f9257825185811115610f745760008081fd5b610f828b89838a0101610d0c565b8352509186019190860190610f5c565b999850505050505050505056fea164736f6c6343000813000a", +} + +var DestinationVerifierProxyABI = DestinationVerifierProxyMetaData.ABI + +var DestinationVerifierProxyBin = DestinationVerifierProxyMetaData.Bin + +func DeployDestinationVerifierProxy(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *DestinationVerifierProxy, error) { + parsed, err := DestinationVerifierProxyMetaData.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(DestinationVerifierProxyBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &DestinationVerifierProxy{address: address, abi: *parsed, DestinationVerifierProxyCaller: DestinationVerifierProxyCaller{contract: contract}, DestinationVerifierProxyTransactor: DestinationVerifierProxyTransactor{contract: contract}, DestinationVerifierProxyFilterer: DestinationVerifierProxyFilterer{contract: contract}}, nil +} + +type DestinationVerifierProxy struct { + address common.Address + abi abi.ABI + DestinationVerifierProxyCaller + DestinationVerifierProxyTransactor + DestinationVerifierProxyFilterer +} + +type DestinationVerifierProxyCaller struct { + contract *bind.BoundContract +} + +type DestinationVerifierProxyTransactor struct { + contract *bind.BoundContract +} + +type DestinationVerifierProxyFilterer struct { + contract *bind.BoundContract +} + +type DestinationVerifierProxySession struct { + Contract *DestinationVerifierProxy + CallOpts bind.CallOpts + TransactOpts bind.TransactOpts +} + +type DestinationVerifierProxyCallerSession struct { + Contract *DestinationVerifierProxyCaller + CallOpts bind.CallOpts +} + +type DestinationVerifierProxyTransactorSession struct { + Contract *DestinationVerifierProxyTransactor + TransactOpts bind.TransactOpts +} + +type DestinationVerifierProxyRaw struct { + Contract *DestinationVerifierProxy +} + +type DestinationVerifierProxyCallerRaw struct { + Contract *DestinationVerifierProxyCaller +} + +type DestinationVerifierProxyTransactorRaw struct { + Contract *DestinationVerifierProxyTransactor +} + +func NewDestinationVerifierProxy(address common.Address, backend bind.ContractBackend) (*DestinationVerifierProxy, error) { + abi, err := abi.JSON(strings.NewReader(DestinationVerifierProxyABI)) + if err != nil { + return nil, err + } + contract, err := bindDestinationVerifierProxy(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &DestinationVerifierProxy{address: address, abi: abi, DestinationVerifierProxyCaller: DestinationVerifierProxyCaller{contract: contract}, DestinationVerifierProxyTransactor: DestinationVerifierProxyTransactor{contract: contract}, DestinationVerifierProxyFilterer: DestinationVerifierProxyFilterer{contract: contract}}, nil +} + +func NewDestinationVerifierProxyCaller(address common.Address, caller bind.ContractCaller) (*DestinationVerifierProxyCaller, error) { + contract, err := bindDestinationVerifierProxy(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &DestinationVerifierProxyCaller{contract: contract}, nil +} + +func NewDestinationVerifierProxyTransactor(address common.Address, transactor bind.ContractTransactor) (*DestinationVerifierProxyTransactor, error) { + contract, err := bindDestinationVerifierProxy(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &DestinationVerifierProxyTransactor{contract: contract}, nil +} + +func NewDestinationVerifierProxyFilterer(address common.Address, filterer bind.ContractFilterer) (*DestinationVerifierProxyFilterer, error) { + contract, err := bindDestinationVerifierProxy(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &DestinationVerifierProxyFilterer{contract: contract}, nil +} + +func bindDestinationVerifierProxy(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := DestinationVerifierProxyMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +func (_DestinationVerifierProxy *DestinationVerifierProxyRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _DestinationVerifierProxy.Contract.DestinationVerifierProxyCaller.contract.Call(opts, result, method, params...) +} + +func (_DestinationVerifierProxy *DestinationVerifierProxyRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _DestinationVerifierProxy.Contract.DestinationVerifierProxyTransactor.contract.Transfer(opts) +} + +func (_DestinationVerifierProxy *DestinationVerifierProxyRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _DestinationVerifierProxy.Contract.DestinationVerifierProxyTransactor.contract.Transact(opts, method, params...) +} + +func (_DestinationVerifierProxy *DestinationVerifierProxyCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _DestinationVerifierProxy.Contract.contract.Call(opts, result, method, params...) +} + +func (_DestinationVerifierProxy *DestinationVerifierProxyTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _DestinationVerifierProxy.Contract.contract.Transfer(opts) +} + +func (_DestinationVerifierProxy *DestinationVerifierProxyTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _DestinationVerifierProxy.Contract.contract.Transact(opts, method, params...) +} + +func (_DestinationVerifierProxy *DestinationVerifierProxyCaller) Owner(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _DestinationVerifierProxy.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 (_DestinationVerifierProxy *DestinationVerifierProxySession) Owner() (common.Address, error) { + return _DestinationVerifierProxy.Contract.Owner(&_DestinationVerifierProxy.CallOpts) +} + +func (_DestinationVerifierProxy *DestinationVerifierProxyCallerSession) Owner() (common.Address, error) { + return _DestinationVerifierProxy.Contract.Owner(&_DestinationVerifierProxy.CallOpts) +} + +func (_DestinationVerifierProxy *DestinationVerifierProxyCaller) SAccessController(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _DestinationVerifierProxy.contract.Call(opts, &out, "s_accessController") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +func (_DestinationVerifierProxy *DestinationVerifierProxySession) SAccessController() (common.Address, error) { + return _DestinationVerifierProxy.Contract.SAccessController(&_DestinationVerifierProxy.CallOpts) +} + +func (_DestinationVerifierProxy *DestinationVerifierProxyCallerSession) SAccessController() (common.Address, error) { + return _DestinationVerifierProxy.Contract.SAccessController(&_DestinationVerifierProxy.CallOpts) +} + +func (_DestinationVerifierProxy *DestinationVerifierProxyCaller) SFeeManager(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _DestinationVerifierProxy.contract.Call(opts, &out, "s_feeManager") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +func (_DestinationVerifierProxy *DestinationVerifierProxySession) SFeeManager() (common.Address, error) { + return _DestinationVerifierProxy.Contract.SFeeManager(&_DestinationVerifierProxy.CallOpts) +} + +func (_DestinationVerifierProxy *DestinationVerifierProxyCallerSession) SFeeManager() (common.Address, error) { + return _DestinationVerifierProxy.Contract.SFeeManager(&_DestinationVerifierProxy.CallOpts) +} + +func (_DestinationVerifierProxy *DestinationVerifierProxyCaller) SupportsInterface(opts *bind.CallOpts, interfaceId [4]byte) (bool, error) { + var out []interface{} + err := _DestinationVerifierProxy.contract.Call(opts, &out, "supportsInterface", interfaceId) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +func (_DestinationVerifierProxy *DestinationVerifierProxySession) SupportsInterface(interfaceId [4]byte) (bool, error) { + return _DestinationVerifierProxy.Contract.SupportsInterface(&_DestinationVerifierProxy.CallOpts, interfaceId) +} + +func (_DestinationVerifierProxy *DestinationVerifierProxyCallerSession) SupportsInterface(interfaceId [4]byte) (bool, error) { + return _DestinationVerifierProxy.Contract.SupportsInterface(&_DestinationVerifierProxy.CallOpts, interfaceId) +} + +func (_DestinationVerifierProxy *DestinationVerifierProxyCaller) TypeAndVersion(opts *bind.CallOpts) (string, error) { + var out []interface{} + err := _DestinationVerifierProxy.contract.Call(opts, &out, "typeAndVersion") + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + +} + +func (_DestinationVerifierProxy *DestinationVerifierProxySession) TypeAndVersion() (string, error) { + return _DestinationVerifierProxy.Contract.TypeAndVersion(&_DestinationVerifierProxy.CallOpts) +} + +func (_DestinationVerifierProxy *DestinationVerifierProxyCallerSession) TypeAndVersion() (string, error) { + return _DestinationVerifierProxy.Contract.TypeAndVersion(&_DestinationVerifierProxy.CallOpts) +} + +func (_DestinationVerifierProxy *DestinationVerifierProxyTransactor) AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) { + return _DestinationVerifierProxy.contract.Transact(opts, "acceptOwnership") +} + +func (_DestinationVerifierProxy *DestinationVerifierProxySession) AcceptOwnership() (*types.Transaction, error) { + return _DestinationVerifierProxy.Contract.AcceptOwnership(&_DestinationVerifierProxy.TransactOpts) +} + +func (_DestinationVerifierProxy *DestinationVerifierProxyTransactorSession) AcceptOwnership() (*types.Transaction, error) { + return _DestinationVerifierProxy.Contract.AcceptOwnership(&_DestinationVerifierProxy.TransactOpts) +} + +func (_DestinationVerifierProxy *DestinationVerifierProxyTransactor) SetVerifier(opts *bind.TransactOpts, verifierAddress common.Address) (*types.Transaction, error) { + return _DestinationVerifierProxy.contract.Transact(opts, "setVerifier", verifierAddress) +} + +func (_DestinationVerifierProxy *DestinationVerifierProxySession) SetVerifier(verifierAddress common.Address) (*types.Transaction, error) { + return _DestinationVerifierProxy.Contract.SetVerifier(&_DestinationVerifierProxy.TransactOpts, verifierAddress) +} + +func (_DestinationVerifierProxy *DestinationVerifierProxyTransactorSession) SetVerifier(verifierAddress common.Address) (*types.Transaction, error) { + return _DestinationVerifierProxy.Contract.SetVerifier(&_DestinationVerifierProxy.TransactOpts, verifierAddress) +} + +func (_DestinationVerifierProxy *DestinationVerifierProxyTransactor) TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) { + return _DestinationVerifierProxy.contract.Transact(opts, "transferOwnership", to) +} + +func (_DestinationVerifierProxy *DestinationVerifierProxySession) TransferOwnership(to common.Address) (*types.Transaction, error) { + return _DestinationVerifierProxy.Contract.TransferOwnership(&_DestinationVerifierProxy.TransactOpts, to) +} + +func (_DestinationVerifierProxy *DestinationVerifierProxyTransactorSession) TransferOwnership(to common.Address) (*types.Transaction, error) { + return _DestinationVerifierProxy.Contract.TransferOwnership(&_DestinationVerifierProxy.TransactOpts, to) +} + +func (_DestinationVerifierProxy *DestinationVerifierProxyTransactor) Verify(opts *bind.TransactOpts, payload []byte, parameterPayload []byte) (*types.Transaction, error) { + return _DestinationVerifierProxy.contract.Transact(opts, "verify", payload, parameterPayload) +} + +func (_DestinationVerifierProxy *DestinationVerifierProxySession) Verify(payload []byte, parameterPayload []byte) (*types.Transaction, error) { + return _DestinationVerifierProxy.Contract.Verify(&_DestinationVerifierProxy.TransactOpts, payload, parameterPayload) +} + +func (_DestinationVerifierProxy *DestinationVerifierProxyTransactorSession) Verify(payload []byte, parameterPayload []byte) (*types.Transaction, error) { + return _DestinationVerifierProxy.Contract.Verify(&_DestinationVerifierProxy.TransactOpts, payload, parameterPayload) +} + +func (_DestinationVerifierProxy *DestinationVerifierProxyTransactor) VerifyBulk(opts *bind.TransactOpts, payloads [][]byte, parameterPayload []byte) (*types.Transaction, error) { + return _DestinationVerifierProxy.contract.Transact(opts, "verifyBulk", payloads, parameterPayload) +} + +func (_DestinationVerifierProxy *DestinationVerifierProxySession) VerifyBulk(payloads [][]byte, parameterPayload []byte) (*types.Transaction, error) { + return _DestinationVerifierProxy.Contract.VerifyBulk(&_DestinationVerifierProxy.TransactOpts, payloads, parameterPayload) +} + +func (_DestinationVerifierProxy *DestinationVerifierProxyTransactorSession) VerifyBulk(payloads [][]byte, parameterPayload []byte) (*types.Transaction, error) { + return _DestinationVerifierProxy.Contract.VerifyBulk(&_DestinationVerifierProxy.TransactOpts, payloads, parameterPayload) +} + +type DestinationVerifierProxyOwnershipTransferRequestedIterator struct { + Event *DestinationVerifierProxyOwnershipTransferRequested + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *DestinationVerifierProxyOwnershipTransferRequestedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(DestinationVerifierProxyOwnershipTransferRequested) + 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(DestinationVerifierProxyOwnershipTransferRequested) + 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 *DestinationVerifierProxyOwnershipTransferRequestedIterator) Error() error { + return it.fail +} + +func (it *DestinationVerifierProxyOwnershipTransferRequestedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type DestinationVerifierProxyOwnershipTransferRequested struct { + From common.Address + To common.Address + Raw types.Log +} + +func (_DestinationVerifierProxy *DestinationVerifierProxyFilterer) FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationVerifierProxyOwnershipTransferRequestedIterator, 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 := _DestinationVerifierProxy.contract.FilterLogs(opts, "OwnershipTransferRequested", fromRule, toRule) + if err != nil { + return nil, err + } + return &DestinationVerifierProxyOwnershipTransferRequestedIterator{contract: _DestinationVerifierProxy.contract, event: "OwnershipTransferRequested", logs: logs, sub: sub}, nil +} + +func (_DestinationVerifierProxy *DestinationVerifierProxyFilterer) WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *DestinationVerifierProxyOwnershipTransferRequested, 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 := _DestinationVerifierProxy.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(DestinationVerifierProxyOwnershipTransferRequested) + if err := _DestinationVerifierProxy.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 (_DestinationVerifierProxy *DestinationVerifierProxyFilterer) ParseOwnershipTransferRequested(log types.Log) (*DestinationVerifierProxyOwnershipTransferRequested, error) { + event := new(DestinationVerifierProxyOwnershipTransferRequested) + if err := _DestinationVerifierProxy.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type DestinationVerifierProxyOwnershipTransferredIterator struct { + Event *DestinationVerifierProxyOwnershipTransferred + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *DestinationVerifierProxyOwnershipTransferredIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(DestinationVerifierProxyOwnershipTransferred) + 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(DestinationVerifierProxyOwnershipTransferred) + 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 *DestinationVerifierProxyOwnershipTransferredIterator) Error() error { + return it.fail +} + +func (it *DestinationVerifierProxyOwnershipTransferredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type DestinationVerifierProxyOwnershipTransferred struct { + From common.Address + To common.Address + Raw types.Log +} + +func (_DestinationVerifierProxy *DestinationVerifierProxyFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationVerifierProxyOwnershipTransferredIterator, 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 := _DestinationVerifierProxy.contract.FilterLogs(opts, "OwnershipTransferred", fromRule, toRule) + if err != nil { + return nil, err + } + return &DestinationVerifierProxyOwnershipTransferredIterator{contract: _DestinationVerifierProxy.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil +} + +func (_DestinationVerifierProxy *DestinationVerifierProxyFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *DestinationVerifierProxyOwnershipTransferred, 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 := _DestinationVerifierProxy.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(DestinationVerifierProxyOwnershipTransferred) + if err := _DestinationVerifierProxy.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 (_DestinationVerifierProxy *DestinationVerifierProxyFilterer) ParseOwnershipTransferred(log types.Log) (*DestinationVerifierProxyOwnershipTransferred, error) { + event := new(DestinationVerifierProxyOwnershipTransferred) + if err := _DestinationVerifierProxy.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +func (_DestinationVerifierProxy *DestinationVerifierProxy) ParseLog(log types.Log) (generated.AbigenLog, error) { + switch log.Topics[0] { + case _DestinationVerifierProxy.abi.Events["OwnershipTransferRequested"].ID: + return _DestinationVerifierProxy.ParseOwnershipTransferRequested(log) + case _DestinationVerifierProxy.abi.Events["OwnershipTransferred"].ID: + return _DestinationVerifierProxy.ParseOwnershipTransferred(log) + + default: + return nil, fmt.Errorf("abigen wrapper received unknown log topic: %v", log.Topics[0]) + } +} + +func (DestinationVerifierProxyOwnershipTransferRequested) Topic() common.Hash { + return common.HexToHash("0xed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278") +} + +func (DestinationVerifierProxyOwnershipTransferred) Topic() common.Hash { + return common.HexToHash("0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0") +} + +func (_DestinationVerifierProxy *DestinationVerifierProxy) Address() common.Address { + return _DestinationVerifierProxy.address +} + +type DestinationVerifierProxyInterface interface { + Owner(opts *bind.CallOpts) (common.Address, error) + + SAccessController(opts *bind.CallOpts) (common.Address, error) + + SFeeManager(opts *bind.CallOpts) (common.Address, error) + + SupportsInterface(opts *bind.CallOpts, interfaceId [4]byte) (bool, error) + + TypeAndVersion(opts *bind.CallOpts) (string, error) + + AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) + + SetVerifier(opts *bind.TransactOpts, verifierAddress common.Address) (*types.Transaction, error) + + TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) + + Verify(opts *bind.TransactOpts, payload []byte, parameterPayload []byte) (*types.Transaction, error) + + VerifyBulk(opts *bind.TransactOpts, payloads [][]byte, parameterPayload []byte) (*types.Transaction, error) + + FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationVerifierProxyOwnershipTransferRequestedIterator, error) + + WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *DestinationVerifierProxyOwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error) + + ParseOwnershipTransferRequested(log types.Log) (*DestinationVerifierProxyOwnershipTransferRequested, error) + + FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*DestinationVerifierProxyOwnershipTransferredIterator, error) + + WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *DestinationVerifierProxyOwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error) + + ParseOwnershipTransferred(log types.Log) (*DestinationVerifierProxyOwnershipTransferred, error) + + ParseLog(log types.Log) (generated.AbigenLog, error) + + Address() common.Address +} diff --git a/core/gethwrappers/llo-feeds/generated/errored_verifier/errored_verifier.go b/core/gethwrappers/llo-feeds/generated/errored_verifier/errored_verifier.go index 4d140ea064..f834686dfe 100644 --- a/core/gethwrappers/llo-feeds/generated/errored_verifier/errored_verifier.go +++ b/core/gethwrappers/llo-feeds/generated/errored_verifier/errored_verifier.go @@ -34,8 +34,8 @@ type CommonAddressAndWeight struct { } var ErroredVerifierMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"activateConfig\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"activateFeed\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"deactivateConfig\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"deactivateFeed\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"latestConfigDetails\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"latestConfigDigestAndEpoch\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"weight\",\"type\":\"uint64\"}],\"internalType\":\"structCommon.AddressAndWeight[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"name\":\"setConfig\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"},{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"weight\",\"type\":\"uint64\"}],\"internalType\":\"structCommon.AddressAndWeight[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"name\":\"setConfigFromSource\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"verify\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}]", - Bin: "0x608060405234801561001057600080fd5b50610c2e806100206000396000f3fe608060405234801561001057600080fd5b50600436106100be5760003560e01c8063b70d929d11610076578063e7db9c2a1161005b578063e7db9c2a146101d1578063e84f128e146101e4578063f01072211461021a57600080fd5b8063b70d929d14610188578063ded6307c146101be57600080fd5b80633dd86430116100a75780633dd864301461014d578063564a0a7a1461016257806394d959801461017557600080fd5b806301ffc9a7146100c35780633d3ac1b51461012d575b600080fd5b6101186100d136600461059a565b7fffffffff00000000000000000000000000000000000000000000000000000000167f3d3ac1b5000000000000000000000000000000000000000000000000000000001490565b60405190151581526020015b60405180910390f35b61014061013b366004610741565b610228565b604051610124919061078f565b61016061015b3660046107fb565b610292565b005b6101606101703660046107fb565b6102f4565b610160610183366004610814565b610356565b61019b6101963660046107fb565b6103b8565b604080519315158452602084019290925263ffffffff1690820152606001610124565b6101606101cc366004610814565b610447565b6101606101df3660046109f1565b6104a9565b6101f76101f23660046107fb565b61050b565b6040805163ffffffff948516815293909216602084015290820152606001610124565b6101606101df366004610b24565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f4661696c656420746f207665726966790000000000000000000000000000000060448201526060906064015b60405180910390fd5b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4661696c656420746f20616374697661746520666565640000000000000000006044820152606401610289565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f4661696c656420746f20646561637469766174652066656564000000000000006044820152606401610289565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f4661696c656420746f206465616374697661746520636f6e66696700000000006044820152606401610289565b60008060006040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610289906020808252602c908201527f4661696c656420746f20676574206c617465737420636f6e666967206469676560408201527f737420616e642065706f63680000000000000000000000000000000000000000606082015260800190565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f4661696c656420746f20616374697661746520636f6e666967000000000000006044820152606401610289565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f4661696c656420746f2073657420636f6e6669670000000000000000000000006044820152606401610289565b60008060006040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102899060208082526023908201527f4661696c656420746f20676574206c617465737420636f6e666967206465746160408201527f696c730000000000000000000000000000000000000000000000000000000000606082015260800190565b6000602082840312156105ac57600080fd5b81357fffffffff00000000000000000000000000000000000000000000000000000000811681146105dc57600080fd5b9392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040805190810167ffffffffffffffff81118282101715610635576106356105e3565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715610682576106826105e3565b604052919050565b600082601f83011261069b57600080fd5b813567ffffffffffffffff8111156106b5576106b56105e3565b6106e660207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8401160161063b565b8181528460208386010111156106fb57600080fd5b816020850160208301376000918101602001919091529392505050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461073c57600080fd5b919050565b6000806040838503121561075457600080fd5b823567ffffffffffffffff81111561076b57600080fd5b6107778582860161068a565b92505061078660208401610718565b90509250929050565b600060208083528351808285015260005b818110156107bc578581018301518582016040015282016107a0565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b60006020828403121561080d57600080fd5b5035919050565b6000806040838503121561082757600080fd5b50508035926020909101359150565b803563ffffffff8116811461073c57600080fd5b600067ffffffffffffffff821115610864576108646105e3565b5060051b60200190565b600082601f83011261087f57600080fd5b8135602061089461088f8361084a565b61063b565b82815260059290921b840181019181810190868411156108b357600080fd5b8286015b848110156108d5576108c881610718565b83529183019183016108b7565b509695505050505050565b600082601f8301126108f157600080fd5b8135602061090161088f8361084a565b82815260059290921b8401810191818101908684111561092057600080fd5b8286015b848110156108d55780358352918301918301610924565b803560ff8116811461073c57600080fd5b803567ffffffffffffffff8116811461073c57600080fd5b600082601f83011261097557600080fd5b8135602061098561088f8361084a565b82815260069290921b840181019181810190868411156109a457600080fd5b8286015b848110156108d557604081890312156109c15760008081fd5b6109c9610612565b6109d282610718565b81526109df85830161094c565b818601528352918301916040016109a8565b60008060008060008060008060008060006101608c8e031215610a1357600080fd5b8b359a5060208c01359950610a2a60408d01610718565b9850610a3860608d01610836565b975067ffffffffffffffff8060808e01351115610a5457600080fd5b610a648e60808f01358f0161086e565b97508060a08e01351115610a7757600080fd5b610a878e60a08f01358f016108e0565b9650610a9560c08e0161093b565b95508060e08e01351115610aa857600080fd5b610ab88e60e08f01358f0161068a565b9450610ac76101008e0161094c565b9350806101208e01351115610adb57600080fd5b610aec8e6101208f01358f0161068a565b9250806101408e01351115610b0057600080fd5b50610b128d6101408e01358e01610964565b90509295989b509295989b9093969950565b600080600080600080600080610100898b031215610b4157600080fd5b88359750602089013567ffffffffffffffff80821115610b6057600080fd5b610b6c8c838d0161086e565b985060408b0135915080821115610b8257600080fd5b610b8e8c838d016108e0565b9750610b9c60608c0161093b565b965060808b0135915080821115610bb257600080fd5b610bbe8c838d0161068a565b9550610bcc60a08c0161094c565b945060c08b0135915080821115610be257600080fd5b610bee8c838d0161068a565b935060e08b0135915080821115610c0457600080fd5b50610c118b828c01610964565b915050929598509295989093965056fea164736f6c6343000813000a", + ABI: "[{\"inputs\":[],\"name\":\"FailedToActivateConfig\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FailedToActivateFeed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FailedToDeactivateConfig\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FailedToDeactivateFeed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FailedToGetLatestConfigDetails\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FailedToGetLatestConfigDigestAndEpoch\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FailedToSetConfig\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FailedToVerify\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"activateConfig\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"activateFeed\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"deactivateConfig\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"deactivateFeed\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"latestConfigDetails\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"latestConfigDigestAndEpoch\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"weight\",\"type\":\"uint64\"}],\"internalType\":\"structCommon.AddressAndWeight[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"name\":\"setConfig\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"},{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"weight\",\"type\":\"uint64\"}],\"internalType\":\"structCommon.AddressAndWeight[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"name\":\"setConfigFromSource\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"verify\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}]", + Bin: "0x608060405234801561001057600080fd5b50610a58806100206000396000f3fe608060405234801561001057600080fd5b50600436106100be5760003560e01c8063b70d929d11610076578063e7db9c2a1161005b578063e7db9c2a146101d1578063e84f128e146101e4578063f01072211461021a57600080fd5b8063b70d929d14610188578063ded6307c146101be57600080fd5b80633dd86430116100a75780633dd864301461014d578063564a0a7a1461016257806394d959801461017557600080fd5b806301ffc9a7146100c35780633d3ac1b51461012d575b600080fd5b6101186100d13660046103c4565b7fffffffff00000000000000000000000000000000000000000000000000000000167f3d3ac1b5000000000000000000000000000000000000000000000000000000001490565b60405190151581526020015b60405180910390f35b61014061013b36600461056b565b610228565b60405161012491906105b9565b61016061015b366004610625565b61025c565b005b610160610170366004610625565b61028e565b61016061018336600461063e565b6102c0565b61019b610196366004610625565b6102f2565b604080519315158452602084019290925263ffffffff1690820152606001610124565b6101606101cc36600461063e565b610329565b6101606101df36600461081b565b61035b565b6101f76101f2366004610625565b61038d565b6040805163ffffffff948516815293909216602084015290820152606001610124565b6101606101df36600461094e565b60606040517fcf2e344600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f9601b68300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517fa03564b300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f8a406e4600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008060006040517fbbc0083000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f7adb7c9600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f35e91bf100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008060006040517fa06d64a000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000602082840312156103d657600080fd5b81357fffffffff000000000000000000000000000000000000000000000000000000008116811461040657600080fd5b9392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040805190810167ffffffffffffffff8111828210171561045f5761045f61040d565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156104ac576104ac61040d565b604052919050565b600082601f8301126104c557600080fd5b813567ffffffffffffffff8111156104df576104df61040d565b61051060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601610465565b81815284602083860101111561052557600080fd5b816020850160208301376000918101602001919091529392505050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461056657600080fd5b919050565b6000806040838503121561057e57600080fd5b823567ffffffffffffffff81111561059557600080fd5b6105a1858286016104b4565b9250506105b060208401610542565b90509250929050565b600060208083528351808285015260005b818110156105e6578581018301518582016040015282016105ca565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b60006020828403121561063757600080fd5b5035919050565b6000806040838503121561065157600080fd5b50508035926020909101359150565b803563ffffffff8116811461056657600080fd5b600067ffffffffffffffff82111561068e5761068e61040d565b5060051b60200190565b600082601f8301126106a957600080fd5b813560206106be6106b983610674565b610465565b82815260059290921b840181019181810190868411156106dd57600080fd5b8286015b848110156106ff576106f281610542565b83529183019183016106e1565b509695505050505050565b600082601f83011261071b57600080fd5b8135602061072b6106b983610674565b82815260059290921b8401810191818101908684111561074a57600080fd5b8286015b848110156106ff578035835291830191830161074e565b803560ff8116811461056657600080fd5b803567ffffffffffffffff8116811461056657600080fd5b600082601f83011261079f57600080fd5b813560206107af6106b983610674565b82815260069290921b840181019181810190868411156107ce57600080fd5b8286015b848110156106ff57604081890312156107eb5760008081fd5b6107f361043c565b6107fc82610542565b8152610809858301610776565b818601528352918301916040016107d2565b60008060008060008060008060008060006101608c8e03121561083d57600080fd5b8b359a5060208c0135995061085460408d01610542565b985061086260608d01610660565b975067ffffffffffffffff8060808e0135111561087e57600080fd5b61088e8e60808f01358f01610698565b97508060a08e013511156108a157600080fd5b6108b18e60a08f01358f0161070a565b96506108bf60c08e01610765565b95508060e08e013511156108d257600080fd5b6108e28e60e08f01358f016104b4565b94506108f16101008e01610776565b9350806101208e0135111561090557600080fd5b6109168e6101208f01358f016104b4565b9250806101408e0135111561092a57600080fd5b5061093c8d6101408e01358e0161078e565b90509295989b509295989b9093969950565b600080600080600080600080610100898b03121561096b57600080fd5b88359750602089013567ffffffffffffffff8082111561098a57600080fd5b6109968c838d01610698565b985060408b01359150808211156109ac57600080fd5b6109b88c838d0161070a565b97506109c660608c01610765565b965060808b01359150808211156109dc57600080fd5b6109e88c838d016104b4565b95506109f660a08c01610776565b945060c08b0135915080821115610a0c57600080fd5b610a188c838d016104b4565b935060e08b0135915080821115610a2e57600080fd5b50610a3b8b828c0161078e565b915050929598509295989093965056fea164736f6c6343000813000a", } var ErroredVerifierABI = ErroredVerifierMetaData.ABI diff --git a/core/gethwrappers/llo-feeds/generated/exposed_channel_verifier/exposed_channel_verifier.go b/core/gethwrappers/llo-feeds/generated/exposed_channel_verifier/exposed_channel_verifier.go deleted file mode 100644 index e516b9a247..0000000000 --- a/core/gethwrappers/llo-feeds/generated/exposed_channel_verifier/exposed_channel_verifier.go +++ /dev/null @@ -1,202 +0,0 @@ -// Code generated - DO NOT EDIT. -// This file is a generated binding and any manual changes will be lost. - -package exposed_channel_verifier - -import ( - "errors" - "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" -) - -var ( - _ = errors.New - _ = big.NewInt - _ = strings.NewReader - _ = ethereum.NotFound - _ = bind.Bind - _ = common.Big1 - _ = types.BloomLookup - _ = event.NewSubscription - _ = abi.ConvertType -) - -var ExposedChannelVerifierMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_chainId\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"_contractAddress\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"_configCount\",\"type\":\"uint64\"},{\"internalType\":\"address[]\",\"name\":\"_signers\",\"type\":\"address[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"_offchainTransmitters\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint8\",\"name\":\"_f\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"_onchainConfig\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"_encodedConfigVersion\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"_encodedConfig\",\"type\":\"bytes\"}],\"name\":\"exposedConfigDigestFromConfigData\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}]", - Bin: "0x608060405234801561001057600080fd5b5061067e806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063b05a355014610030575b600080fd5b61004361003e3660046103f2565b610055565b60405190815260200160405180910390f35b60006100a08b8b8b8b8b8b8b8b8080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152508d92508c91506100af9050565b9b9a5050505050505050505050565b6000808a8a8a8a8a8a8a8a8a6040516020016100d399989796959493929190610594565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291905280516020909101207dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e09000000000000000000000000000000000000000000000000000000000000179150509998505050505050505050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461017e57600080fd5b919050565b803567ffffffffffffffff8116811461017e57600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156102115761021161019b565b604052919050565b600067ffffffffffffffff8211156102335761023361019b565b5060051b60200190565b600082601f83011261024e57600080fd5b8135602061026361025e83610219565b6101ca565b82815260059290921b8401810191818101908684111561028257600080fd5b8286015b848110156102a4576102978161015a565b8352918301918301610286565b509695505050505050565b600082601f8301126102c057600080fd5b813560206102d061025e83610219565b82815260059290921b840181019181810190868411156102ef57600080fd5b8286015b848110156102a457803583529183019183016102f3565b803560ff8116811461017e57600080fd5b60008083601f84011261032d57600080fd5b50813567ffffffffffffffff81111561034557600080fd5b60208301915083602082850101111561035d57600080fd5b9250929050565b600082601f83011261037557600080fd5b813567ffffffffffffffff81111561038f5761038f61019b565b6103c060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116016101ca565b8181528460208386010111156103d557600080fd5b816020850160208301376000918101602001919091529392505050565b6000806000806000806000806000806101208b8d03121561041257600080fd5b8a35995061042260208c0161015a565b985061043060408c01610183565b975060608b013567ffffffffffffffff8082111561044d57600080fd5b6104598e838f0161023d565b985060808d013591508082111561046f57600080fd5b61047b8e838f016102af565b975061048960a08e0161030a565b965060c08d013591508082111561049f57600080fd5b6104ab8e838f0161031b565b90965094508491506104bf60e08e01610183565b93506101008d01359150808211156104d657600080fd5b506104e38d828e01610364565b9150509295989b9194979a5092959850565b600081518084526020808501945080840160005b8381101561052557815187529582019590820190600101610509565b509495945050505050565b6000815180845260005b818110156105565760208185018101518683018201520161053a565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b60006101208083018c8452602073ffffffffffffffffffffffffffffffffffffffff808e168287015267ffffffffffffffff8d1660408701528360608701528293508b5180845261014087019450828d01935060005b818110156106085784518316865294830194938301936001016105ea565b5050505050828103608084015261061f81896104f5565b60ff881660a0850152905082810360c084015261063c8187610530565b67ffffffffffffffff861660e085015290508281036101008401526106618185610530565b9c9b50505050505050505050505056fea164736f6c6343000813000a", -} - -var ExposedChannelVerifierABI = ExposedChannelVerifierMetaData.ABI - -var ExposedChannelVerifierBin = ExposedChannelVerifierMetaData.Bin - -func DeployExposedChannelVerifier(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *ExposedChannelVerifier, error) { - parsed, err := ExposedChannelVerifierMetaData.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(ExposedChannelVerifierBin), backend) - if err != nil { - return common.Address{}, nil, nil, err - } - return address, tx, &ExposedChannelVerifier{address: address, abi: *parsed, ExposedChannelVerifierCaller: ExposedChannelVerifierCaller{contract: contract}, ExposedChannelVerifierTransactor: ExposedChannelVerifierTransactor{contract: contract}, ExposedChannelVerifierFilterer: ExposedChannelVerifierFilterer{contract: contract}}, nil -} - -type ExposedChannelVerifier struct { - address common.Address - abi abi.ABI - ExposedChannelVerifierCaller - ExposedChannelVerifierTransactor - ExposedChannelVerifierFilterer -} - -type ExposedChannelVerifierCaller struct { - contract *bind.BoundContract -} - -type ExposedChannelVerifierTransactor struct { - contract *bind.BoundContract -} - -type ExposedChannelVerifierFilterer struct { - contract *bind.BoundContract -} - -type ExposedChannelVerifierSession struct { - Contract *ExposedChannelVerifier - CallOpts bind.CallOpts - TransactOpts bind.TransactOpts -} - -type ExposedChannelVerifierCallerSession struct { - Contract *ExposedChannelVerifierCaller - CallOpts bind.CallOpts -} - -type ExposedChannelVerifierTransactorSession struct { - Contract *ExposedChannelVerifierTransactor - TransactOpts bind.TransactOpts -} - -type ExposedChannelVerifierRaw struct { - Contract *ExposedChannelVerifier -} - -type ExposedChannelVerifierCallerRaw struct { - Contract *ExposedChannelVerifierCaller -} - -type ExposedChannelVerifierTransactorRaw struct { - Contract *ExposedChannelVerifierTransactor -} - -func NewExposedChannelVerifier(address common.Address, backend bind.ContractBackend) (*ExposedChannelVerifier, error) { - abi, err := abi.JSON(strings.NewReader(ExposedChannelVerifierABI)) - if err != nil { - return nil, err - } - contract, err := bindExposedChannelVerifier(address, backend, backend, backend) - if err != nil { - return nil, err - } - return &ExposedChannelVerifier{address: address, abi: abi, ExposedChannelVerifierCaller: ExposedChannelVerifierCaller{contract: contract}, ExposedChannelVerifierTransactor: ExposedChannelVerifierTransactor{contract: contract}, ExposedChannelVerifierFilterer: ExposedChannelVerifierFilterer{contract: contract}}, nil -} - -func NewExposedChannelVerifierCaller(address common.Address, caller bind.ContractCaller) (*ExposedChannelVerifierCaller, error) { - contract, err := bindExposedChannelVerifier(address, caller, nil, nil) - if err != nil { - return nil, err - } - return &ExposedChannelVerifierCaller{contract: contract}, nil -} - -func NewExposedChannelVerifierTransactor(address common.Address, transactor bind.ContractTransactor) (*ExposedChannelVerifierTransactor, error) { - contract, err := bindExposedChannelVerifier(address, nil, transactor, nil) - if err != nil { - return nil, err - } - return &ExposedChannelVerifierTransactor{contract: contract}, nil -} - -func NewExposedChannelVerifierFilterer(address common.Address, filterer bind.ContractFilterer) (*ExposedChannelVerifierFilterer, error) { - contract, err := bindExposedChannelVerifier(address, nil, nil, filterer) - if err != nil { - return nil, err - } - return &ExposedChannelVerifierFilterer{contract: contract}, nil -} - -func bindExposedChannelVerifier(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { - parsed, err := ExposedChannelVerifierMetaData.GetAbi() - if err != nil { - return nil, err - } - return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil -} - -func (_ExposedChannelVerifier *ExposedChannelVerifierRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { - return _ExposedChannelVerifier.Contract.ExposedChannelVerifierCaller.contract.Call(opts, result, method, params...) -} - -func (_ExposedChannelVerifier *ExposedChannelVerifierRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _ExposedChannelVerifier.Contract.ExposedChannelVerifierTransactor.contract.Transfer(opts) -} - -func (_ExposedChannelVerifier *ExposedChannelVerifierRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _ExposedChannelVerifier.Contract.ExposedChannelVerifierTransactor.contract.Transact(opts, method, params...) -} - -func (_ExposedChannelVerifier *ExposedChannelVerifierCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { - return _ExposedChannelVerifier.Contract.contract.Call(opts, result, method, params...) -} - -func (_ExposedChannelVerifier *ExposedChannelVerifierTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _ExposedChannelVerifier.Contract.contract.Transfer(opts) -} - -func (_ExposedChannelVerifier *ExposedChannelVerifierTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _ExposedChannelVerifier.Contract.contract.Transact(opts, method, params...) -} - -func (_ExposedChannelVerifier *ExposedChannelVerifierCaller) ExposedConfigDigestFromConfigData(opts *bind.CallOpts, _chainId *big.Int, _contractAddress common.Address, _configCount uint64, _signers []common.Address, _offchainTransmitters [][32]byte, _f uint8, _onchainConfig []byte, _encodedConfigVersion uint64, _encodedConfig []byte) ([32]byte, error) { - var out []interface{} - err := _ExposedChannelVerifier.contract.Call(opts, &out, "exposedConfigDigestFromConfigData", _chainId, _contractAddress, _configCount, _signers, _offchainTransmitters, _f, _onchainConfig, _encodedConfigVersion, _encodedConfig) - - if err != nil { - return *new([32]byte), err - } - - out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) - - return out0, err - -} - -func (_ExposedChannelVerifier *ExposedChannelVerifierSession) ExposedConfigDigestFromConfigData(_chainId *big.Int, _contractAddress common.Address, _configCount uint64, _signers []common.Address, _offchainTransmitters [][32]byte, _f uint8, _onchainConfig []byte, _encodedConfigVersion uint64, _encodedConfig []byte) ([32]byte, error) { - return _ExposedChannelVerifier.Contract.ExposedConfigDigestFromConfigData(&_ExposedChannelVerifier.CallOpts, _chainId, _contractAddress, _configCount, _signers, _offchainTransmitters, _f, _onchainConfig, _encodedConfigVersion, _encodedConfig) -} - -func (_ExposedChannelVerifier *ExposedChannelVerifierCallerSession) ExposedConfigDigestFromConfigData(_chainId *big.Int, _contractAddress common.Address, _configCount uint64, _signers []common.Address, _offchainTransmitters [][32]byte, _f uint8, _onchainConfig []byte, _encodedConfigVersion uint64, _encodedConfig []byte) ([32]byte, error) { - return _ExposedChannelVerifier.Contract.ExposedConfigDigestFromConfigData(&_ExposedChannelVerifier.CallOpts, _chainId, _contractAddress, _configCount, _signers, _offchainTransmitters, _f, _onchainConfig, _encodedConfigVersion, _encodedConfig) -} - -func (_ExposedChannelVerifier *ExposedChannelVerifier) Address() common.Address { - return _ExposedChannelVerifier.address -} - -type ExposedChannelVerifierInterface interface { - ExposedConfigDigestFromConfigData(opts *bind.CallOpts, _chainId *big.Int, _contractAddress common.Address, _configCount uint64, _signers []common.Address, _offchainTransmitters [][32]byte, _f uint8, _onchainConfig []byte, _encodedConfigVersion uint64, _encodedConfig []byte) ([32]byte, error) - - Address() common.Address -} diff --git a/core/gethwrappers/llo-feeds/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/core/gethwrappers/llo-feeds/generation/generated-wrapper-dependency-versions-do-not-edit.txt index 729d3a295c..7d7d655ddc 100644 --- a/core/gethwrappers/llo-feeds/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/core/gethwrappers/llo-feeds/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -1,8 +1,13 @@ GETH_VERSION: 1.13.8 -channel_config_store: ../../../contracts/solc/v0.8.19/ChannelConfigStore/ChannelConfigStore.abi ../../../contracts/solc/v0.8.19/ChannelConfigStore/ChannelConfigStore.bin c90e29d9f1a885098982b6175e0447416431b28c605273c807694ac7141e9167 +channel_config_store: ../../../contracts/solc/v0.8.19/ChannelConfigStore/ChannelConfigStore.abi ../../../contracts/solc/v0.8.19/ChannelConfigStore/ChannelConfigStore.bin 3fafe83ea21d50488f5533962f62683988ffa6fd1476dccbbb9040be2369cb37 channel_config_verifier_proxy: ../../../contracts/solc/v0.8.19/ChannelVerifierProxy/ChannelVerifierProxy.abi ../../../contracts/solc/v0.8.19/ChannelVerifierProxy/ChannelVerifierProxy.bin 655658e5f61dfadfe3268de04f948b7e690ad03ca45676e645d6cd6018154661 channel_verifier: ../../../contracts/solc/v0.8.19/ChannelVerifier/ChannelVerifier.abi ../../../contracts/solc/v0.8.19/ChannelVerifier/ChannelVerifier.bin e6020553bd8e3e6b250fcaffe7efd22aea955c8c1a0eb05d282fdeb0ab6550b7 -errored_verifier: ../../../contracts/solc/v0.8.19/ErroredVerifier/ErroredVerifier.abi ../../../contracts/solc/v0.8.19/ErroredVerifier/ErroredVerifier.bin a3e5a77262e13ee30fe8d35551b32a3452d71929e43fd780bbfefeaf4aa62e43 +configurator: ../../../contracts/solc/v0.8.19/Configurator/Configurator.abi ../../../contracts/solc/v0.8.19/Configurator/Configurator.bin 75b9809e6e1cd00d1438028df3bbc7ce00d667dfd882a348081b4286edcdc4e0 +destination_fee_manager: ../../../contracts/solc/v0.8.19/DestinationFeeManager/DestinationFeeManager.abi ../../../contracts/solc/v0.8.19/DestinationFeeManager/DestinationFeeManager.bin a56ae53e35e6610269f086b1e915ca1e80f5d0bf5695d09156e82fccfc2d77b3 +destination_reward_manager: ../../../contracts/solc/v0.8.19/DestinationRewardManager/DestinationRewardManager.abi ../../../contracts/solc/v0.8.19/DestinationRewardManager/DestinationRewardManager.bin 77874e97a54ecbd9c61132964da5b053f0b584dc7b774d75dd51baedd2bc7c40 +destination_verifier: ../../../contracts/solc/v0.8.19/DestinationVerifier/DestinationVerifier.abi ../../../contracts/solc/v0.8.19/DestinationVerifier/DestinationVerifier.bin 369323ce520923b9eb31ed90885f5ebd0f46b6799288fbf4da5d6ede7d697aef +destination_verifier_proxy: ../../../contracts/solc/v0.8.19/DestinationVerifierProxy/DestinationVerifierProxy.abi ../../../contracts/solc/v0.8.19/DestinationVerifierProxy/DestinationVerifierProxy.bin 4e255301cf6657777e7292eccea3e4c0ce65281404341e9248e095703a9fe392 +errored_verifier: ../../../contracts/solc/v0.8.19/ErroredVerifier/ErroredVerifier.abi ../../../contracts/solc/v0.8.19/ErroredVerifier/ErroredVerifier.bin ad8ac8d6b99890081725e2304d79d1ba7dd5212b89d130aa9689f4269eed4691 exposed_channel_verifier: ../../../contracts/solc/v0.8.19/ExposedChannelVerifier/ExposedChannelVerifier.abi ../../../contracts/solc/v0.8.19/ExposedChannelVerifier/ExposedChannelVerifier.bin c21cde078900241c06de69e2bc5d906c5ef558b52db66caa68bed065940a2253 exposed_verifier: ../../../contracts/solc/v0.8.19/ExposedVerifier/ExposedVerifier.abi ../../../contracts/solc/v0.8.19/ExposedVerifier/ExposedVerifier.bin 00816ab345f768e522c79abadeadf9155c2c688067e18f8f73e5d6ab71037663 fee_manager: ../../../contracts/solc/v0.8.19/FeeManager/FeeManager.abi ../../../contracts/solc/v0.8.19/FeeManager/FeeManager.bin edc85f34294ae7c90d45c4c71eb5c105c60a4842dfbbf700c692870ffcc403a1 diff --git a/core/gethwrappers/llo-feeds/go_generate.go b/core/gethwrappers/llo-feeds/go_generate.go index 5e5b841b72..ec950124e9 100644 --- a/core/gethwrappers/llo-feeds/go_generate.go +++ b/core/gethwrappers/llo-feeds/go_generate.go @@ -10,5 +10,9 @@ package gethwrappers //go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/RewardManager/RewardManager.abi ../../../contracts/solc/v0.8.19/RewardManager/RewardManager.bin RewardManager reward_manager //go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/FeeManager/FeeManager.abi ../../../contracts/solc/v0.8.19/FeeManager/FeeManager.bin FeeManager fee_manager //go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/ChannelConfigStore/ChannelConfigStore.abi ../../../contracts/solc/v0.8.19/ChannelConfigStore/ChannelConfigStore.bin ChannelConfigStore channel_config_store -//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/ChannelVerifier/ChannelVerifier.abi ../../../contracts/solc/v0.8.19/ChannelVerifier/ChannelVerifier.bin ChannelVerifier channel_verifier -//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/ExposedChannelVerifier/ExposedChannelVerifier.abi ../../../contracts/solc/v0.8.19/ExposedChannelVerifier/ExposedChannelVerifier.bin ExposedChannelVerifier exposed_channel_verifier + +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/DestinationVerifier/DestinationVerifier.abi ../../../contracts/solc/v0.8.19/DestinationVerifier/DestinationVerifier.bin DestinationVerifier destination_verifier +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/DestinationVerifierProxy/DestinationVerifierProxy.abi ../../../contracts/solc/v0.8.19/DestinationVerifierProxy/DestinationVerifierProxy.bin DestinationVerifierProxy destination_verifier_proxy +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/DestinationFeeManager/DestinationFeeManager.abi ../../../contracts/solc/v0.8.19/DestinationFeeManager/DestinationFeeManager.bin DestinationFeeManager destination_fee_manager +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/DestinationRewardManager/DestinationRewardManager.abi ../../../contracts/solc/v0.8.19/DestinationRewardManager/DestinationRewardManager.bin DestinationRewardManager destination_reward_manager +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/Configurator/Configurator.abi ../../../contracts/solc/v0.8.19/Configurator/Configurator.bin Configurator configurator diff --git a/core/internal/cltest/cltest.go b/core/internal/cltest/cltest.go index 12491300bf..b60dd8d73c 100644 --- a/core/internal/cltest/cltest.go +++ b/core/internal/cltest/cltest.go @@ -212,7 +212,12 @@ type TestApplication struct { func NewApplicationEVMDisabled(t *testing.T) *TestApplication { t.Helper() - c := configtest.NewGeneralConfig(t, nil) + c := configtest.NewGeneralConfig(t, func(config *chainlink.Config, secrets *chainlink.Secrets) { + f := false + for _, c := range config.EVM { + c.Enabled = &f + } + }) return NewApplicationWithConfig(t, c) } @@ -374,7 +379,7 @@ func NewApplicationWithConfig(t testing.TB, cfg chainlink.GeneralConfig, flagsAn keyStore := keystore.NewInMemory(ds, utils.FastScryptParams, lggr) mailMon := mailbox.NewMonitor(cfg.AppID().String(), lggr.Named("Mailbox")) - loopRegistry := plugins.NewLoopRegistry(lggr, nil) + loopRegistry := plugins.NewLoopRegistry(lggr, nil, nil) mercuryPool := wsrpc.NewPool(lggr, cache.Config{ LatestReportTTL: cfg.Mercury().Cache().LatestReportTTL(), @@ -382,12 +387,14 @@ func NewApplicationWithConfig(t testing.TB, cfg chainlink.GeneralConfig, flagsAn LatestReportDeadline: cfg.Mercury().Cache().LatestReportDeadline(), }) + c := clhttptest.NewTestLocalOnlyHTTPClient() relayerFactory := chainlink.RelayerFactory{ Logger: lggr, LoopRegistry: loopRegistry, GRPCOpts: loop.GRPCOpts{}, MercuryPool: mercuryPool, CapabilitiesRegistry: capabilitiesRegistry, + HTTPClient: c, } evmOpts := chainlink.EVMFactoryConfig{ @@ -439,12 +446,18 @@ func NewApplicationWithConfig(t testing.TB, cfg chainlink.GeneralConfig, flagsAn } initOps = append(initOps, chainlink.InitStarknet(testCtx, relayerFactory, starkCfg)) } + if cfg.AptosEnabled() { + aptosCfg := chainlink.AptosFactoryConfig{ + Keystore: keyStore.Aptos(), + TOMLConfigs: cfg.AptosConfigs(), + } + initOps = append(initOps, chainlink.InitAptos(testCtx, relayerFactory, aptosCfg)) + } relayChainInterops, err := chainlink.NewCoreRelayerChainInteroperators(initOps...) if err != nil { t.Fatal(err) } - c := clhttptest.NewTestLocalOnlyHTTPClient() appInstance, err := chainlink.NewApplication(chainlink.ApplicationOpts{ Config: cfg, MailMon: mailMon, @@ -458,7 +471,7 @@ func NewApplicationWithConfig(t testing.TB, cfg chainlink.GeneralConfig, flagsAn RestrictedHTTPClient: c, UnrestrictedHTTPClient: c, SecretGenerator: MockSecretGenerator{}, - LoopRegistry: plugins.NewLoopRegistry(lggr, nil), + LoopRegistry: plugins.NewLoopRegistry(lggr, nil, nil), MercuryPool: mercuryPool, CapabilitiesRegistry: capabilitiesRegistry, CapabilitiesDispatcher: dispatcher, @@ -1376,7 +1389,7 @@ func (b *Blocks) ForkAt(t *testing.T, blockNum int64, numHashes int) *Blocks { } forked.Heads[blockNum].ParentHash = b.Heads[blockNum].ParentHash - forked.Heads[blockNum].Parent = b.Heads[blockNum].Parent + forked.Heads[blockNum].Parent.Store(b.Heads[blockNum].Parent.Load()) return forked } @@ -1390,10 +1403,10 @@ func (b *Blocks) NewHead(number uint64) *evmtypes.Head { Number: parent.Number + 1, Hash: evmutils.NewHash(), ParentHash: parent.Hash, - Parent: parent, Timestamp: time.Unix(parent.Number+1, 0), EVMChainID: ubig.New(&FixtureChainID), } + head.Parent.Store(parent) return head } @@ -1434,7 +1447,7 @@ func NewBlocks(t *testing.T, numHashes int) *Blocks { heads[i] = &evmtypes.Head{Hash: hash, Number: i, Timestamp: time.Unix(i, 0), EVMChainID: ubig.New(&FixtureChainID)} if i > 0 { parent := heads[i-1] - heads[i].Parent = parent + heads[i].Parent.Store(parent) heads[i].ParentHash = parent.Hash } } diff --git a/core/internal/cltest/factories.go b/core/internal/cltest/factories.go index c488dca94a..8f4b2260a0 100644 --- a/core/internal/cltest/factories.go +++ b/core/internal/cltest/factories.go @@ -25,6 +25,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" "github.com/smartcontractkit/chainlink-common/pkg/utils/jsonserializable" + txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" "github.com/smartcontractkit/chainlink/v2/core/auth" @@ -318,13 +319,13 @@ func MustGenerateRandomKeyState(_ testing.TB) ethkey.State { return ethkey.State{Address: NewEIP55Address()} } -func MustInsertHead(t *testing.T, ds sqlutil.DataSource, number int64) evmtypes.Head { +func MustInsertHead(t *testing.T, ds sqlutil.DataSource, number int64) *evmtypes.Head { h := evmtypes.NewHead(big.NewInt(number), evmutils.NewHash(), evmutils.NewHash(), 0, ubig.New(&FixtureChainID)) horm := headtracker.NewORM(FixtureChainID, ds) err := horm.IdempotentInsertHead(testutils.Context(t), &h) require.NoError(t, err) - return h + return &h } func MustInsertV2JobSpec(t *testing.T, db *sqlx.DB, transmitterAddress common.Address) job.Job { diff --git a/core/internal/cltest/mocks.go b/core/internal/cltest/mocks.go index 9e0ee2f3f2..fd01f72c13 100644 --- a/core/internal/cltest/mocks.go +++ b/core/internal/cltest/mocks.go @@ -392,6 +392,7 @@ func NewLegacyChainsWithMockChain(t testing.TB, ethClient evmclient.Client, cfg scopedCfg := evmtest.NewChainScopedConfig(t, cfg) ch.On("ID").Return(scopedCfg.EVM().ChainID()) ch.On("Config").Return(scopedCfg) + ch.On("HeadTracker").Return(nil) return NewLegacyChainsWithChain(ch, cfg) } diff --git a/core/internal/features/features_test.go b/core/internal/features/features_test.go index 7be0307798..a2086d1cb8 100644 --- a/core/internal/features/features_test.go +++ b/core/internal/features/features_test.go @@ -41,6 +41,7 @@ import ( commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink-common/pkg/services/servicetest" + "github.com/smartcontractkit/chainlink/v2/core/auth" "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" diff --git a/core/internal/mocks/prometheus_backend.go b/core/internal/mocks/prometheus_backend.go deleted file mode 100644 index d02f7062cb..0000000000 --- a/core/internal/mocks/prometheus_backend.go +++ /dev/null @@ -1,204 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -package mocks - -import ( - big "math/big" - - mock "github.com/stretchr/testify/mock" -) - -// PrometheusBackend is an autogenerated mock type for the PrometheusBackend type -type PrometheusBackend struct { - mock.Mock -} - -type PrometheusBackend_Expecter struct { - mock *mock.Mock -} - -func (_m *PrometheusBackend) EXPECT() *PrometheusBackend_Expecter { - return &PrometheusBackend_Expecter{mock: &_m.Mock} -} - -// SetMaxUnconfirmedAge provides a mock function with given fields: _a0, _a1 -func (_m *PrometheusBackend) SetMaxUnconfirmedAge(_a0 *big.Int, _a1 float64) { - _m.Called(_a0, _a1) -} - -// PrometheusBackend_SetMaxUnconfirmedAge_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetMaxUnconfirmedAge' -type PrometheusBackend_SetMaxUnconfirmedAge_Call struct { - *mock.Call -} - -// SetMaxUnconfirmedAge is a helper method to define mock.On call -// - _a0 *big.Int -// - _a1 float64 -func (_e *PrometheusBackend_Expecter) SetMaxUnconfirmedAge(_a0 interface{}, _a1 interface{}) *PrometheusBackend_SetMaxUnconfirmedAge_Call { - return &PrometheusBackend_SetMaxUnconfirmedAge_Call{Call: _e.mock.On("SetMaxUnconfirmedAge", _a0, _a1)} -} - -func (_c *PrometheusBackend_SetMaxUnconfirmedAge_Call) Run(run func(_a0 *big.Int, _a1 float64)) *PrometheusBackend_SetMaxUnconfirmedAge_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(*big.Int), args[1].(float64)) - }) - return _c -} - -func (_c *PrometheusBackend_SetMaxUnconfirmedAge_Call) Return() *PrometheusBackend_SetMaxUnconfirmedAge_Call { - _c.Call.Return() - return _c -} - -func (_c *PrometheusBackend_SetMaxUnconfirmedAge_Call) RunAndReturn(run func(*big.Int, float64)) *PrometheusBackend_SetMaxUnconfirmedAge_Call { - _c.Call.Return(run) - return _c -} - -// SetMaxUnconfirmedBlocks provides a mock function with given fields: _a0, _a1 -func (_m *PrometheusBackend) SetMaxUnconfirmedBlocks(_a0 *big.Int, _a1 int64) { - _m.Called(_a0, _a1) -} - -// PrometheusBackend_SetMaxUnconfirmedBlocks_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetMaxUnconfirmedBlocks' -type PrometheusBackend_SetMaxUnconfirmedBlocks_Call struct { - *mock.Call -} - -// SetMaxUnconfirmedBlocks is a helper method to define mock.On call -// - _a0 *big.Int -// - _a1 int64 -func (_e *PrometheusBackend_Expecter) SetMaxUnconfirmedBlocks(_a0 interface{}, _a1 interface{}) *PrometheusBackend_SetMaxUnconfirmedBlocks_Call { - return &PrometheusBackend_SetMaxUnconfirmedBlocks_Call{Call: _e.mock.On("SetMaxUnconfirmedBlocks", _a0, _a1)} -} - -func (_c *PrometheusBackend_SetMaxUnconfirmedBlocks_Call) Run(run func(_a0 *big.Int, _a1 int64)) *PrometheusBackend_SetMaxUnconfirmedBlocks_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(*big.Int), args[1].(int64)) - }) - return _c -} - -func (_c *PrometheusBackend_SetMaxUnconfirmedBlocks_Call) Return() *PrometheusBackend_SetMaxUnconfirmedBlocks_Call { - _c.Call.Return() - return _c -} - -func (_c *PrometheusBackend_SetMaxUnconfirmedBlocks_Call) RunAndReturn(run func(*big.Int, int64)) *PrometheusBackend_SetMaxUnconfirmedBlocks_Call { - _c.Call.Return(run) - return _c -} - -// SetPipelineRunsQueued provides a mock function with given fields: n -func (_m *PrometheusBackend) SetPipelineRunsQueued(n int) { - _m.Called(n) -} - -// PrometheusBackend_SetPipelineRunsQueued_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetPipelineRunsQueued' -type PrometheusBackend_SetPipelineRunsQueued_Call struct { - *mock.Call -} - -// SetPipelineRunsQueued is a helper method to define mock.On call -// - n int -func (_e *PrometheusBackend_Expecter) SetPipelineRunsQueued(n interface{}) *PrometheusBackend_SetPipelineRunsQueued_Call { - return &PrometheusBackend_SetPipelineRunsQueued_Call{Call: _e.mock.On("SetPipelineRunsQueued", n)} -} - -func (_c *PrometheusBackend_SetPipelineRunsQueued_Call) Run(run func(n int)) *PrometheusBackend_SetPipelineRunsQueued_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(int)) - }) - return _c -} - -func (_c *PrometheusBackend_SetPipelineRunsQueued_Call) Return() *PrometheusBackend_SetPipelineRunsQueued_Call { - _c.Call.Return() - return _c -} - -func (_c *PrometheusBackend_SetPipelineRunsQueued_Call) RunAndReturn(run func(int)) *PrometheusBackend_SetPipelineRunsQueued_Call { - _c.Call.Return(run) - return _c -} - -// SetPipelineTaskRunsQueued provides a mock function with given fields: n -func (_m *PrometheusBackend) SetPipelineTaskRunsQueued(n int) { - _m.Called(n) -} - -// PrometheusBackend_SetPipelineTaskRunsQueued_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetPipelineTaskRunsQueued' -type PrometheusBackend_SetPipelineTaskRunsQueued_Call struct { - *mock.Call -} - -// SetPipelineTaskRunsQueued is a helper method to define mock.On call -// - n int -func (_e *PrometheusBackend_Expecter) SetPipelineTaskRunsQueued(n interface{}) *PrometheusBackend_SetPipelineTaskRunsQueued_Call { - return &PrometheusBackend_SetPipelineTaskRunsQueued_Call{Call: _e.mock.On("SetPipelineTaskRunsQueued", n)} -} - -func (_c *PrometheusBackend_SetPipelineTaskRunsQueued_Call) Run(run func(n int)) *PrometheusBackend_SetPipelineTaskRunsQueued_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(int)) - }) - return _c -} - -func (_c *PrometheusBackend_SetPipelineTaskRunsQueued_Call) Return() *PrometheusBackend_SetPipelineTaskRunsQueued_Call { - _c.Call.Return() - return _c -} - -func (_c *PrometheusBackend_SetPipelineTaskRunsQueued_Call) RunAndReturn(run func(int)) *PrometheusBackend_SetPipelineTaskRunsQueued_Call { - _c.Call.Return(run) - return _c -} - -// SetUnconfirmedTransactions provides a mock function with given fields: _a0, _a1 -func (_m *PrometheusBackend) SetUnconfirmedTransactions(_a0 *big.Int, _a1 int64) { - _m.Called(_a0, _a1) -} - -// PrometheusBackend_SetUnconfirmedTransactions_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetUnconfirmedTransactions' -type PrometheusBackend_SetUnconfirmedTransactions_Call struct { - *mock.Call -} - -// SetUnconfirmedTransactions is a helper method to define mock.On call -// - _a0 *big.Int -// - _a1 int64 -func (_e *PrometheusBackend_Expecter) SetUnconfirmedTransactions(_a0 interface{}, _a1 interface{}) *PrometheusBackend_SetUnconfirmedTransactions_Call { - return &PrometheusBackend_SetUnconfirmedTransactions_Call{Call: _e.mock.On("SetUnconfirmedTransactions", _a0, _a1)} -} - -func (_c *PrometheusBackend_SetUnconfirmedTransactions_Call) Run(run func(_a0 *big.Int, _a1 int64)) *PrometheusBackend_SetUnconfirmedTransactions_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(*big.Int), args[1].(int64)) - }) - return _c -} - -func (_c *PrometheusBackend_SetUnconfirmedTransactions_Call) Return() *PrometheusBackend_SetUnconfirmedTransactions_Call { - _c.Call.Return() - return _c -} - -func (_c *PrometheusBackend_SetUnconfirmedTransactions_Call) RunAndReturn(run func(*big.Int, int64)) *PrometheusBackend_SetUnconfirmedTransactions_Call { - _c.Call.Return(run) - return _c -} - -// NewPrometheusBackend creates a new instance of PrometheusBackend. 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 NewPrometheusBackend(t interface { - mock.TestingT - Cleanup(func()) -}) *PrometheusBackend { - mock := &PrometheusBackend{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/core/internal/testutils/testutils.go b/core/internal/testutils/testutils.go index 45609488b4..4e7f9b05c8 100644 --- a/core/internal/testutils/testutils.go +++ b/core/internal/testutils/testutils.go @@ -380,6 +380,19 @@ func WaitForLogMessage(t *testing.T, observedLogs *observer.ObservedLogs, msg st return } +func WaitForLogMessageWithField(t *testing.T, observedLogs *observer.ObservedLogs, msg, field, value string) (le observer.LoggedEntry) { + AssertEventually(t, func() bool { + for _, l := range observedLogs.All() { + if strings.Contains(l.Message, msg) && strings.Contains(l.ContextMap()[field].(string), value) { + le = l + return true + } + } + return false + }) + return +} + // WaitForLogMessageCount waits until at least count log message containing the // specified msg is emitted func WaitForLogMessageCount(t *testing.T, observedLogs *observer.ObservedLogs, msg string, count int) { diff --git a/core/recovery/recover.go b/core/recovery/recover.go index 8e485abc55..61315defa9 100644 --- a/core/recovery/recover.go +++ b/core/recovery/recover.go @@ -3,38 +3,38 @@ package recovery import ( "github.com/getsentry/sentry-go" - "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + + corelogger "github.com/smartcontractkit/chainlink/v2/core/logger" ) func ReportPanics(fn func()) { - defer func() { - if err := recover(); err != nil { - sentry.CurrentHub().Recover(err) - sentry.Flush(logger.SentryFlushDeadline) + HandleFn(fn, func(err any) { + sentry.CurrentHub().Recover(err) + sentry.Flush(corelogger.SentryFlushDeadline) - panic(err) - } - }() - fn() + panic(err) + }) } func WrapRecover(lggr logger.Logger, fn func()) { - defer func() { - if err := recover(); err != nil { - lggr.Recover(err) + WrapRecoverHandle(lggr, fn, nil) +} + +func WrapRecoverHandle(lggr logger.Logger, fn func(), onPanic func(recovered any)) { + HandleFn(fn, func(recovered any) { + logger.Sugared(lggr).Criticalw("Recovered goroutine panic", "panic", recovered) + + if onPanic != nil { + onPanic(recovered) } - }() - fn() + }) } -func WrapRecoverHandle(lggr logger.Logger, fn func(), onPanic func(interface{})) { +func HandleFn(fn func(), onPanic func(recovered any)) { defer func() { - if err := recover(); err != nil { - lggr.Recover(err) - - if onPanic != nil { - onPanic(err) - } + if recovered := recover(); recovered != nil { + onPanic(recovered) } }() fn() diff --git a/core/scripts/ccip/applications/ethsenderreceiver/main.go b/core/scripts/ccip/applications/ethsenderreceiver/main.go index f6acad3e6b..93ba70d358 100644 --- a/core/scripts/ccip/applications/ethsenderreceiver/main.go +++ b/core/scripts/ccip/applications/ethsenderreceiver/main.go @@ -28,6 +28,7 @@ var ( senderABI = abihelpers.MustParseABI(ether_sender_receiver.EtherSenderReceiverABI) ) +// nolint func main() { switch os.Args[1] { case "inject-eth-liquidity": diff --git a/core/scripts/ccip/ccip-revert-reason/main.go b/core/scripts/ccip/ccip-revert-reason/main.go index d8504a0480..e357086306 100644 --- a/core/scripts/ccip/ccip-revert-reason/main.go +++ b/core/scripts/ccip/ccip-revert-reason/main.go @@ -13,7 +13,7 @@ import ( var ( errorCodeString = flag.String("errorCode", "", "Error code string (e.g. 0x08c379a0)") - chainId = flag.Uint64("chainId", 0, "Chain ID for the transaction (e.g. 420)") + chainID = flag.Uint64("chainId", 0, "Chain ID for the transaction (e.g. 420)") txHash = flag.String("txHash", "", "Transaction hash (e.g. 0x97be8559164442595aba46b5f849c23257905b78e72ee43d9b998b28eee78b84)") txRequester = flag.String("txRequester", "", "Transaction requester address (e.g. 0xe88ff73814fb891bb0e149f5578796fa41f20242)") rpcURL = flag.String("rpcURL", "", "RPC URL for the chain (can also be set in env var RPC_)") @@ -28,7 +28,7 @@ func main() { flag.Parse() - if *errorCodeString == "" && (*chainId == 0 || *txHash == "" || *txRequester == "") { + if *errorCodeString == "" && (*chainID == 0 || *txHash == "" || *txRequester == "") { flag.Usage() return } @@ -53,8 +53,8 @@ func getErrorString() (string, error) { } if *rpcURL == "" { - fmt.Printf("RPC URL not provided, looking for RPC_%d env var\n", *chainId) - envRPC := secrets.GetRPC(*chainId) + fmt.Printf("RPC URL not provided, looking for RPC_%d env var\n", *chainID) + envRPC := secrets.GetRPC(*chainID) rpcURL = &envRPC } diff --git a/core/scripts/ccip/debugreceiver/main.go b/core/scripts/ccip/debugreceiver/main.go index 5ea6084515..db742ed126 100644 --- a/core/scripts/ccip/debugreceiver/main.go +++ b/core/scripts/ccip/debugreceiver/main.go @@ -15,7 +15,7 @@ import ( ) type ccipAny struct { - SourceChainId *big.Int + SourceChainID *big.Int Sender []byte Data []byte Tokens []common.Address @@ -87,7 +87,7 @@ func main() { log[0].Data) panicErr(err) send := encodedMsg[0].(struct { - SourceChainId *big.Int `json:"sourceChainId"` + SourceChainID *big.Int `json:"sourceChainId"` SequenceNumber uint64 `json:"sequenceNumber"` Sender common.Address `json:"sender"` Receiver common.Address `json:"receiver"` @@ -106,7 +106,7 @@ func main() { {"internalType":"address[]","name":"tokens","type":"address[]"}, {"internalType":"uint256[]","name":"amounts","type":"uint256[]"}], "internalType":"structCCIP.Any2EVMMessage","name":"message","type":"tuple"}]`, - ccipAny{send.SourceChainId, sender, send.Data, send.Tokens, send.Amounts}) + ccipAny{send.SourceChainID, sender, send.Data, send.Tokens, send.Amounts}) panicErr(err) a, err := dest.CallContract(context.Background(), ethereum.CallMsg{ From: common.HexToAddress("0x2b7ab40413da5077e168546ea376920591aee8e7"), // offramp router diff --git a/core/scripts/ccip/liquiditymanager/arb/finalize.go b/core/scripts/ccip/liquiditymanager/arb/finalize.go index d4a97e2083..38b476a14f 100644 --- a/core/scripts/ccip/liquiditymanager/arb/finalize.go +++ b/core/scripts/ccip/liquiditymanager/arb/finalize.go @@ -35,6 +35,7 @@ var ( NodeConfirmedTopic = arbitrum_rollup_core.ArbRollupCoreNodeConfirmed{}.Topic() ) +// nolint // function executeTransaction( // // bytes32[] calldata proof, diff --git a/core/scripts/ccip/liquiditymanager/arb/send_to_l2.go b/core/scripts/ccip/liquiditymanager/arb/send_to_l2.go index afec433168..7faf797521 100644 --- a/core/scripts/ccip/liquiditymanager/arb/send_to_l2.go +++ b/core/scripts/ccip/liquiditymanager/arb/send_to_l2.go @@ -29,6 +29,7 @@ var ( nodeInterfaceABI = abihelpers.MustParseABI(arb_node_interface.NodeInterfaceMetaData.ABI) ) +// nolint func SendToL2( env multienv.Env, l1ChainID, @@ -237,6 +238,7 @@ func estimateAll(env multienv.Env, l1ChainID, l2ChainID uint64, rd RetryableData } } +// nolint func estimateRetryableGasLimit(l2Client *ethclient.Client, l2ChainID uint64, rd RetryableData) *big.Int { packed, err := nodeInterfaceABI.Pack("estimateRetryableTicket", rd.From, @@ -269,6 +271,7 @@ func estimateMaxFeePerGasOnL2(l2Client *ethclient.Client) *big.Int { return l2BaseFee } +// nolint func estimateSubmissionFee(l1Client *ethclient.Client, l1ChainID uint64, l1BaseFee *big.Int, calldataSize uint64) *big.Int { inbox, err := arbitrum_inbox.NewArbitrumInbox(ArbitrumContracts[l1ChainID]["L1Inbox"], l1Client) helpers.PanicErr(err) diff --git a/core/scripts/ccip/liquiditymanager/arb/withdraw.go b/core/scripts/ccip/liquiditymanager/arb/withdraw.go index b283e5d063..e75aeead35 100644 --- a/core/scripts/ccip/liquiditymanager/arb/withdraw.go +++ b/core/scripts/ccip/liquiditymanager/arb/withdraw.go @@ -13,6 +13,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/liquiditymanager/generated/arbitrum_l2_bridge_adapter" ) +// nolint func WithdrawFromL2( env multienv.Env, l2ChainID uint64, diff --git a/core/scripts/ccip/liquiditymanager/multienv/multienv.go b/core/scripts/ccip/liquiditymanager/multienv/multienv.go index a198177505..4fc320316d 100644 --- a/core/scripts/ccip/liquiditymanager/multienv/multienv.go +++ b/core/scripts/ccip/liquiditymanager/multienv/multienv.go @@ -19,12 +19,12 @@ import ( // Environment variables used to configure // the environment for the rebalancer const ( - // OWNER_KEY is the private key used to deploy contracts and send funds to the rebalancer nodes - OWNER_KEY = "OWNER_KEY" - // RPC_ is the prefix for the environment variable that contains the RPC URL for a chain - RPC_ = "RPC_" - // WS_ is the prefix for the environment variable that contains the WebSocket URL for a chain - WS_ = "WS_" + // OwnerKey is the private key used to deploy contracts and send funds to the rebalancer nodes + OwnerKey = "OWNER_KEY" + // RPCPrefix is the prefix for the environment variable that contains the RPC URL for a chain + RPCPrefix = "RPC_" + // WebSocketPerfix is the prefix for the environment variable that contains the WebSocket URL for a chain + WebSocketPerfix = "WS_" ) type Env struct { @@ -35,6 +35,7 @@ type Env struct { WSURLs map[uint64]string } +// nolint func New(websocket bool, overrideNonce bool) Env { env := Env{ Transactors: make(map[uint64]*bind.TransactOpts), @@ -65,7 +66,7 @@ func New(websocket bool, overrideNonce bool) Env { } func GetRPC(chainID uint64) (string, error) { - envVariable := RPC_ + strconv.FormatUint(chainID, 10) + envVariable := RPCPrefix + strconv.FormatUint(chainID, 10) rpc := os.Getenv(envVariable) if rpc != "" { return rpc, nil @@ -74,7 +75,7 @@ func GetRPC(chainID uint64) (string, error) { } func GetWS(chainID uint64) (string, error) { - envVariable := WS_ + strconv.FormatUint(chainID, 10) + envVariable := WebSocketPerfix + strconv.FormatUint(chainID, 10) ws := os.Getenv(envVariable) if ws != "" { return ws, nil @@ -83,7 +84,7 @@ func GetWS(chainID uint64) (string, error) { } func GetTransactor(chainID *big.Int) *bind.TransactOpts { - ownerKey := os.Getenv(OWNER_KEY) + ownerKey := os.Getenv(OwnerKey) if ownerKey != "" { b, err := hex.DecodeString(ownerKey) if err != nil { diff --git a/core/scripts/ccip/liquiditymanager/opstack/finalize.go b/core/scripts/ccip/liquiditymanager/opstack/finalize.go index 4422b2656e..4966796940 100644 --- a/core/scripts/ccip/liquiditymanager/opstack/finalize.go +++ b/core/scripts/ccip/liquiditymanager/opstack/finalize.go @@ -22,6 +22,7 @@ const ( FinalizationActionFinalizeWithdrawal uint8 = 1 ) +// nolint func FinalizeL1( env multienv.Env, l1ChainID, @@ -55,6 +56,7 @@ func FinalizeL1( helpers.ConfirmTXMined(context.Background(), env.Clients[l1ChainID], tx, int64(l1ChainID), "FinalizeWithdrawalTransaction") } +// nolint func FinalizeWithdrawalViaRebalancer( env multienv.Env, l1ChainID, diff --git a/core/scripts/ccip/liquiditymanager/opstack/prove_withdrawal.go b/core/scripts/ccip/liquiditymanager/opstack/prove_withdrawal.go index 3eed3cba70..fe479a49e2 100644 --- a/core/scripts/ccip/liquiditymanager/opstack/prove_withdrawal.go +++ b/core/scripts/ccip/liquiditymanager/opstack/prove_withdrawal.go @@ -25,6 +25,7 @@ var ( encoderABI = abihelpers.MustParseABI(optimism_l1_bridge_adapter_encoder.OptimismL1BridgeAdapterEncoderMetaData.ABI) ) +// nolint func ProveWithdrawal( env multienv.Env, l1ChainID, @@ -57,6 +58,7 @@ func ProveWithdrawal( helpers.ConfirmTXMined(context.Background(), l1Client, tx, int64(l1ChainID), "ProveWithdrawal") } +// nolint func ProveWithdrawalViaRebalancer( env multienv.Env, l1ChainID, diff --git a/core/scripts/ccip/liquiditymanager/opstack/send_to_l2.go b/core/scripts/ccip/liquiditymanager/opstack/send_to_l2.go index 22a280858c..1aa93804bf 100644 --- a/core/scripts/ccip/liquiditymanager/opstack/send_to_l2.go +++ b/core/scripts/ccip/liquiditymanager/opstack/send_to_l2.go @@ -23,6 +23,7 @@ var ( rebalancerABI = abihelpers.MustParseABI(liquiditymanager.LiquidityManagerMetaData.ABI) ) +// nolint func SendToL2( env multienv.Env, l1ChainID uint64, @@ -115,6 +116,7 @@ func scaleGasCost(gasCost uint64) uint64 { return gasCost * 18 / 10 } +// nolint func SendToL2ViaRebalancer( env multienv.Env, l1ChainID, diff --git a/core/scripts/ccip/liquiditymanager/opstack/withdraw.go b/core/scripts/ccip/liquiditymanager/opstack/withdraw.go index 18f0620e5f..2cc42ea2b6 100644 --- a/core/scripts/ccip/liquiditymanager/opstack/withdraw.go +++ b/core/scripts/ccip/liquiditymanager/opstack/withdraw.go @@ -16,6 +16,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/erc20" ) +// nolint func WithdrawFromL2( env multienv.Env, l2ChainID uint64, @@ -62,6 +63,7 @@ func WithdrawFromL2( helpers.ConfirmTXMined(context.Background(), env.Clients[l2ChainID], tx, int64(l2ChainID), "WithdrawFromL2") } +// nolint func WithdrawFromL2ViaRebalancer( env multienv.Env, l2ChainID, diff --git a/core/scripts/ccip/revert-reason/handler/reason.go b/core/scripts/ccip/revert-reason/handler/reason.go index 7256b85856..d705774b76 100644 --- a/core/scripts/ccip/revert-reason/handler/reason.go +++ b/core/scripts/ccip/revert-reason/handler/reason.go @@ -44,13 +44,13 @@ func (h *BaseHandler) RevertReasonFromErrorCodeString(errorCodeString string) (s func (h *BaseHandler) RevertReasonFromTx(txHash string) (string, error) { // Need a node URL // NOTE: this node needs to run in archive mode - ethUrl := h.cfg.NodeURL - if ethUrl == "" { + ethURL := h.cfg.NodeURL + if ethURL == "" { panicErr(errors.New("you must define ETH_NODE env variable")) } requester := h.cfg.FromAddress - ec, err := ethclient.Dial(ethUrl) + ec, err := ethclient.Dial(ethURL) panicErr(err) errorString, _ := GetErrorForTx(ec, txHash, requester) diff --git a/core/scripts/chaincli/handler/debug.go b/core/scripts/chaincli/handler/debug.go index eac3233ea5..79dfc5d018 100644 --- a/core/scripts/chaincli/handler/debug.go +++ b/core/scripts/chaincli/handler/debug.go @@ -43,7 +43,8 @@ import ( const ( ConditionTrigger uint8 = iota LogTrigger - expectedTypeAndVersion = "KeeperRegistry 2.1.0" + expectedVersion21 = "KeeperRegistry 2.1.0" + expectedVersion23 = "AutomationRegistry 2.3.0" ) var mercuryPacker = mercury.NewAbiPacker() @@ -85,8 +86,8 @@ func (k *Keeper) Debug(ctx context.Context, args []string) { if err != nil { failCheckConfig("failed to get typeAndVersion: make sure your registry contract address and archive node are valid", err) } - if typeAndVersion != expectedTypeAndVersion { - failCheckConfig(fmt.Sprintf("invalid registry contract: this command can only debug %s, got: %s", expectedTypeAndVersion, typeAndVersion), nil) + if typeAndVersion != expectedVersion21 && typeAndVersion != expectedVersion23 { + failCheckConfig(fmt.Sprintf("invalid registry contract: this command can only debug %s or %s, got: %s", expectedVersion21, expectedVersion23, typeAndVersion), nil) } // get upkeepID from command args upkeepID := big.NewInt(0) diff --git a/core/scripts/gateway/connector/run_connector.go b/core/scripts/gateway/connector/run_connector.go index 8d74bb88ae..2a2445c1d9 100644 --- a/core/scripts/gateway/connector/run_connector.go +++ b/core/scripts/gateway/connector/run_connector.go @@ -69,7 +69,13 @@ func main() { sampleKey, _ := crypto.HexToECDSA("cd47d3fafdbd652dd2b66c6104fa79b372c13cb01f4a4fbfc36107cce913ac1d") lggr, _ := logger.NewLogger() client := &client{privateKey: sampleKey, lggr: lggr} - connector, _ := connector.NewGatewayConnector(&cfg, client, client, clockwork.NewRealClock(), lggr) + // client acts as a signer here + connector, _ := connector.NewGatewayConnector(&cfg, client, clockwork.NewRealClock(), lggr) + err = connector.AddHandler([]string{"test_method"}, client) + if err != nil { + fmt.Println("error adding handler:", err) + return + } client.connector = connector ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt) diff --git a/core/scripts/go.mod b/core/scripts/go.mod index 3574bae837..6384d8f6b6 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -7,27 +7,27 @@ replace github.com/smartcontractkit/chainlink/v2 => ../../ require ( github.com/docker/docker v24.0.7+incompatible - github.com/docker/go-connections v0.4.0 + github.com/docker/go-connections v0.5.0 github.com/ethereum/go-ethereum v1.13.8 github.com/gkampitakis/go-snaps v0.5.4 github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.6.0 github.com/jmoiron/sqlx v1.4.0 - github.com/joho/godotenv v1.4.0 + github.com/joho/godotenv v1.5.1 github.com/jonboulle/clockwork v0.4.0 github.com/manyminds/api2go v0.0.0-20171030193247-e7b693844a6f github.com/montanaflynn/stats v0.7.1 github.com/olekukonko/tablewriter v0.0.5 - github.com/pelletier/go-toml/v2 v2.2.0 + github.com/pelletier/go-toml/v2 v2.2.2 github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.17.0 + github.com/prometheus/client_golang v1.20.0 github.com/shopspring/decimal v1.4.0 github.com/smartcontractkit/chain-selectors v1.0.27 github.com/smartcontractkit/chainlink-automation v1.0.4 - github.com/smartcontractkit/chainlink-common v0.2.2-0.20240723123524-e407ecd120b1 + github.com/smartcontractkit/chainlink-common v0.2.3-0.20240925085218-aded1b263ecc 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 + github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.18.2 github.com/stretchr/testify v1.9.0 github.com/umbracle/ethgo v0.1.3 @@ -99,7 +99,7 @@ require ( github.com/cosmos/ibc-go/v7 v7.5.1 // indirect github.com/cosmos/ics23/go v0.10.0 // indirect github.com/cosmos/ledger-cosmos-go v0.12.4 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 // indirect github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect github.com/danieljoos/wincred v1.1.2 // indirect @@ -119,10 +119,10 @@ require ( github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/esote/minmaxheap v1.0.0 // indirect github.com/ethereum/c-kzg-4844 v0.4.0 // indirect - github.com/fatih/color v1.16.0 // indirect + github.com/fatih/color v1.17.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/fxamacker/cbor/v2 v2.6.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gagliardetto/binary v0.7.7 // indirect github.com/gagliardetto/solana-go v1.8.4 // indirect github.com/gagliardetto/treeout v0.1.4 // indirect @@ -152,7 +152,8 @@ require ( github.com/go-openapi/swag v0.22.4 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.15.5 // indirect + github.com/go-playground/validator/v10 v10.22.0 // indirect + github.com/go-viper/mapstructure/v2 v2.1.0 // indirect github.com/go-webauthn/webauthn v0.9.4 // indirect github.com/go-webauthn/x v0.1.5 // indirect github.com/goccy/go-json v0.10.2 // indirect @@ -168,20 +169,20 @@ require ( github.com/google/gnostic-models v0.6.8 // 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-20231023181126-ff6d637d2a7b // indirect + github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/securecookie v1.1.2 // indirect github.com/gorilla/sessions v1.2.2 // indirect github.com/gorilla/websocket v1.5.1 // indirect github.com/grafana/pyroscope-go v1.1.1 // indirect - github.com/grafana/pyroscope-go/godeltaprof v0.1.6 // indirect + github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect github.com/graph-gophers/dataloader v5.0.0+incompatible // indirect - github.com/graph-gophers/graphql-go v1.3.0 // indirect + github.com/graph-gophers/graphql-go v1.5.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/gtank/merlin v0.1.1 // indirect github.com/gtank/ristretto255 v0.1.2 // indirect @@ -190,7 +191,7 @@ require ( github.com/hashicorp/go-envparse v0.1.0 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect - github.com/hashicorp/go-plugin v1.6.0 // indirect + github.com/hashicorp/go-plugin v1.6.2-0.20240829161738-06afb6d7ae99 // indirect github.com/hashicorp/go-retryablehttp v0.7.5 // indirect github.com/hashicorp/golang-lru v0.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect @@ -216,13 +217,13 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/jpillora/backoff v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.17.3 // indirect + github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/leanovate/gopter v0.2.10-0.20210127095200-9abe2343507a // indirect - github.com/leodido/go-urn v1.2.4 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/linxGnu/grocksdb v1.7.16 // indirect @@ -233,7 +234,6 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/mfridman/interpolate v0.0.2 // indirect github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect @@ -246,7 +246,6 @@ require ( github.com/mr-tron/base58 v1.2.0 // indirect github.com/mtibben/percent v0.2.1 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/mwitkow/grpc-proxy v0.0.0-20230212185441-f345521cb9c9 // indirect github.com/oklog/run v1.1.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc5 // indirect @@ -257,9 +256,9 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/pressly/goose/v3 v3.21.1 // indirect - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.45.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.59.1 // indirect + github.com/prometheus/procfs v0.15.1 // indirect github.com/prometheus/prometheus v0.48.1 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rivo/uniseg v0.4.4 // indirect @@ -274,14 +273,15 @@ require ( github.com/sethvargo/go-retry v0.2.4 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/shirou/gopsutil/v3 v3.24.3 // indirect - github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 // indirect - github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240718160222-2dc0c8136bfa // indirect - github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 // indirect - github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e // indirect - github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 // indirect + github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 // indirect + github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240916152957-433914114bd2 // indirect + github.com/smartcontractkit/chainlink-feeds v0.0.0-20240910155501-42f20443189f // indirect + github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240911182932-3c609a6ac664 // indirect + github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240911194142-506bc469d8ae // indirect + github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // 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 - github.com/smartcontractkit/wsrpc v0.7.3 // indirect + github.com/smartcontractkit/wsrpc v0.8.2 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect @@ -322,30 +322,38 @@ require ( go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.49.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240823153156-2a54df7bffb9 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.4.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0 // indirect + go.opentelemetry.io/otel/log v0.4.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/sdk v1.28.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.4.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect 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/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/time v0.5.0 // indirect - golang.org/x/tools v0.23.0 // indirect + golang.org/x/crypto v0.27.0 // indirect + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect + golang.org/x/mod v0.21.0 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/oauth2 v0.22.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/term v0.24.0 // indirect + golang.org/x/text v0.18.0 // indirect + golang.org/x/time v0.6.0 // indirect + golang.org/x/tools v0.25.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 - google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect google.golang.org/grpc v1.65.0 // indirect gopkg.in/guregu/null.v4 v4.0.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect @@ -364,14 +372,11 @@ require ( ) replace ( + // until merged upstream: https://github.com/omissis/go-jsonschema/pull/264 + github.com/atombender/go-jsonschema => github.com/nolag/go-jsonschema v0.16.0-rtinianov + // replicating the replace directive on cosmos SDK github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 - - // until merged upstream: https://github.com/hashicorp/go-plugin/pull/257 - github.com/hashicorp/go-plugin => github.com/smartcontractkit/go-plugin v0.0.0-20240208201424-b3b91517de16 - - // until merged upstream: https://github.com/mwitkow/grpc-proxy/pull/69 - github.com/mwitkow/grpc-proxy => github.com/smartcontractkit/grpc-proxy v0.0.0-20230731113816-f1be6620749f ) -exclude github.com/sourcegraph/sourcegraph/lib v0.0.0-20221216004406-749998a2ac74 +replace github.com/sourcegraph/sourcegraph/lib => github.com/sourcegraph/sourcegraph-public-snapshot/lib v0.0.0-20240822153003-c864f15af264 diff --git a/core/scripts/go.sum b/core/scripts/go.sum index 2b01d2bcd8..8688b12749 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -276,8 +276,8 @@ github.com/cosmos/rosetta-sdk-go v0.10.0/go.mod h1:SImAZkb96YbwvoRkzSMQB6noNJXFg 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.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -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= @@ -324,8 +324,8 @@ github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m3 github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo= @@ -351,8 +351,8 @@ github.com/ethereum/go-ethereum v1.13.8/go.mod h1:sc48XYQxCzH3fG9BcrXCOOgQk2JfZz github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= @@ -366,10 +366,10 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= 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.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA= -github.com/fxamacker/cbor/v2 v2.6.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/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.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gagliardetto/binary v0.7.7 h1:QZpT38+sgoPg+TIQjH94sLbl/vX+nlIRA37pEyOsjfY= github.com/gagliardetto/binary v0.7.7/go.mod h1:mUuay5LL8wFVnIlecHakSZMvcdqfs+CsotR5n77kyjM= github.com/gagliardetto/gofuzz v1.2.2 h1:XL/8qDMzcgvR4+CyRQW9UGdwPRPMHVJfqQ/uMvSUuQw= @@ -432,6 +432,7 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 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= @@ -456,14 +457,16 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= -github.com/go-playground/validator/v10 v10.15.5 h1:LEBecTWb/1j5TNY1YYG2RcOUN3R7NLylN+x8TTueE24= -github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= +github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/go-viper/mapstructure/v2 v2.1.0 h1:gHnMa2Y/pIxElCH2GlZZ1lZSsn6XMtufpGyP1XxdC/w= +github.com/go-viper/mapstructure/v2 v2.1.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-webauthn/webauthn v0.9.4 h1:YxvHSqgUyc5AK2pZbqkWWR55qKeDPhP8zLDr6lpIc2g= github.com/go-webauthn/webauthn v0.9.4/go.mod h1:LqupCtzSef38FcxzaklmOn7AykGKhAhr9xlRbdbgnTw= github.com/go-webauthn/x v0.1.5 h1:V2TCzDU2TGLd0kSZOXdrqDVV5JB9ILnKxA9S53CSBw0= @@ -539,6 +542,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -557,14 +561,13 @@ github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b h1:RMpPgZTSApbPf7xaVel+QkoGPRLFLrwFO89uDUHEGf0= -github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +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/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -591,12 +594,12 @@ github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/ github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/grafana/pyroscope-go v1.1.1 h1:PQoUU9oWtO3ve/fgIiklYuGilvsm8qaGhlY4Vw6MAcQ= github.com/grafana/pyroscope-go v1.1.1/go.mod h1:Mw26jU7jsL/KStNSGGuuVYdUq7Qghem5P8aXYXSXG88= -github.com/grafana/pyroscope-go/godeltaprof v0.1.6 h1:nEdZ8louGAplSvIJi1HVp7kWvFvdiiYg3COLlTwJiFo= -github.com/grafana/pyroscope-go/godeltaprof v0.1.6/go.mod h1:Tk376Nbldo4Cha9RgiU7ik8WKFkNpfds98aUzS8omLE= +github.com/grafana/pyroscope-go/godeltaprof v0.1.8 h1:iwOtYXeeVSAeYefJNaxDytgjKtUuKQbJqgAIjlnicKg= +github.com/grafana/pyroscope-go/godeltaprof v0.1.8/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= github.com/graph-gophers/dataloader v5.0.0+incompatible h1:R+yjsbrNq1Mo3aPG+Z/EKYrXrXXUNJHOgbRt+U6jOug= github.com/graph-gophers/dataloader v5.0.0+incompatible/go.mod h1:jk4jk0c5ZISbKaMe8WsVopGB5/15GvGHMdMdPtwlRp4= -github.com/graph-gophers/graphql-go v1.3.0 h1:Eb9x/q6MFpCLz7jBCiP/WTxjSDrYLR1QY41SORZyNJ0= -github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +github.com/graph-gophers/graphql-go v1.5.0 h1:fDqblo50TEpD0LY7RXk/LFVYEVqo3+tXMNMPSVXA1yc= +github.com/graph-gophers/graphql-go v1.5.0/go.mod h1:YtmJZDLbF1YYNrlNAuiO5zAStUWc3XZT07iGsVqe1Os= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= @@ -608,8 +611,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= @@ -640,6 +643,8 @@ github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJ github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-plugin v1.6.2-0.20240829161738-06afb6d7ae99 h1:OSQYEsRT3tRttZkk6zyC3aAaliwd7Loi/KgXgXxGtwA= +github.com/hashicorp/go-plugin v1.6.2-0.20240829161738-06afb6d7ae99/go.mod h1:CkgLQ5CZqNmdL9U9JzM532t8ZiYQ35+pj3b1FD37R0Q= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= @@ -756,8 +761,8 @@ github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= -github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= -github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= @@ -780,8 +785,8 @@ github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6 github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.17.3 h1:qkRjuerhUU1EmXLYGkSH6EZL+vPSxIrYjLNAK4slzwA= -github.com/klauspost/compress v1.17.3/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= @@ -804,8 +809,8 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+ github.com/leanovate/gopter v0.2.10-0.20210127095200-9abe2343507a h1:dHCfT5W7gghzPtfsW488uPmEOm85wewI+ypUwibyTdU= github.com/leanovate/gopter v0.2.10-0.20210127095200-9abe2343507a/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -856,8 +861,6 @@ github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxU github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= @@ -937,8 +940,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J 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= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE= -github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= @@ -959,8 +962,8 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9 github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= -github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= -github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 h1:hDSdbBuw3Lefr6R18ax0tZ2BJeNB3NehB3trOwYBsdU= github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= @@ -983,26 +986,26 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= -github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= -github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= +github.com/prometheus/client_golang v1.20.0 h1:jBzTZ7B099Rg24tny+qngoynol8LtVYlA2bqx3vEloI= +github.com/prometheus/client_golang v1.20.0/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= -github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0= +github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/prometheus/prometheus v0.48.1 h1:CTszphSNTXkuCG6O0IfpKdHcJkvvnAAE1GbELKS+NFk= github.com/prometheus/prometheus v0.48.1/go.mod h1:SRw624aMAxTfryAcP8rOjg4S/sHHaetx2lyJJ2nM83g= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= @@ -1074,30 +1077,28 @@ github.com/smartcontractkit/chain-selectors v1.0.27 h1:VE/ftX9Aae4gnw67yR1raKi+3 github.com/smartcontractkit/chain-selectors v1.0.27/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240723123524-e407ecd120b1 h1:pdEpjgbZ5w/Sd5lzg/XiuC5gVyrmSovOo+3nUD46SP8= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240723123524-e407ecd120b1/go.mod h1:Jg1sCTsbxg76YByI8ifpFby3FvVqISStHT8ypy9ocmY= -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-20240718160222-2dc0c8136bfa h1:g75H8oh2ws52s8BekwvGQ9XvBVu3E7WM1rfiA0PN0zk= -github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240718160222-2dc0c8136bfa/go.mod h1:wZvLHX/Sd9hskN51016cTFcT3G62KXVa6xbVDS7tRjc= -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.0.3-0.20240712132946-267a37c5ac6e h1:PzwzlHNv1YbJ6ZIdl/pIFRoOuOS4V4WLvjZvFUnZFL4= -github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e/go.mod h1:hsFhop+SlQHKD+DEFjZrMJmbauT1A/wvtZIeeo4PxFU= -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= -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/chainlink-common v0.2.3-0.20240925085218-aded1b263ecc h1:ALbyaoRzUSXQ2NhGFKVOyJqO22IB5yQjhjKWbIZGbrI= +github.com/smartcontractkit/chainlink-common v0.2.3-0.20240925085218-aded1b263ecc/go.mod h1:F6WUS6N4mP5ScwpwyTyAJc9/vjR+GXbMCRUOVekQi1g= +github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 h1:lTGIOQYLk1Ufn++X/AvZnt6VOcuhste5yp+C157No/Q= +github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7/go.mod h1:BMYE1vC/pGmdFSsOJdPrAA0/4gZ0Xo0SxTMdGspBtRo= +github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240916152957-433914114bd2 h1:yRk4ektpx/UxwarqAfgxUXLrsYXlaNeP1NOwzHGrK2Q= +github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240916152957-433914114bd2/go.mod h1:rNhNSrrRMvkgAm5SA6bNTdh2340bTQQZdUVNtZ2o2bk= +github.com/smartcontractkit/chainlink-feeds v0.0.0-20240910155501-42f20443189f h1:p4p3jBT91EQyLuAMvHD+zNJsuAYI/QjJbzuGUJ7wIgg= +github.com/smartcontractkit/chainlink-feeds v0.0.0-20240910155501-42f20443189f/go.mod h1:FLlWBt2hwiMVgt9AcSo6wBJYIRd/nsc8ENbV1Wir1bw= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240911182932-3c609a6ac664 h1:JPs35oSO07PK3Qv7Kyv0GJHVLacIE1IkrvefaPyBjKs= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240911182932-3c609a6ac664/go.mod h1:iJ9DKYo0F64ue7IogAIELwU2DfrhEAh76eSmZOilT8A= +github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240911194142-506bc469d8ae h1:d+B8y2Nd/PrnPMNoaSPn3eDgUgxcVcIqAxGrvYu/gGw= +github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240911194142-506bc469d8ae/go.mod h1:ec/a20UZ7YRK4oxJcnTBFzp1+DBcJcwqEaerUMsktMs= +github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12ijqMM9tvYVEm+nR826WsrNi6zCKpwBhuApq127wHs= +github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7/go.mod h1:FX7/bVdoep147QQhsOPkYsPEXhGZjeYx6lBSaSXtZOA= github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7 h1:e38V5FYE7DA1JfKXeD5Buo/7lczALuVXlJ8YNTAUxcw= github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7/go.mod h1:fb1ZDVXACvu4frX3APHZaEBp0xi1DIm34DcA0CwTsZM= github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1 h1:yiKnypAqP8l0OX0P3klzZ7SCcBUxy5KqTAKZmQOvSQE= github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1/go.mod h1:q6f4fe39oZPdsh1i57WznEZgxd8siidMaSFq3wdPmVg= github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20230906073235-9e478e5e19f1 h1:Dai1bn+Q5cpeGMQwRdjOdVjG8mmFFROVkSKuUgBErRQ= github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20230906073235-9e478e5e19f1/go.mod h1:G5Sd/yzHWf26rQ+X0nG9E0buKPqRGPMJAfk2gwCzOOw= -github.com/smartcontractkit/wsrpc v0.7.3 h1:CKYZfawZShZGfvsQep1F9oBansnFk9ByZPCdTMpLphw= -github.com/smartcontractkit/wsrpc v0.7.3/go.mod h1:sj7QX2NQibhkhxTfs3KOhAj/5xwgqMipTvJVSssT9i0= +github.com/smartcontractkit/wsrpc v0.8.2 h1:XB/xcn/MMseHW+8JE8+a/rceA86ck7Ur6cEa9LiUC8M= +github.com/smartcontractkit/wsrpc v0.8.2/go.mod h1:2u/wfnhl5R4RlSXseN4n6HHIWk8w1Am3AT6gWftQbNg= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -1114,8 +1115,8 @@ github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= 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/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -1146,7 +1147,6 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= @@ -1271,18 +1271,34 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIX go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= go.opentelemetry.io/contrib/propagators/b3 v1.24.0 h1:n4xwCdTx3pZqZs2CjS/CUZAs03y3dZcGhC/FepKtEUY= go.opentelemetry.io/contrib/propagators/b3 v1.24.0/go.mod h1:k5wRxKRU2uXx2F8uNJ4TaonuEO/V7/5xoz7kdsDACT8= +go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI= go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240823153156-2a54df7bffb9 h1:UiRNKd1OgqsLbFwE+wkAWTdiAxXtCBqKIHeBIse4FUA= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240823153156-2a54df7bffb9/go.mod h1:eqZlW3pJWhjyexnDPrdQxix1pn0wwhI4AO4GKpP/bMI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 h1:U2guen0GhqH8o/G2un8f/aG/y++OuW6MyCo6hT9prXk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0/go.mod h1:yeGZANgEcpdx/WK0IvvRFC+2oLiMS2u4L/0Rj2M2Qr0= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 h1:R3X6ZXmNPRR8ul6i3WgFURCHzaXjHdm0karRG/+dj3s= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0/go.mod h1:QWFXnDavXWwMx2EEcZsf3yxgEKAqsxQ+Syjp+seyInw= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.4.0 h1:0MH3f8lZrflbUWXVxyBg/zviDFdGE062uKh5+fu8Vv0= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.4.0/go.mod h1:Vh68vYiHY5mPdekTr0ox0sALsqjoVy0w3Os278yX5SQ= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.28.0 h1:BJee2iLkfRfl9lc7aFmBwkWxY/RI1RDdXepSF6y8TPE= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.28.0/go.mod h1:DIzlHs3DRscCIBU3Y9YSzPfScwnYnzfnCd4g8zA7bZc= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0 h1:EVSnY9JbEEW92bEkIYOVMw4q1WJxIAGoFTrtYOzWuRQ= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0/go.mod h1:Ea1N1QQryNXpCD0I1fdLibBAIpQuBkznMmkdKrapk1Y= +go.opentelemetry.io/otel/log v0.4.0 h1:/vZ+3Utqh18e8TPjuc3ecg284078KWrR8BRz+PQAj3o= +go.opentelemetry.io/otel/log v0.4.0/go.mod h1:DhGnQvky7pHy82MIRV43iXh3FlKN8UUKftn0KbLOq6I= go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/sdk/log v0.4.0 h1:1mMI22L82zLqf6KtkjrRy5BbagOTWdJsqMY/HSqILAA= +go.opentelemetry.io/otel/sdk/log v0.4.0/go.mod h1:AYJ9FVF0hNOgAVzUG/ybg/QttnXhUePWAupmCqtdESo= go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08= go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= +go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5fjhKeJGZPGFs= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= @@ -1313,7 +1329,6 @@ go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.14.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= @@ -1341,8 +1356,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/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.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= 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= @@ -1353,8 +1368,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-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= 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= @@ -1379,8 +1394,8 @@ golang.org/x/mod v0.3.0/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.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 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= @@ -1423,15 +1438,15 @@ 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.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= 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= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= -golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= +golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1443,8 +1458,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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= @@ -1515,8 +1530,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.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.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= @@ -1524,8 +1539,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.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= 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= @@ -1538,13 +1553,13 @@ 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.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.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= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -1591,8 +1606,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 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.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= +golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= 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= @@ -1649,10 +1664,10 @@ google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEY google.golang.org/genproto v0.0.0-20210401141331-865547bb08e2/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d h1:/hmn0Ku5kWij/kjGsrcJeC1T/MrJi2iNWwgAqrihFwc= google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d/go.mod h1:FfBgJBJg9GcpPvKIuHSZ/aE1g2ecGL74upMzGZjiGEY= -google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY= -google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d h1:JU0iKnSg02Gmb5ZdV8nYsKEKsP6o/FGVWTrw4i1DA9A= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= +google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= diff --git a/core/scripts/keystone/.gitignore b/core/scripts/keystone/.gitignore index 4af4a42a01..92bf9aabc5 100644 --- a/core/scripts/keystone/.gitignore +++ b/core/scripts/keystone/.gitignore @@ -3,3 +3,4 @@ !*-sample.sh keystone .cache/ +artefacts/ diff --git a/core/scripts/keystone/main.go b/core/scripts/keystone/main.go index 571623578a..3486830ca3 100644 --- a/core/scripts/keystone/main.go +++ b/core/scripts/keystone/main.go @@ -20,6 +20,8 @@ func main() { src.NewGenerateCribClusterOverridesCommand(), src.NewDeleteJobsCommand(), src.NewDeployAndInitializeCapabilitiesRegistryCommand(), + src.NewDeployWorkflowsCommand(), + src.NewDeleteWorkflowsCommand(), } commandsList := func(commands []command) string { diff --git a/core/scripts/keystone/src/01_deploy_contracts_cmd.go b/core/scripts/keystone/src/01_deploy_contracts_cmd.go index 2ca60bdfaf..b304973795 100644 --- a/core/scripts/keystone/src/01_deploy_contracts_cmd.go +++ b/core/scripts/keystone/src/01_deploy_contracts_cmd.go @@ -52,8 +52,12 @@ func (g *deployContracts) Run(args []string) { skipFunding := fs.Bool("skipfunding", false, "skip funding the transmitters") onlySetConfig := fs.Bool("onlysetconfig", false, "set the config on the OCR3 contract without deploying the contracts or funding transmitters") dryRun := fs.Bool("dryrun", false, "dry run, don't actually deploy the contracts and do not fund transmitters") + publicKeys := fs.String("publickeys", "", "Custom public keys json location") + nodeList := fs.String("nodes", "", "Custom node list location") + artefactsDir := fs.String("artefacts", "", "Custom artefacts directory location") err := fs.Parse(args) + if err != nil || *ocrConfigFile == "" || ocrConfigFile == nil || *ethUrl == "" || ethUrl == nil || @@ -63,11 +67,21 @@ func (g *deployContracts) Run(args []string) { os.Exit(1) } + if *artefactsDir == "" { + *artefactsDir = defaultArtefactsDir + } + if *publicKeys == "" { + *publicKeys = defaultPublicKeys + } + if *nodeList == "" { + *nodeList = defaultNodeList + } + os.Setenv("ETH_URL", *ethUrl) os.Setenv("ETH_CHAIN_ID", fmt.Sprintf("%d", *chainID)) os.Setenv("ACCOUNT_KEY", *accountKey) - deploy(*ocrConfigFile, *skipFunding, *dryRun, *onlySetConfig) + deploy(*nodeList, *publicKeys, *ocrConfigFile, *skipFunding, *dryRun, *onlySetConfig, *artefactsDir) } // deploy does the following: @@ -77,16 +91,20 @@ func (g *deployContracts) Run(args []string) { // 4. Writes the deployed contract addresses to a file // 5. Funds the transmitters func deploy( + nodeList string, + publicKeys string, configFile string, skipFunding bool, dryRun bool, onlySetConfig bool, + artefacts string, ) { env := helpers.SetupEnv(false) ocrConfig := generateOCR3Config( + nodeList, configFile, env.ChainID, - ".cache/PublicKeys.json", + publicKeys, ) if dryRun { @@ -96,11 +114,11 @@ func deploy( if onlySetConfig { fmt.Println("Skipping deployment of contracts and skipping funding transmitters, only setting config") - setOCR3Config(env, ocrConfig) + setOCR3Config(env, ocrConfig, artefacts) return } - if ContractsAlreadyDeployed() { + if ContractsAlreadyDeployed(artefacts) { fmt.Println("Contracts already deployed") return } @@ -118,10 +136,10 @@ func deploy( jsonBytes, err := json.Marshal(contracts) PanicErr(err) - err = os.WriteFile(DeployedContractsFilePath(), jsonBytes, 0600) + err = os.WriteFile(DeployedContractsFilePath(artefacts), jsonBytes, 0600) PanicErr(err) - setOCR3Config(env, ocrConfig) + setOCR3Config(env, ocrConfig, artefacts) if skipFunding { fmt.Println("Skipping funding transmitters") @@ -139,8 +157,9 @@ func deploy( func setOCR3Config( env helpers.Environment, ocrConfig orc2drOracleConfig, + artefacts string, ) { - loadedContracts, err := LoadDeployedContracts() + loadedContracts, err := LoadDeployedContracts(artefacts) PanicErr(err) ocrContract, err := ocr3_capability.NewOCR3Capability(loadedContracts.OCRContract, env.Ec) @@ -161,16 +180,16 @@ func setOCR3Config( loadedContracts.SetConfigTxBlock = receipt.BlockNumber.Uint64() jsonBytes, err := json.Marshal(loadedContracts) PanicErr(err) - err = os.WriteFile(DeployedContractsFilePath(), jsonBytes, 0600) + err = os.WriteFile(DeployedContractsFilePath(artefacts), jsonBytes, 0600) PanicErr(err) } -func LoadDeployedContracts() (deployedContracts, error) { - if !ContractsAlreadyDeployed() { +func LoadDeployedContracts(artefacts string) (deployedContracts, error) { + if !ContractsAlreadyDeployed(artefacts) { return deployedContracts{}, fmt.Errorf("no deployed contracts found, run deploy first") } - jsonBytes, err := os.ReadFile(DeployedContractsFilePath()) + jsonBytes, err := os.ReadFile(DeployedContractsFilePath(artefacts)) if err != nil { return deployedContracts{}, err } @@ -180,13 +199,13 @@ func LoadDeployedContracts() (deployedContracts, error) { return contracts, err } -func ContractsAlreadyDeployed() bool { - _, err := os.Stat(DeployedContractsFilePath()) +func ContractsAlreadyDeployed(artefacts string) bool { + _, err := os.Stat(DeployedContractsFilePath(artefacts)) return err == nil } -func DeployedContractsFilePath() string { - return filepath.Join(artefactsDir, deployedContractsJSON) +func DeployedContractsFilePath(artefacts string) string { + return filepath.Join(artefacts, deployedContractsJSON) } func DeployForwarder(e helpers.Environment) *forwarder.KeystoneForwarder { diff --git a/core/scripts/keystone/src/02_deploy_jobspecs_cmd.go b/core/scripts/keystone/src/02_deploy_jobspecs_cmd.go index 5918650cf8..275943d638 100644 --- a/core/scripts/keystone/src/02_deploy_jobspecs_cmd.go +++ b/core/scripts/keystone/src/02_deploy_jobspecs_cmd.go @@ -16,8 +16,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/cmd" ) -type deployJobSpecs struct { -} +type deployJobSpecs struct{} func NewDeployJobSpecsCommand() *deployJobSpecs { return &deployJobSpecs{} @@ -32,6 +31,11 @@ func (g *deployJobSpecs) Run(args []string) { chainID := fs.Int64("chainid", 11155111, "chain id") p2pPort := fs.Int64("p2pport", 6690, "p2p port") onlyReplay := fs.Bool("onlyreplay", false, "only replay the block from the OCR3 contract setConfig transaction") + templatesLocation := fs.String("templates", "", "Custom templates location") + nodeList := fs.String("nodes", "", "Custom node list location") + publicKeys := fs.String("publickeys", "", "Custom public keys json location") + artefactsDir := fs.String("artefacts", "", "Custom artefacts directory location") + err := fs.Parse(args) if err != nil || chainID == nil || *chainID == 0 || p2pPort == nil || *p2pPort == 0 || onlyReplay == nil { fs.Usage() @@ -43,12 +47,27 @@ func (g *deployJobSpecs) Run(args []string) { fmt.Println("Deploying OCR3 job specs") } - nodes := downloadNodeAPICredentialsDefault() - deployedContracts, err := LoadDeployedContracts() + if *artefactsDir == "" { + *artefactsDir = defaultArtefactsDir + } + if *publicKeys == "" { + *publicKeys = defaultPublicKeys + } + if *nodeList == "" { + *nodeList = defaultNodeList + } + if *templatesLocation == "" { + *templatesLocation = "templates" + } + + nodes := downloadNodeAPICredentials(*nodeList) + deployedContracts, err := LoadDeployedContracts(*artefactsDir) PanicErr(err) jobspecs := genSpecs( - ".cache/PublicKeys.json", ".cache/NodeList.txt", "templates", + *publicKeys, + *nodeList, + *templatesLocation, *chainID, *p2pPort, deployedContracts.OCRContract.Hex(), ) flattenedSpecs := []hostSpec{jobspecs.bootstrap} diff --git a/core/scripts/keystone/src/03_gen_crib_cluster_overrides_cmd.go b/core/scripts/keystone/src/03_gen_crib_cluster_overrides_cmd.go index cb3acf903b..6b98951459 100644 --- a/core/scripts/keystone/src/03_gen_crib_cluster_overrides_cmd.go +++ b/core/scripts/keystone/src/03_gen_crib_cluster_overrides_cmd.go @@ -9,8 +9,7 @@ import ( helpers "github.com/smartcontractkit/chainlink/core/scripts/common" ) -type generateCribClusterOverrides struct { -} +type generateCribClusterOverrides struct{} func NewGenerateCribClusterOverridesCommand() *generateCribClusterOverrides { return &generateCribClusterOverrides{} @@ -24,25 +23,39 @@ func (g *generateCribClusterOverrides) Run(args []string) { fs := flag.NewFlagSet(g.Name(), flag.ContinueOnError) chainID := fs.Int64("chainid", 11155111, "chain id") outputPath := fs.String("outpath", "../crib", "the path to output the generated overrides") + publicKeys := fs.String("publickeys", "", "Custom public keys json location") + nodeList := fs.String("nodes", "", "Custom node list location") + artefactsDir := fs.String("artefacts", "", "Custom artefacts directory location") - deployedContracts, err := LoadDeployedContracts() - helpers.PanicErr(err) templatesDir := "templates" - err = fs.Parse(args) + err := fs.Parse(args) if err != nil || outputPath == nil || *outputPath == "" || chainID == nil || *chainID == 0 { fs.Usage() os.Exit(1) } - lines := generateCribConfig(".cache/PublicKeys.json", chainID, templatesDir, deployedContracts.ForwarderContract.Hex()) + if *artefactsDir == "" { + *artefactsDir = defaultArtefactsDir + } + if *publicKeys == "" { + *publicKeys = defaultPublicKeys + } + if *nodeList == "" { + *nodeList = defaultNodeList + } + + deployedContracts, err := LoadDeployedContracts(*artefactsDir) + helpers.PanicErr(err) + + lines := generateCribConfig(*nodeList, *publicKeys, chainID, templatesDir, deployedContracts.ForwarderContract.Hex()) cribOverridesStr := strings.Join(lines, "\n") err = os.WriteFile(filepath.Join(*outputPath, "crib-cluster-overrides.yaml"), []byte(cribOverridesStr), 0600) helpers.PanicErr(err) } -func generateCribConfig(pubKeysPath string, chainID *int64, templatesDir string, forwarderAddress string) []string { - nca := downloadNodePubKeys(*chainID, pubKeysPath) +func generateCribConfig(nodeList string, pubKeysPath string, chainID *int64, templatesDir string, forwarderAddress string) []string { + nca := downloadNodePubKeys(nodeList, *chainID, pubKeysPath) nodeAddresses := []string{} for _, node := range nca[1:] { diff --git a/core/scripts/keystone/src/03_gen_crib_cluster_overrides_cmd_test.go b/core/scripts/keystone/src/03_gen_crib_cluster_overrides_cmd_test.go index 722b01e91c..53d43c2342 100644 --- a/core/scripts/keystone/src/03_gen_crib_cluster_overrides_cmd_test.go +++ b/core/scripts/keystone/src/03_gen_crib_cluster_overrides_cmd_test.go @@ -13,7 +13,7 @@ func TestGenerateCribConfig(t *testing.T) { forwarderAddress := "0x1234567890abcdef" publicKeysPath := "./testdata/PublicKeys.json" - lines := generateCribConfig(publicKeysPath, &chainID, templatesDir, forwarderAddress) + lines := generateCribConfig(defaultNodeList, publicKeysPath, &chainID, templatesDir, forwarderAddress) snaps.MatchSnapshot(t, strings.Join(lines, "\n")) } diff --git a/core/scripts/keystone/src/04_delete_ocr3_jobs_cmd.go b/core/scripts/keystone/src/04_delete_ocr3_jobs_cmd.go index 2ebed000ed..136691962d 100644 --- a/core/scripts/keystone/src/04_delete_ocr3_jobs_cmd.go +++ b/core/scripts/keystone/src/04_delete_ocr3_jobs_cmd.go @@ -5,14 +5,14 @@ import ( "encoding/json" "flag" "fmt" + "os" "github.com/urfave/cli" helpers "github.com/smartcontractkit/chainlink/core/scripts/common" ) -type deleteJobs struct { -} +type deleteJobs struct{} type OCRSpec struct { ContractID string @@ -22,11 +22,16 @@ type BootSpec struct { ContractID string } +type WorkflowSpec struct { + WorkflowID string +} + type JobSpec struct { Id string Name string BootstrapSpec BootSpec OffChainReporting2OracleSpec OCRSpec + WorkflowSpec WorkflowSpec } func NewDeleteJobsCommand() *deleteJobs { @@ -38,9 +43,26 @@ func (g *deleteJobs) Name() string { } func (g *deleteJobs) Run(args []string) { - deployedContracts, err := LoadDeployedContracts() + fs := flag.NewFlagSet(g.Name(), flag.ContinueOnError) + nodeList := fs.String("nodes", "", "Custom node list location") + artefactsDir := fs.String("artefacts", "", "Custom artefacts directory location") + + err := fs.Parse(args) + if err != nil { + fs.Usage() + os.Exit(1) + } + + if *artefactsDir == "" { + *artefactsDir = defaultArtefactsDir + } + if *nodeList == "" { + *nodeList = defaultNodeList + } + + deployedContracts, err := LoadDeployedContracts(*artefactsDir) helpers.PanicErr(err) - nodes := downloadNodeAPICredentialsDefault() + nodes := downloadNodeAPICredentials(*nodeList) for _, node := range nodes { output := &bytes.Buffer{} diff --git a/core/scripts/keystone/src/05_deploy_initialize_capabilities_registry.go b/core/scripts/keystone/src/05_deploy_initialize_capabilities_registry.go index 8762241543..4ff92b6210 100644 --- a/core/scripts/keystone/src/05_deploy_initialize_capabilities_registry.go +++ b/core/scripts/keystone/src/05_deploy_initialize_capabilities_registry.go @@ -11,10 +11,11 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - ragetypes "github.com/smartcontractkit/libocr/ragep2p/types" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/durationpb" + ragetypes "github.com/smartcontractkit/libocr/ragep2p/types" + capabilitiespb "github.com/smartcontractkit/chainlink-common/pkg/capabilities/pb" "github.com/smartcontractkit/chainlink-common/pkg/values" @@ -108,6 +109,29 @@ var ( Signer: "0x1a684B3d8f917fe496b7B1A8b29EDDAED64F649f", }, } + + aptosTargetDonPeers = []peer{ + { + PeerID: "12D3KooWNBr1AD3vD3dzSLgg1tK56qyJoenDx7EYNnZpbr1g4jD6", + Signer: "a41f9a561ff2266d94240996a76f9c2b3b7d8184", + }, + { + PeerID: "12D3KooWRRgWiZGw5GYsPa62CkwFNKJb5u4hWo4DinnvjG6GE6Nj", + Signer: "e4f3c7204776530fb7833db6f9dbfdb8bd0ec96892965324a71c20d6776f67f0", + }, + { + PeerID: "12D3KooWKwzgUHw5YbqUsYUVt3yiLSJcqc8ANofUtqHX6qTm7ox2", + Signer: "4071ea00e2e2c76b3406018ba9f66bf6b9aee3a6762e62ac823b1ee91ba7d7b0", + }, + { + PeerID: "12D3KooWBRux5o2bw1j3SQwEHzCspjkt7Xe3Y3agRUuab2SUnExj", + Signer: "6f5180c7d276876dbe413bf9b0efff7301d1367f39f4bac64180090cab70989b", + }, + { + PeerID: "12D3KooWFqvDaMSDGa6eMSTF9en6G2c3ZbGLmaA5Xs3AgxVBPb8B", + Signer: "dbce9a6df8a04d54e52a109d01ee9b5d32873b1d2436cf7b7fae61fd6eca46f8", + }, + } ) type deployAndInitializeCapabilitiesRegistryCommand struct{} @@ -235,6 +259,16 @@ func (c *deployAndInitializeCapabilitiesRegistryCommand) Run(args []string) { log.Printf("failed to call GetHashedCapabilityId: %s", err) } + aptosWriteChain := kcr.CapabilitiesRegistryCapability{ + LabelledName: "write_aptos", + Version: "1.0.0", + CapabilityType: uint8(3), // target + } + awid, err := reg.GetHashedCapabilityId(&bind.CallOpts{}, aptosWriteChain.LabelledName, aptosWriteChain.Version) + if err != nil { + log.Printf("failed to call GetHashedCapabilityId: %s", err) + } + ocr := kcr.CapabilitiesRegistryCapability{ LabelledName: "offchain_reporting", Version: "1.0.0", @@ -248,6 +282,7 @@ func (c *deployAndInitializeCapabilitiesRegistryCommand) Run(args []string) { tx, err := reg.AddCapabilities(env.Owner, []kcr.CapabilitiesRegistryCapability{ streamsTrigger, writeChain, + aptosWriteChain, ocr, }) if err != nil { @@ -305,6 +340,16 @@ func (c *deployAndInitializeCapabilitiesRegistryCommand) Run(args []string) { nodes = append(nodes, n) } + for _, targetPeer := range aptosTargetDonPeers { + n, innerErr := peerToNode(nopID, targetPeer) + if innerErr != nil { + panic(innerErr) + } + + n.HashedCapabilityIds = [][32]byte{awid} + nodes = append(nodes, n) + } + tx, err = reg.AddNodes(env.Owner, nodes) if err != nil { log.Printf("failed to AddNodes: %s", err) @@ -373,8 +418,14 @@ func (c *deployAndInitializeCapabilitiesRegistryCommand) Run(args []string) { panic(err) } - cc = newCapabilityConfig() - ccb, err = proto.Marshal(cc) + targetCapabilityConfig := newCapabilityConfig() + targetCapabilityConfig.RemoteConfig = &capabilitiespb.CapabilityConfig_RemoteTargetConfig{ + RemoteTargetConfig: &capabilitiespb.RemoteTargetConfig{ + RequestHashExcludedAttributes: []string{"signed_report.Signatures"}, + }, + } + + remoteTargetConfigBytes, err := proto.Marshal(targetCapabilityConfig) if err != nil { panic(err) } @@ -382,7 +433,36 @@ func (c *deployAndInitializeCapabilitiesRegistryCommand) Run(args []string) { cfgs = []kcr.CapabilitiesRegistryCapabilityConfiguration{ { CapabilityId: wid, - Config: ccb, + Config: remoteTargetConfigBytes, + }, + } + _, err = reg.AddDON(env.Owner, ps, cfgs, true, false, 1) + if err != nil { + log.Printf("targetDON: failed to AddDON: %s", err) + } + + // Aptos target DON + ps, err = peers(aptosTargetDonPeers) + if err != nil { + panic(err) + } + + targetCapabilityConfig = newCapabilityConfig() + targetCapabilityConfig.RemoteConfig = &capabilitiespb.CapabilityConfig_RemoteTargetConfig{ + RemoteTargetConfig: &capabilitiespb.RemoteTargetConfig{ + RequestHashExcludedAttributes: []string{"signed_report.Signatures"}, + }, + } + + remoteTargetConfigBytes, err = proto.Marshal(targetCapabilityConfig) + if err != nil { + panic(err) + } + + cfgs = []kcr.CapabilitiesRegistryCapabilityConfiguration{ + { + CapabilityId: awid, + Config: remoteTargetConfigBytes, }, } _, err = reg.AddDON(env.Owner, ps, cfgs, true, false, 1) diff --git a/core/scripts/keystone/src/06_deploy_workflows_cmd.go b/core/scripts/keystone/src/06_deploy_workflows_cmd.go new file mode 100644 index 0000000000..0ca8e5d4a7 --- /dev/null +++ b/core/scripts/keystone/src/06_deploy_workflows_cmd.go @@ -0,0 +1,71 @@ +package src + +import ( + "bytes" + "errors" + "flag" + "fmt" + "os" + + "github.com/urfave/cli" + + helpers "github.com/smartcontractkit/chainlink/core/scripts/common" +) + +type deployWorkflows struct{} + +func NewDeployWorkflowsCommand() *deployWorkflows { + return &deployWorkflows{} +} + +func (g *deployWorkflows) Name() string { + return "deploy-workflows" +} + +func (g *deployWorkflows) Run(args []string) { + fs := flag.NewFlagSet(g.Name(), flag.ContinueOnError) + workflowFile := fs.String("workflow", "workflow.yml", "path to workflow file") + nodeList := fs.String("nodes", "", "Custom node list location") + err := fs.Parse(args) + if err != nil || workflowFile == nil || *workflowFile == "" { + fs.Usage() + os.Exit(1) + } + if *nodeList == "" { + *nodeList = defaultNodeList + } + fmt.Println("Deploying workflows") + + // use a separate list + nodes := downloadNodeAPICredentials(*nodeList) + + if _, err = os.Stat(*workflowFile); err != nil { + PanicErr(errors.New("toml file does not exist")) + } + + for i, n := range nodes { + if i == 0 { + continue // skip bootstrap node + } + output := &bytes.Buffer{} + client, app := newApp(n, output) + fmt.Println("Logging in:", n.url) + loginFs := flag.NewFlagSet("test", flag.ContinueOnError) + loginFs.Bool("bypass-version-check", true, "") + loginCtx := cli.NewContext(app, loginFs, nil) + err := client.RemoteLogin(loginCtx) + helpers.PanicErr(err) + output.Reset() + + fmt.Printf("Deploying workflow\n... \n") + fs := flag.NewFlagSet("test", flag.ExitOnError) + err = fs.Parse([]string{*workflowFile}) + + helpers.PanicErr(err) + err = client.CreateJob(cli.NewContext(app, fs, nil)) + if err != nil { + fmt.Println("Failed to deploy workflow:", "Error:", err) + } + output.Reset() + } +} diff --git a/core/scripts/keystone/src/07_delete_workflows_cmd.go b/core/scripts/keystone/src/07_delete_workflows_cmd.go new file mode 100644 index 0000000000..cccedaf9e7 --- /dev/null +++ b/core/scripts/keystone/src/07_delete_workflows_cmd.go @@ -0,0 +1,74 @@ +package src + +import ( + "bytes" + "encoding/json" + "flag" + "fmt" + "os" + + "github.com/urfave/cli" + + helpers "github.com/smartcontractkit/chainlink/core/scripts/common" +) + +type deleteWorkflows struct{} + +func NewDeleteWorkflowsCommand() *deleteWorkflows { + return &deleteWorkflows{} +} + +func (g *deleteWorkflows) Name() string { + return "delete-workflows" +} + +func (g *deleteWorkflows) Run(args []string) { + fs := flag.NewFlagSet(g.Name(), flag.ExitOnError) + nodeList := fs.String("nodes", "", "Custom node list location") + + err := fs.Parse(args) + if err != nil { + fs.Usage() + os.Exit(1) + } + + if *nodeList == "" { + *nodeList = defaultNodeList + } + + nodes := downloadNodeAPICredentials(*nodeList) + + for _, node := range nodes { + output := &bytes.Buffer{} + client, app := newApp(node, output) + + fmt.Println("Logging in:", node.url) + loginFs := flag.NewFlagSet("test", flag.ContinueOnError) + loginFs.Bool("bypass-version-check", true, "") + loginCtx := cli.NewContext(app, loginFs, nil) + err := client.RemoteLogin(loginCtx) + helpers.PanicErr(err) + output.Reset() + + fileFs := flag.NewFlagSet("test", flag.ExitOnError) + err = client.ListJobs(cli.NewContext(app, fileFs, nil)) + helpers.PanicErr(err) + + var parsed []JobSpec + err = json.Unmarshal(output.Bytes(), &parsed) + helpers.PanicErr(err) + + for _, jobSpec := range parsed { + if jobSpec.WorkflowSpec.WorkflowID != "" { + fmt.Println("Deleting workflow job ID:", jobSpec.Id, "name:", jobSpec.Name) + set := flag.NewFlagSet("test", flag.ExitOnError) + err = set.Parse([]string{jobSpec.Id}) + helpers.PanicErr(err) + err = client.DeleteJob(cli.NewContext(app, set, nil)) + helpers.PanicErr(err) + } + } + + output.Reset() + } +} diff --git a/core/scripts/keystone/src/88_gen_jobspecs.go b/core/scripts/keystone/src/88_gen_jobspecs.go index 6a9c911a5f..39adcac267 100644 --- a/core/scripts/keystone/src/88_gen_jobspecs.go +++ b/core/scripts/keystone/src/88_gen_jobspecs.go @@ -34,12 +34,12 @@ func genSpecs( ocrConfigContractAddress string, ) donHostSpec { nodes := downloadNodeAPICredentials(nodeListPath) - nca := downloadNodePubKeys(chainID, pubkeysPath) + nca := downloadNodePubKeys(nodeListPath, chainID, pubkeysPath) bootstrapNode := nca[0] bootstrapSpecLines, err := readLines(filepath.Join(templatesDir, bootstrapSpecTemplate)) helpers.PanicErr(err) - bootHost := nodes[0].url.Host + bootHost := nodes[0].remoteURL.Hostname() bootstrapSpecLines = replacePlaceholders( bootstrapSpecLines, chainID, p2pPort, @@ -59,7 +59,7 @@ func genSpecs( ocrConfigContractAddress, bootHost, bootstrapNode, nca[i], ) - oracles = append(oracles, hostSpec{oracleSpecLines, nodes[i].url.Host}) + oracles = append(oracles, hostSpec{oracleSpecLines, nodes[i].remoteURL.Host}) } return donHostSpec{ @@ -82,6 +82,7 @@ func replacePlaceholders( l = strings.Replace(l, "{{ ocr_config_contract_address }}", contractAddress, 1) l = strings.Replace(l, "{{ transmitter_id }}", node.EthAddress, 1) l = strings.Replace(l, "{{ ocr_key_bundle_id }}", node.OCR2BundleID, 1) + l = strings.Replace(l, "{{ aptos_key_bundle_id }}", node.AptosBundleID, 1) l = strings.Replace(l, "{{ bootstrapper_p2p_id }}", bootstrapper, 1) output = append(output, l) } diff --git a/core/scripts/keystone/src/88_gen_ocr3_config.go b/core/scripts/keystone/src/88_gen_ocr3_config.go index fe9241a2bd..f0a03ecafb 100644 --- a/core/scripts/keystone/src/88_gen_ocr3_config.go +++ b/core/scripts/keystone/src/88_gen_ocr3_config.go @@ -4,6 +4,7 @@ import ( "crypto/ed25519" "encoding/hex" "encoding/json" + "fmt" "time" "github.com/ethereum/go-ethereum/common" @@ -13,6 +14,7 @@ import ( "github.com/smartcontractkit/libocr/offchainreporting2plus/types" helpers "github.com/smartcontractkit/chainlink/core/scripts/common" + "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" ) @@ -46,17 +48,20 @@ type OracleConfigSource struct { } type NodeKeys struct { - EthAddress string - P2PPeerID string // p2p_ - OCR2BundleID string // used only in job spec - OCR2OnchainPublicKey string // ocr2on_evm_ - OCR2OffchainPublicKey string // ocr2off_evm_ - OCR2ConfigPublicKey string // ocr2cfg_evm_ - CSAPublicKey string + EthAddress string `json:"EthAddress"` + AptosAccount string `json:"AptosAccount"` + AptosBundleID string `json:"AptosBundleID"` + AptosOnchainPublicKey string `json:"AptosOnchainPublicKey"` + P2PPeerID string `json:"P2PPeerID"` // p2p_ + OCR2BundleID string `json:"OCR2BundleID"` // used only in job spec + OCR2OnchainPublicKey string `json:"OCR2OnchainPublicKey"` // ocr2on_evm_ + OCR2OffchainPublicKey string `json:"OCR2OffchainPublicKey"` // ocr2off_evm_ + OCR2ConfigPublicKey string `json:"OCR2ConfigPublicKey"` // ocr2cfg_evm_ + CSAPublicKey string `json:"CSAPublicKey"` } type orc2drOracleConfig struct { - Signers []common.Address + Signers [][]byte Transmitters []common.Address F uint8 OnchainConfig []byte @@ -82,7 +87,7 @@ func (c orc2drOracleConfig) MarshalJSON() ([]byte, error) { } for i, signer := range c.Signers { - alias.Signers[i] = signer.Hex() + alias.Signers[i] = hex.EncodeToString(signer) } for i, transmitter := range c.Transmitters { @@ -96,14 +101,37 @@ func mustReadConfig(fileName string) (output TopLevelConfigSource) { return mustParseJSON[TopLevelConfigSource](fileName) } -func generateOCR3Config(configFile string, chainID int64, pubKeysPath string) orc2drOracleConfig { +func generateOCR3Config(nodeList string, configFile string, chainID int64, pubKeysPath string) orc2drOracleConfig { topLevelCfg := mustReadConfig(configFile) cfg := topLevelCfg.OracleConfig - nca := downloadNodePubKeys(chainID, pubKeysPath) + nca := downloadNodePubKeys(nodeList, chainID, pubKeysPath) - onchainPubKeys := []common.Address{} + onchainPubKeys := [][]byte{} + allPubKeys := map[string]any{} for _, n := range nca { - onchainPubKeys = append(onchainPubKeys, common.HexToAddress(n.OCR2OnchainPublicKey)) + ethPubKey := common.HexToAddress(n.OCR2OnchainPublicKey) + aptosPubKey, err := hex.DecodeString(n.AptosOnchainPublicKey) + if err != nil { + panic(err) + } + pubKeys := map[string]types.OnchainPublicKey{ + "evm": ethPubKey[:], + "aptos": aptosPubKey, + } + // validate uniqueness of each individual key + for _, key := range pubKeys { + raw := hex.EncodeToString(key) + _, exists := allPubKeys[raw] + if exists { + panic(fmt.Sprintf("Duplicate onchain public key: %v", raw)) + } + allPubKeys[raw] = struct{}{} + } + pubKey, err := ocrcommon.MarshalMultichainPublicKey(pubKeys) + if err != nil { + panic(err) + } + onchainPubKeys = append(onchainPubKeys, pubKey) } offchainPubKeysBytes := []types.OffchainPublicKey{} @@ -170,13 +198,16 @@ func generateOCR3Config(configFile string, chainID int64, pubKeysPath string) or ) helpers.PanicErr(err) - signerAddresses, err := evm.OnchainPublicKeyToAddress(signers) - PanicErr(err) + var configSigners [][]byte + for _, signer := range signers { + configSigners = append(configSigners, signer) + } + transmitterAddresses, err := evm.AccountToAddress(transmitters) PanicErr(err) config := orc2drOracleConfig{ - Signers: signerAddresses, + Signers: configSigners, Transmitters: transmitterAddresses, F: f, OnchainConfig: onchainConfig, diff --git a/core/scripts/keystone/src/88_gen_ocr3_config_test.go b/core/scripts/keystone/src/88_gen_ocr3_config_test.go index 185354ec2f..10cdc07b20 100644 --- a/core/scripts/keystone/src/88_gen_ocr3_config_test.go +++ b/core/scripts/keystone/src/88_gen_ocr3_config_test.go @@ -10,7 +10,7 @@ import ( func TestGenerateOCR3Config(t *testing.T) { // Generate OCR3 config - config := generateOCR3Config("./testdata/SampleConfig.json", 11155111, "./testdata/PublicKeys.json") + config := generateOCR3Config(".cache/NodeList.txt", "./testdata/SampleConfig.json", 11155111, "./testdata/PublicKeys.json") matchOffchainConfig := match.Custom("OffchainConfig", func(s any) (any, error) { // coerce the value to a string diff --git a/core/scripts/keystone/src/99_fetch_keys.go b/core/scripts/keystone/src/99_fetch_keys.go index 4fcb6f138a..91750f7422 100644 --- a/core/scripts/keystone/src/99_fetch_keys.go +++ b/core/scripts/keystone/src/99_fetch_keys.go @@ -14,17 +14,18 @@ import ( helpers "github.com/smartcontractkit/chainlink/core/scripts/common" ubig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" + "github.com/smartcontractkit/chainlink/v2/core/cmd" "github.com/smartcontractkit/chainlink/v2/core/web/presenters" ) -func downloadNodePubKeys(chainID int64, pubKeysPath string) []NodeKeys { +func downloadNodePubKeys(nodeList string, chainID int64, pubKeysPath string) []NodeKeys { // Check if file exists already, and if so, return the keys if _, err := os.Stat(pubKeysPath); err == nil { fmt.Println("Loading existing public keys at:", pubKeysPath) return mustParseJSON[[]NodeKeys](pubKeysPath) } - nodes := downloadNodeAPICredentialsDefault() + nodes := downloadNodeAPICredentials(nodeList) nodesKeys := mustFetchNodesKeys(chainID, nodes) marshalledNodeKeys, err := json.MarshalIndent(nodesKeys, "", " ") @@ -40,13 +41,6 @@ func downloadNodePubKeys(chainID int64, pubKeysPath string) []NodeKeys { return nodesKeys } -// downloadNodeAPICredentialsDefault downloads the node API credentials, or loads them from disk if they already exist -// -// The nodes are sorted by URL. In the case of crib, the bootstrap node is the first node in the list. -func downloadNodeAPICredentialsDefault() []*node { - return downloadNodeAPICredentials(".cache/NodeList.txt") -} - // downloadNodeAPICredentials downloads the node API credentials, or loads them from disk if they already exist // // The nodes are sorted by URL. In the case of crib, the bootstrap node is the first node in the list. @@ -125,6 +119,19 @@ func mustFetchNodesKeys(chainID int64, nodes []*node) (nca []NodeKeys) { helpers.PanicErr(err) output.Reset() + keysClient := cmd.NewAptosKeysClient(client) + err = keysClient.ListKeys(&cli.Context{ + App: app, + }) + helpers.PanicErr(err) + var aptosKeys []presenters.AptosKeyResource + helpers.PanicErr(json.Unmarshal(output.Bytes(), &aptosKeys)) + if len(aptosKeys) != 1 { + helpers.PanicErr(errors.New("node must have single aptos key")) + } + aptosAccount := aptosKeys[0].Account + output.Reset() + err = client.ListP2PKeys(&cli.Context{ App: app, }) @@ -137,18 +144,20 @@ func mustFetchNodesKeys(chainID int64, nodes []*node) (nca []NodeKeys) { peerID := strings.TrimPrefix(p2pKeys[0].PeerID, "p2p_") output.Reset() + chainType := "evm" + var ocr2Bundles []ocr2Bundle err = client.ListOCR2KeyBundles(&cli.Context{ App: app, }) helpers.PanicErr(err) helpers.PanicErr(json.Unmarshal(output.Bytes(), &ocr2Bundles)) - ocr2BundleIndex := findEvmOCR2Bundle(ocr2Bundles) + ocr2BundleIndex := findOCR2Bundle(ocr2Bundles, chainType) output.Reset() if ocr2BundleIndex == -1 { fmt.Println("WARN: node does not have EVM OCR2 bundle, creating one") fs := flag.NewFlagSet("test", flag.ContinueOnError) - err = fs.Parse([]string{"evm"}) + err = fs.Parse([]string{chainType}) helpers.PanicErr(err) ocr2CreateBundleCtx := cli.NewContext(app, fs, nil) err = client.CreateOCR2KeyBundle(ocr2CreateBundleCtx) @@ -160,12 +169,35 @@ func mustFetchNodesKeys(chainID int64, nodes []*node) (nca []NodeKeys) { }) helpers.PanicErr(err) helpers.PanicErr(json.Unmarshal(output.Bytes(), &ocr2Bundles)) - ocr2BundleIndex = findEvmOCR2Bundle(ocr2Bundles) + ocr2BundleIndex = findOCR2Bundle(ocr2Bundles, chainType) output.Reset() } ocr2Bndl := ocr2Bundles[ocr2BundleIndex] + aptosBundleIndex := findOCR2Bundle(ocr2Bundles, "aptos") + if aptosBundleIndex == -1 { + chainType2 := "aptos" + fmt.Println("WARN: node does not have Aptos OCR2 bundle, creating one") + fs := flag.NewFlagSet("test", flag.ContinueOnError) + err = fs.Parse([]string{chainType2}) + helpers.PanicErr(err) + ocr2CreateBundleCtx := cli.NewContext(app, fs, nil) + err = client.CreateOCR2KeyBundle(ocr2CreateBundleCtx) + helpers.PanicErr(err) + output.Reset() + + err = client.ListOCR2KeyBundles(&cli.Context{ + App: app, + }) + helpers.PanicErr(err) + helpers.PanicErr(json.Unmarshal(output.Bytes(), &ocr2Bundles)) + aptosBundleIndex = findOCR2Bundle(ocr2Bundles, chainType2) + output.Reset() + } + + aptosBundle := ocr2Bundles[aptosBundleIndex] + err = client.ListCSAKeys(&cli.Context{ App: app, }) @@ -178,11 +210,14 @@ func mustFetchNodesKeys(chainID int64, nodes []*node) (nca []NodeKeys) { nc := NodeKeys{ EthAddress: ethAddress, + AptosAccount: aptosAccount, P2PPeerID: peerID, + AptosBundleID: aptosBundle.ID, + AptosOnchainPublicKey: strings.TrimPrefix(aptosBundle.OnchainPublicKey, fmt.Sprintf("ocr2on_%s_", "aptos")), OCR2BundleID: ocr2Bndl.ID, - OCR2ConfigPublicKey: strings.TrimPrefix(ocr2Bndl.ConfigPublicKey, "ocr2cfg_evm_"), - OCR2OnchainPublicKey: strings.TrimPrefix(ocr2Bndl.OnchainPublicKey, "ocr2on_evm_"), - OCR2OffchainPublicKey: strings.TrimPrefix(ocr2Bndl.OffchainPublicKey, "ocr2off_evm_"), + OCR2ConfigPublicKey: strings.TrimPrefix(ocr2Bndl.ConfigPublicKey, fmt.Sprintf("ocr2cfg_%s_", chainType)), + OCR2OnchainPublicKey: strings.TrimPrefix(ocr2Bndl.OnchainPublicKey, fmt.Sprintf("ocr2on_%s_", chainType)), + OCR2OffchainPublicKey: strings.TrimPrefix(ocr2Bndl.OffchainPublicKey, fmt.Sprintf("ocr2off_%s_", chainType)), CSAPublicKey: csaPubKey, } @@ -198,9 +233,9 @@ func findFirstCSAPublicKey(csaKeyResources []presenters.CSAKeyResource) (string, return "", errors.New("did not find any CSA Key Resources") } -func findEvmOCR2Bundle(ocr2Bundles []ocr2Bundle) int { +func findOCR2Bundle(ocr2Bundles []ocr2Bundle, chainType string) int { for i, b := range ocr2Bundles { - if b.ChainType == "evm" { + if b.ChainType == chainType { return i } } diff --git a/core/scripts/keystone/src/99_files.go b/core/scripts/keystone/src/99_files.go index d334b0fd56..08ba12e419 100644 --- a/core/scripts/keystone/src/99_files.go +++ b/core/scripts/keystone/src/99_files.go @@ -11,7 +11,9 @@ import ( ) const ( - artefactsDir = "artefacts" + defaultArtefactsDir = "artefacts" + defaultPublicKeys = ".cache/PublicKeys.json" + defaultNodeList = ".cache/NodeList.txt" deployedContractsJSON = "deployed_contracts.json" bootstrapSpecTemplate = "bootstrap.toml" cribOverrideTemplate = "crib-overrides.yaml" diff --git a/core/scripts/keystone/src/99_nodes.go b/core/scripts/keystone/src/99_nodes.go index 961d58c4b1..68d3621ce6 100644 --- a/core/scripts/keystone/src/99_nodes.go +++ b/core/scripts/keystone/src/99_nodes.go @@ -10,9 +10,10 @@ import ( ) type node struct { - url *url.URL - login string - password string + url *url.URL + remoteURL *url.URL + login string + password string } func (n node) IsTerminal() bool { @@ -50,7 +51,7 @@ func mustReadNodesList(path string) []*node { continue } s := strings.Split(rr, " ") - if len(s) != 3 { + if len(s) != 4 { helpers.PanicErr(errors.New("wrong nodes list format")) } if strings.Contains(s[0], "boot") && hasBoot { @@ -58,11 +59,13 @@ func mustReadNodesList(path string) []*node { } hasBoot = true url, err := url.Parse(s[0]) + remoteURL, err := url.Parse(s[1]) helpers.PanicErr(err) nodes = append(nodes, &node{ - url: url, - login: s[1], - password: s[2], + url: url, + remoteURL: remoteURL, + login: s[2], + password: s[3], }) } return nodes diff --git a/core/scripts/keystone/src/__snapshots__/88_gen_jobspecs_test.snap b/core/scripts/keystone/src/__snapshots__/88_gen_jobspecs_test.snap index 1ee7f67894..c0c7c7d7e6 100755 --- a/core/scripts/keystone/src/__snapshots__/88_gen_jobspecs_test.snap +++ b/core/scripts/keystone/src/__snapshots__/88_gen_jobspecs_test.snap @@ -10,6 +10,7 @@ relay = "evm" [relayConfig] chainID = "11155111" +providerType = "ocr3-capability" Oracles: Oracle 0: @@ -33,13 +34,14 @@ chainID = "11155111" command = "chainlink-ocr3-capability" ocrVersion = 3 pluginName = "ocr-capability" -providerType = "plugin" +providerType = "ocr3-capability" telemetryType = "plugin" [onchainSigningStrategy] -strategyName = 'single-chain' +strategyName = 'multi-chain' [onchainSigningStrategy.config] -publicKey = '8fa807463ad73f9ee855cfd60ba406dcf98a2855b3dd8af613107b0f6890a707' +evm = "b3df4d8748b67731a1112e8b45a764941974f5590c93672eebbc4f3504dd10ed" +aptos = "9bebfa953e7a7522746f72b4023308de36db626f3e0bcb9033407b8a183e8bfb" -------------------------------- Oracle 1: @@ -63,13 +65,14 @@ chainID = "11155111" command = "chainlink-ocr3-capability" ocrVersion = 3 pluginName = "ocr-capability" -providerType = "plugin" +providerType = "ocr3-capability" telemetryType = "plugin" [onchainSigningStrategy] -strategyName = 'single-chain' +strategyName = 'multi-chain' [onchainSigningStrategy.config] -publicKey = '8fa807463ad73f9ee855cfd60ba406dcf98a2855b3dd8af613107b0f6890a707' +evm = "38459ae37f29f2c1fde0f25972a973322be8cada82acf43f464756836725be97" +aptos = "9bebfa953e7a7522746f72b4023308de36db626f3e0bcb9033407b8a183e8bfc" -------------------------------- Oracle 2: @@ -93,13 +96,14 @@ chainID = "11155111" command = "chainlink-ocr3-capability" ocrVersion = 3 pluginName = "ocr-capability" -providerType = "plugin" +providerType = "ocr3-capability" telemetryType = "plugin" [onchainSigningStrategy] -strategyName = 'single-chain' +strategyName = 'multi-chain' [onchainSigningStrategy.config] -publicKey = '8fa807463ad73f9ee855cfd60ba406dcf98a2855b3dd8af613107b0f6890a707' +evm = "b5dbc4c9da983cddde2e3226b85807eb7beaf818694a22576af4d80f352702ed" +aptos = "9bebfa953e7a7522746f72b4023308de36db626f3e0bcb9033407b8a183e8bfd" -------------------------------- Oracle 3: @@ -123,13 +127,14 @@ chainID = "11155111" command = "chainlink-ocr3-capability" ocrVersion = 3 pluginName = "ocr-capability" -providerType = "plugin" +providerType = "ocr3-capability" telemetryType = "plugin" [onchainSigningStrategy] -strategyName = 'single-chain' +strategyName = 'multi-chain' [onchainSigningStrategy.config] -publicKey = '8fa807463ad73f9ee855cfd60ba406dcf98a2855b3dd8af613107b0f6890a707' +evm = "260d5c1a618cdf5324509d7db95f5a117511864ebb9e1f709e8969339eb225af" +aptos = "9bebfa953e7a7522746f72b4023308de36db626f3e0bcb9033407b8a183e8bfe" --- diff --git a/core/scripts/keystone/src/__snapshots__/88_gen_ocr3_config_test.snap b/core/scripts/keystone/src/__snapshots__/88_gen_ocr3_config_test.snap index a2ecd236f7..eac3cdaff4 100755 --- a/core/scripts/keystone/src/__snapshots__/88_gen_ocr3_config_test.snap +++ b/core/scripts/keystone/src/__snapshots__/88_gen_ocr3_config_test.snap @@ -6,11 +6,11 @@ "OffchainConfigVersion": 30, "OnchainConfig": "0x", "Signers": [ - "0xA2402db8E549f094EA31e1C0EDd77623F4cA5b12", - "0x4af19c802B244D1d085492C3946391C965E10519", - "0x61925685d2B80b121537341d063c4E57B2F9323c", - "0xFd97eFD53FC20acc098Fcd746C04d8d7540D97E0", - "0xA0b67Dc5345a71D02B396147AE2cb75dDA63CbE9" + "011400a2402db8e549f094ea31e1c0edd77623f4ca5b12052000ea551e503b93a1c9ae26262b4db8f66db4cbe5ddcb6039e29d2665a634d48e4a", + "0114004af19c802b244d1d085492c3946391c965e10519052000ea551e503b93a1c9ae26262b4db8f66db4cbe5ddcb6039e29d2665a634d48e4b", + "01140061925685d2b80b121537341d063c4e57b2f9323c052000ea551e503b93a1c9ae26262b4db8f66db4cbe5ddcb6039e29d2665a634d48e4c", + "011400fd97efd53fc20acc098fcd746c04d8d7540d97e0052000ea551e503b93a1c9ae26262b4db8f66db4cbe5ddcb6039e29d2665a634d48e4d", + "011400a0b67dc5345a71d02b396147ae2cb75dda63cbe9052000ea551e503b93a1c9ae26262b4db8f66db4cbe5ddcb6039e29d2665a634d48e4e" ], "Transmitters": [ "0xF4e7e516146c8567F8E8be0ED1f1A92798628d35", diff --git a/core/scripts/keystone/src/testdata/NodeList.txt b/core/scripts/keystone/src/testdata/NodeList.txt index 9a895e0f3f..6fb65dded6 100644 --- a/core/scripts/keystone/src/testdata/NodeList.txt +++ b/core/scripts/keystone/src/testdata/NodeList.txt @@ -1,5 +1,5 @@ -https://crib-henry-keystone-node1.main.stage.cldev.sh notreal@fakeemail.ch fj293fbBnlQ!f9vNs -https://crib-henry-keystone-node2.main.stage.cldev.sh notreal@fakeemail.ch fj293fbBnlQ!f9vNs -https://crib-henry-keystone-node3.main.stage.cldev.sh notreal@fakeemail.ch fj293fbBnlQ!f9vNs -https://crib-henry-keystone-node4.main.stage.cldev.sh notreal@fakeemail.ch fj293fbBnlQ!f9vNs -https://crib-henry-keystone-node5.main.stage.cldev.sh notreal@fakeemail.ch fj293fbBnlQ!f9vNs +https://local-node1 https://crib-henry-keystone-node1.main.stage.cldev.sh notreal@fakeemail.ch fj293fbBnlQ!f9vNs +https://local-node2 https://crib-henry-keystone-node2.main.stage.cldev.sh notreal@fakeemail.ch fj293fbBnlQ!f9vNs +https://local-node3 https://crib-henry-keystone-node3.main.stage.cldev.sh notreal@fakeemail.ch fj293fbBnlQ!f9vNs +https://local-node4 https://crib-henry-keystone-node4.main.stage.cldev.sh notreal@fakeemail.ch fj293fbBnlQ!f9vNs +https://local-node5 https://crib-henry-keystone-node5.main.stage.cldev.sh notreal@fakeemail.ch fj293fbBnlQ!f9vNs diff --git a/core/scripts/keystone/src/testdata/PublicKeys.json b/core/scripts/keystone/src/testdata/PublicKeys.json index 7ade3d45ad..b29e829089 100644 --- a/core/scripts/keystone/src/testdata/PublicKeys.json +++ b/core/scripts/keystone/src/testdata/PublicKeys.json @@ -1,5 +1,7 @@ [ { + "AptosBundleID": "9bebfa953e7a7522746f72b4023308de36db626f3e0bcb9033407b8a183e8bfa", + "AptosOnchainPublicKey": "ea551e503b93a1c9ae26262b4db8f66db4cbe5ddcb6039e29d2665a634d48e4a", "EthAddress": "0xF4e7e516146c8567F8E8be0ED1f1A92798628d35", "P2PPeerID": "12D3KooWNmhKZL1XW4Vv3rNjLXzJ6mqcVerihdijjGYuexPrFUFZ", "OCR2BundleID": "2f92c96da20fbe39c89e59516e3a7473254523316887394e406527c72071d3db", @@ -9,6 +11,8 @@ "CSAPublicKey": "csa_dbae6965bad0b0fa95ecc34a602eee1c0c570ddc29b56502e400d18574b8c3df" }, { + "AptosBundleID": "9bebfa953e7a7522746f72b4023308de36db626f3e0bcb9033407b8a183e8bfb", + "AptosOnchainPublicKey": "ea551e503b93a1c9ae26262b4db8f66db4cbe5ddcb6039e29d2665a634d48e4b", "EthAddress": "0x8B60FDcc9CAC8ea476b31d17011CB204471431d9", "P2PPeerID": "12D3KooWFUjV73ZYkAMhS2cVwte3kXDWD8Ybyx3u9CEDHNoeEhBH", "OCR2BundleID": "b3df4d8748b67731a1112e8b45a764941974f5590c93672eebbc4f3504dd10ed", @@ -18,6 +22,8 @@ "CSAPublicKey": "csa_c5cc655a9c19b69626519c4a72c44a94a3675daeba9c16cc23e010a7a6dac1be" }, { + "AptosBundleID": "9bebfa953e7a7522746f72b4023308de36db626f3e0bcb9033407b8a183e8bfc", + "AptosOnchainPublicKey": "ea551e503b93a1c9ae26262b4db8f66db4cbe5ddcb6039e29d2665a634d48e4c", "EthAddress": "0x6620F516F29979B214e2451498a057FDd3a0A85d", "P2PPeerID": "12D3KooWRTtH2WWrztD87Do1kXePSmGjyU4r7mZVWThmqTGgdbUC", "OCR2BundleID": "38459ae37f29f2c1fde0f25972a973322be8cada82acf43f464756836725be97", @@ -27,6 +33,8 @@ "CSAPublicKey": "csa_7407fc90c70895c0fb2bdf385e2e4918364bec1f7a74bad7fdf696bffafbcab8" }, { + "AptosBundleID": "9bebfa953e7a7522746f72b4023308de36db626f3e0bcb9033407b8a183e8bfd", + "AptosOnchainPublicKey": "ea551e503b93a1c9ae26262b4db8f66db4cbe5ddcb6039e29d2665a634d48e4d", "EthAddress": "0xFeB61E22FCf4F9740c9D96b05199F195bd61A7c2", "P2PPeerID": "12D3KooWMTZnZtcVK4EJsjkKsV9qXNoNRSjT62CZi3tKkXGaCsGh", "OCR2BundleID": "b5dbc4c9da983cddde2e3226b85807eb7beaf818694a22576af4d80f352702ed", @@ -36,6 +44,8 @@ "CSAPublicKey": "csa_ef55caf17eefc2a9d547b5a3978d396bd237c73af99cd849a4758701122e3cba" }, { + "AptosBundleID": "9bebfa953e7a7522746f72b4023308de36db626f3e0bcb9033407b8a183e8bfe", + "AptosOnchainPublicKey": "ea551e503b93a1c9ae26262b4db8f66db4cbe5ddcb6039e29d2665a634d48e4e", "EthAddress": "0x882Fd04D78A7e7D386Dd5b550f19479E5494B0B2", "P2PPeerID": "12D3KooWRsM9yordRQDhLgbErH8WMMGz1bC1J4hR5gAGvMWu8goN", "OCR2BundleID": "260d5c1a618cdf5324509d7db95f5a117511864ebb9e1f709e8969339eb225af", diff --git a/core/scripts/keystone/templates/bootstrap.toml b/core/scripts/keystone/templates/bootstrap.toml index 1baa43101b..cdd9065cab 100644 --- a/core/scripts/keystone/templates/bootstrap.toml +++ b/core/scripts/keystone/templates/bootstrap.toml @@ -6,3 +6,4 @@ relay = "evm" [relayConfig] chainID = "{{ chain_id }}" +providerType = "ocr3-capability" diff --git a/core/scripts/keystone/templates/oracle.toml b/core/scripts/keystone/templates/oracle.toml index f2ff87de92..053baa2223 100644 --- a/core/scripts/keystone/templates/oracle.toml +++ b/core/scripts/keystone/templates/oracle.toml @@ -17,10 +17,11 @@ chainID = "{{ chain_id }}" command = "chainlink-ocr3-capability" ocrVersion = 3 pluginName = "ocr-capability" -providerType = "plugin" +providerType = "ocr3-capability" telemetryType = "plugin" [onchainSigningStrategy] -strategyName = 'single-chain' +strategyName = 'multi-chain' [onchainSigningStrategy.config] -publicKey = '8fa807463ad73f9ee855cfd60ba406dcf98a2855b3dd8af613107b0f6890a707' +evm = "{{ ocr_key_bundle_id }}" +aptos = "{{ aptos_key_bundle_id }}" diff --git a/core/scripts/vrfv2plus/testnet/main.go b/core/scripts/vrfv2plus/testnet/main.go index a37a539102..f5220f62dd 100644 --- a/core/scripts/vrfv2plus/testnet/main.go +++ b/core/scripts/vrfv2plus/testnet/main.go @@ -213,7 +213,7 @@ func main() { for i := range preSeedSlice { ps, err := proof.BigToSeed(preSeedSlice[i]) helpers.PanicErr(err) - extraArgs, err := extraargs.ExtraArgsV1(*nativePayment) + extraArgs, err := extraargs.EncodeV1(*nativePayment) helpers.PanicErr(err) preSeedData := proof.PreSeedDataV2Plus{ PreSeed: ps, @@ -308,7 +308,7 @@ func main() { helpers.PanicErr(err) parsedSubID := parseUInt256String(*subID) - extraArgs, err := extraargs.ExtraArgsV1(*nativePayment) + extraArgs, err := extraargs.EncodeV1(*nativePayment) helpers.PanicErr(err) preSeedData := proof.PreSeedDataV2Plus{ PreSeed: ps, diff --git a/core/scripts/vrfv2plus/testnet/proofs.go b/core/scripts/vrfv2plus/testnet/proofs.go index ea6943c63f..8f1cef0ca6 100644 --- a/core/scripts/vrfv2plus/testnet/proofs.go +++ b/core/scripts/vrfv2plus/testnet/proofs.go @@ -105,7 +105,7 @@ func generateProofForV2Plus(e helpers.Environment) { if !ok { helpers.PanicErr(fmt.Errorf("unable to parse subID: %s %w", *subId, err)) } - extraArgs, err := extraargs.ExtraArgsV1(*nativePayment) + extraArgs, err := extraargs.EncodeV1(*nativePayment) helpers.PanicErr(err) preSeedData := proof.PreSeedDataV2Plus{ PreSeed: preSeed, diff --git a/core/services/ccip/mocks/orm.go b/core/services/ccip/mocks/orm.go index 8a987c2160..0c9086def7 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 0000000000..8a061893ce --- /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 0000000000..24bfb4a9ec --- /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 d074ea7473..1942c68fef 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 7b7b8d8271..e778ddf6ce 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 e880ecfad2..8a3016576b 100644 --- a/core/services/chainlink/application.go +++ b/core/services/chainlink/application.go @@ -14,6 +14,9 @@ import ( "github.com/grafana/pyroscope-go" "github.com/jonboulle/clockwork" "github.com/pkg/errors" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" "go.uber.org/multierr" "go.uber.org/zap/zapcore" @@ -24,17 +27,13 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/utils/jsonserializable" "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox" - "github.com/smartcontractkit/chainlink/v2/core/capabilities" - remotetypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote/types" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" - "github.com/smartcontractkit/chainlink/v2/core/services/standardcapabilities" - "github.com/smartcontractkit/chainlink/v2/core/static" - - "github.com/smartcontractkit/chainlink/v2/core/services/promreporter" - "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/build" + "github.com/smartcontractkit/chainlink/v2/core/capabilities" + gatewayconnector "github.com/smartcontractkit/chainlink/v2/core/capabilities/gateway_connector" "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote" + remotetypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" evmutils "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" @@ -49,6 +48,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/feeds" "github.com/smartcontractkit/chainlink/v2/core/services/fluxmonitorv2" "github.com/smartcontractkit/chainlink/v2/core/services/gateway" + "github.com/smartcontractkit/chainlink/v2/core/services/headreporter" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/keeper" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" @@ -63,6 +63,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/registrysyncer" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc" + "github.com/smartcontractkit/chainlink/v2/core/services/standardcapabilities" "github.com/smartcontractkit/chainlink/v2/core/services/streams" "github.com/smartcontractkit/chainlink/v2/core/services/telemetry" "github.com/smartcontractkit/chainlink/v2/core/services/vrf" @@ -72,6 +73,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/sessions" "github.com/smartcontractkit/chainlink/v2/core/sessions/ldapauth" "github.com/smartcontractkit/chainlink/v2/core/sessions/localauth" + "github.com/smartcontractkit/chainlink/v2/core/static" "github.com/smartcontractkit/chainlink/v2/plugins" ) @@ -150,7 +152,6 @@ type ChainlinkApplication struct { shutdownOnce sync.Once srvcs []services.ServiceCtx HealthChecker services.Checker - Nurse *services.Nurse logger logger.SugaredLogger AuditLogger audit.AuditLogger closeLogger func() error @@ -215,43 +216,67 @@ func NewApplication(opts ApplicationOpts) (Application, error) { externalPeer := externalp2p.NewExternalPeerWrapper(keyStore.P2P(), cfg.Capabilities().Peering(), opts.DS, globalLogger) signer := externalPeer externalPeerWrapper = externalPeer - remoteDispatcher := remote.NewDispatcher(externalPeerWrapper, signer, opts.CapabilitiesRegistry, globalLogger) - srvcs = append(srvcs, remoteDispatcher) - + remoteDispatcher, err := remote.NewDispatcher(cfg.Capabilities().Dispatcher(), externalPeerWrapper, signer, opts.CapabilitiesRegistry, globalLogger) + if err != nil { + return nil, fmt.Errorf("could not create dispatcher: %w", err) + } dispatcher = remoteDispatcher } else { dispatcher = opts.CapabilitiesDispatcher externalPeerWrapper = opts.CapabilitiesPeerWrapper } - srvcs = append(srvcs, externalPeerWrapper) + srvcs = append(srvcs, externalPeerWrapper, dispatcher) - rid := cfg.Capabilities().ExternalRegistry().RelayID() - registryAddress := cfg.Capabilities().ExternalRegistry().Address() - relayer, err := relayerChainInterops.Get(rid) - if err != nil { - return nil, fmt.Errorf("could not fetch relayer %s configured for capabilities registry: %w", rid, err) - } + if cfg.Capabilities().ExternalRegistry().Address() != "" { + rid := cfg.Capabilities().ExternalRegistry().RelayID() + registryAddress := cfg.Capabilities().ExternalRegistry().Address() + relayer, err := relayerChainInterops.Get(rid) + if err != nil { + return nil, fmt.Errorf("could not fetch relayer %s configured for capabilities registry: %w", rid, err) + } + registrySyncer, err := registrysyncer.New( + globalLogger, + func() (p2ptypes.PeerID, error) { + p := externalPeerWrapper.GetPeer() + if p == nil { + return p2ptypes.PeerID{}, errors.New("could not get peer") + } - registrySyncer, err := registrysyncer.New( - globalLogger, - externalPeerWrapper, - relayer, - registryAddress, - ) - if err != nil { - return nil, fmt.Errorf("could not configure syncer: %w", err) - } + return p.ID(), nil + }, + relayer, + registryAddress, + registrysyncer.NewORM(opts.DS, globalLogger), + ) + if err != nil { + return nil, fmt.Errorf("could not configure syncer: %w", err) + } - wfLauncher := capabilities.NewLauncher( - globalLogger, - externalPeerWrapper, - dispatcher, - opts.CapabilitiesRegistry, - ) - registrySyncer.AddLauncher(wfLauncher) + wfLauncher := capabilities.NewLauncher( + globalLogger, + externalPeerWrapper, + dispatcher, + opts.CapabilitiesRegistry, + ) + registrySyncer.AddLauncher(wfLauncher) + + 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{}) + } - srvcs = append(srvcs, dispatcher, wfLauncher, registrySyncer) + var gatewayConnectorWrapper *gatewayconnector.ServiceWrapper + if cfg.Capabilities().GatewayConnector().DonID() != "" { + globalLogger.Debugw("Creating GatewayConnector wrapper", "donID", cfg.Capabilities().GatewayConnector().DonID()) + gatewayConnectorWrapper = gatewayconnector.NewGatewayConnectorServiceWrapper( + cfg.Capabilities().GatewayConnector(), + keyStore.Eth(), + clockwork.NewRealClock(), + globalLogger) + srvcs = append(srvcs, gatewayConnectorWrapper) } // LOOPs can be created as options, in the case of LOOP relayers, or @@ -260,7 +285,7 @@ func NewApplication(opts ApplicationOpts) (Application, error) { // we need to initialize in case we serve OCR2 LOOPs loopRegistry := opts.LoopRegistry if loopRegistry == nil { - loopRegistry = plugins.NewLoopRegistry(globalLogger, opts.Config.Tracing()) + loopRegistry = plugins.NewLoopRegistry(globalLogger, opts.Config.Tracing(), opts.Config.Telemetry()) } // If the audit logger is enabled @@ -281,14 +306,9 @@ func NewApplication(opts ApplicationOpts) (Application, error) { } ap := cfg.AutoPprof() - var nurse *services.Nurse if ap.Enabled() { globalLogger.Info("Nurse service (automatic pprof profiling) is enabled") - nurse = services.NewNurse(ap, globalLogger) - err := nurse.Start() - if err != nil { - return nil, err - } + srvcs = append(srvcs, services.NewNurse(ap, globalLogger)) } else { globalLogger.Info("Nurse service (automatic pprof profiling) is disabled") } @@ -324,8 +344,6 @@ func NewApplication(opts ApplicationOpts) (Application, error) { srvcs = append(srvcs, mailMon) srvcs = append(srvcs, relayerChainInterops.Services()...) - promReporter := promreporter.NewPromReporter(opts.DS, legacyEVMChains, globalLogger) - srvcs = append(srvcs, promReporter) // Initialize Local Users ORM and Authentication Provider specified in config // BasicAdminUsersORM is initialized and required regardless of separate Authentication Provider @@ -365,8 +383,16 @@ func NewApplication(opts ApplicationOpts) (Application, error) { workflowORM = workflowstore.NewDBStore(opts.DS, globalLogger, clockwork.NewRealClock()) ) + promReporter := headreporter.NewPrometheusReporter(opts.DS, legacyEVMChains) + chainIDs := make([]*big.Int, legacyEVMChains.Len()) + for i, chain := range legacyEVMChains.Slice() { + chainIDs[i] = chain.ID() + } + telemReporter := headreporter.NewTelemetryReporter(telemetryManager, globalLogger, chainIDs...) + headReporter := headreporter.NewHeadReporterService(opts.DS, globalLogger, promReporter, telemReporter) + srvcs = append(srvcs, headReporter) for _, chain := range legacyEVMChains.Slice() { - chain.HeadBroadcaster().Subscribe(promReporter) + chain.HeadBroadcaster().Subscribe(headReporter) chain.TxManager().RegisterResumeCallback(pipelineRunner.ResumeRun) } @@ -433,7 +459,8 @@ func NewApplication(opts ApplicationOpts) (Application, error) { loopRegistrarConfig, telemetryManager, pipelineRunner, - opts.RelayerChainInteroperators), + opts.RelayerChainInteroperators, + gatewayConnectorWrapper), } webhookJobRunner = delegates[job.Webhook].(*webhook.Delegate).WebhookJobRunner() ) @@ -553,6 +580,7 @@ func NewApplication(opts ApplicationOpts) (Application, error) { jobSpawner, keyStore, cfg, + cfg.Feature(), cfg.Insecure(), cfg.JobPipeline(), cfg.OCR(), @@ -592,7 +620,6 @@ func NewApplication(opts ApplicationOpts) (Application, error) { SessionReaper: sessionReaper, ExternalInitiatorManager: externalInitiatorManager, HealthChecker: healthChecker, - Nurse: nurse, logger: globalLogger, AuditLogger: auditLogger, closeLogger: opts.CloseLogger, @@ -625,6 +652,14 @@ func (app *ChainlinkApplication) Start(ctx context.Context) error { panic("application is already started") } + var span trace.Span + ctx, span = otel.Tracer("").Start(ctx, "Start", trace.WithAttributes( + attribute.String("app-id", app.ID().String()), + attribute.String("version", static.Version), + attribute.String("commit", static.Sha), + )) + defer span.End() + if app.FeedsService != nil { if err := app.FeedsService.Start(ctx); err != nil { app.logger.Errorf("[Feeds Service] Failed to start %v", err) @@ -712,10 +747,6 @@ func (app *ChainlinkApplication) stop() (err error) { err = multierr.Append(err, app.FeedsService.Close()) } - if app.Nurse != nil { - err = multierr.Append(err, app.Nurse.Close()) - } - if app.profiler != nil { err = multierr.Append(err, app.profiler.Stop()) } diff --git a/core/services/chainlink/cfgtest/cfgtest.go b/core/services/chainlink/cfgtest/cfgtest.go index 3bf9545265..1dfba71d46 100644 --- a/core/services/chainlink/cfgtest/cfgtest.go +++ b/core/services/chainlink/cfgtest/cfgtest.go @@ -76,7 +76,7 @@ func assertValNotNil(t *testing.T, key string, val reflect.Value) error { t.Helper() k := val.Kind() switch k { //nolint:exhaustive - case reflect.Ptr, reflect.Map: + case reflect.Ptr: if val.IsNil() { return fmt.Errorf("%s: nil", key) } @@ -94,6 +94,9 @@ func assertValNotNil(t *testing.T, key string, val reflect.Value) error { } return assertFieldsNotNil(t, key, val) case reflect.Map: + if val.IsNil() { + return nil // not actually a problem + } return assertValuesNotNil(t, key, val) case reflect.Slice: if val.IsNil() { diff --git a/core/services/chainlink/config.go b/core/services/chainlink/config.go index dc301c0967..476e758ccb 100644 --- a/core/services/chainlink/config.go +++ b/core/services/chainlink/config.go @@ -55,8 +55,13 @@ type RawConfig map[string]any // ValidateConfig returns an error if the Config is not valid for use, as-is. func (c *RawConfig) ValidateConfig() (err error) { if v, ok := (*c)["Enabled"]; ok { - if _, ok := v.(*bool); !ok { - err = multierr.Append(err, commonconfig.ErrInvalid{Name: "Enabled", Value: v, Msg: "expected *bool"}) + if _, ok := v.(bool); !ok { + err = multierr.Append(err, commonconfig.ErrInvalid{Name: "Enabled", Value: v, Msg: "expected bool"}) + } + } + if v, ok := (*c)["ChainID"]; ok { + if _, ok := v.(string); !ok { + err = multierr.Append(err, commonconfig.ErrInvalid{Name: "ChainID", Value: v, Msg: "expected string"}) } } return err @@ -67,7 +72,17 @@ func (c *RawConfig) IsEnabled() bool { return false } - return (*c)["Enabled"] == nil || *(*c)["Enabled"].(*bool) + enabled, ok := (*c)["Enabled"].(bool) + return ok && enabled +} + +func (c *RawConfig) ChainID() string { + if c == nil { + return "" + } + + chainID, _ := (*c)["ChainID"].(string) + return chainID } // TOMLString returns a TOML encoded string. @@ -170,6 +185,9 @@ func (c *Config) SetFrom(f *Config) (err error) { err = multierr.Append(err, commonconfig.NamedMultiErrorList(err4, "Starknet")) } + // the plugin should handle it's own defaults and merging + c.Aptos = f.Aptos + _, err = commonconfig.MultiErrorList(err) return err diff --git a/core/services/chainlink/config_capabilities.go b/core/services/chainlink/config_capabilities.go index c438ca249d..032eec58be 100644 --- a/core/services/chainlink/config_capabilities.go +++ b/core/services/chainlink/config_capabilities.go @@ -1,10 +1,9 @@ package chainlink import ( + "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink/v2/core/config" "github.com/smartcontractkit/chainlink/v2/core/config/toml" - - "github.com/smartcontractkit/chainlink-common/pkg/types" ) var _ config.Capabilities = (*capabilitiesConfig)(nil) @@ -23,6 +22,52 @@ func (c *capabilitiesConfig) ExternalRegistry() config.CapabilitiesExternalRegis } } +func (c *capabilitiesConfig) Dispatcher() config.Dispatcher { + return &dispatcher{d: c.c.Dispatcher} +} + +type dispatcher struct { + d toml.Dispatcher +} + +func (d *dispatcher) SupportedVersion() int { + return *d.d.SupportedVersion +} + +func (d *dispatcher) ReceiverBufferSize() int { + return *d.d.ReceiverBufferSize +} + +func (d *dispatcher) RateLimit() config.DispatcherRateLimit { + return &dispatcherRateLimit{r: d.d.RateLimit} +} + +type dispatcherRateLimit struct { + r toml.DispatcherRateLimit +} + +func (r *dispatcherRateLimit) GlobalRPS() float64 { + return *r.r.GlobalRPS +} + +func (r *dispatcherRateLimit) GlobalBurst() int { + return *r.r.GlobalBurst +} + +func (r *dispatcherRateLimit) PerSenderRPS() float64 { + return *r.r.PerSenderRPS +} + +func (r *dispatcherRateLimit) PerSenderBurst() int { + return *r.r.PerSenderBurst +} + +func (c *capabilitiesConfig) GatewayConnector() config.GatewayConnector { + return &gatewayConnector{ + c: c.c.GatewayConnector, + } +} + type capabilitiesExternalRegistry struct { c toml.ExternalRegistry } @@ -42,3 +87,50 @@ func (c *capabilitiesExternalRegistry) ChainID() string { func (c *capabilitiesExternalRegistry) Address() string { return *c.c.Address } + +type gatewayConnector struct { + c toml.GatewayConnector +} + +func (c *gatewayConnector) ChainIDForNodeKey() string { + return *c.c.ChainIDForNodeKey +} +func (c *gatewayConnector) NodeAddress() string { + return *c.c.NodeAddress +} + +func (c *gatewayConnector) DonID() string { + return *c.c.DonID +} + +func (c *gatewayConnector) Gateways() []config.ConnectorGateway { + t := make([]config.ConnectorGateway, len(c.c.Gateways)) + for index, element := range c.c.Gateways { + t[index] = &connectorGateway{element} + } + return t +} + +func (c *gatewayConnector) WSHandshakeTimeoutMillis() uint32 { + return *c.c.WSHandshakeTimeoutMillis +} + +func (c *gatewayConnector) AuthMinChallengeLen() int { + return *c.c.AuthMinChallengeLen +} + +func (c *gatewayConnector) AuthTimestampToleranceSec() uint32 { + return *c.c.AuthTimestampToleranceSec +} + +type connectorGateway struct { + c toml.ConnectorGateway +} + +func (c *connectorGateway) ID() string { + return *c.c.ID +} + +func (c *connectorGateway) URL() string { + return *c.c.URL +} diff --git a/core/services/chainlink/config_feature.go b/core/services/chainlink/config_feature.go index d013228cc1..c69d8cf795 100644 --- a/core/services/chainlink/config_feature.go +++ b/core/services/chainlink/config_feature.go @@ -21,3 +21,7 @@ func (f *featureConfig) UICSAKeys() bool { func (f *featureConfig) CCIP() bool { return *f.c.CCIP } + +func (f *featureConfig) MultiFeedsManagers() bool { + return *f.c.MultiFeedsManagers +} diff --git a/core/services/chainlink/config_feature_test.go b/core/services/chainlink/config_feature_test.go index bc0418c157..8fa5884450 100644 --- a/core/services/chainlink/config_feature_test.go +++ b/core/services/chainlink/config_feature_test.go @@ -18,4 +18,5 @@ func TestFeatureConfig(t *testing.T) { assert.True(t, f.LogPoller()) assert.True(t, f.FeedsManager()) assert.True(t, f.UICSAKeys()) + assert.True(t, f.MultiFeedsManagers()) } diff --git a/core/services/chainlink/config_general.go b/core/services/chainlink/config_general.go index 5b6b839fb5..dd0dc87b59 100644 --- a/core/services/chainlink/config_general.go +++ b/core/services/chainlink/config_general.go @@ -209,6 +209,10 @@ func (g *generalConfig) StarknetConfigs() starknet.TOMLConfigs { return g.c.Starknet } +func (g *generalConfig) AptosConfigs() RawConfigs { + return g.c.Aptos +} + func (g *generalConfig) Validate() error { return g.validate(g.secrets.Validate) } @@ -520,5 +524,8 @@ func (g *generalConfig) Threshold() coreconfig.Threshold { func (g *generalConfig) Tracing() coreconfig.Tracing { return &tracingConfig{s: g.c.Tracing} } +func (g *generalConfig) Telemetry() coreconfig.Telemetry { + return &telemetryConfig{s: g.c.Telemetry} +} var zeroSha256Hash = models.Sha256Hash{} diff --git a/core/services/chainlink/config_telemetry.go b/core/services/chainlink/config_telemetry.go new file mode 100644 index 0000000000..790f2a1995 --- /dev/null +++ b/core/services/chainlink/config_telemetry.go @@ -0,0 +1,43 @@ +package chainlink + +import ( + "github.com/smartcontractkit/chainlink/v2/core/config/toml" +) + +type telemetryConfig struct { + s toml.Telemetry +} + +func (b *telemetryConfig) Enabled() bool { return *b.s.Enabled } + +func (b *telemetryConfig) InsecureConnection() bool { + if b.s.InsecureConnection == nil { + return false + } + return *b.s.InsecureConnection +} + +func (b *telemetryConfig) CACertFile() string { + if b.s.CACertFile == nil { + return "" + } + return *b.s.CACertFile +} + +func (b *telemetryConfig) OtelExporterGRPCEndpoint() string { + if b.s.Endpoint == nil { + return "" + } + return *b.s.Endpoint +} + +func (b *telemetryConfig) ResourceAttributes() map[string]string { + return b.s.ResourceAttributes +} + +func (b *telemetryConfig) TraceSampleRatio() float64 { + if b.s.TraceSampleRatio == nil { + return 0.0 + } + return *b.s.TraceSampleRatio +} diff --git a/core/services/chainlink/config_telemetry_ingress_test.go b/core/services/chainlink/config_telemetry_ingress_test.go index c371b465a2..64e85b5493 100644 --- a/core/services/chainlink/config_telemetry_ingress_test.go +++ b/core/services/chainlink/config_telemetry_ingress_test.go @@ -17,7 +17,7 @@ func TestTelemetryIngressConfig(t *testing.T) { ticfg := cfg.TelemetryIngress() assert.True(t, ticfg.Logging()) - assert.True(t, ticfg.UniConn()) + assert.False(t, ticfg.UniConn()) assert.Equal(t, uint(1234), ticfg.BufferSize()) assert.Equal(t, uint(4321), ticfg.MaxBatchSize()) assert.Equal(t, time.Minute, ticfg.SendInterval()) diff --git a/core/services/chainlink/config_test.go b/core/services/chainlink/config_test.go index 9afc0aa942..97970df38f 100644 --- a/core/services/chainlink/config_test.go +++ b/core/services/chainlink/config_test.go @@ -258,10 +258,11 @@ func TestConfig_Marshal(t *testing.T) { } full.Feature = toml.Feature{ - FeedsManager: ptr(true), - LogPoller: ptr(true), - UICSAKeys: ptr(true), - CCIP: ptr(false), + FeedsManager: ptr(true), + LogPoller: ptr(true), + UICSAKeys: ptr(true), + CCIP: ptr(true), + MultiFeedsManagers: ptr(true), } full.Database = toml.Database{ DefaultIdleInTxSessionTimeout: commoncfg.MustNewDuration(time.Minute), @@ -289,7 +290,7 @@ func TestConfig_Marshal(t *testing.T) { }, } full.TelemetryIngress = toml.TelemetryIngress{ - UniConn: ptr(true), + UniConn: ptr(false), Logging: ptr(true), BufferSize: ptr[uint16](1234), MaxBatchSize: ptr[uint16](4321), @@ -451,6 +452,27 @@ func TestConfig_Marshal(t *testing.T) { ChainID: ptr("1"), NetworkID: ptr("evm"), }, + Dispatcher: toml.Dispatcher{ + SupportedVersion: ptr(1), + ReceiverBufferSize: ptr(10000), + RateLimit: toml.DispatcherRateLimit{ + GlobalRPS: ptr(800.0), + GlobalBurst: ptr(1000), + PerSenderRPS: ptr(10.0), + PerSenderBurst: ptr(50), + }, + }, + GatewayConnector: toml.GatewayConnector{ + ChainIDForNodeKey: ptr("11155111"), + NodeAddress: ptr("0x68902d681c28119f9b2531473a417088bf008e59"), + DonID: ptr("example_don"), + WSHandshakeTimeoutMillis: ptr[uint32](100), + AuthMinChallengeLen: ptr[int](10), + AuthTimestampToleranceSec: ptr[uint32](10), + Gateways: []toml.ConnectorGateway{ + {ID: ptr("example_gateway"), URL: ptr("wss://localhost:8081/node")}, + }, + }, } full.Keeper = toml.Keeper{ DefaultTransactionQueueDepth: ptr[uint32](17), @@ -491,6 +513,14 @@ func TestConfig_Marshal(t *testing.T) { Environment: ptr("dev"), Release: ptr("v1.2.3"), } + full.Telemetry = toml.Telemetry{ + Enabled: ptr(true), + CACertFile: ptr("cert-file"), + Endpoint: ptr("example.com/collector"), + InsecureConnection: ptr(true), + ResourceAttributes: map[string]string{"Baz": "test", "Foo": "bar"}, + TraceSampleRatio: ptr(0.01), + } full.EVM = []*evmcfg.EVMConfig{ { ChainID: ubig.NewI(1), @@ -502,7 +532,7 @@ func TestConfig_Marshal(t *testing.T) { }, BlockBackfillDepth: ptr[uint32](100), BlockBackfillSkip: ptr(true), - ChainType: chaintype.NewChainTypeConfig("Optimism"), + ChainType: chaintype.NewConfig("Optimism"), FinalityDepth: ptr[uint32](42), FinalityTagEnabled: ptr[bool](false), FlagsContractAddress: mustAddress("0xae4E781a6218A8031764928E88d457937A954fC3"), @@ -569,6 +599,7 @@ func TestConfig_Marshal(t *testing.T) { NonceAutoSync: ptr(true), NoNewHeadsThreshold: &minute, OperatorFactoryAddress: mustAddress("0xa5B85635Be42F21f94F28034B7DA440EeFF0F418"), + LogBroadcasterEnabled: ptr(true), RPCDefaultBatchSize: ptr[uint32](17), RPCBlockQueryDelay: ptr[uint16](10), NoNewFinalizedHeadsThreshold: &hour, @@ -603,6 +634,7 @@ func TestConfig_Marshal(t *testing.T) { FinalizedBlockPollInterval: &second, EnforceRepeatableRead: ptr(true), DeathDeclarationDelay: &minute, + NewHeadsPollInterval: &zeroSeconds, Errors: evmcfg.ClientErrors{ NonceTooLow: ptr[string]("(: |^)nonce too low"), NonceTooHigh: ptr[string]("(: |^)nonce too high"), @@ -618,6 +650,7 @@ func TestConfig_Marshal(t *testing.T) { TransactionAlreadyMined: ptr[string]("(: |^)transaction already mined"), Fatal: ptr[string]("(: |^)fatal"), ServiceUnavailable: ptr[string]("(: |^)service unavailable"), + TooManyResults: ptr[string]("(: |^)too many results"), }, }, OCR: evmcfg.OCR{ @@ -633,6 +666,9 @@ func TestConfig_Marshal(t *testing.T) { GasLimit: ptr[uint32](540), }, }, + Workflow: evmcfg.Workflow{ + GasLimitDefault: ptr[uint64](400000), + }, }, Nodes: []*evmcfg.Node{ { @@ -776,7 +812,8 @@ Headers = ['Authorization: token', 'X-SomeOther-Header: value with spaces | and FeedsManager = true LogPoller = true UICSAKeys = true -CCIP = false +CCIP = true +MultiFeedsManagers = true `}, {"Database", Config{Core: toml.Core{Database: full.Database}}, `[Database] DefaultIdleInTxSessionTimeout = '1m0s' @@ -804,7 +841,7 @@ LeaseDuration = '1m0s' LeaseRefreshInterval = '1s' `}, {"TelemetryIngress", Config{Core: toml.Core{TelemetryIngress: full.TelemetryIngress}}, `[TelemetryIngress] -UniConn = true +UniConn = false Logging = true BufferSize = 1234 MaxBatchSize = 4321 @@ -1000,6 +1037,7 @@ MinContractPayment = '9.223372036854775807 link' NonceAutoSync = true NoNewHeadsThreshold = '1m0s' OperatorFactoryAddress = '0xa5B85635Be42F21f94F28034B7DA440EeFF0F418' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 17 RPCBlockQueryDelay = 10 FinalizedBlockOffset = 16 @@ -1080,6 +1118,7 @@ NodeIsSyncingEnabled = true FinalizedBlockPollInterval = '1s' EnforceRepeatableRead = true DeathDeclarationDelay = '1m0s' +NewHeadsPollInterval = '0s' [EVM.NodePool.Errors] NonceTooLow = '(: |^)nonce too low' @@ -1096,6 +1135,7 @@ L2Full = '(: |^)l2 full' TransactionAlreadyMined = '(: |^)transaction already mined' Fatal = '(: |^)fatal' ServiceUnavailable = '(: |^)service unavailable' +TooManyResults = '(: |^)too many results' [EVM.OCR] ContractConfirmations = 11 @@ -1109,6 +1149,9 @@ ObservationGracePeriod = '1s' [EVM.OCR2.Automation] GasLimit = 540 +[EVM.Workflow] +GasLimitDefault = 400000 + [[EVM.Nodes]] Name = 'foo' WSURL = 'wss://web.socket/test/foo' @@ -1245,6 +1288,9 @@ func TestConfig_full(t *testing.T) { if got.EVM[c].Workflow.ForwarderAddress == nil { got.EVM[c].Workflow.ForwarderAddress = &addr } + if got.EVM[c].Workflow.GasLimitDefault == nil { + got.EVM[c].Workflow.GasLimitDefault = ptr(uint64(400000)) + } for n := range got.EVM[c].Nodes { if got.EVM[c].Nodes[n].WSURL == nil { got.EVM[c].Nodes[n].WSURL = new(commoncfg.URL) @@ -1373,7 +1419,7 @@ func TestConfig_Validate(t *testing.T) { - 1: 2 errors: - ChainID: missing: required for all chains - Nodes: missing: must have at least one node - - Aptos.0.Enabled: invalid value (1): expected *bool`}, + - Aptos.0.Enabled: invalid value (1): expected bool`}, } { t.Run(tt.name, func(t *testing.T) { var c Config diff --git a/core/services/chainlink/mocks/general_config.go b/core/services/chainlink/mocks/general_config.go index c42ed5c701..63a846c6ed 100644 --- a/core/services/chainlink/mocks/general_config.go +++ b/core/services/chainlink/mocks/general_config.go @@ -4,6 +4,8 @@ package mocks import ( chainlinkconfig "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/config" + chainlink "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" + config "github.com/smartcontractkit/chainlink/v2/core/config" cosmosconfig "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" @@ -81,6 +83,53 @@ func (_c *GeneralConfig_AppID_Call) RunAndReturn(run func() uuid.UUID) *GeneralC return _c } +// AptosConfigs provides a mock function with given fields: +func (_m *GeneralConfig) AptosConfigs() chainlink.RawConfigs { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for AptosConfigs") + } + + var r0 chainlink.RawConfigs + if rf, ok := ret.Get(0).(func() chainlink.RawConfigs); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(chainlink.RawConfigs) + } + } + + return r0 +} + +// GeneralConfig_AptosConfigs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AptosConfigs' +type GeneralConfig_AptosConfigs_Call struct { + *mock.Call +} + +// AptosConfigs is a helper method to define mock.On call +func (_e *GeneralConfig_Expecter) AptosConfigs() *GeneralConfig_AptosConfigs_Call { + return &GeneralConfig_AptosConfigs_Call{Call: _e.mock.On("AptosConfigs")} +} + +func (_c *GeneralConfig_AptosConfigs_Call) Run(run func()) *GeneralConfig_AptosConfigs_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *GeneralConfig_AptosConfigs_Call) Return(_a0 chainlink.RawConfigs) *GeneralConfig_AptosConfigs_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *GeneralConfig_AptosConfigs_Call) RunAndReturn(run func() chainlink.RawConfigs) *GeneralConfig_AptosConfigs_Call { + _c.Call.Return(run) + return _c +} + // AptosEnabled provides a mock function with given fields: func (_m *GeneralConfig) AptosEnabled() bool { ret := _m.Called() @@ -1722,6 +1771,53 @@ func (_c *GeneralConfig_StarknetConfigs_Call) RunAndReturn(run func() chainlinkc return _c } +// Telemetry provides a mock function with given fields: +func (_m *GeneralConfig) Telemetry() config.Telemetry { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Telemetry") + } + + var r0 config.Telemetry + if rf, ok := ret.Get(0).(func() config.Telemetry); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(config.Telemetry) + } + } + + return r0 +} + +// GeneralConfig_Telemetry_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Telemetry' +type GeneralConfig_Telemetry_Call struct { + *mock.Call +} + +// Telemetry is a helper method to define mock.On call +func (_e *GeneralConfig_Expecter) Telemetry() *GeneralConfig_Telemetry_Call { + return &GeneralConfig_Telemetry_Call{Call: _e.mock.On("Telemetry")} +} + +func (_c *GeneralConfig_Telemetry_Call) Run(run func()) *GeneralConfig_Telemetry_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *GeneralConfig_Telemetry_Call) Return(_a0 config.Telemetry) *GeneralConfig_Telemetry_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *GeneralConfig_Telemetry_Call) RunAndReturn(run func() config.Telemetry) *GeneralConfig_Telemetry_Call { + _c.Call.Return(run) + return _c +} + // TelemetryIngress provides a mock function with given fields: func (_m *GeneralConfig) TelemetryIngress() config.TelemetryIngress { ret := _m.Called() diff --git a/core/services/chainlink/relayer_chain_interoperators.go b/core/services/chainlink/relayer_chain_interoperators.go index 60381c0d47..582dd61677 100644 --- a/core/services/chainlink/relayer_chain_interoperators.go +++ b/core/services/chainlink/relayer_chain_interoperators.go @@ -8,7 +8,7 @@ import ( "sync" "github.com/smartcontractkit/chainlink-common/pkg/loop" - relay "github.com/smartcontractkit/chainlink-common/pkg/loop/adapters/relay" + "github.com/smartcontractkit/chainlink-common/pkg/loop/adapters/relay" "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos" "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/adapters" @@ -186,6 +186,23 @@ func InitStarknet(ctx context.Context, factory RelayerFactory, config StarkNetFa } } +// InitAptos is a option for instantiating Aptos relayers +func InitAptos(ctx context.Context, factory RelayerFactory, config AptosFactoryConfig) CoreRelayerChainInitFunc { + return func(op *CoreRelayerChainInteroperators) (err error) { + relayers, err := factory.NewAptos(config.Keystore, config.TOMLConfigs) + if err != nil { + return fmt.Errorf("failed to setup aptos relayer: %w", err) + } + + for id, relayer := range relayers { + op.srvs = append(op.srvs, relayer) + op.loopRelayers[id] = relayer + } + + return nil + } +} + // Get a [loop.Relayer] by id func (rs *CoreRelayerChainInteroperators) Get(id types.RelayID) (loop.Relayer, error) { rs.mu.Lock() diff --git a/core/services/chainlink/relayer_chain_interoperators_test.go b/core/services/chainlink/relayer_chain_interoperators_test.go index 5aaf6e16dd..e83c2881c9 100644 --- a/core/services/chainlink/relayer_chain_interoperators_test.go +++ b/core/services/chainlink/relayer_chain_interoperators_test.go @@ -176,7 +176,7 @@ func TestCoreRelayerChainInteroperators(t *testing.T) { factory := chainlink.RelayerFactory{ Logger: lggr, - LoopRegistry: plugins.NewLoopRegistry(lggr, nil), + LoopRegistry: plugins.NewLoopRegistry(lggr, nil, nil), GRPCOpts: loop.GRPCOpts{}, CapabilitiesRegistry: capabilities.NewRegistry(lggr), } diff --git a/core/services/chainlink/relayer_factory.go b/core/services/chainlink/relayer_factory.go index 849964f9be..11e477a54f 100644 --- a/core/services/chainlink/relayer_factory.go +++ b/core/services/chainlink/relayer_factory.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "net/http" "github.com/pelletier/go-toml/v2" @@ -18,7 +19,7 @@ import ( solcfg "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" pkgstarknet "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink" starkchain "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/chain" - "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/config" + starkcfg "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/config" "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" coreconfig "github.com/smartcontractkit/chainlink/v2/core/config" "github.com/smartcontractkit/chainlink/v2/core/config/env" @@ -37,6 +38,7 @@ type RelayerFactory struct { loop.GRPCOpts MercuryPool wsrpc.Pool CapabilitiesRegistry coretypes.CapabilitiesRegistry + HTTPClient *http.Client } type DummyFactoryConfig struct { @@ -85,6 +87,7 @@ func (r *RelayerFactory) NewEVM(ctx context.Context, config EVMFactoryConfig) (m MercuryPool: r.MercuryPool, TransmitterConfig: config.MercuryTransmitter, CapabilitiesRegistry: r.CapabilitiesRegistry, + HTTPClient: r.HTTPClient, } relayer, err2 := evmrelay.NewRelayer(lggr.Named(relayID.ChainID), chain, relayerOpts) if err2 != nil { @@ -171,12 +174,12 @@ func (r *RelayerFactory) NewSolana(ks keystore.Solana, chainCfgs solcfg.TOMLConf type StarkNetFactoryConfig struct { Keystore keystore.StarkNet - config.TOMLConfigs + starkcfg.TOMLConfigs } // TODO BCF-2606 consider consolidating the driving logic with that of NewSolana above via generics // perhaps when we implement a Cosmos LOOP -func (r *RelayerFactory) NewStarkNet(ks keystore.StarkNet, chainCfgs config.TOMLConfigs) (map[types.RelayID]loop.Relayer, error) { +func (r *RelayerFactory) NewStarkNet(ks keystore.StarkNet, chainCfgs starkcfg.TOMLConfigs) (map[types.RelayID]loop.Relayer, error) { starknetRelayers := make(map[types.RelayID]loop.Relayer) var ( @@ -205,7 +208,7 @@ func (r *RelayerFactory) NewStarkNet(ks keystore.StarkNet, chainCfgs config.TOML if cmdName := env.StarknetPlugin.Cmd.Get(); cmdName != "" { // setup the starknet relayer to be a LOOP cfgTOML, err := toml.Marshal(struct { - Starknet config.TOMLConfig + Starknet starkcfg.TOMLConfig }{Starknet: *chainCfg}) if err != nil { return nil, fmt.Errorf("failed to marshal StarkNet configs: %w", err) @@ -301,3 +304,65 @@ func (r *RelayerFactory) NewCosmos(config CosmosFactoryConfig) (map[types.RelayI } return relayers, nil } + +type AptosFactoryConfig struct { + Keystore keystore.Aptos + TOMLConfigs RawConfigs +} + +func (r *RelayerFactory) NewAptos(ks keystore.Aptos, chainCfgs RawConfigs) (map[types.RelayID]loop.Relayer, error) { + plugin := env.NewPlugin("aptos") + loopKs := &keystore.AptosLooppSigner{Aptos: ks} + return r.NewLOOPRelayer("Aptos", corerelay.NetworkAptos, plugin, loopKs, chainCfgs) +} + +func (r *RelayerFactory) NewLOOPRelayer(name string, network string, plugin env.Plugin, ks coretypes.Keystore, chainCfgs RawConfigs) (map[types.RelayID]loop.Relayer, error) { + relayers := make(map[types.RelayID]loop.Relayer) + lggr := r.Logger.Named(name) + + unique := make(map[string]struct{}) + // create one relayer per chain id + for _, chainCfg := range chainCfgs { + relayID := types.RelayID{Network: network, ChainID: chainCfg.ChainID()} + if _, alreadyExists := unique[relayID.Name()]; alreadyExists { + return nil, fmt.Errorf("duplicate chain definitions for %s", relayID.Name()) + } + unique[relayID.Name()] = struct{}{} + + // skip disabled chains from further processing + if !chainCfg.IsEnabled() { + lggr.Warnw("Skipping disabled chain", "id", relayID.ChainID) + continue + } + + lggr2 := lggr.Named(relayID.ChainID) + + cmdName := plugin.Cmd.Get() + if cmdName == "" { + return nil, fmt.Errorf("plugin not defined: %s", "") + } + + // setup the relayer as a LOOP + cfgTOML, err := toml.Marshal(chainCfg) + if err != nil { + return nil, fmt.Errorf("failed to marshal configs: %w", err) + } + + envVars, err := plugins.ParseEnvFile(plugin.Env.Get()) + if err != nil { + return nil, fmt.Errorf("failed to parse env file: %w", err) + } + cmdFn, err := plugins.NewCmdFactory(r.Register, plugins.CmdConfig{ + ID: relayID.Name(), + Cmd: cmdName, + Env: envVars, + }) + if err != nil { + return nil, fmt.Errorf("failed to create LOOP command: %w", err) + } + // the relayer service has a delicate keystore dependency. the value that is passed to NewRelayerService must + // be compatible with instantiating a starknet transaction manager KeystoreAdapter within the LOOPp executable. + relayers[relayID] = loop.NewRelayerService(lggr2, r.GRPCOpts, cmdFn, string(cfgTOML), ks, r.CapabilitiesRegistry) + } + return relayers, nil +} diff --git a/core/services/chainlink/testdata/config-empty-effective.toml b/core/services/chainlink/testdata/config-empty-effective.toml index 16adff24a7..4cfe5e2086 100644 --- a/core/services/chainlink/testdata/config-empty-effective.toml +++ b/core/services/chainlink/testdata/config-empty-effective.toml @@ -6,7 +6,8 @@ ShutdownGracePeriod = '5s' FeedsManager = true LogPoller = false UICSAKeys = false -CCIP = false +CCIP = true +MultiFeedsManagers = false [Database] DefaultIdleInTxSessionTimeout = '1h0m0s' @@ -34,7 +35,7 @@ LeaseDuration = '10s' LeaseRefreshInterval = '1s' [TelemetryIngress] -UniConn = true +UniConn = false Logging = false BufferSize = 100 MaxBatchSize = 50 @@ -252,7 +253,36 @@ DeltaDial = '15s' DeltaReconcile = '1m0s' ListenAddresses = [] +[Capabilities.Dispatcher] +SupportedVersion = 1 +ReceiverBufferSize = 10000 + +[Capabilities.Dispatcher.RateLimit] +GlobalRPS = 800.0 +GlobalBurst = 1000 +PerSenderRPS = 10.0 +PerSenderBurst = 50 + [Capabilities.ExternalRegistry] Address = '' NetworkID = 'evm' ChainID = '1' + +[Capabilities.GatewayConnector] +ChainIDForNodeKey = '' +NodeAddress = '' +DonID = '' +WSHandshakeTimeoutMillis = 0 +AuthMinChallengeLen = 0 +AuthTimestampToleranceSec = 0 + +[[Capabilities.GatewayConnector.Gateways]] +ID = '' +URL = '' + +[Telemetry] +Enabled = false +CACertFile = '' +Endpoint = '' +InsecureConnection = false +TraceSampleRatio = 0.01 diff --git a/core/services/chainlink/testdata/config-full.toml b/core/services/chainlink/testdata/config-full.toml index c10d59f339..32437f99f7 100644 --- a/core/services/chainlink/testdata/config-full.toml +++ b/core/services/chainlink/testdata/config-full.toml @@ -6,7 +6,8 @@ ShutdownGracePeriod = '10s' FeedsManager = true LogPoller = true UICSAKeys = true -CCIP = false +CCIP = true +MultiFeedsManagers = true [Database] DefaultIdleInTxSessionTimeout = '1m0s' @@ -34,7 +35,7 @@ LeaseDuration = '1m0s' LeaseRefreshInterval = '1s' [TelemetryIngress] -UniConn = true +UniConn = false Logging = true BufferSize = 1234 MaxBatchSize = 4321 @@ -262,11 +263,44 @@ DeltaDial = '1m0s' DeltaReconcile = '2s' ListenAddresses = ['foo', 'bar'] +[Capabilities.Dispatcher] +SupportedVersion = 1 +ReceiverBufferSize = 10000 + +[Capabilities.Dispatcher.RateLimit] +GlobalRPS = 800.0 +GlobalBurst = 1000 +PerSenderRPS = 10.0 +PerSenderBurst = 50 + [Capabilities.ExternalRegistry] Address = '' NetworkID = 'evm' ChainID = '1' +[Capabilities.GatewayConnector] +ChainIDForNodeKey = '11155111' +NodeAddress = '0x68902d681c28119f9b2531473a417088bf008e59' +DonID = 'example_don' +WSHandshakeTimeoutMillis = 100 +AuthMinChallengeLen = 10 +AuthTimestampToleranceSec = 10 + +[[Capabilities.GatewayConnector.Gateways]] +ID = 'example_gateway' +URL = 'wss://localhost:8081/node' + +[Telemetry] +Enabled = true +CACertFile = 'cert-file' +Endpoint = 'example.com/collector' +InsecureConnection = true +TraceSampleRatio = 0.01 + +[Telemetry.ResourceAttributes] +Baz = 'test' +Foo = 'bar' + [[EVM]] ChainID = '1' Enabled = false @@ -288,6 +322,7 @@ MinContractPayment = '9.223372036854775807 link' NonceAutoSync = true NoNewHeadsThreshold = '1m0s' OperatorFactoryAddress = '0xa5B85635Be42F21f94F28034B7DA440EeFF0F418' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 17 RPCBlockQueryDelay = 10 FinalizedBlockOffset = 16 @@ -368,6 +403,7 @@ NodeIsSyncingEnabled = true FinalizedBlockPollInterval = '1s' EnforceRepeatableRead = true DeathDeclarationDelay = '1m0s' +NewHeadsPollInterval = '0s' [EVM.NodePool.Errors] NonceTooLow = '(: |^)nonce too low' @@ -384,6 +420,7 @@ L2Full = '(: |^)l2 full' TransactionAlreadyMined = '(: |^)transaction already mined' Fatal = '(: |^)fatal' ServiceUnavailable = '(: |^)service unavailable' +TooManyResults = '(: |^)too many results' [EVM.OCR] ContractConfirmations = 11 @@ -397,6 +434,9 @@ ObservationGracePeriod = '1s' [EVM.OCR2.Automation] GasLimit = 540 +[EVM.Workflow] +GasLimitDefault = 400000 + [[EVM.Nodes]] Name = 'foo' WSURL = 'wss://web.socket/test/foo' diff --git a/core/services/chainlink/testdata/config-multi-chain-effective.toml b/core/services/chainlink/testdata/config-multi-chain-effective.toml index edb8d0a249..096d5b1092 100644 --- a/core/services/chainlink/testdata/config-multi-chain-effective.toml +++ b/core/services/chainlink/testdata/config-multi-chain-effective.toml @@ -6,7 +6,8 @@ ShutdownGracePeriod = '5s' FeedsManager = true LogPoller = false UICSAKeys = false -CCIP = false +CCIP = true +MultiFeedsManagers = false [Database] DefaultIdleInTxSessionTimeout = '1h0m0s' @@ -34,7 +35,7 @@ LeaseDuration = '10s' LeaseRefreshInterval = '1s' [TelemetryIngress] -UniConn = true +UniConn = false Logging = false BufferSize = 100 MaxBatchSize = 50 @@ -252,11 +253,40 @@ DeltaDial = '15s' DeltaReconcile = '1m0s' ListenAddresses = [] +[Capabilities.Dispatcher] +SupportedVersion = 1 +ReceiverBufferSize = 10000 + +[Capabilities.Dispatcher.RateLimit] +GlobalRPS = 800.0 +GlobalBurst = 1000 +PerSenderRPS = 10.0 +PerSenderBurst = 50 + [Capabilities.ExternalRegistry] Address = '' NetworkID = 'evm' ChainID = '1' +[Capabilities.GatewayConnector] +ChainIDForNodeKey = '' +NodeAddress = '' +DonID = '' +WSHandshakeTimeoutMillis = 0 +AuthMinChallengeLen = 0 +AuthTimestampToleranceSec = 0 + +[[Capabilities.GatewayConnector.Gateways]] +ID = '' +URL = '' + +[Telemetry] +Enabled = false +CACertFile = '' +Endpoint = '' +InsecureConnection = false +TraceSampleRatio = 0.01 + [[EVM]] ChainID = '1' AutoCreateKey = true @@ -275,6 +305,7 @@ MinContractPayment = '0.1 link' NonceAutoSync = true NoNewHeadsThreshold = '3m0s' OperatorFactoryAddress = '0x3E64Cd889482443324F91bFA9c84fE72A511f48A' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 12 @@ -339,6 +370,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [EVM.OCR] ContractConfirmations = 4 @@ -352,6 +384,9 @@ ObservationGracePeriod = '1s' [EVM.OCR2.Automation] GasLimit = 10500000 +[EVM.Workflow] +GasLimitDefault = 400000 + [[EVM.Nodes]] Name = 'primary' WSURL = 'wss://web.socket/mainnet' @@ -379,6 +414,7 @@ MinContractPayment = '0.1 link' NonceAutoSync = true NoNewHeadsThreshold = '3m0s' OperatorFactoryAddress = '0x8007e24251b1D2Fc518Eb843A701d9cD21fe0aA3' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -443,6 +479,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [EVM.OCR] ContractConfirmations = 4 @@ -456,6 +493,9 @@ ObservationGracePeriod = '1s' [EVM.OCR2.Automation] GasLimit = 5400000 +[EVM.Workflow] +GasLimitDefault = 400000 + [[EVM.Nodes]] Name = 'foo' WSURL = 'wss://web.socket/test/foo' @@ -477,6 +517,7 @@ MinIncomingConfirmations = 5 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '30s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 100 RPCBlockQueryDelay = 10 FinalizedBlockOffset = 0 @@ -541,6 +582,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [EVM.OCR] ContractConfirmations = 4 @@ -554,6 +596,9 @@ ObservationGracePeriod = '1s' [EVM.OCR2.Automation] GasLimit = 5400000 +[EVM.Workflow] +GasLimitDefault = 400000 + [[EVM.Nodes]] Name = 'bar' WSURL = 'wss://web.socket/test/bar' diff --git a/core/services/chainlink/types.go b/core/services/chainlink/types.go index 4aa3782549..74ffc5dc66 100644 --- a/core/services/chainlink/types.go +++ b/core/services/chainlink/types.go @@ -15,6 +15,7 @@ type GeneralConfig interface { CosmosConfigs() coscfg.TOMLConfigs SolanaConfigs() solcfg.TOMLConfigs StarknetConfigs() stkcfg.TOMLConfigs + AptosConfigs() RawConfigs // ConfigTOML returns both the user provided and effective configuration as TOML. ConfigTOML() (user, effective string) } diff --git a/core/services/feeds/config.go b/core/services/feeds/config.go index e2ec889b23..626dc862e9 100644 --- a/core/services/feeds/config.go +++ b/core/services/feeds/config.go @@ -12,6 +12,10 @@ type GeneralConfig interface { Insecure() coreconfig.Insecure } +type FeatureConfig interface { + MultiFeedsManagers() bool +} + type JobConfig interface { DefaultHTTPTimeout() commonconfig.Duration } diff --git a/core/services/feeds/connection_manager.go b/core/services/feeds/connection_manager.go index ad7e1318e7..aadfc41bd5 100644 --- a/core/services/feeds/connection_manager.go +++ b/core/services/feeds/connection_manager.go @@ -5,9 +5,9 @@ import ( "sync" "github.com/pkg/errors" + "google.golang.org/grpc/connectivity" "github.com/smartcontractkit/wsrpc" - "github.com/smartcontractkit/wsrpc/connectivity" "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -110,7 +110,12 @@ func (mgr *connectionsManager) Connect(opts ConnectOpts) { return } - defer clientConn.Close() + defer func() { + cerr := clientConn.Close() + if cerr != nil { + mgr.lggr.Warnf("Error closing wsrpc client connection: %v", cerr) + } + }() mgr.lggr.Infow("Connected to Feeds Manager", "feedsManagerID", opts.FeedsManagerID) diff --git a/core/services/feeds/mocks/orm.go b/core/services/feeds/mocks/orm.go index 3fce89eb60..d6cae81dc6 100644 --- a/core/services/feeds/mocks/orm.go +++ b/core/services/feeds/mocks/orm.go @@ -6,6 +6,8 @@ import ( context "context" feeds "github.com/smartcontractkit/chainlink/v2/core/services/feeds" + crypto "github.com/smartcontractkit/chainlink/v2/core/utils/crypto" + mock "github.com/stretchr/testify/mock" sqlutil "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" @@ -1269,64 +1271,6 @@ func (_c *ORM_ListChainConfigsByManagerIDs_Call) RunAndReturn(run func(context.C return _c } -// ListJobProposals provides a mock function with given fields: ctx -func (_m *ORM) ListJobProposals(ctx context.Context) ([]feeds.JobProposal, error) { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for ListJobProposals") - } - - var r0 []feeds.JobProposal - var r1 error - if rf, ok := ret.Get(0).(func(context.Context) ([]feeds.JobProposal, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) []feeds.JobProposal); ok { - r0 = rf(ctx) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]feeds.JobProposal) - } - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ORM_ListJobProposals_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListJobProposals' -type ORM_ListJobProposals_Call struct { - *mock.Call -} - -// ListJobProposals is a helper method to define mock.On call -// - ctx context.Context -func (_e *ORM_Expecter) ListJobProposals(ctx interface{}) *ORM_ListJobProposals_Call { - return &ORM_ListJobProposals_Call{Call: _e.mock.On("ListJobProposals", ctx)} -} - -func (_c *ORM_ListJobProposals_Call) Run(run func(ctx context.Context)) *ORM_ListJobProposals_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *ORM_ListJobProposals_Call) Return(jps []feeds.JobProposal, err error) *ORM_ListJobProposals_Call { - _c.Call.Return(jps, err) - return _c -} - -func (_c *ORM_ListJobProposals_Call) RunAndReturn(run func(context.Context) ([]feeds.JobProposal, error)) *ORM_ListJobProposals_Call { - _c.Call.Return(run) - return _c -} - // ListJobProposalsByManagersIDs provides a mock function with given fields: ctx, ids func (_m *ORM) ListJobProposalsByManagersIDs(ctx context.Context, ids []int64) ([]feeds.JobProposal, error) { ret := _m.Called(ctx, ids) @@ -1562,6 +1506,63 @@ func (_c *ORM_ListSpecsByJobProposalIDs_Call) RunAndReturn(run func(context.Cont return _c } +// ManagerExists provides a mock function with given fields: ctx, publicKey +func (_m *ORM) ManagerExists(ctx context.Context, publicKey crypto.PublicKey) (bool, error) { + ret := _m.Called(ctx, publicKey) + + if len(ret) == 0 { + panic("no return value specified for ManagerExists") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, crypto.PublicKey) (bool, error)); ok { + return rf(ctx, publicKey) + } + if rf, ok := ret.Get(0).(func(context.Context, crypto.PublicKey) bool); ok { + r0 = rf(ctx, publicKey) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, crypto.PublicKey) error); ok { + r1 = rf(ctx, publicKey) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ORM_ManagerExists_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ManagerExists' +type ORM_ManagerExists_Call struct { + *mock.Call +} + +// ManagerExists is a helper method to define mock.On call +// - ctx context.Context +// - publicKey crypto.PublicKey +func (_e *ORM_Expecter) ManagerExists(ctx interface{}, publicKey interface{}) *ORM_ManagerExists_Call { + return &ORM_ManagerExists_Call{Call: _e.mock.On("ManagerExists", ctx, publicKey)} +} + +func (_c *ORM_ManagerExists_Call) Run(run func(ctx context.Context, publicKey crypto.PublicKey)) *ORM_ManagerExists_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(crypto.PublicKey)) + }) + return _c +} + +func (_c *ORM_ManagerExists_Call) Return(_a0 bool, _a1 error) *ORM_ManagerExists_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ORM_ManagerExists_Call) RunAndReturn(run func(context.Context, crypto.PublicKey) (bool, error)) *ORM_ManagerExists_Call { + _c.Call.Return(run) + return _c +} + // RejectSpec provides a mock function with given fields: ctx, id func (_m *ORM) RejectSpec(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) diff --git a/core/services/feeds/mocks/service.go b/core/services/feeds/mocks/service.go index d37c327850..d84879bb70 100644 --- a/core/services/feeds/mocks/service.go +++ b/core/services/feeds/mocks/service.go @@ -220,62 +220,6 @@ func (_c *Service_CountJobProposalsByStatus_Call) RunAndReturn(run func(context. return _c } -// CountManagers provides a mock function with given fields: ctx -func (_m *Service) CountManagers(ctx context.Context) (int64, error) { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for CountManagers") - } - - var r0 int64 - var r1 error - if rf, ok := ret.Get(0).(func(context.Context) (int64, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) int64); ok { - r0 = rf(ctx) - } else { - r0 = ret.Get(0).(int64) - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Service_CountManagers_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CountManagers' -type Service_CountManagers_Call struct { - *mock.Call -} - -// CountManagers is a helper method to define mock.On call -// - ctx context.Context -func (_e *Service_Expecter) CountManagers(ctx interface{}) *Service_CountManagers_Call { - return &Service_CountManagers_Call{Call: _e.mock.On("CountManagers", ctx)} -} - -func (_c *Service_CountManagers_Call) Run(run func(ctx context.Context)) *Service_CountManagers_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *Service_CountManagers_Call) Return(_a0 int64, _a1 error) *Service_CountManagers_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *Service_CountManagers_Call) RunAndReturn(run func(context.Context) (int64, error)) *Service_CountManagers_Call { - _c.Call.Return(run) - return _c -} - // CreateChainConfig provides a mock function with given fields: ctx, cfg func (_m *Service) CreateChainConfig(ctx context.Context, cfg feeds.ChainConfig) (int64, error) { ret := _m.Called(ctx, cfg) @@ -799,64 +743,6 @@ func (_c *Service_ListChainConfigsByManagerIDs_Call) RunAndReturn(run func(conte return _c } -// ListJobProposals provides a mock function with given fields: ctx -func (_m *Service) ListJobProposals(ctx context.Context) ([]feeds.JobProposal, error) { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for ListJobProposals") - } - - var r0 []feeds.JobProposal - var r1 error - if rf, ok := ret.Get(0).(func(context.Context) ([]feeds.JobProposal, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) []feeds.JobProposal); ok { - r0 = rf(ctx) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]feeds.JobProposal) - } - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Service_ListJobProposals_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListJobProposals' -type Service_ListJobProposals_Call struct { - *mock.Call -} - -// ListJobProposals is a helper method to define mock.On call -// - ctx context.Context -func (_e *Service_Expecter) ListJobProposals(ctx interface{}) *Service_ListJobProposals_Call { - return &Service_ListJobProposals_Call{Call: _e.mock.On("ListJobProposals", ctx)} -} - -func (_c *Service_ListJobProposals_Call) Run(run func(ctx context.Context)) *Service_ListJobProposals_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *Service_ListJobProposals_Call) Return(_a0 []feeds.JobProposal, _a1 error) *Service_ListJobProposals_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *Service_ListJobProposals_Call) RunAndReturn(run func(context.Context) ([]feeds.JobProposal, error)) *Service_ListJobProposals_Call { - _c.Call.Return(run) - return _c -} - // ListJobProposalsByManagersIDs provides a mock function with given fields: ctx, ids func (_m *Service) ListJobProposalsByManagersIDs(ctx context.Context, ids []int64) ([]feeds.JobProposal, error) { ret := _m.Called(ctx, ids) diff --git a/core/services/feeds/orm.go b/core/services/feeds/orm.go index d130316fb2..82d1114a23 100644 --- a/core/services/feeds/orm.go +++ b/core/services/feeds/orm.go @@ -11,9 +11,12 @@ import ( "github.com/pkg/errors" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" + + "github.com/smartcontractkit/chainlink/v2/core/utils/crypto" ) type ORM interface { + ManagerExists(ctx context.Context, publicKey crypto.PublicKey) (bool, error) CountManagers(ctx context.Context) (int64, error) CreateManager(ctx context.Context, ms *FeedsManager) (int64, error) GetManager(ctx context.Context, id int64) (*FeedsManager, error) @@ -34,7 +37,6 @@ type ORM interface { DeleteProposal(ctx context.Context, id int64) error GetJobProposal(ctx context.Context, id int64) (*JobProposal, error) GetJobProposalByRemoteUUID(ctx context.Context, uuid uuid.UUID) (*JobProposal, error) - ListJobProposals(ctx context.Context) (jps []JobProposal, err error) ListJobProposalsByManagersIDs(ctx context.Context, ids []int64) ([]JobProposal, error) UpdateJobProposalStatus(ctx context.Context, id int64, status JobProposalStatus) error // NEEDED? UpsertJobProposal(ctx context.Context, jp *JobProposal) (int64, error) @@ -74,6 +76,7 @@ func (o *orm) Transact(ctx context.Context, fn func(ORM) error) error { func (o *orm) WithDataSource(ds sqlutil.DataSource) ORM { return &orm{ds} } // Count counts the number of feeds manager records. +// TODO: delete once multiple feeds managers support is released func (o *orm) CountManagers(ctx context.Context) (count int64, err error) { stmt := ` SELECT COUNT(*) @@ -84,6 +87,21 @@ FROM feeds_managers return count, errors.Wrap(err, "CountManagers failed") } +// ManagerExists checks if a feeds manager exists by public key. +func (o *orm) ManagerExists(ctx context.Context, publicKey crypto.PublicKey) (bool, error) { + stmt := ` +SELECT EXISTS ( + SELECT 1 + FROM feeds_managers + WHERE public_key = $1 +); + ` + + var exists bool + err := o.ds.GetContext(ctx, &exists, stmt, publicKey) + return exists, errors.Wrap(err, "ManagerExists failed") +} + // CreateManager creates a feeds manager. func (o *orm) CreateManager(ctx context.Context, ms *FeedsManager) (id int64, err error) { stmt := ` @@ -264,7 +282,8 @@ WHERE id = $1 func (o *orm) ListManagers(ctx context.Context) (mgrs []FeedsManager, err error) { stmt := ` SELECT id, name, uri, public_key, created_at, updated_at -FROM feeds_managers; +FROM feeds_managers +ORDER BY created_at; ` err = o.ds.SelectContext(ctx, &mgrs, stmt) @@ -373,17 +392,6 @@ AND status <> $2; return jp, errors.Wrap(err, "GetJobProposalByRemoteUUID failed") } -// ListJobProposals lists all job proposals. -func (o *orm) ListJobProposals(ctx context.Context) (jps []JobProposal, err error) { - stmt := ` -SELECT * -FROM job_proposals; -` - - err = o.ds.SelectContext(ctx, &jps, stmt) - return jps, errors.Wrap(err, "ListJobProposals failed") -} - // ListJobProposalsByManagersIDs gets job proposals by feeds managers IDs. func (o *orm) ListJobProposalsByManagersIDs(ctx context.Context, ids []int64) ([]JobProposal, error) { stmt := ` @@ -819,6 +827,7 @@ SELECT exists ( FROM job_proposals INNER JOIN jobs ON job_proposals.external_job_id = jobs.external_job_id WHERE jobs.id = $1 + AND job_proposals.status <> 'deleted' ); ` diff --git a/core/services/feeds/orm_test.go b/core/services/feeds/orm_test.go index c4c9ced2ce..976465f37f 100644 --- a/core/services/feeds/orm_test.go +++ b/core/services/feeds/orm_test.go @@ -53,7 +53,7 @@ func setupORM(t *testing.T) *TestORM { // Managers -func Test_ORM_CreateManager(t *testing.T) { +func Test_ORM_CreateManager_CountManagers(t *testing.T) { t.Parallel() ctx := testutils.Context(t) @@ -80,6 +80,33 @@ func Test_ORM_CreateManager(t *testing.T) { assert.NotZero(t, id) } +func Test_ORM_CreateManager(t *testing.T) { + t.Parallel() + ctx := testutils.Context(t) + + var ( + orm = setupORM(t) + mgr = &feeds.FeedsManager{ + URI: uri, + Name: name, + PublicKey: publicKey, + } + ) + + exists, err := orm.ManagerExists(ctx, publicKey) + require.NoError(t, err) + require.Equal(t, false, exists) + + id, err := orm.CreateManager(ctx, mgr) + require.NoError(t, err) + + exists, err = orm.ManagerExists(ctx, publicKey) + require.NoError(t, err) + require.Equal(t, true, exists) + + assert.NotZero(t, id) +} + func Test_ORM_GetManager(t *testing.T) { t.Parallel() ctx := testutils.Context(t) @@ -555,39 +582,6 @@ func Test_ORM_GetJobProposal(t *testing.T) { }) } -func Test_ORM_ListJobProposals(t *testing.T) { - t.Parallel() - ctx := testutils.Context(t) - - orm := setupORM(t) - fmID := createFeedsManager(t, orm) - uuid := uuid.New() - name := null.StringFrom("jp1") - - jp := &feeds.JobProposal{ - Name: name, - RemoteUUID: uuid, - Status: feeds.JobProposalStatusPending, - FeedsManagerID: fmID, - } - - id, err := orm.CreateJobProposal(ctx, jp) - require.NoError(t, err) - - jps, err := orm.ListJobProposals(ctx) - require.NoError(t, err) - require.Len(t, jps, 1) - - actual := jps[0] - assert.Equal(t, id, actual.ID) - assert.Equal(t, name, actual.Name) - assert.Equal(t, uuid, actual.RemoteUUID) - assert.Equal(t, jp.Status, actual.Status) - assert.False(t, actual.ExternalJobID.Valid) - assert.False(t, actual.PendingUpdate) - assert.Equal(t, jp.FeedsManagerID, actual.FeedsManagerID) -} - func Test_ORM_CountJobProposalsByStatus(t *testing.T) { t.Parallel() @@ -1659,6 +1653,14 @@ func Test_ORM_IsJobManaged(t *testing.T) { isManaged, err = orm.IsJobManaged(ctx, int64(j.ID)) require.NoError(t, err) assert.True(t, isManaged) + + // delete the proposal + err = orm.DeleteProposal(ctx, jpID) + require.NoError(t, err) + + isManaged, err = orm.IsJobManaged(ctx, int64(j.ID)) + require.NoError(t, err) + assert.False(t, isManaged) } // Helpers diff --git a/core/services/feeds/proto/feeds_manager.pb.go b/core/services/feeds/proto/feeds_manager.pb.go index 89f351a427..010ee44ae8 100644 --- a/core/services/feeds/proto/feeds_manager.pb.go +++ b/core/services/feeds/proto/feeds_manager.pb.go @@ -7,10 +7,11 @@ package proto import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" + + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" ) const ( @@ -79,8 +80,7 @@ const ( ChainType_CHAIN_TYPE_UNSPECIFIED ChainType = 0 ChainType_CHAIN_TYPE_EVM ChainType = 1 ChainType_CHAIN_TYPE_SOLANA ChainType = 2 - ChainType_CHAIN_TYPE_ZKSYNC ChainType = 3 - ChainType_CHAIN_TYPE_STARKNET ChainType = 4 + ChainType_CHAIN_TYPE_STARKNET ChainType = 3 ) // Enum value maps for ChainType. @@ -89,15 +89,13 @@ var ( 0: "CHAIN_TYPE_UNSPECIFIED", 1: "CHAIN_TYPE_EVM", 2: "CHAIN_TYPE_SOLANA", - 3: "CHAIN_TYPE_ZKSYNC", - 4: "CHAIN_TYPE_STARKNET", + 3: "CHAIN_TYPE_STARKNET", } ChainType_value = map[string]int32{ "CHAIN_TYPE_UNSPECIFIED": 0, "CHAIN_TYPE_EVM": 1, "CHAIN_TYPE_SOLANA": 2, - "CHAIN_TYPE_ZKSYNC": 3, - "CHAIN_TYPE_STARKNET": 4, + "CHAIN_TYPE_STARKNET": 3, } ) @@ -476,13 +474,17 @@ type ChainConfig struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Chain *Chain `protobuf:"bytes,1,opt,name=chain,proto3" json:"chain,omitempty"` - AccountAddress string `protobuf:"bytes,2,opt,name=account_address,json=accountAddress,proto3" json:"account_address,omitempty"` - AdminAddress string `protobuf:"bytes,3,opt,name=admin_address,json=adminAddress,proto3" json:"admin_address,omitempty"` - FluxMonitorConfig *FluxMonitorConfig `protobuf:"bytes,4,opt,name=flux_monitor_config,json=fluxMonitorConfig,proto3" json:"flux_monitor_config,omitempty"` - Ocr1Config *OCR1Config `protobuf:"bytes,5,opt,name=ocr1_config,json=ocr1Config,proto3" json:"ocr1_config,omitempty"` - Ocr2Config *OCR2Config `protobuf:"bytes,6,opt,name=ocr2_config,json=ocr2Config,proto3" json:"ocr2_config,omitempty"` - AccountAddressPublicKey *string `protobuf:"bytes,7,opt,name=account_address_public_key,json=accountAddressPublicKey,proto3,oneof" json:"account_address_public_key,omitempty"` + Chain *Chain `protobuf:"bytes,1,opt,name=chain,proto3" json:"chain,omitempty"` + AccountAddress string `protobuf:"bytes,2,opt,name=account_address,json=accountAddress,proto3" json:"account_address,omitempty"` + AdminAddress string `protobuf:"bytes,3,opt,name=admin_address,json=adminAddress,proto3" json:"admin_address,omitempty"` + FluxMonitorConfig *FluxMonitorConfig `protobuf:"bytes,4,opt,name=flux_monitor_config,json=fluxMonitorConfig,proto3" json:"flux_monitor_config,omitempty"` + Ocr1Config *OCR1Config `protobuf:"bytes,5,opt,name=ocr1_config,json=ocr1Config,proto3" json:"ocr1_config,omitempty"` + Ocr2Config *OCR2Config `protobuf:"bytes,6,opt,name=ocr2_config,json=ocr2Config,proto3" json:"ocr2_config,omitempty"` + // For EVM chains, we do not need this value and it is kept in the node's + // keystore. For starknet, because the wallet address needs to be deployed + // using this value and this pub key needs to be passed into the starknet + // relayer, we request the node to send this directly to CLO. + AccountAddressPublicKey *string `protobuf:"bytes,7,opt,name=account_address_public_key,json=accountAddressPublicKey,proto3,oneof" json:"account_address_public_key,omitempty"` } func (x *ChainConfig) Reset() { @@ -1912,53 +1914,52 @@ var file_pkg_noderpc_proto_feeds_manager_proto_rawDesc = []byte{ 0x5f, 0x4d, 0x4f, 0x4e, 0x49, 0x54, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x4a, 0x4f, 0x42, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4f, 0x43, 0x52, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x4a, 0x4f, 0x42, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4f, 0x43, 0x52, 0x32, 0x10, 0x03, 0x2a, - 0x82, 0x01, 0x0a, 0x09, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, - 0x16, 0x43, 0x48, 0x41, 0x49, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, - 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x43, 0x48, 0x41, - 0x49, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x45, 0x56, 0x4d, 0x10, 0x01, 0x12, 0x15, 0x0a, - 0x11, 0x43, 0x48, 0x41, 0x49, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x4f, 0x4c, 0x41, - 0x4e, 0x41, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x48, 0x41, 0x49, 0x4e, 0x5f, 0x54, 0x59, - 0x50, 0x45, 0x5f, 0x5a, 0x4b, 0x53, 0x59, 0x4e, 0x43, 0x10, 0x03, 0x12, 0x17, 0x0a, 0x13, 0x43, - 0x48, 0x41, 0x49, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x4b, 0x4e, - 0x45, 0x54, 0x10, 0x04, 0x32, 0xd8, 0x02, 0x0a, 0x0c, 0x46, 0x65, 0x65, 0x64, 0x73, 0x4d, 0x61, - 0x6e, 0x61, 0x67, 0x65, 0x72, 0x12, 0x40, 0x0a, 0x0b, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, - 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x17, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x41, 0x70, 0x70, 0x72, 0x6f, - 0x76, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, - 0x63, 0x66, 0x6d, 0x2e, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x6c, 0x74, - 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x17, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x48, 0x65, 0x61, - 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x18, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, - 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x0a, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x17, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x64, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x0b, 0x52, 0x65, 0x6a, 0x65, - 0x63, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x17, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x52, 0x65, - 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x18, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x52, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4a, - 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x0c, 0x43, 0x61, - 0x6e, 0x63, 0x65, 0x6c, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x18, 0x2e, 0x63, 0x66, 0x6d, - 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, - 0x6c, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, - 0xc4, 0x01, 0x0a, 0x0b, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, - 0x3d, 0x0a, 0x0a, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x16, 0x2e, + 0x6b, 0x0a, 0x09, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x16, + 0x43, 0x48, 0x41, 0x49, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, + 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x43, 0x48, 0x41, 0x49, + 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x45, 0x56, 0x4d, 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, + 0x43, 0x48, 0x41, 0x49, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x4f, 0x4c, 0x41, 0x4e, + 0x41, 0x10, 0x02, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x48, 0x41, 0x49, 0x4e, 0x5f, 0x54, 0x59, 0x50, + 0x45, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x4b, 0x4e, 0x45, 0x54, 0x10, 0x03, 0x32, 0xd8, 0x02, 0x0a, + 0x0c, 0x46, 0x65, 0x65, 0x64, 0x73, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x12, 0x40, 0x0a, + 0x0b, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x17, 0x2e, 0x63, + 0x66, 0x6d, 0x2e, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x41, 0x70, 0x70, 0x72, + 0x6f, 0x76, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x40, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x17, + 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x48, 0x65, + 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x3d, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x12, + 0x16, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x64, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x40, 0x0a, 0x0b, 0x52, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x12, + 0x17, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x52, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4a, 0x6f, + 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x52, + 0x65, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x43, 0x0a, 0x0c, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x6c, 0x65, 0x64, 0x4a, + 0x6f, 0x62, 0x12, 0x18, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x6c, + 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, + 0x66, 0x6d, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xc4, 0x01, 0x0a, 0x0b, 0x4e, 0x6f, 0x64, 0x65, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3d, 0x0a, 0x0a, 0x50, 0x72, 0x6f, 0x70, 0x6f, + 0x73, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x16, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x50, 0x72, 0x6f, 0x70, + 0x6f, 0x73, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x50, 0x72, 0x6f, 0x70, - 0x6f, 0x73, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, - 0x0a, 0x09, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x15, 0x2e, 0x63, 0x66, - 0x6d, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4a, - 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x09, 0x52, 0x65, - 0x76, 0x6f, 0x6b, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x15, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x52, 0x65, - 0x76, 0x6f, 0x6b, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, - 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3d, 0x5a, 0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x6d, 0x61, 0x72, 0x74, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, - 0x63, 0x74, 0x6b, 0x69, 0x74, 0x2f, 0x66, 0x65, 0x65, 0x64, 0x73, 0x2d, 0x6d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x72, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x72, 0x70, 0x63, 0x2f, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x09, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x4a, 0x6f, 0x62, 0x12, 0x15, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x63, 0x66, 0x6d, + 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x09, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x4a, 0x6f, 0x62, 0x12, + 0x15, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x4a, 0x6f, 0x62, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x63, 0x66, 0x6d, 0x2e, 0x52, 0x65, 0x76, + 0x6f, 0x6b, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3d, + 0x5a, 0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x6d, 0x61, + 0x72, 0x74, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x6b, 0x69, 0x74, 0x2f, 0x66, 0x65, + 0x65, 0x64, 0x73, 0x2d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2f, 0x70, 0x6b, 0x67, 0x2f, + 0x6e, 0x6f, 0x64, 0x65, 0x72, 0x70, 0x63, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/core/services/feeds/proto/feeds_manager_wsrpc.pb.go b/core/services/feeds/proto/feeds_manager_wsrpc.pb.go index a002257e5c..85476b1188 100644 --- a/core/services/feeds/proto/feeds_manager_wsrpc.pb.go +++ b/core/services/feeds/proto/feeds_manager_wsrpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-wsrpc. DO NOT EDIT. // versions: // - protoc-gen-go-wsrpc v0.0.1 -// - protoc v3.21.7 +// - protoc v4.25.3 package proto diff --git a/core/services/feeds/service.go b/core/services/feeds/service.go index 6dd855787f..9ac7cddad7 100644 --- a/core/services/feeds/service.go +++ b/core/services/feeds/service.go @@ -17,6 +17,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" + "github.com/smartcontractkit/chainlink/v2/plugins" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" @@ -37,11 +38,13 @@ import ( ) var ( - ErrOCR2Disabled = errors.New("ocr2 is disabled") - ErrOCRDisabled = errors.New("ocr is disabled") - ErrSingleFeedsManager = errors.New("only a single feeds manager is supported") - ErrJobAlreadyExists = errors.New("a job for this contract address already exists - please use the 'force' option to replace it") - ErrFeedsManagerDisabled = errors.New("feeds manager is disabled") + ErrOCR2Disabled = errors.New("ocr2 is disabled") + ErrOCRDisabled = errors.New("ocr is disabled") + // TODO: delete once multiple feeds managers support is released + ErrSingleFeedsManager = errors.New("only a single feeds manager is supported") + ErrDuplicateFeedsManager = errors.New("manager was previously registered using the same public key") + ErrJobAlreadyExists = errors.New("a job for this contract address already exists - please use the 'force' option to replace it") + ErrFeedsManagerDisabled = errors.New("feeds manager is disabled") promJobProposalRequest = promauto.NewCounter(prometheus.CounterOpts{ Name: "feeds_job_proposal_requests", @@ -77,7 +80,6 @@ type Service interface { Start(ctx context.Context) error Close() error - CountManagers(ctx context.Context) (int64, error) GetManager(ctx context.Context, id int64) (*FeedsManager, error) ListManagers(ctx context.Context) ([]FeedsManager, error) ListManagersByIDs(ctx context.Context, ids []int64) ([]FeedsManager, error) @@ -98,7 +100,6 @@ type Service interface { CountJobProposalsByStatus(ctx context.Context) (*JobProposalCounts, error) GetJobProposal(ctx context.Context, id int64) (*JobProposal, error) - ListJobProposals(ctx context.Context) ([]JobProposal, error) ListJobProposalsByManagersIDs(ctx context.Context, ids []int64) ([]JobProposal, error) ApproveSpec(ctx context.Context, id int64, force bool) error @@ -108,6 +109,7 @@ type Service interface { RejectSpec(ctx context.Context, id int64) error UpdateSpecDefinition(ctx context.Context, id int64, spec string) error + // Unsafe_SetConnectionsManager Only for testing Unsafe_SetConnectionsManager(ConnectionsManager) } @@ -123,6 +125,7 @@ type service struct { ocr2KeyStore keystore.OCR2 jobSpawner job.Spawner gCfg GeneralConfig + featCfg FeatureConfig insecureCfg InsecureConfig jobCfg JobConfig ocrCfg OCRConfig @@ -142,6 +145,7 @@ func NewService( jobSpawner job.Spawner, keyStore keystore.Master, gCfg GeneralConfig, + fCfg FeatureConfig, insecureCfg InsecureConfig, jobCfg JobConfig, ocrCfg OCRConfig, @@ -162,6 +166,7 @@ func NewService( ocr1KeyStore: keyStore.OCR(), ocr2KeyStore: keyStore.OCR2(), gCfg: gCfg, + featCfg: fCfg, insecureCfg: insecureCfg, jobCfg: jobCfg, ocrCfg: ocrCfg, @@ -185,15 +190,23 @@ type RegisterManagerParams struct { // RegisterManager registers a new ManagerService and attempts to establish a // connection. -// -// Only a single feeds manager is currently supported. func (s *service) RegisterManager(ctx context.Context, params RegisterManagerParams) (int64, error) { - count, err := s.CountManagers(ctx) - if err != nil { - return 0, err - } - if count >= 1 { - return 0, ErrSingleFeedsManager + if s.featCfg.MultiFeedsManagers() { + exists, err := s.orm.ManagerExists(ctx, params.PublicKey) + if err != nil { + return 0, err + } + if exists { + return 0, ErrDuplicateFeedsManager + } + } else { + count, err := s.CountManagers(ctx) + if err != nil { + return 0, err + } + if count >= 1 { + return 0, ErrSingleFeedsManager + } } mgr := FeedsManager{ @@ -204,11 +217,11 @@ func (s *service) RegisterManager(ctx context.Context, params RegisterManagerPar var id int64 - err = s.orm.Transact(ctx, func(tx ORM) error { + err := s.orm.Transact(ctx, func(tx ORM) error { var txerr error id, txerr = tx.CreateManager(ctx, &mgr) - if err != nil { + if txerr != nil { return txerr } @@ -218,6 +231,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 { @@ -321,6 +337,7 @@ func (s *service) ListManagersByIDs(ctx context.Context, ids []int64) ([]FeedsMa } // CountManagers gets the total number of manager services +// TODO: delete once multiple feeds managers support is released func (s *service) CountManagers(ctx context.Context) (int64, error) { return s.orm.CountManagers(ctx) } @@ -417,14 +434,6 @@ func (s *service) UpdateChainConfig(ctx context.Context, cfg ChainConfig) (int64 return id, nil } -// Lists all JobProposals -// -// When we support multiple feed managers, we will need to change this to filter -// by feeds manager -func (s *service) ListJobProposals(ctx context.Context) ([]JobProposal, error) { - return s.orm.ListJobProposals(ctx) -} - // ListJobProposalsByManagersIDs gets job proposals by feeds managers IDs func (s *service) ListJobProposalsByManagersIDs(ctx context.Context, ids []int64) ([]JobProposal, error) { return s.orm.ListJobProposalsByManagersIDs(ctx, ids) @@ -810,6 +819,13 @@ func (s *service) ApproveSpec(ctx context.Context, id int64, force bool) error { return fmt.Errorf("failed while checking for existing workflow job: %w", txerr) } } + case job.CCIP: + existingJobID, txerr = tx.jobORM.FindJobIDByCapabilityNameAndVersion(ctx, *j.CCIPSpec) + // Return an error if the repository errors. If there is a not found + // error we want to continue with approving the job. + if txerr != nil && !errors.Is(txerr, sql.ErrNoRows) { + return fmt.Errorf("failed while checking for existing ccip job: %w", txerr) + } default: return errors.Errorf("unsupported job type when approving job proposal specs: %s", j.Type) } @@ -1019,7 +1035,6 @@ func (s *service) Start(ctx context.Context) error { return err } - // We only support a single feeds manager right now mgrs, err := s.ListManagers(ctx) if err != nil { return err @@ -1030,8 +1045,14 @@ func (s *service) Start(ctx context.Context) error { return nil } - mgr := mgrs[0] - s.connectFeedManager(ctx, mgr, privkey) + if s.featCfg.MultiFeedsManagers() { + s.lggr.Infof("starting connection to %d feeds managers", len(mgrs)) + for _, mgr := range mgrs { + s.connectFeedManager(ctx, mgr, privkey) + } + } else { + s.connectFeedManager(ctx, mgrs[0], privkey) + } if err = s.observeJobProposalCounts(ctx); err != nil { s.lggr.Error("failed to observe job proposal count when starting service", err) @@ -1188,7 +1209,9 @@ func (s *service) generateJob(ctx context.Context, spec string) (*job.Job, error case job.FluxMonitor: js, err = fluxmonitorv2.ValidatedFluxMonitorSpec(s.jobCfg, spec) case job.Workflow: - js, err = workflows.ValidatedWorkflowJobSpec(spec) + js, err = workflows.ValidatedWorkflowJobSpec(ctx, spec) + case job.CCIP: + return nil, fmt.Errorf("CCIP job type is not supported") default: return nil, errors.Errorf("unknown job type: %s", jobType) } @@ -1440,7 +1463,6 @@ func (ns NullService) Close() error { return nil } func (ns NullService) ApproveSpec(ctx context.Context, id int64, force bool) error { return ErrFeedsManagerDisabled } -func (ns NullService) CountManagers(ctx context.Context) (int64, error) { return 0, nil } func (ns NullService) CountJobProposalsByStatus(ctx context.Context) (*JobProposalCounts, error) { return nil, ErrFeedsManagerDisabled } diff --git a/core/services/feeds/service_test.go b/core/services/feeds/service_test.go index 8c2f60bd63..cb629efb67 100644 --- a/core/services/feeds/service_test.go +++ b/core/services/feeds/service_test.go @@ -194,7 +194,7 @@ func setupTestServiceCfg(t *testing.T, overrideCfg func(c *chainlink.Config, s * keyStore.On("P2P").Return(p2pKeystore) keyStore.On("OCR").Return(ocr1Keystore) keyStore.On("OCR2").Return(ocr2Keystore) - svc := feeds.NewService(orm, jobORM, db, spawner, keyStore, gcfg, gcfg.Insecure(), gcfg.JobPipeline(), gcfg.OCR(), gcfg.OCR2(), legacyChains, lggr, "1.0.0", nil) + svc := feeds.NewService(orm, jobORM, db, spawner, keyStore, gcfg, gcfg.Feature(), gcfg.Insecure(), gcfg.JobPipeline(), gcfg.OCR(), gcfg.OCR2(), legacyChains, lggr, "1.0.0", nil) svc.SetConnectionsManager(connMgr) return &TestService{ @@ -265,6 +265,149 @@ func Test_Service_RegisterManager(t *testing.T) { assert.Equal(t, actual, id) } +func Test_Service_RegisterManager_MultiFeedsManager(t *testing.T) { + t.Parallel() + + key := cltest.DefaultCSAKey + + 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 := setupTestServiceCfg(t, func(c *chainlink.Config, s *chainlink.Secrets) { + var multiFeedsManagers = true + c.Feature.MultiFeedsManagers = &multiFeedsManagers + }) + ctx := testutils.Context(t) + + svc.orm.On("ManagerExists", ctx, params.PublicKey).Return(false, nil) + svc.orm.On("CreateManager", mock.Anything, &mgr, mock.Anything). + Return(id, nil) + svc.orm.On("CreateBatchChainConfig", mock.Anything, params.ChainConfigs, mock.Anything). + Return([]int64{}, nil) + svc.csaKeystore.On("GetAll").Return([]csakey.KeyV2{key}, nil) + // ListManagers runs in a goroutine so it might be called. + svc.orm.On("ListManagers", ctx).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)} + }) + svc.connMgr.On("Connect", mock.IsType(feeds.ConnectOpts{})) + + actual, err := svc.RegisterManager(ctx, params) + // We need to stop the service because the manager will attempt to make a + // connection + svc.Close() + require.NoError(t, err) + + 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_RegisterManager_DuplicateFeedsManager(t *testing.T) { + t.Parallel() + + var 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 := setupTestServiceCfg(t, func(c *chainlink.Config, s *chainlink.Secrets) { + var multiFeedsManagers = true + c.Feature.MultiFeedsManagers = &multiFeedsManagers + }) + ctx := testutils.Context(t) + + svc.orm.On("ManagerExists", ctx, params.PublicKey).Return(true, nil) + // ListManagers runs in a goroutine so it might be called. + svc.orm.On("ListManagers", ctx).Return([]feeds.FeedsManager{mgr}, nil).Maybe() + + _, err = svc.RegisterManager(ctx, 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, "manager was previously registered using the same public key", err.Error()) +} + func Test_Service_ListManagers(t *testing.T) { t.Parallel() ctx := testutils.Context(t) @@ -342,24 +485,6 @@ func Test_Service_ListManagersByIDs(t *testing.T) { assert.Equal(t, mgrs, actual) } -func Test_Service_CountManagers(t *testing.T) { - t.Parallel() - ctx := testutils.Context(t) - - var ( - count = int64(1) - ) - svc := setupTestService(t) - - svc.orm.On("CountManagers", mock.Anything). - Return(count, nil) - - actual, err := svc.CountManagers(ctx) - require.NoError(t, err) - - assert.Equal(t, count, actual) -} - func Test_Service_CreateChainConfig(t *testing.T) { var ( mgr = feeds.FeedsManager{ID: 1} @@ -1481,25 +1606,6 @@ func Test_Service_IsJobManaged(t *testing.T) { assert.True(t, isManaged) } -func Test_Service_ListJobProposals(t *testing.T) { - t.Parallel() - ctx := testutils.Context(t) - - var ( - jp = feeds.JobProposal{} - jps = []feeds.JobProposal{jp} - ) - svc := setupTestService(t) - - svc.orm.On("ListJobProposals", mock.Anything). - Return(jps, nil) - - actual, err := svc.ListJobProposals(ctx) - require.NoError(t, err) - - assert.Equal(t, actual, jps) -} - func Test_Service_ListJobProposalsByManagersIDs(t *testing.T) { t.Parallel() ctx := testutils.Context(t) @@ -3805,6 +3911,10 @@ func Test_Service_StartStop(t *testing.T) { ID: 1, URI: "localhost:2000", } + mgr2 = feeds.FeedsManager{ + ID: 2, + URI: "localhost:2001", + } pubKeyHex = "0f17c3bf72de8beef6e2d17a14c0a972f5d7e0e66e70722373f12b88382d40f9" ) @@ -3813,8 +3923,9 @@ func Test_Service_StartStop(t *testing.T) { require.NoError(t, err) tests := []struct { - name string - beforeFunc func(svc *TestService) + name string + enableMultiFeedsManagers bool + beforeFunc func(svc *TestService) }{ { name: "success with a feeds manager connection", @@ -3827,6 +3938,19 @@ func Test_Service_StartStop(t *testing.T) { svc.orm.On("CountJobProposalsByStatus", mock.Anything).Return(&feeds.JobProposalCounts{}, nil) }, }, + { + name: "success with multiple feeds managers connection", + enableMultiFeedsManagers: true, + beforeFunc: func(svc *TestService) { + svc.csaKeystore.On("GetAll").Return([]csakey.KeyV2{key}, nil) + svc.orm.On("ListManagers", mock.Anything).Return([]feeds.FeedsManager{mgr, mgr2}, nil) + svc.connMgr.On("IsConnected", mgr.ID).Return(false) + svc.connMgr.On("IsConnected", mgr2.ID).Return(false) + svc.connMgr.On("Connect", mock.IsType(feeds.ConnectOpts{})).Twice() + svc.connMgr.On("Close") + svc.orm.On("CountJobProposalsByStatus", mock.Anything).Return(&feeds.JobProposalCounts{}, nil) + }, + }, { name: "success with no registered managers", beforeFunc: func(svc *TestService) { @@ -3843,7 +3967,9 @@ func Test_Service_StartStop(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - svc := setupTestService(t) + svc := setupTestServiceCfg(t, func(c *chainlink.Config, s *chainlink.Secrets) { + c.Feature.MultiFeedsManagers = &tt.enableMultiFeedsManagers + }) if tt.beforeFunc != nil { tt.beforeFunc(svc) diff --git a/core/services/fluxmonitorv2/deviation_checker.go b/core/services/fluxmonitorv2/deviation_checker.go index 51e85de371..9dc399b09f 100644 --- a/core/services/fluxmonitorv2/deviation_checker.go +++ b/core/services/fluxmonitorv2/deviation_checker.go @@ -3,7 +3,7 @@ package fluxmonitorv2 import ( "github.com/shopspring/decimal" - "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink-common/pkg/logger" ) // DeviationThresholds carries parameters used by the threshold-trigger logic @@ -26,7 +26,7 @@ func NewDeviationChecker(rel, abs float64, lggr logger.Logger) *DeviationChecker Rel: rel, Abs: abs, }, - lggr: lggr.Named("DeviationChecker").With("threshold", rel, "absoluteThreshold", abs), + lggr: logger.Sugared(lggr).Named("DeviationChecker").With("threshold", rel, "absoluteThreshold", abs), } } diff --git a/core/services/fluxmonitorv2/flux_monitor.go b/core/services/fluxmonitorv2/flux_monitor.go index 9175feb1a6..b8154ab679 100644 --- a/core/services/fluxmonitorv2/flux_monitor.go +++ b/core/services/fluxmonitorv2/flux_monitor.go @@ -13,6 +13,7 @@ import ( "github.com/pkg/errors" "github.com/shopspring/decimal" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" @@ -22,7 +23,6 @@ import ( evmutils "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/flags_wrapper" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/flux_aggregator_wrapper" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/recovery" "github.com/smartcontractkit/chainlink/v2/core/services/fluxmonitorv2/promfm" "github.com/smartcontractkit/chainlink/v2/core/services/job" @@ -56,7 +56,10 @@ const DefaultHibernationPollPeriod = 24 * time.Hour // FluxMonitor polls external price adapters via HTTP to check for price swings. type FluxMonitor struct { - services.StateMachine + services.Service + eng *services.Engine + logger logger.SugaredLogger + contractAddress common.Address oracleAddress common.Address jobSpec job.Job @@ -77,13 +80,8 @@ type FluxMonitor struct { logBroadcaster log.Broadcaster chainID *big.Int - logger logger.SugaredLogger - backlog *utils.BoundedPriorityQueue[log.Broadcast] chProcessLogs chan struct{} - - chStop services.StopChan - waitOnStop chan struct{} } // NewFluxMonitor returns a new instance of PollingDeviationChecker. @@ -105,7 +103,7 @@ func NewFluxMonitor( flags Flags, fluxAggregator flux_aggregator_wrapper.FluxAggregatorInterface, logBroadcaster log.Broadcaster, - fmLogger logger.Logger, + lggr logger.Logger, chainID *big.Int, ) (*FluxMonitor, error) { fm := &FluxMonitor{ @@ -126,7 +124,6 @@ func NewFluxMonitor( flags: flags, logBroadcaster: logBroadcaster, fluxAggregator: fluxAggregator, - logger: logger.Sugared(fmLogger), chainID: chainID, backlog: utils.NewBoundedPriorityQueue[log.Broadcast](map[uint]int{ // We want reconnecting nodes to be able to submit to a round @@ -136,9 +133,13 @@ func NewFluxMonitor( PriorityFlagChangedLog: 2, }), chProcessLogs: make(chan struct{}, 1), - chStop: make(services.StopChan), - waitOnStop: make(chan struct{}), } + fm.Service, fm.eng = services.Config{ + Name: "FluxMonitor", + Start: fm.start, + Close: fm.close, + }.NewServiceEngine(lggr) + fm.logger = logger.Sugared(fm.eng) return fm, nil } @@ -220,7 +221,7 @@ func NewFromJobSpec( return nil, err } - fmLogger := lggr.With( + fmLogger := logger.With(lggr, "jobID", jobSpec.ID, "contract", fmSpec.ContractAddress.Hex(), ) @@ -279,14 +280,9 @@ const ( // Start implements the job.Service interface. It begins the CSP consumer in a // single goroutine to poll the price adapters and listen to NewRound events. -func (fm *FluxMonitor) Start(context.Context) error { - return fm.StartOnce("FluxMonitor", func() error { - fm.logger.Debug("Starting Flux Monitor for job") - - go fm.consume() - - return nil - }) +func (fm *FluxMonitor) start(context.Context) error { + fm.eng.Go(fm.consume) + return nil } func (fm *FluxMonitor) IsHibernating() bool { @@ -304,16 +300,12 @@ func (fm *FluxMonitor) IsHibernating() bool { return !isFlagLowered } -// Close implements the job.Service interface. It stops this instance from +// close stops this instance from // polling, cleaning up resources. -func (fm *FluxMonitor) Close() error { - return fm.StopOnce("FluxMonitor", func() error { - fm.pollManager.Stop() - close(fm.chStop) - <-fm.waitOnStop +func (fm *FluxMonitor) close() error { + fm.pollManager.Stop() - return nil - }) + return nil } // JobID implements the listener.Listener interface. @@ -354,10 +346,8 @@ func (fm *FluxMonitor) HandleLog(ctx context.Context, broadcast log.Broadcast) { } } -func (fm *FluxMonitor) consume() { - defer close(fm.waitOnStop) - - if err := fm.SetOracleAddress(); err != nil { +func (fm *FluxMonitor) consume(ctx context.Context) { + if err := fm.SetOracleAddress(ctx); err != nil { fm.logger.Warnw( "unable to set oracle address, this flux monitor job may not work correctly", "err", err, @@ -398,46 +388,46 @@ func (fm *FluxMonitor) consume() { for { select { - case <-fm.chStop: + case <-ctx.Done(): return case <-fm.chProcessLogs: - recovery.WrapRecover(fm.logger, fm.processLogs) + recovery.WrapRecover(fm.logger, func() { fm.processLogs(ctx) }) case at := <-fm.pollManager.PollTickerTicks(): tickLogger.Debugf("Poll ticker fired on %v", formatTime(at)) recovery.WrapRecover(fm.logger, func() { - fm.pollIfEligible(PollRequestTypePoll, fm.deviationChecker, nil) + fm.pollIfEligible(ctx, PollRequestTypePoll, fm.deviationChecker, nil) }) case at := <-fm.pollManager.IdleTimerTicks(): tickLogger.Debugf("Idle timer fired on %v", formatTime(at)) recovery.WrapRecover(fm.logger, func() { - fm.pollIfEligible(PollRequestTypeIdle, NewZeroDeviationChecker(fm.logger), nil) + fm.pollIfEligible(ctx, PollRequestTypeIdle, NewZeroDeviationChecker(fm.logger), nil) }) case at := <-fm.pollManager.RoundTimerTicks(): tickLogger.Debugf("Round timer fired on %v", formatTime(at)) recovery.WrapRecover(fm.logger, func() { - fm.pollIfEligible(PollRequestTypeRound, fm.deviationChecker, nil) + fm.pollIfEligible(ctx, PollRequestTypeRound, fm.deviationChecker, nil) }) case at := <-fm.pollManager.HibernationTimerTicks(): tickLogger.Debugf("Hibernation timer fired on %v", formatTime(at)) recovery.WrapRecover(fm.logger, func() { - fm.pollIfEligible(PollRequestTypeHibernation, NewZeroDeviationChecker(fm.logger), nil) + fm.pollIfEligible(ctx, PollRequestTypeHibernation, NewZeroDeviationChecker(fm.logger), nil) }) case at := <-fm.pollManager.RetryTickerTicks(): tickLogger.Debugf("Retry ticker fired on %v", formatTime(at)) recovery.WrapRecover(fm.logger, func() { - fm.pollIfEligible(PollRequestTypeRetry, NewZeroDeviationChecker(fm.logger), nil) + fm.pollIfEligible(ctx, PollRequestTypeRetry, NewZeroDeviationChecker(fm.logger), nil) }) case at := <-fm.pollManager.DrumbeatTicks(): tickLogger.Debugf("Drumbeat ticker fired on %v", formatTime(at)) recovery.WrapRecover(fm.logger, func() { - fm.pollIfEligible(PollRequestTypeDrumbeat, NewZeroDeviationChecker(fm.logger), nil) + fm.pollIfEligible(ctx, PollRequestTypeDrumbeat, NewZeroDeviationChecker(fm.logger), nil) }) case request := <-fm.pollManager.Poll(): @@ -446,7 +436,7 @@ func (fm *FluxMonitor) consume() { break default: recovery.WrapRecover(fm.logger, func() { - fm.pollIfEligible(request.Type, fm.deviationChecker, nil) + fm.pollIfEligible(ctx, request.Type, fm.deviationChecker, nil) }) } } @@ -460,11 +450,7 @@ func formatTime(at time.Time) string { // SetOracleAddress sets the oracle address which matches the node's keys. // If none match, it uses the first available key -func (fm *FluxMonitor) SetOracleAddress() error { - // fm on deprecation path, using dangling context - ctx, cancel := fm.chStop.NewCtx() - defer cancel() - +func (fm *FluxMonitor) SetOracleAddress(ctx context.Context) error { oracleAddrs, err := fm.fluxAggregator.GetOracles(nil) if err != nil { fm.logger.Error("failed to get list of oracles from FluxAggregator contract") @@ -502,10 +488,7 @@ func (fm *FluxMonitor) SetOracleAddress() error { return errors.New("No keys found") } -func (fm *FluxMonitor) processLogs() { - ctx, cancel := fm.chStop.NewCtx() - defer cancel() - +func (fm *FluxMonitor) processLogs(ctx context.Context) { for ctx.Err() == nil && !fm.backlog.Empty() { broadcast := fm.backlog.Take() fm.processBroadcast(ctx, broadcast) @@ -529,7 +512,7 @@ func (fm *FluxMonitor) processBroadcast(ctx context.Context, broadcast log.Broad decodedLog := broadcast.DecodedLog() switch log := decodedLog.(type) { case *flux_aggregator_wrapper.FluxAggregatorNewRound: - fm.respondToNewRoundLog(*log, broadcast) + fm.respondToNewRoundLog(ctx, *log, broadcast) case *flux_aggregator_wrapper.FluxAggregatorAnswerUpdated: fm.respondToAnswerUpdatedLog(*log) fm.markLogAsConsumed(ctx, broadcast, decodedLog, started) @@ -540,7 +523,7 @@ func (fm *FluxMonitor) processBroadcast(ctx context.Context, broadcast log.Broad // Only reactivate if it is hibernating if fm.pollManager.isHibernating.Load() { fm.pollManager.Awaken(fm.initialRoundState()) - fm.pollIfEligible(PollRequestTypeAwaken, NewZeroDeviationChecker(fm.logger), broadcast) + fm.pollIfEligible(ctx, PollRequestTypeAwaken, NewZeroDeviationChecker(fm.logger), broadcast) } default: fm.logger.Errorf("unknown log %v of type %T", log, log) @@ -589,10 +572,8 @@ func (fm *FluxMonitor) respondToAnswerUpdatedLog(log flux_aggregator_wrapper.Flu // The NewRound log tells us that an oracle has initiated a new round. This tells us that we // need to poll and submit an answer to the contract regardless of the deviation. -func (fm *FluxMonitor) respondToNewRoundLog(log flux_aggregator_wrapper.FluxAggregatorNewRound, lb log.Broadcast) { +func (fm *FluxMonitor) respondToNewRoundLog(ctx context.Context, log flux_aggregator_wrapper.FluxAggregatorNewRound, lb log.Broadcast) { started := time.Now() - ctx, cancel := fm.chStop.NewCtx() - defer cancel() newRoundLogger := fm.logger.With( "round", log.RoundId, @@ -819,10 +800,8 @@ func (fm *FluxMonitor) checkEligibilityAndAggregatorFunding(roundState flux_aggr return nil } -func (fm *FluxMonitor) pollIfEligible(pollReq PollRequestType, deviationChecker *DeviationChecker, broadcast log.Broadcast) { +func (fm *FluxMonitor) pollIfEligible(ctx context.Context, pollReq PollRequestType, deviationChecker *DeviationChecker, broadcast log.Broadcast) { started := time.Now() - ctx, cancel := fm.chStop.NewCtx() - defer cancel() l := fm.logger.With( "threshold", deviationChecker.Thresholds.Rel, diff --git a/core/services/fluxmonitorv2/flux_monitor_test.go b/core/services/fluxmonitorv2/flux_monitor_test.go index 87ed4e3053..098b1f4a47 100644 --- a/core/services/fluxmonitorv2/flux_monitor_test.go +++ b/core/services/fluxmonitorv2/flux_monitor_test.go @@ -10,6 +10,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/google/uuid" + "github.com/jmoiron/sqlx" "github.com/onsi/gomega" "github.com/pkg/errors" "github.com/shopspring/decimal" @@ -18,11 +19,11 @@ import ( "github.com/stretchr/testify/require" "gopkg.in/guregu/null.v4" - "github.com/jmoiron/sqlx" - "github.com/smartcontractkit/chainlink-common/pkg/assets" "github.com/smartcontractkit/chainlink-common/pkg/services/servicetest" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/log" logmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/log/mocks" @@ -491,7 +492,7 @@ func TestFluxMonitor_PollIfEligible(t *testing.T) { oracles := []common.Address{nodeAddr, testutils.NewAddress()} tm.fluxAggregator.On("GetOracles", nilOpts).Return(oracles, nil) - require.NoError(t, fm.SetOracleAddress()) + require.NoError(t, fm.SetOracleAddress(tests.Context(t))) fm.ExportedPollIfEligible(thresholds.rel, thresholds.abs) }) } @@ -526,7 +527,7 @@ func TestFluxMonitor_PollIfEligible_Creates_JobErr(t *testing.T) { Once() tm.fluxAggregator.On("GetOracles", nilOpts).Return(oracles, nil) - require.NoError(t, fm.SetOracleAddress()) + require.NoError(t, fm.SetOracleAddress(tests.Context(t))) fm.ExportedPollIfEligible(1, 1) } @@ -1171,7 +1172,7 @@ func TestFluxMonitor_RoundTimeoutCausesPoll_timesOutAtZero(t *testing.T) { tm.fluxAggregator.On("Address").Return(common.Address{}) tm.fluxAggregator.On("GetOracles", nilOpts).Return(oracles, nil) - require.NoError(t, fm.SetOracleAddress()) + require.NoError(t, fm.SetOracleAddress(tests.Context(t))) fm.ExportedRoundState(t) servicetest.Run(t, fm) @@ -1506,7 +1507,7 @@ func TestFluxMonitor_DoesNotDoubleSubmit(t *testing.T) { Return(nil) tm.fluxAggregator.On("GetOracles", nilOpts).Return(oracles, nil) - require.NoError(t, fm.SetOracleAddress()) + require.NoError(t, fm.SetOracleAddress(tests.Context(t))) tm.fluxAggregator.On("LatestRoundData", nilOpts).Return(flux_aggregator_wrapper.LatestRoundData{ Answer: big.NewInt(10), @@ -1635,7 +1636,7 @@ func TestFluxMonitor_DoesNotDoubleSubmit(t *testing.T) { Once() tm.fluxAggregator.On("GetOracles", nilOpts).Return(oracles, nil) - require.NoError(t, fm.SetOracleAddress()) + require.NoError(t, fm.SetOracleAddress(tests.Context(t))) fm.ExportedPollIfEligible(0, 0) // Now fire off the NewRound log and ensure it does not respond this time @@ -1732,7 +1733,7 @@ func TestFluxMonitor_DoesNotDoubleSubmit(t *testing.T) { Once() tm.fluxAggregator.On("GetOracles", nilOpts).Return(oracles, nil) - require.NoError(t, fm.SetOracleAddress()) + require.NoError(t, fm.SetOracleAddress(tests.Context(t))) fm.ExportedPollIfEligible(0, 0) // Now fire off the NewRound log and ensure it does not respond this time diff --git a/core/services/fluxmonitorv2/helpers_test.go b/core/services/fluxmonitorv2/helpers_test.go index d321ddc35c..80db82351c 100644 --- a/core/services/fluxmonitorv2/helpers_test.go +++ b/core/services/fluxmonitorv2/helpers_test.go @@ -19,11 +19,15 @@ func (fm *FluxMonitor) Format(f fmt.State, verb rune) { } func (fm *FluxMonitor) ExportedPollIfEligible(threshold, absoluteThreshold float64) { - fm.pollIfEligible(PollRequestTypePoll, NewDeviationChecker(threshold, absoluteThreshold, fm.logger), nil) + ctx, cancel := fm.eng.NewCtx() + defer cancel() + fm.pollIfEligible(ctx, PollRequestTypePoll, NewDeviationChecker(threshold, absoluteThreshold, fm.logger), nil) } func (fm *FluxMonitor) ExportedProcessLogs() { - fm.processLogs() + ctx, cancel := fm.eng.NewCtx() + defer cancel() + fm.processLogs(ctx) } func (fm *FluxMonitor) ExportedBacklog() *utils.BoundedPriorityQueue[log.Broadcast] { @@ -36,7 +40,9 @@ func (fm *FluxMonitor) ExportedRoundState(t *testing.T) { } func (fm *FluxMonitor) ExportedRespondToNewRoundLog(log *flux_aggregator_wrapper.FluxAggregatorNewRound, broadcast log.Broadcast) { - fm.respondToNewRoundLog(*log, broadcast) + ctx, cancel := fm.eng.NewCtx() + defer cancel() + fm.respondToNewRoundLog(ctx, *log, broadcast) } func (fm *FluxMonitor) ExportedRespondToFlagsRaisedLog() { diff --git a/core/services/fluxmonitorv2/integrations_test.go b/core/services/fluxmonitorv2/integrations_test.go index 908d9fb73d..40bdf71743 100644 --- a/core/services/fluxmonitorv2/integrations_test.go +++ b/core/services/fluxmonitorv2/integrations_test.go @@ -26,6 +26,7 @@ import ( commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" + "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/log" diff --git a/core/services/fluxmonitorv2/poll_manager.go b/core/services/fluxmonitorv2/poll_manager.go index 78b99aec4d..aca6c75a31 100644 --- a/core/services/fluxmonitorv2/poll_manager.go +++ b/core/services/fluxmonitorv2/poll_manager.go @@ -5,8 +5,8 @@ import ( "sync/atomic" "time" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/flux_aggregator_wrapper" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -64,7 +64,7 @@ type PollManager struct { } // NewPollManager initializes a new PollManager -func NewPollManager(cfg PollManagerConfig, logger logger.Logger) (*PollManager, error) { +func NewPollManager(cfg PollManagerConfig, lggr logger.Logger) (*PollManager, error) { minBackoffDuration := cfg.MinRetryBackoffDuration if cfg.IdleTimerPeriod < minBackoffDuration { minBackoffDuration = cfg.IdleTimerPeriod @@ -82,7 +82,7 @@ func NewPollManager(cfg PollManagerConfig, logger logger.Logger) (*PollManager, p := &PollManager{ cfg: cfg, - logger: logger.Named("PollManager"), + logger: logger.Named(lggr, "PollManager"), hibernationTimer: utils.NewResettableTimer(), pollTicker: utils.NewPausableTicker(cfg.PollTickerInterval), @@ -277,7 +277,7 @@ func (pm *PollManager) startIdleTimer(roundStartedAtUTC uint64) { deadline := startedAt.Add(pm.cfg.IdleTimerPeriod) deadlineDuration := time.Until(deadline) - log := pm.logger.With( + log := logger.With(pm.logger, "pollFrequency", pm.cfg.PollTickerInterval, "idleDuration", pm.cfg.IdleTimerPeriod, "startedAt", roundStartedAtUTC, @@ -300,7 +300,7 @@ func (pm *PollManager) startIdleTimer(roundStartedAtUTC uint64) { // startRoundTimer starts the round timer func (pm *PollManager) startRoundTimer(roundTimesOutAt uint64) { - log := pm.logger.With( + log := logger.With(pm.logger, "pollFrequency", pm.cfg.PollTickerInterval, "idleDuration", pm.cfg.IdleTimerPeriod, "timesOutAt", roundTimesOutAt, diff --git a/core/services/gateway/connector/connector.go b/core/services/gateway/connector/connector.go index 64ae46e620..82ff1e9e13 100644 --- a/core/services/gateway/connector/connector.go +++ b/core/services/gateway/connector/connector.go @@ -26,6 +26,7 @@ type GatewayConnector interface { job.ServiceCtx network.ConnectionInitiator + AddHandler(methods []string, handler GatewayConnectorHandler) error SendToGateway(ctx context.Context, gatewayId string, msg *api.Message) error } @@ -51,7 +52,7 @@ type gatewayConnector struct { clock clockwork.Clock nodeAddress []byte signer Signer - handler GatewayConnectorHandler + handlers map[string]GatewayConnectorHandler gateways map[string]*gatewayState urlToId map[string]string closeWait sync.WaitGroup @@ -76,8 +77,8 @@ type gatewayState struct { wsClient network.WebSocketClient } -func NewGatewayConnector(config *ConnectorConfig, signer Signer, handler GatewayConnectorHandler, clock clockwork.Clock, lggr logger.Logger) (GatewayConnector, error) { - if config == nil || signer == nil || handler == nil || clock == nil || lggr == nil { +func NewGatewayConnector(config *ConnectorConfig, signer Signer, clock clockwork.Clock, lggr logger.Logger) (GatewayConnector, error) { + if config == nil || signer == nil || clock == nil || lggr == nil { return nil, errors.New("nil dependency") } if len(config.DonId) == 0 || len(config.DonId) > network.HandshakeDonIdLen { @@ -93,7 +94,7 @@ func NewGatewayConnector(config *ConnectorConfig, signer Signer, handler Gateway clock: clock, nodeAddress: addressBytes, signer: signer, - handler: handler, + handlers: make(map[string]GatewayConnectorHandler), shutdownCh: make(chan struct{}), lggr: lggr.Named("GatewayConnector"), } @@ -125,6 +126,22 @@ func NewGatewayConnector(config *ConnectorConfig, signer Signer, handler Gateway return connector, nil } +func (c *gatewayConnector) AddHandler(methods []string, handler GatewayConnectorHandler) error { + if handler == nil { + return errors.New("cannot add a nil handler") + } + for _, method := range methods { + if _, exists := c.handlers[method]; exists { + return fmt.Errorf("handler for method %s already exists", method) + } + } + // add all or nothing + for _, method := range methods { + c.handlers[method] = handler + } + return nil +} + func (c *gatewayConnector) SendToGateway(ctx context.Context, gatewayId string, msg *api.Message) error { data, err := c.codec.EncodeResponse(msg) if err != nil { @@ -159,7 +176,12 @@ func (c *gatewayConnector) readLoop(gatewayState *gatewayState) { c.lggr.Errorw("failed to validate message signature", "id", gatewayState.config.Id, "err", err) break } - c.handler.HandleGatewayMessage(ctx, gatewayState.config.Id, msg) + handler, exists := c.handlers[msg.Body.Method] + if !exists { + c.lggr.Errorw("no handler for method", "id", gatewayState.config.Id, "method", msg.Body.Method) + break + } + handler.HandleGatewayMessage(ctx, gatewayState.config.Id, msg) } } } @@ -194,9 +216,6 @@ func (c *gatewayConnector) reconnectLoop(gatewayState *gatewayState) { func (c *gatewayConnector) Start(ctx context.Context) error { return c.StartOnce("GatewayConnector", func() error { c.lggr.Info("starting gateway connector") - if err := c.handler.Start(ctx); err != nil { - return err - } for _, gatewayState := range c.gateways { gatewayState := gatewayState if err := gatewayState.conn.Start(ctx); err != nil { @@ -214,11 +233,12 @@ func (c *gatewayConnector) Close() error { return c.StopOnce("GatewayConnector", func() (err error) { c.lggr.Info("closing gateway connector") close(c.shutdownCh) + var errs error for _, gatewayState := range c.gateways { - gatewayState.conn.Close() + errs = errors.Join(errs, gatewayState.conn.Close()) } c.closeWait.Wait() - return c.handler.Close() + return errs }) } diff --git a/core/services/gateway/connector/connector_test.go b/core/services/gateway/connector/connector_test.go index 3dd782c626..16520a42f4 100644 --- a/core/services/gateway/connector/connector_test.go +++ b/core/services/gateway/connector/connector_test.go @@ -18,7 +18,8 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/gateway/network" ) -const defaultConfig = ` +const ( + defaultConfig = ` NodeAddress = "0x68902d681c28119f9b2531473a417088bf008e59" DonId = "example_don" AuthMinChallengeLen = 10 @@ -32,6 +33,9 @@ URL = "ws://localhost:8081/node" Id = "another_one" URL = "wss://example.com:8090/node_endpoint" ` + testMethod1 = "test_method_1" + testMethod2 = "test_method_2" +) func parseTOMLConfig(t *testing.T, tomlConfig string) *connector.ConnectorConfig { var cfg connector.ConnectorConfig @@ -40,12 +44,13 @@ func parseTOMLConfig(t *testing.T, tomlConfig string) *connector.ConnectorConfig return &cfg } -func newTestConnector(t *testing.T, config *connector.ConnectorConfig, now time.Time) (connector.GatewayConnector, *mocks.Signer, *mocks.GatewayConnectorHandler) { +func newTestConnector(t *testing.T, config *connector.ConnectorConfig) (connector.GatewayConnector, *mocks.Signer, *mocks.GatewayConnectorHandler) { signer := mocks.NewSigner(t) handler := mocks.NewGatewayConnectorHandler(t) clock := clockwork.NewFakeClock() - connector, err := connector.NewGatewayConnector(config, signer, handler, clock, logger.TestLogger(t)) + connector, err := connector.NewGatewayConnector(config, signer, clock, logger.TestLogger(t)) require.NoError(t, err) + require.NoError(t, connector.AddHandler([]string{testMethod1}, handler)) return connector, signer, handler } @@ -61,7 +66,7 @@ Id = "example_gateway" URL = "ws://localhost:8081/node" `) - newTestConnector(t, tomlConfig, time.Now()) + newTestConnector(t, tomlConfig) } func TestGatewayConnector_NewGatewayConnector_InvalidConfig(t *testing.T) { @@ -103,12 +108,11 @@ URL = "ws://localhost:8081/node" } signer := mocks.NewSigner(t) - handler := mocks.NewGatewayConnectorHandler(t) clock := clockwork.NewFakeClock() for name, config := range invalidCases { config := config t.Run(name, func(t *testing.T) { - _, err := connector.NewGatewayConnector(parseTOMLConfig(t, config), signer, handler, clock, logger.TestLogger(t)) + _, err := connector.NewGatewayConnector(parseTOMLConfig(t, config), signer, clock, logger.TestLogger(t)) require.Error(t, err) }) } @@ -117,9 +121,7 @@ URL = "ws://localhost:8081/node" func TestGatewayConnector_CleanStartAndClose(t *testing.T) { t.Parallel() - connector, signer, handler := newTestConnector(t, parseTOMLConfig(t, defaultConfig), time.Now()) - handler.On("Start", mock.Anything).Return(nil) - handler.On("Close").Return(nil) + connector, signer, _ := newTestConnector(t, parseTOMLConfig(t, defaultConfig)) signer.On("Sign", mock.Anything).Return(nil, errors.New("cannot sign")) servicetest.Run(t, connector) } @@ -127,7 +129,7 @@ func TestGatewayConnector_CleanStartAndClose(t *testing.T) { func TestGatewayConnector_NewAuthHeader_SignerError(t *testing.T) { t.Parallel() - connector, signer, _ := newTestConnector(t, parseTOMLConfig(t, defaultConfig), time.Now()) + connector, signer, _ := newTestConnector(t, parseTOMLConfig(t, defaultConfig)) signer.On("Sign", mock.Anything).Return(nil, errors.New("cannot sign")) url, err := url.Parse("ws://localhost:8081/node") @@ -141,7 +143,7 @@ func TestGatewayConnector_NewAuthHeader_Success(t *testing.T) { testSignature := make([]byte, network.HandshakeSignatureLen) testSignature[1] = 0xfa - connector, signer, _ := newTestConnector(t, parseTOMLConfig(t, defaultConfig), time.Now()) + connector, signer, _ := newTestConnector(t, parseTOMLConfig(t, defaultConfig)) signer.On("Sign", mock.Anything).Return(testSignature, nil) url, err := url.Parse("ws://localhost:8081/node") require.NoError(t, err) @@ -157,7 +159,7 @@ func TestGatewayConnector_ChallengeResponse(t *testing.T) { testSignature := make([]byte, network.HandshakeSignatureLen) testSignature[1] = 0xfa now := time.Now() - connector, signer, _ := newTestConnector(t, parseTOMLConfig(t, defaultConfig), now) + connector, signer, _ := newTestConnector(t, parseTOMLConfig(t, defaultConfig)) signer.On("Sign", mock.Anything).Return(testSignature, nil) url, err := url.Parse("ws://localhost:8081/node") require.NoError(t, err) @@ -191,3 +193,12 @@ func TestGatewayConnector_ChallengeResponse(t *testing.T) { _, err = connector.ChallengeResponse(url, network.PackChallenge(&badChallenge)) require.Equal(t, network.ErrAuthInvalidGateway, err) } + +func TestGatewayConnector_AddHandler(t *testing.T) { + t.Parallel() + + connector, _, _ := newTestConnector(t, parseTOMLConfig(t, defaultConfig)) + // testMethod1 already exists + require.Error(t, connector.AddHandler([]string{testMethod1}, mocks.NewGatewayConnectorHandler(t))) + require.NoError(t, connector.AddHandler([]string{testMethod2}, mocks.NewGatewayConnectorHandler(t))) +} diff --git a/core/services/gateway/connector/mocks/gateway_connector.go b/core/services/gateway/connector/mocks/gateway_connector.go index 931aac8c77..76e3ff5c86 100644 --- a/core/services/gateway/connector/mocks/gateway_connector.go +++ b/core/services/gateway/connector/mocks/gateway_connector.go @@ -4,6 +4,7 @@ package mocks import ( api "github.com/smartcontractkit/chainlink/v2/core/services/gateway/api" + connector "github.com/smartcontractkit/chainlink/v2/core/services/gateway/connector" context "context" @@ -25,6 +26,53 @@ func (_m *GatewayConnector) EXPECT() *GatewayConnector_Expecter { return &GatewayConnector_Expecter{mock: &_m.Mock} } +// AddHandler provides a mock function with given fields: methods, handler +func (_m *GatewayConnector) AddHandler(methods []string, handler connector.GatewayConnectorHandler) error { + ret := _m.Called(methods, handler) + + if len(ret) == 0 { + panic("no return value specified for AddHandler") + } + + var r0 error + if rf, ok := ret.Get(0).(func([]string, connector.GatewayConnectorHandler) error); ok { + r0 = rf(methods, handler) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// GatewayConnector_AddHandler_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddHandler' +type GatewayConnector_AddHandler_Call struct { + *mock.Call +} + +// AddHandler is a helper method to define mock.On call +// - methods []string +// - handler connector.GatewayConnectorHandler +func (_e *GatewayConnector_Expecter) AddHandler(methods interface{}, handler interface{}) *GatewayConnector_AddHandler_Call { + return &GatewayConnector_AddHandler_Call{Call: _e.mock.On("AddHandler", methods, handler)} +} + +func (_c *GatewayConnector_AddHandler_Call) Run(run func(methods []string, handler connector.GatewayConnectorHandler)) *GatewayConnector_AddHandler_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].([]string), args[1].(connector.GatewayConnectorHandler)) + }) + return _c +} + +func (_c *GatewayConnector_AddHandler_Call) Return(_a0 error) *GatewayConnector_AddHandler_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *GatewayConnector_AddHandler_Call) RunAndReturn(run func([]string, connector.GatewayConnectorHandler) error) *GatewayConnector_AddHandler_Call { + _c.Call.Return(run) + return _c +} + // ChallengeResponse provides a mock function with given fields: _a0, challenge func (_m *GatewayConnector) ChallengeResponse(_a0 *url.URL, challenge []byte) ([]byte, error) { ret := _m.Called(_a0, challenge) diff --git a/core/services/gateway/handlers/functions/allowlist/allowlist_test.go b/core/services/gateway/handlers/functions/allowlist/allowlist_test.go index 500985acc3..b8735bbf8a 100644 --- a/core/services/gateway/handlers/functions/allowlist/allowlist_test.go +++ b/core/services/gateway/handlers/functions/allowlist/allowlist_test.go @@ -21,7 +21,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/allowlist" amocks "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/allowlist/mocks" - "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" ) @@ -376,7 +376,7 @@ func encodeTypeAndVersionResponse(typeAndVersion string) ([]byte, error) { codecConfig := types.CodecConfig{Configs: map[string]types.ChainCodecConfig{ codecName: {TypeABI: evmEncoderConfig}, }} - encoder, err := evm.NewCodec(codecConfig) + encoder, err := codec.NewCodec(codecConfig) if err != nil { return nil, err } diff --git a/core/services/gateway/integration_tests/gateway_integration_test.go b/core/services/gateway/integration_tests/gateway_integration_test.go index 38a6b6ebbc..59418819b6 100644 --- a/core/services/gateway/integration_tests/gateway_integration_test.go +++ b/core/services/gateway/integration_tests/gateway_integration_test.go @@ -152,8 +152,10 @@ func TestIntegration_Gateway_NoFullNodes_BasicConnectionAndMessage(t *testing.T) // Launch Connector client := &client{privateKey: nodeKeys.PrivateKey} - connector, err := connector.NewGatewayConnector(parseConnectorConfig(t, nodeConfigTemplate, nodeKeys.Address, nodeUrl), client, client, clockwork.NewRealClock(), lggr) + // client acts as a signer here + connector, err := connector.NewGatewayConnector(parseConnectorConfig(t, nodeConfigTemplate, nodeKeys.Address, nodeUrl), client, clockwork.NewRealClock(), lggr) require.NoError(t, err) + require.NoError(t, connector.AddHandler([]string{"test"}, client)) client.connector = connector servicetest.Run(t, connector) diff --git a/core/services/headreporter/head_reporter.go b/core/services/headreporter/head_reporter.go new file mode 100644 index 0000000000..94de8ae2be --- /dev/null +++ b/core/services/headreporter/head_reporter.go @@ -0,0 +1,111 @@ +package headreporter + +import ( + "context" + "sync" + "time" + + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" + "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker/types" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +type ( + HeadReporter interface { + ReportNewHead(ctx context.Context, head *evmtypes.Head) error + ReportPeriodic(ctx context.Context) error + } + + HeadReporterService struct { + services.StateMachine + ds sqlutil.DataSource + lggr logger.Logger + newHeads *mailbox.Mailbox[*evmtypes.Head] + chStop services.StopChan + wgDone sync.WaitGroup + reportPeriod time.Duration + reporters []HeadReporter + unsubscribeFns []func() + } +) + +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, + reportPeriod: 15 * time.Second, + } +} + +func (hrd *HeadReporterService) Subscribe(subFn func(types.HeadTrackable) (evmtypes.Head, func())) { + _, unsubscribe := subFn(hrd) + hrd.unsubscribeFns = append(hrd.unsubscribeFns, unsubscribe) +} + +func (hrd *HeadReporterService) Start(context.Context) error { + return hrd.StartOnce(hrd.Name(), func() error { + hrd.wgDone.Add(1) + go hrd.eventLoop() + return nil + }) +} + +func (hrd *HeadReporterService) Close() error { + return hrd.StopOnce(hrd.Name(), func() error { + close(hrd.chStop) + hrd.wgDone.Wait() + return nil + }) +} + +func (hrd *HeadReporterService) Name() string { + return hrd.lggr.Name() +} + +func (hrd *HeadReporterService) HealthReport() map[string]error { + return map[string]error{hrd.Name(): hrd.Healthy()} +} + +func (hrd *HeadReporterService) OnNewLongestChain(ctx context.Context, head *evmtypes.Head) { + hrd.newHeads.Deliver(head) +} + +func (hrd *HeadReporterService) eventLoop() { + hrd.lggr.Debug("Starting event loop") + defer hrd.wgDone.Done() + ctx, cancel := hrd.chStop.NewCtx() + defer cancel() + after := time.After(hrd.reportPeriod) + for { + select { + case <-hrd.newHeads.Notify(): + head, exists := hrd.newHeads.Retrieve() + if !exists { + continue + } + for _, reporter := range hrd.reporters { + err := reporter.ReportNewHead(ctx, head) + if err != nil && ctx.Err() == nil { + hrd.lggr.Errorw("Error reporting new head", "err", err) + } + } + case <-after: + for _, reporter := range hrd.reporters { + err := reporter.ReportPeriodic(ctx) + if err != nil && ctx.Err() == nil { + hrd.lggr.Errorw("Error in periodic report", "err", err) + } + } + after = time.After(hrd.reportPeriod) + case <-hrd.chStop: + return + } + } +} diff --git a/core/services/headreporter/head_reporter_mock.go b/core/services/headreporter/head_reporter_mock.go new file mode 100644 index 0000000000..21978abb86 --- /dev/null +++ b/core/services/headreporter/head_reporter_mock.go @@ -0,0 +1,130 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package headreporter + +import ( + context "context" + + types "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + mock "github.com/stretchr/testify/mock" +) + +// MockHeadReporter is an autogenerated mock type for the HeadReporter type +type MockHeadReporter struct { + mock.Mock +} + +type MockHeadReporter_Expecter struct { + mock *mock.Mock +} + +func (_m *MockHeadReporter) EXPECT() *MockHeadReporter_Expecter { + return &MockHeadReporter_Expecter{mock: &_m.Mock} +} + +// ReportNewHead provides a mock function with given fields: ctx, head +func (_m *MockHeadReporter) ReportNewHead(ctx context.Context, head *types.Head) error { + ret := _m.Called(ctx, head) + + if len(ret) == 0 { + panic("no return value specified for ReportNewHead") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *types.Head) error); ok { + r0 = rf(ctx, head) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockHeadReporter_ReportNewHead_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReportNewHead' +type MockHeadReporter_ReportNewHead_Call struct { + *mock.Call +} + +// ReportNewHead is a helper method to define mock.On call +// - ctx context.Context +// - head *types.Head +func (_e *MockHeadReporter_Expecter) ReportNewHead(ctx interface{}, head interface{}) *MockHeadReporter_ReportNewHead_Call { + return &MockHeadReporter_ReportNewHead_Call{Call: _e.mock.On("ReportNewHead", ctx, head)} +} + +func (_c *MockHeadReporter_ReportNewHead_Call) Run(run func(ctx context.Context, head *types.Head)) *MockHeadReporter_ReportNewHead_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*types.Head)) + }) + return _c +} + +func (_c *MockHeadReporter_ReportNewHead_Call) Return(_a0 error) *MockHeadReporter_ReportNewHead_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockHeadReporter_ReportNewHead_Call) RunAndReturn(run func(context.Context, *types.Head) error) *MockHeadReporter_ReportNewHead_Call { + _c.Call.Return(run) + return _c +} + +// ReportPeriodic provides a mock function with given fields: ctx +func (_m *MockHeadReporter) ReportPeriodic(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for ReportPeriodic") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockHeadReporter_ReportPeriodic_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReportPeriodic' +type MockHeadReporter_ReportPeriodic_Call struct { + *mock.Call +} + +// ReportPeriodic is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockHeadReporter_Expecter) ReportPeriodic(ctx interface{}) *MockHeadReporter_ReportPeriodic_Call { + return &MockHeadReporter_ReportPeriodic_Call{Call: _e.mock.On("ReportPeriodic", ctx)} +} + +func (_c *MockHeadReporter_ReportPeriodic_Call) Run(run func(ctx context.Context)) *MockHeadReporter_ReportPeriodic_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockHeadReporter_ReportPeriodic_Call) Return(_a0 error) *MockHeadReporter_ReportPeriodic_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockHeadReporter_ReportPeriodic_Call) RunAndReturn(run func(context.Context) error) *MockHeadReporter_ReportPeriodic_Call { + _c.Call.Return(run) + return _c +} + +// NewMockHeadReporter creates a new instance of MockHeadReporter. 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 NewMockHeadReporter(t interface { + mock.TestingT + Cleanup(func()) +}) *MockHeadReporter { + mock := &MockHeadReporter{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/services/headreporter/head_reporter_test.go b/core/services/headreporter/head_reporter_test.go new file mode 100644 index 0000000000..304dd59a47 --- /dev/null +++ b/core/services/headreporter/head_reporter_test.go @@ -0,0 +1,51 @@ +package headreporter + +import ( + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + 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/internal/testutils/pgtest" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +func NewHead() evmtypes.Head { + return evmtypes.Head{Number: 42, EVMChainID: ubig.NewI(0)} +} + +func Test_HeadReporterService(t *testing.T) { + t.Run("report everything", func(t *testing.T) { + db := pgtest.NewSqlxDB(t) + + headReporter := NewMockHeadReporter(t) + service := NewHeadReporterService(db, logger.TestLogger(t), headReporter) + service.reportPeriod = time.Second + err := service.Start(testutils.Context(t)) + require.NoError(t, err) + + var reportCalls atomic.Int32 + head := NewHead() + headReporter.On("ReportNewHead", mock.Anything, &head).Run(func(args mock.Arguments) { + reportCalls.Add(1) + }).Return(nil) + headReporter.On("ReportPeriodic", mock.Anything).Run(func(args mock.Arguments) { + reportCalls.Add(1) + }).Return(nil) + service.OnNewLongestChain(testutils.Context(t), &head) + + 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/helper_test.go b/core/services/headreporter/helper_test.go new file mode 100644 index 0000000000..fa05182a85 --- /dev/null +++ b/core/services/headreporter/helper_test.go @@ -0,0 +1,5 @@ +package headreporter + +func (p *prometheusReporter) SetBackend(b PrometheusBackend) { + p.backend = b +} diff --git a/core/services/headreporter/prometheus_backend_mock.go b/core/services/headreporter/prometheus_backend_mock.go new file mode 100644 index 0000000000..ca83f6c4fb --- /dev/null +++ b/core/services/headreporter/prometheus_backend_mock.go @@ -0,0 +1,204 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package headreporter + +import ( + big "math/big" + + mock "github.com/stretchr/testify/mock" +) + +// MockPrometheusBackend is an autogenerated mock type for the PrometheusBackend type +type MockPrometheusBackend struct { + mock.Mock +} + +type MockPrometheusBackend_Expecter struct { + mock *mock.Mock +} + +func (_m *MockPrometheusBackend) EXPECT() *MockPrometheusBackend_Expecter { + return &MockPrometheusBackend_Expecter{mock: &_m.Mock} +} + +// SetMaxUnconfirmedAge provides a mock function with given fields: _a0, _a1 +func (_m *MockPrometheusBackend) SetMaxUnconfirmedAge(_a0 *big.Int, _a1 float64) { + _m.Called(_a0, _a1) +} + +// MockPrometheusBackend_SetMaxUnconfirmedAge_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetMaxUnconfirmedAge' +type MockPrometheusBackend_SetMaxUnconfirmedAge_Call struct { + *mock.Call +} + +// SetMaxUnconfirmedAge is a helper method to define mock.On call +// - _a0 *big.Int +// - _a1 float64 +func (_e *MockPrometheusBackend_Expecter) SetMaxUnconfirmedAge(_a0 interface{}, _a1 interface{}) *MockPrometheusBackend_SetMaxUnconfirmedAge_Call { + return &MockPrometheusBackend_SetMaxUnconfirmedAge_Call{Call: _e.mock.On("SetMaxUnconfirmedAge", _a0, _a1)} +} + +func (_c *MockPrometheusBackend_SetMaxUnconfirmedAge_Call) Run(run func(_a0 *big.Int, _a1 float64)) *MockPrometheusBackend_SetMaxUnconfirmedAge_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(*big.Int), args[1].(float64)) + }) + return _c +} + +func (_c *MockPrometheusBackend_SetMaxUnconfirmedAge_Call) Return() *MockPrometheusBackend_SetMaxUnconfirmedAge_Call { + _c.Call.Return() + return _c +} + +func (_c *MockPrometheusBackend_SetMaxUnconfirmedAge_Call) RunAndReturn(run func(*big.Int, float64)) *MockPrometheusBackend_SetMaxUnconfirmedAge_Call { + _c.Call.Return(run) + return _c +} + +// SetMaxUnconfirmedBlocks provides a mock function with given fields: _a0, _a1 +func (_m *MockPrometheusBackend) SetMaxUnconfirmedBlocks(_a0 *big.Int, _a1 int64) { + _m.Called(_a0, _a1) +} + +// MockPrometheusBackend_SetMaxUnconfirmedBlocks_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetMaxUnconfirmedBlocks' +type MockPrometheusBackend_SetMaxUnconfirmedBlocks_Call struct { + *mock.Call +} + +// SetMaxUnconfirmedBlocks is a helper method to define mock.On call +// - _a0 *big.Int +// - _a1 int64 +func (_e *MockPrometheusBackend_Expecter) SetMaxUnconfirmedBlocks(_a0 interface{}, _a1 interface{}) *MockPrometheusBackend_SetMaxUnconfirmedBlocks_Call { + return &MockPrometheusBackend_SetMaxUnconfirmedBlocks_Call{Call: _e.mock.On("SetMaxUnconfirmedBlocks", _a0, _a1)} +} + +func (_c *MockPrometheusBackend_SetMaxUnconfirmedBlocks_Call) Run(run func(_a0 *big.Int, _a1 int64)) *MockPrometheusBackend_SetMaxUnconfirmedBlocks_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(*big.Int), args[1].(int64)) + }) + return _c +} + +func (_c *MockPrometheusBackend_SetMaxUnconfirmedBlocks_Call) Return() *MockPrometheusBackend_SetMaxUnconfirmedBlocks_Call { + _c.Call.Return() + return _c +} + +func (_c *MockPrometheusBackend_SetMaxUnconfirmedBlocks_Call) RunAndReturn(run func(*big.Int, int64)) *MockPrometheusBackend_SetMaxUnconfirmedBlocks_Call { + _c.Call.Return(run) + return _c +} + +// SetPipelineRunsQueued provides a mock function with given fields: n +func (_m *MockPrometheusBackend) SetPipelineRunsQueued(n int) { + _m.Called(n) +} + +// MockPrometheusBackend_SetPipelineRunsQueued_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetPipelineRunsQueued' +type MockPrometheusBackend_SetPipelineRunsQueued_Call struct { + *mock.Call +} + +// SetPipelineRunsQueued is a helper method to define mock.On call +// - n int +func (_e *MockPrometheusBackend_Expecter) SetPipelineRunsQueued(n interface{}) *MockPrometheusBackend_SetPipelineRunsQueued_Call { + return &MockPrometheusBackend_SetPipelineRunsQueued_Call{Call: _e.mock.On("SetPipelineRunsQueued", n)} +} + +func (_c *MockPrometheusBackend_SetPipelineRunsQueued_Call) Run(run func(n int)) *MockPrometheusBackend_SetPipelineRunsQueued_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(int)) + }) + return _c +} + +func (_c *MockPrometheusBackend_SetPipelineRunsQueued_Call) Return() *MockPrometheusBackend_SetPipelineRunsQueued_Call { + _c.Call.Return() + return _c +} + +func (_c *MockPrometheusBackend_SetPipelineRunsQueued_Call) RunAndReturn(run func(int)) *MockPrometheusBackend_SetPipelineRunsQueued_Call { + _c.Call.Return(run) + return _c +} + +// SetPipelineTaskRunsQueued provides a mock function with given fields: n +func (_m *MockPrometheusBackend) SetPipelineTaskRunsQueued(n int) { + _m.Called(n) +} + +// MockPrometheusBackend_SetPipelineTaskRunsQueued_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetPipelineTaskRunsQueued' +type MockPrometheusBackend_SetPipelineTaskRunsQueued_Call struct { + *mock.Call +} + +// SetPipelineTaskRunsQueued is a helper method to define mock.On call +// - n int +func (_e *MockPrometheusBackend_Expecter) SetPipelineTaskRunsQueued(n interface{}) *MockPrometheusBackend_SetPipelineTaskRunsQueued_Call { + return &MockPrometheusBackend_SetPipelineTaskRunsQueued_Call{Call: _e.mock.On("SetPipelineTaskRunsQueued", n)} +} + +func (_c *MockPrometheusBackend_SetPipelineTaskRunsQueued_Call) Run(run func(n int)) *MockPrometheusBackend_SetPipelineTaskRunsQueued_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(int)) + }) + return _c +} + +func (_c *MockPrometheusBackend_SetPipelineTaskRunsQueued_Call) Return() *MockPrometheusBackend_SetPipelineTaskRunsQueued_Call { + _c.Call.Return() + return _c +} + +func (_c *MockPrometheusBackend_SetPipelineTaskRunsQueued_Call) RunAndReturn(run func(int)) *MockPrometheusBackend_SetPipelineTaskRunsQueued_Call { + _c.Call.Return(run) + return _c +} + +// SetUnconfirmedTransactions provides a mock function with given fields: _a0, _a1 +func (_m *MockPrometheusBackend) SetUnconfirmedTransactions(_a0 *big.Int, _a1 int64) { + _m.Called(_a0, _a1) +} + +// MockPrometheusBackend_SetUnconfirmedTransactions_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetUnconfirmedTransactions' +type MockPrometheusBackend_SetUnconfirmedTransactions_Call struct { + *mock.Call +} + +// SetUnconfirmedTransactions is a helper method to define mock.On call +// - _a0 *big.Int +// - _a1 int64 +func (_e *MockPrometheusBackend_Expecter) SetUnconfirmedTransactions(_a0 interface{}, _a1 interface{}) *MockPrometheusBackend_SetUnconfirmedTransactions_Call { + return &MockPrometheusBackend_SetUnconfirmedTransactions_Call{Call: _e.mock.On("SetUnconfirmedTransactions", _a0, _a1)} +} + +func (_c *MockPrometheusBackend_SetUnconfirmedTransactions_Call) Run(run func(_a0 *big.Int, _a1 int64)) *MockPrometheusBackend_SetUnconfirmedTransactions_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(*big.Int), args[1].(int64)) + }) + return _c +} + +func (_c *MockPrometheusBackend_SetUnconfirmedTransactions_Call) Return() *MockPrometheusBackend_SetUnconfirmedTransactions_Call { + _c.Call.Return() + return _c +} + +func (_c *MockPrometheusBackend_SetUnconfirmedTransactions_Call) RunAndReturn(run func(*big.Int, int64)) *MockPrometheusBackend_SetUnconfirmedTransactions_Call { + _c.Call.Return(run) + return _c +} + +// NewMockPrometheusBackend creates a new instance of MockPrometheusBackend. 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 NewMockPrometheusBackend(t interface { + mock.TestingT + Cleanup(func()) +}) *MockPrometheusBackend { + mock := &MockPrometheusBackend{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/services/promreporter/prom_reporter.go b/core/services/headreporter/prometheus_reporter.go similarity index 63% rename from core/services/promreporter/prom_reporter.go rename to core/services/headreporter/prometheus_reporter.go index 31d5f1129e..3e39c7aca4 100644 --- a/core/services/promreporter/prom_reporter.go +++ b/core/services/headreporter/prometheus_reporter.go @@ -1,40 +1,28 @@ -package promreporter +package headreporter import ( "context" "fmt" "math/big" - "sync" "time" - "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" - txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" - "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "go.uber.org/multierr" - "github.com/smartcontractkit/chainlink-common/pkg/services" - "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox" - + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" + txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) type ( - promReporter struct { - services.StateMachine - ds sqlutil.DataSource - chains legacyevm.LegacyChainContainer - lggr logger.Logger - backend PrometheusBackend - newHeads *mailbox.Mailbox[*evmtypes.Head] - chStop services.StopChan - wgDone sync.WaitGroup - reportPeriod time.Duration + prometheusReporter struct { + ds sqlutil.DataSource + chains legacyevm.LegacyChainContainer + backend PrometheusBackend } PrometheusBackend interface { @@ -71,103 +59,15 @@ var ( }) ) -func (defaultBackend) SetUnconfirmedTransactions(evmChainID *big.Int, n int64) { - promUnconfirmedTransactions.WithLabelValues(evmChainID.String()).Set(float64(n)) -} - -func (defaultBackend) SetMaxUnconfirmedAge(evmChainID *big.Int, s float64) { - promMaxUnconfirmedAge.WithLabelValues(evmChainID.String()).Set(s) -} - -func (defaultBackend) SetMaxUnconfirmedBlocks(evmChainID *big.Int, n int64) { - promMaxUnconfirmedBlocks.WithLabelValues(evmChainID.String()).Set(float64(n)) -} - -func (defaultBackend) SetPipelineRunsQueued(n int) { - promPipelineTaskRunsQueued.Set(float64(n)) -} - -func (defaultBackend) SetPipelineTaskRunsQueued(n int) { - promPipelineRunsQueued.Set(float64(n)) -} - -func NewPromReporter(ds sqlutil.DataSource, chainContainer legacyevm.LegacyChainContainer, lggr logger.Logger, opts ...interface{}) *promReporter { - var backend PrometheusBackend = defaultBackend{} - period := 15 * time.Second - for _, opt := range opts { - switch v := opt.(type) { - case time.Duration: - period = v - case PrometheusBackend: - backend = v - } - } - - chStop := make(chan struct{}) - return &promReporter{ - ds: ds, - chains: chainContainer, - lggr: lggr.Named("PromReporter"), - backend: backend, - newHeads: mailbox.NewSingle[*evmtypes.Head](), - chStop: chStop, - reportPeriod: period, +func NewPrometheusReporter(ds sqlutil.DataSource, chainContainer legacyevm.LegacyChainContainer) *prometheusReporter { + return &prometheusReporter{ + ds: ds, + chains: chainContainer, + backend: defaultBackend{}, } } -// Start starts PromReporter. -func (pr *promReporter) Start(context.Context) error { - return pr.StartOnce("PromReporter", func() error { - pr.wgDone.Add(1) - go pr.eventLoop() - return nil - }) -} - -func (pr *promReporter) Close() error { - return pr.StopOnce("PromReporter", func() error { - close(pr.chStop) - pr.wgDone.Wait() - return nil - }) -} -func (pr *promReporter) Name() string { - return pr.lggr.Name() -} - -func (pr *promReporter) HealthReport() map[string]error { - return map[string]error{pr.Name(): pr.Healthy()} -} - -func (pr *promReporter) OnNewLongestChain(ctx context.Context, head *evmtypes.Head) { - pr.newHeads.Deliver(head) -} - -func (pr *promReporter) eventLoop() { - pr.lggr.Debug("Starting event loop") - defer pr.wgDone.Done() - ctx, cancel := pr.chStop.NewCtx() - defer cancel() - for { - select { - case <-pr.newHeads.Notify(): - head, exists := pr.newHeads.Retrieve() - if !exists { - continue - } - pr.reportHeadMetrics(ctx, head) - case <-time.After(pr.reportPeriod): - if err := errors.Wrap(pr.reportPipelineRunStats(ctx), "reportPipelineRunStats failed"); err != nil { - pr.lggr.Errorw("Error reporting prometheus metrics", "err", err) - } - - case <-pr.chStop: - return - } - } -} - -func (pr *promReporter) getTxm(evmChainID *big.Int) (txmgr.TxManager, error) { +func (pr *prometheusReporter) getTxm(evmChainID *big.Int) (txmgr.TxManager, error) { chain, err := pr.chains.Get(evmChainID.String()) if err != nil { return nil, fmt.Errorf("failed to get chain: %w", err) @@ -175,20 +75,16 @@ func (pr *promReporter) getTxm(evmChainID *big.Int) (txmgr.TxManager, error) { return chain.TxManager(), nil } -func (pr *promReporter) reportHeadMetrics(ctx context.Context, head *evmtypes.Head) { +func (pr *prometheusReporter) ReportNewHead(ctx context.Context, head *evmtypes.Head) error { evmChainID := head.EVMChainID.ToInt() - err := multierr.Combine( + return multierr.Combine( errors.Wrap(pr.reportPendingEthTxes(ctx, evmChainID), "reportPendingEthTxes failed"), errors.Wrap(pr.reportMaxUnconfirmedAge(ctx, evmChainID), "reportMaxUnconfirmedAge failed"), errors.Wrap(pr.reportMaxUnconfirmedBlocks(ctx, head), "reportMaxUnconfirmedBlocks failed"), ) - - if err != nil && ctx.Err() == nil { - pr.lggr.Errorw("Error reporting prometheus metrics", "err", err) - } } -func (pr *promReporter) reportPendingEthTxes(ctx context.Context, evmChainID *big.Int) (err error) { +func (pr *prometheusReporter) reportPendingEthTxes(ctx context.Context, evmChainID *big.Int) (err error) { txm, err := pr.getTxm(evmChainID) if err != nil { return fmt.Errorf("failed to get txm: %w", err) @@ -202,7 +98,7 @@ func (pr *promReporter) reportPendingEthTxes(ctx context.Context, evmChainID *bi return nil } -func (pr *promReporter) reportMaxUnconfirmedAge(ctx context.Context, evmChainID *big.Int) (err error) { +func (pr *prometheusReporter) reportMaxUnconfirmedAge(ctx context.Context, evmChainID *big.Int) (err error) { txm, err := pr.getTxm(evmChainID) if err != nil { return fmt.Errorf("failed to get txm: %w", err) @@ -221,7 +117,7 @@ func (pr *promReporter) reportMaxUnconfirmedAge(ctx context.Context, evmChainID return nil } -func (pr *promReporter) reportMaxUnconfirmedBlocks(ctx context.Context, head *evmtypes.Head) (err error) { +func (pr *prometheusReporter) reportMaxUnconfirmedBlocks(ctx context.Context, head *evmtypes.Head) (err error) { txm, err := pr.getTxm(head.EVMChainID.ToInt()) if err != nil { return fmt.Errorf("failed to get txm: %w", err) @@ -240,7 +136,11 @@ func (pr *promReporter) reportMaxUnconfirmedBlocks(ctx context.Context, head *ev return nil } -func (pr *promReporter) reportPipelineRunStats(ctx context.Context) (err error) { +func (pr *prometheusReporter) ReportPeriodic(ctx context.Context) error { + return errors.Wrap(pr.reportPipelineRunStats(ctx), "reportPipelineRunStats failed") +} + +func (pr *prometheusReporter) reportPipelineRunStats(ctx context.Context) (err error) { rows, err := pr.ds.QueryContext(ctx, ` SELECT pipeline_run_id FROM pipeline_task_runs WHERE finished_at IS NULL `) @@ -271,3 +171,23 @@ SELECT pipeline_run_id FROM pipeline_task_runs WHERE finished_at IS NULL return nil } + +func (defaultBackend) SetUnconfirmedTransactions(evmChainID *big.Int, n int64) { + promUnconfirmedTransactions.WithLabelValues(evmChainID.String()).Set(float64(n)) +} + +func (defaultBackend) SetMaxUnconfirmedAge(evmChainID *big.Int, s float64) { + promMaxUnconfirmedAge.WithLabelValues(evmChainID.String()).Set(s) +} + +func (defaultBackend) SetMaxUnconfirmedBlocks(evmChainID *big.Int, n int64) { + promMaxUnconfirmedBlocks.WithLabelValues(evmChainID.String()).Set(float64(n)) +} + +func (defaultBackend) SetPipelineRunsQueued(n int) { + promPipelineTaskRunsQueued.Set(float64(n)) +} + +func (defaultBackend) SetPipelineTaskRunsQueued(n int) { + promPipelineRunsQueued.Set(float64(n)) +} diff --git a/core/services/promreporter/prom_reporter_test.go b/core/services/headreporter/prometheus_reporter_test.go similarity index 64% rename from core/services/promreporter/prom_reporter_test.go rename to core/services/headreporter/prometheus_reporter_test.go index b61fa25bdc..d96e617fd7 100644 --- a/core/services/promreporter/prom_reporter_test.go +++ b/core/services/headreporter/prometheus_reporter_test.go @@ -1,8 +1,7 @@ -package promreporter_test +package headreporter_test import ( "math/big" - "sync/atomic" "testing" "time" @@ -10,90 +9,40 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink-common/pkg/services/servicetest" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" - evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - ubig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" - "github.com/smartcontractkit/chainlink/v2/core/internal/mocks" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/promreporter" + "github.com/smartcontractkit/chainlink/v2/core/services/headreporter" ) -func newHead() evmtypes.Head { - return evmtypes.Head{Number: 42, EVMChainID: ubig.NewI(0)} -} - -func newLegacyChainContainer(t *testing.T, db *sqlx.DB) legacyevm.LegacyChainContainer { - config, dbConfig, evmConfig := txmgr.MakeTestConfigs(t) - keyStore := cltest.NewKeyStore(t, db).Eth() - ethClient := evmtest.NewEthClientMockWithDefaultChain(t) - estimator, err := gas.NewEstimator(logger.TestLogger(t), ethClient, config, evmConfig.GasEstimator()) - require.NoError(t, err) - lggr := logger.TestLogger(t) - lpOpts := logpoller.Opts{ - PollPeriod: 100 * time.Millisecond, - FinalityDepth: 2, - BackfillBatchSize: 3, - RpcBatchSize: 2, - KeepFinalizedBlocksDepth: 1000, - } - ht := headtracker.NewSimulatedHeadTracker(ethClient, lpOpts.UseFinalityTag, lpOpts.FinalityDepth) - lp := logpoller.NewLogPoller(logpoller.NewORM(testutils.FixtureChainID, db, lggr), ethClient, lggr, ht, lpOpts) - - txm, err := txmgr.NewTxm( - db, - evmConfig, - evmConfig.GasEstimator(), - evmConfig.Transactions(), - nil, - dbConfig, - dbConfig.Listener(), - ethClient, - lggr, - lp, - keyStore, - estimator) - require.NoError(t, err) - - cfg := configtest.NewGeneralConfig(t, nil) - return cltest.NewLegacyChainsWithMockChainAndTxManager(t, ethClient, cfg, txm) -} - -func Test_PromReporter_OnNewLongestChain(t *testing.T) { +func Test_PrometheusReporter(t *testing.T) { t.Run("with nothing in the database", func(t *testing.T) { db := pgtest.NewSqlxDB(t) - backend := mocks.NewPrometheusBackend(t) - reporter := promreporter.NewPromReporter(db, newLegacyChainContainer(t, db), logger.TestLogger(t), backend, 10*time.Millisecond) - - var subscribeCalls atomic.Int32 - + backend := headreporter.NewMockPrometheusBackend(t) backend.On("SetUnconfirmedTransactions", big.NewInt(0), int64(0)).Return() backend.On("SetMaxUnconfirmedAge", big.NewInt(0), float64(0)).Return() backend.On("SetMaxUnconfirmedBlocks", big.NewInt(0), int64(0)).Return() - backend.On("SetPipelineTaskRunsQueued", 0).Return() - backend.On("SetPipelineRunsQueued", 0). - Run(func(args mock.Arguments) { - subscribeCalls.Add(1) - }). - Return() - servicetest.Run(t, reporter) + reporter := headreporter.NewPrometheusReporter(db, newLegacyChainContainer(t, db)) + reporter.SetBackend(backend) - head := newHead() - reporter.OnNewLongestChain(testutils.Context(t), &head) + head := headreporter.NewHead() + err := reporter.ReportNewHead(testutils.Context(t), &head) + require.NoError(t, err) - require.Eventually(t, func() bool { return subscribeCalls.Load() >= 1 }, 12*time.Second, 100*time.Millisecond) + backend.On("SetPipelineTaskRunsQueued", 0).Return() + backend.On("SetPipelineRunsQueued", 0).Return() + err = reporter.ReportPeriodic(testutils.Context(t)) + require.NoError(t, err) }) t.Run("with unconfirmed evm.txes", func(t *testing.T) { @@ -102,61 +51,93 @@ func Test_PromReporter_OnNewLongestChain(t *testing.T) { ethKeyStore := cltest.NewKeyStore(t, db).Eth() _, fromAddress := cltest.MustInsertRandomKey(t, ethKeyStore) - var subscribeCalls atomic.Int32 + etx := cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, txStore, 0, fromAddress) + cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, txStore, 1, fromAddress) + cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, txStore, 2, fromAddress) + require.NoError(t, txStore.UpdateTxAttemptBroadcastBeforeBlockNum(testutils.Context(t), etx.ID, 7)) - backend := mocks.NewPrometheusBackend(t) + backend := headreporter.NewMockPrometheusBackend(t) backend.On("SetUnconfirmedTransactions", big.NewInt(0), int64(3)).Return() backend.On("SetMaxUnconfirmedAge", big.NewInt(0), mock.MatchedBy(func(s float64) bool { return s > 0 })).Return() backend.On("SetMaxUnconfirmedBlocks", big.NewInt(0), int64(35)).Return() - backend.On("SetPipelineTaskRunsQueued", 0).Return() - backend.On("SetPipelineRunsQueued", 0). - Run(func(args mock.Arguments) { - subscribeCalls.Add(1) - }). - Return() - reporter := promreporter.NewPromReporter(db, newLegacyChainContainer(t, db), logger.TestLogger(t), backend, 10*time.Millisecond) - servicetest.Run(t, reporter) - etx := cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, txStore, 0, fromAddress) - cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, txStore, 1, fromAddress) - cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, txStore, 2, fromAddress) - require.NoError(t, txStore.UpdateTxAttemptBroadcastBeforeBlockNum(testutils.Context(t), etx.ID, 7)) + reporter := headreporter.NewPrometheusReporter(db, newLegacyChainContainer(t, db)) + reporter.SetBackend(backend) + + head := headreporter.NewHead() + err := reporter.ReportNewHead(testutils.Context(t), &head) + require.NoError(t, err) - head := newHead() - reporter.OnNewLongestChain(testutils.Context(t), &head) + backend.On("SetPipelineTaskRunsQueued", 0).Return() + backend.On("SetPipelineRunsQueued", 0).Return() - require.Eventually(t, func() bool { return subscribeCalls.Load() >= 1 }, 12*time.Second, 100*time.Millisecond) + err = reporter.ReportPeriodic(testutils.Context(t)) + require.NoError(t, err) }) t.Run("with unfinished pipeline task runs", func(t *testing.T) { db := pgtest.NewSqlxDB(t) pgtest.MustExec(t, db, `SET CONSTRAINTS pipeline_task_runs_pipeline_run_id_fkey DEFERRED`) - backend := mocks.NewPrometheusBackend(t) - reporter := promreporter.NewPromReporter(db, newLegacyChainContainer(t, db), logger.TestLogger(t), backend, 10*time.Millisecond) - cltest.MustInsertUnfinishedPipelineTaskRun(t, db, 1) cltest.MustInsertUnfinishedPipelineTaskRun(t, db, 1) cltest.MustInsertUnfinishedPipelineTaskRun(t, db, 2) - var subscribeCalls atomic.Int32 - + backend := headreporter.NewMockPrometheusBackend(t) backend.On("SetUnconfirmedTransactions", big.NewInt(0), int64(0)).Return() backend.On("SetMaxUnconfirmedAge", big.NewInt(0), float64(0)).Return() backend.On("SetMaxUnconfirmedBlocks", big.NewInt(0), int64(0)).Return() - backend.On("SetPipelineTaskRunsQueued", 3).Return() - backend.On("SetPipelineRunsQueued", 2). - Run(func(args mock.Arguments) { - subscribeCalls.Add(1) - }). - Return() - servicetest.Run(t, reporter) - head := newHead() - reporter.OnNewLongestChain(testutils.Context(t), &head) + reporter := headreporter.NewPrometheusReporter(db, newLegacyChainContainer(t, db)) + reporter.SetBackend(backend) + + head := headreporter.NewHead() + err := reporter.ReportNewHead(testutils.Context(t), &head) + require.NoError(t, err) + + backend.On("SetPipelineTaskRunsQueued", 3).Return() + backend.On("SetPipelineRunsQueued", 2).Return() - require.Eventually(t, func() bool { return subscribeCalls.Load() >= 1 }, 12*time.Second, 100*time.Millisecond) + err = reporter.ReportPeriodic(testutils.Context(t)) + require.NoError(t, err) }) } + +func newLegacyChainContainer(t *testing.T, db *sqlx.DB) legacyevm.LegacyChainContainer { + config, dbConfig, evmConfig := txmgr.MakeTestConfigs(t) + keyStore := cltest.NewKeyStore(t, db).Eth() + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + estimator, err := gas.NewEstimator(logger.TestLogger(t), ethClient, config, evmConfig.GasEstimator()) + require.NoError(t, err) + lggr := logger.TestLogger(t) + lpOpts := logpoller.Opts{ + PollPeriod: 100 * time.Millisecond, + FinalityDepth: 2, + BackfillBatchSize: 3, + RpcBatchSize: 2, + KeepFinalizedBlocksDepth: 1000, + } + ht := headtracker.NewSimulatedHeadTracker(ethClient, lpOpts.UseFinalityTag, lpOpts.FinalityDepth) + lp := logpoller.NewLogPoller(logpoller.NewORM(testutils.FixtureChainID, db, lggr), ethClient, lggr, ht, lpOpts) + + txm, err := txmgr.NewTxm( + db, + evmConfig, + evmConfig.GasEstimator(), + evmConfig.Transactions(), + nil, + dbConfig, + dbConfig.Listener(), + ethClient, + lggr, + lp, + keyStore, + estimator, + ht) + require.NoError(t, err) + + cfg := configtest.NewGeneralConfig(t, nil) + return cltest.NewLegacyChainsWithMockChainAndTxManager(t, ethClient, cfg, txm) +} diff --git a/core/services/headreporter/telemetry_reporter.go b/core/services/headreporter/telemetry_reporter.go new file mode 100644 index 0000000000..0d93ca59a4 --- /dev/null +++ b/core/services/headreporter/telemetry_reporter.go @@ -0,0 +1,69 @@ +package headreporter + +import ( + "context" + "math/big" + + "github.com/pkg/errors" + + "github.com/smartcontractkit/libocr/commontypes" + "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, 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{lggr: lggr.Named("TelemetryReporter"), endpoints: endpoints} +} + +func (t *telemetryReporter) ReportNewHead(ctx context.Context, head *evmtypes.Head) error { + monitoringEndpoint := t.endpoints[head.EVMChainID.ToInt().Uint64()] + if monitoringEndpoint == nil { + return errors.Errorf("No monitoring endpoint provided chain_id=%d", head.EVMChainID.Int64()) + } + var finalized *telem.Block + latestFinalizedHead := head.LatestFinalizedHead() + if latestFinalizedHead != nil { + finalized = &telem.Block{ + Timestamp: uint64(latestFinalizedHead.GetTimestamp().UTC().Unix()), + Number: uint64(latestFinalizedHead.BlockNumber()), + Hash: latestFinalizedHead.BlockHash().Hex(), + } + } + request := &telem.HeadReportRequest{ + ChainID: head.EVMChainID.String(), + Latest: &telem.Block{ + Timestamp: uint64(head.Timestamp.UTC().Unix()), + Number: uint64(head.Number), + Hash: head.Hash.Hex(), + }, + Finalized: finalized, + } + bytes, err := proto.Marshal(request) + if err != nil { + return errors.WithMessage(err, "telem.HeadReportRequest marshal error") + } + monitoringEndpoint.SendLog(bytes) + if finalized == nil { + t.lggr.Infow("No finalized block was found", "chainID", head.EVMChainID.Int64(), + "head.number", head.Number, "chainLength", head.ChainLength()) + } + return nil +} + +func (t *telemetryReporter) ReportPeriodic(ctx context.Context) error { + return nil +} diff --git a/core/services/headreporter/telemetry_reporter_test.go b/core/services/headreporter/telemetry_reporter_test.go new file mode 100644 index 0000000000..6d7f4e3dde --- /dev/null +++ b/core/services/headreporter/telemetry_reporter_test.go @@ -0,0 +1,107 @@ +package headreporter_test + +import ( + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/proto" + + mocks2 "github.com/smartcontractkit/chainlink/v2/common/types/mocks" + 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" + "github.com/smartcontractkit/chainlink/v2/core/services/telemetry" +) + +func Test_TelemetryReporter_NewHead(t *testing.T) { + head := evmtypes.Head{ + Number: 42, + EVMChainID: ubig.NewI(100), + Hash: common.HexToHash("0x1010"), + Timestamp: time.UnixMilli(1000), + } + h41 := &evmtypes.Head{ + Number: 41, + Hash: common.HexToHash("0x1009"), + Timestamp: time.UnixMilli(999), + } + h41.IsFinalized.Store(true) + head.Parent.Store(h41) + requestBytes, err := proto.Marshal(&telem.HeadReportRequest{ + ChainID: "100", + Latest: &telem.Block{ + Timestamp: uint64(head.Timestamp.UTC().Unix()), + Number: 42, + Hash: head.Hash.Hex(), + }, + Finalized: &telem.Block{ + Timestamp: uint64(head.Parent.Load().Timestamp.UTC().Unix()), + Number: 41, + Hash: head.Parent.Load().Hash.Hex(), + }, + }) + assert.NoError(t, err) + + monitoringEndpoint := mocks2.NewMonitoringEndpoint(t) + monitoringEndpoint.On("SendLog", requestBytes).Return() + + monitoringEndpointGen := telemetry.NewMockMonitoringEndpointGenerator(t) + monitoringEndpointGen. + On("GenMonitoringEndpoint", "EVM", "100", "", synchronization.HeadReport). + Return(monitoringEndpoint) + reporter := headreporter.NewTelemetryReporter(monitoringEndpointGen, logger.TestLogger(t), big.NewInt(100)) + + err = reporter.ReportNewHead(testutils.Context(t), &head) + assert.NoError(t, err) +} + +func Test_TelemetryReporter_NewHeadMissingFinalized(t *testing.T) { + head := evmtypes.Head{ + Number: 42, + EVMChainID: ubig.NewI(100), + Hash: common.HexToHash("0x1010"), + Timestamp: time.UnixMilli(1000), + } + requestBytes, err := proto.Marshal(&telem.HeadReportRequest{ + ChainID: "100", + Latest: &telem.Block{ + Timestamp: uint64(head.Timestamp.UTC().Unix()), + Number: 42, + Hash: head.Hash.Hex(), + }, + }) + assert.NoError(t, err) + + monitoringEndpoint := mocks2.NewMonitoringEndpoint(t) + monitoringEndpoint.On("SendLog", requestBytes).Return() + + monitoringEndpointGen := telemetry.NewMockMonitoringEndpointGenerator(t) + monitoringEndpointGen. + On("GenMonitoringEndpoint", "EVM", "100", "", synchronization.HeadReport). + Return(monitoringEndpoint) + reporter := headreporter.NewTelemetryReporter(monitoringEndpointGen, logger.TestLogger(t), big.NewInt(100)) + + err = reporter.ReportNewHead(testutils.Context(t), &head) + assert.NoError(t, err) +} + +func Test_TelemetryReporter_NewHead_MissingEndpoint(t *testing.T) { + monitoringEndpointGen := telemetry.NewMockMonitoringEndpointGenerator(t) + monitoringEndpointGen. + On("GenMonitoringEndpoint", "EVM", "100", "", synchronization.HeadReport). + Return(nil) + + reporter := headreporter.NewTelemetryReporter(monitoringEndpointGen, logger.TestLogger(t), big.NewInt(100)) + + head := evmtypes.Head{Number: 42, EVMChainID: ubig.NewI(100)} + + err := reporter.ReportNewHead(testutils.Context(t), &head) + assert.Errorf(t, err, "No monitoring endpoint provided chain_id=100") +} diff --git a/core/services/job/job_orm_test.go b/core/services/job/job_orm_test.go index 37be1b7955..1d72fc5ee4 100644 --- a/core/services/job/job_orm_test.go +++ b/core/services/job/job_orm_test.go @@ -1861,6 +1861,7 @@ func Test_ORM_FindJobByWorkflow(t *testing.T) { spec: &job.WorkflowSpec{ ID: 1, Workflow: pkgworkflows.WFYamlSpec(t, "workflow01", addr1), + SpecType: job.YamlSpec, }, before: mustInsertWFJob, }, @@ -1881,6 +1882,7 @@ func Test_ORM_FindJobByWorkflow(t *testing.T) { var c job.WorkflowSpec c.ID = s.ID c.Workflow = pkgworkflows.WFYamlSpec(t, "workflow99", addr1) // insert with mismatched name + c.SpecType = job.YamlSpec return mustInsertWFJob(t, o, &c) }, }, @@ -1948,18 +1950,21 @@ func Test_ORM_FindJobByWorkflow_Multiple(t *testing.T) { wfYaml1 := pkgworkflows.WFYamlSpec(t, "workflow00", addr1) s1 := job.WorkflowSpec{ Workflow: wfYaml1, + SpecType: job.YamlSpec, } wantJobID1 := mustInsertWFJob(t, o, &s1) wfYaml2 := pkgworkflows.WFYamlSpec(t, "workflow01", addr1) s2 := job.WorkflowSpec{ Workflow: wfYaml2, + SpecType: job.YamlSpec, } wantJobID2 := mustInsertWFJob(t, o, &s2) wfYaml3 := pkgworkflows.WFYamlSpec(t, "workflow00", addr2) s3 := job.WorkflowSpec{ Workflow: wfYaml3, + SpecType: job.YamlSpec, } wantJobID3 := mustInsertWFJob(t, o, &s3) @@ -1976,13 +1981,14 @@ func Test_ORM_FindJobByWorkflow_Multiple(t *testing.T) { assert.EqualValues(t, j.WorkflowSpec.WorkflowID, s.WorkflowID) assert.EqualValues(t, j.WorkflowSpec.WorkflowOwner, s.WorkflowOwner) assert.EqualValues(t, j.WorkflowSpec.WorkflowName, s.WorkflowName) + assert.Equal(t, j.WorkflowSpec.SpecType, job.YamlSpec) } }) } func mustInsertWFJob(t *testing.T, orm job.ORM, s *job.WorkflowSpec) int32 { t.Helper() - err := s.Validate() + err := s.Validate(testutils.Context(t)) require.NoError(t, err, "failed to validate spec %v", s) ctx := testutils.Context(t) _, err = toml.Marshal(s.Workflow) diff --git a/core/services/job/mocks/orm.go b/core/services/job/mocks/orm.go index 0174d6208c..7d3e3de771 100644 --- a/core/services/job/mocks/orm.go +++ b/core/services/job/mocks/orm.go @@ -543,6 +543,63 @@ func (_c *ORM_FindJobIDByAddress_Call) RunAndReturn(run func(context.Context, ty return _c } +// FindJobIDByCapabilityNameAndVersion provides a mock function with given fields: ctx, spec +func (_m *ORM) FindJobIDByCapabilityNameAndVersion(ctx context.Context, spec job.CCIPSpec) (int32, error) { + ret := _m.Called(ctx, spec) + + if len(ret) == 0 { + panic("no return value specified for FindJobIDByCapabilityNameAndVersion") + } + + var r0 int32 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, job.CCIPSpec) (int32, error)); ok { + return rf(ctx, spec) + } + if rf, ok := ret.Get(0).(func(context.Context, job.CCIPSpec) int32); ok { + r0 = rf(ctx, spec) + } else { + r0 = ret.Get(0).(int32) + } + + if rf, ok := ret.Get(1).(func(context.Context, job.CCIPSpec) error); ok { + r1 = rf(ctx, spec) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ORM_FindJobIDByCapabilityNameAndVersion_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindJobIDByCapabilityNameAndVersion' +type ORM_FindJobIDByCapabilityNameAndVersion_Call struct { + *mock.Call +} + +// FindJobIDByCapabilityNameAndVersion is a helper method to define mock.On call +// - ctx context.Context +// - spec job.CCIPSpec +func (_e *ORM_Expecter) FindJobIDByCapabilityNameAndVersion(ctx interface{}, spec interface{}) *ORM_FindJobIDByCapabilityNameAndVersion_Call { + return &ORM_FindJobIDByCapabilityNameAndVersion_Call{Call: _e.mock.On("FindJobIDByCapabilityNameAndVersion", ctx, spec)} +} + +func (_c *ORM_FindJobIDByCapabilityNameAndVersion_Call) Run(run func(ctx context.Context, spec job.CCIPSpec)) *ORM_FindJobIDByCapabilityNameAndVersion_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(job.CCIPSpec)) + }) + return _c +} + +func (_c *ORM_FindJobIDByCapabilityNameAndVersion_Call) Return(_a0 int32, _a1 error) *ORM_FindJobIDByCapabilityNameAndVersion_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ORM_FindJobIDByCapabilityNameAndVersion_Call) RunAndReturn(run func(context.Context, job.CCIPSpec) (int32, error)) *ORM_FindJobIDByCapabilityNameAndVersion_Call { + _c.Call.Return(run) + return _c +} + // FindJobIDByWorkflow provides a mock function with given fields: ctx, spec func (_m *ORM) FindJobIDByWorkflow(ctx context.Context, spec job.WorkflowSpec) (int32, error) { ret := _m.Called(ctx, spec) diff --git a/core/services/job/models.go b/core/services/job/models.go index 1c46d08c59..b9ec8e726f 100644 --- a/core/services/job/models.go +++ b/core/services/job/models.go @@ -1,6 +1,7 @@ package job import ( + "context" "database/sql/driver" "encoding/json" "fmt" @@ -14,9 +15,12 @@ import ( "github.com/pkg/errors" "gopkg.in/guregu/null.v4" + "github.com/smartcontractkit/chainlink-common/pkg/workflows/sdk" + commonassets "github.com/smartcontractkit/chainlink-common/pkg/assets" "github.com/smartcontractkit/chainlink-common/pkg/types" pkgworkflows "github.com/smartcontractkit/chainlink-common/pkg/workflows" + "github.com/smartcontractkit/chainlink/v2/core/services/relay" "github.com/smartcontractkit/chainlink/v2/core/bridges" @@ -857,16 +861,26 @@ type LiquidityBalancerSpec struct { LiquidityBalancerConfig string `toml:"liquidityBalancerConfig" db:"liquidity_balancer_config"` } +type WorkflowSpecType string + +const ( + YamlSpec WorkflowSpecType = "yaml" + DefaultSpecType = YamlSpec +) + type WorkflowSpec struct { ID int32 `toml:"-"` - Workflow string `toml:"workflow"` // the yaml representation of the workflow + Workflow string `toml:"workflow"` // the raw representation of the workflow + Config string `toml:"config"` // the raw representation of the config // fields derived from the yaml spec, used for indexing the database // note: i tried to make these private, but translating them to the database seems to require them to be public - WorkflowID string `toml:"-" db:"workflow_id"` // Derived. Do not modify. the CID of the workflow. - WorkflowOwner string `toml:"-" db:"workflow_owner"` // Derived. Do not modify. the owner of the workflow. - WorkflowName string `toml:"-" db:"workflow_name"` // Derived. Do not modify. the name of the workflow. - CreatedAt time.Time `toml:"-"` - UpdatedAt time.Time `toml:"-"` + WorkflowID string `toml:"-" db:"workflow_id"` // Derived. Do not modify. the CID of the workflow. + WorkflowOwner string `toml:"-" db:"workflow_owner"` // Derived. Do not modify. the owner of the workflow. + WorkflowName string `toml:"-" db:"workflow_name"` // Derived. Do not modify. the name of the workflow. + CreatedAt time.Time `toml:"-"` + UpdatedAt time.Time `toml:"-"` + SpecType WorkflowSpecType `db:"spec_type"` + sdkWorkflow *sdk.WorkflowSpec } var ( @@ -879,14 +893,18 @@ const ( ) // Validate checks the workflow spec for correctness -func (w *WorkflowSpec) Validate() error { +func (w *WorkflowSpec) Validate(ctx context.Context) error { s, err := pkgworkflows.ParseWorkflowSpecYaml(w.Workflow) if err != nil { return fmt.Errorf("%w: failed to parse workflow spec %s: %w", ErrInvalidWorkflowYAMLSpec, w.Workflow, err) } + + if _, err = w.SDKSpec(ctx); err != nil { + return err + } + w.WorkflowOwner = strings.TrimPrefix(s.Owner, "0x") // the json schema validation ensures it is a hex string with 0x prefix, but the database does not store the prefix w.WorkflowName = s.Name - w.WorkflowID = s.CID() if len(w.WorkflowID) != workflowIDLen { return fmt.Errorf("%w: incorrect length for id %s: expected %d, got %d", ErrInvalidWorkflowID, w.WorkflowID, workflowIDLen, len(w.WorkflowID)) @@ -895,6 +913,20 @@ func (w *WorkflowSpec) Validate() error { return nil } +func (w *WorkflowSpec) SDKSpec(ctx context.Context) (sdk.WorkflowSpec, error) { + if w.sdkWorkflow != nil { + return *w.sdkWorkflow, nil + } + + spec, cid, err := workflowSpecFactory.Spec(ctx, w.Workflow, []byte(w.Config), w.SpecType) + if err != nil { + return sdk.WorkflowSpec{}, err + } + w.sdkWorkflow = &spec + w.WorkflowID = cid + return spec, nil +} + type StandardCapabilitiesSpec struct { ID int32 CreatedAt time.Time `toml:"-"` diff --git a/core/services/job/models_test.go b/core/services/job/models_test.go index 1f88bb6d38..49bd29c995 100644 --- a/core/services/job/models_test.go +++ b/core/services/job/models_test.go @@ -11,6 +11,8 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/codec" "github.com/smartcontractkit/chainlink-common/pkg/types" pkgworkflows "github.com/smartcontractkit/chainlink-common/pkg/workflows" + + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/services/relay" "github.com/stretchr/testify/assert" @@ -322,7 +324,7 @@ func TestWorkflowSpec_Validate(t *testing.T) { w := &WorkflowSpec{ Workflow: tt.fields.Workflow, } - err := w.Validate() + err := w.Validate(testutils.Context(t)) require.Equal(t, tt.wantError, err != nil) if !tt.wantError { assert.NotEmpty(t, w.WorkflowID) diff --git a/core/services/job/orm.go b/core/services/job/orm.go index ac3bb65530..d02e0b2920 100644 --- a/core/services/job/orm.go +++ b/core/services/job/orm.go @@ -79,6 +79,7 @@ type ORM interface { WithDataSource(source sqlutil.DataSource) ORM FindJobIDByWorkflow(ctx context.Context, spec WorkflowSpec) (int32, error) + FindJobIDByCapabilityNameAndVersion(ctx context.Context, spec CCIPSpec) (int32, error) } type ORMConfig interface { @@ -408,8 +409,8 @@ func (o *orm) CreateJob(ctx context.Context, jb *Job) error { case Stream: // 'stream' type has no associated spec, nothing to do here case Workflow: - sql := `INSERT INTO workflow_specs (workflow, workflow_id, workflow_owner, workflow_name, created_at, updated_at) - VALUES (:workflow, :workflow_id, :workflow_owner, :workflow_name, NOW(), NOW()) + sql := `INSERT INTO workflow_specs (workflow, workflow_id, workflow_owner, workflow_name, created_at, updated_at, spec_type) + VALUES (:workflow, :workflow_id, :workflow_owner, :workflow_name, NOW(), NOW(), :spec_type) RETURNING id;` specID, err := tx.prepareQuerySpecID(ctx, sql, jb.WorkflowSpec) if err != nil { @@ -960,7 +961,7 @@ func (o *orm) FindJob(ctx context.Context, id int32) (jb Job, err error) { return } -// FindJobWithoutSpecErrors returns a job by ID, without loading Spec Errors preloaded +// FindJobWithoutSpecErrors returns a job by ID, without loading SpecVal Errors preloaded func (o *orm) FindJobWithoutSpecErrors(ctx context.Context, id int32) (jb Job, err error) { err = o.transact(ctx, true, func(tx *orm) error { stmt := "SELECT jobs.*, job_pipeline_specs.pipeline_spec_id as pipeline_spec_id FROM jobs JOIN job_pipeline_specs ON (jobs.id = job_pipeline_specs.job_id) WHERE jobs.id = $1 LIMIT 1" @@ -1123,6 +1124,18 @@ INNER JOIN workflow_specs ws on jobs.workflow_spec_id = ws.id AND ws.workflow_ow return } +func (o *orm) FindJobIDByCapabilityNameAndVersion(ctx context.Context, spec CCIPSpec) (jobID int32, err error) { + stmt := ` +SELECT jobs.id FROM jobs +INNER JOIN ccip_specs ccip on jobs.ccip_spec_id = ccip.id AND ccip.capability_labelled_name = $1 AND ccip.capability_version = $2 +` + err = o.ds.GetContext(ctx, &jobID, stmt, spec.CapabilityLabelledName, spec.CapabilityVersion) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + err = fmt.Errorf("error searching for job for CCIP (capabilityName,capabilityVersion) ('%s','%s'): %w", spec.CapabilityLabelledName, spec.CapabilityVersion, err) + } + return +} + // PipelineRunsByJobsIDs returns pipeline runs for multiple jobs, not preloading data func (o *orm) PipelineRunsByJobsIDs(ctx context.Context, ids []int32) (runs []pipeline.Run, err error) { err = o.transact(ctx, false, func(tx *orm) error { diff --git a/core/services/job/validate.go b/core/services/job/validate.go index 92a08823fc..7cd93b91f0 100644 --- a/core/services/job/validate.go +++ b/core/services/job/validate.go @@ -29,6 +29,7 @@ var ( Webhook: {}, Workflow: {}, StandardCapabilities: {}, + CCIP: {}, } ) diff --git a/core/services/job/validate_test.go b/core/services/job/validate_test.go old mode 100644 new mode 100755 diff --git a/core/services/job/workflow_spec_factory.go b/core/services/job/workflow_spec_factory.go new file mode 100644 index 0000000000..565e6a9fce --- /dev/null +++ b/core/services/job/workflow_spec_factory.go @@ -0,0 +1,51 @@ +package job + +import ( + "context" + "crypto/sha256" + "errors" + "fmt" + + "github.com/smartcontractkit/chainlink-common/pkg/workflows/sdk" +) + +var ErrInvalidWorkflowType = errors.New("invalid workflow type") + +type SDKWorkflowSpecFactory interface { + Spec(ctx context.Context, rawSpec, config []byte) (sdk.WorkflowSpec, error) + RawSpec(ctx context.Context, wf string) ([]byte, error) +} + +type WorkflowSpecFactory map[WorkflowSpecType]SDKWorkflowSpecFactory + +func (wsf WorkflowSpecFactory) Spec( + ctx context.Context, workflow string, config []byte, tpe WorkflowSpecType) (sdk.WorkflowSpec, string, error) { + if tpe == "" { + tpe = DefaultSpecType + } + + factory, ok := wsf[tpe] + if !ok { + return sdk.WorkflowSpec{}, "", ErrInvalidWorkflowType + } + + rawSpec, err := factory.RawSpec(ctx, workflow) + if err != nil { + return sdk.WorkflowSpec{}, "", err + } + + spec, err := factory.Spec(ctx, rawSpec, config) + if err != nil { + return sdk.WorkflowSpec{}, "", err + } + + sum := sha256.New() + sum.Write(rawSpec) + sum.Write(config) + + return spec, fmt.Sprintf("%x", sum.Sum(nil)), nil +} + +var workflowSpecFactory = WorkflowSpecFactory{ + YamlSpec: YAMLSpecFactory{}, +} diff --git a/core/services/job/workflow_spec_factory_test.go b/core/services/job/workflow_spec_factory_test.go new file mode 100644 index 0000000000..dc2ff3bac9 --- /dev/null +++ b/core/services/job/workflow_spec_factory_test.go @@ -0,0 +1,106 @@ +package job_test + +import ( + "context" + "crypto/sha256" + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/workflows/sdk" + + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/services/job" +) + +func TestWorkflowSpecFactory_ToSpec(t *testing.T) { + t.Parallel() + + anyData := "any data" + anyConfig := []byte("any config") + anySpec := sdk.WorkflowSpec{Name: "name", Owner: "owner"} + + t.Run("delegates to factory and calculates CID", func(t *testing.T) { + runYamlSpecTest(t, anySpec, anyData, anyConfig, job.YamlSpec) + }) + + t.Run("delegates default", func(t *testing.T) { + runYamlSpecTest(t, anySpec, anyData, anyConfig, "") + }) + + t.Run("CID without config matches", func(t *testing.T) { + factory := job.WorkflowSpecFactory{ + job.YamlSpec: mockSdkSpecFactory{t: t, noConfig: true, SpecVal: anySpec}, + } + results, cid, err := factory.Spec(testutils.Context(t), anyData, nil, job.YamlSpec) + require.NoError(t, err) + + assert.Equal(t, anySpec, results) + + sha256Hash := sha256.New() + sha256Hash.Write([]byte(anyData)) + expectedCid := fmt.Sprintf("%x", sha256Hash.Sum(nil)) + assert.Equal(t, expectedCid, cid) + }) + + t.Run("returns errors from sdk factory", func(t *testing.T) { + anyErr := errors.New("nope") + factory := job.WorkflowSpecFactory{ + job.YamlSpec: mockSdkSpecFactory{t: t, Err: anyErr}, + } + + _, _, err := factory.Spec(testutils.Context(t), anyData, anyConfig, job.YamlSpec) + assert.Equal(t, anyErr, err) + }) + + t.Run("returns an error if the type is not supported", func(t *testing.T) { + factory := job.WorkflowSpecFactory{ + job.YamlSpec: mockSdkSpecFactory{t: t, SpecVal: anySpec}, + } + + _, _, err := factory.Spec(testutils.Context(t), anyData, anyConfig, "unsupported") + assert.Error(t, err) + }) +} + +func runYamlSpecTest(t *testing.T, anySpec sdk.WorkflowSpec, anyData string, anyConfig []byte, specType job.WorkflowSpecType) { + factory := job.WorkflowSpecFactory{ + job.YamlSpec: mockSdkSpecFactory{t: t, SpecVal: anySpec}, + } + + results, cid, err := factory.Spec(testutils.Context(t), anyData, anyConfig, specType) + + require.NoError(t, err) + assert.Equal(t, anySpec, results) + + sha256Hash := sha256.New() + sha256Hash.Write([]byte(anyData)) + sha256Hash.Write(anyConfig) + expectedCid := fmt.Sprintf("%x", sha256Hash.Sum(nil)) + assert.Equal(t, expectedCid, cid) +} + +type mockSdkSpecFactory struct { + t *testing.T + noConfig bool + SpecVal sdk.WorkflowSpec + Err error +} + +func (f mockSdkSpecFactory) RawSpec(_ context.Context, wf string) ([]byte, error) { + return []byte(wf), nil +} + +func (f mockSdkSpecFactory) Spec(_ context.Context, rawSpec, config []byte) (sdk.WorkflowSpec, error) { + assert.ElementsMatch(f.t, rawSpec, []byte("any data")) + if f.noConfig { + assert.Nil(f.t, config) + } else { + assert.ElementsMatch(f.t, config, []byte("any config")) + } + + return f.SpecVal, f.Err +} diff --git a/core/services/job/yaml_spec_factory.go b/core/services/job/yaml_spec_factory.go new file mode 100644 index 0000000000..ea344a3ffc --- /dev/null +++ b/core/services/job/yaml_spec_factory.go @@ -0,0 +1,20 @@ +package job + +import ( + "context" + + "github.com/smartcontractkit/chainlink-common/pkg/workflows" + "github.com/smartcontractkit/chainlink-common/pkg/workflows/sdk" +) + +type YAMLSpecFactory struct{} + +var _ SDKWorkflowSpecFactory = (*YAMLSpecFactory)(nil) + +func (y YAMLSpecFactory) Spec(_ context.Context, rawSpec, _ []byte) (sdk.WorkflowSpec, error) { + return workflows.ParseWorkflowSpecYaml(string(rawSpec)) +} + +func (y YAMLSpecFactory) RawSpec(_ context.Context, wf string) ([]byte, error) { + return []byte(wf), nil +} diff --git a/core/services/job/yaml_spec_factory_test.go b/core/services/job/yaml_spec_factory_test.go new file mode 100644 index 0000000000..4d075fe6e2 --- /dev/null +++ b/core/services/job/yaml_spec_factory_test.go @@ -0,0 +1,82 @@ +package job_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + commonworkflows "github.com/smartcontractkit/chainlink-common/pkg/workflows" + + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/services/job" +) + +const anyYamlSpec = ` +name: "wf-name" +owner: "0x00000000000000000000000000000000000000aa" +triggers: + - id: "mercury-trigger@1.0.0" + config: + feedIds: + - "0x1111111111111111111100000000000000000000000000000000000000000000" + - "0x2222222222222222222200000000000000000000000000000000000000000000" + - "0x3333333333333333333300000000000000000000000000000000000000000000" + +consensus: + - id: "offchain_reporting@2.0.0" + ref: "evm_median" + inputs: + observations: + - "$(trigger.outputs)" + config: + aggregation_method: "data_feeds_2_0" + aggregation_config: + "0x1111111111111111111100000000000000000000000000000000000000000000": + deviation: "0.001" + heartbeat: 3600 + "0x2222222222222222222200000000000000000000000000000000000000000000": + deviation: "0.001" + heartbeat: 3600 + "0x3333333333333333333300000000000000000000000000000000000000000000": + deviation: "0.001" + heartbeat: 3600 + encoder: "EVM" + encoder_config: + abi: "mercury_reports bytes[]" + +targets: + - id: "write_polygon-testnet-mumbai@3.0.0" + inputs: + report: "$(evm_median.outputs.report)" + config: + address: "0x3F3554832c636721F1fD1822Ccca0354576741Ef" + params: ["$(report)"] + abi: "receive(report bytes)" + - id: "write_ethereum-testnet-sepolia@4.0.0" + inputs: + report: "$(evm_median.outputs.report)" + config: + address: "0x54e220867af6683aE6DcBF535B4f952cB5116510" + params: ["$(report)"] + abi: "receive(report bytes)" +` + +func TestYamlSpecFactory_GetSpec(t *testing.T) { + t.Parallel() + + actual, err := job.YAMLSpecFactory{}.Spec(testutils.Context(t), []byte(anyYamlSpec), []byte{}) + require.NoError(t, err) + + expected, err := commonworkflows.ParseWorkflowSpecYaml(anyYamlSpec) + require.NoError(t, err) + + require.Equal(t, expected, actual) +} + +func TestYamlSpecFactory_GetRawSpec(t *testing.T) { + t.Parallel() + + actual, err := job.YAMLSpecFactory{}.RawSpec(testutils.Context(t), anyYamlSpec) + require.NoError(t, err) + require.Equal(t, []byte(anyYamlSpec), actual) +} diff --git a/core/services/keeper/integration_test.go b/core/services/keeper/integration_test.go index 455f5bb506..494e0a1615 100644 --- a/core/services/keeper/integration_test.go +++ b/core/services/keeper/integration_test.go @@ -17,6 +17,7 @@ import ( "github.com/stretchr/testify/require" commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/forwarders" diff --git a/core/services/keystore/chaintype/chaintype.go b/core/services/keystore/chaintype/chaintype.go index 8a12322ebf..419dfa2d07 100644 --- a/core/services/keystore/chaintype/chaintype.go +++ b/core/services/keystore/chaintype/chaintype.go @@ -36,6 +36,40 @@ func (c ChainTypes) String() (out string) { return sb.String() } +func NewChainType(typ uint8) (ChainType, error) { + switch typ { + case 1: + return EVM, nil + case 2: + return Solana, nil + case 3: + return Cosmos, nil + case 4: + return StarkNet, nil + case 5: + return Aptos, nil + default: + return "", fmt.Errorf("unexpected chaintype.ChainType: %#v", typ) + } +} + +func (c ChainType) Type() (uint8, error) { + switch c { + case EVM: + return 1, nil + case Solana: + return 2, nil + case Cosmos: + return 3, nil + case StarkNet: + return 4, nil + case Aptos: + return 5, nil + default: + return 0, fmt.Errorf("unexpected chaintype.ChainType: %#v", c) + } +} + // SupportedChainTypes contain all chains that are supported var SupportedChainTypes = ChainTypes{EVM, Cosmos, Solana, StarkNet, Aptos} diff --git a/core/services/keystore/keys/aptoskey/account.go b/core/services/keystore/keys/aptoskey/account.go deleted file mode 100644 index 89f62d3301..0000000000 --- a/core/services/keystore/keys/aptoskey/account.go +++ /dev/null @@ -1,72 +0,0 @@ -package aptoskey - -import ( - "encoding/hex" - "errors" - "fmt" - "strings" -) - -// AccountAddress is a 32 byte address on the Aptos blockchain -// It can represent an Object, an Account, and much more. -// -// AccountAddress is copied from the aptos sdk because: -// 1. There are still breaking changes in sdk and we don't want the dependency. -// 2. AccountAddress is just a wrapper and can be easily extracted out. -// -// https://github.com/aptos-labs/aptos-go-sdk/blob/main/internal/types/account.go -type AccountAddress [32]byte - -// IsSpecial Returns whether the address is a "special" address. Addresses are considered -// special if the first 63 characters of the hex string are zero. In other words, -// an address is special if the first 31 bytes are zero and the last byte is -// smaller than `0b10000` (16). In other words, special is defined as an address -// that matches the following regex: `^0x0{63}[0-9a-f]$`. In short form this means -// the addresses in the range from `0x0` to `0xf` (inclusive) are special. -// For more details see the v1 address standard defined as part of AIP-40: -// https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-40.md -func (aa *AccountAddress) IsSpecial() bool { - for _, b := range aa[:31] { - if b != 0 { - return false - } - } - return aa[31] < 0x10 -} - -// String Returns the canonical string representation of the AccountAddress -func (aa *AccountAddress) String() string { - if aa.IsSpecial() { - return fmt.Sprintf("0x%x", aa[31]) - } - return BytesToHex(aa[:]) -} - -// ParseStringRelaxed parses a string into an AccountAddress -func (aa *AccountAddress) ParseStringRelaxed(x string) error { - x = strings.TrimPrefix(x, "0x") - if len(x) < 1 { - return ErrAddressTooShort - } - if len(x) > 64 { - return ErrAddressTooLong - } - if len(x)%2 != 0 { - x = "0" + x - } - bytes, err := hex.DecodeString(x) - if err != nil { - return err - } - // zero-prefix/right-align what bytes we got - copy((*aa)[32-len(bytes):], bytes) - - return nil -} - -var ErrAddressTooShort = errors.New("AccountAddress too short") -var ErrAddressTooLong = errors.New("AccountAddress too long") - -func BytesToHex(bytes []byte) string { - return "0x" + hex.EncodeToString(bytes) -} diff --git a/core/services/keystore/keys/aptoskey/account_test.go b/core/services/keystore/keys/aptoskey/account_test.go deleted file mode 100644 index b9ed4ea04a..0000000000 --- a/core/services/keystore/keys/aptoskey/account_test.go +++ /dev/null @@ -1,141 +0,0 @@ -package aptoskey - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -// Tests extracted from -// https://github.com/aptos-labs/aptos-go-sdk/blob/5ee5ac308e5881b508c0a5124f5e0b8df27a4d40/internal/types/account_test.go - -func TestStringOutput(t *testing.T) { - inputs := [][]byte{ - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F}, - {0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, - {0x02, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, - {0x00, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, - {0x00, 0x04, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, - {0x00, 0x00, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, - } - expected := []string{ - "0x0", - "0x1", - "0xf", - "0x1234123412341234123412341234123412341234123412340123456789abcdef", - "0x0234123412341234123412341234123412341234123412340123456789abcdef", - "0x0034123412341234123412341234123412341234123412340123456789abcdef", - "0x0004123412341234123412341234123412341234123412340123456789abcdef", - "0x0000123412341234123412341234123412341234123412340123456789abcdef", - } - - for i := 0; i < len(inputs); i++ { - addr := AccountAddress(inputs[i]) - assert.Equal(t, expected[i], addr.String()) - } -} - -func TestAccountAddress_ParseStringRelaxed_Error(t *testing.T) { - var owner AccountAddress - err := owner.ParseStringRelaxed("0x") - assert.Error(t, err) - err = owner.ParseStringRelaxed("0xF1234567812345678123456781234567812345678123456781234567812345678") - assert.Error(t, err) - err = owner.ParseStringRelaxed("NotHex") - assert.Error(t, err) -} - -func TestAccountAddress_String(t *testing.T) { - testCases := []struct { - name string - address AccountAddress - expected string - }{ - { - name: "Special address", - address: AccountAddress{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, - expected: "0x1", - }, - { - name: "Non-special address", - address: AccountAddress{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, - expected: "0x123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.expected, tc.address.String()) - }) - } -} - -func TestAccountAddress_IsSpecial(t *testing.T) { - testCases := []struct { - name string - address AccountAddress - expected bool - }{ - { - name: "Special address", - address: AccountAddress{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, - expected: true, - }, - { - name: "Non-special address", - address: AccountAddress{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, - expected: false, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.expected, tc.address.IsSpecial()) - }) - } -} - -func TestBytesToHex(t *testing.T) { - testCases := []struct { - name string - bytes []byte - expected string - }{ - { - name: "Empty bytes", - bytes: []byte{}, - expected: "0x", - }, - { - name: "Non-empty bytes", - bytes: []byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, - expected: "0x123456789abcdef0", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.expected, BytesToHex(tc.bytes)) - }) - } -} - -func TestAccountSpecialString(t *testing.T) { - var aa AccountAddress - aa[31] = 3 - aas := aa.String() - if aas != "0x3" { - t.Errorf("wanted 0x3 got %s", aas) - } - - var aa2 AccountAddress - err := aa2.ParseStringRelaxed("0x3") - if err != nil { - t.Errorf("unexpected err %s", err) - } - if aa2 != aa { - t.Errorf("aa2 != aa") - } -} diff --git a/core/services/keystore/keys/aptoskey/key.go b/core/services/keystore/keys/aptoskey/key.go index ec9b27a359..fa8925454e 100644 --- a/core/services/keystore/keys/aptoskey/key.go +++ b/core/services/keystore/keys/aptoskey/key.go @@ -7,7 +7,7 @@ import ( "fmt" "io" - "github.com/mr-tron/base58" + "golang.org/x/crypto/sha3" ) // Raw represents the Aptos private key @@ -37,6 +37,7 @@ var _ fmt.GoStringer = &Key{} // Key represents Aptos key type Key struct { + // TODO: store initial Account() derivation to support key rotation privkey ed25519.PrivateKey pubKey ed25519.PublicKey } @@ -72,14 +73,20 @@ func (key Key) ID() string { return key.PublicKeyStr() } +// https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-40.md#long +func (key Key) Account() string { + authKey := sha3.Sum256(append([]byte(key.pubKey), 0x00)) + return fmt.Sprintf("%064x", authKey) +} + // GetPublic get Key's public key func (key Key) GetPublic() ed25519.PublicKey { return key.pubKey } -// PublicKeyStr return base58 encoded public key +// PublicKeyStr returns hex encoded public key func (key Key) PublicKeyStr() string { - return base58.Encode(key.pubKey) + return fmt.Sprintf("%064x", key.pubKey) } // Raw returns the seed from private key diff --git a/core/services/keystore/keys/aptoskey/key_test.go b/core/services/keystore/keys/aptoskey/key_test.go new file mode 100644 index 0000000000..277a30eb9f --- /dev/null +++ b/core/services/keystore/keys/aptoskey/key_test.go @@ -0,0 +1,17 @@ +package aptoskey + +import ( + "encoding/hex" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestAptosKey(t *testing.T) { + bytes, err := hex.DecodeString("f0d07ab448018b2754475f9a3b580218b0675a1456aad96ad607c7bbd7d9237b") + require.NoError(t, err) + k := Raw(bytes).Key() + assert.Equal(t, k.PublicKeyStr(), "2acd605efc181e2af8a0b8c0686a5e12578efa1253d15a235fa5e5ad970c4b29") + assert.Equal(t, k.Account(), "69d8b07f5945185873c622ea66873b0e1fb921de7b94d904d3ef9be80770682e") +} diff --git a/core/services/keystore/keys/cosmoskey/key.go b/core/services/keystore/keys/cosmoskey/key.go index 3e516a21ab..b5ea255f23 100644 --- a/core/services/keystore/keys/cosmoskey/key.go +++ b/core/services/keystore/keys/cosmoskey/key.go @@ -59,9 +59,6 @@ func newFrom(reader io.Reader) Key { panic(err) } privKey := secpSigningAlgo.Generate()(rawKey.D.Bytes()) - if err != nil { - panic(err) - } return Key{ d: rawKey.D, diff --git a/core/services/keystore/keys/ocr2key/aptos_keyring.go b/core/services/keystore/keys/ocr2key/aptos_keyring.go index 51e6c3a9c8..cdc5afa792 100644 --- a/core/services/keystore/keys/ocr2key/aptos_keyring.go +++ b/core/services/keystore/keys/ocr2key/aptos_keyring.go @@ -2,12 +2,11 @@ package ocr2key import ( "crypto/ed25519" - "encoding/binary" "io" "github.com/hdevalence/ed25519consensus" "github.com/pkg/errors" - "golang.org/x/crypto/blake2s" + "golang.org/x/crypto/blake2b" "github.com/smartcontractkit/chainlink/v2/core/utils" @@ -37,17 +36,15 @@ func (akr *aptosKeyring) PublicKey() ocrtypes.OnchainPublicKey { func (akr *aptosKeyring) reportToSigData(reportCtx ocrtypes.ReportContext, report ocrtypes.Report) ([]byte, error) { rawReportContext := evmutil.RawReportContext(reportCtx) - h, err := blake2s.New256(nil) + h, err := blake2b.New256(nil) if err != nil { return nil, err } - reportLen := make([]byte, 4) - binary.BigEndian.PutUint32(reportLen[0:], uint32(len(report))) - h.Write(reportLen[:]) - h.Write(report) + // blake2b_256(report_context | report) h.Write(rawReportContext[0][:]) h.Write(rawReportContext[1][:]) h.Write(rawReportContext[2][:]) + h.Write(report) return h.Sum(nil), nil } diff --git a/core/services/llo/bm/dummy_transmitter.go b/core/services/llo/bm/dummy_transmitter.go index c349c9e0ee..06fd0915b3 100644 --- a/core/services/llo/bm/dummy_transmitter.go +++ b/core/services/llo/bm/dummy_transmitter.go @@ -2,6 +2,7 @@ package bm import ( "context" + "fmt" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" @@ -10,12 +11,13 @@ import ( "github.com/smartcontractkit/libocr/offchainreporting2plus/types" ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink/v2/core/services/llo/evm" + "github.com/smartcontractkit/chainlink-data-streams/llo" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" - - "github.com/smartcontractkit/chainlink/v2/core/logger" ) // A dummy transmitter useful for benchmarking and testing @@ -39,7 +41,7 @@ type transmitter struct { func NewTransmitter(lggr logger.Logger, fromAccount string) Transmitter { return &transmitter{ - lggr.Named("DummyTransmitter"), + logger.Named(lggr, "DummyTransmitter"), fromAccount, } } @@ -60,21 +62,44 @@ func (t *transmitter) Transmit( sigs []types.AttributedOnchainSignature, ) error { lggr := t.lggr - switch report.Info.ReportFormat { - case llotypes.ReportFormatJSON: - r, err := (llo.JSONReportCodec{}).Decode(report.Report) - if err != nil { - lggr.Debugw("Failed to decode JSON report", "err", err) + { + switch report.Info.ReportFormat { + case llotypes.ReportFormatJSON: + r, err := (llo.JSONReportCodec{}).Decode(report.Report) + if err != nil { + lggr.Debugw(fmt.Sprintf("Failed to decode report with type %s", report.Info.ReportFormat), "err", err) + } else if r.SeqNr > 0 { + lggr = logger.With(lggr, + "report.Report.ConfigDigest", r.ConfigDigest, + "report.Report.SeqNr", r.SeqNr, + "report.Report.ChannelID", r.ChannelID, + "report.Report.ValidAfterSeconds", r.ValidAfterSeconds, + "report.Report.ObservationTimestampSeconds", r.ObservationTimestampSeconds, + "report.Report.Values", r.Values, + "report.Report.Specimen", r.Specimen, + ) + } + case llotypes.ReportFormatEVMPremiumLegacy: + r, err := (evm.ReportCodecPremiumLegacy{}).Decode(report.Report) + if err != nil { + lggr.Debugw(fmt.Sprintf("Failed to decode report with type %s", report.Info.ReportFormat), "err", err) + } else if r.ObservationsTimestamp > 0 { + lggr = logger.With(lggr, + "report.Report.FeedId", r.FeedId, + "report.Report.ObservationsTimestamp", r.ObservationsTimestamp, + "report.Report.BenchmarkPrice", r.BenchmarkPrice, + "report.Report.Bid", r.Bid, + "report.Report.Ask", r.Ask, + "report.Report.ValidFromTimestamp", r.ValidFromTimestamp, + "report.Report.ExpiresAt", r.ExpiresAt, + "report.Report.LinkFee", r.LinkFee, + "report.Report.NativeFee", r.NativeFee, + ) + } + default: + err := fmt.Errorf("unhandled report format: %s", report.Info.ReportFormat) + lggr.Debugw(fmt.Sprintf("Failed to decode report with type %s", report.Info.ReportFormat), "err", err) } - lggr = lggr.With( - "report.Report.ConfigDigest", r.ConfigDigest, - "report.Report.SeqNr", r.SeqNr, - "report.Report.ChannelID", r.ChannelID, - "report.Report.ValidAfterSeconds", r.ValidAfterSeconds, - "report.Report.Values", r.Values, - "report.Report.Specimen", r.Specimen, - ) - default: } transmitSuccessCount.Inc() lggr.Infow("Transmit (dummy)", "digest", digest, "seqNr", seqNr, "report.Report", report.Report, "report.Info", report.Info, "sigs", sigs) diff --git a/core/services/llo/channel_definition_cache_factory.go b/core/services/llo/channel_definition_cache_factory.go index 51906e0ff1..0cc2543cdf 100644 --- a/core/services/llo/channel_definition_cache_factory.go +++ b/core/services/llo/channel_definition_cache_factory.go @@ -2,14 +2,15 @@ package llo import ( "fmt" + "net/http" "sync" "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/chainlink-common/pkg/logger" llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" - "github.com/smartcontractkit/chainlink/v2/core/logger" lloconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/llo/config" ) @@ -19,25 +20,29 @@ type ChannelDefinitionCacheFactory interface { var _ ChannelDefinitionCacheFactory = &channelDefinitionCacheFactory{} -func NewChannelDefinitionCacheFactory(lggr logger.Logger, orm ChannelDefinitionCacheORM, lp logpoller.LogPoller) ChannelDefinitionCacheFactory { +func NewChannelDefinitionCacheFactory(lggr logger.Logger, orm ChannelDefinitionCacheORM, lp logpoller.LogPoller, client *http.Client) ChannelDefinitionCacheFactory { return &channelDefinitionCacheFactory{ lggr, orm, lp, - make(map[common.Address]struct{}), + client, + make(map[common.Address]map[uint32]struct{}), sync.Mutex{}, } } type channelDefinitionCacheFactory struct { - lggr logger.Logger - orm ChannelDefinitionCacheORM - lp logpoller.LogPoller + lggr logger.Logger + orm ChannelDefinitionCacheORM + lp logpoller.LogPoller + client *http.Client - caches map[common.Address]struct{} + caches map[common.Address]map[uint32]struct{} mu sync.Mutex } +// TODO: Test this +// MERC-3653 func (f *channelDefinitionCacheFactory) NewCache(cfg lloconfig.PluginConfig) (llotypes.ChannelDefinitionCache, error) { if cfg.ChannelDefinitions != "" { return NewStaticChannelDefinitionCache(f.lggr, cfg.ChannelDefinitions) @@ -45,14 +50,18 @@ func (f *channelDefinitionCacheFactory) NewCache(cfg lloconfig.PluginConfig) (ll addr := cfg.ChannelDefinitionsContractAddress fromBlock := cfg.ChannelDefinitionsContractFromBlock + donID := cfg.DonID f.mu.Lock() defer f.mu.Unlock() - if _, exists := f.caches[addr]; exists { + if _, exists := f.caches[addr][donID]; exists { // This shouldn't really happen and isn't supported - return nil, fmt.Errorf("cache already exists for contract address %s", addr.Hex()) + return nil, fmt.Errorf("cache already exists for contract address %s and don ID %d", addr.Hex(), donID) } - f.caches[addr] = struct{}{} - return NewChannelDefinitionCache(f.lggr, f.orm, f.lp, addr, fromBlock), nil + if _, exists := f.caches[addr]; !exists { + f.caches[addr] = make(map[uint32]struct{}) + } + f.caches[addr][donID] = struct{}{} + return NewChannelDefinitionCache(f.lggr, f.orm, f.client, f.lp, addr, donID, fromBlock), nil } diff --git a/core/services/llo/cleanup.go b/core/services/llo/cleanup.go new file mode 100644 index 0000000000..892e925f37 --- /dev/null +++ b/core/services/llo/cleanup.go @@ -0,0 +1,28 @@ +package llo + +import ( + "context" + "fmt" + + "github.com/ethereum/go-ethereum/common" + + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" + "github.com/smartcontractkit/chainlink/v2/core/services/llo/mercurytransmitter" +) + +func Cleanup(ctx context.Context, lp LogPoller, addr common.Address, donID uint32, ds sqlutil.DataSource, chainSelector uint64) error { + if (addr != common.Address{} && donID > 0) { + if err := lp.UnregisterFilter(ctx, filterName(addr, donID)); err != nil { + return fmt.Errorf("failed to unregister filter: %w", err) + } + orm := NewORM(ds, chainSelector) + if err := orm.CleanupChannelDefinitions(ctx, addr, donID); err != nil { + return fmt.Errorf("failed to cleanup channel definitions: %w", err) + } + } + torm := mercurytransmitter.NewORM(ds, donID) + if err := torm.Cleanup(ctx); err != nil { + return fmt.Errorf("failed to cleanup transmitter: %w", err) + } + return nil +} diff --git a/core/services/llo/cleanup_test.go b/core/services/llo/cleanup_test.go new file mode 100644 index 0000000000..7c7f13e02f --- /dev/null +++ b/core/services/llo/cleanup_test.go @@ -0,0 +1,102 @@ +package llo + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" + "github.com/smartcontractkit/chainlink/v2/core/services/llo/mercurytransmitter" +) + +func makeSampleTransmission(seqNr uint64, sURL string) *mercurytransmitter.Transmission { + return &mercurytransmitter.Transmission{ + ServerURL: sURL, + ConfigDigest: types.ConfigDigest{0x0, 0x9, 0x57, 0xdd, 0x2f, 0x63, 0x56, 0x69, 0x34, 0xfd, 0xc2, 0xe1, 0xcd, 0xc1, 0xe, 0x3e, 0x25, 0xb9, 0x26, 0x5a, 0x16, 0x23, 0x91, 0xa6, 0x53, 0x16, 0x66, 0x59, 0x51, 0x0, 0x28, 0x7c}, + SeqNr: seqNr, + Report: ocr3types.ReportWithInfo[llotypes.ReportInfo]{ + Report: ocrtypes.Report{0x0, 0x3, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x66, 0xde, 0xf5, 0xba, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x66, 0xde, 0xf5, 0xba, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1e, 0x8e, 0x95, 0xcf, 0xb5, 0xd8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1a, 0xd0, 0x1c, 0x67, 0xa9, 0xcf, 0xb3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x66, 0xdf, 0x3, 0xca, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x1c, 0x93, 0x6d, 0xa4, 0xf2, 0x17, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x14, 0x8d, 0x9a, 0xc1, 0xd9, 0x6f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x40, 0x5c, 0xcf, 0xa1, 0xbc, 0x63, 0xc0, 0x0}, + Info: llotypes.ReportInfo{ + LifeCycleStage: llotypes.LifeCycleStage("production"), + ReportFormat: llotypes.ReportFormatEVMPremiumLegacy, + }, + }, + Sigs: []types.AttributedOnchainSignature{types.AttributedOnchainSignature{Signature: []uint8{0x9d, 0xab, 0x8f, 0xa7, 0xca, 0x7, 0x62, 0x57, 0xf7, 0x11, 0x2c, 0xb7, 0xf3, 0x49, 0x37, 0x12, 0xbd, 0xe, 0x14, 0x27, 0xfc, 0x32, 0x5c, 0xec, 0xa6, 0xb9, 0x7f, 0xf9, 0xd7, 0x7b, 0xa6, 0x36, 0x30, 0x9d, 0x84, 0x29, 0xbf, 0xd4, 0xeb, 0xc5, 0xc9, 0x29, 0xef, 0xdd, 0xd3, 0x2f, 0xa6, 0x25, 0x63, 0xda, 0xd9, 0x2c, 0xa1, 0x4a, 0xba, 0x75, 0xb2, 0x85, 0x25, 0x8f, 0x2b, 0x84, 0xcd, 0x99, 0x1}, Signer: 0x1}, types.AttributedOnchainSignature{Signature: []uint8{0x9a, 0x47, 0x4a, 0x3, 0x1a, 0x95, 0xcf, 0x46, 0x10, 0xaf, 0xcc, 0x90, 0x49, 0xb2, 0xce, 0xbf, 0x63, 0xaa, 0xc7, 0x25, 0x4d, 0x2a, 0x8, 0x36, 0xda, 0xd5, 0x9f, 0x9d, 0x63, 0x69, 0x22, 0xb3, 0x36, 0xd9, 0x6e, 0xf, 0xae, 0x7b, 0xd1, 0x61, 0x59, 0xf, 0x36, 0x4a, 0x22, 0xec, 0xde, 0x45, 0x32, 0xe0, 0x5b, 0x5c, 0xe3, 0x14, 0x29, 0x4, 0x60, 0x7b, 0xce, 0xa3, 0x89, 0x6b, 0xbb, 0xe0, 0x0}, Signer: 0x3}}, + } +} + +func Test_Cleanup(t *testing.T) { + ctx := testutils.Context(t) + + lp := &mockLogPoller{} + ds := pgtest.NewSqlxDB(t) + + addr1 := common.Address{1, 2, 3} + addr2 := common.Address{4, 5, 6} + donID1 := uint32(1) + donID2 := uint32(2) + chainSelector := uint64(3) + + // add some channel definitions + cdcorm := NewORM(ds, chainSelector) + { + err := cdcorm.StoreChannelDefinitions(ctx, addr1, donID1, 1, llotypes.ChannelDefinitions{}, 1) + require.NoError(t, err) + err = cdcorm.StoreChannelDefinitions(ctx, addr2, donID2, 1, llotypes.ChannelDefinitions{}, 1) + require.NoError(t, err) + } + + // add some transmissions + + torm1 := mercurytransmitter.NewORM(ds, donID1) + srvURL1 := "http://example.com/foo" + srvURL2 := "http://example.test/bar" + { + err := torm1.Insert(ctx, []*mercurytransmitter.Transmission{makeSampleTransmission(1, srvURL1), makeSampleTransmission(1, srvURL2)}) + require.NoError(t, err) + } + + torm2 := mercurytransmitter.NewORM(ds, donID2) + { + err := torm2.Insert(ctx, []*mercurytransmitter.Transmission{makeSampleTransmission(2, srvURL1), makeSampleTransmission(2, srvURL2)}) + require.NoError(t, err) + } + + err := Cleanup(ctx, lp, addr1, donID1, ds, chainSelector) + require.NoError(t, err) + + t.Run("unregisters filter", func(t *testing.T) { + assert.Equal(t, []string{"OCR3 LLO ChannelDefinitionCachePoller - 0x0102030000000000000000000000000000000000:1"}, lp.unregisteredFilterNames) + }) + t.Run("removes channel definitions", func(t *testing.T) { + pd, err := cdcorm.LoadChannelDefinitions(ctx, addr1, donID1) + require.NoError(t, err) + assert.Nil(t, pd) + pd, err = cdcorm.LoadChannelDefinitions(ctx, addr2, donID2) + require.NoError(t, err) + assert.NotNil(t, pd) + }) + t.Run("removes transmissions", func(t *testing.T) { + trs, err := torm1.Get(ctx, srvURL1) + require.NoError(t, err) + assert.Len(t, trs, 0) + trs, err = torm1.Get(ctx, srvURL2) + require.NoError(t, err) + assert.Len(t, trs, 0) + + trs, err = torm2.Get(ctx, srvURL1) + require.NoError(t, err) + assert.Len(t, trs, 1) + trs, err = torm2.Get(ctx, srvURL2) + require.NoError(t, err) + assert.Len(t, trs, 1) + }) +} diff --git a/core/services/llo/codecs.go b/core/services/llo/codecs.go new file mode 100644 index 0000000000..a67b30d2f2 --- /dev/null +++ b/core/services/llo/codecs.go @@ -0,0 +1,18 @@ +package llo + +import ( + llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" + "github.com/smartcontractkit/chainlink-data-streams/llo" + + "github.com/smartcontractkit/chainlink/v2/core/services/llo/evm" +) + +// NOTE: All supported codecs must be specified here +func NewCodecs() map[llotypes.ReportFormat]llo.ReportCodec { + codecs := make(map[llotypes.ReportFormat]llo.ReportCodec) + + codecs[llotypes.ReportFormatJSON] = llo.JSONReportCodec{} + codecs[llotypes.ReportFormatEVMPremiumLegacy] = evm.ReportCodecPremiumLegacy{} + + return codecs +} diff --git a/core/services/llo/codecs_test.go b/core/services/llo/codecs_test.go new file mode 100644 index 0000000000..d26b72dd35 --- /dev/null +++ b/core/services/llo/codecs_test.go @@ -0,0 +1,16 @@ +package llo + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" +) + +func Test_NewCodecs(t *testing.T) { + c := NewCodecs() + + assert.Contains(t, c, llotypes.ReportFormatJSON, "expected JSON to be supported") + assert.Contains(t, c, llotypes.ReportFormatEVMPremiumLegacy, "expected EVMPremiumLegacy to be supported") +} diff --git a/core/services/llo/data_source.go b/core/services/llo/data_source.go index 20b14edad5..bad1bf0896 100644 --- a/core/services/llo/data_source.go +++ b/core/services/llo/data_source.go @@ -3,12 +3,12 @@ package llo import ( "context" "fmt" - "math/big" "sort" "sync" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/shopspring/decimal" "golang.org/x/exp/maps" llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" @@ -17,6 +17,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" "github.com/smartcontractkit/chainlink/v2/core/services/streams" + "github.com/smartcontractkit/chainlink/v2/core/utils" ) var ( @@ -70,10 +71,16 @@ var _ llo.DataSource = &dataSource{} type dataSource struct { lggr logger.Logger registry Registry + + t Telemeter +} + +func NewDataSource(lggr logger.Logger, registry Registry, t Telemeter) llo.DataSource { + return newDataSource(lggr, registry, t) } -func newDataSource(lggr logger.Logger, registry Registry) llo.DataSource { - return &dataSource{lggr.Named("DataSource"), registry} +func newDataSource(lggr logger.Logger, registry Registry, t Telemeter) *dataSource { + return &dataSource{lggr.Named("DataSource"), registry, t} } // Observe looks up all streams in the registry and populates a map of stream ID => value @@ -81,7 +88,7 @@ func (d *dataSource) Observe(ctx context.Context, streamValues llo.StreamValues, var wg sync.WaitGroup wg.Add(len(streamValues)) var svmu sync.Mutex - var errors []ErrObservationFailed + var errs []ErrObservationFailed var errmu sync.Mutex if opts.VerboseLogging() { @@ -90,19 +97,19 @@ func (d *dataSource) Observe(ctx context.Context, streamValues llo.StreamValues, streamIDs = append(streamIDs, streamID) } sort.Slice(streamIDs, func(i, j int) bool { return streamIDs[i] < streamIDs[j] }) - d.lggr.Debugw("Observing streams", "streamIDs", streamIDs, "seqNr", opts.SeqNr()) + d.lggr.Debugw("Observing streams", "streamIDs", streamIDs, "configDigest", opts.ConfigDigest(), "seqNr", opts.OutCtx().SeqNr) } for _, streamID := range maps.Keys(streamValues) { go func(streamID llotypes.StreamID) { defer wg.Done() - var val *big.Int + var val llo.StreamValue stream, exists := d.registry.Get(streamID) if !exists { errmu.Lock() - errors = append(errors, ErrObservationFailed{streamID: streamID, reason: fmt.Sprintf("missing stream: %d", streamID)}) + errs = append(errs, ErrObservationFailed{streamID: streamID, reason: fmt.Sprintf("missing stream: %d", streamID)}) errmu.Unlock() promMissingStreamCount.WithLabelValues(fmt.Sprintf("%d", streamID)).Inc() return @@ -110,25 +117,30 @@ func (d *dataSource) Observe(ctx context.Context, streamValues llo.StreamValues, run, trrs, err := stream.Run(ctx) if err != nil { errmu.Lock() - errors = append(errors, ErrObservationFailed{inner: err, run: run, streamID: streamID, reason: "pipeline run failed"}) + errs = append(errs, ErrObservationFailed{inner: err, run: run, streamID: streamID, reason: "pipeline run failed"}) errmu.Unlock() promObservationErrorCount.WithLabelValues(fmt.Sprintf("%d", streamID)).Inc() + // TODO: Consolidate/reduce telemetry. We should send all observation results in a single packet + // https://smartcontract-it.atlassian.net/browse/MERC-6290 + d.t.EnqueueV3PremiumLegacy(run, trrs, streamID, opts, nil, err) return } - // TODO: support types other than *big.Int - // https://smartcontract-it.atlassian.net/browse/MERC-3525 - val, err = streams.ExtractBigInt(trrs) + // TODO: Consolidate/reduce telemetry. We should send all observation results in a single packet + // https://smartcontract-it.atlassian.net/browse/MERC-6290 + val, err = ExtractStreamValue(trrs) if err != nil { errmu.Lock() - errors = append(errors, ErrObservationFailed{inner: err, run: run, streamID: streamID, reason: "failed to extract big.Int"}) + errs = append(errs, ErrObservationFailed{inner: err, run: run, streamID: streamID, reason: "failed to extract big.Int"}) errmu.Unlock() return } + d.t.EnqueueV3PremiumLegacy(run, trrs, streamID, opts, val, nil) + if val != nil { svmu.Lock() defer svmu.Unlock() - streamValues[streamID] = nil + streamValues[streamID] = val } }(streamID) } @@ -137,25 +149,79 @@ func (d *dataSource) Observe(ctx context.Context, streamValues llo.StreamValues, // Failed observations are always logged at warn level var failedStreamIDs []streams.StreamID - if len(errors) > 0 { - sort.Slice(errors, func(i, j int) bool { return errors[i].streamID < errors[j].streamID }) - failedStreamIDs = make([]streams.StreamID, len(errors)) - for i, e := range errors { + if len(errs) > 0 { + sort.Slice(errs, func(i, j int) bool { return errs[i].streamID < errs[j].streamID }) + failedStreamIDs = make([]streams.StreamID, len(errs)) + errStrs := make([]string, len(errs)) + for i, e := range errs { + errStrs[i] = e.String() failedStreamIDs[i] = e.streamID } - d.lggr.Warnw("Observation failed for streams", "failedStreamIDs", failedStreamIDs, "errors", errors, "seqNr", opts.SeqNr()) + d.lggr.Warnw("Observation failed for streams", "failedStreamIDs", failedStreamIDs, "errs", errStrs, "configDigest", opts.ConfigDigest(), "seqNr", opts.OutCtx().SeqNr) } if opts.VerboseLogging() { successes := make([]streams.StreamID, 0, len(streamValues)) - for strmID, res := range streamValues { - if res != nil { - successes = append(successes, strmID) - } + for strmID := range streamValues { + successes = append(successes, strmID) } sort.Slice(successes, func(i, j int) bool { return successes[i] < successes[j] }) - d.lggr.Debugw("Observation complete", "successfulStreamIDs", successes, "failedStreamIDs", failedStreamIDs, "values", streamValues, "seqNr", opts.SeqNr()) + d.lggr.Debugw("Observation complete", "successfulStreamIDs", successes, "failedStreamIDs", failedStreamIDs, "configDigest", opts.ConfigDigest(), "values", streamValues, "seqNr", opts.OutCtx().SeqNr) } return nil } + +// ExtractStreamValue extracts a StreamValue from a TaskRunResults +func ExtractStreamValue(trrs pipeline.TaskRunResults) (llo.StreamValue, error) { + // pipeline.TaskRunResults comes ordered asc by index, this is guaranteed + // by the pipeline executor + finaltrrs := trrs.Terminals() + + // TODO: Special handling for missing native/link streams? + // https://smartcontract-it.atlassian.net/browse/MERC-5949 + + // HACK: Right now we rely on the number of outputs to determine whether + // its a Decimal or a Quote. + // This isn't very robust or future-proof but is sufficient to support v0.3 + // compat. + // There are a number of different possible ways to solve this in future. + // See: https://smartcontract-it.atlassian.net/browse/MERC-5934 + switch len(finaltrrs) { + case 1: + res := finaltrrs[0].Result + if res.Error != nil { + return nil, res.Error + } + val, err := toDecimal(res.Value) + if err != nil { + return nil, fmt.Errorf("failed to parse BenchmarkPrice: %w", err) + } + return llo.ToDecimal(val), nil + case 3: + // Expect ordering of Benchmark, Bid, Ask + results := make([]decimal.Decimal, 3) + for i, trr := range finaltrrs { + res := trr.Result + if res.Error != nil { + return nil, fmt.Errorf("failed to parse stream output into Quote (task index: %d): %w", i, res.Error) + } + val, err := toDecimal(res.Value) + if err != nil { + return nil, fmt.Errorf("failed to parse decimal: %w", err) + } + results[i] = val + } + return &llo.Quote{ + Benchmark: results[0], + Bid: results[1], + Ask: results[2], + }, nil + default: + return nil, fmt.Errorf("invalid number of results, expected: 1 or 3, got: %d", len(finaltrrs)) + } +} + +func toDecimal(val interface{}) (decimal.Decimal, error) { + return utils.ToDecimal(val) +} diff --git a/core/services/llo/data_source_test.go b/core/services/llo/data_source_test.go index 3716bb2a58..932c4c0c73 100644 --- a/core/services/llo/data_source_test.go +++ b/core/services/llo/data_source_test.go @@ -4,9 +4,15 @@ import ( "context" "errors" "math/big" + "sync" "testing" + "github.com/shopspring/decimal" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "github.com/smartcontractkit/chainlink-data-streams/llo" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" @@ -34,8 +40,9 @@ func (m *mockRegistry) Get(streamID streams.StreamID) (strm streams.Stream, exis return } -func makeStreamWithSingleResult[T any](res T, err error) *mockStream { +func makeStreamWithSingleResult[T any](runID int64, res T, err error) *mockStream { return &mockStream{ + run: &pipeline.Run{ID: runID}, trrs: []pipeline.TaskRunResult{pipeline.TaskRunResult{Task: &pipeline.MemoTask{}, Result: pipeline.Result{Value: res}}}, err: err, } @@ -51,45 +58,113 @@ func makeStreamValues() llo.StreamValues { type mockOpts struct{} -func (m mockOpts) VerboseLogging() bool { return true } -func (m mockOpts) SeqNr() uint64 { return 42 } +func (m *mockOpts) VerboseLogging() bool { return true } +func (m *mockOpts) SeqNr() uint64 { return 1042 } +func (m *mockOpts) OutCtx() ocr3types.OutcomeContext { + return ocr3types.OutcomeContext{SeqNr: 1042, PreviousOutcome: ocr3types.Outcome([]byte("foo"))} +} +func (m *mockOpts) ConfigDigest() ocr2types.ConfigDigest { + return ocr2types.ConfigDigest{6, 5, 4} +} + +type mockTelemeter struct { + mu sync.Mutex + v3PremiumLegacyPackets []v3PremiumLegacyPacket +} + +type v3PremiumLegacyPacket struct { + run *pipeline.Run + trrs pipeline.TaskRunResults + streamID uint32 + opts llo.DSOpts + val llo.StreamValue + err error +} + +var _ Telemeter = &mockTelemeter{} + +func (m *mockTelemeter) EnqueueV3PremiumLegacy(run *pipeline.Run, trrs pipeline.TaskRunResults, streamID uint32, opts llo.DSOpts, val llo.StreamValue, err error) { + m.mu.Lock() + defer m.mu.Unlock() + m.v3PremiumLegacyPackets = append(m.v3PremiumLegacyPackets, v3PremiumLegacyPacket{run, trrs, streamID, opts, val, err}) +} func Test_DataSource(t *testing.T) { - t.Skip("waiting on https://github.com/smartcontractkit/chainlink/pull/13780") lggr := logger.TestLogger(t) reg := &mockRegistry{make(map[streams.StreamID]*mockStream)} - ds := newDataSource(lggr, reg) + ds := newDataSource(lggr, reg, NullTelemeter) ctx := testutils.Context(t) + opts := &mockOpts{} t.Run("Observe", func(t *testing.T) { t.Run("doesn't set any values if no streams are defined", func(t *testing.T) { vals := makeStreamValues() - err := ds.Observe(ctx, vals, mockOpts{}) + err := ds.Observe(ctx, vals, opts) assert.NoError(t, err) assert.Equal(t, makeStreamValues(), vals) }) t.Run("observes each stream with success and returns values matching map argument", func(t *testing.T) { - reg.streams[1] = makeStreamWithSingleResult[*big.Int](big.NewInt(2181), nil) - reg.streams[2] = makeStreamWithSingleResult[*big.Int](big.NewInt(40602), nil) - reg.streams[3] = makeStreamWithSingleResult[*big.Int](big.NewInt(15), nil) + reg.streams[1] = makeStreamWithSingleResult[*big.Int](1, big.NewInt(2181), nil) + reg.streams[2] = makeStreamWithSingleResult[*big.Int](2, big.NewInt(40602), nil) + reg.streams[3] = makeStreamWithSingleResult[*big.Int](3, big.NewInt(15), nil) vals := makeStreamValues() - err := ds.Observe(ctx, vals, mockOpts{}) + err := ds.Observe(ctx, vals, opts) assert.NoError(t, err) - assert.Equal(t, llo.StreamValues{}, vals) + assert.Equal(t, llo.StreamValues{ + 2: llo.ToDecimal(decimal.NewFromInt(40602)), + 1: llo.ToDecimal(decimal.NewFromInt(2181)), + 3: llo.ToDecimal(decimal.NewFromInt(15)), + }, vals) }) t.Run("observes each stream and returns success/errors", func(t *testing.T) { - reg.streams[1] = makeStreamWithSingleResult[*big.Int](big.NewInt(2181), errors.New("something exploded")) - reg.streams[2] = makeStreamWithSingleResult[*big.Int](big.NewInt(40602), nil) - reg.streams[3] = makeStreamWithSingleResult[*big.Int](nil, errors.New("something exploded 2")) + reg.streams[1] = makeStreamWithSingleResult[*big.Int](1, big.NewInt(2181), errors.New("something exploded")) + reg.streams[2] = makeStreamWithSingleResult[*big.Int](2, big.NewInt(40602), nil) + reg.streams[3] = makeStreamWithSingleResult[*big.Int](3, nil, errors.New("something exploded 2")) + + vals := makeStreamValues() + err := ds.Observe(ctx, vals, opts) + assert.NoError(t, err) + + assert.Equal(t, llo.StreamValues{ + 2: llo.ToDecimal(decimal.NewFromInt(40602)), + 1: nil, + 3: nil, + }, vals) + }) + + t.Run("records telemetry", func(t *testing.T) { + tm := &mockTelemeter{} + ds.t = tm + + reg.streams[1] = makeStreamWithSingleResult[*big.Int](100, big.NewInt(2181), nil) + reg.streams[2] = makeStreamWithSingleResult[*big.Int](101, big.NewInt(40602), nil) + reg.streams[3] = makeStreamWithSingleResult[*big.Int](102, big.NewInt(15), nil) vals := makeStreamValues() - err := ds.Observe(ctx, vals, mockOpts{}) + err := ds.Observe(ctx, vals, opts) assert.NoError(t, err) - assert.Equal(t, llo.StreamValues{}, vals) + assert.Equal(t, llo.StreamValues{ + 2: llo.ToDecimal(decimal.NewFromInt(40602)), + 1: llo.ToDecimal(decimal.NewFromInt(2181)), + 3: llo.ToDecimal(decimal.NewFromInt(15)), + }, vals) + + require.Len(t, tm.v3PremiumLegacyPackets, 3) + m := make(map[int]v3PremiumLegacyPacket) + for _, pkt := range tm.v3PremiumLegacyPackets { + m[int(pkt.run.ID)] = pkt + } + pkt := m[100] + assert.Equal(t, 100, int(pkt.run.ID)) + assert.Len(t, pkt.trrs, 1) + assert.Equal(t, 1, int(pkt.streamID)) + assert.Equal(t, opts, pkt.opts) + assert.Equal(t, "2181", pkt.val.(*llo.Decimal).String()) + assert.Nil(t, pkt.err) }) }) } diff --git a/core/services/llo/delegate.go b/core/services/llo/delegate.go index ddcab383ef..ab55c6b2a9 100644 --- a/core/services/llo/delegate.go +++ b/core/services/llo/delegate.go @@ -37,16 +37,18 @@ type delegate struct { prrc llo.PredecessorRetirementReportCache src llo.ShouldRetireCache ds llo.DataSource + t services.Service oracle Closer } type DelegateConfig struct { - Logger logger.Logger - DataSource sqlutil.DataSource - Runner streams.Runner - Registry Registry - JobName null.String + Logger logger.Logger + DataSource sqlutil.DataSource + Runner streams.Runner + Registry Registry + JobName null.String + CaptureEATelemetry bool // LLO ChannelDefinitionCache llotypes.ChannelDefinitionCache @@ -67,6 +69,7 @@ type DelegateConfig struct { } func NewDelegate(cfg DelegateConfig) (job.ServiceCtx, error) { + lggr := cfg.Logger.With("jobName", cfg.JobName.ValueOrZero()) if cfg.DataSource == nil { return nil, errors.New("DataSource must not be nil") } @@ -76,18 +79,21 @@ func NewDelegate(cfg DelegateConfig) (job.ServiceCtx, error) { if cfg.Registry == nil { return nil, errors.New("Registry must not be nil") } - codecs := make(map[llotypes.ReportFormat]llo.ReportCodec) - - // NOTE: All codecs must be specified here - codecs[llotypes.ReportFormatJSON] = llo.JSONReportCodec{} + codecs := NewCodecs() // TODO: Do these services need starting? // https://smartcontract-it.atlassian.net/browse/MERC-3386 prrc := llo.NewPredecessorRetirementReportCache() src := llo.NewShouldRetireCache() - ds := newDataSource(cfg.Logger.Named("DataSource"), cfg.Registry) + var t TelemeterService + if cfg.CaptureEATelemetry { + t = NewTelemeterService(lggr, cfg.MonitoringEndpoint) + } else { + t = NullTelemeter + } + ds := newDataSource(lggr.Named("DataSource"), cfg.Registry, t) - return &delegate{services.StateMachine{}, cfg, codecs, prrc, src, ds, nil}, nil + return &delegate{services.StateMachine{}, cfg, codecs, prrc, src, ds, t, nil}, nil } func (d *delegate) Start(ctx context.Context) error { diff --git a/core/services/llo/evm/fees.go b/core/services/llo/evm/fees.go new file mode 100644 index 0000000000..b74d68b08d --- /dev/null +++ b/core/services/llo/evm/fees.go @@ -0,0 +1,31 @@ +package evm + +import ( + "math/big" + + "github.com/shopspring/decimal" +) + +// FeeScalingFactor indicates the multiplier applied to fees. +// e.g. for a 1e18 multiplier, a LINK fee of 7.42 will be represented as 7.42e18 +// This is what will be baked into the report for use on-chain. +var FeeScalingFactor = decimal.NewFromInt(1e18) + +// NOTE: Inexact divisions will have this degree of precision +const Precision int32 = 18 + +// CalculateFee outputs a fee in wei according to the formula: baseUSDFee / tokenPriceInUSD +func CalculateFee(tokenPriceInUSD decimal.Decimal, baseUSDFee decimal.Decimal) *big.Int { + if tokenPriceInUSD.IsZero() || baseUSDFee.IsZero() { + // zero fee if token price or base fee is zero + return big.NewInt(0) + } + + // fee denominated in token + fee := baseUSDFee.DivRound(tokenPriceInUSD, Precision) + + // fee scaled up + fee = fee.Mul(FeeScalingFactor) + + return fee.BigInt() +} diff --git a/core/services/llo/evm/fees_test.go b/core/services/llo/evm/fees_test.go new file mode 100644 index 0000000000..16ee98db7d --- /dev/null +++ b/core/services/llo/evm/fees_test.go @@ -0,0 +1,45 @@ +package evm + +import ( + "math/big" + "testing" + + "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Fees(t *testing.T) { + BaseUSDFee, err := decimal.NewFromString("0.70") + require.NoError(t, err) + t.Run("with token price > 1", func(t *testing.T) { + tokenPriceInUSD := decimal.NewFromInt32(1630) + fee := CalculateFee(tokenPriceInUSD, BaseUSDFee) + expectedFee := big.NewInt(429447852760736) + if fee.Cmp(expectedFee) != 0 { + t.Errorf("Expected fee to be %v, got %v", expectedFee, fee) + } + }) + + t.Run("with token price < 1", func(t *testing.T) { + tokenPriceInUSD := decimal.NewFromFloat32(0.4) + fee := CalculateFee(tokenPriceInUSD, BaseUSDFee) + expectedFee := big.NewInt(1750000000000000000) + if fee.Cmp(expectedFee) != 0 { + t.Errorf("Expected fee to be %v, got %v", expectedFee, fee) + } + }) + + t.Run("with token price == 0", func(t *testing.T) { + tokenPriceInUSD := decimal.NewFromInt32(0) + fee := CalculateFee(tokenPriceInUSD, BaseUSDFee) + assert.Equal(t, big.NewInt(0), fee) + }) + + t.Run("with base fee == 0", func(t *testing.T) { + tokenPriceInUSD := decimal.NewFromInt32(123) + BaseUSDFee = decimal.NewFromInt32(0) + fee := CalculateFee(tokenPriceInUSD, BaseUSDFee) + assert.Equal(t, big.NewInt(0), fee) + }) +} diff --git a/core/services/llo/evm/report_codec_premium_legacy.go b/core/services/llo/evm/report_codec_premium_legacy.go new file mode 100644 index 0000000000..901d688bda --- /dev/null +++ b/core/services/llo/evm/report_codec_premium_legacy.go @@ -0,0 +1,175 @@ +package evm + +import ( + "encoding/json" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/shopspring/decimal" + + "github.com/smartcontractkit/libocr/offchainreporting2/chains/evmutil" + "github.com/smartcontractkit/libocr/offchainreporting2/types" + ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" + v3 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v3" + "github.com/smartcontractkit/chainlink-data-streams/llo" + ubig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury" + reporttypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v3/types" + + "github.com/smartcontractkit/chainlink/v2/core/logger" + reportcodecv3 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v3/reportcodec" +) + +var ( + _ llo.ReportCodec = ReportCodecPremiumLegacy{} +) + +type ReportCodecPremiumLegacy struct{ logger.Logger } + +func NewReportCodecPremiumLegacy(lggr logger.Logger) llo.ReportCodec { + return ReportCodecPremiumLegacy{lggr.Named("ReportCodecPremiumLegacy")} +} + +type ReportFormatEVMPremiumLegacyOpts struct { + // BaseUSDFee is the cost on-chain of verifying a report + BaseUSDFee decimal.Decimal `json:"baseUSDFee"` + // Expiration window is the length of time in seconds the report is valid + // for, from the observation timestamp + ExpirationWindow uint32 `json:"expirationWindow"` + // FeedID is for compatibility with existing on-chain verifiers + FeedID common.Hash `json:"feedID"` + // Multiplier is used to scale the bid, benchmark and ask values in the + // report. If not specified, or zero is used, a multiplier of 1 is assumed. + Multiplier *ubig.Big `json:"multiplier"` +} + +func (r *ReportFormatEVMPremiumLegacyOpts) Decode(opts []byte) error { + if len(opts) == 0 { + // special case if opts are unspecified, just use the zero options rather than erroring + return nil + } + return json.Unmarshal(opts, r) +} + +func (r ReportCodecPremiumLegacy) Encode(report llo.Report, cd llotypes.ChannelDefinition) ([]byte, error) { + if report.Specimen { + return nil, errors.New("ReportCodecPremiumLegacy does not support encoding specimen reports") + } + nativePrice, linkPrice, quote, err := ExtractReportValues(report) + if err != nil { + return nil, fmt.Errorf("ReportCodecPremiumLegacy cannot encode; got unusable report; %w", err) + } + + // NOTE: It seems suboptimal to have to parse the opts on every encode but + // not sure how to avoid it. Should be negligible performance hit as long + // as Opts is small. + opts := ReportFormatEVMPremiumLegacyOpts{} + if err := (&opts).Decode(cd.Opts); err != nil { + return nil, fmt.Errorf("failed to decode opts; got: '%s'; %w", cd.Opts, err) + } + var multiplier decimal.Decimal + if opts.Multiplier == nil { + multiplier = decimal.NewFromInt(1) + } else if opts.Multiplier.IsZero() { + return nil, errors.New("multiplier, if specified in channel opts, must be non-zero") + } else { + multiplier = decimal.NewFromBigInt(opts.Multiplier.ToInt(), 0) + } + + codec := reportcodecv3.NewReportCodec(opts.FeedID, r.Logger) + + rf := v3.ReportFields{ + ValidFromTimestamp: report.ValidAfterSeconds + 1, + Timestamp: report.ObservationTimestampSeconds, + NativeFee: CalculateFee(nativePrice.Decimal(), opts.BaseUSDFee), + LinkFee: CalculateFee(linkPrice.Decimal(), opts.BaseUSDFee), + ExpiresAt: report.ObservationTimestampSeconds + opts.ExpirationWindow, + BenchmarkPrice: quote.Benchmark.Mul(multiplier).BigInt(), + Bid: quote.Bid.Mul(multiplier).BigInt(), + Ask: quote.Ask.Mul(multiplier).BigInt(), + } + return codec.BuildReport(rf) +} + +func (r ReportCodecPremiumLegacy) Decode(b []byte) (*reporttypes.Report, error) { + codec := reportcodecv3.NewReportCodec([32]byte{}, r.Logger) + return codec.Decode(b) +} + +// Pack assembles the report values into a payload for verifying on-chain +func (r ReportCodecPremiumLegacy) Pack(digest types.ConfigDigest, seqNr uint64, report ocr2types.Report, sigs []types.AttributedOnchainSignature) ([]byte, error) { + var rs [][32]byte + var ss [][32]byte + var vs [32]byte + for i, as := range sigs { + rr, s, v, err := evmutil.SplitSignature(as.Signature) + if err != nil { + return nil, fmt.Errorf("eventTransmit(ev): error in SplitSignature: %w", err) + } + rs = append(rs, rr) + ss = append(ss, s) + vs[i] = v + } + reportCtx := LegacyReportContext(digest, seqNr) + rawReportCtx := evmutil.RawReportContext(reportCtx) + + payload, err := mercury.PayloadTypes.Pack(rawReportCtx, []byte(report), rs, ss, vs) + if err != nil { + return nil, fmt.Errorf("abi.Pack failed; %w", err) + } + return payload, nil +} + +// TODO: Test this +// MERC-3524 +func ExtractReportValues(report llo.Report) (nativePrice, linkPrice *llo.Decimal, quote *llo.Quote, err error) { + if len(report.Values) != 3 { + return nil, nil, nil, fmt.Errorf("ReportCodecPremiumLegacy requires exactly 3 values (NativePrice, LinkPrice, Quote{Bid, Mid, Ask}); got report.Values: %#v", report.Values) + } + var is bool + nativePrice, is = report.Values[0].(*llo.Decimal) + if nativePrice == nil { + // Missing price median will cause a zero fee + nativePrice = llo.ToDecimal(decimal.Zero) + } else if !is { + return nil, nil, nil, fmt.Errorf("ReportCodecPremiumLegacy expects first value to be of type *Decimal; got: %T", report.Values[0]) + } + linkPrice, is = report.Values[1].(*llo.Decimal) + if linkPrice == nil { + // Missing price median will cause a zero fee + linkPrice = llo.ToDecimal(decimal.Zero) + } else if !is { + return nil, nil, nil, fmt.Errorf("ReportCodecPremiumLegacy expects second value to be of type *Decimal; got: %T", report.Values[1]) + } + quote, is = report.Values[2].(*llo.Quote) + if !is { + return nil, nil, nil, fmt.Errorf("ReportCodecPremiumLegacy expects third value to be of type *Quote; got: %T", report.Values[2]) + } + return nativePrice, linkPrice, quote, nil +} + +// TODO: Consider embedding the DON ID here? +// MERC-3524 +var LLOExtraHash = common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001") + +func SeqNrToEpochAndRound(seqNr uint64) (epoch uint32, round uint8) { + // Simulate 256 rounds/epoch + epoch = uint32(seqNr / 256) // nolint + round = uint8(seqNr % 256) // nolint + return +} + +func LegacyReportContext(cd ocr2types.ConfigDigest, seqNr uint64) ocr2types.ReportContext { + epoch, round := SeqNrToEpochAndRound(seqNr) + return ocr2types.ReportContext{ + ReportTimestamp: ocr2types.ReportTimestamp{ + ConfigDigest: cd, + Epoch: epoch, + Round: round, + }, + ExtraHash: LLOExtraHash, // ExtraHash is always zero for mercury, we use LLOExtraHash here to differentiate from the legacy plugin + } +} diff --git a/core/services/llo/evm/report_codec_premium_legacy_test.go b/core/services/llo/evm/report_codec_premium_legacy_test.go new file mode 100644 index 0000000000..dab2a8cf3f --- /dev/null +++ b/core/services/llo/evm/report_codec_premium_legacy_test.go @@ -0,0 +1,130 @@ +package evm + +import ( + "fmt" + "math/big" + "testing" + + "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + reporttypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v3/types" + + llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" + + "github.com/smartcontractkit/chainlink-data-streams/llo" +) + +func newValidPremiumLegacyReport() llo.Report { + return llo.Report{ + ConfigDigest: types.ConfigDigest{1, 2, 3}, + SeqNr: 32, + ChannelID: llotypes.ChannelID(31), + ValidAfterSeconds: 28, + ObservationTimestampSeconds: 34, + Values: []llo.StreamValue{llo.ToDecimal(decimal.NewFromInt(35)), llo.ToDecimal(decimal.NewFromInt(36)), &llo.Quote{Bid: decimal.NewFromInt(37), Benchmark: decimal.NewFromInt(38), Ask: decimal.NewFromInt(39)}}, + Specimen: false, + } +} + +func Test_ReportCodecPremiumLegacy(t *testing.T) { + rc := ReportCodecPremiumLegacy{} + + feedID := [32]uint8{0x1, 0x2, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} + cd := llotypes.ChannelDefinition{Opts: llotypes.ChannelOpts(fmt.Sprintf(`{"baseUSDFee":"10.50","expirationWindow":60,"feedId":"0x%x","multiplier":10}`, feedID))} + + t.Run("Encode errors if no values", func(t *testing.T) { + _, err := rc.Encode(llo.Report{}, cd) + require.Error(t, err) + + assert.Contains(t, err.Error(), "ReportCodecPremiumLegacy cannot encode; got unusable report; ReportCodecPremiumLegacy requires exactly 3 values (NativePrice, LinkPrice, Quote{Bid, Mid, Ask}); got report.Values: []llo.StreamValue(nil)") + }) + + t.Run("does not encode specimen reports", func(t *testing.T) { + report := newValidPremiumLegacyReport() + report.Specimen = true + + _, err := rc.Encode(report, cd) + require.Error(t, err) + assert.EqualError(t, err, "ReportCodecPremiumLegacy does not support encoding specimen reports") + }) + + t.Run("Encode constructs a report from observations", func(t *testing.T) { + report := newValidPremiumLegacyReport() + + encoded, err := rc.Encode(report, cd) + require.NoError(t, err) + + assert.Len(t, encoded, 288) + assert.Equal(t, []byte{0x1, 0x2, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1d, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x22, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x29, 0xd0, 0x69, 0x18, 0x9e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0xc, 0x35, 0x49, 0xbb, 0x7d, 0x2a, 0xab, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x7c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x72, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x86}, encoded) + + decoded, err := reporttypes.Decode(encoded) + require.NoError(t, err) + assert.Equal(t, feedID, decoded.FeedId) + assert.Equal(t, uint32(34), decoded.ObservationsTimestamp) + assert.Equal(t, big.NewInt(380), decoded.BenchmarkPrice) + assert.Equal(t, big.NewInt(370), decoded.Bid) + assert.Equal(t, big.NewInt(390), decoded.Ask) + assert.Equal(t, uint32(29), decoded.ValidFromTimestamp) + assert.Equal(t, uint32(94), decoded.ExpiresAt) + assert.Equal(t, big.NewInt(291666666666666667), decoded.LinkFee) + assert.Equal(t, big.NewInt(300000000000000000), decoded.NativeFee) + + t.Run("Decode decodes the report", func(t *testing.T) { + decoded, err := rc.Decode(encoded) + require.NoError(t, err) + + assert.Equal(t, &reporttypes.Report{ + FeedId: [32]uint8{0x1, 0x2, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + ObservationsTimestamp: 0x22, + BenchmarkPrice: big.NewInt(380), + Bid: big.NewInt(370), + Ask: big.NewInt(390), + ValidFromTimestamp: 0x1d, + ExpiresAt: uint32(94), + LinkFee: big.NewInt(291666666666666667), + NativeFee: big.NewInt(300000000000000000), + }, decoded) + }) + }) + + t.Run("uses zero values if fees are missing", func(t *testing.T) { + report := llo.Report{ + Values: []llo.StreamValue{nil, nil, &llo.Quote{Bid: decimal.NewFromInt(37), Benchmark: decimal.NewFromInt(38), Ask: decimal.NewFromInt(39)}}, + } + + encoded, err := rc.Encode(report, cd) + require.NoError(t, err) + + assert.Len(t, encoded, 288) + assert.Equal(t, []byte{0x1, 0x2, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x7c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x72, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x86}, encoded) + + decoded, err := rc.Decode(encoded) + require.NoError(t, err) + + assert.Equal(t, feedID, decoded.FeedId) + assert.Equal(t, uint32(0), decoded.ObservationsTimestamp) + assert.Equal(t, big.NewInt(380).String(), decoded.BenchmarkPrice.String()) + assert.Equal(t, big.NewInt(370).String(), decoded.Bid.String()) + assert.Equal(t, big.NewInt(390).String(), decoded.Ask.String()) + assert.Equal(t, uint32(1), decoded.ValidFromTimestamp) + assert.Equal(t, uint32(60), decoded.ExpiresAt) + assert.Equal(t, big.NewInt(0).String(), decoded.LinkFee.String()) + assert.Equal(t, big.NewInt(0).String(), decoded.NativeFee.String()) + }) + + t.Run("Decode errors on invalid report", func(t *testing.T) { + _, err := rc.Decode([]byte{1, 2, 3}) + assert.EqualError(t, err, "failed to decode report: abi: cannot marshal in to go type: length insufficient 3 require 32") + + longBad := make([]byte, 64) + for i := 0; i < len(longBad); i++ { + longBad[i] = byte(i) + } + _, err = rc.Decode(longBad) + assert.EqualError(t, err, "failed to decode report: abi: improperly encoded uint32 value") + }) +} diff --git a/core/services/llo/keyring.go b/core/services/llo/keyring.go index 042b5e4625..8137a5ac3d 100644 --- a/core/services/llo/keyring.go +++ b/core/services/llo/keyring.go @@ -1,15 +1,19 @@ package llo import ( + "bytes" "fmt" + "sort" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" "github.com/smartcontractkit/libocr/offchainreporting2plus/types" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "golang.org/x/exp/maps" llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/llo/evm" ) type LLOOnchainKeyring ocr3types.OnchainKeyring[llotypes.ReportInfo] @@ -17,6 +21,11 @@ type LLOOnchainKeyring ocr3types.OnchainKeyring[llotypes.ReportInfo] var _ LLOOnchainKeyring = &onchainKeyring{} type Key interface { + // Legacy Sign/Verify methods needed for v0.3 report compatibility + // New keys can leave these stubbed + Sign(reportCtx ocrtypes.ReportContext, report ocrtypes.Report) ([]byte, error) + Verify(publicKey ocrtypes.OnchainPublicKey, reportCtx ocrtypes.ReportContext, report ocrtypes.Report, signature []byte) bool + Sign3(digest ocrtypes.ConfigDigest, seqNr uint64, r ocrtypes.Report) (signature []byte, err error) Verify3(publicKey ocrtypes.OnchainPublicKey, cd ocrtypes.ConfigDigest, seqNr uint64, r ocrtypes.Report, signature []byte) bool PublicKey() ocrtypes.OnchainPublicKey @@ -35,12 +44,29 @@ func NewOnchainKeyring(lggr logger.Logger, keys map[llotypes.ReportFormat]Key) L } func (okr *onchainKeyring) PublicKey() types.OnchainPublicKey { - // All public keys combined - var pk []byte - for _, k := range okr.keys { - pk = append(pk, k.PublicKey()...) + // All unique public keys sorted in ascending order and combined into one + // byte string + onchainPublicKey := []byte{} + + keys := maps.Values(okr.keys) + if len(keys) == 0 { + return onchainPublicKey } - return pk + sort.Slice(keys, func(i, j int) bool { + return bytes.Compare(keys[i].PublicKey(), keys[j].PublicKey()) < 0 + }) + + onchainPublicKey = append(onchainPublicKey, keys[0].PublicKey()...) + if len(keys) == 1 { + return onchainPublicKey + } + for i := 1; i < len(keys); i++ { + if keys[i] != keys[i-1] { + onchainPublicKey = append(onchainPublicKey, keys[i].PublicKey()...) + } + } + + return onchainPublicKey } func (okr *onchainKeyring) MaxSignatureLength() (n int) { @@ -52,17 +78,37 @@ func (okr *onchainKeyring) MaxSignatureLength() (n int) { } func (okr *onchainKeyring) Sign(digest types.ConfigDigest, seqNr uint64, r ocr3types.ReportWithInfo[llotypes.ReportInfo]) (signature []byte, err error) { - rf := r.Info.ReportFormat - if key, exists := okr.keys[rf]; exists { - return key.Sign3(digest, seqNr, r.Report) + switch r.Info.ReportFormat { + case llotypes.ReportFormatEVMPremiumLegacy: + rf := r.Info.ReportFormat + if key, exists := okr.keys[rf]; exists { + // NOTE: Must use legacy Sign method for compatibility with v0.3 report verification + rc := evm.LegacyReportContext(digest, seqNr) + return key.Sign(rc, r.Report) + } + default: + rf := r.Info.ReportFormat + if key, exists := okr.keys[rf]; exists { + return key.Sign3(digest, seqNr, r.Report) + } } return nil, fmt.Errorf("Sign failed; unsupported report format: %q", r.Info.ReportFormat) } func (okr *onchainKeyring) Verify(key types.OnchainPublicKey, digest types.ConfigDigest, seqNr uint64, r ocr3types.ReportWithInfo[llotypes.ReportInfo], signature []byte) bool { - rf := r.Info.ReportFormat - if verifier, exists := okr.keys[rf]; exists { - return verifier.Verify3(key, digest, seqNr, r.Report, signature) + switch r.Info.ReportFormat { + case llotypes.ReportFormatEVMPremiumLegacy: + rf := r.Info.ReportFormat + if verifier, exists := okr.keys[rf]; exists { + // NOTE: Must use legacy Verify method for compatibility with v0.3 report verification + rc := evm.LegacyReportContext(digest, seqNr) + return verifier.Verify(key, rc, r.Report, signature) + } + default: + rf := r.Info.ReportFormat + if verifier, exists := okr.keys[rf]; exists { + return verifier.Verify3(key, digest, seqNr, r.Report, signature) + } } okr.lggr.Errorf("Verify failed; unsupported report format: %q", r.Info.ReportFormat) return false diff --git a/core/services/llo/keyring_test.go b/core/services/llo/keyring_test.go new file mode 100644 index 0000000000..44371e1496 --- /dev/null +++ b/core/services/llo/keyring_test.go @@ -0,0 +1,115 @@ +package llo + +import ( + "fmt" + "math/rand" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + ocr3types "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" + + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +var _ Key = &mockKey{} + +type mockKey struct { + format llotypes.ReportFormat + verify bool + maxSignatureLen int + sig []byte +} + +func (m *mockKey) Sign(reportCtx ocrtypes.ReportContext, report ocrtypes.Report) ([]byte, error) { + return m.sig, nil +} + +func (m *mockKey) Verify(publicKey ocrtypes.OnchainPublicKey, reportCtx ocrtypes.ReportContext, report ocrtypes.Report, signature []byte) bool { + return m.verify +} + +func (m *mockKey) Sign3(digest ocrtypes.ConfigDigest, seqNr uint64, r ocrtypes.Report) (signature []byte, err error) { + return m.sig, nil +} + +func (m *mockKey) Verify3(publicKey ocrtypes.OnchainPublicKey, cd ocrtypes.ConfigDigest, seqNr uint64, r ocrtypes.Report, signature []byte) bool { + return m.verify +} + +func (m *mockKey) PublicKey() ocrtypes.OnchainPublicKey { + b := make([]byte, m.maxSignatureLen) + for i := 0; i < m.maxSignatureLen; i++ { + b[i] = byte(255) + } + return ocrtypes.OnchainPublicKey(b) +} + +func (m *mockKey) MaxSignatureLength() int { + return m.maxSignatureLen +} + +func (m *mockKey) reset(format llotypes.ReportFormat) { + m.format = format + m.verify = false +} + +func Test_Keyring(t *testing.T) { + lggr := logger.TestLogger(t) + + ks := map[llotypes.ReportFormat]Key{ + llotypes.ReportFormatEVMPremiumLegacy: &mockKey{format: llotypes.ReportFormatEVMPremiumLegacy, maxSignatureLen: 1, sig: []byte("sig-1")}, + llotypes.ReportFormatJSON: &mockKey{format: llotypes.ReportFormatJSON, maxSignatureLen: 2, sig: []byte("sig-2")}, + } + + kr := NewOnchainKeyring(lggr, ks) + + cases := []struct { + format llotypes.ReportFormat + }{ + { + llotypes.ReportFormatEVMPremiumLegacy, + }, + { + llotypes.ReportFormatJSON, + }, + } + + cd, err := ocrtypes.BytesToConfigDigest(testutils.MustRandBytes(32)) + require.NoError(t, err) + seqNr := rand.Uint64() + t.Run("Sign+Verify", func(t *testing.T) { + for _, tc := range cases { + t.Run(tc.format.String(), func(t *testing.T) { + k := ks[tc.format] + defer k.(*mockKey).reset(tc.format) + + sig, err := kr.Sign(cd, seqNr, ocr3types.ReportWithInfo[llotypes.ReportInfo]{Info: llotypes.ReportInfo{ReportFormat: tc.format}}) + require.NoError(t, err) + + assert.Equal(t, []byte(fmt.Sprintf("sig-%d", tc.format)), sig) + + assert.False(t, kr.Verify(nil, cd, seqNr, ocr3types.ReportWithInfo[llotypes.ReportInfo]{Info: llotypes.ReportInfo{ReportFormat: tc.format}}, sig)) + + k.(*mockKey).verify = true + }) + } + }) + + t.Run("MaxSignatureLength", func(t *testing.T) { + assert.Equal(t, 2+1, kr.MaxSignatureLength()) + }) + t.Run("PublicKey", func(t *testing.T) { + b := make([]byte, 2+1) + for i := 0; i < len(b); i++ { + b[i] = byte(255) + } + assert.Equal(t, types.OnchainPublicKey(b), kr.PublicKey()) + }) +} diff --git a/core/services/llo/mercurytransmitter/helpers_test.go b/core/services/llo/mercurytransmitter/helpers_test.go new file mode 100644 index 0000000000..060c07bef4 --- /dev/null +++ b/core/services/llo/mercurytransmitter/helpers_test.go @@ -0,0 +1,47 @@ +package mercurytransmitter + +import ( + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" +) + +func makeSampleReport() ocr3types.ReportWithInfo[llotypes.ReportInfo] { + return ocr3types.ReportWithInfo[llotypes.ReportInfo]{ + Report: ocrtypes.Report{1, 2, 3}, + Info: llotypes.ReportInfo{ + LifeCycleStage: llotypes.LifeCycleStage("production"), + ReportFormat: llotypes.ReportFormatEVMPremiumLegacy, + }, + } +} + +func makeSampleConfigDigest() ocrtypes.ConfigDigest { + return ocrtypes.ConfigDigest{1, 2, 3, 4, 5, 6} +} +func makeSampleTransmission(seqNr uint64) *Transmission { + // valid with seqnr of 3 + return &Transmission{ + ServerURL: "wss://example.com/mercury", + ConfigDigest: types.ConfigDigest{0x0, 0x9, 0x57, 0xdd, 0x2f, 0x63, 0x56, 0x69, 0x34, 0xfd, 0xc2, 0xe1, 0xcd, 0xc1, 0xe, 0x3e, 0x25, 0xb9, 0x26, 0x5a, 0x16, 0x23, 0x91, 0xa6, 0x53, 0x16, 0x66, 0x59, 0x51, 0x0, 0x28, 0x7c}, + SeqNr: seqNr, + Report: ocr3types.ReportWithInfo[llotypes.ReportInfo]{ + Report: ocrtypes.Report{0x0, 0x3, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x66, 0xde, 0xf5, 0xba, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x66, 0xde, 0xf5, 0xba, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1e, 0x8e, 0x95, 0xcf, 0xb5, 0xd8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1a, 0xd0, 0x1c, 0x67, 0xa9, 0xcf, 0xb3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x66, 0xdf, 0x3, 0xca, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x1c, 0x93, 0x6d, 0xa4, 0xf2, 0x17, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x14, 0x8d, 0x9a, 0xc1, 0xd9, 0x6f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x40, 0x5c, 0xcf, 0xa1, 0xbc, 0x63, 0xc0, 0x0}, + Info: llotypes.ReportInfo{ + LifeCycleStage: llotypes.LifeCycleStage("production"), + ReportFormat: llotypes.ReportFormatEVMPremiumLegacy, + }, + }, + Sigs: []types.AttributedOnchainSignature{types.AttributedOnchainSignature{Signature: []uint8{0x9d, 0xab, 0x8f, 0xa7, 0xca, 0x7, 0x62, 0x57, 0xf7, 0x11, 0x2c, 0xb7, 0xf3, 0x49, 0x37, 0x12, 0xbd, 0xe, 0x14, 0x27, 0xfc, 0x32, 0x5c, 0xec, 0xa6, 0xb9, 0x7f, 0xf9, 0xd7, 0x7b, 0xa6, 0x36, 0x30, 0x9d, 0x84, 0x29, 0xbf, 0xd4, 0xeb, 0xc5, 0xc9, 0x29, 0xef, 0xdd, 0xd3, 0x2f, 0xa6, 0x25, 0x63, 0xda, 0xd9, 0x2c, 0xa1, 0x4a, 0xba, 0x75, 0xb2, 0x85, 0x25, 0x8f, 0x2b, 0x84, 0xcd, 0x99, 0x1}, Signer: 0x1}, types.AttributedOnchainSignature{Signature: []uint8{0x9a, 0x47, 0x4a, 0x3, 0x1a, 0x95, 0xcf, 0x46, 0x10, 0xaf, 0xcc, 0x90, 0x49, 0xb2, 0xce, 0xbf, 0x63, 0xaa, 0xc7, 0x25, 0x4d, 0x2a, 0x8, 0x36, 0xda, 0xd5, 0x9f, 0x9d, 0x63, 0x69, 0x22, 0xb3, 0x36, 0xd9, 0x6e, 0xf, 0xae, 0x7b, 0xd1, 0x61, 0x59, 0xf, 0x36, 0x4a, 0x22, 0xec, 0xde, 0x45, 0x32, 0xe0, 0x5b, 0x5c, 0xe3, 0x14, 0x29, 0x4, 0x60, 0x7b, 0xce, 0xa3, 0x89, 0x6b, 0xbb, 0xe0, 0x0}, Signer: 0x3}}, + } +} + +func makeSampleTransmissions() []*Transmission { + return []*Transmission{ + makeSampleTransmission(1001), + makeSampleTransmission(1002), + makeSampleTransmission(1003), + } +} diff --git a/core/services/llo/mercurytransmitter/orm.go b/core/services/llo/mercurytransmitter/orm.go new file mode 100644 index 0000000000..ed8c8843f4 --- /dev/null +++ b/core/services/llo/mercurytransmitter/orm.go @@ -0,0 +1,205 @@ +package mercurytransmitter + +import ( + "context" + "errors" + "fmt" + "math" + + "github.com/lib/pq" + + "github.com/smartcontractkit/libocr/commontypes" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" +) + +// ORM is scoped to a single DON ID +type ORM interface { + DonID() uint32 + Insert(ctx context.Context, transmissions []*Transmission) error + Delete(ctx context.Context, hashes [][32]byte) error + Get(ctx context.Context, serverURL string) ([]*Transmission, error) + Prune(ctx context.Context, serverURL string, maxSize int) error + Cleanup(ctx context.Context) error +} + +type orm struct { + ds sqlutil.DataSource + donID uint32 +} + +func NewORM(ds sqlutil.DataSource, donID uint32) ORM { + return &orm{ds: ds, donID: donID} +} + +func (o *orm) DonID() uint32 { + return o.donID +} + +// Insert inserts the transmissions, ignoring duplicates +func (o *orm) Insert(ctx context.Context, transmissions []*Transmission) error { + if len(transmissions) == 0 { + return nil + } + + type transmission struct { + DonID uint32 `db:"don_id"` + ServerURL string `db:"server_url"` + ConfigDigest ocrtypes.ConfigDigest `db:"config_digest"` + SeqNr int64 `db:"seq_nr"` + Report []byte `db:"report"` + LifecycleStage string `db:"lifecycle_stage"` + ReportFormat uint32 `db:"report_format"` + Signatures [][]byte `db:"signatures"` + Signers []uint8 `db:"signers"` + TransmissionHash []byte `db:"transmission_hash"` + } + records := make([]transmission, len(transmissions)) + for i, t := range transmissions { + signatures := make([][]byte, len(t.Sigs)) + signers := make([]uint8, len(t.Sigs)) + for j, sig := range t.Sigs { + signatures[j] = sig.Signature + signers[j] = uint8(sig.Signer) + } + h := t.Hash() + if t.SeqNr > math.MaxInt64 { + // this is to appease the linter but shouldn't ever happen + return fmt.Errorf("seqNr is too large (got: %d, max: %d)", t.SeqNr, math.MaxInt64) + } + records[i] = transmission{ + DonID: o.donID, + ServerURL: t.ServerURL, + ConfigDigest: t.ConfigDigest, + SeqNr: int64(t.SeqNr), //nolint + Report: t.Report.Report, + LifecycleStage: string(t.Report.Info.LifeCycleStage), + ReportFormat: uint32(t.Report.Info.ReportFormat), + Signatures: signatures, + Signers: signers, + TransmissionHash: h[:], + } + } + + _, err := o.ds.NamedExecContext(ctx, ` + INSERT INTO llo_mercury_transmit_queue (don_id, server_url, config_digest, seq_nr, report, lifecycle_stage, report_format, signatures, signers, transmission_hash) + VALUES (:don_id, :server_url, :config_digest, :seq_nr, :report, :lifecycle_stage, :report_format, :signatures, :signers, :transmission_hash) + ON CONFLICT (transmission_hash) DO NOTHING + `, records) + + if err != nil { + return fmt.Errorf("llo orm: failed to insert transmissions: %w", err) + } + return nil +} + +// Delete deletes the given transmissions +func (o *orm) Delete(ctx context.Context, hashes [][32]byte) error { + if len(hashes) == 0 { + return nil + } + + var pqHashes pq.ByteaArray + for _, hash := range hashes { + pqHashes = append(pqHashes, hash[:]) + } + + _, err := o.ds.ExecContext(ctx, ` + DELETE FROM llo_mercury_transmit_queue + WHERE transmission_hash = ANY($1) + `, pqHashes) + if err != nil { + return fmt.Errorf("llo orm: failed to delete transmissions: %w", err) + } + return nil +} + +// Get returns all transmissions in chronologically descending order +func (o *orm) Get(ctx context.Context, serverURL string) ([]*Transmission, error) { + // The priority queue uses seqnr to sort transmissions so order by + // the same fields here for optimal insertion into the pq. + rows, err := o.ds.QueryContext(ctx, ` + SELECT config_digest, seq_nr, report, lifecycle_stage, report_format, signatures, signers + FROM llo_mercury_transmit_queue + WHERE don_id = $1 AND server_url = $2 + ORDER BY seq_nr DESC, transmission_hash DESC + `, o.donID, serverURL) + if err != nil { + return nil, fmt.Errorf("llo orm: failed to get transmissions: %w", err) + } + defer rows.Close() + + var transmissions []*Transmission + for rows.Next() { + transmission := Transmission{ + ServerURL: serverURL, + } + var digest []byte + var signatures pq.ByteaArray + var signers pq.Int32Array + + err := rows.Scan( + &digest, + &transmission.SeqNr, + &transmission.Report.Report, + &transmission.Report.Info.LifeCycleStage, + &transmission.Report.Info.ReportFormat, + &signatures, + &signers, + ) + if err != nil { + return nil, fmt.Errorf("llo orm: failed to scan transmission: %w", err) + } + transmission.ConfigDigest = ocrtypes.ConfigDigest(digest) + if len(signatures) != len(signers) { + return nil, errors.New("signatures and signers must have the same length") + } + for i, sig := range signatures { + if signers[i] > math.MaxUint8 { + // this is to appease the linter but shouldn't ever happen + return nil, fmt.Errorf("signer is too large (got: %d, max: %d)", signers[i], math.MaxUint8) + } + transmission.Sigs = append(transmission.Sigs, ocrtypes.AttributedOnchainSignature{ + Signature: sig, + Signer: commontypes.OracleID(signers[i]), //nolint + }) + } + + transmissions = append(transmissions, &transmission) + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("llo orm: failed to scan transmissions: %w", err) + } + + return transmissions, nil +} + +// Prune keeps at most maxSize rows for the given job ID, +// deleting the oldest transactions. +func (o *orm) Prune(ctx context.Context, serverURL string, maxSize int) error { + // Prune the oldest requests by epoch and round. + _, err := o.ds.ExecContext(ctx, ` + DELETE FROM llo_mercury_transmit_queue + WHERE don_id = $1 AND server_url = $2 AND + transmission_hash NOT IN ( + SELECT transmission_hash + FROM llo_mercury_transmit_queue + WHERE don_id = $1 AND server_url = $2 + ORDER BY seq_nr DESC, transmission_hash DESC + LIMIT $3 + ) + `, o.donID, serverURL, maxSize) + if err != nil { + return fmt.Errorf("llo orm: failed to prune transmissions: %w", err) + } + return nil +} + +func (o *orm) Cleanup(ctx context.Context) error { + _, err := o.ds.ExecContext(ctx, `DELETE FROM llo_mercury_transmit_queue WHERE don_id = $1`, o.donID) + if err != nil { + return fmt.Errorf("llo orm: failed to cleanup transmissions: %w", err) + } + return nil +} diff --git a/core/services/llo/mercurytransmitter/orm_test.go b/core/services/llo/mercurytransmitter/orm_test.go new file mode 100644 index 0000000000..73d5760a9a --- /dev/null +++ b/core/services/llo/mercurytransmitter/orm_test.go @@ -0,0 +1,89 @@ +package mercurytransmitter + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" +) + +var ( + sURL = "wss://example.com/mercury" + sURL2 = "wss://mercuryserver.test" + sURL3 = "wss://mercuryserver.example/foo" +) + +func TestORM(t *testing.T) { + ctx := testutils.Context(t) + db := pgtest.NewSqlxDB(t) + + donID := uint32(654321) + orm := NewORM(db, donID) + + t.Run("DonID", func(t *testing.T) { + assert.Equal(t, donID, orm.DonID()) + }) + + transmissions := makeSampleTransmissions()[:2] + + t.Run("Insert", func(t *testing.T) { + err := orm.Insert(ctx, transmissions) + require.NoError(t, err) + }) + t.Run("Get", func(t *testing.T) { + result, err := orm.Get(ctx, sURL) + require.NoError(t, err) + + assert.ElementsMatch(t, transmissions, result) + + result, err = orm.Get(ctx, "other server url") + require.NoError(t, err) + + assert.Empty(t, result) + }) + t.Run("Delete", func(t *testing.T) { + err := orm.Delete(ctx, [][32]byte{transmissions[0].Hash()}) + require.NoError(t, err) + + result, err := orm.Get(ctx, sURL) + require.NoError(t, err) + + require.Len(t, result, 1) + assert.Equal(t, transmissions[1], result[0]) + + err = orm.Delete(ctx, [][32]byte{transmissions[1].Hash()}) + require.NoError(t, err) + + result, err = orm.Get(ctx, sURL) + require.NoError(t, err) + require.Len(t, result, 0) + }) + t.Run("Prune", func(t *testing.T) { + err := orm.Insert(ctx, transmissions) + require.NoError(t, err) + + err = orm.Prune(ctx, sURL, 1) + require.NoError(t, err) + + result, err := orm.Get(ctx, sURL) + require.NoError(t, err) + require.Len(t, result, 1) + assert.Equal(t, transmissions[1], result[0]) + + err = orm.Prune(ctx, sURL, 1) + require.NoError(t, err) + result, err = orm.Get(ctx, sURL) + require.NoError(t, err) + require.Len(t, result, 1) + assert.Equal(t, transmissions[1], result[0]) + + err = orm.Prune(ctx, sURL, 0) + require.NoError(t, err) + result, err = orm.Get(ctx, sURL) + require.NoError(t, err) + require.Len(t, result, 0) + }) +} diff --git a/core/services/llo/mercurytransmitter/persistence_manager.go b/core/services/llo/mercurytransmitter/persistence_manager.go new file mode 100644 index 0000000000..eb36a7d1b8 --- /dev/null +++ b/core/services/llo/mercurytransmitter/persistence_manager.go @@ -0,0 +1,143 @@ +package mercurytransmitter + +import ( + "context" + "sync" + "time" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" +) + +var ( + flushDeletesFrequency = time.Second + pruneFrequency = time.Hour +) + +// persistenceManager scopes an ORM to a single serverURL and handles cleanup +// and asynchronous deletion +type persistenceManager struct { + lggr logger.Logger + orm ORM + serverURL string + + once services.StateMachine + stopCh services.StopChan + wg sync.WaitGroup + + deleteMu sync.Mutex + deleteQueue [][32]byte + + maxTransmitQueueSize int + flushDeletesFrequency time.Duration + pruneFrequency time.Duration +} + +func NewPersistenceManager(lggr logger.Logger, orm ORM, serverURL string, maxTransmitQueueSize int, flushDeletesFrequency, pruneFrequency time.Duration) *persistenceManager { + return &persistenceManager{ + orm: orm, + lggr: logger.Sugared(lggr).Named("LLOPersistenceManager").With("serverURL", serverURL), + serverURL: serverURL, + stopCh: make(services.StopChan), + maxTransmitQueueSize: maxTransmitQueueSize, + flushDeletesFrequency: flushDeletesFrequency, + pruneFrequency: pruneFrequency, + } +} + +func (pm *persistenceManager) Start(ctx context.Context) error { + return pm.once.StartOnce("LLOMercuryPersistenceManager", func() error { + pm.wg.Add(2) + go pm.runFlushDeletesLoop() + go pm.runPruneLoop() + return nil + }) +} + +func (pm *persistenceManager) Close() error { + return pm.once.StopOnce("LLOMercuryPersistenceManager", func() error { + close(pm.stopCh) + pm.wg.Wait() + return nil + }) +} + +func (pm *persistenceManager) DonID() uint32 { + return pm.orm.DonID() +} + +func (pm *persistenceManager) AsyncDelete(hash [32]byte) { + pm.addToDeleteQueue(hash) +} + +func (pm *persistenceManager) Load(ctx context.Context) ([]*Transmission, error) { + return pm.orm.Get(ctx, pm.serverURL) +} + +func (pm *persistenceManager) runFlushDeletesLoop() { + defer pm.wg.Done() + + ctx, cancel := pm.stopCh.Ctx(context.Background()) + defer cancel() + + ticker := services.NewTicker(pm.flushDeletesFrequency) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + queuedTransmissionHashes := pm.resetDeleteQueue() + if len(queuedTransmissionHashes) == 0 { + continue + } + if err := pm.orm.Delete(ctx, queuedTransmissionHashes); err != nil { + pm.lggr.Errorw("Failed to delete queued transmit requests", "err", err) + pm.addToDeleteQueue(queuedTransmissionHashes...) + } else { + pm.lggr.Debugw("Deleted queued transmit requests") + } + } + } +} + +func (pm *persistenceManager) runPruneLoop() { + defer pm.wg.Done() + + ctx, cancel := pm.stopCh.NewCtx() + defer cancel() + + ticker := services.NewTicker(pm.pruneFrequency) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + func(ctx context.Context) { + ctx, cancelPrune := context.WithTimeout(sqlutil.WithoutDefaultTimeout(ctx), time.Minute) + defer cancelPrune() + if err := pm.orm.Prune(ctx, pm.serverURL, pm.maxTransmitQueueSize); err != nil { + pm.lggr.Errorw("Failed to prune transmit requests table", "err", err) + } else { + pm.lggr.Debugw("Pruned transmit requests table") + } + }(ctx) + } + } +} + +func (pm *persistenceManager) addToDeleteQueue(hashes ...[32]byte) { + pm.deleteMu.Lock() + defer pm.deleteMu.Unlock() + pm.deleteQueue = append(pm.deleteQueue, hashes...) +} + +func (pm *persistenceManager) resetDeleteQueue() [][32]byte { + pm.deleteMu.Lock() + defer pm.deleteMu.Unlock() + queue := pm.deleteQueue + pm.deleteQueue = nil + return queue +} diff --git a/core/services/llo/mercurytransmitter/persistence_manager_test.go b/core/services/llo/mercurytransmitter/persistence_manager_test.go new file mode 100644 index 0000000000..b05de429ad --- /dev/null +++ b/core/services/llo/mercurytransmitter/persistence_manager_test.go @@ -0,0 +1,127 @@ +package mercurytransmitter + +import ( + "testing" + "time" + + "github.com/jmoiron/sqlx" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" + + "github.com/smartcontractkit/chainlink-common/pkg/services/servicetest" + "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 bootstrapPersistenceManager(t *testing.T, donID uint32, db *sqlx.DB) (*persistenceManager, *observer.ObservedLogs) { + t.Helper() + lggr, observedLogs := logger.TestLoggerObserved(t, zapcore.DebugLevel) + orm := NewORM(db, donID) + return NewPersistenceManager(lggr, orm, "wss://example.com/mercury", 2, 5*time.Millisecond, 5*time.Millisecond), observedLogs +} + +func TestPersistenceManager(t *testing.T) { + donID1 := uint32(1234) + donID2 := uint32(2345) + + ctx := testutils.Context(t) + db := pgtest.NewSqlxDB(t) + pm, _ := bootstrapPersistenceManager(t, donID1, db) + + transmissions := makeSampleTransmissions() + err := pm.orm.Insert(ctx, transmissions) + require.NoError(t, err) + + result, err := pm.Load(ctx) + require.NoError(t, err) + assert.ElementsMatch(t, transmissions, result) + + err = pm.orm.Delete(ctx, [][32]byte{transmissions[0].Hash()}) + require.NoError(t, err) + + t.Run("scopes load to only transmissions with matching don ID", func(t *testing.T) { + pm2, _ := bootstrapPersistenceManager(t, donID2, db) + result, err = pm2.Load(ctx) + require.NoError(t, err) + + assert.Len(t, result, 0) + }) +} + +func TestPersistenceManagerAsyncDelete(t *testing.T) { + ctx := testutils.Context(t) + donID := uint32(1234) + db := pgtest.NewSqlxDB(t) + pm, observedLogs := bootstrapPersistenceManager(t, donID, db) + + transmissions := makeSampleTransmissions() + err := pm.orm.Insert(ctx, transmissions) + require.NoError(t, err) + + servicetest.Run(t, pm) + + pm.AsyncDelete(transmissions[0].Hash()) + + // Wait for next poll. + observedLogs.TakeAll() + testutils.WaitForLogMessage(t, observedLogs, "Deleted queued transmit requests") + + result, err := pm.Load(ctx) + require.NoError(t, err) + require.Len(t, result, 2) + assert.ElementsMatch(t, transmissions[1:], result) +} + +func TestPersistenceManagerPrune(t *testing.T) { + donID1 := uint32(123456) + donID2 := uint32(654321) + db := pgtest.NewSqlxDB(t) + + ctx := testutils.Context(t) + + transmissions := make([]*Transmission, 45) + for i := uint64(0); i < 45; i++ { + transmissions[i] = makeSampleTransmission(i) + } + + pm, _ := bootstrapPersistenceManager(t, donID1, db) + err := pm.orm.Insert(ctx, transmissions[:25]) + require.NoError(t, err) + + pm2, _ := bootstrapPersistenceManager(t, donID2, db) + err = pm2.orm.Insert(ctx, transmissions[25:]) + require.NoError(t, err) + + pm, observedLogs := bootstrapPersistenceManager(t, donID1, db) + + err = pm.Start(ctx) + require.NoError(t, err) + + // Wait for next poll. + observedLogs.TakeAll() + testutils.WaitForLogMessage(t, observedLogs, "Pruned transmit requests table") + + result, err := pm.Load(ctx) + require.NoError(t, err) + require.ElementsMatch(t, transmissions[23:25], result) + + // Test pruning stops after Close. + err = pm.Close() + require.NoError(t, err) + + err = pm.orm.Insert(ctx, transmissions) + require.NoError(t, err) + + result, err = pm.Load(ctx) + require.NoError(t, err) + require.Len(t, result, 25) + + t.Run("prune was scoped to don ID", func(t *testing.T) { + result, err = pm2.Load(ctx) + require.NoError(t, err) + assert.Len(t, result, 20) + }) +} diff --git a/core/services/llo/mercurytransmitter/queue.go b/core/services/llo/mercurytransmitter/queue.go new file mode 100644 index 0000000000..a5a606c5b3 --- /dev/null +++ b/core/services/llo/mercurytransmitter/queue.go @@ -0,0 +1,250 @@ +package mercurytransmitter + +import ( + "context" + "errors" + "fmt" + "sync" + "time" + + heap "github.com/esote/minmaxheap" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" +) + +type asyncDeleter interface { + AsyncDelete(hash [32]byte) + DonID() uint32 +} + +var _ services.Service = (*transmitQueue)(nil) + +var transmitQueueLoad = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "llo_transmit_queue_load", + Help: "Current count of items in the transmit queue", +}, + []string{"donID", "serverURL", "capacity"}, +) + +// Prometheus' default interval is 15s, set this to under 7.5s to avoid +// aliasing (see: https://en.wikipedia.org/wiki/Nyquist_frequency) +const promInterval = 6500 * time.Millisecond + +// TransmitQueue is the high-level package that everything outside of this file should be using +// It stores pending transmissions, yielding the latest (highest priority) first to the caller +type transmitQueue struct { + services.StateMachine + + cond sync.Cond + lggr logger.SugaredLogger + asyncDeleter asyncDeleter + mu *sync.RWMutex + + pq *priorityQueue + maxlen int + closed bool + + // monitor loop + stopMonitor func() + transmitQueueLoad prometheus.Gauge +} + +type TransmitQueue interface { + services.Service + + BlockingPop() (t *Transmission) + Push(t *Transmission) (ok bool) + Init(ts []*Transmission) + IsEmpty() bool +} + +// maxlen controls how many items will be stored in the queue +// 0 means unlimited - be careful, this can cause memory leaks +func NewTransmitQueue(lggr logger.Logger, serverURL string, maxlen int, asyncDeleter asyncDeleter) TransmitQueue { + mu := new(sync.RWMutex) + return &transmitQueue{ + services.StateMachine{}, + sync.Cond{L: mu}, + logger.Sugared(lggr).Named("TransmitQueue"), + asyncDeleter, + mu, + nil, // pq needs to be initialized by calling tq.Init before use + maxlen, + false, + nil, + transmitQueueLoad.WithLabelValues(fmt.Sprintf("%d", asyncDeleter.DonID()), serverURL, fmt.Sprintf("%d", maxlen)), + } +} + +func (tq *transmitQueue) Init(ts []*Transmission) { + pq := priorityQueue(ts) + heap.Init(&pq) // ensure the heap is ordered + tq.pq = &pq +} + +func (tq *transmitQueue) Push(t *Transmission) (ok bool) { + tq.cond.L.Lock() + defer tq.cond.L.Unlock() + + if tq.closed { + return false + } + + if tq.maxlen != 0 && tq.pq.Len() == tq.maxlen { + // evict oldest entry to make room + tq.lggr.Criticalf("Transmit queue is full; dropping oldest transmission (reached max length of %d)", tq.maxlen) + removed := heap.PopMax(tq.pq) + if removed, ok := removed.(*Transmission); ok { + tq.asyncDeleter.AsyncDelete(removed.Hash()) + } + } + + heap.Push(tq.pq, t) + tq.cond.Signal() + + return true +} + +// BlockingPop will block until at least one item is in the heap, and then return it +// If the queue is closed, it will immediately return nil +func (tq *transmitQueue) BlockingPop() (t *Transmission) { + tq.cond.L.Lock() + defer tq.cond.L.Unlock() + if tq.closed { + return nil + } + for t = tq.pop(); t == nil; t = tq.pop() { + tq.cond.Wait() + if tq.closed { + return nil + } + } + return t +} + +func (tq *transmitQueue) IsEmpty() bool { + tq.mu.RLock() + defer tq.mu.RUnlock() + return tq.pq.Len() == 0 +} + +func (tq *transmitQueue) Start(context.Context) error { + return tq.StartOnce("TransmitQueue", func() error { + t := services.NewTicker(promInterval) + wg := new(sync.WaitGroup) + chStop := make(chan struct{}) + tq.stopMonitor = func() { + t.Stop() + close(chStop) + wg.Wait() + } + wg.Add(1) + go tq.monitorLoop(t.C, chStop, wg) + return nil + }) +} + +func (tq *transmitQueue) Close() error { + return tq.StopOnce("TransmitQueue", func() error { + tq.cond.L.Lock() + tq.closed = true + tq.cond.L.Unlock() + tq.cond.Broadcast() + tq.stopMonitor() + return nil + }) +} + +func (tq *transmitQueue) monitorLoop(c <-chan time.Time, chStop <-chan struct{}, wg *sync.WaitGroup) { + defer wg.Done() + + for { + select { + case <-c: + tq.report() + case <-chStop: + return + } + } +} + +func (tq *transmitQueue) report() { + tq.mu.RLock() + length := tq.pq.Len() + tq.mu.RUnlock() + tq.transmitQueueLoad.Set(float64(length)) +} + +func (tq *transmitQueue) Ready() error { + return nil +} +func (tq *transmitQueue) Name() string { return tq.lggr.Name() } +func (tq *transmitQueue) HealthReport() map[string]error { + report := map[string]error{tq.Name(): errors.Join( + tq.status(), + )} + return report +} + +func (tq *transmitQueue) status() (merr error) { + tq.mu.RLock() + length := tq.pq.Len() + closed := tq.closed + tq.mu.RUnlock() + if tq.maxlen != 0 && length > (tq.maxlen/2) { + merr = errors.Join(merr, fmt.Errorf("transmit priority queue is greater than 50%% full (%d/%d)", length, tq.maxlen)) + } + if closed { + merr = errors.New("transmit queue is closed") + } + return merr +} + +// pop latest Transmission from the heap +// Not thread-safe +func (tq *transmitQueue) pop() *Transmission { + if tq.pq.Len() == 0 { + return nil + } + return heap.Pop(tq.pq).(*Transmission) +} + +// HEAP +// Adapted from https://pkg.go.dev/container/heap#example-package-PriorityQueue + +// WARNING: None of these methods are thread-safe, caller must synchronize + +var _ heap.Interface = &priorityQueue{} + +type priorityQueue []*Transmission + +func (pq priorityQueue) Len() int { return len(pq) } + +func (pq priorityQueue) Less(i, j int) bool { + // We want Pop to give us the latest round, so we use greater than here + // i.e. a later seqNr is "less" than an earlier one + return pq[i].SeqNr > pq[j].SeqNr +} + +func (pq priorityQueue) Swap(i, j int) { + pq[i], pq[j] = pq[j], pq[i] +} + +func (pq *priorityQueue) Pop() any { + n := len(*pq) + if n == 0 { + return nil + } + old := *pq + item := old[n-1] + old[n-1] = nil // avoid memory leak + *pq = old[0 : n-1] + return item +} + +func (pq *priorityQueue) Push(x any) { + *pq = append(*pq, x.(*Transmission)) +} diff --git a/core/services/llo/mercurytransmitter/queue_test.go b/core/services/llo/mercurytransmitter/queue_test.go new file mode 100644 index 0000000000..d19d140d6b --- /dev/null +++ b/core/services/llo/mercurytransmitter/queue_test.go @@ -0,0 +1,105 @@ +package mercurytransmitter + +import ( + "sync" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +var _ asyncDeleter = &mockAsyncDeleter{} + +type mockAsyncDeleter struct { + donID uint32 + hashes [][32]byte +} + +func (m *mockAsyncDeleter) AsyncDelete(hash [32]byte) { + m.hashes = append(m.hashes, hash) +} +func (m *mockAsyncDeleter) DonID() uint32 { + return m.donID +} + +func Test_Queue(t *testing.T) { + t.Parallel() + lggr, observedLogs := logger.TestLoggerObserved(t, zapcore.ErrorLevel) + testTransmissions := makeSampleTransmissions() + deleter := &mockAsyncDeleter{} + transmitQueue := NewTransmitQueue(lggr, sURL, 7, deleter) + transmitQueue.Init([]*Transmission{}) + + t.Run("successfully add transmissions to transmit queue", func(t *testing.T) { + for _, tt := range testTransmissions { + ok := transmitQueue.Push(tt) + require.True(t, ok) + } + report := transmitQueue.HealthReport() + assert.Nil(t, report[transmitQueue.Name()]) + }) + + t.Run("transmit queue is more than 50% full", func(t *testing.T) { + transmitQueue.Push(testTransmissions[2]) + report := transmitQueue.HealthReport() + assert.Equal(t, report[transmitQueue.Name()].Error(), "transmit priority queue is greater than 50% full (4/7)") + }) + + t.Run("transmit queue pops the highest priority transmission", func(t *testing.T) { + tr := transmitQueue.BlockingPop() + assert.Equal(t, testTransmissions[2], tr) + }) + + t.Run("transmit queue is full and evicts the oldest transmission", func(t *testing.T) { + // add 5 more transmissions to overflow the queue by 1 + for i := 0; i < 5; i++ { + transmitQueue.Push(testTransmissions[1]) + } + + // expecting testTransmissions[0] to get evicted and not present in the queue anymore + testutils.WaitForLogMessage(t, observedLogs, "Transmit queue is full; dropping oldest transmission (reached max length of 7)") + var transmissions []*Transmission + for i := 0; i < 7; i++ { + tr := transmitQueue.BlockingPop() + transmissions = append(transmissions, tr) + } + + assert.NotContains(t, transmissions, testTransmissions[0]) + require.Len(t, deleter.hashes, 1) + assert.Equal(t, testTransmissions[0].Hash(), deleter.hashes[0]) + }) + + t.Run("transmit queue blocks when empty and resumes when tranmission available", func(t *testing.T) { + assert.True(t, transmitQueue.IsEmpty()) + + var wg sync.WaitGroup + wg.Add(2) + go func() { + defer wg.Done() + tr := transmitQueue.BlockingPop() + assert.Equal(t, tr, testTransmissions[0]) + }() + go func() { + defer wg.Done() + transmitQueue.Push(testTransmissions[0]) + }() + wg.Wait() + }) + + t.Run("initializes transmissions", func(t *testing.T) { + expected := makeSampleTransmission(1) + transmissions := []*Transmission{ + expected, + } + transmitQueue := NewTransmitQueue(lggr, sURL, 7, deleter) + transmitQueue.Init(transmissions) + + transmission := transmitQueue.BlockingPop() + assert.Equal(t, expected, transmission) + assert.True(t, transmitQueue.IsEmpty()) + }) +} diff --git a/core/services/llo/mercurytransmitter/server.go b/core/services/llo/mercurytransmitter/server.go new file mode 100644 index 0000000000..d025e65efa --- /dev/null +++ b/core/services/llo/mercurytransmitter/server.go @@ -0,0 +1,240 @@ +package mercurytransmitter + +import ( + "context" + "fmt" + "sync" + "time" + + "github.com/jpillora/backoff" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + + commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" + + "github.com/smartcontractkit/chainlink/v2/core/services/llo/evm" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc/pb" + "github.com/smartcontractkit/chainlink/v2/core/utils" +) + +var ( + transmitQueueDeleteErrorCount = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "llo_mercury_transmit_queue_delete_error_count", + Help: "Running count of DB errors when trying to delete an item from the queue DB", + }, + []string{"donID", "serverURL"}, + ) + transmitQueueInsertErrorCount = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "llo_mercury_transmit_queue_insert_error_count", + Help: "Running count of DB errors when trying to insert an item into the queue DB", + }, + []string{"donID", "serverURL"}, + ) + transmitQueuePushErrorCount = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "llo_mercury_transmit_queue_push_error_count", + Help: "Running count of DB errors when trying to push an item onto the queue", + }, + []string{"donID", "serverURL"}, + ) + transmitServerErrorCount = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "llo_mercury_transmit_server_error_count", + Help: "Number of errored transmissions that failed due to an error returned by the mercury server", + }, + []string{"donID", "serverURL", "code"}, + ) +) + +// A server handles the queue for a given mercury server + +type server struct { + lggr logger.SugaredLogger + + transmitTimeout time.Duration + + c wsrpc.Client + pm *persistenceManager + q TransmitQueue + + deleteQueue chan [32]byte + + url string + + transmitSuccessCount prometheus.Counter + transmitDuplicateCount prometheus.Counter + transmitConnectionErrorCount prometheus.Counter + transmitQueueDeleteErrorCount prometheus.Counter + transmitQueueInsertErrorCount prometheus.Counter + transmitQueuePushErrorCount prometheus.Counter +} + +type QueueConfig interface { + TransmitQueueMaxSize() uint32 + TransmitTimeout() commonconfig.Duration +} + +func newServer(lggr logger.Logger, cfg QueueConfig, client wsrpc.Client, orm ORM, serverURL string) *server { + pm := NewPersistenceManager(lggr, orm, serverURL, int(cfg.TransmitQueueMaxSize()), flushDeletesFrequency, pruneFrequency) + donIDStr := fmt.Sprintf("%d", pm.DonID()) + return &server{ + logger.Sugared(lggr), + cfg.TransmitTimeout().Duration(), + client, + pm, + NewTransmitQueue(lggr, serverURL, int(cfg.TransmitQueueMaxSize()), pm), + make(chan [32]byte, int(cfg.TransmitQueueMaxSize())), + serverURL, + transmitSuccessCount.WithLabelValues(donIDStr, serverURL), + transmitDuplicateCount.WithLabelValues(donIDStr, serverURL), + transmitConnectionErrorCount.WithLabelValues(donIDStr, serverURL), + transmitQueueDeleteErrorCount.WithLabelValues(donIDStr, serverURL), + transmitQueueInsertErrorCount.WithLabelValues(donIDStr, serverURL), + transmitQueuePushErrorCount.WithLabelValues(donIDStr, serverURL), + } +} + +func (s *server) HealthReport() map[string]error { + report := map[string]error{} + services.CopyHealth(report, s.c.HealthReport()) + services.CopyHealth(report, s.q.HealthReport()) + return report +} + +func (s *server) runDeleteQueueLoop(stopCh services.StopChan, wg *sync.WaitGroup) { + defer wg.Done() + runloopCtx, cancel := stopCh.Ctx(context.Background()) + defer cancel() + + // Exponential backoff for very rarely occurring errors (DB disconnect etc) + b := backoff.Backoff{ + Min: 1 * time.Second, + Max: 120 * time.Second, + Factor: 2, + Jitter: true, + } + + for { + select { + case hash := <-s.deleteQueue: + for { + if err := s.pm.orm.Delete(runloopCtx, [][32]byte{hash}); err != nil { + s.lggr.Errorw("Failed to delete transmission record", "err", err, "transmissionHash", hash) + s.transmitQueueDeleteErrorCount.Inc() + select { + case <-time.After(b.Duration()): + // Wait a backoff duration before trying to delete again + continue + case <-stopCh: + // abort and return immediately on stop even if items remain in queue + return + } + } + break + } + // success + b.Reset() + case <-stopCh: + // abort and return immediately on stop even if items remain in queue + return + } + } +} + +func (s *server) runQueueLoop(stopCh services.StopChan, wg *sync.WaitGroup, donIDStr string) { + defer wg.Done() + // Exponential backoff with very short retry interval (since latency is a priority) + // 5ms, 10ms, 20ms, 40ms etc + b := backoff.Backoff{ + Min: 5 * time.Millisecond, + Max: 1 * time.Second, + Factor: 2, + Jitter: true, + } + runloopCtx, cancel := stopCh.Ctx(context.Background()) + defer cancel() + for { + t := s.q.BlockingPop() + if t == nil { + // queue was closed + return + } + ctx, cancel := context.WithTimeout(runloopCtx, utils.WithJitter(s.transmitTimeout)) + res, err := s.transmit(ctx, t) + cancel() + if runloopCtx.Err() != nil { + // runloop context is only canceled on transmitter close so we can + // exit the runloop here + return + } else if err != nil { + s.transmitConnectionErrorCount.Inc() + s.lggr.Errorw("Transmit report failed", "err", err, "transmission", t) + if ok := s.q.Push(t); !ok { + s.lggr.Error("Failed to push report to transmit queue; queue is closed") + return + } + // Wait a backoff duration before pulling the most recent transmission + // the heap + select { + case <-time.After(b.Duration()): + continue + case <-stopCh: + return + } + } + + b.Reset() + if res.Error == "" { + s.transmitSuccessCount.Inc() + s.lggr.Debugw("Transmit report success", "transmission", t, "response", res) + } else { + // We don't need to retry here because the mercury server + // has confirmed it received the report. We only need to retry + // on networking/unknown errors + switch res.Code { + case DuplicateReport: + s.transmitSuccessCount.Inc() + s.transmitDuplicateCount.Inc() + s.lggr.Debugw("Transmit report success; duplicate report", "transmission", t, "response", res) + default: + transmitServerErrorCount.WithLabelValues(donIDStr, s.url, fmt.Sprintf("%d", res.Code)).Inc() + s.lggr.Errorw("Transmit report failed; mercury server returned error", "response", res, "transmission", t, "err", res.Error, "code", res.Code) + } + } + + select { + case s.deleteQueue <- t.Hash(): + default: + s.lggr.Criticalw("Delete queue is full", "transmission", t) + } + } +} + +func (s *server) transmit(ctx context.Context, t *Transmission) (*pb.TransmitResponse, error) { + var payload []byte + var err error + + switch t.Report.Info.ReportFormat { + case llotypes.ReportFormatJSON: + // TODO: exactly how to handle JSON here? + // https://smartcontract-it.atlassian.net/browse/MERC-3659 + fallthrough + case llotypes.ReportFormatEVMPremiumLegacy: + payload, err = evm.ReportCodecPremiumLegacy{}.Pack(t.ConfigDigest, t.SeqNr, t.Report.Report, t.Sigs) + default: + return nil, fmt.Errorf("Transmit failed; unsupported report format: %q", t.Report.Info.ReportFormat) + } + + if err != nil { + return nil, fmt.Errorf("Transmit: encode failed; %w", err) + } + + req := &pb.TransmitRequest{ + Payload: payload, + ReportFormat: uint32(t.Report.Info.ReportFormat), + } + + return s.c.Transmit(ctx, req) +} diff --git a/core/services/llo/mercurytransmitter/transmitter.go b/core/services/llo/mercurytransmitter/transmitter.go new file mode 100644 index 0000000000..dc032a054d --- /dev/null +++ b/core/services/llo/mercurytransmitter/transmitter.go @@ -0,0 +1,254 @@ +package mercurytransmitter + +import ( + "context" + "crypto/ed25519" + "crypto/sha256" + "encoding/binary" + "errors" + "fmt" + "io" + "sync" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "golang.org/x/sync/errgroup" + + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" + + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc" +) + +const ( + // Mercury server error codes + DuplicateReport = 2 +) + +var ( + transmitSuccessCount = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "llo_mercury_transmit_success_count", + Help: "Number of successful transmissions (duplicates are counted as success)", + }, + []string{"donID", "serverURL"}, + ) + transmitDuplicateCount = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "llo_mercury_transmit_duplicate_count", + Help: "Number of transmissions where the server told us it was a duplicate", + }, + []string{"donID", "serverURL"}, + ) + transmitConnectionErrorCount = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "llo_mercury_transmit_connection_error_count", + Help: "Number of errored transmissions that failed due to problem with the connection", + }, + []string{"donID", "serverURL"}, + ) +) + +type Transmission struct { + ServerURL string + ConfigDigest types.ConfigDigest + SeqNr uint64 + Report ocr3types.ReportWithInfo[llotypes.ReportInfo] + Sigs []types.AttributedOnchainSignature +} + +// Hash takes sha256 hash of all fields +func (t Transmission) Hash() [32]byte { + h := sha256.New() + h.Write([]byte(t.ServerURL)) + h.Write(t.ConfigDigest[:]) + if err := binary.Write(h, binary.BigEndian, t.SeqNr); err != nil { + // This should never happen + panic(err) + } + h.Write(t.Report.Report) + h.Write([]byte(t.Report.Info.LifeCycleStage)) + if err := binary.Write(h, binary.BigEndian, t.Report.Info.ReportFormat); err != nil { + // This should never happen + panic(err) + } + for _, sig := range t.Sigs { + h.Write(sig.Signature) + if err := binary.Write(h, binary.BigEndian, sig.Signer); err != nil { + // This should never happen + panic(err) + } + } + var result [32]byte + h.Sum(result[:0]) + return result +} + +type Transmitter interface { + llotypes.Transmitter + services.Service +} + +var _ Transmitter = (*transmitter)(nil) + +type Config interface { + TransmitQueueMaxSize() uint32 + TransmitTimeout() commonconfig.Duration +} + +type transmitter struct { + services.StateMachine + lggr logger.SugaredLogger + cfg Config + + orm ORM + servers map[string]*server + + donID uint32 + fromAccount string + + stopCh services.StopChan + wg *sync.WaitGroup +} + +type Opts struct { + Lggr logger.Logger + Cfg Config + Clients map[string]wsrpc.Client + FromAccount ed25519.PublicKey + DonID uint32 + ORM ORM +} + +func New(opts Opts) Transmitter { + return newTransmitter(opts) +} + +func newTransmitter(opts Opts) *transmitter { + sugared := logger.Sugared(opts.Lggr).Named("LLOMercuryTransmitter") + servers := make(map[string]*server, len(opts.Clients)) + for serverURL, client := range opts.Clients { + sLggr := sugared.Named(serverURL).With("serverURL", serverURL) + servers[serverURL] = newServer(sLggr, opts.Cfg, client, opts.ORM, serverURL) + } + return &transmitter{ + services.StateMachine{}, + sugared.Named("LLOMercuryTransmitter").With("donID", opts.ORM.DonID()), + opts.Cfg, + opts.ORM, + servers, + opts.DonID, + fmt.Sprintf("%x", opts.FromAccount), + make(services.StopChan), + &sync.WaitGroup{}, + } +} + +func (mt *transmitter) Start(ctx context.Context) (err error) { + return mt.StartOnce("LLOMercuryTransmitter", func() error { + mt.lggr.Debugw("Loading transmit requests from database") + + { + var startClosers []services.StartClose + for _, s := range mt.servers { + transmissions, err := s.pm.Load(ctx) + if err != nil { + return err + } + s.q.Init(transmissions) + // starting pm after loading from it is fine because it simply spawns some garbage collection/prune goroutines + startClosers = append(startClosers, s.c, s.q, s.pm) + + mt.wg.Add(2) + go s.runDeleteQueueLoop(mt.stopCh, mt.wg) + go s.runQueueLoop(mt.stopCh, mt.wg, fmt.Sprintf("%d", mt.donID)) + } + if err := (&services.MultiStart{}).Start(ctx, startClosers...); err != nil { + return err + } + } + + return nil + }) +} + +func (mt *transmitter) Close() error { + return mt.StopOnce("LLOMercuryTransmitter", func() error { + // Drain all the queues first + var qs []io.Closer + for _, s := range mt.servers { + qs = append(qs, s.q) + } + if err := services.CloseAll(qs...); err != nil { + return err + } + + close(mt.stopCh) + mt.wg.Wait() + + // Close all the persistence managers + // Close all the clients + var closers []io.Closer + for _, s := range mt.servers { + closers = append(closers, s.pm) + closers = append(closers, s.c) + } + return services.CloseAll(closers...) + }) +} + +func (mt *transmitter) Name() string { return mt.lggr.Name() } + +func (mt *transmitter) HealthReport() map[string]error { + report := map[string]error{mt.Name(): mt.Healthy()} + for _, s := range mt.servers { + services.CopyHealth(report, s.HealthReport()) + } + return report +} + +// Transmit enqueues the report for transmission to the Mercury servers +func (mt *transmitter) Transmit( + ctx context.Context, + digest types.ConfigDigest, + seqNr uint64, + report ocr3types.ReportWithInfo[llotypes.ReportInfo], + sigs []types.AttributedOnchainSignature) error { + transmissions := make([]*Transmission, 0, len(mt.servers)) + for serverURL := range mt.servers { + transmissions = append(transmissions, &Transmission{ + ServerURL: serverURL, + ConfigDigest: digest, + SeqNr: seqNr, + Report: report, + Sigs: sigs, + }) + } + if err := mt.orm.Insert(ctx, transmissions); err != nil { + return err + } + + g := new(errgroup.Group) + for i := range transmissions { + t := transmissions[i] + mt.lggr.Debugw("LLOMercuryTransmit", "digest", digest.Hex(), "seqNr", seqNr, "reportFormat", report.Info.ReportFormat, "reportLifeCycleStage", report.Info.LifeCycleStage, "transmissionHash", t.Hash()) + g.Go(func() error { + s := mt.servers[t.ServerURL] + if ok := s.q.Push(t); !ok { + s.transmitQueuePushErrorCount.Inc() + return errors.New("transmit queue is closed") + } + return nil + }) + } + + return g.Wait() +} + +// FromAccount returns the stringified (hex) CSA public key +func (mt *transmitter) FromAccount() (ocrtypes.Account, error) { + return ocrtypes.Account(mt.fromAccount), nil +} diff --git a/core/services/llo/mercurytransmitter/transmitter_test.go b/core/services/llo/mercurytransmitter/transmitter_test.go new file mode 100644 index 0000000000..db3d0d2e58 --- /dev/null +++ b/core/services/llo/mercurytransmitter/transmitter_test.go @@ -0,0 +1,258 @@ +package mercurytransmitter + +import ( + "context" + "crypto/ed25519" + "sync" + "testing" + "time" + + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc/mocks" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc/pb" +) + +type mockCfg struct{} + +func (m mockCfg) TransmitQueueMaxSize() uint32 { + return 10_000 +} + +func (m mockCfg) TransmitTimeout() commonconfig.Duration { + return *commonconfig.MustNewDuration(1 * time.Hour) +} + +func Test_Transmitter_Transmit(t *testing.T) { + lggr := logger.TestLogger(t) + db := pgtest.NewSqlxDB(t) + donID := uint32(123456) + orm := NewORM(db, donID) + clients := map[string]wsrpc.Client{} + + t.Run("with multiple mercury servers", func(t *testing.T) { + t.Run("transmission successfully enqueued", func(t *testing.T) { + c := &mocks.MockWSRPCClient{} + clients[sURL] = c + clients[sURL2] = c + clients[sURL3] = c + + mt := newTransmitter(Opts{ + Lggr: lggr, + Cfg: mockCfg{}, + Clients: clients, + FromAccount: ed25519.PublicKey{}, + DonID: donID, + ORM: orm, + }) + // init the queue since we skipped starting transmitter + mt.servers[sURL].q.Init([]*Transmission{}) + mt.servers[sURL2].q.Init([]*Transmission{}) + mt.servers[sURL3].q.Init([]*Transmission{}) + + seqNr := uint64(55) + report := makeSampleReport() + digest := makeSampleConfigDigest() + sigs := []types.AttributedOnchainSignature{{ + Signature: []byte{22}, + Signer: commontypes.OracleID(43), + }} + err := mt.Transmit(testutils.Context(t), digest, seqNr, report, sigs) + require.NoError(t, err) + + // ensure it was added to the queue + require.Equal(t, mt.servers[sURL].q.(*transmitQueue).pq.Len(), 1) + assert.Equal(t, &Transmission{ + ServerURL: sURL, + ConfigDigest: digest, + SeqNr: seqNr, + Report: report, + Sigs: sigs, + }, mt.servers[sURL].q.(*transmitQueue).pq.Pop().(*Transmission)) + require.Equal(t, mt.servers[sURL2].q.(*transmitQueue).pq.Len(), 1) + assert.Equal(t, &Transmission{ + ServerURL: sURL2, + ConfigDigest: digest, + SeqNr: seqNr, + Report: report, + Sigs: sigs, + }, mt.servers[sURL2].q.(*transmitQueue).pq.Pop().(*Transmission)) + require.Equal(t, mt.servers[sURL3].q.(*transmitQueue).pq.Len(), 1) + assert.Equal(t, &Transmission{ + ServerURL: sURL3, + ConfigDigest: digest, + SeqNr: seqNr, + Report: report, + Sigs: sigs, + }, mt.servers[sURL3].q.(*transmitQueue).pq.Pop().(*Transmission)) + }) + }) +} + +type mockQ struct { + ch chan *Transmission +} + +func newMockQ() *mockQ { + return &mockQ{make(chan *Transmission, 100)} +} + +func (m *mockQ) Start(context.Context) error { return nil } +func (m *mockQ) Close() error { + m.ch <- nil + return nil +} +func (m *mockQ) Ready() error { return nil } +func (m *mockQ) HealthReport() map[string]error { return nil } +func (m *mockQ) Name() string { return "" } +func (m *mockQ) BlockingPop() (t *Transmission) { + val := <-m.ch + return val +} +func (m *mockQ) Push(t *Transmission) (ok bool) { + m.ch <- t + return true +} +func (m *mockQ) Init(transmissions []*Transmission) {} +func (m *mockQ) IsEmpty() bool { return false } + +func Test_Transmitter_runQueueLoop(t *testing.T) { + donIDStr := "555" + lggr := logger.TestLogger(t) + c := &mocks.MockWSRPCClient{} + db := pgtest.NewSqlxDB(t) + donID := uint32(123456) + orm := NewORM(db, donID) + cfg := mockCfg{} + + s := newServer(lggr, cfg, c, orm, sURL) + + t.Run("pulls from queue and transmits successfully", func(t *testing.T) { + transmit := make(chan *pb.TransmitRequest, 1) + c.TransmitF = func(ctx context.Context, in *pb.TransmitRequest) (*pb.TransmitResponse, error) { + transmit <- in + return &pb.TransmitResponse{Code: 0, Error: ""}, nil + } + q := newMockQ() + s.q = q + wg := &sync.WaitGroup{} + wg.Add(1) + + go s.runQueueLoop(nil, wg, donIDStr) + + transmission := makeSampleTransmission(1) + q.Push(transmission) + + select { + case tr := <-transmit: + assert.Equal(t, []byte{0x0, 0x9, 0x57, 0xdd, 0x2f, 0x63, 0x56, 0x69, 0x34, 0xfd, 0xc2, 0xe1, 0xcd, 0xc1, 0xe, 0x3e, 0x25, 0xb9, 0x26, 0x5a, 0x16, 0x23, 0x91, 0xa6, 0x53, 0x16, 0x66, 0x59, 0x51, 0x0, 0x28, 0x7c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x80, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x20, 0x0, 0x3, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x66, 0xde, 0xf5, 0xba, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x66, 0xde, 0xf5, 0xba, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1e, 0x8e, 0x95, 0xcf, 0xb5, 0xd8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1a, 0xd0, 0x1c, 0x67, 0xa9, 0xcf, 0xb3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x66, 0xdf, 0x3, 0xca, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x1c, 0x93, 0x6d, 0xa4, 0xf2, 0x17, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x14, 0x8d, 0x9a, 0xc1, 0xd9, 0x6f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x40, 0x5c, 0xcf, 0xa1, 0xbc, 0x63, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x9d, 0xab, 0x8f, 0xa7, 0xca, 0x7, 0x62, 0x57, 0xf7, 0x11, 0x2c, 0xb7, 0xf3, 0x49, 0x37, 0x12, 0xbd, 0xe, 0x14, 0x27, 0xfc, 0x32, 0x5c, 0xec, 0xa6, 0xb9, 0x7f, 0xf9, 0xd7, 0x7b, 0xa6, 0x36, 0x9a, 0x47, 0x4a, 0x3, 0x1a, 0x95, 0xcf, 0x46, 0x10, 0xaf, 0xcc, 0x90, 0x49, 0xb2, 0xce, 0xbf, 0x63, 0xaa, 0xc7, 0x25, 0x4d, 0x2a, 0x8, 0x36, 0xda, 0xd5, 0x9f, 0x9d, 0x63, 0x69, 0x22, 0xb3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x30, 0x9d, 0x84, 0x29, 0xbf, 0xd4, 0xeb, 0xc5, 0xc9, 0x29, 0xef, 0xdd, 0xd3, 0x2f, 0xa6, 0x25, 0x63, 0xda, 0xd9, 0x2c, 0xa1, 0x4a, 0xba, 0x75, 0xb2, 0x85, 0x25, 0x8f, 0x2b, 0x84, 0xcd, 0x99, 0x36, 0xd9, 0x6e, 0xf, 0xae, 0x7b, 0xd1, 0x61, 0x59, 0xf, 0x36, 0x4a, 0x22, 0xec, 0xde, 0x45, 0x32, 0xe0, 0x5b, 0x5c, 0xe3, 0x14, 0x29, 0x4, 0x60, 0x7b, 0xce, 0xa3, 0x89, 0x6b, 0xbb, 0xe0}, tr.Payload) + assert.Equal(t, int(transmission.Report.Info.ReportFormat), int(tr.ReportFormat)) + case <-time.After(testutils.WaitTimeout(t)): + t.Fatal("expected a transmit request to be sent") + } + + q.Close() + wg.Wait() + }) + + t.Run("on duplicate, success", func(t *testing.T) { + transmit := make(chan *pb.TransmitRequest, 1) + c.TransmitF = func(ctx context.Context, in *pb.TransmitRequest) (*pb.TransmitResponse, error) { + transmit <- in + return &pb.TransmitResponse{Code: DuplicateReport, Error: ""}, nil + } + q := newMockQ() + s.q = q + wg := &sync.WaitGroup{} + wg.Add(1) + + go s.runQueueLoop(nil, wg, donIDStr) + + transmission := makeSampleTransmission(1) + q.Push(transmission) + + select { + case tr := <-transmit: + assert.Equal(t, []byte{0x0, 0x9, 0x57, 0xdd, 0x2f, 0x63, 0x56, 0x69, 0x34, 0xfd, 0xc2, 0xe1, 0xcd, 0xc1, 0xe, 0x3e, 0x25, 0xb9, 0x26, 0x5a, 0x16, 0x23, 0x91, 0xa6, 0x53, 0x16, 0x66, 0x59, 0x51, 0x0, 0x28, 0x7c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x80, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x20, 0x0, 0x3, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x66, 0xde, 0xf5, 0xba, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x66, 0xde, 0xf5, 0xba, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1e, 0x8e, 0x95, 0xcf, 0xb5, 0xd8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1a, 0xd0, 0x1c, 0x67, 0xa9, 0xcf, 0xb3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x66, 0xdf, 0x3, 0xca, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x1c, 0x93, 0x6d, 0xa4, 0xf2, 0x17, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x14, 0x8d, 0x9a, 0xc1, 0xd9, 0x6f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x40, 0x5c, 0xcf, 0xa1, 0xbc, 0x63, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x9d, 0xab, 0x8f, 0xa7, 0xca, 0x7, 0x62, 0x57, 0xf7, 0x11, 0x2c, 0xb7, 0xf3, 0x49, 0x37, 0x12, 0xbd, 0xe, 0x14, 0x27, 0xfc, 0x32, 0x5c, 0xec, 0xa6, 0xb9, 0x7f, 0xf9, 0xd7, 0x7b, 0xa6, 0x36, 0x9a, 0x47, 0x4a, 0x3, 0x1a, 0x95, 0xcf, 0x46, 0x10, 0xaf, 0xcc, 0x90, 0x49, 0xb2, 0xce, 0xbf, 0x63, 0xaa, 0xc7, 0x25, 0x4d, 0x2a, 0x8, 0x36, 0xda, 0xd5, 0x9f, 0x9d, 0x63, 0x69, 0x22, 0xb3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x30, 0x9d, 0x84, 0x29, 0xbf, 0xd4, 0xeb, 0xc5, 0xc9, 0x29, 0xef, 0xdd, 0xd3, 0x2f, 0xa6, 0x25, 0x63, 0xda, 0xd9, 0x2c, 0xa1, 0x4a, 0xba, 0x75, 0xb2, 0x85, 0x25, 0x8f, 0x2b, 0x84, 0xcd, 0x99, 0x36, 0xd9, 0x6e, 0xf, 0xae, 0x7b, 0xd1, 0x61, 0x59, 0xf, 0x36, 0x4a, 0x22, 0xec, 0xde, 0x45, 0x32, 0xe0, 0x5b, 0x5c, 0xe3, 0x14, 0x29, 0x4, 0x60, 0x7b, 0xce, 0xa3, 0x89, 0x6b, 0xbb, 0xe0}, tr.Payload) + assert.Equal(t, int(transmission.Report.Info.ReportFormat), int(tr.ReportFormat)) + case <-time.After(testutils.WaitTimeout(t)): + t.Fatal("expected a transmit request to be sent") + } + + q.Close() + wg.Wait() + }) + t.Run("on server-side error, does not retry", func(t *testing.T) { + transmit := make(chan *pb.TransmitRequest, 1) + c.TransmitF = func(ctx context.Context, in *pb.TransmitRequest) (*pb.TransmitResponse, error) { + transmit <- in + return &pb.TransmitResponse{Code: DuplicateReport, Error: ""}, nil + } + q := newMockQ() + s.q = q + wg := &sync.WaitGroup{} + wg.Add(1) + + go s.runQueueLoop(nil, wg, donIDStr) + + transmission := makeSampleTransmission(1) + q.Push(transmission) + + select { + case tr := <-transmit: + assert.Equal(t, []byte{0x0, 0x9, 0x57, 0xdd, 0x2f, 0x63, 0x56, 0x69, 0x34, 0xfd, 0xc2, 0xe1, 0xcd, 0xc1, 0xe, 0x3e, 0x25, 0xb9, 0x26, 0x5a, 0x16, 0x23, 0x91, 0xa6, 0x53, 0x16, 0x66, 0x59, 0x51, 0x0, 0x28, 0x7c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x80, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x20, 0x0, 0x3, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x66, 0xde, 0xf5, 0xba, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x66, 0xde, 0xf5, 0xba, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1e, 0x8e, 0x95, 0xcf, 0xb5, 0xd8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1a, 0xd0, 0x1c, 0x67, 0xa9, 0xcf, 0xb3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x66, 0xdf, 0x3, 0xca, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x1c, 0x93, 0x6d, 0xa4, 0xf2, 0x17, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x14, 0x8d, 0x9a, 0xc1, 0xd9, 0x6f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x40, 0x5c, 0xcf, 0xa1, 0xbc, 0x63, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x9d, 0xab, 0x8f, 0xa7, 0xca, 0x7, 0x62, 0x57, 0xf7, 0x11, 0x2c, 0xb7, 0xf3, 0x49, 0x37, 0x12, 0xbd, 0xe, 0x14, 0x27, 0xfc, 0x32, 0x5c, 0xec, 0xa6, 0xb9, 0x7f, 0xf9, 0xd7, 0x7b, 0xa6, 0x36, 0x9a, 0x47, 0x4a, 0x3, 0x1a, 0x95, 0xcf, 0x46, 0x10, 0xaf, 0xcc, 0x90, 0x49, 0xb2, 0xce, 0xbf, 0x63, 0xaa, 0xc7, 0x25, 0x4d, 0x2a, 0x8, 0x36, 0xda, 0xd5, 0x9f, 0x9d, 0x63, 0x69, 0x22, 0xb3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x30, 0x9d, 0x84, 0x29, 0xbf, 0xd4, 0xeb, 0xc5, 0xc9, 0x29, 0xef, 0xdd, 0xd3, 0x2f, 0xa6, 0x25, 0x63, 0xda, 0xd9, 0x2c, 0xa1, 0x4a, 0xba, 0x75, 0xb2, 0x85, 0x25, 0x8f, 0x2b, 0x84, 0xcd, 0x99, 0x36, 0xd9, 0x6e, 0xf, 0xae, 0x7b, 0xd1, 0x61, 0x59, 0xf, 0x36, 0x4a, 0x22, 0xec, 0xde, 0x45, 0x32, 0xe0, 0x5b, 0x5c, 0xe3, 0x14, 0x29, 0x4, 0x60, 0x7b, 0xce, 0xa3, 0x89, 0x6b, 0xbb, 0xe0}, tr.Payload) + assert.Equal(t, int(transmission.Report.Info.ReportFormat), int(tr.ReportFormat)) + case <-time.After(testutils.WaitTimeout(t)): + t.Fatal("expected a transmit request to be sent") + } + + q.Close() + wg.Wait() + }) + t.Run("on transmit error, retries", func(t *testing.T) { + transmit := make(chan *pb.TransmitRequest, 1) + c.TransmitF = func(ctx context.Context, in *pb.TransmitRequest) (*pb.TransmitResponse, error) { + transmit <- in + return &pb.TransmitResponse{}, errors.New("transmission error") + } + q := newMockQ() + s.q = q + wg := &sync.WaitGroup{} + wg.Add(1) + stopCh := make(chan struct{}, 1) + + go s.runQueueLoop(stopCh, wg, donIDStr) + + transmission := makeSampleTransmission(1) + q.Push(transmission) + + cnt := 0 + Loop: + for { + select { + case tr := <-transmit: + assert.Equal(t, []byte{0x0, 0x9, 0x57, 0xdd, 0x2f, 0x63, 0x56, 0x69, 0x34, 0xfd, 0xc2, 0xe1, 0xcd, 0xc1, 0xe, 0x3e, 0x25, 0xb9, 0x26, 0x5a, 0x16, 0x23, 0x91, 0xa6, 0x53, 0x16, 0x66, 0x59, 0x51, 0x0, 0x28, 0x7c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x80, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x20, 0x0, 0x3, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x66, 0xde, 0xf5, 0xba, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x66, 0xde, 0xf5, 0xba, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1e, 0x8e, 0x95, 0xcf, 0xb5, 0xd8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1a, 0xd0, 0x1c, 0x67, 0xa9, 0xcf, 0xb3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x66, 0xdf, 0x3, 0xca, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x1c, 0x93, 0x6d, 0xa4, 0xf2, 0x17, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x14, 0x8d, 0x9a, 0xc1, 0xd9, 0x6f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x40, 0x5c, 0xcf, 0xa1, 0xbc, 0x63, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x9d, 0xab, 0x8f, 0xa7, 0xca, 0x7, 0x62, 0x57, 0xf7, 0x11, 0x2c, 0xb7, 0xf3, 0x49, 0x37, 0x12, 0xbd, 0xe, 0x14, 0x27, 0xfc, 0x32, 0x5c, 0xec, 0xa6, 0xb9, 0x7f, 0xf9, 0xd7, 0x7b, 0xa6, 0x36, 0x9a, 0x47, 0x4a, 0x3, 0x1a, 0x95, 0xcf, 0x46, 0x10, 0xaf, 0xcc, 0x90, 0x49, 0xb2, 0xce, 0xbf, 0x63, 0xaa, 0xc7, 0x25, 0x4d, 0x2a, 0x8, 0x36, 0xda, 0xd5, 0x9f, 0x9d, 0x63, 0x69, 0x22, 0xb3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x30, 0x9d, 0x84, 0x29, 0xbf, 0xd4, 0xeb, 0xc5, 0xc9, 0x29, 0xef, 0xdd, 0xd3, 0x2f, 0xa6, 0x25, 0x63, 0xda, 0xd9, 0x2c, 0xa1, 0x4a, 0xba, 0x75, 0xb2, 0x85, 0x25, 0x8f, 0x2b, 0x84, 0xcd, 0x99, 0x36, 0xd9, 0x6e, 0xf, 0xae, 0x7b, 0xd1, 0x61, 0x59, 0xf, 0x36, 0x4a, 0x22, 0xec, 0xde, 0x45, 0x32, 0xe0, 0x5b, 0x5c, 0xe3, 0x14, 0x29, 0x4, 0x60, 0x7b, 0xce, 0xa3, 0x89, 0x6b, 0xbb, 0xe0}, tr.Payload) + assert.Equal(t, int(transmission.Report.Info.ReportFormat), int(tr.ReportFormat)) + if cnt > 2 { + break Loop + } + cnt++ + case <-time.After(testutils.WaitTimeout(t)): + t.Fatal("expected 3 transmit requests to be sent") + } + } + + close(stopCh) + wg.Wait() + }) +} diff --git a/core/services/llo/offchain_config_digester.go b/core/services/llo/offchain_config_digester.go deleted file mode 100644 index cd4d9afa3a..0000000000 --- a/core/services/llo/offchain_config_digester.go +++ /dev/null @@ -1,123 +0,0 @@ -package llo - -import ( - "crypto/ed25519" - "encoding/binary" - "encoding/hex" - "fmt" - "math/big" - "strings" - - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/pkg/errors" - - "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - "github.com/smartcontractkit/wsrpc/credentials" - - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/exposed_channel_verifier" -) - -// Originally sourced from: https://github.com/smartcontractkit/offchain-reporting/blob/991ebe1462fd56826a1ddfb34287d542acb2baee/lib/offchainreporting2/chains/evmutil/offchain_config_digester.go - -var _ ocrtypes.OffchainConfigDigester = OffchainConfigDigester{} - -func NewOffchainConfigDigester(chainID *big.Int, contractAddress common.Address) OffchainConfigDigester { - return OffchainConfigDigester{chainID, contractAddress} -} - -type OffchainConfigDigester struct { - ChainID *big.Int - ContractAddress common.Address -} - -func (d OffchainConfigDigester) ConfigDigest(cc ocrtypes.ContractConfig) (ocrtypes.ConfigDigest, error) { - signers := []common.Address{} - for i, signer := range cc.Signers { - if len(signer) != 20 { - return ocrtypes.ConfigDigest{}, errors.Errorf("%v-th evm signer should be a 20 byte address, but got %x", i, signer) - } - a := common.BytesToAddress(signer) - signers = append(signers, a) - } - transmitters := []credentials.StaticSizedPublicKey{} - for i, transmitter := range cc.Transmitters { - if len(transmitter) != 2*ed25519.PublicKeySize { - return ocrtypes.ConfigDigest{}, errors.Errorf("%v-th evm transmitter should be a 64 character hex-encoded ed25519 public key, but got '%v' (%d chars)", i, transmitter, len(transmitter)) - } - var t credentials.StaticSizedPublicKey - b, err := hex.DecodeString(string(transmitter)) - if err != nil { - return ocrtypes.ConfigDigest{}, errors.Wrapf(err, "%v-th evm transmitter is not valid hex, got: %q", i, transmitter) - } - copy(t[:], b) - - transmitters = append(transmitters, t) - } - - return configDigest( - d.ChainID, - d.ContractAddress, - cc.ConfigCount, - signers, - transmitters, - cc.F, - cc.OnchainConfig, - cc.OffchainConfigVersion, - cc.OffchainConfig, - ) -} - -func (d OffchainConfigDigester) ConfigDigestPrefix() (ocrtypes.ConfigDigestPrefix, error) { - return ocrtypes.ConfigDigestPrefixLLO, nil -} - -func makeConfigDigestArgs() abi.Arguments { - abi, err := abi.JSON(strings.NewReader(exposed_channel_verifier.ExposedChannelVerifierABI)) - if err != nil { - // assertion - panic(fmt.Sprintf("could not parse aggregator ABI: %s", err.Error())) - } - return abi.Methods["exposedConfigDigestFromConfigData"].Inputs -} - -var configDigestArgs = makeConfigDigestArgs() - -func configDigest( - chainID *big.Int, - contractAddress common.Address, - configCount uint64, - oracles []common.Address, - transmitters []credentials.StaticSizedPublicKey, - f uint8, - onchainConfig []byte, - offchainConfigVersion uint64, - offchainConfig []byte, -) (types.ConfigDigest, error) { - msg, err := configDigestArgs.Pack( - chainID, - contractAddress, - configCount, - oracles, - transmitters, - f, - onchainConfig, - offchainConfigVersion, - offchainConfig, - ) - if err != nil { - return types.ConfigDigest{}, fmt.Errorf("could not pack config digest args: %v", err) - } - rawHash := crypto.Keccak256(msg) - configDigest := types.ConfigDigest{} - if n := copy(configDigest[:], rawHash); n != len(configDigest) { - return types.ConfigDigest{}, fmt.Errorf("copied too little data: %d/%d", n, len(configDigest)) - } - binary.BigEndian.PutUint16(configDigest[:2], uint16(ocrtypes.ConfigDigestPrefixLLO)) - if !(configDigest[0] == 0 && configDigest[1] == 9) { - return types.ConfigDigest{}, fmt.Errorf("wrong ConfigDigestPrefix; got: %x, expected: %d", configDigest[:2], ocrtypes.ConfigDigestPrefixLLO) - } - return configDigest, nil -} diff --git a/core/services/llo/offchain_config_digester_test.go b/core/services/llo/offchain_config_digester_test.go deleted file mode 100644 index 0de9117e39..0000000000 --- a/core/services/llo/offchain_config_digester_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package llo - -import ( - "math/big" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - "github.com/stretchr/testify/require" -) - -func Test_OffchainConfigDigester_ConfigDigest(t *testing.T) { - // ChainID and ContractAddress are taken into account for computation - cd1, err := OffchainConfigDigester{ChainID: big.NewInt(0)}.ConfigDigest(types.ContractConfig{}) - require.NoError(t, err) - cd2, err := OffchainConfigDigester{ChainID: big.NewInt(0)}.ConfigDigest(types.ContractConfig{}) - require.NoError(t, err) - cd3, err := OffchainConfigDigester{ChainID: big.NewInt(1)}.ConfigDigest(types.ContractConfig{}) - require.NoError(t, err) - cd4, err := OffchainConfigDigester{ChainID: big.NewInt(1), ContractAddress: common.Address{1}}.ConfigDigest(types.ContractConfig{}) - require.NoError(t, err) - - require.Equal(t, cd1, cd2) - require.NotEqual(t, cd2, cd3) - require.NotEqual(t, cd2, cd4) - require.NotEqual(t, cd3, cd4) - - // malformed signers - _, err = OffchainConfigDigester{}.ConfigDigest(types.ContractConfig{ - Signers: []types.OnchainPublicKey{{1, 2}}, - }) - require.Error(t, err) - - // malformed transmitters - _, err = OffchainConfigDigester{}.ConfigDigest(types.ContractConfig{ - Transmitters: []types.Account{"0x"}, - }) - require.Error(t, err) - - _, err = OffchainConfigDigester{}.ConfigDigest(types.ContractConfig{ - Transmitters: []types.Account{"7343581f55146951b0f678dc6cfa8fd360e2f353"}, - }) - require.Error(t, err) - - _, err = OffchainConfigDigester{}.ConfigDigest(types.ContractConfig{ - Transmitters: []types.Account{"7343581f55146951b0f678dc6cfa8fd360e2f353aabbccddeeffaaccddeeffaz"}, - }) - require.Error(t, err) - - // well-formed transmitters - _, err = OffchainConfigDigester{ChainID: big.NewInt(0)}.ConfigDigest(types.ContractConfig{ - Transmitters: []types.Account{"7343581f55146951b0f678dc6cfa8fd360e2f353aabbccddeeffaaccddeeffaa"}, - }) - require.NoError(t, err) -} diff --git a/core/services/llo/onchain_channel_definition_cache.go b/core/services/llo/onchain_channel_definition_cache.go index 4d3fb0e825..0f6e8c161c 100644 --- a/core/services/llo/onchain_channel_definition_cache.go +++ b/core/services/llo/onchain_channel_definition_cache.go @@ -1,34 +1,53 @@ package llo import ( + "bytes" "context" "database/sql" + "encoding/json" "errors" "fmt" + "io" "maps" + "math/big" + "net/http" "strings" "sync" "time" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" + "golang.org/x/crypto/sha3" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/channel_config_store" - "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/utils" + clhttp "github.com/smartcontractkit/chainlink/v2/core/utils/http" ) -type ChannelDefinitionCacheORM interface { - // TODO: What about delete/cleanup? - // https://smartcontract-it.atlassian.net/browse/MERC-3653 - LoadChannelDefinitions(ctx context.Context, addr common.Address) (dfns llotypes.ChannelDefinitions, blockNum int64, err error) - StoreChannelDefinitions(ctx context.Context, addr common.Address, dfns llotypes.ChannelDefinitions, blockNum int64) (err error) -} +const ( + // MaxChannelDefinitionsFileSize is a sanity limit to avoid OOM for a + // maliciously large file. It should be much larger than any real expected + // channel definitions file. + MaxChannelDefinitionsFileSize = 25 * 1024 * 1024 // 25MB + // How often we query logpoller for new logs + defaultLogPollInterval = 1 * time.Second + // How often we check for failed persistence and attempt to save again + dbPersistLoopInterval = 1 * time.Second + + newChannelDefinitionEventName = "NewChannelDefinition" +) -var channelConfigStoreABI abi.ABI +var ( + channelConfigStoreABI abi.ABI + topicNewChannelDefinition = (channel_config_store.ChannelConfigStoreNewChannelDefinition{}).Topic() + + allTopics = []common.Hash{topicNewChannelDefinition} +) func init() { var err error @@ -38,82 +57,130 @@ func init() { } } +type ChannelDefinitionCacheORM interface { + LoadChannelDefinitions(ctx context.Context, addr common.Address, donID uint32) (pd *PersistedDefinitions, err error) + StoreChannelDefinitions(ctx context.Context, addr common.Address, donID, version uint32, dfns llotypes.ChannelDefinitions, blockNum int64) (err error) + CleanupChannelDefinitions(ctx context.Context, addr common.Address, donID uint32) error +} + var _ llotypes.ChannelDefinitionCache = &channelDefinitionCache{} +type LogPoller interface { + UnregisterFilter(ctx context.Context, filterName string) error + RegisterFilter(ctx context.Context, filter logpoller.Filter) error + LatestBlock(ctx context.Context) (logpoller.LogPollerBlock, error) + LogsWithSigs(ctx context.Context, start, end int64, eventSigs []common.Hash, address common.Address) ([]logpoller.Log, error) +} + +type Option func(*channelDefinitionCache) + +func WithLogPollInterval(d time.Duration) Option { + return func(c *channelDefinitionCache) { + c.logPollInterval = d + } +} + type channelDefinitionCache struct { services.StateMachine - orm ChannelDefinitionCacheORM + orm ChannelDefinitionCacheORM + client HTTPClient + httpLimit int64 - filterName string - lp logpoller.LogPoller - fromBlock int64 - addr common.Address - lggr logger.Logger + filterName string + lp LogPoller + logPollInterval time.Duration + addr common.Address + donID uint32 + lggr logger.SugaredLogger + initialBlockNum int64 + + newLogMu sync.RWMutex + newLog *channel_config_store.ChannelConfigStoreNewChannelDefinition + newLogCh chan *channel_config_store.ChannelConfigStoreNewChannelDefinition definitionsMu sync.RWMutex definitions llotypes.ChannelDefinitions + definitionsVersion uint32 definitionsBlockNum int64 + persistMu sync.RWMutex + persistedVersion uint32 + wg sync.WaitGroup chStop chan struct{} } -var ( - topicNewChannelDefinition = (channel_config_store.ChannelConfigStoreNewChannelDefinition{}).Topic() - topicChannelDefinitionRemoved = (channel_config_store.ChannelConfigStoreChannelDefinitionRemoved{}).Topic() +type HTTPClient interface { + Do(req *http.Request) (*http.Response, error) +} - allTopics = []common.Hash{topicNewChannelDefinition, topicChannelDefinitionRemoved} -) +func filterName(addr common.Address, donID uint32) string { + return logpoller.FilterName("OCR3 LLO ChannelDefinitionCachePoller", addr.String(), fmt.Sprintf("%d", donID)) +} -func NewChannelDefinitionCache(lggr logger.Logger, orm ChannelDefinitionCacheORM, lp logpoller.LogPoller, addr common.Address, fromBlock int64) llotypes.ChannelDefinitionCache { - filterName := logpoller.FilterName("OCR3 LLO ChannelDefinitionCachePoller", addr.String()) - return &channelDefinitionCache{ - services.StateMachine{}, - orm, - filterName, - lp, - 0, - addr, - lggr.Named("ChannelDefinitionCache").With("addr", addr, "fromBlock", fromBlock), - sync.RWMutex{}, - nil, - fromBlock, - sync.WaitGroup{}, - make(chan struct{}), +func NewChannelDefinitionCache(lggr logger.Logger, orm ChannelDefinitionCacheORM, client HTTPClient, lp logpoller.LogPoller, addr common.Address, donID uint32, fromBlock int64, options ...Option) llotypes.ChannelDefinitionCache { + filterName := logpoller.FilterName("OCR3 LLO ChannelDefinitionCachePoller", addr.String(), donID) + cdc := &channelDefinitionCache{ + orm: orm, + client: client, + httpLimit: MaxChannelDefinitionsFileSize, + filterName: filterName, + lp: lp, + logPollInterval: defaultLogPollInterval, + addr: addr, + donID: donID, + lggr: logger.Sugared(lggr).Named("ChannelDefinitionCache").With("addr", addr, "fromBlock", fromBlock), + newLogCh: make(chan *channel_config_store.ChannelConfigStoreNewChannelDefinition, 1), + initialBlockNum: fromBlock, + chStop: make(chan struct{}), } + for _, option := range options { + option(cdc) + } + return cdc } func (c *channelDefinitionCache) Start(ctx context.Context) error { // Initial load from DB, then async poll from chain thereafter return c.StartOnce("ChannelDefinitionCache", func() (err error) { - err = c.lp.RegisterFilter(ctx, logpoller.Filter{Name: c.filterName, EventSigs: allTopics, Addresses: []common.Address{c.addr}}) + donIDTopic := common.BigToHash(big.NewInt(int64(c.donID))) + err = c.lp.RegisterFilter(ctx, logpoller.Filter{Name: c.filterName, EventSigs: allTopics, Topic2: []common.Hash{donIDTopic}, Addresses: []common.Address{c.addr}}) if err != nil { return err } - if definitions, definitionsBlockNum, err := c.orm.LoadChannelDefinitions(ctx, c.addr); err != nil { + if pd, err := c.orm.LoadChannelDefinitions(ctx, c.addr, c.donID); err != nil { return err - } else if definitions != nil { - c.definitions = definitions - c.definitionsBlockNum = definitionsBlockNum + } else if pd != nil { + c.definitions = pd.Definitions + c.initialBlockNum = pd.BlockNum + 1 + c.definitionsVersion = pd.Version } else { // ensure non-nil map ready for assignment later c.definitions = make(llotypes.ChannelDefinitions) - // leave c.definitionsBlockNum as provided fromBlock argument + // leave c.initialBlockNum as provided fromBlock argument } - c.wg.Add(1) - go c.poll() + c.wg.Add(3) + // We have three concurrent loops + // 1. Poll chain for new logs + // 2. Fetch latest definitions from URL and verify SHA, according to latest log + // 3. Retry persisting records to DB, if it failed + go c.pollChainLoop() + go c.fetchLatestLoop() + go c.failedPersistLoop() return nil }) } -// TODO: make this configurable? -const pollInterval = 1 * time.Second +//////////////////////////////////////////////////////////////////// +// Log Polling +//////////////////////////////////////////////////////////////////// -func (c *channelDefinitionCache) poll() { +// pollChainLoop periodically checks logpoller for new logs +func (c *channelDefinitionCache) pollChainLoop() { defer c.wg.Done() - pollT := services.NewTicker(pollInterval) + pollT := services.NewTicker(c.logPollInterval) defer pollT.Stop() for { @@ -121,114 +188,304 @@ func (c *channelDefinitionCache) poll() { case <-c.chStop: return case <-pollT.C: - if n, err := c.fetchFromChain(); err != nil { - // TODO: retry with backoff? - // https://smartcontract-it.atlassian.net/browse/MERC-3653 + // failures will be tried again on the next tick + if err := c.readLogs(); err != nil { c.lggr.Errorw("Failed to fetch channel definitions from chain", "err", err) continue - } else { - if n > 0 { - c.lggr.Infow("Updated channel definitions", "nLogs", n, "definitionsBlockNum", c.definitionsBlockNum) - } else { - c.lggr.Debugw("No new channel definitions", "nLogs", 0, "definitionsBlockNum", c.definitionsBlockNum) - } } } } } -func (c *channelDefinitionCache) fetchFromChain() (nLogs int, err error) { - // TODO: Pass context +func (c *channelDefinitionCache) readLogs() (err error) { ctx, cancel := services.StopChan(c.chStop).NewCtx() defer cancel() - // https://smartcontract-it.atlassian.net/browse/MERC-3653 - latest, err := c.lp.LatestBlock(ctx) + latestBlock, err := c.lp.LatestBlock(ctx) if errors.Is(err, sql.ErrNoRows) { c.lggr.Debug("Logpoller has no logs yet, skipping poll") - return 0, nil + return nil } else if err != nil { - return 0, err + return err } - toBlock := latest.BlockNumber + toBlock := latestBlock.BlockNumber - fromBlock := c.definitionsBlockNum + fromBlock := c.scanFromBlockNum() if toBlock <= fromBlock { - return 0, nil + return nil } - // NOTE: We assume that log poller returns logs in ascending order chronologically + // NOTE: We assume that log poller returns logs in order of block_num, log_index ASC logs, err := c.lp.LogsWithSigs(ctx, fromBlock, toBlock, allTopics, c.addr) if err != nil { - // TODO: retry? - // https://smartcontract-it.atlassian.net/browse/MERC-3653 - return 0, err + return err } + for _, log := range logs { - if err = c.applyLog(log); err != nil { - return 0, err + switch log.EventSig { + case topicNewChannelDefinition: + unpacked := new(channel_config_store.ChannelConfigStoreNewChannelDefinition) + + err := channelConfigStoreABI.UnpackIntoInterface(unpacked, newChannelDefinitionEventName, log.Data) + if err != nil { + return fmt.Errorf("failed to unpack log data: %w", err) + } + if len(log.Topics) < 2 { + // should never happen but must guard against unexpected panics + c.lggr.Warnw("Log missing expected topics", "log", log) + continue + } + unpacked.DonId = new(big.Int).SetBytes(log.Topics[1]) + + if unpacked.DonId.Cmp(big.NewInt(int64(c.donID))) != 0 { + c.lggr.Warnw("Got log for unexpected donID", "donID", unpacked.DonId.String(), "expectedDonID", c.donID) + // ignore logs for other donIDs + // NOTE: shouldn't happen anyway since log poller filters on + // donID + continue + } + + c.newLogMu.Lock() + if c.newLog == nil || unpacked.Version > c.newLog.Version { + // assume that donID is correct due to log poller filtering + c.lggr.Infow("Got new channel definitions from chain", "version", unpacked.Version, "blockNumber", log.BlockNumber, "sha", fmt.Sprintf("%x", unpacked.Sha), "url", unpacked.Url) + c.newLog = unpacked + c.newLogCh <- unpacked + } + c.newLogMu.Unlock() + + default: + // ignore unrecognized logs + continue } } - // Use context.Background() here because we want to try to save even if we - // are closing - if err = c.orm.StoreChannelDefinitions(context.Background(), c.addr, c.Definitions(), toBlock); err != nil { - return 0, err + return nil +} + +func (c *channelDefinitionCache) scanFromBlockNum() int64 { + c.newLogMu.RLock() + defer c.newLogMu.RUnlock() + if c.newLog != nil { + return int64(c.newLog.Raw.BlockNumber) + 1 } + return c.initialBlockNum +} + +//////////////////////////////////////////////////////////////////// +// Fetch channel definitions from URL based on latest log +//////////////////////////////////////////////////////////////////// + +// fetchLatestLoop waits for new logs and tries on a loop to fetch the channel definitions from the specified url +func (c *channelDefinitionCache) fetchLatestLoop() { + defer c.wg.Done() + + var fetchCh chan struct{} + + for { + select { + case latest := <-c.newLogCh: + // kill the old retry loop if any + if fetchCh != nil { + close(fetchCh) + } + + fetchCh = make(chan struct{}) - c.definitionsBlockNum = toBlock + c.wg.Add(1) + go c.fetchLoop(fetchCh, latest) - return len(logs), nil + case <-c.chStop: + return + } + } } -func (c *channelDefinitionCache) applyLog(log logpoller.Log) error { - switch log.EventSig { - case topicNewChannelDefinition: - unpacked := new(channel_config_store.ChannelConfigStoreNewChannelDefinition) +func (c *channelDefinitionCache) fetchLoop(closeCh chan struct{}, log *channel_config_store.ChannelConfigStoreNewChannelDefinition) { + defer c.wg.Done() + b := utils.NewHTTPFetchBackoff() + var attemptCnt int - err := channelConfigStoreABI.UnpackIntoInterface(unpacked, "NewChannelDefinition", log.Data) - if err != nil { - return fmt.Errorf("failed to unpack log data: %w", err) + ctx, cancel := services.StopChan(c.chStop).NewCtx() + defer cancel() + + err := c.fetchAndSetChannelDefinitions(ctx, log) + if err == nil { + c.lggr.Debugw("Set new channel definitions", "donID", c.donID, "version", log.Version, "url", log.Url, "sha", fmt.Sprintf("%x", log.Sha)) + return + } + c.lggr.Warnw("Error while fetching channel definitions", "donID", c.donID, "version", log.Version, "url", log.Url, "sha", fmt.Sprintf("%x", log.Sha), "err", err, "attempt", attemptCnt) + + for { + select { + case <-closeCh: + return + case <-time.After(b.Duration()): + attemptCnt++ + err := c.fetchAndSetChannelDefinitions(ctx, log) + if err != nil { + c.lggr.Warnw("Error while fetching channel definitions", "version", log.Version, "url", log.Url, "sha", fmt.Sprintf("%x", log.Sha), "err", err, "attempt", attemptCnt) + continue + } + c.lggr.Debugw("Set new channel definitions", "donID", c.donID, "version", log.Version, "url", log.Url, "sha", fmt.Sprintf("%x", log.Sha)) + return } + } +} - c.applyNewChannelDefinition(unpacked) - case topicChannelDefinitionRemoved: - unpacked := new(channel_config_store.ChannelConfigStoreChannelDefinitionRemoved) +func (c *channelDefinitionCache) fetchAndSetChannelDefinitions(ctx context.Context, log *channel_config_store.ChannelConfigStoreNewChannelDefinition) error { + c.definitionsMu.RLock() + if log.Version <= c.definitionsVersion { + c.definitionsMu.RUnlock() + return nil + } + c.definitionsMu.RUnlock() - err := channelConfigStoreABI.UnpackIntoInterface(unpacked, "ChannelDefinitionRemoved", log.Data) + cd, err := c.fetchChannelDefinitions(ctx, log.Url, log.Sha) + if err != nil { + return err + } + c.definitionsMu.Lock() + if log.Version <= c.definitionsVersion { + c.definitionsMu.Unlock() + return nil + } + c.definitions = cd + c.definitionsBlockNum = int64(log.Raw.BlockNumber) + c.definitionsVersion = log.Version + c.definitionsMu.Unlock() + + if memoryVersion, persistedVersion, err := c.persist(context.Background()); err != nil { + // If this fails, the failedPersistLoop will try again + c.lggr.Warnw("Failed to persist channel definitions", "err", err, "memoryVersion", memoryVersion, "persistedVersion", persistedVersion) + } + + return nil +} + +func (c *channelDefinitionCache) fetchChannelDefinitions(ctx context.Context, url string, expectedSha [32]byte) (llotypes.ChannelDefinitions, error) { + request, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, fmt.Errorf("failed to create http.Request; %w", err) + } + request.Header.Set("Content-Type", "application/json") + + httpRequest := clhttp.HTTPRequest{ + Client: c.client, + Request: request, + Config: clhttp.HTTPRequestConfig{SizeLimit: c.httpLimit}, + Logger: c.lggr.Named("HTTPRequest").With("url", url, "expectedSHA", fmt.Sprintf("%x", expectedSha)), + } + + reader, statusCode, _, err := httpRequest.SendRequestReader() + if err != nil { + return nil, fmt.Errorf("error making http request: %w", err) + } + defer reader.Close() + + if statusCode >= 400 { + // NOTE: Truncate the returned body here as we don't want to spam the + // logs with potentially huge messages + body := http.MaxBytesReader(nil, reader, 1024) + defer body.Close() + bodyBytes, err := io.ReadAll(body) if err != nil { - return fmt.Errorf("failed to unpack log data: %w", err) + return nil, fmt.Errorf("got error from %s: (status code: %d, error reading response body: %w, response body: %s)", url, statusCode, err, bodyBytes) } + return nil, fmt.Errorf("got error from %s: (status code: %d, response body: %s)", url, statusCode, string(bodyBytes)) + } + + var buf bytes.Buffer + // Use a teeReader to avoid excessive copying + teeReader := io.TeeReader(reader, &buf) - c.applyChannelDefinitionRemoved(unpacked) - default: - // don't return error here, we want to ignore unrecognized logs and - // continue rather than interrupting the loop - c.lggr.Errorw("Unexpected log topic", "topic", log.EventSig.Hex()) + hash := sha3.New256() + // Stream the data directly into the hash and copy to buf as we go + if _, err := io.Copy(hash, teeReader); err != nil { + return nil, fmt.Errorf("failed to read from body: %w", err) } - return nil + + actualSha := hash.Sum(nil) + if !bytes.Equal(expectedSha[:], actualSha) { + return nil, fmt.Errorf("SHA3 mismatch: expected %x, got %x", expectedSha, actualSha) + } + + var cd llotypes.ChannelDefinitions + decoder := json.NewDecoder(&buf) + if err := decoder.Decode(&cd); err != nil { + return nil, fmt.Errorf("failed to decode JSON: %w", err) + } + + return cd, nil } -func (c *channelDefinitionCache) applyNewChannelDefinition(log *channel_config_store.ChannelConfigStoreNewChannelDefinition) { - streamIDs := make([]llotypes.StreamID, len(log.ChannelDefinition.StreamIDs)) - copy(streamIDs, log.ChannelDefinition.StreamIDs) - c.definitionsMu.Lock() - defer c.definitionsMu.Unlock() - c.definitions[log.ChannelId] = llotypes.ChannelDefinition{ - ReportFormat: llotypes.ReportFormat(log.ChannelDefinition.ReportFormat), +//////////////////////////////////////////////////////////////////// +// Persistence +//////////////////////////////////////////////////////////////////// + +func (c *channelDefinitionCache) persist(ctx context.Context) (memoryVersion, persistedVersion uint32, err error) { + c.persistMu.RLock() + persistedVersion = c.persistedVersion + c.persistMu.RUnlock() + + c.definitionsMu.RLock() + memoryVersion = c.definitionsVersion + dfns := c.definitions + blockNum := c.definitionsBlockNum + c.definitionsMu.RUnlock() + + if memoryVersion <= persistedVersion { + return + } + + if err = c.orm.StoreChannelDefinitions(ctx, c.addr, c.donID, memoryVersion, dfns, blockNum); err != nil { + return } + + c.persistMu.Lock() + defer c.persistMu.Unlock() + if memoryVersion > c.persistedVersion { + persistedVersion = memoryVersion + c.persistedVersion = persistedVersion + } + + // TODO: we could delete the old logs from logpoller here actually + // https://smartcontract-it.atlassian.net/browse/MERC-3653 + return } -func (c *channelDefinitionCache) applyChannelDefinitionRemoved(log *channel_config_store.ChannelConfigStoreChannelDefinitionRemoved) { - c.definitionsMu.Lock() - defer c.definitionsMu.Unlock() - delete(c.definitions, log.ChannelId) +// nolint:revive +// Checks persisted version and tries to save if necessary on a periodic timer +// Simple backup in case database persistence fails +func (c *channelDefinitionCache) failedPersistLoop() { + defer c.wg.Done() + + ctx, cancel := services.StopChan(c.chStop).NewCtx() + defer cancel() + + for { + select { + case <-time.After(dbPersistLoopInterval): + if memoryVersion, persistedVersion, err := c.persist(ctx); err != nil { + c.lggr.Warnw("Failed to persist channel definitions", "err", err, "memoryVersion", memoryVersion, "persistedVersion", persistedVersion) + } + case <-c.chStop: + // Try one final persist with a short-ish timeout, then return + ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + if memoryVersion, persistedVersion, err := c.persist(ctx); err != nil { + c.lggr.Errorw("Failed to persist channel definitions on shutdown", "err", err, "memoryVersion", memoryVersion, "persistedVersion", persistedVersion) + } + return + } + } } func (c *channelDefinitionCache) Close() error { // TODO: unregister filter (on job delete)? // https://smartcontract-it.atlassian.net/browse/MERC-3653 return c.StopOnce("ChannelDefinitionCache", func() error { + // Cancel all contexts but try one final persist before closing close(c.chStop) c.wg.Wait() return nil diff --git a/core/services/llo/onchain_channel_definition_cache_test.go b/core/services/llo/onchain_channel_definition_cache_test.go index 2fbc0c1b90..78bbcef428 100644 --- a/core/services/llo/onchain_channel_definition_cache_test.go +++ b/core/services/llo/onchain_channel_definition_cache_test.go @@ -1,23 +1,453 @@ package llo import ( + "bytes" + "context" + "database/sql" + "errors" + "fmt" + "io" + "math/big" + "math/rand" + "net/http" "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/channel_config_store" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" ) +type mockLogPoller struct { + latestBlock logpoller.LogPollerBlock + latestBlockErr error + logsWithSigs []logpoller.Log + logsWithSigsErr error + + unregisteredFilterNames []string +} + +func (m *mockLogPoller) RegisterFilter(ctx context.Context, filter logpoller.Filter) error { + return nil +} +func (m *mockLogPoller) LatestBlock(ctx context.Context) (logpoller.LogPollerBlock, error) { + return m.latestBlock, m.latestBlockErr +} +func (m *mockLogPoller) LogsWithSigs(ctx context.Context, start, end int64, eventSigs []common.Hash, address common.Address) ([]logpoller.Log, error) { + return m.logsWithSigs, m.logsWithSigsErr +} +func (m *mockLogPoller) UnregisterFilter(ctx context.Context, name string) error { + m.unregisteredFilterNames = append(m.unregisteredFilterNames, name) + return nil +} + +var _ HTTPClient = &mockHTTPClient{} + +type mockHTTPClient struct { + resp *http.Response + err error +} + +func (m *mockHTTPClient) Do(req *http.Request) (*http.Response, error) { + return m.resp, m.err +} + +var _ ChannelDefinitionCacheORM = &mockORM{} + +type mockORM struct { + err error + + lastPersistedAddr common.Address + lastPersistedDonID uint32 + lastPersistedVersion uint32 + lastPersistedDfns llotypes.ChannelDefinitions + lastPersistedBlockNum int64 +} + +func (m *mockORM) LoadChannelDefinitions(ctx context.Context, addr common.Address, donID uint32) (pd *PersistedDefinitions, err error) { + panic("not implemented") +} +func (m *mockORM) StoreChannelDefinitions(ctx context.Context, addr common.Address, donID, version uint32, dfns llotypes.ChannelDefinitions, blockNum int64) (err error) { + m.lastPersistedAddr = addr + m.lastPersistedDonID = donID + m.lastPersistedVersion = version + m.lastPersistedDfns = dfns + m.lastPersistedBlockNum = blockNum + return m.err +} + +func (m *mockORM) CleanupChannelDefinitions(ctx context.Context, addr common.Address, donID uint32) (err error) { + panic("not implemented") +} + +func makeLog(t *testing.T, donID, version uint32, url string, sha [32]byte) logpoller.Log { + data := makeLogData(t, donID, version, url, sha) + return logpoller.Log{EventSig: topicNewChannelDefinition, Topics: [][]byte{topicNewChannelDefinition[:], makeDonIDTopic(donID)}, Data: data} +} + +func makeLogData(t *testing.T, donID, version uint32, url string, sha [32]byte) []byte { + event := channelConfigStoreABI.Events[newChannelDefinitionEventName] + // donID is indexed + // version, url, sha + data, err := event.Inputs.NonIndexed().Pack(version, url, sha) + require.NoError(t, err) + return data +} + +func makeDonIDTopic(donID uint32) []byte { + return common.BigToHash(big.NewInt(int64(donID))).Bytes() +} + func Test_ChannelDefinitionCache(t *testing.T) { - t.Skip("waiting on https://github.com/smartcontractkit/chainlink/pull/13780") - // t.Run("Definitions", func(t *testing.T) { - // // NOTE: this is covered more thoroughly in the integration tests - // dfns := llotypes.ChannelDefinitions(map[llotypes.ChannelID]llotypes.ChannelDefinition{ - // 1: { - // ReportFormat: llotypes.ReportFormat(43), - // ChainSelector: 42, - // StreamIDs: []llotypes.StreamID{1, 2, 3}, - // }, - // }) - - // cdc := &channelDefinitionCache{definitions: dfns} - - // assert.Equal(t, dfns, cdc.Definitions()) - // }) + donID := rand.Uint32() + + t.Run("Definitions", func(t *testing.T) { + // NOTE: this is covered more thoroughly in the integration tests + dfns := llotypes.ChannelDefinitions(map[llotypes.ChannelID]llotypes.ChannelDefinition{ + 1: { + ReportFormat: llotypes.ReportFormat(43), + Streams: []llotypes.Stream{{StreamID: 1, Aggregator: llotypes.AggregatorMedian}, {StreamID: 2, Aggregator: llotypes.AggregatorMode}, {StreamID: 3, Aggregator: llotypes.AggregatorQuote}}, + Opts: llotypes.ChannelOpts{1, 2, 3}, + }, + }) + + cdc := &channelDefinitionCache{definitions: dfns} + + assert.Equal(t, dfns, cdc.Definitions()) + }) + + t.Run("readLogs", func(t *testing.T) { + lp := &mockLogPoller{latestBlockErr: sql.ErrNoRows} + newLogCh := make(chan *channel_config_store.ChannelConfigStoreNewChannelDefinition, 100) + cdc := &channelDefinitionCache{donID: donID, lp: lp, lggr: logger.TestSugared(t), newLogCh: newLogCh} + + t.Run("skips if logpoller has no blocks", func(t *testing.T) { + err := cdc.readLogs() + assert.NoError(t, err) + assert.Nil(t, cdc.newLog) + }) + t.Run("returns error on LatestBlock failure", func(t *testing.T) { + lp.latestBlockErr = errors.New("test error") + + err := cdc.readLogs() + assert.EqualError(t, err, "test error") + assert.Nil(t, cdc.newLog) + }) + t.Run("does nothing if LatestBlock older or the same as current channel definitions block", func(t *testing.T) { + lp.latestBlockErr = nil + lp.latestBlock = logpoller.LogPollerBlock{BlockNumber: 42} + cdc.definitionsBlockNum = 43 + + err := cdc.readLogs() + assert.NoError(t, err) + assert.Nil(t, cdc.newLog) + }) + t.Run("returns error if LogsWithSigs fails", func(t *testing.T) { + cdc.definitionsBlockNum = 0 + lp.logsWithSigsErr = errors.New("test error 2") + + err := cdc.readLogs() + assert.EqualError(t, err, "test error 2") + assert.Nil(t, cdc.newLog) + }) + t.Run("ignores logs with different topic", func(t *testing.T) { + lp.logsWithSigsErr = nil + lp.logsWithSigs = []logpoller.Log{{EventSig: common.Hash{1, 2, 3, 4}}} + + err := cdc.readLogs() + assert.NoError(t, err) + assert.Nil(t, cdc.newLog) + }) + t.Run("returns error if log is malformed", func(t *testing.T) { + lp.logsWithSigsErr = nil + lp.logsWithSigs = []logpoller.Log{{EventSig: topicNewChannelDefinition}} + + err := cdc.readLogs() + assert.EqualError(t, err, "failed to unpack log data: abi: attempting to unmarshal an empty string while arguments are expected") + assert.Nil(t, cdc.newLog) + }) + t.Run("sets definitions and sends on channel if LogsWithSigs returns new event with a later version", func(t *testing.T) { + lp.logsWithSigsErr = nil + lp.logsWithSigs = []logpoller.Log{makeLog(t, donID, uint32(43), "http://example.com/xxx.json", [32]byte{1, 2, 3, 4})} + + err := cdc.readLogs() + require.NoError(t, err) + require.NotNil(t, cdc.newLog) + assert.Equal(t, uint32(43), cdc.newLog.Version) + assert.Equal(t, "http://example.com/xxx.json", cdc.newLog.Url) + assert.Equal(t, [32]byte{1, 2, 3, 4}, cdc.newLog.Sha) + assert.Equal(t, int64(donID), cdc.newLog.DonId.Int64()) + + func() { + for { + select { + case log := <-newLogCh: + assert.Equal(t, cdc.newLog, log) + default: + return + } + } + }() + }) + t.Run("does nothing if version older or the same as the one currently set", func(t *testing.T) { + lp.logsWithSigsErr = nil + lp.logsWithSigs = []logpoller.Log{ + makeLog(t, donID, uint32(42), "http://example.com/xxx.json", [32]byte{1, 2, 3, 4}), + makeLog(t, donID, uint32(43), "http://example.com/xxx.json", [32]byte{1, 2, 3, 4}), + } + + err := cdc.readLogs() + require.NoError(t, err) + assert.Equal(t, uint32(43), cdc.newLog.Version) + }) + t.Run("in case of multiple logs, takes the latest", func(t *testing.T) { + lp.logsWithSigsErr = nil + lp.logsWithSigs = []logpoller.Log{ + makeLog(t, donID, uint32(42), "http://example.com/xxx.json", [32]byte{1, 2, 3, 4}), + makeLog(t, donID, uint32(45), "http://example.com/xxx2.json", [32]byte{2, 2, 3, 4}), + makeLog(t, donID, uint32(44), "http://example.com/xxx3.json", [32]byte{3, 2, 3, 4}), + makeLog(t, donID, uint32(43), "http://example.com/xxx4.json", [32]byte{4, 2, 3, 4}), + } + + err := cdc.readLogs() + require.NoError(t, err) + assert.Equal(t, uint32(45), cdc.newLog.Version) + assert.Equal(t, "http://example.com/xxx2.json", cdc.newLog.Url) + assert.Equal(t, [32]byte{2, 2, 3, 4}, cdc.newLog.Sha) + assert.Equal(t, int64(donID), cdc.newLog.DonId.Int64()) + + func() { + for { + select { + case log := <-newLogCh: + assert.Equal(t, cdc.newLog, log) + default: + return + } + } + }() + }) + t.Run("ignores logs with incorrect don ID", func(t *testing.T) { + lp.logsWithSigsErr = nil + lp.logsWithSigs = []logpoller.Log{ + makeLog(t, donID+1, uint32(42), "http://example.com/xxx.json", [32]byte{1, 2, 3, 4}), + } + + err := cdc.readLogs() + require.NoError(t, err) + assert.Equal(t, uint32(45), cdc.newLog.Version) + + func() { + for { + select { + case log := <-newLogCh: + t.Fatal("did not expect log with wrong donID, got: ", log) + default: + return + } + } + }() + }) + t.Run("ignores logs with wrong number of topics", func(t *testing.T) { + lp.logsWithSigsErr = nil + lg := makeLog(t, donID, uint32(42), "http://example.com/xxx.json", [32]byte{1, 2, 3, 4}) + lg.Topics = lg.Topics[:1] + lp.logsWithSigs = []logpoller.Log{lg} + + err := cdc.readLogs() + require.NoError(t, err) + assert.Equal(t, uint32(45), cdc.newLog.Version) + + func() { + for { + select { + case log := <-newLogCh: + t.Fatal("did not expect log with missing topics, got: ", log) + default: + return + } + } + }() + }) + }) + + t.Run("fetchChannelDefinitions", func(t *testing.T) { + c := &mockHTTPClient{} + cdc := &channelDefinitionCache{ + lggr: logger.TestSugared(t), + client: c, + httpLimit: 2048, + } + + t.Run("nil ctx returns error", func(t *testing.T) { + _, err := cdc.fetchChannelDefinitions(nil, "notvalid://foos", [32]byte{}) //nolint + assert.EqualError(t, err, "failed to create http.Request; net/http: nil Context") + }) + + t.Run("networking error while making request returns error", func(t *testing.T) { + ctx := tests.Context(t) + c.resp = nil + c.err = errors.New("http request failed") + + _, err := cdc.fetchChannelDefinitions(ctx, "http://example.com/definitions.json", [32]byte{}) + assert.EqualError(t, err, "error making http request: http request failed") + }) + + t.Run("server returns 500 returns error", func(t *testing.T) { + ctx := tests.Context(t) + c.err = nil + c.resp = &http.Response{StatusCode: 500, Body: io.NopCloser(bytes.NewReader([]byte{1, 2, 3}))} + + _, err := cdc.fetchChannelDefinitions(ctx, "http://example.com/definitions.json", [32]byte{}) + assert.EqualError(t, err, "got error from http://example.com/definitions.json: (status code: 500, response body: \x01\x02\x03)") + }) + + var largeBody = make([]byte, 2048) + for i := range largeBody { + largeBody[i] = 'a' + } + + t.Run("server returns 404 returns error (and does not log entirety of huge response body)", func(t *testing.T) { + ctx := tests.Context(t) + c.err = nil + c.resp = &http.Response{StatusCode: 404, Body: io.NopCloser(bytes.NewReader(largeBody))} + + _, err := cdc.fetchChannelDefinitions(ctx, "http://example.com/definitions.json", [32]byte{}) + assert.EqualError(t, err, "got error from http://example.com/definitions.json: (status code: 404, error reading response body: http: request body too large, response body: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)") + }) + + var hugeBody = make([]byte, 8096) + c.resp.Body = io.NopCloser(bytes.NewReader(hugeBody)) + + t.Run("server returns body that is too large", func(t *testing.T) { + ctx := tests.Context(t) + c.err = nil + c.resp = &http.Response{StatusCode: 200, Body: io.NopCloser(bytes.NewReader(hugeBody))} + + _, err := cdc.fetchChannelDefinitions(ctx, "http://example.com/definitions.json", [32]byte{}) + assert.EqualError(t, err, "failed to read from body: http: request body too large") + }) + + t.Run("server returns invalid JSON returns error", func(t *testing.T) { + ctx := tests.Context(t) + c.err = nil + c.resp = &http.Response{StatusCode: 200, Body: io.NopCloser(bytes.NewReader([]byte{1, 2, 3}))} + + _, err := cdc.fetchChannelDefinitions(ctx, "http://example.com/definitions.json", common.HexToHash("0xfd1780a6fc9ee0dab26ceb4b3941ab03e66ccd970d1db91612c66df4515b0a0a")) + assert.EqualError(t, err, "failed to decode JSON: invalid character '\\x01' looking for beginning of value") + }) + + t.Run("SHA mismatch returns error", func(t *testing.T) { + ctx := tests.Context(t) + c.err = nil + c.resp = &http.Response{StatusCode: 200, Body: io.NopCloser(bytes.NewReader([]byte(`{"foo":"bar"}`)))} + + _, err := cdc.fetchChannelDefinitions(ctx, "http://example.com/definitions.json", [32]byte{}) + assert.EqualError(t, err, "SHA3 mismatch: expected 0000000000000000000000000000000000000000000000000000000000000000, got 4d3304d0d87c27a031cbb6bdf95da79b7b4552c3d0bef2e5a94f50810121e1e0") + }) + + t.Run("valid JSON matching SHA returns channel definitions", func(t *testing.T) { + ctx := tests.Context(t) + chainSelector := 4949039107694359620 // arbitrum mainnet + feedID := [32]byte{00, 03, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58, 163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114} + expirationWindow := 3600 + multiplier := big.NewInt(1e18) + baseUSDFee := 10 + valid := fmt.Sprintf(` +{ + "42": { + "reportFormat": %d, + "chainSelector": %d, + "streams": [{"streamId": 52, "aggregator": %d}, {"streamId": 53, "aggregator": %d}, {"streamId": 55, "aggregator": %d}], + "opts": { + "feedId": "0x%x", + "expirationWindow": %d, + "multiplier": "%s", + "baseUSDFee": "%d" + } + } +}`, llotypes.ReportFormatEVMPremiumLegacy, chainSelector, llotypes.AggregatorMedian, llotypes.AggregatorMedian, llotypes.AggregatorQuote, feedID, expirationWindow, multiplier.String(), baseUSDFee) + + c.err = nil + c.resp = &http.Response{StatusCode: 200, Body: io.NopCloser(bytes.NewReader([]byte(valid)))} + + cd, err := cdc.fetchChannelDefinitions(ctx, "http://example.com/definitions.json", common.HexToHash("0x367bbc75f7b6c9fc66a98ea99f837ea7ac4a3c2d6a9ee284de018bd02c41b52d")) + assert.NoError(t, err) + assert.Equal(t, llotypes.ChannelDefinitions{0x2a: llotypes.ChannelDefinition{ReportFormat: 0x1, Streams: []llotypes.Stream{llotypes.Stream{StreamID: 0x34, Aggregator: 0x1}, llotypes.Stream{StreamID: 0x35, Aggregator: 0x1}, llotypes.Stream{StreamID: 0x37, Aggregator: 0x3}}, Opts: llotypes.ChannelOpts{0x7b, 0x22, 0x62, 0x61, 0x73, 0x65, 0x55, 0x53, 0x44, 0x46, 0x65, 0x65, 0x22, 0x3a, 0x22, 0x31, 0x30, 0x22, 0x2c, 0x22, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x22, 0x3a, 0x33, 0x36, 0x30, 0x30, 0x2c, 0x22, 0x66, 0x65, 0x65, 0x64, 0x49, 0x64, 0x22, 0x3a, 0x22, 0x30, 0x78, 0x30, 0x30, 0x30, 0x33, 0x36, 0x62, 0x34, 0x61, 0x61, 0x37, 0x65, 0x35, 0x37, 0x63, 0x61, 0x37, 0x62, 0x36, 0x38, 0x61, 0x65, 0x31, 0x62, 0x66, 0x34, 0x35, 0x36, 0x35, 0x33, 0x66, 0x35, 0x36, 0x62, 0x36, 0x35, 0x36, 0x66, 0x64, 0x33, 0x61, 0x61, 0x33, 0x33, 0x35, 0x65, 0x66, 0x37, 0x66, 0x61, 0x65, 0x36, 0x39, 0x36, 0x62, 0x36, 0x36, 0x33, 0x66, 0x31, 0x62, 0x38, 0x34, 0x37, 0x32, 0x22, 0x2c, 0x22, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x22, 0x3a, 0x22, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x22, 0x7d}}}, cd) + }) + }) + + t.Run("persist", func(t *testing.T) { + cdc := &channelDefinitionCache{ + lggr: logger.TestSugared(t), + orm: nil, + addr: testutils.NewAddress(), + donID: donID, + definitions: llotypes.ChannelDefinitions{ + 1: { + ReportFormat: llotypes.ReportFormat(43), + Streams: []llotypes.Stream{{StreamID: 1, Aggregator: llotypes.AggregatorMedian}, {StreamID: 2, Aggregator: llotypes.AggregatorMode}, {StreamID: 3, Aggregator: llotypes.AggregatorQuote}}, + Opts: llotypes.ChannelOpts{1, 2, 3}, + }, + }, + definitionsBlockNum: 142, + } + + t.Run("does nothing if persisted version is up-to-date", func(t *testing.T) { + ctx := tests.Context(t) + cdc.definitionsVersion = 42 + cdc.persistedVersion = 42 + + memoryVersion, persistedVersion, err := cdc.persist(ctx) + assert.NoError(t, err) + assert.Equal(t, uint32(42), memoryVersion) + assert.Equal(t, uint32(42), persistedVersion) + assert.Equal(t, uint32(42), cdc.persistedVersion) + }) + + orm := &mockORM{} + cdc.orm = orm + + t.Run("returns error on db failure and does not update persisted version", func(t *testing.T) { + ctx := tests.Context(t) + cdc.persistedVersion = 42 + cdc.definitionsVersion = 43 + orm.err = errors.New("test error") + + memoryVersion, persistedVersion, err := cdc.persist(ctx) + assert.EqualError(t, err, "test error") + assert.Equal(t, uint32(43), memoryVersion) + assert.Equal(t, uint32(42), persistedVersion) + assert.Equal(t, uint32(42), cdc.persistedVersion) + }) + + t.Run("updates persisted version on success", func(t *testing.T) { + ctx := tests.Context(t) + cdc.definitionsVersion = 43 + orm.err = nil + + memoryVersion, persistedVersion, err := cdc.persist(ctx) + assert.NoError(t, err) + assert.Equal(t, uint32(43), memoryVersion) + assert.Equal(t, uint32(43), persistedVersion) + assert.Equal(t, uint32(43), cdc.persistedVersion) + + assert.Equal(t, cdc.addr, orm.lastPersistedAddr) + assert.Equal(t, cdc.donID, orm.lastPersistedDonID) + assert.Equal(t, cdc.persistedVersion, orm.lastPersistedVersion) + assert.Equal(t, cdc.definitions, orm.lastPersistedDfns) + assert.Equal(t, cdc.definitionsBlockNum, orm.lastPersistedBlockNum) + }) + }) +} + +func Test_filterName(t *testing.T) { + s := filterName(common.Address{1, 2, 3}, 654) + assert.Equal(t, "OCR3 LLO ChannelDefinitionCachePoller - 0x0102030000000000000000000000000000000000:654", s) } diff --git a/core/services/llo/onchain_config.go b/core/services/llo/onchain_config.go deleted file mode 100644 index 7b5cfffaa9..0000000000 --- a/core/services/llo/onchain_config.go +++ /dev/null @@ -1,21 +0,0 @@ -package llo - -type OnchainConfig struct{} - -type OnchainConfigCodec interface { - Encode(OnchainConfig) ([]byte, error) - Decode([]byte) (OnchainConfig, error) -} - -var _ OnchainConfigCodec = &JSONOnchainConfigCodec{} - -// TODO: Replace this with protobuf, if it is actually used for something -type JSONOnchainConfigCodec struct{} - -func (c *JSONOnchainConfigCodec) Encode(OnchainConfig) ([]byte, error) { - return nil, nil -} - -func (c *JSONOnchainConfigCodec) Decode([]byte) (OnchainConfig, error) { - return OnchainConfig{}, nil -} diff --git a/core/services/llo/orm.go b/core/services/llo/orm.go index 6b14e54326..5b132e6537 100644 --- a/core/services/llo/orm.go +++ b/core/services/llo/orm.go @@ -3,10 +3,9 @@ package llo import ( "context" "database/sql" - "encoding/json" "errors" "fmt" - "math/big" + "time" "github.com/ethereum/go-ethereum/common" @@ -18,48 +17,60 @@ type ORM interface { ChannelDefinitionCacheORM } +type PersistedDefinitions struct { + ChainSelector uint64 `db:"chain_selector"` + Address common.Address `db:"addr"` + Definitions llotypes.ChannelDefinitions `db:"definitions"` + // The block number in which the log for this definitions was emitted + BlockNum int64 `db:"block_num"` + DonID uint32 `db:"don_id"` + Version uint32 `db:"version"` + UpdatedAt time.Time `db:"updated_at"` +} + var _ ORM = &orm{} type orm struct { - ds sqlutil.DataSource - evmChainID *big.Int + ds sqlutil.DataSource + chainSelector uint64 } -func NewORM(ds sqlutil.DataSource, evmChainID *big.Int) ORM { - return &orm{ds, evmChainID} +func NewORM(ds sqlutil.DataSource, chainSelector uint64) ORM { + return &orm{ds, chainSelector} } -func (o *orm) LoadChannelDefinitions(ctx context.Context, addr common.Address) (dfns llotypes.ChannelDefinitions, blockNum int64, err error) { - type scd struct { - Definitions []byte `db:"definitions"` - BlockNum int64 `db:"block_num"` - } - var scanned scd - err = o.ds.GetContext(ctx, &scanned, "SELECT definitions, block_num FROM channel_definitions WHERE evm_chain_id = $1 AND addr = $2", o.evmChainID.String(), addr) +func (o *orm) LoadChannelDefinitions(ctx context.Context, addr common.Address, donID uint32) (pd *PersistedDefinitions, err error) { + pd = new(PersistedDefinitions) + err = o.ds.GetContext(ctx, pd, "SELECT * FROM channel_definitions WHERE chain_selector = $1 AND addr = $2 AND don_id = $3", o.chainSelector, addr, donID) if errors.Is(err, sql.ErrNoRows) { - return dfns, blockNum, nil + return nil, nil } else if err != nil { - return nil, 0, fmt.Errorf("failed to LoadChannelDefinitions; %w", err) - } - - if err = json.Unmarshal(scanned.Definitions, &dfns); err != nil { - return nil, 0, fmt.Errorf("failed to LoadChannelDefinitions; JSON Unmarshal failure; %w", err) + return nil, fmt.Errorf("failed to LoadChannelDefinitions; %w", err) } - return dfns, scanned.BlockNum, nil + return pd, nil } -// TODO: Test this method -// https://smartcontract-it.atlassian.net/jira/software/c/projects/MERC/issues/MERC-3653 -func (o *orm) StoreChannelDefinitions(ctx context.Context, addr common.Address, dfns llotypes.ChannelDefinitions, blockNum int64) error { +// StoreChannelDefinitions will store a ChannelDefinitions list for a given chain_selector, addr, don_id +// It only updates if the new version is greater than the existing record +func (o *orm) StoreChannelDefinitions(ctx context.Context, addr common.Address, donID, version uint32, dfns llotypes.ChannelDefinitions, blockNum int64) error { _, err := o.ds.ExecContext(ctx, ` -INSERT INTO channel_definitions (evm_chain_id, addr, definitions, block_num, updated_at) -VALUES ($1, $2, $3, $4, NOW()) -ON CONFLICT (evm_chain_id, addr) DO UPDATE -SET definitions = $3, block_num = $4, updated_at = NOW() -`, o.evmChainID.String(), addr, dfns, blockNum) +INSERT INTO channel_definitions (chain_selector, addr, don_id, definitions, block_num, version, updated_at) +VALUES ($1, $2, $3, $4, $5, $6, NOW()) +ON CONFLICT (chain_selector, addr, don_id) DO UPDATE +SET definitions = $4, block_num = $5, version = $6, updated_at = NOW() +WHERE EXCLUDED.version > channel_definitions.version +`, o.chainSelector, addr, donID, dfns, blockNum, version) if err != nil { return fmt.Errorf("StoreChannelDefinitions failed: %w", err) } return nil } + +func (o *orm) CleanupChannelDefinitions(ctx context.Context, addr common.Address, donID uint32) error { + _, err := o.ds.ExecContext(ctx, "DELETE FROM channel_definitions WHERE chain_selector = $1 AND addr = $2 AND don_id = $3", o.chainSelector, addr, donID) + if err != nil { + return fmt.Errorf("failed to CleanupChannelDefinitions; %w", err) + } + return nil +} diff --git a/core/services/llo/orm_test.go b/core/services/llo/orm_test.go index bc2d88130e..ec3c06e6e6 100644 --- a/core/services/llo/orm_test.go +++ b/core/services/llo/orm_test.go @@ -1,91 +1,156 @@ package llo import ( + "fmt" + "math/rand" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" + + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" ) func Test_ORM(t *testing.T) { - t.Skip("waiting on https://github.com/smartcontractkit/chainlink/pull/13780") - // db := pgtest.NewSqlxDB(t) - // orm := NewORM(db, testutils.FixtureChainID) - // ctx := testutils.Context(t) - - // addr1 := testutils.NewAddress() - // addr2 := testutils.NewAddress() - // addr3 := testutils.NewAddress() - - // t.Run("LoadChannelDefinitions", func(t *testing.T) { - // t.Run("returns zero values if nothing in database", func(t *testing.T) { - // cd, blockNum, err := orm.LoadChannelDefinitions(ctx, addr1) - // require.NoError(t, err) - - // assert.Zero(t, cd) - // assert.Zero(t, blockNum) - // }) - // t.Run("loads channel definitions from database", func(t *testing.T) { - // expectedBlockNum := rand.Int63() - // expectedBlockNum2 := rand.Int63() - // cid1 := rand.Uint32() - // cid2 := rand.Uint32() - - // channelDefsJSON := fmt.Sprintf(` - // { - // "%d": { - // "reportFormat": 42, - // "chainSelector": 142, - // "streamIds": [1, 2] - // }, - // "%d": { - // "reportFormat": 42, - // "chainSelector": 142, - // "streamIds": [1, 3] - // } - // } - // `, cid1, cid2) - // pgtest.MustExec(t, db, ` - // INSERT INTO channel_definitions(addr, evm_chain_id, definitions, block_num, updated_at) - // VALUES ( $1, $2, $3, $4, NOW()) - // `, addr1, testutils.FixtureChainID.String(), channelDefsJSON, expectedBlockNum) - - // pgtest.MustExec(t, db, ` - // INSERT INTO channel_definitions(addr, evm_chain_id, definitions, block_num, updated_at) - // VALUES ( $1, $2, $3, $4, NOW()) - // `, addr2, testutils.FixtureChainID.String(), `{}`, expectedBlockNum2) - - // { - // // alternative chain ID; we expect these ones to be ignored - // pgtest.MustExec(t, db, ` - // INSERT INTO channel_definitions(addr, evm_chain_id, definitions, block_num, updated_at) - // VALUES ( $1, $2, $3, $4, NOW()) - // `, addr1, testutils.SimulatedChainID.String(), channelDefsJSON, expectedBlockNum) - // pgtest.MustExec(t, db, ` - // INSERT INTO channel_definitions(addr, evm_chain_id, definitions, block_num, updated_at) - // VALUES ( $1, $2, $3, $4, NOW()) - // `, addr3, testutils.SimulatedChainID.String(), channelDefsJSON, expectedBlockNum) - // } - - // cd, blockNum, err := orm.LoadChannelDefinitions(ctx, addr1) - // require.NoError(t, err) - - // assert.Equal(t, llotypes.ChannelDefinitions{ - // cid1: llotypes.ChannelDefinition{ - // ReportFormat: 42, - // ChainSelector: 142, - // StreamIDs: []llotypes.StreamID{1, 2}, - // }, - // cid2: llotypes.ChannelDefinition{ - // ReportFormat: 42, - // ChainSelector: 142, - // StreamIDs: []llotypes.StreamID{1, 3}, - // }, - // }, cd) - // assert.Equal(t, expectedBlockNum, blockNum) - - // cd, blockNum, err = orm.LoadChannelDefinitions(ctx, addr2) - // require.NoError(t, err) - - // assert.Equal(t, llotypes.ChannelDefinitions{}, cd) - // assert.Equal(t, expectedBlockNum2, blockNum) - // }) - // }) + const ETHMainnetChainSelector uint64 = 5009297550715157269 + const OtherChainSelector uint64 = 1234567890 + + db := pgtest.NewSqlxDB(t) + orm := NewORM(db, ETHMainnetChainSelector) + ctx := testutils.Context(t) + + addr1 := testutils.NewAddress() + addr2 := testutils.NewAddress() + addr3 := testutils.NewAddress() + + donID1 := uint32(1) + donID2 := uint32(2) + + t.Run("LoadChannelDefinitions", func(t *testing.T) { + t.Run("returns zero values if nothing in database", func(t *testing.T) { + pd, err := orm.LoadChannelDefinitions(ctx, addr1, donID1) + assert.NoError(t, err) + assert.Nil(t, pd) + }) + t.Run("loads channel definitions from database for the given don ID", func(t *testing.T) { + expectedBlockNum := rand.Int63() + expectedBlockNum2 := rand.Int63() + cid1 := rand.Uint32() + cid2 := rand.Uint32() + + channelDefsJSON := fmt.Sprintf(` +{ + "%d": { + "reportFormat": 42, + "chainSelector": 142, + "streams": [{"streamId": 1, "aggregator": "median"}, {"streamId": 2, "aggregator": "mode"}], + "opts": {"foo":"bar"} + }, + "%d": { + "reportFormat": 43, + "chainSelector": 142, + "streams": [{"streamId": 1, "aggregator": "median"}, {"streamId": 3, "aggregator": "quote"}] + } +} + `, cid1, cid2) + pgtest.MustExec(t, db, ` + INSERT INTO channel_definitions(addr, chain_selector, don_id, definitions, block_num, version, updated_at) + VALUES ($1, $2, $3, $4, $5, $6, NOW()) + `, addr1, ETHMainnetChainSelector, 1, channelDefsJSON, expectedBlockNum, 1) + + pgtest.MustExec(t, db, ` + INSERT INTO channel_definitions(addr, chain_selector, don_id, definitions, block_num, version, updated_at) + VALUES ($1, $2, $3, $4, $5, $6, NOW()) + `, addr2, ETHMainnetChainSelector, 1, `{}`, expectedBlockNum2, 1) + + { + // alternative chain selector; we expect these ones to be ignored + pgtest.MustExec(t, db, ` + INSERT INTO channel_definitions(addr, chain_selector, don_id, definitions, block_num, version, updated_at) + VALUES ($1, $2, $3, $4, $5, $6, NOW()) + `, addr1, OtherChainSelector, 1, channelDefsJSON, expectedBlockNum, 1) + pgtest.MustExec(t, db, ` + INSERT INTO channel_definitions(addr, chain_selector, don_id, definitions, block_num, version, updated_at) + VALUES ($1, $2, $3, $4, $5, $6, NOW()) + `, addr3, OtherChainSelector, 1, channelDefsJSON, expectedBlockNum, 1) + } + + pd, err := orm.LoadChannelDefinitions(ctx, addr1, donID1) + require.NoError(t, err) + + assert.Equal(t, ETHMainnetChainSelector, pd.ChainSelector) + assert.Equal(t, addr1, pd.Address) + assert.Equal(t, expectedBlockNum, pd.BlockNum) + assert.Equal(t, donID1, pd.DonID) + assert.Equal(t, uint32(1), pd.Version) + assert.Equal(t, llotypes.ChannelDefinitions{ + cid1: llotypes.ChannelDefinition{ + ReportFormat: 42, + Streams: []llotypes.Stream{{StreamID: 1, Aggregator: llotypes.AggregatorMedian}, {StreamID: 2, Aggregator: llotypes.AggregatorMode}}, + Opts: []byte(`{"foo":"bar"}`), + }, + cid2: llotypes.ChannelDefinition{ + ReportFormat: 43, + Streams: []llotypes.Stream{{StreamID: 1, Aggregator: llotypes.AggregatorMedian}, {StreamID: 3, Aggregator: llotypes.AggregatorQuote}}, + }, + }, pd.Definitions) + + // does not load erroneously for a different address + pd, err = orm.LoadChannelDefinitions(ctx, addr2, donID1) + require.NoError(t, err) + + assert.Equal(t, llotypes.ChannelDefinitions{}, pd.Definitions) + assert.Equal(t, expectedBlockNum2, pd.BlockNum) + + // does not load erroneously for a different don ID + pd, err = orm.LoadChannelDefinitions(ctx, addr1, donID2) + require.NoError(t, err) + + assert.Equal(t, (*PersistedDefinitions)(nil), pd) + }) + }) + + t.Run("StoreChannelDefinitions", func(t *testing.T) { + expectedBlockNum := rand.Int63() + cid1 := rand.Uint32() + cid2 := rand.Uint32() + defs := llotypes.ChannelDefinitions{ + cid1: llotypes.ChannelDefinition{ + ReportFormat: llotypes.ReportFormatJSON, + Streams: []llotypes.Stream{{StreamID: 1, Aggregator: llotypes.AggregatorMedian}, {StreamID: 2, Aggregator: llotypes.AggregatorMode}}, + Opts: []byte(`{"foo":"bar"}`), + }, + cid2: llotypes.ChannelDefinition{ + ReportFormat: llotypes.ReportFormatEVMPremiumLegacy, + Streams: []llotypes.Stream{{StreamID: 1, Aggregator: llotypes.AggregatorMedian}, {StreamID: 3, Aggregator: llotypes.AggregatorQuote}}, + }, + } + + t.Run("stores channel definitions in the database", func(t *testing.T) { + err := orm.StoreChannelDefinitions(ctx, addr1, donID1, 42, defs, expectedBlockNum) + require.NoError(t, err) + + pd, err := orm.LoadChannelDefinitions(ctx, addr1, donID1) + require.NoError(t, err) + assert.Equal(t, ETHMainnetChainSelector, pd.ChainSelector) + assert.Equal(t, addr1, pd.Address) + assert.Equal(t, expectedBlockNum, pd.BlockNum) + assert.Equal(t, donID1, pd.DonID) + assert.Equal(t, uint32(42), pd.Version) + assert.Equal(t, defs, pd.Definitions) + }) + t.Run("does not update if version is older than the database persisted version", func(t *testing.T) { + // try to update with an older version + err := orm.StoreChannelDefinitions(ctx, addr1, donID1, 41, llotypes.ChannelDefinitions{}, expectedBlockNum) + require.NoError(t, err) + + pd, err := orm.LoadChannelDefinitions(ctx, addr1, donID1) + require.NoError(t, err) + assert.Equal(t, uint32(42), pd.Version) + assert.Equal(t, defs, pd.Definitions) + }) + }) } diff --git a/core/services/llo/static_channel_definitions_cache.go b/core/services/llo/static_channel_definitions_cache.go index 98ef3642cb..980625bd59 100644 --- a/core/services/llo/static_channel_definitions_cache.go +++ b/core/services/llo/static_channel_definitions_cache.go @@ -7,7 +7,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/services" llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" - "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink-common/pkg/logger" ) // A CDC that loads a static JSON of channel definitions; useful for @@ -27,7 +27,7 @@ func NewStaticChannelDefinitionCache(lggr logger.Logger, dfnstr string) (llotype if err := json.Unmarshal([]byte(dfnstr), &definitions); err != nil { return nil, err } - return &staticCDC{services.StateMachine{}, lggr.Named("StaticChannelDefinitionCache"), definitions}, nil + return &staticCDC{services.StateMachine{}, logger.Named(lggr, "StaticChannelDefinitionCache"), definitions}, nil } func (s *staticCDC) Start(context.Context) error { diff --git a/core/services/llo/telemetry.go b/core/services/llo/telemetry.go new file mode 100644 index 0000000000..62b586f5cc --- /dev/null +++ b/core/services/llo/telemetry.go @@ -0,0 +1,184 @@ +package llo + +import ( + "context" + "errors" + "fmt" + + "github.com/smartcontractkit/libocr/commontypes" + "google.golang.org/protobuf/proto" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink-data-streams/llo" + + "github.com/smartcontractkit/chainlink/v2/core/services/llo/evm" + "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" + "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" + "github.com/smartcontractkit/chainlink/v2/core/services/pipeline/eautils" + mercuryutils "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/utils" + "github.com/smartcontractkit/chainlink/v2/core/services/synchronization/telem" +) + +const adapterLWBAErrorName = "AdapterLWBAError" + +type Telemeter interface { + EnqueueV3PremiumLegacy(run *pipeline.Run, trrs pipeline.TaskRunResults, streamID uint32, opts llo.DSOpts, val llo.StreamValue, err error) +} + +type TelemeterService interface { + Telemeter + services.Service +} + +func NewTelemeterService(lggr logger.Logger, monitoringEndpoint commontypes.MonitoringEndpoint) TelemeterService { + if monitoringEndpoint == nil { + return NullTelemeter + } + return newTelemeter(lggr, monitoringEndpoint) +} + +func newTelemeter(lggr logger.Logger, monitoringEndpoint commontypes.MonitoringEndpoint) *telemeter { + chTelemetryObservation := make(chan TelemetryObservation, 100) + t := &telemeter{ + chTelemetryObservation: chTelemetryObservation, + monitoringEndpoint: monitoringEndpoint, + } + t.Service, t.eng = services.Config{ + Name: "LLOTelemeterService", + Start: t.start, + }.NewServiceEngine(lggr) + + return t +} + +type telemeter struct { + services.Service + eng *services.Engine + + monitoringEndpoint commontypes.MonitoringEndpoint + chTelemetryObservation chan TelemetryObservation +} + +func (t *telemeter) EnqueueV3PremiumLegacy(run *pipeline.Run, trrs pipeline.TaskRunResults, streamID uint32, opts llo.DSOpts, val llo.StreamValue, err error) { + var adapterError *eautils.AdapterError + var dpInvariantViolationDetected bool + if errors.As(err, &adapterError) && adapterError.Name == adapterLWBAErrorName { + dpInvariantViolationDetected = true + } else if err != nil { + // ignore errors + return + } + tObs := TelemetryObservation{run, trrs, streamID, opts, val, dpInvariantViolationDetected} + select { + case t.chTelemetryObservation <- tObs: + default: + } +} + +func (t *telemeter) start(_ context.Context) error { + t.eng.Go(func(ctx context.Context) { + for { + select { + case tObs := <-t.chTelemetryObservation: + t.collectV3PremiumLegacyTelemetry(tObs) + case <-ctx.Done(): + return + } + } + }) + return nil +} + +func (t *telemeter) collectV3PremiumLegacyTelemetry(d TelemetryObservation) { + eaTelemetryValues := ocrcommon.ParseMercuryEATelemetry(t.eng.SugaredLogger, d.trrs, mercuryutils.REPORT_V3) + for _, eaTelem := range eaTelemetryValues { + var benchmarkPrice, bidPrice, askPrice int64 + var bp, bid, ask string + switch v := d.val.(type) { + case *llo.Decimal: + benchmarkPrice = v.Decimal().IntPart() + bp = v.Decimal().String() + case *llo.Quote: + benchmarkPrice = v.Benchmark.IntPart() + bp = v.Benchmark.String() + bidPrice = v.Bid.IntPart() + bid = v.Bid.String() + askPrice = v.Ask.IntPart() + ask = v.Ask.String() + } + epoch, round := evm.SeqNrToEpochAndRound(d.opts.OutCtx().SeqNr) + tea := &telem.EnhancedEAMercury{ + DataSource: eaTelem.DataSource, + DpBenchmarkPrice: eaTelem.DpBenchmarkPrice, + DpBid: eaTelem.DpBid, + DpAsk: eaTelem.DpAsk, + DpInvariantViolationDetected: d.dpInvariantViolationDetected, + BridgeTaskRunStartedTimestamp: eaTelem.BridgeTaskRunStartedTimestamp, + BridgeTaskRunEndedTimestamp: eaTelem.BridgeTaskRunEndedTimestamp, + ProviderRequestedTimestamp: eaTelem.ProviderRequestedTimestamp, + ProviderReceivedTimestamp: eaTelem.ProviderReceivedTimestamp, + ProviderDataStreamEstablished: eaTelem.ProviderDataStreamEstablished, + ProviderIndicatedTime: eaTelem.ProviderIndicatedTime, + Feed: fmt.Sprintf("streamID:%d", d.streamID), + ObservationBenchmarkPrice: benchmarkPrice, + ObservationBid: bidPrice, + ObservationAsk: askPrice, + ObservationBenchmarkPriceString: bp, + ObservationBidString: bid, + ObservationAskString: ask, + IsLinkFeed: false, + IsNativeFeed: false, + ConfigDigest: d.opts.ConfigDigest().Hex(), + Round: int64(round), + Epoch: int64(epoch), + AssetSymbol: eaTelem.AssetSymbol, + Version: uint32(1000 + mercuryutils.REPORT_V3), // add 1000 to distinguish between legacy feeds, this can be changed if necessary + } + + bytes, err := proto.Marshal(tea) + if err != nil { + t.eng.SugaredLogger.Warnf("protobuf marshal failed %v", err.Error()) + continue + } + + t.monitoringEndpoint.SendLog(bytes) + } +} + +type TelemetryObservation struct { + run *pipeline.Run + trrs pipeline.TaskRunResults + streamID uint32 + opts llo.DSOpts + val llo.StreamValue + dpInvariantViolationDetected bool +} + +var NullTelemeter TelemeterService = &nullTelemeter{} + +type nullTelemeter struct{} + +func (t *nullTelemeter) EnqueueV3PremiumLegacy(run *pipeline.Run, trrs pipeline.TaskRunResults, streamID uint32, opts llo.DSOpts, val llo.StreamValue, err error) { +} +func (t *nullTelemeter) Start(context.Context) error { + return nil +} +func (t *nullTelemeter) Close() error { + return nil +} +func (t *nullTelemeter) Healthy() error { + return nil +} +func (t *nullTelemeter) Unhealthy() error { + return nil +} +func (t *nullTelemeter) HealthReport() map[string]error { + return nil +} +func (t *nullTelemeter) Name() string { + return "NullTelemeter" +} +func (t *nullTelemeter) Ready() error { + return nil +} diff --git a/core/services/llo/telemetry_test.go b/core/services/llo/telemetry_test.go new file mode 100644 index 0000000000..ec77e959d2 --- /dev/null +++ b/core/services/llo/telemetry_test.go @@ -0,0 +1,216 @@ +package llo + +import ( + "errors" + "testing" + "time" + + "github.com/shopspring/decimal" + "github.com/smartcontractkit/libocr/commontypes" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "gopkg.in/guregu/null.v4" + + "github.com/smartcontractkit/chainlink-data-streams/llo" + "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" + "github.com/smartcontractkit/chainlink/v2/core/services/pipeline/eautils" + "github.com/smartcontractkit/chainlink/v2/core/services/synchronization/telem" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services/servicetest" +) + +var _ commontypes.MonitoringEndpoint = &mockMonitoringEndpoint{} + +type mockMonitoringEndpoint struct { + chLogs chan []byte +} + +func (m *mockMonitoringEndpoint) SendLog(log []byte) { + m.chLogs <- log +} + +const bridgeResponse = `{ + "meta":{ + "adapterName":"data-source-name" + }, + "timestamps":{ + "providerDataRequestedUnixMs":92233720368547760, + "providerDataReceivedUnixMs":-92233720368547760, + "providerDataStreamEstablishedUnixMs":1, + "providerIndicatedTimeUnixMs":-123456789 + } + }` + +var trrs = pipeline.TaskRunResults{ + pipeline.TaskRunResult{ + Task: &pipeline.BridgeTask{ + Name: "test-bridge-1", + BaseTask: pipeline.NewBaseTask(0, "ds1", nil, nil, 0), + RequestData: `{"data":{"from":"eth", "to":"usd"}}`, + }, + Result: pipeline.Result{ + Value: bridgeResponse, + }, + CreatedAt: time.Unix(0, 0), + FinishedAt: null.TimeFrom(time.Unix(0, 0)), + }, + pipeline.TaskRunResult{ + Task: &pipeline.JSONParseTask{ + BaseTask: pipeline.NewBaseTask(1, "ds1_parse", nil, nil, 1), + }, + Result: pipeline.Result{ + Value: "123456.123456789", + }, + }, + pipeline.TaskRunResult{ + Task: &pipeline.BridgeTask{ + Name: "test-bridge-2", + BaseTask: pipeline.NewBaseTask(0, "ds2", nil, nil, 0), + RequestData: `{"data":{"from":"eth", "to":"usd"}}`, + }, + Result: pipeline.Result{ + Value: bridgeResponse, + }, + CreatedAt: time.Unix(1, 0), + FinishedAt: null.TimeFrom(time.Unix(10, 0)), + }, + pipeline.TaskRunResult{ + Task: &pipeline.JSONParseTask{ + BaseTask: pipeline.NewBaseTask(1, "ds2_parse", nil, nil, 1), + }, + Result: pipeline.Result{ + Value: "12345678", + }, + }, + pipeline.TaskRunResult{ + Task: &pipeline.BridgeTask{ + Name: "test-bridge-3", + BaseTask: pipeline.NewBaseTask(0, "ds3", nil, nil, 0), + RequestData: `{"data":{"from":"eth", "to":"usd"}}`, + }, + Result: pipeline.Result{ + Value: bridgeResponse, + }, + CreatedAt: time.Unix(2, 0), + FinishedAt: null.TimeFrom(time.Unix(20, 0)), + }, + pipeline.TaskRunResult{ + Task: &pipeline.JSONParseTask{ + BaseTask: pipeline.NewBaseTask(1, "ds3_parse", nil, nil, 1), + }, + Result: pipeline.Result{ + Value: "1234567890", + }, + }, +} + +func Test_Telemeter(t *testing.T) { + lggr := logger.Test(t) + m := &mockMonitoringEndpoint{} + + run := &pipeline.Run{ID: 42} + streamID := uint32(135) + opts := &mockOpts{} + + t.Run("with error", func(t *testing.T) { + tm := newTelemeter(lggr, m) + servicetest.Run(t, tm) + + t.Run("if error is some random failure returns immediately", func(t *testing.T) { + // should return immediately and not even send on the channel + m.chLogs = nil + tm.EnqueueV3PremiumLegacy(run, trrs, streamID, opts, nil, errors.New("test error")) + }) + t.Run("if error is dp invariant violation, sets this flag", func(t *testing.T) { + m.chLogs = make(chan []byte, 100) + adapterError := new(eautils.AdapterError) + adapterError.Name = adapterLWBAErrorName + tm.EnqueueV3PremiumLegacy(run, trrs, streamID, opts, nil, adapterError) + + var i int + for log := range m.chLogs { + decoded := &telem.EnhancedEAMercury{} + require.NoError(t, proto.Unmarshal(log, decoded)) + assert.True(t, decoded.DpInvariantViolationDetected) + if i == 2 { + return + } + i++ + } + }) + }) + t.Run("with decimal value, sets all values correctly", func(t *testing.T) { + tm := newTelemeter(lggr, m) + val := llo.ToDecimal(decimal.NewFromFloat32(102.12)) + servicetest.Run(t, tm) + tm.EnqueueV3PremiumLegacy(run, trrs, streamID, opts, val, nil) + + var i int + for log := range m.chLogs { + decoded := &telem.EnhancedEAMercury{} + require.NoError(t, proto.Unmarshal(log, decoded)) + assert.Equal(t, int(1003), int(decoded.Version)) + assert.Equal(t, float64(123456.123456789), decoded.DpBenchmarkPrice) + assert.Zero(t, decoded.DpBid) + assert.Zero(t, decoded.DpAsk) + assert.False(t, decoded.DpInvariantViolationDetected) + assert.Zero(t, decoded.CurrentBlockNumber) + assert.Zero(t, decoded.CurrentBlockHash) + assert.Zero(t, decoded.CurrentBlockTimestamp) + assert.Zero(t, decoded.FetchMaxFinalizedTimestamp) + assert.Zero(t, decoded.MaxFinalizedTimestamp) + assert.Zero(t, decoded.ObservationTimestamp) + assert.False(t, decoded.IsLinkFeed) + assert.Zero(t, decoded.LinkPrice) + assert.False(t, decoded.IsNativeFeed) + assert.Zero(t, decoded.NativePrice) + assert.Equal(t, int64(i*1000), decoded.BridgeTaskRunStartedTimestamp) + assert.Equal(t, int64(i*10000), decoded.BridgeTaskRunEndedTimestamp) + assert.Equal(t, int64(92233720368547760), decoded.ProviderRequestedTimestamp) + assert.Equal(t, int64(-92233720368547760), decoded.ProviderReceivedTimestamp) + assert.Equal(t, int64(1), decoded.ProviderDataStreamEstablished) + assert.Equal(t, int64(-123456789), decoded.ProviderIndicatedTime) + assert.Equal(t, "streamID:135", decoded.Feed) + assert.Equal(t, int64(102), decoded.ObservationBenchmarkPrice) + assert.Equal(t, "102.12", decoded.ObservationBenchmarkPriceString) + assert.Zero(t, decoded.ObservationBid) + assert.Zero(t, decoded.ObservationBidString) + assert.Zero(t, decoded.ObservationAsk) + assert.Zero(t, decoded.ObservationAskString) + assert.Zero(t, decoded.ObservationMarketStatus) + assert.Equal(t, "0605040000000000000000000000000000000000000000000000000000000000", decoded.ConfigDigest) + assert.Equal(t, int64(18), decoded.Round) + assert.Equal(t, int64(4), decoded.Epoch) + assert.Equal(t, "eth/usd", decoded.AssetSymbol) + if i == 2 { + return + } + i++ + } + }) + t.Run("with quote value", func(t *testing.T) { + tm := newTelemeter(lggr, m) + val := &llo.Quote{Bid: decimal.NewFromFloat32(102.12), Benchmark: decimal.NewFromFloat32(103.32), Ask: decimal.NewFromFloat32(104.25)} + servicetest.Run(t, tm) + tm.EnqueueV3PremiumLegacy(run, trrs, streamID, opts, val, nil) + + var i int + for log := range m.chLogs { + decoded := &telem.EnhancedEAMercury{} + require.NoError(t, proto.Unmarshal(log, decoded)) + assert.Equal(t, int64(103), decoded.ObservationBenchmarkPrice) + assert.Equal(t, "103.32", decoded.ObservationBenchmarkPriceString) + assert.Equal(t, int64(102), decoded.ObservationBid) + assert.Equal(t, "102.12", decoded.ObservationBidString) + assert.Equal(t, int64(104), decoded.ObservationAsk) + assert.Equal(t, "104.25", decoded.ObservationAskString) + assert.Zero(t, decoded.ObservationMarketStatus) + if i == 2 { + return + } + i++ + } + }) +} diff --git a/core/services/llo/transmitter.go b/core/services/llo/transmitter.go index b8cfb86de3..3b7ef66e6a 100644 --- a/core/services/llo/transmitter.go +++ b/core/services/llo/transmitter.go @@ -2,24 +2,24 @@ package llo import ( "context" - "crypto/ed25519" - "errors" - "fmt" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" "github.com/smartcontractkit/libocr/offchainreporting2plus/types" ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "golang.org/x/sync/errgroup" + "github.com/smartcontractkit/chainlink/v2/core/services/llo/mercurytransmitter" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" - - "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc" ) // LLO Transmitter implementation, based on // core/services/relay/evm/mercury/transmitter.go +// +// If you need to "fan-out" transmits and send reports to a new destination, +// add a new subTransmitter // TODO: prom metrics (common with mercury/transmitter.go?) // https://smartcontract-it.atlassian.net/browse/MERC-3659 @@ -34,25 +34,6 @@ const ( // transmitTimeout = 5 * time.Second ) -var PayloadTypes = getPayloadTypes() - -func getPayloadTypes() abi.Arguments { - mustNewType := func(t string) abi.Type { - result, err := abi.NewType(t, "", []abi.ArgumentMarshaling{}) - if err != nil { - panic(fmt.Sprintf("Unexpected error during abi.NewType: %s", err)) - } - return result - } - return abi.Arguments([]abi.Argument{ - {Name: "reportContext", Type: mustNewType("bytes32[2]")}, - {Name: "report", Type: mustNewType("bytes")}, - {Name: "rawRs", Type: mustNewType("bytes32[]")}, - {Name: "rawSs", Type: mustNewType("bytes32[]")}, - {Name: "rawVs", Type: mustNewType("bytes32")}, - }) -} - type Transmitter interface { llotypes.Transmitter services.Service @@ -61,30 +42,57 @@ type Transmitter interface { type transmitter struct { services.StateMachine lggr logger.Logger - rpcClient wsrpc.Client fromAccount string + + subTransmitters []Transmitter } -func NewTransmitter(lggr logger.Logger, rpcClient wsrpc.Client, fromAccount ed25519.PublicKey) Transmitter { +type TransmitterOpts struct { + Lggr logger.Logger + FromAccount string + MercuryTransmitterOpts mercurytransmitter.Opts +} + +// The transmitter will handle starting and stopping the subtransmitters +func NewTransmitter(opts TransmitterOpts) Transmitter { + subTransmitters := []Transmitter{ + mercurytransmitter.New(opts.MercuryTransmitterOpts), + } return &transmitter{ services.StateMachine{}, - lggr, - rpcClient, - fmt.Sprintf("%x", fromAccount), + opts.Lggr, + opts.FromAccount, + subTransmitters, } } func (t *transmitter) Start(ctx context.Context) error { - return nil + return t.StartOnce("llo.Transmitter", func() error { + for _, st := range t.subTransmitters { + if err := st.Start(ctx); err != nil { + return err + } + } + return nil + }) } func (t *transmitter) Close() error { - return nil + return t.StopOnce("llo.Transmitter", func() error { + for _, st := range t.subTransmitters { + if err := st.Close(); err != nil { + return err + } + } + return nil + }) } func (t *transmitter) HealthReport() map[string]error { report := map[string]error{t.Name(): t.Healthy()} - services.CopyHealth(report, t.rpcClient.HealthReport()) + for _, st := range t.subTransmitters { + services.CopyHealth(report, st.HealthReport()) + } return report } @@ -97,7 +105,14 @@ func (t *transmitter) Transmit( report ocr3types.ReportWithInfo[llotypes.ReportInfo], sigs []types.AttributedOnchainSignature, ) (err error) { - return errors.New("not implemented") + g := new(errgroup.Group) + for _, st := range t.subTransmitters { + st := st + g.Go(func() error { + return st.Transmit(ctx, digest, seqNr, report, sigs) + }) + } + return g.Wait() } // FromAccount returns the stringified (hex) CSA public key diff --git a/core/services/llo/transmitter_test.go b/core/services/llo/transmitter_test.go deleted file mode 100644 index eb231494c0..0000000000 --- a/core/services/llo/transmitter_test.go +++ /dev/null @@ -1,7 +0,0 @@ -package llo - -import "testing" - -func Test_Transmitter(t *testing.T) { - // TODO: https://smartcontract-it.atlassian.net/browse/MERC-3659 -} diff --git a/core/services/nurse.go b/core/services/nurse.go index a9069b5181..7f3cad13e7 100644 --- a/core/services/nurse.go +++ b/core/services/nurse.go @@ -3,6 +3,7 @@ package services import ( "bytes" "compress/gzip" + "context" "fmt" "io/fs" "os" @@ -19,22 +20,21 @@ import ( commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/timeutil" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/utils" ) type Nurse struct { - services.StateMachine + services.Service + eng *services.Engine cfg Config - log logger.Logger checks map[string]CheckFunc checksMu sync.RWMutex chGather chan gatherRequest - chStop chan struct{} - wgDone sync.WaitGroup } type Config interface { @@ -66,85 +66,63 @@ const ( ) func NewNurse(cfg Config, log logger.Logger) *Nurse { - return &Nurse{ + n := &Nurse{ cfg: cfg, - log: log.Named("Nurse"), checks: make(map[string]CheckFunc), chGather: make(chan gatherRequest, 1), - chStop: make(chan struct{}), } + n.Service, n.eng = services.Config{ + Name: "Nurse", + Start: n.start, + }.NewServiceEngine(log) + + return n } -func (n *Nurse) Start() error { - return n.StartOnce("Nurse", func() error { - // This must be set *once*, and it must occur as early as possible - if n.cfg.MemProfileRate() != runtime.MemProfileRate { - runtime.MemProfileRate = n.cfg.BlockProfileRate() - } +func (n *Nurse) start(_ context.Context) error { + // This must be set *once*, and it must occur as early as possible + if n.cfg.MemProfileRate() != runtime.MemProfileRate { + runtime.MemProfileRate = n.cfg.BlockProfileRate() + } - n.log.Debugf("Starting nurse with config %+v", n.cfg) - runtime.SetCPUProfileRate(n.cfg.CPUProfileRate()) - runtime.SetBlockProfileRate(n.cfg.BlockProfileRate()) - runtime.SetMutexProfileFraction(n.cfg.MutexProfileFraction()) + n.eng.Debugf("Starting nurse with config %+v", n.cfg) + runtime.SetCPUProfileRate(n.cfg.CPUProfileRate()) + runtime.SetBlockProfileRate(n.cfg.BlockProfileRate()) + runtime.SetMutexProfileFraction(n.cfg.MutexProfileFraction()) - err := utils.EnsureDirAndMaxPerms(n.cfg.ProfileRoot(), 0744) - if err != nil { - return err - } + err := utils.EnsureDirAndMaxPerms(n.cfg.ProfileRoot(), 0744) + if err != nil { + return err + } - n.AddCheck("mem", n.checkMem) - n.AddCheck("goroutines", n.checkGoroutines) - - n.wgDone.Add(1) - // Checker - go func() { - defer n.wgDone.Done() - for { - select { - case <-n.chStop: - return - case <-time.After(n.cfg.PollInterval().Duration()): - } - - func() { - n.checksMu.RLock() - defer n.checksMu.RUnlock() - for reason, checkFunc := range n.checks { - if unwell, meta := checkFunc(); unwell { - n.GatherVitals(reason, meta) - break - } - } - }() - } - }() - - n.wgDone.Add(1) - // Responder - go func() { - defer n.wgDone.Done() - for { - select { - case <-n.chStop: - return - case req := <-n.chGather: - n.gatherVitals(req.reason, req.meta) - } - } - }() + n.AddCheck("mem", n.checkMem) + n.AddCheck("goroutines", n.checkGoroutines) - return nil + // Checker + n.eng.GoTick(timeutil.NewTicker(n.cfg.PollInterval().Duration), func(ctx context.Context) { + n.checksMu.RLock() + defer n.checksMu.RUnlock() + for reason, checkFunc := range n.checks { + if unwell, meta := checkFunc(); unwell { + n.GatherVitals(ctx, reason, meta) + break + } + } }) -} -func (n *Nurse) Close() error { - return n.StopOnce("Nurse", func() error { - n.log.Debug("Nurse closing...") - defer n.log.Debug("Nurse closed") - close(n.chStop) - n.wgDone.Wait() - return nil + // Responder + n.eng.Go(func(ctx context.Context) { + for { + select { + case <-ctx.Done(): + return + case req := <-n.chGather: + n.gatherVitals(req.reason, req.meta) + } + } }) + + return nil } func (n *Nurse) AddCheck(reason string, checkFunc CheckFunc) { @@ -153,9 +131,9 @@ func (n *Nurse) AddCheck(reason string, checkFunc CheckFunc) { n.checks[reason] = checkFunc } -func (n *Nurse) GatherVitals(reason string, meta Meta) { +func (n *Nurse) GatherVitals(ctx context.Context, reason string, meta Meta) { select { - case <-n.chStop: + case <-ctx.Done(): case n.chGather <- gatherRequest{reason, meta}: default: } @@ -189,14 +167,14 @@ func (n *Nurse) checkGoroutines() (bool, Meta) { func (n *Nurse) gatherVitals(reason string, meta Meta) { loggerFields := (logger.Fields{"reason": reason}).Merge(logger.Fields(meta)) - n.log.Debugw("Nurse is gathering vitals", loggerFields.Slice()...) + n.eng.Debugw("Nurse is gathering vitals", loggerFields.Slice()...) size, err := n.totalProfileBytes() if err != nil { - n.log.Errorw("could not fetch total profile bytes", loggerFields.With("err", err).Slice()...) + n.eng.Errorw("could not fetch total profile bytes", loggerFields.With("err", err).Slice()...) return } else if size >= uint64(n.cfg.MaxProfileSize()) { - n.log.Warnw("cannot write pprof profile, total profile size exceeds configured PPROF_MAX_PROFILE_SIZE", + n.eng.Warnw("cannot write pprof profile, total profile size exceeds configured PPROF_MAX_PROFILE_SIZE", loggerFields.With("total", size, "max", n.cfg.MaxProfileSize()).Slice()..., ) return @@ -206,7 +184,7 @@ func (n *Nurse) gatherVitals(reason string, meta Meta) { err = n.appendLog(now, reason, meta) if err != nil { - n.log.Warnw("cannot write pprof profile", loggerFields.With("err", err).Slice()...) + n.eng.Warnw("cannot write pprof profile", loggerFields.With("err", err).Slice()...) return } var wg sync.WaitGroup @@ -227,7 +205,7 @@ func (n *Nurse) gatherVitals(reason string, meta Meta) { wg.Add(1) go n.gather("heap", now, &wg) } else { - n.log.Info("skipping heap collection because runtime.MemProfileRate = 0") + n.eng.Info("skipping heap collection because runtime.MemProfileRate = 0") } wg.Add(1) @@ -236,15 +214,13 @@ func (n *Nurse) gatherVitals(reason string, meta Meta) { go n.gather("threadcreate", now, &wg) ch := make(chan struct{}) - n.wgDone.Add(1) - go func() { - defer n.wgDone.Done() + n.eng.Go(func(ctx context.Context) { defer close(ch) wg.Wait() - }() + }) select { - case <-n.chStop: + case <-n.eng.StopChan: case <-ch: } } @@ -252,7 +228,7 @@ func (n *Nurse) gatherVitals(reason string, meta Meta) { func (n *Nurse) appendLog(now time.Time, reason string, meta Meta) error { filename := filepath.Join(n.cfg.ProfileRoot(), "nurse.log") - n.log.Debugf("creating nurse log %s", filename) + n.eng.Debugf("creating nurse log %s", filename) file, err := os.Create(filename) if err != nil { @@ -288,34 +264,34 @@ func (n *Nurse) appendLog(now time.Time, reason string, meta Meta) error { func (n *Nurse) gatherCPU(now time.Time, wg *sync.WaitGroup) { defer wg.Done() - n.log.Debugf("gather cpu %d ...", now.UnixMicro()) - defer n.log.Debugf("gather cpu %d done", now.UnixMicro()) + n.eng.Debugf("gather cpu %d ...", now.UnixMicro()) + defer n.eng.Debugf("gather cpu %d done", now.UnixMicro()) wc, err := n.createFile(now, cpuProfName, false) if err != nil { - n.log.Errorw("could not write cpu profile", "err", err) + n.eng.Errorw("could not write cpu profile", "err", err) return } defer wc.Close() err = pprof.StartCPUProfile(wc) if err != nil { - n.log.Errorw("could not start cpu profile", "err", err) + n.eng.Errorw("could not start cpu profile", "err", err) return } select { - case <-n.chStop: - n.log.Debug("gather cpu received stop") + case <-n.eng.StopChan: + n.eng.Debug("gather cpu received stop") case <-time.After(n.cfg.GatherDuration().Duration()): - n.log.Debugf("gather cpu duration elapsed %s. stoping profiling.", n.cfg.GatherDuration().Duration().String()) + n.eng.Debugf("gather cpu duration elapsed %s. stoping profiling.", n.cfg.GatherDuration().Duration().String()) } pprof.StopCPUProfile() err = wc.Close() if err != nil { - n.log.Errorw("could not close cpu profile", "err", err) + n.eng.Errorw("could not close cpu profile", "err", err) return } } @@ -323,23 +299,23 @@ func (n *Nurse) gatherCPU(now time.Time, wg *sync.WaitGroup) { func (n *Nurse) gatherTrace(now time.Time, wg *sync.WaitGroup) { defer wg.Done() - n.log.Debugf("gather trace %d ...", now.UnixMicro()) - defer n.log.Debugf("gather trace %d done", now.UnixMicro()) + n.eng.Debugf("gather trace %d ...", now.UnixMicro()) + defer n.eng.Debugf("gather trace %d done", now.UnixMicro()) wc, err := n.createFile(now, traceProfName, true) if err != nil { - n.log.Errorw("could not write trace profile", "err", err) + n.eng.Errorw("could not write trace profile", "err", err) return } defer wc.Close() err = trace.Start(wc) if err != nil { - n.log.Errorw("could not start trace profile", "err", err) + n.eng.Errorw("could not start trace profile", "err", err) return } select { - case <-n.chStop: + case <-n.eng.StopChan: case <-time.After(n.cfg.GatherTraceDuration().Duration()): } @@ -347,7 +323,7 @@ func (n *Nurse) gatherTrace(now time.Time, wg *sync.WaitGroup) { err = wc.Close() if err != nil { - n.log.Errorw("could not close trace profile", "err", err) + n.eng.Errorw("could not close trace profile", "err", err) return } } @@ -355,18 +331,18 @@ func (n *Nurse) gatherTrace(now time.Time, wg *sync.WaitGroup) { func (n *Nurse) gather(typ string, now time.Time, wg *sync.WaitGroup) { defer wg.Done() - n.log.Debugf("gather %s %d ...", typ, now.UnixMicro()) - n.log.Debugf("gather %s %d done", typ, now.UnixMicro()) + n.eng.Debugf("gather %s %d ...", typ, now.UnixMicro()) + n.eng.Debugf("gather %s %d done", typ, now.UnixMicro()) p := pprof.Lookup(typ) if p == nil { - n.log.Errorf("Invariant violation: pprof type '%v' does not exist", typ) + n.eng.Errorf("Invariant violation: pprof type '%v' does not exist", typ) return } p0, err := collectProfile(p) if err != nil { - n.log.Errorw(fmt.Sprintf("could not collect %v profile", typ), "err", err) + n.eng.Errorw(fmt.Sprintf("could not collect %v profile", typ), "err", err) return } @@ -374,14 +350,14 @@ func (n *Nurse) gather(typ string, now time.Time, wg *sync.WaitGroup) { defer t.Stop() select { - case <-n.chStop: + case <-n.eng.StopChan: return case <-t.C: } p1, err := collectProfile(p) if err != nil { - n.log.Errorw(fmt.Sprintf("could not collect %v profile", typ), "err", err) + n.eng.Errorw(fmt.Sprintf("could not collect %v profile", typ), "err", err) return } ts := p1.TimeNanos @@ -391,7 +367,7 @@ func (n *Nurse) gather(typ string, now time.Time, wg *sync.WaitGroup) { p1, err = profile.Merge([]*profile.Profile{p0, p1}) if err != nil { - n.log.Errorw(fmt.Sprintf("could not compute delta for %v profile", typ), "err", err) + n.eng.Errorw(fmt.Sprintf("could not compute delta for %v profile", typ), "err", err) return } @@ -400,19 +376,19 @@ func (n *Nurse) gather(typ string, now time.Time, wg *sync.WaitGroup) { wc, err := n.createFile(now, typ, false) if err != nil { - n.log.Errorw(fmt.Sprintf("could not write %v profile", typ), "err", err) + n.eng.Errorw(fmt.Sprintf("could not write %v profile", typ), "err", err) return } defer wc.Close() err = p1.Write(wc) if err != nil { - n.log.Errorw(fmt.Sprintf("could not write %v profile", typ), "err", err) + n.eng.Errorw(fmt.Sprintf("could not write %v profile", typ), "err", err) return } err = wc.Close() if err != nil { - n.log.Errorw(fmt.Sprintf("could not close file for %v profile", typ), "err", err) + n.eng.Errorw(fmt.Sprintf("could not close file for %v profile", typ), "err", err) return } } @@ -437,7 +413,7 @@ func (n *Nurse) createFile(now time.Time, typ string, shouldGzip bool) (*utils.D filename += ".gz" } fullpath := filepath.Join(n.cfg.ProfileRoot(), filename) - n.log.Debugf("creating file %s", fullpath) + n.eng.Debugf("creating file %s", fullpath) file, err := os.Create(fullpath) if err != nil { diff --git a/core/services/nurse_test.go b/core/services/nurse_test.go index 4597eeb456..ed6f6872dc 100644 --- a/core/services/nurse_test.go +++ b/core/services/nurse_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/utils" @@ -102,7 +103,7 @@ func TestNurse(t *testing.T) { nrse := NewNurse(newMockConfig(t), l) nrse.AddCheck("test", func() (bool, Meta) { return true, Meta{} }) - require.NoError(t, nrse.Start()) + require.NoError(t, nrse.Start(tests.Context(t))) defer func() { require.NoError(t, nrse.Close()) }() require.NoError(t, nrse.appendLog(time.Now(), "test", Meta{})) diff --git a/core/services/ocr/contract_tracker.go b/core/services/ocr/contract_tracker.go index 6e742e48fb..0d0dc45c0d 100644 --- a/core/services/ocr/contract_tracker.go +++ b/core/services/ocr/contract_tracker.go @@ -391,6 +391,7 @@ func (t *OCRContractTracker) ConfigFromLogs(ctx context.Context, changedInBlock return confighelper.ContractConfigFromConfigSetEvent(*latest), err } +// nolint:exhaustive // LatestBlockHeight queries the eth node for the most recent header func (t *OCRContractTracker) LatestBlockHeight(ctx context.Context) (blockheight uint64, err error) { switch t.cfg.ChainType() { @@ -399,7 +400,7 @@ func (t *OCRContractTracker) LatestBlockHeight(ctx context.Context) (blockheight // care about the block height; we have no way of getting the L1 block // height anyway return 0, nil - case "", chaintype.ChainArbitrum, chaintype.ChainAstar, chaintype.ChainCelo, chaintype.ChainGnosis, chaintype.ChainHedera, chaintype.ChainKroma, chaintype.ChainOptimismBedrock, chaintype.ChainScroll, chaintype.ChainWeMix, chaintype.ChainXLayer, chaintype.ChainZkEvm, chaintype.ChainZkSync, chaintype.ChainMantle: + case "", chaintype.ChainArbitrum, chaintype.ChainAstar, chaintype.ChainCelo, chaintype.ChainGnosis, chaintype.ChainHedera, chaintype.ChainKroma, chaintype.ChainOptimismBedrock, chaintype.ChainScroll, chaintype.ChainWeMix, chaintype.ChainXLayer, chaintype.ChainZkEvm, chaintype.ChainZkSync: // continue } latestBlockHeight := t.getLatestBlockHeight() diff --git a/core/services/ocr2/delegate.go b/core/services/ocr2/delegate.go index 03c1bc971f..8bca97c37e 100644 --- a/core/services/ocr2/delegate.go +++ b/core/services/ocr2/delegate.go @@ -10,17 +10,23 @@ import ( "strings" "time" + "gopkg.in/guregu/null.v4" + + cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" + "github.com/smartcontractkit/chainlink-common/pkg/types/core" + + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/config" + "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" - "google.golang.org/grpc" - "gopkg.in/guregu/null.v4" - chainselectors "github.com/smartcontractkit/chain-selectors" "github.com/smartcontractkit/libocr/commontypes" libocr2 "github.com/smartcontractkit/libocr/offchainreporting2plus" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "google.golang.org/grpc" ocr2keepers20 "github.com/smartcontractkit/chainlink-automation/pkg/v2" ocr2keepers20config "github.com/smartcontractkit/chainlink-automation/pkg/v2/config" @@ -29,14 +35,11 @@ import ( ocr2keepers20runner "github.com/smartcontractkit/chainlink-automation/pkg/v2/runner" ocr2keepers21config "github.com/smartcontractkit/chainlink-automation/pkg/v3/config" ocr2keepers21 "github.com/smartcontractkit/chainlink-automation/pkg/v3/plugin" - commonlogger "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/loop" "github.com/smartcontractkit/chainlink-common/pkg/loop/reportingplugins" "github.com/smartcontractkit/chainlink-common/pkg/loop/reportingplugins/ocr3" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" "github.com/smartcontractkit/chainlink-common/pkg/types" - cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" - "github.com/smartcontractkit/chainlink-common/pkg/types/core" llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox" datastreamsllo "github.com/smartcontractkit/chainlink-data-streams/llo" @@ -47,23 +50,17 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" - "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ocr2key" "github.com/smartcontractkit/chainlink/v2/core/services/llo" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/ccipcommit" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/ccipexec" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/config" - ccipconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/config" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/functions" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/generic" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/liquiditymanager" - liquiditymanagermodels "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/liquiditymanager/models" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/liquiditymanager/ocr3impls" lloconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/llo/config" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/median" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/mercury" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/autotelemetry21" ocr2keeper21core "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/validate" @@ -79,6 +76,8 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/synchronization" "github.com/smartcontractkit/chainlink/v2/core/services/telemetry" "github.com/smartcontractkit/chainlink/v2/plugins" + + ccipconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/config" ) type ErrJobSpecNoRelayer struct { @@ -362,6 +361,18 @@ func (d *Delegate) cleanupEVM(ctx context.Context, jb job.Job, relayID types.Rel d.lggr.Errorw("failed to unregister ccip exec plugin filters", "err", err2, "spec", spec) } return nil + case types.LLO: + var pluginCfg lloconfig.PluginConfig + err = json.Unmarshal(spec.PluginConfig.Bytes(), &pluginCfg) + if err != nil { + return err + } + var chainSelector uint64 + chainSelector, err = chainselectors.SelectorFromChainId(chain.ID().Uint64()) + if err != nil { + return err + } + return llo.Cleanup(ctx, lp, pluginCfg.ChannelDefinitionsContractAddress, pluginCfg.DonID, d.ds, chainSelector) default: return nil } @@ -509,8 +520,6 @@ func (d *Delegate) ServicesForSpec(ctx context.Context, jb job.Job) ([]job.Servi return d.newServicesCCIPCommit(ctx, lggr, jb, bootstrapPeers, kb, ocrDB, lc, transmitterID) case types.CCIPExecution: return d.newServicesCCIPExecution(ctx, lggr, jb, bootstrapPeers, kb, ocrDB, lc, transmitterID) - case "liquiditymanager": // TODO: add constant to chainlink-common - return d.newServicesLiquidityManager(ctx, lggr, jb, bootstrapPeers, kb, ocrDB, lc) default: return nil, errors.Errorf("plugin type %s not supported", spec.PluginType) } @@ -874,6 +883,21 @@ func (d *Delegate) newServicesMercury( lggr.ErrorIf(d.jobORM.RecordError(ctx, jb.ID, msg), "unable to record error") }) + var relayConfig evmrelaytypes.RelayConfig + err = json.Unmarshal(jb.OCR2OracleSpec.RelayConfig.Bytes(), &relayConfig) + if err != nil { + return nil, fmt.Errorf("error while unmarshalling relay config: %w", err) + } + + var telemetryType synchronization.TelemetryType + if relayConfig.EnableTriggerCapability && len(jb.OCR2OracleSpec.PluginConfig) == 0 { + telemetryType = synchronization.OCR3DataFeeds + // First use case for TriggerCapability transmission is Data Feeds, so telemetry should be routed accordingly. + // This is only true if TriggerCapability is the *only* transmission method (PluginConfig is empty). + } else { + telemetryType = synchronization.OCR3Mercury + } + oracleArgsNoPlugin := libocr2.MercuryOracleArgs{ BinaryNetworkEndpointFactory: d.peerWrapper.Peer2, V2Bootstrappers: bootstrapPeers, @@ -882,7 +906,7 @@ func (d *Delegate) newServicesMercury( Database: ocrDB, LocalConfig: lc, Logger: ocrLogger, - MonitoringEndpoint: d.monitoringEndpointGen.GenMonitoringEndpoint(rid.Network, rid.ChainID, spec.FeedID.String(), synchronization.OCR3Mercury), + MonitoringEndpoint: d.monitoringEndpointGen.GenMonitoringEndpoint(rid.Network, rid.ChainID, spec.FeedID.String(), telemetryType), OffchainConfigDigester: mercuryProvider.OffchainConfigDigester(), OffchainKeyring: kb, OnchainKeyring: kb, @@ -893,7 +917,7 @@ func (d *Delegate) newServicesMercury( mCfg := mercury.NewMercuryConfig(d.cfg.JobPipeline().MaxSuccessfulRuns(), d.cfg.JobPipeline().ResultWriteQueueDepth(), d.cfg) - mercuryServices, err2 := mercury.NewServices(jb, mercuryProvider, d.pipelineRunner, lggr, oracleArgsNoPlugin, mCfg, chEnhancedTelem, d.mercuryORM, (mercuryutils.FeedID)(*spec.FeedID)) + mercuryServices, err2 := mercury.NewServices(jb, mercuryProvider, d.pipelineRunner, lggr, oracleArgsNoPlugin, mCfg, chEnhancedTelem, d.mercuryORM, (mercuryutils.FeedID)(*spec.FeedID), relayConfig.EnableTriggerCapability) if ocrcommon.ShouldCollectEnhancedTelemetryMercury(jb) { enhancedTelemService := ocrcommon.NewEnhancedTelemetryService(&jb, chEnhancedTelem, make(chan struct{}), d.monitoringEndpointGen.GenMonitoringEndpoint(rid.Network, rid.ChainID, spec.FeedID.String(), synchronization.EnhancedEAMercury), lggr.Named("EnhancedTelemetryMercury")) @@ -956,6 +980,7 @@ func (d *Delegate) newServicesLLO( return nil, err } + // Handle key bundle IDs explicitly specified in job spec kbm := make(map[llotypes.ReportFormat]llo.Key) for rfStr, kbid := range pluginCfg.KeyBundleIDs { k, err3 := d.ks.Get(kbid) @@ -968,26 +993,19 @@ func (d *Delegate) newServicesLLO( } kbm[rf] = k } - // NOTE: This is a bit messy because we assume chain type matches report - // format, and it may not in all cases. We don't yet know what report - // formats we need or how they correspond to chain types, so assume it's - // 1:1 for now but will change in future - // + + // Use the default key bundle if not specified + // NOTE: Only JSON and EVMPremiumLegacy supported for now // https://smartcontract-it.atlassian.net/browse/MERC-3722 - for _, s := range chaintype.SupportedChainTypes { - rf, err3 := llotypes.ReportFormatFromString(string(s)) - if err3 != nil { - return nil, fmt.Errorf("job %d (%s) has a chain type with no matching report format %s: %w", jb.ID, jb.Name.ValueOrZero(), s, err3) - } + for _, rf := range []llotypes.ReportFormat{llotypes.ReportFormatJSON, llotypes.ReportFormatEVMPremiumLegacy} { if _, exists := kbm[rf]; !exists { // Use the first if unspecified - kbs, err4 := d.ks.GetAllOfType(s) - if err4 != nil { - return nil, err4 + kbs, err3 := d.ks.GetAllOfType("evm") + if err3 != nil { + return nil, err3 } if len(kbs) == 0 { - // unsupported key type - continue + return nil, fmt.Errorf("no on-chain signing keys found for report format %s", "evm") } else if len(kbs) > 1 { lggr.Debugf("Multiple on-chain signing keys found for report format %s, using the first", rf.String()) } @@ -1013,7 +1031,8 @@ func (d *Delegate) newServicesLLO( Runner: d.pipelineRunner, Registry: d.streamRegistry, - JobName: jb.Name, + JobName: jb.Name, + CaptureEATelemetry: jb.OCR2OracleSpec.CaptureEATelemetry, ChannelDefinitionCache: provider.ChannelDefinitionCache(), @@ -1023,13 +1042,11 @@ func (d *Delegate) newServicesLLO( ContractConfigTracker: provider.ContractConfigTracker(), Database: ocrDB, LocalConfig: lc, - // TODO: Telemetry for llo - // https://smartcontract-it.atlassian.net/browse/MERC-3603 - MonitoringEndpoint: nil, - OffchainConfigDigester: provider.OffchainConfigDigester(), - OffchainKeyring: kb, - OnchainKeyring: kr, - OCRLogger: ocrLogger, + MonitoringEndpoint: d.monitoringEndpointGen.GenMonitoringEndpoint(rid.Network, rid.ChainID, fmt.Sprintf("%d", pluginCfg.DonID), synchronization.EnhancedEAMercury), + OffchainConfigDigester: provider.OffchainConfigDigester(), + OffchainKeyring: kb, + OnchainKeyring: kr, + OCRLogger: ocrLogger, // Enable verbose logging if either Mercury.VerboseLogging is on or OCR2.TraceLogging is on ReportingPluginConfig: datastreamsllo.Config{VerboseLogging: d.cfg.Mercury().VerboseLogging() || d.cfg.OCR2().TraceLogging()}, @@ -1969,76 +1986,6 @@ func newExecPluginConfig(isSourceProvider bool, srcStartBlock uint64, dstStartBl } } -func (d *Delegate) newServicesLiquidityManager(ctx context.Context, lggr logger.SugaredLogger, jb job.Job, bootstrapPeers []commontypes.BootstrapperLocator, kb ocr2key.KeyBundle, ocrDB *db, lc ocrtypes.LocalConfig) ([]job.ServiceCtx, error) { - spec := jb.OCR2OracleSpec - if spec.Relay != relay.NetworkEVM { - return nil, errors.New("Non evm chains are not supported for rebalancer execution") - } - // the relay ID specified in the spec will be that of the main/master chain - rid, err := spec.RelayID() - if err != nil { - return nil, ErrJobSpecNoRelayer{Err: err, PluginName: string(spec.PluginType)} - } - relayer := evmrelay.NewRebalancerRelayer( - d.legacyChains, - d.lggr, - d.ethKs, - ) - rebalancerProvider, err := relayer.NewRebalancerProvider( - ctx, - types.RelayArgs{ - ExternalJobID: jb.ExternalJobID, - JobID: jb.ID, - ContractID: spec.ContractID, - New: d.isNewlyCreatedJob, - RelayConfig: spec.RelayConfig.Bytes(), - ProviderType: string(spec.PluginType), - }, types.PluginArgs{ - TransmitterID: spec.TransmitterID.String, - PluginConfig: spec.PluginConfig.Bytes(), - }) - if err != nil { - return nil, fmt.Errorf("failed to create rebalancer provider: %w", err) - } - factory, err := liquiditymanager.NewPluginFactory( - lggr, - spec.PluginConfig.Bytes(), - rebalancerProvider.LiquidityManagerFactory(), - rebalancerProvider.DiscovererFactory(), - rebalancerProvider.BridgeFactory(), - ) - if err != nil { - return nil, fmt.Errorf("failed to create rebalancer plugin factory: %w", err) - } - oracleArgsNoPlugin := libocr2.OCR3OracleArgs[liquiditymanagermodels.Report]{ - BinaryNetworkEndpointFactory: d.peerWrapper.Peer2, - V2Bootstrappers: bootstrapPeers, - ContractTransmitter: rebalancerProvider.ContractTransmitterOCR3(), - ContractConfigTracker: rebalancerProvider.ContractConfigTracker(), - Database: ocrDB, - LocalConfig: lc, - MonitoringEndpoint: d.monitoringEndpointGen.GenMonitoringEndpoint( - rid.Network, - rid.ChainID, - spec.ContractID, - synchronization.OCR3Rebalancer, - ), - OffchainConfigDigester: rebalancerProvider.OffchainConfigDigester(), - OffchainKeyring: kb, - OnchainKeyring: ocr3impls.NewOnchainKeyring[liquiditymanagermodels.Report](kb, lggr), - ReportingPluginFactory: factory, - Logger: commonlogger.NewOCRWrapper(lggr.Named("RebalancerOracle"), d.cfg.OCR2().TraceLogging(), func(msg string) { - lggr.ErrorIf(d.jobORM.RecordError(context.Background(), jb.ID, msg), "unable to record error") - }), - MetricsRegisterer: prometheus.WrapRegistererWith(map[string]string{"job_name": jb.Name.ValueOrZero()}, prometheus.DefaultRegisterer), - } - oracle, err := libocr2.NewOracle(oracleArgsNoPlugin) - if err != nil { - return nil, fmt.Errorf("failed to create oracle: %w", err) - } - return []job.ServiceCtx{rebalancerProvider, job.NewServiceAdapter(oracle)}, nil -} - // errorLog implements [loop.ErrorLog] type errorLog struct { jobID int32 diff --git a/core/services/ocr2/plugins/ccip/ccipcommit/initializers.go b/core/services/ocr2/plugins/ccip/ccipcommit/initializers.go index b7543df155..9872aa6b6e 100644 --- a/core/services/ocr2/plugins/ccip/ccipcommit/initializers.go +++ b/core/services/ocr2/plugins/ccip/ccipcommit/initializers.go @@ -117,7 +117,7 @@ func NewCommitServices(ctx context.Context, ds sqlutil.DataSource, srcProvider c onRampAddress, ) - orm, err := cciporm.NewORM(ds) + orm, err := cciporm.NewORM(ds, lggr) if err != nil { return nil, err } diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/commit_store.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/commit_store.go index 3e58143a28..45026cc08b 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/commit_store.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/commit_store.go @@ -349,7 +349,7 @@ func (c *CommitStore) GetAcceptedCommitReportsGteTimestamp(ctx context.Context, logs, err := c.lp.FilteredLogs( ctx, - reportsQuery, + reportsQuery.Expressions, query.NewLimitAndSort(query.Limit{}, query.NewSortBySequence(query.Asc)), "GetAcceptedCommitReportsGteTimestamp", ) diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/commit_store.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/commit_store.go index ecc8acb576..5830c5e235 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/commit_store.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/commit_store.go @@ -364,7 +364,7 @@ func (c *CommitStore) GetAcceptedCommitReportsGteTimestamp(ctx context.Context, logs, err := c.lp.FilteredLogs( ctx, - reportsQuery, + reportsQuery.Expressions, query.NewLimitAndSort(query.Limit{}, query.NewSortBySequence(query.Asc)), "GetAcceptedCommitReportsGteTimestamp", ) diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/offramp.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/offramp.go index f853adfb6f..6debd78f14 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/offramp.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/offramp.go @@ -12,7 +12,6 @@ import ( "github.com/pkg/errors" "github.com/smartcontractkit/chainlink-common/pkg/config" - cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" 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 ad44555477..2806c26e22 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdb/price_service.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdb/price_service.go @@ -14,7 +14,6 @@ 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" @@ -24,6 +23,7 @@ import ( "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. @@ -49,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 @@ -100,10 +88,8 @@ func NewPriceService( 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, @@ -142,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() @@ -156,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 { @@ -240,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. @@ -446,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), }) @@ -478,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 0468c3addb..a25c5d3c47 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 @@ -19,7 +19,6 @@ 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" @@ -30,81 +29,6 @@ import ( "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, @@ -802,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) @@ -824,7 +749,7 @@ 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) @@ -896,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)) @@ -918,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/evm.go b/core/services/ocr2/plugins/ccip/internal/pricegetter/evm.go index 532ca4bee0..2d27dd1919 100644 --- a/core/services/ocr2/plugins/ccip/internal/pricegetter/evm.go +++ b/core/services/ocr2/plugins/ccip/internal/pricegetter/evm.go @@ -170,18 +170,24 @@ func (d *DynamicPriceGetter) performBatchCall(ctx context.Context, chainID uint6 // Construct request, adding a decimals and latestRound req per contract name var decimalsReq uint8 - batchGetLatestValuesRequest := make(map[string]types.ContractBatch) + batchGetLatestValuesRequest := make(types.BatchGetLatestValuesRequest) for i, call := range batchCalls.decimalCalls { - contractName := fmt.Sprintf("%v_%v", OffchainAggregator, i) - batchGetLatestValuesRequest[contractName] = append(batchGetLatestValuesRequest[contractName], types.BatchRead{ + boundContract := types.BoundContract{ + Address: call.ContractAddress().Hex(), + Name: fmt.Sprintf("%v_%v", OffchainAggregator, i), + } + batchGetLatestValuesRequest[boundContract] = append(batchGetLatestValuesRequest[boundContract], types.BatchRead{ ReadName: call.MethodName(), ReturnVal: &decimalsReq, }) } for i, call := range batchCalls.latestRoundDataCalls { - contractName := fmt.Sprintf("%v_%v", OffchainAggregator, i) - batchGetLatestValuesRequest[contractName] = append(batchGetLatestValuesRequest[contractName], types.BatchRead{ + boundContract := types.BoundContract{ + Address: call.ContractAddress().Hex(), + Name: fmt.Sprintf("%v_%v", OffchainAggregator, i), + } + batchGetLatestValuesRequest[boundContract] = append(batchGetLatestValuesRequest[boundContract], types.BatchRead{ ReadName: call.MethodName(), ReturnVal: &aggregator_v3_interface.LatestRoundData{}, }) @@ -200,8 +206,11 @@ func (d *DynamicPriceGetter) performBatchCall(ctx context.Context, chainID uint6 latestRoundCR := make([]aggregator_v3_interface.LatestRoundData, 0, nbDecimalCalls) var respErr error for j := range nbCalls { - contractName := fmt.Sprintf("%v_%v", OffchainAggregator, j) - offchainAggregatorRespSlice := result[contractName] + boundContract := types.BoundContract{ + Address: batchCalls.decimalCalls[j].ContractAddress().Hex(), + Name: fmt.Sprintf("%v_%v", OffchainAggregator, j), + } + offchainAggregatorRespSlice := result[boundContract] for _, read := range offchainAggregatorRespSlice { val, readErr := read.GetResult() 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 8ab2ce2bc4..cc3eb3d35c 100644 --- a/core/services/ocr2/plugins/ccip/internal/pricegetter/evm_test.go +++ b/core/services/ocr2/plugins/ccip/internal/pricegetter/evm_test.go @@ -198,10 +198,10 @@ func testParamAggregatorOnly(t *testing.T) testParameters { AnsweredInRound: big.NewInt(4000), } contractReaders := map[uint64]types.ContractReader{ - uint64(101): mockCR(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round1}), - uint64(102): mockCR(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round2}), - uint64(103): mockCR(t, []uint8{18}, []aggregator_v3_interface.LatestRoundData{round3}), - uint64(104): mockCR(t, []uint8{20}, []aggregator_v3_interface.LatestRoundData{round4}), + uint64(101): mockCR(t, []uint8{8}, cfg, []common.Address{TK1}, []aggregator_v3_interface.LatestRoundData{round1}), + uint64(102): mockCR(t, []uint8{8}, cfg, []common.Address{TK2}, []aggregator_v3_interface.LatestRoundData{round2}), + uint64(103): mockCR(t, []uint8{18}, cfg, []common.Address{TK3}, []aggregator_v3_interface.LatestRoundData{round3}), + uint64(104): mockCR(t, []uint8{20}, cfg, []common.Address{TK4}, []aggregator_v3_interface.LatestRoundData{round4}), } expectedTokenPrices := map[common.Address]big.Int{ TK1: *multExp(round1.Answer, 10), // expected in 1e18 format. @@ -261,8 +261,8 @@ func testParamAggregatorOnlyMulti(t *testing.T) testParameters { AnsweredInRound: big.NewInt(3000), } contractReaders := map[uint64]types.ContractReader{ - uint64(101): mockCR(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round1}), - uint64(102): mockCR(t, []uint8{8, 8}, []aggregator_v3_interface.LatestRoundData{round2, round3}), + uint64(101): mockCR(t, []uint8{8}, cfg, []common.Address{TK1}, []aggregator_v3_interface.LatestRoundData{round1}), + uint64(102): mockCR(t, []uint8{8, 8}, cfg, []common.Address{TK2, TK3}, []aggregator_v3_interface.LatestRoundData{round2, round3}), } expectedTokenPrices := map[common.Address]big.Int{ TK1: *multExp(round1.Answer, 10), @@ -347,8 +347,8 @@ func testParamNoAggregatorForToken(t *testing.T) testParameters { AnsweredInRound: big.NewInt(2000), } contractReaders := map[uint64]types.ContractReader{ - uint64(101): mockCR(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round1}), - uint64(102): mockCR(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round2}), + uint64(101): mockCR(t, []uint8{8}, cfg, []common.Address{TK1}, []aggregator_v3_interface.LatestRoundData{round1}), + uint64(102): mockCR(t, []uint8{8}, cfg, []common.Address{TK2}, []aggregator_v3_interface.LatestRoundData{round2}), } expectedTokenPrices := map[common.Address]big.Int{ TK1: *round1.Answer, @@ -401,8 +401,8 @@ func testParamAggregatorAndStaticValid(t *testing.T) testParameters { AnsweredInRound: big.NewInt(2000), } contractReaders := map[uint64]types.ContractReader{ - uint64(101): mockCR(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round1}), - uint64(102): mockCR(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round2}), + uint64(101): mockCR(t, []uint8{8}, cfg, []common.Address{TK1}, []aggregator_v3_interface.LatestRoundData{round1}), + uint64(102): mockCR(t, []uint8{8}, cfg, []common.Address{TK2}, []aggregator_v3_interface.LatestRoundData{round2}), } expectedTokenPrices := map[common.Address]big.Int{ TK1: *multExp(round1.Answer, 10), @@ -464,9 +464,9 @@ func testParamAggregatorAndStaticTokenCollision(t *testing.T) testParameters { AnsweredInRound: big.NewInt(3000), } contractReaders := map[uint64]types.ContractReader{ - uint64(101): mockCR(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round1}), - uint64(102): mockCR(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round2}), - uint64(103): mockCR(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round3}), + uint64(101): mockCR(t, []uint8{8}, cfg, []common.Address{TK1}, []aggregator_v3_interface.LatestRoundData{round1}), + uint64(102): mockCR(t, []uint8{8}, cfg, []common.Address{TK2}, []aggregator_v3_interface.LatestRoundData{round2}), + uint64(103): mockCR(t, []uint8{8}, cfg, []common.Address{TK3}, []aggregator_v3_interface.LatestRoundData{round3}), } return testParameters{ cfg: cfg, @@ -504,7 +504,7 @@ func testParamBatchCallReturnsErr(t *testing.T) testParameters { AnsweredInRound: big.NewInt(1000), } contractReaders := map[uint64]types.ContractReader{ - uint64(101): mockCR(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round1}), + uint64(101): mockCR(t, []uint8{8}, cfg, []common.Address{TK1}, []aggregator_v3_interface.LatestRoundData{round1}), uint64(102): mockErrCR(t), } return testParameters{ @@ -563,9 +563,9 @@ func testLessInputsThanDefinedPrices(t *testing.T) testParameters { AnsweredInRound: big.NewInt(3000), } contractReaders := map[uint64]types.ContractReader{ - uint64(101): mockCR(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round1}), - uint64(102): mockCR(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round2}), - uint64(103): mockCR(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round3}), + uint64(101): mockCR(t, []uint8{8}, cfg, []common.Address{TK1}, []aggregator_v3_interface.LatestRoundData{round1}), + uint64(102): mockCR(t, []uint8{8}, cfg, []common.Address{TK2}, []aggregator_v3_interface.LatestRoundData{round2}), + uint64(103): mockCR(t, []uint8{8}, cfg, []common.Address{TK3}, []aggregator_v3_interface.LatestRoundData{round3}), } expectedTokenPrices := map[common.Address]big.Int{ TK1: *multExp(round1.Answer, 10), @@ -628,9 +628,9 @@ func testGetAllTokensAggregatorAndStatic(t *testing.T) testParameters { AnsweredInRound: big.NewInt(3000), } contractReaders := map[uint64]types.ContractReader{ - uint64(101): mockCR(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round1}), - uint64(102): mockCR(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round2}), - uint64(103): mockCR(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round3}), + uint64(101): mockCR(t, []uint8{8}, cfg, []common.Address{TK1}, []aggregator_v3_interface.LatestRoundData{round1}), + uint64(102): mockCR(t, []uint8{8}, cfg, []common.Address{TK2}, []aggregator_v3_interface.LatestRoundData{round2}), + uint64(103): mockCR(t, []uint8{8}, cfg, []common.Address{TK3}, []aggregator_v3_interface.LatestRoundData{round3}), } expectedTokenPricesForAll := map[common.Address]big.Int{ TK1: *multExp(round1.Answer, 10), @@ -688,9 +688,9 @@ func testGetAllTokensAggregatorOnly(t *testing.T) testParameters { AnsweredInRound: big.NewInt(3000), } contractReaders := map[uint64]types.ContractReader{ - uint64(101): mockCR(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round1}), - uint64(102): mockCR(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round2}), - uint64(103): mockCR(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round3}), + uint64(101): mockCR(t, []uint8{8}, cfg, []common.Address{TK1}, []aggregator_v3_interface.LatestRoundData{round1}), + uint64(102): mockCR(t, []uint8{8}, cfg, []common.Address{TK2}, []aggregator_v3_interface.LatestRoundData{round2}), + uint64(103): mockCR(t, []uint8{8}, cfg, []common.Address{TK3}, []aggregator_v3_interface.LatestRoundData{round3}), } expectedTokenPricesForAll := map[common.Address]big.Int{ @@ -737,17 +737,18 @@ func testGetAllTokensStaticOnly(t *testing.T) testParameters { } } -func mockCR(t *testing.T, decimals []uint8, rounds []aggregator_v3_interface.LatestRoundData) *mocks.ChainReader { - caller := mocks.NewChainReader(t) +func mockCR(t *testing.T, decimals []uint8, cfg config.DynamicPriceGetterConfig, addr []common.Address, rounds []aggregator_v3_interface.LatestRoundData) *mocks.ContractReader { + caller := mocks.NewContractReader(t) // Mock batch calls per chain: all decimals calls then all latestRoundData calls. - // bGLVR = batchGetLatestValueResult - //nolint:all - var bGLVR types.BatchGetLatestValuesResult - bGLVR = make(map[string]types.ContractBatchResults, 1) + bGLVR := make(types.BatchGetLatestValuesResult) for i := range len(decimals) { - bGLVR[fmt.Sprintf("%v_%v", OffchainAggregator, i)] = make([]types.BatchReadResult, 0, 2) + boundContract := types.BoundContract{ + Address: cfg.AggregatorPrices[addr[i]].AggregatorContractAddress.Hex(), + Name: fmt.Sprintf("%v_%v", OffchainAggregator, i), + } + bGLVR[boundContract] = types.ContractBatchResults{} } for i, d := range decimals { contractName := fmt.Sprintf("%v_%v", OffchainAggregator, i) @@ -755,7 +756,11 @@ func mockCR(t *testing.T, decimals []uint8, rounds []aggregator_v3_interface.Lat ReadName: DecimalsMethodName, } readRes.SetResult(&d, nil) - bGLVR[contractName] = append(bGLVR[contractName], readRes) + boundContract := types.BoundContract{ + Address: cfg.AggregatorPrices[addr[i]].AggregatorContractAddress.Hex(), + Name: contractName, + } + bGLVR[boundContract] = append(bGLVR[boundContract], readRes) } for i, r := range rounds { @@ -764,7 +769,11 @@ func mockCR(t *testing.T, decimals []uint8, rounds []aggregator_v3_interface.Lat ReadName: LatestRoundDataMethodName, } readRes.SetResult(&r, nil) - bGLVR[contractName] = append(bGLVR[contractName], readRes) + boundContract := types.BoundContract{ + Address: cfg.AggregatorPrices[addr[i]].AggregatorContractAddress.Hex(), + Name: contractName, + } + bGLVR[boundContract] = append(bGLVR[boundContract], readRes) } caller.On("Bind", mock.Anything, mock.Anything).Return(nil).Maybe() @@ -772,8 +781,8 @@ func mockCR(t *testing.T, decimals []uint8, rounds []aggregator_v3_interface.Lat return caller } -func mockErrCR(t *testing.T) *mocks.ChainReader { - caller := mocks.NewChainReader(t) +func mockErrCR(t *testing.T) *mocks.ContractReader { + caller := mocks.NewContractReader(t) caller.On("Bind", mock.Anything, mock.Anything).Return(nil).Maybe() caller.On("BatchGetLatestValues", mock.Anything, mock.Anything).Return(nil, assert.AnError).Maybe() return caller diff --git a/core/services/ocr2/plugins/ccip/prices/da_price_estimator.go b/core/services/ocr2/plugins/ccip/prices/da_price_estimator.go index 34239398c8..d5d2aa6865 100644 --- a/core/services/ocr2/plugins/ccip/prices/da_price_estimator.go +++ b/core/services/ocr2/plugins/ccip/prices/da_price_estimator.go @@ -43,7 +43,6 @@ func (g DAGasPriceEstimator) GetGasPrice(ctx context.Context) (*big.Int, error) return nil, err } var gasPrice *big.Int = execGasPrice - if gasPrice.BitLen() > int(g.priceEncodingLength) { return nil, fmt.Errorf("native gas price exceeded max range %+v", gasPrice) } diff --git a/core/services/ocr2/plugins/ccip/testhelpers/integration/chainlink.go b/core/services/ocr2/plugins/ccip/testhelpers/integration/chainlink.go index 04d6663959..a689424d0e 100644 --- a/core/services/ocr2/plugins/ccip/testhelpers/integration/chainlink.go +++ b/core/services/ocr2/plugins/ccip/testhelpers/integration/chainlink.go @@ -460,7 +460,7 @@ func setupNodeCCIP( }, CSAETHKeystore: simEthKeyStore, } - loopRegistry := plugins.NewLoopRegistry(lggr.Named("LoopRegistry"), config.Tracing()) + loopRegistry := plugins.NewLoopRegistry(lggr.Named("LoopRegistry"), config.Tracing(), config.Telemetry()) relayerFactory := chainlink.RelayerFactory{ Logger: lggr, LoopRegistry: loopRegistry, @@ -490,7 +490,7 @@ func setupNodeCCIP( RestrictedHTTPClient: &http.Client{}, AuditLogger: audit.NoopLogger, MailMon: mailMon, - LoopRegistry: plugins.NewLoopRegistry(lggr, config.Tracing()), + LoopRegistry: plugins.NewLoopRegistry(lggr, config.Tracing(), config.Telemetry()), }) require.NoError(t, err) require.NoError(t, app.GetKeyStore().Unlock(ctx, "password")) diff --git a/core/services/ocr2/plugins/ccip/testhelpers/testhelpers_1_4_0/chainlink.go b/core/services/ocr2/plugins/ccip/testhelpers/testhelpers_1_4_0/chainlink.go index 8d7a4551f1..769874c6e4 100644 --- a/core/services/ocr2/plugins/ccip/testhelpers/testhelpers_1_4_0/chainlink.go +++ b/core/services/ocr2/plugins/ccip/testhelpers/testhelpers_1_4_0/chainlink.go @@ -456,7 +456,7 @@ func setupNodeCCIP( }, CSAETHKeystore: simEthKeyStore, } - loopRegistry := plugins.NewLoopRegistry(lggr.Named("LoopRegistry"), config.Tracing()) + loopRegistry := plugins.NewLoopRegistry(lggr.Named("LoopRegistry"), config.Tracing(), config.Telemetry()) relayerFactory := chainlink.RelayerFactory{ Logger: lggr, LoopRegistry: loopRegistry, @@ -486,7 +486,7 @@ func setupNodeCCIP( RestrictedHTTPClient: &http.Client{}, AuditLogger: audit.NoopLogger, MailMon: mailMon, - LoopRegistry: plugins.NewLoopRegistry(lggr, config.Tracing()), + LoopRegistry: plugins.NewLoopRegistry(lggr, config.Tracing(), config.Telemetry()), }) ctx := testutils.Context(t) require.NoError(t, err) diff --git a/core/services/ocr2/plugins/functions/integration_tests/v1/internal/testutils.go b/core/services/ocr2/plugins/functions/integration_tests/v1/internal/testutils.go index 639d8ec04d..104e9f4da6 100644 --- a/core/services/ocr2/plugins/functions/integration_tests/v1/internal/testutils.go +++ b/core/services/ocr2/plugins/functions/integration_tests/v1/internal/testutils.go @@ -29,6 +29,7 @@ import ( ocrtypes2 "github.com/smartcontractkit/libocr/offchainreporting2plus/types" commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" diff --git a/core/services/ocr2/plugins/functions/plugin.go b/core/services/ocr2/plugins/functions/plugin.go index d6ffa1a3f0..b36d3e37db 100644 --- a/core/services/ocr2/plugins/functions/plugin.go +++ b/core/services/ocr2/plugins/functions/plugin.go @@ -23,6 +23,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/functions" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/connector" hc "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/common" + hf "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions" gwAllowlist "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/allowlist" gwSubscriptions "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/subscriptions" "github.com/smartcontractkit/chainlink/v2/core/services/job" @@ -174,11 +175,12 @@ func NewFunctionsServices(ctx context.Context, functionsOracleArgs, thresholdOra return nil, errors.Wrap(err, "failed to create a OnchainSubscriptions") } connectorLogger := conf.Logger.Named("GatewayConnector").With("jobName", conf.Job.PipelineSpec.JobName) - connector, err2 := NewConnector(ctx, &pluginConfig, conf.EthKeystore, conf.Chain.ID(), s4Storage, allowlist, rateLimiter, subscriptions, functionsListener, offchainTransmitter, connectorLogger) + connector, handler, err2 := NewConnector(ctx, &pluginConfig, conf.EthKeystore, conf.Chain.ID(), s4Storage, allowlist, rateLimiter, subscriptions, functionsListener, offchainTransmitter, connectorLogger) if err2 != nil { return nil, errors.Wrap(err, "failed to create a GatewayConnector") } allServices = append(allServices, connector) + allServices = append(allServices, handler) } else { listenerLogger.Warn("Insufficient config, GatewayConnector will not be enabled") } @@ -201,29 +203,34 @@ func NewFunctionsServices(ctx context.Context, functionsOracleArgs, thresholdOra return allServices, nil } -func NewConnector(ctx context.Context, pluginConfig *config.PluginConfig, ethKeystore keystore.Eth, chainID *big.Int, s4Storage s4.Storage, allowlist gwAllowlist.OnchainAllowlist, rateLimiter *hc.RateLimiter, subscriptions gwSubscriptions.OnchainSubscriptions, listener functions.FunctionsListener, offchainTransmitter functions.OffchainTransmitter, lggr logger.Logger) (connector.GatewayConnector, error) { +func NewConnector(ctx context.Context, pluginConfig *config.PluginConfig, ethKeystore keystore.Eth, chainID *big.Int, s4Storage s4.Storage, allowlist gwAllowlist.OnchainAllowlist, rateLimiter *hc.RateLimiter, subscriptions gwSubscriptions.OnchainSubscriptions, listener functions.FunctionsListener, offchainTransmitter functions.OffchainTransmitter, lggr logger.Logger) (connector.GatewayConnector, connector.GatewayConnectorHandler, error) { enabledKeys, err := ethKeystore.EnabledKeysForChain(ctx, chainID) if err != nil { - return nil, err + return nil, nil, err } configuredNodeAddress := common.HexToAddress(pluginConfig.GatewayConnectorConfig.NodeAddress) idx := slices.IndexFunc(enabledKeys, func(key ethkey.KeyV2) bool { return key.Address == configuredNodeAddress }) if idx == -1 { - return nil, errors.New("key for configured node address not found") + return nil, nil, errors.New("key for configured node address not found") } signerKey := enabledKeys[idx].ToEcdsaPrivKey() if enabledKeys[idx].ID() != pluginConfig.GatewayConnectorConfig.NodeAddress { - return nil, errors.New("node address mismatch") + return nil, nil, errors.New("node address mismatch") } handler, err := functions.NewFunctionsConnectorHandler(pluginConfig, signerKey, s4Storage, allowlist, rateLimiter, subscriptions, listener, offchainTransmitter, lggr) if err != nil { - return nil, err + return nil, nil, err } - connector, err := connector.NewGatewayConnector(pluginConfig.GatewayConnectorConfig, handler, handler, clockwork.NewRealClock(), lggr) + // handler acts as a signer here + connector, err := connector.NewGatewayConnector(pluginConfig.GatewayConnectorConfig, handler, clockwork.NewRealClock(), lggr) if err != nil { - return nil, err + return nil, nil, err + } + err = connector.AddHandler([]string{hf.MethodSecretsSet, hf.MethodSecretsList, hf.MethodHeartbeat}, handler) + if err != nil { + return nil, nil, err } handler.SetConnector(connector) - return connector, nil + return connector, handler, nil } diff --git a/core/services/ocr2/plugins/functions/plugin_test.go b/core/services/ocr2/plugins/functions/plugin_test.go index fdd20b0a93..b3a120894d 100644 --- a/core/services/ocr2/plugins/functions/plugin_test.go +++ b/core/services/ocr2/plugins/functions/plugin_test.go @@ -47,7 +47,7 @@ func TestNewConnector_Success(t *testing.T) { config := &config.PluginConfig{ GatewayConnectorConfig: gwcCfg, } - _, err = functions.NewConnector(ctx, config, ethKeystore, chainID, s4Storage, allowlist, rateLimiter, subscriptions, listener, offchainTransmitter, logger.TestLogger(t)) + _, _, err = functions.NewConnector(ctx, config, ethKeystore, chainID, s4Storage, allowlist, rateLimiter, subscriptions, listener, offchainTransmitter, logger.TestLogger(t)) require.NoError(t, err) } @@ -78,6 +78,6 @@ func TestNewConnector_NoKeyForConfiguredAddress(t *testing.T) { config := &config.PluginConfig{ GatewayConnectorConfig: gwcCfg, } - _, err = functions.NewConnector(ctx, config, ethKeystore, chainID, s4Storage, allowlist, rateLimiter, subscriptions, listener, offchainTransmitter, logger.TestLogger(t)) + _, _, err = functions.NewConnector(ctx, config, ethKeystore, chainID, s4Storage, allowlist, rateLimiter, subscriptions, listener, offchainTransmitter, logger.TestLogger(t)) require.Error(t, err) } diff --git a/core/services/ocr2/plugins/generic/relayerset_test.go b/core/services/ocr2/plugins/generic/relayerset_test.go index b5eb16682d..44ed216520 100644 --- a/core/services/ocr2/plugins/generic/relayerset_test.go +++ b/core/services/ocr2/plugins/generic/relayerset_test.go @@ -158,6 +158,10 @@ func (t *TestRelayer) NewContractReader(_ context.Context, _ []byte) (types.Cont panic("implement me") } +func (t *TestRelayer) LatestHead(_ context.Context) (types.Head, error) { + panic("implement me") +} + func (t *TestRelayer) GetChainStatus(ctx context.Context) (types.ChainStatus, error) { panic("implement me") } diff --git a/core/services/ocr2/plugins/liquiditymanager/internal/integration_test.go b/core/services/ocr2/plugins/liquiditymanager/internal/integration_test.go index 765366a5c3..080e9b82e3 100644 --- a/core/services/ocr2/plugins/liquiditymanager/internal/integration_test.go +++ b/core/services/ocr2/plugins/liquiditymanager/internal/integration_test.go @@ -160,7 +160,7 @@ func setupNodeOCR3( } relayerFactory := chainlink.RelayerFactory{ Logger: lggr, - LoopRegistry: plugins.NewLoopRegistry(lggr.Named("LoopRegistry"), config.Tracing()), + LoopRegistry: plugins.NewLoopRegistry(lggr.Named("LoopRegistry"), config.Tracing(), config.Telemetry()), GRPCOpts: loop.GRPCOpts{}, } initOps := []chainlink.CoreRelayerChainInitFunc{chainlink.InitEVM(testutils.Context(t), relayerFactory, evmOpts)} @@ -179,7 +179,7 @@ func setupNodeOCR3( RestrictedHTTPClient: &http.Client{}, AuditLogger: audit.NoopLogger, MailMon: mailMon, - LoopRegistry: plugins.NewLoopRegistry(lggr, config.Tracing()), + LoopRegistry: plugins.NewLoopRegistry(lggr, config.Tracing(), config.Telemetry()), }) require.NoError(t, err) require.NoError(t, app.GetKeyStore().Unlock(ctx, "password")) diff --git a/core/services/ocr2/plugins/llo/config/config.go b/core/services/ocr2/plugins/llo/config/config.go index 892229c46c..7bd36b4ff8 100644 --- a/core/services/ocr2/plugins/llo/config/config.go +++ b/core/services/ocr2/plugins/llo/config/config.go @@ -9,19 +9,18 @@ import ( "fmt" "net/url" "regexp" + "sort" "github.com/ethereum/go-ethereum/common" llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" + mercuryconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/mercury/config" "github.com/smartcontractkit/chainlink/v2/core/utils" ) type PluginConfig struct { - RawServerURL string `json:"serverURL" toml:"serverURL"` - ServerPubKey utils.PlainHexBytes `json:"serverPubKey" toml:"serverPubKey"` - ChannelDefinitionsContractAddress common.Address `json:"channelDefinitionsContractAddress" toml:"channelDefinitionsContractAddress"` ChannelDefinitionsContractFromBlock int64 `json:"channelDefinitionsContractFromBlock" toml:"channelDefinitionsContractFromBlock"` @@ -39,27 +38,42 @@ type PluginConfig struct { // KeyBundleIDs maps supported keys to their respective bundle IDs // Key must match llo's ReportFormat KeyBundleIDs map[string]string `json:"keyBundleIDs" toml:"keyBundleIDs"` + + DonID uint32 `json:"donID" toml:"donID"` + + // Mercury servers + Servers map[string]utils.PlainHexBytes `json:"servers" toml:"servers"` } func (p *PluginConfig) Unmarshal(data []byte) error { return json.Unmarshal(data, p) } +func (p PluginConfig) GetServers() (servers []mercuryconfig.Server) { + for url, pubKey := range p.Servers { + servers = append(servers, mercuryconfig.Server{URL: wssRegexp.ReplaceAllString(url, ""), PubKey: pubKey}) + } + sort.Slice(servers, func(i, j int) bool { + return servers[i].URL < servers[j].URL + }) + return +} + func (p PluginConfig) Validate() (merr error) { - if p.RawServerURL == "" { - merr = errors.New("llo: ServerURL must be specified") + if p.DonID == 0 { + merr = errors.Join(merr, errors.New("llo: DonID must be specified and not zero")) + } + + if len(p.Servers) == 0 { + merr = errors.Join(merr, errors.New("llo: At least one Mercury server must be specified")) } else { - var normalizedURI string - if schemeRegexp.MatchString(p.RawServerURL) { - normalizedURI = p.RawServerURL - } else { - normalizedURI = fmt.Sprintf("wss://%s", p.RawServerURL) - } - uri, err := url.ParseRequestURI(normalizedURI) - if err != nil { - merr = fmt.Errorf("llo: invalid value for ServerURL: %w", err) - } else if uri.Scheme != "wss" { - merr = fmt.Errorf(`llo: invalid scheme specified for MercuryServer, got: %q (scheme: %q) but expected a websocket url e.g. "192.0.2.2:4242" or "wss://192.0.2.2:4242"`, p.RawServerURL, uri.Scheme) + for serverName, serverPubKey := range p.Servers { + if err := validateURL(serverName); err != nil { + merr = errors.Join(merr, fmt.Errorf("llo: invalid value for ServerURL: %w", err)) + } + if len(serverPubKey) != 32 { + merr = errors.Join(merr, errors.New("llo: ServerPubKey must be a 32-byte hex string")) + } } } @@ -74,21 +88,36 @@ func (p PluginConfig) Validate() (merr error) { if err := json.Unmarshal([]byte(p.ChannelDefinitions), &cd); err != nil { merr = errors.Join(merr, fmt.Errorf("channelDefinitions is invalid JSON: %w", err)) } + // TODO: Verify Opts format here? + // MERC-3524 } else { if p.ChannelDefinitionsContractAddress == (common.Address{}) { merr = errors.Join(merr, errors.New("llo: ChannelDefinitionsContractAddress is required if ChannelDefinitions is not specified")) } } - if len(p.ServerPubKey) != 32 { - merr = errors.Join(merr, errors.New("llo: ServerPubKey is required and must be a 32-byte hex string")) - } - merr = errors.Join(merr, validateKeyBundleIDs(p.KeyBundleIDs)) return merr } +func validateURL(rawServerURL string) error { + var normalizedURI string + if schemeRegexp.MatchString(rawServerURL) { + normalizedURI = rawServerURL + } else { + normalizedURI = fmt.Sprintf("wss://%s", rawServerURL) + } + uri, err := url.ParseRequestURI(normalizedURI) + if err != nil { + return fmt.Errorf(`llo: invalid value for ServerURL, got: %q`, rawServerURL) + } + if uri.Scheme != "wss" { + return fmt.Errorf(`llo: invalid scheme specified for MercuryServer, got: %q (scheme: %q) but expected a websocket url e.g. "192.0.2.2:4242" or "wss://192.0.2.2:4242"`, rawServerURL, uri.Scheme) + } + return nil +} + func validateKeyBundleIDs(keyBundleIDs map[string]string) error { for k, v := range keyBundleIDs { if k == "" { @@ -109,7 +138,3 @@ func validateKeyBundleIDs(keyBundleIDs map[string]string) error { var schemeRegexp = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9+.-]*://`) var wssRegexp = regexp.MustCompile(`^wss://`) - -func (p PluginConfig) ServerURL() string { - return wssRegexp.ReplaceAllString(p.RawServerURL, "") -} diff --git a/core/services/ocr2/plugins/llo/config/config_test.go b/core/services/ocr2/plugins/llo/config/config_test.go index 136fac87a5..096543b252 100644 --- a/core/services/ocr2/plugins/llo/config/config_test.go +++ b/core/services/ocr2/plugins/llo/config/config_test.go @@ -7,6 +7,8 @@ import ( "github.com/pelletier/go-toml/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/v2/core/utils" ) func Test_Config(t *testing.T) { @@ -31,8 +33,7 @@ func Test_Config(t *testing.T) { t.Run("with all possible values set", func(t *testing.T) { rawToml := fmt.Sprintf(` - ServerURL = "example.com:80" - ServerPubKey = "724ff6eae9e900270edfff233e16322a70ec06e1a6e62a81ef13921f398f6c93" + Servers = { "example.com:80" = "724ff6eae9e900270edfff233e16322a70ec06e1a6e62a81ef13921f398f6c93", "example2.invalid:1234" = "524ff6eae9e900270edfff233e16322a70ec06e1a6e62a81ef13921f398f6c93" } BenchmarkMode = true ChannelDefinitionsContractAddress = "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" ChannelDefinitionsContractFromBlock = 1234 @@ -44,8 +45,8 @@ func Test_Config(t *testing.T) { err := toml.Unmarshal([]byte(rawToml), &mc) require.NoError(t, err) - assert.Equal(t, "example.com:80", mc.RawServerURL) - assert.Equal(t, "724ff6eae9e900270edfff233e16322a70ec06e1a6e62a81ef13921f398f6c93", mc.ServerPubKey.String()) + assert.Len(t, mc.Servers, 2) + assert.Equal(t, map[string]utils.PlainHexBytes{"example.com:80": utils.PlainHexBytes{0x72, 0x4f, 0xf6, 0xea, 0xe9, 0xe9, 0x0, 0x27, 0xe, 0xdf, 0xff, 0x23, 0x3e, 0x16, 0x32, 0x2a, 0x70, 0xec, 0x6, 0xe1, 0xa6, 0xe6, 0x2a, 0x81, 0xef, 0x13, 0x92, 0x1f, 0x39, 0x8f, 0x6c, 0x93}, "example2.invalid:1234": utils.PlainHexBytes{0x52, 0x4f, 0xf6, 0xea, 0xe9, 0xe9, 0x0, 0x27, 0xe, 0xdf, 0xff, 0x23, 0x3e, 0x16, 0x32, 0x2a, 0x70, 0xec, 0x6, 0xe1, 0xa6, 0xe6, 0x2a, 0x81, 0xef, 0x13, 0x92, 0x1f, 0x39, 0x8f, 0x6c, 0x93}}, mc.Servers) assert.Equal(t, "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF", mc.ChannelDefinitionsContractAddress.Hex()) assert.Equal(t, int64(1234), mc.ChannelDefinitionsContractFromBlock) assert.JSONEq(t, cdjson, mc.ChannelDefinitions) @@ -60,8 +61,8 @@ func Test_Config(t *testing.T) { t.Run("with only channelDefinitions", func(t *testing.T) { rawToml := fmt.Sprintf(` - ServerURL = "example.com:80" - ServerPubKey = "724ff6eae9e900270edfff233e16322a70ec06e1a6e62a81ef13921f398f6c93" + Servers = { "example.com:80" = "724ff6eae9e900270edfff233e16322a70ec06e1a6e62a81ef13921f398f6c93" } + DonID = 12345 ChannelDefinitions = """ %s """`, cdjson) @@ -70,9 +71,9 @@ func Test_Config(t *testing.T) { err := toml.Unmarshal([]byte(rawToml), &mc) require.NoError(t, err) - assert.Equal(t, "example.com:80", mc.RawServerURL) - assert.Equal(t, "724ff6eae9e900270edfff233e16322a70ec06e1a6e62a81ef13921f398f6c93", mc.ServerPubKey.String()) + assert.Len(t, mc.Servers, 1) assert.JSONEq(t, cdjson, mc.ChannelDefinitions) + assert.Equal(t, uint32(12345), mc.DonID) assert.False(t, mc.BenchmarkMode) err = mc.Validate() @@ -80,17 +81,17 @@ func Test_Config(t *testing.T) { }) t.Run("with only channelDefinitions contract details", func(t *testing.T) { rawToml := ` - ServerURL = "example.com:80" - ServerPubKey = "724ff6eae9e900270edfff233e16322a70ec06e1a6e62a81ef13921f398f6c93" + Servers = { "example.com:80" = "724ff6eae9e900270edfff233e16322a70ec06e1a6e62a81ef13921f398f6c93" } + DonID = 12345 ChannelDefinitionsContractAddress = "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"` var mc PluginConfig err := toml.Unmarshal([]byte(rawToml), &mc) require.NoError(t, err) - assert.Equal(t, "example.com:80", mc.RawServerURL) - assert.Equal(t, "724ff6eae9e900270edfff233e16322a70ec06e1a6e62a81ef13921f398f6c93", mc.ServerPubKey.String()) + assert.Len(t, mc.Servers, 1) assert.Equal(t, "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF", mc.ChannelDefinitionsContractAddress.Hex()) + assert.Equal(t, uint32(12345), mc.DonID) assert.False(t, mc.BenchmarkMode) err = mc.Validate() @@ -98,15 +99,16 @@ func Test_Config(t *testing.T) { }) t.Run("with missing ChannelDefinitionsContractAddress", func(t *testing.T) { rawToml := ` - ServerURL = "example.com:80" - ServerPubKey = "724ff6eae9e900270edfff233e16322a70ec06e1a6e62a81ef13921f398f6c93"` + DonID = 12345 + Servers = { "example.com:80" = "724ff6eae9e900270edfff233e16322a70ec06e1a6e62a81ef13921f398f6c93" } + ` var mc PluginConfig err := toml.Unmarshal([]byte(rawToml), &mc) require.NoError(t, err) - assert.Equal(t, "example.com:80", mc.RawServerURL) - assert.Equal(t, "724ff6eae9e900270edfff233e16322a70ec06e1a6e62a81ef13921f398f6c93", mc.ServerPubKey.String()) + assert.Len(t, mc.Servers, 1) + assert.Equal(t, uint32(12345), mc.DonID) assert.False(t, mc.BenchmarkMode) err = mc.Validate() @@ -135,8 +137,39 @@ func Test_Config(t *testing.T) { err = mc.Validate() require.Error(t, err) - assert.Contains(t, err.Error(), `invalid scheme specified for MercuryServer, got: "http://example.com" (scheme: "http") but expected a websocket url e.g. "192.0.2.2:4242" or "wss://192.0.2.2:4242"`) - assert.Contains(t, err.Error(), `ServerPubKey is required and must be a 32-byte hex string`) + assert.Contains(t, err.Error(), `DonID must be specified and not zero`) + assert.Contains(t, err.Error(), `At least one Mercury server must be specified`) + assert.Contains(t, err.Error(), `ChannelDefinitionsContractAddress is required if ChannelDefinitions is not specified`) }) }) } + +func Test_PluginConfig_Validate(t *testing.T) { + t.Run("with invalid URLs or keys", func(t *testing.T) { + servers := map[string]utils.PlainHexBytes{ + "not a valid url": utils.PlainHexBytes([]byte{1, 2, 3}), + "mercuryserver.invalid:1234/foo": nil, + } + pc := PluginConfig{Servers: servers} + + err := pc.Validate() + assert.Contains(t, err.Error(), "ServerPubKey must be a 32-byte hex string") + assert.Contains(t, err.Error(), "invalid value for ServerURL: llo: invalid value for ServerURL, got: \"not a valid url\"") + }) +} + +func Test_PluginConfig_GetServers(t *testing.T) { + t.Run("with multiple servers", func(t *testing.T) { + servers := map[string]utils.PlainHexBytes{ + "example.com:80": utils.PlainHexBytes([]byte{1, 2, 3}), + "mercuryserver.invalid:1234/foo": utils.PlainHexBytes([]byte{4, 5, 6}), + } + pc := PluginConfig{Servers: servers} + + require.Len(t, pc.GetServers(), 2) + assert.Equal(t, "example.com:80", pc.GetServers()[0].URL) + assert.Equal(t, utils.PlainHexBytes{1, 2, 3}, pc.GetServers()[0].PubKey) + assert.Equal(t, "mercuryserver.invalid:1234/foo", pc.GetServers()[1].URL) + assert.Equal(t, utils.PlainHexBytes{4, 5, 6}, pc.GetServers()[1].PubKey) + }) +} diff --git a/core/services/ocr2/plugins/llo/helpers_test.go b/core/services/ocr2/plugins/llo/helpers_test.go new file mode 100644 index 0000000000..bd73dcccfc --- /dev/null +++ b/core/services/ocr2/plugins/llo/helpers_test.go @@ -0,0 +1,425 @@ +package llo_test + +import ( + "context" + "crypto/ed25519" + "errors" + "fmt" + "io" + "math/big" + "net" + "net/http" + "net/http/httptest" + "net/url" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/common" + "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" + + "github.com/smartcontractkit/wsrpc" + "github.com/smartcontractkit/wsrpc/credentials" + "github.com/smartcontractkit/wsrpc/peer" + + ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" + + "github.com/smartcontractkit/chainlink/v2/core/bridges" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/keystest" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/csakey" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ocr2key" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/validate" + "github.com/smartcontractkit/chainlink/v2/core/services/ocrbootstrap" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc/pb" + "github.com/smartcontractkit/chainlink/v2/core/services/streams" + "github.com/smartcontractkit/chainlink/v2/core/store/models" + "github.com/smartcontractkit/chainlink/v2/core/utils/testutils/heavyweight" +) + +var _ pb.MercuryServer = &mercuryServer{} + +type request struct { + pk credentials.StaticSizedPublicKey + req *pb.TransmitRequest +} + +func (r request) TransmitterID() ocr2types.Account { + return ocr2types.Account(fmt.Sprintf("%x", r.pk)) +} + +type mercuryServer struct { + privKey ed25519.PrivateKey + reqsCh chan request + t *testing.T +} + +func NewMercuryServer(t *testing.T, privKey ed25519.PrivateKey, reqsCh chan request) *mercuryServer { + return &mercuryServer{privKey, reqsCh, t} +} + +func (s *mercuryServer) Transmit(ctx context.Context, req *pb.TransmitRequest) (*pb.TransmitResponse, error) { + p, ok := peer.FromContext(ctx) + if !ok { + return nil, errors.New("could not extract public key") + } + r := request{p.PublicKey, req} + s.reqsCh <- r + + return &pb.TransmitResponse{ + Code: 1, + Error: "", + }, nil +} + +func (s *mercuryServer) LatestReport(ctx context.Context, lrr *pb.LatestReportRequest) (*pb.LatestReportResponse, error) { + panic("should not be called") +} + +func startMercuryServer(t *testing.T, srv *mercuryServer, pubKeys []ed25519.PublicKey) (serverURL string) { + // Set up the wsrpc server + lis, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("[MAIN] failed to listen: %v", err) + } + serverURL = lis.Addr().String() + s := wsrpc.NewServer(wsrpc.WithCreds(srv.privKey, pubKeys)) + + // Register mercury implementation with the wsrpc server + pb.RegisterMercuryServer(s, srv) + + // Start serving + go s.Serve(lis) + t.Cleanup(s.Stop) + + return +} + +type Node struct { + App chainlink.Application + ClientPubKey credentials.StaticSizedPublicKey + KeyBundle ocr2key.KeyBundle + ObservedLogs *observer.ObservedLogs +} + +func (node *Node) AddStreamJob(t *testing.T, spec string) (id int32) { + job, err := streams.ValidatedStreamSpec(spec) + require.NoError(t, err) + err = node.App.AddJobV2(testutils.Context(t), &job) + require.NoError(t, err) + return job.ID +} + +func (node *Node) DeleteJob(t *testing.T, id int32) { + err := node.App.DeleteJob(testutils.Context(t), id) + require.NoError(t, err) +} + +func (node *Node) AddLLOJob(t *testing.T, spec string) { + c := node.App.GetConfig() + job, err := validate.ValidatedOracleSpecToml(testutils.Context(t), c.OCR2(), c.Insecure(), spec, nil) + require.NoError(t, err) + err = node.App.AddJobV2(testutils.Context(t), &job) + require.NoError(t, err) +} + +func (node *Node) AddBootstrapJob(t *testing.T, spec string) { + job, err := ocrbootstrap.ValidatedBootstrapSpecToml(spec) + require.NoError(t, err) + err = node.App.AddJobV2(testutils.Context(t), &job) + require.NoError(t, err) +} + +func setupNode( + t *testing.T, + port int, + dbName string, + backend *backends.SimulatedBackend, + csaKey csakey.KeyV2, +) (app chainlink.Application, peerID string, clientPubKey credentials.StaticSizedPublicKey, ocr2kb ocr2key.KeyBundle, observedLogs *observer.ObservedLogs) { + k := big.NewInt(int64(port)) // keys unique to port + p2pKey := p2pkey.MustNewV2XXXTestingOnly(k) + rdr := keystest.NewRandReaderFromSeed(int64(port)) + ocr2kb = ocr2key.MustNewInsecure(rdr, chaintype.EVM) + + p2paddresses := []string{fmt.Sprintf("127.0.0.1:%d", port)} + + config, _ := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { + // [JobPipeline] + c.JobPipeline.MaxSuccessfulRuns = ptr(uint64(0)) + c.JobPipeline.VerboseLogging = ptr(true) + + // [Feature] + c.Feature.UICSAKeys = ptr(true) + c.Feature.LogPoller = ptr(true) + c.Feature.FeedsManager = ptr(false) + + // [OCR] + c.OCR.Enabled = ptr(false) + + // [OCR2] + c.OCR2.Enabled = ptr(true) + c.OCR2.ContractPollInterval = commonconfig.MustNewDuration(100 * time.Millisecond) + + // [P2P] + c.P2P.PeerID = ptr(p2pKey.PeerID()) + c.P2P.TraceLogging = ptr(true) + + // [P2P.V2] + c.P2P.V2.Enabled = ptr(true) + c.P2P.V2.AnnounceAddresses = &p2paddresses + c.P2P.V2.ListenAddresses = &p2paddresses + c.P2P.V2.DeltaDial = commonconfig.MustNewDuration(500 * time.Millisecond) + c.P2P.V2.DeltaReconcile = commonconfig.MustNewDuration(5 * time.Second) + + // [Mercury] + c.Mercury.VerboseLogging = ptr(true) + }) + + lggr, observedLogs := logger.TestLoggerObserved(t, zapcore.DebugLevel) + if backend != nil { + app = cltest.NewApplicationWithConfigV2OnSimulatedBlockchain(t, config, backend, p2pKey, ocr2kb, csaKey, lggr.Named(dbName)) + } else { + app = cltest.NewApplicationWithConfig(t, config, p2pKey, ocr2kb, csaKey, lggr.Named(dbName)) + } + err := app.Start(testutils.Context(t)) + require.NoError(t, err) + + t.Cleanup(func() { + assert.NoError(t, app.Stop()) + }) + + return app, p2pKey.PeerID().Raw(), csaKey.StaticSizedPublicKey(), ocr2kb, observedLogs +} + +func ptr[T any](t T) *T { return &t } + +func addSingleDecimalStreamJob( + t *testing.T, + node Node, + streamID uint32, + bridgeName string, +) (id int32) { + return node.AddStreamJob(t, fmt.Sprintf(` +type = "stream" +schemaVersion = 1 +name = "strm-spec-%d" +streamID = %d +observationSource = """ + // Benchmark Price + price1 [type=bridge name="%s" requestData="{\\"data\\":{\\"data\\":\\"foo\\"}}"]; + price1_parse [type=jsonparse path="result"]; + + price1 -> price1_parse; +""" + + `, + streamID, + streamID, + bridgeName, + )) +} + +func addQuoteStreamJob( + t *testing.T, + node Node, + streamID uint32, + benchmarkBridgeName string, + bidBridgeName string, + askBridgeName string, +) (id int32) { + return node.AddStreamJob(t, fmt.Sprintf(` +type = "stream" +schemaVersion = 1 +name = "strm-spec-%d" +streamID = %d +observationSource = """ + // Benchmark Price + price1 [type=bridge name="%s" requestData="{\\"data\\":{\\"data\\":\\"foo\\"}}"]; + price1_parse [type=jsonparse path="result" index=0]; + + price1 -> price1_parse; + + // Bid + price2 [type=bridge name="%s" requestData="{\\"data\\":{\\"data\\":\\"foo\\"}}"]; + price2_parse [type=jsonparse path="result" index=1]; + + price2 -> price2_parse; + + // Ask + price3 [type=bridge name="%s" requestData="{\\"data\\":{\\"data\\":\\"foo\\"}}"]; + price3_parse [type=jsonparse path="result" index=2]; + + price3 -> price3_parse; +""" + + `, + streamID, + streamID, + benchmarkBridgeName, + bidBridgeName, + askBridgeName, + )) +} +func addBootstrapJob(t *testing.T, bootstrapNode Node, configuratorAddress common.Address, name string, relayType, relayConfig string) { + bootstrapNode.AddBootstrapJob(t, fmt.Sprintf(` +type = "bootstrap" +relay = "%s" +schemaVersion = 1 +name = "boot-%s" +contractID = "%s" +contractConfigTrackerPollInterval = "1s" + +[relayConfig] +%s +providerType = "llo"`, relayType, name, configuratorAddress.Hex(), relayConfig)) +} + +func addLLOJob( + t *testing.T, + node Node, + configuratorAddr common.Address, + bootstrapPeerID string, + bootstrapNodePort int, + clientPubKey ed25519.PublicKey, + jobName string, + pluginConfig, + relayType, + relayConfig string, +) { + node.AddLLOJob(t, fmt.Sprintf(` +type = "offchainreporting2" +schemaVersion = 1 +name = "%s" +forwardingAllowed = false +maxTaskDuration = "1s" +contractID = "%s" +contractConfigTrackerPollInterval = "1s" +ocrKeyBundleID = "%s" +p2pv2Bootstrappers = [ + "%s" +] +relay = "%s" +pluginType = "llo" +transmitterID = "%x" + +[pluginConfig] +%s + +[relayConfig] +%s`, + jobName, + configuratorAddr.Hex(), + node.KeyBundle.ID(), + fmt.Sprintf("%s@127.0.0.1:%d", bootstrapPeerID, bootstrapNodePort), + relayType, + clientPubKey, + pluginConfig, + relayConfig, + )) +} + +func createBridge(t *testing.T, name string, i int, p decimal.Decimal, borm bridges.ORM) (bridgeName string) { + ctx := testutils.Context(t) + bridge := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + b, err := io.ReadAll(req.Body) + require.NoError(t, err) + require.Equal(t, `{"data":{"data":"foo"}}`, string(b)) + + res.WriteHeader(http.StatusOK) + val := p.String() + resp := fmt.Sprintf(`{"result": %s}`, val) + _, err = res.Write([]byte(resp)) + require.NoError(t, err) + })) + t.Cleanup(bridge.Close) + u, _ := url.Parse(bridge.URL) + bridgeName = fmt.Sprintf("bridge-%s-%d", name, i) + require.NoError(t, borm.CreateBridgeType(ctx, &bridges.BridgeType{ + Name: bridges.BridgeName(bridgeName), + URL: models.WebURL(*u), + })) + + return bridgeName +} + +func addOCRJobsEVMPremiumLegacy( + t *testing.T, + streams []Stream, + serverPubKey ed25519.PublicKey, + serverURL string, + configuratorAddress common.Address, + bootstrapPeerID string, + bootstrapNodePort int, + nodes []Node, + configStoreAddress common.Address, + clientPubKeys []ed25519.PublicKey, + pluginConfig, + relayType, + relayConfig string) (jobIDs map[int]map[uint32]int32) { + // node idx => stream id => job id + jobIDs = make(map[int]map[uint32]int32) + // Add OCR jobs - one per feed on each node + for i, node := range nodes { + if jobIDs[i] == nil { + jobIDs[i] = make(map[uint32]int32) + } + for j, strm := range streams { + // assume that streams are native, link and additionals are quote + if j < 2 { + var name string + if j == 0 { + name = "nativeprice" + } else { + name = "linkprice" + } + name = fmt.Sprintf("%s-%d-%d", name, strm.id, j) + bmBridge := createBridge(t, name, i, strm.baseBenchmarkPrice, node.App.BridgeORM()) + jobID := addSingleDecimalStreamJob( + t, + node, + strm.id, + bmBridge, + ) + jobIDs[i][strm.id] = jobID + } else { + bmBridge := createBridge(t, fmt.Sprintf("benchmarkprice-%d-%d", strm.id, j), i, strm.baseBenchmarkPrice, node.App.BridgeORM()) + bidBridge := createBridge(t, fmt.Sprintf("bid-%d-%d", strm.id, j), i, strm.baseBid, node.App.BridgeORM()) + askBridge := createBridge(t, fmt.Sprintf("ask-%d-%d", strm.id, j), i, strm.baseAsk, node.App.BridgeORM()) + jobID := addQuoteStreamJob( + t, + node, + strm.id, + bmBridge, + bidBridge, + askBridge, + ) + jobIDs[i][strm.id] = jobID + } + } + addLLOJob( + t, + node, + configuratorAddress, + bootstrapPeerID, + bootstrapNodePort, + clientPubKeys[i], + "feed-1", + pluginConfig, + relayType, + relayConfig, + ) + } + return jobIDs +} diff --git a/core/services/ocr2/plugins/llo/integration_test.go b/core/services/ocr2/plugins/llo/integration_test.go new file mode 100644 index 0000000000..03dca8f370 --- /dev/null +++ b/core/services/ocr2/plugins/llo/integration_test.go @@ -0,0 +1,434 @@ +package llo_test + +import ( + "crypto/ed25519" + "encoding/hex" + "encoding/json" + "fmt" + "math/big" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/hashicorp/consul/sdk/freeport" + "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/crypto/sha3" + + "github.com/smartcontractkit/libocr/offchainreporting2/types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3confighelper" + ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/wsrpc/credentials" + + llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" + datastreamsllo "github.com/smartcontractkit/chainlink-data-streams/llo" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/channel_config_store" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/configurator" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/destination_verifier" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/destination_verifier_proxy" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/csakey" + lloevm "github.com/smartcontractkit/chainlink/v2/core/services/llo/evm" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury" + reportcodecv3 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v3/reportcodec" + mercuryverifier "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/verifier" +) + +var ( + fNodes = uint8(1) + nNodes = 4 // number of nodes (not including bootstrap) +) + +func setupBlockchain(t *testing.T) (*bind.TransactOpts, *backends.SimulatedBackend, *configurator.Configurator, common.Address, *destination_verifier.DestinationVerifier, common.Address, *destination_verifier_proxy.DestinationVerifierProxy, common.Address, *channel_config_store.ChannelConfigStore, common.Address) { + steve := testutils.MustNewSimTransactor(t) // config contract deployer and owner + genesisData := core.GenesisAlloc{steve.From: {Balance: assets.Ether(1000).ToInt()}} + backend := cltest.NewSimulatedBackend(t, genesisData, uint32(ethconfig.Defaults.Miner.GasCeil)) + backend.Commit() + backend.Commit() // ensure starting block number at least 1 + + // Configurator + configuratorAddress, _, configurator, err := configurator.DeployConfigurator(steve, backend) + require.NoError(t, err) + + // DestinationVerifierProxy + verifierProxyAddr, _, verifierProxy, err := destination_verifier_proxy.DeployDestinationVerifierProxy(steve, backend) + require.NoError(t, err) + // DestinationVerifier + verifierAddr, _, verifier, err := destination_verifier.DeployDestinationVerifier(steve, backend, verifierProxyAddr) + require.NoError(t, err) + // AddVerifier + _, err = verifierProxy.SetVerifier(steve, verifierAddr) + require.NoError(t, err) + + // ChannelConfigStore + configStoreAddress, _, configStore, err := channel_config_store.DeployChannelConfigStore(steve, backend) + require.NoError(t, err) + + backend.Commit() + + return steve, backend, configurator, configuratorAddress, verifier, verifierAddr, verifierProxy, verifierProxyAddr, configStore, configStoreAddress +} + +type Stream struct { + id uint32 + baseBenchmarkPrice decimal.Decimal + baseBid decimal.Decimal + baseAsk decimal.Decimal +} + +const ( + ethStreamID = 52 + linkStreamID = 53 + quoteStreamID1 = 55 + quoteStreamID2 = 56 +) + +var ( + quoteStreamFeedID1 = common.HexToHash(`0x0003111111111111111111111111111111111111111111111111111111111111`) + quoteStreamFeedID2 = common.HexToHash(`0x0003222222222222222222222222222222222222222222222222222222222222`) + ethStream = Stream{ + id: 52, + baseBenchmarkPrice: decimal.NewFromFloat32(2_976.39), + } + linkStream = Stream{ + id: 53, + baseBenchmarkPrice: decimal.NewFromFloat32(13.25), + } + quoteStream1 = Stream{ + id: 55, + baseBenchmarkPrice: decimal.NewFromFloat32(1000.1212), + baseBid: decimal.NewFromFloat32(998.5431), + baseAsk: decimal.NewFromFloat32(1001.6999), + } + quoteStream2 = Stream{ + id: 56, + baseBenchmarkPrice: decimal.NewFromFloat32(500.1212), + baseBid: decimal.NewFromFloat32(499.5431), + baseAsk: decimal.NewFromFloat32(502.6999), + } +) + +func generateConfig(t *testing.T, oracles []confighelper.OracleIdentityExtra) ( + signers []types.OnchainPublicKey, + transmitters []types.Account, + f uint8, + onchainConfig []byte, + offchainConfigVersion uint64, + offchainConfig []byte, +) { + rawReportingPluginConfig := datastreamsllo.OffchainConfig{} + reportingPluginConfig, err := rawReportingPluginConfig.Encode() + require.NoError(t, err) + + signers, transmitters, f, onchainConfig, offchainConfigVersion, offchainConfig, err = ocr3confighelper.ContractSetConfigArgsForTests( + 2*time.Second, // DeltaProgress + 20*time.Second, // DeltaResend + 400*time.Millisecond, // DeltaInitial + 1000*time.Millisecond, // DeltaRound + 500*time.Millisecond, // DeltaGrace + 300*time.Millisecond, // DeltaCertifiedCommitRequest + 1*time.Minute, // DeltaStage + 100, // rMax + []int{len(oracles)}, // S + oracles, + reportingPluginConfig, // reportingPluginConfig []byte, + 0, // maxDurationQuery + 250*time.Millisecond, // maxDurationObservation + 0, // maxDurationShouldAcceptAttestedReport + 0, // maxDurationShouldTransmitAcceptedReport + int(fNodes), // f + onchainConfig, // encoded onchain config + ) + + require.NoError(t, err) + + return +} + +func setConfig(t *testing.T, donID uint32, steve *bind.TransactOpts, backend *backends.SimulatedBackend, configurator *configurator.Configurator, configuratorAddress common.Address, nodes []Node, oracles []confighelper.OracleIdentityExtra) ocr2types.ConfigDigest { + signers, _, _, _, offchainConfigVersion, offchainConfig := generateConfig(t, oracles) + + signerAddresses, err := evm.OnchainPublicKeyToAddress(signers) + require.NoError(t, err) + offchainTransmitters := make([][32]byte, nNodes) + for i := 0; i < nNodes; i++ { + offchainTransmitters[i] = nodes[i].ClientPubKey + } + donIDPadded := evm.DonIDToBytes32(donID) + _, err = configurator.SetConfig(steve, donIDPadded, signerAddresses, offchainTransmitters, fNodes, offchainConfig, offchainConfigVersion, offchainConfig) + require.NoError(t, err) + + // libocr requires a few confirmations to accept the config + backend.Commit() + backend.Commit() + backend.Commit() + backend.Commit() + + logs, err := backend.FilterLogs(testutils.Context(t), ethereum.FilterQuery{Addresses: []common.Address{configuratorAddress}, Topics: [][]common.Hash{[]common.Hash{mercury.FeedScopedConfigSet, donIDPadded}}}) + require.NoError(t, err) + require.Len(t, logs, 1) + + cfg, err := mercury.ConfigFromLog(logs[0].Data) + require.NoError(t, err) + + return cfg.ConfigDigest +} + +func TestIntegration_LLO(t *testing.T) { + testStartTimeStamp := time.Now() + donID := uint32(995544) + multiplier, err := decimal.NewFromString("1e18") + require.NoError(t, err) + expirationWindow := time.Hour / time.Second + + reqs := make(chan request) + serverKey := csakey.MustNewV2XXXTestingOnly(big.NewInt(-1)) + serverPubKey := serverKey.PublicKey + srv := NewMercuryServer(t, ed25519.PrivateKey(serverKey.Raw()), reqs) + + clientCSAKeys := make([]csakey.KeyV2, nNodes) + clientPubKeys := make([]ed25519.PublicKey, nNodes) + for i := 0; i < nNodes; i++ { + k := big.NewInt(int64(i)) + key := csakey.MustNewV2XXXTestingOnly(k) + clientCSAKeys[i] = key + clientPubKeys[i] = key.PublicKey + } + serverURL := startMercuryServer(t, srv, clientPubKeys) + + steve, backend, configurator, configuratorAddress, verifier, _, verifierProxy, _, configStore, configStoreAddress := setupBlockchain(t) + fromBlock := 1 + + // Setup bootstrap + bootstrapCSAKey := csakey.MustNewV2XXXTestingOnly(big.NewInt(-1)) + bootstrapNodePort := freeport.GetOne(t) + appBootstrap, bootstrapPeerID, _, bootstrapKb, _ := setupNode(t, bootstrapNodePort, "bootstrap_llo", backend, bootstrapCSAKey) + bootstrapNode := Node{App: appBootstrap, KeyBundle: bootstrapKb} + + t.Run("produces reports in v0.3 format", func(t *testing.T) { + streams := []Stream{ethStream, linkStream, quoteStream1, quoteStream2} + streamMap := make(map[uint32]Stream) + for _, strm := range streams { + streamMap[strm.id] = strm + } + + // Setup oracle nodes + var ( + oracles []confighelper.OracleIdentityExtra + nodes []Node + ) + ports := freeport.GetN(t, nNodes) + for i := 0; i < nNodes; i++ { + app, peerID, transmitter, kb, observedLogs := setupNode(t, ports[i], fmt.Sprintf("oracle_streams_%d", i), backend, clientCSAKeys[i]) + + nodes = append(nodes, Node{ + app, transmitter, kb, observedLogs, + }) + offchainPublicKey, _ := hex.DecodeString(strings.TrimPrefix(kb.OnChainPublicKey(), "0x")) + oracles = append(oracles, confighelper.OracleIdentityExtra{ + OracleIdentity: confighelper.OracleIdentity{ + OnchainPublicKey: offchainPublicKey, + TransmitAccount: ocr2types.Account(fmt.Sprintf("%x", transmitter[:])), + OffchainPublicKey: kb.OffchainPublicKey(), + PeerID: peerID, + }, + ConfigEncryptionPublicKey: kb.ConfigEncryptionPublicKey(), + }) + } + + chainID := testutils.SimulatedChainID + relayType := "evm" + relayConfig := fmt.Sprintf(` +chainID = "%s" +fromBlock = %d +lloDonID = %d +`, chainID, fromBlock, donID) + addBootstrapJob(t, bootstrapNode, configuratorAddress, "job-2", relayType, relayConfig) + + // Channel definitions + channelDefinitions := llotypes.ChannelDefinitions{ + 1: { + ReportFormat: llotypes.ReportFormatEVMPremiumLegacy, + Streams: []llotypes.Stream{ + { + StreamID: ethStreamID, + Aggregator: llotypes.AggregatorMedian, + }, + { + StreamID: linkStreamID, + Aggregator: llotypes.AggregatorMedian, + }, + { + StreamID: quoteStreamID1, + Aggregator: llotypes.AggregatorQuote, + }, + }, + Opts: llotypes.ChannelOpts([]byte(fmt.Sprintf(`{"baseUSDFee":"0.1","expirationWindow":%d,"feedId":"0x%x","multiplier":"%s"}`, expirationWindow, quoteStreamFeedID1, multiplier.String()))), + }, + 2: { + ReportFormat: llotypes.ReportFormatEVMPremiumLegacy, + Streams: []llotypes.Stream{ + { + StreamID: ethStreamID, + Aggregator: llotypes.AggregatorMedian, + }, + { + StreamID: linkStreamID, + Aggregator: llotypes.AggregatorMedian, + }, + { + StreamID: quoteStreamID2, + Aggregator: llotypes.AggregatorQuote, + }, + }, + Opts: llotypes.ChannelOpts([]byte(fmt.Sprintf(`{"baseUSDFee":"0.1","expirationWindow":%d,"feedId":"0x%x","multiplier":"%s"}`, expirationWindow, quoteStreamFeedID2, multiplier.String()))), + }, + } + + channelDefinitionsJSON, err := json.MarshalIndent(channelDefinitions, "", " ") + require.NoError(t, err) + channelDefinitionsSHA := sha3.Sum256(channelDefinitionsJSON) + + // Set up channel definitions server + channelDefinitionsServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/channel-definitions", r.URL.Path) + assert.Equal(t, "GET", r.Method) + assert.Equal(t, "application/json", r.Header.Get("Content-Type")) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, err2 := w.Write(channelDefinitionsJSON) + require.NoError(t, err2) + })) + t.Cleanup(channelDefinitionsServer.Close) + + // Set channel definitions + _, err = configStore.SetChannelDefinitions(steve, donID, channelDefinitionsServer.URL+"/channel-definitions", channelDefinitionsSHA) + require.NoError(t, err) + backend.Commit() + + pluginConfig := fmt.Sprintf(`servers = { "%s" = "%x" } +donID = %d +channelDefinitionsContractAddress = "0x%x" +channelDefinitionsContractFromBlock = %d`, serverURL, serverPubKey, donID, configStoreAddress, fromBlock) + addOCRJobsEVMPremiumLegacy(t, streams, serverPubKey, serverURL, configuratorAddress, bootstrapPeerID, bootstrapNodePort, nodes, configStoreAddress, clientPubKeys, pluginConfig, relayType, relayConfig) + + // Set config on configurator + setConfig( + t, donID, steve, backend, configurator, configuratorAddress, nodes, oracles, + ) + + // Set config on the destination verifier + signerAddresses := make([]common.Address, len(oracles)) + for i, oracle := range oracles { + signerAddresses[i] = common.BytesToAddress(oracle.OracleIdentity.OnchainPublicKey) + } + { + recipientAddressesAndWeights := []destination_verifier.CommonAddressAndWeight{} + + _, err := verifier.SetConfig(steve, signerAddresses, fNodes, recipientAddressesAndWeights) + require.NoError(t, err) + backend.Commit() + } + + t.Run("receives at least one report per channel from each oracle when EAs are at 100% reliability", func(t *testing.T) { + // Expect at least one report per feed from each oracle + seen := make(map[[32]byte]map[credentials.StaticSizedPublicKey]struct{}) + for _, cd := range channelDefinitions { + var opts lloevm.ReportFormatEVMPremiumLegacyOpts + err := json.Unmarshal(cd.Opts, &opts) + require.NoError(t, err) + // feedID will be deleted when all n oracles have reported + seen[opts.FeedID] = make(map[credentials.StaticSizedPublicKey]struct{}, nNodes) + } + for req := range reqs { + v := make(map[string]interface{}) + err := mercury.PayloadTypes.UnpackIntoMap(v, req.req.Payload) + require.NoError(t, err) + report, exists := v["report"] + if !exists { + t.Fatalf("expected payload %#v to contain 'report'", v) + } + reportElems := make(map[string]interface{}) + err = reportcodecv3.ReportTypes.UnpackIntoMap(reportElems, report.([]byte)) + require.NoError(t, err) + + feedID := reportElems["feedId"].([32]uint8) + + if _, exists := seen[feedID]; !exists { + continue // already saw all oracles for this feed + } + + var expectedBm, expectedBid, expectedAsk *big.Int + if feedID == quoteStreamFeedID1 { + expectedBm = quoteStream1.baseBenchmarkPrice.Mul(multiplier).BigInt() + expectedBid = quoteStream1.baseBid.Mul(multiplier).BigInt() + expectedAsk = quoteStream1.baseAsk.Mul(multiplier).BigInt() + } else if feedID == quoteStreamFeedID2 { + expectedBm = quoteStream2.baseBenchmarkPrice.Mul(multiplier).BigInt() + expectedBid = quoteStream2.baseBid.Mul(multiplier).BigInt() + expectedAsk = quoteStream2.baseAsk.Mul(multiplier).BigInt() + } else { + t.Fatalf("unrecognized feedID: 0x%x", feedID) + } + + assert.GreaterOrEqual(t, reportElems["validFromTimestamp"].(uint32), uint32(testStartTimeStamp.Unix())) + assert.GreaterOrEqual(t, int(reportElems["observationsTimestamp"].(uint32)), int(testStartTimeStamp.Unix())) + assert.Equal(t, "33597747607000", reportElems["nativeFee"].(*big.Int).String()) + assert.Equal(t, "7547169811320755", reportElems["linkFee"].(*big.Int).String()) + assert.Equal(t, reportElems["observationsTimestamp"].(uint32)+uint32(expirationWindow), reportElems["expiresAt"].(uint32)) + assert.Equal(t, expectedBm.String(), reportElems["benchmarkPrice"].(*big.Int).String()) + assert.Equal(t, expectedBid.String(), reportElems["bid"].(*big.Int).String()) + assert.Equal(t, expectedAsk.String(), reportElems["ask"].(*big.Int).String()) + + t.Run(fmt.Sprintf("emulate mercury server verifying report (local verification) - node %x", req.pk), func(t *testing.T) { + rv := mercuryverifier.NewVerifier() + + reportSigners, err2 := rv.Verify(mercuryverifier.SignedReport{ + RawRs: v["rawRs"].([][32]byte), + RawSs: v["rawSs"].([][32]byte), + RawVs: v["rawVs"].([32]byte), + ReportContext: v["reportContext"].([3][32]byte), + Report: v["report"].([]byte), + }, fNodes, signerAddresses) + require.NoError(t, err2) + assert.GreaterOrEqual(t, len(reportSigners), int(fNodes+1)) + assert.Subset(t, signerAddresses, reportSigners) + }) + + t.Run(fmt.Sprintf("test on-chain verification - node %x", req.pk), func(t *testing.T) { + _, err = verifierProxy.Verify(steve, req.req.Payload, []byte{}) + require.NoError(t, err) + }) + + t.Logf("oracle %x reported for 0x%x", req.pk, feedID) + + seen[feedID][req.pk] = struct{}{} + if len(seen[feedID]) == nNodes { + t.Logf("all oracles reported for 0x%x", feedID) + delete(seen, feedID) + if len(seen) == 0 { + break // saw all oracles; success! + } + } + } + }) + + t.Run("deleting LLO jobs cleans up resources", func(t *testing.T) { + t.Skip("TODO - https://smartcontract-it.atlassian.net/browse/MERC-3653") + }) + }) +} diff --git a/core/services/ocr2/plugins/llo/onchain_channel_definition_cache_integration_test.go b/core/services/ocr2/plugins/llo/onchain_channel_definition_cache_integration_test.go index 9d2d52ce50..ec918e2375 100644 --- a/core/services/ocr2/plugins/llo/onchain_channel_definition_cache_integration_test.go +++ b/core/services/ocr2/plugins/llo/onchain_channel_definition_cache_integration_test.go @@ -1,215 +1,368 @@ package llo_test import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "math/rand/v2" + "net/http" + "sync" "testing" + "time" + + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + "golang.org/x/crypto/sha3" + + "github.com/smartcontractkit/chainlink-common/pkg/services/servicetest" + llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" + "github.com/smartcontractkit/chainlink-common/pkg/utils" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/channel_config_store" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/llo" ) +type mockHTTPClient struct { + resp *http.Response + err error + mu sync.Mutex +} + +func (h *mockHTTPClient) Do(req *http.Request) (*http.Response, error) { + h.mu.Lock() + defer h.mu.Unlock() + return h.resp, h.err +} + +func (h *mockHTTPClient) SetResponse(resp *http.Response, err error) { + h.mu.Lock() + defer h.mu.Unlock() + h.resp = resp + h.err = err +} + +type MockReadCloser struct { + data []byte + mu sync.Mutex + reader *bytes.Reader +} + +func NewMockReadCloser(data []byte) *MockReadCloser { + return &MockReadCloser{ + data: data, + reader: bytes.NewReader(data), + } +} + +// Read reads from the underlying data +func (m *MockReadCloser) Read(p []byte) (int, error) { + m.mu.Lock() + defer m.mu.Unlock() + return m.reader.Read(p) +} + +// Close resets the reader to the beginning of the data +func (m *MockReadCloser) Close() error { + m.mu.Lock() + defer m.mu.Unlock() + _, err := m.reader.Seek(0, io.SeekStart) + return err +} + func Test_ChannelDefinitionCache_Integration(t *testing.T) { - t.Skip("waiting on https://github.com/smartcontractkit/chainlink/pull/13780") - // lggr, observedLogs := logger.TestLoggerObserved(t, zapcore.InfoLevel) - // db := pgtest.NewSqlxDB(t) - // ctx := testutils.Context(t) - // orm := llo.NewORM(db, testutils.SimulatedChainID) - - // steve := testutils.MustNewSimTransactor(t) // config contract deployer and owner - // genesisData := core.GenesisAlloc{steve.From: {Balance: assets.Ether(1000).ToInt()}} - // backend := cltest.NewSimulatedBackend(t, genesisData, uint32(ethconfig.Defaults.Miner.GasCeil)) - // backend.Commit() // ensure starting block number at least 1 - - // ethClient := client.NewSimulatedBackendClient(t, backend, testutils.SimulatedChainID) - - // configStoreAddress, _, configStoreContract, err := channel_config_store.DeployChannelConfigStore(steve, backend) - // require.NoError(t, err) - - // channel1 := rand.Uint32() - // channel2 := rand.Uint32() - // channel3 := rand.Uint32() - - // chainSelector, err := chainselectors.SelectorFromChainId(testutils.SimulatedChainID.Uint64()) - // require.NoError(t, err) - - // streamIDs := []uint32{1, 2, 3} - // channel1Def := channel_config_store.IChannelConfigStoreChannelDefinition{ - // ReportFormat: uint32(llotypes.ReportFormatSolana), - // ChainSelector: chainSelector, - // StreamIDs: streamIDs, - // } - // channel2Def := channel_config_store.IChannelConfigStoreChannelDefinition{ - // ReportFormat: uint32(llotypes.ReportFormatEVM), - // ChainSelector: chainSelector, - // StreamIDs: streamIDs, - // } - // channel3Def := channel_config_store.IChannelConfigStoreChannelDefinition{ - // ReportFormat: uint32(llotypes.ReportFormatEVM), - // ChainSelector: chainSelector, - // StreamIDs: append(streamIDs, 4), - // } - - // require.NoError(t, utils.JustError(configStoreContract.AddChannel(steve, channel1, channel1Def))) - // require.NoError(t, utils.JustError(configStoreContract.AddChannel(steve, channel2, channel2Def))) - - // h := backend.Commit() - // channel2Block, err := backend.BlockByHash(ctx, h) - // require.NoError(t, err) - - // t.Run("with zero fromblock", func(t *testing.T) { - // lpOpts := logpoller.Opts{ - // PollPeriod: 100 * time.Millisecond, - // FinalityDepth: 1, - // BackfillBatchSize: 3, - // RpcBatchSize: 2, - // KeepFinalizedBlocksDepth: 1000, - // } - // ht := headtracker.NewSimulatedHeadTracker(ethClient, lpOpts.UseFinalityTag, lpOpts.FinalityDepth) - // lp := logpoller.NewLogPoller( - // logpoller.NewORM(testutils.SimulatedChainID, db, lggr), ethClient, lggr, ht, lpOpts) - // servicetest.Run(t, lp) - // cdc := llo.NewChannelDefinitionCache(lggr, orm, lp, configStoreAddress, 0) - - // servicetest.Run(t, cdc) - - // testutils.WaitForLogMessage(t, observedLogs, "Updated channel definitions") - - // dfns := cdc.Definitions() - - // require.Len(t, dfns, 2) - // require.Contains(t, dfns, channel1) - // require.Contains(t, dfns, channel2) - // assert.Equal(t, llotypes.ChannelDefinition{ - // ReportFormat: llotypes.ReportFormatSolana, - // ChainSelector: chainSelector, - // StreamIDs: []uint32{1, 2, 3}, - // }, dfns[channel1]) - // assert.Equal(t, llotypes.ChannelDefinition{ - // ReportFormat: llotypes.ReportFormatEVM, - // ChainSelector: chainSelector, - // StreamIDs: []uint32{1, 2, 3}, - // }, dfns[channel2]) - - // // remove solana - // require.NoError(t, utils.JustError(configStoreContract.RemoveChannel(steve, channel1))) - // backend.Commit() - // testutils.WaitForLogMessageCount(t, observedLogs, "Updated channel definitions", 2) - // dfns = cdc.Definitions() - - // require.Len(t, dfns, 1) - // assert.NotContains(t, dfns, channel1) - // require.Contains(t, dfns, channel2) - - // assert.Equal(t, llotypes.ChannelDefinition{ - // ReportFormat: llotypes.ReportFormatEVM, - // ChainSelector: chainSelector, - // StreamIDs: []uint32{1, 2, 3}, - // }, dfns[channel2]) - - // // add channel3 with additional stream - // require.NoError(t, utils.JustError(configStoreContract.AddChannel(steve, channel3, channel3Def))) - // backend.Commit() - // testutils.WaitForLogMessageCount(t, observedLogs, "Updated channel definitions", 3) - // dfns = cdc.Definitions() - - // require.Len(t, dfns, 2) - // require.Contains(t, dfns, channel2) - // require.Contains(t, dfns, channel3) - - // assert.Equal(t, llotypes.ChannelDefinition{ - // ReportFormat: llotypes.ReportFormatEVM, - // ChainSelector: chainSelector, - // StreamIDs: []uint32{1, 2, 3}, - // }, dfns[channel2]) - // assert.Equal(t, llotypes.ChannelDefinition{ - // ReportFormat: llotypes.ReportFormatEVM, - // ChainSelector: chainSelector, - // StreamIDs: []uint32{1, 2, 3, 4}, - // }, dfns[channel3]) - // }) - - // t.Run("loads from ORM", func(t *testing.T) { - // // Override logpoller to always return no logs - // lpOpts := logpoller.Opts{ - // PollPeriod: 100 * time.Millisecond, - // FinalityDepth: 1, - // BackfillBatchSize: 3, - // RpcBatchSize: 2, - // KeepFinalizedBlocksDepth: 1000, - // } - // ht := headtracker.NewSimulatedHeadTracker(ethClient, lpOpts.UseFinalityTag, lpOpts.FinalityDepth) - // lp := &mockLogPoller{ - // LogPoller: logpoller.NewLogPoller(logpoller.NewORM(testutils.SimulatedChainID, db, lggr), ethClient, lggr, ht, lpOpts), - // LatestBlockFn: func(ctx context.Context) (int64, error) { - // return 0, nil - // }, - // LogsWithSigsFn: func(ctx context.Context, start, end int64, eventSigs []common.Hash, address common.Address) ([]logpoller.Log, error) { - // return []logpoller.Log{}, nil - // }, - // } - // cdc := llo.NewChannelDefinitionCache(lggr, orm, lp, configStoreAddress, 0) - - // servicetest.Run(t, cdc) - - // dfns := cdc.Definitions() - - // require.Len(t, dfns, 2) - // require.Contains(t, dfns, channel2) - // require.Contains(t, dfns, channel3) - - // assert.Equal(t, llotypes.ChannelDefinition{ - // ReportFormat: llotypes.ReportFormatEVM, - // ChainSelector: chainSelector, - // StreamIDs: []uint32{1, 2, 3}, - // }, dfns[channel2]) - // assert.Equal(t, llotypes.ChannelDefinition{ - // ReportFormat: llotypes.ReportFormatEVM, - // ChainSelector: chainSelector, - // StreamIDs: []uint32{1, 2, 3, 4}, - // }, dfns[channel3]) - // }) - - // // clear out DB for next test - // pgtest.MustExec(t, db, `DELETE FROM channel_definitions`) - - // t.Run("with non-zero fromBlock", func(t *testing.T) { - // lpOpts := logpoller.Opts{ - // PollPeriod: 100 * time.Millisecond, - // FinalityDepth: 1, - // BackfillBatchSize: 3, - // RpcBatchSize: 2, - // KeepFinalizedBlocksDepth: 1000, - // } - // ht := headtracker.NewSimulatedHeadTracker(ethClient, lpOpts.UseFinalityTag, lpOpts.FinalityDepth) - // lp := logpoller.NewLogPoller(logpoller.NewORM(testutils.SimulatedChainID, db, lggr), ethClient, lggr, ht, lpOpts) - // servicetest.Run(t, lp) - // cdc := llo.NewChannelDefinitionCache(lggr, orm, lp, configStoreAddress, channel2Block.Number().Int64()+1) - - // // should only detect events from AFTER channel 2 was added - // servicetest.Run(t, cdc) - - // testutils.WaitForLogMessageCount(t, observedLogs, "Updated channel definitions", 4) - - // dfns := cdc.Definitions() - - // require.Len(t, dfns, 1) - // require.Contains(t, dfns, channel3) - - // assert.Equal(t, llotypes.ChannelDefinition{ - // ReportFormat: llotypes.ReportFormatEVM, - // ChainSelector: chainSelector, - // StreamIDs: []uint32{1, 2, 3, 4}, - // }, dfns[channel3]) - // }) - // } - - // type mockLogPoller struct { - // logpoller.LogPoller - // LatestBlockFn func(ctx context.Context) (int64, error) - // LogsWithSigsFn func(ctx context.Context, start, end int64, eventSigs []common.Hash, address common.Address) ([]logpoller.Log, error) - // } - - // func (p *mockLogPoller) LogsWithSigs(ctx context.Context, start, end int64, eventSigs []common.Hash, address common.Address) ([]logpoller.Log, error) { - // return p.LogsWithSigsFn(ctx, start, end, eventSigs, address) - // } - // - // func (p *mockLogPoller) LatestBlock(ctx context.Context) (logpoller.LogPollerBlock, error) { - // block, err := p.LatestBlockFn(ctx) - // return logpoller.LogPollerBlock{BlockNumber: block}, err + var ( + invalidDefinitions = []byte(`{{{`) + invalidDefinitionsSHA = sha3.Sum256(invalidDefinitions) + + sampleDefinitions = llotypes.ChannelDefinitions{ + 1: { + ReportFormat: llotypes.ReportFormatJSON, + Streams: []llotypes.Stream{ + { + StreamID: 1, + Aggregator: llotypes.AggregatorMedian, + }, + { + StreamID: 2, + Aggregator: llotypes.AggregatorMode, + }, + }, + }, + 2: { + ReportFormat: llotypes.ReportFormatEVMPremiumLegacy, + Streams: []llotypes.Stream{ + { + StreamID: 1, + Aggregator: llotypes.AggregatorMedian, + }, + { + StreamID: 2, + Aggregator: llotypes.AggregatorMedian, + }, + { + StreamID: 3, + Aggregator: llotypes.AggregatorQuote, + }, + }, + Opts: llotypes.ChannelOpts([]byte(`{"baseUSDFee":"0.1","expirationWindow":86400,"feedId":"0x0003aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","multiplier":"1000000000000000000"}`)), + }, + } + ) + + sampleDefinitionsJSON, err := json.MarshalIndent(sampleDefinitions, "", " ") + require.NoError(t, err) + sampleDefinitionsSHA := sha3.Sum256(sampleDefinitionsJSON) + + lggr, observedLogs := logger.TestLoggerObserved(t, zapcore.DebugLevel) + db := pgtest.NewSqlxDB(t) + const ETHMainnetChainSelector uint64 = 5009297550715157269 + orm := llo.NewORM(db, ETHMainnetChainSelector) + + steve := testutils.MustNewSimTransactor(t) // config contract deployer and owner + genesisData := core.GenesisAlloc{steve.From: {Balance: assets.Ether(1000).ToInt()}} + backend := cltest.NewSimulatedBackend(t, genesisData, uint32(ethconfig.Defaults.Miner.GasCeil)) + backend.Commit() // ensure starting block number at least 1 + + ethClient := client.NewSimulatedBackendClient(t, backend, testutils.SimulatedChainID) + + configStoreAddress, _, configStoreContract, err := channel_config_store.DeployChannelConfigStore(steve, backend) + require.NoError(t, err) + + lpOpts := logpoller.Opts{ + PollPeriod: 100 * time.Millisecond, + FinalityDepth: 1, + BackfillBatchSize: 3, + RpcBatchSize: 2, + KeepFinalizedBlocksDepth: 1000, + } + ht := headtracker.NewSimulatedHeadTracker(ethClient, lpOpts.UseFinalityTag, lpOpts.FinalityDepth) + lp := logpoller.NewLogPoller( + logpoller.NewORM(testutils.SimulatedChainID, db, lggr), ethClient, lggr, ht, lpOpts) + servicetest.Run(t, lp) + + client := &mockHTTPClient{} + donID := rand.Uint32() + + cdc := llo.NewChannelDefinitionCache(lggr, orm, client, lp, configStoreAddress, donID, 0, llo.WithLogPollInterval(100*time.Millisecond)) + servicetest.Run(t, cdc) + + t.Run("before any logs, returns empty Definitions", func(t *testing.T) { + assert.Empty(t, cdc.Definitions()) + }) + + { + rc := NewMockReadCloser(invalidDefinitions) + client.SetResponse(&http.Response{ + StatusCode: 200, + Body: rc, + }, nil) + + url := "http://example.com/foo" + require.NoError(t, utils.JustError(configStoreContract.SetChannelDefinitions(steve, donID, url, sampleDefinitionsSHA))) + + backend.Commit() + } + + t.Run("with sha mismatch, should not update", func(t *testing.T) { + // clear the log messages + t.Cleanup(func() { observedLogs.TakeAll() }) + + testutils.WaitForLogMessage(t, observedLogs, "Got new channel definitions from chain") + le := testutils.WaitForLogMessage(t, observedLogs, "Error while fetching channel definitions") + fields := le.ContextMap() + assert.Contains(t, fields, "err") + assert.Equal(t, fmt.Sprintf("SHA3 mismatch: expected %x, got %x", sampleDefinitionsSHA, invalidDefinitionsSHA), fields["err"]) + + assert.Empty(t, cdc.Definitions()) + }) + + { + rc := NewMockReadCloser(invalidDefinitions) + client.SetResponse(&http.Response{ + StatusCode: 200, + Body: rc, + }, nil) + + url := "http://example.com/foo" + require.NoError(t, utils.JustError(configStoreContract.SetChannelDefinitions(steve, donID, url, invalidDefinitionsSHA))) + backend.Commit() + } + + t.Run("after correcting sha with new channel definitions set on-chain, but with invalid JSON at url, should not update", func(t *testing.T) { + // clear the log messages + t.Cleanup(func() { observedLogs.TakeAll() }) + + testutils.WaitForLogMessage(t, observedLogs, "Got new channel definitions from chain") + testutils.WaitForLogMessageWithField(t, observedLogs, "Error while fetching channel definitions", "err", "failed to decode JSON: invalid character '{' looking for beginning of object key string") + + assert.Empty(t, cdc.Definitions()) + }) + + { + rc := NewMockReadCloser([]byte("not found")) + client.SetResponse(&http.Response{ + StatusCode: 404, + Body: rc, + }, nil) + + url := "http://example.com/foo3" + require.NoError(t, utils.JustError(configStoreContract.SetChannelDefinitions(steve, donID, url, sampleDefinitionsSHA))) + backend.Commit() + } + + t.Run("if server returns 404, should not update", func(t *testing.T) { + // clear the log messages + t.Cleanup(func() { observedLogs.TakeAll() }) + + testutils.WaitForLogMessageWithField(t, observedLogs, "Error while fetching channel definitions", "err", "got error from http://example.com/foo3: (status code: 404, response body: not found)") + }) + + { + rc := NewMockReadCloser([]byte{}) + client.SetResponse(&http.Response{ + StatusCode: 200, + Body: rc, + }, nil) + } + + t.Run("if server starts returning empty body, still does not update", func(t *testing.T) { + // clear the log messages + t.Cleanup(func() { observedLogs.TakeAll() }) + + testutils.WaitForLogMessageWithField(t, observedLogs, "Error while fetching channel definitions", "err", fmt.Sprintf("SHA3 mismatch: expected %x, got %x", sampleDefinitionsSHA, sha3.Sum256([]byte{}))) + }) + + { + rc := NewMockReadCloser(sampleDefinitionsJSON) + client.SetResponse(&http.Response{ + StatusCode: 200, + Body: rc, + }, nil) + } + + t.Run("when URL starts returning valid JSON, updates even without needing new logs", func(t *testing.T) { + // clear the log messages + t.Cleanup(func() { observedLogs.TakeAll() }) + + le := testutils.WaitForLogMessage(t, observedLogs, "Set new channel definitions") + fields := le.ContextMap() + assert.Contains(t, fields, "version") + assert.Contains(t, fields, "url") + assert.Contains(t, fields, "sha") + assert.Contains(t, fields, "donID") + assert.NotContains(t, fields, "err") + + assert.Equal(t, uint32(3), fields["version"]) + assert.Equal(t, "http://example.com/foo3", fields["url"]) + assert.Equal(t, fmt.Sprintf("%x", sampleDefinitionsSHA), fields["sha"]) + assert.Equal(t, donID, fields["donID"]) + + assert.Equal(t, sampleDefinitions, cdc.Definitions()) + + t.Run("latest channel definitions are persisted", func(t *testing.T) { + pd, err := orm.LoadChannelDefinitions(testutils.Context(t), configStoreAddress, donID) + require.NoError(t, err) + assert.Equal(t, ETHMainnetChainSelector, pd.ChainSelector) + assert.Equal(t, configStoreAddress, pd.Address) + assert.Equal(t, sampleDefinitions, pd.Definitions) + assert.Equal(t, donID, pd.DonID) + assert.Equal(t, uint32(3), pd.Version) + }) + + t.Run("new cdc with same config should load from DB", func(t *testing.T) { + // fromBlock far in the future to ensure logs are not used + cdc2 := llo.NewChannelDefinitionCache(lggr, orm, client, lp, configStoreAddress, donID, 1000) + servicetest.Run(t, cdc2) + + assert.Equal(t, sampleDefinitions, cdc.Definitions()) + }) + }) + + { + url := "not a real URL" + require.NoError(t, utils.JustError(configStoreContract.SetChannelDefinitions(steve, donID, url, sampleDefinitionsSHA))) + + backend.Commit() + + client.SetResponse(nil, errors.New("failed; not a real URL")) + } + + t.Run("new log with invalid channel definitions URL does not affect old channel definitions", func(t *testing.T) { + // clear the log messages + t.Cleanup(func() { observedLogs.TakeAll() }) + + le := testutils.WaitForLogMessage(t, observedLogs, "Error while fetching channel definitions") + fields := le.ContextMap() + assert.Contains(t, fields, "err") + assert.Equal(t, "error making http request: failed; not a real URL", fields["err"]) + }) + + { + // add a new definition, it should get loaded + sampleDefinitions[3] = llotypes.ChannelDefinition{ + ReportFormat: llotypes.ReportFormatJSON, + Streams: []llotypes.Stream{ + { + StreamID: 6, + Aggregator: llotypes.AggregatorMedian, + }, + }, + } + var err error + sampleDefinitionsJSON, err = json.MarshalIndent(sampleDefinitions, "", " ") + require.NoError(t, err) + sampleDefinitionsSHA = sha3.Sum256(sampleDefinitionsJSON) + rc := NewMockReadCloser(sampleDefinitionsJSON) + client.SetResponse(&http.Response{ + StatusCode: 200, + Body: rc, + }, nil) + + url := "http://example.com/foo5" + require.NoError(t, utils.JustError(configStoreContract.SetChannelDefinitions(steve, donID, url, sampleDefinitionsSHA))) + + backend.Commit() + } + + t.Run("successfully updates to new channel definitions with new log", func(t *testing.T) { + t.Cleanup(func() { observedLogs.TakeAll() }) + + le := testutils.WaitForLogMessage(t, observedLogs, "Set new channel definitions") + fields := le.ContextMap() + assert.Contains(t, fields, "version") + assert.Contains(t, fields, "url") + assert.Contains(t, fields, "sha") + assert.Contains(t, fields, "donID") + assert.NotContains(t, fields, "err") + + assert.Equal(t, uint32(5), fields["version"]) + assert.Equal(t, "http://example.com/foo5", fields["url"]) + assert.Equal(t, fmt.Sprintf("%x", sampleDefinitionsSHA), fields["sha"]) + assert.Equal(t, donID, fields["donID"]) + + assert.Equal(t, sampleDefinitions, cdc.Definitions()) + }) + + t.Run("latest channel definitions are persisted and overwrite previous value", func(t *testing.T) { + pd, err := orm.LoadChannelDefinitions(testutils.Context(t), configStoreAddress, donID) + require.NoError(t, err) + assert.Equal(t, ETHMainnetChainSelector, pd.ChainSelector) + assert.Equal(t, configStoreAddress, pd.Address) + assert.Equal(t, sampleDefinitions, pd.Definitions) + assert.Equal(t, donID, pd.DonID) + assert.Equal(t, uint32(5), pd.Version) + }) } diff --git a/core/services/ocr2/plugins/median/services.go b/core/services/ocr2/plugins/median/services.go index 739eb9759f..866044fcc9 100644 --- a/core/services/ocr2/plugins/median/services.go +++ b/core/services/ocr2/plugins/median/services.go @@ -167,11 +167,11 @@ func NewMedianServices(ctx context.Context, abort() return } - medianService := loop.NewMedianService(lggr, telem, cmdFn, medianProvider, dataSource, juelsPerFeeCoinSource, dataSource, errorLog) - argsNoPlugin.ReportingPluginFactory = medianService - srvs = append(srvs, medianService) + median := loop.NewMedianService(lggr, telem, cmdFn, medianProvider, spec.ContractID, dataSource, juelsPerFeeCoinSource, gasPriceSubunitsDataSource, errorLog) + argsNoPlugin.ReportingPluginFactory = median + srvs = append(srvs, median) } else { - argsNoPlugin.ReportingPluginFactory, err = median.NewPlugin(lggr).NewMedianFactory(ctx, medianProvider, dataSource, juelsPerFeeCoinSource, gasPriceSubunitsDataSource, errorLog) + argsNoPlugin.ReportingPluginFactory, err = median.NewPlugin(lggr).NewMedianFactory(ctx, medianProvider, spec.ContractID, dataSource, juelsPerFeeCoinSource, gasPriceSubunitsDataSource, errorLog) if err != nil { err = fmt.Errorf("failed to create median factory: %w", err) abort() diff --git a/core/services/ocr2/plugins/mercury/config/config.go b/core/services/ocr2/plugins/mercury/config/config.go index 5763b883ac..40854bd8c0 100644 --- a/core/services/ocr2/plugins/mercury/config/config.go +++ b/core/services/ocr2/plugins/mercury/config/config.go @@ -108,7 +108,7 @@ func ValidatePluginConfig(config PluginConfig, feedID mercuryutils.FeedID) (merr if config.NativeFeedID != nil { merr = errors.Join(merr, errors.New("nativeFeedID may not be specified for v1 jobs")) } - case 2, 3: + case 2, 3, 4: if config.LinkFeedID == nil { merr = errors.Join(merr, fmt.Errorf("linkFeedID must be specified for v%d jobs", feedID.Version())) } @@ -119,7 +119,7 @@ func ValidatePluginConfig(config PluginConfig, feedID mercuryutils.FeedID) (merr merr = errors.Join(merr, fmt.Errorf("initialBlockNumber may not be specified for v%d jobs", feedID.Version())) } default: - merr = errors.Join(merr, fmt.Errorf("got unsupported schema version %d; supported versions are 1,2,3", feedID.Version())) + merr = errors.Join(merr, fmt.Errorf("got unsupported schema version %d; supported versions are 1,2,3,4", feedID.Version())) } return merr diff --git a/core/services/ocr2/plugins/mercury/helpers_test.go b/core/services/ocr2/plugins/mercury/helpers_test.go index 3637b806de..48d320c8de 100644 --- a/core/services/ocr2/plugins/mercury/helpers_test.go +++ b/core/services/ocr2/plugins/mercury/helpers_test.go @@ -27,6 +27,7 @@ import ( ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" @@ -103,7 +104,7 @@ func startMercuryServer(t *testing.T, srv *mercuryServer, pubKeys []ed25519.Publ t.Fatalf("[MAIN] failed to listen: %v", err) } serverURL = lis.Addr().String() - s := wsrpc.NewServer(wsrpc.Creds(srv.privKey, pubKeys)) + s := wsrpc.NewServer(wsrpc.WithCreds(srv.privKey, pubKeys)) // Register mercury implementation with the wsrpc server pb.RegisterMercuryServer(s, srv) @@ -121,6 +122,7 @@ type Feed struct { baseBenchmarkPrice *big.Int baseBid *big.Int baseAsk *big.Int + baseMarketStatus uint32 } func randomFeedID(version uint16) [32]byte { @@ -467,3 +469,79 @@ chainID = 1337 nativeFeedID, )) } + +func addV4MercuryJob( + t *testing.T, + node Node, + i int, + verifierAddress common.Address, + bootstrapPeerID string, + bootstrapNodePort int, + bmBridge, + marketStatusBridge string, + servers map[string]string, + clientPubKey ed25519.PublicKey, + feedName string, + feedID [32]byte, + linkFeedID [32]byte, + nativeFeedID [32]byte, +) { + srvs := make([]string, 0, len(servers)) + for u, k := range servers { + srvs = append(srvs, fmt.Sprintf("%q = %q", u, k)) + } + serversStr := fmt.Sprintf("{ %s }", strings.Join(srvs, ", ")) + + node.AddJob(t, fmt.Sprintf(` +type = "offchainreporting2" +schemaVersion = 1 +name = "mercury-%[1]d-%[9]s" +forwardingAllowed = false +maxTaskDuration = "1s" +contractID = "%[2]s" +feedID = "0x%[8]x" +contractConfigTrackerPollInterval = "1s" +ocrKeyBundleID = "%[3]s" +p2pv2Bootstrappers = [ + "%[4]s" +] +relay = "evm" +pluginType = "mercury" +transmitterID = "%[7]x" +observationSource = """ + // Benchmark Price + price1 [type=bridge name="%[5]s" timeout="50ms" requestData="{\\"data\\":{\\"from\\":\\"ETH\\",\\"to\\":\\"USD\\"}}"]; + price1_parse [type=jsonparse path="result"]; + price1_multiply [type=multiply times=100000000 index=0]; + + price1 -> price1_parse -> price1_multiply; + + // Market Status + marketstatus [type=bridge name="%[12]s" timeout="50ms" requestData="{\\"data\\":{\\"from\\":\\"ETH\\",\\"to\\":\\"USD\\"}}"]; + marketstatus_parse [type=jsonparse path="result" index=1]; + + marketstatus -> marketstatus_parse; +""" + +[pluginConfig] +servers = %[6]s +linkFeedID = "0x%[10]x" +nativeFeedID = "0x%[11]x" + +[relayConfig] +chainID = 1337 + `, + i, + verifierAddress, + node.KeyBundle.ID(), + fmt.Sprintf("%s@127.0.0.1:%d", bootstrapPeerID, bootstrapNodePort), + bmBridge, + serversStr, + clientPubKey, + feedID, + feedName, + linkFeedID, + nativeFeedID, + marketStatusBridge, + )) +} diff --git a/core/services/ocr2/plugins/mercury/integration_test.go b/core/services/ocr2/plugins/mercury/integration_test.go index 832a39237e..a8d49150b8 100644 --- a/core/services/ocr2/plugins/mercury/integration_test.go +++ b/core/services/ocr2/plugins/mercury/integration_test.go @@ -24,25 +24,24 @@ import ( "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/hashicorp/consul/sdk/freeport" "github.com/shopspring/decimal" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/zap/zapcore" - "go.uber.org/zap/zaptest/observer" - "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3confighelper" ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "github.com/smartcontractkit/wsrpc/credentials" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" mercurytypes "github.com/smartcontractkit/chainlink-common/pkg/types/mercury" v1 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v1" v2 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v2" v3 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v3" + v4 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v4" datastreamsmercury "github.com/smartcontractkit/chainlink-data-streams/mercury" - "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" - token "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/link_token_interface" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/link_token_interface" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/fee_manager" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/reward_manager" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/verifier" @@ -56,6 +55,7 @@ import ( reportcodecv1 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v1/reportcodec" reportcodecv2 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v2/reportcodec" reportcodecv3 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v3/reportcodec" + reportcodecv4 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v4/reportcodec" "github.com/smartcontractkit/chainlink/v2/core/store/models" ) @@ -98,11 +98,11 @@ func setupBlockchain(t *testing.T) (*bind.TransactOpts, *backends.SimulatedBacke t.Cleanup(stopMining) // Deploy contracts - linkTokenAddress, _, linkToken, err := token.DeployLinkToken(steve, backend) + linkTokenAddress, _, linkToken, err := link_token_interface.DeployLinkToken(steve, backend) require.NoError(t, err) _, err = linkToken.Transfer(steve, steve.From, big.NewInt(1000)) require.NoError(t, err) - nativeTokenAddress, _, nativeToken, err := token.DeployLinkToken(steve, backend) + nativeTokenAddress, _, nativeToken, err := link_token_interface.DeployLinkToken(steve, backend) require.NoError(t, err) _, err = nativeToken.Transfer(steve, steve.From, big.NewInt(1000)) require.NoError(t, err) @@ -146,9 +146,9 @@ func integration_MercuryV1(t *testing.T) { pError := atomic.Int64{} // feeds - btcFeed := Feed{"BTC/USD", randomFeedID(1), big.NewInt(20_000 * multiplier), big.NewInt(19_997 * multiplier), big.NewInt(20_004 * multiplier)} - ethFeed := Feed{"ETH/USD", randomFeedID(1), big.NewInt(1_568 * multiplier), big.NewInt(1_566 * multiplier), big.NewInt(1_569 * multiplier)} - linkFeed := Feed{"LINK/USD", randomFeedID(1), big.NewInt(7150 * multiplier / 1000), big.NewInt(7123 * multiplier / 1000), big.NewInt(7177 * multiplier / 1000)} + btcFeed := Feed{"BTC/USD", randomFeedID(1), big.NewInt(20_000 * multiplier), big.NewInt(19_997 * multiplier), big.NewInt(20_004 * multiplier), 0} + ethFeed := Feed{"ETH/USD", randomFeedID(1), big.NewInt(1_568 * multiplier), big.NewInt(1_566 * multiplier), big.NewInt(1_569 * multiplier), 0} + linkFeed := Feed{"LINK/USD", randomFeedID(1), big.NewInt(7150 * multiplier / 1000), big.NewInt(7123 * multiplier / 1000), big.NewInt(7177 * multiplier / 1000), 0} feeds := []Feed{btcFeed, ethFeed, linkFeed} feedM := make(map[[32]byte]Feed, len(feeds)) for i := range feeds { @@ -1036,3 +1036,302 @@ func integration_MercuryV3(t *testing.T) { } }) } + +func TestIntegration_MercuryV4(t *testing.T) { + t.Parallel() + + integration_MercuryV4(t) +} + +func integration_MercuryV4(t *testing.T) { + ctx := testutils.Context(t) + var logObservers []*observer.ObservedLogs + t.Cleanup(func() { + detectPanicLogs(t, logObservers) + }) + + testStartTimeStamp := uint32(time.Now().Unix()) + + // test vars + // pError is the probability that an EA will return an error instead of a result, as integer percentage + // pError = 0 means it will never return error + pError := atomic.Int64{} + + // feeds + btcFeed := Feed{ + name: "BTC/USD", + id: randomFeedID(4), + baseBenchmarkPrice: big.NewInt(20_000 * multiplier), + baseBid: big.NewInt(19_997 * multiplier), + baseAsk: big.NewInt(20_004 * multiplier), + baseMarketStatus: 1, + } + ethFeed := Feed{ + name: "ETH/USD", + id: randomFeedID(4), + baseBenchmarkPrice: big.NewInt(1_568 * multiplier), + baseBid: big.NewInt(1_566 * multiplier), + baseAsk: big.NewInt(1_569 * multiplier), + baseMarketStatus: 2, + } + linkFeed := Feed{ + name: "LINK/USD", + id: randomFeedID(4), + baseBenchmarkPrice: big.NewInt(7150 * multiplier / 1000), + baseBid: big.NewInt(7123 * multiplier / 1000), + baseAsk: big.NewInt(7177 * multiplier / 1000), + baseMarketStatus: 3, + } + feeds := []Feed{btcFeed, ethFeed, linkFeed} + feedM := make(map[[32]byte]Feed, len(feeds)) + for i := range feeds { + feedM[feeds[i].id] = feeds[i] + } + + clientCSAKeys := make([]csakey.KeyV2, n+1) + clientPubKeys := make([]ed25519.PublicKey, n+1) + for i := 0; i < n+1; i++ { + k := big.NewInt(int64(i)) + key := csakey.MustNewV2XXXTestingOnly(k) + clientCSAKeys[i] = key + clientPubKeys[i] = key.PublicKey + } + + // Test multi-send to three servers + const nSrvs = 3 + reqChs := make([]chan request, nSrvs) + servers := make(map[string]string) + for i := 0; i < nSrvs; i++ { + k := csakey.MustNewV2XXXTestingOnly(big.NewInt(int64(-(i + 1)))) + reqs := make(chan request, 100) + srv := NewMercuryServer(t, ed25519.PrivateKey(k.Raw()), reqs, func() []byte { + report, err := (&reportcodecv4.ReportCodec{}).BuildReport(v4.ReportFields{BenchmarkPrice: big.NewInt(234567), LinkFee: big.NewInt(1), NativeFee: big.NewInt(1), MarketStatus: 1}) + if err != nil { + panic(err) + } + return report + }) + serverURL := startMercuryServer(t, srv, clientPubKeys) + reqChs[i] = reqs + servers[serverURL] = fmt.Sprintf("%x", k.PublicKey) + } + chainID := testutils.SimulatedChainID + + steve, backend, verifier, verifierAddress := setupBlockchain(t) + + // Setup bootstrap + oracle nodes + bootstrapNodePort := freeport.GetOne(t) + appBootstrap, bootstrapPeerID, _, bootstrapKb, observedLogs := setupNode(t, bootstrapNodePort, "bootstrap_mercury", backend, clientCSAKeys[n]) + bootstrapNode := Node{App: appBootstrap, KeyBundle: bootstrapKb} + logObservers = append(logObservers, observedLogs) + + // Commit blocks to finality depth to ensure LogPoller has finalized blocks to read from + ch, err := bootstrapNode.App.GetRelayers().LegacyEVMChains().Get(testutils.SimulatedChainID.String()) + require.NoError(t, err) + finalityDepth := ch.Config().EVM().FinalityDepth() + for i := 0; i < int(finalityDepth); i++ { + backend.Commit() + } + + // Set up n oracles + var ( + oracles []confighelper.OracleIdentityExtra + nodes []Node + ) + ports := freeport.GetN(t, n) + for i := 0; i < n; i++ { + app, peerID, transmitter, kb, observedLogs := setupNode(t, ports[i], fmt.Sprintf("oracle_mercury%d", i), backend, clientCSAKeys[i]) + + nodes = append(nodes, Node{ + app, transmitter, kb, + }) + + offchainPublicKey, _ := hex.DecodeString(strings.TrimPrefix(kb.OnChainPublicKey(), "0x")) + oracles = append(oracles, confighelper.OracleIdentityExtra{ + OracleIdentity: confighelper.OracleIdentity{ + OnchainPublicKey: offchainPublicKey, + TransmitAccount: ocr2types.Account(fmt.Sprintf("%x", transmitter[:])), + OffchainPublicKey: kb.OffchainPublicKey(), + PeerID: peerID, + }, + ConfigEncryptionPublicKey: kb.ConfigEncryptionPublicKey(), + }) + logObservers = append(logObservers, observedLogs) + } + + for _, feed := range feeds { + addBootstrapJob(t, bootstrapNode, chainID, verifierAddress, feed.name, feed.id) + } + + createBridge := func(name string, i int, p *big.Int, marketStatus uint32, borm bridges.ORM) (bridgeName string) { + bridge := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + b, herr := io.ReadAll(req.Body) + require.NoError(t, herr) + require.Equal(t, `{"data":{"from":"ETH","to":"USD"}}`, string(b)) + + r := rand.Int63n(101) + if r > pError.Load() { + res.WriteHeader(http.StatusOK) + + var val string + if p != nil { + val = decimal.NewFromBigInt(p, 0).Div(decimal.NewFromInt(multiplier)).Add(decimal.NewFromInt(int64(i)).Div(decimal.NewFromInt(100))).String() + } else { + val = fmt.Sprintf("%d", marketStatus) + } + + resp := fmt.Sprintf(`{"result": %s}`, val) + _, herr = res.Write([]byte(resp)) + require.NoError(t, herr) + } else { + res.WriteHeader(http.StatusInternalServerError) + resp := `{"error": "pError test error"}` + _, herr = res.Write([]byte(resp)) + require.NoError(t, herr) + } + })) + t.Cleanup(bridge.Close) + u, _ := url.Parse(bridge.URL) + bridgeName = fmt.Sprintf("bridge-%s-%d", name, i) + require.NoError(t, borm.CreateBridgeType(ctx, &bridges.BridgeType{ + Name: bridges.BridgeName(bridgeName), + URL: models.WebURL(*u), + })) + + return bridgeName + } + + // Add OCR jobs - one per feed on each node + for i, node := range nodes { + for j, feed := range feeds { + bmBridge := createBridge(fmt.Sprintf("benchmarkprice-%d", j), i, feed.baseBenchmarkPrice, 0, node.App.BridgeORM()) + marketStatusBridge := createBridge(fmt.Sprintf("marketstatus-%d", j), i, nil, feed.baseMarketStatus, node.App.BridgeORM()) + + addV4MercuryJob( + t, + node, + i, + verifierAddress, + bootstrapPeerID, + bootstrapNodePort, + bmBridge, + marketStatusBridge, + servers, + clientPubKeys[i], + feed.name, + feed.id, + randomFeedID(2), + randomFeedID(2), + ) + } + } + + // Setup config on contract + onchainConfig, err := (datastreamsmercury.StandardOnchainConfigCodec{}).Encode(rawOnchainConfig) + require.NoError(t, err) + + reportingPluginConfig, err := json.Marshal(rawReportingPluginConfig) + require.NoError(t, err) + + signers, _, _, onchainConfig, offchainConfigVersion, offchainConfig, err := ocr3confighelper.ContractSetConfigArgsForTestsMercuryV02( + 2*time.Second, // DeltaProgress + 20*time.Second, // DeltaResend + 400*time.Millisecond, // DeltaInitial + 100*time.Millisecond, // DeltaRound + 0, // DeltaGrace + 300*time.Millisecond, // DeltaCertifiedCommitRequest + 1*time.Minute, // DeltaStage + 100, // rMax + []int{len(nodes)}, // S + oracles, + reportingPluginConfig, // reportingPluginConfig []byte, + 250*time.Millisecond, // Max duration observation + int(f), // f + onchainConfig, + ) + + require.NoError(t, err) + signerAddresses, err := evm.OnchainPublicKeyToAddress(signers) + require.NoError(t, err) + + offchainTransmitters := make([][32]byte, n) + for i := 0; i < n; i++ { + offchainTransmitters[i] = nodes[i].ClientPubKey + } + + for _, feed := range feeds { + _, ferr := verifier.SetConfig( + steve, + feed.id, + signerAddresses, + offchainTransmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig, + nil, + ) + require.NoError(t, ferr) + backend.Commit() + } + + runTestSetup := func(reqs chan request) { + // Expect at least one report per feed from each oracle, per server + seen := make(map[[32]byte]map[credentials.StaticSizedPublicKey]struct{}) + for i := range feeds { + // feedID will be deleted when all n oracles have reported + seen[feeds[i].id] = make(map[credentials.StaticSizedPublicKey]struct{}, n) + } + + for req := range reqs { + v := make(map[string]interface{}) + err := mercury.PayloadTypes.UnpackIntoMap(v, req.req.Payload) + require.NoError(t, err) + report, exists := v["report"] + if !exists { + t.Fatalf("expected payload %#v to contain 'report'", v) + } + reportElems := make(map[string]interface{}) + err = reportcodecv4.ReportTypes.UnpackIntoMap(reportElems, report.([]byte)) + require.NoError(t, err) + + feedID := reportElems["feedId"].([32]uint8) + feed, exists := feedM[feedID] + require.True(t, exists) + + if _, exists := seen[feedID]; !exists { + continue // already saw all oracles for this feed + } + + expectedFee := datastreamsmercury.CalculateFee(big.NewInt(234567), rawReportingPluginConfig.BaseUSDFee) + expectedExpiresAt := reportElems["observationsTimestamp"].(uint32) + rawReportingPluginConfig.ExpirationWindow + + assert.GreaterOrEqual(t, int(reportElems["observationsTimestamp"].(uint32)), int(testStartTimeStamp)) + assert.InDelta(t, feed.baseBenchmarkPrice.Int64(), reportElems["benchmarkPrice"].(*big.Int).Int64(), 5000000) + assert.NotZero(t, reportElems["validFromTimestamp"].(uint32)) + assert.GreaterOrEqual(t, reportElems["observationsTimestamp"].(uint32), reportElems["validFromTimestamp"].(uint32)) + assert.Equal(t, expectedExpiresAt, reportElems["expiresAt"].(uint32)) + assert.Equal(t, expectedFee, reportElems["linkFee"].(*big.Int)) + assert.Equal(t, expectedFee, reportElems["nativeFee"].(*big.Int)) + assert.Equal(t, feed.baseMarketStatus, reportElems["marketStatus"].(uint32)) + + t.Logf("oracle %x reported for feed %s (0x%x)", req.pk, feed.name, feed.id) + + seen[feedID][req.pk] = struct{}{} + if len(seen[feedID]) == n { + t.Logf("all oracles reported for feed %s (0x%x)", feed.name, feed.id) + delete(seen, feedID) + if len(seen) == 0 { + break // saw all oracles; success! + } + } + } + } + + t.Run("receives at least one report per feed for every server from each oracle when EAs are at 100% reliability", func(t *testing.T) { + for i := 0; i < nSrvs; i++ { + reqs := reqChs[i] + runTestSetup(reqs) + } + }) +} diff --git a/core/services/ocr2/plugins/mercury/plugin.go b/core/services/ocr2/plugins/mercury/plugin.go index c5eba78b0d..8a4101804d 100644 --- a/core/services/ocr2/plugins/mercury/plugin.go +++ b/core/services/ocr2/plugins/mercury/plugin.go @@ -13,6 +13,7 @@ import ( relaymercuryv1 "github.com/smartcontractkit/chainlink-data-streams/mercury/v1" relaymercuryv2 "github.com/smartcontractkit/chainlink-data-streams/mercury/v2" relaymercuryv3 "github.com/smartcontractkit/chainlink-data-streams/mercury/v3" + relaymercuryv4 "github.com/smartcontractkit/chainlink-data-streams/mercury/v4" "github.com/smartcontractkit/chainlink-common/pkg/loop" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" @@ -29,6 +30,7 @@ import ( mercuryv1 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v1" mercuryv2 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v2" mercuryv3 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v3" + mercuryv4 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v4" "github.com/smartcontractkit/chainlink/v2/plugins" ) @@ -71,27 +73,36 @@ func NewServices( chEnhancedTelem chan ocrcommon.EnhancedTelemetryMercuryData, orm types.DataSourceORM, feedID utils.FeedID, + enableTriggerCapability bool, ) ([]job.ServiceCtx, error) { if jb.PipelineSpec == nil { return nil, errors.New("expected job to have a non-nil PipelineSpec") } + var err error var pluginConfig config.PluginConfig - err := json.Unmarshal(jb.OCR2OracleSpec.PluginConfig.Bytes(), &pluginConfig) - if err != nil { - return nil, errors.WithStack(err) - } - err = config.ValidatePluginConfig(pluginConfig, feedID) - if err != nil { - return nil, err + if len(jb.OCR2OracleSpec.PluginConfig) == 0 { + if !enableTriggerCapability { + return nil, fmt.Errorf("at least one transmission option must be configured") + } + } else { + err = json.Unmarshal(jb.OCR2OracleSpec.PluginConfig.Bytes(), &pluginConfig) + if err != nil { + return nil, errors.WithStack(err) + } + err = config.ValidatePluginConfig(pluginConfig, feedID) + if err != nil { + return nil, err + } } + lggr = lggr.Named("MercuryPlugin").With("jobID", jb.ID, "jobName", jb.Name.ValueOrZero()) // encapsulate all the subservices and ensure we close them all if any fail to start srvs := []job.ServiceCtx{ocr2Provider} abort := func() { - if cerr := services.MultiCloser(srvs).Close(); err != nil { - lggr.Errorw("Error closing unused services", "err", cerr) + if err = services.MultiCloser(srvs).Close(); err != nil { + lggr.Errorw("Error closing unused services", "err", err) } } saver := ocrcommon.NewResultRunSaver(pipelineRunner, lggr, cfg.MaxSuccessfulRuns(), cfg.ResultWriteQueueDepth()) @@ -136,6 +147,13 @@ func NewServices( return nil, fmt.Errorf("failed to create mercury v3 factory: %w", err) } srvs = append(srvs, factoryServices...) + case 4: + factory, factoryServices, err = newv4factory(fCfg) + if err != nil { + abort() + return nil, fmt.Errorf("failed to create mercury v4 factory: %w", err) + } + srvs = append(srvs, factoryServices...) default: return nil, errors.Errorf("unknown Mercury report schema version: %d", feedID.Version()) } @@ -162,10 +180,61 @@ type factoryCfg struct { feedID utils.FeedID } +func getPluginFeedIDs(pluginConfig config.PluginConfig) (linkFeedID utils.FeedID, nativeFeedID utils.FeedID) { + if pluginConfig.LinkFeedID != nil { + linkFeedID = *pluginConfig.LinkFeedID + } + if pluginConfig.NativeFeedID != nil { + nativeFeedID = *pluginConfig.NativeFeedID + } + return linkFeedID, nativeFeedID +} + +func newv4factory(factoryCfg factoryCfg) (ocr3types.MercuryPluginFactory, []job.ServiceCtx, error) { + var factory ocr3types.MercuryPluginFactory + srvs := make([]job.ServiceCtx, 0) + + linkFeedID, nativeFeedID := getPluginFeedIDs(factoryCfg.reportingPluginConfig) + + ds := mercuryv4.NewDataSource( + factoryCfg.orm, + factoryCfg.pipelineRunner, + factoryCfg.jb, + *factoryCfg.jb.PipelineSpec, + factoryCfg.feedID, + factoryCfg.lggr, + factoryCfg.saver, + factoryCfg.chEnhancedTelem, + factoryCfg.ocr2Provider.MercuryServerFetcher(), + linkFeedID, + nativeFeedID, + ) + + loopCmd := env.MercuryPlugin.Cmd.Get() + loopEnabled := loopCmd != "" + + if loopEnabled { + cmdFn, opts, mercuryLggr, err := initLoop(loopCmd, factoryCfg.cfg, factoryCfg.feedID, factoryCfg.lggr) + if err != nil { + return nil, nil, fmt.Errorf("failed to init loop for feed %s: %w", factoryCfg.feedID, err) + } + // in loop mode, the factory is grpc server, and we need to handle the server lifecycle + factoryServer := loop.NewMercuryV4Service(mercuryLggr, opts, cmdFn, factoryCfg.ocr2Provider, ds) + srvs = append(srvs, factoryServer) + // adapt the grpc server to the vanilla mercury plugin factory interface used by the oracle + factory = factoryServer + } else { + factory = relaymercuryv4.NewFactory(ds, factoryCfg.lggr, factoryCfg.ocr2Provider.OnchainConfigCodec(), factoryCfg.ocr2Provider.ReportCodecV4()) + } + return factory, srvs, nil +} + func newv3factory(factoryCfg factoryCfg) (ocr3types.MercuryPluginFactory, []job.ServiceCtx, error) { var factory ocr3types.MercuryPluginFactory srvs := make([]job.ServiceCtx, 0) + linkFeedID, nativeFeedID := getPluginFeedIDs(factoryCfg.reportingPluginConfig) + ds := mercuryv3.NewDataSource( factoryCfg.orm, factoryCfg.pipelineRunner, @@ -176,8 +245,8 @@ func newv3factory(factoryCfg factoryCfg) (ocr3types.MercuryPluginFactory, []job. factoryCfg.saver, factoryCfg.chEnhancedTelem, factoryCfg.ocr2Provider.MercuryServerFetcher(), - *factoryCfg.reportingPluginConfig.LinkFeedID, - *factoryCfg.reportingPluginConfig.NativeFeedID, + linkFeedID, + nativeFeedID, ) loopCmd := env.MercuryPlugin.Cmd.Get() @@ -203,6 +272,8 @@ func newv2factory(factoryCfg factoryCfg) (ocr3types.MercuryPluginFactory, []job. var factory ocr3types.MercuryPluginFactory srvs := make([]job.ServiceCtx, 0) + linkFeedID, nativeFeedID := getPluginFeedIDs(factoryCfg.reportingPluginConfig) + ds := mercuryv2.NewDataSource( factoryCfg.orm, factoryCfg.pipelineRunner, @@ -213,8 +284,8 @@ func newv2factory(factoryCfg factoryCfg) (ocr3types.MercuryPluginFactory, []job. factoryCfg.saver, factoryCfg.chEnhancedTelem, factoryCfg.ocr2Provider.MercuryServerFetcher(), - *factoryCfg.reportingPluginConfig.LinkFeedID, - *factoryCfg.reportingPluginConfig.NativeFeedID, + linkFeedID, + nativeFeedID, ) loopCmd := env.MercuryPlugin.Cmd.Get() diff --git a/core/services/ocr2/plugins/mercury/plugin_test.go b/core/services/ocr2/plugins/mercury/plugin_test.go index 95aaabec14..22aaf7522d 100644 --- a/core/services/ocr2/plugins/mercury/plugin_test.go +++ b/core/services/ocr2/plugins/mercury/plugin_test.go @@ -21,6 +21,7 @@ import ( v1 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v1" v2 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v2" v3 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v3" + v4 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v4" mercuryocr2 "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/mercury" @@ -37,6 +38,7 @@ var ( v1FeedId = [32]uint8{00, 01, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58, 163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114} v2FeedId = [32]uint8{00, 02, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58, 163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114} v3FeedId = [32]uint8{00, 03, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58, 163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114} + v4FeedId = [32]uint8{00, 04, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58, 163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114} testArgsNoPlugin = libocr2.MercuryOracleArgs{ LocalConfig: libocr2types.LocalConfig{ @@ -66,6 +68,13 @@ var ( "nativeFeedID": "0x00036b4aa7e57ca7b68ae1bf45653f56b656fd3aa335ef7fae696b663f1b8472", } + v4jsonCfg = job.JSONConfig{ + "serverURL": "example.com:80", + "serverPubKey": "724ff6eae9e900270edfff233e16322a70ec06e1a6e62a81ef13921f398f6c93", + "linkFeedID": "0x00026b4aa7e57ca7b68ae1bf45653f56b656fd3aa335ef7fae696b663f1b8472", + "nativeFeedID": "0x00036b4aa7e57ca7b68ae1bf45653f56b656fd3aa335ef7fae696b663f1b8472", + } + testJob = job.Job{ ID: 1, ExternalJobID: uuid.Must(uuid.NewRandom()), @@ -135,6 +144,15 @@ func TestNewServices(t *testing.T) { wantServiceCnt: expectedEmbeddedServiceCnt, wantErr: false, }, + { + name: "v4 legacy", + args: args{ + pluginConfig: v4jsonCfg, + feedID: v4FeedId, + }, + wantServiceCnt: expectedEmbeddedServiceCnt, + wantErr: false, + }, { name: "v1 loop", loopMode: true, @@ -168,6 +186,17 @@ func TestNewServices(t *testing.T) { wantErr: false, wantLoopFactory: &loop.MercuryV3Service{}, }, + { + name: "v4 loop", + loopMode: true, + args: args{ + pluginConfig: v4jsonCfg, + feedID: v4FeedId, + }, + wantServiceCnt: expectedLoopServiceCnt, + wantErr: false, + wantLoopFactory: &loop.MercuryV4Service{}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -201,13 +230,13 @@ func newServicesTestWrapper(t *testing.T, pluginConfig job.JSONConfig, feedID ut t.Helper() jb := testJob jb.OCR2OracleSpec.PluginConfig = pluginConfig - return mercuryocr2.NewServices(jb, &testProvider{}, nil, logger.TestLogger(t), testArgsNoPlugin, testCfg, nil, &testDataSourceORM{}, feedID) + return mercuryocr2.NewServices(jb, &testProvider{}, nil, logger.TestLogger(t), testArgsNoPlugin, testCfg, nil, &testDataSourceORM{}, feedID, false) } type testProvider struct{} // ChainReader implements types.MercuryProvider. -func (*testProvider) ChainReader() commontypes.ContractReader { panic("unimplemented") } +func (*testProvider) ContractReader() commontypes.ContractReader { panic("unimplemented") } // Close implements types.MercuryProvider. func (*testProvider) Close() error { return nil } @@ -259,6 +288,9 @@ func (*testProvider) ReportCodecV2() v2.ReportCodec { return nil } // ReportCodecV3 implements types.MercuryProvider. func (*testProvider) ReportCodecV3() v3.ReportCodec { return nil } +// ReportCodecV4 implements types.MercuryProvider. +func (*testProvider) ReportCodecV4() v4.ReportCodec { return nil } + // Start implements types.MercuryProvider. func (*testProvider) Start(context.Context) error { panic("unimplemented") } diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/autotelemetry21/custom_telemetry.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/autotelemetry21/custom_telemetry.go index 53303553db..2c997a2d38 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/autotelemetry21/custom_telemetry.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/autotelemetry21/custom_telemetry.go @@ -3,6 +3,7 @@ package autotelemetry21 import ( "context" "encoding/hex" + "sync" "time" "google.golang.org/protobuf/proto" @@ -29,6 +30,7 @@ type AutomationCustomTelemetryService struct { lggr logger.Logger configDigest [32]byte contractConfigTracker types.ContractConfigTracker + mu sync.RWMutex } // NewAutomationCustomTelemetryService creates a telemetry service for new blocks and node version @@ -66,8 +68,15 @@ func (e *AutomationCustomTelemetryService) Start(ctx context.Context) error { if err != nil { e.lggr.Errorf("Error occurred while getting newestConfigDetails in configDigest loop %s", err) } + configChanged := false + e.mu.Lock() if newConfigDigest != e.configDigest { e.configDigest = newConfigDigest + configChanged = true + } + e.mu.Unlock() + + if configChanged { e.sendNodeVersionMsg() } case <-hourTicker.C: @@ -121,10 +130,14 @@ func (e *AutomationCustomTelemetryService) Close() error { } func (e *AutomationCustomTelemetryService) sendNodeVersionMsg() { + e.mu.RLock() + configDigest := e.configDigest + e.mu.RUnlock() + vMsg := &telem.NodeVersion{ Timestamp: uint64(time.Now().UTC().UnixMilli()), NodeVersion: static.Version, - ConfigDigest: e.configDigest[:], + ConfigDigest: configDigest[:], } wrappedVMsg := &telem.AutomationTelemWrapper{ Msg: &telem.AutomationTelemWrapper_NodeVersion{ @@ -141,11 +154,15 @@ func (e *AutomationCustomTelemetryService) sendNodeVersionMsg() { } func (e *AutomationCustomTelemetryService) sendBlockNumberMsg(blockKey ocr2keepers.BlockKey) { + e.mu.RLock() + configDigest := e.configDigest + e.mu.RUnlock() + blockNumMsg := &telem.BlockNumber{ Timestamp: uint64(time.Now().UTC().UnixMilli()), BlockNumber: uint64(blockKey.Number), BlockHash: hex.EncodeToString(blockKey.Hash[:]), - ConfigDigest: e.configDigest[:], + ConfigDigest: configDigest[:], } wrappedBlockNumMsg := &telem.AutomationTelemWrapper{ Msg: &telem.AutomationTelemWrapper_BlockNumber{ diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/block_subscriber.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/block_subscriber.go index d07af8a8de..21adc12d30 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/block_subscriber.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/block_subscriber.go @@ -9,14 +9,13 @@ import ( "github.com/ethereum/go-ethereum/common" - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" + ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" httypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -74,7 +73,7 @@ func NewBlockSubscriber(hb httypes.HeadBroadcaster, lp logpoller.LogPoller, fina blockSize: lookbackDepth, finalityDepth: finalityDepth, latestBlock: atomic.Pointer[ocr2keepers.BlockKey]{}, - lggr: lggr.Named("BlockSubscriber"), + lggr: logger.Named(lggr, "BlockSubscriber"), } } @@ -235,7 +234,7 @@ func (bs *BlockSubscriber) processHead(h *evmtypes.Head) { // head parent is a linked list with EVM finality depth // when re-org happens, new heads will have pointers to the new blocks i := int64(0) - for cp := h; cp != nil; cp = cp.Parent { + for cp := h; cp != nil; cp = cp.Parent.Load() { // we don't stop when a matching (block number/hash) entry is seen in the map because parent linked list may be // cut short during a re-org if head broadcaster backfill is not complete. This can cause some re-orged blocks // left in the map. for example, re-org happens for block 98, 99, 100. next head 101 from broadcaster has parent list diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/block_subscriber_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/block_subscriber_test.go index fefbda77cd..bdcc37dc6b 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/block_subscriber_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/block_subscriber_test.go @@ -310,23 +310,22 @@ func TestBlockSubscriber_Start(t *testing.T) { h97 := evmtypes.Head{ Number: 97, Hash: common.HexToHash("0xda2f9d1359eadd7b93338703adc07d942021a78195564038321ef53f23f87333"), - Parent: nil, } h98 := evmtypes.Head{ Number: 98, Hash: common.HexToHash("0xc20c7b47466c081a44a3b168994e89affe85cb894547845d938f923b67c633c0"), - Parent: &h97, } + h98.Parent.Store(&h97) h99 := evmtypes.Head{ Number: 99, Hash: common.HexToHash("0x9bc2b51e147f9cad05f1614b7f1d8181cb24c544cbcf841f3155e54e752a3b44"), - Parent: &h98, } + h99.Parent.Store(&h98) h100 := evmtypes.Head{ Number: 100, Hash: common.HexToHash("0x5e7fadfc14e1cfa9c05a91128c16a20c6cbc3be38b4723c3d482d44bf9c0e07b"), - Parent: &h99, } + h100.Parent.Store(&h99) // no subscribers yet bs.headC <- &h100 @@ -353,8 +352,8 @@ func TestBlockSubscriber_Start(t *testing.T) { h101 := &evmtypes.Head{ Number: 101, Hash: common.HexToHash("0xc20c7b47466c081a44a3b168994e89affe85cb894547845d938f923b67c633c0"), - Parent: &h100, } + h101.Parent.Store(&h100) bs.headC <- h101 time.Sleep(100 * time.Millisecond) @@ -387,24 +386,24 @@ func TestBlockSubscriber_Start(t *testing.T) { new99 := &evmtypes.Head{ Number: 99, Hash: common.HexToHash("0x70c03acc4ddbfb253ba41a25dc13fb21b25da8b63bcd1aa7fb55713d33a36c71"), - Parent: &h98, } + new99.Parent.Store(&h98) new100 := &evmtypes.Head{ Number: 100, Hash: common.HexToHash("0x8a876b62d252e63e16cf3487db3486c0a7c0a8e06bc3792a3b116c5ca480503f"), - Parent: new99, } + new100.Parent.Store(new99) new101 := &evmtypes.Head{ Number: 101, Hash: common.HexToHash("0x41b5842b8847dcf834e39556d2ac51cc7d960a7de9471ec504673d0038fd6c8e"), - Parent: new100, } + new101.Parent.Store(new100) new102 := &evmtypes.Head{ Number: 102, Hash: common.HexToHash("0x9ac1ebc307554cf1bcfcc2a49462278e89d6878d613a33df38a64d0aeac971b5"), - Parent: new101, } + new102.Parent.Store(new101) bs.headC <- new102 diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/gasprice/gasprice.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/gasprice/gasprice.go index 36460683d4..095972fbf5 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/gasprice/gasprice.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/gasprice/gasprice.go @@ -6,10 +6,11 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/v2/core/cbor" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/encoding" ) diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/buffer_v1.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/buffer_v1.go index e58d5ad9c9..00a56496a0 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/buffer_v1.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/buffer_v1.go @@ -6,8 +6,9 @@ import ( "sync" "sync/atomic" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/prommetrics" ) @@ -79,7 +80,7 @@ type logBuffer struct { func NewLogBuffer(lggr logger.Logger, lookback, blockRate, logLimit uint32) LogBuffer { return &logBuffer{ - lggr: lggr.Named("KeepersRegistry.LogEventBufferV1"), + lggr: logger.Sugared(lggr).Named("KeepersRegistry").Named("LogEventBufferV1"), opts: newLogBufferOptions(lookback, blockRate, logLimit), lastBlockSeen: new(atomic.Int64), queueIDs: []string{}, @@ -313,7 +314,7 @@ type upkeepLogQueue struct { func newUpkeepLogQueue(lggr logger.Logger, id *big.Int, opts *logBufferOptions) *upkeepLogQueue { return &upkeepLogQueue{ - lggr: lggr.With("upkeepID", id.String()), + lggr: logger.With(lggr, "upkeepID", id.String()), id: id, opts: opts, logs: map[int64][]logpoller.Log{}, diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/buffer_v1_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/buffer_v1_test.go index f742d39689..4c46b9b3fe 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/buffer_v1_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/buffer_v1_test.go @@ -54,8 +54,6 @@ func TestLogEventBufferV1_SyncFilters(t *testing.T) { type readableLogger struct { logger.Logger DebugwFn func(msg string, keysAndValues ...interface{}) - NamedFn func(name string) logger.Logger - WithFn func(args ...interface{}) logger.Logger } func (l *readableLogger) Debugw(msg string, keysAndValues ...interface{}) { @@ -74,6 +72,7 @@ func TestLogEventBufferV1_EnqueueViolations(t *testing.T) { t.Run("enqueuing logs for a block older than latest seen logs a message", func(t *testing.T) { logReceived := false readableLogger := &readableLogger{ + Logger: logger.TestLogger(t), DebugwFn: func(msg string, keysAndValues ...interface{}) { if msg == "enqueuing logs from a block older than latest seen block" { logReceived = true @@ -103,6 +102,7 @@ func TestLogEventBufferV1_EnqueueViolations(t *testing.T) { t.Run("enqueuing logs for the same block over multiple calls logs a message", func(t *testing.T) { logReceived := false readableLogger := &readableLogger{ + Logger: logger.TestLogger(t), DebugwFn: func(msg string, keysAndValues ...interface{}) { if msg == "enqueuing logs again for a previously seen block" { logReceived = true diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/factory.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/factory.go index 7ec65ff474..57b48841a2 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/factory.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/factory.go @@ -4,9 +4,10 @@ import ( "math/big" "time" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" ) @@ -74,7 +75,7 @@ func (o *LogTriggersOptions) Defaults(finalityDepth int64) { func (o *LogTriggersOptions) defaultBlockRate() uint32 { switch o.chainID.Int64() { - case 42161, 421613, 421614: // Arbitrum + case 42161, 421613, 421614: // Arbitrum, Arb Goerli, Arb Sepolia return 2 default: return 1 @@ -83,10 +84,10 @@ func (o *LogTriggersOptions) defaultBlockRate() uint32 { func (o *LogTriggersOptions) defaultLogLimit() uint32 { switch o.chainID.Int64() { - case 1, 4, 5, 42, 11155111: // Eth + case 1, 4, 5, 42, 11155111: // Eth, Rinkeby, Goerli, Kovan, Sepolia return 20 - case 10, 420, 56, 97, 137, 80001, 43113, 43114, 8453, 84531: // Optimism, BSC, Polygon, Avax, Base - return 5 + case 10, 420, 11155420, 56, 97, 137, 80001, 80002, 43114, 43113, 8453, 84531, 84532: // Optimism, OP Goerli, OP Sepolia, BSC, BSC Test, Polygon, Mumbai, Amoy, Avax, Avax Fuji, Base, Base Goerli, Base Sepolia + return 4 default: return 1 } diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/provider.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/provider.go index f1de1ef512..50b2ebc0d0 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/provider.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/provider.go @@ -16,13 +16,12 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" + ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" ac "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/automation_compatible_utils" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/prommetrics" "github.com/smartcontractkit/chainlink/v2/core/utils" @@ -111,7 +110,7 @@ type logEventProvider struct { func NewLogProvider(lggr logger.Logger, poller logpoller.LogPoller, chainID *big.Int, packer LogDataPacker, filterStore UpkeepFilterStore, opts LogTriggersOptions) *logEventProvider { return &logEventProvider{ threadCtrl: utils.NewThreadControl(), - lggr: lggr.Named("KeepersRegistry.LogEventProvider"), + lggr: logger.Named(lggr, "KeepersRegistry.LogEventProvider"), packer: packer, buffer: NewLogBuffer(lggr, uint32(opts.LookbackBlocks), opts.BlockRate, opts.LogLimit), poller: poller, @@ -135,7 +134,7 @@ func (p *logEventProvider) SetConfig(cfg ocr2keepers.LogEventProviderConfig) { logLimit = p.opts.defaultLogLimit() } - p.lggr.With("where", "setConfig").Infow("setting config ", "bockRate", blockRate, "logLimit", logLimit) + p.lggr.Infow("setting config", "where", "setConfig", "bockRate", blockRate, "logLimit", logLimit) atomic.StoreUint32(&p.opts.BlockRate, blockRate) atomic.StoreUint32(&p.opts.LogLimit, logLimit) @@ -156,7 +155,7 @@ func (p *logEventProvider) Start(context.Context) error { } p.threadCtrl.Go(func(ctx context.Context) { - lggr := p.lggr.With("where", "scheduler") + lggr := logger.With(p.lggr, "where", "scheduler") p.scheduleReadJobs(ctx, func(ids []*big.Int) { select { @@ -369,7 +368,7 @@ func (p *logEventProvider) startReader(pctx context.Context, readQ <-chan []*big ctx, cancel := context.WithCancel(pctx) defer cancel() - lggr := p.lggr.With("where", "reader") + lggr := logger.With(p.lggr, "where", "reader") for { select { diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/provider_life_cycle.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/provider_life_cycle.go index db47ac2ecd..cbd493bf2e 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/provider_life_cycle.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/provider_life_cycle.go @@ -10,6 +10,8 @@ import ( "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" ) @@ -106,7 +108,7 @@ func (p *logEventProvider) register(ctx context.Context, lpFilter logpoller.Filt if err != nil { return fmt.Errorf("failed to get latest block while registering filter: %w", err) } - lggr := p.lggr.With("upkeepID", ufilter.upkeepID.String()) + lggr := logger.With(p.lggr, "upkeepID", ufilter.upkeepID.String()) logPollerHasFilter := p.poller.HasFilter(lpFilter.Name) filterStoreHasFilter := p.filterStore.Has(ufilter.upkeepID) if filterStoreHasFilter { diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/recoverer.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/recoverer.go index 9e41008ed8..984856bf3c 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/recoverer.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/recoverer.go @@ -14,18 +14,17 @@ import ( "sync/atomic" "time" - "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/chainlink-automation/pkg/v3/random" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink-automation/pkg/v3/random" + "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/prommetrics" "github.com/smartcontractkit/chainlink/v2/core/utils" @@ -71,7 +70,7 @@ type logRecoverer struct { services.StateMachine threadCtrl utils.ThreadControl - lggr logger.Logger + lggr logger.SugaredLogger lookbackBlocks *atomic.Int64 blockTime *atomic.Int64 @@ -96,7 +95,7 @@ var _ LogRecoverer = &logRecoverer{} func NewLogRecoverer(lggr logger.Logger, poller logpoller.LogPoller, client client.Client, stateStore core.UpkeepStateReader, packer LogDataPacker, filterStore UpkeepFilterStore, opts LogTriggersOptions) *logRecoverer { rec := &logRecoverer{ - lggr: lggr.Named(LogRecovererServiceName), + lggr: logger.Sugared(lggr).Named(LogRecovererServiceName), threadCtrl: utils.NewThreadControl(), diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/streams/streams.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/streams/streams.go index 5a4b701f61..1700593921 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/streams/streams.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/streams/streams.go @@ -15,11 +15,11 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/patrickmn/go-cache" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" autov2common "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_automation_v21_plus_common" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/encoding" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury" @@ -119,7 +119,7 @@ func (s *streams) Lookup(ctx context.Context, checkResults []ocr2keepers.CheckRe // buildResult checks if the upkeep is allowed by Mercury and builds a streams lookup request from the check result func (s *streams) buildResult(ctx context.Context, i int, checkResult ocr2keepers.CheckResult, checkResults []ocr2keepers.CheckResult, lookups map[int]*mercury.StreamsLookup) { - lookupLggr := s.lggr.With("where", "StreamsLookup") + lookupLggr := logger.Sugared(s.lggr).With("where", "StreamsLookup") if checkResult.IneligibilityReason != uint8(encoding.UpkeepFailureReasonTargetCheckReverted) { // Streams Lookup only works when upkeep target check reverts prommetrics.AutomationStreamsLookupError.WithLabelValues(prommetrics.StreamsLookupErrorReasonNotReverted).Inc() diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/v02/request.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/v02/request.go index 5e954475a8..c02b7c10de 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/v02/request.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/v02/request.go @@ -11,14 +11,14 @@ import ( "strconv" "time" - automationTypes "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + "github.com/avast/retry-go/v4" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" - "github.com/avast/retry-go/v4" - "github.com/ethereum/go-ethereum/common/hexutil" + automationTypes "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/encoding" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/prommetrics" diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/v03/request.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/v03/request.go index 39a26b6b5d..c2ffb2172b 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/v03/request.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/v03/request.go @@ -10,14 +10,14 @@ import ( "strings" "time" - automationTypes "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + "github.com/avast/retry-go/v4" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" - "github.com/avast/retry-go/v4" - "github.com/ethereum/go-ethereum/common/hexutil" + automationTypes "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/encoding" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/prommetrics" diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/payload_builder.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/payload_builder.go index 7f29cb3b7a..c6262899a3 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/payload_builder.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/payload_builder.go @@ -3,11 +3,11 @@ package evm import ( "context" - "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - + "github.com/smartcontractkit/chainlink-common/pkg/logger" ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider" ) diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/registry.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/registry.go index 16b8627cf7..25bd7a445e 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/registry.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/registry.go @@ -10,10 +10,6 @@ import ( "sync" "time" - types2 "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - - "github.com/smartcontractkit/chainlink-common/pkg/types" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" @@ -22,19 +18,20 @@ import ( "github.com/pkg/errors" "go.uber.org/multierr" - evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" - + "github.com/smartcontractkit/chainlink-common/pkg/types" ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" + types2 "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated" ac "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_automation_v21_plus_common" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/encoding" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider" @@ -98,7 +95,7 @@ func NewEvmRegistry( return &EvmRegistry{ stopCh: make(chan struct{}), threadCtrl: utils.NewThreadControl(), - lggr: lggr.Named(RegistryServiceName), + lggr: logger.Sugared(lggr).Named(RegistryServiceName), poller: client.LogPoller(), addr: addr, client: client.Client(), @@ -175,7 +172,7 @@ func (c *MercuryConfig) SetPluginRetry(k string, v interface{}, d time.Duration) type EvmRegistry struct { services.StateMachine threadCtrl utils.ThreadControl - lggr logger.Logger + lggr logger.SugaredLogger poller logpoller.LogPoller addr common.Address client client.Client diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/registry_check_pipeline_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/registry_check_pipeline_test.go index 6f8785fda7..cb014e1d3e 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/registry_check_pipeline_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/registry_check_pipeline_test.go @@ -8,10 +8,6 @@ import ( "sync/atomic" "testing" - types3 "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - - types2 "github.com/smartcontractkit/chainlink-common/pkg/types" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rpc" @@ -20,8 +16,12 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + types2 "github.com/smartcontractkit/chainlink-common/pkg/types" ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" + types3 "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + evmClientMocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" gasMocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" @@ -30,7 +30,6 @@ import ( ac "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_automation_v21_plus_common" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/streams_lookup_compatible_interface" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/encoding" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mocks" @@ -82,7 +81,7 @@ func TestRegistry_GetBlockAndUpkeepId(t *testing.T) { } func TestRegistry_VerifyCheckBlock(t *testing.T) { - lggr := logger.TestLogger(t) + lggr := logger.Test(t) upkeepId := ocr2keepers.UpkeepIdentifier{} upkeepId.FromBigInt(big.NewInt(12345)) tests := []struct { @@ -197,7 +196,7 @@ func TestRegistry_VerifyCheckBlock(t *testing.T) { } bs.latestBlock.Store(tc.latestBlock) e := &EvmRegistry{ - lggr: lggr, + lggr: logger.Sugared(lggr), bs: bs, poller: tc.poller, } @@ -229,7 +228,7 @@ func (p *mockLogPoller) IndexedLogs(ctx context.Context, eventSig common.Hash, a } func TestRegistry_VerifyLogExists(t *testing.T) { - lggr := logger.TestLogger(t) + lggr := logger.Test(t) upkeepId := ocr2keepers.UpkeepIdentifier{} upkeepId.FromBigInt(big.NewInt(12345)) @@ -351,7 +350,7 @@ func TestRegistry_VerifyLogExists(t *testing.T) { blocks: tc.blocks, } e := &EvmRegistry{ - lggr: lggr, + lggr: logger.Sugared(lggr), bs: bs, } @@ -379,7 +378,7 @@ func TestRegistry_VerifyLogExists(t *testing.T) { } func TestRegistry_CheckUpkeeps(t *testing.T) { - lggr := logger.TestLogger(t) + lggr := logger.Test(t) uid0 := core.GenUpkeepID(types3.UpkeepType(0), "p0") uid1 := core.GenUpkeepID(types3.UpkeepType(1), "p1") uid2 := core.GenUpkeepID(types3.UpkeepType(1), "p2") @@ -509,7 +508,7 @@ func TestRegistry_CheckUpkeeps(t *testing.T) { } bs.latestBlock.Store(tc.latestBlock) e := &EvmRegistry{ - lggr: lggr, + lggr: logger.Sugared(lggr), bs: bs, poller: tc.poller, } @@ -669,7 +668,7 @@ func TestRegistry_SimulatePerformUpkeeps(t *testing.T) { // setups up an evm registry for tests. func setupEVMRegistry(t *testing.T) *EvmRegistry { - lggr := logger.TestLogger(t) + lggr := logger.Test(t) addr := common.HexToAddress("0x6cA639822c6C241Fa9A7A6b5032F6F7F1C513CAD") keeperRegistryABI, err := abi.JSON(strings.NewReader(ac.IAutomationV21PlusCommonABI)) require.Nil(t, err, "need registry abi") @@ -682,7 +681,7 @@ func setupEVMRegistry(t *testing.T) *EvmRegistry { ge := gasMocks.NewEvmFeeEstimator(t) r := &EvmRegistry{ - lggr: lggr, + lggr: logger.Sugared(lggr), poller: logPoller, addr: addr, client: client, diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/registry_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/registry_test.go index ab530f877a..1a3f103dd1 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/registry_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/registry_test.go @@ -15,19 +15,19 @@ import ( "github.com/stretchr/testify/mock" types2 "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" - evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" types3 "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller/mocks" + 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/gethwrappers/generated" ac "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/automation_compatible_utils" autov2common "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_automation_v21_plus_common" - "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/encoding" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider" @@ -546,7 +546,7 @@ func TestRegistry_refreshLogTriggerUpkeeps(t *testing.T) { } { t.Run(tc.name, func(t *testing.T) { ctx := tests.Context(t) - lggr := logger.TestLogger(t) + lggr := logger.Test(t) var hb types3.HeadBroadcaster var lp logpoller.LogPoller @@ -560,7 +560,7 @@ func TestRegistry_refreshLogTriggerUpkeeps(t *testing.T) { bs: bs, registry: tc.registry, packer: tc.packer, - lggr: lggr, + lggr: logger.Sugared(lggr), } err := registry.refreshLogTriggerUpkeeps(ctx, tc.ids) diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/transmit/event_provider.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/transmit/event_provider.go index f1a6468804..697f56c866 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/transmit/event_provider.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/transmit/event_provider.go @@ -6,18 +6,17 @@ import ( "fmt" "sync" - "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" - "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" ac "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_automation_v21_plus_common" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" ) diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeepstate/scanner.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeepstate/scanner.go index d11970864a..27a35ddff1 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeepstate/scanner.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeepstate/scanner.go @@ -8,10 +8,11 @@ import ( "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" ac "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_automation_v21_plus_common" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider" ) @@ -43,7 +44,7 @@ func NewPerformedEventsScanner( finalityDepth uint32, ) *performedEventsScanner { return &performedEventsScanner{ - lggr: lggr.Named("EventsScanner"), + lggr: logger.Named(lggr, "EventsScanner"), poller: poller, registryAddress: registryAddress, finalityDepth: finalityDepth, diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeepstate/store.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeepstate/store.go index e6486ca56a..27cac24a9f 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeepstate/store.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/upkeepstate/store.go @@ -8,13 +8,12 @@ import ( "sync" "time" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" - "github.com/smartcontractkit/chainlink-common/pkg/services" - ubig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/core" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -84,7 +83,7 @@ type upkeepStateStore struct { func NewUpkeepStateStore(orm ORM, lggr logger.Logger, scanner PerformedLogsScanner) *upkeepStateStore { return &upkeepStateStore{ orm: orm, - lggr: lggr.Named(UpkeepStateStoreServiceName), + lggr: logger.Named(lggr, UpkeepStateStoreServiceName), cache: map[string]*upkeepStateRecord{}, scanner: scanner, retention: CacheExpiration, diff --git a/core/services/ocr2/validate/validate.go b/core/services/ocr2/validate/validate.go index 9b25705c05..7b951cbce8 100644 --- a/core/services/ocr2/validate/validate.go +++ b/core/services/ocr2/validate/validate.go @@ -27,6 +27,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" "github.com/smartcontractkit/chainlink/v2/core/services/relay" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" "github.com/smartcontractkit/chainlink/v2/plugins" ) @@ -85,7 +86,6 @@ var ( "relay": {}, "relayConfig": {}, "pluginType": {}, - "pluginConfig": {}, } notExpectedParams = map[string]struct{}{ "isBootstrapPeer": {}, @@ -118,7 +118,7 @@ func validateSpec(ctx context.Context, tree *toml.Tree, spec job.Job, rc plugins // TODO validator for DR-OCR spec: https://smartcontract-it.atlassian.net/browse/FUN-112 return nil case types.Mercury: - return validateOCR2MercurySpec(spec.OCR2OracleSpec.PluginConfig, *spec.OCR2OracleSpec.FeedID) + return validateOCR2MercurySpec(spec.OCR2OracleSpec, *spec.OCR2OracleSpec.FeedID) case types.CCIPExecution: return validateOCR2CCIPExecutionSpec(spec.OCR2OracleSpec.PluginConfig) case types.CCIPCommit: @@ -200,18 +200,6 @@ func (o *OCR2OnchainSigningStrategy) IsMultiChain() bool { return o.StrategyName == "multi-chain" } -func (o *OCR2OnchainSigningStrategy) PublicKey() (string, error) { - pk, ok := o.Config["publicKey"] - if !ok { - return "", nil - } - pkString, ok := pk.(string) - if !ok { - return "", fmt.Errorf("expected string publicKey value, but got: %T", pk) - } - return pkString, nil -} - func (o *OCR2OnchainSigningStrategy) ConfigCopy() job.JSONConfig { copiedConfig := make(job.JSONConfig) for k, v := range o.Config { @@ -254,13 +242,6 @@ func validateGenericPluginSpec(ctx context.Context, spec *job.OCR2OracleSpec, rc if err != nil { return err } - pk, ossErr := onchainSigningStrategy.PublicKey() - if ossErr != nil { - return ossErr - } - if pk == "" { - return errors.New("generic config invalid: must provide public key for the onchain signing strategy") - } } plugEnv := env.NewPlugin(p.PluginName) @@ -340,13 +321,26 @@ func validateOCR2KeeperSpec(jsonConfig job.JSONConfig) error { return nil } -func validateOCR2MercurySpec(jsonConfig job.JSONConfig, feedId [32]byte) error { +func validateOCR2MercurySpec(spec *job.OCR2OracleSpec, feedID [32]byte) error { + var relayConfig evmtypes.RelayConfig + err := json.Unmarshal(spec.RelayConfig.Bytes(), &relayConfig) + if err != nil { + return pkgerrors.Wrap(err, "error while unmarshalling plugin config") + } + + if len(spec.PluginConfig) == 0 { + if !relayConfig.EnableTriggerCapability { + return pkgerrors.Wrap(err, "at least one transmission option must be configured") + } + return nil + } + var pluginConfig mercuryconfig.PluginConfig - err := json.Unmarshal(jsonConfig.Bytes(), &pluginConfig) + err = json.Unmarshal(spec.PluginConfig.Bytes(), &pluginConfig) if err != nil { return pkgerrors.Wrap(err, "error while unmarshalling plugin config") } - return pkgerrors.Wrap(mercuryconfig.ValidatePluginConfig(pluginConfig, feedId), "Mercury PluginConfig is invalid") + return pkgerrors.Wrap(mercuryconfig.ValidatePluginConfig(pluginConfig, feedID), "Mercury PluginConfig is invalid") } func validateOCR2CCIPExecutionSpec(jsonConfig job.JSONConfig) error { diff --git a/core/services/ocr2/validate/validate_test.go b/core/services/ocr2/validate/validate_test.go index b352445bf4..7f511c2706 100644 --- a/core/services/ocr2/validate/validate_test.go +++ b/core/services/ocr2/validate/validate_test.go @@ -2,11 +2,9 @@ package validate_test import ( "encoding/json" - "math/big" "testing" "time" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/manyminds/api2go/jsonapi" "github.com/pelletier/go-toml" @@ -14,12 +12,10 @@ import ( "github.com/stretchr/testify/require" commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" - cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/job" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/config" medianconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/median/config" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/validate" ) @@ -52,7 +48,6 @@ chainID = 1337 strategyName = "single-chain" [onchainSigningStrategy.config] evm = "" -publicKey = "0x1234567890123456789012345678901234567890" [pluginConfig] juelsPerFeeCoinSource = """ ds1 [type=bridge name=voter_turnout]; @@ -108,7 +103,6 @@ chainID = 1337 strategyName = "single-chain" [onchainSigningStrategy.config] evm = "" -publicKey = "0x1234567890123456789012345678901234567890" [pluginConfig] juelsPerFeeCoinSource = """ ds1 [type=bridge name=voter_turnout]; @@ -153,7 +147,6 @@ chainID = 1337 strategyName = "single-chain" [onchainSigningStrategy.config] evm = "" -publicKey = "0x1234567890123456789012345678901234567890" [pluginConfig] `, assertion: func(t *testing.T, os job.Job, err error) { @@ -177,7 +170,6 @@ chainID = 1337 strategyName = "single-chain" [onchainSigningStrategy.config] evm = "" -publicKey = "0x1234567890123456789012345678901234567890" [pluginConfig] `, assertion: func(t *testing.T, os job.Job, err error) { @@ -203,7 +195,6 @@ chainID = 1337 strategyName = "single-chain" [onchainSigningStrategy.config] evm = "" -publicKey = "0x1234567890123456789012345678901234567890" [pluginConfig] `, assertion: func(t *testing.T, os job.Job, err error) { @@ -229,7 +220,6 @@ chainID = 1337 strategyName = "single-chain" [onchainSigningStrategy.config] evm = "" -publicKey = "0x1234567890123456789012345678901234567890" [pluginConfig] `, assertion: func(t *testing.T, os job.Job, err error) { @@ -256,7 +246,6 @@ chainID = 1337 strategyName = "single-chain" [onchainSigningStrategy.config] evm = "" -publicKey = "0x1234567890123456789012345678901234567890" [pluginConfig] `, assertion: func(t *testing.T, os job.Job, err error) { @@ -282,7 +271,6 @@ chainID = 1337 strategyName = "single-chain" [onchainSigningStrategy.config] evm = "" -publicKey = "0x1234567890123456789012345678901234567890" [pluginConfig] `, assertion: func(t *testing.T, os job.Job, err error) { @@ -306,7 +294,6 @@ chainID = 1337 strategyName = "single-chain" [onchainSigningStrategy.config] evm = "" -publicKey = "0x1234567890123456789012345678901234567890" [pluginConfig] `, assertion: func(t *testing.T, os job.Job, err error) { @@ -347,7 +334,6 @@ answer1 [type=median index=0]; strategyName = "single-chain" [onchainSigningStrategy.config] evm = "" -publicKey = "0x1234567890123456789012345678901234567890" [pluginConfig] juelsPerFeeCoinSource = """ ds1 [type=bridge name=voter_turnout]; @@ -386,7 +372,6 @@ answer1 [type=median index=0]; strategyName = "single-chain" [onchainSigningStrategy.config] evm = "" -publicKey = "0x1234567890123456789012345678901234567890" [pluginConfig] juelsPerFeeCoinSource = """ -> @@ -418,7 +403,6 @@ answer1 [type=median index=0]; strategyName = "single-chain" [onchainSigningStrategy.config] evm = "" -publicKey = "0x1234567890123456789012345678901234567890" [pluginConfig] juelsPerFeeCoinSource = """ ds1 [type=bridge name=voter_turnout]; @@ -432,328 +416,6 @@ chainID = 1337 require.Contains(t, err.Error(), "no such relay blerg supported") }, }, - { - name: "Generic public onchain signing strategy with no public key", - toml: ` -type = "offchainreporting2" -pluginType = "plugin" -schemaVersion = 1 -relay = "evm" -contractID = "0x613a38AC1659769640aaE063C651F48E0250454C" -p2pPeerID = "12D3KooWHfYFQ8hGttAYbMCevQVESEQhzJAqFZokMVtom8bNxwGq" -p2pv2Bootstrappers = [ -"12D3KooWHfYFQ8hGttAYbMCevQVESEQhzJAqFZokMVtom8bNxwGq@127.0.0.1:5001", -] -ocrKeyBundleID = "73e8966a78ca09bb912e9565cfb79fbe8a6048fab1f0cf49b18047c3895e0447" -monitoringEndpoint = "chain.link:4321" -transmitterID = "0xF67D0290337bca0847005C7ffD1BC75BA9AAE6e4" -observationTimeout = "10s" -observationSource = """ -ds1 [type=bridge name=voter_turnout]; -ds1_parse [type=jsonparse path="one,two"]; -ds1_multiply [type=multiply times=1.23]; -ds1 -> ds1_parse -> ds1_multiply -> answer1; -answer1 [type=median index=0]; -""" -[relayConfig] -chainID = 1337 -[onchainSigningStrategy] -strategyName = "single-chain" -[onchainSigningStrategy.config] -evm = "" -publicKey = "" -[pluginConfig] -pluginName = "median" -telemetryType = "median" -OCRVersion=2 -`, - assertion: func(t *testing.T, os job.Job, err error) { - require.Error(t, err) - require.Contains(t, err.Error(), "must provide public key for the onchain signing strategy") - }, - }, - { - name: "Valid ccip-execute", - toml: ` -type = "offchainreporting2" -schemaVersion = 1 -relay = "evm" -contractID = "0x1234567" -pluginType = "ccip-execution" - -[relayConfig] -chainID = 1337 - -[pluginConfig] -SourceStartBlock = 1 -DestStartBlock = 2 -USDCConfig.SourceTokenAddress = "0x1234567890123456789012345678901234567890" -USDCConfig.SourceMessageTransmitterAddress = "0x0987654321098765432109876543210987654321" -USDCConfig.AttestationAPI = "some api" -USDCConfig.AttestationAPITimeoutSeconds = 12 -USDCConfig.AttestationAPIIntervalMilliseconds = 100 -`, - assertion: func(t *testing.T, os job.Job, err error) { - require.NoError(t, err) - expected := config.ExecPluginJobSpecConfig{ - SourceStartBlock: 1, - DestStartBlock: 2, - USDCConfig: config.USDCConfig{ - SourceTokenAddress: common.HexToAddress("0x1234567890123456789012345678901234567890"), - SourceMessageTransmitterAddress: common.HexToAddress("0x0987654321098765432109876543210987654321"), - AttestationAPI: "some api", - AttestationAPITimeoutSeconds: 12, - AttestationAPIIntervalMilliseconds: 100, - }, - } - var cfg config.ExecPluginJobSpecConfig - err = json.Unmarshal(os.OCR2OracleSpec.PluginConfig.Bytes(), &cfg) - require.NoError(t, err) - require.Equal(t, expected, cfg) - }, - }, - { - name: "ccip-execute non hex address unmarshalling", - toml: ` -type = "offchainreporting2" -schemaVersion = 1 -relay = "evm" -contractID = "0x1234567" -pluginType = "ccip-execution" - -[relayConfig] -chainID = 1337 - -[pluginConfig] -SourceStartBlock = 1 -DestStartBlock = 2 -USDCConfig.SourceTokenAddress = "non-hex" -USDCConfig.SourceMessageTransmitterAddress = "0x0987654321098765432109876543210987654321" -USDCConfig.AttestationAPI = "some api" -USDCConfig.AttestationAPITimeoutSeconds = 12 -`, - assertion: func(t *testing.T, os job.Job, err error) { - require.Error(t, err) - require.Contains(t, err.Error(), "cannot unmarshal hex string without 0x prefix into Go struct field USDCConfig.USDCConfig.SourceTokenAddress of type common.Address") - }, - }, - { - name: "ccip-execute usdcconfig validation failure", - toml: ` -type = "offchainreporting2" -schemaVersion = 1 -relay = "evm" -contractID = "0x1234567" -pluginType = "ccip-execution" - -[relayConfig] -chainID = 1337 - -[pluginConfig] -SourceStartBlock = 1 -DestStartBlock = 2 -USDCConfig.SourceTokenAddress = "0x1234567890123456789012345678901234567890" -USDCConfig.SourceMessageTransmitterAddress = "0x0987654321098765432109876543210987654321" -USDCConfig.AttestationAPI = "some api" -USDCConfig.AttestationAPIIntervalMilliseconds = 100 -USDCConfig.AttestationAPITimeoutSeconds = -12 -`, - assertion: func(t *testing.T, os job.Job, err error) { - require.Error(t, err) - require.Contains(t, err.Error(), "error while unmarshalling plugin config: json: cannot unmarshal number -12 into Go struct field USDCConfig.USDCConfig.AttestationAPITimeoutSeconds of type uint") - }, - }, - { - name: "Valid ccip-commit pipeline", - toml: ` -type = "offchainreporting2" -schemaVersion = 1 -relay = "evm" -contractID = "0x1234567" -pluginType = "ccip-commit" - -[relayConfig] -chainID = 1337 - -[pluginConfig] -SourceStartBlock = 1 -DestStartBlock = 2 -offRamp = "0x1234567890123456789012345678901234567890" -tokenPricesUSDPipeline = "merge [type=merge left=\"{}\" right=\"{\\\"0xC79b96044906550A5652BCf20a6EA02f139B9Ae5\\\":\\\"1000000000000000000\\\"}\"];" -`, - assertion: func(t *testing.T, os job.Job, err error) { - require.NoError(t, err) - expected := config.CommitPluginJobSpecConfig{ - SourceStartBlock: 1, - DestStartBlock: 2, - OffRamp: cciptypes.Address(common.HexToAddress("0x1234567890123456789012345678901234567890").String()), - TokenPricesUSDPipeline: `merge [type=merge left="{}" right="{\"0xC79b96044906550A5652BCf20a6EA02f139B9Ae5\":\"1000000000000000000\"}"];`, - PriceGetterConfig: nil, - } - var cfg config.CommitPluginJobSpecConfig - err = json.Unmarshal(os.OCR2OracleSpec.PluginConfig.Bytes(), &cfg) - require.NoError(t, err) - require.Equal(t, expected, cfg) - }, - }, - { - name: "Valid ccip-commit dynamic price getter", - toml: ` -type = "offchainreporting2" -schemaVersion = 1 -relay = "evm" -contractID = "0x1234567" -pluginType = "ccip-commit" - -[relayConfig] -chainID = 1337 - -[pluginConfig] -SourceStartBlock = 1 -DestStartBlock = 2 -offRamp = "0x1234567890123456789012345678901234567890" -priceGetterConfig = """ -{ - "aggregatorPrices": { - "0x0820c05e1fba1244763a494a52272170c321cad3": { - "chainID": "1000", - "contractAddress": "0xb8dabd288955d302d05ca6b011bb46dfa3ea7acf" - }, - "0x4a98bb4d65347016a7ab6f85bea24b129c9a1272": { - "chainID": "1337", - "contractAddress": "0xb80244cc8b0bb18db071c150b36e9bcb8310b236" - } - }, - "staticPrices": { - "0xec8c353470ccaa4f43067fcde40558e084a12927": { - "chainID": "1057", - "price": 1000000000000000000 - } - } -} -""" -`, - assertion: func(t *testing.T, os job.Job, err error) { - require.NoError(t, err) - expected := config.CommitPluginJobSpecConfig{ - SourceStartBlock: 1, - DestStartBlock: 2, - OffRamp: cciptypes.Address(common.HexToAddress("0x1234567890123456789012345678901234567890").String()), - TokenPricesUSDPipeline: "", - PriceGetterConfig: &config.DynamicPriceGetterConfig{ - AggregatorPrices: map[common.Address]config.AggregatorPriceConfig{ - common.HexToAddress("0x0820c05e1fba1244763a494a52272170c321cad3"): { - ChainID: 1000, - AggregatorContractAddress: common.HexToAddress("0xb8dabd288955d302d05ca6b011bb46dfa3ea7acf"), - }, - common.HexToAddress("0x4a98bb4d65347016a7ab6f85bea24b129c9a1272"): { - ChainID: 1337, - AggregatorContractAddress: common.HexToAddress("0xb80244cc8b0bb18db071c150b36e9bcb8310b236"), - }, - }, - StaticPrices: map[common.Address]config.StaticPriceConfig{ - common.HexToAddress("0xec8c353470ccaa4f43067fcde40558e084a12927"): { - ChainID: 1057, - Price: big.NewInt(1000000000000000000), - }, - }, - }, - } - var cfg config.CommitPluginJobSpecConfig - err = json.Unmarshal(os.OCR2OracleSpec.PluginConfig.Bytes(), &cfg) - require.NoError(t, err) - require.Equal(t, expected, cfg) - }, - }, - { - name: "ccip-commit dual price getter configuration", - toml: ` -type = "offchainreporting2" -schemaVersion = 1 -relay = "evm" -contractID = "0x1234567" -pluginType = "ccip-commit" - -[relayConfig] -chainID = 1337 - -[pluginConfig] -SourceStartBlock = 1 -DestStartBlock = 2 -offRamp = "0x1234567890123456789012345678901234567890" -tokenPricesUSDPipeline = "merge [type=merge left=\"{}\" right=\"{\\\"0xC79b96044906550A5652BCf20a6EA02f139B9Ae5\\\":\\\"1000000000000000000\\\"}\"];" -priceGetterConfig = """ -{ - "aggregatorPrices": { - "0x0820c05e1fba1244763a494a52272170c321cad3": { - "chainID": "1000", - "contractAddress": "0xb8dabd288955d302d05ca6b011bb46dfa3ea7acf" - }, - "0x4a98bb4d65347016a7ab6f85bea24b129c9a1272": { - "chainID": "1337", - "contractAddress": "0xb80244cc8b0bb18db071c150b36e9bcb8310b236" - } - }, - "staticPrices": { - "0xec8c353470ccaa4f43067fcde40558e084a12927": { - "chainID": "1057", - "price": 1000000000000000000 - } - } -} -""" -`, - assertion: func(t *testing.T, os job.Job, err error) { - require.Error(t, err) - require.ErrorContains(t, err, "only one of tokenPricesUSDPipeline or priceGetterConfig must be set") - }, - }, - { - name: "ccip-commit invalid pipeline", - toml: ` -type = "offchainreporting2" -schemaVersion = 1 -relay = "evm" -contractID = "0x1234567" -pluginType = "ccip-commit" - -[relayConfig] -chainID = 1337 - -[pluginConfig] -SourceStartBlock = 1 -DestStartBlock = 2 -offRamp = "0x1234567890123456789012345678901234567890" -tokenPricesUSDPipeline = "this is not a pipeline" -`, - assertion: func(t *testing.T, os job.Job, err error) { - require.Error(t, err) - require.ErrorContains(t, err, "invalid token prices pipeline") - }, - }, - { - name: "ccip-commit invalid dynamic token prices config", - toml: ` -type = "offchainreporting2" -schemaVersion = 1 -relay = "evm" -contractID = "0x1234567" -pluginType = "ccip-commit" - -[relayConfig] -chainID = 1337 - -[pluginConfig] -SourceStartBlock = 1 -DestStartBlock = 2 -offRamp = "0x1234567890123456789012345678901234567890" -priceGetterConfig = "this is not a proper dynamic price config" -`, - assertion: func(t *testing.T, os job.Job, err error) { - require.Error(t, err) - require.ErrorContains(t, err, "error while unmarshalling plugin config") - }, - }, { name: "Generic plugin config validation - nothing provided", toml: ` @@ -778,7 +440,6 @@ chainID = 4 strategyName = "single-chain" [onchainSigningStrategy.config] evm = "" -publicKey = "0x1234567890123456789012345678901234567890" [pluginConfig] `, @@ -810,7 +471,6 @@ chainID = 4 strategyName = "single-chain" [onchainSigningStrategy.config] evm = "" -publicKey = "0x1234567890123456789012345678901234567890" [pluginConfig] PluginName="some random name" @@ -844,7 +504,6 @@ chainID = 4 strategyName = "single-chain" [onchainSigningStrategy.config] evm = "" -publicKey = "0x1234567890123456789012345678901234567890" [pluginConfig] PluginName="some random name" @@ -879,7 +538,6 @@ chainID = 4 strategyName = "single-chain" [onchainSigningStrategy.config] evm = "" -publicKey = "0x1234567890123456789012345678901234567890" [pluginConfig] PluginName="some random name" @@ -997,7 +655,6 @@ func TestOCR2OnchainSigningStrategy_Unmarshal(t *testing.T) { strategyName = "single-chain" [onchainSigningStrategy.config] evm = "08d14c6eed757414d72055d28de6caf06535806c6a14e450f3a2f1c854420e17" -publicKey = "0x1234567890123456789012345678901234567890" ` oss := &envelope2{} tree, err := toml.Load(payload) @@ -1010,12 +667,9 @@ publicKey = "0x1234567890123456789012345678901234567890" err = json.Unmarshal(b, oss) require.NoError(t, err) - pk, err := oss.OnchainSigningStrategy.PublicKey() - require.NoError(t, err) kbID, err := oss.OnchainSigningStrategy.KeyBundleID("evm") require.NoError(t, err) assert.False(t, oss.OnchainSigningStrategy.IsMultiChain()) - assert.Equal(t, "0x1234567890123456789012345678901234567890", pk) assert.Equal(t, "08d14c6eed757414d72055d28de6caf06535806c6a14e450f3a2f1c854420e17", kbID) } diff --git a/core/services/ocrbootstrap/delegate.go b/core/services/ocrbootstrap/delegate.go index fdcb68ceec..10ac0cb45d 100644 --- a/core/services/ocrbootstrap/delegate.go +++ b/core/services/ocrbootstrap/delegate.go @@ -39,10 +39,16 @@ type Delegate struct { type relayConfig struct { // providerType used for determining which type of contract to track config on ProviderType string `json:"providerType"` + // HACK // Extra fields to enable router proxy contract support. Must match field names of functions' PluginConfig. DONID string `json:"donID"` ContractVersion uint32 `json:"contractVersion"` ContractUpdateCheckFrequencySec uint32 `json:"contractUpdateCheckFrequencySec"` + + // Annoyingly, the pre-existing donID field is already reserved and has a + // special-case usage just for functions. It's also a string and not uint32 + // as Baku requires. + LLODONID uint32 `json:"lloDonID"` } // NewDelegateBootstrap creates a new Delegate @@ -111,7 +117,7 @@ func (d *Delegate) ServicesForSpec(ctx context.Context, jb job.Job) (services [] var relayCfg relayConfig if err = json.Unmarshal(spec.RelayConfig.Bytes(), &relayCfg); err != nil { - return nil, err + return nil, fmt.Errorf("failed to unmarshal relay config for bootstrap job: %w", err) } var configProvider types.ConfigProvider diff --git a/core/services/ocrcommon/adapters.go b/core/services/ocrcommon/adapters.go index 372d9e37f1..b75a8a003c 100644 --- a/core/services/ocrcommon/adapters.go +++ b/core/services/ocrcommon/adapters.go @@ -1,8 +1,14 @@ package ocrcommon import ( + "bytes" + "cmp" "context" + "encoding/binary" "fmt" + "io" + "math" + "slices" "github.com/pkg/errors" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" @@ -11,6 +17,7 @@ import ( "google.golang.org/protobuf/types/known/structpb" "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ocr2key" ) @@ -81,6 +88,82 @@ func (c *OCR3ContractTransmitterAdapter) FromAccount() (ocrtypes.Account, error) var _ ocr3types.OnchainKeyring[[]byte] = (*OCR3OnchainKeyringMultiChainAdapter)(nil) +func MarshalMultichainKeyBundle(ost map[string]ocr2key.KeyBundle) (ocrtypes.OnchainPublicKey, error) { + pubKeys := map[string]ocrtypes.OnchainPublicKey{} + for k, b := range ost { + pubKeys[k] = []byte(b.PublicKey()) + } + return MarshalMultichainPublicKey(pubKeys) +} + +func MarshalMultichainPublicKey(ost map[string]ocrtypes.OnchainPublicKey) (ocrtypes.OnchainPublicKey, error) { + var pubKeys [][]byte + for k, pubKey := range ost { + typ, err := chaintype.ChainType(k).Type() + if err != nil { + // skipping unknown key type + continue + } + buf := new(bytes.Buffer) + if err = binary.Write(buf, binary.LittleEndian, typ); err != nil { + return nil, err + } + length := len(pubKey) + if length < 0 || length > math.MaxUint16 { + return nil, fmt.Errorf("pubKey doesn't fit into uint16") + } + if err = binary.Write(buf, binary.LittleEndian, uint16(length)); err != nil { //nolint:gosec + return nil, err + } + _, _ = buf.Write(pubKey) + pubKeys = append(pubKeys, buf.Bytes()) + } + // sort keys based on encoded type to make encoding deterministic + slices.SortFunc(pubKeys, func(a, b []byte) int { return cmp.Compare(a[0], b[0]) }) + return bytes.Join(pubKeys, nil), nil +} + +func UnmarshalMultichainPublicKey(d []byte) (map[string]ocrtypes.OnchainPublicKey, error) { + m := map[string]ocrtypes.OnchainPublicKey{} + buf := bytes.NewReader(d) + + for { + // type + typ, err := buf.ReadByte() + if err != nil { + return nil, err + } + // length + var length uint16 + err = binary.Read(buf, binary.LittleEndian, &length) + if err != nil { + return nil, err + } + // value + pubKey := make([]byte, length) + n, err := buf.Read(pubKey) + if err != nil { + return nil, err + } + if n != int(length) { + return nil, io.EOF + } + + k, err := chaintype.NewChainType(typ) + if err != nil { + // skipping unknown key type + continue + } + m[string(k)] = pubKey + + if buf.Len() == 0 { + break + } + } + + return m, nil +} + type OCR3OnchainKeyringMultiChainAdapter struct { keyBundles map[string]ocr2key.KeyBundle publicKey ocrtypes.OnchainPublicKey @@ -91,38 +174,41 @@ func NewOCR3OnchainKeyringMultiChainAdapter(ost map[string]ocr2key.KeyBundle, lg if len(ost) == 0 { return nil, errors.New("no key bundles provided") } - // We don't need to check for the existence of `publicKey` in the keyBundles map because it is required on validation on `validate/validate.go` - return &OCR3OnchainKeyringMultiChainAdapter{ost, ost["publicKey"].PublicKey(), lggr}, nil + publicKey, err := MarshalMultichainKeyBundle(ost) + if err != nil { + return nil, err + } + return &OCR3OnchainKeyringMultiChainAdapter{ost, publicKey, lggr}, nil } func (a *OCR3OnchainKeyringMultiChainAdapter) PublicKey() ocrtypes.OnchainPublicKey { return a.publicKey } -func (a *OCR3OnchainKeyringMultiChainAdapter) getKeyBundleFromInfo(info []byte) (ocr2key.KeyBundle, error) { +func (a *OCR3OnchainKeyringMultiChainAdapter) getKeyBundleFromInfo(info []byte) (string, ocr2key.KeyBundle, error) { unmarshalledInfo := new(structpb.Struct) err := proto.Unmarshal(info, unmarshalledInfo) if err != nil { - return nil, fmt.Errorf("failed to unmarshal report info: %v", err) + return "", nil, fmt.Errorf("failed to unmarshal report info: %v", err) } infoMap := unmarshalledInfo.AsMap() keyBundleName, ok := infoMap["keyBundleName"] if !ok { - return nil, errors.New("keyBundleName not found in report info") + return "", nil, errors.New("keyBundleName not found in report info") } name, ok := keyBundleName.(string) if !ok { - return nil, errors.New("keyBundleName is not a string") + return "", nil, errors.New("keyBundleName is not a string") } kb, ok := a.keyBundles[name] if !ok { - return nil, fmt.Errorf("keyBundle not found: %s", name) + return "", nil, fmt.Errorf("keyBundle not found: %s", name) } - return kb, nil + return name, kb, nil } func (a *OCR3OnchainKeyringMultiChainAdapter) Sign(digest ocrtypes.ConfigDigest, seqNr uint64, r ocr3types.ReportWithInfo[[]byte]) (signature []byte, err error) { - kb, err := a.getKeyBundleFromInfo(r.Info) + _, kb, err := a.getKeyBundleFromInfo(r.Info) if err != nil { return nil, fmt.Errorf("sign: failed to get key bundle from report info: %v", err) } @@ -137,12 +223,22 @@ func (a *OCR3OnchainKeyringMultiChainAdapter) Sign(digest ocrtypes.ConfigDigest, } func (a *OCR3OnchainKeyringMultiChainAdapter) Verify(opk ocrtypes.OnchainPublicKey, digest ocrtypes.ConfigDigest, seqNr uint64, ri ocr3types.ReportWithInfo[[]byte], signature []byte) bool { - kb, err := a.getKeyBundleFromInfo(ri.Info) + kbName, kb, err := a.getKeyBundleFromInfo(ri.Info) if err != nil { a.lggr.Warnf("verify: failed to get key bundle from report info: %v", err) return false } - return kb.Verify(opk, ocrtypes.ReportContext{ + keys, err := UnmarshalMultichainPublicKey(opk) + if err != nil { + a.lggr.Warnf("verify: failed to unmarshal public keys: %v", err) + return false + } + publicKey, ok := keys[kbName] + if !ok { + a.lggr.Warnf("verify: publicKey not found: %v", kbName) + return false + } + return kb.Verify(publicKey, ocrtypes.ReportContext{ ReportTimestamp: ocrtypes.ReportTimestamp{ ConfigDigest: digest, Epoch: uint32(seqNr), diff --git a/core/services/ocrcommon/adapters_test.go b/core/services/ocrcommon/adapters_test.go index fed854b0b3..fddfb297ec 100644 --- a/core/services/ocrcommon/adapters_test.go +++ b/core/services/ocrcommon/adapters_test.go @@ -2,25 +2,23 @@ package ocrcommon_test import ( "context" - "encoding/json" "fmt" + "math" "reflect" "testing" - "github.com/pelletier/go-toml" "github.com/smartcontractkit/libocr/offchainreporting2/types" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/keystest" "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ocr2key" - keystoreMocks "github.com/smartcontractkit/chainlink/v2/core/services/keystore/mocks" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/validate" "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" ) @@ -115,63 +113,116 @@ func TestOCR3OnchainKeyringAdapter(t *testing.T) { require.Equal(t, maxSignatureLength, kr.MaxSignatureLength()) } -type envelope struct { - OnchainSigningStrategy *validate.OCR2OnchainSigningStrategy -} - func TestNewOCR3OnchainKeyringMultiChainAdapter(t *testing.T) { - payload := ` -[onchainSigningStrategy] -strategyName = "single-chain" -[onchainSigningStrategy.config] -evm = "08d14c6eed757414d72055d28de6caf06535806c6a14e450f3a2f1c854420e17" -publicKey = "pub-key" -` - oss := &envelope{} - tree, err := toml.Load(payload) - require.NoError(t, err) - o := map[string]any{} - err = tree.Unmarshal(&o) + evmBundle, err := ocr2key.New(chaintype.EVM) require.NoError(t, err) - b, err := json.Marshal(o) - require.NoError(t, err) - err = json.Unmarshal(b, oss) + + aptosBundle, err := ocr2key.New(chaintype.Aptos) require.NoError(t, err) - reportInfo := ocr3types.ReportWithInfo[[]byte]{ - Report: []byte("multi-chain-report"), + + bundles := map[string]ocr2key.KeyBundle{ + "evm": evmBundle, + "aptos": aptosBundle, } - info, err := structpb.NewStruct(map[string]interface{}{ + adapter, err := ocrcommon.NewOCR3OnchainKeyringMultiChainAdapter(bundles, logger.TestLogger(t)) + require.NoError(t, err) + + maxLength := math.Max(float64(evmBundle.MaxSignatureLength()), float64(aptosBundle.MaxSignatureLength())) + assert.Equal(t, int(maxLength), adapter.MaxSignatureLength()) + + // evm signature + info, err := structpb.NewStruct(map[string]any{ "keyBundleName": "evm", }) require.NoError(t, err) - infoB, err := proto.Marshal(info) + + infob, err := proto.Marshal(info) require.NoError(t, err) - reportInfo.Info = infoB + r := ocr3types.ReportWithInfo[[]byte]{ + Report: []byte("report"), + Info: infob, + } - ks := keystoreMocks.NewOCR2(t) - fakeKey := ocr2key.MustNewInsecure(keystest.NewRandReaderFromSeed(1), "evm") - pk := fakeKey.PublicKey() - ks.On("Get", "pub-key").Return(fakeKey, nil) - ks.On("Get", "08d14c6eed757414d72055d28de6caf06535806c6a14e450f3a2f1c854420e17").Return(fakeKey, nil) - keyBundles := map[string]ocr2key.KeyBundle{} - for name := range oss.OnchainSigningStrategy.ConfigCopy() { - kbID, ostErr := oss.OnchainSigningStrategy.KeyBundleID(name) - require.NoError(t, ostErr) - os, ostErr := ks.Get(kbID) - require.NoError(t, ostErr) - keyBundles[name] = os + sig, err := adapter.Sign(configDigest, seqNr, r) + require.NoError(t, err) + assert.True(t, adapter.Verify(adapter.PublicKey(), configDigest, seqNr, r, sig)) + + // aptos signature + info, err = structpb.NewStruct(map[string]any{ + "keyBundleName": "aptos", + }) + require.NoError(t, err) + + infob, err = proto.Marshal(info) + require.NoError(t, err) + r = ocr3types.ReportWithInfo[[]byte]{ + Report: []byte("report"), + Info: infob, } - adapter, err := ocrcommon.NewOCR3OnchainKeyringMultiChainAdapter(keyBundles, logger.TestLogger(t)) + sig, err = adapter.Sign(configDigest, seqNr, r) require.NoError(t, err) + assert.True(t, adapter.Verify(adapter.PublicKey(), configDigest, seqNr, r, sig)) + + // no bundles _, err = ocrcommon.NewOCR3OnchainKeyringMultiChainAdapter(map[string]ocr2key.KeyBundle{}, logger.TestLogger(t)) require.Error(t, err, "no key bundles provided") +} + +func newMultichainAdapter(t *testing.T) *ocrcommon.OCR3OnchainKeyringMultiChainAdapter { + evmBundle, err := ocr2key.New(chaintype.EVM) + require.NoError(t, err) + + aptosBundle, err := ocr2key.New(chaintype.Aptos) + require.NoError(t, err) + + bundles := map[string]ocr2key.KeyBundle{ + "evm": evmBundle, + "aptos": aptosBundle, + } + adapter, err := ocrcommon.NewOCR3OnchainKeyringMultiChainAdapter(bundles, logger.TestLogger(t)) + require.NoError(t, err) + + return adapter +} + +func TestNewOCR3OnchainKeyringMultiChainAdapter_VerifyFromDifferentNodesPublicKeys(t *testing.T) { + firstNodeAdapter := newMultichainAdapter(t) + secondNodeAdapter := newMultichainAdapter(t) - sig, err := adapter.Sign(configDigest, seqNr, reportInfo) - assert.NoError(t, err) - assert.True(t, adapter.Verify(pk, configDigest, seqNr, reportInfo, sig)) - assert.Equal(t, pk, adapter.PublicKey()) - assert.Equal(t, fakeKey.MaxSignatureLength(), adapter.MaxSignatureLength()) + // evm signature + info, err := structpb.NewStruct(map[string]any{ + "keyBundleName": "evm", + }) + require.NoError(t, err) + + infob, err := proto.Marshal(info) + require.NoError(t, err) + r := ocr3types.ReportWithInfo[[]byte]{ + Report: []byte("report"), + Info: infob, + } + + sig, err := firstNodeAdapter.Sign(configDigest, seqNr, r) + require.NoError(t, err) + assert.True(t, secondNodeAdapter.Verify(firstNodeAdapter.PublicKey(), configDigest, seqNr, r, sig)) + + // aptos signature + info, err = structpb.NewStruct(map[string]any{ + "keyBundleName": "aptos", + }) + require.NoError(t, err) + + infob, err = proto.Marshal(info) + require.NoError(t, err) + r = ocr3types.ReportWithInfo[[]byte]{ + Report: []byte("report"), + Info: infob, + } + + sig, err = secondNodeAdapter.Sign(configDigest, seqNr, r) + require.NoError(t, err) + assert.True(t, firstNodeAdapter.Verify(secondNodeAdapter.PublicKey(), configDigest, seqNr, r, sig)) } var _ ocrtypes.ContractTransmitter = (*fakeContractTransmitter)(nil) diff --git a/core/services/ocrcommon/arbitrum_block_translator.go b/core/services/ocrcommon/arbitrum_block_translator.go index 1b7c371238..9179fe3227 100644 --- a/core/services/ocrcommon/arbitrum_block_translator.go +++ b/core/services/ocrcommon/arbitrum_block_translator.go @@ -10,9 +10,10 @@ import ( "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -33,7 +34,7 @@ type ArbitrumBlockTranslator struct { func NewArbitrumBlockTranslator(ethClient evmclient.Client, lggr logger.Logger) *ArbitrumBlockTranslator { return &ArbitrumBlockTranslator{ ethClient, - lggr.Named("ArbitrumBlockTranslator"), + logger.Named(lggr, "ArbitrumBlockTranslator"), make(map[int64]int64), sync.RWMutex{}, utils.KeyedMutex{}, diff --git a/core/services/ocrcommon/arbitrum_block_translator_test.go b/core/services/ocrcommon/arbitrum_block_translator_test.go index fa6875fb79..6b9abc93bf 100644 --- a/core/services/ocrcommon/arbitrum_block_translator_test.go +++ b/core/services/ocrcommon/arbitrum_block_translator_test.go @@ -1,6 +1,7 @@ package ocrcommon_test import ( + "context" "database/sql" "math/big" mrand "math/rand" @@ -34,7 +35,7 @@ func TestArbitrumBlockTranslator_BinarySearch(t *testing.T) { var changedInL1Block int64 = 5541 latestBlock := blocks[1000] - client.On("HeadByNumber", ctx, (*big.Int)(nil)).Return(&latestBlock, nil).Once() + client.On("HeadByNumber", ctx, (*big.Int)(nil)).Return(latestBlock, nil).Once() from, to, err := abt.BinarySearch(ctx, changedInL1Block) require.NoError(t, err) @@ -51,11 +52,10 @@ func TestArbitrumBlockTranslator_BinarySearch(t *testing.T) { var changedInL1Block int64 = 42 latestBlock := blocks[1000] - client.On("HeadByNumber", ctx, (*big.Int)(nil)).Return(&latestBlock, nil).Once() + client.On("HeadByNumber", ctx, (*big.Int)(nil)).Return(latestBlock, nil).Once() - tmp := new(evmtypes.Head) - client.On("HeadByNumber", ctx, mock.AnythingOfType("*big.Int")).Return(tmp, nil).Run(func(args mock.Arguments) { - *tmp = blocks[args[1].(*big.Int).Int64()] + client.On("HeadByNumber", ctx, mock.AnythingOfType("*big.Int")).Return(func(_ context.Context, num *big.Int) (*evmtypes.Head, error) { + return blocks[num.Int64()], nil }) _, _, err := abt.BinarySearch(ctx, changedInL1Block) @@ -71,11 +71,10 @@ func TestArbitrumBlockTranslator_BinarySearch(t *testing.T) { var changedInL1Block int64 = 5043 latestBlock := blocks[1000] - client.On("HeadByNumber", ctx, (*big.Int)(nil)).Return(&latestBlock, nil).Once() + client.On("HeadByNumber", ctx, (*big.Int)(nil)).Return(latestBlock, nil).Once() - tmp := new(evmtypes.Head) - client.On("HeadByNumber", ctx, mock.AnythingOfType("*big.Int")).Return(tmp, nil).Run(func(args mock.Arguments) { - *tmp = blocks[args[1].(*big.Int).Int64()] + client.On("HeadByNumber", ctx, mock.AnythingOfType("*big.Int")).Return(func(_ context.Context, num *big.Int) (*evmtypes.Head, error) { + return blocks[num.Int64()], nil }) _, _, err := abt.BinarySearch(ctx, changedInL1Block) @@ -91,12 +90,10 @@ func TestArbitrumBlockTranslator_BinarySearch(t *testing.T) { var changedInL1Block int64 = 5042 latestBlock := blocks[1000] - client.On("HeadByNumber", ctx, (*big.Int)(nil)).Return(&latestBlock, nil).Once() + client.On("HeadByNumber", ctx, (*big.Int)(nil)).Return(latestBlock, nil).Once() - tmp := new(evmtypes.Head) - client.On("HeadByNumber", ctx, mock.AnythingOfType("*big.Int")).Return(tmp, nil).Run(func(args mock.Arguments) { - h := blocks[args[1].(*big.Int).Int64()] - *tmp = h + client.On("HeadByNumber", ctx, mock.AnythingOfType("*big.Int")).Return(func(_ context.Context, num *big.Int) (*evmtypes.Head, error) { + return blocks[num.Int64()], nil }) from, to, err := abt.BinarySearch(ctx, changedInL1Block) @@ -114,12 +111,10 @@ func TestArbitrumBlockTranslator_BinarySearch(t *testing.T) { var changedInL1Block int64 = 5000 latestBlock := blocks[1000] - client.On("HeadByNumber", ctx, (*big.Int)(nil)).Return(&latestBlock, nil).Once() + client.On("HeadByNumber", ctx, (*big.Int)(nil)).Return(latestBlock, nil).Once() - tmp := new(evmtypes.Head) - client.On("HeadByNumber", ctx, mock.AnythingOfType("*big.Int")).Return(tmp, nil).Run(func(args mock.Arguments) { - h := blocks[args[1].(*big.Int).Int64()] - *tmp = h + client.On("HeadByNumber", ctx, mock.AnythingOfType("*big.Int")).Return(func(_ context.Context, num *big.Int) (*evmtypes.Head, error) { + return blocks[num.Int64()], nil }) from, to, err := abt.BinarySearch(ctx, changedInL1Block) @@ -137,12 +132,10 @@ func TestArbitrumBlockTranslator_BinarySearch(t *testing.T) { var changedInL1Block int64 = 5540 latestBlock := blocks[1000] - client.On("HeadByNumber", ctx, (*big.Int)(nil)).Return(&latestBlock, nil).Once() + client.On("HeadByNumber", ctx, (*big.Int)(nil)).Return(latestBlock, nil).Once() - tmp := new(evmtypes.Head) - client.On("HeadByNumber", ctx, mock.AnythingOfType("*big.Int")).Return(tmp, nil).Run(func(args mock.Arguments) { - h := blocks[args[1].(*big.Int).Int64()] - *tmp = h + client.On("HeadByNumber", ctx, mock.AnythingOfType("*big.Int")).Return(func(_ context.Context, num *big.Int) (*evmtypes.Head, error) { + return blocks[num.Int64()], nil }) from, to, err := abt.BinarySearch(ctx, changedInL1Block) @@ -161,12 +154,10 @@ func TestArbitrumBlockTranslator_BinarySearch(t *testing.T) { latestBlock := blocks[1000] // Latest is never cached - client.On("HeadByNumber", ctx, (*big.Int)(nil)).Return(&latestBlock, nil).Once() + client.On("HeadByNumber", ctx, (*big.Int)(nil)).Return(latestBlock, nil).Once() - tmp := new(evmtypes.Head) - client.On("HeadByNumber", ctx, mock.AnythingOfType("*big.Int")).Times(20+18+14).Return(tmp, nil).Run(func(args mock.Arguments) { - h := blocks[args[1].(*big.Int).Int64()] - *tmp = h + client.On("HeadByNumber", ctx, mock.AnythingOfType("*big.Int")).Return(func(_ context.Context, num *big.Int) (*evmtypes.Head, error) { + return blocks[num.Int64()], nil }) // First search, nothing cached (total 21 - bsearch 20) @@ -230,14 +221,14 @@ func TestArbitrumBlockTranslator_NumberToQueryRange(t *testing.T) { }) } -func generateDeterministicL2Blocks() (heads []evmtypes.Head) { +func generateDeterministicL2Blocks() (heads []*evmtypes.Head) { source := mrand.NewSource(0) deterministicRand := mrand.New(source) l2max := 1000 var l1BlockNumber int64 = 5000 var parentHash common.Hash for i := 0; i <= l2max; i++ { - head := evmtypes.Head{ + head := &evmtypes.Head{ Number: int64(i), L1BlockNumber: sql.NullInt64{Int64: l1BlockNumber, Valid: true}, Hash: utils.NewHash(), diff --git a/core/services/ocrcommon/block_translator.go b/core/services/ocrcommon/block_translator.go index d7ceffc5ea..fa44d79c2d 100644 --- a/core/services/ocrcommon/block_translator.go +++ b/core/services/ocrcommon/block_translator.go @@ -4,10 +4,11 @@ import ( "context" "math/big" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/chaintype" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) // BlockTranslator converts emitted block numbers (from block.number) into a diff --git a/core/services/ocrcommon/telemetry.go b/core/services/ocrcommon/telemetry.go index 2ef76800a4..810952f455 100644 --- a/core/services/ocrcommon/telemetry.go +++ b/core/services/ocrcommon/telemetry.go @@ -11,11 +11,13 @@ import ( ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "google.golang.org/protobuf/proto" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" v1types "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v1" v2types "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v2" v3types "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v3" - "github.com/smartcontractkit/chainlink/v2/core/logger" + v4types "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v4" + "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" mercuryutils "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/utils" @@ -23,12 +25,20 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/utils" ) -type eaTelemetry struct { +type EATelemetry struct { DataSource string ProviderRequestedTimestamp int64 ProviderReceivedTimestamp int64 ProviderDataStreamEstablished int64 ProviderIndicatedTime int64 + + DpBenchmarkPrice float64 + DpBid float64 + DpAsk float64 + BridgeTaskRunStartedTimestamp int64 + BridgeTaskRunEndedTimestamp int64 + AssetSymbol string + BridgeRequestData string } type EnhancedTelemetryData struct { @@ -41,6 +51,7 @@ type EnhancedTelemetryMercuryData struct { V1Observation *v1types.Observation V2Observation *v2types.Observation V3Observation *v3types.Observation + V4Observation *v4types.Observation TaskRunResults pipeline.TaskRunResults RepTimestamp ocrtypes.ReportTimestamp FeedVersion mercuryutils.FeedVersion @@ -141,8 +152,44 @@ func (e *EnhancedTelemetryService[T]) getChainID() string { } } +func ParseMercuryEATelemetry(lggr logger.Logger, trrs pipeline.TaskRunResults, feedVersion mercuryutils.FeedVersion) (eaTelemetryValues []EATelemetry) { + for _, trr := range trrs { + if trr.Task.Type() != pipeline.TaskTypeBridge { + continue + } + bridgeTask := trr.Task.(*pipeline.BridgeTask) + bridgeName := bridgeTask.Name + + bridgeRawResponse, ok := trr.Result.Value.(string) + if !ok { + lggr.Warnw(fmt.Sprintf("cannot get bridge response from bridge task, id=%s, name=%q, expected string got %T", trr.Task.DotID(), bridgeName, trr.Result.Value), "dotID", trr.Task.DotID(), "bridgeName", bridgeName) + continue + } + eaTelem, err := parseEATelemetry([]byte(bridgeRawResponse)) + if err != nil { + lggr.Warnw(fmt.Sprintf("cannot parse EA telemetry, id=%s, name=%q", trr.Task.DotID(), bridgeName), "err", err, "dotID", trr.Task.DotID(), "bridgeName", bridgeName) + } + eaTelem.BridgeRequestData = bridgeTask.RequestData + eaTelem.DpBenchmarkPrice, eaTelem.DpBid, eaTelem.DpAsk = getPricesFromBridgeTask(lggr, trr, trrs, feedVersion) + + eaTelem.BridgeTaskRunStartedTimestamp = trr.CreatedAt.UnixMilli() + eaTelem.BridgeTaskRunEndedTimestamp = trr.FinishedAt.Time.UnixMilli() + + parsedBridgeData := parseBridgeRequestData(bridgeTask.RequestData, feedVersion) + if parsedBridgeData.IsMarketStatus { + // Only collect telemetry for pricing bridges. + continue + } + + eaTelem.AssetSymbol = parsedBridgeData.AssetSymbol + + eaTelemetryValues = append(eaTelemetryValues, eaTelem) + } + return +} + // parseEATelemetry attempts to parse the bridge telemetry -func parseEATelemetry(b []byte) (eaTelemetry, error) { +func parseEATelemetry(b []byte) (EATelemetry, error) { type eaTimestamps struct { ProviderRequestedTimestamp int64 `json:"providerDataRequestedUnixMs"` ProviderReceivedTimestamp int64 `json:"providerDataReceivedUnixMs"` @@ -160,10 +207,10 @@ func parseEATelemetry(b []byte) (eaTelemetry, error) { t := eaTelem{} if err := json.Unmarshal(b, &t); err != nil { - return eaTelemetry{}, err + return EATelemetry{}, err } - return eaTelemetry{ + return EATelemetry{ DataSource: t.TelemMeta.AdapterName, ProviderRequestedTimestamp: t.TelemTimestamps.ProviderRequestedTimestamp, ProviderReceivedTimestamp: t.TelemTimestamps.ProviderReceivedTimestamp, @@ -291,13 +338,15 @@ func (e *EnhancedTelemetryService[T]) collectMercuryEnhancedTelemetry(d Enhanced var bn int64 var bh string var bt uint64 - // v1+v2+v3 fields + // v1+v2+v3+v4 fields bp := big.NewInt(0) // v1+v3 fields bid := big.NewInt(0) ask := big.NewInt(0) // v2+v3 fields var mfts, lp, np int64 + // v4 fields + var marketStatus telem.MarketStatus switch { case d.V1Observation != nil: @@ -354,42 +403,40 @@ func (e *EnhancedTelemetryService[T]) collectMercuryEnhancedTelemetry(d Enhanced if obs.Ask.Err == nil && obs.Ask.Val != nil { ask = obs.Ask.Val } - } - - for _, trr := range d.TaskRunResults { - if trr.Task.Type() != pipeline.TaskTypeBridge { - continue + case d.V4Observation != nil: + obs := *d.V4Observation + if obs.MaxFinalizedTimestamp.Err == nil { + mfts = obs.MaxFinalizedTimestamp.Val } - bridgeTask := trr.Task.(*pipeline.BridgeTask) - bridgeName := bridgeTask.Name - - bridgeRawResponse, ok := trr.Result.Value.(string) - if !ok { - e.lggr.Warnw(fmt.Sprintf("cannot get bridge response from bridge task, job=%d, id=%s, name=%q, expected string got %T", e.job.ID, trr.Task.DotID(), bridgeName, trr.Result.Value), "jobID", e.job.ID, "dotID", trr.Task.DotID(), "bridgeName", bridgeName) - continue + if obs.LinkPrice.Err == nil && obs.LinkPrice.Val != nil { + lp = obs.LinkPrice.Val.Int64() } - eaTelem, err := parseEATelemetry([]byte(bridgeRawResponse)) - if err != nil { - e.lggr.Warnw(fmt.Sprintf("cannot parse EA telemetry, job=%d, id=%s, name=%q", e.job.ID, trr.Task.DotID(), bridgeName), "err", err, "jobID", e.job.ID, "dotID", trr.Task.DotID(), "bridgeName", bridgeName) + if obs.NativePrice.Err == nil && obs.NativePrice.Val != nil { + np = obs.NativePrice.Val.Int64() } + if obs.BenchmarkPrice.Err == nil && obs.BenchmarkPrice.Val != nil { + bp = obs.BenchmarkPrice.Val + } + if obs.MarketStatus.Err == nil { + marketStatus = telem.MarketStatus(obs.MarketStatus.Val) + } + } - assetSymbol := e.getAssetSymbolFromRequestData(bridgeTask.RequestData) - - benchmarkPrice, bidPrice, askPrice := e.getPricesFromResults(trr, d.TaskRunResults, d.FeedVersion) - + eaTelemetryValues := ParseMercuryEATelemetry(logger.Sugared(e.lggr).With("jobID", e.job.ID), d.TaskRunResults, d.FeedVersion) + for _, eaTelem := range eaTelemetryValues { t := &telem.EnhancedEAMercury{ DataSource: eaTelem.DataSource, - DpBenchmarkPrice: benchmarkPrice, - DpBid: bidPrice, - DpAsk: askPrice, + DpBenchmarkPrice: eaTelem.DpBenchmarkPrice, + DpBid: eaTelem.DpBid, + DpAsk: eaTelem.DpAsk, DpInvariantViolationDetected: d.DpInvariantViolationDetected, CurrentBlockNumber: bn, CurrentBlockHash: bh, CurrentBlockTimestamp: bt, FetchMaxFinalizedTimestamp: d.FetchMaxFinalizedTimestamp, MaxFinalizedTimestamp: mfts, - BridgeTaskRunStartedTimestamp: trr.CreatedAt.UnixMilli(), - BridgeTaskRunEndedTimestamp: trr.FinishedAt.Time.UnixMilli(), + BridgeTaskRunStartedTimestamp: eaTelem.BridgeTaskRunStartedTimestamp, + BridgeTaskRunEndedTimestamp: eaTelem.BridgeTaskRunEndedTimestamp, ProviderRequestedTimestamp: eaTelem.ProviderRequestedTimestamp, ProviderReceivedTimestamp: eaTelem.ProviderReceivedTimestamp, ProviderDataStreamEstablished: eaTelem.ProviderDataStreamEstablished, @@ -401,6 +448,7 @@ func (e *EnhancedTelemetryService[T]) collectMercuryEnhancedTelemetry(d Enhanced ObservationBenchmarkPriceString: stringOrEmpty(bp), ObservationBidString: stringOrEmpty(bid), ObservationAskString: stringOrEmpty(ask), + ObservationMarketStatus: marketStatus, IsLinkFeed: d.IsLinkFeed, LinkPrice: lp, IsNativeFeed: d.IsNativeFeed, @@ -408,10 +456,11 @@ func (e *EnhancedTelemetryService[T]) collectMercuryEnhancedTelemetry(d Enhanced ConfigDigest: d.RepTimestamp.ConfigDigest.Hex(), Round: int64(d.RepTimestamp.Round), Epoch: int64(d.RepTimestamp.Epoch), - AssetSymbol: assetSymbol, + BridgeRequestData: eaTelem.BridgeRequestData, + AssetSymbol: eaTelem.AssetSymbol, Version: uint32(d.FeedVersion), } - + e.lggr.Debugw(fmt.Sprintf("EA Telemetry = %+v", t), "feedID", e.job.OCR2OracleSpec.FeedID.Hex(), "jobID", e.job.ID, "datasource", eaTelem.DataSource) bytes, err := proto.Marshal(t) if err != nil { e.lggr.Warnf("protobuf marshal failed %v", err.Error()) @@ -422,11 +471,32 @@ func (e *EnhancedTelemetryService[T]) collectMercuryEnhancedTelemetry(d Enhanced } } -// getAssetSymbolFromRequestData parses the requestData of the bridge to generate an asset symbol pair -func (e *EnhancedTelemetryService[T]) getAssetSymbolFromRequestData(requestData string) string { +type telemetryAttributes struct { + PriceType *string `json:"priceType"` +} + +func parseTelemetryAttributes(a string) (telemetryAttributes, error) { + attrs := &telemetryAttributes{} + err := json.Unmarshal([]byte(a), attrs) + if err != nil { + return telemetryAttributes{}, err + } + return *attrs, nil +} + +type bridgeRequestData struct { + AssetSymbol string + IsMarketStatus bool +} + +// parseRequestData parses the requestData of the bridge. +func parseBridgeRequestData(requestData string, mercuryVersion mercuryutils.FeedVersion) bridgeRequestData { type reqDataPayload struct { - To string `json:"to"` - From string `json:"from"` + Endpoint *string `json:"endpoint"` + To *string `json:"to"` + From *string `json:"from"` + Address *string `json:"address"` // used for view function ea only + Market *string `json:"market"` // used for market status ea only } type reqData struct { Data reqDataPayload `json:"data"` @@ -435,10 +505,25 @@ func (e *EnhancedTelemetryService[T]) getAssetSymbolFromRequestData(requestData rd := &reqData{} err := json.Unmarshal([]byte(requestData), rd) if err != nil { - return "" + return bridgeRequestData{} + } + + if mercuryVersion == 4 && ((rd.Data.Endpoint != nil && *rd.Data.Endpoint == "market-status") || (rd.Data.Market != nil && *rd.Data.Market != "")) { + return bridgeRequestData{ + AssetSymbol: *rd.Data.Market, + IsMarketStatus: true, + } + } + + if rd.Data.From != nil && rd.Data.To != nil { + return bridgeRequestData{AssetSymbol: *rd.Data.From + "/" + *rd.Data.To} } - return rd.Data.From + "/" + rd.Data.To + if rd.Data.Address != nil { + return bridgeRequestData{AssetSymbol: *rd.Data.Address} + } + + return bridgeRequestData{} } // ShouldCollectEnhancedTelemetryMercury checks if enhanced telemetry should be collected and sent @@ -449,64 +534,131 @@ func ShouldCollectEnhancedTelemetryMercury(jb job.Job) bool { return false } -// getPricesFromResults parses the pipeline.TaskRunResults for pipeline.TaskTypeJSONParse and gets the benchmarkPrice, +const ( + bid = "bid" + ask = "ask" + benchmark = "benchmark" + exchangeRate = "exchangeRate" +) + +func getPricesFromBridgeTask(lggr logger.Logger, bridgeTask pipeline.TaskRunResult, allTasks pipeline.TaskRunResults, mercuryVersion mercuryutils.FeedVersion) (float64, float64, float64) { + var benchmarkPrice, bidPrice, askPrice float64 + + // This will assume that all fields we care about are tagged with the correct priceType + benchmarkPrice, bidPrice, askPrice = getPricesFromBridgeTaskByTelemetryField(lggr, bridgeTask, allTasks) + + // If prices weren't parsed by telemetry fields - attempt to get prices using the legacy method + // This is for backwards compatibility with job specs that don't have the telemetry attributes set + if benchmarkPrice == 0 && bidPrice == 0 && askPrice == 0 { + benchmarkP, bidP, askP := getPricesFromResultsByOrder(lggr, bridgeTask, allTasks, mercuryVersion) + bidPrice = bidP + askPrice = askP + benchmarkPrice = benchmarkP + } + + return benchmarkPrice, bidPrice, askPrice +} + +// CollectTaskRunResultsWithTags collects TaskRunResults for descendent tasks with non-empty TaskTags. +func collectTaskRunResultsWithTags(bridgeTask pipeline.TaskRunResult, allTasks pipeline.TaskRunResults) []pipeline.TaskRunResult { + startTask := bridgeTask.Task + descendants := startTask.GetDescendantTasks() + var taskRunResultsWithTags []pipeline.TaskRunResult + for _, task := range descendants { + trr := allTasks.GetTaskRunResultOf(task) + if trr != nil { + if trr.Task.TaskTags() != "" { + taskRunResultsWithTags = append(taskRunResultsWithTags, *trr) + } + } + } + return taskRunResultsWithTags +} + +// getPricesFromBridgeTaskByTelemetryField attempts to parse prices from via telemetry fields in the TaskTags +func getPricesFromBridgeTaskByTelemetryField(lggr logger.Logger, bridgeTask pipeline.TaskRunResult, allTasks pipeline.TaskRunResults) (float64, float64, float64) { + var benchmarkPrice, bidPrice, askPrice float64 + + // Outputs are the mapped tasks from this task. + var tasksWithTags = collectTaskRunResultsWithTags(bridgeTask, allTasks) + + for _, trr := range tasksWithTags { + attributes, err := parseTelemetryAttributes(trr.Task.TaskTags()) + if err != nil { + lggr.Warnw(fmt.Sprintf("found telemetry attributes but cannot them, taskTags=%s", trr.Task.TaskTags()), "err", err) + continue + } + + if attributes.PriceType != nil { + switch *attributes.PriceType { + case bid: + bidPrice = parsePriceFromTask(lggr, trr) + case ask: + askPrice = parsePriceFromTask(lggr, trr) + case benchmark: + benchmarkPrice = parsePriceFromTask(lggr, trr) + case exchangeRate: + price := parsePriceFromTask(lggr, trr) + benchmarkPrice, bidPrice, askPrice = price, price, price + case "": + lggr.Warnw(fmt.Sprintf("no priceType found in attributes, parsedAttributes=%+v, id %s", attributes, trr.Task.DotID())) + } + } + } + + return benchmarkPrice, bidPrice, askPrice +} + +func parsePriceFromTask(lggr logger.Logger, trr pipeline.TaskRunResult) float64 { + var val float64 + if trr.Result.Error != nil { + lggr.Warnw(fmt.Sprintf("got error on EA telemetry price task, id %s: %s", trr.Task.DotID(), trr.Result.Error), "err", trr.Result.Error) + return 0 + } + val, err := getResultFloat64(&trr) + if err != nil { + lggr.Warnw(fmt.Sprintf("cannot parse EA telemetry price to float64, DOT id %s", trr.Task.DotID()), "task_type", trr.Task.Type(), "task_tags", trr.Task.TaskTags(), "err", err) + } + return val +} + +// getPricesFromResultsByOrder parses the pipeline.TaskRunResults for pipeline.TaskTypeJSONParse and gets the benchmarkPrice, // bid and ask. This functions expects the pipeline.TaskRunResults to be correctly ordered -func (e *EnhancedTelemetryService[T]) getPricesFromResults(startTask pipeline.TaskRunResult, allTasks pipeline.TaskRunResults, mercuryVersion mercuryutils.FeedVersion) (float64, float64, float64) { +func getPricesFromResultsByOrder(lggr logger.Logger, startTask pipeline.TaskRunResult, allTasks pipeline.TaskRunResults, mercuryVersion mercuryutils.FeedVersion) (float64, float64, float64) { var benchmarkPrice, askPrice, bidPrice float64 - var err error + // We rely on task results to be sorted in the correct order benchmarkPriceTask := allTasks.GetNextTaskOf(startTask) if benchmarkPriceTask == nil { - e.lggr.Warnf("cannot parse enhanced EA telemetry benchmark price, task is nil, job %d", e.job.ID) + lggr.Warn("cannot parse enhanced EA telemetry benchmark price, task is nil") return 0, 0, 0 } if benchmarkPriceTask.Task.Type() == pipeline.TaskTypeJSONParse { - if benchmarkPriceTask.Result.Error != nil { - e.lggr.Warnw(fmt.Sprintf("got error for enhanced EA telemetry benchmark price, job %d, id %s: %s", e.job.ID, benchmarkPriceTask.Task.DotID(), benchmarkPriceTask.Result.Error), "err", benchmarkPriceTask.Result.Error) - } else { - benchmarkPrice, err = getResultFloat64(benchmarkPriceTask) - if err != nil { - e.lggr.Warnw(fmt.Sprintf("cannot parse enhanced EA telemetry benchmark price, job %d, id %s", e.job.ID, benchmarkPriceTask.Task.DotID()), "err", err) - } - } + benchmarkPrice = parsePriceFromTask(lggr, *benchmarkPriceTask) } - // mercury version 2 only supports benchmarkPrice - if mercuryVersion == 2 { + // mercury versions 2 and 4 only supports benchmarkPrice + if mercuryVersion == 2 || mercuryVersion == 4 { return benchmarkPrice, 0, 0 } bidTask := allTasks.GetNextTaskOf(*benchmarkPriceTask) if bidTask == nil { - e.lggr.Warnf("cannot parse enhanced EA telemetry bid price, task is nil, job %d, id %s", e.job.ID, benchmarkPriceTask.Task.DotID()) + lggr.Warnf("cannot parse enhanced EA telemetry bid price, task is nil, id %s", benchmarkPriceTask.Task.DotID()) return benchmarkPrice, 0, 0 } - if bidTask != nil && bidTask.Task.Type() == pipeline.TaskTypeJSONParse { - if bidTask.Result.Error != nil { - e.lggr.Warnw(fmt.Sprintf("got error for enhanced EA telemetry bid price, job %d, id %s: %s", e.job.ID, bidTask.Task.DotID(), bidTask.Result.Error), "err", bidTask.Result.Error) - } else { - bidPrice, err = getResultFloat64(bidTask) - if err != nil { - e.lggr.Warnw(fmt.Sprintf("cannot parse enhanced EA telemetry bid price, job %d, id %s", e.job.ID, bidTask.Task.DotID()), "err", err) - } - } + if bidTask.Task.Type() == pipeline.TaskTypeJSONParse { + bidPrice = parsePriceFromTask(lggr, *bidTask) } askTask := allTasks.GetNextTaskOf(*bidTask) if askTask == nil { - e.lggr.Warnf("cannot parse enhanced EA telemetry ask price, task is nil, job %d, id %s", e.job.ID, benchmarkPriceTask.Task.DotID()) + lggr.Warnf("cannot parse enhanced EA telemetry ask price, task is nil, id %s", benchmarkPriceTask.Task.DotID()) return benchmarkPrice, bidPrice, 0 } - if askTask != nil && askTask.Task.Type() == pipeline.TaskTypeJSONParse { - if bidTask.Result.Error != nil { - e.lggr.Warnw(fmt.Sprintf("got error for enhanced EA telemetry ask price, job %d, id %s: %s", e.job.ID, askTask.Task.DotID(), askTask.Result.Error), "err", askTask.Result.Error) - } else { - askPrice, err = getResultFloat64(askTask) - if err != nil { - e.lggr.Warnw(fmt.Sprintf("cannot parse enhanced EA telemetry ask price, job %d, id %s", e.job.ID, askTask.Task.DotID()), "err", err) - } - } + if askTask.Task.Type() == pipeline.TaskTypeJSONParse { + askPrice = parsePriceFromTask(lggr, *askTask) } return benchmarkPrice, bidPrice, askPrice diff --git a/core/services/ocrcommon/telemetry_test.go b/core/services/ocrcommon/telemetry_test.go index f764e7380f..ed64e45c2d 100644 --- a/core/services/ocrcommon/telemetry_test.go +++ b/core/services/ocrcommon/telemetry_test.go @@ -6,19 +6,19 @@ import ( "testing" "github.com/ethereum/go-ethereum/common" + "github.com/shopspring/decimal" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "go.uber.org/zap" "google.golang.org/protobuf/proto" - "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - "github.com/smartcontractkit/chainlink-common/pkg/services/servicetest" "github.com/smartcontractkit/chainlink-common/pkg/types/mercury" mercuryv1 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v1" mercuryv2 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v2" - + mercuryv4 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v4" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" ubig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" @@ -273,7 +273,6 @@ func TestSendEATelemetry(t *testing.T) { expectedMessage, _ := proto.Marshal(&expectedTelemetry) wg.Wait() assert.Equal(t, expectedMessage, sentMessage) - //enhancedTelemService.StopOnce("EnhancedTelemetryService", func() error { return nil }) doneCh <- struct{}{} } @@ -445,21 +444,123 @@ var trrsMercuryV2 = pipeline.TaskRunResults{ }, } -func TestGetPricesFromResults(t *testing.T) { - lggr, logs := logger.TestLoggerObserved(t, zap.WarnLevel) - e := EnhancedTelemetryService[EnhancedTelemetryMercuryData]{ - lggr: lggr, - job: &job.Job{ - ID: 0, +var trrsMercuryV4 = pipeline.TaskRunResults{ + pipeline.TaskRunResult{ + Task: &pipeline.BridgeTask{ + Name: "link-usd-test-bridge-v2", + BaseTask: pipeline.NewBaseTask(0, "ds1", nil, nil, 0), + RequestData: `{"data":{"to":"LINK","from":"USD"}}`, + }, + Result: pipeline.Result{ + Value: bridgeResponse, + }, + }, + pipeline.TaskRunResult{ + Task: &pipeline.JSONParseTask{ + BaseTask: pipeline.NewBaseTask(1, "ds1_benchmark", nil, nil, 1), + }, + Result: pipeline.Result{ + Value: 123456.123456, + }, + }, + pipeline.TaskRunResult{ + Task: &pipeline.BridgeTask{ + Name: "market-status-bridge", + BaseTask: pipeline.NewBaseTask(2, "ds2", nil, nil, 2), + RequestData: `{"data":{"endpoint":"market-status","market":"forex"}}`, + }, + Result: pipeline.Result{ + Value: bridgeResponse, }, + }, + pipeline.TaskRunResult{ + Task: &pipeline.JSONParseTask{ + BaseTask: pipeline.NewBaseTask(3, "market_status", nil, nil, 3), + }, + Result: pipeline.Result{ + Value: 2.0, + }, + }, +} + +func TestGetPricesFromBridgeByTelemetryField(t *testing.T) { + lggr, _ := logger.TestLoggerObserved(t, zap.WarnLevel) + // These are intentionally out of order from the "legacy" method which expects order of `benchmark, bid, ask` + jsonParseTaskBid := pipeline.JSONParseTask{ + BaseTask: pipeline.NewBaseTask(1, "json_parse_2", nil, nil, 2), + } + jsonParseTaskBid.BaseTask.Tags = `{"priceType": "bid"}` + jsonParseTaskAsk := pipeline.JSONParseTask{ + BaseTask: pipeline.NewBaseTask(2, "json_parse_3", nil, nil, 3), + } + jsonParseTaskAsk.BaseTask.Tags = `{"priceType": "ask"}` + jsonParseTaskBenchmark := pipeline.JSONParseTask{ + BaseTask: pipeline.NewBaseTask(3, "json_parse_1", nil, nil, 1), + } + jsonParseTaskBenchmark.BaseTask.Tags = `{"priceType": "benchmark"}` + + bridgeOutputs := []pipeline.Task{&jsonParseTaskAsk, &jsonParseTaskBid, &jsonParseTaskBenchmark} + + bridgeTask := pipeline.BridgeTask{ + Name: "bridge-task", + BaseTask: pipeline.NewBaseTask(0, "bridge", nil, bridgeOutputs, 0), } - benchmarkPrice, bid, ask := e.getPricesFromResults(trrsMercuryV1[0], trrsMercuryV1, 1) + // Create task run results + taskRunResults := pipeline.TaskRunResults{ + pipeline.TaskRunResult{ + Task: &bridgeTask, + Result: pipeline.Result{ + Value: bridgeResponse, + }, + }, + pipeline.TaskRunResult{ + Task: &jsonParseTaskBenchmark, + Result: pipeline.Result{ + Value: "123456.123456", + }, + }, + pipeline.TaskRunResult{ + Task: &jsonParseTaskBid, + Result: pipeline.Result{ + Value: "1234567.1234567", + }, + }, + pipeline.TaskRunResult{ + Task: &jsonParseTaskAsk, + Result: pipeline.Result{ + Value: "321123", + }, + }, + } + + benchmarkPrice, bidPrice, askPrice := getPricesFromBridgeTask(lggr, taskRunResults[0], taskRunResults, 1) + + require.Equal(t, 123456.123456, benchmarkPrice) + require.Equal(t, 1234567.1234567, bidPrice) + require.Equal(t, 321123.0, askPrice) + + // now removing the TaskTags will throw off the parsed order - and we'll be parsing the "incorrect" prices + // according to the legacy ordering approach + jsonParseTaskAsk.BaseTask.Tags = "" + jsonParseTaskBid.BaseTask.Tags = "" + jsonParseTaskBenchmark.BaseTask.Tags = "" + + wrongBenchmarkPrice, wrongBidPrice, wrongAskPrice := getPricesFromBridgeTask(lggr, taskRunResults[0], taskRunResults, 1) + require.Equal(t, 1234567.1234567, wrongBenchmarkPrice) + require.Equal(t, 321123.0, wrongBidPrice) + require.Equal(t, 123456.123456, wrongAskPrice) +} + +func TestGetPricesFromBridgeTaskByOrder(t *testing.T) { + lggr, logs := logger.TestLoggerObserved(t, zap.WarnLevel) + + benchmarkPrice, bid, ask := getPricesFromBridgeTask(lggr, trrsMercuryV1[0], trrsMercuryV1, 1) require.Equal(t, 123456.123456, benchmarkPrice) require.Equal(t, 1234567.1234567, bid) require.Equal(t, float64(321123), ask) - benchmarkPrice, bid, ask = e.getPricesFromResults(trrsMercuryV1[0], pipeline.TaskRunResults{}, 1) + benchmarkPrice, bid, ask = getPricesFromBridgeTask(lggr, trrsMercuryV1[0], pipeline.TaskRunResults{}, 1) require.Equal(t, float64(0), benchmarkPrice) require.Equal(t, float64(0), bid) require.Equal(t, float64(0), ask) @@ -467,12 +568,12 @@ func TestGetPricesFromResults(t *testing.T) { require.Contains(t, logs.All()[0].Message, "cannot parse enhanced EA telemetry") tt := trrsMercuryV1[:2] - e.getPricesFromResults(trrsMercuryV1[0], tt, 1) + getPricesFromBridgeTask(lggr, trrsMercuryV1[0], tt, 1) require.Equal(t, 2, logs.Len()) require.Contains(t, logs.All()[1].Message, "cannot parse enhanced EA telemetry bid price, task is nil") tt = trrsMercuryV1[:3] - e.getPricesFromResults(trrsMercuryV1[0], tt, 1) + getPricesFromBridgeTask(lggr, trrsMercuryV1[0], tt, 1) require.Equal(t, 3, logs.Len()) require.Contains(t, logs.All()[2].Message, "cannot parse enhanced EA telemetry ask price, task is nil") @@ -510,16 +611,16 @@ func TestGetPricesFromResults(t *testing.T) { Value: nil, }, }} - benchmarkPrice, bid, ask = e.getPricesFromResults(trrsMercuryV1[0], trrs2, 3) + benchmarkPrice, bid, ask = getPricesFromBridgeTask(lggr, trrsMercuryV1[0], trrs2, 3) require.Equal(t, benchmarkPrice, float64(0)) require.Equal(t, bid, float64(0)) require.Equal(t, ask, float64(0)) require.Equal(t, logs.Len(), 6) - require.Contains(t, logs.All()[3].Message, "cannot parse enhanced EA telemetry benchmark price") - require.Contains(t, logs.All()[4].Message, "cannot parse enhanced EA telemetry bid price") - require.Contains(t, logs.All()[5].Message, "cannot parse enhanced EA telemetry ask price") + require.Contains(t, logs.All()[3].Message, "cannot parse EA telemetry price to float64, DOT id ds1_benchmark") + require.Contains(t, logs.All()[4].Message, "cannot parse EA telemetry price to float64, DOT id ds2_bid") + require.Contains(t, logs.All()[5].Message, "cannot parse EA telemetry price to float64, DOT id ds3_ask") - benchmarkPrice, bid, ask = e.getPricesFromResults(trrsMercuryV1[0], trrsMercuryV2, 2) + benchmarkPrice, bid, ask = getPricesFromBridgeTask(lggr, trrsMercuryV1[0], trrsMercuryV2, 2) require.Equal(t, 123456.123456, benchmarkPrice) require.Equal(t, float64(0), bid) require.Equal(t, float64(0), ask) @@ -541,11 +642,180 @@ func TestShouldCollectEnhancedTelemetryMercury(t *testing.T) { require.Equal(t, ShouldCollectEnhancedTelemetryMercury(j), false) } -func TestGetAssetSymbolFromRequestData(t *testing.T) { - e := EnhancedTelemetryService[EnhancedTelemetryMercuryData]{} - require.Equal(t, e.getAssetSymbolFromRequestData(""), "") +func TestParseBridgeRequestData(t *testing.T) { + require.Equal(t, parseBridgeRequestData("", 2), bridgeRequestData{}) + reqData := `{"data":{"to":"LINK","from":"USD"}}` - require.Equal(t, e.getAssetSymbolFromRequestData(reqData), "USD/LINK") + require.Equal(t, parseBridgeRequestData(reqData, 2), bridgeRequestData{AssetSymbol: "USD/LINK"}) + + reqData = `{"data":{"to":"LINK","from":"USD","market":"forex"}}` + require.Equal(t, parseBridgeRequestData(reqData, 2), bridgeRequestData{AssetSymbol: "USD/LINK"}) + + reqData = `{"data":{"endpoint":"market-status","market":"forex"}}` + require.Equal(t, parseBridgeRequestData(reqData, 4), bridgeRequestData{AssetSymbol: "forex", IsMarketStatus: true}) + + reqData = `{"data":{"market":"metals"}}` + require.Equal(t, parseBridgeRequestData(reqData, 4), bridgeRequestData{AssetSymbol: "metals", IsMarketStatus: true}) + + viewFunctionReqData := `{"data":{"address":"0x12345678", "signature": "function stEthPerToken() view returns (int256)"}}` + require.Equal(t, parseBridgeRequestData(viewFunctionReqData, 3), bridgeRequestData{AssetSymbol: "0x12345678"}) +} + +func getViewFunctionTaskRunResults() pipeline.TaskRunResults { + var taskViewFunctionParseValue = func() pipeline.MultiplyTask { + task := pipeline.MultiplyTask{ + BaseTask: pipeline.NewBaseTask(3, "ds1_parse", nil, nil, 3), + Times: "1", + } + task.BaseTask.Tags = `{"priceType": "exchangeRate"}` + return task + }() + + var taskViewFunctionDecode = pipeline.ETHABIDecodeTask{ + ABI: "uint256 data", + BaseTask: pipeline.NewBaseTask(2, "ds1_decode", nil, []pipeline.Task{&taskViewFunctionParseValue}, 2), + } + + var taskViewFunctionJSONParse = pipeline.JSONParseTask{ + BaseTask: pipeline.NewBaseTask(1, "ds1_parse", nil, []pipeline.Task{&taskViewFunctionDecode}, 1), + } + + const viewFunctionBridgeResponse = `{ + "data": { + "result": "0x000000000000000000000000000000000000000000000000105ba6a589b23a81" + }, + "statusCode": 200, + "result": "0x000000000000000000000000000000000000000000000000105ba6a589b23a81", + "timestamps": { + "providerDataRequestedUnixMs": 1726243598046, + "providerDataReceivedUnixMs": 1726243598341 + }, + "meta": { + "adapterName": "VIEW_FUNCTION" + } + }` + + var taskViewFunctionBridgeRequest = pipeline.BridgeTask{ + Name: "bridge-view-function", + BaseTask: pipeline.NewBaseTask(0, "ds1", nil, []pipeline.Task{&taskViewFunctionJSONParse}, 0), + RequestData: `{"data":{"address":"0x1234","signature":"function stEthPerToken() external view returns (uint256)"}}`, + } + + return pipeline.TaskRunResults{ + pipeline.TaskRunResult{ + Task: &taskViewFunctionBridgeRequest, + Result: pipeline.Result{ + Value: viewFunctionBridgeResponse, + }, + }, + pipeline.TaskRunResult{ + Task: &taskViewFunctionJSONParse, + Result: pipeline.Result{ + Value: `0x000000000000000000000000000000000000000000000000105ba6a589b23a81`, + }, + }, + pipeline.TaskRunResult{ + Task: &taskViewFunctionDecode, + Result: pipeline.Result{ + Value: map[string]interface{}{ + "data": big.NewInt(1178718957397490305), + }, + }, + }, + pipeline.TaskRunResult{ + Task: &taskViewFunctionParseValue, + Result: pipeline.Result{ + Value: decimal.NewFromInt(1178718957397490305), + }, + }, + } +} + +func TestCollectMercuryEnhancedTelemetryV1ViewFunction(t *testing.T) { + wg := sync.WaitGroup{} + ingressClient := mocks.NewTelemetryService(t) + ingressAgent := telemetry.NewIngressAgentWrapper(ingressClient) + monitoringEndpoint := ingressAgent.GenMonitoringEndpoint("test-network", "test-chainID", "0xa", synchronization.EnhancedEAMercury) + + var sentMessage []byte + ingressClient.On("Send", mock.Anything, mock.AnythingOfType("[]uint8"), mock.AnythingOfType("string"), mock.AnythingOfType("TelemetryType")).Return().Run(func(args mock.Arguments) { + sentMessage = args[1].([]byte) + wg.Done() + }) + + lggr, _ := logger.TestLoggerObserved(t, zap.WarnLevel) + chTelem := make(chan EnhancedTelemetryMercuryData, 100) + chDone := make(chan struct{}) + feedID := common.HexToHash("0x111") + e := EnhancedTelemetryService[EnhancedTelemetryMercuryData]{ + chDone: chDone, + chTelem: chTelem, + job: &job.Job{ + Type: job.Type(pipeline.OffchainReporting2JobType), + OCR2OracleSpec: &job.OCR2OracleSpec{ + CaptureEATelemetry: true, + FeedID: &feedID, + }, + }, + lggr: lggr, + monitoringEndpoint: monitoringEndpoint, + } + servicetest.Run(t, &e) + + wg.Add(1) + + taskRunResults := getViewFunctionTaskRunResults() + + chTelem <- EnhancedTelemetryMercuryData{ + TaskRunResults: taskRunResults, + V1Observation: &mercuryv1.Observation{ + BenchmarkPrice: mercury.ObsResult[*big.Int]{Val: big.NewInt(111111)}, + Bid: mercury.ObsResult[*big.Int]{Val: big.NewInt(222222)}, + Ask: mercury.ObsResult[*big.Int]{Val: big.NewInt(333333)}, + CurrentBlockNum: mercury.ObsResult[int64]{Val: 123456789}, + CurrentBlockHash: mercury.ObsResult[[]byte]{Val: common.HexToHash("0x123321").Bytes()}, + CurrentBlockTimestamp: mercury.ObsResult[uint64]{Val: 987654321}, + }, + RepTimestamp: types.ReportTimestamp{ + ConfigDigest: types.ConfigDigest{2}, + Epoch: 11, + Round: 22, + }, + } + + expectedTelemetry := telem.EnhancedEAMercury{ + DataSource: "VIEW_FUNCTION", + DpBenchmarkPrice: 1178718957397490400, + DpBid: 1178718957397490400, + DpAsk: 1178718957397490400, + CurrentBlockNumber: 123456789, + CurrentBlockHash: common.HexToHash("0x123321").String(), + CurrentBlockTimestamp: 987654321, + BridgeTaskRunStartedTimestamp: taskRunResults[0].CreatedAt.UnixMilli(), + BridgeTaskRunEndedTimestamp: taskRunResults[0].FinishedAt.Time.UnixMilli(), + ProviderRequestedTimestamp: 1726243598046, + ProviderReceivedTimestamp: 1726243598341, + ProviderDataStreamEstablished: 0, + ProviderIndicatedTime: 0, + Feed: common.HexToHash("0x111").String(), + ObservationBenchmarkPrice: 111111, + ObservationBid: 222222, + ObservationAsk: 333333, + ConfigDigest: "0200000000000000000000000000000000000000000000000000000000000000", + Round: 22, + Epoch: 11, + BridgeRequestData: `{"data":{"address":"0x1234","signature":"function stEthPerToken() external view returns (uint256)"}}`, + AssetSymbol: "0x1234", + ObservationBenchmarkPriceString: "111111", + ObservationBidString: "222222", + ObservationAskString: "333333", + } + + expectedMessage, _ := proto.Marshal(&expectedTelemetry) + wg.Wait() + require.Equal(t, expectedMessage, sentMessage) + + chDone <- struct{}{} } func TestCollectMercuryEnhancedTelemetryV1(t *testing.T) { @@ -619,6 +889,7 @@ func TestCollectMercuryEnhancedTelemetryV1(t *testing.T) { ConfigDigest: "0200000000000000000000000000000000000000000000000000000000000000", Round: 22, Epoch: 11, + BridgeRequestData: `{"data":{"to":"LINK","from":"USD"}}`, AssetSymbol: "USD/LINK", ObservationBenchmarkPriceString: "111111", ObservationBidString: "222222", @@ -660,7 +931,7 @@ func TestCollectMercuryEnhancedTelemetryV1(t *testing.T) { wg.Wait() require.Equal(t, 2, logs.Len()) - require.Contains(t, logs.All()[0].Message, `cannot get bridge response from bridge task, job=0, id=ds1, name="test-mercury-bridge-1"`) + require.Contains(t, logs.All()[0].Message, `cannot get bridge response from bridge task, id=ds1, name="test-mercury-bridge-1"`) require.Contains(t, logs.All()[1].Message, "cannot parse EA telemetry") chDone <- struct{}{} } @@ -732,6 +1003,7 @@ func TestCollectMercuryEnhancedTelemetryV2(t *testing.T) { ConfigDigest: "0200000000000000000000000000000000000000000000000000000000000000", Round: 22, Epoch: 11, + BridgeRequestData: `{"data":{"to":"LINK","from":"USD"}}`, AssetSymbol: "USD/LINK", ObservationBenchmarkPriceString: "111111", MaxFinalizedTimestamp: 321, @@ -781,3 +1053,81 @@ func TestCollectMercuryEnhancedTelemetryV2(t *testing.T) { require.Contains(t, logs.All()[3].Message, "cannot parse enhanced EA telemetry bid price") chDone <- struct{}{} } + +func TestCollectMercuryEnhancedTelemetryV4(t *testing.T) { + ingressClient := mocks.NewTelemetryService(t) + ingressAgent := telemetry.NewIngressAgentWrapper(ingressClient) + monitoringEndpoint := ingressAgent.GenMonitoringEndpoint("test-network", "test-chainID", "0xa", synchronization.EnhancedEAMercury) + + sentMessageCh := make(chan []byte) + ingressClient.On("Send", mock.Anything, mock.AnythingOfType("[]uint8"), mock.AnythingOfType("string"), mock.AnythingOfType("TelemetryType")).Return().Run(func(args mock.Arguments) { + sentMessageCh <- args[1].([]byte) + }) + + lggr, _ := logger.TestLoggerObserved(t, zap.WarnLevel) + chTelem := make(chan EnhancedTelemetryMercuryData, 100) + chDone := make(chan struct{}) + feedID := common.HexToHash("0x0004") + e := EnhancedTelemetryService[EnhancedTelemetryMercuryData]{ + chDone: chDone, + chTelem: chTelem, + job: &job.Job{ + Type: job.Type(pipeline.OffchainReporting2JobType), + OCR2OracleSpec: &job.OCR2OracleSpec{ + CaptureEATelemetry: true, + FeedID: &feedID, + }, + }, + lggr: lggr, + monitoringEndpoint: monitoringEndpoint, + } + servicetest.Run(t, &e) + + chTelem <- EnhancedTelemetryMercuryData{ + TaskRunResults: trrsMercuryV4, + FeedVersion: 4, + V4Observation: &mercuryv4.Observation{ + BenchmarkPrice: mercury.ObsResult[*big.Int]{Val: big.NewInt(111111)}, + MarketStatus: mercury.ObsResult[uint32]{Val: 2}, + MaxFinalizedTimestamp: mercury.ObsResult[int64]{Val: 321}, + LinkPrice: mercury.ObsResult[*big.Int]{Val: big.NewInt(4321)}, + NativePrice: mercury.ObsResult[*big.Int]{Val: big.NewInt(54321)}, + }, + RepTimestamp: types.ReportTimestamp{ + ConfigDigest: types.ConfigDigest{2}, + Epoch: 11, + Round: 22, + }, + } + + expectedPricingTelemetry := telem.EnhancedEAMercury{ + DataSource: "data-source-name", + DpBenchmarkPrice: 123456.123456, + BridgeTaskRunStartedTimestamp: trrsMercuryV4[0].CreatedAt.UnixMilli(), + BridgeTaskRunEndedTimestamp: trrsMercuryV4[0].FinishedAt.Time.UnixMilli(), + ProviderRequestedTimestamp: 92233720368547760, + ProviderReceivedTimestamp: -92233720368547760, + ProviderDataStreamEstablished: 1, + ProviderIndicatedTime: -123456789, + Feed: common.HexToHash("0x0004").String(), + ObservationBenchmarkPrice: 111111, + ObservationMarketStatus: 2, + ConfigDigest: "0200000000000000000000000000000000000000000000000000000000000000", + Round: 22, + Epoch: 11, + AssetSymbol: "USD/LINK", + ObservationBenchmarkPriceString: "111111", + MaxFinalizedTimestamp: 321, + LinkPrice: 4321, + NativePrice: 54321, + Version: 4, + BridgeRequestData: `{"data":{"to":"LINK","from":"USD"}}`, + } + expectedPricingMessage, _ := proto.Marshal(&expectedPricingTelemetry) + require.Equal(t, expectedPricingMessage, <-sentMessageCh) + + chDone <- struct{}{} + + // Verify that no other telemetry is sent. + require.Len(t, sentMessageCh, 0) +} diff --git a/core/services/pipeline/common.go b/core/services/pipeline/common.go index 1b36c8a664..50611ee32a 100644 --- a/core/services/pipeline/common.go +++ b/core/services/pipeline/common.go @@ -11,14 +11,15 @@ import ( "strings" "time" + "github.com/go-viper/mapstructure/v2" "github.com/google/uuid" - "github.com/mitchellh/mapstructure" pkgerrors "github.com/pkg/errors" "gopkg.in/guregu/null.v4" commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" cutils "github.com/smartcontractkit/chainlink-common/pkg/utils" "github.com/smartcontractkit/chainlink-common/pkg/utils/jsonserializable" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" "github.com/smartcontractkit/chainlink/v2/core/logger" cnull "github.com/smartcontractkit/chainlink/v2/core/null" @@ -59,6 +60,8 @@ type ( TaskRetries() uint32 TaskMinBackoff() time.Duration TaskMaxBackoff() time.Duration + TaskTags() string + GetDescendantTasks() []Task } Config interface { @@ -262,6 +265,16 @@ func (trrs TaskRunResults) Terminals() (terminals []TaskRunResult) { return } +// GetNextTaskOf returns the task with the next id or nil if it does not exist +func (trrs *TaskRunResults) GetTaskRunResultOf(task Task) *TaskRunResult { + for _, trr := range *trrs { + if trr.Task.Base().id == task.Base().id { + return &trr + } + } + return nil +} + // GetNextTaskOf returns the task with the next id or nil if it does not exist func (trrs *TaskRunResults) GetNextTaskOf(task TaskRunResult) *TaskRunResult { nextID := task.Task.Base().id + 1 diff --git a/core/services/pipeline/common_test.go b/core/services/pipeline/common_test.go index ce545ec14a..ed7998b79e 100644 --- a/core/services/pipeline/common_test.go +++ b/core/services/pipeline/common_test.go @@ -17,6 +17,14 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" ) +func TestAtrributesAttribute(t *testing.T) { + a := `ds1 [type=http method=GET tags=<{"attribute1":"value1", "attribute2":42}>];` + p, err := pipeline.Parse(a) + require.NoError(t, err) + task := p.Tasks[0] + assert.Equal(t, "{\"attribute1\":\"value1\", \"attribute2\":42}", task.TaskTags()) +} + func TestTimeoutAttribute(t *testing.T) { t.Parallel() @@ -320,3 +328,69 @@ func TestGetNextTaskOf(t *testing.T) { nextTask = trrs.GetNextTaskOf(*nextTask) assert.Empty(t, nextTask) } + +func TestGetDescendantTasks(t *testing.T) { + t.Parallel() + + t.Run("GetDescendantTasks with multiple levels of tasks", func(t *testing.T) { + l3T2 := pipeline.AnyTask{ + BaseTask: pipeline.NewBaseTask(6, "l3T2", nil, nil, 1), + } + l3T1 := pipeline.MedianTask{ + BaseTask: pipeline.NewBaseTask(5, "l3T1", nil, nil, 1), + } + l2T1 := pipeline.MultiplyTask{ + BaseTask: pipeline.NewBaseTask(4, "l2T1", nil, []pipeline.Task{&l3T1, &l3T2}, 1), + } + l1T1 := pipeline.JSONParseTask{ + BaseTask: pipeline.NewBaseTask(3, "l1T1", nil, []pipeline.Task{&l2T1}, 2), + } + l1T2 := pipeline.JSONParseTask{ + BaseTask: pipeline.NewBaseTask(2, "l1T2", nil, nil, 3), + } + l1T3 := pipeline.JSONParseTask{ + BaseTask: pipeline.NewBaseTask(1, "l1T3", nil, nil, 4), + } + + baseTask := pipeline.BridgeTask{ + Name: "bridge-task", + BaseTask: pipeline.NewBaseTask(0, "baseTask", nil, []pipeline.Task{&l1T1, &l1T2, &l1T3}, 0), + } + + descendents := baseTask.GetDescendantTasks() + assert.Len(t, descendents, 6) + }) + + t.Run("GetDescendantTasks with duplicate tasks defined", func(t *testing.T) { + l2T1 := pipeline.JSONParseTask{ + BaseTask: pipeline.NewBaseTask(2, "l1T2", nil, nil, 3), + } + l1T1 := pipeline.JSONParseTask{ + BaseTask: pipeline.NewBaseTask(1, "l1T2", nil, []pipeline.Task{&l2T1, &l2T1, &l2T1}, 3), + } + taskWithRepeats := pipeline.BridgeTask{ + Name: "bridge-task", + BaseTask: pipeline.NewBaseTask(0, "taskWithRepeats", nil, []pipeline.Task{&l1T1, &l1T1, &l1T1}, 0), + } + descendents := taskWithRepeats.GetDescendantTasks() + assert.Len(t, descendents, 2) + }) + + t.Run("GetDescendantTasks with nil output tasks", func(t *testing.T) { + taskWithRepeats := pipeline.BridgeTask{ + Name: "bridge-task", + BaseTask: pipeline.NewBaseTask(0, "taskWithRepeats", nil, nil, 0), + } + descendents := taskWithRepeats.GetDescendantTasks() + assert.Len(t, descendents, 0) + }) + + t.Run("GetDescendantTasks with empty list of output tasks", func(t *testing.T) { + taskWithRepeats := pipeline.BridgeTask{ + Name: "bridge-task", + BaseTask: pipeline.NewBaseTask(0, "taskWithRepeats", nil, []pipeline.Task{}, 0), + } + descendents := taskWithRepeats.GetDescendantTasks() + assert.Len(t, descendents, 0) + }) +} diff --git a/core/services/pipeline/runner_test.go b/core/services/pipeline/runner_test.go index 022a77c947..ea30b3ff08 100644 --- a/core/services/pipeline/runner_test.go +++ b/core/services/pipeline/runner_test.go @@ -131,6 +131,50 @@ ds5 [type=http method="GET" url="%s" index=2] require.Len(t, errorResults, 3) } +func Test_PipelineRunner_ExecuteEthAbiDecode(t *testing.T) { + db := pgtest.NewSqlxDB(t) + cfg := configtest.NewTestGeneralConfig(t) + + mockResult := `{"data":{"result":"0x000000000000000000000000000000000000000000000000105ba6a589b23a81"}}` + s1 := httptest.NewServer(NewMockHandler(mockResult)) + defer s1.Close() + + bridgeFeedURL, err := url.ParseRequestURI(s1.URL) + require.NoError(t, err) + + _, bt := cltest.MustCreateBridge(t, db, cltest.BridgeOpts{URL: bridgeFeedURL.String()}) + + btORM := bridgesMocks.NewORM(t) + btORM.On("FindBridge", mock.Anything, bt.Name).Return(*bt, nil).Once() + + r, _ := newRunner(t, db, btORM, cfg) + + s := fmt.Sprintf(` + ds1 [type=bridge name="%s" timeout=0 requestData=<{"data": {"address": "0x1234"}}>] + ds1_parse [type=jsonparse path="data,result"] + ds1_decode [type=ethabidecode abi="int256 data" data="$(ds1_parse)"]; + ds1_value [type="multiply" input="$(ds1_decode.data)" times=1] + + ds1->ds1_parse->ds1_decode->ds1_value + +`, bt.Name.String()) + d, err := pipeline.Parse(s) + require.NoError(t, err) + + spec := pipeline.Spec{DotDagSource: s} + vars := pipeline.NewVarsFrom(nil) + + _, trrs, err := r.ExecuteRun(testutils.Context(t), spec, vars) + require.NoError(t, err) + require.Len(t, trrs, len(d.Tasks)) + + finalResults := trrs.FinalResult() + + val := finalResults.Values[0].(decimal.Decimal) + + assert.Equal(t, decimal.NewFromInt(1178718957397490305), val) +} + type taskRunWithVars struct { bridgeName string ds2URL, ds4URL string diff --git a/core/services/pipeline/task.base.go b/core/services/pipeline/task.base.go index 7a62f4e7ff..3e1db5fcdb 100644 --- a/core/services/pipeline/task.base.go +++ b/core/services/pipeline/task.base.go @@ -22,6 +22,8 @@ type BaseTask struct { MinBackoff time.Duration `mapstructure:"minBackoff"` MaxBackoff time.Duration `mapstructure:"maxBackoff"` + Tags string `mapstructure:"tags" json:"-"` + uuid uuid.UUID } @@ -77,3 +79,32 @@ func (t BaseTask) TaskMaxBackoff() time.Duration { } return time.Minute } + +func (t BaseTask) TaskTags() string { + return t.Tags +} + +// GetDescendantTasks retrieves all descendant tasks of a given task +func (t BaseTask) GetDescendantTasks() []Task { + if len(t.outputs) == 0 { + return []Task{} + } + var descendants []Task + queue := append([]Task{}, t.outputs...) + visited := make(map[int]bool) + + for len(queue) > 0 { + currentTask := queue[0] + queue = queue[1:] + + taskID := currentTask.ID() + if visited[taskID] { + continue + } + visited[taskID] = true + descendants = append(descendants, currentTask) + queue = append(queue, currentTask.Outputs()...) + } + + return descendants +} diff --git a/core/services/pipeline/task.bridge_test.go b/core/services/pipeline/task.bridge_test.go index d7519232eb..cd81f8656f 100644 --- a/core/services/pipeline/task.bridge_test.go +++ b/core/services/pipeline/task.bridge_test.go @@ -117,6 +117,18 @@ func mustReadFile(t testing.TB, file string) string { return string(content) } +// NewMockHandler returns an http.HandlerFunc that responds with the given payload for any request +func NewMockHandler(payload string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte(payload)) + if err != nil { + http.Error(w, "Failed to write response", http.StatusInternalServerError) + } + } +} + func fakePriceResponder(t *testing.T, requestData map[string]interface{}, result decimal.Decimal, inputKey string, expectedInput interface{}) http.Handler { t.Helper() diff --git a/core/services/pipeline/task.eth_abi_decode_test.go b/core/services/pipeline/task.eth_abi_decode_test.go index 3c7f5b4776..565e8b485d 100644 --- a/core/services/pipeline/task.eth_abi_decode_test.go +++ b/core/services/pipeline/task.eth_abi_decode_test.go @@ -25,6 +25,20 @@ var testsABIDecode = []struct { expectedErrorCause error expectedErrorContains string }{ + { + "uint256", + "uint256 data", + "$(data)", + NewVarsFrom(map[string]interface{}{ + "data": "0x000000000000000000000000000000000000000000000000105ba6a589b23a81", + }), + nil, + map[string]interface{}{ + "data": big.NewInt(1178718957397490305), + }, + nil, + "", + }, { "uint256, bool, int256, string", "uint256 u, bool b, int256 i, string s", diff --git a/core/services/pipeline/task.eth_tx.go b/core/services/pipeline/task.eth_tx.go index 964591cacd..506a2518f7 100644 --- a/core/services/pipeline/task.eth_tx.go +++ b/core/services/pipeline/task.eth_tx.go @@ -8,13 +8,14 @@ import ( "strconv" "github.com/ethereum/go-ethereum/common" - "github.com/mitchellh/mapstructure" + "github.com/go-viper/mapstructure/v2" "github.com/pkg/errors" "go.uber.org/multierr" "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/chainlink-common/pkg/utils/hex" clnull "github.com/smartcontractkit/chainlink-common/pkg/utils/null" + txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" diff --git a/core/services/registrysyncer/local_registry.go b/core/services/registrysyncer/local_registry.go index 4e4a632bf8..d4bf4a49f5 100644 --- a/core/services/registrysyncer/local_registry.go +++ b/core/services/registrysyncer/local_registry.go @@ -16,7 +16,11 @@ type DonID uint32 type DON struct { capabilities.DON - CapabilityConfigurations map[string]capabilities.CapabilityConfiguration + CapabilityConfigurations map[string]CapabilityConfiguration +} + +type CapabilityConfiguration struct { + Config []byte } type Capability struct { @@ -26,22 +30,37 @@ type Capability struct { type LocalRegistry struct { lggr logger.Logger - peerWrapper p2ptypes.PeerWrapper + getPeerID func() (p2ptypes.PeerID, error) IDsToDONs map[DonID]DON IDsToNodes map[p2ptypes.PeerID]kcr.CapabilitiesRegistryNodeInfo IDsToCapabilities map[string]Capability } +func NewLocalRegistry( + lggr logger.Logger, + getPeerID func() (p2ptypes.PeerID, error), + IDsToDONs map[DonID]DON, + IDsToNodes map[p2ptypes.PeerID]kcr.CapabilitiesRegistryNodeInfo, + IDsToCapabilities map[string]Capability, +) LocalRegistry { + return LocalRegistry{ + lggr: lggr.Named("LocalRegistry"), + getPeerID: getPeerID, + IDsToDONs: IDsToDONs, + IDsToNodes: IDsToNodes, + IDsToCapabilities: IDsToCapabilities, + } +} + func (l *LocalRegistry) LocalNode(ctx context.Context) (capabilities.Node, error) { // Load the current nodes PeerWrapper, this gets us the current node's // PeerID, allowing us to contextualize registry information in terms of DON ownership // (eg. get my current DON configuration, etc). - if l.peerWrapper.GetPeer() == nil { + pid, err := l.getPeerID() + if err != nil { return capabilities.Node{}, errors.New("unable to get local node: peerWrapper hasn't started yet") } - pid := l.peerWrapper.GetPeer().ID() - var workflowDON capabilities.DON capabilityDONs := []capabilities.DON{} for _, d := range l.IDsToDONs { @@ -70,15 +89,15 @@ func (l *LocalRegistry) LocalNode(ctx context.Context) (capabilities.Node, error }, nil } -func (l *LocalRegistry) ConfigForCapability(ctx context.Context, capabilityID string, donID uint32) (capabilities.CapabilityConfiguration, error) { +func (l *LocalRegistry) ConfigForCapability(ctx context.Context, capabilityID string, donID uint32) (CapabilityConfiguration, error) { d, ok := l.IDsToDONs[DonID(donID)] if !ok { - return capabilities.CapabilityConfiguration{}, fmt.Errorf("could not find don %d", donID) + return CapabilityConfiguration{}, fmt.Errorf("could not find don %d", donID) } cc, ok := d.CapabilityConfigurations[capabilityID] if !ok { - return capabilities.CapabilityConfiguration{}, fmt.Errorf("could not find capability configuration for capability %s and donID %d", capabilityID, donID) + return CapabilityConfiguration{}, fmt.Errorf("could not find capability configuration for capability %s and donID %d", capabilityID, donID) } return cc, nil diff --git a/core/services/registrysyncer/mocks/orm.go b/core/services/registrysyncer/mocks/orm.go new file mode 100644 index 0000000000..d7777ecb6e --- /dev/null +++ b/core/services/registrysyncer/mocks/orm.go @@ -0,0 +1,142 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mocks + +import ( + context "context" + + registrysyncer "github.com/smartcontractkit/chainlink/v2/core/services/registrysyncer" + mock "github.com/stretchr/testify/mock" +) + +// ORM is an autogenerated mock type for the ORM type +type ORM struct { + mock.Mock +} + +type ORM_Expecter struct { + mock *mock.Mock +} + +func (_m *ORM) EXPECT() *ORM_Expecter { + return &ORM_Expecter{mock: &_m.Mock} +} + +// AddLocalRegistry provides a mock function with given fields: ctx, localRegistry +func (_m *ORM) AddLocalRegistry(ctx context.Context, localRegistry registrysyncer.LocalRegistry) error { + ret := _m.Called(ctx, localRegistry) + + if len(ret) == 0 { + panic("no return value specified for AddLocalRegistry") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, registrysyncer.LocalRegistry) error); ok { + r0 = rf(ctx, localRegistry) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ORM_AddLocalRegistry_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddLocalRegistry' +type ORM_AddLocalRegistry_Call struct { + *mock.Call +} + +// AddLocalRegistry is a helper method to define mock.On call +// - ctx context.Context +// - localRegistry registrysyncer.LocalRegistry +func (_e *ORM_Expecter) AddLocalRegistry(ctx interface{}, localRegistry interface{}) *ORM_AddLocalRegistry_Call { + return &ORM_AddLocalRegistry_Call{Call: _e.mock.On("AddLocalRegistry", ctx, localRegistry)} +} + +func (_c *ORM_AddLocalRegistry_Call) Run(run func(ctx context.Context, localRegistry registrysyncer.LocalRegistry)) *ORM_AddLocalRegistry_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(registrysyncer.LocalRegistry)) + }) + return _c +} + +func (_c *ORM_AddLocalRegistry_Call) Return(_a0 error) *ORM_AddLocalRegistry_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ORM_AddLocalRegistry_Call) RunAndReturn(run func(context.Context, registrysyncer.LocalRegistry) error) *ORM_AddLocalRegistry_Call { + _c.Call.Return(run) + return _c +} + +// LatestLocalRegistry provides a mock function with given fields: ctx +func (_m *ORM) LatestLocalRegistry(ctx context.Context) (*registrysyncer.LocalRegistry, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for LatestLocalRegistry") + } + + var r0 *registrysyncer.LocalRegistry + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*registrysyncer.LocalRegistry, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) *registrysyncer.LocalRegistry); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*registrysyncer.LocalRegistry) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ORM_LatestLocalRegistry_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LatestLocalRegistry' +type ORM_LatestLocalRegistry_Call struct { + *mock.Call +} + +// LatestLocalRegistry is a helper method to define mock.On call +// - ctx context.Context +func (_e *ORM_Expecter) LatestLocalRegistry(ctx interface{}) *ORM_LatestLocalRegistry_Call { + return &ORM_LatestLocalRegistry_Call{Call: _e.mock.On("LatestLocalRegistry", ctx)} +} + +func (_c *ORM_LatestLocalRegistry_Call) Run(run func(ctx context.Context)) *ORM_LatestLocalRegistry_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *ORM_LatestLocalRegistry_Call) Return(_a0 *registrysyncer.LocalRegistry, _a1 error) *ORM_LatestLocalRegistry_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ORM_LatestLocalRegistry_Call) RunAndReturn(run func(context.Context) (*registrysyncer.LocalRegistry, error)) *ORM_LatestLocalRegistry_Call { + _c.Call.Return(run) + return _c +} + +// NewORM creates a new instance of ORM. 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 NewORM(t interface { + mock.TestingT + Cleanup(func()) +}) *ORM { + mock := &ORM{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/services/registrysyncer/orm.go b/core/services/registrysyncer/orm.go new file mode 100644 index 0000000000..cb08eaafea --- /dev/null +++ b/core/services/registrysyncer/orm.go @@ -0,0 +1,167 @@ +package registrysyncer + +import ( + "context" + "crypto/sha256" + "encoding/json" + "fmt" + "math/big" + + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" + + kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" + "github.com/smartcontractkit/chainlink/v2/core/logger" + p2ptypes "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types" +) + +type capabilitiesRegistryNodeInfo struct { + NodeOperatorId uint32 `json:"nodeOperatorId"` + ConfigCount uint32 `json:"configCount"` + WorkflowDONId uint32 `json:"workflowDONId"` + Signer p2ptypes.PeerID `json:"signer"` + P2pId p2ptypes.PeerID `json:"p2pId"` + HashedCapabilityIds []p2ptypes.PeerID `json:"hashedCapabilityIds"` + CapabilitiesDONIds []string `json:"capabilitiesDONIds"` +} + +func (l *LocalRegistry) MarshalJSON() ([]byte, error) { + idsToNodes := make(map[p2ptypes.PeerID]capabilitiesRegistryNodeInfo) + for k, v := range l.IDsToNodes { + hashedCapabilityIds := make([]p2ptypes.PeerID, len(v.HashedCapabilityIds)) + for i, id := range v.HashedCapabilityIds { + hashedCapabilityIds[i] = p2ptypes.PeerID(id[:]) + } + capabilitiesDONIds := make([]string, len(v.CapabilitiesDONIds)) + for i, id := range v.CapabilitiesDONIds { + capabilitiesDONIds[i] = id.String() + } + idsToNodes[k] = capabilitiesRegistryNodeInfo{ + NodeOperatorId: v.NodeOperatorId, + ConfigCount: v.ConfigCount, + WorkflowDONId: v.WorkflowDONId, + Signer: p2ptypes.PeerID(v.Signer[:]), + P2pId: p2ptypes.PeerID(v.P2pId[:]), + HashedCapabilityIds: hashedCapabilityIds, + CapabilitiesDONIds: capabilitiesDONIds, + } + } + + b, err := json.Marshal(&struct { + IDsToDONs map[DonID]DON + IDsToNodes map[p2ptypes.PeerID]capabilitiesRegistryNodeInfo + IDsToCapabilities map[string]Capability + }{ + IDsToDONs: l.IDsToDONs, + IDsToNodes: idsToNodes, + IDsToCapabilities: l.IDsToCapabilities, + }) + if err != nil { + return []byte{}, err + } + return b, nil +} + +func (l *LocalRegistry) UnmarshalJSON(data []byte) error { + temp := struct { + IDsToDONs map[DonID]DON + IDsToNodes map[p2ptypes.PeerID]capabilitiesRegistryNodeInfo + IDsToCapabilities map[string]Capability + }{ + IDsToDONs: make(map[DonID]DON), + IDsToNodes: make(map[p2ptypes.PeerID]capabilitiesRegistryNodeInfo), + IDsToCapabilities: make(map[string]Capability), + } + + if err := json.Unmarshal(data, &temp); err != nil { + return fmt.Errorf("failed to unmarshal state: %w", err) + } + + l.IDsToDONs = temp.IDsToDONs + + l.IDsToNodes = make(map[p2ptypes.PeerID]kcr.CapabilitiesRegistryNodeInfo) + for peerID, v := range temp.IDsToNodes { + hashedCapabilityIds := make([][32]byte, len(v.HashedCapabilityIds)) + for i, id := range v.HashedCapabilityIds { + copy(hashedCapabilityIds[i][:], id[:]) + } + + capabilitiesDONIds := make([]*big.Int, len(v.CapabilitiesDONIds)) + for i, id := range v.CapabilitiesDONIds { + bigInt := new(big.Int) + bigInt.SetString(id, 10) + capabilitiesDONIds[i] = bigInt + } + l.IDsToNodes[peerID] = kcr.CapabilitiesRegistryNodeInfo{ + NodeOperatorId: v.NodeOperatorId, + ConfigCount: v.ConfigCount, + WorkflowDONId: v.WorkflowDONId, + Signer: v.Signer, + P2pId: v.P2pId, + HashedCapabilityIds: hashedCapabilityIds, + CapabilitiesDONIds: capabilitiesDONIds, + } + } + + l.IDsToCapabilities = temp.IDsToCapabilities + + return nil +} + +type ORM interface { + AddLocalRegistry(ctx context.Context, localRegistry LocalRegistry) error + LatestLocalRegistry(ctx context.Context) (*LocalRegistry, error) +} + +type orm struct { + ds sqlutil.DataSource + lggr logger.Logger +} + +var _ ORM = (*orm)(nil) + +func NewORM(ds sqlutil.DataSource, lggr logger.Logger) orm { + namedLogger := lggr.Named("RegistrySyncerORM") + return orm{ + ds: ds, + lggr: namedLogger, + } +} + +func (orm orm) AddLocalRegistry(ctx context.Context, localRegistry LocalRegistry) error { + return sqlutil.TransactDataSource(ctx, orm.ds, nil, func(tx sqlutil.DataSource) error { + localRegistryJSON, err := localRegistry.MarshalJSON() + if err != nil { + return err + } + hash := sha256.Sum256(localRegistryJSON) + _, err = tx.ExecContext( + ctx, + `INSERT INTO registry_syncer_states (data, data_hash) VALUES ($1, $2) ON CONFLICT (data_hash) DO NOTHING`, + localRegistryJSON, fmt.Sprintf("%x", hash[:]), + ) + if err != nil { + return err + } + _, err = tx.ExecContext(ctx, `DELETE FROM registry_syncer_states +WHERE data_hash NOT IN ( + SELECT data_hash FROM registry_syncer_states + ORDER BY id DESC + LIMIT 10 +);`) + return err + }) +} + +func (orm orm) LatestLocalRegistry(ctx context.Context) (*LocalRegistry, error) { + var localRegistry LocalRegistry + var localRegistryJSON string + err := orm.ds.GetContext(ctx, &localRegistryJSON, `SELECT data FROM registry_syncer_states ORDER BY id DESC LIMIT 1`) + if err != nil { + return nil, err + } + err = localRegistry.UnmarshalJSON([]byte(localRegistryJSON)) + if err != nil { + return nil, err + } + return &localRegistry, nil +} diff --git a/core/services/registrysyncer/orm_test.go b/core/services/registrysyncer/orm_test.go new file mode 100644 index 0000000000..03772ea22b --- /dev/null +++ b/core/services/registrysyncer/orm_test.go @@ -0,0 +1,145 @@ +package registrysyncer_test + +import ( + "encoding/hex" + "math/big" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/durationpb" + + ragetypes "github.com/smartcontractkit/libocr/ragep2p/types" + + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + capabilitiespb "github.com/smartcontractkit/chainlink-common/pkg/capabilities/pb" + "github.com/smartcontractkit/chainlink-common/pkg/values" + + kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types" + "github.com/smartcontractkit/chainlink/v2/core/services/registrysyncer" +) + +func TestRegistrySyncerORM_InsertAndRetrieval(t *testing.T) { + db := pgtest.NewSqlxDB(t) + ctx := testutils.Context(t) + lggr := logger.TestLogger(t) + orm := registrysyncer.NewORM(db, lggr) + + var states []registrysyncer.LocalRegistry + for i := 0; i < 11; i++ { + state := generateState(t) + err := orm.AddLocalRegistry(ctx, state) + require.NoError(t, err) + states = append(states, state) + } + + var count int + err := db.Get(&count, `SELECT count(*) FROM registry_syncer_states`) + require.NoError(t, err) + assert.Equal(t, 10, count) + + state, err := orm.LatestLocalRegistry(ctx) + require.NoError(t, err) + assert.Equal(t, states[10], *state) +} + +func generateState(t *testing.T) registrysyncer.LocalRegistry { + dID := uint32(1) + var pid ragetypes.PeerID + err := pid.UnmarshalText([]byte("12D3KooWBCF1XT5Wi8FzfgNCqRL76Swv8TRU3TiD4QiJm8NMNX7N")) + require.NoError(t, err) + nodes := [][32]byte{ + pid, + randomWord(), + randomWord(), + randomWord(), + } + capabilityID := randomWord() + capabilityID2 := randomWord() + capabilityIDStr := hex.EncodeToString(capabilityID[:]) + capabilityID2Str := hex.EncodeToString(capabilityID2[:]) + + config := &capabilitiespb.CapabilityConfig{ + DefaultConfig: values.Proto(values.EmptyMap()).GetMapValue(), + RemoteConfig: &capabilitiespb.CapabilityConfig_RemoteTriggerConfig{ + RemoteTriggerConfig: &capabilitiespb.RemoteTriggerConfig{ + RegistrationRefresh: durationpb.New(20 * time.Second), + RegistrationExpiry: durationpb.New(60 * time.Second), + // F + 1 + MinResponsesToAggregate: uint32(1) + 1, + MessageExpiry: durationpb.New(120 * time.Second), + }, + }, + } + configb, err := proto.Marshal(config) + require.NoError(t, err) + + return registrysyncer.LocalRegistry{ + IDsToDONs: map[registrysyncer.DonID]registrysyncer.DON{ + registrysyncer.DonID(dID): { + DON: capabilities.DON{ + ID: dID, + ConfigVersion: uint32(0), + F: uint8(1), + IsPublic: true, + AcceptsWorkflows: true, + Members: toPeerIDs(nodes), + }, + CapabilityConfigurations: map[string]registrysyncer.CapabilityConfiguration{ + capabilityIDStr: { + Config: configb, + }, + capabilityID2Str: { + Config: configb, + }, + }, + }, + }, + IDsToCapabilities: map[string]registrysyncer.Capability{ + capabilityIDStr: { + ID: capabilityIDStr, + CapabilityType: capabilities.CapabilityTypeAction, + }, + capabilityID2Str: { + ID: capabilityID2Str, + CapabilityType: capabilities.CapabilityTypeConsensus, + }, + }, + IDsToNodes: map[types.PeerID]kcr.CapabilitiesRegistryNodeInfo{ + nodes[0]: { + NodeOperatorId: 1, + Signer: randomWord(), + P2pId: nodes[0], + HashedCapabilityIds: [][32]byte{capabilityID, capabilityID2}, + CapabilitiesDONIds: []*big.Int{}, + }, + nodes[1]: { + NodeOperatorId: 1, + Signer: randomWord(), + P2pId: nodes[1], + HashedCapabilityIds: [][32]byte{capabilityID, capabilityID2}, + CapabilitiesDONIds: []*big.Int{}, + }, + nodes[2]: { + NodeOperatorId: 1, + Signer: randomWord(), + P2pId: nodes[2], + HashedCapabilityIds: [][32]byte{capabilityID, capabilityID2}, + CapabilitiesDONIds: []*big.Int{}, + }, + nodes[3]: { + NodeOperatorId: 1, + Signer: randomWord(), + P2pId: nodes[3], + HashedCapabilityIds: [][32]byte{capabilityID, capabilityID2}, + CapabilitiesDONIds: []*big.Int{}, + }, + }, + } +} diff --git a/core/services/registrysyncer/syncer.go b/core/services/registrysyncer/syncer.go index 6a44ff561d..1ef78b6edc 100644 --- a/core/services/registrysyncer/syncer.go +++ b/core/services/registrysyncer/syncer.go @@ -4,17 +4,14 @@ import ( "context" "encoding/json" "fmt" + "math/big" "sync" "time" - "google.golang.org/protobuf/proto" - "github.com/smartcontractkit/chainlink-common/pkg/capabilities" - capabilitiespb "github.com/smartcontractkit/chainlink-common/pkg/capabilities/pb" "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" - "github.com/smartcontractkit/chainlink-common/pkg/values" kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -31,15 +28,33 @@ type Syncer interface { AddLauncher(h ...Launcher) } +type ContractReaderFactory interface { + NewContractReader(context.Context, []byte) (types.ContractReader, error) +} + +type RegistrySyncer interface { + Sync(ctx context.Context, isInitialSync bool) error + AddLauncher(launchers ...Launcher) + Start(ctx context.Context) error + Close() error + Ready() error + HealthReport() map[string]error + Name() string +} + type registrySyncer struct { services.StateMachine - stopCh services.StopChan - launchers []Launcher - reader types.ContractReader - initReader func(ctx context.Context, lggr logger.Logger, relayer contractReaderFactory, registryAddress string) (types.ContractReader, error) - relayer contractReaderFactory - registryAddress string - peerWrapper p2ptypes.PeerWrapper + stopCh services.StopChan + launchers []Launcher + reader types.ContractReader + initReader func(ctx context.Context, lggr logger.Logger, relayer ContractReaderFactory, capabilitiesContract types.BoundContract) (types.ContractReader, error) + relayer ContractReaderFactory + capabilitiesContract types.BoundContract + getPeerID func() (p2ptypes.PeerID, error) + + orm ORM + + updateChan chan *LocalRegistry wg sync.WaitGroup lggr logger.Logger @@ -55,29 +70,30 @@ var ( // New instantiates a new RegistrySyncer func New( lggr logger.Logger, - peerWrapper p2ptypes.PeerWrapper, - relayer contractReaderFactory, + getPeerID func() (p2ptypes.PeerID, error), + relayer ContractReaderFactory, registryAddress string, -) (*registrySyncer, error) { - stopCh := make(services.StopChan) + orm ORM, +) (RegistrySyncer, error) { return ®istrySyncer{ - stopCh: stopCh, - lggr: lggr.Named("RegistrySyncer"), - relayer: relayer, - registryAddress: registryAddress, - initReader: newReader, - peerWrapper: peerWrapper, + stopCh: make(services.StopChan), + updateChan: make(chan *LocalRegistry), + lggr: lggr.Named("RegistrySyncer"), + relayer: relayer, + capabilitiesContract: types.BoundContract{ + Address: registryAddress, + Name: "CapabilitiesRegistry", + }, + initReader: newReader, + orm: orm, + getPeerID: getPeerID, }, nil } -type contractReaderFactory interface { - NewContractReader(context.Context, []byte) (types.ContractReader, error) -} - // NOTE: this can't be called while initializing the syncer and needs to be called in the sync loop. // This is because Bind() makes an onchain call to verify that the contract address exists, and if // called during initialization, this results in a "no live nodes" error. -func newReader(ctx context.Context, lggr logger.Logger, relayer contractReaderFactory, remoteRegistryAddress string) (types.ContractReader, error) { +func newReader(ctx context.Context, lggr logger.Logger, relayer ContractReaderFactory, capabilitiesContract types.BoundContract) (types.ContractReader, error) { contractReaderConfig := evmrelaytypes.ChainReaderConfig{ Contracts: map[string]evmrelaytypes.ChainContractReader{ "CapabilitiesRegistry": { @@ -107,12 +123,7 @@ func newReader(ctx context.Context, lggr logger.Logger, relayer contractReaderFa return nil, err } - err = cr.Bind(ctx, []types.BoundContract{ - { - Address: remoteRegistryAddress, - Name: "CapabilitiesRegistry", - }, - }) + err = cr.Bind(ctx, []types.BoundContract{capabilitiesContract}) return cr, err } @@ -124,6 +135,11 @@ func (s *registrySyncer) Start(ctx context.Context) error { defer s.wg.Done() s.syncLoop() }() + s.wg.Add(1) + go func() { + defer s.wg.Done() + s.updateStateLoop() + }() return nil }) } @@ -139,7 +155,7 @@ func (s *registrySyncer) syncLoop() { // sync immediately once spinning up syncLoop, as by default a ticker will // fire for the first time at T+N, where N is the interval. s.lggr.Debug("starting initial sync with remote registry") - err := s.sync(ctx) + err := s.Sync(ctx, true) if err != nil { s.lggr.Errorw("failed to sync with remote registry", "error", err) } @@ -150,7 +166,7 @@ func (s *registrySyncer) syncLoop() { return case <-ticker.C: s.lggr.Debug("starting regular sync with the remote registry") - err := s.sync(ctx) + err := s.Sync(ctx, false) if err != nil { s.lggr.Errorw("failed to sync with remote registry", "error", err) } @@ -158,30 +174,30 @@ func (s *registrySyncer) syncLoop() { } } -func unmarshalCapabilityConfig(data []byte) (capabilities.CapabilityConfiguration, error) { - cconf := &capabilitiespb.CapabilityConfig{} - err := proto.Unmarshal(data, cconf) - if err != nil { - return capabilities.CapabilityConfiguration{}, err - } +func (s *registrySyncer) updateStateLoop() { + ctx, cancel := s.stopCh.NewCtx() + defer cancel() - var rtc capabilities.RemoteTriggerConfig - if prtc := cconf.GetRemoteTriggerConfig(); prtc != nil { - rtc.RegistrationRefresh = prtc.RegistrationRefresh.AsDuration() - rtc.RegistrationExpiry = prtc.RegistrationExpiry.AsDuration() - rtc.MinResponsesToAggregate = prtc.MinResponsesToAggregate - rtc.MessageExpiry = prtc.MessageExpiry.AsDuration() + for { + select { + case <-s.stopCh: + return + case localRegistry, ok := <-s.updateChan: + if !ok { + // channel has been closed, terminating. + return + } + if err := s.orm.AddLocalRegistry(ctx, *localRegistry); err != nil { + s.lggr.Errorw("failed to save state to local registry", "error", err) + } + } } - - return capabilities.CapabilityConfiguration{ - DefaultConfig: values.FromMapValueProto(cconf.DefaultConfig), - RemoteTriggerConfig: rtc, - }, nil } func (s *registrySyncer) localRegistry(ctx context.Context) (*LocalRegistry, error) { caps := []kcr.CapabilitiesRegistryCapabilityInfo{} - err := s.reader.GetLatestValue(ctx, "CapabilitiesRegistry", "getCapabilities", primitives.Unconfirmed, nil, &caps) + + err := s.reader.GetLatestValue(ctx, s.capabilitiesContract.ReadIdentifier("getCapabilities"), primitives.Unconfirmed, nil, &caps) if err != nil { return nil, err } @@ -199,28 +215,24 @@ func (s *registrySyncer) localRegistry(ctx context.Context) (*LocalRegistry, err } dons := []kcr.CapabilitiesRegistryDONInfo{} - err = s.reader.GetLatestValue(ctx, "CapabilitiesRegistry", "getDONs", primitives.Unconfirmed, nil, &dons) + + err = s.reader.GetLatestValue(ctx, s.capabilitiesContract.ReadIdentifier("getDONs"), primitives.Unconfirmed, nil, &dons) if err != nil { return nil, err } idsToDONs := map[DonID]DON{} for _, d := range dons { - cc := map[string]capabilities.CapabilityConfiguration{} + cc := map[string]CapabilityConfiguration{} for _, dc := range d.CapabilityConfigurations { cid, ok := hashedIDsToCapabilityIDs[dc.CapabilityId] if !ok { return nil, fmt.Errorf("invariant violation: could not find full ID for hashed ID %s", dc.CapabilityId) } - cconf, innerErr := unmarshalCapabilityConfig(dc.Config) - if innerErr != nil { - return nil, innerErr + cc[cid] = CapabilityConfiguration{ + Config: dc.Config, } - - cconf.RemoteTriggerConfig.ApplyDefaults() - - cc[cid] = cconf } idsToDONs[DonID(d.Id)] = DON{ @@ -230,7 +242,8 @@ func (s *registrySyncer) localRegistry(ctx context.Context) (*LocalRegistry, err } nodes := []kcr.CapabilitiesRegistryNodeInfo{} - err = s.reader.GetLatestValue(ctx, "CapabilitiesRegistry", "getNodes", primitives.Unconfirmed, nil, &nodes) + + err = s.reader.GetLatestValue(ctx, s.capabilitiesContract.ReadIdentifier("getNodes"), primitives.Unconfirmed, nil, &nodes) if err != nil { return nil, err } @@ -242,14 +255,14 @@ func (s *registrySyncer) localRegistry(ctx context.Context) (*LocalRegistry, err return &LocalRegistry{ lggr: s.lggr, - peerWrapper: s.peerWrapper, + getPeerID: s.getPeerID, IDsToDONs: idsToDONs, IDsToCapabilities: idsToCapabilities, IDsToNodes: idsToNodes, }, nil } -func (s *registrySyncer) sync(ctx context.Context) error { +func (s *registrySyncer) Sync(ctx context.Context, isInitialSync bool) error { s.mu.RLock() defer s.mu.RUnlock() @@ -259,7 +272,7 @@ func (s *registrySyncer) sync(ctx context.Context) error { } if s.reader == nil { - reader, err := s.initReader(ctx, s.lggr, s.relayer, s.registryAddress) + reader, err := s.initReader(ctx, s.lggr, s.relayer, s.capabilitiesContract) if err != nil { return err } @@ -267,13 +280,44 @@ func (s *registrySyncer) sync(ctx context.Context) error { s.reader = reader } - lr, err := s.localRegistry(ctx) - if err != nil { - return fmt.Errorf("failed to sync with remote registry: %w", err) + var lr *LocalRegistry + var err error + + if isInitialSync { + s.lggr.Debug("syncing with local registry") + lr, err = s.orm.LatestLocalRegistry(ctx) + if err != nil { + s.lggr.Warnw("failed to sync with local registry, using remote registry instead", "error", err) + } else { + lr.lggr = s.lggr + lr.getPeerID = s.getPeerID + } + } + + if lr == nil { + s.lggr.Debug("syncing with remote registry") + localRegistry, err := s.localRegistry(ctx) + if err != nil { + return fmt.Errorf("failed to sync with remote registry: %w", err) + } + lr = localRegistry + // Attempt to send local registry to the update channel without blocking + // This is to prevent the tests from hanging if they are not calling `Start()` on the syncer + select { + case <-s.stopCh: + s.lggr.Debug("sync cancelled, stopping") + case s.updateChan <- lr: + // Successfully sent state + s.lggr.Debug("remote registry update triggered successfully") + default: + // No one is ready to receive the state, handle accordingly + s.lggr.Debug("no listeners on update channel, remote registry update skipped") + } } for _, h := range s.launchers { - if err := h.Launch(ctx, lr); err != nil { + lrCopy := deepCopyLocalRegistry(lr) + if err := h.Launch(ctx, &lrCopy); err != nil { s.lggr.Errorf("error calling launcher: %s", err) } } @@ -281,6 +325,58 @@ func (s *registrySyncer) sync(ctx context.Context) error { return nil } +func deepCopyLocalRegistry(lr *LocalRegistry) LocalRegistry { + var lrCopy LocalRegistry + lrCopy.lggr = lr.lggr + lrCopy.getPeerID = lr.getPeerID + lrCopy.IDsToDONs = make(map[DonID]DON, len(lr.IDsToDONs)) + for id, don := range lr.IDsToDONs { + d := capabilities.DON{ + ID: don.ID, + ConfigVersion: don.ConfigVersion, + Members: make([]p2ptypes.PeerID, len(don.Members)), + F: don.F, + IsPublic: don.IsPublic, + AcceptsWorkflows: don.AcceptsWorkflows, + } + copy(d.Members, don.Members) + capCfgs := make(map[string]CapabilityConfiguration, len(don.CapabilityConfigurations)) + for capID, capCfg := range don.CapabilityConfigurations { + capCfgs[capID] = CapabilityConfiguration{ + Config: capCfg.Config[:], + } + } + lrCopy.IDsToDONs[id] = DON{ + DON: d, + CapabilityConfigurations: capCfgs, + } + } + + lrCopy.IDsToCapabilities = make(map[string]Capability, len(lr.IDsToCapabilities)) + for id, capability := range lr.IDsToCapabilities { + cp := capability + lrCopy.IDsToCapabilities[id] = cp + } + + lrCopy.IDsToNodes = make(map[p2ptypes.PeerID]kcr.CapabilitiesRegistryNodeInfo, len(lr.IDsToNodes)) + for id, node := range lr.IDsToNodes { + nodeInfo := kcr.CapabilitiesRegistryNodeInfo{ + NodeOperatorId: node.NodeOperatorId, + ConfigCount: node.ConfigCount, + WorkflowDONId: node.WorkflowDONId, + Signer: node.Signer, + P2pId: node.P2pId, + HashedCapabilityIds: make([][32]byte, len(node.HashedCapabilityIds)), + CapabilitiesDONIds: make([]*big.Int, len(node.CapabilitiesDONIds)), + } + copy(nodeInfo.HashedCapabilityIds, node.HashedCapabilityIds) + copy(nodeInfo.CapabilitiesDONIds, node.CapabilitiesDONIds) + lrCopy.IDsToNodes[id] = nodeInfo + } + + return lrCopy +} + func toCapabilityType(capabilityType uint8) capabilities.CapabilityType { switch capabilityType { case 0: @@ -292,8 +388,7 @@ func toCapabilityType(capabilityType uint8) capabilities.CapabilityType { case 3: return capabilities.CapabilityTypeTarget default: - // Not found - return capabilities.CapabilityType(-1) + return capabilities.CapabilityTypeUnknown } } @@ -322,6 +417,9 @@ func (s *registrySyncer) AddLauncher(launchers ...Launcher) { func (s *registrySyncer) Close() error { return s.StopOnce("RegistrySyncer", func() error { close(s.stopCh) + s.mu.Lock() + defer s.mu.Unlock() + close(s.updateChan) s.wg.Wait() return nil }) diff --git a/core/services/registrysyncer/syncer_test.go b/core/services/registrysyncer/syncer_test.go index b926183394..9e51b7498f 100644 --- a/core/services/registrysyncer/syncer_test.go +++ b/core/services/registrysyncer/syncer_test.go @@ -1,4 +1,4 @@ -package registrysyncer +package registrysyncer_test import ( "context" @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "math/big" + "sync" "testing" "time" @@ -15,6 +16,7 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/durationpb" @@ -25,6 +27,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/values" capabilitiespb "github.com/smartcontractkit/chainlink-common/pkg/capabilities/pb" + evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" @@ -33,7 +36,8 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" p2ptypes "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types" - "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types/mocks" + "github.com/smartcontractkit/chainlink/v2/core/services/registrysyncer" + syncerMocks "github.com/smartcontractkit/chainlink/v2/core/services/registrysyncer/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" evmrelaytypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" ) @@ -127,16 +131,51 @@ func randomWord() [32]byte { } type launcher struct { - localRegistry *LocalRegistry + localRegistry *registrysyncer.LocalRegistry + mu sync.RWMutex } -func (l *launcher) Launch(ctx context.Context, localRegistry *LocalRegistry) error { +func (l *launcher) Launch(ctx context.Context, localRegistry *registrysyncer.LocalRegistry) error { + l.mu.Lock() + defer l.mu.Unlock() l.localRegistry = localRegistry return nil } +type orm struct { + ormMock *syncerMocks.ORM + latestLocalRegistryCh chan struct{} + addLocalRegistryCh chan struct{} +} + +func newORM(t *testing.T) *orm { + t.Helper() + + return &orm{ + ormMock: syncerMocks.NewORM(t), + latestLocalRegistryCh: make(chan struct{}, 1), + addLocalRegistryCh: make(chan struct{}, 1), + } +} + +func (o *orm) Cleanup() { + close(o.latestLocalRegistryCh) + close(o.addLocalRegistryCh) +} + +func (o *orm) AddLocalRegistry(ctx context.Context, localRegistry registrysyncer.LocalRegistry) error { + o.addLocalRegistryCh <- struct{}{} + err := o.ormMock.AddLocalRegistry(ctx, localRegistry) + return err +} + +func (o *orm) LatestLocalRegistry(ctx context.Context) (*registrysyncer.LocalRegistry, error) { + o.latestLocalRegistryCh <- struct{}{} + return o.ormMock.LatestLocalRegistry(ctx) +} + func toPeerIDs(ids [][32]byte) []p2ptypes.PeerID { - pids := []p2ptypes.PeerID{} + var pids []p2ptypes.PeerID for _, id := range ids { pids = append(pids, id) } @@ -210,6 +249,7 @@ func TestReader_Integration(t *testing.T) { RegistrationExpiry: durationpb.New(60 * time.Second), // F + 1 MinResponsesToAggregate: uint32(1) + 1, + MessageExpiry: durationpb.New(120 * time.Second), }, }, } @@ -236,49 +276,38 @@ func TestReader_Integration(t *testing.T) { require.NoError(t, err) - wrapper := mocks.NewPeerWrapper(t) + db := pgtest.NewSqlxDB(t) factory := newContractReaderFactory(t, sim) - syncer, err := New(logger.TestLogger(t), wrapper, factory, regAddress.Hex()) + syncerORM := registrysyncer.NewORM(db, logger.TestLogger(t)) + syncer, err := registrysyncer.New(logger.TestLogger(t), func() (p2ptypes.PeerID, error) { return p2ptypes.PeerID{}, nil }, factory, regAddress.Hex(), syncerORM) require.NoError(t, err) l := &launcher{} syncer.AddLauncher(l) - err = syncer.sync(ctx) + err = syncer.Sync(ctx, false) // not looking to load from the DB in this specific test. s := l.localRegistry require.NoError(t, err) assert.Len(t, s.IDsToCapabilities, 1) gotCap := s.IDsToCapabilities[cid] - assert.Equal(t, Capability{ + assert.Equal(t, registrysyncer.Capability{ CapabilityType: capabilities.CapabilityTypeTarget, ID: "write-chain@1.0.1", }, gotCap) assert.Len(t, s.IDsToDONs, 1) - rtc := capabilities.RemoteTriggerConfig{ - RegistrationRefresh: 20 * time.Second, - MinResponsesToAggregate: 2, - RegistrationExpiry: 60 * time.Second, - MessageExpiry: 120 * time.Second, - } - expectedDON := DON{ - DON: capabilities.DON{ - ID: 1, - ConfigVersion: 1, - IsPublic: true, - AcceptsWorkflows: true, - F: 1, - Members: toPeerIDs(nodeSet), - }, - CapabilityConfigurations: map[string]capabilities.CapabilityConfiguration{ - cid: { - DefaultConfig: values.EmptyMap(), - RemoteTriggerConfig: rtc, - }, - }, + expectedDON := capabilities.DON{ + ID: 1, + ConfigVersion: 1, + IsPublic: true, + AcceptsWorkflows: true, + F: 1, + Members: toPeerIDs(nodeSet), } - assert.Equal(t, expectedDON, s.IDsToDONs[1]) + gotDon := s.IDsToDONs[1] + assert.Equal(t, expectedDON, gotDon.DON) + assert.Equal(t, configb, gotDon.CapabilityConfigurations[cid].Config) nodesInfo := []kcr.CapabilitiesRegistryNodeInfo{ { @@ -321,6 +350,127 @@ func TestReader_Integration(t *testing.T) { }, s.IDsToNodes) } +func TestSyncer_DBIntegration(t *testing.T) { + ctx := testutils.Context(t) + reg, regAddress, owner, sim := startNewChainWithRegistry(t) + + _, err := reg.AddCapabilities(owner, []kcr.CapabilitiesRegistryCapability{writeChainCapability}) + require.NoError(t, err, "AddCapability failed for %s", writeChainCapability.LabelledName) + sim.Commit() + + cid, err := reg.GetHashedCapabilityId(&bind.CallOpts{}, writeChainCapability.LabelledName, writeChainCapability.Version) + require.NoError(t, err) + + _, err = reg.AddNodeOperators(owner, []kcr.CapabilitiesRegistryNodeOperator{ + { + Admin: owner.From, + Name: "TEST_NOP", + }, + }) + require.NoError(t, err) + + nodeSet := [][32]byte{ + randomWord(), + randomWord(), + randomWord(), + } + + signersSet := [][32]byte{ + randomWord(), + randomWord(), + randomWord(), + } + + nodes := []kcr.CapabilitiesRegistryNodeParams{ + { + // The first NodeOperatorId has id 1 since the id is auto-incrementing. + NodeOperatorId: uint32(1), + Signer: signersSet[0], + P2pId: nodeSet[0], + HashedCapabilityIds: [][32]byte{cid}, + }, + { + // The first NodeOperatorId has id 1 since the id is auto-incrementing. + NodeOperatorId: uint32(1), + Signer: signersSet[1], + P2pId: nodeSet[1], + HashedCapabilityIds: [][32]byte{cid}, + }, + { + // The first NodeOperatorId has id 1 since the id is auto-incrementing. + NodeOperatorId: uint32(1), + Signer: signersSet[2], + P2pId: nodeSet[2], + HashedCapabilityIds: [][32]byte{cid}, + }, + } + _, err = reg.AddNodes(owner, nodes) + require.NoError(t, err) + + config := &capabilitiespb.CapabilityConfig{ + DefaultConfig: values.Proto(values.EmptyMap()).GetMapValue(), + RemoteConfig: &capabilitiespb.CapabilityConfig_RemoteTriggerConfig{ + RemoteTriggerConfig: &capabilitiespb.RemoteTriggerConfig{ + RegistrationRefresh: durationpb.New(20 * time.Second), + RegistrationExpiry: durationpb.New(60 * time.Second), + // F + 1 + MinResponsesToAggregate: uint32(1) + 1, + }, + }, + } + configb, err := proto.Marshal(config) + require.NoError(t, err) + + cfgs := []kcr.CapabilitiesRegistryCapabilityConfiguration{ + { + CapabilityId: cid, + Config: configb, + }, + } + _, err = reg.AddDON( + owner, + nodeSet, + cfgs, + true, + true, + 1, + ) + sim.Commit() + + require.NoError(t, err) + + factory := newContractReaderFactory(t, sim) + syncerORM := newORM(t) + syncerORM.ormMock.On("LatestLocalRegistry", mock.Anything).Return(nil, fmt.Errorf("no state found")) + syncerORM.ormMock.On("AddLocalRegistry", mock.Anything, mock.Anything).Return(nil) + syncer, err := newTestSyncer(logger.TestLogger(t), func() (p2ptypes.PeerID, error) { return p2ptypes.PeerID{}, nil }, factory, regAddress.Hex(), syncerORM) + require.NoError(t, err) + require.NoError(t, syncer.Start(ctx)) + t.Cleanup(func() { + syncerORM.Cleanup() + require.NoError(t, syncer.Close()) + }) + + l := &launcher{} + syncer.AddLauncher(l) + + var latestLocalRegistryCalled, addLocalRegistryCalled bool + timeout := time.After(testutils.WaitTimeout(t)) + + for !latestLocalRegistryCalled || !addLocalRegistryCalled { + select { + case val := <-syncerORM.latestLocalRegistryCh: + assert.Equal(t, struct{}{}, val) + latestLocalRegistryCalled = true + case val := <-syncerORM.addLocalRegistryCh: + assert.Equal(t, struct{}{}, val) + addLocalRegistryCalled = true + case <-timeout: + t.Fatal("test timed out; channels did not received data") + } + } +} + func TestSyncer_LocalNode(t *testing.T) { ctx := tests.Context(t) lggr := logger.TestLogger(t) @@ -328,10 +478,6 @@ func TestSyncer_LocalNode(t *testing.T) { var pid p2ptypes.PeerID err := pid.UnmarshalText([]byte("12D3KooWBCF1XT5Wi8FzfgNCqRL76Swv8TRU3TiD4QiJm8NMNX7N")) require.NoError(t, err) - peer := mocks.NewPeer(t) - peer.On("ID").Return(pid) - wrapper := mocks.NewPeerWrapper(t) - wrapper.On("GetPeer").Return(peer) workflowDonNodes := []p2ptypes.PeerID{ pid, @@ -344,11 +490,11 @@ func TestSyncer_LocalNode(t *testing.T) { // The below state describes a Workflow DON (AcceptsWorkflows = true), // which exposes the streams-trigger and write_chain capabilities. // We expect receivers to be wired up and both capabilities to be added to the registry. - localRegistry := LocalRegistry{ - lggr: lggr, - peerWrapper: wrapper, - IDsToDONs: map[DonID]DON{ - DonID(dID): { + localRegistry := registrysyncer.NewLocalRegistry( + lggr, + func() (p2ptypes.PeerID, error) { return pid, nil }, + map[registrysyncer.DonID]registrysyncer.DON{ + registrysyncer.DonID(dID): { DON: capabilities.DON{ ID: dID, ConfigVersion: uint32(2), @@ -359,7 +505,7 @@ func TestSyncer_LocalNode(t *testing.T) { }, }, }, - IDsToNodes: map[p2ptypes.PeerID]kcr.CapabilitiesRegistryNodeInfo{ + map[p2ptypes.PeerID]kcr.CapabilitiesRegistryNodeInfo{ workflowDonNodes[0]: { NodeOperatorId: 1, Signer: randomWord(), @@ -381,7 +527,8 @@ func TestSyncer_LocalNode(t *testing.T) { P2pId: workflowDonNodes[3], }, }, - } + map[string]registrysyncer.Capability{}, + ) node, err := localRegistry.LocalNode(ctx) require.NoError(t, err) @@ -401,3 +548,17 @@ func TestSyncer_LocalNode(t *testing.T) { } assert.Equal(t, expectedNode, node) } + +func newTestSyncer( + lggr logger.Logger, + getPeerID func() (p2ptypes.PeerID, error), + relayer registrysyncer.ContractReaderFactory, + registryAddress string, + orm *orm, +) (registrysyncer.RegistrySyncer, error) { + rs, err := registrysyncer.New(lggr, getPeerID, relayer, registryAddress, orm) + if err != nil { + return nil, err + } + return rs, nil +} diff --git a/core/services/relay/dummy/config_provider.go b/core/services/relay/dummy/config_provider.go index 10662ee296..db36acba83 100644 --- a/core/services/relay/dummy/config_provider.go +++ b/core/services/relay/dummy/config_provider.go @@ -63,7 +63,7 @@ type configProvider struct { } func NewConfigProvider(lggr logger.Logger, cfg RelayConfig) (types.ConfigProvider, error) { - cp := &configProvider{lggr: lggr.Named("DummyConfigProvider")} + cp := &configProvider{lggr: lggr.Named("DummyConfigProvider").Named(cfg.ConfigTracker.ConfigDigest.String())} { contractConfig, err := cfg.ConfigTracker.ToContractConfig() diff --git a/core/services/relay/dummy/config_tracker.go b/core/services/relay/dummy/config_tracker.go index 0ae188361f..ecd08196e5 100644 --- a/core/services/relay/dummy/config_tracker.go +++ b/core/services/relay/dummy/config_tracker.go @@ -21,7 +21,7 @@ func NewContractConfigTracker(lggr logger.Logger, cfg ConfigTrackerCfg) (ocrtype if err != nil { return nil, err } - return &configTracker{lggr.Named("DummyConfigProvider"), contractConfig, cfg.ChangedInBlock, cfg.BlockHeight}, nil + return &configTracker{lggr.Named("DummyConfigTracker"), contractConfig, cfg.ChangedInBlock, cfg.BlockHeight}, nil } // Notify may optionally emit notification events when the contract's diff --git a/core/services/relay/dummy/llo_provider.go b/core/services/relay/dummy/llo_provider.go index 4aeb21bed8..88f7258815 100644 --- a/core/services/relay/dummy/llo_provider.go +++ b/core/services/relay/dummy/llo_provider.go @@ -11,14 +11,18 @@ import ( relaytypes "github.com/smartcontractkit/chainlink-common/pkg/types" llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/llo" ) var _ commontypes.LLOProvider = (*lloProvider)(nil) +type Transmitter interface { + services.Service + llotypes.Transmitter +} + type lloProvider struct { cp commontypes.ConfigProvider - transmitter llo.Transmitter + transmitter Transmitter logger logger.Logger channelDefinitionCache llotypes.ChannelDefinitionCache @@ -28,7 +32,7 @@ type lloProvider struct { func NewLLOProvider( lggr logger.Logger, cp commontypes.ConfigProvider, - transmitter llo.Transmitter, + transmitter Transmitter, channelDefinitionCache llotypes.ChannelDefinitionCache, ) relaytypes.LLOProvider { return &lloProvider{ @@ -73,10 +77,6 @@ func (p *lloProvider) OffchainConfigDigester() ocrtypes.OffchainConfigDigester { return p.cp.OffchainConfigDigester() } -func (p *lloProvider) OnchainConfigCodec() llo.OnchainConfigCodec { - return &llo.JSONOnchainConfigCodec{} -} - func (p *lloProvider) ContractTransmitter() llotypes.Transmitter { return p.transmitter } diff --git a/core/services/relay/dummy/relayer.go b/core/services/relay/dummy/relayer.go index cf3aa732c4..a9b90b9a2f 100644 --- a/core/services/relay/dummy/relayer.go +++ b/core/services/relay/dummy/relayer.go @@ -64,6 +64,9 @@ func (r *relayer) NewLLOProvider(ctx context.Context, rargs types.RelayArgs, par } return NewLLOProvider(r.lggr, cp, transmitter, cdc), nil } +func (r *relayer) LatestHead(_ context.Context) (types.Head, error) { + return types.Head{}, nil +} func (r *relayer) GetChainStatus(ctx context.Context) (types.ChainStatus, error) { return types.ChainStatus{}, nil } diff --git a/core/services/relay/evm/binding.go b/core/services/relay/evm/binding.go deleted file mode 100644 index 412e33c609..0000000000 --- a/core/services/relay/evm/binding.go +++ /dev/null @@ -1,18 +0,0 @@ -package evm - -import ( - "context" - - commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" - "github.com/smartcontractkit/chainlink-common/pkg/types/query" - "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" -) - -type readBinding interface { - Bind(ctx context.Context, binding commontypes.BoundContract) error - SetCodec(codec commontypes.RemoteCodec) - Register(ctx context.Context) error - Unregister(ctx context.Context) error - GetLatestValue(ctx context.Context, confidenceLevel primitives.ConfidenceLevel, params, returnVal any) error - QueryKey(ctx context.Context, filter query.KeyFilter, limitAndSort query.LimitAndSort, sequenceDataType any) ([]commontypes.Sequence, error) -} diff --git a/core/services/relay/evm/bindings.go b/core/services/relay/evm/bindings.go deleted file mode 100644 index 4d4760db52..0000000000 --- a/core/services/relay/evm/bindings.go +++ /dev/null @@ -1,120 +0,0 @@ -package evm - -import ( - "context" - "fmt" - - commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" -) - -// bindings manage all contract bindings, key is contract name. - -type bindings struct { - contractBindings map[string]*contractBinding - BatchCaller -} - -func (b bindings) GetReadBinding(contractName, readName string) (readBinding, error) { - // GetReadBindings should only be called after Chain Reader init. - cb, cbExists := b.contractBindings[contractName] - if !cbExists { - return nil, fmt.Errorf("%w: no contract named %s", commontypes.ErrInvalidType, contractName) - } - - rb, rbExists := cb.readBindings[readName] - if !rbExists { - return nil, fmt.Errorf("%w: no readName named %s in contract %s", commontypes.ErrInvalidType, readName, contractName) - } - return rb, nil -} - -// AddReadBinding adds read bindings. Calling this outside of Chain Reader init is not thread safe. -func (b bindings) AddReadBinding(contractName, readName string, rb readBinding) { - cb, cbExists := b.contractBindings[contractName] - if !cbExists { - cb = &contractBinding{ - name: contractName, - readBindings: make(map[string]readBinding), - } - b.contractBindings[contractName] = cb - } - cb.readBindings[readName] = rb -} - -// Bind binds contract addresses to contract bindings and read bindings. -// Bind also registers the common contract polling filter and eventBindings polling filters. -func (b bindings) Bind(ctx context.Context, lp logpoller.LogPoller, boundContracts []commontypes.BoundContract) error { - for _, bc := range boundContracts { - cb, cbExists := b.contractBindings[bc.Name] - if !cbExists { - return fmt.Errorf("%w: no contract named %s", commontypes.ErrInvalidConfig, bc.Name) - } - - if err := cb.Bind(ctx, lp, bc); err != nil { - return err - } - - for _, rb := range cb.readBindings { - if err := rb.Bind(ctx, bc); err != nil { - return err - } - } - } - return nil -} - -func (b bindings) BatchGetLatestValues(ctx context.Context, request commontypes.BatchGetLatestValuesRequest) (commontypes.BatchGetLatestValuesResult, error) { - var batchCall BatchCall - toChainAgnosticMethodName := make(map[string]string) - for contractName, contractBatch := range request { - cb := b.contractBindings[contractName] - for i := range contractBatch { - req := contractBatch[i] - switch rb := cb.readBindings[req.ReadName].(type) { - case *methodBinding: - toChainAgnosticMethodName[rb.method] = req.ReadName - batchCall = append(batchCall, Call{ - ContractAddress: rb.address, - ContractName: cb.name, - MethodName: rb.method, - Params: req.Params, - ReturnVal: req.ReturnVal, - }) - // results here will have chain specific method names. - case *eventBinding: - // TODO Use FilteredLogs to batch? This isn't a priority right now, but should get implemented at some point. - return nil, fmt.Errorf("%w: events are not yet supported in batch get latest values", commontypes.ErrInvalidType) - default: - return nil, fmt.Errorf("%w: missing read binding type for contract: %s read: %s", commontypes.ErrInvalidType, contractName, req.ReadName) - } - } - } - - results, err := b.BatchCall(ctx, 0, batchCall) - if err != nil { - return nil, err - } - - // reconstruct results from batchCall and filteredLogs into common type while maintaining order from request. - batchGetLatestValuesResults := make(commontypes.BatchGetLatestValuesResult) - for contractName, contractResult := range results { - batchGetLatestValuesResults[contractName] = commontypes.ContractBatchResults{} - for _, methodResult := range contractResult { - brr := commontypes.BatchReadResult{ReadName: toChainAgnosticMethodName[methodResult.MethodName]} - brr.SetResult(methodResult.ReturnValue, methodResult.Err) - batchGetLatestValuesResults[contractName] = append(batchGetLatestValuesResults[contractName], brr) - } - } - - return batchGetLatestValuesResults, err -} - -func (b bindings) ForEach(ctx context.Context, fn func(context.Context, *contractBinding) error) error { - for _, cb := range b.contractBindings { - if err := fn(ctx, cb); err != nil { - return err - } - } - return nil -} diff --git a/core/services/relay/evm/cap_encoder.go b/core/services/relay/evm/cap_encoder.go index 790114e4c0..2a6f288a5d 100644 --- a/core/services/relay/evm/cap_encoder.go +++ b/core/services/relay/evm/cap_encoder.go @@ -10,7 +10,9 @@ import ( consensustypes "github.com/smartcontractkit/chainlink-common/pkg/capabilities/consensus/ocr3/types" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink-common/pkg/values" + abiutil "github.com/smartcontractkit/chainlink/v2/core/chains/evm/abi" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" ) @@ -47,7 +49,7 @@ func NewEVMEncoder(config *values.Map) (consensustypes.Encoder, error) { codecConfig := types.CodecConfig{Configs: map[string]types.ChainCodecConfig{ encoderName: {TypeABI: string(jsonSelector)}, }} - c, err := NewCodec(codecConfig) + c, err := codec.NewCodec(codecConfig) if err != nil { return nil, err } diff --git a/core/services/relay/evm/ccip.go b/core/services/relay/evm/ccip.go index 945763de85..0318ab0ee0 100644 --- a/core/services/relay/evm/ccip.go +++ b/core/services/relay/evm/ccip.go @@ -7,6 +7,7 @@ import ( "time" cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" diff --git a/core/services/relay/evm/chain_reader_test.go b/core/services/relay/evm/chain_components_test.go similarity index 60% rename from core/services/relay/evm/chain_reader_test.go rename to core/services/relay/evm/chain_components_test.go index f30bba0e44..50c530904d 100644 --- a/core/services/relay/evm/chain_reader_test.go +++ b/core/services/relay/evm/chain_components_test.go @@ -2,6 +2,7 @@ package evm_test import ( "context" + "crypto/ecdsa" "fmt" "math" "math/big" @@ -18,26 +19,31 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" + commontestutils "github.com/smartcontractkit/chainlink-common/pkg/loop/testutils" clcommontypes "github.com/smartcontractkit/chainlink-common/pkg/types" - . "github.com/smartcontractkit/chainlink-common/pkg/types/interfacetests" //nolint common practice to import test mods with . - "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" - commontestutils "github.com/smartcontractkit/chainlink-common/pkg/loop/testutils" - + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + evmtxmgr "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" + keytypes "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" . "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/evmtesting" //nolint common practice to import test mods with . ) const commonGasLimitOnEvms = uint64(4712388) -func TestChainReaderEventsInitValidation(t *testing.T) { +func TestContractReaderEventsInitValidation(t *testing.T) { tests := []struct { name string chainContractReaders map[string]types.ChainContractReader @@ -143,43 +149,77 @@ func TestChainReaderEventsInitValidation(t *testing.T) { } } -func TestChainReader(t *testing.T) { +func TestChainComponents(t *testing.T) { t.Parallel() - it := &EVMChainReaderInterfaceTester[*testing.T]{Helper: &helper{}} + it := &EVMChainComponentsInterfaceTester[*testing.T]{Helper: &helper{}} + + it.Helper.Init(t) + // add new subtests here so that it can be run on real chains too - RunChainReaderEvmTests(t, it) - RunChainReaderInterfaceTests[*testing.T](t, commontestutils.WrapChainReaderTesterForLoop(it)) + RunChainComponentsEvmTests(t, it) + RunChainComponentsInLoopEvmTests[*testing.T](t, commontestutils.WrapContractReaderTesterForLoop(it)) } type helper struct { - sim *backends.SimulatedBackend - auth *bind.TransactOpts + sim *backends.SimulatedBackend + accounts []*bind.TransactOpts + deployerKey *ecdsa.PrivateKey + senderKey *ecdsa.PrivateKey + txm evmtxmgr.TxManager + client client.Client + db *sqlx.DB } -func (h *helper) MustGenerateRandomKey(t *testing.T) ethkey.KeyV2 { - return cltest.MustGenerateRandomKey(t) +func (h *helper) Init(t *testing.T) { + h.SetupKeys(t) + + h.accounts = h.Accounts(t) + + h.db = pgtest.NewSqlxDB(t) + + h.Backend() + h.client = h.Client(t) + + h.txm = h.TXM(t, h.client) + h.Commit() } -func (h *helper) GasPriceBufferPercent() int64 { - return 0 +func (h *helper) SetupKeys(t *testing.T) { + deployerPkey, err := crypto.GenerateKey() + require.NoError(t, err) + h.deployerKey = deployerPkey + + senderPkey, err := crypto.GenerateKey() + require.NoError(t, err) + h.senderKey = senderPkey } -func (h *helper) SetupAuth(t *testing.T) *bind.TransactOpts { - privateKey, err := crypto.GenerateKey() +func (h *helper) Accounts(t *testing.T) []*bind.TransactOpts { + if h.accounts != nil { + return h.accounts + } + deployer, err := bind.NewKeyedTransactorWithChainID(h.deployerKey, big.NewInt(1337)) require.NoError(t, err) - h.auth, err = bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(1337)) + sender, err := bind.NewKeyedTransactorWithChainID(h.senderKey, big.NewInt(1337)) require.NoError(t, err) - h.Backend() - h.Commit() - return h.auth + return []*bind.TransactOpts{deployer, sender} +} + +func (h *helper) MustGenerateRandomKey(t *testing.T) ethkey.KeyV2 { + return cltest.MustGenerateRandomKey(t) +} + +func (h *helper) GasPriceBufferPercent() int64 { + return 0 } func (h *helper) Backend() bind.ContractBackend { if h.sim == nil { h.sim = backends.NewSimulatedBackend( - core.GenesisAlloc{h.auth.From: {Balance: big.NewInt(math.MaxInt64)}}, commonGasLimitOnEvms*5000) + core.GenesisAlloc{h.accounts[0].From: {Balance: big.NewInt(math.MaxInt64)}, h.accounts[1].From: {Balance: big.NewInt(math.MaxInt64)}}, commonGasLimitOnEvms*5000) + cltest.Mine(h.sim, 1*time.Second) } return h.sim @@ -190,6 +230,9 @@ func (h *helper) Commit() { } func (h *helper) Client(t *testing.T) client.Client { + if h.client != nil { + return h.client + } return client.NewSimulatedBackendClient(t, h.sim, big.NewInt(1337)) } @@ -205,6 +248,18 @@ func (h *helper) Context(t *testing.T) context.Context { return testutils.Context(t) } +func (h *helper) ChainReaderEVMClient(ctx context.Context, t *testing.T, ht logpoller.HeadTracker, conf types.ChainReaderConfig) client.Client { + // wrap the client so that we can mock historical contract state + cwh := &evm.ClientWithContractHistory{Client: h.Client(t), HT: ht} + require.NoError(t, cwh.Init(ctx, conf)) + return cwh +} + +func (h *helper) WrappedChainWriter(cw clcommontypes.ChainWriter, client client.Client) clcommontypes.ChainWriter { + cwhw := evm.NewChainWriterHistoricalWrapper(cw, client.(*evm.ClientWithContractHistory)) + return cwhw +} + func (h *helper) MaxWaitTimeForEvents() time.Duration { // From trial and error, when running on CI, sometimes the boxes get slow maxWaitTime := time.Second * 30 @@ -216,6 +271,41 @@ func (h *helper) MaxWaitTimeForEvents() time.Duration { } maxWaitTime = time.Second * time.Duration(waitS) } - return maxWaitTime } + +func (h *helper) TXM(t *testing.T, client client.Client) evmtxmgr.TxManager { + if h.txm != nil { + return h.txm + } + db := h.db + + clconfig := configtest.NewGeneralConfigSimulated(t, func(c *chainlink.Config, s *chainlink.Secrets) { + c.Database.Listener.FallbackPollInterval = commonconfig.MustNewDuration(100 * time.Millisecond) + c.EVM[0].GasEstimator.EIP1559DynamicFees = ptr(true) + }) + + clconfig.EVMConfigs()[0].GasEstimator.PriceMax = assets.GWei(100) + + app := cltest.NewApplicationWithConfigV2AndKeyOnSimulatedBlockchain(t, clconfig, h.sim, db, client) + err := app.Start(h.Context(t)) + require.NoError(t, err) + + keyStore := app.KeyStore.Eth() + + keyStore.XXXTestingOnlyAdd(h.Context(t), keytypes.FromPrivateKey(h.deployerKey)) + require.NoError(t, keyStore.Add(h.Context(t), h.accounts[0].From, h.ChainID())) + require.NoError(t, keyStore.Enable(h.Context(t), h.accounts[0].From, h.ChainID())) + + keyStore.XXXTestingOnlyAdd(h.Context(t), keytypes.FromPrivateKey(h.senderKey)) + require.NoError(t, keyStore.Add(h.Context(t), h.accounts[1].From, h.ChainID())) + require.NoError(t, keyStore.Enable(h.Context(t), h.accounts[1].From, h.ChainID())) + + chain, err := app.GetRelayers().LegacyEVMChains().Get((h.ChainID()).String()) + require.NoError(t, err) + + h.txm = chain.TxManager() + return h.txm +} + +func ptr[T any](v T) *T { return &v } diff --git a/core/services/relay/evm/chain_reader.go b/core/services/relay/evm/chain_reader.go index d84c2f00a9..8a770d4744 100644 --- a/core/services/relay/evm/chain_reader.go +++ b/core/services/relay/evm/chain_reader.go @@ -4,25 +4,28 @@ import ( "context" "errors" "fmt" - "reflect" + "maps" "slices" "strings" - "sync" "time" "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/chainlink-common/pkg/codec" + commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" + "github.com/smartcontractkit/chainlink-common/pkg/logger" commonservices "github.com/smartcontractkit/chainlink-common/pkg/services" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink-common/pkg/types/query" "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" + "github.com/smartcontractkit/chainlink-common/pkg/values" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/read" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" ) @@ -32,13 +35,14 @@ type ChainReaderService interface { } type chainReader struct { - lggr logger.Logger - ht logpoller.HeadTracker - lp logpoller.LogPoller - client evmclient.Client - parsed *ParsedTypes - bindings - codec commontypes.RemoteCodec + commontypes.UnimplementedContractReader + lggr logger.Logger + ht logpoller.HeadTracker + lp logpoller.LogPoller + client evmclient.Client + parsed *codec.ParsedTypes + bindings *read.BindingsRegistry + codec commontypes.RemoteCodec commonservices.StateMachine } @@ -47,14 +51,14 @@ var _ commontypes.ContractTypeProvider = &chainReader{} // NewChainReaderService is a constructor for ChainReader, returns nil if there is any error // Note that the ChainReaderService returned does not support anonymous events. -func NewChainReaderService(ctx context.Context, lggr logger.Logger, lp logpoller.LogPoller, ht logpoller.HeadTracker, client evmclient.Client, config types.ChainReaderConfig) (ChainReaderService, error) { +func NewChainReaderService(_ context.Context, lggr logger.Logger, lp logpoller.LogPoller, ht logpoller.HeadTracker, client evmclient.Client, config types.ChainReaderConfig) (ChainReaderService, error) { cr := &chainReader{ - lggr: lggr.Named("ChainReader"), + lggr: logger.Named(lggr, "ChainReader"), ht: ht, lp: lp, client: client, - bindings: bindings{contractBindings: make(map[string]*contractBinding)}, - parsed: &ParsedTypes{EncoderDefs: map[string]types.CodecEntry{}, DecoderDefs: map[string]types.CodecEntry{}}, + bindings: read.NewBindingsRegistry(), + parsed: &codec.ParsedTypes{EncoderDefs: map[string]types.CodecEntry{}, DecoderDefs: map[string]types.CodecEntry{}}, } var err error @@ -66,21 +70,16 @@ func NewChainReaderService(ctx context.Context, lggr logger.Logger, lp logpoller return nil, err } - cr.bindings.BatchCaller = NewDynamicLimitedBatchCaller( + cr.bindings.SetBatchCaller(read.NewDynamicLimitedBatchCaller( cr.lggr, cr.codec, cr.client, - DefaultRpcBatchSizeLimit, - DefaultRpcBatchBackOffMultiplier, - DefaultMaxParallelRpcCalls, - ) - - err = cr.bindings.ForEach(ctx, func(c context.Context, cb *contractBinding) error { - for _, rb := range cb.readBindings { - rb.SetCodec(cr.codec) - } - return nil - }) + read.DefaultRpcBatchSizeLimit, + read.DefaultRpcBatchBackOffMultiplier, + read.DefaultMaxParallelRpcCalls, + )) + + cr.bindings.SetCodecAll(cr.codec) return cr, err } @@ -128,7 +127,14 @@ func (cr *chainReader) init(chainContractReaders map[string]types.ChainContractR return err } } - cr.bindings.contractBindings[contractName].pollingFilter = chainContractReader.PollingFilter.ToLPFilter(eventSigsForContractFilter) + + if !cr.bindings.HasContractBinding(contractName) { + return fmt.Errorf("%w: no read bindings added for contract: %s", commontypes.ErrInvalidConfig, contractName) + } + + if err = cr.bindings.SetFilter(contractName, chainContractReader.PollingFilter.ToLPFilter(eventSigsForContractFilter)); err != nil { + return err + } } return nil } @@ -138,14 +144,7 @@ func (cr *chainReader) Name() string { return cr.lggr.Name() } // Start registers polling filters if contracts are already bound. func (cr *chainReader) Start(ctx context.Context) error { return cr.StartOnce("ChainReader", func() error { - return cr.bindings.ForEach(ctx, func(c context.Context, cb *contractBinding) error { - for _, rb := range cb.readBindings { - if err := rb.Register(ctx); err != nil { - return err - } - } - return cb.Register(ctx, cr.lp) - }) + return cr.bindings.RegisterAll(ctx, cr.lp) }) } @@ -154,14 +153,7 @@ func (cr *chainReader) Close() error { return cr.StopOnce("ChainReader", func() error { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() - return cr.bindings.ForEach(ctx, func(c context.Context, cb *contractBinding) error { - for _, rb := range cb.readBindings { - if err := rb.Unregister(ctx); err != nil { - return err - } - } - return cb.Unregister(ctx, cr.lp) - }) + return cr.bindings.UnregisterAll(ctx, cr.lp) }) } @@ -171,41 +163,93 @@ func (cr *chainReader) HealthReport() map[string]error { return map[string]error{cr.Name(): nil} } -func (cr *chainReader) GetLatestValue(ctx context.Context, contractName, method string, confidenceLevel primitives.ConfidenceLevel, params, returnVal any) error { - b, err := cr.bindings.GetReadBinding(contractName, method) +func (cr *chainReader) Bind(ctx context.Context, bindings []commontypes.BoundContract) error { + return cr.bindings.Bind(ctx, cr.lp, bindings) +} + +func (cr *chainReader) Unbind(ctx context.Context, bindings []commontypes.BoundContract) error { + return cr.bindings.Unbind(ctx, cr.lp, bindings) +} + +func (cr *chainReader) GetLatestValue(ctx context.Context, readName string, confidenceLevel primitives.ConfidenceLevel, params any, returnVal any) error { + binding, address, err := cr.bindings.GetReader(readName) + if err != nil { + return err + } + + ptrToValue, isValue := returnVal.(*values.Value) + if !isValue { + return binding.GetLatestValue(ctx, common.HexToAddress(address), confidenceLevel, params, returnVal) + } + + contractType, err := cr.CreateContractType(readName, false) if err != nil { return err } - return b.GetLatestValue(ctx, confidenceLevel, params, returnVal) + if err = cr.GetLatestValue(ctx, readName, confidenceLevel, params, contractType); err != nil { + return err + } + + value, err := values.Wrap(contractType) + if err != nil { + return err + } + + *ptrToValue = value + + return nil } func (cr *chainReader) BatchGetLatestValues(ctx context.Context, request commontypes.BatchGetLatestValuesRequest) (commontypes.BatchGetLatestValuesResult, error) { return cr.bindings.BatchGetLatestValues(ctx, request) } -func (cr *chainReader) Bind(ctx context.Context, bindings []commontypes.BoundContract) error { - return cr.bindings.Bind(ctx, cr.lp, bindings) -} +func (cr *chainReader) QueryKey( + ctx context.Context, + contract commontypes.BoundContract, + filter query.KeyFilter, + limitAndSort query.LimitAndSort, + sequenceDataType any, +) ([]commontypes.Sequence, error) { + binding, address, err := cr.bindings.GetReader(contract.ReadIdentifier(filter.Key)) + if err != nil { + return nil, err + } + + _, isValuePtr := sequenceDataType.(*values.Value) + if !isValuePtr { + return binding.QueryKey(ctx, common.HexToAddress(address), filter, limitAndSort, sequenceDataType) + } -func (cr *chainReader) QueryKey(ctx context.Context, contractName string, filter query.KeyFilter, limitAndSort query.LimitAndSort, sequenceDataType any) ([]commontypes.Sequence, error) { - b, err := cr.bindings.GetReadBinding(contractName, filter.Key) + dataTypeFromReadIdentifier, err := cr.CreateContractType(contract.ReadIdentifier(filter.Key), false) if err != nil { return nil, err } - return b.QueryKey(ctx, filter, limitAndSort, sequenceDataType) -} + sequence, err := binding.QueryKey(ctx, common.HexToAddress(address), filter, limitAndSort, dataTypeFromReadIdentifier) + if err != nil { + return nil, err + } -func (cr *chainReader) CreateContractType(contractName, itemType string, forEncoding bool) (any, error) { - return cr.codec.CreateType(WrapItemType(contractName, itemType, forEncoding), forEncoding) + sequenceOfValues := make([]commontypes.Sequence, len(sequence)) + for idx, entry := range sequence { + value, err := values.Wrap(entry.Data) + if err != nil { + return nil, err + } + sequenceOfValues[idx] = commontypes.Sequence{ + Cursor: entry.Cursor, + Head: entry.Head, + Data: &value, + } + } + + return sequenceOfValues, nil } -func WrapItemType(contractName, itemType string, isParams bool) string { - if isParams { - return fmt.Sprintf("params.%s.%s", contractName, itemType) - } - return fmt.Sprintf("return.%s.%s", contractName, itemType) +func (cr *chainReader) CreateContractType(readIdentifier string, forEncoding bool) (any, error) { + return cr.codec.CreateType(cr.bindings.ReadTypeIdentifier(readIdentifier, forEncoding), forEncoding) } func (cr *chainReader) addMethod( @@ -223,16 +267,11 @@ func (cr *chainReader) addMethod( return err } - cr.bindings.AddReadBinding(contractName, methodName, &methodBinding{ - lggr: cr.lggr, - contractName: contractName, - method: methodName, - ht: cr.ht, - client: cr.client, - confirmationsMapping: confirmations, - }) + if err = cr.bindings.AddReader(contractName, methodName, read.NewMethodBinding(contractName, methodName, cr.client, cr.ht, confirmations, cr.lggr)); err != nil { + return err + } - if err := cr.addEncoderDef(contractName, methodName, method.Inputs, method.ID, chainReaderDefinition.InputModifications); err != nil { + if err = cr.addEncoderDef(contractName, methodName, method.Inputs, method.ID, chainReaderDefinition.InputModifications); err != nil { return err } @@ -245,116 +284,117 @@ func (cr *chainReader) addEvent(contractName, eventName string, a abi.ABI, chain return fmt.Errorf("%w: event %s doesn't exist", commontypes.ErrInvalidConfig, chainReaderDefinition.ChainSpecificName) } - var inputFields []string - if chainReaderDefinition.EventDefinitions != nil { - inputFields = chainReaderDefinition.EventDefinitions.InputFields - } - - filterArgs, codecTopicInfo, indexArgNames := setupEventInput(event, inputFields) - if err := verifyEventIndexedInputsUsed(eventName, inputFields, indexArgNames); err != nil { - return err - } - - if err := codecTopicInfo.Init(); err != nil { + indexedAsUnIndexedABITypes, indexedTopicsCodecTypes, eventDWs := getEventTypes(event) + if err := indexedTopicsCodecTypes.Init(); err != nil { return err } - // Encoder def's codec won't be used to encode, only for its type as input for GetLatestValue - if err := cr.addEncoderDef(contractName, eventName, filterArgs, nil, chainReaderDefinition.InputModifications); err != nil { - return err - } - - inputInfo, inputModifier, err := cr.getEventInput(chainReaderDefinition, contractName, eventName) + // Encoder defs codec won't be used for encoding, but for storing caller filtering params which won't be hashed. + err := cr.addEncoderDef(contractName, eventName, indexedAsUnIndexedABITypes, nil, chainReaderDefinition.InputModifications) if err != nil { return err } + codecTypes, codecModifiers := make(map[string]types.CodecEntry), make(map[string]commoncodec.Modifier) + topicTypeID := codec.WrapItemType(contractName, eventName, true) + codecTypes[topicTypeID], codecModifiers[topicTypeID] = cr.getEventItemTypeAndModifier(topicTypeID) + confirmations, err := ConfirmationsFromConfig(chainReaderDefinition.ConfidenceConfirmations) if err != nil { return err } - eb := &eventBinding{ - contractName: contractName, - eventName: eventName, - lp: cr.lp, - hash: event.ID, - inputInfo: inputInfo, - inputModifier: inputModifier, - codecTopicInfo: codecTopicInfo, - topics: make(map[string]topicDetail), - eventDataWords: make(map[string]uint8), - confirmationsMapping: confirmations, - } - + eb := read.NewEventBinding(contractName, eventName, cr.lp, event.ID, indexedTopicsCodecTypes, confirmations) if eventDefinitions := chainReaderDefinition.EventDefinitions; eventDefinitions != nil { if eventDefinitions.PollingFilter != nil { - eb.filterRegisterer = &filterRegisterer{ - pollingFilter: eventDefinitions.PollingFilter.ToLPFilter(evmtypes.HashArray{a.Events[event.Name].ID}), - filterLock: sync.Mutex{}, - } + eb.SetFilter(eventDefinitions.PollingFilter.ToLPFilter(evmtypes.HashArray{a.Events[event.Name].ID})) } - if eventDefinitions.GenericDataWordNames != nil { - eb.eventDataWords = eventDefinitions.GenericDataWordNames + topicsDetails, topicsCodecTypeInfo, topicsModifiers, initQueryingErr := cr.initTopicQuerying(contractName, eventName, event.Inputs, eventDefinitions.GenericTopicNames, chainReaderDefinition.InputModifications) + if initQueryingErr != nil { + return initQueryingErr } + maps.Copy(codecTypes, topicsCodecTypeInfo) + // TODO BCFR-44 reused GetLatestValue params modifiers, probably can be left like this + maps.Copy(codecModifiers, topicsModifiers) + + // TODO BCFR-44 no dw modifier for now + dataWordsDetails, dWSCodecTypeInfo, initDWQueryingErr := cr.initDWQuerying(contractName, eventName, eventDWs, eventDefinitions.GenericDataWordNames) + if initDWQueryingErr != nil { + return initDWQueryingErr + } + maps.Copy(codecTypes, dWSCodecTypeInfo) - cr.addQueryingReadBindings(contractName, eventDefinitions.GenericTopicNames, event.Inputs, eb) + eb.SetTopicDetails(topicsDetails) + eb.SetDataWordsDetails(dataWordsDetails) } - cr.bindings.AddReadBinding(contractName, eventName, eb) + eb.SetCodecTypesAndModifiers(codecTypes, codecModifiers) + if err = cr.bindings.AddReader(contractName, eventName, eb); err != nil { + return err + } return cr.addDecoderDef(contractName, eventName, event.Inputs, chainReaderDefinition.OutputModifications) } -// addQueryingReadBindings reuses the eventBinding and maps it to topic and dataWord keys used for QueryKey. -func (cr *chainReader) addQueryingReadBindings(contractName string, genericTopicNames map[string]string, eventInputs abi.Arguments, eb *eventBinding) { - // add topic readBindings for QueryKey +// initTopicQuerying registers codec types and modifiers for topics to be used for typing value comparator QueryKey filters. +func (cr *chainReader) initTopicQuerying(contractName, eventName string, eventInputs abi.Arguments, genericTopicNames map[string]string, inputModifications commoncodec.ModifiersConfig) (map[string]read.TopicDetail, map[string]types.CodecEntry, map[string]commoncodec.Modifier, error) { + topicsDetails := make(map[string]read.TopicDetail) + topicsTypes := make(map[string]types.CodecEntry) + topicsModifiers := make(map[string]commoncodec.Modifier) for topicIndex, topic := range eventInputs { genericTopicName, ok := genericTopicNames[topic.Name] if ok { - eb.topics[genericTopicName] = topicDetail{ - Argument: topic, - Index: uint64(topicIndex), + topicsDetails[genericTopicName] = read.TopicDetail{Argument: topic, Index: uint64(topicIndex + 1)} + + topicTypeID := eventName + "." + genericTopicName + if err := cr.addEncoderDef(contractName, topicTypeID, abi.Arguments{{Type: topic.Type}}, nil, inputModifications); err != nil { + return nil, nil, nil, err } - } - cr.bindings.AddReadBinding(contractName, genericTopicName, eb) - } - // add data word readBindings for QueryKey - for genericDataWordName := range eb.eventDataWords { - cr.bindings.AddReadBinding(contractName, genericDataWordName, eb) + topicCodecTypeID := codec.WrapItemType(contractName, topicTypeID, true) + topicsTypes[topicCodecTypeID], topicsModifiers[topicCodecTypeID] = cr.getEventItemTypeAndModifier(topicCodecTypeID) + } } + return topicsDetails, topicsTypes, topicsModifiers, nil } -func (cr *chainReader) getEventInput(def types.ChainReaderDefinition, contractName, eventName string) ( - types.CodecEntry, codec.Modifier, error) { - inputInfo := cr.parsed.EncoderDefs[WrapItemType(contractName, eventName, true)] - inMod, err := def.InputModifications.ToModifier(DecoderHooks...) - if err != nil { - return nil, nil, err - } +// initDWQuerying registers codec types for evm data words to be used for typing value comparator QueryKey filters. +func (cr *chainReader) initDWQuerying(contractName, eventName string, eventDWs map[string]read.DataWordDetail, dWDefs map[string]string) (map[string]read.DataWordDetail, map[string]types.CodecEntry, error) { + dwsCodecTypeInfo := make(map[string]types.CodecEntry) + dWsDetail := make(map[string]read.DataWordDetail) - // initialize the modification - if _, err = inMod.RetypeToOffChain(reflect.PointerTo(inputInfo.CheckedType()), ""); err != nil { - return nil, nil, err - } + for genericName, onChainName := range dWDefs { + for _, dWDetail := range eventDWs { + if dWDetail.Name == onChainName { + dWsDetail[genericName] = dWDetail - return inputInfo, inMod, nil -} + dwTypeID := eventName + "." + genericName + if err := cr.addEncoderDef(contractName, dwTypeID, abi.Arguments{abi.Argument{Type: dWDetail.Type}}, nil, nil); err != nil { + return nil, nil, fmt.Errorf("%w: failed to init codec for data word %s on index %d querying for event: %q", err, genericName, dWDetail.Index, eventName) + } -func verifyEventIndexedInputsUsed(eventName string, inputFields []string, indexArgNames map[string]bool) error { - for _, value := range inputFields { - if !indexArgNames[abi.ToCamelCase(value)] { - return fmt.Errorf("%w: %s is not an indexed argument of event %s", commontypes.ErrInvalidConfig, value, eventName) + dwCodecTypeID := codec.WrapItemType(contractName, dwTypeID, true) + dwsCodecTypeInfo[dwCodecTypeID] = cr.parsed.EncoderDefs[dwCodecTypeID] + break + } + } + if _, ok := dWsDetail[genericName]; !ok { + return nil, nil, fmt.Errorf("failed to find data word: %q for event: %q, it either doesn't exist or can't be searched for", genericName, eventName) } } - return nil + return dWsDetail, dwsCodecTypeInfo, nil +} + +// getEventItemTypeAndModifier returns codec entry for expected incoming event item and the modifier. +func (cr *chainReader) getEventItemTypeAndModifier(itemType string) (types.CodecEntry, commoncodec.Modifier) { + inputTypeInfo := cr.parsed.EncoderDefs[itemType] + return inputTypeInfo, inputTypeInfo.Modifier() } -func (cr *chainReader) addEncoderDef(contractName, itemType string, args abi.Arguments, prefix []byte, inputModifications codec.ModifiersConfig) error { +func (cr *chainReader) addEncoderDef(contractName, itemType string, args abi.Arguments, prefix []byte, inputModifications commoncodec.ModifiersConfig) error { // ABI.Pack prepends the method.ID to the encodings, we'll need the encoder to do the same. - inputMod, err := inputModifications.ToModifier(DecoderHooks...) + inputMod, err := inputModifications.ToModifier(codec.DecoderHooks...) if err != nil { return err } @@ -364,51 +404,56 @@ func (cr *chainReader) addEncoderDef(contractName, itemType string, args abi.Arg return err } - cr.parsed.EncoderDefs[WrapItemType(contractName, itemType, true)] = input + cr.parsed.EncoderDefs[codec.WrapItemType(contractName, itemType, true)] = input return nil } -func (cr *chainReader) addDecoderDef(contractName, itemType string, outputs abi.Arguments, outputModifications codec.ModifiersConfig) error { - mod, err := outputModifications.ToModifier(DecoderHooks...) +func (cr *chainReader) addDecoderDef(contractName, itemType string, outputs abi.Arguments, outputModifications commoncodec.ModifiersConfig) error { + mod, err := outputModifications.ToModifier(codec.DecoderHooks...) if err != nil { return err } output := types.NewCodecEntry(outputs, nil, mod) - cr.parsed.DecoderDefs[WrapItemType(contractName, itemType, false)] = output + cr.parsed.DecoderDefs[codec.WrapItemType(contractName, itemType, false)] = output return output.Init() } -func setupEventInput(event abi.Event, inputFields []string) ([]abi.Argument, types.CodecEntry, map[string]bool) { - topicFieldDefs := map[string]bool{} - for _, value := range inputFields { - capFirstValue := abi.ToCamelCase(value) - topicFieldDefs[capFirstValue] = true - } - - filterArgs := make([]abi.Argument, 0, types.MaxTopicFields) - inputArgs := make([]abi.Argument, 0, len(event.Inputs)) - indexArgNames := map[string]bool{} +// getEventTypes returns abi args where indexed flag is set to false because we expect caller to filter with params that aren't hashed, +// codecEntry where expected on chain types are set, for e.g. indexed topics of type string or uint8[32] array are expected as common.Hash onchain, +// and un-indexed data info in form of evm indexed 32 byte data words. +func getEventTypes(event abi.Event) ([]abi.Argument, types.CodecEntry, map[string]read.DataWordDetail) { + indexedAsUnIndexedTypes := make([]abi.Argument, 0, types.MaxTopicFields) + indexedTypes := make([]abi.Argument, 0, len(event.Inputs)) + dataWords := make(map[string]read.DataWordDetail) + hadDynamicType := false + var dwIndex uint8 for _, input := range event.Inputs { if !input.Indexed { - continue - } + // there are some cases where we can calculate the exact data word index even if there was a dynamic type before, but it is complex and probably not needed. + if input.Type.T == abi.TupleTy || input.Type.T == abi.SliceTy || input.Type.T == abi.StringTy || input.Type.T == abi.BytesTy { + hadDynamicType = true + } + if hadDynamicType { + continue + } - filterWith := topicFieldDefs[abi.ToCamelCase(input.Name)] - if filterWith { - // When presenting the filter off-chain, - // the user will provide the unhashed version of the input - // The reader will hash topics if needed. - inputUnindexed := input - inputUnindexed.Indexed = false - filterArgs = append(filterArgs, inputUnindexed) + dataWords[event.Name+"."+input.Name] = read.DataWordDetail{ + Index: dwIndex, + Argument: input, + } + dwIndex++ + continue } - inputArgs = append(inputArgs, input) - indexArgNames[abi.ToCamelCase(input.Name)] = true + indexedAsUnIndexed := input + indexedAsUnIndexed.Indexed = false + // when presenting the filter off-chain, the caller will provide the unHashed version of the input and CR will hash topics when needed. + indexedAsUnIndexedTypes = append(indexedAsUnIndexedTypes, indexedAsUnIndexed) + indexedTypes = append(indexedTypes, input) } - return filterArgs, types.NewCodecEntry(inputArgs, nil, nil), indexArgNames + return indexedAsUnIndexedTypes, types.NewCodecEntry(indexedTypes, nil, nil), dataWords } // ConfirmationsFromConfig maps chain agnostic confidence levels defined in config to predefined EVM finality. @@ -433,12 +478,3 @@ func ConfirmationsFromConfig(values map[string]int) (map[primitives.ConfidenceLe return mappings, nil } - -// confidenceToConfirmations matches predefined chain agnostic confidence levels to predefined EVM finality. -func confidenceToConfirmations(confirmationsMapping map[primitives.ConfidenceLevel]evmtypes.Confirmations, confidenceLevel primitives.ConfidenceLevel) (evmtypes.Confirmations, error) { - confirmations, exists := confirmationsMapping[confidenceLevel] - if !exists { - return 0, fmt.Errorf("missing mapping for confidence level: %s", confidenceLevel) - } - return confirmations, nil -} diff --git a/core/services/relay/evm/evmtesting/chain_reader_historical_client_wrapper.go b/core/services/relay/evm/chain_reader_historical_client_wrapper_test.go similarity index 89% rename from core/services/relay/evm/evmtesting/chain_reader_historical_client_wrapper.go rename to core/services/relay/evm/chain_reader_historical_client_wrapper_test.go index b3d73be28f..28984779c3 100644 --- a/core/services/relay/evm/evmtesting/chain_reader_historical_client_wrapper.go +++ b/core/services/relay/evm/chain_reader_historical_client_wrapper_test.go @@ -1,4 +1,4 @@ -package evmtesting +package evm import ( "context" @@ -14,9 +14,10 @@ import ( clcommontypes "github.com/smartcontractkit/chainlink-common/pkg/types" . "github.com/smartcontractkit/chainlink-common/pkg/types/interfacetests" //nolint common practice to import test mods with . + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" - "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" ) @@ -30,7 +31,7 @@ type ClientWithContractHistory struct { func (cwh *ClientWithContractHistory) Init(_ context.Context, config types.ChainReaderConfig) error { cwh.valsWithCall = make(map[int64]valWithCall) - parsedTypes := evm.ParsedTypes{ + parsedTypes := codec.ParsedTypes{ EncoderDefs: make(map[string]types.CodecEntry), DecoderDefs: make(map[string]types.CodecEntry), } @@ -47,12 +48,12 @@ func (cwh *ClientWithContractHistory) Init(_ context.Context, config types.Chain continue } - inputMod, err := readDef.InputModifications.ToModifier(evm.DecoderHooks...) + inputMod, err := readDef.InputModifications.ToModifier(codec.DecoderHooks...) if err != nil { return err } - outputMod, err := readDef.OutputModifications.ToModifier(evm.DecoderHooks...) + outputMod, err := readDef.OutputModifications.ToModifier(codec.DecoderHooks...) if err != nil { return err } @@ -67,16 +68,18 @@ func (cwh *ClientWithContractHistory) Init(_ context.Context, config types.Chain return err } - parsedTypes.EncoderDefs[evm.WrapItemType(contractName, genericName, true)] = input - parsedTypes.DecoderDefs[evm.WrapItemType(contractName, genericName, false)] = output + parsedTypes.EncoderDefs[codec.WrapItemType(contractName, genericName, true)] = input + parsedTypes.DecoderDefs[codec.WrapItemType(contractName, genericName, false)] = output } } - codec, err := parsedTypes.ToCodec() + parsedCodec, err := parsedTypes.ToCodec() if err != nil { return err } - cwh.codec = codec + + cwh.codec = parsedCodec + return nil } @@ -90,7 +93,6 @@ func (cwh *ClientWithContractHistory) SetUintLatestValue(ctx context.Context, va ExpectedGetLatestValueArgs: forCall, val: val, } - return nil } @@ -123,7 +125,7 @@ func (cwh *ClientWithContractHistory) CallContract(ctx context.Context, msg ethe } // encode the expected call to compare with the actual call - dataToCmp, err := cwh.codec.Encode(ctx, valAndCall.Params, evm.WrapItemType(valAndCall.ContractName, valAndCall.ReadName, true)) + dataToCmp, err := cwh.codec.Encode(ctx, valAndCall.Params, codec.WrapItemType(valAndCall.ContractName, valAndCall.ReadName, true)) if err != nil { return nil, err } diff --git a/core/services/relay/evm/chain_writer.go b/core/services/relay/evm/chain_writer.go index 466811d115..d61c17b2ae 100644 --- a/core/services/relay/evm/chain_writer.go +++ b/core/services/relay/evm/chain_writer.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/chainlink-common/pkg/logger" commonservices "github.com/smartcontractkit/chainlink-common/pkg/services" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" @@ -18,8 +19,8 @@ import ( evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" evmtxmgr "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" ) @@ -45,7 +46,7 @@ func NewChainWriterService(logger logger.Logger, client evmclient.Client, txm ev sendStrategy: txmgr.NewSendEveryStrategy(), contracts: config.Contracts, - parsedContracts: &ParsedTypes{EncoderDefs: map[string]types.CodecEntry{}, DecoderDefs: map[string]types.CodecEntry{}}, + parsedContracts: &codec.ParsedTypes{EncoderDefs: map[string]types.CodecEntry{}, DecoderDefs: map[string]types.CodecEntry{}}, } if config.SendStrategy != nil { @@ -75,7 +76,7 @@ type chainWriter struct { sendStrategy txmgrtypes.TxStrategy contracts map[string]*types.ContractConfig - parsedContracts *ParsedTypes + parsedContracts *codec.ParsedTypes encoder commontypes.Encoder } @@ -100,7 +101,7 @@ func (w *chainWriter) SubmitTransaction(ctx context.Context, contract, method st return fmt.Errorf("method config not found: %v", method) } - calldata, err := w.encoder.Encode(ctx, args, WrapItemType(contract, method, true)) + calldata, err := w.encoder.Encode(ctx, args, codec.WrapItemType(contract, method, true)) if err != nil { return fmt.Errorf("%w: failed to encode args", err) } @@ -122,11 +123,16 @@ func (w *chainWriter) SubmitTransaction(ctx context.Context, contract, method st } } + gasLimit := methodConfig.GasLimit + if meta != nil && meta.GasLimit != nil { + gasLimit = meta.GasLimit.Uint64() + } + req := evmtxmgr.TxRequest{ FromAddress: methodConfig.FromAddress, ToAddress: common.HexToAddress(toAddress), EncodedPayload: calldata, - FeeLimit: methodConfig.GasLimit, + FeeLimit: gasLimit, Meta: txMeta, IdempotencyKey: &transactionID, Strategy: w.sendStrategy, @@ -156,7 +162,7 @@ func (w *chainWriter) parseContracts() error { } // ABI.Pack prepends the method.ID to the encodings, we'll need the encoder to do the same. - inputMod, err := methodConfig.InputModifications.ToModifier(DecoderHooks...) + inputMod, err := methodConfig.InputModifications.ToModifier(codec.DecoderHooks...) if err != nil { return fmt.Errorf("%w: failed to create input mods", err) } @@ -167,7 +173,7 @@ func (w *chainWriter) parseContracts() error { return fmt.Errorf("%w: failed to init codec entry for method %s", err, method) } - w.parsedContracts.EncoderDefs[WrapItemType(contract, method, true)] = input + w.parsedContracts.EncoderDefs[codec.WrapItemType(contract, method, true)] = input } } diff --git a/core/services/relay/evm/chain_writer_historical_wrapper_test.go b/core/services/relay/evm/chain_writer_historical_wrapper_test.go new file mode 100644 index 0000000000..c849d1f3d5 --- /dev/null +++ b/core/services/relay/evm/chain_writer_historical_wrapper_test.go @@ -0,0 +1,39 @@ +package evm + +import ( + "context" + "math/big" + + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + interfacetesttypes "github.com/smartcontractkit/chainlink-common/pkg/types/interfacetests" + primitives "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" +) + +// This wrapper is required to enable the ChainReader to access historical data +// Since the geth simulated backend doesn't support historical data, we use this +// thin wrapper. +type ChainWriterHistoricalWrapper struct { + commontypes.ChainWriter + cwh *ClientWithContractHistory +} + +func NewChainWriterHistoricalWrapper(cw commontypes.ChainWriter, cwh *ClientWithContractHistory) *ChainWriterHistoricalWrapper { + return &ChainWriterHistoricalWrapper{ChainWriter: cw, cwh: cwh} +} + +func (cwhw *ChainWriterHistoricalWrapper) SubmitTransaction(ctx context.Context, contractName, method string, args any, transactionID string, toAddress string, meta *commontypes.TxMeta, value *big.Int) error { + if primArgs, ok := args.(interfacetesttypes.PrimitiveArgs); ok { + callArgs := interfacetesttypes.ExpectedGetLatestValueArgs{ + ContractName: contractName, + ReadName: "GetAlterablePrimitiveValue", + ConfidenceLevel: primitives.Unconfirmed, + Params: nil, + ReturnVal: nil, + } + err := cwhw.cwh.SetUintLatestValue(ctx, primArgs.Value, callArgs) + if err != nil { + return err + } + } + return cwhw.ChainWriter.SubmitTransaction(ctx, contractName, method, args, transactionID, toAddress, meta, value) +} diff --git a/core/services/relay/evm/chain_writer_test.go b/core/services/relay/evm/chain_writer_test.go index e3fc8f8e22..b70a0dd0e3 100644 --- a/core/services/relay/evm/chain_writer_test.go +++ b/core/services/relay/evm/chain_writer_test.go @@ -67,6 +67,7 @@ func TestChainWriter(t *testing.T) { status commontypes.TransactionStatus }{ {uuid.NewString(), commontypes.Unknown}, + {uuid.NewString(), commontypes.Pending}, {uuid.NewString(), commontypes.Unconfirmed}, {uuid.NewString(), commontypes.Finalized}, {uuid.NewString(), commontypes.Failed}, diff --git a/core/services/relay/evm/codec.go b/core/services/relay/evm/codec/codec.go similarity index 88% rename from core/services/relay/evm/codec.go rename to core/services/relay/evm/codec/codec.go index 2f01adfbdc..3a859c89a8 100644 --- a/core/services/relay/evm/codec.go +++ b/core/services/relay/evm/codec/codec.go @@ -1,4 +1,4 @@ -package evm +package codec import ( "encoding/json" @@ -9,10 +9,9 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/mitchellh/mapstructure" - - "github.com/smartcontractkit/chainlink-common/pkg/codec" + "github.com/go-viper/mapstructure/v2" + commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" @@ -33,10 +32,10 @@ import ( // it was a *big.Int var DecoderHooks = []mapstructure.DecodeHookFunc{ decodeAccountAndAllowArraySliceHook, - codec.BigIntHook, - codec.SliceToArrayVerifySizeHook, + commoncodec.BigIntHook, + commoncodec.SliceToArrayVerifySizeHook, sizeVerifyBigIntHook, - codec.NumberHook, + commoncodec.NumberHook, } // NewCodec creates a new [commontypes.RemoteCodec] for EVM. @@ -97,13 +96,21 @@ func (c *evmCodec) CreateType(itemType string, forEncoding bool) (any, error) { return reflect.New(def.CheckedType()).Interface(), nil } +func WrapItemType(contractName, itemType string, isParams bool) string { + if isParams { + return fmt.Sprintf("params.%s.%s", contractName, itemType) + } + + return fmt.Sprintf("return.%s.%s", contractName, itemType) +} + var bigIntType = reflect.TypeOf((*big.Int)(nil)) func sizeVerifyBigIntHook(from, to reflect.Type, data any) (any, error) { if from.Implements(types.SizedBigIntType()) && !to.Implements(types.SizedBigIntType()) && !reflect.PointerTo(to).Implements(types.SizedBigIntType()) { - return codec.BigIntHook(from, bigIntType, reflect.ValueOf(data).Convert(bigIntType).Interface()) + return commoncodec.BigIntHook(from, bigIntType, reflect.ValueOf(data).Convert(bigIntType).Interface()) } if !to.Implements(types.SizedBigIntType()) { @@ -111,7 +118,7 @@ func sizeVerifyBigIntHook(from, to reflect.Type, data any) (any, error) { } var err error - data, err = codec.BigIntHook(from, bigIntType, data) + data, err = commoncodec.BigIntHook(from, bigIntType, data) if err != nil { return nil, err } diff --git a/core/services/relay/evm/codec_fuzz_test.go b/core/services/relay/evm/codec/codec_fuzz_test.go similarity index 92% rename from core/services/relay/evm/codec_fuzz_test.go rename to core/services/relay/evm/codec/codec_fuzz_test.go index 5870e9d77a..1b65a1bc29 100644 --- a/core/services/relay/evm/codec_fuzz_test.go +++ b/core/services/relay/evm/codec/codec_fuzz_test.go @@ -1,4 +1,4 @@ -package evm_test +package codec_test import ( "testing" diff --git a/core/services/relay/evm/codec_test.go b/core/services/relay/evm/codec/codec_test.go similarity index 96% rename from core/services/relay/evm/codec_test.go rename to core/services/relay/evm/codec/codec_test.go index af3170abf0..d43f118010 100644 --- a/core/services/relay/evm/codec_test.go +++ b/core/services/relay/evm/codec/codec_test.go @@ -1,4 +1,4 @@ -package evm_test +package codec_test import ( "encoding/json" @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink-common/pkg/codec" + commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/evmtesting" looptestutils "github.com/smartcontractkit/chainlink-common/pkg/loop/testutils" //nolint common practice to import test mods with . @@ -21,7 +21,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/chain_reader_tester" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" ) @@ -45,7 +45,7 @@ func TestCodec(t *testing.T) { codecConfig := types.CodecConfig{Configs: map[string]types.ChainCodecConfig{ codecName: {TypeABI: evmEncoderConfig}, }} - c, err := evm.NewCodec(codecConfig) + c, err := codec.NewCodec(codecConfig) require.NoError(t, err) result, err := c.Encode(testutils.Context(t), encode, codecName) @@ -91,7 +91,7 @@ func TestCodec_SimpleEncode(t *testing.T) { codecConfig := types.CodecConfig{Configs: map[string]types.ChainCodecConfig{ codecName: {TypeABI: evmEncoderConfig}, }} - c, err := evm.NewCodec(codecConfig) + c, err := codec.NewCodec(codecConfig) require.NoError(t, err) result, err := c.Encode(testutils.Context(t), input, codecName) @@ -120,7 +120,7 @@ func TestCodec_EncodeTuple(t *testing.T) { codecConfig := types.CodecConfig{Configs: map[string]types.ChainCodecConfig{ codecName: {TypeABI: evmEncoderConfig}, }} - c, err := evm.NewCodec(codecConfig) + c, err := codec.NewCodec(codecConfig) require.NoError(t, err) result, err := c.Encode(testutils.Context(t), input, codecName) @@ -152,7 +152,7 @@ func TestCodec_EncodeTupleWithLists(t *testing.T) { codecConfig := types.CodecConfig{Configs: map[string]types.ChainCodecConfig{ codecName: {TypeABI: evmEncoderConfig}, }} - c, err := evm.NewCodec(codecConfig) + c, err := codec.NewCodec(codecConfig) require.NoError(t, err) result, err := c.Encode(testutils.Context(t), input, codecName) @@ -204,13 +204,13 @@ func (it *codecInterfaceTester) GetCodec(t *testing.T) commontypes.Codec { entry.TypeABI = string(defBytes) if k != sizeItemType && k != NilType { - entry.ModifierConfigs = codec.ModifiersConfig{ - &codec.RenameModifierConfig{Fields: map[string]string{"NestedStruct.Inner.IntVal": "I"}}, + entry.ModifierConfigs = commoncodec.ModifiersConfig{ + &commoncodec.RenameModifierConfig{Fields: map[string]string{"NestedStruct.Inner.IntVal": "I"}}, } } if k == TestItemWithConfigExtra { - hardCode := &codec.HardCodeModifierConfig{ + hardCode := &commoncodec.HardCodeModifierConfig{ OnChainValues: map[string]any{ "BigField": testStruct.BigField.String(), "Account": hexutil.Encode(testStruct.Account), @@ -222,7 +222,7 @@ func (it *codecInterfaceTester) GetCodec(t *testing.T) commontypes.Codec { codecConfig.Configs[k] = entry } - c, err := evm.NewCodec(codecConfig) + c, err := codec.NewCodec(codecConfig) require.NoError(t, err) return c } diff --git a/core/services/relay/evm/decoder.go b/core/services/relay/evm/codec/decoder.go similarity index 92% rename from core/services/relay/evm/decoder.go rename to core/services/relay/evm/codec/decoder.go index 732ee91d9e..70c0f1d668 100644 --- a/core/services/relay/evm/decoder.go +++ b/core/services/relay/evm/codec/decoder.go @@ -1,11 +1,11 @@ -package evm +package codec import ( "context" "fmt" "reflect" - "github.com/mitchellh/mapstructure" + "github.com/go-viper/mapstructure/v2" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" @@ -45,7 +45,7 @@ func (m *decoder) Decode(_ context.Context, raw []byte, into any, itemType strin iInto.Set(reflect.MakeSlice(iInto.Type(), length, length)) return setElements(length, rDecode, iInto) default: - return mapstructureDecode(decode, into) + return MapstructureDecode(decode, into) } } @@ -83,7 +83,7 @@ func extractDecoding(info types.CodecEntry, raw []byte) (any, error) { func setElements(length int, rDecode reflect.Value, iInto reflect.Value) error { for i := 0; i < length; i++ { - if err := mapstructureDecode(rDecode.Index(i).Interface(), iInto.Index(i).Addr().Interface()); err != nil { + if err := MapstructureDecode(rDecode.Index(i).Interface(), iInto.Index(i).Addr().Interface()); err != nil { return err } } @@ -91,7 +91,7 @@ func setElements(length int, rDecode reflect.Value, iInto reflect.Value) error { return nil } -func mapstructureDecode(src, dest any) error { +func MapstructureDecode(src, dest any) error { mDecoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ DecodeHook: mapstructure.ComposeDecodeHookFunc(DecoderHooks...), Result: dest, diff --git a/core/services/relay/evm/encoder.go b/core/services/relay/evm/codec/encoder.go similarity index 90% rename from core/services/relay/evm/encoder.go rename to core/services/relay/evm/codec/encoder.go index ae60e4ab35..b7facde328 100644 --- a/core/services/relay/evm/encoder.go +++ b/core/services/relay/evm/codec/encoder.go @@ -1,4 +1,4 @@ -package evm +package codec import ( "context" @@ -52,13 +52,13 @@ func encode(item reflect.Value, info types.CodecEntry) ([]byte, error) { } switch item.Kind() { case reflect.Array, reflect.Slice: - native, err := representArray(item, info) + native, err := RepresentArray(item, info) if err != nil { return nil, err } return pack(info, native) case reflect.Struct, reflect.Map: - values, err := unrollItem(item, info) + values, err := UnrollItem(item, info) if err != nil { return nil, err } @@ -68,7 +68,7 @@ func encode(item reflect.Value, info types.CodecEntry) ([]byte, error) { } } -func representArray(item reflect.Value, info types.CodecEntry) (any, error) { +func RepresentArray(item reflect.Value, info types.CodecEntry) (any, error) { length := item.Len() checkedType := info.CheckedType() checked := reflect.New(checkedType) @@ -87,7 +87,7 @@ func representArray(item reflect.Value, info types.CodecEntry) (any, error) { checkedElm := checkedType.Elem() for i := 0; i < length; i++ { tmp := reflect.New(checkedElm) - if err := mapstructureDecode(item.Index(i).Interface(), tmp.Interface()); err != nil { + if err := MapstructureDecode(item.Index(i).Interface(), tmp.Interface()); err != nil { return nil, err } iChecked.Index(i).Set(tmp.Elem()) @@ -100,7 +100,7 @@ func representArray(item reflect.Value, info types.CodecEntry) (any, error) { return native.Elem().Interface(), nil } -func unrollItem(item reflect.Value, info types.CodecEntry) ([]any, error) { +func UnrollItem(item reflect.Value, info types.CodecEntry) ([]any, error) { checkedType := info.CheckedType() if item.CanAddr() { item = item.Addr() @@ -114,7 +114,7 @@ func unrollItem(item reflect.Value, info types.CodecEntry) ([]any, error) { } else if !info.IsNativePointer(item.Type()) { var err error checked := reflect.New(checkedType) - if err = mapstructureDecode(item.Interface(), checked.Interface()); err != nil { + if err = MapstructureDecode(item.Interface(), checked.Interface()); err != nil { return nil, err } if item, err = info.ToNative(checked); err != nil { diff --git a/core/services/relay/evm/parsed_types.go b/core/services/relay/evm/codec/parsed_types.go similarity index 80% rename from core/services/relay/evm/parsed_types.go rename to core/services/relay/evm/codec/parsed_types.go index 902c182e1d..31c95caf4d 100644 --- a/core/services/relay/evm/parsed_types.go +++ b/core/services/relay/evm/codec/parsed_types.go @@ -1,10 +1,10 @@ -package evm +package codec import ( "fmt" "reflect" - "github.com/smartcontractkit/chainlink-common/pkg/codec" + commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" @@ -16,7 +16,7 @@ type ParsedTypes struct { } func (parsed *ParsedTypes) ToCodec() (commontypes.RemoteCodec, error) { - modByTypeName := map[string]codec.Modifier{} + modByTypeName := map[string]commoncodec.Modifier{} if err := AddEntries(parsed.EncoderDefs, modByTypeName); err != nil { return nil, err } @@ -24,7 +24,7 @@ func (parsed *ParsedTypes) ToCodec() (commontypes.RemoteCodec, error) { return nil, err } - mod, err := codec.NewByItemTypeModifier(modByTypeName) + mod, err := commoncodec.NewByItemTypeModifier(modByTypeName) if err != nil { return nil, err } @@ -33,12 +33,12 @@ func (parsed *ParsedTypes) ToCodec() (commontypes.RemoteCodec, error) { decoder: &decoder{Definitions: parsed.DecoderDefs}, ParsedTypes: parsed, } - return codec.NewModifierCodec(underlying, mod, DecoderHooks...) + return commoncodec.NewModifierCodec(underlying, mod, DecoderHooks...) } // AddEntries extracts the mods from codecEntry and adds them to modByTypeName use with codec.NewByItemTypeModifier // Since each input/output can have its own modifications, we need to keep track of them by type name -func AddEntries(defs map[string]types.CodecEntry, modByTypeName map[string]codec.Modifier) error { +func AddEntries(defs map[string]types.CodecEntry, modByTypeName map[string]commoncodec.Modifier) error { for k, def := range defs { modByTypeName[k] = def.Modifier() _, err := def.Modifier().RetypeToOffChain(reflect.PointerTo(def.CheckedType()), k) diff --git a/core/services/relay/evm/commit_provider.go b/core/services/relay/evm/commit_provider.go index 780d1cee7b..7b007ec543 100644 --- a/core/services/relay/evm/commit_provider.go +++ b/core/services/relay/evm/commit_provider.go @@ -157,7 +157,7 @@ func (P *SrcCommitProvider) ContractTransmitter() ocrtypes.ContractTransmitter { return UnimplementedContractTransmitter{} } -func (P *SrcCommitProvider) ChainReader() commontypes.ContractReader { +func (P *SrcCommitProvider) ContractReader() commontypes.ContractReader { return nil } @@ -215,7 +215,7 @@ func (P *DstCommitProvider) ContractTransmitter() ocrtypes.ContractTransmitter { return P.contractTransmitter } -func (P *DstCommitProvider) ChainReader() commontypes.ContractReader { +func (P *DstCommitProvider) ContractReader() commontypes.ContractReader { return nil } diff --git a/core/services/relay/evm/config_poller.go b/core/services/relay/evm/config_poller.go index 2280d60d7e..a00b04b078 100644 --- a/core/services/relay/evm/config_poller.go +++ b/core/services/relay/evm/config_poller.go @@ -15,11 +15,11 @@ import ( "github.com/smartcontractkit/libocr/gethwrappers2/ocrconfigurationstoreevmsimple" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" - "github.com/smartcontractkit/chainlink/v2/core/logger" evmRelayTypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" ) diff --git a/core/services/relay/evm/contract_binding.go b/core/services/relay/evm/contract_binding.go deleted file mode 100644 index da2d7ed9bd..0000000000 --- a/core/services/relay/evm/contract_binding.go +++ /dev/null @@ -1,100 +0,0 @@ -package evm - -import ( - "context" - "fmt" - "sync" - - "github.com/ethereum/go-ethereum/common" - "github.com/google/uuid" - - commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" - evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" -) - -type filterRegisterer struct { - pollingFilter logpoller.Filter - filterLock sync.Mutex - // registerCalled is used to determine if Register was called during Chain Reader service Start. - // This is done to avoid calling Register while the service is not running because log poller is most likely also not running. - registerCalled bool -} - -// contractBinding stores read bindings and manages the common contract event filter. -type contractBinding struct { - name string - // filterRegisterer is used to manage polling filter registration for the common contract filter. - // The common contract filter should be used by events that share filtering args. - filterRegisterer - // key is read name method, event or event keys used for queryKey. - readBindings map[string]readBinding - // bound determines if address is set to the contract binding. - bound bool - bindLock sync.Mutex -} - -// Bind binds contract addresses to contract binding and registers the common contract polling filter. -func (cb *contractBinding) Bind(ctx context.Context, lp logpoller.LogPoller, boundContract commontypes.BoundContract) error { - // it's enough to just lock bound here since Register/Unregister are only called from here and from Start/Close - // even if they somehow happen at the same time it will be fine because of filter lock and hasFilter check - cb.bindLock.Lock() - defer cb.bindLock.Unlock() - - if cb.bound { - // we are changing contract address reference, so we need to unregister old filter it exists - if err := cb.Unregister(ctx, lp); err != nil { - return err - } - } - - cb.pollingFilter.Addresses = evmtypes.AddressArray{common.HexToAddress(boundContract.Address)} - cb.pollingFilter.Name = logpoller.FilterName(boundContract.Name+"."+uuid.NewString(), boundContract.Address) - cb.bound = true - - if cb.registerCalled { - return cb.Register(ctx, lp) - } - - return nil -} - -// Register registers the common contract filter. -func (cb *contractBinding) Register(ctx context.Context, lp logpoller.LogPoller) error { - cb.filterLock.Lock() - defer cb.filterLock.Unlock() - - cb.registerCalled = true - // can't be true before filters params are set so there is no race with a bad filter outcome - if !cb.bound { - return nil - } - - if len(cb.pollingFilter.EventSigs) > 0 && !lp.HasFilter(cb.pollingFilter.Name) { - if err := lp.RegisterFilter(ctx, cb.pollingFilter); err != nil { - return fmt.Errorf("%w: %w", commontypes.ErrInternal, err) - } - } - - return nil -} - -// Unregister unregisters the common contract filter. -func (cb *contractBinding) Unregister(ctx context.Context, lp logpoller.LogPoller) error { - cb.filterLock.Lock() - defer cb.filterLock.Unlock() - - if !cb.bound { - return nil - } - - if !lp.HasFilter(cb.pollingFilter.Name) { - return nil - } - - if err := lp.UnregisterFilter(ctx, cb.pollingFilter.Name); err != nil { - return fmt.Errorf("%w: %w", commontypes.ErrInternal, err) - } - - return nil -} diff --git a/core/services/relay/evm/contract_transmitter.go b/core/services/relay/evm/contract_transmitter.go index d594dfb921..10ab4697df 100644 --- a/core/services/relay/evm/contract_transmitter.go +++ b/core/services/relay/evm/contract_transmitter.go @@ -12,13 +12,15 @@ import ( "github.com/ethereum/go-ethereum/common" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" + "github.com/smartcontractkit/libocr/offchainreporting2plus/chains/evmutil" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services" ) @@ -102,7 +104,7 @@ func NewOCRContractTransmitter( transmittedEventSig: transmitted.ID, lp: lp, contractReader: caller, - lggr: lggr.Named("OCRContractTransmitter"), + lggr: logger.Named(lggr, "OCRContractTransmitter"), reportToEvmTxMeta: reportToEvmTxMetaNoop, excludeSigs: false, retention: 0, @@ -213,9 +215,6 @@ func (oc *contractTransmitter) LatestConfigDigestAndEpoch(ctx context.Context) ( } // Otherwise, we have to scan for the logs. - if err != nil { - return ocrtypes.ConfigDigest{}, 0, err - } latest, err := oc.lp.LatestLogByEventSigWithConfs(ctx, oc.transmittedEventSig, oc.contractAddress, 1) if err != nil { if errors.Is(err, sql.ErrNoRows) { diff --git a/core/services/relay/evm/event_binding.go b/core/services/relay/evm/event_binding.go deleted file mode 100644 index acfb1aa630..0000000000 --- a/core/services/relay/evm/event_binding.go +++ /dev/null @@ -1,465 +0,0 @@ -package evm - -import ( - "context" - "fmt" - "reflect" - "strings" - "sync" - - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common" - "github.com/google/uuid" - - "github.com/smartcontractkit/chainlink-common/pkg/codec" - commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" - "github.com/smartcontractkit/chainlink-common/pkg/types/query" - "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" - - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" - evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" -) - -type eventBinding struct { - address common.Address - contractName string - eventName string - lp logpoller.LogPoller - // filterRegisterer in eventBinding is to be used as an override for lp filter defined in the contract binding. - // If filterRegisterer is nil, this event should be registered with the lp filter defined in the contract binding. - *filterRegisterer - hash common.Hash - codec commontypes.RemoteCodec - // bound determines if address is set to the contract binding. - bound bool - bindLock sync.Mutex - inputInfo types.CodecEntry - inputModifier codec.Modifier - codecTopicInfo types.CodecEntry - // topics maps a generic topic name (key) to topic data - topics map[string]topicDetail - // eventDataWords maps a generic name to a word index - // key is a predefined generic name for evm log event data word - // for e.g. first evm data word(32bytes) of USDC log event is value so the key can be called value - eventDataWords map[string]uint8 - confirmationsMapping map[primitives.ConfidenceLevel]evmtypes.Confirmations -} - -type topicDetail struct { - abi.Argument - Index uint64 -} - -var _ readBinding = &eventBinding{} - -func (e *eventBinding) SetCodec(codec commontypes.RemoteCodec) { - e.codec = codec -} - -func (e *eventBinding) Bind(ctx context.Context, binding commontypes.BoundContract) error { - // it's enough to just lock bound here since Register/Unregister are only called from here and from Start/Close - // even if they somehow happen at the same time it will be fine because of filter lock and hasFilter check - e.bindLock.Lock() - defer e.bindLock.Unlock() - - if e.bound { - // we are changing contract address reference, so we need to unregister old filter it exists - if err := e.Unregister(ctx); err != nil { - return err - } - } - - e.address = common.HexToAddress(binding.Address) - - // filterRegisterer isn't required here because the event can also be polled for by the contractBinding common filter. - if e.filterRegisterer != nil { - id := fmt.Sprintf("%s.%s.%s", e.contractName, e.eventName, uuid.NewString()) - e.pollingFilter.Name = logpoller.FilterName(id, e.address) - e.pollingFilter.Addresses = evmtypes.AddressArray{e.address} - e.bound = true - if e.registerCalled { - return e.Register(ctx) - } - } - e.bound = true - return nil -} - -func (e *eventBinding) Register(ctx context.Context) error { - if e.filterRegisterer == nil { - return nil - } - - e.filterLock.Lock() - defer e.filterLock.Unlock() - - e.registerCalled = true - // can't be true before filters params are set so there is no race with a bad filter outcome - if !e.bound { - return nil - } - - if e.lp.HasFilter(e.pollingFilter.Name) { - return nil - } - - if err := e.lp.RegisterFilter(ctx, e.pollingFilter); err != nil { - return fmt.Errorf("%w: %w", commontypes.ErrInternal, err) - } - - return nil -} - -func (e *eventBinding) Unregister(ctx context.Context) error { - if e.filterRegisterer == nil { - return nil - } - - e.filterLock.Lock() - defer e.filterLock.Unlock() - - if !e.bound { - return nil - } - - if !e.lp.HasFilter(e.pollingFilter.Name) { - return nil - } - - if err := e.lp.UnregisterFilter(ctx, e.pollingFilter.Name); err != nil { - return fmt.Errorf("%w: %w", commontypes.ErrInternal, err) - } - - return nil -} - -func (e *eventBinding) GetLatestValue(ctx context.Context, confidenceLevel primitives.ConfidenceLevel, params, into any) error { - if err := e.validateBound(); err != nil { - return err - } - - confirmations, err := confidenceToConfirmations(e.confirmationsMapping, confidenceLevel) - if err != nil { - return err - } - - if len(e.inputInfo.Args()) == 0 { - return e.getLatestValueWithoutFilters(ctx, confirmations, into) - } - - return e.getLatestValueWithFilters(ctx, confirmations, params, into) -} - -func (e *eventBinding) QueryKey(ctx context.Context, filter query.KeyFilter, limitAndSort query.LimitAndSort, sequenceDataType any) ([]commontypes.Sequence, error) { - if err := e.validateBound(); err != nil { - return nil, err - } - - remapped, err := e.remap(filter) - if err != nil { - return nil, err - } - - // filter should always use the address and event sig - defaultExpressions := []query.Expression{ - logpoller.NewAddressFilter(e.address), - logpoller.NewEventSigFilter(e.hash), - } - remapped.Expressions = append(defaultExpressions, remapped.Expressions...) - - logs, err := e.lp.FilteredLogs(ctx, remapped, limitAndSort, e.contractName+"-"+e.eventName) - if err != nil { - return nil, err - } - - // no need to return an error. an empty list is fine - if len(logs) == 0 { - return []commontypes.Sequence{}, nil - } - - return e.decodeLogsIntoSequences(ctx, logs, sequenceDataType) -} - -func (e *eventBinding) validateBound() error { - if !e.bound { - return fmt.Errorf( - "%w: event %s that belongs to contract: %s, not bound", - commontypes.ErrInvalidType, - e.eventName, - e.contractName, - ) - } - return nil -} - -func (e *eventBinding) getLatestValueWithoutFilters(ctx context.Context, confs evmtypes.Confirmations, into any) error { - log, err := e.lp.LatestLogByEventSigWithConfs(ctx, e.hash, e.address, confs) - if err = wrapInternalErr(err); err != nil { - return err - } - - return e.decodeLog(ctx, log, into) -} - -func (e *eventBinding) getLatestValueWithFilters( - ctx context.Context, confs evmtypes.Confirmations, params, into any) error { - offChain, err := e.convertToOffChainType(params) - if err != nil { - return err - } - - checkedParams, err := e.inputModifier.TransformToOnChain(offChain, "" /* unused */) - if err != nil { - return err - } - - nativeParams, err := e.inputInfo.ToNative(reflect.ValueOf(checkedParams)) - if err != nil { - return err - } - - filtersAndIndices, err := e.encodeParams(nativeParams) - if err != nil { - return err - } - - fai := filtersAndIndices[0] - remainingFilters := filtersAndIndices[1:] - - logs, err := e.lp.IndexedLogs(ctx, e.hash, e.address, 1, []common.Hash{fai}, confs) - if err != nil { - return wrapInternalErr(err) - } - - // TODO Use filtered logs here BCF-3316 - // TODO: there should be a better way to ask log poller to filter these - // First, you should be able to ask for as many topics to match - // Second, you should be able to get the latest only - var logToUse *logpoller.Log - for _, log := range logs { - tmp := log - if compareLogs(&tmp, logToUse) > 0 && matchesRemainingFilters(&tmp, remainingFilters) { - // copy so that it's not pointing to the changing variable - logToUse = &tmp - } - } - - if logToUse == nil { - return fmt.Errorf("%w: no events found", commontypes.ErrNotFound) - } - - return e.decodeLog(ctx, logToUse, into) -} - -func (e *eventBinding) convertToOffChainType(params any) (any, error) { - offChain, err := e.codec.CreateType(WrapItemType(e.contractName, e.eventName, true), true) - if err != nil { - return nil, err - } - - if err = mapstructureDecode(params, offChain); err != nil { - return nil, err - } - - return offChain, nil -} - -func compareLogs(log, use *logpoller.Log) int64 { - if use == nil { - return 1 - } - - if log.BlockNumber != use.BlockNumber { - return log.BlockNumber - use.BlockNumber - } - - return log.LogIndex - use.LogIndex -} - -func matchesRemainingFilters(log *logpoller.Log, filters []common.Hash) bool { - for i, rfai := range filters { - if !reflect.DeepEqual(rfai[:], log.Topics[i+2]) { - return false - } - } - - return true -} - -func (e *eventBinding) encodeParams(item reflect.Value) ([]common.Hash, error) { - for item.Kind() == reflect.Pointer { - item = reflect.Indirect(item) - } - - var topics []any - switch item.Kind() { - case reflect.Array, reflect.Slice: - native, err := representArray(item, e.inputInfo) - if err != nil { - return nil, err - } - topics = []any{native} - case reflect.Struct, reflect.Map: - var err error - if topics, err = unrollItem(item, e.inputInfo); err != nil { - return nil, err - } - default: - return nil, fmt.Errorf("%w: cannot encode kind %v", commontypes.ErrInvalidType, item.Kind()) - } - - // abi params allow you to Pack a pointers, but MakeTopics doesn't work with pointers. - if err := e.derefTopics(topics); err != nil { - return nil, err - } - - hashes, err := abi.MakeTopics(topics) - if err != nil { - return nil, wrapInternalErr(err) - } - - if len(hashes) != 1 { - return nil, fmt.Errorf("%w: expected 1 filter set, got %d", commontypes.ErrInternal, len(hashes)) - } - - return hashes[0], nil -} - -func (e *eventBinding) derefTopics(topics []any) error { - for i, topic := range topics { - rTopic := reflect.ValueOf(topic) - if rTopic.Kind() == reflect.Pointer { - if rTopic.IsNil() { - return fmt.Errorf( - "%w: input topic %s cannot be nil", commontypes.ErrInvalidType, e.inputInfo.Args()[i].Name) - } - topics[i] = rTopic.Elem().Interface() - } - } - return nil -} - -func (e *eventBinding) decodeLog(ctx context.Context, log *logpoller.Log, into any) error { - if err := e.codec.Decode(ctx, log.Data, into, WrapItemType(e.contractName, e.eventName, false)); err != nil { - return err - } - - topics := make([]common.Hash, len(e.codecTopicInfo.Args())) - if len(log.Topics) < len(topics)+1 { - return fmt.Errorf("%w: not enough topics to decode", commontypes.ErrInvalidType) - } - - for i := 0; i < len(topics); i++ { - topics[i] = common.Hash(log.Topics[i+1]) - } - - topicsInto := map[string]any{} - if err := abi.ParseTopicsIntoMap(topicsInto, e.codecTopicInfo.Args(), topics); err != nil { - return fmt.Errorf("%w: %w", commontypes.ErrInvalidType, err) - } - - return mapstructureDecode(topicsInto, into) -} - -func (e *eventBinding) decodeLogsIntoSequences(ctx context.Context, logs []logpoller.Log, into any) ([]commontypes.Sequence, error) { - sequences := make([]commontypes.Sequence, len(logs)) - - for idx := range logs { - sequences[idx] = commontypes.Sequence{ - Cursor: fmt.Sprintf("%s-%s-%d", logs[idx].BlockHash, logs[idx].TxHash, logs[idx].LogIndex), - Head: commontypes.Head{ - Identifier: fmt.Sprint(logs[idx].BlockNumber), - Hash: logs[idx].BlockHash.Bytes(), - Timestamp: uint64(logs[idx].BlockTimestamp.Unix()), - }, - } - - var typeVal reflect.Value - - typeInto := reflect.TypeOf(into) - if typeInto.Kind() == reflect.Pointer { - typeVal = reflect.New(typeInto.Elem()) - } else { - typeVal = reflect.Indirect(reflect.New(typeInto)) - } - - // create a new value of the same type as 'into' for the data to be extracted to - sequences[idx].Data = typeVal.Interface() - - if err := e.decodeLog(ctx, &logs[idx], sequences[idx].Data); err != nil { - return nil, err - } - } - - return sequences, nil -} - -func (e *eventBinding) remap(filter query.KeyFilter) (query.KeyFilter, error) { - remapped := query.KeyFilter{} - - for _, expression := range filter.Expressions { - remappedExpression, err := e.remapExpression(filter.Key, expression) - if err != nil { - return query.KeyFilter{}, err - } - - remapped.Expressions = append(remapped.Expressions, remappedExpression) - } - - return remapped, nil -} - -func (e *eventBinding) remapExpression(key string, expression query.Expression) (query.Expression, error) { - if !expression.IsPrimitive() { - remappedBoolExpressions := make([]query.Expression, len(expression.BoolExpression.Expressions)) - - for i := range expression.BoolExpression.Expressions { - remapped, err := e.remapExpression(key, expression.BoolExpression.Expressions[i]) - if err != nil { - return query.Expression{}, err - } - - remappedBoolExpressions[i] = remapped - } - - if expression.BoolExpression.BoolOperator == query.AND { - return query.And(remappedBoolExpressions...), nil - } - - return query.Or(remappedBoolExpressions...), nil - } - - return e.remapPrimitive(key, expression) -} - -// remap chain agnostic primitives to chain specific -func (e *eventBinding) remapPrimitive(key string, expression query.Expression) (query.Expression, error) { - switch primitive := expression.Primitive.(type) { - case *primitives.Comparator: - if val, ok := e.eventDataWords[primitive.Name]; ok { - return logpoller.NewEventByWordFilter(e.hash, val, primitive.ValueComparators), nil - } - return logpoller.NewEventByTopicFilter(e.topics[key].Index, primitive.ValueComparators), nil - case *primitives.Confidence: - confirmations, err := confidenceToConfirmations(e.confirmationsMapping, primitive.ConfidenceLevel) - if err != nil { - return query.Expression{}, err - } - return logpoller.NewConfirmationsFilter(confirmations), nil - default: - return expression, nil - } -} - -func wrapInternalErr(err error) error { - if err == nil { - return nil - } - - errStr := err.Error() - if strings.Contains(errStr, "not found") || strings.Contains(errStr, "no rows") { - return fmt.Errorf("%w: %w", commontypes.ErrNotFound, err) - } - return fmt.Errorf("%w: %w", commontypes.ErrInternal, err) -} diff --git a/core/services/relay/evm/evm.go b/core/services/relay/evm/evm.go index f2143ceded..1dff3e7b7a 100644 --- a/core/services/relay/evm/evm.go +++ b/core/services/relay/evm/evm.go @@ -8,19 +8,12 @@ import ( "errors" "fmt" "math/big" + "net/http" "strings" "sync" - cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/estimatorconfig/interceptors/mantle" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/ccipcommit" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/ccipexec" - ccipconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/config" - cciptransmitter "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/transmitter" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/google/uuid" @@ -32,31 +25,42 @@ import ( "github.com/smartcontractkit/libocr/offchainreporting2/reportingplugin/median/evmreportcodec" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + chainselectors "github.com/smartcontractkit/chain-selectors" + ocr3capability "github.com/smartcontractkit/chainlink-common/pkg/capabilities/consensus/ocr3" "github.com/smartcontractkit/chainlink-common/pkg/capabilities/triggers" "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" coretypes "github.com/smartcontractkit/chainlink-common/pkg/types/core" + "github.com/smartcontractkit/chainlink/v2/core/logger" txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" txm "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" "github.com/smartcontractkit/chainlink/v2/core/services/llo" "github.com/smartcontractkit/chainlink/v2/core/services/llo/bm" + "github.com/smartcontractkit/chainlink/v2/core/services/llo/mercurytransmitter" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/ccipcommit" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/ccipexec" + ccipconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/config" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/estimatorconfig" + cciptransmitter "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/transmitter" lloconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/llo/config" mercuryconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/mercury/config" "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/functions" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury" mercuryutils "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/utils" reportcodecv1 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v1/reportcodec" reportcodecv2 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v2/reportcodec" reportcodecv3 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v3/reportcodec" + reportcodecv4 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v4/reportcodec" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" ) @@ -64,7 +68,7 @@ import ( var ( OCR2AggregatorTransmissionContractABI abi.ABI OCR2AggregatorLogDecoder LogDecoder - ChannelVerifierLogDecoder LogDecoder + OCR3CapabilityLogDecoder LogDecoder ) func init() { @@ -77,7 +81,7 @@ func init() { if err != nil { panic(err) } - ChannelVerifierLogDecoder, err = newChannelVerifierLogDecoder() + OCR3CapabilityLogDecoder, err = newOCR3CapabilityLogDecoder() if err != nil { panic(err) } @@ -139,10 +143,9 @@ func (u UnimplementedContractTransmitter) LatestConfigDigestAndEpoch(ctx context type Relayer struct { ds sqlutil.DataSource chain legacyevm.Chain - lggr logger.Logger + lggr logger.SugaredLogger ks CSAETHKeystore mercuryPool wsrpc.Pool - chainReader commontypes.ContractReader codec commontypes.Codec capabilitiesRegistry coretypes.CapabilitiesRegistry @@ -152,8 +155,7 @@ type Relayer struct { triggerCapability *triggers.MercuryTriggerService // LLO/data streams - cdcFactory llo.ChannelDefinitionCacheFactory - lloORM llo.ORM + cdcFactory func() (llo.ChannelDefinitionCacheFactory, error) } type CSAETHKeystore interface { @@ -167,6 +169,7 @@ type RelayerOpts struct { MercuryPool wsrpc.Pool TransmitterConfig mercury.TransmitterConfig CapabilitiesRegistry coretypes.CapabilitiesRegistry + HTTPClient *http.Client } func (c RelayerOpts) Validate() error { @@ -191,19 +194,23 @@ func NewRelayer(lggr logger.Logger, chain legacyevm.Chain, opts RelayerOpts) (*R if err != nil { return nil, fmt.Errorf("cannot create evm relayer: %w", err) } - lggr = lggr.Named("Relayer") - + sugared := logger.Sugared(lggr).Named("Relayer") mercuryORM := mercury.NewORM(opts.DS) - lloORM := llo.NewORM(opts.DS, chain.ID()) - cdcFactory := llo.NewChannelDefinitionCacheFactory(lggr, lloORM, chain.LogPoller()) + cdcFactory := sync.OnceValues(func() (llo.ChannelDefinitionCacheFactory, error) { + chainSelector, err := chainselectors.SelectorFromChainId(chain.ID().Uint64()) + if err != nil { + return nil, fmt.Errorf("failed to get chain selector for chain id %s: %w", chain.ID(), err) + } + lloORM := llo.NewORM(opts.DS, chainSelector) + return llo.NewChannelDefinitionCacheFactory(sugared, lloORM, chain.LogPoller(), opts.HTTPClient), nil + }) relayer := &Relayer{ ds: opts.DS, chain: chain, - lggr: lggr, + lggr: logger.Sugared(sugared), ks: opts.CSAETHKeystore, mercuryPool: opts.MercuryPool, cdcFactory: cdcFactory, - lloORM: lloORM, mercuryORM: mercuryORM, transmitterCfg: opts.TransmitterConfig, capabilitiesRegistry: opts.CapabilitiesRegistry, @@ -212,7 +219,12 @@ func NewRelayer(lggr logger.Logger, chain legacyevm.Chain, opts RelayerOpts) (*R // Initialize write target capability if configuration is defined if chain.Config().EVM().Workflow().ForwarderAddress() != nil { ctx := context.Background() - capability, err := NewWriteTarget(ctx, relayer, chain, lggr) + if chain.Config().EVM().Workflow().GasLimitDefault() == nil { + return nil, fmt.Errorf("unable to instantiate write target as default gas limit is not set") + } + + capability, err := NewWriteTarget(ctx, relayer, chain, *chain.Config().EVM().Workflow().GasLimitDefault(), + lggr) if err != nil { return nil, fmt.Errorf("failed to initialize write target: %w", err) } @@ -252,12 +264,57 @@ func (r *Relayer) HealthReport() (report map[string]error) { return } +func newOCR3CapabilityConfigProvider(ctx context.Context, lggr logger.Logger, chain legacyevm.Chain, opts *types.RelayOpts) (*configWatcher, error) { + if !common.IsHexAddress(opts.ContractID) { + return nil, errors.New("invalid contractID, expected hex address") + } + + aggregatorAddress := common.HexToAddress(opts.ContractID) + offchainConfigDigester := OCR3CapabilityOffchainConfigDigester{ + ChainID: chain.Config().EVM().ChainID().Uint64(), + ContractAddress: aggregatorAddress, + } + return newContractConfigProvider(ctx, lggr, chain, opts, aggregatorAddress, OCR3CapabilityLogDecoder, offchainConfigDigester) +} + +// NewPluginProvider, but customized to use a different config provider func (r *Relayer) NewOCR3CapabilityProvider(rargs commontypes.RelayArgs, pargs commontypes.PluginArgs) (commontypes.OCR3CapabilityProvider, error) { - pp, err := r.NewPluginProvider(rargs, pargs) + // TODO https://smartcontract-it.atlassian.net/browse/BCF-2887 + ctx := context.Background() + lggr := logger.Sugared(r.lggr).Named("PluginProvider").Named(rargs.ExternalJobID.String()) + relayOpts := types.NewRelayOpts(rargs) + relayConfig, err := relayOpts.RelayConfig() + if err != nil { + return nil, fmt.Errorf("failed to get relay config: %w", err) + } + + configWatcher, err := newOCR3CapabilityConfigProvider(ctx, r.lggr, r.chain, relayOpts) if err != nil { return nil, err } + transmitter, err := newOnChainContractTransmitter(ctx, r.lggr, rargs, r.ks.Eth(), configWatcher, configTransmitterOpts{}, OCR2AggregatorTransmissionContractABI) + if err != nil { + return nil, err + } + + var chainReaderService ChainReaderService + if relayConfig.ChainReader != nil { + if chainReaderService, err = NewChainReaderService(ctx, lggr, r.chain.LogPoller(), r.chain.HeadTracker(), r.chain.Client(), *relayConfig.ChainReader); err != nil { + return nil, err + } + } else { + lggr.Info("ChainReader missing from RelayConfig") + } + + pp := NewPluginProvider( + chainReaderService, + r.codec, + transmitter, + configWatcher, + lggr, + ) + fromAccount, err := pp.ContractTransmitter().FromAccount() if err != nil { return nil, err @@ -272,10 +329,14 @@ func (r *Relayer) NewOCR3CapabilityProvider(rargs commontypes.RelayArgs, pargs c func (r *Relayer) NewPluginProvider(rargs commontypes.RelayArgs, pargs commontypes.PluginArgs) (commontypes.PluginProvider, error) { // TODO https://smartcontract-it.atlassian.net/browse/BCF-2887 ctx := context.Background() + lggr := logger.Sugared(r.lggr).Named("PluginProvider").Named(rargs.ExternalJobID.String()) + relayOpts := types.NewRelayOpts(rargs) + relayConfig, err := relayOpts.RelayConfig() + if err != nil { + return nil, fmt.Errorf("failed to get relay config: %w", err) + } - lggr := r.lggr.Named("PluginProvider").Named(rargs.ExternalJobID.String()) - - configWatcher, err := newStandardConfigProvider(ctx, r.lggr, r.chain, types.NewRelayOpts(rargs)) + configWatcher, err := newStandardConfigProvider(ctx, r.lggr, r.chain, relayOpts) if err != nil { return nil, err } @@ -285,8 +346,17 @@ func (r *Relayer) NewPluginProvider(rargs commontypes.RelayArgs, pargs commontyp return nil, err } + var chainReaderService ChainReaderService + if relayConfig.ChainReader != nil { + if chainReaderService, err = NewChainReaderService(ctx, lggr, r.chain.LogPoller(), r.chain.HeadTracker(), r.chain.Client(), *relayConfig.ChainReader); err != nil { + return nil, err + } + } else { + lggr.Info("ChainReader missing from RelayConfig") + } + return NewPluginProvider( - r.chainReader, + chainReaderService, r.codec, transmitter, configWatcher, @@ -297,7 +367,7 @@ func (r *Relayer) NewPluginProvider(rargs commontypes.RelayArgs, pargs commontyp func (r *Relayer) NewMercuryProvider(rargs commontypes.RelayArgs, pargs commontypes.PluginArgs) (commontypes.MercuryProvider, error) { // TODO https://smartcontract-it.atlassian.net/browse/BCF-2887 ctx := context.Background() - lggr := r.lggr.Named("MercuryProvider").Named(rargs.ExternalJobID.String()) + lggr := logger.Sugared(r.lggr).Named("MercuryProvider").Named(rargs.ExternalJobID.String()) relayOpts := types.NewRelayOpts(rargs) relayConfig, err := relayOpts.RelayConfig() if err != nil { @@ -361,6 +431,7 @@ func (r *Relayer) NewMercuryProvider(rargs commontypes.RelayArgs, pargs commonty reportCodecV1 := reportcodecv1.NewReportCodec(*relayConfig.FeedID, lggr.Named("ReportCodecV1")) reportCodecV2 := reportcodecv2.NewReportCodec(*relayConfig.FeedID, lggr.Named("ReportCodecV2")) reportCodecV3 := reportcodecv3.NewReportCodec(*relayConfig.FeedID, lggr.Named("ReportCodecV3")) + reportCodecV4 := reportcodecv4.NewReportCodec(*relayConfig.FeedID, lggr.Named("ReportCodecV4")) var transmitterCodec mercury.TransmitterReportDecoder switch feedID.Version() { @@ -370,12 +441,14 @@ func (r *Relayer) NewMercuryProvider(rargs commontypes.RelayArgs, pargs commonty transmitterCodec = reportCodecV2 case 3: transmitterCodec = reportCodecV3 + case 4: + transmitterCodec = reportCodecV4 default: return nil, fmt.Errorf("invalid feed version %d", feedID.Version()) } transmitter := mercury.NewTransmitter(lggr, r.transmitterCfg, clients, privKey.PublicKey, rargs.JobID, *relayConfig.FeedID, r.mercuryORM, transmitterCodec, r.triggerCapability) - return NewMercuryProvider(cp, r.chainReader, r.codec, NewMercuryChainReader(r.chain.HeadTracker()), transmitter, reportCodecV1, reportCodecV2, reportCodecV3, lggr), nil + return NewMercuryProvider(cp, r.codec, NewMercuryChainReader(r.chain.HeadTracker()), transmitter, reportCodecV1, reportCodecV2, reportCodecV3, reportCodecV4, lggr), nil } func chainToUUID(chainID *big.Int) uuid.UUID { @@ -604,20 +677,38 @@ func (r *Relayer) NewLLOProvider(rargs commontypes.RelayArgs, pargs commontypes. // FIXME: Remove after benchmarking is done // https://smartcontract-it.atlassian.net/browse/MERC-3487 - var transmitter llo.Transmitter + var transmitter LLOTransmitter if lloCfg.BenchmarkMode { r.lggr.Info("Benchmark mode enabled, using dummy transmitter. NOTE: THIS WILL NOT TRANSMIT ANYTHING") transmitter = bm.NewTransmitter(r.lggr, fmt.Sprintf("%x", privKey.PublicKey)) } else { - var client wsrpc.Client - client, err = r.mercuryPool.Checkout(context.Background(), privKey, lloCfg.ServerPubKey, lloCfg.ServerURL()) - if err != nil { - return nil, err + clients := make(map[string]wsrpc.Client) + for _, server := range lloCfg.GetServers() { + client, err2 := r.mercuryPool.Checkout(context.Background(), privKey, server.PubKey, server.URL) + if err2 != nil { + return nil, err2 + } + clients[server.URL] = client } - transmitter = llo.NewTransmitter(r.lggr, client, privKey.PublicKey) + transmitter = llo.NewTransmitter(llo.TransmitterOpts{ + Lggr: r.lggr, + FromAccount: fmt.Sprintf("%x", privKey.PublicKey), // NOTE: This may need to change if we support e.g. multiple tranmsmitters, to be a composite of all keys + MercuryTransmitterOpts: mercurytransmitter.Opts{ + Lggr: r.lggr, + Cfg: r.transmitterCfg, + Clients: clients, + FromAccount: privKey.PublicKey, + DonID: relayConfig.LLODONID, + ORM: mercurytransmitter.NewORM(r.ds, relayConfig.LLODONID), + }, + }) + } + + cdcFactory, err := r.cdcFactory() + if err != nil { + return nil, err } - - cdc, err := r.cdcFactory.NewCache(lloCfg) + cdc, err := cdcFactory.NewCache(lloCfg) if err != nil { return nil, err } @@ -654,6 +745,8 @@ func (r *Relayer) NewConfigProvider(args commontypes.RelayArgs) (configProvider if args.ProviderType == "" { if relayConfig.FeedID == nil { args.ProviderType = "median" + } else if relayConfig.LLODONID > 0 { + args.ProviderType = "llo" } else { args.ProviderType = "mercury" } @@ -666,6 +759,8 @@ func (r *Relayer) NewConfigProvider(args commontypes.RelayArgs) (configProvider configProvider, err = newMercuryConfigProvider(ctx, lggr, r.chain, relayOpts) case "llo": configProvider, err = newLLOConfigProvider(ctx, lggr, r.chain, relayOpts) + case "ocr3-capability": + configProvider, err = newOCR3CapabilityConfigProvider(ctx, r.lggr, r.chain, relayOpts) default: return nil, fmt.Errorf("unrecognized provider type: %q", args.ProviderType) } @@ -696,16 +791,15 @@ func FilterNamesFromRelayArgs(args commontypes.RelayArgs) (filterNames []string, } type configWatcher struct { - services.StateMachine - lggr logger.Logger + services.Service + eng *services.Engine + contractAddress common.Address offchainDigester ocrtypes.OffchainConfigDigester configPoller types.ConfigPoller chain legacyevm.Chain runReplay bool fromBlock uint64 - stopCh services.StopChan - wg sync.WaitGroup } func newConfigWatcher(lggr logger.Logger, @@ -716,54 +810,41 @@ func newConfigWatcher(lggr logger.Logger, fromBlock uint64, runReplay bool, ) *configWatcher { - return &configWatcher{ - lggr: lggr.Named("ConfigWatcher").Named(contractAddress.String()), + cw := &configWatcher{ contractAddress: contractAddress, offchainDigester: offchainDigester, configPoller: configPoller, chain: chain, runReplay: runReplay, fromBlock: fromBlock, - stopCh: make(chan struct{}), } + cw.Service, cw.eng = services.Config{ + Name: fmt.Sprintf("ConfigWatcher.%s", contractAddress), + NewSubServices: nil, + Start: cw.start, + Close: cw.close, + }.NewServiceEngine(lggr) + return cw } -func (c *configWatcher) Name() string { - return c.lggr.Name() -} - -func (c *configWatcher) Start(ctx context.Context) error { - return c.StartOnce(fmt.Sprintf("configWatcher %x", c.contractAddress), func() error { - if c.runReplay && c.fromBlock != 0 { - // Only replay if it's a brand runReplay job. - c.wg.Add(1) - go func() { - defer c.wg.Done() - ctx, cancel := c.stopCh.NewCtx() - defer cancel() - c.lggr.Infow("starting replay for config", "fromBlock", c.fromBlock) - if err := c.configPoller.Replay(ctx, int64(c.fromBlock)); err != nil { - c.lggr.Errorw("error replaying for config", "err", err) - } else { - c.lggr.Infow("completed replaying for config", "fromBlock", c.fromBlock) - } - }() - } - c.configPoller.Start() - return nil - }) -} - -func (c *configWatcher) Close() error { - return c.StopOnce(fmt.Sprintf("configWatcher %x", c.contractAddress), func() error { - close(c.stopCh) - c.wg.Wait() - return c.configPoller.Close() - }) +func (c *configWatcher) start(ctx context.Context) error { + if c.runReplay && c.fromBlock != 0 { + // Only replay if it's a brand runReplay job. + c.eng.Go(func(ctx context.Context) { + c.eng.Infow("starting replay for config", "fromBlock", c.fromBlock) + if err := c.configPoller.Replay(ctx, int64(c.fromBlock)); err != nil { + c.eng.Errorw("error replaying for config", "err", err) + } else { + c.eng.Infow("completed replaying for config", "fromBlock", c.fromBlock) + } + }) + } + c.configPoller.Start() + return nil } -func (c *configWatcher) HealthReport() map[string]error { - return map[string]error{c.Name(): c.Healthy()} +func (c *configWatcher) close() error { + return c.configPoller.Close() } func (c *configWatcher) OffchainConfigDigester() ocrtypes.OffchainConfigDigester { @@ -917,7 +998,7 @@ func (r *Relayer) NewMedianProvider(rargs commontypes.RelayArgs, pargs commontyp // TODO https://smartcontract-it.atlassian.net/browse/BCF-2887 ctx := context.Background() - lggr := r.lggr.Named("MedianProvider").Named(rargs.ExternalJobID.String()) + lggr := logger.Sugared(r.lggr).Named("MedianProvider").Named(rargs.ExternalJobID.String()) relayOpts := types.NewRelayOpts(rargs) relayConfig, err := relayOpts.RelayConfig() if err != nil { @@ -974,7 +1055,7 @@ func (r *Relayer) NewMedianProvider(rargs commontypes.RelayArgs, pargs commontyp medianProvider.chainReader = chainReaderService if relayConfig.Codec != nil { - medianProvider.codec, err = NewCodec(*relayConfig.Codec) + medianProvider.codec, err = codec.NewCodec(*relayConfig.Codec) if err != nil { return nil, err } @@ -986,7 +1067,7 @@ func (r *Relayer) NewMedianProvider(rargs commontypes.RelayArgs, pargs commontyp } func (r *Relayer) NewAutomationProvider(rargs commontypes.RelayArgs, pargs commontypes.PluginArgs) (commontypes.AutomationProvider, error) { - lggr := r.lggr.Named("AutomationProvider").Named(rargs.ExternalJobID.String()) + lggr := logger.Sugared(r.lggr).Named("AutomationProvider").Named(rargs.ExternalJobID.String()) ocr2keeperRelayer := NewOCR2KeeperRelayer(r.ds, r.chain, lggr.Named("OCR2KeeperRelayer"), r.ks.Eth()) return ocr2keeperRelayer.NewOCR2KeeperProvider(rargs, pargs) @@ -1052,7 +1133,7 @@ func (p *medianProvider) ContractConfigTracker() ocrtypes.ContractConfigTracker return p.configWatcher.ContractConfigTracker() } -func (p *medianProvider) ChainReader() commontypes.ContractReader { +func (p *medianProvider) ContractReader() commontypes.ContractReader { return p.chainReader } diff --git a/core/services/relay/evm/evmtesting/chain_reader_interface_tester.go b/core/services/relay/evm/evmtesting/chain_components_interface_tester.go similarity index 55% rename from core/services/relay/evm/evmtesting/chain_reader_interface_tester.go rename to core/services/relay/evm/evmtesting/chain_components_interface_tester.go index 4474f054db..df34d63fc4 100644 --- a/core/services/relay/evm/evmtesting/chain_reader_interface_tester.go +++ b/core/services/relay/evm/evmtesting/chain_components_interface_tester.go @@ -3,7 +3,6 @@ package evmtesting import ( "context" "encoding/json" - "fmt" "math/big" "time" @@ -17,9 +16,13 @@ import ( clcommontypes "github.com/smartcontractkit/chainlink-common/pkg/types" . "github.com/smartcontractkit/chainlink-common/pkg/types/interfacetests" //nolint common practice to import test mods with . "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + evmtxmgr "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/chain_reader_tester" _ "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" // force binding for tx type @@ -32,13 +35,14 @@ import ( ) const ( - triggerWithDynamicTopic = "TriggeredEventWithDynamicTopic" - triggerWithAllTopics = "TriggeredWithFourTopics" - finalityDepth = 4 + triggerWithDynamicTopic = "TriggeredEventWithDynamicTopic" + triggerWithAllTopics = "TriggeredWithFourTopics" + triggerWithAllTopicsWithHashed = "TriggeredWithFourTopicsWithHashed" + finalityDepth = 4 ) -type EVMChainReaderInterfaceTesterHelper[T TestingT[T]] interface { - SetupAuth(t T) *bind.TransactOpts +type EVMChainComponentsInterfaceTesterHelper[T TestingT[T]] interface { + Init(t T) Client(t T) client.Client Commit() Backend() bind.ContractBackend @@ -47,21 +51,31 @@ type EVMChainReaderInterfaceTesterHelper[T TestingT[T]] interface { NewSqlxDB(t T) *sqlx.DB MaxWaitTimeForEvents() time.Duration GasPriceBufferPercent() int64 + Accounts(t T) []*bind.TransactOpts + TXM(T, client.Client) evmtxmgr.TxManager + // To enable the historical wrappers required for Simulated Backend tests. + ChainReaderEVMClient(ctx context.Context, t T, ht logpoller.HeadTracker, conf types.ChainReaderConfig) client.Client + WrappedChainWriter(cw clcommontypes.ChainWriter, client client.Client) clcommontypes.ChainWriter } -type EVMChainReaderInterfaceTester[T TestingT[T]] struct { - Helper EVMChainReaderInterfaceTesterHelper[T] - client client.Client - address string - address2 string - contractTesters map[string]*chain_reader_tester.ChainReaderTester - chainConfig types.ChainReaderConfig - auth *bind.TransactOpts - cr evm.ChainReaderService - dirtyContracts bool +type EVMChainComponentsInterfaceTester[T TestingT[T]] struct { + Helper EVMChainComponentsInterfaceTesterHelper[T] + client client.Client + address string + address2 string + contractTesters map[string]*chain_reader_tester.ChainReaderTester + chainReaderConfig types.ChainReaderConfig + chainWriterConfig types.ChainWriterConfig + deployerAuth *bind.TransactOpts + senderAuth *bind.TransactOpts + cr evm.ChainReaderService + cw evm.ChainWriterService + dirtyContracts bool + txm evmtxmgr.TxManager + gasEstimator gas.EvmFeeEstimator } -func (it *EVMChainReaderInterfaceTester[T]) Setup(t T) { +func (it *EVMChainComponentsInterfaceTester[T]) Setup(t T) { t.Cleanup(func() { // DB may be closed by the test already, ignore errors if it.cr != nil { @@ -72,6 +86,11 @@ func (it *EVMChainReaderInterfaceTester[T]) Setup(t T) { if it.dirtyContracts { it.contractTesters = nil } + + if it.cw != nil { + _ = it.cw.Close() + } + it.cw = nil }) // can re-use the same chain for tests, just make new contract for each test @@ -80,7 +99,11 @@ func (it *EVMChainReaderInterfaceTester[T]) Setup(t T) { return } - it.auth = it.Helper.SetupAuth(t) + // Need to separate accounts to ensure the nonce doesn't get misaligned after the + // contract deployments. + accounts := it.Helper.Accounts(t) + it.deployerAuth = accounts[0] + it.senderAuth = accounts[1] testStruct := CreateTestStruct[T](0, it) @@ -91,12 +114,12 @@ func (it *EVMChainReaderInterfaceTester[T]) Setup(t T) { }, } - it.chainConfig = types.ChainReaderConfig{ + it.chainReaderConfig = types.ChainReaderConfig{ Contracts: map[string]types.ChainContractReader{ AnyContractName: { ContractABI: chain_reader_tester.ChainReaderTesterMetaData.ABI, ContractPollingFilter: types.ContractPollingFilter{ - GenericEventNames: []string{EventName, EventWithFilterName}, + GenericEventNames: []string{EventName, EventWithFilterName, triggerWithAllTopicsWithHashed}, }, Configs: map[string]*types.ChainReaderDefinition{ MethodTakingLatestParamsReturningTestStruct: &methodTakingLatestParamsReturningTestStructConfig, @@ -112,6 +135,10 @@ func (it *EVMChainReaderInterfaceTester[T]) Setup(t T) { EventName: { ChainSpecificName: "Triggered", ReadType: types.Event, + EventDefinitions: &types.EventDefinitions{ + GenericTopicNames: map[string]string{"field": "Field"}, + GenericDataWordNames: map[string]string{"OracleID": "oracleId"}, + }, OutputModifications: codec.ModifiersConfig{ &codec.RenameModifierConfig{Fields: map[string]string{"NestedStruct.Inner.IntVal": "I"}}, }, @@ -119,13 +146,11 @@ func (it *EVMChainReaderInterfaceTester[T]) Setup(t T) { EventWithFilterName: { ChainSpecificName: "Triggered", ReadType: types.Event, - EventDefinitions: &types.EventDefinitions{InputFields: []string{"Field"}}, }, triggerWithDynamicTopic: { ChainSpecificName: triggerWithDynamicTopic, ReadType: types.Event, EventDefinitions: &types.EventDefinitions{ - InputFields: []string{"fieldHash"}, // No specific reason for filter being defined here instead of on contract level, this is just for test case variety. PollingFilter: &types.PollingFilter{}, }, @@ -137,7 +162,6 @@ func (it *EVMChainReaderInterfaceTester[T]) Setup(t T) { ChainSpecificName: triggerWithAllTopics, ReadType: types.Event, EventDefinitions: &types.EventDefinitions{ - InputFields: []string{"Field1", "Field2", "Field3"}, PollingFilter: &types.PollingFilter{}, }, // This doesn't have to be here, since the defalt mapping would work, but is left as an example. @@ -145,6 +169,11 @@ func (it *EVMChainReaderInterfaceTester[T]) Setup(t T) { // These float values can map to different finality concepts across chains. ConfidenceConfirmations: map[string]int{"0.0": int(evmtypes.Unconfirmed), "1.0": int(evmtypes.Finalized)}, }, + triggerWithAllTopicsWithHashed: { + ChainSpecificName: triggerWithAllTopicsWithHashed, + ReadType: types.Event, + EventDefinitions: &types.EventDefinitions{}, + }, MethodReturningSeenStruct: { ChainSpecificName: "returnSeen", InputModifications: codec.ModifiersConfig{ @@ -174,23 +203,90 @@ func (it *EVMChainReaderInterfaceTester[T]) Setup(t T) { }, }, } - it.client = it.Helper.Client(t) + it.GetContractReader(t) + it.txm = it.Helper.TXM(t, it.client) + it.chainWriterConfig = types.ChainWriterConfig{ + Contracts: map[string]*types.ContractConfig{ + AnyContractName: { + ContractABI: chain_reader_tester.ChainReaderTesterMetaData.ABI, + Configs: map[string]*types.ChainWriterDefinition{ + "addTestStruct": { + ChainSpecificName: "addTestStruct", + FromAddress: it.Helper.Accounts(t)[1].From, + GasLimit: 2_000_000, + Checker: "simulate", + InputModifications: codec.ModifiersConfig{ + &codec.RenameModifierConfig{Fields: map[string]string{"NestedStruct.Inner.IntVal": "I"}}, + }, + }, + "setAlterablePrimitiveValue": { + ChainSpecificName: "setAlterablePrimitiveValue", + FromAddress: it.Helper.Accounts(t)[1].From, + GasLimit: 2_000_000, + Checker: "simulate", + }, + "triggerEvent": { + ChainSpecificName: "triggerEvent", + FromAddress: it.Helper.Accounts(t)[1].From, + GasLimit: 2_000_000, + Checker: "simulate", + InputModifications: codec.ModifiersConfig{ + &codec.RenameModifierConfig{Fields: map[string]string{"NestedStruct.Inner.IntVal": "I"}}, + }, + }, + "triggerEventWithDynamicTopic": { + ChainSpecificName: "triggerEventWithDynamicTopic", + FromAddress: it.Helper.Accounts(t)[1].From, + GasLimit: 2_000_000, + Checker: "simulate", + }, + "triggerWithFourTopics": { + ChainSpecificName: "triggerWithFourTopics", + FromAddress: it.Helper.Accounts(t)[1].From, + GasLimit: 2_000_000, + Checker: "simulate", + }, + "triggerWithFourTopicsWithHashed": { + ChainSpecificName: "triggerWithFourTopicsWithHashed", + FromAddress: it.Helper.Accounts(t)[1].From, + GasLimit: 2_000_000, + Checker: "simulate", + }, + }, + }, + AnySecondContractName: { + ContractABI: chain_reader_tester.ChainReaderTesterMetaData.ABI, + Configs: map[string]*types.ChainWriterDefinition{ + "addTestStruct": { + ChainSpecificName: "addTestStruct", + FromAddress: it.Helper.Accounts(t)[1].From, + GasLimit: 2_000_000, + Checker: "simulate", + InputModifications: codec.ModifiersConfig{ + &codec.RenameModifierConfig{Fields: map[string]string{"NestedStruct.Inner.IntVal": "I"}}, + }, + }, + }, + }, + }, + MaxGasPrice: assets.NewWei(big.NewInt(1000000000000000000)), + } it.deployNewContracts(t) } -func (it *EVMChainReaderInterfaceTester[T]) Name() string { +func (it *EVMChainComponentsInterfaceTester[T]) Name() string { return "EVM" } -func (it *EVMChainReaderInterfaceTester[T]) GetAccountBytes(i int) []byte { +func (it *EVMChainComponentsInterfaceTester[T]) GetAccountBytes(i int) []byte { account := [20]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10} account[i%20] += byte(i) account[(i+3)%20] += byte(i + 3) return account[:] } -func (it *EVMChainReaderInterfaceTester[T]) GetChainReader(t T) clcommontypes.ContractReader { +func (it *EVMChainComponentsInterfaceTester[T]) GetContractReader(t T) clcommontypes.ContractReader { ctx := it.Helper.Context(t) if it.cr != nil { return it.cr @@ -205,20 +301,18 @@ func (it *EVMChainReaderInterfaceTester[T]) GetChainReader(t T) clcommontypes.Co RpcBatchSize: 1, KeepFinalizedBlocksDepth: 10000, } - ht := headtracker.NewSimulatedHeadTracker(it.client, lpOpts.UseFinalityTag, lpOpts.FinalityDepth) - lp := logpoller.NewLogPoller(logpoller.NewORM(it.Helper.ChainID(), db, lggr), it.client, lggr, ht, lpOpts) + ht := headtracker.NewSimulatedHeadTracker(it.Helper.Client(t), lpOpts.UseFinalityTag, lpOpts.FinalityDepth) + lp := logpoller.NewLogPoller(logpoller.NewORM(it.Helper.ChainID(), db, lggr), it.Helper.Client(t), lggr, ht, lpOpts) require.NoError(t, lp.Start(ctx)) // encode and decode the config to ensure the test covers type issues - confBytes, err := json.Marshal(it.chainConfig) + confBytes, err := json.Marshal(it.chainReaderConfig) require.NoError(t, err) conf, err := types.ChainReaderConfigFromBytes(confBytes) require.NoError(t, err) - // wrap the client so that we can mock historical contract state - cwh := &ClientWithContractHistory{Client: it.Helper.Client(t), HT: ht} - require.NoError(t, cwh.Init(ctx, conf)) + cwh := it.Helper.ChainReaderEVMClient(ctx, t, ht, conf) it.client = cwh cr, err := evm.NewChainReaderService(ctx, lggr, lp, ht, it.client, conf) @@ -228,136 +322,61 @@ func (it *EVMChainReaderInterfaceTester[T]) GetChainReader(t T) clcommontypes.Co return cr } -func (it *EVMChainReaderInterfaceTester[T]) SetTestStructLatestValue(t T, testStruct *TestStruct) { - it.sendTxWithTestStruct(t, it.address, testStruct, (*chain_reader_tester.ChainReaderTesterTransactor).AddTestStruct) -} - -func (it *EVMChainReaderInterfaceTester[T]) SetBatchLatestValues(t T, batchCallEntry BatchCallEntry) { - nameToAddress := make(map[string]string) - boundContracts := it.GetBindings(t) - for _, bc := range boundContracts { - nameToAddress[bc.Name] = bc.Address - } - - for contractName, contractBatch := range batchCallEntry { - require.Contains(t, nameToAddress, contractName) - for _, readEntry := range contractBatch { - val, isOk := readEntry.ReturnValue.(*TestStruct) - if !isOk { - require.Fail(t, "expected *TestStruct for contract: %s read: %s, but received %T", contractName, readEntry.Name, readEntry.ReturnValue) - } - it.sendTxWithTestStruct(t, nameToAddress[contractName], val, (*chain_reader_tester.ChainReaderTesterTransactor).AddTestStruct) - } - } +// This function is no longer necessary for Simulated Backend or Testnet tests. +func (it *EVMChainComponentsInterfaceTester[T]) GenerateBlocksTillConfidenceLevel(t T, contractName, readName string, confidenceLevel primitives.ConfidenceLevel) { } -// SetUintLatestValue is supposed to be used for testing confidence levels, but geth simulated backend doesn't support calling past state -func (it *EVMChainReaderInterfaceTester[T]) SetUintLatestValue(t T, val uint64, forCall ExpectedGetLatestValueArgs) { - cw, ok := it.client.(*ClientWithContractHistory) - if !ok { - require.True(t, ok, "SetUintLatestValue should always be used for tests involving finality") +func (it *EVMChainComponentsInterfaceTester[T]) GetChainWriter(t T) clcommontypes.ChainWriter { + ctx := it.Helper.Context(t) + if it.cw != nil { + return it.cw } - it.sendTxWithUintVal(t, it.address, val, (*chain_reader_tester.ChainReaderTesterTransactor).SetAlterablePrimitiveValue) - require.NoError(t, cw.SetUintLatestValue(it.Helper.Context(t), val, forCall)) -} - -func (it *EVMChainReaderInterfaceTester[T]) TriggerEvent(t T, testStruct *TestStruct) { - it.sendTxWithTestStruct(t, it.address, testStruct, (*chain_reader_tester.ChainReaderTesterTransactor).TriggerEvent) -} + cw, err := evm.NewChainWriterService(logger.NullLogger, it.client, it.txm, it.gasEstimator, it.chainWriterConfig) + require.NoError(t, err) + it.cw = it.Helper.WrappedChainWriter(cw, it.client) -// GenerateBlocksTillConfidenceLevel is supposed to be used for testing confidence levels, but geth simulated backend doesn't support calling past state -func (it *EVMChainReaderInterfaceTester[T]) GenerateBlocksTillConfidenceLevel(t T, contractName, readName string, confidenceLevel primitives.ConfidenceLevel) { - contractCfg, ok := it.chainConfig.Contracts[contractName] - if !ok { - t.Errorf("contract %s not found", contractName) - return - } - readCfg, ok := contractCfg.Configs[readName] - require.True(t, ok, fmt.Sprintf("readName: %s not found for contract: %s", readName, contractName)) - toEvmConf, err := evm.ConfirmationsFromConfig(readCfg.ConfidenceConfirmations) - require.True(t, ok, fmt.Errorf("failed to parse confidence level mapping:%s not found for contract: %s readName: %s, err:%w", confidenceLevel, readName, contractName, err)) - confirmations, ok := toEvmConf[confidenceLevel] - require.True(t, ok, fmt.Sprintf("confidence level mapping:%s not found for contract: %s readName: %s", confidenceLevel, readName, contractName)) - - if confirmations == evmtypes.Finalized { - for i := 0; i < finalityDepth; i++ { - it.Helper.Commit() - } - } + require.NoError(t, err) + require.NoError(t, cw.Start(ctx)) + return it.cw } -func (it *EVMChainReaderInterfaceTester[T]) GetBindings(_ T) []clcommontypes.BoundContract { +func (it *EVMChainComponentsInterfaceTester[T]) GetBindings(_ T) []clcommontypes.BoundContract { return []clcommontypes.BoundContract{ {Name: AnyContractName, Address: it.address}, {Name: AnySecondContractName, Address: it.address2}, } } -type uintFn = func(*chain_reader_tester.ChainReaderTesterTransactor, *bind.TransactOpts, uint64) (*gethtypes.Transaction, error) - -// sendTxWithUintVal is supposed to be used for testing confidence levels, but geth simulated backend doesn't support calling past state -func (it *EVMChainReaderInterfaceTester[T]) sendTxWithUintVal(t T, contractAddress string, val uint64, fn uintFn) { - tx, err := fn( - &it.contractTesters[contractAddress].ChainReaderTesterTransactor, - it.GetAuthWithGasSet(t), - val, - ) - - require.NoError(t, err) - it.Helper.Commit() - it.IncNonce() - it.AwaitTx(t, tx) - it.dirtyContracts = true -} - -type testStructFn = func(*chain_reader_tester.ChainReaderTesterTransactor, *bind.TransactOpts, int32, string, uint8, [32]uint8, common.Address, []common.Address, *big.Int, chain_reader_tester.MidLevelTestStruct) (*gethtypes.Transaction, error) - -func (it *EVMChainReaderInterfaceTester[T]) sendTxWithTestStruct(t T, contractAddress string, testStruct *TestStruct, fn testStructFn) { - tx, err := fn( - &it.contractTesters[contractAddress].ChainReaderTesterTransactor, - it.GetAuthWithGasSet(t), - *testStruct.Field, - testStruct.DifferentField, - uint8(testStruct.OracleID), - OracleIdsToBytes(testStruct.OracleIDs), - common.Address(testStruct.Account), - ConvertAccounts(testStruct.Accounts), - testStruct.BigField, - MidToInternalType(testStruct.NestedStruct), - ) - require.NoError(t, err) - it.Helper.Commit() - it.IncNonce() - it.AwaitTx(t, tx) +func (it *EVMChainComponentsInterfaceTester[T]) DirtyContracts() { it.dirtyContracts = true } -func (it *EVMChainReaderInterfaceTester[T]) GetAuthWithGasSet(t T) *bind.TransactOpts { +func (it *EVMChainComponentsInterfaceTester[T]) GetAuthWithGasSet(t T) *bind.TransactOpts { gasPrice, err := it.client.SuggestGasPrice(it.Helper.Context(t)) require.NoError(t, err) extra := new(big.Int).Mul(gasPrice, big.NewInt(it.Helper.GasPriceBufferPercent())) extra = extra.Div(extra, big.NewInt(100)) - it.auth.GasPrice = gasPrice.Add(gasPrice, extra) - return it.auth + it.deployerAuth.GasPrice = gasPrice.Add(gasPrice, extra) + return it.deployerAuth } -func (it *EVMChainReaderInterfaceTester[T]) IncNonce() { - if it.auth.Nonce == nil { - it.auth.Nonce = big.NewInt(1) +func (it *EVMChainComponentsInterfaceTester[T]) IncNonce() { + if it.deployerAuth.Nonce == nil { + it.deployerAuth.Nonce = big.NewInt(1) } else { - it.auth.Nonce = it.auth.Nonce.Add(it.auth.Nonce, big.NewInt(1)) + it.deployerAuth.Nonce = it.deployerAuth.Nonce.Add(it.deployerAuth.Nonce, big.NewInt(1)) } } -func (it *EVMChainReaderInterfaceTester[T]) AwaitTx(t T, tx *gethtypes.Transaction) { +func (it *EVMChainComponentsInterfaceTester[T]) AwaitTx(t T, tx *gethtypes.Transaction) { ctx := it.Helper.Context(t) receipt, err := bind.WaitMined(ctx, it.client, tx) require.NoError(t, err) require.Equal(t, gethtypes.ReceiptStatusSuccessful, receipt.Status) } -func (it *EVMChainReaderInterfaceTester[T]) deployNewContracts(t T) { +func (it *EVMChainComponentsInterfaceTester[T]) deployNewContracts(t T) { // First test deploy both contracts, otherwise only deploy contracts if cleanup decides that we need to. if it.address == "" || it.contractTesters == nil { it.contractTesters = make(map[string]*chain_reader_tester.ChainReaderTester, 2) @@ -366,13 +385,14 @@ func (it *EVMChainReaderInterfaceTester[T]) deployNewContracts(t T) { it.address, it.address2 = address, address2 it.contractTesters[it.address] = ts1 it.contractTesters[it.address2] = ts2 + it.dirtyContracts = false } } -func (it *EVMChainReaderInterfaceTester[T]) deployNewContract(t T) (string, *chain_reader_tester.ChainReaderTester) { +func (it *EVMChainComponentsInterfaceTester[T]) deployNewContract(t T) (string, *chain_reader_tester.ChainReaderTester) { // 105528 was in the error: gas too low: have 0, want 105528 // Not sure if there's a better way to get it. - it.auth.GasLimit = 10552800 + it.deployerAuth.GasLimit = 10552800 address, tx, ts, err := chain_reader_tester.DeployChainReaderTester(it.GetAuthWithGasSet(t), it.Helper.Backend()) require.NoError(t, err) @@ -383,16 +403,16 @@ func (it *EVMChainReaderInterfaceTester[T]) deployNewContract(t T) (string, *cha return address.String(), ts } -func (it *EVMChainReaderInterfaceTester[T]) MaxWaitTimeForEvents() time.Duration { +func (it *EVMChainComponentsInterfaceTester[T]) MaxWaitTimeForEvents() time.Duration { return it.Helper.MaxWaitTimeForEvents() } -func OracleIdsToBytes(oracleIDs [32]commontypes.OracleID) [32]byte { - convertedIds := [32]byte{} +func OracleIDsToBytes(oracleIDs [32]commontypes.OracleID) [32]byte { + convertedIDs := [32]byte{} for i, id := range oracleIDs { - convertedIds[i] = byte(id) + convertedIDs[i] = byte(id) } - return convertedIds + return convertedIDs } func ConvertAccounts(accounts [][]byte) []common.Address { @@ -408,7 +428,7 @@ func ToInternalType(testStruct TestStruct) chain_reader_tester.TestStruct { Field: *testStruct.Field, DifferentField: testStruct.DifferentField, OracleId: byte(testStruct.OracleID), - OracleIds: OracleIdsToBytes(testStruct.OracleIDs), + OracleIds: OracleIDsToBytes(testStruct.OracleIDs), Account: common.Address(testStruct.Account), Accounts: ConvertAccounts(testStruct.Accounts), BigField: testStruct.BigField, diff --git a/core/services/relay/evm/evmtesting/run_tests.go b/core/services/relay/evm/evmtesting/run_tests.go index f958c055ca..c999693de5 100644 --- a/core/services/relay/evm/evmtesting/run_tests.go +++ b/core/services/relay/evm/evmtesting/run_tests.go @@ -10,40 +10,58 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-common/pkg/types" clcommontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types/query" "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/read" . "github.com/smartcontractkit/chainlink-common/pkg/types/interfacetests" //nolint common practice to import test mods with . - - "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" ) -func RunChainReaderEvmTests[T TestingT[T]](t T, it *EVMChainReaderInterfaceTester[T]) { - RunChainReaderInterfaceTests[T](t, it) +func RunChainComponentsEvmTests[T TestingT[T]](t T, it *EVMChainComponentsInterfaceTester[T]) { + RunContractReaderEvmTests[T](t, it) + // Add ChainWriter tests here +} + +func RunChainComponentsInLoopEvmTests[T TestingT[T]](t T, it ChainComponentsInterfaceTester[T]) { + RunContractReaderInLoopTests[T](t, it) + // Add ChainWriter tests here +} + +func RunContractReaderEvmTests[T TestingT[T]](t T, it *EVMChainComponentsInterfaceTester[T]) { + RunContractReaderInterfaceTests[T](t, it, false) t.Run("Dynamically typed topics can be used to filter and have type correct in return", func(t T) { it.Setup(t) anyString := "foo" - it.dirtyContracts = true - tx, err := it.contractTesters[it.address].ChainReaderTesterTransactor.TriggerEventWithDynamicTopic(it.GetAuthWithGasSet(t), anyString) - require.NoError(t, err) - it.Helper.Commit() - it.IncNonce() - it.AwaitTx(t, tx) ctx := it.Helper.Context(t) - cr := it.GetChainReader(t) - require.NoError(t, cr.Bind(ctx, it.GetBindings(t))) + cr := it.GetContractReader(t) + bindings := it.GetBindings(t) + require.NoError(t, cr.Bind(ctx, bindings)) + + type DynamicEvent struct { + Field string + } + SubmitTransactionToCW(t, it, "triggerEventWithDynamicTopic", DynamicEvent{Field: anyString}, bindings[0], types.Unconfirmed) input := struct{ Field string }{Field: anyString} tp := cr.(clcommontypes.ContractTypeProvider) - output, err := tp.CreateContractType(AnyContractName, triggerWithDynamicTopic, false) + + readName := types.BoundContract{ + Address: bindings[0].Address, + Name: AnyContractName, + }.ReadIdentifier(triggerWithDynamicTopic) + + output, err := tp.CreateContractType(readName, false) require.NoError(t, err) rOutput := reflect.Indirect(reflect.ValueOf(output)) require.Eventually(t, func() bool { - return cr.GetLatestValue(ctx, AnyContractName, triggerWithDynamicTopic, primitives.Unconfirmed, input, output) == nil + return cr.GetLatestValue(ctx, readName, primitives.Unconfirmed, input, output) == nil }, it.MaxWaitTimeForEvents(), 100*time.Millisecond) assert.Equal(t, &anyString, rOutput.FieldByName("Field").Interface()) @@ -54,44 +72,134 @@ func RunChainReaderEvmTests[T TestingT[T]](t T, it *EVMChainReaderInterfaceTeste t.Run("Multiple topics can filter together", func(t T) { it.Setup(t) - it.dirtyContracts = true + ctx := it.Helper.Context(t) + cr := it.GetContractReader(t) + bindings := it.GetBindings(t) + + require.NoError(t, cr.Bind(ctx, bindings)) + triggerFourTopics(t, it, int32(1), int32(2), int32(3)) triggerFourTopics(t, it, int32(2), int32(2), int32(3)) triggerFourTopics(t, it, int32(1), int32(3), int32(3)) triggerFourTopics(t, it, int32(1), int32(2), int32(4)) - ctx := it.Helper.Context(t) - cr := it.GetChainReader(t) - require.NoError(t, cr.Bind(ctx, it.GetBindings(t))) + var bound types.BoundContract + for idx := range bindings { + if bindings[idx].Name == AnyContractName { + bound = bindings[idx] + } + } + var latest struct{ Field1, Field2, Field3 int32 } params := struct{ Field1, Field2, Field3 int32 }{Field1: 1, Field2: 2, Field3: 3} time.Sleep(it.MaxWaitTimeForEvents()) - require.NoError(t, cr.GetLatestValue(ctx, AnyContractName, triggerWithAllTopics, primitives.Unconfirmed, params, &latest)) + require.NoError(t, cr.GetLatestValue(ctx, bound.ReadIdentifier(triggerWithAllTopics), primitives.Unconfirmed, params, &latest)) assert.Equal(t, int32(1), latest.Field1) assert.Equal(t, int32(2), latest.Field2) assert.Equal(t, int32(3), latest.Field3) }) + t.Run("Filtering can be done on indexed topics that get hashed", func(t T) { + it.Setup(t) + + cr := it.GetContractReader(t) + ctx := it.Helper.Context(t) + bindings := it.GetBindings(t) + + require.NoError(t, cr.Bind(ctx, bindings)) + + triggerFourTopicsWithHashed(t, it, "1", [32]uint8{2}, [32]byte{5}) + triggerFourTopicsWithHashed(t, it, "2", [32]uint8{2}, [32]byte{3}) + triggerFourTopicsWithHashed(t, it, "1", [32]uint8{3}, [32]byte{3}) + + var bound types.BoundContract + for idx := range bindings { + if bindings[idx].Name == AnyContractName { + bound = bindings[idx] + } + } + + var latest struct { + Field3 [32]byte + } + params := struct { + Field1 string + Field2 [32]uint8 + Field3 [32]byte + }{Field1: "1", Field2: [32]uint8{2}, Field3: [32]byte{5}} + + time.Sleep(it.MaxWaitTimeForEvents()) + require.NoError(t, cr.GetLatestValue(ctx, bound.ReadIdentifier(triggerWithAllTopicsWithHashed), primitives.Unconfirmed, params, &latest)) + // only checking Field3 topic makes sense since it isn't hashed, to check other fields we'd have to replicate solidity encoding and hashing + assert.Equal(t, [32]uint8{5}, latest.Field3) + }) + t.Run("Bind returns error on missing contract at address", func(t T) { it.Setup(t) addr := common.BigToAddress(big.NewInt(42)) - reader := it.GetChainReader(t) + reader := it.GetContractReader(t) ctx := it.Helper.Context(t) err := reader.Bind(ctx, []clcommontypes.BoundContract{{Name: AnyContractName, Address: addr.Hex()}}) - require.ErrorIs(t, err, evm.NoContractExistsError{Address: addr}) + require.ErrorIs(t, err, read.NoContractExistsError{Address: addr}) + }) +} + +func RunContractReaderInLoopTests[T TestingT[T]](t T, it ChainComponentsInterfaceTester[T]) { + RunContractReaderInterfaceTests[T](t, it, false) + + t.Run("Filtering can be done on data words using value comparator", func(t T) { + it.Setup(t) + + ctx := tests.Context(t) + cr := it.GetContractReader(t) + require.NoError(t, cr.Bind(ctx, it.GetBindings(t))) + bindings := it.GetBindings(t) + boundContract := BindingsByName(bindings, AnyContractName)[0] + require.NoError(t, cr.Bind(ctx, bindings)) + + ts1 := CreateTestStruct[T](0, it) + _ = SubmitTransactionToCW(t, it, MethodTriggeringEvent, ts1, boundContract, types.Unconfirmed) + ts2 := CreateTestStruct[T](15, it) + _ = SubmitTransactionToCW(t, it, MethodTriggeringEvent, ts2, boundContract, types.Unconfirmed) + ts3 := CreateTestStruct[T](35, it) + _ = SubmitTransactionToCW(t, it, MethodTriggeringEvent, ts3, boundContract, types.Unconfirmed) + + ts := &TestStruct{} + assert.Eventually(t, func() bool { + sequences, err := cr.QueryKey(ctx, boundContract, query.KeyFilter{Key: EventName, Expressions: []query.Expression{ + query.Comparator("OracleID", + primitives.ValueComparator{ + Value: uint8(ts2.OracleID), + Operator: primitives.Eq, + }), + }, + }, query.LimitAndSort{}, ts) + return err == nil && len(sequences) == 1 && reflect.DeepEqual(&ts2, sequences[0].Data) + }, it.MaxWaitTimeForEvents(), time.Millisecond*10) }) } -func triggerFourTopics[T TestingT[T]](t T, it *EVMChainReaderInterfaceTester[T], i1, i2, i3 int32) { - tx, err := it.contractTesters[it.address].ChainReaderTesterTransactor.TriggerWithFourTopics(it.GetAuthWithGasSet(t), i1, i2, i3) - require.NoError(t, err) - require.NoError(t, err) - it.Helper.Commit() - it.IncNonce() - it.AwaitTx(t, tx) +func triggerFourTopics[T TestingT[T]](t T, it *EVMChainComponentsInterfaceTester[T], i1, i2, i3 int32) { + type DynamicEvent struct { + Field1 int32 + Field2 int32 + Field3 int32 + } + contracts := it.GetBindings(t) + SubmitTransactionToCW(t, it, "triggerWithFourTopics", DynamicEvent{Field1: i1, Field2: i2, Field3: i3}, contracts[0], types.Unconfirmed) +} + +func triggerFourTopicsWithHashed[T TestingT[T]](t T, it *EVMChainComponentsInterfaceTester[T], i1 string, i2 [32]uint8, i3 [32]byte) { + type DynamicEvent struct { + Field1 string + Field2 [32]uint8 + Field3 [32]byte + } + contracts := it.GetBindings(t) + SubmitTransactionToCW(t, it, "triggerWithFourTopicsWithHashed", DynamicEvent{Field1: i1, Field2: i2, Field3: i3}, contracts[0], types.Unconfirmed) } diff --git a/core/services/relay/evm/exec_provider.go b/core/services/relay/evm/exec_provider.go index e50ae41351..193f5e3b00 100644 --- a/core/services/relay/evm/exec_provider.go +++ b/core/services/relay/evm/exec_provider.go @@ -16,6 +16,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/types" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" @@ -155,7 +156,7 @@ func (s *SrcExecProvider) ContractTransmitter() ocrtypes.ContractTransmitter { return UnimplementedContractTransmitter{} } -func (s *SrcExecProvider) ChainReader() commontypes.ContractReader { +func (s *SrcExecProvider) ContractReader() commontypes.ContractReader { return nil } @@ -342,7 +343,7 @@ func (d *DstExecProvider) ContractTransmitter() ocrtypes.ContractTransmitter { return d.contractTransmitter } -func (d *DstExecProvider) ChainReader() commontypes.ContractReader { +func (d *DstExecProvider) ContractReader() commontypes.ContractReader { return nil } diff --git a/core/services/relay/evm/functions.go b/core/services/relay/evm/functions.go index a04a991e37..74a133edd1 100644 --- a/core/services/relay/evm/functions.go +++ b/core/services/relay/evm/functions.go @@ -8,10 +8,9 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" - "go.uber.org/multierr" - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + commonlogger "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" @@ -26,12 +25,29 @@ import ( ) type functionsProvider struct { - services.StateMachine + services.Service + eng *services.Engine + configWatcher *configWatcher contractTransmitter ContractTransmitter logPollerWrapper evmRelayTypes.LogPollerWrapper } +func newFunctionsProvider(lggr logger.Logger, cw *configWatcher, ct ContractTransmitter, lpw evmRelayTypes.LogPollerWrapper) *functionsProvider { + p := &functionsProvider{ + configWatcher: cw, + contractTransmitter: ct, + logPollerWrapper: lpw, + } + p.Service, p.eng = services.Config{ + Name: "FunctionsProvider", + NewSubServices: func(lggr commonlogger.Logger) []services.Service { + return []services.Service{p.configWatcher, p.logPollerWrapper} + }, + }.NewServiceEngine(lggr) + return p +} + var _ evmRelayTypes.FunctionsProvider = (*functionsProvider)(nil) func (p *functionsProvider) ContractTransmitter() ocrtypes.ContractTransmitter { @@ -47,23 +63,6 @@ func (p *functionsProvider) FunctionsEvents() commontypes.FunctionsEvents { return nil } -func (p *functionsProvider) Start(ctx context.Context) error { - return p.StartOnce("FunctionsProvider", func() error { - if err := p.configWatcher.Start(ctx); err != nil { - return err - } - return p.logPollerWrapper.Start(ctx) - }) -} - -func (p *functionsProvider) Close() error { - return p.StopOnce("FunctionsProvider", func() (err error) { - err = multierr.Combine(err, p.logPollerWrapper.Close()) - err = multierr.Combine(err, p.configWatcher.Close()) - return - }) -} - // Forward all calls to the underlying configWatcher func (p *functionsProvider) OffchainConfigDigester() ocrtypes.OffchainConfigDigester { return p.configWatcher.OffchainConfigDigester() @@ -73,15 +72,7 @@ func (p *functionsProvider) ContractConfigTracker() ocrtypes.ContractConfigTrack return p.configWatcher.ContractConfigTracker() } -func (p *functionsProvider) HealthReport() map[string]error { - return p.configWatcher.HealthReport() -} - -func (p *functionsProvider) Name() string { - return p.configWatcher.Name() -} - -func (p *functionsProvider) ChainReader() commontypes.ContractReader { +func (p *functionsProvider) ContractReader() commontypes.ContractReader { return nil } @@ -127,11 +118,7 @@ func NewFunctionsProvider(ctx context.Context, chain legacyevm.Chain, rargs comm } else { lggr.Warn("no sending keys configured for functions plugin, not starting contract transmitter") } - return &functionsProvider{ - configWatcher: configWatcher, - contractTransmitter: contractTransmitter, - logPollerWrapper: logPollerWrapper, - }, nil + return newFunctionsProvider(lggr, configWatcher, contractTransmitter, logPollerWrapper), nil } func newFunctionsConfigProvider(ctx context.Context, pluginType functionsRelay.FunctionsPluginType, chain legacyevm.Chain, args commontypes.RelayArgs, fromBlock uint64, logPollerWrapper evmRelayTypes.LogPollerWrapper, lggr logger.Logger) (*configWatcher, error) { diff --git a/core/services/relay/evm/functions/config_poller.go b/core/services/relay/evm/functions/config_poller.go index 71616f2e84..2cb21738b9 100644 --- a/core/services/relay/evm/functions/config_poller.go +++ b/core/services/relay/evm/functions/config_poller.go @@ -9,12 +9,13 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" - "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" + "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2/types" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" ) diff --git a/core/services/relay/evm/functions/contract_transmitter.go b/core/services/relay/evm/functions/contract_transmitter.go index 23143ed3ef..f588a02390 100644 --- a/core/services/relay/evm/functions/contract_transmitter.go +++ b/core/services/relay/evm/functions/contract_transmitter.go @@ -13,14 +13,16 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" + "github.com/smartcontractkit/libocr/offchainreporting2plus/chains/evmutil" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/functions/encoding" evmRelayTypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" @@ -105,7 +107,7 @@ func NewFunctionsContractTransmitter( transmittedEventSig: transmitted.ID, lp: lp, contractReader: caller, - lggr: lggr.Named("OCRFunctionsContractTransmitter"), + lggr: logger.Named(lggr, "OCRFunctionsContractTransmitter"), contractVersion: contractVersion, reportCodec: codec, txm: txm, diff --git a/core/services/relay/evm/functions/logpoller_wrapper.go b/core/services/relay/evm/functions/logpoller_wrapper.go index 559b1ec33f..260c366eb8 100644 --- a/core/services/relay/evm/functions/logpoller_wrapper.go +++ b/core/services/relay/evm/functions/logpoller_wrapper.go @@ -10,19 +10,20 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/functions/generated/functions_coordinator" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/functions/generated/functions_router" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/functions/config" evmRelayTypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" ) type logPollerWrapper struct { - services.StateMachine + services.Service + eng *services.Engine routerContract *functions_router.FunctionsRouter pluginConfig config.PluginConfig @@ -38,9 +39,6 @@ type logPollerWrapper struct { detectedRequests detectedEvents detectedResponses detectedEvents mu sync.Mutex - closeWait sync.WaitGroup - stopCh services.StopChan - lggr logger.Logger } type detectedEvent struct { @@ -94,7 +92,7 @@ func NewLogPollerWrapper(routerContractAddress common.Address, pluginConfig conf return nil, errors.Errorf("invalid config: number of required confirmation blocks >= pastBlocksToPoll") } - return &logPollerWrapper{ + w := &logPollerWrapper{ routerContract: routerContract, pluginConfig: pluginConfig, requestBlockOffset: requestBlockOffset, @@ -106,40 +104,25 @@ func NewLogPollerWrapper(routerContractAddress common.Address, pluginConfig conf logPoller: logPoller, client: client, subscribers: make(map[string]evmRelayTypes.RouteUpdateSubscriber), - stopCh: make(services.StopChan), - lggr: lggr.Named("LogPollerWrapper"), - }, nil -} - -func (l *logPollerWrapper) Start(context.Context) error { - return l.StartOnce("LogPollerWrapper", func() error { - l.lggr.Infow("starting LogPollerWrapper", "routerContract", l.routerContract.Address().Hex(), "contractVersion", l.pluginConfig.ContractVersion) - l.mu.Lock() - defer l.mu.Unlock() - if l.pluginConfig.ContractVersion != 1 { - return errors.New("only contract version 1 is supported") - } - l.closeWait.Add(1) - go l.checkForRouteUpdates() - return nil - }) -} - -func (l *logPollerWrapper) Close() error { - return l.StopOnce("LogPollerWrapper", func() (err error) { - l.lggr.Info("closing LogPollerWrapper") - close(l.stopCh) - l.closeWait.Wait() - return nil - }) + } + w.Service, w.eng = services.Config{ + Name: "LoggPollerWrapper", + Start: w.start, + }.NewServiceEngine(lggr) + return w, nil } -func (l *logPollerWrapper) HealthReport() map[string]error { - return map[string]error{l.Name(): l.Ready()} +func (l *logPollerWrapper) start(context.Context) error { + l.eng.Infow("starting LogPollerWrapper", "routerContract", l.routerContract.Address().Hex(), "contractVersion", l.pluginConfig.ContractVersion) + l.mu.Lock() + defer l.mu.Unlock() + if l.pluginConfig.ContractVersion != 1 { + return errors.New("only contract version 1 is supported") + } + l.eng.Go(l.checkForRouteUpdates) + return nil } -func (l *logPollerWrapper) Name() string { return l.lggr.Name() } - // methods of LogPollerWrapper func (l *logPollerWrapper) LatestEvents(ctx context.Context) ([]evmRelayTypes.OracleRequest, []evmRelayTypes.OracleResponse, error) { l.mu.Lock() @@ -166,7 +149,7 @@ func (l *logPollerWrapper) LatestEvents(ctx context.Context) ([]evmRelayTypes.Or resultsReq := []evmRelayTypes.OracleRequest{} resultsResp := []evmRelayTypes.OracleResponse{} if len(coordinators) == 0 { - l.lggr.Debug("LatestEvents: no non-zero coordinators to check") + l.eng.Debug("LatestEvents: no non-zero coordinators to check") return resultsReq, resultsResp, errors.New("no non-zero coordinators to check") } @@ -174,32 +157,32 @@ func (l *logPollerWrapper) LatestEvents(ctx context.Context) ([]evmRelayTypes.Or requestEndBlock := latestBlockNum - l.requestBlockOffset requestLogs, err := l.logPoller.Logs(ctx, startBlockNum, requestEndBlock, functions_coordinator.FunctionsCoordinatorOracleRequest{}.Topic(), coordinator) if err != nil { - l.lggr.Errorw("LatestEvents: fetching request logs from LogPoller failed", "startBlock", startBlockNum, "endBlock", requestEndBlock) + l.eng.Errorw("LatestEvents: fetching request logs from LogPoller failed", "startBlock", startBlockNum, "endBlock", requestEndBlock) return nil, nil, err } - l.lggr.Debugw("LatestEvents: fetched request logs", "nRequestLogs", len(requestLogs), "latestBlock", latest, "startBlock", startBlockNum, "endBlock", requestEndBlock) + l.eng.Debugw("LatestEvents: fetched request logs", "nRequestLogs", len(requestLogs), "latestBlock", latest, "startBlock", startBlockNum, "endBlock", requestEndBlock) requestLogs = l.filterPreviouslyDetectedEvents(requestLogs, &l.detectedRequests, "requests") responseEndBlock := latestBlockNum - l.responseBlockOffset responseLogs, err := l.logPoller.Logs(ctx, startBlockNum, responseEndBlock, functions_coordinator.FunctionsCoordinatorOracleResponse{}.Topic(), coordinator) if err != nil { - l.lggr.Errorw("LatestEvents: fetching response logs from LogPoller failed", "startBlock", startBlockNum, "endBlock", responseEndBlock) + l.eng.Errorw("LatestEvents: fetching response logs from LogPoller failed", "startBlock", startBlockNum, "endBlock", responseEndBlock) return nil, nil, err } - l.lggr.Debugw("LatestEvents: fetched request logs", "nResponseLogs", len(responseLogs), "latestBlock", latest, "startBlock", startBlockNum, "endBlock", responseEndBlock) + l.eng.Debugw("LatestEvents: fetched request logs", "nResponseLogs", len(responseLogs), "latestBlock", latest, "startBlock", startBlockNum, "endBlock", responseEndBlock) responseLogs = l.filterPreviouslyDetectedEvents(responseLogs, &l.detectedResponses, "responses") parsingContract, err := functions_coordinator.NewFunctionsCoordinator(coordinator, l.client) if err != nil { - l.lggr.Error("LatestEvents: creating a contract instance for parsing failed") + l.eng.Error("LatestEvents: creating a contract instance for parsing failed") return nil, nil, err } - l.lggr.Debugw("LatestEvents: parsing logs", "nRequestLogs", len(requestLogs), "nResponseLogs", len(responseLogs), "coordinatorAddress", coordinator.Hex()) + l.eng.Debugw("LatestEvents: parsing logs", "nRequestLogs", len(requestLogs), "nResponseLogs", len(responseLogs), "coordinatorAddress", coordinator.Hex()) for _, log := range requestLogs { gethLog := log.ToGethLog() oracleRequest, err := parsingContract.ParseOracleRequest(gethLog) if err != nil { - l.lggr.Errorw("LatestEvents: failed to parse a request log, skipping", "err", err) + l.eng.Errorw("LatestEvents: failed to parse a request log, skipping", "err", err) continue } @@ -212,7 +195,7 @@ func (l *logPollerWrapper) LatestEvents(ctx context.Context) ([]evmRelayTypes.Or bytes32Type, errType7 := abi.NewType("bytes32", "bytes32", nil) if errType1 != nil || errType2 != nil || errType3 != nil || errType4 != nil || errType5 != nil || errType6 != nil || errType7 != nil { - l.lggr.Errorw("LatestEvents: failed to initialize types", "errType1", errType1, + l.eng.Errorw("LatestEvents: failed to initialize types", "errType1", errType1, "errType2", errType2, "errType3", errType3, "errType4", errType4, "errType5", errType5, "errType6", errType6, "errType7", errType7, ) continue @@ -244,7 +227,7 @@ func (l *logPollerWrapper) LatestEvents(ctx context.Context) ([]evmRelayTypes.Or oracleRequest.Commitment.TimeoutTimestamp, ) if err != nil { - l.lggr.Errorw("LatestEvents: failed to pack commitment bytes, skipping", "err", err) + l.eng.Errorw("LatestEvents: failed to pack commitment bytes, skipping", "err", err) } resultsReq = append(resultsReq, evmRelayTypes.OracleRequest{ @@ -266,7 +249,7 @@ func (l *logPollerWrapper) LatestEvents(ctx context.Context) ([]evmRelayTypes.Or gethLog := log.ToGethLog() oracleResponse, err := parsingContract.ParseOracleResponse(gethLog) if err != nil { - l.lggr.Errorw("LatestEvents: failed to parse a response log, skipping") + l.eng.Errorw("LatestEvents: failed to parse a response log, skipping") continue } resultsResp = append(resultsResp, evmRelayTypes.OracleResponse{ @@ -275,13 +258,13 @@ func (l *logPollerWrapper) LatestEvents(ctx context.Context) ([]evmRelayTypes.Or } } - l.lggr.Debugw("LatestEvents: done", "nRequestLogs", len(resultsReq), "nResponseLogs", len(resultsResp), "startBlock", startBlockNum, "endBlock", latestBlockNum) + l.eng.Debugw("LatestEvents: done", "nRequestLogs", len(resultsReq), "nResponseLogs", len(resultsResp), "startBlock", startBlockNum, "endBlock", latestBlockNum) return resultsReq, resultsResp, nil } func (l *logPollerWrapper) filterPreviouslyDetectedEvents(logs []logpoller.Log, detectedEvents *detectedEvents, filterType string) []logpoller.Log { if len(logs) > maxLogsToProcess { - l.lggr.Errorw("filterPreviouslyDetectedEvents: too many logs to process, only processing latest maxLogsToProcess logs", "filterType", filterType, "nLogs", len(logs), "maxLogsToProcess", maxLogsToProcess) + l.eng.Errorw("filterPreviouslyDetectedEvents: too many logs to process, only processing latest maxLogsToProcess logs", "filterType", filterType, "nLogs", len(logs), "maxLogsToProcess", maxLogsToProcess) logs = logs[len(logs)-maxLogsToProcess:] } l.mu.Lock() @@ -290,7 +273,7 @@ func (l *logPollerWrapper) filterPreviouslyDetectedEvents(logs []logpoller.Log, for _, log := range logs { var requestId [32]byte if len(log.Topics) < 2 || len(log.Topics[1]) != 32 { - l.lggr.Errorw("filterPreviouslyDetectedEvents: invalid log, skipping", "filterType", filterType, "log", log) + l.eng.Errorw("filterPreviouslyDetectedEvents: invalid log, skipping", "filterType", filterType, "log", log) continue } copy(requestId[:], log.Topics[1]) // requestId is the second topic (1st topic is the event signature) @@ -310,7 +293,7 @@ func (l *logPollerWrapper) filterPreviouslyDetectedEvents(logs []logpoller.Log, expiredRequests++ } detectedEvents.detectedEventsOrdered = detectedEvents.detectedEventsOrdered[expiredRequests:] - l.lggr.Debugw("filterPreviouslyDetectedEvents: done", "filterType", filterType, "nLogs", len(logs), "nFilteredLogs", len(filteredLogs), "nExpiredRequests", expiredRequests, "previouslyDetectedCacheSize", len(detectedEvents.detectedEventsOrdered)) + l.eng.Debugw("filterPreviouslyDetectedEvents: done", "filterType", filterType, "nLogs", len(logs), "nFilteredLogs", len(filteredLogs), "nExpiredRequests", expiredRequests, "previouslyDetectedCacheSize", len(detectedEvents.detectedEventsOrdered)) return filteredLogs } @@ -319,7 +302,7 @@ func (l *logPollerWrapper) SubscribeToUpdates(ctx context.Context, subscriberNam if l.pluginConfig.ContractVersion == 0 { // in V0, immediately set contract address to Oracle contract and never update again if err := subscriber.UpdateRoutes(ctx, l.routerContract.Address(), l.routerContract.Address()); err != nil { - l.lggr.Errorw("LogPollerWrapper: Failed to update routes", "subscriberName", subscriberName, "err", err) + l.eng.Errorw("LogPollerWrapper: Failed to update routes", "subscriberName", subscriberName, "err", err) } } else if l.pluginConfig.ContractVersion == 1 { l.mu.Lock() @@ -328,37 +311,36 @@ func (l *logPollerWrapper) SubscribeToUpdates(ctx context.Context, subscriberNam } } -func (l *logPollerWrapper) checkForRouteUpdates() { - defer l.closeWait.Done() +func (l *logPollerWrapper) checkForRouteUpdates(ctx context.Context) { freqSec := l.pluginConfig.ContractUpdateCheckFrequencySec if freqSec == 0 { - l.lggr.Errorw("LogPollerWrapper: ContractUpdateCheckFrequencySec is zero - route update checks disabled") + l.eng.Errorw("LogPollerWrapper: ContractUpdateCheckFrequencySec is zero - route update checks disabled") return } - updateOnce := func() { + updateOnce := func(ctx context.Context) { // NOTE: timeout == frequency here, could be changed to a separate config value timeout := time.Duration(l.pluginConfig.ContractUpdateCheckFrequencySec) * time.Second - ctx, cancel := l.stopCh.CtxCancel(context.WithTimeout(context.Background(), timeout)) + ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() active, proposed, err := l.getCurrentCoordinators(ctx) if err != nil { - l.lggr.Errorw("LogPollerWrapper: error calling getCurrentCoordinators", "err", err) + l.eng.Errorw("LogPollerWrapper: error calling getCurrentCoordinators", "err", err) return } l.handleRouteUpdate(ctx, active, proposed) } - updateOnce() // update once right away + updateOnce(ctx) // update once right away ticker := time.NewTicker(time.Duration(freqSec) * time.Second) defer ticker.Stop() for { select { - case <-l.stopCh: + case <-ctx.Done(): return case <-ticker.C: - updateOnce() + updateOnce(ctx) } } } @@ -394,22 +376,22 @@ func (l *logPollerWrapper) handleRouteUpdate(ctx context.Context, activeCoordina defer l.mu.Unlock() if activeCoordinator == (common.Address{}) { - l.lggr.Error("LogPollerWrapper: cannot update activeCoordinator to zero address") + l.eng.Error("LogPollerWrapper: cannot update activeCoordinator to zero address") return } if activeCoordinator == l.activeCoordinator && proposedCoordinator == l.proposedCoordinator { - l.lggr.Debug("LogPollerWrapper: no changes to routes") + l.eng.Debug("LogPollerWrapper: no changes to routes") return } errActive := l.registerFilters(ctx, activeCoordinator) errProposed := l.registerFilters(ctx, proposedCoordinator) if errActive != nil || errProposed != nil { - l.lggr.Errorw("LogPollerWrapper: Failed to register filters", "errorActive", errActive, "errorProposed", errProposed) + l.eng.Errorw("LogPollerWrapper: Failed to register filters", "errorActive", errActive, "errorProposed", errProposed) return } - l.lggr.Debugw("LogPollerWrapper: new routes", "activeCoordinator", activeCoordinator.Hex(), "proposedCoordinator", proposedCoordinator.Hex()) + l.eng.Debugw("LogPollerWrapper: new routes", "activeCoordinator", activeCoordinator.Hex(), "proposedCoordinator", proposedCoordinator.Hex()) l.activeCoordinator = activeCoordinator l.proposedCoordinator = proposedCoordinator @@ -417,7 +399,7 @@ func (l *logPollerWrapper) handleRouteUpdate(ctx context.Context, activeCoordina for _, subscriber := range l.subscribers { err := subscriber.UpdateRoutes(ctx, activeCoordinator, proposedCoordinator) if err != nil { - l.lggr.Errorw("LogPollerWrapper: Failed to update routes", "err", err) + l.eng.Errorw("LogPollerWrapper: Failed to update routes", "err", err) } } @@ -430,9 +412,9 @@ func (l *logPollerWrapper) handleRouteUpdate(ctx context.Context, activeCoordina continue } if err := l.logPoller.UnregisterFilter(ctx, filter.Name); err != nil { - l.lggr.Errorw("LogPollerWrapper: Failed to unregister filter", "filterName", filter.Name, "err", err) + l.eng.Errorw("LogPollerWrapper: Failed to unregister filter", "filterName", filter.Name, "err", err) } - l.lggr.Debugw("LogPollerWrapper: Successfully unregistered filter", "filterName", filter.Name) + l.eng.Debugw("LogPollerWrapper: Successfully unregistered filter", "filterName", filter.Name) } } diff --git a/core/services/relay/evm/liquidity_manager.go b/core/services/relay/evm/liquidity_manager.go index 3895961a30..7ae7d19977 100644 --- a/core/services/relay/evm/liquidity_manager.go +++ b/core/services/relay/evm/liquidity_manager.go @@ -13,6 +13,7 @@ import ( "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink-common/pkg/types" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink/v2/common/txmgr" @@ -142,6 +143,10 @@ type rebalancerProvider struct { bridgeFactory bridge.Factory } +func (r *rebalancerProvider) ContractReader() types.ContractReader { + return nil +} + func (r *rebalancerProvider) Codec() commontypes.Codec { return nil } diff --git a/core/services/relay/evm/llo_config_provider.go b/core/services/relay/evm/llo_config_provider.go index 6efd0ccada..aa4819e711 100644 --- a/core/services/relay/evm/llo_config_provider.go +++ b/core/services/relay/evm/llo_config_provider.go @@ -2,22 +2,53 @@ package evm import ( "context" + "fmt" + "math/big" "github.com/ethereum/go-ethereum/common" - pkgerrors "github.com/pkg/errors" + "github.com/pkg/errors" + + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/llo" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" ) +func DonIDToBytes32(donID uint32) [32]byte { + var b [32]byte + copy(b[:], common.LeftPadBytes(big.NewInt(int64(donID)).Bytes(), 32)) + return b +} + func newLLOConfigProvider(ctx context.Context, lggr logger.Logger, chain legacyevm.Chain, opts *types.RelayOpts) (*configWatcher, error) { if !common.IsHexAddress(opts.ContractID) { - return nil, pkgerrors.Errorf("invalid contractID, expected hex address") + return nil, errors.New("invalid contractID, expected hex address") + } + + configuratorAddress := common.HexToAddress(opts.ContractID) + + relayConfig, err := opts.RelayConfig() + if err != nil { + return nil, fmt.Errorf("failed to get relay config: %w", err) + } + if relayConfig.LLODONID == 0 { + return nil, errors.New("donID must be specified in relayConfig for LLO jobs") + } + donIDPadded := DonIDToBytes32(relayConfig.LLODONID) + cp, err := mercury.NewConfigPoller( + ctx, + lggr.Named(fmt.Sprintf("LLO-%d", relayConfig.LLODONID)), + chain.LogPoller(), + configuratorAddress, + donIDPadded, + // TODO: Does LLO need to support config contract? MERC-1827 + ) + if err != nil { + return nil, err } - aggregatorAddress := common.HexToAddress(opts.ContractID) - configDigester := llo.NewOffchainConfigDigester(chain.Config().EVM().ChainID(), aggregatorAddress) - return newContractConfigProvider(ctx, lggr, chain, opts, aggregatorAddress, ChannelVerifierLogDecoder, configDigester) + configDigester := mercury.NewOffchainConfigDigester(donIDPadded, chain.Config().EVM().ChainID(), configuratorAddress, ocrtypes.ConfigDigestPrefixLLO) + return newConfigWatcher(lggr, configuratorAddress, configDigester, cp, chain, relayConfig.FromBlock, opts.New), nil } diff --git a/core/services/relay/evm/llo_provider.go b/core/services/relay/evm/llo_provider.go index b685565e6e..2cf6163047 100644 --- a/core/services/relay/evm/llo_provider.go +++ b/core/services/relay/evm/llo_provider.go @@ -6,20 +6,23 @@ import ( ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" relaytypes "github.com/smartcontractkit/chainlink-common/pkg/types" llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" - - "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/llo" ) var _ commontypes.LLOProvider = (*lloProvider)(nil) +type LLOTransmitter interface { + services.Service + llotypes.Transmitter +} + type lloProvider struct { cp commontypes.ConfigProvider - transmitter llo.Transmitter + transmitter LLOTransmitter logger logger.Logger channelDefinitionCache llotypes.ChannelDefinitionCache @@ -28,14 +31,14 @@ type lloProvider struct { func NewLLOProvider( cp commontypes.ConfigProvider, - transmitter llo.Transmitter, + transmitter LLOTransmitter, lggr logger.Logger, channelDefinitionCache llotypes.ChannelDefinitionCache, ) relaytypes.LLOProvider { return &lloProvider{ cp, transmitter, - lggr.Named("LLOProvider"), + logger.Named(lggr, "LLOProvider"), channelDefinitionCache, services.MultiStart{}, } @@ -74,10 +77,6 @@ func (p *lloProvider) OffchainConfigDigester() ocrtypes.OffchainConfigDigester { return p.cp.OffchainConfigDigester() } -func (p *lloProvider) OnchainConfigCodec() llo.OnchainConfigCodec { - return &llo.JSONOnchainConfigCodec{} -} - func (p *lloProvider) ContractTransmitter() llotypes.Transmitter { return p.transmitter } diff --git a/core/services/relay/evm/llo_verifier_decoder.go b/core/services/relay/evm/llo_verifier_decoder.go deleted file mode 100644 index 922b83bec0..0000000000 --- a/core/services/relay/evm/llo_verifier_decoder.go +++ /dev/null @@ -1,67 +0,0 @@ -package evm - -import ( - "fmt" - - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common" - "github.com/pkg/errors" - - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/channel_verifier" -) - -var _ LogDecoder = &channelVerifierLogDecoder{} - -type channelVerifierLogDecoder struct { - eventName string - eventSig common.Hash - abi *abi.ABI -} - -func newChannelVerifierLogDecoder() (*channelVerifierLogDecoder, error) { - const eventName = "ConfigSet" - abi, err := channel_verifier.ChannelVerifierMetaData.GetAbi() - if err != nil { - return nil, err - } - return &channelVerifierLogDecoder{ - eventName: eventName, - eventSig: abi.Events[eventName].ID, - abi: abi, - }, nil -} - -func (d *channelVerifierLogDecoder) Decode(rawLog []byte) (ocrtypes.ContractConfig, error) { - unpacked := new(channel_verifier.ChannelVerifierConfigSet) - err := d.abi.UnpackIntoInterface(unpacked, d.eventName, rawLog) - if err != nil { - return ocrtypes.ContractConfig{}, errors.Wrap(err, "failed to unpack log data") - } - - var transmitAccounts []ocrtypes.Account - for _, addr := range unpacked.OffchainTransmitters { - transmitAccounts = append(transmitAccounts, ocrtypes.Account(fmt.Sprintf("%x", addr))) - } - var signers []ocrtypes.OnchainPublicKey - for _, addr := range unpacked.Signers { - addr := addr - signers = append(signers, addr[:]) - } - - return ocrtypes.ContractConfig{ - ConfigDigest: unpacked.ConfigDigest, - ConfigCount: unpacked.ConfigCount, - Signers: signers, - Transmitters: transmitAccounts, - F: unpacked.F, - OnchainConfig: unpacked.OnchainConfig, - OffchainConfigVersion: unpacked.OffchainConfigVersion, - OffchainConfig: unpacked.OffchainConfig, - }, nil -} - -func (d *channelVerifierLogDecoder) EventSig() common.Hash { - return d.eventSig -} diff --git a/core/services/relay/evm/median.go b/core/services/relay/evm/median.go index 2407cff714..60a63994bd 100644 --- a/core/services/relay/evm/median.go +++ b/core/services/relay/evm/median.go @@ -8,16 +8,17 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" + "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" "github.com/smartcontractkit/libocr/offchainreporting2/reportingplugin/median" "github.com/smartcontractkit/libocr/offchainreporting2plus/types" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" offchain_aggregator_wrapper "github.com/smartcontractkit/chainlink/v2/core/internal/gethwrappers2/generated/offchainaggregator" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) var _ median.MedianContract = &medianContract{} @@ -31,7 +32,7 @@ type medianContract struct { } func newMedianContract(configTracker types.ContractConfigTracker, contractAddress common.Address, chain legacyevm.Chain, specID int32, ds sqlutil.DataSource, lggr logger.Logger) (*medianContract, error) { - lggr = lggr.Named("MedianContract") + lggr = logger.Named(lggr, "MedianContract") contract, err := offchain_aggregator_wrapper.NewOffchainAggregator(contractAddress, chain.Client()) if err != nil { return nil, errors.Wrap(err, "could not instantiate NewOffchainAggregator") diff --git a/core/services/relay/evm/median_test.go b/core/services/relay/evm/median_test.go index 9c474006aa..65e5525596 100644 --- a/core/services/relay/evm/median_test.go +++ b/core/services/relay/evm/median_test.go @@ -8,11 +8,11 @@ import ( "github.com/stretchr/testify/require" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm/mocks" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/logger" evmtypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" ) @@ -23,7 +23,7 @@ func TestNewMedianProvider(t *testing.T) { chainID := testutils.NewRandomEVMChainID() chain.On("ID").Return(chainID) contractID := testutils.NewAddress() - relayer := Relayer{lggr: lggr, chain: chain} + relayer := Relayer{lggr: logger.Sugared(lggr), chain: chain} pargs := commontypes.PluginArgs{} diff --git a/core/services/relay/evm/mercury/config_digest.go b/core/services/relay/evm/mercury/config_digest.go index 291a723ee3..c7a6076c88 100644 --- a/core/services/relay/evm/mercury/config_digest.go +++ b/core/services/relay/evm/mercury/config_digest.go @@ -37,6 +37,7 @@ func configDigest( onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte, + prefix types.ConfigDigestPrefix, ) types.ConfigDigest { msg, err := configDigestArgs.Pack( feedID, @@ -60,10 +61,6 @@ func configDigest( // assertion panic("copy too little data") } - binary.BigEndian.PutUint16(configDigest[:2], uint16(types.ConfigDigestPrefixMercuryV02)) - if !(configDigest[0] == 0 && configDigest[1] == 6) { - // assertion - panic("unexpected mismatch") - } + binary.BigEndian.PutUint16(configDigest[:2], uint16(prefix)) return configDigest } diff --git a/core/services/relay/evm/mercury/config_digest_test.go b/core/services/relay/evm/mercury/config_digest_test.go index fe718e92fe..680513688a 100644 --- a/core/services/relay/evm/mercury/config_digest_test.go +++ b/core/services/relay/evm/mercury/config_digest_test.go @@ -15,6 +15,7 @@ import ( "github.com/leanovate/gopter" "github.com/leanovate/gopter/gen" "github.com/leanovate/gopter/prop" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "github.com/smartcontractkit/wsrpc/credentials" "github.com/stretchr/testify/require" @@ -63,6 +64,7 @@ func TestConfigCalculationMatches(t *testing.T) { onchainConfig, offchainConfigVersion, offchainConfig, + ocrtypes.ConfigDigestPrefixMercuryV02, ) bigChainID := new(big.Int) diff --git a/core/services/relay/evm/mercury/config_poller.go b/core/services/relay/evm/mercury/config_poller.go index 2da541a8e4..7bb5d2af7b 100644 --- a/core/services/relay/evm/mercury/config_poller.go +++ b/core/services/relay/evm/mercury/config_poller.go @@ -11,9 +11,10 @@ import ( "github.com/pkg/errors" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/llo-feeds/generated/verifier" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/utils" ) @@ -53,7 +54,7 @@ func unpackLogData(d []byte) (*verifier.VerifierConfigSet, error) { return unpacked, nil } -func configFromLog(logData []byte) (FullConfigFromLog, error) { +func ConfigFromLog(logData []byte) (FullConfigFromLog, error) { unpacked, err := unpackLogData(logData) if err != nil { return FullConfigFromLog{}, err @@ -139,7 +140,7 @@ func (cp *ConfigPoller) LatestConfigDetails(ctx context.Context) (changedInBlock return 0, ocrtypes.ConfigDigest{}, nil } latest := logs[len(logs)-1] - latestConfigSet, err := configFromLog(latest.Data) + latestConfigSet, err := ConfigFromLog(latest.Data) if err != nil { return 0, ocrtypes.ConfigDigest{}, err } @@ -155,7 +156,7 @@ func (cp *ConfigPoller) LatestConfig(ctx context.Context, changedInBlock uint64) if len(lgs) == 0 { return ocrtypes.ContractConfig{}, nil } - latestConfigSet, err := configFromLog(lgs[len(lgs)-1].Data) + latestConfigSet, err := ConfigFromLog(lgs[len(lgs)-1].Data) if err != nil { return ocrtypes.ContractConfig{}, err } diff --git a/core/services/relay/evm/mercury/mocks/pipeline.go b/core/services/relay/evm/mercury/mocks/pipeline.go index 44be1377ae..a7183c9a03 100644 --- a/core/services/relay/evm/mercury/mocks/pipeline.go +++ b/core/services/relay/evm/mercury/mocks/pipeline.go @@ -23,6 +23,10 @@ type MockTask struct { result pipeline.Result } +func (m *MockTask) GetDescendantTasks() []pipeline.Task { return nil } + +func (m *MockTask) TaskTags() string { return "{\"anything\": \"here\"}" } + func (m *MockTask) Type() pipeline.TaskType { return "MockTask" } func (m *MockTask) ID() int { return 0 } func (m *MockTask) DotID() string { return "" } diff --git a/core/services/relay/evm/mercury/offchain_config_digester.go b/core/services/relay/evm/mercury/offchain_config_digester.go index f9ba0b2309..80b3005455 100644 --- a/core/services/relay/evm/mercury/offchain_config_digester.go +++ b/core/services/relay/evm/mercury/offchain_config_digester.go @@ -18,14 +18,15 @@ import ( var _ ocrtypes.OffchainConfigDigester = OffchainConfigDigester{} -func NewOffchainConfigDigester(feedID [32]byte, chainID *big.Int, contractAddress common.Address) OffchainConfigDigester { - return OffchainConfigDigester{feedID, chainID, contractAddress} +func NewOffchainConfigDigester(feedID [32]byte, chainID *big.Int, contractAddress common.Address, prefix ocrtypes.ConfigDigestPrefix) OffchainConfigDigester { + return OffchainConfigDigester{feedID, chainID, contractAddress, prefix} } type OffchainConfigDigester struct { FeedID utils.FeedID ChainID *big.Int ContractAddress common.Address + Prefix ocrtypes.ConfigDigestPrefix } func (d OffchainConfigDigester) ConfigDigest(cc ocrtypes.ContractConfig) (ocrtypes.ConfigDigest, error) { @@ -63,9 +64,10 @@ func (d OffchainConfigDigester) ConfigDigest(cc ocrtypes.ContractConfig) (ocrtyp cc.OnchainConfig, cc.OffchainConfigVersion, cc.OffchainConfig, + d.Prefix, ), nil } func (d OffchainConfigDigester) ConfigDigestPrefix() (ocrtypes.ConfigDigestPrefix, error) { - return ocrtypes.ConfigDigestPrefixMercuryV02, nil + return d.Prefix, nil } diff --git a/core/services/relay/evm/mercury/persistence_manager.go b/core/services/relay/evm/mercury/persistence_manager.go index d7f3d8eaa0..dfe75e7c3c 100644 --- a/core/services/relay/evm/mercury/persistence_manager.go +++ b/core/services/relay/evm/mercury/persistence_manager.go @@ -7,9 +7,10 @@ import ( ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" - "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc/pb" ) @@ -39,7 +40,7 @@ type PersistenceManager struct { func NewPersistenceManager(lggr logger.Logger, serverURL string, orm ORM, jobID int32, maxTransmitQueueSize int, flushDeletesFrequency, pruneFrequency time.Duration) *PersistenceManager { return &PersistenceManager{ - lggr: lggr.Named("MercuryPersistenceManager").With("serverURL", serverURL), + lggr: logger.Sugared(lggr).Named("MercuryPersistenceManager").With("serverURL", serverURL), orm: orm, serverURL: serverURL, stopCh: make(services.StopChan), diff --git a/core/services/relay/evm/mercury/queue.go b/core/services/relay/evm/mercury/queue.go index 8b39be72a6..a450d21af6 100644 --- a/core/services/relay/evm/mercury/queue.go +++ b/core/services/relay/evm/mercury/queue.go @@ -13,9 +13,9 @@ import ( ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc/pb" ) @@ -42,7 +42,7 @@ type transmitQueue struct { services.StateMachine cond sync.Cond - lggr logger.Logger + lggr logger.SugaredLogger asyncDeleter asyncDeleter mu *sync.RWMutex @@ -76,7 +76,7 @@ func NewTransmitQueue(lggr logger.Logger, serverURL, feedID string, maxlen int, return &transmitQueue{ services.StateMachine{}, sync.Cond{L: mu}, - lggr.Named("TransmitQueue"), + logger.Sugared(lggr).Named("TransmitQueue"), asyncDeleter, mu, nil, // pq needs to be initialized by calling tq.Init before use diff --git a/core/services/relay/evm/mercury/transmitter.go b/core/services/relay/evm/mercury/transmitter.go index f1434bf20f..b914e67b45 100644 --- a/core/services/relay/evm/mercury/transmitter.go +++ b/core/services/relay/evm/mercury/transmitter.go @@ -27,10 +27,10 @@ import ( capStreams "github.com/smartcontractkit/chainlink-common/pkg/capabilities/datastreams" "github.com/smartcontractkit/chainlink-common/pkg/capabilities/triggers" commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/types/mercury" - "github.com/smartcontractkit/chainlink/v2/core/logger" mercuryutils "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/utils" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc/pb" @@ -110,7 +110,7 @@ type TransmitterConfig interface { type mercuryTransmitter struct { services.StateMachine - lggr logger.Logger + lggr logger.SugaredLogger cfg TransmitterConfig orm ORM @@ -147,7 +147,7 @@ func getPayloadTypes() abi.Arguments { } type server struct { - lggr logger.Logger + lggr logger.SugaredLogger transmitTimeout time.Duration @@ -285,7 +285,7 @@ func (s *server) runQueueLoop(stopCh services.StopChan, wg *sync.WaitGroup, feed func newServer(lggr logger.Logger, cfg TransmitterConfig, client wsrpc.Client, pm *PersistenceManager, serverURL, feedIDHex string) *server { return &server{ - lggr, + logger.Sugared(lggr), cfg.TransmitTimeout().Duration(), client, pm, @@ -302,16 +302,17 @@ func newServer(lggr logger.Logger, cfg TransmitterConfig, client wsrpc.Client, p } func NewTransmitter(lggr logger.Logger, cfg TransmitterConfig, clients map[string]wsrpc.Client, fromAccount ed25519.PublicKey, jobID int32, feedID [32]byte, orm ORM, codec TransmitterReportDecoder, triggerCapability *triggers.MercuryTriggerService) *mercuryTransmitter { + sugared := logger.Sugared(lggr) feedIDHex := fmt.Sprintf("0x%x", feedID[:]) servers := make(map[string]*server, len(clients)) for serverURL, client := range clients { - cLggr := lggr.Named(serverURL).With("serverURL", serverURL) + cLggr := sugared.Named(serverURL).With("serverURL", serverURL) pm := NewPersistenceManager(cLggr, serverURL, orm, jobID, int(cfg.TransmitQueueMaxSize()), flushDeletesFrequency, pruneFrequency) servers[serverURL] = newServer(cLggr, cfg, client, pm, serverURL, feedIDHex) } return &mercuryTransmitter{ services.StateMachine{}, - lggr.Named("MercuryTransmitter").With("feedID", feedIDHex), + sugared.Named("MercuryTransmitter").With("feedID", feedIDHex), cfg, orm, servers, diff --git a/core/services/relay/evm/mercury/utils/feeds.go b/core/services/relay/evm/mercury/utils/feeds.go index 6f8978bbf0..eb4f685022 100644 --- a/core/services/relay/evm/mercury/utils/feeds.go +++ b/core/services/relay/evm/mercury/utils/feeds.go @@ -83,6 +83,7 @@ const ( REPORT_V1 REPORT_V2 REPORT_V3 + REPORT_V4 _ ) @@ -103,10 +104,14 @@ func (f *FeedID) UnmarshalText(input []byte) error { func (f FeedID) Version() FeedVersion { if _, exists := legacyV1FeedIDM[f]; exists { return REPORT_V1 + } else if f[0] == 0x01 { // Keystone Feed IDs + return FeedVersion(binary.BigEndian.Uint16(f[5:7])) } + return FeedVersion(binary.BigEndian.Uint16(f[:2])) } func (f FeedID) IsV1() bool { return f.Version() == REPORT_V1 } func (f FeedID) IsV2() bool { return f.Version() == REPORT_V2 } func (f FeedID) IsV3() bool { return f.Version() == REPORT_V3 } +func (f FeedID) IsV4() bool { return f.Version() == REPORT_V4 } diff --git a/core/services/relay/evm/mercury/utils/feeds_test.go b/core/services/relay/evm/mercury/utils/feeds_test.go index 37b9b47de7..d6db7a4a8c 100644 --- a/core/services/relay/evm/mercury/utils/feeds_test.go +++ b/core/services/relay/evm/mercury/utils/feeds_test.go @@ -7,27 +7,48 @@ import ( ) var ( - v1FeedId = (FeedID)([32]uint8{00, 01, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58, 163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114}) - v2FeedId = (FeedID)([32]uint8{00, 02, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58, 163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114}) - v3FeedId = (FeedID)([32]uint8{00, 03, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58, 163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114}) + v1FeedID = (FeedID)([32]uint8{00, 01, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58, 163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114}) + v2FeedID = (FeedID)([32]uint8{00, 02, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58, 163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114}) + v3FeedID = (FeedID)([32]uint8{00, 03, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58, 163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114}) + keystonev2Feed = (FeedID)([32]uint8{01, 12, 34, 56, 78, 00, 02, 04, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00}) + keystonev3Feed = (FeedID)([32]uint8{01, 12, 34, 56, 78, 00, 03, 04, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00}) + keystonev4Feed = (FeedID)([32]uint8{01, 12, 34, 56, 78, 00, 04, 04, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00}) ) func Test_FeedID_Version(t *testing.T) { t.Run("versioned feed ID", func(t *testing.T) { - assert.Equal(t, REPORT_V1, v1FeedId.Version()) - assert.True(t, v1FeedId.IsV1()) - assert.False(t, v1FeedId.IsV2()) - assert.False(t, v1FeedId.IsV3()) - - assert.Equal(t, REPORT_V2, v2FeedId.Version()) - assert.False(t, v2FeedId.IsV1()) - assert.True(t, v2FeedId.IsV2()) - assert.False(t, v2FeedId.IsV3()) - - assert.Equal(t, REPORT_V3, v3FeedId.Version()) - assert.False(t, v3FeedId.IsV1()) - assert.False(t, v3FeedId.IsV2()) - assert.True(t, v3FeedId.IsV3()) + assert.Equal(t, REPORT_V1, v1FeedID.Version()) + assert.True(t, v1FeedID.IsV1()) + assert.False(t, v1FeedID.IsV2()) + assert.False(t, v1FeedID.IsV3()) + + assert.Equal(t, REPORT_V2, v2FeedID.Version()) + assert.False(t, v2FeedID.IsV1()) + assert.True(t, v2FeedID.IsV2()) + assert.False(t, v2FeedID.IsV3()) + + assert.Equal(t, REPORT_V3, v3FeedID.Version()) + assert.False(t, v3FeedID.IsV1()) + assert.False(t, v3FeedID.IsV2()) + assert.True(t, v3FeedID.IsV3()) + + assert.Equal(t, REPORT_V2, keystonev2Feed.Version()) + assert.False(t, keystonev2Feed.IsV1()) + assert.True(t, keystonev2Feed.IsV2()) + assert.False(t, keystonev2Feed.IsV3()) + assert.False(t, keystonev2Feed.IsV4()) + + assert.Equal(t, REPORT_V3, keystonev3Feed.Version()) + assert.False(t, keystonev3Feed.IsV1()) + assert.False(t, keystonev3Feed.IsV2()) + assert.True(t, keystonev3Feed.IsV3()) + assert.False(t, keystonev3Feed.IsV4()) + + assert.Equal(t, REPORT_V4, keystonev4Feed.Version()) + assert.False(t, keystonev4Feed.IsV1()) + assert.False(t, keystonev4Feed.IsV2()) + assert.False(t, keystonev4Feed.IsV3()) + assert.True(t, keystonev4Feed.IsV4()) }) t.Run("legacy special cases", func(t *testing.T) { for _, feedID := range legacyV1FeedIDs { diff --git a/core/services/relay/evm/mercury/v1/data_source_test.go b/core/services/relay/evm/mercury/v1/data_source_test.go index 197d802a3b..7f5117a0aa 100644 --- a/core/services/relay/evm/mercury/v1/data_source_test.go +++ b/core/services/relay/evm/mercury/v1/data_source_test.go @@ -332,16 +332,15 @@ func TestMercury_Observe(t *testing.T) { t.Run("when chain is too short", func(t *testing.T) { h4 := &evmtypes.Head{ Number: 4, - Parent: nil, } h5 := &evmtypes.Head{ Number: 5, - Parent: h4, } + h5.Parent.Store(h4) h6 := &evmtypes.Head{ Number: 6, - Parent: h5, } + h6.Parent.Store(h5) ht2 := htmocks.NewHeadTracker[*evmtypes.Head, common.Hash](t) ht2.On("LatestChain").Return(h6) @@ -362,7 +361,7 @@ func TestMercury_Observe(t *testing.T) { for i := range heads { heads[i] = &evmtypes.Head{Number: int64(i)} if i > 0 { - heads[i].Parent = heads[i-1] + heads[i].Parent.Store(heads[i-1]) } } diff --git a/core/services/relay/evm/mercury/v1/reportcodec/report_codec.go b/core/services/relay/evm/mercury/v1/reportcodec/report_codec.go index f4c6af32b8..fb332dcc8f 100644 --- a/core/services/relay/evm/mercury/v1/reportcodec/report_codec.go +++ b/core/services/relay/evm/mercury/v1/reportcodec/report_codec.go @@ -11,8 +11,9 @@ import ( ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink-common/pkg/logger" v1 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v1" - "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/utils" reporttypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v1/types" ) diff --git a/core/services/relay/evm/mercury/v2/data_source.go b/core/services/relay/evm/mercury/v2/data_source.go index 28487ec714..fed748ac93 100644 --- a/core/services/relay/evm/mercury/v2/data_source.go +++ b/core/services/relay/evm/mercury/v2/data_source.go @@ -108,7 +108,9 @@ func (ds *datasource) Observe(ctx context.Context, repts ocrtypes.ReportTimestam }() var isLink, isNative bool - if ds.feedID == ds.linkFeedID { + if len(ds.jb.OCR2OracleSpec.PluginConfig) == 0 { + obs.LinkPrice.Val = v2.MissingPrice + } else if ds.feedID == ds.linkFeedID { isLink = true } else { wg.Add(1) @@ -126,7 +128,9 @@ func (ds *datasource) Observe(ctx context.Context, repts ocrtypes.ReportTimestam }() } - if ds.feedID == ds.nativeFeedID { + if len(ds.jb.OCR2OracleSpec.PluginConfig) == 0 { + obs.NativePrice.Val = v2.MissingPrice + } else if ds.feedID == ds.nativeFeedID { isNative = true } else { wg.Add(1) diff --git a/core/services/relay/evm/mercury/v2/data_source_test.go b/core/services/relay/evm/mercury/v2/data_source_test.go index 19af909c8e..25716521d8 100644 --- a/core/services/relay/evm/mercury/v2/data_source_test.go +++ b/core/services/relay/evm/mercury/v2/data_source_test.go @@ -15,6 +15,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" mercurymocks "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/utils" @@ -72,7 +73,16 @@ func (ms *mockSaver) Save(r *pipeline.Run) { func Test_Datasource(t *testing.T) { orm := &mockORM{} - ds := &datasource{orm: orm, lggr: logger.TestLogger(t)} + jb := job.Job{ + Type: job.Type(pipeline.OffchainReporting2JobType), + OCR2OracleSpec: &job.OCR2OracleSpec{ + CaptureEATelemetry: true, + PluginConfig: map[string]interface{}{ + "serverURL": "a", + }, + }, + } + ds := &datasource{orm: orm, lggr: logger.TestLogger(t), jb: jb} ctx := testutils.Context(t) repts := ocrtypes.ReportTimestamp{} @@ -274,6 +284,25 @@ func Test_Datasource(t *testing.T) { assert.EqualError(t, obs.NativePrice.Err, "some error fetching native price") }) + t.Run("when PluginConfig is empty", func(t *testing.T) { + t.Cleanup(func() { + ds.jb = jb + }) + + fetcher.linkPriceErr = errors.New("some error fetching link price") + fetcher.nativePriceErr = errors.New("some error fetching native price") + + ds.jb.OCR2OracleSpec.PluginConfig = job.JSONConfig{} + + obs, err := ds.Observe(ctx, repts, false) + assert.NoError(t, err) + assert.Nil(t, obs.LinkPrice.Err) + assert.Equal(t, obs.LinkPrice.Val, v2.MissingPrice) + assert.Nil(t, obs.NativePrice.Err) + assert.Equal(t, obs.NativePrice.Val, v2.MissingPrice) + assert.Equal(t, big.NewInt(122), obs.BenchmarkPrice.Val) + }) + t.Run("when succeeds to fetch linkPrice or nativePrice but got nil (new feed)", func(t *testing.T) { obs, err := ds.Observe(ctx, repts, false) assert.NoError(t, err) diff --git a/core/services/relay/evm/mercury/v2/reportcodec/report_codec.go b/core/services/relay/evm/mercury/v2/reportcodec/report_codec.go index 33c5fa9a32..ebbdfac66c 100644 --- a/core/services/relay/evm/mercury/v2/reportcodec/report_codec.go +++ b/core/services/relay/evm/mercury/v2/reportcodec/report_codec.go @@ -9,9 +9,9 @@ import ( ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink-common/pkg/logger" v2 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v2" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/utils" reporttypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v2/types" ) diff --git a/core/services/relay/evm/mercury/v3/data_source.go b/core/services/relay/evm/mercury/v3/data_source.go index 644f4e775e..9744ec45d8 100644 --- a/core/services/relay/evm/mercury/v3/data_source.go +++ b/core/services/relay/evm/mercury/v3/data_source.go @@ -112,7 +112,9 @@ func (ds *datasource) Observe(ctx context.Context, repts ocrtypes.ReportTimestam }() var isLink, isNative bool - if ds.feedID == ds.linkFeedID { + if len(ds.jb.OCR2OracleSpec.PluginConfig) == 0 { + obs.LinkPrice.Val = v3.MissingPrice + } else if ds.feedID == ds.linkFeedID { isLink = true } else { wg.Add(1) @@ -130,7 +132,9 @@ func (ds *datasource) Observe(ctx context.Context, repts ocrtypes.ReportTimestam }() } - if ds.feedID == ds.nativeFeedID { + if len(ds.jb.OCR2OracleSpec.PluginConfig) == 0 { + obs.NativePrice.Val = v3.MissingPrice + } else if ds.feedID == ds.nativeFeedID { isNative = true } else { wg.Add(1) diff --git a/core/services/relay/evm/mercury/v3/data_source_test.go b/core/services/relay/evm/mercury/v3/data_source_test.go index a0f624c78d..518fabb12c 100644 --- a/core/services/relay/evm/mercury/v3/data_source_test.go +++ b/core/services/relay/evm/mercury/v3/data_source_test.go @@ -5,18 +5,19 @@ import ( "math/big" "testing" + relaymercuryv3 "github.com/smartcontractkit/chainlink-data-streams/mercury/v3" + "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" + "github.com/smartcontractkit/chainlink/v2/core/services/pipeline/eautils" + "github.com/pkg/errors" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "github.com/stretchr/testify/assert" mercurytypes "github.com/smartcontractkit/chainlink-common/pkg/types/mercury" - relaymercuryv3 "github.com/smartcontractkit/chainlink-data-streams/mercury/v3" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" - "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" - "github.com/smartcontractkit/chainlink/v2/core/services/pipeline/eautils" mercurymocks "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/utils" reportcodecv3 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v3/reportcodec" @@ -77,6 +78,9 @@ func Test_Datasource(t *testing.T) { Type: job.Type(pipeline.OffchainReporting2JobType), OCR2OracleSpec: &job.OCR2OracleSpec{ CaptureEATelemetry: true, + PluginConfig: map[string]interface{}{ + "serverURL": "a", + }, }, } ds := &datasource{orm: orm, lggr: logger.TestLogger(t), jb: jb} @@ -360,6 +364,25 @@ func Test_Datasource(t *testing.T) { assert.EqualError(t, obs.NativePrice.Err, "some error fetching native price") }) + t.Run("when PluginConfig is empty", func(t *testing.T) { + t.Cleanup(func() { + ds.jb = jb + }) + + fetcher.linkPriceErr = errors.New("some error fetching link price") + fetcher.nativePriceErr = errors.New("some error fetching native price") + + ds.jb.OCR2OracleSpec.PluginConfig = job.JSONConfig{} + + obs, err := ds.Observe(ctx, repts, false) + assert.NoError(t, err) + assert.Nil(t, obs.LinkPrice.Err) + assert.Equal(t, obs.LinkPrice.Val, relaymercuryv3.MissingPrice) + assert.Nil(t, obs.NativePrice.Err) + assert.Equal(t, obs.NativePrice.Val, relaymercuryv3.MissingPrice) + assert.Equal(t, big.NewInt(122), obs.BenchmarkPrice.Val) + }) + t.Run("when succeeds to fetch linkPrice or nativePrice but got nil (new feed)", func(t *testing.T) { obs, err := ds.Observe(ctx, repts, false) assert.NoError(t, err) diff --git a/core/services/relay/evm/mercury/v3/reportcodec/report_codec.go b/core/services/relay/evm/mercury/v3/reportcodec/report_codec.go index 601431838d..1bf750fbf9 100644 --- a/core/services/relay/evm/mercury/v3/reportcodec/report_codec.go +++ b/core/services/relay/evm/mercury/v3/reportcodec/report_codec.go @@ -9,9 +9,9 @@ import ( ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink-common/pkg/logger" v3 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v3" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/utils" reporttypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v3/types" ) diff --git a/core/services/relay/evm/mercury/v4/data_source.go b/core/services/relay/evm/mercury/v4/data_source.go new file mode 100644 index 0000000000..bf45ccc87f --- /dev/null +++ b/core/services/relay/evm/mercury/v4/data_source.go @@ -0,0 +1,262 @@ +package v4 + +import ( + "context" + "errors" + "fmt" + "math/big" + "sync" + + pkgerrors "github.com/pkg/errors" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "github.com/smartcontractkit/chainlink-common/pkg/types/mercury" + v4types "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v4" + v4 "github.com/smartcontractkit/chainlink-data-streams/mercury/v4" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/job" + "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" + "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/types" + mercurytypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/types" + mercuryutils "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/utils" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v4/reportcodec" + "github.com/smartcontractkit/chainlink/v2/core/utils" +) + +type Runner interface { + ExecuteRun(ctx context.Context, spec pipeline.Spec, vars pipeline.Vars) (run *pipeline.Run, trrs pipeline.TaskRunResults, err error) +} + +type LatestReportFetcher interface { + LatestPrice(ctx context.Context, feedID [32]byte) (*big.Int, error) + LatestTimestamp(context.Context) (int64, error) +} + +type datasource struct { + pipelineRunner Runner + jb job.Job + spec pipeline.Spec + feedID mercuryutils.FeedID + lggr logger.Logger + saver ocrcommon.Saver + orm types.DataSourceORM + codec reportcodec.ReportCodec + + fetcher LatestReportFetcher + linkFeedID mercuryutils.FeedID + nativeFeedID mercuryutils.FeedID + + mu sync.RWMutex + + chEnhancedTelem chan<- ocrcommon.EnhancedTelemetryMercuryData +} + +var _ v4.DataSource = &datasource{} + +func NewDataSource(orm types.DataSourceORM, pr pipeline.Runner, jb job.Job, spec pipeline.Spec, feedID mercuryutils.FeedID, lggr logger.Logger, s ocrcommon.Saver, enhancedTelemChan chan ocrcommon.EnhancedTelemetryMercuryData, fetcher LatestReportFetcher, linkFeedID, nativeFeedID mercuryutils.FeedID) *datasource { + return &datasource{pr, jb, spec, feedID, lggr, s, orm, reportcodec.ReportCodec{}, fetcher, linkFeedID, nativeFeedID, sync.RWMutex{}, enhancedTelemChan} +} + +func (ds *datasource) Observe(ctx context.Context, repts ocrtypes.ReportTimestamp, fetchMaxFinalizedTimestamp bool) (obs v4types.Observation, pipelineExecutionErr error) { + var wg sync.WaitGroup + ctx, cancel := context.WithCancel(ctx) + + if fetchMaxFinalizedTimestamp { + wg.Add(1) + go func() { + defer wg.Done() + latest, dbErr := ds.orm.LatestReport(ctx, ds.feedID) + if dbErr != nil { + obs.MaxFinalizedTimestamp.Err = dbErr + return + } + if latest != nil { + maxFinalizedBlockNumber, decodeErr := ds.codec.ObservationTimestampFromReport(latest) + obs.MaxFinalizedTimestamp.Val, obs.MaxFinalizedTimestamp.Err = int64(maxFinalizedBlockNumber), decodeErr + return + } + obs.MaxFinalizedTimestamp.Val, obs.MaxFinalizedTimestamp.Err = ds.fetcher.LatestTimestamp(ctx) + }() + } + + var trrs pipeline.TaskRunResults + wg.Add(1) + go func() { + defer wg.Done() + var run *pipeline.Run + run, trrs, pipelineExecutionErr = ds.executeRun(ctx) + if pipelineExecutionErr != nil { + cancel() + pipelineExecutionErr = fmt.Errorf("Observe failed while executing run: %w", pipelineExecutionErr) + return + } + + ds.saver.Save(run) + + var parsed parseOutput + parsed, pipelineExecutionErr = ds.parse(trrs) + if pipelineExecutionErr != nil { + cancel() + // This is not expected under normal circumstances + ds.lggr.Errorw("Observe failed while parsing run results", "err", pipelineExecutionErr) + pipelineExecutionErr = fmt.Errorf("Observe failed while parsing run results: %w", pipelineExecutionErr) + return + } + obs.BenchmarkPrice = parsed.benchmarkPrice + obs.MarketStatus = parsed.marketStatus + }() + + var isLink, isNative bool + if len(ds.jb.OCR2OracleSpec.PluginConfig) == 0 { + obs.LinkPrice.Val = v4.MissingPrice + } else if ds.feedID == ds.linkFeedID { + isLink = true + } else { + wg.Add(1) + go func() { + defer wg.Done() + obs.LinkPrice.Val, obs.LinkPrice.Err = ds.fetcher.LatestPrice(ctx, ds.linkFeedID) + if obs.LinkPrice.Val == nil && obs.LinkPrice.Err == nil { + mercurytypes.PriceFeedMissingCount.WithLabelValues(ds.linkFeedID.String()).Inc() + ds.lggr.Warnw(fmt.Sprintf("Mercury server was missing LINK feed, using sentinel value of %s", v4.MissingPrice), "linkFeedID", ds.linkFeedID) + obs.LinkPrice.Val = v4.MissingPrice + } else if obs.LinkPrice.Err != nil { + mercurytypes.PriceFeedErrorCount.WithLabelValues(ds.linkFeedID.String()).Inc() + ds.lggr.Errorw("Mercury server returned error querying LINK price feed", "err", obs.LinkPrice.Err, "linkFeedID", ds.linkFeedID) + } + }() + } + + if len(ds.jb.OCR2OracleSpec.PluginConfig) == 0 { + obs.NativePrice.Val = v4.MissingPrice + } else if ds.feedID == ds.nativeFeedID { + isNative = true + } else { + wg.Add(1) + go func() { + defer wg.Done() + obs.NativePrice.Val, obs.NativePrice.Err = ds.fetcher.LatestPrice(ctx, ds.nativeFeedID) + if obs.NativePrice.Val == nil && obs.NativePrice.Err == nil { + mercurytypes.PriceFeedMissingCount.WithLabelValues(ds.nativeFeedID.String()).Inc() + ds.lggr.Warnw(fmt.Sprintf("Mercury server was missing native feed, using sentinel value of %s", v4.MissingPrice), "nativeFeedID", ds.nativeFeedID) + obs.NativePrice.Val = v4.MissingPrice + } else if obs.NativePrice.Err != nil { + mercurytypes.PriceFeedErrorCount.WithLabelValues(ds.nativeFeedID.String()).Inc() + ds.lggr.Errorw("Mercury server returned error querying native price feed", "err", obs.NativePrice.Err, "nativeFeedID", ds.nativeFeedID) + } + }() + } + + wg.Wait() + cancel() + + if pipelineExecutionErr != nil { + return + } + + if isLink || isNative { + // run has now completed so it is safe to use benchmark price + if isLink { + // This IS the LINK feed, use our observed price + obs.LinkPrice.Val, obs.LinkPrice.Err = obs.BenchmarkPrice.Val, obs.BenchmarkPrice.Err + } + if isNative { + // This IS the native feed, use our observed price + obs.NativePrice.Val, obs.NativePrice.Err = obs.BenchmarkPrice.Val, obs.BenchmarkPrice.Err + } + } + + ocrcommon.MaybeEnqueueEnhancedTelem(ds.jb, ds.chEnhancedTelem, ocrcommon.EnhancedTelemetryMercuryData{ + V4Observation: &obs, + TaskRunResults: trrs, + RepTimestamp: repts, + FeedVersion: mercuryutils.REPORT_V4, + FetchMaxFinalizedTimestamp: fetchMaxFinalizedTimestamp, + IsLinkFeed: isLink, + IsNativeFeed: isNative, + }) + + return obs, nil +} + +func toBigInt(val interface{}) (*big.Int, error) { + dec, err := utils.ToDecimal(val) + if err != nil { + return nil, err + } + return dec.BigInt(), nil +} + +type parseOutput struct { + benchmarkPrice mercury.ObsResult[*big.Int] + marketStatus mercury.ObsResult[uint32] +} + +func (ds *datasource) parse(trrs pipeline.TaskRunResults) (o parseOutput, merr error) { + var finaltrrs []pipeline.TaskRunResult + for _, trr := range trrs { + // only return terminal trrs from executeRun + if trr.IsTerminal() { + finaltrrs = append(finaltrrs, trr) + } + } + + // pipeline.TaskRunResults comes ordered asc by index, this is guaranteed + // by the pipeline executor + if len(finaltrrs) != 2 { + return o, fmt.Errorf("invalid number of results, expected: 2, got: %d", len(finaltrrs)) + } + + merr = errors.Join( + setBenchmarkPrice(&o, finaltrrs[0].Result), + setMarketStatus(&o, finaltrrs[1].Result), + ) + + return o, merr +} + +func setBenchmarkPrice(o *parseOutput, res pipeline.Result) error { + if res.Error != nil { + o.benchmarkPrice.Err = res.Error + return res.Error + } + val, err := toBigInt(res.Value) + if err != nil { + return fmt.Errorf("failed to parse BenchmarkPrice: %w", err) + } + o.benchmarkPrice.Val = val + return nil +} + +func setMarketStatus(o *parseOutput, res pipeline.Result) error { + if res.Error != nil { + o.marketStatus.Err = res.Error + return res.Error + } + val, err := toBigInt(res.Value) + if err != nil { + return fmt.Errorf("failed to parse MarketStatus: %w", err) + } + o.marketStatus.Val = uint32(val.Int64()) + return nil +} + +// The context passed in here has a timeout of (ObservationTimeout + ObservationGracePeriod). +// Upon context cancellation, its expected that we return any usable values within ObservationGracePeriod. +func (ds *datasource) executeRun(ctx context.Context) (*pipeline.Run, pipeline.TaskRunResults, error) { + vars := pipeline.NewVarsFrom(map[string]interface{}{ + "jb": map[string]interface{}{ + "databaseID": ds.jb.ID, + "externalJobID": ds.jb.ExternalJobID, + "name": ds.jb.Name.ValueOrZero(), + }, + }) + + run, trrs, err := ds.pipelineRunner.ExecuteRun(ctx, ds.spec, vars) + if err != nil { + return nil, nil, pkgerrors.Wrapf(err, "error executing run for spec ID %v", ds.spec.ID) + } + + return run, trrs, err +} diff --git a/core/services/relay/evm/mercury/v4/data_source_test.go b/core/services/relay/evm/mercury/v4/data_source_test.go new file mode 100644 index 0000000000..48aec5989a --- /dev/null +++ b/core/services/relay/evm/mercury/v4/data_source_test.go @@ -0,0 +1,348 @@ +package v4 + +import ( + "context" + "math/big" + "testing" + + "github.com/pkg/errors" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/stretchr/testify/assert" + + mercurytypes "github.com/smartcontractkit/chainlink-common/pkg/types/mercury" + relaymercuryv4 "github.com/smartcontractkit/chainlink-data-streams/mercury/v4" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/job" + "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" + mercurymocks "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/mocks" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/utils" + reportcodecv4 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v4/reportcodec" +) + +var _ mercurytypes.ServerFetcher = &mockFetcher{} + +type mockFetcher struct { + ts int64 + tsErr error + linkPrice *big.Int + linkPriceErr error + nativePrice *big.Int + nativePriceErr error +} + +var feedId utils.FeedID = [32]byte{1} +var linkFeedId utils.FeedID = [32]byte{2} +var nativeFeedId utils.FeedID = [32]byte{3} + +func (m *mockFetcher) FetchInitialMaxFinalizedBlockNumber(context.Context) (*int64, error) { + return nil, nil +} + +func (m *mockFetcher) LatestPrice(ctx context.Context, fId [32]byte) (*big.Int, error) { + if fId == linkFeedId { + return m.linkPrice, m.linkPriceErr + } else if fId == nativeFeedId { + return m.nativePrice, m.nativePriceErr + } + return nil, nil +} + +func (m *mockFetcher) LatestTimestamp(context.Context) (int64, error) { + return m.ts, m.tsErr +} + +type mockORM struct { + report []byte + err error +} + +func (m *mockORM) LatestReport(ctx context.Context, feedID [32]byte) (report []byte, err error) { + return m.report, m.err +} + +type mockSaver struct { + r *pipeline.Run +} + +func (ms *mockSaver) Save(r *pipeline.Run) { + ms.r = r +} + +func Test_Datasource(t *testing.T) { + orm := &mockORM{} + jb := job.Job{ + Type: job.Type(pipeline.OffchainReporting2JobType), + OCR2OracleSpec: &job.OCR2OracleSpec{ + CaptureEATelemetry: true, + PluginConfig: map[string]interface{}{ + "serverURL": "a", + }, + }, + } + ds := &datasource{orm: orm, lggr: logger.TestLogger(t), jb: jb} + ctx := testutils.Context(t) + repts := ocrtypes.ReportTimestamp{} + + fetcher := &mockFetcher{} + ds.fetcher = fetcher + + saver := &mockSaver{} + ds.saver = saver + + goodTrrs := []pipeline.TaskRunResult{ + { + // bp + Result: pipeline.Result{Value: "122.345"}, + Task: &mercurymocks.MockTask{}, + }, + { + // marketStatus + Result: pipeline.Result{Value: "1"}, + Task: &mercurymocks.MockTask{}, + }, + } + + ds.pipelineRunner = &mercurymocks.MockRunner{ + Trrs: goodTrrs, + } + + spec := pipeline.Spec{} + ds.spec = spec + + t.Run("when fetchMaxFinalizedTimestamp=true", func(t *testing.T) { + t.Run("with latest report in database", func(t *testing.T) { + orm.report = buildSamplev4Report() + orm.err = nil + + obs, err := ds.Observe(ctx, repts, true) + assert.NoError(t, err) + + assert.NoError(t, obs.MaxFinalizedTimestamp.Err) + assert.Equal(t, int64(124), obs.MaxFinalizedTimestamp.Val) + }) + t.Run("if querying latest report fails", func(t *testing.T) { + orm.report = nil + orm.err = errors.New("something exploded") + + obs, err := ds.Observe(ctx, repts, true) + assert.NoError(t, err) + + assert.EqualError(t, obs.MaxFinalizedTimestamp.Err, "something exploded") + assert.Zero(t, obs.MaxFinalizedTimestamp.Val) + }) + t.Run("if codec fails to decode", func(t *testing.T) { + orm.report = []byte{1, 2, 3} + orm.err = nil + + obs, err := ds.Observe(ctx, repts, true) + assert.NoError(t, err) + + assert.EqualError(t, obs.MaxFinalizedTimestamp.Err, "failed to decode report: abi: cannot marshal in to go type: length insufficient 3 require 32") + assert.Zero(t, obs.MaxFinalizedTimestamp.Val) + }) + + orm.report = nil + orm.err = nil + + t.Run("if LatestTimestamp returns error", func(t *testing.T) { + fetcher.tsErr = errors.New("some error") + + obs, err := ds.Observe(ctx, repts, true) + assert.NoError(t, err) + + assert.EqualError(t, obs.MaxFinalizedTimestamp.Err, "some error") + assert.Zero(t, obs.MaxFinalizedTimestamp.Val) + }) + + t.Run("if LatestTimestamp succeeds", func(t *testing.T) { + fetcher.tsErr = nil + fetcher.ts = 123 + + obs, err := ds.Observe(ctx, repts, true) + assert.NoError(t, err) + + assert.Equal(t, int64(123), obs.MaxFinalizedTimestamp.Val) + assert.NoError(t, obs.MaxFinalizedTimestamp.Err) + }) + + t.Run("if LatestTimestamp succeeds but ts=0 (new feed)", func(t *testing.T) { + fetcher.tsErr = nil + fetcher.ts = 0 + + obs, err := ds.Observe(ctx, repts, true) + assert.NoError(t, err) + + assert.NoError(t, obs.MaxFinalizedTimestamp.Err) + assert.Zero(t, obs.MaxFinalizedTimestamp.Val) + }) + + t.Run("when run execution succeeded", func(t *testing.T) { + t.Run("when feedId=linkFeedID=nativeFeedId", func(t *testing.T) { + t.Cleanup(func() { + ds.feedID, ds.linkFeedID, ds.nativeFeedID = feedId, linkFeedId, nativeFeedId + }) + + ds.feedID, ds.linkFeedID, ds.nativeFeedID = feedId, feedId, feedId + + fetcher.ts = 123123 + fetcher.tsErr = nil + + obs, err := ds.Observe(ctx, repts, true) + assert.NoError(t, err) + + assert.Equal(t, big.NewInt(122), obs.BenchmarkPrice.Val) + assert.NoError(t, obs.BenchmarkPrice.Err) + assert.Equal(t, int64(123123), obs.MaxFinalizedTimestamp.Val) + assert.NoError(t, obs.MaxFinalizedTimestamp.Err) + assert.Equal(t, big.NewInt(122), obs.LinkPrice.Val) + assert.NoError(t, obs.LinkPrice.Err) + assert.Equal(t, big.NewInt(122), obs.NativePrice.Val) + assert.NoError(t, obs.NativePrice.Err) + assert.Equal(t, uint32(1), obs.MarketStatus.Val) + assert.NoError(t, obs.MarketStatus.Err) + }) + }) + }) + + t.Run("when fetchMaxFinalizedTimestamp=false", func(t *testing.T) { + t.Run("when run execution fails, returns error", func(t *testing.T) { + t.Cleanup(func() { + ds.pipelineRunner = &mercurymocks.MockRunner{ + Trrs: goodTrrs, + Err: nil, + } + }) + + ds.pipelineRunner = &mercurymocks.MockRunner{ + Trrs: goodTrrs, + Err: errors.New("run execution failed"), + } + + _, err := ds.Observe(ctx, repts, false) + assert.EqualError(t, err, "Observe failed while executing run: error executing run for spec ID 0: run execution failed") + }) + + t.Run("when parsing run results fails, return error", func(t *testing.T) { + t.Cleanup(func() { + runner := &mercurymocks.MockRunner{ + Trrs: goodTrrs, + Err: nil, + } + ds.pipelineRunner = runner + }) + + badTrrs := []pipeline.TaskRunResult{ + { + // benchmark price + Result: pipeline.Result{Error: errors.New("some error with bp")}, + Task: &mercurymocks.MockTask{}, + }, + { + // marketStatus + Result: pipeline.Result{Value: "1"}, + Task: &mercurymocks.MockTask{}, + }, + } + + ds.pipelineRunner = &mercurymocks.MockRunner{ + Trrs: badTrrs, + Err: nil, + } + + _, err := ds.Observe(ctx, repts, false) + assert.EqualError(t, err, "Observe failed while parsing run results: some error with bp") + }) + + t.Run("when run execution succeeded", func(t *testing.T) { + t.Run("when feedId=linkFeedID=nativeFeedId", func(t *testing.T) { + t.Cleanup(func() { + ds.feedID, ds.linkFeedID, ds.nativeFeedID = feedId, linkFeedId, nativeFeedId + }) + + var feedId utils.FeedID = [32]byte{1} + ds.feedID, ds.linkFeedID, ds.nativeFeedID = feedId, feedId, feedId + + obs, err := ds.Observe(ctx, repts, false) + assert.NoError(t, err) + + assert.Equal(t, big.NewInt(122), obs.BenchmarkPrice.Val) + assert.NoError(t, obs.BenchmarkPrice.Err) + assert.Equal(t, int64(0), obs.MaxFinalizedTimestamp.Val) + assert.NoError(t, obs.MaxFinalizedTimestamp.Err) + assert.Equal(t, big.NewInt(122), obs.LinkPrice.Val) + assert.NoError(t, obs.LinkPrice.Err) + assert.Equal(t, big.NewInt(122), obs.NativePrice.Val) + assert.NoError(t, obs.NativePrice.Err) + assert.Equal(t, uint32(1), obs.MarketStatus.Val) + assert.NoError(t, obs.MarketStatus.Err) + }) + + t.Run("when fails to fetch linkPrice or nativePrice", func(t *testing.T) { + t.Cleanup(func() { + fetcher.linkPriceErr = nil + fetcher.nativePriceErr = nil + }) + + fetcher.linkPriceErr = errors.New("some error fetching link price") + fetcher.nativePriceErr = errors.New("some error fetching native price") + + obs, err := ds.Observe(ctx, repts, false) + assert.NoError(t, err) + + assert.Nil(t, obs.LinkPrice.Val) + assert.EqualError(t, obs.LinkPrice.Err, "some error fetching link price") + assert.Nil(t, obs.NativePrice.Val) + assert.EqualError(t, obs.NativePrice.Err, "some error fetching native price") + }) + + t.Run("when PluginConfig is empty", func(t *testing.T) { + t.Cleanup(func() { + ds.jb = jb + }) + + fetcher.linkPriceErr = errors.New("some error fetching link price") + fetcher.nativePriceErr = errors.New("some error fetching native price") + + ds.jb.OCR2OracleSpec.PluginConfig = job.JSONConfig{} + + obs, err := ds.Observe(ctx, repts, false) + assert.NoError(t, err) + assert.Nil(t, obs.LinkPrice.Err) + assert.Equal(t, obs.LinkPrice.Val, relaymercuryv4.MissingPrice) + assert.Nil(t, obs.NativePrice.Err) + assert.Equal(t, obs.NativePrice.Val, relaymercuryv4.MissingPrice) + assert.Equal(t, big.NewInt(122), obs.BenchmarkPrice.Val) + }) + + t.Run("when succeeds to fetch linkPrice or nativePrice but got nil (new feed)", func(t *testing.T) { + obs, err := ds.Observe(ctx, repts, false) + assert.NoError(t, err) + + assert.Equal(t, obs.LinkPrice.Val, relaymercuryv4.MissingPrice) + assert.Nil(t, obs.LinkPrice.Err) + assert.Equal(t, obs.NativePrice.Val, relaymercuryv4.MissingPrice) + assert.Nil(t, obs.NativePrice.Err) + }) + }) + }) +} + +var sampleFeedID = [32]uint8{28, 145, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58, 163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114} + +func buildSamplev4Report() []byte { + feedID := sampleFeedID + timestamp := uint32(124) + bp := big.NewInt(242) + validFromTimestamp := uint32(123) + expiresAt := uint32(456) + linkFee := big.NewInt(3334455) + nativeFee := big.NewInt(556677) + marketStatus := uint32(1) + + b, err := reportcodecv4.ReportTypes.Pack(feedID, validFromTimestamp, timestamp, nativeFee, linkFee, expiresAt, bp, marketStatus) + if err != nil { + panic(err) + } + return b +} diff --git a/core/services/relay/evm/mercury/v4/reportcodec/report_codec.go b/core/services/relay/evm/mercury/v4/reportcodec/report_codec.go new file mode 100644 index 0000000000..dd98ef272e --- /dev/null +++ b/core/services/relay/evm/mercury/v4/reportcodec/report_codec.go @@ -0,0 +1,77 @@ +package reportcodec + +import ( + "errors" + "fmt" + "math/big" + + pkgerrors "github.com/pkg/errors" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + v4 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v4" + + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/utils" + reporttypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v4/types" +) + +var ReportTypes = reporttypes.GetSchema() +var maxReportLength = 32 * len(ReportTypes) // each arg is 256 bit EVM word +var zero = big.NewInt(0) + +var _ v4.ReportCodec = &ReportCodec{} + +type ReportCodec struct { + logger logger.Logger + feedID utils.FeedID +} + +func NewReportCodec(feedID [32]byte, lggr logger.Logger) *ReportCodec { + return &ReportCodec{lggr, feedID} +} + +func (r *ReportCodec) BuildReport(rf v4.ReportFields) (ocrtypes.Report, error) { + var merr error + if rf.BenchmarkPrice == nil { + merr = errors.Join(merr, errors.New("benchmarkPrice may not be nil")) + } + if rf.LinkFee == nil { + merr = errors.Join(merr, errors.New("linkFee may not be nil")) + } else if rf.LinkFee.Cmp(zero) < 0 { + merr = errors.Join(merr, fmt.Errorf("linkFee may not be negative (got: %s)", rf.LinkFee)) + } + if rf.NativeFee == nil { + merr = errors.Join(merr, errors.New("nativeFee may not be nil")) + } else if rf.NativeFee.Cmp(zero) < 0 { + merr = errors.Join(merr, fmt.Errorf("nativeFee may not be negative (got: %s)", rf.NativeFee)) + } + if merr != nil { + return nil, merr + } + reportBytes, err := ReportTypes.Pack(r.feedID, rf.ValidFromTimestamp, rf.Timestamp, rf.NativeFee, rf.LinkFee, rf.ExpiresAt, rf.BenchmarkPrice, rf.MarketStatus) + return ocrtypes.Report(reportBytes), pkgerrors.Wrap(err, "failed to pack report blob") +} + +func (r *ReportCodec) MaxReportLength(n int) (int, error) { + return maxReportLength, nil +} + +func (r *ReportCodec) ObservationTimestampFromReport(report ocrtypes.Report) (uint32, error) { + decoded, err := r.Decode(report) + if err != nil { + return 0, err + } + return decoded.ObservationsTimestamp, nil +} + +func (r *ReportCodec) Decode(report ocrtypes.Report) (*reporttypes.Report, error) { + return reporttypes.Decode(report) +} + +func (r *ReportCodec) BenchmarkPriceFromReport(report ocrtypes.Report) (*big.Int, error) { + decoded, err := r.Decode(report) + if err != nil { + return nil, err + } + return decoded.BenchmarkPrice, nil +} diff --git a/core/services/relay/evm/mercury/v4/reportcodec/report_codec_test.go b/core/services/relay/evm/mercury/v4/reportcodec/report_codec_test.go new file mode 100644 index 0000000000..7be81bf279 --- /dev/null +++ b/core/services/relay/evm/mercury/v4/reportcodec/report_codec_test.go @@ -0,0 +1,155 @@ +package reportcodec + +import ( + "math/big" + "testing" + + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + v4 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v4" +) + +func newValidReportFields() v4.ReportFields { + return v4.ReportFields{ + Timestamp: 242, + BenchmarkPrice: big.NewInt(243), + ValidFromTimestamp: 123, + ExpiresAt: 20, + LinkFee: big.NewInt(456), + NativeFee: big.NewInt(457), + MarketStatus: 1, + } +} + +func Test_ReportCodec_BuildReport(t *testing.T) { + r := ReportCodec{} + + t.Run("BuildReport errors on zero values", func(t *testing.T) { + _, err := r.BuildReport(v4.ReportFields{}) + require.Error(t, err) + assert.Contains(t, err.Error(), "benchmarkPrice may not be nil") + assert.Contains(t, err.Error(), "linkFee may not be nil") + assert.Contains(t, err.Error(), "nativeFee may not be nil") + }) + + t.Run("BuildReport constructs a report from observations", func(t *testing.T) { + rf := newValidReportFields() + // only need to test happy path since validations are done in relaymercury + + report, err := r.BuildReport(rf) + require.NoError(t, err) + + reportElems := make(map[string]interface{}) + err = ReportTypes.UnpackIntoMap(reportElems, report) + require.NoError(t, err) + + assert.Equal(t, int(reportElems["observationsTimestamp"].(uint32)), 242) + assert.Equal(t, reportElems["benchmarkPrice"].(*big.Int).Int64(), int64(243)) + assert.Equal(t, reportElems["validFromTimestamp"].(uint32), uint32(123)) + assert.Equal(t, reportElems["expiresAt"].(uint32), uint32(20)) + assert.Equal(t, reportElems["linkFee"].(*big.Int).Int64(), int64(456)) + assert.Equal(t, reportElems["nativeFee"].(*big.Int).Int64(), int64(457)) + assert.Equal(t, reportElems["marketStatus"].(uint32), uint32(1)) + + assert.Equal(t, types.Report{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xc9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xc8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, report) + max, err := r.MaxReportLength(4) + require.NoError(t, err) + assert.LessOrEqual(t, len(report), max) + + t.Run("Decode decodes the report", func(t *testing.T) { + decoded, err := r.Decode(report) + require.NoError(t, err) + + require.NotNil(t, decoded) + + assert.Equal(t, uint32(242), decoded.ObservationsTimestamp) + assert.Equal(t, big.NewInt(243), decoded.BenchmarkPrice) + assert.Equal(t, uint32(123), decoded.ValidFromTimestamp) + assert.Equal(t, uint32(20), decoded.ExpiresAt) + assert.Equal(t, big.NewInt(456), decoded.LinkFee) + assert.Equal(t, big.NewInt(457), decoded.NativeFee) + assert.Equal(t, uint32(1), decoded.MarketStatus) + }) + }) + + t.Run("errors on negative fee", func(t *testing.T) { + rf := newValidReportFields() + rf.LinkFee = big.NewInt(-1) + rf.NativeFee = big.NewInt(-1) + _, err := r.BuildReport(rf) + require.Error(t, err) + + assert.Contains(t, err.Error(), "linkFee may not be negative (got: -1)") + assert.Contains(t, err.Error(), "nativeFee may not be negative (got: -1)") + }) + + t.Run("Decode errors on invalid report", func(t *testing.T) { + _, err := r.Decode([]byte{1, 2, 3}) + assert.EqualError(t, err, "failed to decode report: abi: cannot marshal in to go type: length insufficient 3 require 32") + + longBad := make([]byte, 64) + for i := 0; i < len(longBad); i++ { + longBad[i] = byte(i) + } + _, err = r.Decode(longBad) + assert.EqualError(t, err, "failed to decode report: abi: improperly encoded uint32 value") + }) +} + +func buildSampleReport(ts int64) []byte { + feedID := [32]byte{'f', 'o', 'o'} + timestamp := uint32(ts) + bp := big.NewInt(242) + validFromTimestamp := uint32(123) + expiresAt := uint32(456) + linkFee := big.NewInt(3334455) + nativeFee := big.NewInt(556677) + marketStatus := uint32(1) + + b, err := ReportTypes.Pack(feedID, validFromTimestamp, timestamp, nativeFee, linkFee, expiresAt, bp, marketStatus) + if err != nil { + panic(err) + } + return b +} + +func Test_ReportCodec_ObservationTimestampFromReport(t *testing.T) { + r := ReportCodec{} + + t.Run("ObservationTimestampFromReport extracts observation timestamp from a valid report", func(t *testing.T) { + report := buildSampleReport(123) + + ts, err := r.ObservationTimestampFromReport(report) + require.NoError(t, err) + + assert.Equal(t, ts, uint32(123)) + }) + t.Run("ObservationTimestampFromReport returns error when report is invalid", func(t *testing.T) { + report := []byte{1, 2, 3} + + _, err := r.ObservationTimestampFromReport(report) + require.Error(t, err) + + assert.EqualError(t, err, "failed to decode report: abi: cannot marshal in to go type: length insufficient 3 require 32") + }) +} + +func Test_ReportCodec_BenchmarkPriceFromReport(t *testing.T) { + r := ReportCodec{} + + t.Run("BenchmarkPriceFromReport extracts the benchmark price from valid report", func(t *testing.T) { + report := buildSampleReport(123) + + bp, err := r.BenchmarkPriceFromReport(report) + require.NoError(t, err) + + assert.Equal(t, big.NewInt(242), bp) + }) + t.Run("BenchmarkPriceFromReport errors on invalid report", func(t *testing.T) { + _, err := r.BenchmarkPriceFromReport([]byte{1, 2, 3}) + require.Error(t, err) + assert.EqualError(t, err, "failed to decode report: abi: cannot marshal in to go type: length insufficient 3 require 32") + }) +} diff --git a/core/services/relay/evm/mercury/v4/types/types.go b/core/services/relay/evm/mercury/v4/types/types.go new file mode 100644 index 0000000000..584836c1e9 --- /dev/null +++ b/core/services/relay/evm/mercury/v4/types/types.go @@ -0,0 +1,54 @@ +package reporttypes + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi" +) + +var schema = GetSchema() + +func GetSchema() abi.Arguments { + mustNewType := func(t string) abi.Type { + result, err := abi.NewType(t, "", []abi.ArgumentMarshaling{}) + if err != nil { + panic(fmt.Sprintf("Unexpected error during abi.NewType: %s", err)) + } + return result + } + return abi.Arguments([]abi.Argument{ + {Name: "feedId", Type: mustNewType("bytes32")}, + {Name: "validFromTimestamp", Type: mustNewType("uint32")}, + {Name: "observationsTimestamp", Type: mustNewType("uint32")}, + {Name: "nativeFee", Type: mustNewType("uint192")}, + {Name: "linkFee", Type: mustNewType("uint192")}, + {Name: "expiresAt", Type: mustNewType("uint32")}, + {Name: "benchmarkPrice", Type: mustNewType("int192")}, + {Name: "marketStatus", Type: mustNewType("uint32")}, + }) +} + +type Report struct { + FeedId [32]byte + ObservationsTimestamp uint32 + BenchmarkPrice *big.Int + ValidFromTimestamp uint32 + ExpiresAt uint32 + LinkFee *big.Int + NativeFee *big.Int + MarketStatus uint32 +} + +// Decode is made available to external users (i.e. mercury server) +func Decode(report []byte) (*Report, error) { + values, err := schema.Unpack(report) + if err != nil { + return nil, fmt.Errorf("failed to decode report: %w", err) + } + decoded := new(Report) + if err = schema.Copy(decoded, values); err != nil { + return nil, fmt.Errorf("failed to copy report values to struct: %w", err) + } + return decoded, nil +} diff --git a/core/services/relay/evm/mercury/verifier/verifier.go b/core/services/relay/evm/mercury/verifier/verifier.go new file mode 100644 index 0000000000..02bb17d387 --- /dev/null +++ b/core/services/relay/evm/mercury/verifier/verifier.go @@ -0,0 +1,111 @@ +package verifier + +import ( + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" +) + +var ( + ErrVerificationFailed = errors.New("verification failed") + + ErrFailedUnmarshalPubkey = fmt.Errorf("%w: failed to unmarshal pubkey", ErrVerificationFailed) + ErrVerifyInvalidSignatureCount = fmt.Errorf("%w: invalid signature count", ErrVerificationFailed) + ErrVerifyMismatchedSignatureCount = fmt.Errorf("%w: mismatched signature count", ErrVerificationFailed) + ErrVerifyInvalidSignature = fmt.Errorf("%w: invalid signature", ErrVerificationFailed) + ErrVerifySomeSignerUnauthorized = fmt.Errorf("%w: node unauthorized", ErrVerificationFailed) + ErrVerifyNonUniqueSignature = fmt.Errorf("%w: signer has already signed", ErrVerificationFailed) +) + +type SignedReport struct { + RawRs [][32]byte + RawSs [][32]byte + RawVs [32]byte + ReportContext [3][32]byte + Report []byte +} + +type Verifier interface { + // Verify checks the report against its configuration, and then verifies signatures. + // It replicates the Verifier contract's "verify" function for server side + // report verification. + // See also: contracts/src/v0.8/llo-feeds/Verifier.sol + Verify(report SignedReport, f uint8, authorizedSigners []common.Address) (signers []common.Address, err error) +} + +var _ Verifier = (*verifier)(nil) + +type verifier struct{} + +func NewVerifier() Verifier { + return &verifier{} +} + +func (v *verifier) Verify(sr SignedReport, f uint8, authorizedSigners []common.Address) (signers []common.Address, err error) { + if len(sr.RawRs) != int(f+1) { + return signers, fmt.Errorf("%w: expected the number of signatures (len(rs)) to equal the number of signatures required (f), but f=%d and len(rs)=%d", ErrVerifyInvalidSignatureCount, f+1, len(sr.RawRs)) + } + if len(sr.RawRs) != len(sr.RawSs) { + return signers, fmt.Errorf("%w: got %d rs and %d ss, expected equal", ErrVerifyMismatchedSignatureCount, len(sr.RawRs), len(sr.RawSs)) + } + + sigData := ReportToSigData(sr.ReportContext, sr.Report) + + signerMap := make(map[common.Address]bool) + for _, signer := range authorizedSigners { + signerMap[signer] = false + } + + // Loop over every signature and collect errors. This wastes some CPU cycles, but we need to know everyone who + // signed the report. Some risk mitigated by checking that the number of signatures matches the expected (F) earlier + var verifyErrors error + reportSigners := make([]common.Address, len(sr.RawRs)) // For logging + metrics, string for convenience + for i := 0; i < len(sr.RawRs); i++ { + sig := append(sr.RawRs[i][:], sr.RawSs[i][:]...) + sig = append(sig, sr.RawVs[i]) // In the contract, you'll see vs+27. We don't do that here since geth adds +27 internally + + sigPubKey, err := crypto.Ecrecover(sigData, sig) + if err != nil { + verifyErrors = errors.Join(verifyErrors, fmt.Errorf("failed to recover signature: %w", err)) + continue + } + + verified := crypto.VerifySignature(sigPubKey, sigData, sig[:64]) + if !verified { + verifyErrors = errors.Join(verifyErrors, ErrVerifyInvalidSignature, fmt.Errorf("signature verification failed for pubKey: %x, sig: %x", sigPubKey, sig)) + continue + } + + unmarshalledPub, err := crypto.UnmarshalPubkey(sigPubKey) + if err != nil { + verifyErrors = errors.Join(verifyErrors, ErrFailedUnmarshalPubkey, fmt.Errorf("public key=%x error=%w", sigPubKey, err)) + continue + } + + address := crypto.PubkeyToAddress(*unmarshalledPub) + reportSigners[i] = address + encountered, authorized := signerMap[address] + if !authorized { + verifyErrors = errors.Join(verifyErrors, ErrVerifySomeSignerUnauthorized, fmt.Errorf("signer %s not in list of authorized nodes", address.String())) + continue + } + if encountered { + verifyErrors = errors.Join(verifyErrors, ErrVerifyNonUniqueSignature, fmt.Errorf("signer %s has already signed this report", address.String())) + continue + } + signerMap[address] = true + signers = append(signers, address) + } + return signers, verifyErrors +} + +func ReportToSigData(reportCtx [3][32]byte, sr types.Report) []byte { + sigData := crypto.Keccak256(sr) + sigData = append(sigData, reportCtx[0][:]...) + sigData = append(sigData, reportCtx[1][:]...) + sigData = append(sigData, reportCtx[2][:]...) + return crypto.Keccak256(sigData) +} diff --git a/core/services/relay/evm/mercury/verifier/verifier_test.go b/core/services/relay/evm/mercury/verifier/verifier_test.go new file mode 100644 index 0000000000..abe891b583 --- /dev/null +++ b/core/services/relay/evm/mercury/verifier/verifier_test.go @@ -0,0 +1,80 @@ +package verifier + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury" +) + +func Test_Verifier(t *testing.T) { + t.Parallel() + + signedReportBinary := hexutil.MustDecode(`0x0006e1dde86b8a12add45546a14ea7e5efd10b67a373c6f4c41ecfa17d0005350000000000000000000000000000000000000000000000000000000000000201000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000002800001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012000034c9214519c942ad0aa84a3dd31870e6efe8b3fcab4e176c5226879b26c77000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000669150aa0000000000000000000000000000000000001504e1e6c380271bb8b129ac8f7c0000000000000000000000000000000000001504e1e6c380271bb8b129ac8f7c00000000000000000000000000000000000000000000000000000000669150ab0000000000000000000000000000000000000000000000000000002482116240000000000000000000000000000000000000000000000000000000247625a04000000000000000000000000000000000000000000000000000000024880743400000000000000000000000000000000000000000000000000000000000000002710ac21df88ab70c8822b68be53d7bed65c82ffc9204c1d7ccf3c6c4048b3ca2cafb26e7bbd8f13fe626c946baa5ffcb444319c4229b945ea65d0c99c21978a100000000000000000000000000000000000000000000000000000000000000022c07843f17aa3ecd55f52e99e889906f825f49e4ddfa9c74ca487dd4ff101cc636108a5323be838e658dffa1be67bd91e99f68c4bf86936b76c5d8193b707597`) + m := make(map[string]interface{}) + err := mercury.PayloadTypes.UnpackIntoMap(m, signedReportBinary) + require.NoError(t, err) + + signedReport := SignedReport{ + RawRs: m["rawRs"].([][32]byte), + RawSs: m["rawSs"].([][32]byte), + RawVs: m["rawVs"].([32]byte), + ReportContext: m["reportContext"].([3][32]byte), + Report: m["report"].([]byte), + } + + f := uint8(1) + + v := NewVerifier() + + t.Run("Verify errors with unauthorized signers", func(t *testing.T) { + _, err := v.Verify(signedReport, f, []common.Address{}) + require.Error(t, err) + assert.EqualError(t, err, "verification failed: node unauthorized\nsigner 0x3fc9FaA15d71EeD614e5322bd9554Fb35cC381d2 not in list of authorized nodes\nverification failed: node unauthorized\nsigner 0xBa6534da0E49c71cD9d0292203F1524876f33E23 not in list of authorized nodes") + }) + + t.Run("Verify succeeds with authorized signers", func(t *testing.T) { + signers, err := v.Verify(signedReport, f, []common.Address{ + common.HexToAddress("0xde25e5b4005f611e356ce203900da4e37d72d58f"), + common.HexToAddress("0x256431d41cf0d944f5877bc6c93846a9829dfc03"), + common.HexToAddress("0x3fc9faa15d71eed614e5322bd9554fb35cc381d2"), + common.HexToAddress("0xba6534da0e49c71cd9d0292203f1524876f33e23"), + }) + require.NoError(t, err) + assert.Equal(t, []common.Address{ + common.HexToAddress("0x3fc9faa15d71eed614e5322bd9554fb35cc381d2"), + common.HexToAddress("0xBa6534da0E49c71cD9d0292203F1524876f33E23"), + }, signers) + }) + + t.Run("Verify fails if report has been tampered with", func(t *testing.T) { + badReport := signedReport + badReport.Report = []byte{0x0011} + _, err := v.Verify(badReport, f, []common.Address{ + common.HexToAddress("0xde25e5b4005f611e356ce203900da4e37d72d58f"), + common.HexToAddress("0x256431d41cf0d944f5877bc6c93846a9829dfc03"), + common.HexToAddress("0x3fc9faa15d71eed614e5322bd9554fb35cc381d2"), + common.HexToAddress("0xba6534da0e49c71cd9d0292203f1524876f33e23"), + }) + + require.Error(t, err) + }) + + t.Run("Verify fails if rawVs has been changed", func(t *testing.T) { + badReport := signedReport + badReport.RawVs = [32]byte{0x0011} + _, err := v.Verify(badReport, f, []common.Address{ + common.HexToAddress("0xde25e5b4005f611e356ce203900da4e37d72d58f"), + common.HexToAddress("0x256431d41cf0d944f5877bc6c93846a9829dfc03"), + common.HexToAddress("0x3fc9faa15d71eed614e5322bd9554fb35cc381d2"), + common.HexToAddress("0xba6534da0e49c71cd9d0292203f1524876f33e23"), + }) + + require.Error(t, err) + assert.Contains(t, err.Error(), "failed to recover signature: invalid signature recovery id") + }) +} diff --git a/core/services/relay/evm/mercury/wsrpc/client.go b/core/services/relay/evm/mercury/wsrpc/client.go index b5d784face..3720751065 100644 --- a/core/services/relay/evm/mercury/wsrpc/client.go +++ b/core/services/relay/evm/mercury/wsrpc/client.go @@ -11,6 +11,7 @@ import ( "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" + grpc_connectivity "google.golang.org/grpc/connectivity" "github.com/smartcontractkit/wsrpc" "github.com/smartcontractkit/wsrpc/connectivity" @@ -70,8 +71,8 @@ type Client interface { type Conn interface { WaitForReady(ctx context.Context) bool - GetState() connectivity.State - Close() + GetState() grpc_connectivity.State + Close() error } type client struct { @@ -230,7 +231,7 @@ func (w *client) Healthy() (err error) { return err } state := w.conn.GetState() - if state != connectivity.Ready { + if state != grpc_connectivity.Ready { return errors.Errorf("client state should be %s; got %s", connectivity.Ready, state) } return nil diff --git a/core/services/relay/evm/mercury/wsrpc/mocks/mocks.go b/core/services/relay/evm/mercury/wsrpc/mocks/mocks.go index 61912c26b0..e202802ea7 100644 --- a/core/services/relay/evm/mercury/wsrpc/mocks/mocks.go +++ b/core/services/relay/evm/mercury/wsrpc/mocks/mocks.go @@ -3,7 +3,7 @@ package mocks import ( "context" - "github.com/smartcontractkit/wsrpc/connectivity" + grpc_connectivity "google.golang.org/grpc/connectivity" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc/pb" ) @@ -29,15 +29,16 @@ func (m *MockWSRPCClient) ServerURL() string { return "mock server url" } func (m *MockWSRPCClient) RawClient() pb.MercuryClient { return nil } type MockConn struct { - State connectivity.State + State grpc_connectivity.State Ready bool Closed bool } -func (m *MockConn) Close() { +func (m *MockConn) Close() error { m.Closed = true + return nil } func (m MockConn) WaitForReady(ctx context.Context) bool { return m.Ready } -func (m MockConn) GetState() connectivity.State { return m.State } +func (m MockConn) GetState() grpc_connectivity.State { return m.State } diff --git a/core/services/relay/evm/mercury_config_provider.go b/core/services/relay/evm/mercury_config_provider.go index bd0749e5ae..443c82ab2c 100644 --- a/core/services/relay/evm/mercury_config_provider.go +++ b/core/services/relay/evm/mercury_config_provider.go @@ -7,6 +7,8 @@ import ( "github.com/ethereum/go-ethereum/common" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" @@ -41,6 +43,6 @@ func newMercuryConfigProvider(ctx context.Context, lggr logger.Logger, chain leg return nil, err } - offchainConfigDigester := mercury.NewOffchainConfigDigester(*relayConfig.FeedID, chain.Config().EVM().ChainID(), aggregatorAddress) + offchainConfigDigester := mercury.NewOffchainConfigDigester(*relayConfig.FeedID, chain.Config().EVM().ChainID(), aggregatorAddress, ocrtypes.ConfigDigestPrefixMercuryV02) return newConfigWatcher(lggr, aggregatorAddress, offchainConfigDigester, cp, chain, relayConfig.FromBlock, opts.New), nil } diff --git a/core/services/relay/evm/mercury_provider.go b/core/services/relay/evm/mercury_provider.go index 48882b701c..85f633e063 100644 --- a/core/services/relay/evm/mercury_provider.go +++ b/core/services/relay/evm/mercury_provider.go @@ -6,17 +6,18 @@ import ( ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" - mercurytypes "github.com/smartcontractkit/chainlink-common/pkg/types/mercury" v1 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v1" v2 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v2" v3 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v3" + v4 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v4" + "github.com/smartcontractkit/chainlink-data-streams/mercury" httypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" evmmercury "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury" ) @@ -24,12 +25,12 @@ var _ commontypes.MercuryProvider = (*mercuryProvider)(nil) type mercuryProvider struct { cp commontypes.ConfigProvider - chainReader commontypes.ContractReader codec commontypes.Codec transmitter evmmercury.Transmitter reportCodecV1 v1.ReportCodec reportCodecV2 v2.ReportCodec reportCodecV3 v3.ReportCodec + reportCodecV4 v4.ReportCodec mercuryChainReader mercurytypes.ChainReader logger logger.Logger ms services.MultiStart @@ -37,23 +38,23 @@ type mercuryProvider struct { func NewMercuryProvider( cp commontypes.ConfigProvider, - chainReader commontypes.ContractReader, codec commontypes.Codec, mercuryChainReader mercurytypes.ChainReader, transmitter evmmercury.Transmitter, reportCodecV1 v1.ReportCodec, reportCodecV2 v2.ReportCodec, reportCodecV3 v3.ReportCodec, + reportCodecV4 v4.ReportCodec, lggr logger.Logger, ) *mercuryProvider { return &mercuryProvider{ cp, - chainReader, codec, transmitter, reportCodecV1, reportCodecV2, reportCodecV3, + reportCodecV4, mercuryChainReader, lggr, services.MultiStart{}, @@ -115,6 +116,10 @@ func (p *mercuryProvider) ReportCodecV3() v3.ReportCodec { return p.reportCodecV3 } +func (p *mercuryProvider) ReportCodecV4() v4.ReportCodec { + return p.reportCodecV4 +} + func (p *mercuryProvider) ContractTransmitter() ocrtypes.ContractTransmitter { return p.transmitter } @@ -123,8 +128,8 @@ func (p *mercuryProvider) MercuryServerFetcher() mercurytypes.ServerFetcher { return p.transmitter } -func (p *mercuryProvider) ChainReader() commontypes.ContractReader { - return p.chainReader +func (p *mercuryProvider) ContractReader() commontypes.ContractReader { + return nil } var _ mercurytypes.ChainReader = (*mercuryChainReader)(nil) diff --git a/core/services/relay/evm/method_binding.go b/core/services/relay/evm/method_binding.go deleted file mode 100644 index 448f1b9fbf..0000000000 --- a/core/services/relay/evm/method_binding.go +++ /dev/null @@ -1,130 +0,0 @@ -package evm - -import ( - "context" - "fmt" - "math/big" - - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/common" - - commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" - "github.com/smartcontractkit/chainlink-common/pkg/types/query" - "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" - evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" - - evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" -) - -type NoContractExistsError struct { - Address common.Address -} - -func (e NoContractExistsError) Error() string { - return fmt.Sprintf("contract does not exist at address: %s", e.Address) -} - -type methodBinding struct { - lggr logger.Logger - ht logpoller.HeadTracker - address common.Address - contractName string - method string - client evmclient.Client - codec commontypes.Codec - bound bool - confirmationsMapping map[primitives.ConfidenceLevel]evmtypes.Confirmations -} - -var _ readBinding = &methodBinding{} - -func (m *methodBinding) SetCodec(codec commontypes.RemoteCodec) { - m.codec = codec -} - -func (m *methodBinding) Bind(ctx context.Context, binding commontypes.BoundContract) error { - addr := common.HexToAddress(binding.Address) - - // check for contract byte code at the latest block and provided address - byteCode, err := m.client.CodeAt(ctx, addr, nil) - if err != nil { - return err - } - - if len(byteCode) == 0 { - return NoContractExistsError{Address: addr} - } - - m.address = addr - m.bound = true - - return nil -} - -func (m *methodBinding) Register(_ context.Context) error { - return nil -} - -func (m *methodBinding) Unregister(_ context.Context) error { - return nil -} - -func (m *methodBinding) GetLatestValue(ctx context.Context, confidenceLevel primitives.ConfidenceLevel, params, returnVal any) error { - if !m.bound { - return fmt.Errorf("%w: method not bound", commontypes.ErrInvalidType) - } - - data, err := m.codec.Encode(ctx, params, WrapItemType(m.contractName, m.method, true)) - if err != nil { - return err - } - - callMsg := ethereum.CallMsg{ - To: &m.address, - From: m.address, - Data: data, - } - - block, err := m.blockNumberFromConfidence(ctx, confidenceLevel) - if err != nil { - return err - } - - bytes, err := m.client.CallContract(ctx, callMsg, block) - if err != nil { - return fmt.Errorf("%w: %w", commontypes.ErrInternal, err) - } - - return m.codec.Decode(ctx, bytes, returnVal, WrapItemType(m.contractName, m.method, false)) -} - -func (m *methodBinding) QueryKey(_ context.Context, _ query.KeyFilter, _ query.LimitAndSort, _ any) ([]commontypes.Sequence, error) { - return nil, nil -} - -func (m *methodBinding) blockNumberFromConfidence(ctx context.Context, confidenceLevel primitives.ConfidenceLevel) (*big.Int, error) { - confirmations, err := confidenceToConfirmations(m.confirmationsMapping, confidenceLevel) - if err != nil { - err = fmt.Errorf("%w for contract: %s, method: %s", err, m.contractName, m.method) - if confidenceLevel == primitives.Unconfirmed { - m.lggr.Errorf("%v, now falling back to default contract call behaviour that calls latest state", err) - return nil, nil - } - return nil, err - } - - _, finalized, err := m.ht.LatestAndFinalizedBlock(ctx) - if err != nil { - return nil, err - } - - if confirmations == evmtypes.Finalized { - return big.NewInt(finalized.Number), nil - } else if confirmations == evmtypes.Unconfirmed { - return nil, nil - } - - return nil, fmt.Errorf("unknown evm confirmations: %v for contract: %s, method: %s", confirmations, m.contractName, m.method) -} diff --git a/core/services/relay/evm/mocks/loop_relay_adapter.go b/core/services/relay/evm/mocks/loop_relay_adapter.go index 50b1dd5f39..76579d4746 100644 --- a/core/services/relay/evm/mocks/loop_relay_adapter.go +++ b/core/services/relay/evm/mocks/loop_relay_adapter.go @@ -221,6 +221,62 @@ func (_c *LoopRelayAdapter_HealthReport_Call) RunAndReturn(run func() map[string return _c } +// LatestHead provides a mock function with given fields: ctx +func (_m *LoopRelayAdapter) LatestHead(ctx context.Context) (types.Head, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for LatestHead") + } + + var r0 types.Head + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (types.Head, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) types.Head); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(types.Head) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// LoopRelayAdapter_LatestHead_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LatestHead' +type LoopRelayAdapter_LatestHead_Call struct { + *mock.Call +} + +// LatestHead is a helper method to define mock.On call +// - ctx context.Context +func (_e *LoopRelayAdapter_Expecter) LatestHead(ctx interface{}) *LoopRelayAdapter_LatestHead_Call { + return &LoopRelayAdapter_LatestHead_Call{Call: _e.mock.On("LatestHead", ctx)} +} + +func (_c *LoopRelayAdapter_LatestHead_Call) Run(run func(ctx context.Context)) *LoopRelayAdapter_LatestHead_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *LoopRelayAdapter_LatestHead_Call) Return(_a0 types.Head, _a1 error) *LoopRelayAdapter_LatestHead_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *LoopRelayAdapter_LatestHead_Call) RunAndReturn(run func(context.Context) (types.Head, error)) *LoopRelayAdapter_LatestHead_Call { + _c.Call.Return(run) + return _c +} + // ListNodeStatuses provides a mock function with given fields: ctx, pageSize, pageToken func (_m *LoopRelayAdapter) ListNodeStatuses(ctx context.Context, pageSize int32, pageToken string) ([]types.NodeStatus, string, int, error) { ret := _m.Called(ctx, pageSize, pageToken) @@ -459,23 +515,23 @@ func (_c *LoopRelayAdapter_NewConfigProvider_Call) RunAndReturn(run func(context } // NewContractReader provides a mock function with given fields: ctx, contractReaderConfig -func (_m *LoopRelayAdapter) NewContractReader(ctx context.Context, contractReaderConfig []byte) (types.ChainReader, error) { +func (_m *LoopRelayAdapter) NewContractReader(ctx context.Context, contractReaderConfig []byte) (types.ContractReader, error) { ret := _m.Called(ctx, contractReaderConfig) if len(ret) == 0 { panic("no return value specified for NewContractReader") } - var r0 types.ChainReader + var r0 types.ContractReader var r1 error - if rf, ok := ret.Get(0).(func(context.Context, []byte) (types.ChainReader, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, []byte) (types.ContractReader, error)); ok { return rf(ctx, contractReaderConfig) } - if rf, ok := ret.Get(0).(func(context.Context, []byte) types.ChainReader); ok { + if rf, ok := ret.Get(0).(func(context.Context, []byte) types.ContractReader); ok { r0 = rf(ctx, contractReaderConfig) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(types.ChainReader) + r0 = ret.Get(0).(types.ContractReader) } } @@ -507,12 +563,12 @@ func (_c *LoopRelayAdapter_NewContractReader_Call) Run(run func(ctx context.Cont return _c } -func (_c *LoopRelayAdapter_NewContractReader_Call) Return(_a0 types.ChainReader, _a1 error) *LoopRelayAdapter_NewContractReader_Call { +func (_c *LoopRelayAdapter_NewContractReader_Call) Return(_a0 types.ContractReader, _a1 error) *LoopRelayAdapter_NewContractReader_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *LoopRelayAdapter_NewContractReader_Call) RunAndReturn(run func(context.Context, []byte) (types.ChainReader, error)) *LoopRelayAdapter_NewContractReader_Call { +func (_c *LoopRelayAdapter_NewContractReader_Call) RunAndReturn(run func(context.Context, []byte) (types.ContractReader, error)) *LoopRelayAdapter_NewContractReader_Call { _c.Call.Return(run) return _c } diff --git a/core/services/relay/evm/ocr2keeper.go b/core/services/relay/evm/ocr2keeper.go index b2d19c1170..c2c65c9603 100644 --- a/core/services/relay/evm/ocr2keeper.go +++ b/core/services/relay/evm/ocr2keeper.go @@ -196,7 +196,7 @@ func (c *ocr2keeperProvider) ContractTransmitter() ocrtypes.ContractTransmitter return c.contractTransmitter } -func (c *ocr2keeperProvider) ChainReader() commontypes.ContractReader { +func (c *ocr2keeperProvider) ContractReader() commontypes.ContractReader { return nil } diff --git a/core/services/relay/evm/ocr3_capability_provider.go b/core/services/relay/evm/ocr3_capability_provider.go index 2080e1df87..ca41466570 100644 --- a/core/services/relay/evm/ocr3_capability_provider.go +++ b/core/services/relay/evm/ocr3_capability_provider.go @@ -1,9 +1,23 @@ package evm import ( + "encoding/hex" + "fmt" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "github.com/smartcontractkit/chainlink-common/pkg/types" + + "math/big" + "strings" + + "github.com/ethereum/go-ethereum/crypto" + + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/ocr3_capability" + "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" ) type ocr3CapabilityProvider struct { @@ -14,3 +28,181 @@ type ocr3CapabilityProvider struct { func (o *ocr3CapabilityProvider) OCR3ContractTransmitter() ocr3types.ContractTransmitter[[]byte] { return o.transmitter } + +var _ LogDecoder = &ocr3CapabilityLogDecoder{} + +type ocr3CapabilityLogDecoder struct { + eventName string + eventSig common.Hash + abi *abi.ABI +} + +// Modified newOCR2AggregatorLogDecoder to use OCR3Capability ABI +func newOCR3CapabilityLogDecoder() (*ocr3CapabilityLogDecoder, error) { + const eventName = "ConfigSet" + abi, err := ocr3_capability.OCR3CapabilityMetaData.GetAbi() + if err != nil { + return nil, err + } + return &ocr3CapabilityLogDecoder{ + eventName: eventName, + eventSig: abi.Events[eventName].ID, + abi: abi, + }, nil +} + +func (d *ocr3CapabilityLogDecoder) Decode(rawLog []byte) (ocrtypes.ContractConfig, error) { + unpacked := new(ocr3_capability.OCR3CapabilityConfigSet) + err := d.abi.UnpackIntoInterface(unpacked, d.eventName, rawLog) + if err != nil { + return ocrtypes.ContractConfig{}, fmt.Errorf("failed to unpack log data: %w", err) + } + + var transmitAccounts []ocrtypes.Account + for _, addr := range unpacked.Transmitters { + transmitAccounts = append(transmitAccounts, ocrtypes.Account(addr.Hex())) + } + var signers []ocrtypes.OnchainPublicKey + allPubKeys := map[string]any{} + for _, pubKey := range unpacked.Signers { + pubKey := pubKey + + // validate uniqueness of each individual key + pubKeys, err := ocrcommon.UnmarshalMultichainPublicKey(pubKey) + if err != nil { + return ocrtypes.ContractConfig{}, err + } + for _, key := range pubKeys { + raw := hex.EncodeToString(key) + _, exists := allPubKeys[raw] + if exists { + return ocrtypes.ContractConfig{}, fmt.Errorf("Duplicate onchain public key: %v", raw) + } + allPubKeys[raw] = struct{}{} + } + + signers = append(signers, pubKey[:]) + } + + return ocrtypes.ContractConfig{ + ConfigDigest: unpacked.ConfigDigest, + ConfigCount: unpacked.ConfigCount, + Signers: signers, + Transmitters: transmitAccounts, + F: unpacked.F, + OnchainConfig: unpacked.OnchainConfig, + OffchainConfigVersion: unpacked.OffchainConfigVersion, + OffchainConfig: unpacked.OffchainConfig, + }, nil +} + +func (d *ocr3CapabilityLogDecoder) EventSig() common.Hash { + return d.eventSig +} + +var _ ocrtypes.OffchainConfigDigester = OCR3CapabilityOffchainConfigDigester{} + +// EVMOffchainConfigDigester forked to not assume signers are 20 byte addresses +type OCR3CapabilityOffchainConfigDigester struct { + ChainID uint64 + ContractAddress common.Address +} + +func (d OCR3CapabilityOffchainConfigDigester) ConfigDigest(cc ocrtypes.ContractConfig) (ocrtypes.ConfigDigest, error) { + signers := [][]byte{} + for _, signer := range cc.Signers { + signers = append(signers, signer) + } + transmitters := []common.Address{} + for i, transmitter := range cc.Transmitters { + if !strings.HasPrefix(string(transmitter), "0x") || len(transmitter) != 42 || !common.IsHexAddress(string(transmitter)) { + return ocrtypes.ConfigDigest{}, fmt.Errorf("%v-th evm transmitter should be a 42 character Ethereum address string, but got '%v'", i, transmitter) + } + a := common.HexToAddress(string(transmitter)) + transmitters = append(transmitters, a) + } + + return ocr3CapabilityConfigDigest( + d.ChainID, + d.ContractAddress, + cc.ConfigCount, + signers, + transmitters, + cc.F, + cc.OnchainConfig, + cc.OffchainConfigVersion, + cc.OffchainConfig, + ), nil +} + +const ConfigDigestPrefixKeystoneOCR3Capability ocrtypes.ConfigDigestPrefix = 0x000e + +func (d OCR3CapabilityOffchainConfigDigester) ConfigDigestPrefix() (ocrtypes.ConfigDigestPrefix, error) { + return ConfigDigestPrefixKeystoneOCR3Capability, nil +} + +func makeOCR3CapabilityConfigDigestArgs() abi.Arguments { + mustNewType := func(t string) abi.Type { + result, err := abi.NewType(t, "", []abi.ArgumentMarshaling{}) + if err != nil { + panic(fmt.Sprintf("Unexpected error during abi.NewType: %s", err)) + } + return result + } + return abi.Arguments([]abi.Argument{ + {Name: "chainId", Type: mustNewType("uint256")}, + {Name: "contractAddress", Type: mustNewType("address")}, + {Name: "configCount", Type: mustNewType("uint64")}, + {Name: "signers", Type: mustNewType("bytes[]")}, + {Name: "transmitters", Type: mustNewType("address[]")}, + {Name: "f", Type: mustNewType("uint8")}, + {Name: "onchainConfig", Type: mustNewType("bytes")}, + {Name: "encodedConfigVersion", Type: mustNewType("uint64")}, + {Name: "encodedConfig", Type: mustNewType("bytes")}, + }) +} + +var ocr3CapabilityConfigDigestArgs = makeOCR3CapabilityConfigDigestArgs() + +func ocr3CapabilityConfigDigest( + chainID uint64, + contractAddress common.Address, + configCount uint64, + oracles [][]byte, + transmitters []common.Address, + f uint8, + onchainConfig []byte, + offchainConfigVersion uint64, + offchainConfig []byte, +) ocrtypes.ConfigDigest { + chainIDBig := new(big.Int) + chainIDBig.SetUint64(chainID) + msg, err := ocr3CapabilityConfigDigestArgs.Pack( + chainIDBig, + contractAddress, + configCount, + oracles, + transmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig, + ) + if err != nil { + // assertion + panic(err) + } + rawHash := crypto.Keccak256(msg) + configDigest := ocrtypes.ConfigDigest{} + if n := copy(configDigest[:], rawHash); n != len(configDigest) { + // assertion + panic("copy too little data") + } + if ConfigDigestPrefixKeystoneOCR3Capability != 0x000e { + // assertion + panic("wrong ConfigDigestPrefix") + } + configDigest[0] = 0 + configDigest[1] = 0x0e + return configDigest +} diff --git a/core/services/relay/evm/plugin_provider.go b/core/services/relay/evm/plugin_provider.go index ffcea48db2..b8ce56ff12 100644 --- a/core/services/relay/evm/plugin_provider.go +++ b/core/services/relay/evm/plugin_provider.go @@ -5,17 +5,16 @@ import ( ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/types" - - "github.com/smartcontractkit/chainlink/v2/core/logger" ) type pluginProvider struct { services.Service - chainReader types.ContractReader + chainReader ChainReaderService codec types.Codec - contractTransmitter ocrtypes.ContractTransmitter + contractTransmitter ContractTransmitter configWatcher *configWatcher lggr logger.Logger ms services.MultiStart @@ -24,9 +23,9 @@ type pluginProvider struct { var _ types.PluginProvider = (*pluginProvider)(nil) func NewPluginProvider( - chainReader types.ContractReader, + chainReader ChainReaderService, codec types.Codec, - contractTransmitter ocrtypes.ContractTransmitter, + contractTransmitter ContractTransmitter, configWatcher *configWatcher, lggr logger.Logger, ) *pluginProvider { @@ -47,6 +46,10 @@ func (p *pluginProvider) Ready() error { return nil } func (p *pluginProvider) HealthReport() map[string]error { hp := map[string]error{p.Name(): p.Ready()} services.CopyHealth(hp, p.configWatcher.HealthReport()) + services.CopyHealth(hp, p.contractTransmitter.HealthReport()) + if p.chainReader != nil { + services.CopyHealth(hp, p.chainReader.HealthReport()) + } return hp } @@ -62,7 +65,7 @@ func (p *pluginProvider) ContractConfigTracker() ocrtypes.ContractConfigTracker return p.configWatcher.configPoller } -func (p *pluginProvider) ChainReader() types.ContractReader { +func (p *pluginProvider) ContractReader() types.ContractReader { return p.chainReader } @@ -71,9 +74,14 @@ func (p *pluginProvider) Codec() types.Codec { } func (p *pluginProvider) Start(ctx context.Context) error { - return p.configWatcher.Start(ctx) + srvcs := []services.StartClose{p.configWatcher, p.contractTransmitter} + if p.chainReader != nil { + srvcs = append(srvcs, p.chainReader) + } + + return p.ms.Start(ctx, srvcs...) } func (p *pluginProvider) Close() error { - return p.configWatcher.Close() + return p.ms.Close() } diff --git a/core/services/relay/evm/batch_caller.go b/core/services/relay/evm/read/batch.go similarity index 94% rename from core/services/relay/evm/batch_caller.go rename to core/services/relay/evm/read/batch.go index 3ee4525827..1d72e22296 100644 --- a/core/services/relay/evm/batch_caller.go +++ b/core/services/relay/evm/read/batch.go @@ -1,4 +1,4 @@ -package evm +package read import ( "context" @@ -11,9 +11,11 @@ import ( "github.com/pkg/errors" "golang.org/x/sync/errgroup" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) var errEmptyOutput = errors.New("rpc call output is empty (make sure that the contract method exists and rpc is healthy)") @@ -37,6 +39,7 @@ const ( type BatchResult map[string]ContractResults type ContractResults []MethodCallResult type MethodCallResult struct { + Address string MethodName string ReturnValue any Err error @@ -132,7 +135,7 @@ func (c *defaultEvmBatchCaller) batchCall(ctx context.Context, blockNumber uint6 packedOutputs := make([]string, len(batchCall)) rpcBatchCalls := make([]rpc.BatchElem, len(batchCall)) for i, call := range batchCall { - data, err := c.codec.Encode(ctx, call.Params, WrapItemType(call.ContractName, call.MethodName, true)) + data, err := c.codec.Encode(ctx, call.Params, codec.WrapItemType(call.ContractName, call.MethodName, true)) if err != nil { return nil, err } @@ -148,7 +151,7 @@ func (c *defaultEvmBatchCaller) batchCall(ctx context.Context, blockNumber uint6 map[string]interface{}{ "from": common.Address{}, "to": call.ContractAddress, - "data": data, + "data": hexutil.Bytes(data), }, blockNumStr, }, @@ -163,6 +166,7 @@ func (c *defaultEvmBatchCaller) batchCall(ctx context.Context, blockNumber uint6 results := make([]dataAndErr, len(batchCall)) for i, call := range batchCall { results[i] = dataAndErr{ + address: call.ContractAddress.Hex(), contractName: call.ContractName, methodName: call.MethodName, returnVal: call.ReturnVal, @@ -184,7 +188,7 @@ func (c *defaultEvmBatchCaller) batchCall(ctx context.Context, blockNumber uint6 return nil, fmt.Errorf("decode result %s: packedOutputs %s: %w", call, packedOutputs[i], err) } - if err = c.codec.Decode(ctx, b, call.ReturnVal, WrapItemType(call.ContractName, call.MethodName, false)); err != nil { + if err = c.codec.Decode(ctx, b, call.ReturnVal, codec.WrapItemType(call.ContractName, call.MethodName, false)); err != nil { if len(b) == 0 { results[i].err = fmt.Errorf("unpack result %s: %s: %w", call, err.Error(), errEmptyOutput) } else { @@ -225,6 +229,7 @@ func (c *defaultEvmBatchCaller) batchCallDynamicLimitRetries(ctx context.Context } type dataAndErr struct { + address string contractName, methodName string returnVal any err error @@ -295,6 +300,7 @@ func convertToBatchResult(data []dataAndErr) BatchResult { batchResult := make(BatchResult) for _, d := range data { methodCall := MethodCallResult{ + Address: d.address, MethodName: d.methodName, ReturnValue: d.returnVal, Err: d.err, diff --git a/core/services/relay/evm/batch_caller_test.go b/core/services/relay/evm/read/batch_caller_test.go similarity index 87% rename from core/services/relay/evm/batch_caller_test.go rename to core/services/relay/evm/read/batch_caller_test.go index 995e47618c..4f50bdc691 100644 --- a/core/services/relay/evm/batch_caller_test.go +++ b/core/services/relay/evm/read/batch_caller_test.go @@ -1,12 +1,12 @@ -package evm_test +package read_test import ( - "encoding/hex" "fmt" "math/big" "testing" "github.com/cometbft/cometbft/libs/rand" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/rpc" "github.com/pkg/errors" "github.com/stretchr/testify/assert" @@ -16,8 +16,9 @@ import ( chainmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mocks" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/read" evmtypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" ) @@ -31,8 +32,8 @@ func TestDefaultEvmBatchCaller_BatchCallDynamicLimit(t *testing.T) { }{ { name: "defaults", - maxBatchSize: evm.DefaultRpcBatchSizeLimit, - backOffMultiplier: evm.DefaultRpcBatchBackOffMultiplier, + maxBatchSize: read.DefaultRpcBatchSizeLimit, + backOffMultiplier: read.DefaultRpcBatchBackOffMultiplier, numCalls: 200, expectedBatchSizesOnEachRetry: []int{100, 20, 4, 1}, }, @@ -92,12 +93,12 @@ func TestDefaultEvmBatchCaller_BatchCallDynamicLimit(t *testing.T) { batchSizes = append(batchSizes, len(evmCalls)) }).Return(errors.New("some error")) - calls := make(evm.BatchCall, tc.numCalls) + calls := make(read.BatchCall, tc.numCalls) for i := range calls { - calls[i] = evm.Call{} + calls[i] = read.Call{} } - bc := evm.NewDynamicLimitedBatchCaller(logger.TestLogger(t), mockCodec, ec, tc.maxBatchSize, tc.backOffMultiplier, 1) + bc := read.NewDynamicLimitedBatchCaller(logger.TestLogger(t), mockCodec, ec, tc.maxBatchSize, tc.backOffMultiplier, 1) _, _ = bc.BatchCall(testutils.Context(t), 123, calls) assert.Equal(t, tc.expectedBatchSizesOnEachRetry, batchSizes) }) @@ -131,7 +132,7 @@ func TestDefaultEvmBatchCaller_batchCallLimit(t *testing.T) { for i, tc := range testCases { t.Run(fmt.Sprintf("%v", tc), func(t *testing.T) { ec := chainmocks.NewClient(t) - calls := make(evm.BatchCall, tc.numCalls) + calls := make(read.BatchCall, tc.numCalls) for j := range calls { contractName := fmt.Sprintf("testCase_%d", i) methodName := fmt.Sprintf("method_%d", j) @@ -140,7 +141,7 @@ func TestDefaultEvmBatchCaller_batchCallLimit(t *testing.T) { params := MethodParam{A: uint64(j)} var returnVal MethodReturn - calls[j] = evm.Call{ + calls[j] = read.Call{ ContractName: contractName, MethodName: methodName, Params: ¶ms, @@ -152,18 +153,16 @@ func TestDefaultEvmBatchCaller_batchCallLimit(t *testing.T) { Run(func(args mock.Arguments) { evmCalls := args.Get(1).([]rpc.BatchElem) for i := range evmCalls { - arg := evmCalls[i].Args[0].(map[string]interface{})["data"].([]uint8) - bytes, err := hex.DecodeString(fmt.Sprintf("%x", arg)) - require.NoError(t, err) + arg := evmCalls[i].Args[0].(map[string]interface{})["data"].(hexutil.Bytes) str, isOk := evmCalls[i].Result.(*string) require.True(t, isOk) - *str = fmt.Sprintf("0x%064x", new(big.Int).SetBytes(bytes[24:]).Uint64()) + *str = fmt.Sprintf("0x%064x", new(big.Int).SetBytes(arg[24:]).Uint64()) } }).Return(nil) - testCodec, err := evm.NewCodec(codecConfig) + testCodec, err := codec.NewCodec(codecConfig) require.NoError(t, err) - bc := evm.NewDynamicLimitedBatchCaller(logger.TestLogger(t), testCodec, ec, tc.batchSize, 99999, tc.parallelRpcCallsLimit) + bc := read.NewDynamicLimitedBatchCaller(logger.TestLogger(t), testCodec, ec, tc.batchSize, 99999, tc.parallelRpcCallsLimit) // make the call and make sure the results are there results, err := bc.BatchCall(ctx, 0, calls) diff --git a/core/services/relay/evm/read/bindings.go b/core/services/relay/evm/read/bindings.go new file mode 100644 index 0000000000..55f237af0e --- /dev/null +++ b/core/services/relay/evm/read/bindings.go @@ -0,0 +1,296 @@ +package read + +import ( + "context" + "errors" + "fmt" + "strings" + "sync" + + "github.com/ethereum/go-ethereum/common" + + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types/query" + "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" +) + +type Reader interface { + BatchCall(address common.Address, params, retVal any) (Call, error) + GetLatestValue(ctx context.Context, addr common.Address, confidence primitives.ConfidenceLevel, params, returnVal any) error + QueryKey(context.Context, common.Address, query.KeyFilter, query.LimitAndSort, any) ([]commontypes.Sequence, error) + + Bind(context.Context, ...common.Address) error + Unbind(context.Context, ...common.Address) error + SetCodec(commontypes.RemoteCodec) + + Register(context.Context) error + Unregister(context.Context) error +} + +type BindingsRegistry struct { + // dependencies + batch BatchCaller + + // private state + mu sync.RWMutex + contractBindings map[string]*contractBinding + contractLookup *lookup +} + +func NewBindingsRegistry() *BindingsRegistry { + return &BindingsRegistry{ + contractBindings: make(map[string]*contractBinding), + contractLookup: newLookup(), + } +} + +func (b *BindingsRegistry) SetBatchCaller(caller BatchCaller) { + b.mu.Lock() + defer b.mu.Unlock() + + b.batch = caller +} + +func (b *BindingsRegistry) HasContractBinding(contractName string) bool { + b.mu.RLock() + defer b.mu.RUnlock() + + _, ok := b.contractBindings[contractName] + + return ok +} + +// GetReader should only be called after Chain Reader is started. +func (b *BindingsRegistry) GetReader(readName string) (Reader, string, error) { + b.mu.RLock() + defer b.mu.RUnlock() + + values, ok := b.contractLookup.getContractForReadName(readName) + if !ok { + return nil, "", fmt.Errorf("%w: no reader for read name %s", commontypes.ErrInvalidType, readName) + } + + cb, cbExists := b.contractBindings[values.contract] + if !cbExists { + return nil, "", fmt.Errorf("%w: no contract named %s", commontypes.ErrInvalidType, values.contract) + } + + binding, rbExists := cb.GetReaderNamed(values.readName) + if !rbExists { + return nil, "", fmt.Errorf("%w: no reader named %s in contract %s", commontypes.ErrInvalidType, values.readName, values.contract) + } + + return binding, values.address, nil +} + +func (b *BindingsRegistry) AddReader(contractName, readName string, rdr Reader) error { + b.mu.Lock() + defer b.mu.Unlock() + + switch v := rdr.(type) { + case *EventBinding: + // unwrap codec type naming for event data words and topics to be used by lookup for Querying by Value Comparators + // For e.g. "params.contractName.eventName.IndexedTopic" -> "eventName.IndexedTopic" + // or "params.contractName.eventName.someFieldInData" -> "eventName.someFieldInData" + for name := range v.eventTypes { + split := strings.Split(name, ".") + if len(split) < 3 || split[1] != contractName { + return fmt.Errorf("invalid event type name %s", name) + } + b.contractLookup.addReadNameForContract(contractName, strings.Join(split[2:], ".")) + } + } + + b.contractLookup.addReadNameForContract(contractName, readName) + + cb, cbExists := b.contractBindings[contractName] + if !cbExists { + cb = newContractBinding(contractName) + b.contractBindings[contractName] = cb + } + + cb.AddReaderNamed(readName, rdr) + return nil +} + +// Bind binds contract addresses to contract bindings and read bindings. +// Bind also registers the common contract polling filter and eventBindings polling filters. +func (b *BindingsRegistry) Bind(ctx context.Context, reg Registrar, bindings []commontypes.BoundContract) error { + b.mu.RLock() + defer b.mu.RUnlock() + + for _, binding := range bindings { + contract, exists := b.contractBindings[binding.Name] + if !exists { + return fmt.Errorf("%w: no contract named %s", commontypes.ErrInvalidConfig, binding.Name) + } + + b.contractLookup.bindAddressForContract(binding.Name, binding.Address) + + if err := errors.Join( + contract.Bind(ctx, reg, common.HexToAddress(binding.Address)), + contract.BindReaders(ctx, common.HexToAddress(binding.Address)), + ); err != nil { + return err + } + } + + return nil +} + +func (b *BindingsRegistry) BatchGetLatestValues(ctx context.Context, request commontypes.BatchGetLatestValuesRequest) (commontypes.BatchGetLatestValuesResult, error) { + b.mu.RLock() + defer b.mu.RUnlock() + + var batchCall BatchCall + + for binding, contractBatch := range request { + cb := b.contractBindings[binding.Name] + + for i := range contractBatch { + req := contractBatch[i] + + values, ok := b.contractLookup.getContractForReadName(binding.ReadIdentifier(req.ReadName)) + if !ok { + return nil, fmt.Errorf("%w: no method for read name %s", commontypes.ErrInvalidType, binding.ReadIdentifier(req.ReadName)) + } + + rdr, exists := cb.GetReaderNamed(values.readName) + if !exists { + return nil, fmt.Errorf("%w: no contract read binding for %s", commontypes.ErrInvalidType, values.readName) + } + + call, err := rdr.BatchCall(common.HexToAddress(values.address), req.Params, req.ReturnVal) + if err != nil { + return nil, err + } + + batchCall = append(batchCall, call) + } + } + + results, err := b.batch.BatchCall(ctx, 0, batchCall) + if err != nil { + return nil, err + } + + // reconstruct results from batchCall and filteredLogs into common type while maintaining order from request. + batchGetLatestValuesResults := make(commontypes.BatchGetLatestValuesResult) + for contractName, contractResult := range results { + for _, methodResult := range contractResult { + binding := commontypes.BoundContract{ + Name: contractName, + Address: methodResult.Address, + } + + brr := commontypes.BatchReadResult{ + ReadName: methodResult.MethodName, + } + + brr.SetResult(methodResult.ReturnValue, methodResult.Err) + + if _, ok := batchGetLatestValuesResults[binding]; !ok { + batchGetLatestValuesResults[binding] = make(commontypes.ContractBatchResults, 0) + } + + batchGetLatestValuesResults[binding] = append(batchGetLatestValuesResults[binding], brr) + } + } + + return batchGetLatestValuesResults, err +} + +// Unbind unbinds contract addresses to contract bindings and read bindings. +func (b *BindingsRegistry) Unbind(ctx context.Context, reg Registrar, bindings []commontypes.BoundContract) error { + b.mu.RLock() + defer b.mu.RUnlock() + + for _, binding := range bindings { + contract, exists := b.contractBindings[binding.Name] + if !exists { + return fmt.Errorf("%w: no contract named %s", commontypes.ErrInvalidConfig, binding.Name) + } + + b.contractLookup.unbindAddressForContract(binding.Name, binding.Address) + + if err := errors.Join( + contract.Unbind(ctx, reg, common.HexToAddress(binding.Address)), + contract.UnbindReaders(ctx, common.HexToAddress(binding.Address)), + ); err != nil { + return err + } + } + + return nil +} + +func (b *BindingsRegistry) RegisterAll(ctx context.Context, reg Registrar) error { + b.mu.RLock() + defer b.mu.RUnlock() + + for _, cb := range b.contractBindings { + if err := errors.Join(cb.RegisterReaders(ctx), cb.Register(ctx, reg)); err != nil { + return err + } + } + + return nil +} + +func (b *BindingsRegistry) UnregisterAll(ctx context.Context, reg Registrar) error { + b.mu.RLock() + defer b.mu.RUnlock() + + for _, cb := range b.contractBindings { + if err := errors.Join(cb.UnregisterReaders(ctx), cb.Unregister(ctx, reg)); err != nil { + return err + } + } + + return nil +} + +func (b *BindingsRegistry) SetCodecAll(codec commontypes.RemoteCodec) { + b.mu.RLock() + defer b.mu.RUnlock() + + for _, cb := range b.contractBindings { + cb.SetCodecAll(codec) + } +} + +func (b *BindingsRegistry) SetFilter(name string, filter logpoller.Filter) error { + b.mu.RLock() + defer b.mu.RUnlock() + + contract, ok := b.contractBindings[name] + if !ok { + return fmt.Errorf("%w: no contract binding for %s", commontypes.ErrInvalidConfig, name) + } + + contract.SetFilter(filter) + + return nil +} + +func (b *BindingsRegistry) ReadTypeIdentifier(readName string, forEncoding bool) string { + values, ok := b.contractLookup.getContractForReadName(readName) + if !ok { + return "" + } + + return codec.WrapItemType(values.contract, values.readName, forEncoding) +} + +// confidenceToConfirmations matches predefined chain agnostic confidence levels to predefined EVM finality. +func confidenceToConfirmations(confirmationsMapping map[primitives.ConfidenceLevel]evmtypes.Confirmations, confidenceLevel primitives.ConfidenceLevel) (evmtypes.Confirmations, error) { + confirmations, exists := confirmationsMapping[confidenceLevel] + if !exists { + return 0, fmt.Errorf("missing mapping for confidence level: %s", confidenceLevel) + } + return confirmations, nil +} diff --git a/core/services/relay/evm/read/bindings_test.go b/core/services/relay/evm/read/bindings_test.go new file mode 100644 index 0000000000..d9cfa91a98 --- /dev/null +++ b/core/services/relay/evm/read/bindings_test.go @@ -0,0 +1,112 @@ +package read_test + +import ( + "context" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/read" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/read/mocks" +) + +var ( + contractName1 = "Contract1" + methodName1 = "Method1" + methodName2 = "Method2" + eventName0 = "EventName0" + filterWithSigs = logpoller.Filter{ + Name: eventName0, + EventSigs: []common.Hash{common.HexToHash("0x25"), common.HexToHash("0x29")}, + } +) + +func TestBindingsRegistry(t *testing.T) { + t.Parallel() + + t.Run("readers are addable, bindable, and retrievable", func(t *testing.T) { + t.Parallel() + + mBatch := new(mocks.BatchCaller) + mRdr := new(mocks.Reader) + mReg := new(mocks.Registrar) + + mRdr.EXPECT().Bind(mock.Anything, mock.Anything).Return(nil) + + named := read.NewBindingsRegistry() + named.SetBatchCaller(mBatch) + + require.NoError(t, named.AddReader(contractName1, methodName1, mRdr)) + + bindings := []commontypes.BoundContract{{Address: "0x24", Name: contractName1}} + _ = named.Bind(context.Background(), mReg, bindings) + + rdr, _, err := named.GetReader(bindings[0].ReadIdentifier(methodName1)) + + require.NoError(t, err) + require.NotNil(t, rdr) + }) + + t.Run("register all before bind", func(t *testing.T) { + t.Parallel() + + mBatch := new(mocks.BatchCaller) + mRdr0 := new(mocks.Reader) + mRdr1 := new(mocks.Reader) + mReg := new(mocks.Registrar) + + named := read.NewBindingsRegistry() + named.SetBatchCaller(mBatch) + + // register is called once through RegisterAll and again in Bind + mRdr0.EXPECT().Register(mock.Anything).Return(nil) + mRdr1.EXPECT().Register(mock.Anything).Return(nil) + + mRdr0.EXPECT().Bind(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) + mRdr1.EXPECT().Bind(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) + + mReg.EXPECT().HasFilter(mock.Anything).Return(false) + mReg.EXPECT().RegisterFilter(mock.Anything, mock.Anything).Return(nil) + mRdr0.EXPECT().GetLatestValue(mock.Anything, common.HexToAddress("0x25"), mock.Anything, mock.Anything, mock.Anything).Return(nil) + mRdr0.EXPECT().GetLatestValue(mock.Anything, common.HexToAddress("0x24"), mock.Anything, mock.Anything, mock.Anything).Return(nil) + mRdr1.EXPECT().GetLatestValue(mock.Anything, common.HexToAddress("0x26"), mock.Anything, mock.Anything, mock.Anything).Return(nil) + + // part of the init phase of chain reader + require.NoError(t, named.AddReader(contractName1, methodName1, mRdr0)) + require.NoError(t, named.AddReader(contractName1, methodName2, mRdr1)) + _ = named.SetFilter(contractName1, filterWithSigs) + + // run within the start phase of chain reader + require.NoError(t, named.RegisterAll(context.Background(), mReg)) + + bindings := []commontypes.BoundContract{ + {Address: "0x24", Name: contractName1}, + {Address: "0x25", Name: contractName1}, + {Address: "0x26", Name: contractName1}, + } + + // calling bind will also call register for each reader + _ = named.Bind(context.Background(), mReg, bindings) + + rdr1, _, err := named.GetReader(bindings[0].ReadIdentifier(methodName1)) + require.NoError(t, err) + + rdr2, _, err := named.GetReader(bindings[0].ReadIdentifier(methodName2)) + require.NoError(t, err) + + require.NoError(t, rdr1.GetLatestValue(context.Background(), common.HexToAddress("0x25"), primitives.Finalized, nil, nil)) + require.NoError(t, rdr1.GetLatestValue(context.Background(), common.HexToAddress("0x24"), primitives.Finalized, nil, nil)) + require.NoError(t, rdr2.GetLatestValue(context.Background(), common.HexToAddress("0x26"), primitives.Finalized, nil, nil)) + + mBatch.AssertExpectations(t) + mRdr0.AssertExpectations(t) + mRdr1.AssertExpectations(t) + mReg.AssertExpectations(t) + }) +} diff --git a/core/services/relay/evm/read/contract.go b/core/services/relay/evm/read/contract.go new file mode 100644 index 0000000000..1b47c9ee87 --- /dev/null +++ b/core/services/relay/evm/read/contract.go @@ -0,0 +1,243 @@ +package read + +import ( + "context" + "errors" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/google/uuid" + + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" +) + +// contractBinding stores read bindings and manages the common contract event filter. +type contractBinding struct { + // filterRegistrar is used to manage polling filter registration for the common contract filter. + // The common contract filter should be used by events that share filtering args. + registrar *syncedFilter + + // registered is used to determine if Register was called during Chain Reader service Start. + // This is done to avoid calling Register while the service is not running because log poller is most likely also not running. + registerCalled bool + + // internal properties + name string + readers map[string]Reader // key is read name method, event or event keys used for queryKey. + bound map[common.Address]bool // bound determines if address is set to the contract binding. + mu sync.RWMutex +} + +func newContractBinding(name string) *contractBinding { + return &contractBinding{ + name: name, + readers: make(map[string]Reader), + bound: make(map[common.Address]bool), + registrar: newSyncedFilter(), + } +} + +// GetReaderNamed returns a reader for the provided contract name. This method is thread safe. +func (cb *contractBinding) GetReaderNamed(name string) (Reader, bool) { + cb.mu.RLock() + defer cb.mu.RUnlock() + + binding, exists := cb.readers[name] + + return binding, exists +} + +// AddReaderNamed adds a new reader to the contract bindings for the provided contract name. This +// method is thread safe. +func (cb *contractBinding) AddReaderNamed(name string, rdr Reader) { + cb.mu.Lock() + defer cb.mu.Unlock() + + cb.readers[name] = rdr +} + +// Bind binds contract addresses to contract binding and registers the common contract polling filter. +func (cb *contractBinding) Bind(ctx context.Context, registrar Registrar, bindings ...common.Address) error { + if cb.isBound() { + if err := cb.Unregister(ctx, registrar); err != nil { + return err + } + } + + for _, binding := range bindings { + if cb.bindingExists(binding) { + continue + } + + cb.registrar.SetName(logpoller.FilterName(cb.name + "." + uuid.NewString())) + cb.registrar.AddAddress(binding) + cb.addBinding(binding) + } + + // registerCalled during ChainReader start + if cb.registered() { + return cb.Register(ctx, registrar) + } + + return nil +} + +func (cb *contractBinding) BindReaders(ctx context.Context, addresses ...common.Address) error { + cb.mu.RLock() + defer cb.mu.RUnlock() + + var err error + + for _, reader := range cb.readers { + err = errors.Join(err, reader.Bind(ctx, addresses...)) + } + + return err +} + +// Unbind unbinds contract addresses from contract binding and unregisters the common contract polling filter. +func (cb *contractBinding) Unbind(ctx context.Context, registrar Registrar, bindings ...common.Address) error { + for _, binding := range bindings { + if !cb.bindingExists(binding) { + continue + } + + cb.registrar.RemoveAddress(binding) + cb.removeBinding(binding) + } + + // we are changing contract address reference, so we need to unregister old filter or re-register existing filter + if cb.registrar.Count() == 0 { + cb.registrar.SetName("") + + return cb.Unregister(ctx, registrar) + } else if cb.registered() { + return cb.Register(ctx, registrar) + } + + return nil +} + +func (cb *contractBinding) UnbindReaders(ctx context.Context, addresses ...common.Address) error { + cb.mu.RLock() + defer cb.mu.RUnlock() + + var err error + + for _, reader := range cb.readers { + err = errors.Join(reader.Unbind(ctx, addresses...)) + } + + return err +} + +func (cb *contractBinding) SetCodecAll(codec commontypes.RemoteCodec) { + cb.mu.RLock() + defer cb.mu.RUnlock() + + for _, binding := range cb.readers { + binding.SetCodec(codec) + } +} + +// Register registers the common contract filter. +func (cb *contractBinding) Register(ctx context.Context, registrar Registrar) error { + cb.setRegistered() + + if !cb.isBound() { + return nil + } + + if cb.registrar.HasEventSigs() { + if err := cb.registrar.Register(ctx, registrar); err != nil { + return err + } + } + + return nil +} + +func (cb *contractBinding) RegisterReaders(ctx context.Context) error { + cb.mu.RLock() + defer cb.mu.RUnlock() + + for _, binding := range cb.readers { + if err := binding.Register(ctx); err != nil { + return err + } + } + + return nil +} + +// Unregister unregisters the common contract filter. +func (cb *contractBinding) Unregister(ctx context.Context, registrar Registrar) error { + if !cb.isBound() { + return nil + } + + return cb.registrar.Unregister(ctx, registrar) +} + +func (cb *contractBinding) UnregisterReaders(ctx context.Context) error { + cb.mu.RLock() + defer cb.mu.RUnlock() + + for _, binding := range cb.readers { + if err := binding.Unregister(ctx); err != nil { + return err + } + } + + return nil +} + +func (cb *contractBinding) SetFilter(filter logpoller.Filter) { + cb.registrar.SetFilter(filter) +} + +func (cb *contractBinding) isBound() bool { + cb.mu.RLock() + defer cb.mu.RUnlock() + + return len(cb.bound) > 0 +} + +func (cb *contractBinding) bindingExists(binding common.Address) bool { + cb.mu.RLock() + defer cb.mu.RUnlock() + + bound, exists := cb.bound[binding] + + return exists && bound +} + +func (cb *contractBinding) addBinding(binding common.Address) { + cb.mu.Lock() + defer cb.mu.Unlock() + + cb.bound[binding] = true +} + +func (cb *contractBinding) removeBinding(binding common.Address) { + cb.mu.Lock() + defer cb.mu.Unlock() + + delete(cb.bound, binding) +} + +func (cb *contractBinding) registered() bool { + cb.mu.RLock() + defer cb.mu.RUnlock() + + return cb.registerCalled +} + +func (cb *contractBinding) setRegistered() { + cb.mu.Lock() + defer cb.mu.Unlock() + + cb.registerCalled = true +} diff --git a/core/services/relay/evm/read/event.go b/core/services/relay/evm/read/event.go new file mode 100644 index 0000000000..03bee7472b --- /dev/null +++ b/core/services/relay/evm/read/event.go @@ -0,0 +1,706 @@ +package read + +import ( + "context" + "fmt" + "reflect" + "strings" + "sync" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/google/uuid" + + commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types/query" + "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" +) + +type EventBinding struct { + // read-only properties + contractName string + eventName string + hash common.Hash + // eventTypes has all the types for GetLatestValue unHashed indexed topics params and for QueryKey data words or unHashed indexed topics value comparators. + eventTypes map[string]types.CodecEntry + // indexedTopicsTypes has type info about hashed indexed topics. + indexedTopicsTypes types.CodecEntry + // eventModifiers only has a modifier for indexed topic filtering, but data words can also be added if needed. + eventModifiers map[string]commoncodec.Modifier + + // dependencies + // filterRegistrar in EventBinding is to be used as an override for lp filter defined in the contract binding. + // If filterRegisterer is nil, this event should be registered with the lp filter defined in the contract binding. + registrar *syncedFilter + registerCalled bool + lp logpoller.LogPoller + + // internal properties / state + codec commontypes.RemoteCodec + bound map[common.Address]bool // bound determines if address is set to the contract binding. + mu sync.RWMutex + // topics map a generic topic name (key) to topic data + topics map[string]TopicDetail + // dataWords key is the generic dataWordNamb. + dataWords map[string]DataWordDetail + confirmationsMapping map[primitives.ConfidenceLevel]evmtypes.Confirmations +} + +func NewEventBinding( + contract, event string, + poller logpoller.LogPoller, + hash common.Hash, + indexedTopicsTypes types.CodecEntry, + confirmations map[primitives.ConfidenceLevel]evmtypes.Confirmations, +) *EventBinding { + return &EventBinding{ + contractName: contract, + eventName: event, + lp: poller, + hash: hash, + indexedTopicsTypes: indexedTopicsTypes, + confirmationsMapping: confirmations, + topics: make(map[string]TopicDetail), + dataWords: make(map[string]DataWordDetail), + bound: make(map[common.Address]bool), + } +} + +type TopicDetail struct { + abi.Argument + Index uint64 +} + +// DataWordDetail contains all the information about a single evm Data word. +// For b.g. first evm data word(32bytes) of USDC log event is uint256 var called valub. +type DataWordDetail struct { + Index uint8 + abi.Argument +} + +var _ Reader = &EventBinding{} + +func (b *EventBinding) SetCodec(codec commontypes.RemoteCodec) { + b.mu.Lock() + defer b.mu.Unlock() + + b.codec = codec +} + +func (b *EventBinding) SetFilter(filter logpoller.Filter) { + b.mu.Lock() + defer b.mu.Unlock() + + b.registrar = newSyncedFilter() + b.registrar.SetFilter(filter) +} + +func (b *EventBinding) SetCodecTypesAndModifiers(types map[string]types.CodecEntry, modifiers map[string]commoncodec.Modifier) { + b.mu.Lock() + defer b.mu.Unlock() + + b.eventTypes = types + b.eventModifiers = modifiers +} + +func (b *EventBinding) SetDataWordsDetails(dwDetail map[string]DataWordDetail) { + b.mu.Lock() + defer b.mu.Unlock() + + b.dataWords = dwDetail +} + +func (b *EventBinding) SetTopicDetails(topicDetails map[string]TopicDetail) { + b.mu.Lock() + defer b.mu.Unlock() + + b.topics = topicDetails +} + +func (b *EventBinding) GetDataWords() map[string]DataWordDetail { + b.mu.RLock() + defer b.mu.RUnlock() + + return b.dataWords +} + +func (b *EventBinding) Bind(ctx context.Context, bindings ...common.Address) error { + if b.hasBindings() { + // we are changing contract address reference, so we need to unregister old filter if it exists + if err := b.Unregister(ctx); err != nil { + return err + } + } + + // filterRegisterer isn't required here because the event can also be polled for by the contractBinding common filter. + if b.registrar != nil { + b.registrar.SetName(logpoller.FilterName(fmt.Sprintf("%s.%s.%s", b.contractName, b.eventName, uuid.NewString()))) + } + + for _, binding := range bindings { + if b.isBound(binding) { + continue + } + + if b.registrar != nil { + b.registrar.AddAddress(binding) + } + + b.addBinding(binding) + } + + if b.registered() { + return b.Register(ctx) + } + + return nil +} + +func (b *EventBinding) Unbind(ctx context.Context, bindings ...common.Address) error { + for _, binding := range bindings { + if !b.isBound(binding) { + continue + } + + if b.registrar != nil { + b.registrar.RemoveAddress(binding) + } + + b.removeBinding(binding) + } + + if err := b.Unregister(ctx); err != nil { + return err + } + + // we are changing contract address reference, so we need to unregister old filter or re-register existing filter + if b.registrar != nil { + if b.registrar.Count() == 0 { + b.registrar.SetName("") + + return b.Unregister(ctx) + } else if b.registered() { + return b.Register(ctx) + } + } + + return nil +} + +func (b *EventBinding) Register(ctx context.Context) error { + b.mu.Lock() + defer b.mu.Unlock() + + if b.registrar == nil { + return nil + } + + b.registerCalled = true + + // can't be true before filters params are set so there is no race with a bad filter outcome + if len(b.bound) == 0 { + return nil + } + + return b.registrar.Register(ctx, b.lp) +} + +func (b *EventBinding) Unregister(ctx context.Context) error { + b.mu.Lock() + defer b.mu.Unlock() + + if b.registrar == nil { + return nil + } + + if len(b.bound) == 0 { + return nil + } + + return b.registrar.Unregister(ctx, b.lp) +} + +func (b *EventBinding) BatchCall(_ common.Address, _, _ any) (Call, error) { + return Call{}, fmt.Errorf("%w: events are not yet supported in batch get latest values", commontypes.ErrInvalidType) +} + +func (b *EventBinding) GetLatestValue(ctx context.Context, address common.Address, confidenceLevel primitives.ConfidenceLevel, params, into any) error { + if err := b.validateBound(address); err != nil { + return err + } + + confs, err := confidenceToConfirmations(b.confirmationsMapping, confidenceLevel) + if err != nil { + return err + } + + topicTypeID := codec.WrapItemType(b.contractName, b.eventName, true) + + onChainTypedVal, err := b.toNativeOnChainType(topicTypeID, params) + if err != nil { + return fmt.Errorf("failed to convert params to native on chain types: %w", err) + } + + filterTopics, err := b.extractFilterTopics(topicTypeID, onChainTypedVal) + if err != nil { + return err + } + + var log *logpoller.Log + if len(filterTopics) != 0 { + var hashedTopics []common.Hash + hashedTopics, err = b.hashTopics(topicTypeID, filterTopics) + if err != nil { + return err + } + + if log, err = b.getLatestLog(ctx, address, confs, hashedTopics); err != nil { + return err + } + } else { + if log, err = b.lp.LatestLogByEventSigWithConfs(ctx, b.hash, address, confs); err != nil { + return wrapInternalErr(err) + } + } + + return b.decodeLog(ctx, log, into) +} + +func (b *EventBinding) QueryKey(ctx context.Context, address common.Address, filter query.KeyFilter, limitAndSort query.LimitAndSort, sequenceDataType any) ([]commontypes.Sequence, error) { + if err := b.validateBound(address); err != nil { + return nil, err + } + + remapped, err := b.remap(filter) + if err != nil { + return nil, err + } + + // filter should always use the address and event sig + defaultExpressions := []query.Expression{ + logpoller.NewAddressFilter(address), + logpoller.NewEventSigFilter(b.hash), + } + remapped.Expressions = append(defaultExpressions, remapped.Expressions...) + + logs, err := b.lp.FilteredLogs(ctx, remapped.Expressions, limitAndSort, b.contractName+"-"+address.String()+"-"+b.eventName) + if err != nil { + return nil, err + } + + // no need to return an error. an empty list is fine + if len(logs) == 0 { + return []commontypes.Sequence{}, nil + } + + return b.decodeLogsIntoSequences(ctx, logs, sequenceDataType) +} + +func (b *EventBinding) getLatestLog(ctx context.Context, address common.Address, confs evmtypes.Confirmations, hashedTopics []common.Hash) (*logpoller.Log, error) { + // Create limiter and filter for the query. + limiter := query.NewLimitAndSort(query.CountLimit(1), query.NewSortBySequence(query.Desc)) + topicFilters, err := createTopicFilters(hashedTopics) + if err != nil { + return nil, err + } + + filter, err := logpoller.Where( + topicFilters, + logpoller.NewAddressFilter(address), + logpoller.NewEventSigFilter(b.hash), + logpoller.NewConfirmationsFilter(confs), + ) + if err != nil { + return nil, wrapInternalErr(err) + } + + // Gets the latest log that matches the filter and limiter. + logs, err := b.lp.FilteredLogs(ctx, filter, limiter, b.contractName+"-"+address.String()+"-"+b.eventName) + if err != nil { + return nil, wrapInternalErr(err) + } + + if len(logs) == 0 { + return nil, fmt.Errorf("%w: no events found", commontypes.ErrNotFound) + } + return &logs[0], err +} + +func (b *EventBinding) decodeLogsIntoSequences(ctx context.Context, logs []logpoller.Log, into any) ([]commontypes.Sequence, error) { + sequences := make([]commontypes.Sequence, len(logs)) + + for idx := range logs { + sequences[idx] = commontypes.Sequence{ + Cursor: fmt.Sprintf("%s-%s-%d", logs[idx].BlockHash, logs[idx].TxHash, logs[idx].LogIndex), + Head: commontypes.Head{ + Height: fmt.Sprint(logs[idx].BlockNumber), + Hash: logs[idx].BlockHash.Bytes(), + Timestamp: uint64(logs[idx].BlockTimestamp.Unix()), + }, + } + + var typeVal reflect.Value + + typeInto := reflect.TypeOf(into) + if typeInto.Kind() == reflect.Pointer { + typeVal = reflect.New(typeInto.Elem()) + } else { + typeVal = reflect.Indirect(reflect.New(typeInto)) + } + + // create a new value of the same type as 'into' for the data to be extracted to + sequences[idx].Data = typeVal.Interface() + + if err := b.decodeLog(ctx, &logs[idx], sequences[idx].Data); err != nil { + return nil, err + } + } + return sequences, nil +} + +// extractFilterTopics extracts filter topics from input params and returns them as a slice of any. +// returned slice will retain the order of the topics and fill in missing topics with nil, if all values are nil, empty slice is returned. +func (b *EventBinding) extractFilterTopics(topicTypeID string, value any) (filterTopics []any, err error) { + item := reflect.ValueOf(value) + switch item.Kind() { + case reflect.Array, reflect.Slice: + var native any + native, err = codec.RepresentArray(item, b.eventTypes[topicTypeID]) + if err != nil { + return nil, err + } + filterTopics = []any{native} + case reflect.Struct, reflect.Map: + if filterTopics, err = codec.UnrollItem(item, b.eventTypes[topicTypeID]); err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("%w: cannot encode kind %v", commontypes.ErrInvalidType, item.Kind()) + } + + // check if at least one topic filter is present + for _, filterVal := range derefValues(filterTopics) { + if filterVal != nil { + return filterTopics, nil + } + } + + return []any{}, nil +} + +// hashTopics hashes topic filters values to match on chain indexed topics. +func (b *EventBinding) hashTopics(topicTypeID string, topics []any) ([]common.Hash, error) { + var hashableTopics []any + for i, topic := range derefValues(topics) { + if topic == nil { + continue + } + + // make topic value for non-fixed bytes array manually because geth MakeTopics doesn't support it + topicTyp, exists := b.eventTypes[topicTypeID] + if !exists { + return nil, fmt.Errorf("cannot find event type entry") + } + + if abiArg := topicTyp.Args()[i]; abiArg.Type.T == abi.ArrayTy && (abiArg.Type.Elem != nil && abiArg.Type.Elem.T == abi.UintTy) { + packed, err := abi.Arguments{abiArg}.Pack(topic) + if err != nil { + return nil, err + } + topic = crypto.Keccak256Hash(packed) + } + + hashableTopics = append(hashableTopics, topic) + } + + hashes, err := abi.MakeTopics(hashableTopics) + if err != nil { + return nil, wrapInternalErr(err) + } + + if len(hashes) != 1 { + return nil, fmt.Errorf("%w: expected 1 filter set, got %d", commontypes.ErrInternal, len(hashes)) + } + + return hashes[0], nil +} + +func (b *EventBinding) decodeLog(ctx context.Context, log *logpoller.Log, into any) error { + // decode non indexed topics and apply output modifiers + if err := b.codec.Decode(ctx, log.Data, into, codec.WrapItemType(b.contractName, b.eventName, false)); err != nil { + return err + } + + // decode indexed topics which is rarely useful since most indexed topic types get Keccak256 hashed and should be just used for log filtering. + topics := make([]common.Hash, len(b.indexedTopicsTypes.Args())) + if len(log.Topics) < len(topics)+1 { + return fmt.Errorf("%w: not enough topics to decode", commontypes.ErrInvalidType) + } + + for i := 0; i < len(topics); i++ { + topics[i] = common.Hash(log.Topics[i+1]) + } + + topicsInto := map[string]any{} + if err := abi.ParseTopicsIntoMap(topicsInto, b.indexedTopicsTypes.Args(), topics); err != nil { + return fmt.Errorf("%w: %w", commontypes.ErrInvalidType, err) + } + + return codec.MapstructureDecode(topicsInto, into) +} + +// remap chain agnostic primitives to chain specific logPoller primitives. +func (b *EventBinding) remap(filter query.KeyFilter) (remapped query.KeyFilter, err error) { + for _, expression := range filter.Expressions { + remappedExpression, err := b.remapExpression(filter.Key, expression) + if err != nil { + return query.KeyFilter{}, err + } + + remapped.Expressions = append(remapped.Expressions, remappedExpression) + } + + return remapped, nil +} + +func (b *EventBinding) remapExpression(key string, expression query.Expression) (query.Expression, error) { + if !expression.IsPrimitive() { + remappedBoolExpressions := make([]query.Expression, len(expression.BoolExpression.Expressions)) + for i := range expression.BoolExpression.Expressions { + remapped, err := b.remapExpression(key, expression.BoolExpression.Expressions[i]) + if err != nil { + return query.Expression{}, err + } + + remappedBoolExpressions[i] = remapped + } + + if expression.BoolExpression.BoolOperator == query.AND { + return query.And(remappedBoolExpressions...), nil + } + + return query.Or(remappedBoolExpressions...), nil + } + + return b.remapPrimitive(expression) +} + +func (b *EventBinding) remapPrimitive(expression query.Expression) (query.Expression, error) { + switch primitive := expression.Primitive.(type) { + case *primitives.Comparator: + hashedValComps, err := b.encodeComparator(primitive) + if err != nil { + return query.Expression{}, fmt.Errorf("failed to encode comparator %q: %w", primitive.Name, err) + } + return hashedValComps, nil + case *primitives.Confidence: + confirmations, err := confidenceToConfirmations(b.confirmationsMapping, primitive.ConfidenceLevel) + if err != nil { + return query.Expression{}, err + } + + return logpoller.NewConfirmationsFilter(confirmations), nil + default: + return expression, nil + } +} + +func (b *EventBinding) encodeComparator(comparator *primitives.Comparator) (query.Expression, error) { + dwInfo, isDW := b.dataWords[comparator.Name] + if !isDW { + if _, exists := b.topics[comparator.Name]; !exists { + return query.Expression{}, fmt.Errorf("comparator name doesn't match any of the indexed topics or data words") + } + } + + var hashedValComps []logpoller.HashedValueComparator + itemType := codec.WrapItemType(b.contractName, b.eventName+"."+comparator.Name, true) + for _, valComp := range comparator.ValueComparators { + onChainTypedVal, err := b.toNativeOnChainType(itemType, valComp.Value) + if err != nil { + return query.Expression{}, fmt.Errorf("failed to convert comparator value to native on chain type: %w", err) + } + + hashedValComp := logpoller.HashedValueComparator{Operator: valComp.Operator} + if isDW { + hashedValComp.Value, err = b.encodeValComparatorDataWord(itemType, onChainTypedVal) + } else { + hashedValComp.Value, err = b.encodeValComparatorTopic(itemType, onChainTypedVal) + } + if err != nil { + return query.Expression{}, err + } + hashedValComps = append(hashedValComps, hashedValComp) + } + + if isDW { + return logpoller.NewEventByWordFilter(dwInfo.Index, hashedValComps), nil + } + + return logpoller.NewEventByTopicFilter(b.topics[comparator.Name].Index, hashedValComps), nil +} + +func (b *EventBinding) encodeValComparatorDataWord(dwTypeID string, value any) (hash common.Hash, err error) { + dwTypes, exists := b.eventTypes[dwTypeID] + if !exists { + return common.Hash{}, fmt.Errorf("cannot find data word type for %s", dwTypeID) + } + + packedArgs, err := dwTypes.Args().Pack(value) + if err != nil { + return common.Hash{}, err + } + + return common.BytesToHash(packedArgs), nil +} + +func (b *EventBinding) encodeValComparatorTopic(topicTypeID string, value any) (hash common.Hash, err error) { + hashedTopics, err := b.hashTopics(topicTypeID, []any{value}) + if err != nil { + return common.Hash{}, err + } + + return hashedTopics[0], nil +} + +// toNativeOnChainType converts value into its on chain version by applying codec modifiers, map structure hooks and abi typing. +func (b *EventBinding) toNativeOnChainType(itemType string, value any) (any, error) { + offChain, err := b.codec.CreateType(itemType, true) + if err != nil { + return nil, fmt.Errorf("failed to create type: %w", err) + } + + // apply map struct evm hooks to correct incoming values + if err = codec.MapstructureDecode(value, offChain); err != nil { + return nil, err + } + + // apply modifiers if present + onChain := offChain + if modifier, exists := b.eventModifiers[itemType]; exists { + onChain, err = modifier.TransformToOnChain(offChain, "" /* unused */) + if err != nil { + return nil, fmt.Errorf("failed to apply modifiers to offchain type %T: %w", onChain, err) + } + } + + typ, exists := b.eventTypes[itemType] + if !exists { + return query.Expression{}, fmt.Errorf("cannot find event type entry") + } + + native, err := typ.ToNative(reflect.ValueOf(onChain)) + if err != nil { + return query.Expression{}, err + } + + for native.Kind() == reflect.Pointer { + native = reflect.Indirect(native) + } + + return native.Interface(), nil +} + +func (b *EventBinding) validateBound(address common.Address) error { + b.mu.Lock() + defer b.mu.Unlock() + + bound, exists := b.bound[address] + if !exists || !bound { + return fmt.Errorf( + "%w: event %s that belongs to contract: %s, not bound", + commontypes.ErrInvalidType, + b.eventName, + b.contractName, + ) + } + + return nil +} + +func createTopicFilters(hashedTopics []common.Hash) (query.Expression, error) { + var expressions []query.Expression + for topicID, hash := range hashedTopics { + // first topic index is 1-based, so we add 1. + expressions = append(expressions, logpoller.NewEventByTopicFilter( + uint64(topicID+1), []logpoller.HashedValueComparator{{Value: hash, Operator: primitives.Eq}}, + )) + } + + if len(expressions) == 0 { + return query.Expression{}, fmt.Errorf("%w: no topic filters found during query creation", commontypes.ErrInternal) + } + + return query.And(expressions...), nil +} + +// derefValues dereferences pointers to nil values to nil. +func derefValues(topics []any) []any { + for i, topic := range topics { + rTopic := reflect.ValueOf(topic) + if rTopic.Kind() == reflect.Pointer { + if rTopic.IsNil() { + topics[i] = nil + } else { + topics[i] = rTopic.Elem().Interface() + } + } + } + return topics +} + +func wrapInternalErr(err error) error { + if err == nil { + return nil + } + + errStr := err.Error() + if strings.Contains(errStr, "not found") || strings.Contains(errStr, "no rows") { + return fmt.Errorf("%w: %w", commontypes.ErrNotFound, err) + } + return fmt.Errorf("%w: %w", commontypes.ErrInternal, err) +} + +func (b *EventBinding) hasBindings() bool { + b.mu.RLock() + defer b.mu.RUnlock() + + return len(b.bound) > 0 +} + +func (b *EventBinding) isBound(binding common.Address) bool { + b.mu.RLock() + defer b.mu.RUnlock() + + _, exists := b.bound[binding] + + return exists +} + +func (b *EventBinding) addBinding(binding common.Address) { + b.mu.Lock() + defer b.mu.Unlock() + + b.bound[binding] = true +} + +func (b *EventBinding) removeBinding(binding common.Address) { + b.mu.Lock() + defer b.mu.Unlock() + + delete(b.bound, binding) +} + +func (b *EventBinding) registered() bool { + b.mu.RLock() + defer b.mu.RUnlock() + + return b.registerCalled +} diff --git a/core/services/relay/evm/read/filter.go b/core/services/relay/evm/read/filter.go new file mode 100644 index 0000000000..08f45729ec --- /dev/null +++ b/core/services/relay/evm/read/filter.go @@ -0,0 +1,107 @@ +package read + +import ( + "context" + "fmt" + "sync" + + "github.com/ethereum/go-ethereum/common" + + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" +) + +type Registrar interface { + HasFilter(string) bool + RegisterFilter(context.Context, logpoller.Filter) error + UnregisterFilter(context.Context, string) error +} + +type syncedFilter struct { + // internal state properties + mu sync.RWMutex + filter logpoller.Filter +} + +func newSyncedFilter() *syncedFilter { + return &syncedFilter{} +} + +func (r *syncedFilter) Register(ctx context.Context, registrar Registrar) error { + r.mu.RLock() + defer r.mu.RUnlock() + + if !registrar.HasFilter(r.filter.Name) { + if err := registrar.RegisterFilter(ctx, r.filter); err != nil { + return fmt.Errorf("%w: %w", commontypes.ErrInternal, err) + } + } + + return nil +} + +func (r *syncedFilter) Unregister(ctx context.Context, registrar Registrar) error { + r.mu.RLock() + defer r.mu.RUnlock() + + if !registrar.HasFilter(r.filter.Name) { + return nil + } + + if err := registrar.UnregisterFilter(ctx, r.filter.Name); err != nil { + return fmt.Errorf("%w: %w", commontypes.ErrInternal, err) + } + + return nil +} + +func (r *syncedFilter) SetFilter(filter logpoller.Filter) { + r.mu.Lock() + defer r.mu.Unlock() + + r.filter = filter +} + +func (r *syncedFilter) SetName(name string) { + r.mu.Lock() + defer r.mu.Unlock() + + r.filter.Name = name +} + +func (r *syncedFilter) AddAddress(address common.Address) { + r.mu.Lock() + defer r.mu.Unlock() + + r.filter.Addresses = append(r.filter.Addresses, address) +} + +func (r *syncedFilter) RemoveAddress(address common.Address) { + r.mu.Lock() + defer r.mu.Unlock() + + var addrIdx int + for idx, addr := range r.filter.Addresses { + if addr.Hex() == address.Hex() { + addrIdx = idx + } + } + + r.filter.Addresses[addrIdx] = r.filter.Addresses[len(r.filter.Addresses)-1] + r.filter.Addresses = r.filter.Addresses[:len(r.filter.Addresses)-1] +} + +func (r *syncedFilter) Count() int { + r.mu.RLock() + defer r.mu.RUnlock() + + return len(r.filter.Addresses) +} + +func (r *syncedFilter) HasEventSigs() bool { + r.mu.RLock() + defer r.mu.RUnlock() + + return len(r.filter.EventSigs) > 0 && len(r.filter.Addresses) > 0 +} diff --git a/core/services/relay/evm/read/lookup.go b/core/services/relay/evm/read/lookup.go new file mode 100644 index 0000000000..5679d8cee0 --- /dev/null +++ b/core/services/relay/evm/read/lookup.go @@ -0,0 +1,84 @@ +package read + +import ( + "sync" + + "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +type readValues struct { + address string + contract string + readName string +} + +// lookup provides basic utilities for mapping a complete readIdentifier to +// finite contract read information +type lookup struct { + mu sync.RWMutex + // contractReadNames maps a contract name to all available readNames (method, log, event, etc.) + contractReadNames map[string][]string + // readIdentifiers maps from a complete readIdentifier string to finite read data + // a readIdentifier is a combination of address, contract, and readName as a concatenated string + readIdentifiers map[string]readValues +} + +func newLookup() *lookup { + return &lookup{ + contractReadNames: make(map[string][]string), + readIdentifiers: make(map[string]readValues), + } +} + +func (l *lookup) addReadNameForContract(contract, readName string) { + l.mu.Lock() + defer l.mu.Unlock() + + readNames, exists := l.contractReadNames[contract] + if !exists { + readNames = []string{} + } + + l.contractReadNames[contract] = append(readNames, readName) +} + +func (l *lookup) bindAddressForContract(contract, address string) { + l.mu.Lock() + defer l.mu.Unlock() + + for _, readName := range l.contractReadNames[contract] { + readIdentifier := types.BoundContract{ + Address: address, + Name: contract, + }.ReadIdentifier(readName) + + l.readIdentifiers[readIdentifier] = readValues{ + address: address, + contract: contract, + readName: readName, + } + } +} + +func (l *lookup) unbindAddressForContract(contract, address string) { + l.mu.Lock() + defer l.mu.Unlock() + + for _, readName := range l.contractReadNames[contract] { + readIdentifier := types.BoundContract{ + Address: address, + Name: contract, + }.ReadIdentifier(readName) + + delete(l.readIdentifiers, readIdentifier) + } +} + +func (l *lookup) getContractForReadName(readName string) (readValues, bool) { + l.mu.RLock() + defer l.mu.RUnlock() + + contract, ok := l.readIdentifiers[readName] + + return contract, ok +} diff --git a/core/services/relay/evm/read/method.go b/core/services/relay/evm/read/method.go new file mode 100644 index 0000000000..26a7254471 --- /dev/null +++ b/core/services/relay/evm/read/method.go @@ -0,0 +1,204 @@ +package read + +import ( + "context" + "fmt" + "math/big" + "sync" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types/query" + "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + + evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" +) + +type NoContractExistsError struct { + Address common.Address +} + +func (e NoContractExistsError) Error() string { + return fmt.Sprintf("contract does not exist at address: %s", e.Address) +} + +type MethodBinding struct { + // read-only properties + contractName string + method string + + // dependencies + client evmclient.Client + ht logpoller.HeadTracker + lggr logger.Logger + confirmationsMapping map[primitives.ConfidenceLevel]evmtypes.Confirmations + + // internal state properties + codec commontypes.Codec + bindings map[common.Address]struct{} + mu sync.RWMutex +} + +func NewMethodBinding( + name, method string, + client evmclient.Client, + heads logpoller.HeadTracker, + confs map[primitives.ConfidenceLevel]evmtypes.Confirmations, + lggr logger.Logger, +) *MethodBinding { + return &MethodBinding{ + contractName: name, + method: method, + client: client, + ht: heads, + lggr: lggr, + confirmationsMapping: confs, + bindings: make(map[common.Address]struct{}), + } +} + +var _ Reader = &MethodBinding{} + +func (b *MethodBinding) Bind(ctx context.Context, bindings ...common.Address) error { + for _, binding := range bindings { + if b.isBound(binding) { + continue + } + + // check for contract byte code at the latest block and provided address + byteCode, err := b.client.CodeAt(ctx, binding, nil) + if err != nil { + return err + } + + if len(byteCode) == 0 { + return NoContractExistsError{Address: binding} + } + + b.setBinding(binding) + } + + return nil +} + +func (b *MethodBinding) Unbind(ctx context.Context, bindings ...common.Address) error { + b.mu.Lock() + defer b.mu.Unlock() + + for _, binding := range bindings { + delete(b.bindings, binding) + } + + return nil +} + +func (b *MethodBinding) SetCodec(codec commontypes.RemoteCodec) { + b.mu.Lock() + defer b.mu.Unlock() + + b.codec = codec +} + +func (b *MethodBinding) BatchCall(address common.Address, params, retVal any) (Call, error) { + if !b.isBound(address) { + return Call{}, fmt.Errorf("%w: address (%s) not bound to method (%s) for contract (%s)", commontypes.ErrInvalidConfig, address.Hex(), b.method, b.contractName) + } + + return Call{ + ContractAddress: address, + ContractName: b.contractName, + MethodName: b.method, + Params: params, + ReturnVal: retVal, + }, nil +} + +func (b *MethodBinding) GetLatestValue(ctx context.Context, addr common.Address, confidenceLevel primitives.ConfidenceLevel, params, returnVal any) error { + if !b.isBound(addr) { + return fmt.Errorf("%w: method not bound", commontypes.ErrInvalidType) + } + + data, err := b.codec.Encode(ctx, params, codec.WrapItemType(b.contractName, b.method, true)) + if err != nil { + return err + } + + callMsg := ethereum.CallMsg{ + To: &addr, + From: addr, + Data: data, + } + + block, err := b.blockNumberFromConfidence(ctx, confidenceLevel) + if err != nil { + return err + } + + bytes, err := b.client.CallContract(ctx, callMsg, block) + if err != nil { + return fmt.Errorf("%w: %w", commontypes.ErrInternal, err) + } + + return b.codec.Decode(ctx, bytes, returnVal, codec.WrapItemType(b.contractName, b.method, false)) +} + +func (b *MethodBinding) QueryKey( + _ context.Context, + _ common.Address, + _ query.KeyFilter, + _ query.LimitAndSort, + _ any, +) ([]commontypes.Sequence, error) { + return nil, nil +} + +func (b *MethodBinding) Register(_ context.Context) error { return nil } +func (b *MethodBinding) Unregister(_ context.Context) error { return nil } + +func (b *MethodBinding) blockNumberFromConfidence(ctx context.Context, confidenceLevel primitives.ConfidenceLevel) (*big.Int, error) { + confirmations, err := confidenceToConfirmations(b.confirmationsMapping, confidenceLevel) + if err != nil { + err = fmt.Errorf("%w for contract: %s, method: %s", err, b.contractName, b.method) + if confidenceLevel == primitives.Unconfirmed { + b.lggr.Errorf("%v, now falling back to default contract call behaviour that calls latest state", err) + return nil, nil + } + return nil, err + } + + _, finalized, err := b.ht.LatestAndFinalizedBlock(ctx) + if err != nil { + return nil, err + } + + if confirmations == evmtypes.Finalized { + return big.NewInt(finalized.Number), nil + } else if confirmations == evmtypes.Unconfirmed { + return nil, nil + } + + return nil, fmt.Errorf("unknown evm confirmations: %v for contract: %s, method: %s", confirmations, b.contractName, b.method) +} + +func (b *MethodBinding) isBound(binding common.Address) bool { + b.mu.RLock() + defer b.mu.RUnlock() + + _, exists := b.bindings[binding] + + return exists +} + +func (b *MethodBinding) setBinding(binding common.Address) { + b.mu.Lock() + defer b.mu.Unlock() + + b.bindings[binding] = struct{}{} +} diff --git a/core/services/relay/evm/rpclibmocks/batch_caller.go b/core/services/relay/evm/read/mocks/batch_caller.go similarity index 69% rename from core/services/relay/evm/rpclibmocks/batch_caller.go rename to core/services/relay/evm/read/mocks/batch_caller.go index 0bb2c7f4fa..5f1144b5d8 100644 --- a/core/services/relay/evm/rpclibmocks/batch_caller.go +++ b/core/services/relay/evm/read/mocks/batch_caller.go @@ -1,11 +1,11 @@ // Code generated by mockery v2.43.2. DO NOT EDIT. -package rpclibmocks +package mocks import ( context "context" - evm "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" + read "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/read" mock "github.com/stretchr/testify/mock" ) @@ -23,27 +23,27 @@ func (_m *BatchCaller) EXPECT() *BatchCaller_Expecter { } // BatchCall provides a mock function with given fields: ctx, blockNumber, batchRequests -func (_m *BatchCaller) BatchCall(ctx context.Context, blockNumber uint64, batchRequests evm.BatchCall) (evm.BatchResult, error) { +func (_m *BatchCaller) BatchCall(ctx context.Context, blockNumber uint64, batchRequests read.BatchCall) (read.BatchResult, error) { ret := _m.Called(ctx, blockNumber, batchRequests) if len(ret) == 0 { panic("no return value specified for BatchCall") } - var r0 evm.BatchResult + var r0 read.BatchResult var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint64, evm.BatchCall) (evm.BatchResult, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, uint64, read.BatchCall) (read.BatchResult, error)); ok { return rf(ctx, blockNumber, batchRequests) } - if rf, ok := ret.Get(0).(func(context.Context, uint64, evm.BatchCall) evm.BatchResult); ok { + if rf, ok := ret.Get(0).(func(context.Context, uint64, read.BatchCall) read.BatchResult); ok { r0 = rf(ctx, blockNumber, batchRequests) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(evm.BatchResult) + r0 = ret.Get(0).(read.BatchResult) } } - if rf, ok := ret.Get(1).(func(context.Context, uint64, evm.BatchCall) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, uint64, read.BatchCall) error); ok { r1 = rf(ctx, blockNumber, batchRequests) } else { r1 = ret.Error(1) @@ -60,24 +60,24 @@ type BatchCaller_BatchCall_Call struct { // BatchCall is a helper method to define mock.On call // - ctx context.Context // - blockNumber uint64 -// - batchRequests evm.BatchCall +// - batchRequests read.BatchCall func (_e *BatchCaller_Expecter) BatchCall(ctx interface{}, blockNumber interface{}, batchRequests interface{}) *BatchCaller_BatchCall_Call { return &BatchCaller_BatchCall_Call{Call: _e.mock.On("BatchCall", ctx, blockNumber, batchRequests)} } -func (_c *BatchCaller_BatchCall_Call) Run(run func(ctx context.Context, blockNumber uint64, batchRequests evm.BatchCall)) *BatchCaller_BatchCall_Call { +func (_c *BatchCaller_BatchCall_Call) Run(run func(ctx context.Context, blockNumber uint64, batchRequests read.BatchCall)) *BatchCaller_BatchCall_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint64), args[2].(evm.BatchCall)) + run(args[0].(context.Context), args[1].(uint64), args[2].(read.BatchCall)) }) return _c } -func (_c *BatchCaller_BatchCall_Call) Return(_a0 evm.BatchResult, _a1 error) *BatchCaller_BatchCall_Call { +func (_c *BatchCaller_BatchCall_Call) Return(_a0 read.BatchResult, _a1 error) *BatchCaller_BatchCall_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *BatchCaller_BatchCall_Call) RunAndReturn(run func(context.Context, uint64, evm.BatchCall) (evm.BatchResult, error)) *BatchCaller_BatchCall_Call { +func (_c *BatchCaller_BatchCall_Call) RunAndReturn(run func(context.Context, uint64, read.BatchCall) (read.BatchResult, error)) *BatchCaller_BatchCall_Call { _c.Call.Return(run) return _c } diff --git a/core/services/relay/evm/read/mocks/reader.go b/core/services/relay/evm/read/mocks/reader.go new file mode 100644 index 0000000000..6bb9dd70cc --- /dev/null +++ b/core/services/relay/evm/read/mocks/reader.go @@ -0,0 +1,463 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mocks + +import ( + context "context" + + common "github.com/ethereum/go-ethereum/common" + + mock "github.com/stretchr/testify/mock" + + primitives "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" + + query "github.com/smartcontractkit/chainlink-common/pkg/types/query" + + read "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/read" + + types "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +// Reader is an autogenerated mock type for the Reader type +type Reader struct { + mock.Mock +} + +type Reader_Expecter struct { + mock *mock.Mock +} + +func (_m *Reader) EXPECT() *Reader_Expecter { + return &Reader_Expecter{mock: &_m.Mock} +} + +// BatchCall provides a mock function with given fields: address, params, retVal +func (_m *Reader) BatchCall(address common.Address, params interface{}, retVal interface{}) (read.Call, error) { + ret := _m.Called(address, params, retVal) + + if len(ret) == 0 { + panic("no return value specified for BatchCall") + } + + var r0 read.Call + var r1 error + if rf, ok := ret.Get(0).(func(common.Address, interface{}, interface{}) (read.Call, error)); ok { + return rf(address, params, retVal) + } + if rf, ok := ret.Get(0).(func(common.Address, interface{}, interface{}) read.Call); ok { + r0 = rf(address, params, retVal) + } else { + r0 = ret.Get(0).(read.Call) + } + + if rf, ok := ret.Get(1).(func(common.Address, interface{}, interface{}) error); ok { + r1 = rf(address, params, retVal) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Reader_BatchCall_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BatchCall' +type Reader_BatchCall_Call struct { + *mock.Call +} + +// BatchCall is a helper method to define mock.On call +// - address common.Address +// - params interface{} +// - retVal interface{} +func (_e *Reader_Expecter) BatchCall(address interface{}, params interface{}, retVal interface{}) *Reader_BatchCall_Call { + return &Reader_BatchCall_Call{Call: _e.mock.On("BatchCall", address, params, retVal)} +} + +func (_c *Reader_BatchCall_Call) Run(run func(address common.Address, params interface{}, retVal interface{})) *Reader_BatchCall_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(common.Address), args[1].(interface{}), args[2].(interface{})) + }) + return _c +} + +func (_c *Reader_BatchCall_Call) Return(_a0 read.Call, _a1 error) *Reader_BatchCall_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Reader_BatchCall_Call) RunAndReturn(run func(common.Address, interface{}, interface{}) (read.Call, error)) *Reader_BatchCall_Call { + _c.Call.Return(run) + return _c +} + +// Bind provides a mock function with given fields: _a0, _a1 +func (_m *Reader) Bind(_a0 context.Context, _a1 ...common.Address) error { + _va := make([]interface{}, len(_a1)) + for _i := range _a1 { + _va[_i] = _a1[_i] + } + var _ca []interface{} + _ca = append(_ca, _a0) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Bind") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, ...common.Address) error); ok { + r0 = rf(_a0, _a1...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Reader_Bind_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Bind' +type Reader_Bind_Call struct { + *mock.Call +} + +// Bind is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 ...common.Address +func (_e *Reader_Expecter) Bind(_a0 interface{}, _a1 ...interface{}) *Reader_Bind_Call { + return &Reader_Bind_Call{Call: _e.mock.On("Bind", + append([]interface{}{_a0}, _a1...)...)} +} + +func (_c *Reader_Bind_Call) Run(run func(_a0 context.Context, _a1 ...common.Address)) *Reader_Bind_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]common.Address, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.(common.Address) + } + } + run(args[0].(context.Context), variadicArgs...) + }) + return _c +} + +func (_c *Reader_Bind_Call) Return(_a0 error) *Reader_Bind_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Reader_Bind_Call) RunAndReturn(run func(context.Context, ...common.Address) error) *Reader_Bind_Call { + _c.Call.Return(run) + return _c +} + +// GetLatestValue provides a mock function with given fields: ctx, addr, confidence, params, returnVal +func (_m *Reader) GetLatestValue(ctx context.Context, addr common.Address, confidence primitives.ConfidenceLevel, params interface{}, returnVal interface{}) error { + ret := _m.Called(ctx, addr, confidence, params, returnVal) + + if len(ret) == 0 { + panic("no return value specified for GetLatestValue") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, common.Address, primitives.ConfidenceLevel, interface{}, interface{}) error); ok { + r0 = rf(ctx, addr, confidence, params, returnVal) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Reader_GetLatestValue_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLatestValue' +type Reader_GetLatestValue_Call struct { + *mock.Call +} + +// GetLatestValue is a helper method to define mock.On call +// - ctx context.Context +// - addr common.Address +// - confidence primitives.ConfidenceLevel +// - params interface{} +// - returnVal interface{} +func (_e *Reader_Expecter) GetLatestValue(ctx interface{}, addr interface{}, confidence interface{}, params interface{}, returnVal interface{}) *Reader_GetLatestValue_Call { + return &Reader_GetLatestValue_Call{Call: _e.mock.On("GetLatestValue", ctx, addr, confidence, params, returnVal)} +} + +func (_c *Reader_GetLatestValue_Call) Run(run func(ctx context.Context, addr common.Address, confidence primitives.ConfidenceLevel, params interface{}, returnVal interface{})) *Reader_GetLatestValue_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Address), args[2].(primitives.ConfidenceLevel), args[3].(interface{}), args[4].(interface{})) + }) + return _c +} + +func (_c *Reader_GetLatestValue_Call) Return(_a0 error) *Reader_GetLatestValue_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Reader_GetLatestValue_Call) RunAndReturn(run func(context.Context, common.Address, primitives.ConfidenceLevel, interface{}, interface{}) error) *Reader_GetLatestValue_Call { + _c.Call.Return(run) + return _c +} + +// QueryKey provides a mock function with given fields: _a0, _a1, _a2, _a3, _a4 +func (_m *Reader) QueryKey(_a0 context.Context, _a1 common.Address, _a2 query.KeyFilter, _a3 query.LimitAndSort, _a4 interface{}) ([]types.Sequence, error) { + ret := _m.Called(_a0, _a1, _a2, _a3, _a4) + + if len(ret) == 0 { + panic("no return value specified for QueryKey") + } + + var r0 []types.Sequence + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Address, query.KeyFilter, query.LimitAndSort, interface{}) ([]types.Sequence, error)); ok { + return rf(_a0, _a1, _a2, _a3, _a4) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Address, query.KeyFilter, query.LimitAndSort, interface{}) []types.Sequence); ok { + r0 = rf(_a0, _a1, _a2, _a3, _a4) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]types.Sequence) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Address, query.KeyFilter, query.LimitAndSort, interface{}) error); ok { + r1 = rf(_a0, _a1, _a2, _a3, _a4) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Reader_QueryKey_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'QueryKey' +type Reader_QueryKey_Call struct { + *mock.Call +} + +// QueryKey is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 common.Address +// - _a2 query.KeyFilter +// - _a3 query.LimitAndSort +// - _a4 interface{} +func (_e *Reader_Expecter) QueryKey(_a0 interface{}, _a1 interface{}, _a2 interface{}, _a3 interface{}, _a4 interface{}) *Reader_QueryKey_Call { + return &Reader_QueryKey_Call{Call: _e.mock.On("QueryKey", _a0, _a1, _a2, _a3, _a4)} +} + +func (_c *Reader_QueryKey_Call) Run(run func(_a0 context.Context, _a1 common.Address, _a2 query.KeyFilter, _a3 query.LimitAndSort, _a4 interface{})) *Reader_QueryKey_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Address), args[2].(query.KeyFilter), args[3].(query.LimitAndSort), args[4].(interface{})) + }) + return _c +} + +func (_c *Reader_QueryKey_Call) Return(_a0 []types.Sequence, _a1 error) *Reader_QueryKey_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Reader_QueryKey_Call) RunAndReturn(run func(context.Context, common.Address, query.KeyFilter, query.LimitAndSort, interface{}) ([]types.Sequence, error)) *Reader_QueryKey_Call { + _c.Call.Return(run) + return _c +} + +// Register provides a mock function with given fields: _a0 +func (_m *Reader) Register(_a0 context.Context) error { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for Register") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Reader_Register_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Register' +type Reader_Register_Call struct { + *mock.Call +} + +// Register is a helper method to define mock.On call +// - _a0 context.Context +func (_e *Reader_Expecter) Register(_a0 interface{}) *Reader_Register_Call { + return &Reader_Register_Call{Call: _e.mock.On("Register", _a0)} +} + +func (_c *Reader_Register_Call) Run(run func(_a0 context.Context)) *Reader_Register_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *Reader_Register_Call) Return(_a0 error) *Reader_Register_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Reader_Register_Call) RunAndReturn(run func(context.Context) error) *Reader_Register_Call { + _c.Call.Return(run) + return _c +} + +// SetCodec provides a mock function with given fields: _a0 +func (_m *Reader) SetCodec(_a0 types.RemoteCodec) { + _m.Called(_a0) +} + +// Reader_SetCodec_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetCodec' +type Reader_SetCodec_Call struct { + *mock.Call +} + +// SetCodec is a helper method to define mock.On call +// - _a0 types.RemoteCodec +func (_e *Reader_Expecter) SetCodec(_a0 interface{}) *Reader_SetCodec_Call { + return &Reader_SetCodec_Call{Call: _e.mock.On("SetCodec", _a0)} +} + +func (_c *Reader_SetCodec_Call) Run(run func(_a0 types.RemoteCodec)) *Reader_SetCodec_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(types.RemoteCodec)) + }) + return _c +} + +func (_c *Reader_SetCodec_Call) Return() *Reader_SetCodec_Call { + _c.Call.Return() + return _c +} + +func (_c *Reader_SetCodec_Call) RunAndReturn(run func(types.RemoteCodec)) *Reader_SetCodec_Call { + _c.Call.Return(run) + return _c +} + +// Unbind provides a mock function with given fields: _a0, _a1 +func (_m *Reader) Unbind(_a0 context.Context, _a1 ...common.Address) error { + _va := make([]interface{}, len(_a1)) + for _i := range _a1 { + _va[_i] = _a1[_i] + } + var _ca []interface{} + _ca = append(_ca, _a0) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Unbind") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, ...common.Address) error); ok { + r0 = rf(_a0, _a1...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Reader_Unbind_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Unbind' +type Reader_Unbind_Call struct { + *mock.Call +} + +// Unbind is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 ...common.Address +func (_e *Reader_Expecter) Unbind(_a0 interface{}, _a1 ...interface{}) *Reader_Unbind_Call { + return &Reader_Unbind_Call{Call: _e.mock.On("Unbind", + append([]interface{}{_a0}, _a1...)...)} +} + +func (_c *Reader_Unbind_Call) Run(run func(_a0 context.Context, _a1 ...common.Address)) *Reader_Unbind_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]common.Address, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.(common.Address) + } + } + run(args[0].(context.Context), variadicArgs...) + }) + return _c +} + +func (_c *Reader_Unbind_Call) Return(_a0 error) *Reader_Unbind_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Reader_Unbind_Call) RunAndReturn(run func(context.Context, ...common.Address) error) *Reader_Unbind_Call { + _c.Call.Return(run) + return _c +} + +// Unregister provides a mock function with given fields: _a0 +func (_m *Reader) Unregister(_a0 context.Context) error { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for Unregister") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Reader_Unregister_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Unregister' +type Reader_Unregister_Call struct { + *mock.Call +} + +// Unregister is a helper method to define mock.On call +// - _a0 context.Context +func (_e *Reader_Expecter) Unregister(_a0 interface{}) *Reader_Unregister_Call { + return &Reader_Unregister_Call{Call: _e.mock.On("Unregister", _a0)} +} + +func (_c *Reader_Unregister_Call) Run(run func(_a0 context.Context)) *Reader_Unregister_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *Reader_Unregister_Call) Return(_a0 error) *Reader_Unregister_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Reader_Unregister_Call) RunAndReturn(run func(context.Context) error) *Reader_Unregister_Call { + _c.Call.Return(run) + return _c +} + +// NewReader creates a new instance of Reader. 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 NewReader(t interface { + mock.TestingT + Cleanup(func()) +}) *Reader { + mock := &Reader{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/services/relay/evm/read/mocks/registrar.go b/core/services/relay/evm/read/mocks/registrar.go new file mode 100644 index 0000000000..670c29317a --- /dev/null +++ b/core/services/relay/evm/read/mocks/registrar.go @@ -0,0 +1,177 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mocks + +import ( + context "context" + + logpoller "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + mock "github.com/stretchr/testify/mock" +) + +// Registrar is an autogenerated mock type for the Registrar type +type Registrar struct { + mock.Mock +} + +type Registrar_Expecter struct { + mock *mock.Mock +} + +func (_m *Registrar) EXPECT() *Registrar_Expecter { + return &Registrar_Expecter{mock: &_m.Mock} +} + +// HasFilter provides a mock function with given fields: _a0 +func (_m *Registrar) HasFilter(_a0 string) bool { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for HasFilter") + } + + var r0 bool + if rf, ok := ret.Get(0).(func(string) bool); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// Registrar_HasFilter_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HasFilter' +type Registrar_HasFilter_Call struct { + *mock.Call +} + +// HasFilter is a helper method to define mock.On call +// - _a0 string +func (_e *Registrar_Expecter) HasFilter(_a0 interface{}) *Registrar_HasFilter_Call { + return &Registrar_HasFilter_Call{Call: _e.mock.On("HasFilter", _a0)} +} + +func (_c *Registrar_HasFilter_Call) Run(run func(_a0 string)) *Registrar_HasFilter_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *Registrar_HasFilter_Call) Return(_a0 bool) *Registrar_HasFilter_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Registrar_HasFilter_Call) RunAndReturn(run func(string) bool) *Registrar_HasFilter_Call { + _c.Call.Return(run) + return _c +} + +// RegisterFilter provides a mock function with given fields: _a0, _a1 +func (_m *Registrar) RegisterFilter(_a0 context.Context, _a1 logpoller.Filter) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for RegisterFilter") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, logpoller.Filter) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Registrar_RegisterFilter_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RegisterFilter' +type Registrar_RegisterFilter_Call struct { + *mock.Call +} + +// RegisterFilter is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 logpoller.Filter +func (_e *Registrar_Expecter) RegisterFilter(_a0 interface{}, _a1 interface{}) *Registrar_RegisterFilter_Call { + return &Registrar_RegisterFilter_Call{Call: _e.mock.On("RegisterFilter", _a0, _a1)} +} + +func (_c *Registrar_RegisterFilter_Call) Run(run func(_a0 context.Context, _a1 logpoller.Filter)) *Registrar_RegisterFilter_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(logpoller.Filter)) + }) + return _c +} + +func (_c *Registrar_RegisterFilter_Call) Return(_a0 error) *Registrar_RegisterFilter_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Registrar_RegisterFilter_Call) RunAndReturn(run func(context.Context, logpoller.Filter) error) *Registrar_RegisterFilter_Call { + _c.Call.Return(run) + return _c +} + +// UnregisterFilter provides a mock function with given fields: _a0, _a1 +func (_m *Registrar) UnregisterFilter(_a0 context.Context, _a1 string) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for UnregisterFilter") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Registrar_UnregisterFilter_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UnregisterFilter' +type Registrar_UnregisterFilter_Call struct { + *mock.Call +} + +// UnregisterFilter is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 string +func (_e *Registrar_Expecter) UnregisterFilter(_a0 interface{}, _a1 interface{}) *Registrar_UnregisterFilter_Call { + return &Registrar_UnregisterFilter_Call{Call: _e.mock.On("UnregisterFilter", _a0, _a1)} +} + +func (_c *Registrar_UnregisterFilter_Call) Run(run func(_a0 context.Context, _a1 string)) *Registrar_UnregisterFilter_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *Registrar_UnregisterFilter_Call) Return(_a0 error) *Registrar_UnregisterFilter_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Registrar_UnregisterFilter_Call) RunAndReturn(run func(context.Context, string) error) *Registrar_UnregisterFilter_Call { + _c.Call.Return(run) + return _c +} + +// NewRegistrar creates a new instance of Registrar. 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 NewRegistrar(t interface { + mock.TestingT + Cleanup(func()) +}) *Registrar { + mock := &Registrar{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/services/relay/evm/relayer_extender.go b/core/services/relay/evm/relayer_extender.go index f262948c9c..f447749157 100644 --- a/core/services/relay/evm/relayer_extender.go +++ b/core/services/relay/evm/relayer_extender.go @@ -74,6 +74,10 @@ type ChainRelayerExt struct { var _ EVMChainRelayerExtender = &ChainRelayerExt{} +func (s *ChainRelayerExt) LatestHead(ctx context.Context) (commontypes.Head, error) { + return s.chain.LatestHead(ctx) +} + func (s *ChainRelayerExt) GetChainStatus(ctx context.Context) (commontypes.ChainStatus, error) { return s.chain.GetChainStatus(ctx) } diff --git a/core/services/relay/evm/request_round_db.go b/core/services/relay/evm/request_round_db.go index 1aa3dfd747..07a2d1cbc3 100644 --- a/core/services/relay/evm/request_round_db.go +++ b/core/services/relay/evm/request_round_db.go @@ -8,8 +8,8 @@ import ( "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) // RequestRoundDB stores requested rounds for querying by the median plugin. diff --git a/core/services/relay/evm/request_round_tracker.go b/core/services/relay/evm/request_round_tracker.go index 7cf1377569..b9200fff75 100644 --- a/core/services/relay/evm/request_round_tracker.go +++ b/core/services/relay/evm/request_round_tracker.go @@ -12,13 +12,13 @@ import ( "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/log" offchain_aggregator_wrapper "github.com/smartcontractkit/chainlink/v2/core/internal/gethwrappers2/generated/offchainaggregator" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" ) diff --git a/core/services/relay/evm/types/codec_entry.go b/core/services/relay/evm/types/codec_entry.go index e5c15f8148..981166b95b 100644 --- a/core/services/relay/evm/types/codec_entry.go +++ b/core/services/relay/evm/types/codec_entry.go @@ -57,9 +57,15 @@ func (entry *codecEntry) NativeType() reflect.Type { return entry.nativeType } -func (entry *codecEntry) ToNative(checked reflect.Value) (reflect.Value, error) { +func (entry *codecEntry) ToNative(checked reflect.Value) (val reflect.Value, err error) { + defer func() { + if r := recover(); r != nil { + val = reflect.Value{} + err = fmt.Errorf("invalid checked value: %v", r) + } + }() if checked.Type() != reflect.PointerTo(entry.checkedType) { - return reflect.Value{}, fmt.Errorf("%w: checked type %v does not match expected type %v", commontypes.ErrInvalidType, reflect.TypeOf(checked), entry.checkedType) + return reflect.Value{}, fmt.Errorf("%w: checked type %v does not match expected type %v", commontypes.ErrInvalidType, checked.Type(), entry.checkedType) } return reflect.NewAt(entry.nativeType, checked.UnsafePointer()), nil diff --git a/core/services/relay/evm/types/codec_entry_test.go b/core/services/relay/evm/types/codec_entry_test.go index 06b08fcecf..64e0998716 100644 --- a/core/services/relay/evm/types/codec_entry_test.go +++ b/core/services/relay/evm/types/codec_entry_test.go @@ -273,17 +273,27 @@ func TestCodecEntry(t *testing.T) { assertHaveSameStructureAndNames(t, iNative.Type(), entry.CheckedType()) }) - t.Run("Indexed non basic types change to hash", func(t *testing.T) { - anyType, err := abi.NewType("string", "", []abi.ArgumentMarshaling{}) + t.Run("Indexed string and bytes array change to hash", func(t *testing.T) { + stringType, err := abi.NewType("string", "", []abi.ArgumentMarshaling{}) require.NoError(t, err) - entry := NewCodecEntry(abi.Arguments{{Name: "Name", Type: anyType, Indexed: true}}, nil, nil) - require.NoError(t, entry.Init()) - nativeField, ok := entry.CheckedType().FieldByName("Name") - require.True(t, ok) - assert.Equal(t, reflect.TypeOf(&common.Hash{}), nativeField.Type) - native, err := entry.ToNative(reflect.New(entry.CheckedType())) + arrayType, err := abi.NewType("uint8[32]", "", []abi.ArgumentMarshaling{}) require.NoError(t, err) - assertHaveSameStructureAndNames(t, native.Type().Elem(), entry.CheckedType()) + + abiArgs := abi.Arguments{ + {Name: "String", Type: stringType, Indexed: true}, + {Name: "Array", Type: arrayType, Indexed: true}, + } + + for i := 0; i < len(abiArgs); i++ { + entry := NewCodecEntry(abi.Arguments{abiArgs[i]}, nil, nil) + require.NoError(t, entry.Init()) + nativeField, ok := entry.CheckedType().FieldByName(abiArgs[i].Name) + require.True(t, ok) + assert.Equal(t, reflect.TypeOf(&common.Hash{}), nativeField.Type) + native, err := entry.ToNative(reflect.New(entry.CheckedType())) + require.NoError(t, err) + assertHaveSameStructureAndNames(t, native.Type().Elem(), entry.CheckedType()) + } }) t.Run("Too many indexed items returns an error", func(t *testing.T) { diff --git a/core/services/relay/evm/types/types.go b/core/services/relay/evm/types/types.go index 68d0b88e4b..704d8b5c72 100644 --- a/core/services/relay/evm/types/types.go +++ b/core/services/relay/evm/types/types.go @@ -100,11 +100,9 @@ type EventDefinitions struct { // GenericTopicNames helps QueryingKeys not rely on EVM specific topic names. Key is chain specific name, value is generic name. // This helps us translate chain agnostic querying key "transfer-value" to EVM specific "evmTransferEvent-weiAmountTopic". GenericTopicNames map[string]string `json:"genericTopicNames,omitempty"` - // key is a predefined generic name for evm log event data word - // for e.g. first evm data word(32bytes) of USDC log event is value so the key can be called value - GenericDataWordNames map[string]uint8 `json:"genericDataWordNames,omitempty"` - // InputFields allows you to choose which indexed fields are expected from the input - InputFields []string `json:"inputFields,omitempty"` + // GenericDataWordNames key is generic name for evm log event data word that maps to on chain name. + // For e.g. first evm data word(32bytes) of USDC log event is value so the key can be called value. + GenericDataWordNames map[string]string `json:"genericDataWordDefs,omitempty"` // PollingFilter should be defined on a contract level in ContractPollingFilter, // unless event needs to override the contract level filter options. // This will create a separate log poller filter for this event. @@ -199,6 +197,9 @@ type RelayConfig struct { // Rebalancer specific // FromBlocks specifies the block numbers to replay from for each chain. FromBlocks map[string]int64 `json:"fromBlocks"` + + // LLO-specific + LLODONID uint32 `json:"lloDonID" toml:"lloDonID"` } var ErrBadRelayConfig = errors.New("bad relay config") diff --git a/core/services/relay/evm/types/types_test.go b/core/services/relay/evm/types/types_test.go index 37d5e77693..11825ae5c4 100644 --- a/core/services/relay/evm/types/types_test.go +++ b/core/services/relay/evm/types/types_test.go @@ -80,7 +80,7 @@ func Test_ChainReaderConfig(t *testing.T) { } }, "configs":{ - "config1":"{\"cacheEnabled\":true,\"chainSpecificName\":\"specificName1\",\"inputModifications\":[{\"Fields\":[\"ts\"],\"Type\":\"epoch to time\"},{\"Fields\":{\"a\":\"b\"},\"Type\":\"rename\"}],\"outputModifications\":[{\"Fields\":[\"ts\"],\"Type\":\"epoch to time\"},{\"Fields\":{\"c\":\"d\"},\"Type\":\"rename\"}],\"eventDefinitions\":{\"genericTopicNames\":{\"TopicKey1\":\"TopicVal1\"},\"genericDataWordNames\":{\"DataWordKey\":1},\"inputFields\":[\"Event1\",\"Event2\"],\"pollingFilter\":{\"topic2\":[\"0x4abbe4784b1fb071039bb9cb50b82978fb5d3ab98fb512c032e75786b93e2c52\"],\"topic3\":[\"0x5abbe4784b1fb071039bb9cb50b82978fb5d3ab98fb512c032e75786b93e2c52\"],\"topic4\":[\"0x6abbe4784b1fb071039bb9cb50b82978fb5d3ab98fb512c032e75786b93e2c52\"],\"retention\":\"1m0s\",\"maxLogsKept\":100,\"logsPerBlock\":10}},\"confidenceConfirmations\":{\"0.0\":0,\"1.0\":-1}}" + "config1":"{\"cacheEnabled\":true,\"chainSpecificName\":\"specificName1\",\"inputModifications\":[{\"Fields\":[\"ts\"],\"Type\":\"epoch to time\"},{\"Fields\":{\"a\":\"b\"},\"Type\":\"rename\"}],\"outputModifications\":[{\"Fields\":[\"ts\"],\"Type\":\"epoch to time\"},{\"Fields\":{\"c\":\"d\"},\"Type\":\"rename\"}],\"eventDefinitions\":{\"genericTopicNames\":{\"TopicKey1\":\"TopicVal1\"},\"genericDataWordDefs\":{\"DataWordKey\": \"DataWordKey\"},\"pollingFilter\":{\"topic2\":[\"0x4abbe4784b1fb071039bb9cb50b82978fb5d3ab98fb512c032e75786b93e2c52\"],\"topic3\":[\"0x5abbe4784b1fb071039bb9cb50b82978fb5d3ab98fb512c032e75786b93e2c52\"],\"topic4\":[\"0x6abbe4784b1fb071039bb9cb50b82978fb5d3ab98fb512c032e75786b93e2c52\"],\"retention\":\"1m0s\",\"maxLogsKept\":100,\"logsPerBlock\":10}},\"confidenceConfirmations\":{\"0.0\":0,\"1.0\":-1}}" } } } @@ -128,8 +128,7 @@ func Test_ChainReaderConfig(t *testing.T) { ConfidenceConfirmations: map[string]int{"0.0": 0, "1.0": -1}, EventDefinitions: &EventDefinitions{ GenericTopicNames: map[string]string{"TopicKey1": "TopicVal1"}, - GenericDataWordNames: map[string]uint8{"DataWordKey": 1}, - InputFields: []string{"Event1", "Event2"}, + GenericDataWordNames: map[string]string{"DataWordKey": "DataWordKey"}, PollingFilter: &PollingFilter{ Topic2: evmtypes.HashArray{common.HexToHash("0x4abbe4784b1fb071039bb9cb50b82978fb5d3ab98fb512c032e75786b93e2c52")}, Topic3: evmtypes.HashArray{common.HexToHash("0x5abbe4784b1fb071039bb9cb50b82978fb5d3ab98fb512c032e75786b93e2c52")}, diff --git a/core/services/relay/evm/write_target.go b/core/services/relay/evm/write_target.go index fb1c694a2e..14e6d1bf34 100644 --- a/core/services/relay/evm/write_target.go +++ b/core/services/relay/evm/write_target.go @@ -7,14 +7,15 @@ import ( chainselectors "github.com/smartcontractkit/chain-selectors" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/targets" "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/forwarder" - "github.com/smartcontractkit/chainlink/v2/core/logger" relayevmtypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" ) -func NewWriteTarget(ctx context.Context, relayer *Relayer, chain legacyevm.Chain, lggr logger.Logger) (*targets.WriteTarget, error) { +func NewWriteTarget(ctx context.Context, relayer *Relayer, chain legacyevm.Chain, gasLimitDefault uint64, lggr logger.Logger) (*targets.WriteTarget, error) { // generate ID based on chain selector id := fmt.Sprintf("write_%v@1.0.0", chain.ID()) chainName, err := chainselectors.NameFromChainId(chain.ID().Uint64()) @@ -31,8 +32,8 @@ func NewWriteTarget(ctx context.Context, relayer *Relayer, chain legacyevm.Chain "forwarder": { ContractABI: forwarder.KeystoneForwarderABI, Configs: map[string]*relayevmtypes.ChainReaderDefinition{ - "getTransmitter": { - ChainSpecificName: "getTransmitter", + "getTransmissionInfo": { + ChainSpecificName: "getTransmissionInfo", }, }, }, @@ -53,9 +54,8 @@ func NewWriteTarget(ctx context.Context, relayer *Relayer, chain legacyevm.Chain Configs: map[string]*relayevmtypes.ChainWriterDefinition{ "report": { ChainSpecificName: "report", - Checker: "simulate", FromAddress: config.FromAddress().Address(), - GasLimit: 200_000, + GasLimit: gasLimitDefault, }, }, }, @@ -73,5 +73,5 @@ func NewWriteTarget(ctx context.Context, relayer *Relayer, chain legacyevm.Chain return nil, err } - return targets.NewWriteTarget(lggr, id, cr, cw, config.ForwarderAddress().String()), nil + return targets.NewWriteTarget(logger.Named(lggr, "WriteTarget"), id, cr, cw, config.ForwarderAddress().String(), gasLimitDefault), nil } diff --git a/core/services/relay/evm/write_target_test.go b/core/services/relay/evm/write_target_test.go index f3dcae220e..94b2367b6d 100644 --- a/core/services/relay/evm/write_target_test.go +++ b/core/services/relay/evm/write_target_test.go @@ -1,7 +1,10 @@ package evm_test import ( + "bytes" + "encoding/hex" "errors" + "fmt" "math/big" "testing" @@ -9,6 +12,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/values" "github.com/smartcontractkit/chainlink/v2/common/headtracker/mocks" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/targets" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" @@ -17,6 +21,7 @@ import ( evmcapabilities "github.com/smartcontractkit/chainlink/v2/core/capabilities" evmclimocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" gasmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/mocks" + pollermocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" txmmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" @@ -36,22 +41,79 @@ import ( var forwardABI = types.MustGetABI(forwarder.KeystoneForwarderMetaData.ABI) +func newMockedEncodeTransmissionInfo() ([]byte, error) { + info := targets.TransmissionInfo{ + GasLimit: big.NewInt(0), + InvalidReceiver: false, + State: 0, + Success: false, + TransmissionId: [32]byte{}, + Transmitter: common.HexToAddress("0x0"), + } + + var buffer bytes.Buffer + gasLimitBytes := info.GasLimit.Bytes() + if len(gasLimitBytes) > 80 { + return nil, fmt.Errorf("GasLimit too large") + } + paddedGasLimit := make([]byte, 80-len(gasLimitBytes)) + buffer.Write(paddedGasLimit) + buffer.Write(gasLimitBytes) + + // Encode InvalidReceiver (as uint8) + if info.InvalidReceiver { + buffer.WriteByte(1) + } else { + buffer.WriteByte(0) + } + + // Padding for InvalidReceiver to fit into 32 bytes + padInvalidReceiver := make([]byte, 31) + buffer.Write(padInvalidReceiver) + + // Encode State (as uint8) + buffer.WriteByte(info.State) + + // Padding for State to fit into 32 bytes + padState := make([]byte, 31) + buffer.Write(padState) + + // Encode Success (as uint8) + if info.Success { + buffer.WriteByte(1) + } else { + buffer.WriteByte(0) + } + + // Padding for Success to fit into 32 bytes + padSuccess := make([]byte, 31) + buffer.Write(padSuccess) + + // Encode TransmissionId (as bytes32) + buffer.Write(info.TransmissionId[:]) + + // Encode Transmitter (as address) + buffer.Write(info.Transmitter.Bytes()) + + return buffer.Bytes(), nil +} + func TestEvmWrite(t *testing.T) { chain := evmmocks.NewChain(t) txManager := txmmocks.NewMockEvmTxManager(t) evmClient := evmclimocks.NewClient(t) + poller := pollermocks.NewLogPoller(t) - // This probably isn't the best way to do this, but couldn't find a simpler way to mock the CallContract response - var mockCall []byte - for i := 0; i < 32; i++ { - mockCall = append(mockCall, byte(0)) - } + // This is a very error-prone way to mock an on-chain response to a GetLatestValue("getTransmissionInfo") call + // It's a bit of a hack, but it's the best way to do it without a lot of refactoring + mockCall, err := newMockedEncodeTransmissionInfo() + require.NoError(t, err) evmClient.On("CallContract", mock.Anything, mock.Anything, mock.Anything).Return(mockCall, nil).Maybe() evmClient.On("CodeAt", mock.Anything, mock.Anything, mock.Anything).Return([]byte("test"), nil) chain.On("ID").Return(big.NewInt(11155111)) chain.On("TxManager").Return(txManager) - chain.On("LogPoller").Return(nil) + chain.On("LogPoller").Return(poller) ht := mocks.NewHeadTracker[*types.Head, common.Hash](t) ht.On("LatestAndFinalizedBlock", mock.Anything).Return(&types.Head{}, &types.Head{}, nil) @@ -87,86 +149,76 @@ func TestEvmWrite(t *testing.T) { }) require.NoError(t, err) - txManager.On("CreateTransaction", mock.Anything, mock.Anything).Return(txmgr.Tx{}, nil).Run(func(args mock.Arguments) { - req := args.Get(1).(txmgr.TxRequest) - payload := make(map[string]any) - method := forwardABI.Methods["report"] - err = method.Inputs.UnpackIntoMap(payload, req.EncodedPayload[4:]) - require.NoError(t, err) - require.Equal(t, []byte{0x1, 0x2, 0x3}, payload["rawReport"]) - require.Equal(t, [][]byte{}, payload["signatures"]) - }).Once() - - t.Run("succeeds with valid report", func(t *testing.T) { - ctx := testutils.Context(t) - capability, err := evm.NewWriteTarget(ctx, relayer, chain, lggr) - require.NoError(t, err) + reportID := [2]byte{0x00, 0x01} + reportMetadata := targets.ReportV1Metadata{ + Version: 1, + WorkflowExecutionID: [32]byte{}, + Timestamp: 0, + DonID: 0, + DonConfigVersion: 0, + WorkflowCID: [32]byte{}, + WorkflowName: [10]byte{}, + WorkflowOwner: [20]byte{}, + ReportID: reportID, + } - config, err := values.NewMap(map[string]any{ - "Address": evmCfg.EVM().Workflow().ForwarderAddress().String(), - }) - require.NoError(t, err) + reportMetadataBytes, err := reportMetadata.Encode() + require.NoError(t, err) - inputs, err := values.NewMap(map[string]any{ - "signed_report": map[string]any{ - "report": []byte{1, 2, 3}, - "signatures": [][]byte{}, - "context": []byte{4, 5}, - "id": []byte{9, 9}, - }, - }) - require.NoError(t, err) + signatures := [][]byte{} - req := capabilities.CapabilityRequest{ - Metadata: capabilities.RequestMetadata{ - WorkflowID: "test-id", - }, - Config: config, - Inputs: inputs, - } + validInputs, err := values.NewMap(map[string]any{ + "signed_report": map[string]any{ + "report": reportMetadataBytes, + "signatures": signatures, + "context": []byte{4, 5}, + "id": reportID[:], + }, + }) + require.NoError(t, err) - ch, err := capability.Execute(ctx, req) - require.NoError(t, err) + validMetadata := capabilities.RequestMetadata{ + WorkflowID: hex.EncodeToString(reportMetadata.WorkflowCID[:]), + WorkflowOwner: hex.EncodeToString(reportMetadata.WorkflowOwner[:]), + WorkflowName: hex.EncodeToString(reportMetadata.WorkflowName[:]), + WorkflowExecutionID: hex.EncodeToString(reportMetadata.WorkflowExecutionID[:]), + } - response := <-ch - require.Nil(t, response.Err) + validConfig, err := values.NewMap(map[string]any{ + "Address": evmCfg.EVM().Workflow().ForwarderAddress().String(), }) + require.NoError(t, err) - t.Run("succeeds with empty report", func(t *testing.T) { - ctx := testutils.Context(t) - capability, err := evm.NewWriteTarget(ctx, relayer, chain, logger.TestLogger(t)) + txManager.On("CreateTransaction", mock.Anything, mock.Anything).Return(txmgr.Tx{}, nil).Run(func(args mock.Arguments) { + req := args.Get(1).(txmgr.TxRequest) + payload := make(map[string]any) + method := forwardABI.Methods["report"] + err = method.Inputs.UnpackIntoMap(payload, req.EncodedPayload[4:]) require.NoError(t, err) + require.Equal(t, reportMetadataBytes, payload["rawReport"]) + require.Equal(t, signatures, payload["signatures"]) + }).Once() - config, err := values.NewMap(map[string]any{ - "Address": evmCfg.EVM().Workflow().ForwarderAddress().String(), - }) - require.NoError(t, err) + gasLimitDefault := uint64(400_000) - inputs, err := values.NewMap(map[string]any{ - "signed_report": map[string]any{ - "report": nil, - }, - }) + t.Run("succeeds with valid report", func(t *testing.T) { + ctx := testutils.Context(t) + capability, err := evm.NewWriteTarget(ctx, relayer, chain, gasLimitDefault, lggr) require.NoError(t, err) req := capabilities.CapabilityRequest{ - Metadata: capabilities.RequestMetadata{ - WorkflowID: "test-id", - }, - Config: config, - Inputs: inputs, + Metadata: validMetadata, + Config: validConfig, + Inputs: validInputs, } - ch, err := capability.Execute(ctx, req) + _, err = capability.Execute(ctx, req) require.NoError(t, err) - - response := <-ch - require.Nil(t, response.Err) }) t.Run("fails with invalid config", func(t *testing.T) { ctx := testutils.Context(t) - capability, err := evm.NewWriteTarget(ctx, relayer, chain, logger.TestLogger(t)) + capability, err := evm.NewWriteTarget(ctx, relayer, chain, gasLimitDefault, logger.TestLogger(t)) require.NoError(t, err) invalidConfig, err := values.NewMap(map[string]any{ @@ -174,19 +226,10 @@ func TestEvmWrite(t *testing.T) { }) require.NoError(t, err) - inputs, err := values.NewMap(map[string]any{ - "signed_report": map[string]any{ - "report": nil, - }, - }) - require.NoError(t, err) - req := capabilities.CapabilityRequest{ - Metadata: capabilities.RequestMetadata{ - WorkflowID: "test-id", - }, - Config: invalidConfig, - Inputs: inputs, + Metadata: validMetadata, + Config: invalidConfig, + Inputs: validInputs, } _, err = capability.Execute(ctx, req) @@ -195,30 +238,13 @@ func TestEvmWrite(t *testing.T) { t.Run("fails when TXM CreateTransaction returns error", func(t *testing.T) { ctx := testutils.Context(t) - capability, err := evm.NewWriteTarget(ctx, relayer, chain, logger.TestLogger(t)) - require.NoError(t, err) - - config, err := values.NewMap(map[string]any{ - "Address": evmCfg.EVM().Workflow().ForwarderAddress().String(), - }) - require.NoError(t, err) - - inputs, err := values.NewMap(map[string]any{ - "signed_report": map[string]any{ - "report": []byte{1, 2, 3}, - "signatures": [][]byte{}, - "context": []byte{4, 5}, - "id": []byte{9, 9}, - }, - }) + capability, err := evm.NewWriteTarget(ctx, relayer, chain, gasLimitDefault, logger.TestLogger(t)) require.NoError(t, err) req := capabilities.CapabilityRequest{ - Metadata: capabilities.RequestMetadata{ - WorkflowID: "test-id", - }, - Config: config, - Inputs: inputs, + Metadata: validMetadata, + Config: validConfig, + Inputs: validInputs, } txManager.On("CreateTransaction", mock.Anything, mock.Anything).Return(txmgr.Tx{}, errors.New("TXM error")) diff --git a/core/services/standardcapabilities/delegate.go b/core/services/standardcapabilities/delegate.go index d072c94846..64134995b2 100644 --- a/core/services/standardcapabilities/delegate.go +++ b/core/services/standardcapabilities/delegate.go @@ -12,6 +12,8 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink-common/pkg/types/core" + gatewayconnector "github.com/smartcontractkit/chainlink/v2/core/capabilities/gateway_connector" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/webapi" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/generic" @@ -26,23 +28,28 @@ type RelayGetter interface { } type Delegate struct { - logger logger.Logger - ds sqlutil.DataSource - jobORM job.ORM - registry core.CapabilitiesRegistry - cfg plugins.RegistrarConfig - monitoringEndpointGen telemetry.MonitoringEndpointGenerator - pipelineRunner pipeline.Runner - relayers RelayGetter + logger logger.Logger + ds sqlutil.DataSource + jobORM job.ORM + registry core.CapabilitiesRegistry + cfg plugins.RegistrarConfig + monitoringEndpointGen telemetry.MonitoringEndpointGenerator + pipelineRunner pipeline.Runner + relayers RelayGetter + gatewayConnectorWrapper *gatewayconnector.ServiceWrapper isNewlyCreatedJob bool } +const ( + commandOverrideForWebAPITrigger = "__builtin_web-api-trigger" +) + func NewDelegate(logger logger.Logger, ds sqlutil.DataSource, jobORM job.ORM, registry core.CapabilitiesRegistry, cfg plugins.RegistrarConfig, monitoringEndpointGen telemetry.MonitoringEndpointGenerator, pipelineRunner pipeline.Runner, - relayers RelayGetter) *Delegate { + relayers RelayGetter, gatewayConnectorWrapper *gatewayconnector.ServiceWrapper) *Delegate { return &Delegate{logger: logger, ds: ds, jobORM: jobORM, registry: registry, cfg: cfg, monitoringEndpointGen: monitoringEndpointGen, pipelineRunner: pipelineRunner, - relayers: relayers, isNewlyCreatedJob: false} + relayers: relayers, isNewlyCreatedJob: false, gatewayConnectorWrapper: gatewayConnectorWrapper} } func (d *Delegate) JobType() job.Type { @@ -67,6 +74,19 @@ func (d *Delegate) ServicesForSpec(ctx context.Context, spec job.Job) ([]job.Ser return nil, fmt.Errorf("failed to create relayer set: %w", err) } + // NOTE: special cases for built-in capabilities (to be moved into LOOPPs in the future) + if spec.StandardCapabilitiesSpec.Command == commandOverrideForWebAPITrigger { + if d.gatewayConnectorWrapper == nil { + return nil, errors.New("gateway connector is required for web API Trigger capability") + } + connector := d.gatewayConnectorWrapper.GetGatewayConnector() + triggerSrvc, err := webapi.NewTrigger(spec.StandardCapabilitiesSpec.Config, d.registry, connector, log) + if err != nil { + return nil, fmt.Errorf("failed to create a Web API Trigger service: %w", err) + } + return []job.ServiceCtx{triggerSrvc}, nil + } + standardCapability := newStandardCapabilities(log, spec.StandardCapabilitiesSpec, d.cfg, telemetryService, kvStore, d.registry, errorLog, pr, relayerSet) diff --git a/core/services/streams/stream.go b/core/services/streams/stream.go index 8825cd3b34..b65c6dc12f 100644 --- a/core/services/streams/stream.go +++ b/core/services/streams/stream.go @@ -3,12 +3,10 @@ package streams import ( "context" "fmt" - "math/big" "sync" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" - "github.com/smartcontractkit/chainlink/v2/core/utils" ) type Runner interface { @@ -94,35 +92,3 @@ func (s *stream) executeRun(ctx context.Context) (*pipeline.Run, pipeline.TaskRu return run, trrs, err } - -// ExtractBigInt returns a result of a pipeline run that returns one single -// decimal result, as a *big.Int. -// This acts as a reference/example method, other methods can be implemented to -// extract any desired type that matches a particular pipeline run output. -// Returns error on parse errors: if results are wrong type -func ExtractBigInt(trrs pipeline.TaskRunResults) (*big.Int, error) { - // pipeline.TaskRunResults comes ordered asc by index, this is guaranteed - // by the pipeline executor - finaltrrs := trrs.Terminals() - - if len(finaltrrs) != 1 { - return nil, fmt.Errorf("invalid number of results, expected: 1, got: %d", len(finaltrrs)) - } - res := finaltrrs[0].Result - if res.Error != nil { - return nil, res.Error - } - val, err := toBigInt(res.Value) - if err != nil { - return nil, fmt.Errorf("failed to parse BenchmarkPrice: %w", err) - } - return val, nil -} - -func toBigInt(val interface{}) (*big.Int, error) { - dec, err := utils.ToDecimal(val) - if err != nil { - return nil, err - } - return dec.BigInt(), nil -} diff --git a/core/services/streams/stream_test.go b/core/services/streams/stream_test.go index 61e5187880..7817413812 100644 --- a/core/services/streams/stream_test.go +++ b/core/services/streams/stream_test.go @@ -2,7 +2,6 @@ package streams import ( "context" - "math/big" "testing" "time" @@ -99,35 +98,3 @@ succeed; }) }) } - -func Test_ExtractBigInt(t *testing.T) { - t.Run("wrong number of inputs", func(t *testing.T) { - trrs := []pipeline.TaskRunResult{} - - _, err := ExtractBigInt(trrs) - assert.EqualError(t, err, "invalid number of results, expected: 1, got: 0") - }) - t.Run("wrong type", func(t *testing.T) { - trrs := []pipeline.TaskRunResult{ - { - Result: pipeline.Result{Value: []byte{1, 2, 3}}, - Task: &MockTask{}, - }, - } - - _, err := ExtractBigInt(trrs) - assert.EqualError(t, err, "failed to parse BenchmarkPrice: type []uint8 cannot be converted to decimal.Decimal ([1 2 3])") - }) - t.Run("correct inputs", func(t *testing.T) { - trrs := []pipeline.TaskRunResult{ - { - Result: pipeline.Result{Value: "122.345"}, - Task: &MockTask{}, - }, - } - - val, err := ExtractBigInt(trrs) - require.NoError(t, err) - assert.Equal(t, big.NewInt(122), val) - }) -} diff --git a/core/services/synchronization/common.go b/core/services/synchronization/common.go index a6c0191e3a..9145a3c9ac 100644 --- a/core/services/synchronization/common.go +++ b/core/services/synchronization/common.go @@ -22,6 +22,7 @@ const ( OCR2S4 TelemetryType = "ocr2-s4" OCR2Median TelemetryType = "ocr2-median" OCR3Mercury TelemetryType = "ocr3-mercury" + OCR3DataFeeds TelemetryType = "ocr3-data-feeds" AutomationCustom TelemetryType = "automation-custom" OCR3Automation TelemetryType = "ocr3-automation" OCR3Rebalancer TelemetryType = "ocr3-rebalancer" diff --git a/core/services/synchronization/helpers_test.go b/core/services/synchronization/helpers_test.go index 7bb2dde763..aea9bf77f4 100644 --- a/core/services/synchronization/helpers_test.go +++ b/core/services/synchronization/helpers_test.go @@ -12,15 +12,15 @@ import ( // NewTestTelemetryIngressClient calls NewTelemetryIngressClient and injects telemClient. func NewTestTelemetryIngressClient(t *testing.T, url *url.URL, serverPubKeyHex string, ks keystore.CSA, logging bool, telemClient telemPb.TelemClient) TelemetryService { - tc := NewTelemetryIngressClient(url, serverPubKeyHex, ks, logging, logger.TestLogger(t), 100, "test", "test") + tc := NewTelemetryIngressClient(url, serverPubKeyHex, ks, logging, logger.TestLogger(t), 100) tc.(*telemetryIngressClient).telemClient = telemClient return tc } // NewTestTelemetryIngressBatchClient calls NewTelemetryIngressBatchClient and injects telemClient. func NewTestTelemetryIngressBatchClient(t *testing.T, url *url.URL, serverPubKeyHex string, ks keystore.CSA, logging bool, telemClient telemPb.TelemClient, sendInterval time.Duration, uniconn bool) TelemetryService { - tc := NewTelemetryIngressBatchClient(url, serverPubKeyHex, ks, logging, logger.TestLogger(t), 100, 50, sendInterval, time.Second, uniconn, "test", "test") - tc.(*telemetryIngressBatchClient).close = func() error { return nil } + tc := NewTelemetryIngressBatchClient(url, serverPubKeyHex, ks, logging, logger.TestLogger(t), 100, 50, sendInterval, time.Second, uniconn) + tc.(*telemetryIngressBatchClient).closeFn = func() error { return nil } tc.(*telemetryIngressBatchClient).telemClient = telemClient return tc } diff --git a/core/services/synchronization/telem/telem.pb.go b/core/services/synchronization/telem/telem.pb.go index 4c86bf3453..0396fd462d 100644 --- a/core/services/synchronization/telem/telem.pb.go +++ b/core/services/synchronization/telem/telem.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.1 -// protoc v4.25.1 +// protoc-gen-go v1.34.2 +// protoc v5.28.0 // source: core/services/synchronization/telem/telem.proto package telem @@ -265,7 +265,7 @@ func file_core_services_synchronization_telem_telem_proto_rawDescGZIP() []byte { } var file_core_services_synchronization_telem_telem_proto_msgTypes = make([]protoimpl.MessageInfo, 3) -var file_core_services_synchronization_telem_telem_proto_goTypes = []interface{}{ +var file_core_services_synchronization_telem_telem_proto_goTypes = []any{ (*TelemRequest)(nil), // 0: telem.TelemRequest (*TelemBatchRequest)(nil), // 1: telem.TelemBatchRequest (*TelemResponse)(nil), // 2: telem.TelemResponse @@ -288,7 +288,7 @@ func file_core_services_synchronization_telem_telem_proto_init() { return } if !protoimpl.UnsafeEnabled { - file_core_services_synchronization_telem_telem_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_core_services_synchronization_telem_telem_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*TelemRequest); i { case 0: return &v.state @@ -300,7 +300,7 @@ func file_core_services_synchronization_telem_telem_proto_init() { return nil } } - file_core_services_synchronization_telem_telem_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + file_core_services_synchronization_telem_telem_proto_msgTypes[1].Exporter = func(v any, i int) any { switch v := v.(*TelemBatchRequest); i { case 0: return &v.state @@ -312,7 +312,7 @@ func file_core_services_synchronization_telem_telem_proto_init() { return nil } } - file_core_services_synchronization_telem_telem_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + file_core_services_synchronization_telem_telem_proto_msgTypes[2].Exporter = func(v any, i int) any { switch v := v.(*TelemResponse); i { case 0: return &v.state diff --git a/core/services/synchronization/telem/telem_automation_custom.pb.go b/core/services/synchronization/telem/telem_automation_custom.pb.go index dd135ed702..1000ee0131 100644 --- a/core/services/synchronization/telem/telem_automation_custom.pb.go +++ b/core/services/synchronization/telem/telem_automation_custom.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.1 -// protoc v4.25.1 +// protoc-gen-go v1.34.2 +// protoc v5.28.0 // source: core/services/synchronization/telem/telem_automation_custom.proto package telem @@ -290,7 +290,7 @@ func file_core_services_synchronization_telem_telem_automation_custom_proto_rawD } var file_core_services_synchronization_telem_telem_automation_custom_proto_msgTypes = make([]protoimpl.MessageInfo, 3) -var file_core_services_synchronization_telem_telem_automation_custom_proto_goTypes = []interface{}{ +var file_core_services_synchronization_telem_telem_automation_custom_proto_goTypes = []any{ (*BlockNumber)(nil), // 0: telem.BlockNumber (*NodeVersion)(nil), // 1: telem.NodeVersion (*AutomationTelemWrapper)(nil), // 2: telem.AutomationTelemWrapper @@ -311,7 +311,7 @@ func file_core_services_synchronization_telem_telem_automation_custom_proto_init return } if !protoimpl.UnsafeEnabled { - file_core_services_synchronization_telem_telem_automation_custom_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_core_services_synchronization_telem_telem_automation_custom_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*BlockNumber); i { case 0: return &v.state @@ -323,7 +323,7 @@ func file_core_services_synchronization_telem_telem_automation_custom_proto_init return nil } } - file_core_services_synchronization_telem_telem_automation_custom_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + file_core_services_synchronization_telem_telem_automation_custom_proto_msgTypes[1].Exporter = func(v any, i int) any { switch v := v.(*NodeVersion); i { case 0: return &v.state @@ -335,7 +335,7 @@ func file_core_services_synchronization_telem_telem_automation_custom_proto_init return nil } } - file_core_services_synchronization_telem_telem_automation_custom_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + file_core_services_synchronization_telem_telem_automation_custom_proto_msgTypes[2].Exporter = func(v any, i int) any { switch v := v.(*AutomationTelemWrapper); i { case 0: return &v.state @@ -348,7 +348,7 @@ func file_core_services_synchronization_telem_telem_automation_custom_proto_init } } } - file_core_services_synchronization_telem_telem_automation_custom_proto_msgTypes[2].OneofWrappers = []interface{}{ + file_core_services_synchronization_telem_telem_automation_custom_proto_msgTypes[2].OneofWrappers = []any{ (*AutomationTelemWrapper_BlockNumber)(nil), (*AutomationTelemWrapper_NodeVersion)(nil), } diff --git a/core/services/synchronization/telem/telem_enhanced_ea.pb.go b/core/services/synchronization/telem/telem_enhanced_ea.pb.go index 901494bcb8..d239ebe6ff 100644 --- a/core/services/synchronization/telem/telem_enhanced_ea.pb.go +++ b/core/services/synchronization/telem/telem_enhanced_ea.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.1 -// protoc v4.25.1 +// protoc-gen-go v1.34.2 +// protoc v5.28.0 // source: core/services/synchronization/telem/telem_enhanced_ea.proto package telem @@ -240,7 +240,7 @@ func file_core_services_synchronization_telem_telem_enhanced_ea_proto_rawDescGZI } var file_core_services_synchronization_telem_telem_enhanced_ea_proto_msgTypes = make([]protoimpl.MessageInfo, 1) -var file_core_services_synchronization_telem_telem_enhanced_ea_proto_goTypes = []interface{}{ +var file_core_services_synchronization_telem_telem_enhanced_ea_proto_goTypes = []any{ (*EnhancedEA)(nil), // 0: telem.EnhancedEA } var file_core_services_synchronization_telem_telem_enhanced_ea_proto_depIdxs = []int32{ @@ -257,7 +257,7 @@ func file_core_services_synchronization_telem_telem_enhanced_ea_proto_init() { return } if !protoimpl.UnsafeEnabled { - file_core_services_synchronization_telem_telem_enhanced_ea_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_core_services_synchronization_telem_telem_enhanced_ea_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*EnhancedEA); i { case 0: return &v.state diff --git a/core/services/synchronization/telem/telem_enhanced_ea_mercury.pb.go b/core/services/synchronization/telem/telem_enhanced_ea_mercury.pb.go index 2d6ce6f7bd..b5dcfdabc0 100644 --- a/core/services/synchronization/telem/telem_enhanced_ea_mercury.pb.go +++ b/core/services/synchronization/telem/telem_enhanced_ea_mercury.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.1 -// protoc v4.25.1 +// protoc-gen-go v1.34.2 +// protoc v5.28.0 // source: core/services/synchronization/telem/telem_enhanced_ea_mercury.proto package telem @@ -21,6 +21,56 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +type MarketStatus int32 + +const ( + // Same values as those used by OCR. + MarketStatus_UNKNOWN MarketStatus = 0 + MarketStatus_CLOSED MarketStatus = 1 + MarketStatus_OPEN MarketStatus = 2 +) + +// Enum value maps for MarketStatus. +var ( + MarketStatus_name = map[int32]string{ + 0: "UNKNOWN", + 1: "CLOSED", + 2: "OPEN", + } + MarketStatus_value = map[string]int32{ + "UNKNOWN": 0, + "CLOSED": 1, + "OPEN": 2, + } +) + +func (x MarketStatus) Enum() *MarketStatus { + p := new(MarketStatus) + *p = x + return p +} + +func (x MarketStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (MarketStatus) Descriptor() protoreflect.EnumDescriptor { + return file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_enumTypes[0].Descriptor() +} + +func (MarketStatus) Type() protoreflect.EnumType { + return &file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_enumTypes[0] +} + +func (x MarketStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use MarketStatus.Descriptor instead. +func (MarketStatus) EnumDescriptor() ([]byte, []int) { + return file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_rawDescGZIP(), []int{0} +} + type EnhancedEAMercury struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -32,6 +82,7 @@ type EnhancedEAMercury struct { DpBid float64 `protobuf:"fixed64,3,opt,name=dp_bid,json=dpBid,proto3" json:"dp_bid,omitempty"` DpAsk float64 `protobuf:"fixed64,4,opt,name=dp_ask,json=dpAsk,proto3" json:"dp_ask,omitempty"` DpInvariantViolationDetected bool `protobuf:"varint,33,opt,name=dp_invariant_violation_detected,json=dpInvariantViolationDetected,proto3" json:"dp_invariant_violation_detected,omitempty"` + BridgeRequestData string `protobuf:"bytes,35,opt,name=bridge_request_data,json=bridgeRequestData,proto3" json:"bridge_request_data,omitempty"` // v1 fields (block range) CurrentBlockNumber int64 `protobuf:"varint,5,opt,name=current_block_number,json=currentBlockNumber,proto3" json:"current_block_number,omitempty"` CurrentBlockHash string `protobuf:"bytes,6,opt,name=current_block_hash,json=currentBlockHash,proto3" json:"current_block_hash,omitempty"` @@ -51,7 +102,7 @@ type EnhancedEAMercury struct { ProviderDataStreamEstablished int64 `protobuf:"varint,12,opt,name=provider_data_stream_established,json=providerDataStreamEstablished,proto3" json:"provider_data_stream_established,omitempty"` ProviderIndicatedTime int64 `protobuf:"varint,13,opt,name=provider_indicated_time,json=providerIndicatedTime,proto3" json:"provider_indicated_time,omitempty"` Feed string `protobuf:"bytes,14,opt,name=feed,proto3" json:"feed,omitempty"` - // v1+v2+v3 + // v1+v2+v3+v4 ObservationBenchmarkPrice int64 `protobuf:"varint,15,opt,name=observation_benchmark_price,json=observationBenchmarkPrice,proto3" json:"observation_benchmark_price,omitempty"` // This value overflows, will be reserved and removed in future versions ObservationBenchmarkPriceString string `protobuf:"bytes,22,opt,name=observation_benchmark_price_string,json=observationBenchmarkPriceString,proto3" json:"observation_benchmark_price_string,omitempty"` // v1+v3 @@ -59,10 +110,12 @@ type EnhancedEAMercury struct { ObservationAsk int64 `protobuf:"varint,17,opt,name=observation_ask,json=observationAsk,proto3" json:"observation_ask,omitempty"` // This value overflows, will be reserved and removed in future versions ObservationBidString string `protobuf:"bytes,23,opt,name=observation_bid_string,json=observationBidString,proto3" json:"observation_bid_string,omitempty"` ObservationAskString string `protobuf:"bytes,24,opt,name=observation_ask_string,json=observationAskString,proto3" json:"observation_ask_string,omitempty"` - ConfigDigest string `protobuf:"bytes,18,opt,name=config_digest,json=configDigest,proto3" json:"config_digest,omitempty"` - Round int64 `protobuf:"varint,19,opt,name=round,proto3" json:"round,omitempty"` - Epoch int64 `protobuf:"varint,20,opt,name=epoch,proto3" json:"epoch,omitempty"` - AssetSymbol string `protobuf:"bytes,21,opt,name=asset_symbol,json=assetSymbol,proto3" json:"asset_symbol,omitempty"` + // v4 + ObservationMarketStatus MarketStatus `protobuf:"varint,34,opt,name=observation_market_status,json=observationMarketStatus,proto3,enum=telem.MarketStatus" json:"observation_market_status,omitempty"` + ConfigDigest string `protobuf:"bytes,18,opt,name=config_digest,json=configDigest,proto3" json:"config_digest,omitempty"` + Round int64 `protobuf:"varint,19,opt,name=round,proto3" json:"round,omitempty"` + Epoch int64 `protobuf:"varint,20,opt,name=epoch,proto3" json:"epoch,omitempty"` + AssetSymbol string `protobuf:"bytes,21,opt,name=asset_symbol,json=assetSymbol,proto3" json:"asset_symbol,omitempty"` } func (x *EnhancedEAMercury) Reset() { @@ -139,6 +192,13 @@ func (x *EnhancedEAMercury) GetDpInvariantViolationDetected() bool { return false } +func (x *EnhancedEAMercury) GetBridgeRequestData() string { + if x != nil { + return x.BridgeRequestData + } + return "" +} + func (x *EnhancedEAMercury) GetCurrentBlockNumber() int64 { if x != nil { return x.CurrentBlockNumber @@ -300,6 +360,13 @@ func (x *EnhancedEAMercury) GetObservationAskString() string { return "" } +func (x *EnhancedEAMercury) GetObservationMarketStatus() MarketStatus { + if x != nil { + return x.ObservationMarketStatus + } + return MarketStatus_UNKNOWN +} + func (x *EnhancedEAMercury) GetConfigDigest() string { if x != nil { return x.ConfigDigest @@ -335,7 +402,7 @@ var file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_raw 0x73, 0x79, 0x6e, 0x63, 0x68, 0x72, 0x6f, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x5f, 0x65, 0x6e, 0x68, 0x61, 0x6e, 0x63, 0x65, 0x64, 0x5f, 0x65, 0x61, 0x5f, 0x6d, 0x65, 0x72, 0x63, 0x75, 0x72, 0x79, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x22, 0xa9, 0x0c, 0x0a, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x22, 0xaa, 0x0d, 0x0a, 0x11, 0x45, 0x6e, 0x68, 0x61, 0x6e, 0x63, 0x65, 0x64, 0x45, 0x41, 0x4d, 0x65, 0x72, 0x63, 0x75, 0x72, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x20, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, @@ -351,6 +418,9 @@ var file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_raw 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x21, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1c, 0x64, 0x70, 0x49, 0x6e, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x56, 0x69, 0x6f, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, + 0x12, 0x2e, 0x0a, 0x13, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x23, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x62, + 0x72, 0x69, 0x64, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x44, 0x61, 0x74, 0x61, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62, @@ -427,19 +497,28 @@ var file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_raw 0x0a, 0x16, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x73, 0x6b, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x18, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x73, 0x6b, 0x53, 0x74, - 0x72, 0x69, 0x6e, 0x67, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x64, - 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x75, - 0x6e, 0x64, 0x18, 0x13, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x12, - 0x14, 0x0a, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, - 0x65, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x73, - 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x73, 0x73, - 0x65, 0x74, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x42, 0x4e, 0x5a, 0x4c, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x6d, 0x61, 0x72, 0x74, 0x63, 0x6f, 0x6e, 0x74, - 0x72, 0x61, 0x63, 0x74, 0x6b, 0x69, 0x74, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x69, 0x6e, - 0x6b, 0x2f, 0x76, 0x32, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x73, 0x2f, 0x73, 0x79, 0x6e, 0x63, 0x68, 0x72, 0x6f, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x72, 0x69, 0x6e, 0x67, 0x12, 0x4f, 0x0a, 0x19, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x22, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x2e, + 0x4d, 0x61, 0x72, 0x6b, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x17, 0x6f, 0x62, + 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x72, 0x6b, 0x65, 0x74, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, + 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, + 0x75, 0x6e, 0x64, 0x18, 0x13, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, + 0x12, 0x14, 0x0a, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, + 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x73, + 0x73, 0x65, 0x74, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x2a, 0x31, 0x0a, 0x0c, 0x4d, 0x61, 0x72, + 0x6b, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, + 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x44, + 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x4f, 0x50, 0x45, 0x4e, 0x10, 0x02, 0x42, 0x4e, 0x5a, 0x4c, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x6d, 0x61, 0x72, 0x74, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x6b, 0x69, 0x74, 0x2f, 0x63, 0x68, 0x61, 0x69, + 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x2f, 0x76, 0x32, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x73, 0x79, 0x6e, 0x63, 0x68, 0x72, 0x6f, 0x6e, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -454,16 +533,19 @@ func file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_ra return file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_rawDescData } +var file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_msgTypes = make([]protoimpl.MessageInfo, 1) -var file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_goTypes = []interface{}{ - (*EnhancedEAMercury)(nil), // 0: telem.EnhancedEAMercury +var file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_goTypes = []any{ + (MarketStatus)(0), // 0: telem.MarketStatus + (*EnhancedEAMercury)(nil), // 1: telem.EnhancedEAMercury } var file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_depIdxs = []int32{ - 0, // [0:0] is the sub-list for method output_type - 0, // [0:0] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name + 0, // 0: telem.EnhancedEAMercury.observation_market_status:type_name -> telem.MarketStatus + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name } func init() { file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_init() } @@ -472,7 +554,7 @@ func file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_in return } if !protoimpl.UnsafeEnabled { - file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*EnhancedEAMercury); i { case 0: return &v.state @@ -490,13 +572,14 @@ func file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_in File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_rawDesc, - NumEnums: 0, + NumEnums: 1, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_goTypes, DependencyIndexes: file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_depIdxs, + EnumInfos: file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_enumTypes, MessageInfos: file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_msgTypes, }.Build() File_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto = out.File diff --git a/core/services/synchronization/telem/telem_enhanced_ea_mercury.proto b/core/services/synchronization/telem/telem_enhanced_ea_mercury.proto index 8488eb1d50..d57b7ca836 100644 --- a/core/services/synchronization/telem/telem_enhanced_ea_mercury.proto +++ b/core/services/synchronization/telem/telem_enhanced_ea_mercury.proto @@ -4,6 +4,13 @@ option go_package = "github.com/smartcontractkit/chainlink/v2/core/services/sync package telem; +enum MarketStatus { + // Same values as those used by OCR. + UNKNOWN = 0; + CLOSED = 1; + OPEN = 2; +} + message EnhancedEAMercury { uint32 version = 32; @@ -12,6 +19,7 @@ message EnhancedEAMercury { double dp_bid=3; double dp_ask=4; bool dp_invariant_violation_detected=33; + string bridge_request_data = 35; // v1 fields (block range) int64 current_block_number=5; @@ -36,7 +44,7 @@ message EnhancedEAMercury { string feed=14; - // v1+v2+v3 + // v1+v2+v3+v4 int64 observation_benchmark_price=15; // This value overflows, will be reserved and removed in future versions string observation_benchmark_price_string = 22; // v1+v3 @@ -44,6 +52,8 @@ message EnhancedEAMercury { int64 observation_ask=17; // This value overflows, will be reserved and removed in future versions string observation_bid_string = 23; string observation_ask_string = 24; + // v4 + MarketStatus observation_market_status=34; string config_digest = 18; int64 round=19; diff --git a/core/services/synchronization/telem/telem_functions_request.pb.go b/core/services/synchronization/telem/telem_functions_request.pb.go index fdf44cfbb7..b2b3427f85 100644 --- a/core/services/synchronization/telem/telem_functions_request.pb.go +++ b/core/services/synchronization/telem/telem_functions_request.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.1 -// protoc v4.25.1 +// protoc-gen-go v1.34.2 +// protoc v5.28.0 // source: core/services/synchronization/telem/telem_functions_request.proto package telem @@ -120,7 +120,7 @@ func file_core_services_synchronization_telem_telem_functions_request_proto_rawD } var file_core_services_synchronization_telem_telem_functions_request_proto_msgTypes = make([]protoimpl.MessageInfo, 1) -var file_core_services_synchronization_telem_telem_functions_request_proto_goTypes = []interface{}{ +var file_core_services_synchronization_telem_telem_functions_request_proto_goTypes = []any{ (*FunctionsRequest)(nil), // 0: telem.FunctionsRequest } var file_core_services_synchronization_telem_telem_functions_request_proto_depIdxs = []int32{ @@ -137,7 +137,7 @@ func file_core_services_synchronization_telem_telem_functions_request_proto_init return } if !protoimpl.UnsafeEnabled { - file_core_services_synchronization_telem_telem_functions_request_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_core_services_synchronization_telem_telem_functions_request_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*FunctionsRequest); i { case 0: return &v.state diff --git a/core/services/synchronization/telem/telem_head_report.pb.go b/core/services/synchronization/telem/telem_head_report.pb.go index 12801314a7..c4101d0656 100644 --- a/core/services/synchronization/telem/telem_head_report.pb.go +++ b/core/services/synchronization/telem/telem_head_report.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.33.0 -// protoc v4.25.1 +// protoc-gen-go v1.34.2 +// protoc v5.28.0 // source: core/services/synchronization/telem/telem_head_report.proto package telem @@ -189,7 +189,7 @@ func file_core_services_synchronization_telem_telem_head_report_proto_rawDescGZI } var file_core_services_synchronization_telem_telem_head_report_proto_msgTypes = make([]protoimpl.MessageInfo, 2) -var file_core_services_synchronization_telem_telem_head_report_proto_goTypes = []interface{}{ +var file_core_services_synchronization_telem_telem_head_report_proto_goTypes = []any{ (*HeadReportRequest)(nil), // 0: telem.HeadReportRequest (*Block)(nil), // 1: telem.Block } @@ -209,7 +209,7 @@ func file_core_services_synchronization_telem_telem_head_report_proto_init() { return } if !protoimpl.UnsafeEnabled { - file_core_services_synchronization_telem_telem_head_report_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_core_services_synchronization_telem_telem_head_report_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*HeadReportRequest); i { case 0: return &v.state @@ -221,7 +221,7 @@ func file_core_services_synchronization_telem_telem_head_report_proto_init() { return nil } } - file_core_services_synchronization_telem_telem_head_report_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + file_core_services_synchronization_telem_telem_head_report_proto_msgTypes[1].Exporter = func(v any, i int) any { switch v := v.(*Block); i { case 0: return &v.state @@ -234,7 +234,7 @@ func file_core_services_synchronization_telem_telem_head_report_proto_init() { } } } - file_core_services_synchronization_telem_telem_head_report_proto_msgTypes[0].OneofWrappers = []interface{}{} + file_core_services_synchronization_telem_telem_head_report_proto_msgTypes[0].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/core/services/synchronization/telem/telem_wsrpc.pb.go b/core/services/synchronization/telem/telem_wsrpc.pb.go index e4028b4de4..e7df2090e4 100644 --- a/core/services/synchronization/telem/telem_wsrpc.pb.go +++ b/core/services/synchronization/telem/telem_wsrpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-wsrpc. DO NOT EDIT. // versions: // - protoc-gen-go-wsrpc v0.0.1 -// - protoc v4.25.1 +// - protoc v5.28.0 package telem diff --git a/core/services/synchronization/telemetry_ingress_batch_client.go b/core/services/synchronization/telemetry_ingress_batch_client.go index cade98cf60..26ce1e3066 100644 --- a/core/services/synchronization/telemetry_ingress_batch_client.go +++ b/core/services/synchronization/telemetry_ingress_batch_client.go @@ -12,8 +12,9 @@ import ( "github.com/smartcontractkit/wsrpc" "github.com/smartcontractkit/wsrpc/examples/simple/keys" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" - "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink-common/pkg/timeutil" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" telemPb "github.com/smartcontractkit/chainlink/v2/core/services/synchronization/telem" ) @@ -37,21 +38,18 @@ func (NoopTelemetryIngressBatchClient) Name() string { return func (NoopTelemetryIngressBatchClient) Ready() error { return nil } type telemetryIngressBatchClient struct { - services.StateMachine + services.Service + eng *services.Engine + url *url.URL ks keystore.CSA serverPubKeyHex string connected atomic.Bool telemClient telemPb.TelemClient - close func() error - - globalLogger logger.Logger - logging bool - lggr logger.Logger + closeFn func() error - wgDone sync.WaitGroup - chDone services.StopChan + logging bool telemBufferSize uint telemMaxBatchSize uint @@ -66,8 +64,8 @@ type telemetryIngressBatchClient struct { // NewTelemetryIngressBatchClient returns a client backed by wsrpc that // can send telemetry to the telemetry ingress server -func NewTelemetryIngressBatchClient(url *url.URL, serverPubKeyHex string, ks keystore.CSA, logging bool, lggr logger.Logger, telemBufferSize uint, telemMaxBatchSize uint, telemSendInterval time.Duration, telemSendTimeout time.Duration, useUniconn bool, network string, chainID string) TelemetryService { - return &telemetryIngressBatchClient{ +func NewTelemetryIngressBatchClient(url *url.URL, serverPubKeyHex string, ks keystore.CSA, logging bool, lggr logger.Logger, telemBufferSize uint, telemMaxBatchSize uint, telemSendInterval time.Duration, telemSendTimeout time.Duration, useUniconn bool) TelemetryService { + c := &telemetryIngressBatchClient{ telemBufferSize: telemBufferSize, telemMaxBatchSize: telemMaxBatchSize, telemSendInterval: telemSendInterval, @@ -75,13 +73,17 @@ func NewTelemetryIngressBatchClient(url *url.URL, serverPubKeyHex string, ks key url: url, ks: ks, serverPubKeyHex: serverPubKeyHex, - globalLogger: lggr, logging: logging, - lggr: lggr.Named("TelemetryIngressBatchClient").Named(network).Named(chainID), - chDone: make(services.StopChan), workers: make(map[string]*telemetryIngressBatchWorker), useUniConn: useUniconn, } + c.Service, c.eng = services.Config{ + Name: "TelemetryIngressBatchClient", + Start: c.start, + Close: c.close, + }.NewServiceEngine(lggr) + + return c } // Start connects the wsrpc client to the telemetry ingress server @@ -90,71 +92,53 @@ func NewTelemetryIngressBatchClient(url *url.URL, serverPubKeyHex string, ks key // an error and wsrpc will continue to retry the connection. Eventually when the ingress // server does come back up, wsrpc will establish the connection without any interaction // on behalf of the node operator. -func (tc *telemetryIngressBatchClient) Start(ctx context.Context) error { - return tc.StartOnce("TelemetryIngressBatchClient", func() error { - clientPrivKey, err := tc.getCSAPrivateKey() - if err != nil { - return err - } +func (tc *telemetryIngressBatchClient) start(ctx context.Context) error { + clientPrivKey, err := tc.getCSAPrivateKey() + if err != nil { + return err + } - serverPubKey := keys.FromHex(tc.serverPubKeyHex) - - // Initialize a new wsrpc client caller - // This is used to call RPC methods on the server - if tc.telemClient == nil { // only preset for tests - if tc.useUniConn { - tc.wgDone.Add(1) - go func() { - defer tc.wgDone.Done() - ctx2, cancel := tc.chDone.NewCtx() - defer cancel() - conn, err := wsrpc.DialUniWithContext(ctx2, tc.lggr, tc.url.String(), clientPrivKey, serverPubKey) - if err != nil { - if ctx2.Err() != nil { - tc.lggr.Warnw("gave up connecting to telemetry endpoint", "err", err) - } else { - tc.lggr.Criticalw("telemetry endpoint dial errored unexpectedly", "err", err, "server pubkey", tc.serverPubKeyHex) - tc.SvcErrBuffer.Append(err) - } - return - } - tc.telemClient = telemPb.NewTelemClient(conn) - tc.close = conn.Close - tc.connected.Store(true) - }() - } else { - // Spawns a goroutine that will eventually connect - conn, err := wsrpc.DialWithContext(ctx, tc.url.String(), wsrpc.WithTransportCreds(clientPrivKey, serverPubKey), wsrpc.WithLogger(tc.lggr)) + serverPubKey := keys.FromHex(tc.serverPubKeyHex) + + // Initialize a new wsrpc client caller + // This is used to call RPC methods on the server + if tc.telemClient == nil { // only preset for tests + if tc.useUniConn { + tc.eng.Go(func(ctx context.Context) { + conn, err := wsrpc.DialUniWithContext(ctx, tc.eng, tc.url.String(), clientPrivKey, serverPubKey) if err != nil { - return fmt.Errorf("could not start TelemIngressBatchClient, Dial returned error: %v", err) + if ctx.Err() != nil { + tc.eng.Warnw("gave up connecting to telemetry endpoint", "err", err) + } else { + tc.eng.Criticalw("telemetry endpoint dial errored unexpectedly", "err", err, "server pubkey", tc.serverPubKeyHex) + tc.eng.EmitHealthErr(err) + } + return } tc.telemClient = telemPb.NewTelemClient(conn) - tc.close = func() error { conn.Close(); return nil } + tc.closeFn = conn.Close + tc.connected.Store(true) + }) + } else { + // Spawns a goroutine that will eventually connect + conn, err := wsrpc.DialWithContext(ctx, tc.url.String(), wsrpc.WithTransportCreds(clientPrivKey, serverPubKey), wsrpc.WithLogger(tc.eng)) + if err != nil { + return fmt.Errorf("could not start TelemIngressBatchClient, Dial returned error: %v", err) } + tc.telemClient = telemPb.NewTelemClient(conn) + tc.closeFn = func() error { conn.Close(); return nil } } + } - return nil - }) + return nil } // Close disconnects the wsrpc client from the ingress server and waits for all workers to exit -func (tc *telemetryIngressBatchClient) Close() error { - return tc.StopOnce("TelemetryIngressBatchClient", func() error { - close(tc.chDone) - tc.wgDone.Wait() - if (tc.useUniConn && tc.connected.Load()) || !tc.useUniConn { - return tc.close() - } - return nil - }) -} - -func (tc *telemetryIngressBatchClient) Name() string { - return tc.lggr.Name() -} - -func (tc *telemetryIngressBatchClient) HealthReport() map[string]error { - return map[string]error{tc.Name(): tc.Healthy()} +func (tc *telemetryIngressBatchClient) close() error { + if (tc.useUniConn && tc.connected.Load()) || !tc.useUniConn { + return tc.closeFn() + } + return nil } // getCSAPrivateKey gets the client's CSA private key @@ -175,7 +159,7 @@ func (tc *telemetryIngressBatchClient) getCSAPrivateKey() (privkey []byte, err e // and a warning is logged. func (tc *telemetryIngressBatchClient) Send(ctx context.Context, telemData []byte, contractID string, telemType TelemetryType) { if tc.useUniConn && !tc.connected.Load() { - tc.lggr.Warnw("not connected to telemetry endpoint", "endpoint", tc.url.String()) + tc.eng.Warnw("not connected to telemetry endpoint", "endpoint", tc.url.String()) return } payload := TelemPayload{ @@ -206,18 +190,17 @@ func (tc *telemetryIngressBatchClient) findOrCreateWorker(payload TelemPayload) if !found { worker = NewTelemetryIngressBatchWorker( tc.telemMaxBatchSize, - tc.telemSendInterval, tc.telemSendTimeout, tc.telemClient, - &tc.wgDone, - tc.chDone, make(chan TelemPayload, tc.telemBufferSize), payload.ContractID, payload.TelemType, - tc.globalLogger, + tc.eng, tc.logging, ) - worker.Start() + tc.eng.GoTick(timeutil.NewTicker(func() time.Duration { + return tc.telemSendInterval + }), worker.Send) tc.workers[workerKey] = worker } diff --git a/core/services/synchronization/telemetry_ingress_batch_worker.go b/core/services/synchronization/telemetry_ingress_batch_worker.go index e7ea659581..7eca26f02c 100644 --- a/core/services/synchronization/telemetry_ingress_batch_worker.go +++ b/core/services/synchronization/telemetry_ingress_batch_worker.go @@ -2,13 +2,12 @@ package synchronization import ( "context" - "sync" "sync/atomic" "time" "github.com/smartcontractkit/chainlink-common/pkg/services" - "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink-common/pkg/logger" telemPb "github.com/smartcontractkit/chainlink/v2/core/services/synchronization/telem" ) @@ -18,11 +17,8 @@ type telemetryIngressBatchWorker struct { services.Service telemMaxBatchSize uint - telemSendInterval time.Duration telemSendTimeout time.Duration telemClient telemPb.TelemClient - wgDone *sync.WaitGroup - chDone services.StopChan chTelemetry chan TelemPayload contractID string telemType TelemetryType @@ -35,65 +31,45 @@ type telemetryIngressBatchWorker struct { // telemetry to the ingress server via WSRPC func NewTelemetryIngressBatchWorker( telemMaxBatchSize uint, - telemSendInterval time.Duration, telemSendTimeout time.Duration, telemClient telemPb.TelemClient, - wgDone *sync.WaitGroup, - chDone chan struct{}, chTelemetry chan TelemPayload, contractID string, telemType TelemetryType, - globalLogger logger.Logger, + lggr logger.Logger, logging bool, ) *telemetryIngressBatchWorker { return &telemetryIngressBatchWorker{ - telemSendInterval: telemSendInterval, telemSendTimeout: telemSendTimeout, telemMaxBatchSize: telemMaxBatchSize, telemClient: telemClient, - wgDone: wgDone, - chDone: chDone, chTelemetry: chTelemetry, contractID: contractID, telemType: telemType, logging: logging, - lggr: globalLogger.Named("TelemetryIngressBatchWorker"), + lggr: logger.Named(lggr, "TelemetryIngressBatchWorker"), } } -// Start sends batched telemetry to the ingress server on an interval -func (tw *telemetryIngressBatchWorker) Start() { - tw.wgDone.Add(1) - sendTicker := time.NewTicker(tw.telemSendInterval) - - go func() { - defer tw.wgDone.Done() - - for { - select { - case <-sendTicker.C: - if len(tw.chTelemetry) == 0 { - continue - } +// Send sends batched telemetry to the ingress server on an interval +func (tw *telemetryIngressBatchWorker) Send(ctx context.Context) { + if len(tw.chTelemetry) == 0 { + return + } - // Send batched telemetry to the ingress server, log any errors - telemBatchReq := tw.BuildTelemBatchReq() - ctx, cancel := tw.chDone.CtxCancel(context.WithTimeout(context.Background(), tw.telemSendTimeout)) - _, err := tw.telemClient.TelemBatch(ctx, telemBatchReq) - cancel() + // Send batched telemetry to the ingress server, log any errors + telemBatchReq := tw.BuildTelemBatchReq() + ctx, cancel := context.WithTimeout(ctx, tw.telemSendTimeout) + _, err := tw.telemClient.TelemBatch(ctx, telemBatchReq) + cancel() - if err != nil { - tw.lggr.Warnf("Could not send telemetry: %v", err) - continue - } - if tw.logging { - tw.lggr.Debugw("Successfully sent telemetry to ingress server", "contractID", telemBatchReq.ContractId, "telemType", telemBatchReq.TelemetryType, "telemetry", telemBatchReq.Telemetry) - } - case <-tw.chDone: - return - } - } - }() + if err != nil { + tw.lggr.Warnf("Could not send telemetry: %v", err) + return + } + if tw.logging { + tw.lggr.Debugw("Successfully sent telemetry to ingress server", "contractID", telemBatchReq.ContractId, "telemType", telemBatchReq.TelemetryType, "telemetry", telemBatchReq.Telemetry) + } } // logBufferFullWithExpBackoff logs messages at diff --git a/core/services/synchronization/telemetry_ingress_batch_worker_test.go b/core/services/synchronization/telemetry_ingress_batch_worker_test.go index 109022c713..bf44ee9195 100644 --- a/core/services/synchronization/telemetry_ingress_batch_worker_test.go +++ b/core/services/synchronization/telemetry_ingress_batch_worker_test.go @@ -1,7 +1,6 @@ package synchronization_test import ( - "sync" "testing" "time" @@ -22,11 +21,8 @@ func TestTelemetryIngressWorker_BuildTelemBatchReq(t *testing.T) { chTelemetry := make(chan synchronization.TelemPayload, 10) worker := synchronization.NewTelemetryIngressBatchWorker( uint(maxTelemBatchSize), - time.Millisecond*1, time.Second, mocks.NewTelemClient(t), - &sync.WaitGroup{}, - make(chan struct{}), chTelemetry, "0xa", synchronization.OCR, diff --git a/core/services/synchronization/telemetry_ingress_client.go b/core/services/synchronization/telemetry_ingress_client.go index dc4ced31d0..1ed55bb546 100644 --- a/core/services/synchronization/telemetry_ingress_client.go +++ b/core/services/synchronization/telemetry_ingress_client.go @@ -4,15 +4,14 @@ import ( "context" "errors" "net/url" - "sync" "sync/atomic" "time" "github.com/smartcontractkit/wsrpc" "github.com/smartcontractkit/wsrpc/examples/simple/keys" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" telemPb "github.com/smartcontractkit/chainlink/v2/core/services/synchronization/telem" ) @@ -35,82 +34,59 @@ func (NoopTelemetryIngressClient) Name() string { return "Noop func (NoopTelemetryIngressClient) Ready() error { return nil } type telemetryIngressClient struct { - services.StateMachine + services.Service + eng *services.Engine + url *url.URL ks keystore.CSA serverPubKeyHex string telemClient telemPb.TelemClient logging bool - lggr logger.Logger - wgDone sync.WaitGroup - chDone services.StopChan dropMessageCount atomic.Uint32 chTelemetry chan TelemPayload } // NewTelemetryIngressClient returns a client backed by wsrpc that // can send telemetry to the telemetry ingress server -func NewTelemetryIngressClient(url *url.URL, serverPubKeyHex string, ks keystore.CSA, logging bool, lggr logger.Logger, telemBufferSize uint, network string, chainID string) TelemetryService { - return &telemetryIngressClient{ +func NewTelemetryIngressClient(url *url.URL, serverPubKeyHex string, ks keystore.CSA, logging bool, lggr logger.Logger, telemBufferSize uint) TelemetryService { + c := &telemetryIngressClient{ url: url, ks: ks, serverPubKeyHex: serverPubKeyHex, logging: logging, - lggr: lggr.Named("TelemetryIngressClient").Named(network).Named(chainID), chTelemetry: make(chan TelemPayload, telemBufferSize), - chDone: make(services.StopChan), } + c.Service, c.eng = services.Config{ + Name: "TelemetryIngressClient", + Start: c.start, + }.NewServiceEngine(lggr) + return c } // Start connects the wsrpc client to the telemetry ingress server -func (tc *telemetryIngressClient) Start(context.Context) error { - return tc.StartOnce("TelemetryIngressClient", func() error { - privkey, err := tc.getCSAPrivateKey() - if err != nil { - return err - } - - tc.connect(privkey) - - return nil - }) -} - -// Close disconnects the wsrpc client from the ingress server -func (tc *telemetryIngressClient) Close() error { - return tc.StopOnce("TelemetryIngressClient", func() error { - close(tc.chDone) - tc.wgDone.Wait() - return nil - }) -} +func (tc *telemetryIngressClient) start(context.Context) error { + privkey, err := tc.getCSAPrivateKey() + if err != nil { + return err + } -func (tc *telemetryIngressClient) Name() string { - return tc.lggr.Name() -} + tc.connect(privkey) -func (tc *telemetryIngressClient) HealthReport() map[string]error { - return map[string]error{tc.Name(): tc.Healthy()} + return nil } func (tc *telemetryIngressClient) connect(clientPrivKey []byte) { - tc.wgDone.Add(1) - - go func() { - defer tc.wgDone.Done() - ctx, cancel := tc.chDone.NewCtx() - defer cancel() - + tc.eng.Go(func(ctx context.Context) { serverPubKey := keys.FromHex(tc.serverPubKeyHex) - conn, err := wsrpc.DialWithContext(ctx, tc.url.String(), wsrpc.WithTransportCreds(clientPrivKey, serverPubKey), wsrpc.WithLogger(tc.lggr)) + conn, err := wsrpc.DialWithContext(ctx, tc.url.String(), wsrpc.WithTransportCreds(clientPrivKey, serverPubKey), wsrpc.WithLogger(tc.eng)) if err != nil { if ctx.Err() != nil { - tc.lggr.Warnw("gave up connecting to telemetry endpoint", "err", err) + tc.eng.Warnw("gave up connecting to telemetry endpoint", "err", err) } else { - tc.lggr.Criticalw("telemetry endpoint dial errored unexpectedly", "err", err) - tc.SvcErrBuffer.Append(err) + tc.eng.Criticalw("telemetry endpoint dial errored unexpectedly", "err", err) + tc.eng.EmitHealthErr(err) } return } @@ -126,16 +102,12 @@ func (tc *telemetryIngressClient) connect(clientPrivKey []byte) { tc.handleTelemetry() // Wait for close - <-tc.chDone - }() + <-ctx.Done() + }) } func (tc *telemetryIngressClient) handleTelemetry() { - tc.wgDone.Add(1) - go func() { - defer tc.wgDone.Done() - ctx, cancel := tc.chDone.NewCtx() - defer cancel() + tc.eng.Go(func(ctx context.Context) { for { select { case p := <-tc.chTelemetry: @@ -148,17 +120,17 @@ func (tc *telemetryIngressClient) handleTelemetry() { } _, err := tc.telemClient.Telem(ctx, telemReq) if err != nil { - tc.lggr.Errorf("Could not send telemetry: %v", err) + tc.eng.Errorf("Could not send telemetry: %v", err) continue } if tc.logging { - tc.lggr.Debugw("successfully sent telemetry to ingress server", "contractID", p.ContractID, "telemetry", p.Telemetry) + tc.eng.Debugw("successfully sent telemetry to ingress server", "contractID", p.ContractID, "telemetry", p.Telemetry) } - case <-tc.chDone: + case <-ctx.Done(): return } } - }() + }) } // logBufferFullWithExpBackoff logs messages at @@ -176,7 +148,7 @@ func (tc *telemetryIngressClient) handleTelemetry() { func (tc *telemetryIngressClient) logBufferFullWithExpBackoff(payload TelemPayload) { count := tc.dropMessageCount.Add(1) if count > 0 && (count%100 == 0 || count&(count-1) == 0) { - tc.lggr.Warnw("telemetry ingress client buffer full, dropping message", "telemetry", payload.Telemetry, "droppedCount", count) + tc.eng.Warnw("telemetry ingress client buffer full, dropping message", "telemetry", payload.Telemetry, "droppedCount", count) } } diff --git a/core/services/telemetry/manager.go b/core/services/telemetry/manager.go index a65759a5c6..7b788c4806 100644 --- a/core/services/telemetry/manager.go +++ b/core/services/telemetry/manager.go @@ -1,29 +1,29 @@ package telemetry import ( - "context" "net/url" "strings" "time" "github.com/pkg/errors" - "go.uber.org/multierr" - "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + common "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink/v2/core/config" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" "github.com/smartcontractkit/chainlink/v2/core/services/synchronization" ) type Manager struct { - services.StateMachine - bufferSize uint - endpoints []*telemetryEndpoint - ks keystore.CSA - lggr logger.Logger + services.Service + eng *services.Engine + + bufferSize uint + endpoints []*telemetryEndpoint + ks keystore.CSA + logging bool maxBatchSize uint sendInterval time.Duration @@ -45,9 +45,7 @@ type telemetryEndpoint struct { func NewManager(cfg config.TelemetryIngress, csaKeyStore keystore.CSA, lggr logger.Logger) *Manager { m := &Manager{ bufferSize: cfg.BufferSize(), - endpoints: nil, ks: csaKeyStore, - lggr: lggr.Named("TelemetryManager"), logging: cfg.Logging(), maxBatchSize: cfg.MaxBatchSize(), sendInterval: cfg.SendInterval(), @@ -55,44 +53,21 @@ func NewManager(cfg config.TelemetryIngress, csaKeyStore keystore.CSA, lggr logg uniConn: cfg.UniConn(), useBatchSend: cfg.UseBatchSend(), } - for _, e := range cfg.Endpoints() { - if err := m.addEndpoint(e); err != nil { - m.lggr.Error(err) - } - } - return m -} - -func (m *Manager) Start(ctx context.Context) error { - return m.StartOnce("TelemetryManager", func() error { - var err error - for _, e := range m.endpoints { - err = multierr.Append(err, e.client.Start(ctx)) - } - return err - }) -} -func (m *Manager) Close() error { - return m.StopOnce("TelemetryManager", func() error { - var err error - for _, e := range m.endpoints { - err = multierr.Append(err, e.client.Close()) - } - return err - }) -} - -func (m *Manager) Name() string { - return m.lggr.Name() -} + m.Service, m.eng = services.Config{ + Name: "TelemetryManager", + NewSubServices: func(lggr common.Logger) (subs []services.Service) { + for _, e := range cfg.Endpoints() { + if sub, err := m.newEndpoint(e, lggr, cfg); err != nil { + lggr.Error(err) + } else { + subs = append(subs, sub) + } + } + return + }, + }.NewServiceEngine(lggr) -func (m *Manager) HealthReport() map[string]error { - hr := map[string]error{m.Name(): m.Healthy()} - - for _, e := range m.endpoints { - services.CopyHealth(hr, e.client.HealthReport()) - } - return hr + return m } // GenMonitoringEndpoint creates a new monitoring endpoints based on the existing available endpoints defined in the core config TOML, if no endpoint for the network and chainID exists, a NOOP agent will be used and the telemetry will not be sent @@ -100,7 +75,7 @@ func (m *Manager) GenMonitoringEndpoint(network string, chainID string, contract e, found := m.getEndpoint(network, chainID) if !found { - m.lggr.Warnf("no telemetry endpoint found for network %q chainID %q, telemetry %q for contactID %q will NOT be sent", network, chainID, telemType, contractID) + m.eng.Warnf("no telemetry endpoint found for network %q chainID %q, telemetry %q for contractID %q will NOT be sent", network, chainID, telemType, contractID) return &NoopAgent{} } @@ -111,32 +86,33 @@ func (m *Manager) GenMonitoringEndpoint(network string, chainID string, contract return NewIngressAgent(e.client, network, chainID, contractID, telemType) } -func (m *Manager) addEndpoint(e config.TelemetryIngressEndpoint) error { +func (m *Manager) newEndpoint(e config.TelemetryIngressEndpoint, lggr logger.Logger, cfg config.TelemetryIngress) (services.Service, error) { if e.Network() == "" { - return errors.New("cannot add telemetry endpoint, network cannot be empty") + return nil, errors.New("cannot add telemetry endpoint, network cannot be empty") } if e.ChainID() == "" { - return errors.New("cannot add telemetry endpoint, chainID cannot be empty") + return nil, errors.New("cannot add telemetry endpoint, chainID cannot be empty") } if e.URL() == nil { - return errors.New("cannot add telemetry endpoint, URL cannot be empty") + return nil, errors.New("cannot add telemetry endpoint, URL cannot be empty") } if e.ServerPubKey() == "" { - return errors.New("cannot add telemetry endpoint, ServerPubKey cannot be empty") + return nil, errors.New("cannot add telemetry endpoint, ServerPubKey cannot be empty") } if _, found := m.getEndpoint(e.Network(), e.ChainID()); found { - return errors.Errorf("cannot add telemetry endpoint for network %q and chainID %q, endpoint already exists", e.Network(), e.ChainID()) + return nil, errors.Errorf("cannot add telemetry endpoint for network %q and chainID %q, endpoint already exists", e.Network(), e.ChainID()) } + lggr = logger.Sugared(lggr).Named(e.Network()).Named(e.ChainID()) var tClient synchronization.TelemetryService if m.useBatchSend { - tClient = synchronization.NewTelemetryIngressBatchClient(e.URL(), e.ServerPubKey(), m.ks, m.logging, m.lggr, m.bufferSize, m.maxBatchSize, m.sendInterval, m.sendTimeout, m.uniConn, e.Network(), e.ChainID()) + tClient = synchronization.NewTelemetryIngressBatchClient(e.URL(), e.ServerPubKey(), m.ks, cfg.Logging(), lggr, cfg.BufferSize(), cfg.MaxBatchSize(), cfg.SendInterval(), cfg.SendTimeout(), cfg.UniConn()) } else { - tClient = synchronization.NewTelemetryIngressClient(e.URL(), e.ServerPubKey(), m.ks, m.logging, m.lggr, m.bufferSize, e.Network(), e.ChainID()) + tClient = synchronization.NewTelemetryIngressClient(e.URL(), e.ServerPubKey(), m.ks, cfg.Logging(), lggr, cfg.BufferSize()) } te := telemetryEndpoint{ @@ -148,7 +124,7 @@ func (m *Manager) addEndpoint(e config.TelemetryIngressEndpoint) error { } m.endpoints = append(m.endpoints, &te) - return nil + return te.client, nil } func (m *Manager) getEndpoint(network string, chainID string) (*telemetryEndpoint, bool) { diff --git a/core/services/telemetry/manager_test.go b/core/services/telemetry/manager_test.go index 4e55cb7575..fef065b572 100644 --- a/core/services/telemetry/manager_test.go +++ b/core/services/telemetry/manager_test.go @@ -156,7 +156,7 @@ func TestNewManager(t *testing.T) { require.Equal(t, uint(123), m.bufferSize) require.Equal(t, ks, m.ks) - require.Equal(t, "TelemetryManager", m.lggr.Name()) + require.Equal(t, "TelemetryManager", m.Name()) require.Equal(t, true, m.logging) require.Equal(t, uint(51), m.maxBatchSize) require.Equal(t, time.Millisecond*512, m.sendInterval) diff --git a/core/services/versioning/orm_test.go b/core/services/versioning/orm_test.go index 3504c2bc77..44c63f60d0 100644 --- a/core/services/versioning/orm_test.go +++ b/core/services/versioning/orm_test.go @@ -97,6 +97,86 @@ func Test_Version_CheckVersion(t *testing.T) { assert.Equal(t, "9.9.8", dbv.String()) } +func TestORM_CheckVersion_CCIP(t *testing.T) { + ctx := testutils.Context(t) + db := pgtest.NewSqlxDB(t) + + lggr := logger.TestLogger(t) + + orm := NewORM(db, lggr) + + tests := []struct { + name string + currentVersion string + newVersion string + expectedError bool + }{ + { + name: "ccip patch version bump from 0 -> 2", + currentVersion: "2.5.0-ccip1.4.0", + newVersion: "2.5.0-ccip1.4.2", + expectedError: false, + }, + { + name: "ccip patch downgrade errors", + currentVersion: "2.5.0-ccip1.4.2", + newVersion: "2.5.0-ccip1.4.1", + expectedError: true, + }, + { + name: "ccip patch version bump from 2 -> 10", + currentVersion: "2.5.0-ccip1.4.2", + newVersion: "2.5.0-ccip1.4.10", + expectedError: false, + }, + { + name: "ccip patch version bump from 9 -> 101", + currentVersion: "2.5.0-ccip1.4.9", + newVersion: "2.5.0-ccip1.4.101", + expectedError: false, + }, + { + name: "upgrading only core version", + currentVersion: "2.5.0-ccip1.4.10", + newVersion: "2.6.0-ccip1.4.10", + expectedError: false, + }, + { + name: "downgrading only core version errors", + currentVersion: "2.6.0-ccip1.4.10", + newVersion: "2.5.0-ccip1.4.10", + expectedError: true, + }, + { + name: "upgrading both core and ccip version", + currentVersion: "2.5.0-ccip1.4.10", + newVersion: "2.6.0-ccip1.4.11", + expectedError: false, + }, + { + name: "upgrading both core and ccip version but minor version", + currentVersion: "2.5.0-ccip1.4.10", + newVersion: "2.6.0-ccip1.5.0", + expectedError: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, err := db.ExecContext(ctx, `TRUNCATE node_versions;`) + require.NoError(t, err) + + require.NoError(t, orm.UpsertNodeVersion(ctx, NewNodeVersion(test.currentVersion))) + _, _, err = CheckVersion(ctx, db, lggr, test.newVersion) + if test.expectedError { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + func TestORM_NodeVersion_FindLatestNodeVersion(t *testing.T) { ctx := testutils.Context(t) db := pgtest.NewSqlxDB(t) diff --git a/core/services/vrf/delegate_test.go b/core/services/vrf/delegate_test.go index 889b19d0e0..9718dc376a 100644 --- a/core/services/vrf/delegate_test.go +++ b/core/services/vrf/delegate_test.go @@ -83,7 +83,7 @@ func buildVrfUni(t *testing.T, db *sqlx.DB, cfg chainlink.GeneralConfig) vrfUniv btORM := bridges.NewORM(db) ks := keystore.NewInMemory(db, utils.FastScryptParams, lggr) _, dbConfig, evmConfig := txmgr.MakeTestConfigs(t) - txm, err := txmgr.NewTxm(db, evmConfig, evmConfig.GasEstimator(), evmConfig.Transactions(), nil, dbConfig, dbConfig.Listener(), ec, logger.TestLogger(t), nil, ks.Eth(), nil) + txm, err := txmgr.NewTxm(db, evmConfig, evmConfig.GasEstimator(), evmConfig.Transactions(), nil, dbConfig, dbConfig.Listener(), ec, logger.TestLogger(t), nil, ks.Eth(), nil, nil) orm := headtracker.NewORM(*testutils.FixtureChainID, db) require.NoError(t, orm.IdempotentInsertHead(testutils.Context(t), cltest.Head(51))) jrm := job.NewORM(db, prm, btORM, ks, lggr) diff --git a/core/services/vrf/extraargs/types.go b/core/services/vrf/extraargs/types.go index eecd0bfa33..540de0f015 100644 --- a/core/services/vrf/extraargs/types.go +++ b/core/services/vrf/extraargs/types.go @@ -13,7 +13,7 @@ const boolAbiType = `[{ "type": "bool" }]` var extraArgsV1Tag = crypto.Keccak256([]byte("VRF ExtraArgsV1"))[:4] -func FromExtraArgsV1(extraArgs []byte) (nativePayment bool, err error) { +func DecodeV1(extraArgs []byte) (nativePayment bool, err error) { decodedBool, err := utils.ABIDecode(boolAbiType, extraArgs[functionSignatureLength:]) if err != nil { return false, fmt.Errorf("failed to decode 0x%x to bool", extraArgs[functionSignatureLength:]) @@ -25,7 +25,7 @@ func FromExtraArgsV1(extraArgs []byte) (nativePayment bool, err error) { return nativePayment, nil } -func ExtraArgsV1(nativePayment bool) ([]byte, error) { +func EncodeV1(nativePayment bool) ([]byte, error) { encodedArgs, err := utils.ABIEncode(boolAbiType, nativePayment) if err != nil { return nil, err diff --git a/core/services/vrf/v1/integration_test.go b/core/services/vrf/v1/integration_test.go index d7f791ad29..125388b6b2 100644 --- a/core/services/vrf/v1/integration_test.go +++ b/core/services/vrf/v1/integration_test.go @@ -16,6 +16,7 @@ import ( "gopkg.in/guregu/null.v4" commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" ubig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/solidity_vrf_coordinator_interface" diff --git a/core/services/vrf/v2/coordinator_v2x_interface.go b/core/services/vrf/v2/coordinator_v2x_interface.go index 3162156258..35e8bcd470 100644 --- a/core/services/vrf/v2/coordinator_v2x_interface.go +++ b/core/services/vrf/v2/coordinator_v2x_interface.go @@ -250,7 +250,7 @@ func (c *coordinatorV2_5) ParseRandomWordsFulfilled(log types.Log) (RandomWordsF } func (c *coordinatorV2_5) RequestRandomWords(opts *bind.TransactOpts, keyHash [32]byte, subID *big.Int, requestConfirmations uint16, callbackGasLimit uint32, numWords uint32, payInEth bool) (*types.Transaction, error) { - extraArgs, err := extraargs.ExtraArgsV1(payInEth) + extraArgs, err := extraargs.EncodeV1(payInEth) if err != nil { return nil, err } @@ -569,7 +569,7 @@ func (r *v2_5RandomWordsRequested) CallbackGasLimit() uint32 { } func (r *v2_5RandomWordsRequested) NativePayment() bool { - nativePayment, err := extraargs.FromExtraArgsV1(r.event.ExtraArgs) + nativePayment, err := extraargs.DecodeV1(r.event.ExtraArgs) if err != nil { panic(err) } @@ -1073,7 +1073,7 @@ func (r *RequestCommitment) NativePayment() bool { if r.VRFVersion == vrfcommon.V2 { return false } - nativePayment, err := extraargs.FromExtraArgsV1(r.V2Plus.ExtraArgs) + nativePayment, err := extraargs.DecodeV1(r.V2Plus.ExtraArgs) if err != nil { panic(err) } diff --git a/core/services/vrf/v2/integration_v2_plus_test.go b/core/services/vrf/v2/integration_v2_plus_test.go index 3aa2302e55..b4c47c3e0b 100644 --- a/core/services/vrf/v2/integration_v2_plus_test.go +++ b/core/services/vrf/v2/integration_v2_plus_test.go @@ -17,6 +17,7 @@ import ( "github.com/stretchr/testify/require" commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" @@ -965,7 +966,7 @@ func requestAndEstimateFulfillmentCost( requestLog := FindLatestRandomnessRequestedLog(t, uni.rootContract, vrfkey.PublicKey.MustHash(), nil) s, err := proof.BigToSeed(requestLog.PreSeed()) require.NoError(t, err) - extraArgs, err := extraargs.ExtraArgsV1(nativePayment) + extraArgs, err := extraargs.EncodeV1(nativePayment) require.NoError(t, err) proof, rc, err := proof.GenerateProofResponseV2Plus(app.GetKeyStore().VRF(), vrfkey.ID(), proof.PreSeedDataV2Plus{ PreSeed: s, diff --git a/core/services/vrf/v2/integration_v2_test.go b/core/services/vrf/v2/integration_v2_test.go index becee19aaa..33ed5a76b5 100644 --- a/core/services/vrf/v2/integration_v2_test.go +++ b/core/services/vrf/v2/integration_v2_test.go @@ -142,7 +142,7 @@ func makeTestTxm(t *testing.T, txStore txmgr.TestEvmTxStore, keyStore keystore.M _, _, evmConfig := txmgr.MakeTestConfigs(t) txmConfig := txmgr.NewEvmTxmConfig(evmConfig) txm := txmgr.NewEvmTxm(ec.ConfiguredChainID(), txmConfig, evmConfig.Transactions(), keyStore.Eth(), logger.TestLogger(t), nil, nil, - nil, txStore, nil, nil, nil, nil) + nil, txStore, nil, nil, nil, nil, nil) return txm } diff --git a/core/services/vrf/v2/listener_v2_test.go b/core/services/vrf/v2/listener_v2_test.go index ac59f1fdb6..b7a8710c4f 100644 --- a/core/services/vrf/v2/listener_v2_test.go +++ b/core/services/vrf/v2/listener_v2_test.go @@ -40,7 +40,7 @@ func makeTestTxm(t *testing.T, txStore txmgr.TestEvmTxStore, keyStore keystore.M ec := evmtest.NewEthClientMockWithDefaultChain(t) txmConfig := txmgr.NewEvmTxmConfig(evmConfig) txm := txmgr.NewEvmTxm(ec.ConfiguredChainID(), txmConfig, evmConfig.Transactions(), keyStore.Eth(), logger.TestLogger(t), nil, nil, - nil, txStore, nil, nil, nil, nil) + nil, txStore, nil, nil, nil, nil, nil) return txm } diff --git a/core/services/workflows/delegate.go b/core/services/workflows/delegate.go index acd006940f..e20cff244b 100644 --- a/core/services/workflows/delegate.go +++ b/core/services/workflows/delegate.go @@ -34,15 +34,21 @@ func (d *Delegate) BeforeJobDeleted(spec job.Job) {} func (d *Delegate) OnDeleteJob(context.Context, job.Job) error { return nil } // ServicesForSpec satisfies the job.Delegate interface. -func (d *Delegate) ServicesForSpec(_ context.Context, spec job.Job) ([]job.ServiceCtx, error) { +func (d *Delegate) ServicesForSpec(ctx context.Context, spec job.Job) ([]job.ServiceCtx, error) { + sdkSpec, err := spec.WorkflowSpec.SDKSpec(ctx) + if err != nil { + return nil, err + } + cfg := Config{ Lggr: d.logger, - Spec: spec.WorkflowSpec.Workflow, + Workflow: sdkSpec, WorkflowID: spec.WorkflowSpec.WorkflowID, WorkflowOwner: spec.WorkflowSpec.WorkflowOwner, WorkflowName: spec.WorkflowSpec.WorkflowName, Registry: d.registry, Store: d.store, + Config: []byte(spec.WorkflowSpec.Config), } engine, err := NewEngine(cfg) if err != nil { @@ -59,7 +65,7 @@ func NewDelegate( return &Delegate{logger: logger, registry: registry, store: store} } -func ValidatedWorkflowJobSpec(tomlString string) (job.Job, error) { +func ValidatedWorkflowJobSpec(ctx context.Context, tomlString string) (job.Job, error) { var jb = job.Job{ExternalJobID: uuid.New()} tree, err := toml.Load(tomlString) @@ -81,16 +87,21 @@ func ValidatedWorkflowJobSpec(tomlString string) (job.Job, error) { return jb, fmt.Errorf("toml unmarshal error on workflow spec: %w", err) } - err = spec.Validate() + sdkSpec, err := spec.SDKSpec(ctx) if err != nil { - return jb, fmt.Errorf("invalid WorkflowSpec: %w", err) + return jb, fmt.Errorf("failed to convert to sdk workflow spec: %w", err) } // ensure the embedded workflow graph is valid - _, err = Parse(spec.Workflow) - if err != nil { + if _, err = Parse(sdkSpec); err != nil { return jb, fmt.Errorf("failed to parse workflow graph: %w", err) } + + err = spec.Validate(ctx) + if err != nil { + return jb, fmt.Errorf("invalid WorkflowSpec: %w", err) + } + jb.WorkflowSpec = &spec jb.WorkflowSpecID = &spec.ID diff --git a/core/services/workflows/delegate_test.go b/core/services/workflows/delegate_test.go index a12eac80bb..d27a1012e6 100644 --- a/core/services/workflows/delegate_test.go +++ b/core/services/workflows/delegate_test.go @@ -5,6 +5,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/services/workflows" "github.com/smartcontractkit/chainlink/v2/core/testdata/testspecs" ) @@ -99,7 +100,7 @@ schemaVersion = 1 for _, tc := range tt { tc := tc t.Run(tc.name, func(t *testing.T) { - _, err := workflows.ValidatedWorkflowJobSpec(tc.workflowTomlFn()) + _, err := workflows.ValidatedWorkflowJobSpec(testutils.Context(t), tc.workflowTomlFn()) if tc.valid { require.NoError(t, err) } else { diff --git a/core/services/workflows/engine.go b/core/services/workflows/engine.go index ed5daaf210..8abfbcea5b 100644 --- a/core/services/workflows/engine.go +++ b/core/services/workflows/engine.go @@ -16,6 +16,8 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/types/core" "github.com/smartcontractkit/chainlink-common/pkg/values" "github.com/smartcontractkit/chainlink-common/pkg/workflows" + "github.com/smartcontractkit/chainlink-common/pkg/workflows/exec" + "github.com/smartcontractkit/chainlink-common/pkg/workflows/sdk" "github.com/smartcontractkit/chainlink/v2/core/capabilities/transmission" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -36,7 +38,7 @@ type Engine struct { localNode capabilities.Node executionStates store.Store pendingStepRequests chan stepRequest - triggerEvents chan capabilities.CapabilityResponse + triggerEvents chan capabilities.TriggerResponse stepUpdateCh chan store.WorkflowExecutionStep wg sync.WaitGroup stopCh services.StopChan @@ -180,7 +182,7 @@ func (e *Engine) initializeCapability(ctx context.Context, step *step) error { // We configure actions, consensus and targets here, and // they all satisfy the `CallbackCapability` interface - cc, ok := cp.(capabilities.CallbackCapability) + cc, ok := cp.(capabilities.ExecutableCapability) if !ok { return newCPErr("capability does not satisfy CallbackCapability") } @@ -319,32 +321,25 @@ func generateTriggerId(workflowID string, triggerIdx int) string { // registerTrigger is used during the initialization phase to bind a trigger to this workflow func (e *Engine) registerTrigger(ctx context.Context, t *triggerCapability, triggerIdx int) error { triggerID := generateTriggerId(e.workflow.id, triggerIdx) - triggerInputs, err := values.NewMap( - map[string]any{ - "triggerId": triggerID, - }, - ) - if err != nil { - return err - } tc, err := values.NewMap(t.Config) if err != nil { return err } - t.config = tc + t.config.Store(tc) - triggerRegRequest := capabilities.CapabilityRequest{ + triggerRegRequest := capabilities.TriggerRegistrationRequest{ Metadata: capabilities.RequestMetadata{ WorkflowID: e.workflow.id, + WorkflowOwner: e.workflow.owner, + WorkflowName: e.workflow.name, WorkflowDonID: e.localNode.WorkflowDON.ID, WorkflowDonConfigVersion: e.localNode.WorkflowDON.ConfigVersion, - WorkflowName: e.workflow.name, - WorkflowOwner: e.workflow.owner, + ReferenceID: t.Ref, }, - Config: tc, - Inputs: triggerInputs, + Config: t.config.Load(), + TriggerID: triggerID, } eventsCh, err := t.trigger.RegisterTrigger(ctx, triggerRegRequest) if err != nil { @@ -422,10 +417,10 @@ func (e *Engine) loop(ctx context.Context) { continue } - te := &capabilities.TriggerEvent{} - err := resp.Value.UnwrapTo(te) - if err != nil { - e.logger.Errorf("could not unwrap trigger event; error %v", resp.Err) + te := resp.Event + + if te.ID == "" { + e.logger.With(tIDKey, te.TriggerType).Error("trigger event ID is empty; not executing") continue } @@ -435,7 +430,7 @@ func (e *Engine) loop(ctx context.Context) { continue } - err = e.startExecution(ctx, executionID, resp.Value) + err = e.startExecution(ctx, executionID, resp.Event.Outputs) if err != nil { e.logger.With(eIDKey, executionID).Errorf("failed to start execution: %v", err) } @@ -466,7 +461,7 @@ func generateExecutionID(workflowID, eventID string) (string, error) { } // startExecution kicks off a new workflow execution when a trigger event is received. -func (e *Engine) startExecution(ctx context.Context, executionID string, event values.Value) error { +func (e *Engine) startExecution(ctx context.Context, executionID string, event *values.Map) error { e.logger.With("event", event, eIDKey, executionID).Debug("executing on a trigger event") ec := &store.WorkflowExecution{ Steps: map[string]*store.WorkflowExecutionStep{ @@ -713,6 +708,10 @@ func (e *Engine) configForStep(ctx context.Context, executionID string, step *st return step.config, nil } + if capConfig.DefaultConfig == nil { + return step.config, nil + } + // Merge the configs for now; note that this means that a workflow can override // all of the config set by the capability. This is probably not desirable in // the long-term, but we don't know much about those use cases so stick to a simpler @@ -734,7 +733,7 @@ func (e *Engine) executeStep(ctx context.Context, msg stepRequest) (*values.Map, inputs = step.Inputs.Mapping } - i, err := findAndInterpolateAllKeys(inputs, msg.state) + i, err := exec.FindAndInterpolateAllKeys(inputs, msg.state) if err != nil { return nil, nil, err } @@ -759,36 +758,30 @@ func (e *Engine) executeStep(ctx context.Context, msg stepRequest) (*values.Map, WorkflowName: e.workflow.name, WorkflowDonID: e.localNode.WorkflowDON.ID, WorkflowDonConfigVersion: e.localNode.WorkflowDON.ConfigVersion, + ReferenceID: msg.stepRef, }, } - output, err := executeSyncAndUnwrapSingleValue(ctx, step.capability, tr) + output, err := step.capability.Execute(ctx, tr) if err != nil { return inputsMap, nil, err } - return inputsMap, output, err + return inputsMap, output.Value, err } func (e *Engine) deregisterTrigger(ctx context.Context, t *triggerCapability, triggerIdx int) error { - triggerInputs, err := values.NewMap( - map[string]any{ - "triggerId": generateTriggerId(e.workflow.id, triggerIdx), - }, - ) - if err != nil { - return err - } - deregRequest := capabilities.CapabilityRequest{ + deregRequest := capabilities.TriggerRegistrationRequest{ Metadata: capabilities.RequestMetadata{ WorkflowID: e.workflow.id, WorkflowDonID: e.localNode.WorkflowDON.ID, WorkflowDonConfigVersion: e.localNode.WorkflowDON.ConfigVersion, WorkflowName: e.workflow.name, WorkflowOwner: e.workflow.owner, + ReferenceID: t.Ref, }, - Inputs: triggerInputs, - Config: t.config, + TriggerID: generateTriggerId(e.workflow.id, triggerIdx), + Config: t.config.Load(), } // if t.trigger == nil, then we haven't initialized the workflow @@ -860,7 +853,7 @@ func (e *Engine) Close() error { } type Config struct { - Spec string + Workflow sdk.WorkflowSpec WorkflowID string WorkflowOwner string WorkflowName string @@ -871,6 +864,7 @@ type Config struct { NewWorkerTimeout time.Duration MaxExecutionDuration time.Duration Store store.Store + Config []byte // For testing purposes only maxRetries int @@ -936,7 +930,7 @@ func NewEngine(cfg Config) (engine *Engine, err error) { // - that the resulting graph is strongly connected (i.e. no disjointed subgraphs exist) // - etc. - workflow, err := Parse(cfg.Spec) + workflow, err := Parse(cfg.Workflow) if err != nil { return nil, err } @@ -952,7 +946,7 @@ func NewEngine(cfg Config) (engine *Engine, err error) { executionStates: cfg.Store, pendingStepRequests: make(chan stepRequest, cfg.QueueSize), stepUpdateCh: make(chan store.WorkflowExecutionStep), - triggerEvents: make(chan capabilities.CapabilityResponse), + triggerEvents: make(chan capabilities.TriggerResponse), stopCh: make(chan struct{}), newWorkerTimeout: cfg.NewWorkerTimeout, maxExecutionDuration: cfg.MaxExecutionDuration, @@ -967,24 +961,6 @@ func NewEngine(cfg Config) (engine *Engine, err error) { return engine, nil } -// ExecuteSyncAndUnwrapSingleValue is a convenience method that executes a capability synchronously and unwraps the -// result if it is a single value otherwise returns the list. -func executeSyncAndUnwrapSingleValue(ctx context.Context, cap capabilities.CallbackCapability, req capabilities.CapabilityRequest) (values.Value, error) { - l, err := capabilities.ExecuteSync(ctx, cap, req) - if err != nil { - return nil, err - } - - // `ExecuteSync` returns a `values.List` even if there was - // just one return value. If that is the case, let's unwrap the - // single value to make it easier to use in -- for example -- variable interpolation. - if len(l.Underlying) > 1 { - return l, nil - } - - return l.Underlying[0], nil -} - // Logging keys const ( cIDKey = "capabilityID" diff --git a/core/services/workflows/engine_test.go b/core/services/workflows/engine_test.go index b8d5a9591e..9bd1b28537 100644 --- a/core/services/workflows/engine_test.go +++ b/core/services/workflows/engine_test.go @@ -11,17 +11,21 @@ import ( "github.com/shopspring/decimal" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + capabilitiespb "github.com/smartcontractkit/chainlink-common/pkg/capabilities/pb" "github.com/smartcontractkit/chainlink-common/pkg/services/servicetest" "github.com/smartcontractkit/chainlink-common/pkg/values" "github.com/smartcontractkit/chainlink-common/pkg/workflows" + coreCap "github.com/smartcontractkit/chainlink/v2/core/capabilities" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" p2ptypes "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types" + "github.com/smartcontractkit/chainlink/v2/core/services/registrysyncer" "github.com/smartcontractkit/chainlink/v2/core/services/workflows/store" ) @@ -101,7 +105,7 @@ func newTestDBStore(t *testing.T, clock clockwork.Clock) store.Store { type testConfigProvider struct { localNode func(ctx context.Context) (capabilities.Node, error) - configForCapability func(ctx context.Context, capabilityID string, donID uint32) (capabilities.CapabilityConfiguration, error) + configForCapability func(ctx context.Context, capabilityID string, donID uint32) (registrysyncer.CapabilityConfiguration, error) } func (t testConfigProvider) LocalNode(ctx context.Context) (capabilities.Node, error) { @@ -118,12 +122,12 @@ func (t testConfigProvider) LocalNode(ctx context.Context) (capabilities.Node, e }, nil } -func (t testConfigProvider) ConfigForCapability(ctx context.Context, capabilityID string, donID uint32) (capabilities.CapabilityConfiguration, error) { +func (t testConfigProvider) ConfigForCapability(ctx context.Context, capabilityID string, donID uint32) (registrysyncer.CapabilityConfiguration, error) { if t.configForCapability != nil { return t.configForCapability(ctx, capabilityID, donID) } - return capabilities.CapabilityConfiguration{DefaultConfig: values.EmptyMap()}, nil + return registrysyncer.CapabilityConfiguration{}, nil } // newTestEngine creates a new engine with some test defaults. @@ -133,12 +137,18 @@ func newTestEngine(t *testing.T, reg *coreCap.Registry, spec string, opts ...fun executionFinished := make(chan string, 100) clock := clockwork.NewFakeClock() + sdkSpec, err := (&job.WorkflowSpec{ + Workflow: spec, + SpecType: job.YamlSpec, + }).SDKSpec(testutils.Context(t)) + require.NoError(t, err) + reg.SetLocalRegistry(&testConfigProvider{}) cfg := Config{ WorkflowID: testWorkflowId, Lggr: logger.TestLogger(t), Registry: reg, - Spec: spec, + Workflow: sdkSpec, maxRetries: 1, retryMs: 100, afterInit: func(success bool) { @@ -183,7 +193,7 @@ func getExecutionId(t *testing.T, eng *Engine, hooks *testHooks) string { type mockCapability struct { capabilities.CapabilityInfo - capabilities.CallbackExecutable + capabilities.Executable response chan capabilities.CapabilityResponse transform func(capabilities.CapabilityRequest) (capabilities.CapabilityResponse, error) } @@ -196,18 +206,14 @@ func newMockCapability(info capabilities.CapabilityInfo, transform func(capabili } } -func (m *mockCapability) Execute(ctx context.Context, req capabilities.CapabilityRequest) (<-chan capabilities.CapabilityResponse, error) { +func (m *mockCapability) Execute(ctx context.Context, req capabilities.CapabilityRequest) (capabilities.CapabilityResponse, error) { cr, err := m.transform(req) if err != nil { - return nil, err + return capabilities.CapabilityResponse{}, err } - ch := make(chan capabilities.CapabilityResponse, 10) - m.response <- cr - ch <- cr - close(ch) - return ch, nil + return cr, nil } func (m *mockCapability) RegisterToWorkflow(ctx context.Context, request capabilities.RegisterToWorkflowRequest) error { @@ -220,20 +226,20 @@ func (m *mockCapability) UnregisterFromWorkflow(ctx context.Context, request cap type mockTriggerCapability struct { capabilities.CapabilityInfo - triggerEvent *capabilities.CapabilityResponse - ch chan capabilities.CapabilityResponse + triggerEvent *capabilities.TriggerResponse + ch chan capabilities.TriggerResponse } var _ capabilities.TriggerCapability = (*mockTriggerCapability)(nil) -func (m *mockTriggerCapability) RegisterTrigger(ctx context.Context, req capabilities.CapabilityRequest) (<-chan capabilities.CapabilityResponse, error) { +func (m *mockTriggerCapability) RegisterTrigger(ctx context.Context, req capabilities.TriggerRegistrationRequest) (<-chan capabilities.TriggerResponse, error) { if m.triggerEvent != nil { m.ch <- *m.triggerEvent } return m.ch, nil } -func (m *mockTriggerCapability) UnregisterTrigger(ctx context.Context, req capabilities.CapabilityRequest) error { +func (m *mockTriggerCapability) UnregisterTrigger(ctx context.Context, req capabilities.TriggerRegistrationRequest) error { return nil } @@ -272,8 +278,11 @@ func TestEngineWithHardcodedWorkflow(t *testing.T) { servicetest.Run(t, eng) eid := getExecutionId(t, eng, testHooks) - assert.Equal(t, cr, <-target1.response) - assert.Equal(t, cr, <-target2.response) + resp1 := <-target1.response + assert.Equal(t, cr.Event.Outputs, resp1.Value) + + resp2 := <-target2.response + assert.Equal(t, cr.Event.Outputs, resp2.Value) state, err := eng.executionStates.Get(ctx, eid) require.NoError(t, err) @@ -324,14 +333,14 @@ targets: ` ) -func mockTrigger(t *testing.T) (capabilities.TriggerCapability, capabilities.CapabilityResponse) { +func mockTrigger(t *testing.T) (capabilities.TriggerCapability, capabilities.TriggerResponse) { mt := &mockTriggerCapability{ CapabilityInfo: capabilities.MustNewCapabilityInfo( "mercury-trigger@1.0.0", capabilities.CapabilityTypeTrigger, "issues a trigger when a mercury report is received.", ), - ch: make(chan capabilities.CapabilityResponse, 10), + ch: make(chan capabilities.TriggerResponse, 10), } resp, err := values.NewMap(map[string]any{ "123": decimal.NewFromFloat(1.00), @@ -339,11 +348,15 @@ func mockTrigger(t *testing.T) (capabilities.TriggerCapability, capabilities.Cap "789": decimal.NewFromFloat(1.50), }) require.NoError(t, err) - cr := capabilities.CapabilityResponse{ - Value: resp, + tr := capabilities.TriggerResponse{ + Event: capabilities.TriggerEvent{ + TriggerType: mt.ID, + ID: time.Now().UTC().Format(time.RFC3339), + Outputs: resp, + }, } - mt.triggerEvent = &cr - return mt, cr + mt.triggerEvent = &tr + return mt, tr } func mockNoopTrigger(t *testing.T) capabilities.TriggerCapability { @@ -353,7 +366,7 @@ func mockNoopTrigger(t *testing.T) capabilities.TriggerCapability { capabilities.CapabilityTypeTrigger, "issues a trigger when a mercury report is received.", ), - ch: make(chan capabilities.CapabilityResponse, 10), + ch: make(chan capabilities.TriggerResponse, 10), } return mt } @@ -379,10 +392,9 @@ func mockConsensusWithEarlyTermination() *mockCapability { "an ocr3 consensus capability", ), func(req capabilities.CapabilityRequest) (capabilities.CapabilityResponse, error) { - return capabilities.CapabilityResponse{ + return capabilities.CapabilityResponse{}, // copy error object to make sure message comparison works as expected - Err: errors.New(capabilities.ErrStopExecution.Error()), - }, nil + errors.New(capabilities.ErrStopExecution.Error()) }, ) } @@ -548,7 +560,7 @@ func TestEngine_MultiStepDependencies(t *testing.T) { ctx := testutils.Context(t) reg := coreCap.NewRegistry(logger.TestLogger(t)) - trigger, cr := mockTrigger(t) + trigger, tr := mockTrigger(t) require.NoError(t, reg.Add(ctx, trigger)) require.NoError(t, reg.Add(ctx, mockConsensus())) @@ -575,9 +587,10 @@ func TestEngine_MultiStepDependencies(t *testing.T) { obs := unw.(map[string]any)["observations"] assert.Len(t, obs, 2) - tunw, err := values.Unwrap(cr.Value) require.NoError(t, err) - assert.Equal(t, obs.([]any)[0], tunw) + uo, err := values.Unwrap(tr.Event.Outputs) + require.NoError(t, err) + assert.Equal(t, obs.([]any)[0].(map[string]any), uo) o, err := values.Unwrap(out) require.NoError(t, err) @@ -1028,11 +1041,9 @@ func TestEngine_MergesWorkflowConfigAndCRConfig(t *testing.T) { simpleWorkflow, ) reg.SetLocalRegistry(testConfigProvider{ - configForCapability: func(ctx context.Context, capabilityID string, donID uint32) (capabilities.CapabilityConfiguration, error) { + configForCapability: func(ctx context.Context, capabilityID string, donID uint32) (registrysyncer.CapabilityConfiguration, error) { if capabilityID != writeID { - return capabilities.CapabilityConfiguration{ - DefaultConfig: values.EmptyMap(), - }, nil + return registrysyncer.CapabilityConfiguration{}, nil } cm, err := values.WrapMap(map[string]any{ @@ -1040,12 +1051,15 @@ func TestEngine_MergesWorkflowConfigAndCRConfig(t *testing.T) { "schedule": "allAtOnce", }) if err != nil { - return capabilities.CapabilityConfiguration{}, err + return registrysyncer.CapabilityConfiguration{}, err } - return capabilities.CapabilityConfiguration{ - DefaultConfig: cm, - }, nil + cb, err := proto.Marshal(&capabilitiespb.CapabilityConfig{ + DefaultConfig: values.ProtoMap(cm), + }) + return registrysyncer.CapabilityConfiguration{ + Config: cb, + }, err }, }) @@ -1063,3 +1077,54 @@ func TestEngine_MergesWorkflowConfigAndCRConfig(t *testing.T) { assert.Equal(t, m.(map[string]any)["deltaStage"], "1s") assert.Equal(t, m.(map[string]any)["schedule"], "allAtOnce") } + +func TestEngine_HandlesNilConfigOnchain(t *testing.T) { + ctx := testutils.Context(t) + reg := coreCap.NewRegistry(logger.TestLogger(t)) + + trigger, _ := mockTrigger(t) + + require.NoError(t, reg.Add(ctx, trigger)) + require.NoError(t, reg.Add(ctx, mockConsensus())) + writeID := "write_polygon-testnet-mumbai@1.0.0" + + gotConfig := values.EmptyMap() + target := newMockCapability( + // Create a remote capability so we don't use the local transmission protocol. + capabilities.MustNewRemoteCapabilityInfo( + writeID, + capabilities.CapabilityTypeTarget, + "a write capability targeting polygon testnet", + &capabilities.DON{ID: 1}, + ), + func(req capabilities.CapabilityRequest) (capabilities.CapabilityResponse, error) { + gotConfig = req.Config + + return capabilities.CapabilityResponse{ + Value: req.Inputs, + }, nil + }, + ) + require.NoError(t, reg.Add(ctx, target)) + + eng, testHooks := newTestEngine( + t, + reg, + simpleWorkflow, + ) + reg.SetLocalRegistry(testConfigProvider{}) + + servicetest.Run(t, eng) + + eid := getExecutionId(t, eng, testHooks) + + state, err := eng.executionStates.Get(ctx, eid) + require.NoError(t, err) + + assert.Equal(t, state.Status, store.StatusCompleted) + + m, err := values.Unwrap(gotConfig) + require.NoError(t, err) + // The write target config contains three keys + assert.Len(t, m.(map[string]any), 3) +} diff --git a/core/services/workflows/models.go b/core/services/workflows/models.go index 8d970dfa94..2935a7d6cc 100644 --- a/core/services/workflows/models.go +++ b/core/services/workflows/models.go @@ -3,12 +3,14 @@ package workflows import ( "errors" "fmt" + "sync/atomic" "github.com/dominikbraun/graph" "github.com/smartcontractkit/chainlink-common/pkg/capabilities" "github.com/smartcontractkit/chainlink-common/pkg/values" "github.com/smartcontractkit/chainlink-common/pkg/workflows" + "github.com/smartcontractkit/chainlink-common/pkg/workflows/sdk" ) // workflow is a directed graph of nodes, where each node is a step. @@ -24,7 +26,7 @@ type workflow struct { triggers []*triggerCapability - spec *workflows.WorkflowSpec + spec *sdk.WorkflowSpec } func (w *workflow) walkDo(start string, do func(s *step) error) error { @@ -78,23 +80,26 @@ func (w *workflow) dependents(start string) ([]*step, error) { // step wraps a Vertex with additional context for execution that is mutated by the engine type step struct { workflows.Vertex - capability capabilities.CallbackCapability + capability capabilities.ExecutableCapability info capabilities.CapabilityInfo config *values.Map } type triggerCapability struct { - workflows.StepDefinition + sdk.StepDefinition trigger capabilities.TriggerCapability - config *values.Map + + config atomic.Pointer[values.Map] } -func Parse(yamlWorkflow string) (*workflow, error) { - wf2, err := workflows.ParseDependencyGraph(yamlWorkflow) +func Parse(sdkSpec sdk.WorkflowSpec) (*workflow, error) { + wf2, err := workflows.BuildDependencyGraph(sdkSpec) if err != nil { return nil, err } - return createWorkflow(wf2) + + wfs, err := createWorkflow(wf2) + return wfs, err } // createWorkflow converts a StaticWorkflow to an executable workflow diff --git a/core/services/workflows/models_test.go b/core/services/workflows/models_test.go index a28aeb9df0..ecb9c1995b 100644 --- a/core/services/workflows/models_test.go +++ b/core/services/workflows/models_test.go @@ -7,6 +7,9 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-common/pkg/workflows" + + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/services/job" ) func TestParse_Graph(t *testing.T) { @@ -290,7 +293,10 @@ targets: for _, tc := range testCases { t.Run(tc.name, func(st *testing.T) { - wf, err := Parse(tc.yaml) + spec, err := job.YAMLSpecFactory{}.Spec(testutils.Context(t), []byte(tc.yaml), nil) + require.NoError(t, err) + + wf, err := Parse(spec) if tc.errMsg != "" { assert.ErrorContains(st, err, tc.errMsg) } else { @@ -316,7 +322,10 @@ targets: } func TestParsesIntsCorrectly(t *testing.T) { - wf, err := Parse(hardcodedWorkflow) + spec, err := job.YAMLSpecFactory{}.Spec(testutils.Context(t), []byte(hardcodedWorkflow), nil) + require.NoError(t, err) + + wf, err := Parse(spec) require.NoError(t, err) n, err := wf.Vertex("evm_median") diff --git a/core/services/workflows/state.go b/core/services/workflows/state.go index 6fc61af395..3a67f82a1e 100644 --- a/core/services/workflows/state.go +++ b/core/services/workflows/state.go @@ -1,14 +1,9 @@ package workflows import ( - "fmt" - "strconv" - "strings" - "github.com/smartcontractkit/chainlink/v2/core/services/workflows/store" "github.com/smartcontractkit/chainlink-common/pkg/values" - "github.com/smartcontractkit/chainlink-common/pkg/workflows" ) // copyState returns a deep copy of the input executionState @@ -17,12 +12,10 @@ func copyState(es store.WorkflowExecution) store.WorkflowExecution { for ref, step := range es.Steps { var mval *values.Map if step.Inputs != nil { - mp := values.Proto(step.Inputs).GetMapValue() - mval = values.FromMapValueProto(mp) + mval = step.Inputs.CopyMap() } - op := values.Proto(step.Outputs.Value) - copiedov := values.FromProto(op) + copiedov := values.Copy(step.Outputs.Value) newState := &store.WorkflowExecutionStep{ ExecutionID: step.ExecutionID, @@ -46,93 +39,3 @@ func copyState(es store.WorkflowExecution) store.WorkflowExecution { Steps: steps, } } - -// interpolateKey takes a multi-part, dot-separated key and attempts to replace -// it with its corresponding value in `state`. -// -// A key is valid if it contains at least two parts, with: -// - the first part being the workflow step's `ref` variable -// - the second part being one of `inputs` or `outputs` -// -// If a key has more than two parts, then we traverse the parts -// to find the value we want to replace. -// We support traversing both nested maps and lists and any combination of the two. -func interpolateKey(key string, state store.WorkflowExecution) (any, error) { - parts := strings.Split(key, ".") - - if len(parts) < 2 { - return "", fmt.Errorf("cannot interpolate %s: must have at least two parts", key) - } - - // lookup the step we want to get either input or output state from - sc, ok := state.Steps[parts[0]] - if !ok { - return "", fmt.Errorf("could not find ref `%s`", parts[0]) - } - - var value values.Value - switch parts[1] { - case "inputs": - value = sc.Inputs - case "outputs": - if sc.Outputs.Err != nil { - return "", fmt.Errorf("cannot interpolate ref part `%s` in `%+v`: step has errored", parts[1], sc) - } - - value = sc.Outputs.Value - default: - return "", fmt.Errorf("cannot interpolate ref part `%s` in `%+v`: second part must be `inputs` or `outputs`", parts[1], sc) - } - - val, err := values.Unwrap(value) - if err != nil { - return "", err - } - - remainingParts := parts[2:] - for _, r := range remainingParts { - switch v := val.(type) { - case map[string]any: - inner, ok := v[r] - if !ok { - return "", fmt.Errorf("could not find ref part `%s` (ref: `%s`) in `%+v`", r, key, v) - } - - val = inner - case []any: - i, err := strconv.Atoi(r) - if err != nil { - return "", fmt.Errorf("could not interpolate ref part `%s` (ref: `%s`) in `%+v`: `%s` is not convertible to an int", r, key, v, r) - } - - if (i > len(v)-1) || (i < 0) { - return "", fmt.Errorf("could not interpolate ref part `%s` (ref: `%s`) in `%+v`: index out of bounds %d", r, key, v, i) - } - - val = v[i] - default: - return "", fmt.Errorf("could not interpolate ref part `%s` (ref: `%s`) in `%+v`", r, key, val) - } - } - - return val, nil -} - -// findAndInterpolateAllKeys takes an `input` any value, and recursively -// identifies any values that should be replaced from `state`. -// -// A value `v` should be replaced if it is wrapped as follows: `$(v)`. -func findAndInterpolateAllKeys(input any, state store.WorkflowExecution) (any, error) { - return workflows.DeepMap( - input, - func(el string) (any, error) { - matches := workflows.InterpolationTokenRe.FindStringSubmatch(el) - if len(matches) < 2 { - return el, nil - } - - interpolatedVar := matches[1] - return interpolateKey(interpolatedVar, state) - }, - ) -} diff --git a/core/services/workflows/state_test.go b/core/services/workflows/state_test.go deleted file mode 100644 index a9829a97c7..0000000000 --- a/core/services/workflows/state_test.go +++ /dev/null @@ -1,268 +0,0 @@ -package workflows - -import ( - "errors" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink-common/pkg/values" - "github.com/smartcontractkit/chainlink/v2/core/services/workflows/store" -) - -func TestInterpolateKey(t *testing.T) { - t.Parallel() - val, err := values.NewMap( - map[string]any{ - "reports": map[string]any{ - "inner": "key", - }, - "reportsList": []any{ - "listElement", - }, - }, - ) - require.NoError(t, err) - - testCases := []struct { - name string - key string - state store.WorkflowExecution - expected any - errMsg string - }{ - { - name: "digging into a string", - key: "evm_median.outputs.reports", - state: store.WorkflowExecution{ - Steps: map[string]*store.WorkflowExecutionStep{ - "evm_median": { - Outputs: store.StepOutput{ - Value: values.NewString(""), - }, - }, - }, - }, - errMsg: "could not interpolate ref part `reports` (ref: `evm_median.outputs.reports`) in ``", - }, - { - name: "ref doesn't exist", - key: "evm_median.outputs.reports", - state: store.WorkflowExecution{ - Steps: map[string]*store.WorkflowExecutionStep{}, - }, - errMsg: "could not find ref `evm_median`", - }, - { - name: "less than 2 parts", - key: "evm_median", - state: store.WorkflowExecution{ - Steps: map[string]*store.WorkflowExecutionStep{}, - }, - errMsg: "must have at least two parts", - }, - { - name: "second part isn't `inputs` or `outputs`", - key: "evm_median.foo", - state: store.WorkflowExecution{ - Steps: map[string]*store.WorkflowExecutionStep{ - "evm_median": { - Outputs: store.StepOutput{ - Value: values.NewString(""), - }, - }, - }, - }, - errMsg: "second part must be `inputs` or `outputs`", - }, - { - name: "outputs has errored", - key: "evm_median.outputs", - state: store.WorkflowExecution{ - Steps: map[string]*store.WorkflowExecutionStep{ - "evm_median": { - Outputs: store.StepOutput{ - Err: errors.New("catastrophic error"), - }, - }, - }, - }, - errMsg: "step has errored", - }, - { - name: "digging into a recursive map", - key: "evm_median.outputs.reports.inner", - state: store.WorkflowExecution{ - Steps: map[string]*store.WorkflowExecutionStep{ - "evm_median": { - Outputs: store.StepOutput{ - Value: val, - }, - }, - }, - }, - expected: "key", - }, - { - name: "missing key in map", - key: "evm_median.outputs.reports.missing", - state: store.WorkflowExecution{ - Steps: map[string]*store.WorkflowExecutionStep{ - "evm_median": { - Outputs: store.StepOutput{ - Value: val, - }, - }, - }, - }, - errMsg: "could not find ref part `missing` (ref: `evm_median.outputs.reports.missing`) in", - }, - { - name: "digging into an array", - key: "evm_median.outputs.reportsList.0", - state: store.WorkflowExecution{ - Steps: map[string]*store.WorkflowExecutionStep{ - "evm_median": { - Outputs: store.StepOutput{ - Value: val, - }, - }, - }, - }, - expected: "listElement", - }, - { - name: "digging into an array that's too small", - key: "evm_median.outputs.reportsList.2", - state: store.WorkflowExecution{ - Steps: map[string]*store.WorkflowExecutionStep{ - "evm_median": { - Outputs: store.StepOutput{ - Value: val, - }, - }, - }, - }, - errMsg: "index out of bounds 2", - }, - { - name: "digging into an array with a string key", - key: "evm_median.outputs.reportsList.notAString", - state: store.WorkflowExecution{ - Steps: map[string]*store.WorkflowExecutionStep{ - "evm_median": { - Outputs: store.StepOutput{ - Value: val, - }, - }, - }, - }, - errMsg: "could not interpolate ref part `notAString` (ref: `evm_median.outputs.reportsList.notAString`) in `[listElement]`: `notAString` is not convertible to an int", - }, - { - name: "digging into an array with a negative index", - key: "evm_median.outputs.reportsList.-1", - state: store.WorkflowExecution{ - Steps: map[string]*store.WorkflowExecutionStep{ - "evm_median": { - Outputs: store.StepOutput{ - Value: val, - }, - }, - }, - }, - errMsg: "could not interpolate ref part `-1` (ref: `evm_median.outputs.reportsList.-1`) in `[listElement]`: index out of bounds -1", - }, - { - name: "empty element", - key: "evm_median.outputs..notAString", - state: store.WorkflowExecution{ - Steps: map[string]*store.WorkflowExecutionStep{ - "evm_median": { - Outputs: store.StepOutput{ - Value: val, - }, - }, - }, - }, - errMsg: "could not find ref part `` (ref: `evm_median.outputs..notAString`) in", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(st *testing.T) { - got, err := interpolateKey(tc.key, tc.state) - if tc.errMsg != "" { - require.ErrorContains(st, err, tc.errMsg) - } else { - require.NoError(t, err) - assert.Equal(t, tc.expected, got) - } - }) - } -} - -func TestInterpolateInputsFromState(t *testing.T) { - t.Parallel() - testCases := []struct { - name string - inputs map[string]any - state store.WorkflowExecution - expected any - errMsg string - }{ - { - name: "substituting with a variable that exists", - inputs: map[string]any{ - "shouldnotinterpolate": map[string]any{ - "shouldinterpolate": "$(evm_median.outputs)", - }, - }, - state: store.WorkflowExecution{ - Steps: map[string]*store.WorkflowExecutionStep{ - "evm_median": { - Outputs: store.StepOutput{ - Value: values.NewString(""), - }, - }, - }, - }, - expected: map[string]any{ - "shouldnotinterpolate": map[string]any{ - "shouldinterpolate": "", - }, - }, - }, - { - name: "no substitution required", - inputs: map[string]any{ - "foo": "bar", - }, - state: store.WorkflowExecution{ - Steps: map[string]*store.WorkflowExecutionStep{ - "evm_median": { - Outputs: store.StepOutput{ - Value: values.NewString(""), - }, - }, - }, - }, - expected: map[string]any{ - "foo": "bar", - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(st *testing.T) { - got, err := findAndInterpolateAllKeys(tc.inputs, tc.state) - if tc.errMsg != "" { - require.ErrorContains(st, err, tc.errMsg) - } else { - require.NoError(t, err) - assert.Equal(t, tc.expected, got) - } - }) - } -} diff --git a/core/services/workflows/store/models.go b/core/services/workflows/store/models.go index 275ca85b4f..b7c7d189ad 100644 --- a/core/services/workflows/store/models.go +++ b/core/services/workflows/store/models.go @@ -4,6 +4,7 @@ import ( "time" "github.com/smartcontractkit/chainlink-common/pkg/values" + "github.com/smartcontractkit/chainlink-common/pkg/workflows/exec" ) // Note: any update to the enum below should be reflected in @@ -50,3 +51,18 @@ type WorkflowExecution struct { UpdatedAt *time.Time FinishedAt *time.Time } + +func (w WorkflowExecution) ResultForStep(s string) (*exec.Result, bool) { + step, ok := w.Steps[s] + if !ok { + return &exec.Result{}, false + } + + return &exec.Result{ + Inputs: step.Inputs, + Outputs: step.Outputs.Value, + Error: step.Outputs.Err, + }, true +} + +var _ exec.Results = WorkflowExecution{} diff --git a/core/services/workflows/store/store_db.go b/core/services/workflows/store/store_db.go index e1d0862905..80ecfbb2d6 100644 --- a/core/services/workflows/store/store_db.go +++ b/core/services/workflows/store/store_db.go @@ -127,7 +127,10 @@ func stepToState(step workflowStepRow) (*WorkflowExecutionStep, error) { return nil, err } - inputs = values.FromMapValueProto(vmProto) + inputs, err = values.FromMapValueProto(vmProto) + if err != nil { + return nil, err + } } var ( @@ -146,7 +149,10 @@ func stepToState(step workflowStepRow) (*WorkflowExecutionStep, error) { return nil, err } - outputs = values.FromProto(vProto) + outputs, err = values.FromProto(vProto) + if err != nil { + return nil, err + } } return &WorkflowExecutionStep{ diff --git a/core/store/migrate/migrate_test.go b/core/store/migrate/migrate_test.go index 4138e0d266..adbc0ca2f6 100644 --- a/core/store/migrate/migrate_test.go +++ b/core/store/migrate/migrate_test.go @@ -618,3 +618,14 @@ func BenchmarkBackfillingRecordsWithMigration202(b *testing.B) { require.NoError(b, err) } } + +func TestRollback_247_TxStateEnumUpdate(t *testing.T) { + ctx := testutils.Context(t) + _, db := heavyweight.FullTestDBV2(t, nil) + p, err := migrate.NewProvider(ctx, db.DB) + require.NoError(t, err) + _, err = p.DownTo(ctx, 54) + require.NoError(t, err) + _, err = p.UpTo(ctx, 247) + require.NoError(t, err) +} diff --git a/core/store/migrate/migrations/0248_add_tx_finalized_state.sql b/core/store/migrate/migrations/0248_add_tx_finalized_state.sql new file mode 100644 index 0000000000..dcfe8eec73 --- /dev/null +++ b/core/store/migrate/migrations/0248_add_tx_finalized_state.sql @@ -0,0 +1,135 @@ +-- +goose Up +-- Creating new column and enum instead of just adding new value to the existing enum so the migration changes match the rollback logic +-- Otherwise, migration will complain about mismatching column order + +-- +goose StatementBegin +-- Rename the existing enum with finalized state to mark it as old +ALTER TYPE evm.txes_state RENAME TO txes_state_old; + +-- Create new enum without finalized state +CREATE TYPE evm.txes_state AS ENUM ( + 'unstarted', + 'in_progress', + 'fatal_error', + 'unconfirmed', + 'confirmed_missing_receipt', + 'confirmed', + 'finalized' +); + +-- Add a new state column with the new enum type to the txes table +ALTER TABLE evm.txes ADD COLUMN state_new evm.txes_state; + +-- Copy data from the old column to the new +UPDATE evm.txes SET state_new = state::text::evm.txes_state; + +-- Drop constraints referring to old enum type on the old state column +ALTER TABLE evm.txes ALTER COLUMN state DROP DEFAULT; +ALTER TABLE evm.txes DROP CONSTRAINT chk_eth_txes_fsm; +DROP INDEX IF EXISTS idx_eth_txes_state_from_address_evm_chain_id; +DROP INDEX IF EXISTS idx_eth_txes_min_unconfirmed_nonce_for_key_evm_chain_id; +DROP INDEX IF EXISTS idx_only_one_in_progress_tx_per_account_id_per_evm_chain_id; +DROP INDEX IF EXISTS idx_eth_txes_unstarted_subject_id_evm_chain_id; + +-- Drop the old state column +ALTER TABLE evm.txes DROP state; + +-- Drop the old enum type +DROP TYPE evm.txes_state_old; + +-- Rename the new column name state to replace the old column +ALTER TABLE evm.txes RENAME state_new TO state; + +-- Reset the state column's default +ALTER TABLE evm.txes ALTER COLUMN state SET DEFAULT 'unstarted'::evm.txes_state, ALTER COLUMN state SET NOT NULL; + +-- Recreate constraint with finalized state +ALTER TABLE evm.txes ADD CONSTRAINT chk_eth_txes_fsm CHECK ( + state = 'unstarted'::evm.txes_state AND nonce IS NULL AND error IS NULL AND broadcast_at IS NULL AND initial_broadcast_at IS NULL + OR + state = 'in_progress'::evm.txes_state AND nonce IS NOT NULL AND error IS NULL AND broadcast_at IS NULL AND initial_broadcast_at IS NULL + OR + state = 'fatal_error'::evm.txes_state AND error IS NOT NULL + OR + state = 'unconfirmed'::evm.txes_state AND nonce IS NOT NULL AND error IS NULL AND broadcast_at IS NOT NULL AND initial_broadcast_at IS NOT NULL + OR + state = 'confirmed'::evm.txes_state AND nonce IS NOT NULL AND error IS NULL AND broadcast_at IS NOT NULL AND initial_broadcast_at IS NOT NULL + OR + state = 'confirmed_missing_receipt'::evm.txes_state AND nonce IS NOT NULL AND error IS NULL AND broadcast_at IS NOT NULL AND initial_broadcast_at IS NOT NULL + OR + state = 'finalized'::evm.txes_state AND nonce IS NOT NULL AND error IS NULL AND broadcast_at IS NOT NULL AND initial_broadcast_at IS NOT NULL +) NOT VALID; + +-- Recreate index to include finalized state +CREATE INDEX idx_eth_txes_state_from_address_evm_chain_id ON evm.txes(evm_chain_id, from_address, state) WHERE state <> 'confirmed'::evm.txes_state AND state <> 'finalized'::evm.txes_state; +CREATE INDEX idx_eth_txes_min_unconfirmed_nonce_for_key_evm_chain_id ON evm.txes(evm_chain_id, from_address, nonce) WHERE state = 'unconfirmed'::evm.txes_state; +CREATE UNIQUE INDEX idx_only_one_in_progress_tx_per_account_id_per_evm_chain_id ON evm.txes(evm_chain_id, from_address) WHERE state = 'in_progress'::evm.txes_state; +CREATE INDEX idx_eth_txes_unstarted_subject_id_evm_chain_id ON evm.txes(evm_chain_id, subject, id) WHERE subject IS NOT NULL AND state = 'unstarted'::evm.txes_state; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin + +-- Rename the existing enum with finalized state to mark it as old +ALTER TYPE evm.txes_state RENAME TO txes_state_old; + +-- Create new enum without finalized state +CREATE TYPE evm.txes_state AS ENUM ( + 'unstarted', + 'in_progress', + 'fatal_error', + 'unconfirmed', + 'confirmed_missing_receipt', + 'confirmed' +); + +-- Add a new state column with the new enum type to the txes table +ALTER TABLE evm.txes ADD COLUMN state_new evm.txes_state; + +-- Update all transactions with finalized state to confirmed in the old state column +UPDATE evm.txes SET state = 'confirmed'::evm.txes_state_old WHERE state = 'finalized'::evm.txes_state_old; + +-- Copy data from the old column to the new +UPDATE evm.txes SET state_new = state::text::evm.txes_state; + +-- Drop constraints referring to old enum type on the old state column +ALTER TABLE evm.txes ALTER COLUMN state DROP DEFAULT; +ALTER TABLE evm.txes DROP CONSTRAINT chk_eth_txes_fsm; +DROP INDEX IF EXISTS idx_eth_txes_state_from_address_evm_chain_id; +DROP INDEX IF EXISTS idx_eth_txes_min_unconfirmed_nonce_for_key_evm_chain_id; +DROP INDEX IF EXISTS idx_only_one_in_progress_tx_per_account_id_per_evm_chain_id; +DROP INDEX IF EXISTS idx_eth_txes_unstarted_subject_id_evm_chain_id; + +-- Drop the old state column +ALTER TABLE evm.txes DROP state; + +-- Drop the old enum type +DROP TYPE evm.txes_state_old; + +-- Rename the new column name state to replace the old column +ALTER TABLE evm.txes RENAME state_new TO state; + +-- Reset the state column's default +ALTER TABLE evm.txes ALTER COLUMN state SET DEFAULT 'unstarted'::evm.txes_state, ALTER COLUMN state SET NOT NULL; + +-- Recereate constraint without finalized state +ALTER TABLE evm.txes ADD CONSTRAINT chk_eth_txes_fsm CHECK ( + state = 'unstarted'::evm.txes_state AND nonce IS NULL AND error IS NULL AND broadcast_at IS NULL AND initial_broadcast_at IS NULL + OR + state = 'in_progress'::evm.txes_state AND nonce IS NOT NULL AND error IS NULL AND broadcast_at IS NULL AND initial_broadcast_at IS NULL + OR + state = 'fatal_error'::evm.txes_state AND error IS NOT NULL + OR + state = 'unconfirmed'::evm.txes_state AND nonce IS NOT NULL AND error IS NULL AND broadcast_at IS NOT NULL AND initial_broadcast_at IS NOT NULL + OR + state = 'confirmed'::evm.txes_state AND nonce IS NOT NULL AND error IS NULL AND broadcast_at IS NOT NULL AND initial_broadcast_at IS NOT NULL + OR + state = 'confirmed_missing_receipt'::evm.txes_state AND nonce IS NOT NULL AND error IS NULL AND broadcast_at IS NOT NULL AND initial_broadcast_at IS NOT NULL +) NOT VALID; + +-- Recreate index with new enum type +CREATE INDEX idx_eth_txes_state_from_address_evm_chain_id ON evm.txes(evm_chain_id, from_address, state) WHERE state <> 'confirmed'::evm.txes_state; +CREATE INDEX idx_eth_txes_min_unconfirmed_nonce_for_key_evm_chain_id ON evm.txes(evm_chain_id, from_address, nonce) WHERE state = 'unconfirmed'::evm.txes_state; +CREATE UNIQUE INDEX idx_only_one_in_progress_tx_per_account_id_per_evm_chain_id ON evm.txes(evm_chain_id, from_address) WHERE state = 'in_progress'::evm.txes_state; +CREATE INDEX idx_eth_txes_unstarted_subject_id_evm_chain_id ON evm.txes(evm_chain_id, subject, id) WHERE subject IS NOT NULL AND state = 'unstarted'::evm.txes_state; +-- +goose StatementEnd diff --git a/core/store/migrate/migrations/0249_registry_syncer_state.sql b/core/store/migrate/migrations/0249_registry_syncer_state.sql new file mode 100644 index 0000000000..e34a3790a3 --- /dev/null +++ b/core/store/migrate/migrations/0249_registry_syncer_state.sql @@ -0,0 +1,11 @@ +-- +goose Up +CREATE TABLE registry_syncer_states ( + id SERIAL PRIMARY KEY, + data JSONB NOT NULL, + data_hash TEXT NOT NULL UNIQUE, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); +-- +goose Down +-- +goose StatementBegin +DROP TABLE registry_syncer_states; +-- +goose StatementEnd 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 0000000000..6c6cf02b43 --- /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/core/store/migrate/migrations/0251_add_don_id_to_channel_definitions.sql b/core/store/migrate/migrations/0251_add_don_id_to_channel_definitions.sql new file mode 100644 index 0000000000..9c77592b0a --- /dev/null +++ b/core/store/migrate/migrations/0251_add_don_id_to_channel_definitions.sql @@ -0,0 +1,13 @@ +-- +goose Up +DELETE FROM channel_definitions; +ALTER TABLE channel_definitions DROP CONSTRAINT channel_definitions_pkey; +ALTER TABLE channel_definitions ADD COLUMN don_id bigint, ADD COLUMN version bigint; +ALTER TABLE channel_definitions RENAME COLUMN evm_chain_id TO chain_selector; +ALTER TABLE channel_definitions ALTER COLUMN chain_selector TYPE NUMERIC(20, 0); +ALTER TABLE channel_definitions ADD PRIMARY KEY (chain_selector, addr, don_id); + +-- +goose Down +ALTER TABLE channel_definitions DROP COLUMN don_id, DROP COLUMN version; +ALTER TABLE channel_definitions RENAME COLUMN chain_selector TO evm_chain_id; +ALTER TABLE channel_definitions ALTER COLUMN evm_chain_id TYPE bigint; +ALTER TABLE channel_definitions ADD PRIMARY KEY (evm_chain_id, addr); diff --git a/core/store/migrate/migrations/0252_add_llo_transmit_requests.sql b/core/store/migrate/migrations/0252_add_llo_transmit_requests.sql new file mode 100644 index 0000000000..d08ae0c921 --- /dev/null +++ b/core/store/migrate/migrations/0252_add_llo_transmit_requests.sql @@ -0,0 +1,21 @@ +-- +goose Up + +CREATE TABLE llo_mercury_transmit_queue ( + don_id BIGINT NOT NULL, + server_url TEXT NOT NULL, + config_digest BYTEA NOT NULL, + seq_nr BIGINT NOT NULL, + report BYTEA NOT NULL, + lifecycle_stage TEXT NOT NULL, + report_format BIGINT NOT NULL, + signatures BYTEA[] NOT NULL, + signers SMALLINT[] NOT NULL, + transmission_hash BYTEA NOT NULL, + PRIMARY KEY (transmission_hash) +); + + CREATE INDEX idx_llo_mercury_transmit_queue_don_id_server_url_seq_nr ON llo_mercury_transmit_queue (don_id, server_url, seq_nr DESC); + +-- +goose Down + +DROP TABLE llo_mercury_transmit_queue; diff --git a/core/store/migrate/migrations/0253_add_spec_type_to_workflow_spec.sql b/core/store/migrate/migrations/0253_add_spec_type_to_workflow_spec.sql new file mode 100644 index 0000000000..3c62c46097 --- /dev/null +++ b/core/store/migrate/migrations/0253_add_spec_type_to_workflow_spec.sql @@ -0,0 +1,13 @@ +-- +goose Up +-- +goose StatementBegin + +ALTER TABLE workflow_specs ADD COLUMN spec_type varchar(255) DEFAULT 'yaml'; + +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin + +ALTER TABLE workflow_specs DROP COLUMN spec_type; + +-- +goose StatementEnd \ No newline at end of file diff --git a/core/testdata/testspecs/v2_specs.go b/core/testdata/testspecs/v2_specs.go index 3a1798ab5a..554b18ae81 100644 --- a/core/testdata/testspecs/v2_specs.go +++ b/core/testdata/testspecs/v2_specs.go @@ -1,6 +1,7 @@ package testspecs import ( + "crypto/sha256" "fmt" "strconv" "strings" @@ -10,9 +11,9 @@ import ( "github.com/google/uuid" "github.com/test-go/testify/require" - pkgworkflows "github.com/smartcontractkit/chainlink-common/pkg/workflows" "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/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/vrf/vrfcommon" "github.com/smartcontractkit/chainlink/v2/core/services/webhook" @@ -899,9 +900,8 @@ func (w WorkflowJobSpec) Job() job.Job { // GenerateWorkflowJobSpec creates a WorkflowJobSpec from the given workflow yaml spec string func GenerateWorkflowJobSpec(t *testing.T, spec string) WorkflowJobSpec { t.Helper() - s, err := pkgworkflows.ParseWorkflowSpecYaml(spec) - require.NoError(t, err, "failed to parse YAML workflow spec %s", spec) - id := s.CID + sum := sha256.Sum256([]byte(spec)) + id := fmt.Sprintf("%x", sum) template := ` type = "workflow" schemaVersion = 1 @@ -913,7 +913,7 @@ workflow = """ ` toml := fmt.Sprintf(template, id, spec) - j, err := workflows.ValidatedWorkflowJobSpec(toml) + j, err := workflows.ValidatedWorkflowJobSpec(testutils.Context(t), toml) require.NoError(t, err, "failed to validate TOML job spec for workflow %s", toml) return WorkflowJobSpec{toml: toml, j: j} } diff --git a/core/utils/http/http.go b/core/utils/http/http.go index 3336ac9f42..0c713f9662 100644 --- a/core/utils/http/http.go +++ b/core/utils/http/http.go @@ -7,7 +7,7 @@ import ( "net/url" "time" - "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink-common/pkg/logger" ) type httpClientConfig interface { @@ -38,9 +38,13 @@ func newDefaultTransport() *http.Transport { return t } +type HTTPClient interface { + Do(req *http.Request) (*http.Response, error) +} + // HTTPRequest holds the request and config struct for a http request type HTTPRequest struct { - Client *http.Client + Client HTTPClient Request *http.Request Config HTTPRequestConfig Logger logger.Logger @@ -48,35 +52,46 @@ type HTTPRequest struct { // HTTPRequestConfig holds the configurable settings for a http request type HTTPRequestConfig struct { + // SizeLimit in bytes SizeLimit int64 } -// SendRequest sends a HTTPRequest, -// returns a body, status code, and error. -func (h *HTTPRequest) SendRequest() (responseBody []byte, statusCode int, headers http.Header, err error) { +// SendRequestReader allows for streaming the body directly and does not read +// it all into memory +// +// CALLER IS RESPONSIBLE FOR CLOSING RETURNED RESPONSE BODY +func (h *HTTPRequest) SendRequestReader() (responseBody io.ReadCloser, statusCode int, headers http.Header, err error) { start := time.Now() - r, err := h.Client.Do(h.Request) if err != nil { - h.Logger.Tracew("http adapter got error", "err", err) + logger.Sugared(h.Logger).Tracew("http adapter got error", "err", err) return nil, 0, nil, err } - defer logger.Sugared(h.Logger).ErrorIfFn(r.Body.Close, "Error closing SendRequest response body") statusCode = r.StatusCode elapsed := time.Since(start) - h.Logger.Tracew(fmt.Sprintf("http adapter got %v in %s", statusCode, elapsed), "statusCode", statusCode, "timeElapsedSeconds", elapsed) + logger.Sugared(h.Logger).Tracew(fmt.Sprintf("http adapter got %v in %s", statusCode, elapsed), "statusCode", statusCode, "timeElapsedSeconds", elapsed) source := http.MaxBytesReader(nil, r.Body, h.Config.SizeLimit) - bytes, err := io.ReadAll(source) + + return source, statusCode, r.Header, nil +} + +// SendRequest sends a HTTPRequest, +// returns a body, status code, and error. +func (h *HTTPRequest) SendRequest() (responseBody []byte, statusCode int, headers http.Header, err error) { + start := time.Now() + + source, statusCode, headers, err := h.SendRequestReader() if err != nil { - h.Logger.Errorw("http adapter error reading body", "err", err) - return nil, statusCode, nil, err + return nil, statusCode, headers, err } - elapsed = time.Since(start) - h.Logger.Tracew(fmt.Sprintf("http adapter finished after %s", elapsed), "statusCode", statusCode, "timeElapsedSeconds", elapsed) + defer logger.Sugared(h.Logger).ErrorIfFn(source.Close, "Error closing SendRequest response body") + bytes, err := io.ReadAll(source) + elapsed := time.Since(start) + logger.Sugared(h.Logger).Tracew(fmt.Sprintf("http adapter finished after %s", elapsed), "statusCode", statusCode, "timeElapsedSeconds", elapsed) responseBody = bytes - return responseBody, statusCode, r.Header, nil + return responseBody, statusCode, headers, nil } diff --git a/core/utils/http/http_allowed_ips.go b/core/utils/http/http_allowed_ips.go index 6432e4ff91..2b77e89c7d 100644 --- a/core/utils/http/http_allowed_ips.go +++ b/core/utils/http/http_allowed_ips.go @@ -9,7 +9,7 @@ import ( "github.com/pkg/errors" "go.uber.org/multierr" - "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink-common/pkg/logger" ) var privateIPBlocks []*net.IPNet diff --git a/core/utils/utils.go b/core/utils/utils.go index d076284112..237f6a4358 100644 --- a/core/utils/utils.go +++ b/core/utils/utils.go @@ -481,6 +481,23 @@ func NewRedialBackoff() backoff.Backoff { } } +func NewHTTPFetchBackoff() backoff.Backoff { + return backoff.Backoff{ + Min: 100 * time.Millisecond, + Max: 15 * time.Second, + Jitter: true, + } +} + +// NewDBBackoff is a standard backoff to use for database connection issues +func NewDBBackoff() backoff.Backoff { + return backoff.Backoff{ + Min: 100 * time.Millisecond, + Max: 5 * time.Second, + Jitter: true, + } +} + // KeyedMutex allows to lock based on particular values type KeyedMutex struct { mutexes sync.Map diff --git a/core/web/assets/index.html b/core/web/assets/index.html index e50dfc0709..d5411542a9 100644 --- a/core/web/assets/index.html +++ b/core/web/assets/index.html @@ -1 +1 @@ -Operator UIChainlink
\ No newline at end of file +Operator UIChainlink
\ No newline at end of file diff --git a/core/web/assets/index.html.gz b/core/web/assets/index.html.gz index f32bc10dbc..4f63d9ebf4 100644 Binary files a/core/web/assets/index.html.gz and b/core/web/assets/index.html.gz differ diff --git a/core/web/assets/main.6f07a88cfc748f57e21d.js b/core/web/assets/main.84f90f8fc23465846aa7.js similarity index 89% rename from core/web/assets/main.6f07a88cfc748f57e21d.js rename to core/web/assets/main.84f90f8fc23465846aa7.js index bf6a280a99..f9770459be 100644 --- a/core/web/assets/main.6f07a88cfc748f57e21d.js +++ b/core/web/assets/main.84f90f8fc23465846aa7.js @@ -168,7 +168,7 @@ object-assign * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - */ Object.defineProperty(t,"__esModule",{value:!0}),"undefined"==typeof window||"function"!=typeof MessageChannel){var n,r,i,a,o,s=null,u=null,c=function(){if(null!==s)try{var e=t.unstable_now();s(!0,e),s=null}catch(n){throw setTimeout(c,0),n}},l=Date.now();t.unstable_now=function(){return Date.now()-l},n=function(e){null!==s?setTimeout(n,0,e):(s=e,setTimeout(c,0))},r=function(e,t){u=setTimeout(e,t)},i=function(){clearTimeout(u)},a=function(){return!1},o=t.unstable_forceFrameRate=function(){}}else{var f=window.performance,d=window.Date,h=window.setTimeout,p=window.clearTimeout;if("undefined"!=typeof console){var b=window.cancelAnimationFrame;"function"!=typeof window.requestAnimationFrame&&console.error("This browser doesn't support requestAnimationFrame. Make sure that you load a polyfill in older browsers. https://fb.me/react-polyfills"),"function"!=typeof b&&console.error("This browser doesn't support cancelAnimationFrame. Make sure that you load a polyfill in older browsers. https://fb.me/react-polyfills")}if("object"==typeof f&&"function"==typeof f.now)t.unstable_now=function(){return f.now()};else{var m=d.now();t.unstable_now=function(){return d.now()-m}}var g=!1,v=null,y=-1,w=5,_=0;a=function(){return t.unstable_now()>=_},o=function(){},t.unstable_forceFrameRate=function(e){0>e||125M(o,n))void 0!==u&&0>M(u,o)?(e[r]=u,e[s]=n,r=s):(e[r]=o,e[a]=n,r=a);else if(void 0!==u&&0>M(u,n))e[r]=u,e[s]=n,r=s;else break a}}return t}return null}function M(e,t){var n=e.sortIndex-t.sortIndex;return 0!==n?n:e.id-t.id}var O=[],A=[],L=1,C=null,I=3,D=!1,N=!1,P=!1;function R(e){for(var t=x(A);null!==t;){if(null===t.callback)T(A);else if(t.startTime<=e)T(A),t.sortIndex=t.expirationTime,k(O,t);else break;t=x(A)}}function j(e){if(P=!1,R(e),!N){if(null!==x(O))N=!0,n(F);else{var t=x(A);null!==t&&r(j,t.startTime-e)}}}function F(e,n){N=!1,P&&(P=!1,i()),D=!0;var o=I;try{for(R(n),C=x(O);null!==C&&(!(C.expirationTime>n)||e&&!a());){var s=C.callback;if(null!==s){C.callback=null,I=C.priorityLevel;var u=s(C.expirationTime<=n);n=t.unstable_now(),"function"==typeof u?C.callback=u:C===x(O)&&T(O),R(n)}else T(O);C=x(O)}if(null!==C)var c=!0;else{var l=x(A);null!==l&&r(j,l.startTime-n),c=!1}return c}finally{C=null,I=o,D=!1}}function Y(e){switch(e){case 1:return -1;case 2:return 250;case 5:return 1073741823;case 4:return 1e4;default:return 5e3}}var B=o;t.unstable_ImmediatePriority=1,t.unstable_UserBlockingPriority=2,t.unstable_NormalPriority=3,t.unstable_IdlePriority=5,t.unstable_LowPriority=4,t.unstable_runWithPriority=function(e,t){switch(e){case 1:case 2:case 3:case 4:case 5:break;default:e=3}var n=I;I=e;try{return t()}finally{I=n}},t.unstable_next=function(e){switch(I){case 1:case 2:case 3:var t=3;break;default:t=I}var n=I;I=t;try{return e()}finally{I=n}},t.unstable_scheduleCallback=function(e,a,o){var s=t.unstable_now();if("object"==typeof o&&null!==o){var u=o.delay;u="number"==typeof u&&0s?(e.sortIndex=u,k(A,e),null===x(O)&&e===x(A)&&(P?i():P=!0,r(j,u-s))):(e.sortIndex=o,k(O,e),N||D||(N=!0,n(F))),e},t.unstable_cancelCallback=function(e){e.callback=null},t.unstable_wrapCallback=function(e){var t=I;return function(){var n=I;I=t;try{return e.apply(this,arguments)}finally{I=n}}},t.unstable_getCurrentPriorityLevel=function(){return I},t.unstable_shouldYield=function(){var e=t.unstable_now();R(e);var n=x(O);return n!==C&&null!==C&&null!==n&&null!==n.callback&&n.startTime<=e&&n.expirationTime>5==6?2:e>>4==14?3:e>>3==30?4:e>>6==2?-1:-2}function c(e,t,n){var r=t.length-1;if(r=0?(i>0&&(e.lastNeed=i-1),i):--r=0?(i>0&&(e.lastNeed=i-2),i):--r=0?(i>0&&(2===i?i=0:e.lastNeed=i-3),i):0}function l(e,t,n){if((192&t[0])!=128)return e.lastNeed=0,"�";if(e.lastNeed>1&&t.length>1){if((192&t[1])!=128)return e.lastNeed=1,"�";if(e.lastNeed>2&&t.length>2&&(192&t[2])!=128)return e.lastNeed=2,"�"}}function f(e){var t=this.lastTotal-this.lastNeed,n=l(this,e,t);return void 0!==n?n:this.lastNeed<=e.length?(e.copy(this.lastChar,t,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal)):void(e.copy(this.lastChar,t,0,e.length),this.lastNeed-=e.length)}function d(e,t){var n=c(this,e,t);if(!this.lastNeed)return e.toString("utf8",t);this.lastTotal=n;var r=e.length-(n-this.lastNeed);return e.copy(this.lastChar,0,r),e.toString("utf8",t,r)}function h(e){var t=e&&e.length?this.write(e):"";return this.lastNeed?t+"�":t}function p(e,t){if((e.length-t)%2==0){var n=e.toString("utf16le",t);if(n){var r=n.charCodeAt(n.length-1);if(r>=55296&&r<=56319)return this.lastNeed=2,this.lastTotal=4,this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1],n.slice(0,-1)}return n}return this.lastNeed=1,this.lastTotal=2,this.lastChar[0]=e[e.length-1],e.toString("utf16le",t,e.length-1)}function b(e){var t=e&&e.length?this.write(e):"";if(this.lastNeed){var n=this.lastTotal-this.lastNeed;return t+this.lastChar.toString("utf16le",0,n)}return t}function m(e,t){var n=(e.length-t)%3;return 0===n?e.toString("base64",t):(this.lastNeed=3-n,this.lastTotal=3,1===n?this.lastChar[0]=e[e.length-1]:(this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1]),e.toString("base64",t,e.length-n))}function g(e){var t=e&&e.length?this.write(e):"";return this.lastNeed?t+this.lastChar.toString("base64",0,3-this.lastNeed):t}function v(e){return e.toString(this.encoding)}function y(e){return e&&e.length?this.write(e):""}t.s=s,s.prototype.write=function(e){var t,n;if(0===e.length)return"";if(this.lastNeed){if(void 0===(t=this.fillLast(e)))return"";n=this.lastNeed,this.lastNeed=0}else n=0;return n */ var r=n(48764),i=r.Buffer;function a(e,t){for(var n in e)t[n]=e[n]}function o(e,t,n){return i(e,t,n)}i.from&&i.alloc&&i.allocUnsafe&&i.allocUnsafeSlow?e.exports=r:(a(r,t),t.Buffer=o),o.prototype=Object.create(i.prototype),a(i,o),o.from=function(e,t,n){if("number"==typeof e)throw TypeError("Argument must not be a number");return i(e,t,n)},o.alloc=function(e,t,n){if("number"!=typeof e)throw TypeError("Argument must be a number");var r=i(e);return void 0!==t?"string"==typeof n?r.fill(t,n):r.fill(t):r.fill(0),r},o.allocUnsafe=function(e){if("number"!=typeof e)throw TypeError("Argument must be a number");return i(e)},o.allocUnsafeSlow=function(e){if("number"!=typeof e)throw TypeError("Argument must be a number");return r.SlowBuffer(e)}},93379(e,t,n){"use strict";var r,i,a=function(){return void 0===r&&(r=Boolean(window&&document&&document.all&&!window.atob)),r},o=(i={},function(e){if(void 0===i[e]){var t=document.querySelector(e);if(window.HTMLIFrameElement&&t instanceof window.HTMLIFrameElement)try{t=t.contentDocument.head}catch(n){t=null}i[e]=t}return i[e]}),s=[];function u(e){for(var t=-1,n=0;nOW});var r,i,a,o,s,u,c,l=n(67294),f=n.t(l,2),d=n(39814),h=n(5977),p=n(57209),b=n(32316),m=n(95880),g=n(17051),v=n(71381),y=n(81701),w=n(3022),_=n(60323),E=n(87591),S=n(25649),k=n(28902),x=n(71426),T=n(48884),M=n(94184),O=n.n(M),A=n(37703),L=n(73935),C=function(){if("undefined"!=typeof Map)return Map;function e(e,t){var n=-1;return e.some(function(e,r){return e[0]===t&&(n=r,!0)}),n}return function(){function t(){this.__entries__=[]}return Object.defineProperty(t.prototype,"size",{get:function(){return this.__entries__.length},enumerable:!0,configurable:!0}),t.prototype.get=function(t){var n=e(this.__entries__,t),r=this.__entries__[n];return r&&r[1]},t.prototype.set=function(t,n){var r=e(this.__entries__,t);~r?this.__entries__[r][1]=n:this.__entries__.push([t,n])},t.prototype.delete=function(t){var n=this.__entries__,r=e(n,t);~r&&n.splice(r,1)},t.prototype.has=function(t){return!!~e(this.__entries__,t)},t.prototype.clear=function(){this.__entries__.splice(0)},t.prototype.forEach=function(e,t){void 0===t&&(t=null);for(var n=0,r=this.__entries__;n0},e.prototype.connect_=function(){I&&!this.connected_&&(document.addEventListener("transitionend",this.onTransitionEnd_),window.addEventListener("resize",this.refresh),Y?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener("DOMSubtreeModified",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},e.prototype.disconnect_=function(){I&&this.connected_&&(document.removeEventListener("transitionend",this.onTransitionEnd_),window.removeEventListener("resize",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener("DOMSubtreeModified",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},e.prototype.onTransitionEnd_=function(e){var t=e.propertyName,n=void 0===t?"":t;F.some(function(e){return!!~n.indexOf(e)})&&this.refresh()},e.getInstance=function(){return this.instance_||(this.instance_=new e),this.instance_},e.instance_=null,e}(),U=function(e,t){for(var n=0,r=Object.keys(t);n0},e}(),er="undefined"!=typeof WeakMap?new WeakMap:new C,ei=function(){function e(t){if(!(this instanceof e))throw TypeError("Cannot call a class as a function.");if(!arguments.length)throw TypeError("1 argument required, but only 0 present.");var n=B.getInstance(),r=new en(t,n,this);er.set(this,r)}return e}();["observe","unobserve","disconnect"].forEach(function(e){ei.prototype[e]=function(){var t;return(t=er.get(this))[e].apply(t,arguments)}});var ea=void 0!==D.ResizeObserver?D.ResizeObserver:ei;let eo=ea;var es=function(e){var t=[],n=null,r=function(){for(var r=arguments.length,i=Array(r),a=0;a=t||n<0||f&&r>=a}function g(){var e=eb();if(m(e))return v(e);s=setTimeout(g,b(e))}function v(e){return(s=void 0,d&&r)?h(e):(r=i=void 0,o)}function y(){void 0!==s&&clearTimeout(s),c=0,r=u=i=s=void 0}function w(){return void 0===s?o:v(eb())}function _(){var e=eb(),n=m(e);if(r=arguments,i=this,u=e,n){if(void 0===s)return p(u);if(f)return clearTimeout(s),s=setTimeout(g,t),h(u)}return void 0===s&&(s=setTimeout(g,t)),o}return t=ez(t)||0,ed(n)&&(l=!!n.leading,a=(f="maxWait"in n)?eW(ez(n.maxWait)||0,t):a,d="trailing"in n?!!n.trailing:d),_.cancel=y,_.flush=w,_}let eq=eV;var eZ="Expected a function";function eX(e,t,n){var r=!0,i=!0;if("function"!=typeof e)throw TypeError(eZ);return ed(n)&&(r="leading"in n?!!n.leading:r,i="trailing"in n?!!n.trailing:i),eq(e,t,{leading:r,maxWait:t,trailing:i})}let eJ=eX;var eQ={debounce:eq,throttle:eJ},e1=function(e){return eQ[e]},e0=function(e){return"function"==typeof e},e2=function(){return"undefined"==typeof window},e3=function(e){return e instanceof Element||e instanceof HTMLDocument};function e4(e){return(e4="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function e6(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}function e5(e,t){for(var n=0;ne.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&l.createElement(tG.Z,{variant:"indeterminate",classes:r}))};tK.propTypes={fetchCount:el().number.isRequired};let tV=(0,b.withStyles)(tW)(tK);var tq=n(5536);let tZ=n.p+"ba8bbf16ebf8e1d05bef.svg";function tX(){return(tX=Object.assign||function(e){for(var t=1;t120){for(var d=Math.floor(u/80),h=u%80,p=[],b=0;b0},name:{enumerable:!1},nodes:{enumerable:!1},source:{enumerable:!1},positions:{enumerable:!1},originalError:{enumerable:!1}}),null!=s&&s.stack)?(Object.defineProperty(nf(b),"stack",{value:s.stack,writable:!0,configurable:!0}),nl(b)):(Error.captureStackTrace?Error.captureStackTrace(nf(b),n):Object.defineProperty(nf(b),"stack",{value:Error().stack,writable:!0,configurable:!0}),b)}return ns(n,[{key:"toString",value:function(){return nw(this)}},{key:t4.YF,get:function(){return"Object"}}]),n}(nd(Error));function ny(e){return void 0===e||0===e.length?void 0:e}function nw(e){var t=e.message;if(e.nodes)for(var n=0,r=e.nodes;n",EOF:"",BANG:"!",DOLLAR:"$",AMP:"&",PAREN_L:"(",PAREN_R:")",SPREAD:"...",COLON:":",EQUALS:"=",AT:"@",BRACKET_L:"[",BRACKET_R:"]",BRACE_L:"{",PIPE:"|",BRACE_R:"}",NAME:"Name",INT:"Int",FLOAT:"Float",STRING:"String",BLOCK_STRING:"BlockString",COMMENT:"Comment"}),nx=n(10143),nT=Object.freeze({QUERY:"QUERY",MUTATION:"MUTATION",SUBSCRIPTION:"SUBSCRIPTION",FIELD:"FIELD",FRAGMENT_DEFINITION:"FRAGMENT_DEFINITION",FRAGMENT_SPREAD:"FRAGMENT_SPREAD",INLINE_FRAGMENT:"INLINE_FRAGMENT",VARIABLE_DEFINITION:"VARIABLE_DEFINITION",SCHEMA:"SCHEMA",SCALAR:"SCALAR",OBJECT:"OBJECT",FIELD_DEFINITION:"FIELD_DEFINITION",ARGUMENT_DEFINITION:"ARGUMENT_DEFINITION",INTERFACE:"INTERFACE",UNION:"UNION",ENUM:"ENUM",ENUM_VALUE:"ENUM_VALUE",INPUT_OBJECT:"INPUT_OBJECT",INPUT_FIELD_DEFINITION:"INPUT_FIELD_DEFINITION"}),nM=n(87392),nO=function(){function e(e){var t=new nS.WU(nk.SOF,0,0,0,0,null);this.source=e,this.lastToken=t,this.token=t,this.line=1,this.lineStart=0}var t=e.prototype;return t.advance=function(){return this.lastToken=this.token,this.token=this.lookahead()},t.lookahead=function(){var e,t=this.token;if(t.kind!==nk.EOF)do t=null!==(e=t.next)&&void 0!==e?e:t.next=nC(this,t);while(t.kind===nk.COMMENT)return t},e}();function nA(e){return e===nk.BANG||e===nk.DOLLAR||e===nk.AMP||e===nk.PAREN_L||e===nk.PAREN_R||e===nk.SPREAD||e===nk.COLON||e===nk.EQUALS||e===nk.AT||e===nk.BRACKET_L||e===nk.BRACKET_R||e===nk.BRACE_L||e===nk.PIPE||e===nk.BRACE_R}function nL(e){return isNaN(e)?nk.EOF:e<127?JSON.stringify(String.fromCharCode(e)):'"\\u'.concat(("00"+e.toString(16).toUpperCase()).slice(-4),'"')}function nC(e,t){for(var n=e.source,r=n.body,i=r.length,a=t.end;a31||9===a))return new nS.WU(nk.COMMENT,t,s,n,r,i,o.slice(t+1,s))}function nN(e,t,n,r,i,a){var o=e.body,s=n,u=t,c=!1;if(45===s&&(s=o.charCodeAt(++u)),48===s){if((s=o.charCodeAt(++u))>=48&&s<=57)throw n_(e,u,"Invalid number, unexpected digit after 0: ".concat(nL(s),"."))}else u=nP(e,u,s),s=o.charCodeAt(u);if(46===s&&(c=!0,s=o.charCodeAt(++u),u=nP(e,u,s),s=o.charCodeAt(u)),(69===s||101===s)&&(c=!0,(43===(s=o.charCodeAt(++u))||45===s)&&(s=o.charCodeAt(++u)),u=nP(e,u,s),s=o.charCodeAt(u)),46===s||nU(s))throw n_(e,u,"Invalid number, expected digit but got: ".concat(nL(s),"."));return new nS.WU(c?nk.FLOAT:nk.INT,t,u,r,i,a,o.slice(t,u))}function nP(e,t,n){var r=e.body,i=t,a=n;if(a>=48&&a<=57){do a=r.charCodeAt(++i);while(a>=48&&a<=57)return i}throw n_(e,i,"Invalid number, expected digit but got: ".concat(nL(a),"."))}function nR(e,t,n,r,i){for(var a=e.body,o=t+1,s=o,u=0,c="";o=48&&e<=57?e-48:e>=65&&e<=70?e-55:e>=97&&e<=102?e-87:-1}function nB(e,t,n,r,i){for(var a=e.body,o=a.length,s=t+1,u=0;s!==o&&!isNaN(u=a.charCodeAt(s))&&(95===u||u>=48&&u<=57||u>=65&&u<=90||u>=97&&u<=122);)++s;return new nS.WU(nk.NAME,t,s,n,r,i,a.slice(t,s))}function nU(e){return 95===e||e>=65&&e<=90||e>=97&&e<=122}function nH(e,t){return new n$(e,t).parseDocument()}var n$=function(){function e(e,t){var n=(0,nx.T)(e)?e:new nx.H(e);this._lexer=new nO(n),this._options=t}var t=e.prototype;return t.parseName=function(){var e=this.expectToken(nk.NAME);return{kind:nE.h.NAME,value:e.value,loc:this.loc(e)}},t.parseDocument=function(){var e=this._lexer.token;return{kind:nE.h.DOCUMENT,definitions:this.many(nk.SOF,this.parseDefinition,nk.EOF),loc:this.loc(e)}},t.parseDefinition=function(){if(this.peek(nk.NAME))switch(this._lexer.token.value){case"query":case"mutation":case"subscription":return this.parseOperationDefinition();case"fragment":return this.parseFragmentDefinition();case"schema":case"scalar":case"type":case"interface":case"union":case"enum":case"input":case"directive":return this.parseTypeSystemDefinition();case"extend":return this.parseTypeSystemExtension()}else if(this.peek(nk.BRACE_L))return this.parseOperationDefinition();else if(this.peekDescription())return this.parseTypeSystemDefinition();throw this.unexpected()},t.parseOperationDefinition=function(){var e,t=this._lexer.token;if(this.peek(nk.BRACE_L))return{kind:nE.h.OPERATION_DEFINITION,operation:"query",name:void 0,variableDefinitions:[],directives:[],selectionSet:this.parseSelectionSet(),loc:this.loc(t)};var n=this.parseOperationType();return this.peek(nk.NAME)&&(e=this.parseName()),{kind:nE.h.OPERATION_DEFINITION,operation:n,name:e,variableDefinitions:this.parseVariableDefinitions(),directives:this.parseDirectives(!1),selectionSet:this.parseSelectionSet(),loc:this.loc(t)}},t.parseOperationType=function(){var e=this.expectToken(nk.NAME);switch(e.value){case"query":return"query";case"mutation":return"mutation";case"subscription":return"subscription"}throw this.unexpected(e)},t.parseVariableDefinitions=function(){return this.optionalMany(nk.PAREN_L,this.parseVariableDefinition,nk.PAREN_R)},t.parseVariableDefinition=function(){var e=this._lexer.token;return{kind:nE.h.VARIABLE_DEFINITION,variable:this.parseVariable(),type:(this.expectToken(nk.COLON),this.parseTypeReference()),defaultValue:this.expectOptionalToken(nk.EQUALS)?this.parseValueLiteral(!0):void 0,directives:this.parseDirectives(!0),loc:this.loc(e)}},t.parseVariable=function(){var e=this._lexer.token;return this.expectToken(nk.DOLLAR),{kind:nE.h.VARIABLE,name:this.parseName(),loc:this.loc(e)}},t.parseSelectionSet=function(){var e=this._lexer.token;return{kind:nE.h.SELECTION_SET,selections:this.many(nk.BRACE_L,this.parseSelection,nk.BRACE_R),loc:this.loc(e)}},t.parseSelection=function(){return this.peek(nk.SPREAD)?this.parseFragment():this.parseField()},t.parseField=function(){var e,t,n=this._lexer.token,r=this.parseName();return this.expectOptionalToken(nk.COLON)?(e=r,t=this.parseName()):t=r,{kind:nE.h.FIELD,alias:e,name:t,arguments:this.parseArguments(!1),directives:this.parseDirectives(!1),selectionSet:this.peek(nk.BRACE_L)?this.parseSelectionSet():void 0,loc:this.loc(n)}},t.parseArguments=function(e){var t=e?this.parseConstArgument:this.parseArgument;return this.optionalMany(nk.PAREN_L,t,nk.PAREN_R)},t.parseArgument=function(){var e=this._lexer.token,t=this.parseName();return this.expectToken(nk.COLON),{kind:nE.h.ARGUMENT,name:t,value:this.parseValueLiteral(!1),loc:this.loc(e)}},t.parseConstArgument=function(){var e=this._lexer.token;return{kind:nE.h.ARGUMENT,name:this.parseName(),value:(this.expectToken(nk.COLON),this.parseValueLiteral(!0)),loc:this.loc(e)}},t.parseFragment=function(){var e=this._lexer.token;this.expectToken(nk.SPREAD);var t=this.expectOptionalKeyword("on");return!t&&this.peek(nk.NAME)?{kind:nE.h.FRAGMENT_SPREAD,name:this.parseFragmentName(),directives:this.parseDirectives(!1),loc:this.loc(e)}:{kind:nE.h.INLINE_FRAGMENT,typeCondition:t?this.parseNamedType():void 0,directives:this.parseDirectives(!1),selectionSet:this.parseSelectionSet(),loc:this.loc(e)}},t.parseFragmentDefinition=function(){var e,t=this._lexer.token;return(this.expectKeyword("fragment"),(null===(e=this._options)||void 0===e?void 0:e.experimentalFragmentVariables)===!0)?{kind:nE.h.FRAGMENT_DEFINITION,name:this.parseFragmentName(),variableDefinitions:this.parseVariableDefinitions(),typeCondition:(this.expectKeyword("on"),this.parseNamedType()),directives:this.parseDirectives(!1),selectionSet:this.parseSelectionSet(),loc:this.loc(t)}:{kind:nE.h.FRAGMENT_DEFINITION,name:this.parseFragmentName(),typeCondition:(this.expectKeyword("on"),this.parseNamedType()),directives:this.parseDirectives(!1),selectionSet:this.parseSelectionSet(),loc:this.loc(t)}},t.parseFragmentName=function(){if("on"===this._lexer.token.value)throw this.unexpected();return this.parseName()},t.parseValueLiteral=function(e){var t=this._lexer.token;switch(t.kind){case nk.BRACKET_L:return this.parseList(e);case nk.BRACE_L:return this.parseObject(e);case nk.INT:return this._lexer.advance(),{kind:nE.h.INT,value:t.value,loc:this.loc(t)};case nk.FLOAT:return this._lexer.advance(),{kind:nE.h.FLOAT,value:t.value,loc:this.loc(t)};case nk.STRING:case nk.BLOCK_STRING:return this.parseStringLiteral();case nk.NAME:switch(this._lexer.advance(),t.value){case"true":return{kind:nE.h.BOOLEAN,value:!0,loc:this.loc(t)};case"false":return{kind:nE.h.BOOLEAN,value:!1,loc:this.loc(t)};case"null":return{kind:nE.h.NULL,loc:this.loc(t)};default:return{kind:nE.h.ENUM,value:t.value,loc:this.loc(t)}}case nk.DOLLAR:if(!e)return this.parseVariable()}throw this.unexpected()},t.parseStringLiteral=function(){var e=this._lexer.token;return this._lexer.advance(),{kind:nE.h.STRING,value:e.value,block:e.kind===nk.BLOCK_STRING,loc:this.loc(e)}},t.parseList=function(e){var t=this,n=this._lexer.token,r=function(){return t.parseValueLiteral(e)};return{kind:nE.h.LIST,values:this.any(nk.BRACKET_L,r,nk.BRACKET_R),loc:this.loc(n)}},t.parseObject=function(e){var t=this,n=this._lexer.token,r=function(){return t.parseObjectField(e)};return{kind:nE.h.OBJECT,fields:this.any(nk.BRACE_L,r,nk.BRACE_R),loc:this.loc(n)}},t.parseObjectField=function(e){var t=this._lexer.token,n=this.parseName();return this.expectToken(nk.COLON),{kind:nE.h.OBJECT_FIELD,name:n,value:this.parseValueLiteral(e),loc:this.loc(t)}},t.parseDirectives=function(e){for(var t=[];this.peek(nk.AT);)t.push(this.parseDirective(e));return t},t.parseDirective=function(e){var t=this._lexer.token;return this.expectToken(nk.AT),{kind:nE.h.DIRECTIVE,name:this.parseName(),arguments:this.parseArguments(e),loc:this.loc(t)}},t.parseTypeReference=function(){var e,t=this._lexer.token;return(this.expectOptionalToken(nk.BRACKET_L)?(e=this.parseTypeReference(),this.expectToken(nk.BRACKET_R),e={kind:nE.h.LIST_TYPE,type:e,loc:this.loc(t)}):e=this.parseNamedType(),this.expectOptionalToken(nk.BANG))?{kind:nE.h.NON_NULL_TYPE,type:e,loc:this.loc(t)}:e},t.parseNamedType=function(){var e=this._lexer.token;return{kind:nE.h.NAMED_TYPE,name:this.parseName(),loc:this.loc(e)}},t.parseTypeSystemDefinition=function(){var e=this.peekDescription()?this._lexer.lookahead():this._lexer.token;if(e.kind===nk.NAME)switch(e.value){case"schema":return this.parseSchemaDefinition();case"scalar":return this.parseScalarTypeDefinition();case"type":return this.parseObjectTypeDefinition();case"interface":return this.parseInterfaceTypeDefinition();case"union":return this.parseUnionTypeDefinition();case"enum":return this.parseEnumTypeDefinition();case"input":return this.parseInputObjectTypeDefinition();case"directive":return this.parseDirectiveDefinition()}throw this.unexpected(e)},t.peekDescription=function(){return this.peek(nk.STRING)||this.peek(nk.BLOCK_STRING)},t.parseDescription=function(){if(this.peekDescription())return this.parseStringLiteral()},t.parseSchemaDefinition=function(){var e=this._lexer.token,t=this.parseDescription();this.expectKeyword("schema");var n=this.parseDirectives(!0),r=this.many(nk.BRACE_L,this.parseOperationTypeDefinition,nk.BRACE_R);return{kind:nE.h.SCHEMA_DEFINITION,description:t,directives:n,operationTypes:r,loc:this.loc(e)}},t.parseOperationTypeDefinition=function(){var e=this._lexer.token,t=this.parseOperationType();this.expectToken(nk.COLON);var n=this.parseNamedType();return{kind:nE.h.OPERATION_TYPE_DEFINITION,operation:t,type:n,loc:this.loc(e)}},t.parseScalarTypeDefinition=function(){var e=this._lexer.token,t=this.parseDescription();this.expectKeyword("scalar");var n=this.parseName(),r=this.parseDirectives(!0);return{kind:nE.h.SCALAR_TYPE_DEFINITION,description:t,name:n,directives:r,loc:this.loc(e)}},t.parseObjectTypeDefinition=function(){var e=this._lexer.token,t=this.parseDescription();this.expectKeyword("type");var n=this.parseName(),r=this.parseImplementsInterfaces(),i=this.parseDirectives(!0),a=this.parseFieldsDefinition();return{kind:nE.h.OBJECT_TYPE_DEFINITION,description:t,name:n,interfaces:r,directives:i,fields:a,loc:this.loc(e)}},t.parseImplementsInterfaces=function(){var e;if(!this.expectOptionalKeyword("implements"))return[];if((null===(e=this._options)||void 0===e?void 0:e.allowLegacySDLImplementsInterfaces)===!0){var t=[];this.expectOptionalToken(nk.AMP);do t.push(this.parseNamedType());while(this.expectOptionalToken(nk.AMP)||this.peek(nk.NAME))return t}return this.delimitedMany(nk.AMP,this.parseNamedType)},t.parseFieldsDefinition=function(){var e;return(null===(e=this._options)||void 0===e?void 0:e.allowLegacySDLEmptyFields)===!0&&this.peek(nk.BRACE_L)&&this._lexer.lookahead().kind===nk.BRACE_R?(this._lexer.advance(),this._lexer.advance(),[]):this.optionalMany(nk.BRACE_L,this.parseFieldDefinition,nk.BRACE_R)},t.parseFieldDefinition=function(){var e=this._lexer.token,t=this.parseDescription(),n=this.parseName(),r=this.parseArgumentDefs();this.expectToken(nk.COLON);var i=this.parseTypeReference(),a=this.parseDirectives(!0);return{kind:nE.h.FIELD_DEFINITION,description:t,name:n,arguments:r,type:i,directives:a,loc:this.loc(e)}},t.parseArgumentDefs=function(){return this.optionalMany(nk.PAREN_L,this.parseInputValueDef,nk.PAREN_R)},t.parseInputValueDef=function(){var e,t=this._lexer.token,n=this.parseDescription(),r=this.parseName();this.expectToken(nk.COLON);var i=this.parseTypeReference();this.expectOptionalToken(nk.EQUALS)&&(e=this.parseValueLiteral(!0));var a=this.parseDirectives(!0);return{kind:nE.h.INPUT_VALUE_DEFINITION,description:n,name:r,type:i,defaultValue:e,directives:a,loc:this.loc(t)}},t.parseInterfaceTypeDefinition=function(){var e=this._lexer.token,t=this.parseDescription();this.expectKeyword("interface");var n=this.parseName(),r=this.parseImplementsInterfaces(),i=this.parseDirectives(!0),a=this.parseFieldsDefinition();return{kind:nE.h.INTERFACE_TYPE_DEFINITION,description:t,name:n,interfaces:r,directives:i,fields:a,loc:this.loc(e)}},t.parseUnionTypeDefinition=function(){var e=this._lexer.token,t=this.parseDescription();this.expectKeyword("union");var n=this.parseName(),r=this.parseDirectives(!0),i=this.parseUnionMemberTypes();return{kind:nE.h.UNION_TYPE_DEFINITION,description:t,name:n,directives:r,types:i,loc:this.loc(e)}},t.parseUnionMemberTypes=function(){return this.expectOptionalToken(nk.EQUALS)?this.delimitedMany(nk.PIPE,this.parseNamedType):[]},t.parseEnumTypeDefinition=function(){var e=this._lexer.token,t=this.parseDescription();this.expectKeyword("enum");var n=this.parseName(),r=this.parseDirectives(!0),i=this.parseEnumValuesDefinition();return{kind:nE.h.ENUM_TYPE_DEFINITION,description:t,name:n,directives:r,values:i,loc:this.loc(e)}},t.parseEnumValuesDefinition=function(){return this.optionalMany(nk.BRACE_L,this.parseEnumValueDefinition,nk.BRACE_R)},t.parseEnumValueDefinition=function(){var e=this._lexer.token,t=this.parseDescription(),n=this.parseName(),r=this.parseDirectives(!0);return{kind:nE.h.ENUM_VALUE_DEFINITION,description:t,name:n,directives:r,loc:this.loc(e)}},t.parseInputObjectTypeDefinition=function(){var e=this._lexer.token,t=this.parseDescription();this.expectKeyword("input");var n=this.parseName(),r=this.parseDirectives(!0),i=this.parseInputFieldsDefinition();return{kind:nE.h.INPUT_OBJECT_TYPE_DEFINITION,description:t,name:n,directives:r,fields:i,loc:this.loc(e)}},t.parseInputFieldsDefinition=function(){return this.optionalMany(nk.BRACE_L,this.parseInputValueDef,nk.BRACE_R)},t.parseTypeSystemExtension=function(){var e=this._lexer.lookahead();if(e.kind===nk.NAME)switch(e.value){case"schema":return this.parseSchemaExtension();case"scalar":return this.parseScalarTypeExtension();case"type":return this.parseObjectTypeExtension();case"interface":return this.parseInterfaceTypeExtension();case"union":return this.parseUnionTypeExtension();case"enum":return this.parseEnumTypeExtension();case"input":return this.parseInputObjectTypeExtension()}throw this.unexpected(e)},t.parseSchemaExtension=function(){var e=this._lexer.token;this.expectKeyword("extend"),this.expectKeyword("schema");var t=this.parseDirectives(!0),n=this.optionalMany(nk.BRACE_L,this.parseOperationTypeDefinition,nk.BRACE_R);if(0===t.length&&0===n.length)throw this.unexpected();return{kind:nE.h.SCHEMA_EXTENSION,directives:t,operationTypes:n,loc:this.loc(e)}},t.parseScalarTypeExtension=function(){var e=this._lexer.token;this.expectKeyword("extend"),this.expectKeyword("scalar");var t=this.parseName(),n=this.parseDirectives(!0);if(0===n.length)throw this.unexpected();return{kind:nE.h.SCALAR_TYPE_EXTENSION,name:t,directives:n,loc:this.loc(e)}},t.parseObjectTypeExtension=function(){var e=this._lexer.token;this.expectKeyword("extend"),this.expectKeyword("type");var t=this.parseName(),n=this.parseImplementsInterfaces(),r=this.parseDirectives(!0),i=this.parseFieldsDefinition();if(0===n.length&&0===r.length&&0===i.length)throw this.unexpected();return{kind:nE.h.OBJECT_TYPE_EXTENSION,name:t,interfaces:n,directives:r,fields:i,loc:this.loc(e)}},t.parseInterfaceTypeExtension=function(){var e=this._lexer.token;this.expectKeyword("extend"),this.expectKeyword("interface");var t=this.parseName(),n=this.parseImplementsInterfaces(),r=this.parseDirectives(!0),i=this.parseFieldsDefinition();if(0===n.length&&0===r.length&&0===i.length)throw this.unexpected();return{kind:nE.h.INTERFACE_TYPE_EXTENSION,name:t,interfaces:n,directives:r,fields:i,loc:this.loc(e)}},t.parseUnionTypeExtension=function(){var e=this._lexer.token;this.expectKeyword("extend"),this.expectKeyword("union");var t=this.parseName(),n=this.parseDirectives(!0),r=this.parseUnionMemberTypes();if(0===n.length&&0===r.length)throw this.unexpected();return{kind:nE.h.UNION_TYPE_EXTENSION,name:t,directives:n,types:r,loc:this.loc(e)}},t.parseEnumTypeExtension=function(){var e=this._lexer.token;this.expectKeyword("extend"),this.expectKeyword("enum");var t=this.parseName(),n=this.parseDirectives(!0),r=this.parseEnumValuesDefinition();if(0===n.length&&0===r.length)throw this.unexpected();return{kind:nE.h.ENUM_TYPE_EXTENSION,name:t,directives:n,values:r,loc:this.loc(e)}},t.parseInputObjectTypeExtension=function(){var e=this._lexer.token;this.expectKeyword("extend"),this.expectKeyword("input");var t=this.parseName(),n=this.parseDirectives(!0),r=this.parseInputFieldsDefinition();if(0===n.length&&0===r.length)throw this.unexpected();return{kind:nE.h.INPUT_OBJECT_TYPE_EXTENSION,name:t,directives:n,fields:r,loc:this.loc(e)}},t.parseDirectiveDefinition=function(){var e=this._lexer.token,t=this.parseDescription();this.expectKeyword("directive"),this.expectToken(nk.AT);var n=this.parseName(),r=this.parseArgumentDefs(),i=this.expectOptionalKeyword("repeatable");this.expectKeyword("on");var a=this.parseDirectiveLocations();return{kind:nE.h.DIRECTIVE_DEFINITION,description:t,name:n,arguments:r,repeatable:i,locations:a,loc:this.loc(e)}},t.parseDirectiveLocations=function(){return this.delimitedMany(nk.PIPE,this.parseDirectiveLocation)},t.parseDirectiveLocation=function(){var e=this._lexer.token,t=this.parseName();if(void 0!==nT[t.value])return t;throw this.unexpected(e)},t.loc=function(e){var t;if((null===(t=this._options)||void 0===t?void 0:t.noLocation)!==!0)return new nS.Ye(e,this._lexer.lastToken,this._lexer.source)},t.peek=function(e){return this._lexer.token.kind===e},t.expectToken=function(e){var t=this._lexer.token;if(t.kind===e)return this._lexer.advance(),t;throw n_(this._lexer.source,t.start,"Expected ".concat(nG(e),", found ").concat(nz(t),"."))},t.expectOptionalToken=function(e){var t=this._lexer.token;if(t.kind===e)return this._lexer.advance(),t},t.expectKeyword=function(e){var t=this._lexer.token;if(t.kind===nk.NAME&&t.value===e)this._lexer.advance();else throw n_(this._lexer.source,t.start,'Expected "'.concat(e,'", found ').concat(nz(t),"."))},t.expectOptionalKeyword=function(e){var t=this._lexer.token;return t.kind===nk.NAME&&t.value===e&&(this._lexer.advance(),!0)},t.unexpected=function(e){var t=null!=e?e:this._lexer.token;return n_(this._lexer.source,t.start,"Unexpected ".concat(nz(t),"."))},t.any=function(e,t,n){this.expectToken(e);for(var r=[];!this.expectOptionalToken(n);)r.push(t.call(this));return r},t.optionalMany=function(e,t,n){if(this.expectOptionalToken(e)){var r=[];do r.push(t.call(this));while(!this.expectOptionalToken(n))return r}return[]},t.many=function(e,t,n){this.expectToken(e);var r=[];do r.push(t.call(this));while(!this.expectOptionalToken(n))return r},t.delimitedMany=function(e,t){this.expectOptionalToken(e);var n=[];do n.push(t.call(this));while(this.expectOptionalToken(e))return n},e}();function nz(e){var t=e.value;return nG(e.kind)+(null!=t?' "'.concat(t,'"'):"")}function nG(e){return nA(e)?'"'.concat(e,'"'):e}var nW=new Map,nK=new Map,nV=!0,nq=!1;function nZ(e){return e.replace(/[\s,]+/g," ").trim()}function nX(e){return nZ(e.source.body.substring(e.start,e.end))}function nJ(e){var t=new Set,n=[];return e.definitions.forEach(function(e){if("FragmentDefinition"===e.kind){var r=e.name.value,i=nX(e.loc),a=nK.get(r);a&&!a.has(i)?nV&&console.warn("Warning: fragment with name "+r+" already exists.\ngraphql-tag enforces all fragment names across your application to be unique; read more about\nthis in the docs: http://dev.apollodata.com/core/fragments.html#unique-names"):a||nK.set(r,a=new Set),a.add(i),t.has(i)||(t.add(i),n.push(e))}else n.push(e)}),(0,t0.pi)((0,t0.pi)({},e),{definitions:n})}function nQ(e){var t=new Set(e.definitions);t.forEach(function(e){e.loc&&delete e.loc,Object.keys(e).forEach(function(n){var r=e[n];r&&"object"==typeof r&&t.add(r)})});var n=e.loc;return n&&(delete n.startToken,delete n.endToken),e}function n1(e){var t=nZ(e);if(!nW.has(t)){var n=nH(e,{experimentalFragmentVariables:nq,allowLegacyFragmentVariables:nq});if(!n||"Document"!==n.kind)throw Error("Not a valid GraphQL document.");nW.set(t,nQ(nJ(n)))}return nW.get(t)}function n0(e){for(var t=[],n=1;n, or pass an ApolloClient instance in via options.'):(0,n9.kG)(!!n,32),n}var rp=n(10542),rb=n(53712),rm=n(21436),rg=Object.prototype.hasOwnProperty;function rv(e,t){return void 0===t&&(t=Object.create(null)),ry(rh(t.client),e).useQuery(t)}function ry(e,t){var n=(0,l.useRef)();n.current&&e===n.current.client&&t===n.current.query||(n.current=new rw(e,t,n.current));var r=n.current,i=(0,l.useState)(0),a=(i[0],i[1]);return r.forceUpdate=function(){a(function(e){return e+1})},r}var rw=function(){function e(e,t,n){this.client=e,this.query=t,this.ssrDisabledResult=(0,rp.J)({loading:!0,data:void 0,error:void 0,networkStatus:ru.I.loading}),this.skipStandbyResult=(0,rp.J)({loading:!1,data:void 0,error:void 0,networkStatus:ru.I.ready}),this.toQueryResultCache=new(n7.mr?WeakMap:Map),rd(t,r.Query);var i=n&&n.result,a=i&&i.data;a&&(this.previousData=a)}return e.prototype.forceUpdate=function(){__DEV__&&n9.kG.warn("Calling default no-op implementation of InternalState#forceUpdate")},e.prototype.executeQuery=function(e){var t,n=this;e.query&&Object.assign(this,{query:e.query}),this.watchQueryOptions=this.createWatchQueryOptions(this.queryHookOptions=e);var r=this.observable.reobserveAsConcast(this.getObsQueryOptions());return this.previousData=(null===(t=this.result)||void 0===t?void 0:t.data)||this.previousData,this.result=void 0,this.forceUpdate(),new Promise(function(e){var t;r.subscribe({next:function(e){t=e},error:function(){e(n.toQueryResult(n.observable.getCurrentResult()))},complete:function(){e(n.toQueryResult(t))}})})},e.prototype.useQuery=function(e){var t=this;this.renderPromises=(0,l.useContext)((0,ro.K)()).renderPromises,this.useOptions(e);var n=this.useObservableQuery(),r=rt((0,l.useCallback)(function(){if(t.renderPromises)return function(){};var e=function(){var e=t.result,r=n.getCurrentResult();!(e&&e.loading===r.loading&&e.networkStatus===r.networkStatus&&(0,ri.D)(e.data,r.data))&&t.setResult(r)},r=function(a){var o=n.last;i.unsubscribe();try{n.resetLastResults(),i=n.subscribe(e,r)}finally{n.last=o}if(!rg.call(a,"graphQLErrors"))throw a;var s=t.result;(!s||s&&s.loading||!(0,ri.D)(a,s.error))&&t.setResult({data:s&&s.data,error:a,loading:!1,networkStatus:ru.I.error})},i=n.subscribe(e,r);return function(){return setTimeout(function(){return i.unsubscribe()})}},[n,this.renderPromises,this.client.disableNetworkFetches,]),function(){return t.getCurrentResult()},function(){return t.getCurrentResult()});return this.unsafeHandlePartialRefetch(r),this.toQueryResult(r)},e.prototype.useOptions=function(t){var n,r=this.createWatchQueryOptions(this.queryHookOptions=t),i=this.watchQueryOptions;!(0,ri.D)(r,i)&&(this.watchQueryOptions=r,i&&this.observable&&(this.observable.reobserve(this.getObsQueryOptions()),this.previousData=(null===(n=this.result)||void 0===n?void 0:n.data)||this.previousData,this.result=void 0)),this.onCompleted=t.onCompleted||e.prototype.onCompleted,this.onError=t.onError||e.prototype.onError,(this.renderPromises||this.client.disableNetworkFetches)&&!1===this.queryHookOptions.ssr&&!this.queryHookOptions.skip?this.result=this.ssrDisabledResult:this.queryHookOptions.skip||"standby"===this.watchQueryOptions.fetchPolicy?this.result=this.skipStandbyResult:(this.result===this.ssrDisabledResult||this.result===this.skipStandbyResult)&&(this.result=void 0)},e.prototype.getObsQueryOptions=function(){var e=[],t=this.client.defaultOptions.watchQuery;return t&&e.push(t),this.queryHookOptions.defaultOptions&&e.push(this.queryHookOptions.defaultOptions),e.push((0,rb.o)(this.observable&&this.observable.options,this.watchQueryOptions)),e.reduce(ra.J)},e.prototype.createWatchQueryOptions=function(e){void 0===e&&(e={});var t,n=e.skip,r=Object.assign((e.ssr,e.onCompleted,e.onError,e.defaultOptions,(0,t0._T)(e,["skip","ssr","onCompleted","onError","defaultOptions"])),{query:this.query});if(this.renderPromises&&("network-only"===r.fetchPolicy||"cache-and-network"===r.fetchPolicy)&&(r.fetchPolicy="cache-first"),r.variables||(r.variables={}),n){var i=r.fetchPolicy,a=void 0===i?this.getDefaultFetchPolicy():i,o=r.initialFetchPolicy;Object.assign(r,{initialFetchPolicy:void 0===o?a:o,fetchPolicy:"standby"})}else r.fetchPolicy||(r.fetchPolicy=(null===(t=this.observable)||void 0===t?void 0:t.options.initialFetchPolicy)||this.getDefaultFetchPolicy());return r},e.prototype.getDefaultFetchPolicy=function(){var e,t;return(null===(e=this.queryHookOptions.defaultOptions)||void 0===e?void 0:e.fetchPolicy)||(null===(t=this.client.defaultOptions.watchQuery)||void 0===t?void 0:t.fetchPolicy)||"cache-first"},e.prototype.onCompleted=function(e){},e.prototype.onError=function(e){},e.prototype.useObservableQuery=function(){var e=this.observable=this.renderPromises&&this.renderPromises.getSSRObservable(this.watchQueryOptions)||this.observable||this.client.watchQuery(this.getObsQueryOptions());this.obsQueryFields=(0,l.useMemo)(function(){return{refetch:e.refetch.bind(e),reobserve:e.reobserve.bind(e),fetchMore:e.fetchMore.bind(e),updateQuery:e.updateQuery.bind(e),startPolling:e.startPolling.bind(e),stopPolling:e.stopPolling.bind(e),subscribeToMore:e.subscribeToMore.bind(e)}},[e]);var t=!(!1===this.queryHookOptions.ssr||this.queryHookOptions.skip);return this.renderPromises&&t&&(this.renderPromises.registerSSRObservable(e),e.getCurrentResult().loading&&this.renderPromises.addObservableQueryPromise(e)),e},e.prototype.setResult=function(e){var t=this.result;t&&t.data&&(this.previousData=t.data),this.result=e,this.forceUpdate(),this.handleErrorOrCompleted(e)},e.prototype.handleErrorOrCompleted=function(e){var t=this;if(!e.loading){var n=this.toApolloError(e);Promise.resolve().then(function(){n?t.onError(n):e.data&&t.onCompleted(e.data)}).catch(function(e){__DEV__&&n9.kG.warn(e)})}},e.prototype.toApolloError=function(e){return(0,rm.O)(e.errors)?new rs.cA({graphQLErrors:e.errors}):e.error},e.prototype.getCurrentResult=function(){return this.result||this.handleErrorOrCompleted(this.result=this.observable.getCurrentResult()),this.result},e.prototype.toQueryResult=function(e){var t=this.toQueryResultCache.get(e);if(t)return t;var n=e.data,r=(e.partial,(0,t0._T)(e,["data","partial"]));return this.toQueryResultCache.set(e,t=(0,t0.pi)((0,t0.pi)((0,t0.pi)({data:n},r),this.obsQueryFields),{client:this.client,observable:this.observable,variables:this.observable.variables,called:!this.queryHookOptions.skip,previousData:this.previousData})),!t.error&&(0,rm.O)(e.errors)&&(t.error=new rs.cA({graphQLErrors:e.errors})),t},e.prototype.unsafeHandlePartialRefetch=function(e){e.partial&&this.queryHookOptions.partialRefetch&&!e.loading&&(!e.data||0===Object.keys(e.data).length)&&"cache-only"!==this.observable.options.fetchPolicy&&(Object.assign(e,{loading:!0,networkStatus:ru.I.refetch}),this.observable.refetch())},e}();function r_(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&void 0!==arguments[0]?arguments[0]:{};return rv(iH,e)},iz=function(){var e=ij(),t=parseInt(e.get("page")||"1",10),n=parseInt(e.get("per")||"50",10),r=i$({variables:{offset:(t-1)*n,limit:n},fetchPolicy:"network-only"}),i=r.data,a=r.loading,o=r.error;return a?l.createElement(iR,null):o?l.createElement(iD,{error:o}):i?l.createElement(iI,{chains:i.chains.results,page:t,pageSize:n,total:i.chains.metadata.total}):null},iG=n(67932),iW=n(8126),iK="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};function iV(e){if(iq())return Intl.DateTimeFormat.supportedLocalesOf(e)[0]}function iq(){return("undefined"==typeof Intl?"undefined":iK(Intl))==="object"&&"function"==typeof Intl.DateTimeFormat}var iZ="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},iX=function(){function e(e,t){for(var n=0;n=i.length)break;s=i[o++]}else{if((o=i.next()).done)break;s=o.value}var s,u=s;if((void 0===e?"undefined":iZ(e))!=="object")return;e=e[u]}return e}},{key:"put",value:function(){for(var e=arguments.length,t=Array(e),n=0;n=o.length)break;c=o[u++]}else{if((u=o.next()).done)break;c=u.value}var c,l=c;"object"!==iZ(a[l])&&(a[l]={}),a=a[l]}return a[i]=r}}]),e}();let i1=iQ;var i0=new i1;function i2(e,t){if(!iq())return function(e){return e.toString()};var n=i4(e),r=JSON.stringify(t),i=i0.get(String(n),r)||i0.put(String(n),r,new Intl.DateTimeFormat(n,t));return function(e){return i.format(e)}}var i3={};function i4(e){var t=e.toString();return i3[t]?i3[t]:i3[t]=iV(e)}var i6="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};function i5(e){return i8(e)?e:new Date(e)}function i8(e){return e instanceof Date||i9(e)}function i9(e){return(void 0===e?"undefined":i6(e))==="object"&&"function"==typeof e.getTime}var i7=n(54087),ae=n.n(i7);function at(e,t){if(0===e.length)return 0;for(var n=0,r=e.length-1,i=void 0;n<=r;){var a=t(e[i=Math.floor((r+n)/2)]);if(0===a)return i;if(a<0){if((n=i+1)>r)return n}else if((r=i-1)=t.nextUpdateTime)aa(t,this.instances);else break}},scheduleNextTick:function(){var e=this;this.scheduledTick=ae()(function(){e.tick(),e.scheduleNextTick()})},start:function(){this.scheduleNextTick()},stop:function(){ae().cancel(this.scheduledTick)}};function ai(e){var t=an(e.getNextValue(),2),n=t[0],r=t[1];e.setValue(n),e.nextUpdateTime=r}function aa(e,t){ai(e),as(t,e),ao(t,e)}function ao(e,t){var n=au(e,t);e.splice(n,0,t)}function as(e,t){var n=e.indexOf(t);e.splice(n,1)}function au(e,t){var n=t.nextUpdateTime;return at(e,function(e){return e.nextUpdateTime===n?0:e.nextUpdateTime>n?1:-1})}var ac=(0,ec.oneOfType)([(0,ec.shape)({minTime:ec.number,formatAs:ec.string.isRequired}),(0,ec.shape)({test:ec.func,formatAs:ec.string.isRequired}),(0,ec.shape)({minTime:ec.number,format:ec.func.isRequired}),(0,ec.shape)({test:ec.func,format:ec.func.isRequired})]),al=(0,ec.oneOfType)([ec.string,(0,ec.shape)({steps:(0,ec.arrayOf)(ac).isRequired,labels:(0,ec.oneOfType)([ec.string,(0,ec.arrayOf)(ec.string)]).isRequired,round:ec.string})]),af=Object.assign||function(e){for(var t=1;t=0)&&Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n}function ap(e){var t=e.date,n=e.future,r=e.timeStyle,i=e.round,a=e.minTimeLeft,o=e.tooltip,s=e.component,u=e.container,c=e.wrapperComponent,f=e.wrapperProps,d=e.locale,h=e.locales,p=e.formatVerboseDate,b=e.verboseDateFormat,m=e.updateInterval,g=e.tick,v=ah(e,["date","future","timeStyle","round","minTimeLeft","tooltip","component","container","wrapperComponent","wrapperProps","locale","locales","formatVerboseDate","verboseDateFormat","updateInterval","tick"]),y=(0,l.useMemo)(function(){return d&&(h=[d]),h.concat(iW.Z.getDefaultLocale())},[d,h]),w=(0,l.useMemo)(function(){return new iW.Z(y)},[y]);t=(0,l.useMemo)(function(){return i5(t)},[t]);var _=(0,l.useCallback)(function(){var e=Date.now(),o=void 0;if(n&&e>=t.getTime()&&(e=t.getTime(),o=!0),void 0!==a){var s=t.getTime()-1e3*a;e>s&&(e=s,o=!0)}var u=w.format(t,r,{getTimeToNextUpdate:!0,now:e,future:n,round:i}),c=ad(u,2),l=c[0],f=c[1];return f=o?ag:m||f||6e4,[l,e+f]},[t,n,r,m,i,a,w]),E=(0,l.useRef)();E.current=_;var S=(0,l.useMemo)(_,[]),k=ad(S,2),x=k[0],T=k[1],M=(0,l.useState)(x),O=ad(M,2),A=O[0],L=O[1],C=ad((0,l.useState)(),2),I=C[0],D=C[1],N=(0,l.useRef)();(0,l.useEffect)(function(){if(g)return N.current=ar.add({getNextValue:function(){return E.current()},setValue:L,nextUpdateTime:T}),function(){return N.current.stop()}},[g]),(0,l.useEffect)(function(){if(N.current)N.current.forceUpdate();else{var e=_(),t=ad(e,1)[0];L(t)}},[_]),(0,l.useEffect)(function(){D(!0)},[]);var P=(0,l.useMemo)(function(){if("undefined"!=typeof window)return i2(y,b)},[y,b]),R=(0,l.useMemo)(function(){if("undefined"!=typeof window)return p?p(t):P(t)},[t,p,P]),j=l.createElement(s,af({date:t,verboseDate:I?R:void 0,tooltip:o},v),A),F=c||u;return F?l.createElement(F,af({},f,{verboseDate:I?R:void 0}),j):j}ap.propTypes={date:el().oneOfType([el().instanceOf(Date),el().number]).isRequired,locale:el().string,locales:el().arrayOf(el().string),future:el().bool,timeStyle:al,round:el().string,minTimeLeft:el().number,component:el().elementType.isRequired,tooltip:el().bool.isRequired,formatVerboseDate:el().func,verboseDateFormat:el().object,updateInterval:el().oneOfType([el().number,el().arrayOf(el().shape({threshold:el().number,interval:el().number.isRequired}))]),tick:el().bool,wrapperComponent:el().func,wrapperProps:el().object},ap.defaultProps={locales:[],component:av,tooltip:!0,verboseDateFormat:{weekday:"long",day:"numeric",month:"long",year:"numeric",hour:"numeric",minute:"2-digit",second:"2-digit"},tick:!0},ap=l.memo(ap);let ab=ap;var am,ag=31536e9;function av(e){var t=e.date,n=e.verboseDate,r=e.tooltip,i=e.children,a=ah(e,["date","verboseDate","tooltip","children"]),o=(0,l.useMemo)(function(){return t.toISOString()},[t]);return l.createElement("time",af({},a,{dateTime:o,title:r?n:void 0}),i)}av.propTypes={date:el().instanceOf(Date).isRequired,verboseDate:el().string,tooltip:el().bool.isRequired,children:el().string.isRequired};var ay=n(30381),aw=n.n(ay),a_=n(31657);function aE(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function aS(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]0?new rs.cA({graphQLErrors:i}):void 0;if(u===s.current.mutationId&&!c.ignoreResults){var f={called:!0,loading:!1,data:r,error:l,client:a};s.current.isMounted&&!(0,ri.D)(s.current.result,f)&&o(s.current.result=f)}var d=e.onCompleted||(null===(n=s.current.options)||void 0===n?void 0:n.onCompleted);return null==d||d(t.data,c),t}).catch(function(t){if(u===s.current.mutationId&&s.current.isMounted){var n,r={loading:!1,error:t,data:void 0,called:!0,client:a};(0,ri.D)(s.current.result,r)||o(s.current.result=r)}var i=e.onError||(null===(n=s.current.options)||void 0===n?void 0:n.onError);if(i)return i(t,c),{data:void 0,errors:t};throw t})},[]),c=(0,l.useCallback)(function(){s.current.isMounted&&o({called:!1,loading:!1,client:n})},[]);return(0,l.useEffect)(function(){return s.current.isMounted=!0,function(){s.current.isMounted=!1}},[]),[u,(0,t0.pi)({reset:c},a)]}var os=n(59067),ou=n(28428),oc=n(11186),ol=n(78513);function of(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var od=function(e){return(0,b.createStyles)({paper:{display:"flex",margin:"".concat(2.5*e.spacing.unit,"px 0"),padding:"".concat(3*e.spacing.unit,"px ").concat(3.5*e.spacing.unit,"px")},content:{flex:1,width:"100%"},actions:of({marginTop:-(1.5*e.spacing.unit),marginLeft:-(4*e.spacing.unit)},e.breakpoints.up("sm"),{marginLeft:0,marginRight:-(1.5*e.spacing.unit)}),itemBlock:{border:"1px solid rgba(224, 224, 224, 1)",borderRadius:e.shape.borderRadius,padding:2*e.spacing.unit,marginTop:e.spacing.unit},itemBlockText:{overflowWrap:"anywhere"}})},oh=(0,b.withStyles)(od)(function(e){var t=e.actions,n=e.children,r=e.classes;return l.createElement(ii.default,{className:r.paper},l.createElement("div",{className:r.content},n),t&&l.createElement("div",{className:r.actions},t))}),op=function(e){var t=e.title;return l.createElement(x.default,{variant:"subtitle2",gutterBottom:!0},t)},ob=function(e){var t=e.children,n=e.value;return l.createElement(x.default,{variant:"body1",noWrap:!0},t||n)},om=(0,b.withStyles)(od)(function(e){var t=e.children,n=e.classes,r=e.value;return l.createElement("div",{className:n.itemBlock},l.createElement(x.default,{variant:"body1",className:n.itemBlockText},t||r))});function og(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]-1}let sq=sV;function sZ(e,t){var n=this.__data__,r=sH(n,e);return r<0?(++this.size,n.push([e,t])):n[r][1]=t,this}let sX=sZ;function sJ(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t-1&&e%1==0&&e-1&&e%1==0&&e<=cC}let cD=cI;var cN="[object Arguments]",cP="[object Array]",cR="[object Boolean]",cj="[object Date]",cF="[object Error]",cY="[object Function]",cB="[object Map]",cU="[object Number]",cH="[object Object]",c$="[object RegExp]",cz="[object Set]",cG="[object String]",cW="[object WeakMap]",cK="[object ArrayBuffer]",cV="[object DataView]",cq="[object Float64Array]",cZ="[object Int8Array]",cX="[object Int16Array]",cJ="[object Int32Array]",cQ="[object Uint8Array]",c1="[object Uint8ClampedArray]",c0="[object Uint16Array]",c2="[object Uint32Array]",c3={};function c4(e){return eD(e)&&cD(e.length)&&!!c3[eC(e)]}c3["[object Float32Array]"]=c3[cq]=c3[cZ]=c3[cX]=c3[cJ]=c3[cQ]=c3[c1]=c3[c0]=c3[c2]=!0,c3[cN]=c3[cP]=c3[cK]=c3[cR]=c3[cV]=c3[cj]=c3[cF]=c3[cY]=c3[cB]=c3[cU]=c3[cH]=c3[c$]=c3[cz]=c3[cG]=c3[cW]=!1;let c6=c4;function c5(e){return function(t){return e(t)}}let c8=c5;var c9=n(79730),c7=c9.Z&&c9.Z.isTypedArray,le=c7?c8(c7):c6;let lt=le;var ln=Object.prototype.hasOwnProperty;function lr(e,t){var n=cx(e),r=!n&&cS(e),i=!n&&!r&&(0,cT.Z)(e),a=!n&&!r&&!i&<(e),o=n||r||i||a,s=o?cb(e.length,String):[],u=s.length;for(var c in e)(t||ln.call(e,c))&&!(o&&("length"==c||i&&("offset"==c||"parent"==c)||a&&("buffer"==c||"byteLength"==c||"byteOffset"==c)||cL(c,u)))&&s.push(c);return s}let li=lr;var la=Object.prototype;function lo(e){var t=e&&e.constructor;return e===("function"==typeof t&&t.prototype||la)}let ls=lo;var lu=sT(Object.keys,Object);let lc=lu;var ll=Object.prototype.hasOwnProperty;function lf(e){if(!ls(e))return lc(e);var t=[];for(var n in Object(e))ll.call(e,n)&&"constructor"!=n&&t.push(n);return t}let ld=lf;function lh(e){return null!=e&&cD(e.length)&&!ur(e)}let lp=lh;function lb(e){return lp(e)?li(e):ld(e)}let lm=lb;function lg(e,t){return e&&ch(t,lm(t),e)}let lv=lg;function ly(e){var t=[];if(null!=e)for(var n in Object(e))t.push(n);return t}let lw=ly;var l_=Object.prototype.hasOwnProperty;function lE(e){if(!ed(e))return lw(e);var t=ls(e),n=[];for(var r in e)"constructor"==r&&(t||!l_.call(e,r))||n.push(r);return n}let lS=lE;function lk(e){return lp(e)?li(e,!0):lS(e)}let lx=lk;function lT(e,t){return e&&ch(t,lx(t),e)}let lM=lT;var lO=n(42896);function lA(e,t){var n=-1,r=e.length;for(t||(t=Array(r));++n=0||(i[n]=e[n]);return i}function hu(e){if(void 0===e)throw ReferenceError("this hasn't been initialised - super() hasn't been called");return e}var hc=function(e){return Array.isArray(e)&&0===e.length},hl=function(e){return"function"==typeof e},hf=function(e){return null!==e&&"object"==typeof e},hd=function(e){return String(Math.floor(Number(e)))===e},hh=function(e){return"[object String]"===Object.prototype.toString.call(e)},hp=function(e){return 0===l.Children.count(e)},hb=function(e){return hf(e)&&hl(e.then)};function hm(e,t,n,r){void 0===r&&(r=0);for(var i=d8(t);e&&r=0?[]:{}}}return(0===a?e:i)[o[a]]===n?e:(void 0===n?delete i[o[a]]:i[o[a]]=n,0===a&&void 0===n&&delete r[o[a]],r)}function hv(e,t,n,r){void 0===n&&(n=new WeakMap),void 0===r&&(r={});for(var i=0,a=Object.keys(e);i0?t.map(function(t){return x(t,hm(e,t))}):[Promise.resolve("DO_NOT_DELETE_YOU_WILL_BE_FIRED")]).then(function(e){return e.reduce(function(e,n,r){return"DO_NOT_DELETE_YOU_WILL_BE_FIRED"===n||n&&(e=hg(e,t[r],n)),e},{})})},[x]),M=(0,l.useCallback)(function(e){return Promise.all([T(e),h.validationSchema?k(e):{},h.validate?S(e):{}]).then(function(e){var t=e[0],n=e[1],r=e[2];return sk.all([t,n,r],{arrayMerge:hL})})},[h.validate,h.validationSchema,T,S,k]),O=hN(function(e){return void 0===e&&(e=_.values),E({type:"SET_ISVALIDATING",payload:!0}),M(e).then(function(e){return v.current&&(E({type:"SET_ISVALIDATING",payload:!1}),sd()(_.errors,e)||E({type:"SET_ERRORS",payload:e})),e})});(0,l.useEffect)(function(){o&&!0===v.current&&sd()(p.current,h.initialValues)&&O(p.current)},[o,O]);var A=(0,l.useCallback)(function(e){var t=e&&e.values?e.values:p.current,n=e&&e.errors?e.errors:b.current?b.current:h.initialErrors||{},r=e&&e.touched?e.touched:m.current?m.current:h.initialTouched||{},i=e&&e.status?e.status:g.current?g.current:h.initialStatus;p.current=t,b.current=n,m.current=r,g.current=i;var a=function(){E({type:"RESET_FORM",payload:{isSubmitting:!!e&&!!e.isSubmitting,errors:n,touched:r,status:i,values:t,isValidating:!!e&&!!e.isValidating,submitCount:e&&e.submitCount&&"number"==typeof e.submitCount?e.submitCount:0}})};if(h.onReset){var o=h.onReset(_.values,V);hb(o)?o.then(a):a()}else a()},[h.initialErrors,h.initialStatus,h.initialTouched]);(0,l.useEffect)(function(){!0===v.current&&!sd()(p.current,h.initialValues)&&(c&&(p.current=h.initialValues,A()),o&&O(p.current))},[c,h.initialValues,A,o,O]),(0,l.useEffect)(function(){c&&!0===v.current&&!sd()(b.current,h.initialErrors)&&(b.current=h.initialErrors||hS,E({type:"SET_ERRORS",payload:h.initialErrors||hS}))},[c,h.initialErrors]),(0,l.useEffect)(function(){c&&!0===v.current&&!sd()(m.current,h.initialTouched)&&(m.current=h.initialTouched||hk,E({type:"SET_TOUCHED",payload:h.initialTouched||hk}))},[c,h.initialTouched]),(0,l.useEffect)(function(){c&&!0===v.current&&!sd()(g.current,h.initialStatus)&&(g.current=h.initialStatus,E({type:"SET_STATUS",payload:h.initialStatus}))},[c,h.initialStatus,h.initialTouched]);var L=hN(function(e){if(y.current[e]&&hl(y.current[e].validate)){var t=hm(_.values,e),n=y.current[e].validate(t);return hb(n)?(E({type:"SET_ISVALIDATING",payload:!0}),n.then(function(e){return e}).then(function(t){E({type:"SET_FIELD_ERROR",payload:{field:e,value:t}}),E({type:"SET_ISVALIDATING",payload:!1})})):(E({type:"SET_FIELD_ERROR",payload:{field:e,value:n}}),Promise.resolve(n))}return h.validationSchema?(E({type:"SET_ISVALIDATING",payload:!0}),k(_.values,e).then(function(e){return e}).then(function(t){E({type:"SET_FIELD_ERROR",payload:{field:e,value:t[e]}}),E({type:"SET_ISVALIDATING",payload:!1})})):Promise.resolve()}),C=(0,l.useCallback)(function(e,t){var n=t.validate;y.current[e]={validate:n}},[]),I=(0,l.useCallback)(function(e){delete y.current[e]},[]),D=hN(function(e,t){return E({type:"SET_TOUCHED",payload:e}),(void 0===t?i:t)?O(_.values):Promise.resolve()}),N=(0,l.useCallback)(function(e){E({type:"SET_ERRORS",payload:e})},[]),P=hN(function(e,t){var r=hl(e)?e(_.values):e;return E({type:"SET_VALUES",payload:r}),(void 0===t?n:t)?O(r):Promise.resolve()}),R=(0,l.useCallback)(function(e,t){E({type:"SET_FIELD_ERROR",payload:{field:e,value:t}})},[]),j=hN(function(e,t,r){return E({type:"SET_FIELD_VALUE",payload:{field:e,value:t}}),(void 0===r?n:r)?O(hg(_.values,e,t)):Promise.resolve()}),F=(0,l.useCallback)(function(e,t){var n,r=t,i=e;if(!hh(e)){e.persist&&e.persist();var a=e.target?e.target:e.currentTarget,o=a.type,s=a.name,u=a.id,c=a.value,l=a.checked,f=(a.outerHTML,a.options),d=a.multiple;r=t||s||u,i=/number|range/.test(o)?(n=parseFloat(c),isNaN(n)?"":n):/checkbox/.test(o)?hI(hm(_.values,r),l,c):d?hC(f):c}r&&j(r,i)},[j,_.values]),Y=hN(function(e){if(hh(e))return function(t){return F(t,e)};F(e)}),B=hN(function(e,t,n){return void 0===t&&(t=!0),E({type:"SET_FIELD_TOUCHED",payload:{field:e,value:t}}),(void 0===n?i:n)?O(_.values):Promise.resolve()}),U=(0,l.useCallback)(function(e,t){e.persist&&e.persist();var n,r=e.target,i=r.name,a=r.id;r.outerHTML,B(t||i||a,!0)},[B]),H=hN(function(e){if(hh(e))return function(t){return U(t,e)};U(e)}),$=(0,l.useCallback)(function(e){hl(e)?E({type:"SET_FORMIK_STATE",payload:e}):E({type:"SET_FORMIK_STATE",payload:function(){return e}})},[]),z=(0,l.useCallback)(function(e){E({type:"SET_STATUS",payload:e})},[]),G=(0,l.useCallback)(function(e){E({type:"SET_ISSUBMITTING",payload:e})},[]),W=hN(function(){return E({type:"SUBMIT_ATTEMPT"}),O().then(function(e){var t,n=e instanceof Error;if(!n&&0===Object.keys(e).length){try{if(void 0===(t=q()))return}catch(r){throw r}return Promise.resolve(t).then(function(e){return v.current&&E({type:"SUBMIT_SUCCESS"}),e}).catch(function(e){if(v.current)throw E({type:"SUBMIT_FAILURE"}),e})}if(v.current&&(E({type:"SUBMIT_FAILURE"}),n))throw e})}),K=hN(function(e){e&&e.preventDefault&&hl(e.preventDefault)&&e.preventDefault(),e&&e.stopPropagation&&hl(e.stopPropagation)&&e.stopPropagation(),W().catch(function(e){console.warn("Warning: An unhandled error was caught from submitForm()",e)})}),V={resetForm:A,validateForm:O,validateField:L,setErrors:N,setFieldError:R,setFieldTouched:B,setFieldValue:j,setStatus:z,setSubmitting:G,setTouched:D,setValues:P,setFormikState:$,submitForm:W},q=hN(function(){return f(_.values,V)}),Z=hN(function(e){e&&e.preventDefault&&hl(e.preventDefault)&&e.preventDefault(),e&&e.stopPropagation&&hl(e.stopPropagation)&&e.stopPropagation(),A()}),X=(0,l.useCallback)(function(e){return{value:hm(_.values,e),error:hm(_.errors,e),touched:!!hm(_.touched,e),initialValue:hm(p.current,e),initialTouched:!!hm(m.current,e),initialError:hm(b.current,e)}},[_.errors,_.touched,_.values]),J=(0,l.useCallback)(function(e){return{setValue:function(t,n){return j(e,t,n)},setTouched:function(t,n){return B(e,t,n)},setError:function(t){return R(e,t)}}},[j,B,R]),Q=(0,l.useCallback)(function(e){var t=hf(e),n=t?e.name:e,r=hm(_.values,n),i={name:n,value:r,onChange:Y,onBlur:H};if(t){var a=e.type,o=e.value,s=e.as,u=e.multiple;"checkbox"===a?void 0===o?i.checked=!!r:(i.checked=!!(Array.isArray(r)&&~r.indexOf(o)),i.value=o):"radio"===a?(i.checked=r===o,i.value=o):"select"===s&&u&&(i.value=i.value||[],i.multiple=!0)}return i},[H,Y,_.values]),ee=(0,l.useMemo)(function(){return!sd()(p.current,_.values)},[p.current,_.values]),et=(0,l.useMemo)(function(){return void 0!==s?ee?_.errors&&0===Object.keys(_.errors).length:!1!==s&&hl(s)?s(h):s:_.errors&&0===Object.keys(_.errors).length},[s,ee,_.errors,h]);return ha({},_,{initialValues:p.current,initialErrors:b.current,initialTouched:m.current,initialStatus:g.current,handleBlur:H,handleChange:Y,handleReset:Z,handleSubmit:K,resetForm:A,setErrors:N,setFormikState:$,setFieldTouched:B,setFieldValue:j,setFieldError:R,setStatus:z,setSubmitting:G,setTouched:D,setValues:P,submitForm:W,validateForm:O,validateField:L,isValid:et,dirty:ee,unregisterField:I,registerField:C,getFieldProps:Q,getFieldMeta:X,getFieldHelpers:J,validateOnBlur:i,validateOnChange:n,validateOnMount:o})}function hT(e){var t=hx(e),n=e.component,r=e.children,i=e.render,a=e.innerRef;return(0,l.useImperativeHandle)(a,function(){return t}),(0,l.createElement)(hw,{value:t},n?(0,l.createElement)(n,t):i?i(t):r?hl(r)?r(t):hp(r)?null:l.Children.only(r):null)}function hM(e){var t={};if(e.inner){if(0===e.inner.length)return hg(t,e.path,e.message);for(var n=e.inner,r=Array.isArray(n),i=0,n=r?n:n[Symbol.iterator]();;){if(r){if(i>=n.length)break;a=n[i++]}else{if((i=n.next()).done)break;a=i.value}var a,o=a;hm(t,o.path)||(t=hg(t,o.path,o.message))}}return t}function hO(e,t,n,r){void 0===n&&(n=!1),void 0===r&&(r={});var i=hA(e);return t[n?"validateSync":"validate"](i,{abortEarly:!1,context:r})}function hA(e){var t=Array.isArray(e)?[]:{};for(var n in e)if(Object.prototype.hasOwnProperty.call(e,n)){var r=String(n);!0===Array.isArray(e[r])?t[r]=e[r].map(function(e){return!0===Array.isArray(e)||sR(e)?hA(e):""!==e?e:void 0}):sR(e[r])?t[r]=hA(e[r]):t[r]=""!==e[r]?e[r]:void 0}return t}function hL(e,t,n){var r=e.slice();return t.forEach(function(t,i){if(void 0===r[i]){var a=!1!==n.clone&&n.isMergeableObject(t);r[i]=a?sk(Array.isArray(t)?[]:{},t,n):t}else n.isMergeableObject(t)?r[i]=sk(e[i],t,n):-1===e.indexOf(t)&&r.push(t)}),r}function hC(e){return Array.from(e).filter(function(e){return e.selected}).map(function(e){return e.value})}function hI(e,t,n){if("boolean"==typeof e)return Boolean(t);var r=[],i=!1,a=-1;if(Array.isArray(e))r=e,i=(a=e.indexOf(n))>=0;else if(!n||"true"==n||"false"==n)return Boolean(t);return t&&n&&!i?r.concat(n):i?r.slice(0,a).concat(r.slice(a+1)):r}var hD="undefined"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement?l.useLayoutEffect:l.useEffect;function hN(e){var t=(0,l.useRef)(e);return hD(function(){t.current=e}),(0,l.useCallback)(function(){for(var e=arguments.length,n=Array(e),r=0;re?t:e},0);return Array.from(ha({},e,{length:t+1}))};(function(e){function t(t){var n;return(n=e.call(this,t)||this).updateArrayField=function(e,t,r){var i=n.props,a=i.name;(0,i.formik.setFormikState)(function(n){var i="function"==typeof r?r:e,o="function"==typeof t?t:e,s=hg(n.values,a,e(hm(n.values,a))),u=r?i(hm(n.errors,a)):void 0,c=t?o(hm(n.touched,a)):void 0;return hc(u)&&(u=void 0),hc(c)&&(c=void 0),ha({},n,{values:s,errors:r?hg(n.errors,a,u):n.errors,touched:t?hg(n.touched,a,c):n.touched})})},n.push=function(e){return n.updateArrayField(function(t){return[].concat(hU(t),[hi(e)])},!1,!1)},n.handlePush=function(e){return function(){return n.push(e)}},n.swap=function(e,t){return n.updateArrayField(function(n){return hF(n,e,t)},!0,!0)},n.handleSwap=function(e,t){return function(){return n.swap(e,t)}},n.move=function(e,t){return n.updateArrayField(function(n){return hj(n,e,t)},!0,!0)},n.handleMove=function(e,t){return function(){return n.move(e,t)}},n.insert=function(e,t){return n.updateArrayField(function(n){return hY(n,e,t)},function(t){return hY(t,e,null)},function(t){return hY(t,e,null)})},n.handleInsert=function(e,t){return function(){return n.insert(e,t)}},n.replace=function(e,t){return n.updateArrayField(function(n){return hB(n,e,t)},!1,!1)},n.handleReplace=function(e,t){return function(){return n.replace(e,t)}},n.unshift=function(e){var t=-1;return n.updateArrayField(function(n){var r=n?[e].concat(n):[e];return t<0&&(t=r.length),r},function(e){var n=e?[null].concat(e):[null];return t<0&&(t=n.length),n},function(e){var n=e?[null].concat(e):[null];return t<0&&(t=n.length),n}),t},n.handleUnshift=function(e){return function(){return n.unshift(e)}},n.handleRemove=function(e){return function(){return n.remove(e)}},n.handlePop=function(){return function(){return n.pop()}},n.remove=n.remove.bind(hu(n)),n.pop=n.pop.bind(hu(n)),n}ho(t,e);var n=t.prototype;return n.componentDidUpdate=function(e){this.props.validateOnChange&&this.props.formik.validateOnChange&&!sd()(hm(e.formik.values,e.name),hm(this.props.formik.values,this.props.name))&&this.props.formik.validateForm(this.props.formik.values)},n.remove=function(e){var t;return this.updateArrayField(function(n){var r=n?hU(n):[];return t||(t=r[e]),hl(r.splice)&&r.splice(e,1),r},!0,!0),t},n.pop=function(){var e;return this.updateArrayField(function(t){var n=t;return e||(e=n&&n.pop&&n.pop()),n},!0,!0),e},n.render=function(){var e={push:this.push,pop:this.pop,swap:this.swap,move:this.move,insert:this.insert,replace:this.replace,unshift:this.unshift,remove:this.remove,handlePush:this.handlePush,handlePop:this.handlePop,handleSwap:this.handleSwap,handleMove:this.handleMove,handleInsert:this.handleInsert,handleReplace:this.handleReplace,handleUnshift:this.handleUnshift,handleRemove:this.handleRemove},t=this.props,n=t.component,r=t.render,i=t.children,a=t.name,o=hs(t.formik,["validate","validationSchema"]),s=ha({},e,{form:o,name:a});return n?(0,l.createElement)(n,s):r?r(s):i?"function"==typeof i?i(s):hp(i)?null:l.Children.only(i):null},t})(l.Component).defaultProps={validateOnChange:!0},l.Component,l.Component;var hH=n(24802),h$=n(71209),hz=n(91750),hG=n(11970),hW=n(4689),hK=n(67598),hV=function(){return(hV=Object.assign||function(e){for(var t,n=1,r=arguments.length;nt.indexOf(r)&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var i=0,r=Object.getOwnPropertySymbols(e);it.indexOf(r[i])&&(n[r[i]]=e[r[i]]);return n}function hZ(e){var t=e.disabled,n=e.field,r=n.onBlur,i=hq(n,["onBlur"]),a=e.form,o=a.isSubmitting,s=a.touched,u=a.errors,c=e.onBlur,l=e.helperText,f=hq(e,["disabled","field","form","onBlur","helperText"]),d=hm(u,i.name),h=hm(s,i.name)&&!!d;return hV(hV({variant:f.variant,error:h,helperText:h?d:l,disabled:null!=t?t:o,onBlur:null!=c?c:function(e){r(null!=e?e:i.name)}},i),f)}function hX(e){var t=e.children,n=hq(e,["children"]);return(0,l.createElement)(iw.Z,hV({},hZ(n)),t)}function hJ(e){var t=e.disabled,n=e.field,r=n.onBlur,i=hq(n,["onBlur"]),a=e.form.isSubmitting,o=(e.type,e.onBlur),s=hq(e,["disabled","field","form","type","onBlur"]);return hV(hV({disabled:null!=t?t:a,onBlur:null!=o?o:function(e){r(null!=e?e:i.name)}},i),s)}function hQ(e){return(0,l.createElement)(hH.Z,hV({},hJ(e)))}function h1(e){var t,n=e.disabled,r=e.field,i=r.onBlur,a=hq(r,["onBlur"]),o=e.form.isSubmitting,s=(e.type,e.onBlur),u=hq(e,["disabled","field","form","type","onBlur"]);return hV(hV({disabled:null!=n?n:o,indeterminate:!Array.isArray(a.value)&&null==a.value,onBlur:null!=s?s:function(e){i(null!=e?e:a.name)}},a),u)}function h0(e){return(0,l.createElement)(h$.Z,hV({},h1(e)))}function h2(e){var t=e.Label,n=hq(e,["Label"]);return(0,l.createElement)(hz.Z,hV({control:(0,l.createElement)(h$.Z,hV({},h1(n)))},t))}function h3(e){var t=e.disabled,n=e.field,r=n.onBlur,i=hq(n,["onBlur"]),a=e.form.isSubmitting,o=e.onBlur,s=hq(e,["disabled","field","form","onBlur"]);return hV(hV({disabled:null!=t?t:a,onBlur:null!=o?o:function(e){r(null!=e?e:i.name)}},i),s)}function h4(e){return(0,l.createElement)(hG.default,hV({},h3(e)))}function h6(e){var t=e.field,n=t.onBlur,r=hq(t,["onBlur"]),i=(e.form,e.onBlur),a=hq(e,["field","form","onBlur"]);return hV(hV({onBlur:null!=i?i:function(e){n(null!=e?e:r.name)}},r),a)}function h5(e){return(0,l.createElement)(hW.Z,hV({},h6(e)))}function h8(e){var t=e.disabled,n=e.field,r=n.onBlur,i=hq(n,["onBlur"]),a=e.form.isSubmitting,o=e.onBlur,s=hq(e,["disabled","field","form","onBlur"]);return hV(hV({disabled:null!=t?t:a,onBlur:null!=o?o:function(e){r(null!=e?e:i.name)}},i),s)}function h9(e){return(0,l.createElement)(hK.default,hV({},h8(e)))}hX.displayName="FormikMaterialUITextField",hQ.displayName="FormikMaterialUISwitch",h0.displayName="FormikMaterialUICheckbox",h2.displayName="FormikMaterialUICheckboxWithLabel",h4.displayName="FormikMaterialUISelect",h5.displayName="FormikMaterialUIRadioGroup",h9.displayName="FormikMaterialUIInputBase";try{a=Map}catch(h7){}try{o=Set}catch(pe){}function pt(e,t,n){if(!e||"object"!=typeof e||"function"==typeof e)return e;if(e.nodeType&&"cloneNode"in e)return e.cloneNode(!0);if(e instanceof Date)return new Date(e.getTime());if(e instanceof RegExp)return RegExp(e);if(Array.isArray(e))return e.map(pn);if(a&&e instanceof a)return new Map(Array.from(e.entries()));if(o&&e instanceof o)return new Set(Array.from(e.values()));if(e instanceof Object){t.push(e);var r=Object.create(e);for(var i in n.push(r),e){var s=t.findIndex(function(t){return t===e[i]});r[i]=s>-1?n[s]:pt(e[i],t,n)}return r}return e}function pn(e){return pt(e,[],[])}let pr=Object.prototype.toString,pi=Error.prototype.toString,pa=RegExp.prototype.toString,po="undefined"!=typeof Symbol?Symbol.prototype.toString:()=>"",ps=/^Symbol\((.*)\)(.*)$/;function pu(e){if(e!=+e)return"NaN";let t=0===e&&1/e<0;return t?"-0":""+e}function pc(e,t=!1){if(null==e||!0===e||!1===e)return""+e;let n=typeof e;if("number"===n)return pu(e);if("string"===n)return t?`"${e}"`:e;if("function"===n)return"[Function "+(e.name||"anonymous")+"]";if("symbol"===n)return po.call(e).replace(ps,"Symbol($1)");let r=pr.call(e).slice(8,-1);return"Date"===r?isNaN(e.getTime())?""+e:e.toISOString(e):"Error"===r||e instanceof Error?"["+pi.call(e)+"]":"RegExp"===r?pa.call(e):null}function pl(e,t){let n=pc(e,t);return null!==n?n:JSON.stringify(e,function(e,n){let r=pc(this[e],t);return null!==r?r:n},2)}let pf={default:"${path} is invalid",required:"${path} is a required field",oneOf:"${path} must be one of the following values: ${values}",notOneOf:"${path} must not be one of the following values: ${values}",notType({path:e,type:t,value:n,originalValue:r}){let i=null!=r&&r!==n,a=`${e} must be a \`${t}\` type, but the final value was: \`${pl(n,!0)}\``+(i?` (cast from the value \`${pl(r,!0)}\`).`:".");return null===n&&(a+='\n If "null" is intended as an empty value be sure to mark the schema as `.nullable()`'),a},defined:"${path} must be defined"},pd={length:"${path} must be exactly ${length} characters",min:"${path} must be at least ${min} characters",max:"${path} must be at most ${max} characters",matches:'${path} must match the following: "${regex}"',email:"${path} must be a valid email",url:"${path} must be a valid URL",uuid:"${path} must be a valid UUID",trim:"${path} must be a trimmed string",lowercase:"${path} must be a lowercase string",uppercase:"${path} must be a upper case string"},ph={min:"${path} must be greater than or equal to ${min}",max:"${path} must be less than or equal to ${max}",lessThan:"${path} must be less than ${less}",moreThan:"${path} must be greater than ${more}",positive:"${path} must be a positive number",negative:"${path} must be a negative number",integer:"${path} must be an integer"},pp={min:"${path} field must be later than ${min}",max:"${path} field must be at earlier than ${max}"},pb={isValue:"${path} field must be ${value}"},pm={noUnknown:"${path} field has unspecified keys: ${unknown}"},pg={min:"${path} field must have at least ${min} items",max:"${path} field must have less than or equal to ${max} items",length:"${path} must be have ${length} items"};Object.assign(Object.create(null),{mixed:pf,string:pd,number:ph,date:pp,object:pm,array:pg,boolean:pb});var pv=n(18721),py=n.n(pv);let pw=e=>e&&e.__isYupSchema__;class p_{constructor(e,t){if(this.refs=e,this.refs=e,"function"==typeof t){this.fn=t;return}if(!py()(t,"is"))throw TypeError("`is:` is required for `when()` conditions");if(!t.then&&!t.otherwise)throw TypeError("either `then:` or `otherwise:` is required for `when()` conditions");let{is:n,then:r,otherwise:i}=t,a="function"==typeof n?n:(...e)=>e.every(e=>e===n);this.fn=function(...e){let t=e.pop(),n=e.pop(),o=a(...e)?r:i;if(o)return"function"==typeof o?o(n):n.concat(o.resolve(t))}}resolve(e,t){let n=this.refs.map(e=>e.getValue(null==t?void 0:t.value,null==t?void 0:t.parent,null==t?void 0:t.context)),r=this.fn.apply(e,n.concat(e,t));if(void 0===r||r===e)return e;if(!pw(r))throw TypeError("conditions must return a schema object");return r.resolve(t)}}let pE=p_;function pS(e){return null==e?[]:[].concat(e)}function pk(){return(pk=Object.assign||function(e){for(var t=1;tpl(t[n])):"function"==typeof e?e(t):e}static isError(e){return e&&"ValidationError"===e.name}constructor(e,t,n,r){super(),this.name="ValidationError",this.value=t,this.path=n,this.type=r,this.errors=[],this.inner=[],pS(e).forEach(e=>{pT.isError(e)?(this.errors.push(...e.errors),this.inner=this.inner.concat(e.inner.length?e.inner:e)):this.errors.push(e)}),this.message=this.errors.length>1?`${this.errors.length} errors occurred`:this.errors[0],Error.captureStackTrace&&Error.captureStackTrace(this,pT)}}let pM=e=>{let t=!1;return(...n)=>{t||(t=!0,e(...n))}};function pO(e,t){let{endEarly:n,tests:r,args:i,value:a,errors:o,sort:s,path:u}=e,c=pM(t),l=r.length,f=[];if(o=o||[],!l)return o.length?c(new pT(o,a,u)):c(null,a);for(let d=0;d=0||(i[n]=e[n]);return i}function pR(e){function t(t,n){let{value:r,path:i="",label:a,options:o,originalValue:s,sync:u}=t,c=pP(t,["value","path","label","options","originalValue","sync"]),{name:l,test:f,params:d,message:h}=e,{parent:p,context:b}=o;function m(e){return pD.isRef(e)?e.getValue(r,p,b):e}function g(e={}){let t=pL()(pN({value:r,originalValue:s,label:a,path:e.path||i},d,e.params),m),n=new pT(pT.formatError(e.message||h,t),r,t.path,e.type||l);return n.params=t,n}let v=pN({path:i,parent:p,type:l,createError:g,resolve:m,options:o,originalValue:s},c);if(!u){try{Promise.resolve(f.call(v,r,v)).then(e=>{pT.isError(e)?n(e):e?n(null,e):n(g())})}catch(y){n(y)}return}let w;try{var _;if(w=f.call(v,r,v),"function"==typeof(null==(_=w)?void 0:_.then))throw Error(`Validation test of type: "${v.type}" returned a Promise during a synchronous validate. This test will finish after the validate call has returned`)}catch(E){n(E);return}pT.isError(w)?n(w):w?n(null,w):n(g())}return t.OPTIONS=e,t}pD.prototype.__isYupRef=!0;let pj=e=>e.substr(0,e.length-1).substr(1);function pF(e,t,n,r=n){let i,a,o;return t?((0,pC.forEach)(t,(s,u,c)=>{let l=u?pj(s):s;if((e=e.resolve({context:r,parent:i,value:n})).innerType){let f=c?parseInt(l,10):0;if(n&&f>=n.length)throw Error(`Yup.reach cannot resolve an array item at index: ${s}, in the path: ${t}. because there is no value at that index. `);i=n,n=n&&n[f],e=e.innerType}if(!c){if(!e.fields||!e.fields[l])throw Error(`The schema does not contain the path: ${t}. (failed at: ${o} which is a type: "${e._type}")`);i=n,n=n&&n[l],e=e.fields[l]}a=l,o=u?"["+s+"]":"."+s}),{schema:e,parent:i,parentPath:a}):{parent:i,parentPath:t,schema:e}}class pY{constructor(){this.list=new Set,this.refs=new Map}get size(){return this.list.size+this.refs.size}describe(){let e=[];for(let t of this.list)e.push(t);for(let[,n]of this.refs)e.push(n.describe());return e}toArray(){return Array.from(this.list).concat(Array.from(this.refs.values()))}add(e){pD.isRef(e)?this.refs.set(e.key,e):this.list.add(e)}delete(e){pD.isRef(e)?this.refs.delete(e.key):this.list.delete(e)}has(e,t){if(this.list.has(e))return!0;let n,r=this.refs.values();for(;!(n=r.next()).done;)if(t(n.value)===e)return!0;return!1}clone(){let e=new pY;return e.list=new Set(this.list),e.refs=new Map(this.refs),e}merge(e,t){let n=this.clone();return e.list.forEach(e=>n.add(e)),e.refs.forEach(e=>n.add(e)),t.list.forEach(e=>n.delete(e)),t.refs.forEach(e=>n.delete(e)),n}}function pB(){return(pB=Object.assign||function(e){for(var t=1;t{this.typeError(pf.notType)}),this.type=(null==e?void 0:e.type)||"mixed",this.spec=pB({strip:!1,strict:!1,abortEarly:!0,recursive:!0,nullable:!1,presence:"optional"},null==e?void 0:e.spec)}get _type(){return this.type}_typeCheck(e){return!0}clone(e){if(this._mutate)return e&&Object.assign(this.spec,e),this;let t=Object.create(Object.getPrototypeOf(this));return t.type=this.type,t._typeError=this._typeError,t._whitelistError=this._whitelistError,t._blacklistError=this._blacklistError,t._whitelist=this._whitelist.clone(),t._blacklist=this._blacklist.clone(),t.exclusiveTests=pB({},this.exclusiveTests),t.deps=[...this.deps],t.conditions=[...this.conditions],t.tests=[...this.tests],t.transforms=[...this.transforms],t.spec=pn(pB({},this.spec,e)),t}label(e){var t=this.clone();return t.spec.label=e,t}meta(...e){if(0===e.length)return this.spec.meta;let t=this.clone();return t.spec.meta=Object.assign(t.spec.meta||{},e[0]),t}withMutation(e){let t=this._mutate;this._mutate=!0;let n=e(this);return this._mutate=t,n}concat(e){if(!e||e===this)return this;if(e.type!==this.type&&"mixed"!==this.type)throw TypeError(`You cannot \`concat()\` schema's of different types: ${this.type} and ${e.type}`);let t=this,n=e.clone(),r=pB({},t.spec,n.spec);return n.spec=r,n._typeError||(n._typeError=t._typeError),n._whitelistError||(n._whitelistError=t._whitelistError),n._blacklistError||(n._blacklistError=t._blacklistError),n._whitelist=t._whitelist.merge(e._whitelist,e._blacklist),n._blacklist=t._blacklist.merge(e._blacklist,e._whitelist),n.tests=t.tests,n.exclusiveTests=t.exclusiveTests,n.withMutation(t=>{e.tests.forEach(e=>{t.test(e.OPTIONS)})}),n}isType(e){return!!this.spec.nullable&&null===e||this._typeCheck(e)}resolve(e){let t=this;if(t.conditions.length){let n=t.conditions;(t=t.clone()).conditions=[],t=(t=n.reduce((t,n)=>n.resolve(t,e),t)).resolve(e)}return t}cast(e,t={}){let n=this.resolve(pB({value:e},t)),r=n._cast(e,t);if(void 0!==e&&!1!==t.assert&&!0!==n.isType(r)){let i=pl(e),a=pl(r);throw TypeError(`The value of ${t.path||"field"} could not be cast to a value that satisfies the schema type: "${n._type}". + */ Object.defineProperty(t,"__esModule",{value:!0}),"undefined"==typeof window||"function"!=typeof MessageChannel){var n,r,i,a,o,s=null,u=null,c=function(){if(null!==s)try{var e=t.unstable_now();s(!0,e),s=null}catch(n){throw setTimeout(c,0),n}},l=Date.now();t.unstable_now=function(){return Date.now()-l},n=function(e){null!==s?setTimeout(n,0,e):(s=e,setTimeout(c,0))},r=function(e,t){u=setTimeout(e,t)},i=function(){clearTimeout(u)},a=function(){return!1},o=t.unstable_forceFrameRate=function(){}}else{var f=window.performance,d=window.Date,h=window.setTimeout,p=window.clearTimeout;if("undefined"!=typeof console){var b=window.cancelAnimationFrame;"function"!=typeof window.requestAnimationFrame&&console.error("This browser doesn't support requestAnimationFrame. Make sure that you load a polyfill in older browsers. https://fb.me/react-polyfills"),"function"!=typeof b&&console.error("This browser doesn't support cancelAnimationFrame. Make sure that you load a polyfill in older browsers. https://fb.me/react-polyfills")}if("object"==typeof f&&"function"==typeof f.now)t.unstable_now=function(){return f.now()};else{var m=d.now();t.unstable_now=function(){return d.now()-m}}var g=!1,v=null,y=-1,w=5,_=0;a=function(){return t.unstable_now()>=_},o=function(){},t.unstable_forceFrameRate=function(e){0>e||125M(o,n))void 0!==u&&0>M(u,o)?(e[r]=u,e[s]=n,r=s):(e[r]=o,e[a]=n,r=a);else if(void 0!==u&&0>M(u,n))e[r]=u,e[s]=n,r=s;else break a}}return t}return null}function M(e,t){var n=e.sortIndex-t.sortIndex;return 0!==n?n:e.id-t.id}var O=[],A=[],L=1,C=null,I=3,D=!1,N=!1,P=!1;function R(e){for(var t=x(A);null!==t;){if(null===t.callback)T(A);else if(t.startTime<=e)T(A),t.sortIndex=t.expirationTime,k(O,t);else break;t=x(A)}}function j(e){if(P=!1,R(e),!N){if(null!==x(O))N=!0,n(F);else{var t=x(A);null!==t&&r(j,t.startTime-e)}}}function F(e,n){N=!1,P&&(P=!1,i()),D=!0;var o=I;try{for(R(n),C=x(O);null!==C&&(!(C.expirationTime>n)||e&&!a());){var s=C.callback;if(null!==s){C.callback=null,I=C.priorityLevel;var u=s(C.expirationTime<=n);n=t.unstable_now(),"function"==typeof u?C.callback=u:C===x(O)&&T(O),R(n)}else T(O);C=x(O)}if(null!==C)var c=!0;else{var l=x(A);null!==l&&r(j,l.startTime-n),c=!1}return c}finally{C=null,I=o,D=!1}}function Y(e){switch(e){case 1:return -1;case 2:return 250;case 5:return 1073741823;case 4:return 1e4;default:return 5e3}}var B=o;t.unstable_ImmediatePriority=1,t.unstable_UserBlockingPriority=2,t.unstable_NormalPriority=3,t.unstable_IdlePriority=5,t.unstable_LowPriority=4,t.unstable_runWithPriority=function(e,t){switch(e){case 1:case 2:case 3:case 4:case 5:break;default:e=3}var n=I;I=e;try{return t()}finally{I=n}},t.unstable_next=function(e){switch(I){case 1:case 2:case 3:var t=3;break;default:t=I}var n=I;I=t;try{return e()}finally{I=n}},t.unstable_scheduleCallback=function(e,a,o){var s=t.unstable_now();if("object"==typeof o&&null!==o){var u=o.delay;u="number"==typeof u&&0s?(e.sortIndex=u,k(A,e),null===x(O)&&e===x(A)&&(P?i():P=!0,r(j,u-s))):(e.sortIndex=o,k(O,e),N||D||(N=!0,n(F))),e},t.unstable_cancelCallback=function(e){e.callback=null},t.unstable_wrapCallback=function(e){var t=I;return function(){var n=I;I=t;try{return e.apply(this,arguments)}finally{I=n}}},t.unstable_getCurrentPriorityLevel=function(){return I},t.unstable_shouldYield=function(){var e=t.unstable_now();R(e);var n=x(O);return n!==C&&null!==C&&null!==n&&null!==n.callback&&n.startTime<=e&&n.expirationTime>5==6?2:e>>4==14?3:e>>3==30?4:e>>6==2?-1:-2}function c(e,t,n){var r=t.length-1;if(r=0?(i>0&&(e.lastNeed=i-1),i):--r=0?(i>0&&(e.lastNeed=i-2),i):--r=0?(i>0&&(2===i?i=0:e.lastNeed=i-3),i):0}function l(e,t,n){if((192&t[0])!=128)return e.lastNeed=0,"�";if(e.lastNeed>1&&t.length>1){if((192&t[1])!=128)return e.lastNeed=1,"�";if(e.lastNeed>2&&t.length>2&&(192&t[2])!=128)return e.lastNeed=2,"�"}}function f(e){var t=this.lastTotal-this.lastNeed,n=l(this,e,t);return void 0!==n?n:this.lastNeed<=e.length?(e.copy(this.lastChar,t,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal)):void(e.copy(this.lastChar,t,0,e.length),this.lastNeed-=e.length)}function d(e,t){var n=c(this,e,t);if(!this.lastNeed)return e.toString("utf8",t);this.lastTotal=n;var r=e.length-(n-this.lastNeed);return e.copy(this.lastChar,0,r),e.toString("utf8",t,r)}function h(e){var t=e&&e.length?this.write(e):"";return this.lastNeed?t+"�":t}function p(e,t){if((e.length-t)%2==0){var n=e.toString("utf16le",t);if(n){var r=n.charCodeAt(n.length-1);if(r>=55296&&r<=56319)return this.lastNeed=2,this.lastTotal=4,this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1],n.slice(0,-1)}return n}return this.lastNeed=1,this.lastTotal=2,this.lastChar[0]=e[e.length-1],e.toString("utf16le",t,e.length-1)}function b(e){var t=e&&e.length?this.write(e):"";if(this.lastNeed){var n=this.lastTotal-this.lastNeed;return t+this.lastChar.toString("utf16le",0,n)}return t}function m(e,t){var n=(e.length-t)%3;return 0===n?e.toString("base64",t):(this.lastNeed=3-n,this.lastTotal=3,1===n?this.lastChar[0]=e[e.length-1]:(this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1]),e.toString("base64",t,e.length-n))}function g(e){var t=e&&e.length?this.write(e):"";return this.lastNeed?t+this.lastChar.toString("base64",0,3-this.lastNeed):t}function v(e){return e.toString(this.encoding)}function y(e){return e&&e.length?this.write(e):""}t.s=s,s.prototype.write=function(e){var t,n;if(0===e.length)return"";if(this.lastNeed){if(void 0===(t=this.fillLast(e)))return"";n=this.lastNeed,this.lastNeed=0}else n=0;return n */ var r=n(48764),i=r.Buffer;function a(e,t){for(var n in e)t[n]=e[n]}function o(e,t,n){return i(e,t,n)}i.from&&i.alloc&&i.allocUnsafe&&i.allocUnsafeSlow?e.exports=r:(a(r,t),t.Buffer=o),o.prototype=Object.create(i.prototype),a(i,o),o.from=function(e,t,n){if("number"==typeof e)throw TypeError("Argument must not be a number");return i(e,t,n)},o.alloc=function(e,t,n){if("number"!=typeof e)throw TypeError("Argument must be a number");var r=i(e);return void 0!==t?"string"==typeof n?r.fill(t,n):r.fill(t):r.fill(0),r},o.allocUnsafe=function(e){if("number"!=typeof e)throw TypeError("Argument must be a number");return i(e)},o.allocUnsafeSlow=function(e){if("number"!=typeof e)throw TypeError("Argument must be a number");return r.SlowBuffer(e)}},93379(e,t,n){"use strict";var r,i,a=function(){return void 0===r&&(r=Boolean(window&&document&&document.all&&!window.atob)),r},o=(i={},function(e){if(void 0===i[e]){var t=document.querySelector(e);if(window.HTMLIFrameElement&&t instanceof window.HTMLIFrameElement)try{t=t.contentDocument.head}catch(n){t=null}i[e]=t}return i[e]}),s=[];function u(e){for(var t=-1,n=0;nOq});var r,i,a,o,s,u,c,l=n(67294),f=n.t(l,2),d=n(39814),h=n(5977),p=n(57209),b=n(32316),m=n(95880),g=n(17051),v=n(71381),y=n(81701),w=n(3022),_=n(60323),E=n(87591),S=n(25649),k=n(28902),x=n(71426),T=n(48884),M=n(94184),O=n.n(M),A=n(37703),L=n(73935),C=function(){if("undefined"!=typeof Map)return Map;function e(e,t){var n=-1;return e.some(function(e,r){return e[0]===t&&(n=r,!0)}),n}return function(){function t(){this.__entries__=[]}return Object.defineProperty(t.prototype,"size",{get:function(){return this.__entries__.length},enumerable:!0,configurable:!0}),t.prototype.get=function(t){var n=e(this.__entries__,t),r=this.__entries__[n];return r&&r[1]},t.prototype.set=function(t,n){var r=e(this.__entries__,t);~r?this.__entries__[r][1]=n:this.__entries__.push([t,n])},t.prototype.delete=function(t){var n=this.__entries__,r=e(n,t);~r&&n.splice(r,1)},t.prototype.has=function(t){return!!~e(this.__entries__,t)},t.prototype.clear=function(){this.__entries__.splice(0)},t.prototype.forEach=function(e,t){void 0===t&&(t=null);for(var n=0,r=this.__entries__;n0},e.prototype.connect_=function(){I&&!this.connected_&&(document.addEventListener("transitionend",this.onTransitionEnd_),window.addEventListener("resize",this.refresh),Y?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener("DOMSubtreeModified",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},e.prototype.disconnect_=function(){I&&this.connected_&&(document.removeEventListener("transitionend",this.onTransitionEnd_),window.removeEventListener("resize",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener("DOMSubtreeModified",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},e.prototype.onTransitionEnd_=function(e){var t=e.propertyName,n=void 0===t?"":t;F.some(function(e){return!!~n.indexOf(e)})&&this.refresh()},e.getInstance=function(){return this.instance_||(this.instance_=new e),this.instance_},e.instance_=null,e}(),U=function(e,t){for(var n=0,r=Object.keys(t);n0},e}(),er="undefined"!=typeof WeakMap?new WeakMap:new C,ei=function(){function e(t){if(!(this instanceof e))throw TypeError("Cannot call a class as a function.");if(!arguments.length)throw TypeError("1 argument required, but only 0 present.");var n=B.getInstance(),r=new en(t,n,this);er.set(this,r)}return e}();["observe","unobserve","disconnect"].forEach(function(e){ei.prototype[e]=function(){var t;return(t=er.get(this))[e].apply(t,arguments)}});var ea=void 0!==D.ResizeObserver?D.ResizeObserver:ei;let eo=ea;var es=function(e){var t=[],n=null,r=function(){for(var r=arguments.length,i=Array(r),a=0;a=t||n<0||f&&r>=a}function g(){var e=eb();if(m(e))return v(e);s=setTimeout(g,b(e))}function v(e){return(s=void 0,d&&r)?h(e):(r=i=void 0,o)}function y(){void 0!==s&&clearTimeout(s),c=0,r=u=i=s=void 0}function w(){return void 0===s?o:v(eb())}function _(){var e=eb(),n=m(e);if(r=arguments,i=this,u=e,n){if(void 0===s)return p(u);if(f)return clearTimeout(s),s=setTimeout(g,t),h(u)}return void 0===s&&(s=setTimeout(g,t)),o}return t=ez(t)||0,ed(n)&&(l=!!n.leading,a=(f="maxWait"in n)?eW(ez(n.maxWait)||0,t):a,d="trailing"in n?!!n.trailing:d),_.cancel=y,_.flush=w,_}let eq=eV;var eZ="Expected a function";function eX(e,t,n){var r=!0,i=!0;if("function"!=typeof e)throw TypeError(eZ);return ed(n)&&(r="leading"in n?!!n.leading:r,i="trailing"in n?!!n.trailing:i),eq(e,t,{leading:r,maxWait:t,trailing:i})}let eJ=eX;var eQ={debounce:eq,throttle:eJ},e1=function(e){return eQ[e]},e0=function(e){return"function"==typeof e},e2=function(){return"undefined"==typeof window},e3=function(e){return e instanceof Element||e instanceof HTMLDocument};function e4(e){return(e4="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function e6(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}function e5(e,t){for(var n=0;ne.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&l.createElement(tG.Z,{variant:"indeterminate",classes:r}))};tK.propTypes={fetchCount:el().number.isRequired};let tV=(0,b.withStyles)(tW)(tK);var tq=n(5536);let tZ=n.p+"ba8bbf16ebf8e1d05bef.svg";function tX(){return(tX=Object.assign||function(e){for(var t=1;t120){for(var d=Math.floor(u/80),h=u%80,p=[],b=0;b0},name:{enumerable:!1},nodes:{enumerable:!1},source:{enumerable:!1},positions:{enumerable:!1},originalError:{enumerable:!1}}),null!=s&&s.stack)?(Object.defineProperty(nf(b),"stack",{value:s.stack,writable:!0,configurable:!0}),nl(b)):(Error.captureStackTrace?Error.captureStackTrace(nf(b),n):Object.defineProperty(nf(b),"stack",{value:Error().stack,writable:!0,configurable:!0}),b)}return ns(n,[{key:"toString",value:function(){return nw(this)}},{key:t4.YF,get:function(){return"Object"}}]),n}(nd(Error));function ny(e){return void 0===e||0===e.length?void 0:e}function nw(e){var t=e.message;if(e.nodes)for(var n=0,r=e.nodes;n",EOF:"",BANG:"!",DOLLAR:"$",AMP:"&",PAREN_L:"(",PAREN_R:")",SPREAD:"...",COLON:":",EQUALS:"=",AT:"@",BRACKET_L:"[",BRACKET_R:"]",BRACE_L:"{",PIPE:"|",BRACE_R:"}",NAME:"Name",INT:"Int",FLOAT:"Float",STRING:"String",BLOCK_STRING:"BlockString",COMMENT:"Comment"}),nx=n(10143),nT=Object.freeze({QUERY:"QUERY",MUTATION:"MUTATION",SUBSCRIPTION:"SUBSCRIPTION",FIELD:"FIELD",FRAGMENT_DEFINITION:"FRAGMENT_DEFINITION",FRAGMENT_SPREAD:"FRAGMENT_SPREAD",INLINE_FRAGMENT:"INLINE_FRAGMENT",VARIABLE_DEFINITION:"VARIABLE_DEFINITION",SCHEMA:"SCHEMA",SCALAR:"SCALAR",OBJECT:"OBJECT",FIELD_DEFINITION:"FIELD_DEFINITION",ARGUMENT_DEFINITION:"ARGUMENT_DEFINITION",INTERFACE:"INTERFACE",UNION:"UNION",ENUM:"ENUM",ENUM_VALUE:"ENUM_VALUE",INPUT_OBJECT:"INPUT_OBJECT",INPUT_FIELD_DEFINITION:"INPUT_FIELD_DEFINITION"}),nM=n(87392),nO=function(){function e(e){var t=new nS.WU(nk.SOF,0,0,0,0,null);this.source=e,this.lastToken=t,this.token=t,this.line=1,this.lineStart=0}var t=e.prototype;return t.advance=function(){return this.lastToken=this.token,this.token=this.lookahead()},t.lookahead=function(){var e,t=this.token;if(t.kind!==nk.EOF)do t=null!==(e=t.next)&&void 0!==e?e:t.next=nC(this,t);while(t.kind===nk.COMMENT)return t},e}();function nA(e){return e===nk.BANG||e===nk.DOLLAR||e===nk.AMP||e===nk.PAREN_L||e===nk.PAREN_R||e===nk.SPREAD||e===nk.COLON||e===nk.EQUALS||e===nk.AT||e===nk.BRACKET_L||e===nk.BRACKET_R||e===nk.BRACE_L||e===nk.PIPE||e===nk.BRACE_R}function nL(e){return isNaN(e)?nk.EOF:e<127?JSON.stringify(String.fromCharCode(e)):'"\\u'.concat(("00"+e.toString(16).toUpperCase()).slice(-4),'"')}function nC(e,t){for(var n=e.source,r=n.body,i=r.length,a=t.end;a31||9===a))return new nS.WU(nk.COMMENT,t,s,n,r,i,o.slice(t+1,s))}function nN(e,t,n,r,i,a){var o=e.body,s=n,u=t,c=!1;if(45===s&&(s=o.charCodeAt(++u)),48===s){if((s=o.charCodeAt(++u))>=48&&s<=57)throw n_(e,u,"Invalid number, unexpected digit after 0: ".concat(nL(s),"."))}else u=nP(e,u,s),s=o.charCodeAt(u);if(46===s&&(c=!0,s=o.charCodeAt(++u),u=nP(e,u,s),s=o.charCodeAt(u)),(69===s||101===s)&&(c=!0,(43===(s=o.charCodeAt(++u))||45===s)&&(s=o.charCodeAt(++u)),u=nP(e,u,s),s=o.charCodeAt(u)),46===s||nU(s))throw n_(e,u,"Invalid number, expected digit but got: ".concat(nL(s),"."));return new nS.WU(c?nk.FLOAT:nk.INT,t,u,r,i,a,o.slice(t,u))}function nP(e,t,n){var r=e.body,i=t,a=n;if(a>=48&&a<=57){do a=r.charCodeAt(++i);while(a>=48&&a<=57)return i}throw n_(e,i,"Invalid number, expected digit but got: ".concat(nL(a),"."))}function nR(e,t,n,r,i){for(var a=e.body,o=t+1,s=o,u=0,c="";o=48&&e<=57?e-48:e>=65&&e<=70?e-55:e>=97&&e<=102?e-87:-1}function nB(e,t,n,r,i){for(var a=e.body,o=a.length,s=t+1,u=0;s!==o&&!isNaN(u=a.charCodeAt(s))&&(95===u||u>=48&&u<=57||u>=65&&u<=90||u>=97&&u<=122);)++s;return new nS.WU(nk.NAME,t,s,n,r,i,a.slice(t,s))}function nU(e){return 95===e||e>=65&&e<=90||e>=97&&e<=122}function nH(e,t){return new n$(e,t).parseDocument()}var n$=function(){function e(e,t){var n=(0,nx.T)(e)?e:new nx.H(e);this._lexer=new nO(n),this._options=t}var t=e.prototype;return t.parseName=function(){var e=this.expectToken(nk.NAME);return{kind:nE.h.NAME,value:e.value,loc:this.loc(e)}},t.parseDocument=function(){var e=this._lexer.token;return{kind:nE.h.DOCUMENT,definitions:this.many(nk.SOF,this.parseDefinition,nk.EOF),loc:this.loc(e)}},t.parseDefinition=function(){if(this.peek(nk.NAME))switch(this._lexer.token.value){case"query":case"mutation":case"subscription":return this.parseOperationDefinition();case"fragment":return this.parseFragmentDefinition();case"schema":case"scalar":case"type":case"interface":case"union":case"enum":case"input":case"directive":return this.parseTypeSystemDefinition();case"extend":return this.parseTypeSystemExtension()}else if(this.peek(nk.BRACE_L))return this.parseOperationDefinition();else if(this.peekDescription())return this.parseTypeSystemDefinition();throw this.unexpected()},t.parseOperationDefinition=function(){var e,t=this._lexer.token;if(this.peek(nk.BRACE_L))return{kind:nE.h.OPERATION_DEFINITION,operation:"query",name:void 0,variableDefinitions:[],directives:[],selectionSet:this.parseSelectionSet(),loc:this.loc(t)};var n=this.parseOperationType();return this.peek(nk.NAME)&&(e=this.parseName()),{kind:nE.h.OPERATION_DEFINITION,operation:n,name:e,variableDefinitions:this.parseVariableDefinitions(),directives:this.parseDirectives(!1),selectionSet:this.parseSelectionSet(),loc:this.loc(t)}},t.parseOperationType=function(){var e=this.expectToken(nk.NAME);switch(e.value){case"query":return"query";case"mutation":return"mutation";case"subscription":return"subscription"}throw this.unexpected(e)},t.parseVariableDefinitions=function(){return this.optionalMany(nk.PAREN_L,this.parseVariableDefinition,nk.PAREN_R)},t.parseVariableDefinition=function(){var e=this._lexer.token;return{kind:nE.h.VARIABLE_DEFINITION,variable:this.parseVariable(),type:(this.expectToken(nk.COLON),this.parseTypeReference()),defaultValue:this.expectOptionalToken(nk.EQUALS)?this.parseValueLiteral(!0):void 0,directives:this.parseDirectives(!0),loc:this.loc(e)}},t.parseVariable=function(){var e=this._lexer.token;return this.expectToken(nk.DOLLAR),{kind:nE.h.VARIABLE,name:this.parseName(),loc:this.loc(e)}},t.parseSelectionSet=function(){var e=this._lexer.token;return{kind:nE.h.SELECTION_SET,selections:this.many(nk.BRACE_L,this.parseSelection,nk.BRACE_R),loc:this.loc(e)}},t.parseSelection=function(){return this.peek(nk.SPREAD)?this.parseFragment():this.parseField()},t.parseField=function(){var e,t,n=this._lexer.token,r=this.parseName();return this.expectOptionalToken(nk.COLON)?(e=r,t=this.parseName()):t=r,{kind:nE.h.FIELD,alias:e,name:t,arguments:this.parseArguments(!1),directives:this.parseDirectives(!1),selectionSet:this.peek(nk.BRACE_L)?this.parseSelectionSet():void 0,loc:this.loc(n)}},t.parseArguments=function(e){var t=e?this.parseConstArgument:this.parseArgument;return this.optionalMany(nk.PAREN_L,t,nk.PAREN_R)},t.parseArgument=function(){var e=this._lexer.token,t=this.parseName();return this.expectToken(nk.COLON),{kind:nE.h.ARGUMENT,name:t,value:this.parseValueLiteral(!1),loc:this.loc(e)}},t.parseConstArgument=function(){var e=this._lexer.token;return{kind:nE.h.ARGUMENT,name:this.parseName(),value:(this.expectToken(nk.COLON),this.parseValueLiteral(!0)),loc:this.loc(e)}},t.parseFragment=function(){var e=this._lexer.token;this.expectToken(nk.SPREAD);var t=this.expectOptionalKeyword("on");return!t&&this.peek(nk.NAME)?{kind:nE.h.FRAGMENT_SPREAD,name:this.parseFragmentName(),directives:this.parseDirectives(!1),loc:this.loc(e)}:{kind:nE.h.INLINE_FRAGMENT,typeCondition:t?this.parseNamedType():void 0,directives:this.parseDirectives(!1),selectionSet:this.parseSelectionSet(),loc:this.loc(e)}},t.parseFragmentDefinition=function(){var e,t=this._lexer.token;return(this.expectKeyword("fragment"),(null===(e=this._options)||void 0===e?void 0:e.experimentalFragmentVariables)===!0)?{kind:nE.h.FRAGMENT_DEFINITION,name:this.parseFragmentName(),variableDefinitions:this.parseVariableDefinitions(),typeCondition:(this.expectKeyword("on"),this.parseNamedType()),directives:this.parseDirectives(!1),selectionSet:this.parseSelectionSet(),loc:this.loc(t)}:{kind:nE.h.FRAGMENT_DEFINITION,name:this.parseFragmentName(),typeCondition:(this.expectKeyword("on"),this.parseNamedType()),directives:this.parseDirectives(!1),selectionSet:this.parseSelectionSet(),loc:this.loc(t)}},t.parseFragmentName=function(){if("on"===this._lexer.token.value)throw this.unexpected();return this.parseName()},t.parseValueLiteral=function(e){var t=this._lexer.token;switch(t.kind){case nk.BRACKET_L:return this.parseList(e);case nk.BRACE_L:return this.parseObject(e);case nk.INT:return this._lexer.advance(),{kind:nE.h.INT,value:t.value,loc:this.loc(t)};case nk.FLOAT:return this._lexer.advance(),{kind:nE.h.FLOAT,value:t.value,loc:this.loc(t)};case nk.STRING:case nk.BLOCK_STRING:return this.parseStringLiteral();case nk.NAME:switch(this._lexer.advance(),t.value){case"true":return{kind:nE.h.BOOLEAN,value:!0,loc:this.loc(t)};case"false":return{kind:nE.h.BOOLEAN,value:!1,loc:this.loc(t)};case"null":return{kind:nE.h.NULL,loc:this.loc(t)};default:return{kind:nE.h.ENUM,value:t.value,loc:this.loc(t)}}case nk.DOLLAR:if(!e)return this.parseVariable()}throw this.unexpected()},t.parseStringLiteral=function(){var e=this._lexer.token;return this._lexer.advance(),{kind:nE.h.STRING,value:e.value,block:e.kind===nk.BLOCK_STRING,loc:this.loc(e)}},t.parseList=function(e){var t=this,n=this._lexer.token,r=function(){return t.parseValueLiteral(e)};return{kind:nE.h.LIST,values:this.any(nk.BRACKET_L,r,nk.BRACKET_R),loc:this.loc(n)}},t.parseObject=function(e){var t=this,n=this._lexer.token,r=function(){return t.parseObjectField(e)};return{kind:nE.h.OBJECT,fields:this.any(nk.BRACE_L,r,nk.BRACE_R),loc:this.loc(n)}},t.parseObjectField=function(e){var t=this._lexer.token,n=this.parseName();return this.expectToken(nk.COLON),{kind:nE.h.OBJECT_FIELD,name:n,value:this.parseValueLiteral(e),loc:this.loc(t)}},t.parseDirectives=function(e){for(var t=[];this.peek(nk.AT);)t.push(this.parseDirective(e));return t},t.parseDirective=function(e){var t=this._lexer.token;return this.expectToken(nk.AT),{kind:nE.h.DIRECTIVE,name:this.parseName(),arguments:this.parseArguments(e),loc:this.loc(t)}},t.parseTypeReference=function(){var e,t=this._lexer.token;return(this.expectOptionalToken(nk.BRACKET_L)?(e=this.parseTypeReference(),this.expectToken(nk.BRACKET_R),e={kind:nE.h.LIST_TYPE,type:e,loc:this.loc(t)}):e=this.parseNamedType(),this.expectOptionalToken(nk.BANG))?{kind:nE.h.NON_NULL_TYPE,type:e,loc:this.loc(t)}:e},t.parseNamedType=function(){var e=this._lexer.token;return{kind:nE.h.NAMED_TYPE,name:this.parseName(),loc:this.loc(e)}},t.parseTypeSystemDefinition=function(){var e=this.peekDescription()?this._lexer.lookahead():this._lexer.token;if(e.kind===nk.NAME)switch(e.value){case"schema":return this.parseSchemaDefinition();case"scalar":return this.parseScalarTypeDefinition();case"type":return this.parseObjectTypeDefinition();case"interface":return this.parseInterfaceTypeDefinition();case"union":return this.parseUnionTypeDefinition();case"enum":return this.parseEnumTypeDefinition();case"input":return this.parseInputObjectTypeDefinition();case"directive":return this.parseDirectiveDefinition()}throw this.unexpected(e)},t.peekDescription=function(){return this.peek(nk.STRING)||this.peek(nk.BLOCK_STRING)},t.parseDescription=function(){if(this.peekDescription())return this.parseStringLiteral()},t.parseSchemaDefinition=function(){var e=this._lexer.token,t=this.parseDescription();this.expectKeyword("schema");var n=this.parseDirectives(!0),r=this.many(nk.BRACE_L,this.parseOperationTypeDefinition,nk.BRACE_R);return{kind:nE.h.SCHEMA_DEFINITION,description:t,directives:n,operationTypes:r,loc:this.loc(e)}},t.parseOperationTypeDefinition=function(){var e=this._lexer.token,t=this.parseOperationType();this.expectToken(nk.COLON);var n=this.parseNamedType();return{kind:nE.h.OPERATION_TYPE_DEFINITION,operation:t,type:n,loc:this.loc(e)}},t.parseScalarTypeDefinition=function(){var e=this._lexer.token,t=this.parseDescription();this.expectKeyword("scalar");var n=this.parseName(),r=this.parseDirectives(!0);return{kind:nE.h.SCALAR_TYPE_DEFINITION,description:t,name:n,directives:r,loc:this.loc(e)}},t.parseObjectTypeDefinition=function(){var e=this._lexer.token,t=this.parseDescription();this.expectKeyword("type");var n=this.parseName(),r=this.parseImplementsInterfaces(),i=this.parseDirectives(!0),a=this.parseFieldsDefinition();return{kind:nE.h.OBJECT_TYPE_DEFINITION,description:t,name:n,interfaces:r,directives:i,fields:a,loc:this.loc(e)}},t.parseImplementsInterfaces=function(){var e;if(!this.expectOptionalKeyword("implements"))return[];if((null===(e=this._options)||void 0===e?void 0:e.allowLegacySDLImplementsInterfaces)===!0){var t=[];this.expectOptionalToken(nk.AMP);do t.push(this.parseNamedType());while(this.expectOptionalToken(nk.AMP)||this.peek(nk.NAME))return t}return this.delimitedMany(nk.AMP,this.parseNamedType)},t.parseFieldsDefinition=function(){var e;return(null===(e=this._options)||void 0===e?void 0:e.allowLegacySDLEmptyFields)===!0&&this.peek(nk.BRACE_L)&&this._lexer.lookahead().kind===nk.BRACE_R?(this._lexer.advance(),this._lexer.advance(),[]):this.optionalMany(nk.BRACE_L,this.parseFieldDefinition,nk.BRACE_R)},t.parseFieldDefinition=function(){var e=this._lexer.token,t=this.parseDescription(),n=this.parseName(),r=this.parseArgumentDefs();this.expectToken(nk.COLON);var i=this.parseTypeReference(),a=this.parseDirectives(!0);return{kind:nE.h.FIELD_DEFINITION,description:t,name:n,arguments:r,type:i,directives:a,loc:this.loc(e)}},t.parseArgumentDefs=function(){return this.optionalMany(nk.PAREN_L,this.parseInputValueDef,nk.PAREN_R)},t.parseInputValueDef=function(){var e,t=this._lexer.token,n=this.parseDescription(),r=this.parseName();this.expectToken(nk.COLON);var i=this.parseTypeReference();this.expectOptionalToken(nk.EQUALS)&&(e=this.parseValueLiteral(!0));var a=this.parseDirectives(!0);return{kind:nE.h.INPUT_VALUE_DEFINITION,description:n,name:r,type:i,defaultValue:e,directives:a,loc:this.loc(t)}},t.parseInterfaceTypeDefinition=function(){var e=this._lexer.token,t=this.parseDescription();this.expectKeyword("interface");var n=this.parseName(),r=this.parseImplementsInterfaces(),i=this.parseDirectives(!0),a=this.parseFieldsDefinition();return{kind:nE.h.INTERFACE_TYPE_DEFINITION,description:t,name:n,interfaces:r,directives:i,fields:a,loc:this.loc(e)}},t.parseUnionTypeDefinition=function(){var e=this._lexer.token,t=this.parseDescription();this.expectKeyword("union");var n=this.parseName(),r=this.parseDirectives(!0),i=this.parseUnionMemberTypes();return{kind:nE.h.UNION_TYPE_DEFINITION,description:t,name:n,directives:r,types:i,loc:this.loc(e)}},t.parseUnionMemberTypes=function(){return this.expectOptionalToken(nk.EQUALS)?this.delimitedMany(nk.PIPE,this.parseNamedType):[]},t.parseEnumTypeDefinition=function(){var e=this._lexer.token,t=this.parseDescription();this.expectKeyword("enum");var n=this.parseName(),r=this.parseDirectives(!0),i=this.parseEnumValuesDefinition();return{kind:nE.h.ENUM_TYPE_DEFINITION,description:t,name:n,directives:r,values:i,loc:this.loc(e)}},t.parseEnumValuesDefinition=function(){return this.optionalMany(nk.BRACE_L,this.parseEnumValueDefinition,nk.BRACE_R)},t.parseEnumValueDefinition=function(){var e=this._lexer.token,t=this.parseDescription(),n=this.parseName(),r=this.parseDirectives(!0);return{kind:nE.h.ENUM_VALUE_DEFINITION,description:t,name:n,directives:r,loc:this.loc(e)}},t.parseInputObjectTypeDefinition=function(){var e=this._lexer.token,t=this.parseDescription();this.expectKeyword("input");var n=this.parseName(),r=this.parseDirectives(!0),i=this.parseInputFieldsDefinition();return{kind:nE.h.INPUT_OBJECT_TYPE_DEFINITION,description:t,name:n,directives:r,fields:i,loc:this.loc(e)}},t.parseInputFieldsDefinition=function(){return this.optionalMany(nk.BRACE_L,this.parseInputValueDef,nk.BRACE_R)},t.parseTypeSystemExtension=function(){var e=this._lexer.lookahead();if(e.kind===nk.NAME)switch(e.value){case"schema":return this.parseSchemaExtension();case"scalar":return this.parseScalarTypeExtension();case"type":return this.parseObjectTypeExtension();case"interface":return this.parseInterfaceTypeExtension();case"union":return this.parseUnionTypeExtension();case"enum":return this.parseEnumTypeExtension();case"input":return this.parseInputObjectTypeExtension()}throw this.unexpected(e)},t.parseSchemaExtension=function(){var e=this._lexer.token;this.expectKeyword("extend"),this.expectKeyword("schema");var t=this.parseDirectives(!0),n=this.optionalMany(nk.BRACE_L,this.parseOperationTypeDefinition,nk.BRACE_R);if(0===t.length&&0===n.length)throw this.unexpected();return{kind:nE.h.SCHEMA_EXTENSION,directives:t,operationTypes:n,loc:this.loc(e)}},t.parseScalarTypeExtension=function(){var e=this._lexer.token;this.expectKeyword("extend"),this.expectKeyword("scalar");var t=this.parseName(),n=this.parseDirectives(!0);if(0===n.length)throw this.unexpected();return{kind:nE.h.SCALAR_TYPE_EXTENSION,name:t,directives:n,loc:this.loc(e)}},t.parseObjectTypeExtension=function(){var e=this._lexer.token;this.expectKeyword("extend"),this.expectKeyword("type");var t=this.parseName(),n=this.parseImplementsInterfaces(),r=this.parseDirectives(!0),i=this.parseFieldsDefinition();if(0===n.length&&0===r.length&&0===i.length)throw this.unexpected();return{kind:nE.h.OBJECT_TYPE_EXTENSION,name:t,interfaces:n,directives:r,fields:i,loc:this.loc(e)}},t.parseInterfaceTypeExtension=function(){var e=this._lexer.token;this.expectKeyword("extend"),this.expectKeyword("interface");var t=this.parseName(),n=this.parseImplementsInterfaces(),r=this.parseDirectives(!0),i=this.parseFieldsDefinition();if(0===n.length&&0===r.length&&0===i.length)throw this.unexpected();return{kind:nE.h.INTERFACE_TYPE_EXTENSION,name:t,interfaces:n,directives:r,fields:i,loc:this.loc(e)}},t.parseUnionTypeExtension=function(){var e=this._lexer.token;this.expectKeyword("extend"),this.expectKeyword("union");var t=this.parseName(),n=this.parseDirectives(!0),r=this.parseUnionMemberTypes();if(0===n.length&&0===r.length)throw this.unexpected();return{kind:nE.h.UNION_TYPE_EXTENSION,name:t,directives:n,types:r,loc:this.loc(e)}},t.parseEnumTypeExtension=function(){var e=this._lexer.token;this.expectKeyword("extend"),this.expectKeyword("enum");var t=this.parseName(),n=this.parseDirectives(!0),r=this.parseEnumValuesDefinition();if(0===n.length&&0===r.length)throw this.unexpected();return{kind:nE.h.ENUM_TYPE_EXTENSION,name:t,directives:n,values:r,loc:this.loc(e)}},t.parseInputObjectTypeExtension=function(){var e=this._lexer.token;this.expectKeyword("extend"),this.expectKeyword("input");var t=this.parseName(),n=this.parseDirectives(!0),r=this.parseInputFieldsDefinition();if(0===n.length&&0===r.length)throw this.unexpected();return{kind:nE.h.INPUT_OBJECT_TYPE_EXTENSION,name:t,directives:n,fields:r,loc:this.loc(e)}},t.parseDirectiveDefinition=function(){var e=this._lexer.token,t=this.parseDescription();this.expectKeyword("directive"),this.expectToken(nk.AT);var n=this.parseName(),r=this.parseArgumentDefs(),i=this.expectOptionalKeyword("repeatable");this.expectKeyword("on");var a=this.parseDirectiveLocations();return{kind:nE.h.DIRECTIVE_DEFINITION,description:t,name:n,arguments:r,repeatable:i,locations:a,loc:this.loc(e)}},t.parseDirectiveLocations=function(){return this.delimitedMany(nk.PIPE,this.parseDirectiveLocation)},t.parseDirectiveLocation=function(){var e=this._lexer.token,t=this.parseName();if(void 0!==nT[t.value])return t;throw this.unexpected(e)},t.loc=function(e){var t;if((null===(t=this._options)||void 0===t?void 0:t.noLocation)!==!0)return new nS.Ye(e,this._lexer.lastToken,this._lexer.source)},t.peek=function(e){return this._lexer.token.kind===e},t.expectToken=function(e){var t=this._lexer.token;if(t.kind===e)return this._lexer.advance(),t;throw n_(this._lexer.source,t.start,"Expected ".concat(nG(e),", found ").concat(nz(t),"."))},t.expectOptionalToken=function(e){var t=this._lexer.token;if(t.kind===e)return this._lexer.advance(),t},t.expectKeyword=function(e){var t=this._lexer.token;if(t.kind===nk.NAME&&t.value===e)this._lexer.advance();else throw n_(this._lexer.source,t.start,'Expected "'.concat(e,'", found ').concat(nz(t),"."))},t.expectOptionalKeyword=function(e){var t=this._lexer.token;return t.kind===nk.NAME&&t.value===e&&(this._lexer.advance(),!0)},t.unexpected=function(e){var t=null!=e?e:this._lexer.token;return n_(this._lexer.source,t.start,"Unexpected ".concat(nz(t),"."))},t.any=function(e,t,n){this.expectToken(e);for(var r=[];!this.expectOptionalToken(n);)r.push(t.call(this));return r},t.optionalMany=function(e,t,n){if(this.expectOptionalToken(e)){var r=[];do r.push(t.call(this));while(!this.expectOptionalToken(n))return r}return[]},t.many=function(e,t,n){this.expectToken(e);var r=[];do r.push(t.call(this));while(!this.expectOptionalToken(n))return r},t.delimitedMany=function(e,t){this.expectOptionalToken(e);var n=[];do n.push(t.call(this));while(this.expectOptionalToken(e))return n},e}();function nz(e){var t=e.value;return nG(e.kind)+(null!=t?' "'.concat(t,'"'):"")}function nG(e){return nA(e)?'"'.concat(e,'"'):e}var nW=new Map,nK=new Map,nV=!0,nq=!1;function nZ(e){return e.replace(/[\s,]+/g," ").trim()}function nX(e){return nZ(e.source.body.substring(e.start,e.end))}function nJ(e){var t=new Set,n=[];return e.definitions.forEach(function(e){if("FragmentDefinition"===e.kind){var r=e.name.value,i=nX(e.loc),a=nK.get(r);a&&!a.has(i)?nV&&console.warn("Warning: fragment with name "+r+" already exists.\ngraphql-tag enforces all fragment names across your application to be unique; read more about\nthis in the docs: http://dev.apollodata.com/core/fragments.html#unique-names"):a||nK.set(r,a=new Set),a.add(i),t.has(i)||(t.add(i),n.push(e))}else n.push(e)}),(0,t0.pi)((0,t0.pi)({},e),{definitions:n})}function nQ(e){var t=new Set(e.definitions);t.forEach(function(e){e.loc&&delete e.loc,Object.keys(e).forEach(function(n){var r=e[n];r&&"object"==typeof r&&t.add(r)})});var n=e.loc;return n&&(delete n.startToken,delete n.endToken),e}function n1(e){var t=nZ(e);if(!nW.has(t)){var n=nH(e,{experimentalFragmentVariables:nq,allowLegacyFragmentVariables:nq});if(!n||"Document"!==n.kind)throw Error("Not a valid GraphQL document.");nW.set(t,nQ(nJ(n)))}return nW.get(t)}function n0(e){for(var t=[],n=1;n, or pass an ApolloClient instance in via options.'):(0,n9.kG)(!!n,32),n}var rp=n(10542),rb=n(53712),rm=n(21436),rg=Object.prototype.hasOwnProperty;function rv(e,t){return void 0===t&&(t=Object.create(null)),ry(rh(t.client),e).useQuery(t)}function ry(e,t){var n=(0,l.useRef)();n.current&&e===n.current.client&&t===n.current.query||(n.current=new rw(e,t,n.current));var r=n.current,i=(0,l.useState)(0),a=(i[0],i[1]);return r.forceUpdate=function(){a(function(e){return e+1})},r}var rw=function(){function e(e,t,n){this.client=e,this.query=t,this.ssrDisabledResult=(0,rp.J)({loading:!0,data:void 0,error:void 0,networkStatus:ru.I.loading}),this.skipStandbyResult=(0,rp.J)({loading:!1,data:void 0,error:void 0,networkStatus:ru.I.ready}),this.toQueryResultCache=new(n7.mr?WeakMap:Map),rd(t,r.Query);var i=n&&n.result,a=i&&i.data;a&&(this.previousData=a)}return e.prototype.forceUpdate=function(){__DEV__&&n9.kG.warn("Calling default no-op implementation of InternalState#forceUpdate")},e.prototype.executeQuery=function(e){var t,n=this;e.query&&Object.assign(this,{query:e.query}),this.watchQueryOptions=this.createWatchQueryOptions(this.queryHookOptions=e);var r=this.observable.reobserveAsConcast(this.getObsQueryOptions());return this.previousData=(null===(t=this.result)||void 0===t?void 0:t.data)||this.previousData,this.result=void 0,this.forceUpdate(),new Promise(function(e){var t;r.subscribe({next:function(e){t=e},error:function(){e(n.toQueryResult(n.observable.getCurrentResult()))},complete:function(){e(n.toQueryResult(t))}})})},e.prototype.useQuery=function(e){var t=this;this.renderPromises=(0,l.useContext)((0,ro.K)()).renderPromises,this.useOptions(e);var n=this.useObservableQuery(),r=rt((0,l.useCallback)(function(){if(t.renderPromises)return function(){};var e=function(){var e=t.result,r=n.getCurrentResult();!(e&&e.loading===r.loading&&e.networkStatus===r.networkStatus&&(0,ri.D)(e.data,r.data))&&t.setResult(r)},r=function(a){var o=n.last;i.unsubscribe();try{n.resetLastResults(),i=n.subscribe(e,r)}finally{n.last=o}if(!rg.call(a,"graphQLErrors"))throw a;var s=t.result;(!s||s&&s.loading||!(0,ri.D)(a,s.error))&&t.setResult({data:s&&s.data,error:a,loading:!1,networkStatus:ru.I.error})},i=n.subscribe(e,r);return function(){return setTimeout(function(){return i.unsubscribe()})}},[n,this.renderPromises,this.client.disableNetworkFetches,]),function(){return t.getCurrentResult()},function(){return t.getCurrentResult()});return this.unsafeHandlePartialRefetch(r),this.toQueryResult(r)},e.prototype.useOptions=function(t){var n,r=this.createWatchQueryOptions(this.queryHookOptions=t),i=this.watchQueryOptions;!(0,ri.D)(r,i)&&(this.watchQueryOptions=r,i&&this.observable&&(this.observable.reobserve(this.getObsQueryOptions()),this.previousData=(null===(n=this.result)||void 0===n?void 0:n.data)||this.previousData,this.result=void 0)),this.onCompleted=t.onCompleted||e.prototype.onCompleted,this.onError=t.onError||e.prototype.onError,(this.renderPromises||this.client.disableNetworkFetches)&&!1===this.queryHookOptions.ssr&&!this.queryHookOptions.skip?this.result=this.ssrDisabledResult:this.queryHookOptions.skip||"standby"===this.watchQueryOptions.fetchPolicy?this.result=this.skipStandbyResult:(this.result===this.ssrDisabledResult||this.result===this.skipStandbyResult)&&(this.result=void 0)},e.prototype.getObsQueryOptions=function(){var e=[],t=this.client.defaultOptions.watchQuery;return t&&e.push(t),this.queryHookOptions.defaultOptions&&e.push(this.queryHookOptions.defaultOptions),e.push((0,rb.o)(this.observable&&this.observable.options,this.watchQueryOptions)),e.reduce(ra.J)},e.prototype.createWatchQueryOptions=function(e){void 0===e&&(e={});var t,n=e.skip,r=Object.assign((e.ssr,e.onCompleted,e.onError,e.defaultOptions,(0,t0._T)(e,["skip","ssr","onCompleted","onError","defaultOptions"])),{query:this.query});if(this.renderPromises&&("network-only"===r.fetchPolicy||"cache-and-network"===r.fetchPolicy)&&(r.fetchPolicy="cache-first"),r.variables||(r.variables={}),n){var i=r.fetchPolicy,a=void 0===i?this.getDefaultFetchPolicy():i,o=r.initialFetchPolicy;Object.assign(r,{initialFetchPolicy:void 0===o?a:o,fetchPolicy:"standby"})}else r.fetchPolicy||(r.fetchPolicy=(null===(t=this.observable)||void 0===t?void 0:t.options.initialFetchPolicy)||this.getDefaultFetchPolicy());return r},e.prototype.getDefaultFetchPolicy=function(){var e,t;return(null===(e=this.queryHookOptions.defaultOptions)||void 0===e?void 0:e.fetchPolicy)||(null===(t=this.client.defaultOptions.watchQuery)||void 0===t?void 0:t.fetchPolicy)||"cache-first"},e.prototype.onCompleted=function(e){},e.prototype.onError=function(e){},e.prototype.useObservableQuery=function(){var e=this.observable=this.renderPromises&&this.renderPromises.getSSRObservable(this.watchQueryOptions)||this.observable||this.client.watchQuery(this.getObsQueryOptions());this.obsQueryFields=(0,l.useMemo)(function(){return{refetch:e.refetch.bind(e),reobserve:e.reobserve.bind(e),fetchMore:e.fetchMore.bind(e),updateQuery:e.updateQuery.bind(e),startPolling:e.startPolling.bind(e),stopPolling:e.stopPolling.bind(e),subscribeToMore:e.subscribeToMore.bind(e)}},[e]);var t=!(!1===this.queryHookOptions.ssr||this.queryHookOptions.skip);return this.renderPromises&&t&&(this.renderPromises.registerSSRObservable(e),e.getCurrentResult().loading&&this.renderPromises.addObservableQueryPromise(e)),e},e.prototype.setResult=function(e){var t=this.result;t&&t.data&&(this.previousData=t.data),this.result=e,this.forceUpdate(),this.handleErrorOrCompleted(e)},e.prototype.handleErrorOrCompleted=function(e){var t=this;if(!e.loading){var n=this.toApolloError(e);Promise.resolve().then(function(){n?t.onError(n):e.data&&t.onCompleted(e.data)}).catch(function(e){__DEV__&&n9.kG.warn(e)})}},e.prototype.toApolloError=function(e){return(0,rm.O)(e.errors)?new rs.cA({graphQLErrors:e.errors}):e.error},e.prototype.getCurrentResult=function(){return this.result||this.handleErrorOrCompleted(this.result=this.observable.getCurrentResult()),this.result},e.prototype.toQueryResult=function(e){var t=this.toQueryResultCache.get(e);if(t)return t;var n=e.data,r=(e.partial,(0,t0._T)(e,["data","partial"]));return this.toQueryResultCache.set(e,t=(0,t0.pi)((0,t0.pi)((0,t0.pi)({data:n},r),this.obsQueryFields),{client:this.client,observable:this.observable,variables:this.observable.variables,called:!this.queryHookOptions.skip,previousData:this.previousData})),!t.error&&(0,rm.O)(e.errors)&&(t.error=new rs.cA({graphQLErrors:e.errors})),t},e.prototype.unsafeHandlePartialRefetch=function(e){e.partial&&this.queryHookOptions.partialRefetch&&!e.loading&&(!e.data||0===Object.keys(e.data).length)&&"cache-only"!==this.observable.options.fetchPolicy&&(Object.assign(e,{loading:!0,networkStatus:ru.I.refetch}),this.observable.refetch())},e}();function r_(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&void 0!==arguments[0]?arguments[0]:{};return rv(iH,e)},iz=function(){var e=ij(),t=parseInt(e.get("page")||"1",10),n=parseInt(e.get("per")||"50",10),r=i$({variables:{offset:(t-1)*n,limit:n},fetchPolicy:"network-only"}),i=r.data,a=r.loading,o=r.error;return a?l.createElement(iR,null):o?l.createElement(iD,{error:o}):i?l.createElement(iI,{chains:i.chains.results,page:t,pageSize:n,total:i.chains.metadata.total}):null},iG=n(67932),iW=n(8126),iK="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};function iV(e){if(iq())return Intl.DateTimeFormat.supportedLocalesOf(e)[0]}function iq(){return("undefined"==typeof Intl?"undefined":iK(Intl))==="object"&&"function"==typeof Intl.DateTimeFormat}var iZ="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},iX=function(){function e(e,t){for(var n=0;n=i.length)break;s=i[o++]}else{if((o=i.next()).done)break;s=o.value}var s,u=s;if((void 0===e?"undefined":iZ(e))!=="object")return;e=e[u]}return e}},{key:"put",value:function(){for(var e=arguments.length,t=Array(e),n=0;n=o.length)break;c=o[u++]}else{if((u=o.next()).done)break;c=u.value}var c,l=c;"object"!==iZ(a[l])&&(a[l]={}),a=a[l]}return a[i]=r}}]),e}();let i1=iQ;var i0=new i1;function i2(e,t){if(!iq())return function(e){return e.toString()};var n=i4(e),r=JSON.stringify(t),i=i0.get(String(n),r)||i0.put(String(n),r,new Intl.DateTimeFormat(n,t));return function(e){return i.format(e)}}var i3={};function i4(e){var t=e.toString();return i3[t]?i3[t]:i3[t]=iV(e)}var i6="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};function i5(e){return i8(e)?e:new Date(e)}function i8(e){return e instanceof Date||i9(e)}function i9(e){return(void 0===e?"undefined":i6(e))==="object"&&"function"==typeof e.getTime}var i7=n(54087),ae=n.n(i7);function at(e,t){if(0===e.length)return 0;for(var n=0,r=e.length-1,i=void 0;n<=r;){var a=t(e[i=Math.floor((r+n)/2)]);if(0===a)return i;if(a<0){if((n=i+1)>r)return n}else if((r=i-1)=t.nextUpdateTime)aa(t,this.instances);else break}},scheduleNextTick:function(){var e=this;this.scheduledTick=ae()(function(){e.tick(),e.scheduleNextTick()})},start:function(){this.scheduleNextTick()},stop:function(){ae().cancel(this.scheduledTick)}};function ai(e){var t=an(e.getNextValue(),2),n=t[0],r=t[1];e.setValue(n),e.nextUpdateTime=r}function aa(e,t){ai(e),as(t,e),ao(t,e)}function ao(e,t){var n=au(e,t);e.splice(n,0,t)}function as(e,t){var n=e.indexOf(t);e.splice(n,1)}function au(e,t){var n=t.nextUpdateTime;return at(e,function(e){return e.nextUpdateTime===n?0:e.nextUpdateTime>n?1:-1})}var ac=(0,ec.oneOfType)([(0,ec.shape)({minTime:ec.number,formatAs:ec.string.isRequired}),(0,ec.shape)({test:ec.func,formatAs:ec.string.isRequired}),(0,ec.shape)({minTime:ec.number,format:ec.func.isRequired}),(0,ec.shape)({test:ec.func,format:ec.func.isRequired})]),al=(0,ec.oneOfType)([ec.string,(0,ec.shape)({steps:(0,ec.arrayOf)(ac).isRequired,labels:(0,ec.oneOfType)([ec.string,(0,ec.arrayOf)(ec.string)]).isRequired,round:ec.string})]),af=Object.assign||function(e){for(var t=1;t=0)&&Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n}function ap(e){var t=e.date,n=e.future,r=e.timeStyle,i=e.round,a=e.minTimeLeft,o=e.tooltip,s=e.component,u=e.container,c=e.wrapperComponent,f=e.wrapperProps,d=e.locale,h=e.locales,p=e.formatVerboseDate,b=e.verboseDateFormat,m=e.updateInterval,g=e.tick,v=ah(e,["date","future","timeStyle","round","minTimeLeft","tooltip","component","container","wrapperComponent","wrapperProps","locale","locales","formatVerboseDate","verboseDateFormat","updateInterval","tick"]),y=(0,l.useMemo)(function(){return d&&(h=[d]),h.concat(iW.Z.getDefaultLocale())},[d,h]),w=(0,l.useMemo)(function(){return new iW.Z(y)},[y]);t=(0,l.useMemo)(function(){return i5(t)},[t]);var _=(0,l.useCallback)(function(){var e=Date.now(),o=void 0;if(n&&e>=t.getTime()&&(e=t.getTime(),o=!0),void 0!==a){var s=t.getTime()-1e3*a;e>s&&(e=s,o=!0)}var u=w.format(t,r,{getTimeToNextUpdate:!0,now:e,future:n,round:i}),c=ad(u,2),l=c[0],f=c[1];return f=o?ag:m||f||6e4,[l,e+f]},[t,n,r,m,i,a,w]),E=(0,l.useRef)();E.current=_;var S=(0,l.useMemo)(_,[]),k=ad(S,2),x=k[0],T=k[1],M=(0,l.useState)(x),O=ad(M,2),A=O[0],L=O[1],C=ad((0,l.useState)(),2),I=C[0],D=C[1],N=(0,l.useRef)();(0,l.useEffect)(function(){if(g)return N.current=ar.add({getNextValue:function(){return E.current()},setValue:L,nextUpdateTime:T}),function(){return N.current.stop()}},[g]),(0,l.useEffect)(function(){if(N.current)N.current.forceUpdate();else{var e=_(),t=ad(e,1)[0];L(t)}},[_]),(0,l.useEffect)(function(){D(!0)},[]);var P=(0,l.useMemo)(function(){if("undefined"!=typeof window)return i2(y,b)},[y,b]),R=(0,l.useMemo)(function(){if("undefined"!=typeof window)return p?p(t):P(t)},[t,p,P]),j=l.createElement(s,af({date:t,verboseDate:I?R:void 0,tooltip:o},v),A),F=c||u;return F?l.createElement(F,af({},f,{verboseDate:I?R:void 0}),j):j}ap.propTypes={date:el().oneOfType([el().instanceOf(Date),el().number]).isRequired,locale:el().string,locales:el().arrayOf(el().string),future:el().bool,timeStyle:al,round:el().string,minTimeLeft:el().number,component:el().elementType.isRequired,tooltip:el().bool.isRequired,formatVerboseDate:el().func,verboseDateFormat:el().object,updateInterval:el().oneOfType([el().number,el().arrayOf(el().shape({threshold:el().number,interval:el().number.isRequired}))]),tick:el().bool,wrapperComponent:el().func,wrapperProps:el().object},ap.defaultProps={locales:[],component:av,tooltip:!0,verboseDateFormat:{weekday:"long",day:"numeric",month:"long",year:"numeric",hour:"numeric",minute:"2-digit",second:"2-digit"},tick:!0},ap=l.memo(ap);let ab=ap;var am,ag=31536e9;function av(e){var t=e.date,n=e.verboseDate,r=e.tooltip,i=e.children,a=ah(e,["date","verboseDate","tooltip","children"]),o=(0,l.useMemo)(function(){return t.toISOString()},[t]);return l.createElement("time",af({},a,{dateTime:o,title:r?n:void 0}),i)}av.propTypes={date:el().instanceOf(Date).isRequired,verboseDate:el().string,tooltip:el().bool.isRequired,children:el().string.isRequired};var ay=n(30381),aw=n.n(ay),a_=n(31657);function aE(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function aS(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]0?new rs.cA({graphQLErrors:i}):void 0;if(u===s.current.mutationId&&!c.ignoreResults){var f={called:!0,loading:!1,data:r,error:l,client:a};s.current.isMounted&&!(0,ri.D)(s.current.result,f)&&o(s.current.result=f)}var d=e.onCompleted||(null===(n=s.current.options)||void 0===n?void 0:n.onCompleted);return null==d||d(t.data,c),t}).catch(function(t){if(u===s.current.mutationId&&s.current.isMounted){var n,r={loading:!1,error:t,data:void 0,called:!0,client:a};(0,ri.D)(s.current.result,r)||o(s.current.result=r)}var i=e.onError||(null===(n=s.current.options)||void 0===n?void 0:n.onError);if(i)return i(t,c),{data:void 0,errors:t};throw t})},[]),c=(0,l.useCallback)(function(){s.current.isMounted&&o({called:!1,loading:!1,client:n})},[]);return(0,l.useEffect)(function(){return s.current.isMounted=!0,function(){s.current.isMounted=!1}},[]),[u,(0,t0.pi)({reset:c},a)]}var os=n(59067),ou=n(28428),oc=n(11186),ol=n(78513);function of(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var od=function(e){return(0,b.createStyles)({paper:{display:"flex",margin:"".concat(2.5*e.spacing.unit,"px 0"),padding:"".concat(3*e.spacing.unit,"px ").concat(3.5*e.spacing.unit,"px")},content:{flex:1,width:"100%"},actions:of({marginTop:-(1.5*e.spacing.unit),marginLeft:-(4*e.spacing.unit)},e.breakpoints.up("sm"),{marginLeft:0,marginRight:-(1.5*e.spacing.unit)}),itemBlock:{border:"1px solid rgba(224, 224, 224, 1)",borderRadius:e.shape.borderRadius,padding:2*e.spacing.unit,marginTop:e.spacing.unit},itemBlockText:{overflowWrap:"anywhere"}})},oh=(0,b.withStyles)(od)(function(e){var t=e.actions,n=e.children,r=e.classes;return l.createElement(ii.default,{className:r.paper},l.createElement("div",{className:r.content},n),t&&l.createElement("div",{className:r.actions},t))}),op=function(e){var t=e.title;return l.createElement(x.default,{variant:"subtitle2",gutterBottom:!0},t)},ob=function(e){var t=e.children,n=e.value;return l.createElement(x.default,{variant:"body1",noWrap:!0},t||n)},om=(0,b.withStyles)(od)(function(e){var t=e.children,n=e.classes,r=e.value;return l.createElement("div",{className:n.itemBlock},l.createElement(x.default,{variant:"body1",className:n.itemBlockText},t||r))});function og(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]-1}let sq=sV;function sZ(e,t){var n=this.__data__,r=sH(n,e);return r<0?(++this.size,n.push([e,t])):n[r][1]=t,this}let sX=sZ;function sJ(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t-1&&e%1==0&&e-1&&e%1==0&&e<=cC}let cD=cI;var cN="[object Arguments]",cP="[object Array]",cR="[object Boolean]",cj="[object Date]",cF="[object Error]",cY="[object Function]",cB="[object Map]",cU="[object Number]",cH="[object Object]",c$="[object RegExp]",cz="[object Set]",cG="[object String]",cW="[object WeakMap]",cK="[object ArrayBuffer]",cV="[object DataView]",cq="[object Float64Array]",cZ="[object Int8Array]",cX="[object Int16Array]",cJ="[object Int32Array]",cQ="[object Uint8Array]",c1="[object Uint8ClampedArray]",c0="[object Uint16Array]",c2="[object Uint32Array]",c3={};function c4(e){return eD(e)&&cD(e.length)&&!!c3[eC(e)]}c3["[object Float32Array]"]=c3[cq]=c3[cZ]=c3[cX]=c3[cJ]=c3[cQ]=c3[c1]=c3[c0]=c3[c2]=!0,c3[cN]=c3[cP]=c3[cK]=c3[cR]=c3[cV]=c3[cj]=c3[cF]=c3[cY]=c3[cB]=c3[cU]=c3[cH]=c3[c$]=c3[cz]=c3[cG]=c3[cW]=!1;let c6=c4;function c5(e){return function(t){return e(t)}}let c8=c5;var c9=n(79730),c7=c9.Z&&c9.Z.isTypedArray,le=c7?c8(c7):c6;let lt=le;var ln=Object.prototype.hasOwnProperty;function lr(e,t){var n=cx(e),r=!n&&cS(e),i=!n&&!r&&(0,cT.Z)(e),a=!n&&!r&&!i&<(e),o=n||r||i||a,s=o?cb(e.length,String):[],u=s.length;for(var c in e)(t||ln.call(e,c))&&!(o&&("length"==c||i&&("offset"==c||"parent"==c)||a&&("buffer"==c||"byteLength"==c||"byteOffset"==c)||cL(c,u)))&&s.push(c);return s}let li=lr;var la=Object.prototype;function lo(e){var t=e&&e.constructor;return e===("function"==typeof t&&t.prototype||la)}let ls=lo;var lu=sT(Object.keys,Object);let lc=lu;var ll=Object.prototype.hasOwnProperty;function lf(e){if(!ls(e))return lc(e);var t=[];for(var n in Object(e))ll.call(e,n)&&"constructor"!=n&&t.push(n);return t}let ld=lf;function lh(e){return null!=e&&cD(e.length)&&!ur(e)}let lp=lh;function lb(e){return lp(e)?li(e):ld(e)}let lm=lb;function lg(e,t){return e&&ch(t,lm(t),e)}let lv=lg;function ly(e){var t=[];if(null!=e)for(var n in Object(e))t.push(n);return t}let lw=ly;var l_=Object.prototype.hasOwnProperty;function lE(e){if(!ed(e))return lw(e);var t=ls(e),n=[];for(var r in e)"constructor"==r&&(t||!l_.call(e,r))||n.push(r);return n}let lS=lE;function lk(e){return lp(e)?li(e,!0):lS(e)}let lx=lk;function lT(e,t){return e&&ch(t,lx(t),e)}let lM=lT;var lO=n(42896);function lA(e,t){var n=-1,r=e.length;for(t||(t=Array(r));++n=0||(i[n]=e[n]);return i}function hu(e){if(void 0===e)throw ReferenceError("this hasn't been initialised - super() hasn't been called");return e}var hc=function(e){return Array.isArray(e)&&0===e.length},hl=function(e){return"function"==typeof e},hf=function(e){return null!==e&&"object"==typeof e},hd=function(e){return String(Math.floor(Number(e)))===e},hh=function(e){return"[object String]"===Object.prototype.toString.call(e)},hp=function(e){return 0===l.Children.count(e)},hb=function(e){return hf(e)&&hl(e.then)};function hm(e,t,n,r){void 0===r&&(r=0);for(var i=d8(t);e&&r=0?[]:{}}}return(0===a?e:i)[o[a]]===n?e:(void 0===n?delete i[o[a]]:i[o[a]]=n,0===a&&void 0===n&&delete r[o[a]],r)}function hv(e,t,n,r){void 0===n&&(n=new WeakMap),void 0===r&&(r={});for(var i=0,a=Object.keys(e);i0?t.map(function(t){return x(t,hm(e,t))}):[Promise.resolve("DO_NOT_DELETE_YOU_WILL_BE_FIRED")]).then(function(e){return e.reduce(function(e,n,r){return"DO_NOT_DELETE_YOU_WILL_BE_FIRED"===n||n&&(e=hg(e,t[r],n)),e},{})})},[x]),M=(0,l.useCallback)(function(e){return Promise.all([T(e),h.validationSchema?k(e):{},h.validate?S(e):{}]).then(function(e){var t=e[0],n=e[1],r=e[2];return sk.all([t,n,r],{arrayMerge:hL})})},[h.validate,h.validationSchema,T,S,k]),O=hN(function(e){return void 0===e&&(e=_.values),E({type:"SET_ISVALIDATING",payload:!0}),M(e).then(function(e){return v.current&&(E({type:"SET_ISVALIDATING",payload:!1}),sd()(_.errors,e)||E({type:"SET_ERRORS",payload:e})),e})});(0,l.useEffect)(function(){o&&!0===v.current&&sd()(p.current,h.initialValues)&&O(p.current)},[o,O]);var A=(0,l.useCallback)(function(e){var t=e&&e.values?e.values:p.current,n=e&&e.errors?e.errors:b.current?b.current:h.initialErrors||{},r=e&&e.touched?e.touched:m.current?m.current:h.initialTouched||{},i=e&&e.status?e.status:g.current?g.current:h.initialStatus;p.current=t,b.current=n,m.current=r,g.current=i;var a=function(){E({type:"RESET_FORM",payload:{isSubmitting:!!e&&!!e.isSubmitting,errors:n,touched:r,status:i,values:t,isValidating:!!e&&!!e.isValidating,submitCount:e&&e.submitCount&&"number"==typeof e.submitCount?e.submitCount:0}})};if(h.onReset){var o=h.onReset(_.values,V);hb(o)?o.then(a):a()}else a()},[h.initialErrors,h.initialStatus,h.initialTouched]);(0,l.useEffect)(function(){!0===v.current&&!sd()(p.current,h.initialValues)&&(c&&(p.current=h.initialValues,A()),o&&O(p.current))},[c,h.initialValues,A,o,O]),(0,l.useEffect)(function(){c&&!0===v.current&&!sd()(b.current,h.initialErrors)&&(b.current=h.initialErrors||hS,E({type:"SET_ERRORS",payload:h.initialErrors||hS}))},[c,h.initialErrors]),(0,l.useEffect)(function(){c&&!0===v.current&&!sd()(m.current,h.initialTouched)&&(m.current=h.initialTouched||hk,E({type:"SET_TOUCHED",payload:h.initialTouched||hk}))},[c,h.initialTouched]),(0,l.useEffect)(function(){c&&!0===v.current&&!sd()(g.current,h.initialStatus)&&(g.current=h.initialStatus,E({type:"SET_STATUS",payload:h.initialStatus}))},[c,h.initialStatus,h.initialTouched]);var L=hN(function(e){if(y.current[e]&&hl(y.current[e].validate)){var t=hm(_.values,e),n=y.current[e].validate(t);return hb(n)?(E({type:"SET_ISVALIDATING",payload:!0}),n.then(function(e){return e}).then(function(t){E({type:"SET_FIELD_ERROR",payload:{field:e,value:t}}),E({type:"SET_ISVALIDATING",payload:!1})})):(E({type:"SET_FIELD_ERROR",payload:{field:e,value:n}}),Promise.resolve(n))}return h.validationSchema?(E({type:"SET_ISVALIDATING",payload:!0}),k(_.values,e).then(function(e){return e}).then(function(t){E({type:"SET_FIELD_ERROR",payload:{field:e,value:t[e]}}),E({type:"SET_ISVALIDATING",payload:!1})})):Promise.resolve()}),C=(0,l.useCallback)(function(e,t){var n=t.validate;y.current[e]={validate:n}},[]),I=(0,l.useCallback)(function(e){delete y.current[e]},[]),D=hN(function(e,t){return E({type:"SET_TOUCHED",payload:e}),(void 0===t?i:t)?O(_.values):Promise.resolve()}),N=(0,l.useCallback)(function(e){E({type:"SET_ERRORS",payload:e})},[]),P=hN(function(e,t){var r=hl(e)?e(_.values):e;return E({type:"SET_VALUES",payload:r}),(void 0===t?n:t)?O(r):Promise.resolve()}),R=(0,l.useCallback)(function(e,t){E({type:"SET_FIELD_ERROR",payload:{field:e,value:t}})},[]),j=hN(function(e,t,r){return E({type:"SET_FIELD_VALUE",payload:{field:e,value:t}}),(void 0===r?n:r)?O(hg(_.values,e,t)):Promise.resolve()}),F=(0,l.useCallback)(function(e,t){var n,r=t,i=e;if(!hh(e)){e.persist&&e.persist();var a=e.target?e.target:e.currentTarget,o=a.type,s=a.name,u=a.id,c=a.value,l=a.checked,f=(a.outerHTML,a.options),d=a.multiple;r=t||s||u,i=/number|range/.test(o)?(n=parseFloat(c),isNaN(n)?"":n):/checkbox/.test(o)?hI(hm(_.values,r),l,c):d?hC(f):c}r&&j(r,i)},[j,_.values]),Y=hN(function(e){if(hh(e))return function(t){return F(t,e)};F(e)}),B=hN(function(e,t,n){return void 0===t&&(t=!0),E({type:"SET_FIELD_TOUCHED",payload:{field:e,value:t}}),(void 0===n?i:n)?O(_.values):Promise.resolve()}),U=(0,l.useCallback)(function(e,t){e.persist&&e.persist();var n,r=e.target,i=r.name,a=r.id;r.outerHTML,B(t||i||a,!0)},[B]),H=hN(function(e){if(hh(e))return function(t){return U(t,e)};U(e)}),$=(0,l.useCallback)(function(e){hl(e)?E({type:"SET_FORMIK_STATE",payload:e}):E({type:"SET_FORMIK_STATE",payload:function(){return e}})},[]),z=(0,l.useCallback)(function(e){E({type:"SET_STATUS",payload:e})},[]),G=(0,l.useCallback)(function(e){E({type:"SET_ISSUBMITTING",payload:e})},[]),W=hN(function(){return E({type:"SUBMIT_ATTEMPT"}),O().then(function(e){var t,n=e instanceof Error;if(!n&&0===Object.keys(e).length){try{if(void 0===(t=q()))return}catch(r){throw r}return Promise.resolve(t).then(function(e){return v.current&&E({type:"SUBMIT_SUCCESS"}),e}).catch(function(e){if(v.current)throw E({type:"SUBMIT_FAILURE"}),e})}if(v.current&&(E({type:"SUBMIT_FAILURE"}),n))throw e})}),K=hN(function(e){e&&e.preventDefault&&hl(e.preventDefault)&&e.preventDefault(),e&&e.stopPropagation&&hl(e.stopPropagation)&&e.stopPropagation(),W().catch(function(e){console.warn("Warning: An unhandled error was caught from submitForm()",e)})}),V={resetForm:A,validateForm:O,validateField:L,setErrors:N,setFieldError:R,setFieldTouched:B,setFieldValue:j,setStatus:z,setSubmitting:G,setTouched:D,setValues:P,setFormikState:$,submitForm:W},q=hN(function(){return f(_.values,V)}),Z=hN(function(e){e&&e.preventDefault&&hl(e.preventDefault)&&e.preventDefault(),e&&e.stopPropagation&&hl(e.stopPropagation)&&e.stopPropagation(),A()}),X=(0,l.useCallback)(function(e){return{value:hm(_.values,e),error:hm(_.errors,e),touched:!!hm(_.touched,e),initialValue:hm(p.current,e),initialTouched:!!hm(m.current,e),initialError:hm(b.current,e)}},[_.errors,_.touched,_.values]),J=(0,l.useCallback)(function(e){return{setValue:function(t,n){return j(e,t,n)},setTouched:function(t,n){return B(e,t,n)},setError:function(t){return R(e,t)}}},[j,B,R]),Q=(0,l.useCallback)(function(e){var t=hf(e),n=t?e.name:e,r=hm(_.values,n),i={name:n,value:r,onChange:Y,onBlur:H};if(t){var a=e.type,o=e.value,s=e.as,u=e.multiple;"checkbox"===a?void 0===o?i.checked=!!r:(i.checked=!!(Array.isArray(r)&&~r.indexOf(o)),i.value=o):"radio"===a?(i.checked=r===o,i.value=o):"select"===s&&u&&(i.value=i.value||[],i.multiple=!0)}return i},[H,Y,_.values]),ee=(0,l.useMemo)(function(){return!sd()(p.current,_.values)},[p.current,_.values]),et=(0,l.useMemo)(function(){return void 0!==s?ee?_.errors&&0===Object.keys(_.errors).length:!1!==s&&hl(s)?s(h):s:_.errors&&0===Object.keys(_.errors).length},[s,ee,_.errors,h]);return ha({},_,{initialValues:p.current,initialErrors:b.current,initialTouched:m.current,initialStatus:g.current,handleBlur:H,handleChange:Y,handleReset:Z,handleSubmit:K,resetForm:A,setErrors:N,setFormikState:$,setFieldTouched:B,setFieldValue:j,setFieldError:R,setStatus:z,setSubmitting:G,setTouched:D,setValues:P,submitForm:W,validateForm:O,validateField:L,isValid:et,dirty:ee,unregisterField:I,registerField:C,getFieldProps:Q,getFieldMeta:X,getFieldHelpers:J,validateOnBlur:i,validateOnChange:n,validateOnMount:o})}function hT(e){var t=hx(e),n=e.component,r=e.children,i=e.render,a=e.innerRef;return(0,l.useImperativeHandle)(a,function(){return t}),(0,l.createElement)(hw,{value:t},n?(0,l.createElement)(n,t):i?i(t):r?hl(r)?r(t):hp(r)?null:l.Children.only(r):null)}function hM(e){var t={};if(e.inner){if(0===e.inner.length)return hg(t,e.path,e.message);for(var n=e.inner,r=Array.isArray(n),i=0,n=r?n:n[Symbol.iterator]();;){if(r){if(i>=n.length)break;a=n[i++]}else{if((i=n.next()).done)break;a=i.value}var a,o=a;hm(t,o.path)||(t=hg(t,o.path,o.message))}}return t}function hO(e,t,n,r){void 0===n&&(n=!1),void 0===r&&(r={});var i=hA(e);return t[n?"validateSync":"validate"](i,{abortEarly:!1,context:r})}function hA(e){var t=Array.isArray(e)?[]:{};for(var n in e)if(Object.prototype.hasOwnProperty.call(e,n)){var r=String(n);!0===Array.isArray(e[r])?t[r]=e[r].map(function(e){return!0===Array.isArray(e)||sR(e)?hA(e):""!==e?e:void 0}):sR(e[r])?t[r]=hA(e[r]):t[r]=""!==e[r]?e[r]:void 0}return t}function hL(e,t,n){var r=e.slice();return t.forEach(function(t,i){if(void 0===r[i]){var a=!1!==n.clone&&n.isMergeableObject(t);r[i]=a?sk(Array.isArray(t)?[]:{},t,n):t}else n.isMergeableObject(t)?r[i]=sk(e[i],t,n):-1===e.indexOf(t)&&r.push(t)}),r}function hC(e){return Array.from(e).filter(function(e){return e.selected}).map(function(e){return e.value})}function hI(e,t,n){if("boolean"==typeof e)return Boolean(t);var r=[],i=!1,a=-1;if(Array.isArray(e))r=e,i=(a=e.indexOf(n))>=0;else if(!n||"true"==n||"false"==n)return Boolean(t);return t&&n&&!i?r.concat(n):i?r.slice(0,a).concat(r.slice(a+1)):r}var hD="undefined"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement?l.useLayoutEffect:l.useEffect;function hN(e){var t=(0,l.useRef)(e);return hD(function(){t.current=e}),(0,l.useCallback)(function(){for(var e=arguments.length,n=Array(e),r=0;re?t:e},0);return Array.from(ha({},e,{length:t+1}))};(function(e){function t(t){var n;return(n=e.call(this,t)||this).updateArrayField=function(e,t,r){var i=n.props,a=i.name;(0,i.formik.setFormikState)(function(n){var i="function"==typeof r?r:e,o="function"==typeof t?t:e,s=hg(n.values,a,e(hm(n.values,a))),u=r?i(hm(n.errors,a)):void 0,c=t?o(hm(n.touched,a)):void 0;return hc(u)&&(u=void 0),hc(c)&&(c=void 0),ha({},n,{values:s,errors:r?hg(n.errors,a,u):n.errors,touched:t?hg(n.touched,a,c):n.touched})})},n.push=function(e){return n.updateArrayField(function(t){return[].concat(hU(t),[hi(e)])},!1,!1)},n.handlePush=function(e){return function(){return n.push(e)}},n.swap=function(e,t){return n.updateArrayField(function(n){return hF(n,e,t)},!0,!0)},n.handleSwap=function(e,t){return function(){return n.swap(e,t)}},n.move=function(e,t){return n.updateArrayField(function(n){return hj(n,e,t)},!0,!0)},n.handleMove=function(e,t){return function(){return n.move(e,t)}},n.insert=function(e,t){return n.updateArrayField(function(n){return hY(n,e,t)},function(t){return hY(t,e,null)},function(t){return hY(t,e,null)})},n.handleInsert=function(e,t){return function(){return n.insert(e,t)}},n.replace=function(e,t){return n.updateArrayField(function(n){return hB(n,e,t)},!1,!1)},n.handleReplace=function(e,t){return function(){return n.replace(e,t)}},n.unshift=function(e){var t=-1;return n.updateArrayField(function(n){var r=n?[e].concat(n):[e];return t<0&&(t=r.length),r},function(e){var n=e?[null].concat(e):[null];return t<0&&(t=n.length),n},function(e){var n=e?[null].concat(e):[null];return t<0&&(t=n.length),n}),t},n.handleUnshift=function(e){return function(){return n.unshift(e)}},n.handleRemove=function(e){return function(){return n.remove(e)}},n.handlePop=function(){return function(){return n.pop()}},n.remove=n.remove.bind(hu(n)),n.pop=n.pop.bind(hu(n)),n}ho(t,e);var n=t.prototype;return n.componentDidUpdate=function(e){this.props.validateOnChange&&this.props.formik.validateOnChange&&!sd()(hm(e.formik.values,e.name),hm(this.props.formik.values,this.props.name))&&this.props.formik.validateForm(this.props.formik.values)},n.remove=function(e){var t;return this.updateArrayField(function(n){var r=n?hU(n):[];return t||(t=r[e]),hl(r.splice)&&r.splice(e,1),r},!0,!0),t},n.pop=function(){var e;return this.updateArrayField(function(t){var n=t;return e||(e=n&&n.pop&&n.pop()),n},!0,!0),e},n.render=function(){var e={push:this.push,pop:this.pop,swap:this.swap,move:this.move,insert:this.insert,replace:this.replace,unshift:this.unshift,remove:this.remove,handlePush:this.handlePush,handlePop:this.handlePop,handleSwap:this.handleSwap,handleMove:this.handleMove,handleInsert:this.handleInsert,handleReplace:this.handleReplace,handleUnshift:this.handleUnshift,handleRemove:this.handleRemove},t=this.props,n=t.component,r=t.render,i=t.children,a=t.name,o=hs(t.formik,["validate","validationSchema"]),s=ha({},e,{form:o,name:a});return n?(0,l.createElement)(n,s):r?r(s):i?"function"==typeof i?i(s):hp(i)?null:l.Children.only(i):null},t})(l.Component).defaultProps={validateOnChange:!0},l.Component,l.Component;var hH=n(24802),h$=n(71209),hz=n(91750),hG=n(11970),hW=n(4689),hK=n(67598),hV=function(){return(hV=Object.assign||function(e){for(var t,n=1,r=arguments.length;nt.indexOf(r)&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var i=0,r=Object.getOwnPropertySymbols(e);it.indexOf(r[i])&&(n[r[i]]=e[r[i]]);return n}function hZ(e){var t=e.disabled,n=e.field,r=n.onBlur,i=hq(n,["onBlur"]),a=e.form,o=a.isSubmitting,s=a.touched,u=a.errors,c=e.onBlur,l=e.helperText,f=hq(e,["disabled","field","form","onBlur","helperText"]),d=hm(u,i.name),h=hm(s,i.name)&&!!d;return hV(hV({variant:f.variant,error:h,helperText:h?d:l,disabled:null!=t?t:o,onBlur:null!=c?c:function(e){r(null!=e?e:i.name)}},i),f)}function hX(e){var t=e.children,n=hq(e,["children"]);return(0,l.createElement)(iw.Z,hV({},hZ(n)),t)}function hJ(e){var t=e.disabled,n=e.field,r=n.onBlur,i=hq(n,["onBlur"]),a=e.form.isSubmitting,o=(e.type,e.onBlur),s=hq(e,["disabled","field","form","type","onBlur"]);return hV(hV({disabled:null!=t?t:a,onBlur:null!=o?o:function(e){r(null!=e?e:i.name)}},i),s)}function hQ(e){return(0,l.createElement)(hH.Z,hV({},hJ(e)))}function h1(e){var t,n=e.disabled,r=e.field,i=r.onBlur,a=hq(r,["onBlur"]),o=e.form.isSubmitting,s=(e.type,e.onBlur),u=hq(e,["disabled","field","form","type","onBlur"]);return hV(hV({disabled:null!=n?n:o,indeterminate:!Array.isArray(a.value)&&null==a.value,onBlur:null!=s?s:function(e){i(null!=e?e:a.name)}},a),u)}function h0(e){return(0,l.createElement)(h$.Z,hV({},h1(e)))}function h2(e){var t=e.Label,n=hq(e,["Label"]);return(0,l.createElement)(hz.Z,hV({control:(0,l.createElement)(h$.Z,hV({},h1(n)))},t))}function h3(e){var t=e.disabled,n=e.field,r=n.onBlur,i=hq(n,["onBlur"]),a=e.form.isSubmitting,o=e.onBlur,s=hq(e,["disabled","field","form","onBlur"]);return hV(hV({disabled:null!=t?t:a,onBlur:null!=o?o:function(e){r(null!=e?e:i.name)}},i),s)}function h4(e){return(0,l.createElement)(hG.default,hV({},h3(e)))}function h6(e){var t=e.field,n=t.onBlur,r=hq(t,["onBlur"]),i=(e.form,e.onBlur),a=hq(e,["field","form","onBlur"]);return hV(hV({onBlur:null!=i?i:function(e){n(null!=e?e:r.name)}},r),a)}function h5(e){return(0,l.createElement)(hW.Z,hV({},h6(e)))}function h8(e){var t=e.disabled,n=e.field,r=n.onBlur,i=hq(n,["onBlur"]),a=e.form.isSubmitting,o=e.onBlur,s=hq(e,["disabled","field","form","onBlur"]);return hV(hV({disabled:null!=t?t:a,onBlur:null!=o?o:function(e){r(null!=e?e:i.name)}},i),s)}function h9(e){return(0,l.createElement)(hK.default,hV({},h8(e)))}hX.displayName="FormikMaterialUITextField",hQ.displayName="FormikMaterialUISwitch",h0.displayName="FormikMaterialUICheckbox",h2.displayName="FormikMaterialUICheckboxWithLabel",h4.displayName="FormikMaterialUISelect",h5.displayName="FormikMaterialUIRadioGroup",h9.displayName="FormikMaterialUIInputBase";try{a=Map}catch(h7){}try{o=Set}catch(pe){}function pt(e,t,n){if(!e||"object"!=typeof e||"function"==typeof e)return e;if(e.nodeType&&"cloneNode"in e)return e.cloneNode(!0);if(e instanceof Date)return new Date(e.getTime());if(e instanceof RegExp)return RegExp(e);if(Array.isArray(e))return e.map(pn);if(a&&e instanceof a)return new Map(Array.from(e.entries()));if(o&&e instanceof o)return new Set(Array.from(e.values()));if(e instanceof Object){t.push(e);var r=Object.create(e);for(var i in n.push(r),e){var s=t.findIndex(function(t){return t===e[i]});r[i]=s>-1?n[s]:pt(e[i],t,n)}return r}return e}function pn(e){return pt(e,[],[])}let pr=Object.prototype.toString,pi=Error.prototype.toString,pa=RegExp.prototype.toString,po="undefined"!=typeof Symbol?Symbol.prototype.toString:()=>"",ps=/^Symbol\((.*)\)(.*)$/;function pu(e){if(e!=+e)return"NaN";let t=0===e&&1/e<0;return t?"-0":""+e}function pc(e,t=!1){if(null==e||!0===e||!1===e)return""+e;let n=typeof e;if("number"===n)return pu(e);if("string"===n)return t?`"${e}"`:e;if("function"===n)return"[Function "+(e.name||"anonymous")+"]";if("symbol"===n)return po.call(e).replace(ps,"Symbol($1)");let r=pr.call(e).slice(8,-1);return"Date"===r?isNaN(e.getTime())?""+e:e.toISOString(e):"Error"===r||e instanceof Error?"["+pi.call(e)+"]":"RegExp"===r?pa.call(e):null}function pl(e,t){let n=pc(e,t);return null!==n?n:JSON.stringify(e,function(e,n){let r=pc(this[e],t);return null!==r?r:n},2)}let pf={default:"${path} is invalid",required:"${path} is a required field",oneOf:"${path} must be one of the following values: ${values}",notOneOf:"${path} must not be one of the following values: ${values}",notType({path:e,type:t,value:n,originalValue:r}){let i=null!=r&&r!==n,a=`${e} must be a \`${t}\` type, but the final value was: \`${pl(n,!0)}\``+(i?` (cast from the value \`${pl(r,!0)}\`).`:".");return null===n&&(a+='\n If "null" is intended as an empty value be sure to mark the schema as `.nullable()`'),a},defined:"${path} must be defined"},pd={length:"${path} must be exactly ${length} characters",min:"${path} must be at least ${min} characters",max:"${path} must be at most ${max} characters",matches:'${path} must match the following: "${regex}"',email:"${path} must be a valid email",url:"${path} must be a valid URL",uuid:"${path} must be a valid UUID",trim:"${path} must be a trimmed string",lowercase:"${path} must be a lowercase string",uppercase:"${path} must be a upper case string"},ph={min:"${path} must be greater than or equal to ${min}",max:"${path} must be less than or equal to ${max}",lessThan:"${path} must be less than ${less}",moreThan:"${path} must be greater than ${more}",positive:"${path} must be a positive number",negative:"${path} must be a negative number",integer:"${path} must be an integer"},pp={min:"${path} field must be later than ${min}",max:"${path} field must be at earlier than ${max}"},pb={isValue:"${path} field must be ${value}"},pm={noUnknown:"${path} field has unspecified keys: ${unknown}"},pg={min:"${path} field must have at least ${min} items",max:"${path} field must have less than or equal to ${max} items",length:"${path} must be have ${length} items"};Object.assign(Object.create(null),{mixed:pf,string:pd,number:ph,date:pp,object:pm,array:pg,boolean:pb});var pv=n(18721),py=n.n(pv);let pw=e=>e&&e.__isYupSchema__;class p_{constructor(e,t){if(this.refs=e,this.refs=e,"function"==typeof t){this.fn=t;return}if(!py()(t,"is"))throw TypeError("`is:` is required for `when()` conditions");if(!t.then&&!t.otherwise)throw TypeError("either `then:` or `otherwise:` is required for `when()` conditions");let{is:n,then:r,otherwise:i}=t,a="function"==typeof n?n:(...e)=>e.every(e=>e===n);this.fn=function(...e){let t=e.pop(),n=e.pop(),o=a(...e)?r:i;if(o)return"function"==typeof o?o(n):n.concat(o.resolve(t))}}resolve(e,t){let n=this.refs.map(e=>e.getValue(null==t?void 0:t.value,null==t?void 0:t.parent,null==t?void 0:t.context)),r=this.fn.apply(e,n.concat(e,t));if(void 0===r||r===e)return e;if(!pw(r))throw TypeError("conditions must return a schema object");return r.resolve(t)}}let pE=p_;function pS(e){return null==e?[]:[].concat(e)}function pk(){return(pk=Object.assign||function(e){for(var t=1;tpl(t[n])):"function"==typeof e?e(t):e}static isError(e){return e&&"ValidationError"===e.name}constructor(e,t,n,r){super(),this.name="ValidationError",this.value=t,this.path=n,this.type=r,this.errors=[],this.inner=[],pS(e).forEach(e=>{pT.isError(e)?(this.errors.push(...e.errors),this.inner=this.inner.concat(e.inner.length?e.inner:e)):this.errors.push(e)}),this.message=this.errors.length>1?`${this.errors.length} errors occurred`:this.errors[0],Error.captureStackTrace&&Error.captureStackTrace(this,pT)}}let pM=e=>{let t=!1;return(...n)=>{t||(t=!0,e(...n))}};function pO(e,t){let{endEarly:n,tests:r,args:i,value:a,errors:o,sort:s,path:u}=e,c=pM(t),l=r.length,f=[];if(o=o||[],!l)return o.length?c(new pT(o,a,u)):c(null,a);for(let d=0;d=0||(i[n]=e[n]);return i}function pR(e){function t(t,n){let{value:r,path:i="",label:a,options:o,originalValue:s,sync:u}=t,c=pP(t,["value","path","label","options","originalValue","sync"]),{name:l,test:f,params:d,message:h}=e,{parent:p,context:b}=o;function m(e){return pD.isRef(e)?e.getValue(r,p,b):e}function g(e={}){let t=pL()(pN({value:r,originalValue:s,label:a,path:e.path||i},d,e.params),m),n=new pT(pT.formatError(e.message||h,t),r,t.path,e.type||l);return n.params=t,n}let v=pN({path:i,parent:p,type:l,createError:g,resolve:m,options:o,originalValue:s},c);if(!u){try{Promise.resolve(f.call(v,r,v)).then(e=>{pT.isError(e)?n(e):e?n(null,e):n(g())})}catch(y){n(y)}return}let w;try{var _;if(w=f.call(v,r,v),"function"==typeof(null==(_=w)?void 0:_.then))throw Error(`Validation test of type: "${v.type}" returned a Promise during a synchronous validate. This test will finish after the validate call has returned`)}catch(E){n(E);return}pT.isError(w)?n(w):w?n(null,w):n(g())}return t.OPTIONS=e,t}pD.prototype.__isYupRef=!0;let pj=e=>e.substr(0,e.length-1).substr(1);function pF(e,t,n,r=n){let i,a,o;return t?((0,pC.forEach)(t,(s,u,c)=>{let l=u?pj(s):s;if((e=e.resolve({context:r,parent:i,value:n})).innerType){let f=c?parseInt(l,10):0;if(n&&f>=n.length)throw Error(`Yup.reach cannot resolve an array item at index: ${s}, in the path: ${t}. because there is no value at that index. `);i=n,n=n&&n[f],e=e.innerType}if(!c){if(!e.fields||!e.fields[l])throw Error(`The schema does not contain the path: ${t}. (failed at: ${o} which is a type: "${e._type}")`);i=n,n=n&&n[l],e=e.fields[l]}a=l,o=u?"["+s+"]":"."+s}),{schema:e,parent:i,parentPath:a}):{parent:i,parentPath:t,schema:e}}class pY{constructor(){this.list=new Set,this.refs=new Map}get size(){return this.list.size+this.refs.size}describe(){let e=[];for(let t of this.list)e.push(t);for(let[,n]of this.refs)e.push(n.describe());return e}toArray(){return Array.from(this.list).concat(Array.from(this.refs.values()))}add(e){pD.isRef(e)?this.refs.set(e.key,e):this.list.add(e)}delete(e){pD.isRef(e)?this.refs.delete(e.key):this.list.delete(e)}has(e,t){if(this.list.has(e))return!0;let n,r=this.refs.values();for(;!(n=r.next()).done;)if(t(n.value)===e)return!0;return!1}clone(){let e=new pY;return e.list=new Set(this.list),e.refs=new Map(this.refs),e}merge(e,t){let n=this.clone();return e.list.forEach(e=>n.add(e)),e.refs.forEach(e=>n.add(e)),t.list.forEach(e=>n.delete(e)),t.refs.forEach(e=>n.delete(e)),n}}function pB(){return(pB=Object.assign||function(e){for(var t=1;t{this.typeError(pf.notType)}),this.type=(null==e?void 0:e.type)||"mixed",this.spec=pB({strip:!1,strict:!1,abortEarly:!0,recursive:!0,nullable:!1,presence:"optional"},null==e?void 0:e.spec)}get _type(){return this.type}_typeCheck(e){return!0}clone(e){if(this._mutate)return e&&Object.assign(this.spec,e),this;let t=Object.create(Object.getPrototypeOf(this));return t.type=this.type,t._typeError=this._typeError,t._whitelistError=this._whitelistError,t._blacklistError=this._blacklistError,t._whitelist=this._whitelist.clone(),t._blacklist=this._blacklist.clone(),t.exclusiveTests=pB({},this.exclusiveTests),t.deps=[...this.deps],t.conditions=[...this.conditions],t.tests=[...this.tests],t.transforms=[...this.transforms],t.spec=pn(pB({},this.spec,e)),t}label(e){var t=this.clone();return t.spec.label=e,t}meta(...e){if(0===e.length)return this.spec.meta;let t=this.clone();return t.spec.meta=Object.assign(t.spec.meta||{},e[0]),t}withMutation(e){let t=this._mutate;this._mutate=!0;let n=e(this);return this._mutate=t,n}concat(e){if(!e||e===this)return this;if(e.type!==this.type&&"mixed"!==this.type)throw TypeError(`You cannot \`concat()\` schema's of different types: ${this.type} and ${e.type}`);let t=this,n=e.clone(),r=pB({},t.spec,n.spec);return n.spec=r,n._typeError||(n._typeError=t._typeError),n._whitelistError||(n._whitelistError=t._whitelistError),n._blacklistError||(n._blacklistError=t._blacklistError),n._whitelist=t._whitelist.merge(e._whitelist,e._blacklist),n._blacklist=t._blacklist.merge(e._blacklist,e._whitelist),n.tests=t.tests,n.exclusiveTests=t.exclusiveTests,n.withMutation(t=>{e.tests.forEach(e=>{t.test(e.OPTIONS)})}),n}isType(e){return!!this.spec.nullable&&null===e||this._typeCheck(e)}resolve(e){let t=this;if(t.conditions.length){let n=t.conditions;(t=t.clone()).conditions=[],t=(t=n.reduce((t,n)=>n.resolve(t,e),t)).resolve(e)}return t}cast(e,t={}){let n=this.resolve(pB({value:e},t)),r=n._cast(e,t);if(void 0!==e&&!1!==t.assert&&!0!==n.isType(r)){let i=pl(e),a=pl(r);throw TypeError(`The value of ${t.path||"field"} could not be cast to a value that satisfies the schema type: "${n._type}". attempted value: ${i} -`+(a!==i?`result of cast: ${a}`:""))}return r}_cast(e,t){let n=void 0===e?e:this.transforms.reduce((t,n)=>n.call(this,t,e,this),e);return void 0===n&&(n=this.getDefault()),n}_validate(e,t={},n){let{sync:r,path:i,from:a=[],originalValue:o=e,strict:s=this.spec.strict,abortEarly:u=this.spec.abortEarly}=t,c=e;s||(c=this._cast(c,pB({assert:!1},t)));let l={value:c,path:i,options:t,originalValue:o,schema:this,label:this.spec.label,sync:r,from:a},f=[];this._typeError&&f.push(this._typeError),this._whitelistError&&f.push(this._whitelistError),this._blacklistError&&f.push(this._blacklistError),pO({args:l,value:c,path:i,sync:r,tests:f,endEarly:u},e=>{if(e)return void n(e,c);pO({tests:this.tests,args:l,path:i,sync:r,value:c,endEarly:u},n)})}validate(e,t,n){let r=this.resolve(pB({},t,{value:e}));return"function"==typeof n?r._validate(e,t,n):new Promise((n,i)=>r._validate(e,t,(e,t)=>{e?i(e):n(t)}))}validateSync(e,t){let n;return this.resolve(pB({},t,{value:e}))._validate(e,pB({},t,{sync:!0}),(e,t)=>{if(e)throw e;n=t}),n}isValid(e,t){return this.validate(e,t).then(()=>!0,e=>{if(pT.isError(e))return!1;throw e})}isValidSync(e,t){try{return this.validateSync(e,t),!0}catch(n){if(pT.isError(n))return!1;throw n}}_getDefault(){let e=this.spec.default;return null==e?e:"function"==typeof e?e.call(this):pn(e)}getDefault(e){return this.resolve(e||{})._getDefault()}default(e){return 0===arguments.length?this._getDefault():this.clone({default:e})}strict(e=!0){var t=this.clone();return t.spec.strict=e,t}_isPresent(e){return null!=e}defined(e=pf.defined){return this.test({message:e,name:"defined",exclusive:!0,test:e=>void 0!==e})}required(e=pf.required){return this.clone({presence:"required"}).withMutation(t=>t.test({message:e,name:"required",exclusive:!0,test(e){return this.schema._isPresent(e)}}))}notRequired(){var e=this.clone({presence:"optional"});return e.tests=e.tests.filter(e=>"required"!==e.OPTIONS.name),e}nullable(e=!0){return this.clone({nullable:!1!==e})}transform(e){var t=this.clone();return t.transforms.push(e),t}test(...e){let t;if(void 0===(t=1===e.length?"function"==typeof e[0]?{test:e[0]}:e[0]:2===e.length?{name:e[0],test:e[1]}:{name:e[0],message:e[1],test:e[2]}).message&&(t.message=pf.default),"function"!=typeof t.test)throw TypeError("`test` is a required parameters");let n=this.clone(),r=pR(t),i=t.exclusive||t.name&&!0===n.exclusiveTests[t.name];if(t.exclusive&&!t.name)throw TypeError("Exclusive tests must provide a unique `name` identifying the test");return t.name&&(n.exclusiveTests[t.name]=!!t.exclusive),n.tests=n.tests.filter(e=>e.OPTIONS.name!==t.name||!i&&e.OPTIONS.test!==r.OPTIONS.test),n.tests.push(r),n}when(e,t){Array.isArray(e)||"string"==typeof e||(t=e,e=".");let n=this.clone(),r=pS(e).map(e=>new pD(e));return r.forEach(e=>{e.isSibling&&n.deps.push(e.key)}),n.conditions.push(new pE(r,t)),n}typeError(e){var t=this.clone();return t._typeError=pR({message:e,name:"typeError",test(e){return!!(void 0===e||this.schema.isType(e))||this.createError({params:{type:this.schema._type}})}}),t}oneOf(e,t=pf.oneOf){var n=this.clone();return e.forEach(e=>{n._whitelist.add(e),n._blacklist.delete(e)}),n._whitelistError=pR({message:t,name:"oneOf",test(e){if(void 0===e)return!0;let t=this.schema._whitelist;return!!t.has(e,this.resolve)||this.createError({params:{values:t.toArray().join(", ")}})}}),n}notOneOf(e,t=pf.notOneOf){var n=this.clone();return e.forEach(e=>{n._blacklist.add(e),n._whitelist.delete(e)}),n._blacklistError=pR({message:t,name:"notOneOf",test(e){let t=this.schema._blacklist;return!t.has(e,this.resolve)||this.createError({params:{values:t.toArray().join(", ")}})}}),n}strip(e=!0){let t=this.clone();return t.spec.strip=e,t}describe(){let e=this.clone(),{label:t,meta:n}=e.spec,r={meta:n,label:t,type:e.type,oneOf:e._whitelist.describe(),notOneOf:e._blacklist.describe(),tests:e.tests.map(e=>({name:e.OPTIONS.name,params:e.OPTIONS.params})).filter((e,t,n)=>n.findIndex(t=>t.name===e.name)===t)};return r}}for(let pH of(pU.prototype.__isYupSchema__=!0,["validate","validateSync"]))pU.prototype[`${pH}At`]=function(e,t,n={}){let{parent:r,parentPath:i,schema:a}=pF(this,e,t,n.context);return a[pH](r&&r[i],pB({},n,{parent:r,path:e}))};for(let p$ of["equals","is"])pU.prototype[p$]=pU.prototype.oneOf;for(let pz of["not","nope"])pU.prototype[pz]=pU.prototype.notOneOf;pU.prototype.optional=pU.prototype.notRequired;let pG=pU;function pW(){return new pG}pW.prototype=pG.prototype;let pK=e=>null==e;function pV(){return new pq}class pq extends pU{constructor(){super({type:"boolean"}),this.withMutation(()=>{this.transform(function(e){if(!this.isType(e)){if(/^(true|1)$/i.test(String(e)))return!0;if(/^(false|0)$/i.test(String(e)))return!1}return e})})}_typeCheck(e){return e instanceof Boolean&&(e=e.valueOf()),"boolean"==typeof e}isTrue(e=pb.isValue){return this.test({message:e,name:"is-value",exclusive:!0,params:{value:"true"},test:e=>pK(e)||!0===e})}isFalse(e=pb.isValue){return this.test({message:e,name:"is-value",exclusive:!0,params:{value:"false"},test:e=>pK(e)||!1===e})}}pV.prototype=pq.prototype;let pZ=/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i,pX=/^((https?|ftp):)?\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i,pJ=/^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i,pQ=e=>pK(e)||e===e.trim(),p1=({}).toString();function p0(){return new p2}class p2 extends pU{constructor(){super({type:"string"}),this.withMutation(()=>{this.transform(function(e){if(this.isType(e)||Array.isArray(e))return e;let t=null!=e&&e.toString?e.toString():e;return t===p1?e:t})})}_typeCheck(e){return e instanceof String&&(e=e.valueOf()),"string"==typeof e}_isPresent(e){return super._isPresent(e)&&!!e.length}length(e,t=pd.length){return this.test({message:t,name:"length",exclusive:!0,params:{length:e},test(t){return pK(t)||t.length===this.resolve(e)}})}min(e,t=pd.min){return this.test({message:t,name:"min",exclusive:!0,params:{min:e},test(t){return pK(t)||t.length>=this.resolve(e)}})}max(e,t=pd.max){return this.test({name:"max",exclusive:!0,message:t,params:{max:e},test(t){return pK(t)||t.length<=this.resolve(e)}})}matches(e,t){let n=!1,r,i;return t&&("object"==typeof t?{excludeEmptyString:n=!1,message:r,name:i}=t:r=t),this.test({name:i||"matches",message:r||pd.matches,params:{regex:e},test:t=>pK(t)||""===t&&n||-1!==t.search(e)})}email(e=pd.email){return this.matches(pZ,{name:"email",message:e,excludeEmptyString:!0})}url(e=pd.url){return this.matches(pX,{name:"url",message:e,excludeEmptyString:!0})}uuid(e=pd.uuid){return this.matches(pJ,{name:"uuid",message:e,excludeEmptyString:!1})}ensure(){return this.default("").transform(e=>null===e?"":e)}trim(e=pd.trim){return this.transform(e=>null!=e?e.trim():e).test({message:e,name:"trim",test:pQ})}lowercase(e=pd.lowercase){return this.transform(e=>pK(e)?e:e.toLowerCase()).test({message:e,name:"string_case",exclusive:!0,test:e=>pK(e)||e===e.toLowerCase()})}uppercase(e=pd.uppercase){return this.transform(e=>pK(e)?e:e.toUpperCase()).test({message:e,name:"string_case",exclusive:!0,test:e=>pK(e)||e===e.toUpperCase()})}}p0.prototype=p2.prototype;let p3=e=>e!=+e;function p4(){return new p6}class p6 extends pU{constructor(){super({type:"number"}),this.withMutation(()=>{this.transform(function(e){let t=e;if("string"==typeof t){if(""===(t=t.replace(/\s/g,"")))return NaN;t=+t}return this.isType(t)?t:parseFloat(t)})})}_typeCheck(e){return e instanceof Number&&(e=e.valueOf()),"number"==typeof e&&!p3(e)}min(e,t=ph.min){return this.test({message:t,name:"min",exclusive:!0,params:{min:e},test(t){return pK(t)||t>=this.resolve(e)}})}max(e,t=ph.max){return this.test({message:t,name:"max",exclusive:!0,params:{max:e},test(t){return pK(t)||t<=this.resolve(e)}})}lessThan(e,t=ph.lessThan){return this.test({message:t,name:"max",exclusive:!0,params:{less:e},test(t){return pK(t)||tthis.resolve(e)}})}positive(e=ph.positive){return this.moreThan(0,e)}negative(e=ph.negative){return this.lessThan(0,e)}integer(e=ph.integer){return this.test({name:"integer",message:e,test:e=>pK(e)||Number.isInteger(e)})}truncate(){return this.transform(e=>pK(e)?e:0|e)}round(e){var t,n=["ceil","floor","round","trunc"];if("trunc"===(e=(null==(t=e)?void 0:t.toLowerCase())||"round"))return this.truncate();if(-1===n.indexOf(e.toLowerCase()))throw TypeError("Only valid options for round() are: "+n.join(", "));return this.transform(t=>pK(t)?t:Math[e](t))}}p4.prototype=p6.prototype;var p5=/^(\d{4}|[+\-]\d{6})(?:-?(\d{2})(?:-?(\d{2}))?)?(?:[ T]?(\d{2}):?(\d{2})(?::?(\d{2})(?:[,\.](\d{1,}))?)?(?:(Z)|([+\-])(\d{2})(?::?(\d{2}))?)?)?$/;function p8(e){var t,n,r=[1,4,5,6,7,10,11],i=0;if(n=p5.exec(e)){for(var a,o=0;a=r[o];++o)n[a]=+n[a]||0;n[2]=(+n[2]||1)-1,n[3]=+n[3]||1,n[7]=n[7]?String(n[7]).substr(0,3):0,(void 0===n[8]||""===n[8])&&(void 0===n[9]||""===n[9])?t=+new Date(n[1],n[2],n[3],n[4],n[5],n[6],n[7]):("Z"!==n[8]&&void 0!==n[9]&&(i=60*n[10]+n[11],"+"===n[9]&&(i=0-i)),t=Date.UTC(n[1],n[2],n[3],n[4],n[5]+i,n[6],n[7]))}else t=Date.parse?Date.parse(e):NaN;return t}let p9=new Date(""),p7=e=>"[object Date]"===Object.prototype.toString.call(e);function be(){return new bt}class bt extends pU{constructor(){super({type:"date"}),this.withMutation(()=>{this.transform(function(e){return this.isType(e)?e:(e=p8(e),isNaN(e)?p9:new Date(e))})})}_typeCheck(e){return p7(e)&&!isNaN(e.getTime())}prepareParam(e,t){let n;if(pD.isRef(e))n=e;else{let r=this.cast(e);if(!this._typeCheck(r))throw TypeError(`\`${t}\` must be a Date or a value that can be \`cast()\` to a Date`);n=r}return n}min(e,t=pp.min){let n=this.prepareParam(e,"min");return this.test({message:t,name:"min",exclusive:!0,params:{min:e},test(e){return pK(e)||e>=this.resolve(n)}})}max(e,t=pp.max){var n=this.prepareParam(e,"max");return this.test({message:t,name:"max",exclusive:!0,params:{max:e},test(e){return pK(e)||e<=this.resolve(n)}})}}bt.INVALID_DATE=p9,be.prototype=bt.prototype,be.INVALID_DATE=p9;var bn=n(11865),br=n.n(bn),bi=n(68929),ba=n.n(bi),bo=n(67523),bs=n.n(bo),bu=n(94633),bc=n.n(bu);function bl(e,t=[]){let n=[],r=[];function i(e,i){var a=(0,pC.split)(e)[0];~r.indexOf(a)||r.push(a),~t.indexOf(`${i}-${a}`)||n.push([i,a])}for(let a in e)if(py()(e,a)){let o=e[a];~r.indexOf(a)||r.push(a),pD.isRef(o)&&o.isSibling?i(o.path,a):pw(o)&&"deps"in o&&o.deps.forEach(e=>i(e,a))}return bc().array(r,n).reverse()}function bf(e,t){let n=1/0;return e.some((e,r)=>{var i;if((null==(i=t.path)?void 0:i.indexOf(e))!==-1)return n=r,!0}),n}function bd(e){return(t,n)=>bf(e,t)-bf(e,n)}function bh(){return(bh=Object.assign||function(e){for(var t=1;t"[object Object]"===Object.prototype.toString.call(e);function bb(e,t){let n=Object.keys(e.fields);return Object.keys(t).filter(e=>-1===n.indexOf(e))}let bm=bd([]);class bg extends pU{constructor(e){super({type:"object"}),this.fields=Object.create(null),this._sortErrors=bm,this._nodes=[],this._excludedEdges=[],this.withMutation(()=>{this.transform(function(e){if("string"==typeof e)try{e=JSON.parse(e)}catch(t){e=null}return this.isType(e)?e:null}),e&&this.shape(e)})}_typeCheck(e){return bp(e)||"function"==typeof e}_cast(e,t={}){var n;let r=super._cast(e,t);if(void 0===r)return this.getDefault();if(!this._typeCheck(r))return r;let i=this.fields,a=null!=(n=t.stripUnknown)?n:this.spec.noUnknown,o=this._nodes.concat(Object.keys(r).filter(e=>-1===this._nodes.indexOf(e))),s={},u=bh({},t,{parent:s,__validating:t.__validating||!1}),c=!1;for(let l of o){let f=i[l],d=py()(r,l);if(f){let h,p=r[l];u.path=(t.path?`${t.path}.`:"")+l;let b="spec"in(f=f.resolve({value:p,context:t.context,parent:s}))?f.spec:void 0,m=null==b?void 0:b.strict;if(null==b?void 0:b.strip){c=c||l in r;continue}void 0!==(h=t.__validating&&m?r[l]:f.cast(r[l],u))&&(s[l]=h)}else d&&!a&&(s[l]=r[l]);s[l]!==r[l]&&(c=!0)}return c?s:r}_validate(e,t={},n){let r=[],{sync:i,from:a=[],originalValue:o=e,abortEarly:s=this.spec.abortEarly,recursive:u=this.spec.recursive}=t;a=[{schema:this,value:o},...a],t.__validating=!0,t.originalValue=o,t.from=a,super._validate(e,t,(e,c)=>{if(e){if(!pT.isError(e)||s)return void n(e,c);r.push(e)}if(!u||!bp(c)){n(r[0]||null,c);return}o=o||c;let l=this._nodes.map(e=>(n,r)=>{let i=-1===e.indexOf(".")?(t.path?`${t.path}.`:"")+e:`${t.path||""}["${e}"]`,s=this.fields[e];if(s&&"validate"in s){s.validate(c[e],bh({},t,{path:i,from:a,strict:!0,parent:c,originalValue:o[e]}),r);return}r(null)});pO({sync:i,tests:l,value:c,errors:r,endEarly:s,sort:this._sortErrors,path:t.path},n)})}clone(e){let t=super.clone(e);return t.fields=bh({},this.fields),t._nodes=this._nodes,t._excludedEdges=this._excludedEdges,t._sortErrors=this._sortErrors,t}concat(e){let t=super.concat(e),n=t.fields;for(let[r,i]of Object.entries(this.fields)){let a=n[r];void 0===a?n[r]=i:a instanceof pU&&i instanceof pU&&(n[r]=i.concat(a))}return t.withMutation(()=>t.shape(n))}getDefaultFromShape(){let e={};return this._nodes.forEach(t=>{let n=this.fields[t];e[t]="default"in n?n.getDefault():void 0}),e}_getDefault(){return"default"in this.spec?super._getDefault():this._nodes.length?this.getDefaultFromShape():void 0}shape(e,t=[]){let n=this.clone(),r=Object.assign(n.fields,e);if(n.fields=r,n._sortErrors=bd(Object.keys(r)),t.length){Array.isArray(t[0])||(t=[t]);let i=t.map(([e,t])=>`${e}-${t}`);n._excludedEdges=n._excludedEdges.concat(i)}return n._nodes=bl(r,n._excludedEdges),n}pick(e){let t={};for(let n of e)this.fields[n]&&(t[n]=this.fields[n]);return this.clone().withMutation(e=>(e.fields={},e.shape(t)))}omit(e){let t=this.clone(),n=t.fields;for(let r of(t.fields={},e))delete n[r];return t.withMutation(()=>t.shape(n))}from(e,t,n){let r=(0,pC.getter)(e,!0);return this.transform(i=>{if(null==i)return i;let a=i;return py()(i,e)&&(a=bh({},i),n||delete a[e],a[t]=r(i)),a})}noUnknown(e=!0,t=pm.noUnknown){"string"==typeof e&&(t=e,e=!0);let n=this.test({name:"noUnknown",exclusive:!0,message:t,test(t){if(null==t)return!0;let n=bb(this.schema,t);return!e||0===n.length||this.createError({params:{unknown:n.join(", ")}})}});return n.spec.noUnknown=e,n}unknown(e=!0,t=pm.noUnknown){return this.noUnknown(!e,t)}transformKeys(e){return this.transform(t=>t&&bs()(t,(t,n)=>e(n)))}camelCase(){return this.transformKeys(ba())}snakeCase(){return this.transformKeys(br())}constantCase(){return this.transformKeys(e=>br()(e).toUpperCase())}describe(){let e=super.describe();return e.fields=pL()(this.fields,e=>e.describe()),e}}function bv(e){return new bg(e)}function by(){return(by=Object.assign||function(e){for(var t=1;t{this.transform(function(e){if("string"==typeof e)try{e=JSON.parse(e)}catch(t){e=null}return this.isType(e)?e:null})})}_typeCheck(e){return Array.isArray(e)}get _subType(){return this.innerType}_cast(e,t){let n=super._cast(e,t);if(!this._typeCheck(n)||!this.innerType)return n;let r=!1,i=n.map((e,n)=>{let i=this.innerType.cast(e,by({},t,{path:`${t.path||""}[${n}]`}));return i!==e&&(r=!0),i});return r?i:n}_validate(e,t={},n){var r,i;let a=[],o=t.sync,s=t.path,u=this.innerType,c=null!=(r=t.abortEarly)?r:this.spec.abortEarly,l=null!=(i=t.recursive)?i:this.spec.recursive,f=null!=t.originalValue?t.originalValue:e;super._validate(e,t,(e,r)=>{if(e){if(!pT.isError(e)||c)return void n(e,r);a.push(e)}if(!l||!u||!this._typeCheck(r)){n(a[0]||null,r);return}f=f||r;let i=Array(r.length);for(let d=0;du.validate(h,b,t)}pO({sync:o,path:s,value:r,errors:a,endEarly:c,tests:i},n)})}clone(e){let t=super.clone(e);return t.innerType=this.innerType,t}concat(e){let t=super.concat(e);return t.innerType=this.innerType,e.innerType&&(t.innerType=t.innerType?t.innerType.concat(e.innerType):e.innerType),t}of(e){let t=this.clone();if(!pw(e))throw TypeError("`array.of()` sub-schema must be a valid yup schema not: "+pl(e));return t.innerType=e,t}length(e,t=pg.length){return this.test({message:t,name:"length",exclusive:!0,params:{length:e},test(t){return pK(t)||t.length===this.resolve(e)}})}min(e,t){return t=t||pg.min,this.test({message:t,name:"min",exclusive:!0,params:{min:e},test(t){return pK(t)||t.length>=this.resolve(e)}})}max(e,t){return t=t||pg.max,this.test({message:t,name:"max",exclusive:!0,params:{max:e},test(t){return pK(t)||t.length<=this.resolve(e)}})}ensure(){return this.default(()=>[]).transform((e,t)=>this._typeCheck(e)?e:null==t?[]:[].concat(t))}compact(e){let t=e?(t,n,r)=>!e(t,n,r):e=>!!e;return this.transform(e=>null!=e?e.filter(t):e)}describe(){let e=super.describe();return this.innerType&&(e.innerType=this.innerType.describe()),e}nullable(e=!0){return super.nullable(e)}defined(){return super.defined()}required(e){return super.required(e)}}bw.prototype=b_.prototype;var bE=bv().shape({name:p0().required("Required"),url:p0().required("Required")}),bS=function(e){var t=e.initialValues,n=e.onSubmit,r=e.submitButtonText,i=e.nameDisabled,a=void 0!==i&&i;return l.createElement(hT,{initialValues:t,validationSchema:bE,onSubmit:n},function(e){var t=e.isSubmitting;return l.createElement(l.Fragment,null,l.createElement(hR,{"data-testid":"bridge-form",noValidate:!0},l.createElement(d.Z,{container:!0,spacing:16},l.createElement(d.Z,{item:!0,xs:12,md:7},l.createElement(hP,{component:hX,id:"name",name:"name",label:"Name",disabled:a,required:!0,fullWidth:!0,FormHelperTextProps:{"data-testid":"name-helper-text"}})),l.createElement(d.Z,{item:!0,xs:12,md:7},l.createElement(hP,{component:hX,id:"url",name:"url",label:"Bridge URL",placeholder:"https://",required:!0,fullWidth:!0,FormHelperTextProps:{"data-testid":"url-helper-text"}})),l.createElement(d.Z,{item:!0,xs:12,md:7},l.createElement(d.Z,{container:!0,spacing:16},l.createElement(d.Z,{item:!0,xs:7},l.createElement(hP,{component:hX,id:"minimumContractPayment",name:"minimumContractPayment",label:"Minimum Contract Payment",placeholder:"0",fullWidth:!0,inputProps:{min:0},FormHelperTextProps:{"data-testid":"minimumContractPayment-helper-text"}})),l.createElement(d.Z,{item:!0,xs:7},l.createElement(hP,{component:hX,id:"confirmations",name:"confirmations",label:"Confirmations",placeholder:"0",type:"number",fullWidth:!0,inputProps:{min:0},FormHelperTextProps:{"data-testid":"confirmations-helper-text"}})))),l.createElement(d.Z,{item:!0,xs:12,md:7},l.createElement(ok.default,{variant:"contained",color:"primary",type:"submit",disabled:t,size:"large"},r)))))})},bk=function(e){var t=e.bridge,n=e.onSubmit,r={name:t.name,url:t.url,minimumContractPayment:t.minimumContractPayment,confirmations:t.confirmations};return l.createElement(ig,null,l.createElement(d.Z,{container:!0,spacing:40},l.createElement(d.Z,{item:!0,xs:12,md:11,lg:9},l.createElement(r5.Z,null,l.createElement(sl.Z,{title:"Edit Bridge",action:l.createElement(aA.Z,{component:tz,href:"/bridges/".concat(t.id)},"Cancel")}),l.createElement(aW.Z,null,l.createElement(bS,{nameDisabled:!0,initialValues:r,onSubmit:n,submitButtonText:"Save Bridge"}))))))};function bx(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&void 0!==arguments[0]&&arguments[0],t=e?function(){return l.createElement(x.default,{variant:"body1"},"Loading...")}:function(){return null};return{isLoading:e,LoadingPlaceholder:t}},mc=n(76023);function ml(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}function mB(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n=4?[e[0],e[1],e[2],e[3],"".concat(e[0],".").concat(e[1]),"".concat(e[0],".").concat(e[2]),"".concat(e[0],".").concat(e[3]),"".concat(e[1],".").concat(e[0]),"".concat(e[1],".").concat(e[2]),"".concat(e[1],".").concat(e[3]),"".concat(e[2],".").concat(e[0]),"".concat(e[2],".").concat(e[1]),"".concat(e[2],".").concat(e[3]),"".concat(e[3],".").concat(e[0]),"".concat(e[3],".").concat(e[1]),"".concat(e[3],".").concat(e[2]),"".concat(e[0],".").concat(e[1],".").concat(e[2]),"".concat(e[0],".").concat(e[1],".").concat(e[3]),"".concat(e[0],".").concat(e[2],".").concat(e[1]),"".concat(e[0],".").concat(e[2],".").concat(e[3]),"".concat(e[0],".").concat(e[3],".").concat(e[1]),"".concat(e[0],".").concat(e[3],".").concat(e[2]),"".concat(e[1],".").concat(e[0],".").concat(e[2]),"".concat(e[1],".").concat(e[0],".").concat(e[3]),"".concat(e[1],".").concat(e[2],".").concat(e[0]),"".concat(e[1],".").concat(e[2],".").concat(e[3]),"".concat(e[1],".").concat(e[3],".").concat(e[0]),"".concat(e[1],".").concat(e[3],".").concat(e[2]),"".concat(e[2],".").concat(e[0],".").concat(e[1]),"".concat(e[2],".").concat(e[0],".").concat(e[3]),"".concat(e[2],".").concat(e[1],".").concat(e[0]),"".concat(e[2],".").concat(e[1],".").concat(e[3]),"".concat(e[2],".").concat(e[3],".").concat(e[0]),"".concat(e[2],".").concat(e[3],".").concat(e[1]),"".concat(e[3],".").concat(e[0],".").concat(e[1]),"".concat(e[3],".").concat(e[0],".").concat(e[2]),"".concat(e[3],".").concat(e[1],".").concat(e[0]),"".concat(e[3],".").concat(e[1],".").concat(e[2]),"".concat(e[3],".").concat(e[2],".").concat(e[0]),"".concat(e[3],".").concat(e[2],".").concat(e[1]),"".concat(e[0],".").concat(e[1],".").concat(e[2],".").concat(e[3]),"".concat(e[0],".").concat(e[1],".").concat(e[3],".").concat(e[2]),"".concat(e[0],".").concat(e[2],".").concat(e[1],".").concat(e[3]),"".concat(e[0],".").concat(e[2],".").concat(e[3],".").concat(e[1]),"".concat(e[0],".").concat(e[3],".").concat(e[1],".").concat(e[2]),"".concat(e[0],".").concat(e[3],".").concat(e[2],".").concat(e[1]),"".concat(e[1],".").concat(e[0],".").concat(e[2],".").concat(e[3]),"".concat(e[1],".").concat(e[0],".").concat(e[3],".").concat(e[2]),"".concat(e[1],".").concat(e[2],".").concat(e[0],".").concat(e[3]),"".concat(e[1],".").concat(e[2],".").concat(e[3],".").concat(e[0]),"".concat(e[1],".").concat(e[3],".").concat(e[0],".").concat(e[2]),"".concat(e[1],".").concat(e[3],".").concat(e[2],".").concat(e[0]),"".concat(e[2],".").concat(e[0],".").concat(e[1],".").concat(e[3]),"".concat(e[2],".").concat(e[0],".").concat(e[3],".").concat(e[1]),"".concat(e[2],".").concat(e[1],".").concat(e[0],".").concat(e[3]),"".concat(e[2],".").concat(e[1],".").concat(e[3],".").concat(e[0]),"".concat(e[2],".").concat(e[3],".").concat(e[0],".").concat(e[1]),"".concat(e[2],".").concat(e[3],".").concat(e[1],".").concat(e[0]),"".concat(e[3],".").concat(e[0],".").concat(e[1],".").concat(e[2]),"".concat(e[3],".").concat(e[0],".").concat(e[2],".").concat(e[1]),"".concat(e[3],".").concat(e[1],".").concat(e[0],".").concat(e[2]),"".concat(e[3],".").concat(e[1],".").concat(e[2],".").concat(e[0]),"".concat(e[3],".").concat(e[2],".").concat(e[0],".").concat(e[1]),"".concat(e[3],".").concat(e[2],".").concat(e[1],".").concat(e[0])]:void 0}var mZ={};function mX(e){if(0===e.length||1===e.length)return e;var t=e.join(".");return mZ[t]||(mZ[t]=mq(e)),mZ[t]}function mJ(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2?arguments[2]:void 0;return mX(e.filter(function(e){return"token"!==e})).reduce(function(e,t){return mK({},e,n[t])},t)}function mQ(e){return e.join(" ")}function m1(e,t){var n=0;return function(r){return n+=1,r.map(function(r,i){return m0({node:r,stylesheet:e,useInlineStyles:t,key:"code-segment-".concat(n,"-").concat(i)})})}}function m0(e){var t=e.node,n=e.stylesheet,r=e.style,i=void 0===r?{}:r,a=e.useInlineStyles,o=e.key,s=t.properties,u=t.type,c=t.tagName,f=t.value;if("text"===u)return f;if(c){var d,h=m1(n,a);if(a){var p=Object.keys(n).reduce(function(e,t){return t.split(".").forEach(function(t){e.includes(t)||e.push(t)}),e},[]),b=s.className&&s.className.includes("token")?["token"]:[],m=s.className&&b.concat(s.className.filter(function(e){return!p.includes(e)}));d=mK({},s,{className:mQ(m)||void 0,style:mJ(s.className,Object.assign({},s.style,i),n)})}else d=mK({},s,{className:mQ(s.className)});var g=h(t.children);return l.createElement(c,(0,mV.Z)({key:o},d),g)}}let m2=function(e,t){return -1!==e.listLanguages().indexOf(t)};var m3=/\n/g;function m4(e){return e.match(m3)}function m6(e){var t=e.lines,n=e.startingLineNumber,r=e.style;return t.map(function(e,t){var i=t+n;return l.createElement("span",{key:"line-".concat(t),className:"react-syntax-highlighter-line-number",style:"function"==typeof r?r(i):r},"".concat(i,"\n"))})}function m5(e){var t=e.codeString,n=e.codeStyle,r=e.containerStyle,i=void 0===r?{float:"left",paddingRight:"10px"}:r,a=e.numberStyle,o=void 0===a?{}:a,s=e.startingLineNumber;return l.createElement("code",{style:Object.assign({},n,i)},m6({lines:t.replace(/\n$/,"").split("\n"),style:o,startingLineNumber:s}))}function m8(e){return"".concat(e.toString().length,".25em")}function m9(e,t){return{type:"element",tagName:"span",properties:{key:"line-number--".concat(e),className:["comment","linenumber","react-syntax-highlighter-line-number"],style:t},children:[{type:"text",value:e}]}}function m7(e,t,n){var r,i={display:"inline-block",minWidth:m8(n),paddingRight:"1em",textAlign:"right",userSelect:"none"};return mK({},i,"function"==typeof e?e(t):e)}function ge(e){var t=e.children,n=e.lineNumber,r=e.lineNumberStyle,i=e.largestLineNumber,a=e.showInlineLineNumbers,o=e.lineProps,s=void 0===o?{}:o,u=e.className,c=void 0===u?[]:u,l=e.showLineNumbers,f=e.wrapLongLines,d="function"==typeof s?s(n):s;if(d.className=c,n&&a){var h=m7(r,n,i);t.unshift(m9(n,h))}return f&l&&(d.style=mK({},d.style,{display:"flex"})),{type:"element",tagName:"span",properties:d,children:t}}function gt(e){for(var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],r=0;r2&&void 0!==arguments[2]?arguments[2]:[];return ge({children:e,lineNumber:t,lineNumberStyle:s,largestLineNumber:o,showInlineLineNumbers:i,lineProps:n,className:a,showLineNumbers:r,wrapLongLines:u})}function b(e,t){if(r&&t&&i){var n=m7(s,t,o);e.unshift(m9(t,n))}return e}function m(e,n){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[];return t||r.length>0?p(e,n,r):b(e,n)}for(var g=function(){var e=l[h],t=e.children[0].value;if(m4(t)){var n=t.split("\n");n.forEach(function(t,i){var o=r&&f.length+a,s={type:"text",value:"".concat(t,"\n")};if(0===i){var u=l.slice(d+1,h).concat(ge({children:[s],className:e.properties.className})),c=m(u,o);f.push(c)}else if(i===n.length-1){if(l[h+1]&&l[h+1].children&&l[h+1].children[0]){var p={type:"text",value:"".concat(t)},b=ge({children:[p],className:e.properties.className});l.splice(h+1,0,b)}else{var g=[s],v=m(g,o,e.properties.className);f.push(v)}}else{var y=[s],w=m(y,o,e.properties.className);f.push(w)}}),d=h}h++};h code[class*="language-"]':{background:"#f5f2f0",padding:".1em",borderRadius:".3em",whiteSpace:"normal"},comment:{color:"slategray"},prolog:{color:"slategray"},doctype:{color:"slategray"},cdata:{color:"slategray"},punctuation:{color:"#999"},namespace:{Opacity:".7"},property:{color:"#905"},tag:{color:"#905"},boolean:{color:"#905"},number:{color:"#905"},constant:{color:"#905"},symbol:{color:"#905"},deleted:{color:"#905"},selector:{color:"#690"},"attr-name":{color:"#690"},string:{color:"#690"},char:{color:"#690"},builtin:{color:"#690"},inserted:{color:"#690"},operator:{color:"#9a6e3a",background:"hsla(0, 0%, 100%, .5)"},entity:{color:"#9a6e3a",background:"hsla(0, 0%, 100%, .5)",cursor:"help"},url:{color:"#9a6e3a",background:"hsla(0, 0%, 100%, .5)"},".language-css .token.string":{color:"#9a6e3a",background:"hsla(0, 0%, 100%, .5)"},".style .token.string":{color:"#9a6e3a",background:"hsla(0, 0%, 100%, .5)"},atrule:{color:"#07a"},"attr-value":{color:"#07a"},keyword:{color:"#07a"},function:{color:"#DD4A68"},"class-name":{color:"#DD4A68"},regex:{color:"#e90"},important:{color:"#e90",fontWeight:"bold"},variable:{color:"#e90"},bold:{fontWeight:"bold"},italic:{fontStyle:"italic"}};var gu=n(98695),gc=n.n(gu);let gl=["abap","abnf","actionscript","ada","agda","al","antlr4","apacheconf","apl","applescript","aql","arduino","arff","asciidoc","asm6502","aspnet","autohotkey","autoit","bash","basic","batch","bbcode","birb","bison","bnf","brainfuck","brightscript","bro","bsl","c","cil","clike","clojure","cmake","coffeescript","concurnas","cpp","crystal","csharp","csp","css-extras","css","cypher","d","dart","dax","dhall","diff","django","dns-zone-file","docker","ebnf","editorconfig","eiffel","ejs","elixir","elm","erb","erlang","etlua","excel-formula","factor","firestore-security-rules","flow","fortran","fsharp","ftl","gcode","gdscript","gedcom","gherkin","git","glsl","gml","go","graphql","groovy","haml","handlebars","haskell","haxe","hcl","hlsl","hpkp","hsts","http","ichigojam","icon","iecst","ignore","inform7","ini","io","j","java","javadoc","javadoclike","javascript","javastacktrace","jolie","jq","js-extras","js-templates","jsdoc","json","json5","jsonp","jsstacktrace","jsx","julia","keyman","kotlin","latex","latte","less","lilypond","liquid","lisp","livescript","llvm","lolcode","lua","makefile","markdown","markup-templating","markup","matlab","mel","mizar","mongodb","monkey","moonscript","n1ql","n4js","nand2tetris-hdl","naniscript","nasm","neon","nginx","nim","nix","nsis","objectivec","ocaml","opencl","oz","parigp","parser","pascal","pascaligo","pcaxis","peoplecode","perl","php-extras","php","phpdoc","plsql","powerquery","powershell","processing","prolog","properties","protobuf","pug","puppet","pure","purebasic","purescript","python","q","qml","qore","r","racket","reason","regex","renpy","rest","rip","roboconf","robotframework","ruby","rust","sas","sass","scala","scheme","scss","shell-session","smali","smalltalk","smarty","sml","solidity","solution-file","soy","sparql","splunk-spl","sqf","sql","stan","stylus","swift","t4-cs","t4-templating","t4-vb","tap","tcl","textile","toml","tsx","tt2","turtle","twig","typescript","typoscript","unrealscript","vala","vbnet","velocity","verilog","vhdl","vim","visual-basic","warpscript","wasm","wiki","xeora","xml-doc","xojo","xquery","yaml","yang","zig"];var gf=go(gc(),gs);gf.supportedLanguages=gl;let gd=gf;var gh=n(64566);function gp(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function gb(){var e=gp(["\n query FetchConfigV2 {\n configv2 {\n user\n effective\n }\n }\n"]);return gb=function(){return e},e}var gm=n0(gb()),gg=function(e){var t=e.children;return l.createElement(ir.Z,null,l.createElement(r7.default,{component:"th",scope:"row",colSpan:3},t))},gv=function(){return l.createElement(gg,null,"...")},gy=function(e){var t=e.children;return l.createElement(gg,null,t)},gw=function(e){var t=e.loading,n=e.toml,r=e.error,i=void 0===r?"":r,a=e.title,o=e.expanded;if(i)return l.createElement(gy,null,i);if(t)return l.createElement(gv,null);a||(a="TOML");var s={display:"block"};return l.createElement(x.default,null,l.createElement(mP.Z,{defaultExpanded:o},l.createElement(mR.Z,{expandIcon:l.createElement(gh.Z,null)},a),l.createElement(mj.Z,{style:s},l.createElement(gd,{language:"toml",style:gs},n))))},g_=function(){var e=rv(gm,{fetchPolicy:"cache-and-network"}),t=e.data,n=e.loading,r=e.error;return(null==t?void 0:t.configv2.effective)=="N/A"?l.createElement(l.Fragment,null,l.createElement(d.Z,{item:!0,xs:12},l.createElement(r5.Z,null,l.createElement(sl.Z,{title:"TOML Configuration"}),l.createElement(gw,{title:"V2 config dump:",error:null==r?void 0:r.message,loading:n,toml:null==t?void 0:t.configv2.user,showHead:!0})))):l.createElement(l.Fragment,null,l.createElement(d.Z,{container:!0},l.createElement(d.Z,{item:!0,xs:12},l.createElement(r5.Z,null,l.createElement(sl.Z,{title:"TOML Configuration"}),l.createElement(gw,{title:"User specified:",error:null==r?void 0:r.message,loading:n,toml:null==t?void 0:t.configv2.user,showHead:!0,expanded:!0}),l.createElement(gw,{title:"Effective (with defaults):",error:null==r?void 0:r.message,loading:n,toml:null==t?void 0:t.configv2.effective,showHead:!0})))))},gE=n(34823),gS=function(e){return(0,b.createStyles)({cell:{paddingTop:1.5*e.spacing.unit,paddingBottom:1.5*e.spacing.unit}})},gk=(0,b.withStyles)(gS)(function(e){var t=e.classes,n=(0,A.I0)();(0,l.useEffect)(function(){n((0,ty.DQ)())});var r=(0,A.v9)(gE.N,A.wU);return l.createElement(r5.Z,null,l.createElement(sl.Z,{title:"Node"}),l.createElement(r8.Z,null,l.createElement(r9.Z,null,l.createElement(ir.Z,null,l.createElement(r7.default,{className:t.cell},l.createElement(x.default,null,"Version"),l.createElement(x.default,{variant:"subtitle1",color:"textSecondary"},r.version))),l.createElement(ir.Z,null,l.createElement(r7.default,{className:t.cell},l.createElement(x.default,null,"SHA"),l.createElement(x.default,{variant:"subtitle1",color:"textSecondary"},r.commitSHA))))))}),gx=function(){return l.createElement(ig,null,l.createElement(d.Z,{container:!0},l.createElement(d.Z,{item:!0,sm:12,md:8},l.createElement(d.Z,{container:!0},l.createElement(g_,null))),l.createElement(d.Z,{item:!0,sm:12,md:4},l.createElement(d.Z,{container:!0},l.createElement(d.Z,{item:!0,xs:12},l.createElement(gk,null)),l.createElement(d.Z,{item:!0,xs:12},l.createElement(mN,null)),l.createElement(d.Z,{item:!0,xs:12},l.createElement(mE,null))))))},gT=function(){return l.createElement(gx,null)},gM=function(){return l.createElement(gT,null)},gO=n(44431),gA=1e18,gL=function(e){return new gO.BigNumber(e).dividedBy(gA).toFixed(8)},gC=function(e){var t=e.keys,n=e.chainID,r=e.hideHeaderTitle;return l.createElement(l.Fragment,null,l.createElement(sl.Z,{title:!r&&"Account Balances",subheader:"Chain ID "+n}),l.createElement(aW.Z,null,l.createElement(w.default,{dense:!1,disablePadding:!0},t&&t.map(function(e,r){return l.createElement(l.Fragment,null,l.createElement(_.default,{disableGutters:!0,key:["acc-balance",n.toString(),r.toString()].join("-")},l.createElement(E.Z,{primary:l.createElement(l.Fragment,null,l.createElement(d.Z,{container:!0,spacing:16},l.createElement(d.Z,{item:!0,xs:12},l.createElement(op,{title:"Address"}),l.createElement(ob,{value:e.address})),l.createElement(d.Z,{item:!0,xs:6},l.createElement(op,{title:"Native Token Balance"}),l.createElement(ob,{value:e.ethBalance||"--"})),l.createElement(d.Z,{item:!0,xs:6},l.createElement(op,{title:"LINK Balance"}),l.createElement(ob,{value:e.linkBalance?gL(e.linkBalance):"--"}))))})),r+1s&&l.createElement(gB.Z,null,l.createElement(ir.Z,null,l.createElement(r7.default,{className:r.footer},l.createElement(aA.Z,{href:"/runs",component:tz},"View More"))))))});function vt(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function vn(){var e=vt(["\n ","\n query FetchRecentJobRuns($offset: Int, $limit: Int) {\n jobRuns(offset: $offset, limit: $limit) {\n results {\n ...RecentJobRunsPayload_ResultsFields\n }\n metadata {\n total\n }\n }\n }\n"]);return vn=function(){return e},e}var vr=5,vi=n0(vn(),g9),va=function(){var e=rv(vi,{variables:{offset:0,limit:vr},fetchPolicy:"cache-and-network"}),t=e.data,n=e.loading,r=e.error;return l.createElement(ve,{data:t,errorMsg:null==r?void 0:r.message,loading:n,maxRunsSize:vr})},vo=function(e){return(0,b.createStyles)({style:{textAlign:"center",padding:2.5*e.spacing.unit,position:"fixed",left:"0",bottom:"0",width:"100%",borderRadius:0},bareAnchor:{color:e.palette.common.black,textDecoration:"none"}})},vs=(0,b.withStyles)(vo)(function(e){var t=e.classes,n=(0,A.v9)(gE.N,A.wU),r=(0,A.I0)();return(0,l.useEffect)(function(){r((0,ty.DQ)())}),l.createElement(ii.default,{className:t.style},l.createElement(x.default,null,"Chainlink Node ",n.version," at commit"," ",l.createElement("a",{target:"_blank",rel:"noopener noreferrer",href:"https://github.com/smartcontractkit/chainlink/commit/".concat(n.commitSHA),className:t.bareAnchor},n.commitSHA)))}),vu=function(e){return(0,b.createStyles)({cell:{borderColor:e.palette.divider,borderTop:"1px solid",borderBottom:"none",paddingTop:2*e.spacing.unit,paddingBottom:2*e.spacing.unit,paddingLeft:2*e.spacing.unit},block:{display:"block"},overflowEllipsis:{textOverflow:"ellipsis",overflow:"hidden"}})},vc=(0,b.withStyles)(vu)(function(e){var t=e.classes,n=e.job;return l.createElement(ir.Z,null,l.createElement(r7.default,{scope:"row",className:t.cell},l.createElement(d.Z,{container:!0,spacing:0},l.createElement(d.Z,{item:!0,xs:12},l.createElement(ih,{href:"/jobs/".concat(n.id),classes:{linkContent:t.block}},l.createElement(x.default,{className:t.overflowEllipsis,variant:"body1",component:"span",color:"primary"},n.name||n.id))),l.createElement(d.Z,{item:!0,xs:12},l.createElement(x.default,{variant:"body1",color:"textSecondary"},"Created ",l.createElement(aO,{tooltip:!0},n.createdAt))))))});function vl(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function vf(){var e=vl(["\n fragment RecentJobsPayload_ResultsFields on Job {\n id\n name\n createdAt\n }\n"]);return vf=function(){return e},e}var vd=n0(vf()),vh=function(){return(0,b.createStyles)({cardHeader:{borderBottom:0},table:{tableLayout:"fixed"}})},vp=(0,b.withStyles)(vh)(function(e){var t,n,r=e.classes,i=e.data,a=e.errorMsg,o=e.loading;return l.createElement(r5.Z,null,l.createElement(sl.Z,{title:"Recent Jobs",className:r.cardHeader}),l.createElement(r8.Z,{className:r.table},l.createElement(r9.Z,null,l.createElement(g$,{visible:o}),l.createElement(gz,{visible:(null===(t=null==i?void 0:i.jobs.results)||void 0===t?void 0:t.length)===0},"No recently created jobs"),l.createElement(gU,{msg:a}),null===(n=null==i?void 0:i.jobs.results)||void 0===n?void 0:n.map(function(e,t){return l.createElement(vc,{job:e,key:t})}))))});function vb(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function vm(){var e=vb(["\n ","\n query FetchRecentJobs($offset: Int, $limit: Int) {\n jobs(offset: $offset, limit: $limit) {\n results {\n ...RecentJobsPayload_ResultsFields\n }\n }\n }\n"]);return vm=function(){return e},e}var vg=5,vv=n0(vm(),vd),vy=function(){var e=rv(vv,{variables:{offset:0,limit:vg},fetchPolicy:"cache-and-network"}),t=e.data,n=e.loading,r=e.error;return l.createElement(vp,{data:t,errorMsg:null==r?void 0:r.message,loading:n})},vw=function(){return l.createElement(ig,null,l.createElement(d.Z,{container:!0},l.createElement(d.Z,{item:!0,xs:8},l.createElement(va,null)),l.createElement(d.Z,{item:!0,xs:4},l.createElement(d.Z,{container:!0},l.createElement(d.Z,{item:!0,xs:12},l.createElement(gY,null)),l.createElement(d.Z,{item:!0,xs:12},l.createElement(vy,null))))),l.createElement(vs,null))},v_=function(){return l.createElement(vw,null)},vE=function(){return l.createElement(v_,null)},vS=n(87239),vk=function(e){switch(e){case"DirectRequestSpec":return"Direct Request";case"FluxMonitorSpec":return"Flux Monitor";default:return e.replace(/Spec$/,"")}},vx=n(5022),vT=n(78718),vM=n.n(vT);function vO(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n1?t-1:0),r=1;r1?t-1:0),r=1;re.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&n.map(function(e){return l.createElement(ir.Z,{key:e.id,style:{cursor:"pointer"},onClick:function(){return r.push("/runs/".concat(e.id))}},l.createElement(r7.default,{className:t.idCell,scope:"row"},l.createElement("div",{className:t.runDetails},l.createElement(x.default,{variant:"h5",color:"primary",component:"span"},e.id))),l.createElement(r7.default,{className:t.stampCell},l.createElement(x.default,{variant:"body1",color:"textSecondary",className:t.stamp},"Created ",l.createElement(aO,{tooltip:!0},e.createdAt))),l.createElement(r7.default,{className:t.statusCell,scope:"row"},l.createElement(x.default,{variant:"body1",className:O()(t.status,yh(t,e.status))},e.status.toLowerCase())))})))}),yb=n(16839),ym=n.n(yb);function yg(e){var t=e.replace(/\w+\s*=\s*<([^>]|[\r\n])*>/g,""),n=ym().read(t),r=n.edges();return n.nodes().map(function(e){var t={id:e,parentIds:r.filter(function(t){return t.w===e}).map(function(e){return e.v})};return Object.keys(n.node(e)).length>0&&(t.attributes=n.node(e)),t})}var yv=n(94164),yy=function(e){var t=e.data,n=[];return(null==t?void 0:t.attributes)&&Object.keys(t.attributes).forEach(function(e){var r;n.push(l.createElement("div",{key:e},l.createElement(x.default,{variant:"body1",color:"textSecondary",component:"div"},l.createElement("b",null,e,":")," ",null===(r=t.attributes)||void 0===r?void 0:r[e])))}),l.createElement("div",null,t&&l.createElement(x.default,{variant:"body1",color:"textPrimary"},l.createElement("b",null,t.id)),n)},yw=n(73343),y_=n(3379),yE=n.n(y_);function yS(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);nwindow.innerWidth?u-r.getBoundingClientRect().width-a:u+a,n=c+r.getBoundingClientRect().height+i>window.innerHeight?c-r.getBoundingClientRect().height-a:c+a,r.style.opacity=String(1),r.style.top="".concat(n,"px"),r.style.left="".concat(t,"px"),r.style.zIndex=String(1)}},h=function(e){var t=document.getElementById("tooltip-d3-chart-".concat(e));t&&(t.style.opacity=String(0),t.style.zIndex=String(-1))};return l.createElement("div",{style:{fontFamily:"sans-serif",fontWeight:"normal"}},l.createElement(yv.kJ,{id:"task-list-graph-d3",data:i,config:s,onMouseOverNode:d,onMouseOutNode:h},"D3 chart"),n.map(function(e){return l.createElement("div",{key:"d3-tooltip-key-".concat(e.id),id:"tooltip-d3-chart-".concat(e.id),style:{position:"absolute",opacity:"0",border:"1px solid rgba(0, 0, 0, 0.1)",padding:yw.r.spacing.unit,background:"white",borderRadius:5,zIndex:-1,inlineSize:"min-content"}},l.createElement(yy,{data:e}))}))};function yL(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);nyY&&l.createElement("div",{className:t.runDetails},l.createElement(aA.Z,{href:"/jobs/".concat(n.id,"/runs"),component:tz},"View more")))),l.createElement(d.Z,{item:!0,xs:12,sm:6},l.createElement(yF,{observationSource:n.observationSource})))});function yH(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&void 0!==arguments[0]?arguments[0]:"";try{return vx.parse(e),!0}catch(t){return!1}})}),wW=function(e){var t=e.initialValues,n=e.onSubmit,r=e.onTOMLChange;return l.createElement(hT,{initialValues:t,validationSchema:wG,onSubmit:n},function(e){var t=e.isSubmitting,n=e.values;return r&&r(n.toml),l.createElement(hR,{"data-testid":"job-form",noValidate:!0},l.createElement(d.Z,{container:!0,spacing:16},l.createElement(d.Z,{item:!0,xs:12},l.createElement(hP,{component:hX,id:"toml",name:"toml",label:"Job Spec (TOML)",required:!0,fullWidth:!0,multiline:!0,rows:10,rowsMax:25,variant:"outlined",autoComplete:"off",FormHelperTextProps:{"data-testid":"toml-helper-text"}})),l.createElement(d.Z,{item:!0,xs:12,md:7},l.createElement(ok.default,{variant:"contained",color:"primary",type:"submit",disabled:t,size:"large"},"Create Job"))))})},wK=n(50109),wV="persistSpec";function wq(e){var t=e.query,n=new URLSearchParams(t).get("definition");return n?(wK.t8(wV,n),{toml:n}):{toml:wK.U2(wV)||""}}var wZ=function(e){var t=e.onSubmit,n=e.onTOMLChange,r=wq({query:(0,h.TH)().search}),i=function(e){var t=e.replace(/[\u200B-\u200D\uFEFF]/g,"");wK.t8("".concat(wV),t),n&&n(t)};return l.createElement(r5.Z,null,l.createElement(sl.Z,{title:"New Job"}),l.createElement(aW.Z,null,l.createElement(wW,{initialValues:r,onSubmit:t,onTOMLChange:i})))};function wX(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);n1&&void 0!==arguments[1]?arguments[1]:{},n=t.start,r=void 0===n?6:n,i=t.end,a=void 0===i?4:i;return e.substring(0,r)+"..."+e.substring(e.length-a)}function _M(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]0&&void 0!==arguments[0]?arguments[0]:{};return rv(_W,e)},_V=function(){var e=_K({fetchPolicy:"cache-and-network"}),t=e.data,n=e.loading,r=e.error,i=e.refetch;return l.createElement(_U,{loading:n,data:t,errorMsg:null==r?void 0:r.message,refetch:i})},_q=function(e){var t=e.csaKey;return l.createElement(ir.Z,{hover:!0},l.createElement(r7.default,null,l.createElement(x.default,{variant:"body1"},t.publicKey," ",l.createElement(_x,{data:t.publicKey}))))};function _Z(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function _X(){var e=_Z(["\n fragment CSAKeysPayload_ResultsFields on CSAKey {\n id\n publicKey\n }\n"]);return _X=function(){return e},e}var _J=n0(_X()),_Q=function(e){var t,n,r,i=e.data,a=e.errorMsg,o=e.loading,s=e.onCreate;return l.createElement(r5.Z,null,l.createElement(sl.Z,{action:(null===(t=null==i?void 0:i.csaKeys.results)||void 0===t?void 0:t.length)===0&&l.createElement(ok.default,{variant:"outlined",color:"primary",onClick:s},"New CSA Key"),title:"CSA Key",subheader:"Manage your CSA Key"}),l.createElement(r8.Z,null,l.createElement(ie.Z,null,l.createElement(ir.Z,null,l.createElement(r7.default,null,"Public Key"))),l.createElement(r9.Z,null,l.createElement(g$,{visible:o}),l.createElement(gz,{visible:(null===(n=null==i?void 0:i.csaKeys.results)||void 0===n?void 0:n.length)===0}),l.createElement(gU,{msg:a}),null===(r=null==i?void 0:i.csaKeys.results)||void 0===r?void 0:r.map(function(e,t){return l.createElement(_q,{csaKey:e,key:t})}))))};function _1(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]0&&void 0!==arguments[0]?arguments[0]:{};return rv(EM,e)};function EA(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]0&&void 0!==arguments[0]?arguments[0]:{};return rv(EJ,e)},E3=function(){return oo(EQ)},E4=function(){return oo(E1)},E6=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return rv(E0,e)};function E5(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]0&&void 0!==arguments[0]?arguments[0]:{};return rv(SK,e)};function Sq(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);n=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}function kV(e,t){if(null==e)return{};var n,r,i={},a=Object.keys(e);for(r=0;r=0||(i[n]=e[n]);return i}var kq=function(e){var t=e.run,n=l.useMemo(function(){var e=t.inputs,n=t.outputs,r=t.taskRuns,i=kK(t,["inputs","outputs","taskRuns"]),a={};try{a=JSON.parse(e)}catch(o){a={}}return kW(kz({},i),{inputs:a,outputs:n,taskRuns:r})},[t]);return l.createElement(r5.Z,null,l.createElement(aW.Z,null,l.createElement(kH,{object:n})))};function kZ(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function kX(e){for(var t=1;t0&&l.createElement(kr,{errors:t.allErrors})),l.createElement(d.Z,{item:!0,xs:12},l.createElement(h.rs,null,l.createElement(h.AW,{path:"".concat(n,"/json")},l.createElement(kq,{run:t})),l.createElement(h.AW,{path:n},t.taskRuns.length>0&&l.createElement(kN,{taskRuns:t.taskRuns,observationSource:t.job.observationSource}))))))))};function k5(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function k8(){var e=k5(["\n ","\n query FetchJobRun($id: ID!) {\n jobRun(id: $id) {\n __typename\n ... on JobRun {\n ...JobRunPayload_Fields\n }\n ... on NotFoundError {\n message\n }\n }\n }\n"]);return k8=function(){return e},e}var k9=n0(k8(),k4),k7=function(){var e=rv(k9,{variables:{id:(0,h.UO)().id}}),t=e.data,n=e.loading,r=e.error;if(n)return l.createElement(iR,null);if(r)return l.createElement(iD,{error:r});var i=null==t?void 0:t.jobRun;switch(null==i?void 0:i.__typename){case"JobRun":return l.createElement(k6,{run:i});case"NotFoundError":return l.createElement(oa,null);default:return null}};function xe(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function xt(){var e=xe(["\n fragment JobRunsPayload_ResultsFields on JobRun {\n id\n allErrors\n createdAt\n finishedAt\n status\n job {\n id\n }\n }\n"]);return xt=function(){return e},e}var xn=n0(xt()),xr=function(e){var t=e.loading,n=e.data,r=e.page,i=e.pageSize,a=(0,h.k6)(),o=l.useMemo(function(){return null==n?void 0:n.jobRuns.results.map(function(e){var t,n=e.allErrors,r=e.id,i=e.createdAt;return{id:r,createdAt:i,errors:n,finishedAt:e.finishedAt,status:e.status}})},[n]);return l.createElement(ig,null,l.createElement(d.Z,{container:!0,spacing:32},l.createElement(d.Z,{item:!0,xs:12},l.createElement(iy,null,"Job Runs")),t&&l.createElement(iR,null),n&&o&&l.createElement(d.Z,{item:!0,xs:12},l.createElement(r5.Z,null,l.createElement(yp,{runs:o}),l.createElement(it.Z,{component:"div",count:n.jobRuns.metadata.total,rowsPerPage:i,rowsPerPageOptions:[i],page:r-1,onChangePage:function(e,t){a.push("/runs?page=".concat(t+1,"&per=").concat(i))},onChangeRowsPerPage:function(){},backIconButtonProps:{"aria-label":"prev-page"},nextIconButtonProps:{"aria-label":"next-page"}})))))};function xi(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function xa(){var e=xi(["\n ","\n query FetchJobRuns($offset: Int, $limit: Int) {\n jobRuns(offset: $offset, limit: $limit) {\n results {\n ...JobRunsPayload_ResultsFields\n }\n metadata {\n total\n }\n }\n }\n"]);return xa=function(){return e},e}var xo=n0(xa(),xn),xs=function(){var e=ij(),t=parseInt(e.get("page")||"1",10),n=parseInt(e.get("per")||"25",10),r=rv(xo,{variables:{offset:(t-1)*n,limit:n},fetchPolicy:"cache-and-network"}),i=r.data,a=r.loading,o=r.error;return o?l.createElement(iD,{error:o}):l.createElement(xr,{loading:a,data:i,page:t,pageSize:n})},xu=function(){var e=(0,h.$B)().path;return l.createElement(h.rs,null,l.createElement(h.AW,{exact:!0,path:e},l.createElement(xs,null)),l.createElement(h.AW,{path:"".concat(e,"/:id")},l.createElement(k7,null)))},xc=bv().shape({name:p0().required("Required"),uri:p0().required("Required"),publicKey:p0().required("Required")}),xl=function(e){var t=e.initialValues,n=e.onSubmit;return l.createElement(hT,{initialValues:t,validationSchema:xc,onSubmit:n},function(e){var t=e.isSubmitting,n=e.submitForm;return l.createElement(hR,{"data-testid":"feeds-manager-form"},l.createElement(d.Z,{container:!0,spacing:16},l.createElement(d.Z,{item:!0,xs:12,md:6},l.createElement(hP,{component:hX,id:"name",name:"name",label:"Name",required:!0,fullWidth:!0,FormHelperTextProps:{"data-testid":"name-helper-text"}})),l.createElement(d.Z,{item:!0,xs:!1,md:6}),l.createElement(d.Z,{item:!0,xs:12,md:6},l.createElement(hP,{component:hX,id:"uri",name:"uri",label:"URI",required:!0,fullWidth:!0,helperText:"Provided by the Feeds Manager operator",FormHelperTextProps:{"data-testid":"uri-helper-text"}})),l.createElement(d.Z,{item:!0,xs:12,md:6},l.createElement(hP,{component:hX,id:"publicKey",name:"publicKey",label:"Public Key",required:!0,fullWidth:!0,helperText:"Provided by the Feeds Manager operator",FormHelperTextProps:{"data-testid":"publicKey-helper-text"}})),l.createElement(d.Z,{item:!0,xs:12},l.createElement(ok.default,{variant:"contained",color:"primary",disabled:t,onClick:n},"Submit"))))})},xf=function(e){var t=e.data,n=e.onSubmit,r={name:t.name,uri:t.uri,publicKey:t.publicKey};return l.createElement(d.Z,{container:!0},l.createElement(d.Z,{item:!0,xs:12,md:11,lg:9},l.createElement(r5.Z,null,l.createElement(sl.Z,{title:"Edit Feeds Manager"}),l.createElement(aW.Z,null,l.createElement(xl,{initialValues:r,onSubmit:n})))))};function xd(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function xh(){var e=xd(["\n query FetchFeedsManagers {\n feedsManagers {\n results {\n __typename\n id\n name\n uri\n publicKey\n isConnectionActive\n createdAt\n }\n }\n }\n"]);return xh=function(){return e},e}var xp=n0(xh()),xb=function(){return rv(xp)};function xm(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);n=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}function xF(e,t){if(null==e)return{};var n,r,i={},a=Object.keys(e);for(r=0;r=0||(i[n]=e[n]);return i}function xY(e,t){return xD(e)||xP(e,t)||xB(e,t)||xR()}function xB(e,t){if(e){if("string"==typeof e)return xI(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);if("Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n)return Array.from(n);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return xI(e,t)}}var xU=function(e){return"SN_MAIN"===e||"SN_SEPOLIA"===e},xH=bv().shape({chainID:p0().required("Required"),chainType:p0().required("Required"),accountAddr:p0().required("Required"),accountAddrPubKey:p0().nullable(),adminAddr:p0().required("Required"),ocr1Multiaddr:p0().when(["ocr1Enabled","ocr1IsBootstrap"],{is:function(e,t){return e&&t},then:p0().required("Required").nullable()}).nullable(),ocr1P2PPeerID:p0().when(["ocr1Enabled","ocr1IsBootstrap"],{is:function(e,t){return e&&!t},then:p0().required("Required").nullable()}).nullable(),ocr1KeyBundleID:p0().when(["ocr1Enabled","ocr1IsBootstrap"],{is:function(e,t){return e&&!t},then:p0().required("Required").nullable()}).nullable(),ocr2Multiaddr:p0().when(["ocr2Enabled","ocr2IsBootstrap"],{is:function(e,t){return e&&t},then:p0().required("Required").nullable()}).nullable(),ocr2P2PPeerID:p0().when(["ocr2Enabled","ocr2IsBootstrap"],{is:function(e,t){return e&&!t},then:p0().required("Required").nullable()}).nullable(),ocr2KeyBundleID:p0().when(["ocr2Enabled","ocr2IsBootstrap"],{is:function(e,t){return e&&!t},then:p0().required("Required").nullable()}).nullable(),ocr2CommitPluginEnabled:pV().required("Required"),ocr2ExecutePluginEnabled:pV().required("Required"),ocr2MedianPluginEnabled:pV().required("Required"),ocr2MercuryPluginEnabled:pV().required("Required"),ocr2ForwarderAddress:p0().nullable()}),x$=function(e){return(0,b.createStyles)({supportedJobOptionsPaper:{padding:2*e.spacing.unit}})},xz=function(e){var t=e.chainAccounts,n=xj(e,["chainAccounts"]),r=h_(),i=r.values,a=i.chainID,o=i.accountAddr,s=r.setFieldValue,u=xY(l.useState(!1),2),c=u[0],f=u[1],d=l.useRef();l.useEffect(function(){d.current=a},[a]),l.useEffect(function(){a!==d.current&&(s(n.name,""),f(!1))},[a,s,n.name]);var h=function(e){var t=e.target.value;"custom"===t?(f(!0),s(n.name,"")):(f(!1),s(n.name,t))};return l.createElement(l.Fragment,null,!xU(a)&&l.createElement(hP,xN({},n,{select:!0,value:c?"custom":o,onChange:h}),t.map(function(e){return l.createElement(tE.default,{key:e.address,value:e.address},e.address)})),xU(a)&&l.createElement(hP,{component:hX,id:"accountAddr",name:"accountAddr",label:"Enter your account address",inputProps:{"data-testid":"customAccountAddr-input"},helperText:"The account address used for this chain",required:!0,fullWidth:!0}),xU(a)&&l.createElement("div",null,l.createElement(hP,{component:hX,id:"accountAddrPubKey",name:"accountAddrPubKey",label:"Account Address Public Key",required:!0,fullWidth:!0,helperText:"The public key for your account address",FormHelperTextProps:{"data-testid":"accountAddrPubKey-helper-text"}})))},xG=(0,b.withStyles)(x$)(function(e){var t=e.classes,n=e.editing,r=void 0!==n&&n,i=e.innerRef,a=e.initialValues,o=e.onSubmit,s=e.chainIDs,u=void 0===s?[]:s,c=e.accounts,f=void 0===c?[]:c,h=e.p2pKeys,p=void 0===h?[]:h,b=e.ocrKeys,m=void 0===b?[]:b,g=e.ocr2Keys,v=void 0===g?[]:g,y=e.showSubmit,w=void 0!==y&&y;return l.createElement(hT,{innerRef:i,initialValues:a,validationSchema:xH,onSubmit:o},function(e){var n=e.values,i=f.filter(function(e){return e.chain.id==n.chainID&&!e.isDisabled});return l.createElement(hR,{"data-testid":"feeds-manager-form",id:"chain-configuration-form",noValidate:!0},l.createElement(d.Z,{container:!0,spacing:16},l.createElement(d.Z,{item:!0,xs:12,md:6},l.createElement(hP,{component:hX,id:"chainType",name:"chainType",label:"Chain Type",select:!0,required:!0,fullWidth:!0,disabled:!0},l.createElement(tE.default,{key:"EVM",value:"EVM"},"EVM"))),l.createElement(d.Z,{item:!0,xs:12,md:6},l.createElement(hP,{component:hX,id:"chainID",name:"chainID",label:"Chain ID",required:!0,fullWidth:!0,select:!0,disabled:r,inputProps:{"data-testid":"chainID-input"},FormHelperTextProps:{"data-testid":"chainID-helper-text"}},u.map(function(e){return l.createElement(tE.default,{key:e,value:e},e)}))),l.createElement(d.Z,{item:!0,xs:12,md:6},l.createElement(xz,{component:hX,id:"accountAddr",name:"accountAddr",label:"Account Address",inputProps:{"data-testid":"accountAddr-input"},required:!0,fullWidth:!0,select:!0,helperText:"The account address used for this chain",chainAccounts:i,FormHelperTextProps:{"data-testid":"accountAddr-helper-text"}})),l.createElement(d.Z,{item:!0,xs:12,md:6},l.createElement(hP,{component:hX,id:"adminAddr",name:"adminAddr",label:"Admin Address",required:!0,fullWidth:!0,helperText:"The address used for LINK payments",FormHelperTextProps:{"data-testid":"adminAddr-helper-text"}})),l.createElement(d.Z,{item:!0,xs:12},l.createElement(x.default,null,"Supported Job Types")),l.createElement(d.Z,{item:!0,xs:12},l.createElement(hP,{component:h2,name:"fluxMonitorEnabled",type:"checkbox",Label:{label:"Flux Monitor"}})),l.createElement(d.Z,{item:!0,xs:12},l.createElement(hP,{component:h2,name:"ocr1Enabled",type:"checkbox",Label:{label:"OCR"}}),n.ocr1Enabled&&l.createElement(ii.default,{className:t.supportedJobOptionsPaper},l.createElement(d.Z,{container:!0,spacing:8},l.createElement(l.Fragment,null,l.createElement(d.Z,{item:!0,xs:12},l.createElement(hP,{component:h2,name:"ocr1IsBootstrap",type:"checkbox",Label:{label:"Is this node running as a bootstrap peer?"}})),n.ocr1IsBootstrap?l.createElement(d.Z,{item:!0,xs:12},l.createElement(hP,{component:hX,id:"ocr1Multiaddr",name:"ocr1Multiaddr",label:"Multiaddr",required:!0,fullWidth:!0,helperText:"The OCR Multiaddr which oracles use to query for network information",FormHelperTextProps:{"data-testid":"ocr1Multiaddr-helper-text"}})):l.createElement(l.Fragment,null,l.createElement(d.Z,{item:!0,xs:12,md:6},l.createElement(hP,{component:hX,id:"ocr1P2PPeerID",name:"ocr1P2PPeerID",label:"Peer ID",select:!0,required:!0,fullWidth:!0,helperText:"The Peer ID used for this chain",FormHelperTextProps:{"data-testid":"ocr1P2PPeerID-helper-text"}},p.map(function(e){return l.createElement(tE.default,{key:e.peerID,value:e.peerID},e.peerID)}))),l.createElement(d.Z,{item:!0,xs:12,md:6},l.createElement(hP,{component:hX,id:"ocr1KeyBundleID",name:"ocr1KeyBundleID",label:"Key Bundle ID",select:!0,required:!0,fullWidth:!0,helperText:"The OCR Key Bundle ID used for this chain",FormHelperTextProps:{"data-testid":"ocr1KeyBundleID-helper-text"}},m.map(function(e){return l.createElement(tE.default,{key:e.id,value:e.id},e.id)})))))))),l.createElement(d.Z,{item:!0,xs:12},l.createElement(hP,{component:h2,name:"ocr2Enabled",type:"checkbox",Label:{label:"OCR2"}}),n.ocr2Enabled&&l.createElement(ii.default,{className:t.supportedJobOptionsPaper},l.createElement(d.Z,{container:!0,spacing:8},l.createElement(l.Fragment,null,l.createElement(d.Z,{item:!0,xs:12},l.createElement(hP,{component:h2,name:"ocr2IsBootstrap",type:"checkbox",Label:{label:"Is this node running as a bootstrap peer?"}})),n.ocr2IsBootstrap?l.createElement(d.Z,{item:!0,xs:12},l.createElement(hP,{component:hX,id:"ocr2Multiaddr",name:"ocr2Multiaddr",label:"Multiaddr",required:!0,fullWidth:!0,helperText:"The OCR2 Multiaddr which oracles use to query for network information",FormHelperTextProps:{"data-testid":"ocr2Multiaddr-helper-text"}})):l.createElement(l.Fragment,null,l.createElement(d.Z,{item:!0,xs:12,md:6},l.createElement(hP,{component:hX,id:"ocr2P2PPeerID",name:"ocr2P2PPeerID",label:"Peer ID",select:!0,required:!0,fullWidth:!0,helperText:"The Peer ID used for this chain",FormHelperTextProps:{"data-testid":"ocr2P2PPeerID-helper-text"}},p.map(function(e){return l.createElement(tE.default,{key:e.peerID,value:e.peerID},e.peerID)}))),l.createElement(d.Z,{item:!0,xs:12,md:6},l.createElement(hP,{component:hX,id:"ocr2KeyBundleID",name:"ocr2KeyBundleID",label:"Key Bundle ID",select:!0,required:!0,fullWidth:!0,helperText:"The OCR2 Key Bundle ID used for this chain",FormHelperTextProps:{"data-testid":"ocr2KeyBundleID-helper-text"}},v.map(function(e){return l.createElement(tE.default,{key:e.id,value:e.id},e.id)}))),l.createElement(d.Z,{item:!0,xs:12},l.createElement(x.default,null,"Supported Plugins")),l.createElement(d.Z,{item:!0,xs:6},l.createElement(hP,{component:h2,name:"ocr2CommitPluginEnabled",type:"checkbox",Label:{label:"Commit"}})),l.createElement(d.Z,{item:!0,xs:6},l.createElement(hP,{component:h2,name:"ocr2ExecutePluginEnabled",type:"checkbox",Label:{label:"Execute"}})),l.createElement(d.Z,{item:!0,xs:6},l.createElement(hP,{component:h2,name:"ocr2RebalancerPluginEnabled",type:"checkbox",Label:{label:"Rebalancer"}})),l.createElement(d.Z,{item:!0,xs:6},l.createElement(hP,{component:h2,name:"ocr2MedianPluginEnabled",type:"checkbox",Label:{label:"Median"}})),l.createElement(d.Z,{item:!0,xs:6},l.createElement(hP,{component:h2,name:"ocr2MercuryPluginEnabled",type:"checkbox",Label:{label:"Mercury"}})),l.createElement(d.Z,{item:!0,xs:12,md:12},l.createElement(hP,{component:hX,id:"ocr2ForwarderAddress",name:"ocr2ForwarderAddress",label:"Forwarder Address (optional)",fullWidth:!0,helperText:"The forwarder address from the Operator Forwarder Contract",FormHelperTextProps:{"data-testid":"ocr2ForwarderAddress-helper-text"}}))))))),w&&l.createElement(d.Z,{item:!0,xs:12,md:7},l.createElement(ok.default,{variant:"contained",color:"primary",type:"submit",size:"large"},"Submit"))))})}),xW=function(e){var t=e.onClose,n=e.open,r=e.onSubmit,i=l.useRef(),a=i$({fetchPolicy:"network-only"}).data,o=_K({fetchPolicy:"cache-and-network"}).data,s=SV({fetchPolicy:"cache-and-network"}).data,u=EO({fetchPolicy:"cache-and-network"}).data,c=E2({fetchPolicy:"cache-and-network"}).data,f={chainID:"",chainType:"EVM",accountAddr:"",adminAddr:"",accountAddrPubKey:"",fluxMonitorEnabled:!1,ocr1Enabled:!1,ocr1IsBootstrap:!1,ocr1Multiaddr:"",ocr1P2PPeerID:"",ocr1KeyBundleID:"",ocr2Enabled:!1,ocr2IsBootstrap:!1,ocr2Multiaddr:"",ocr2P2PPeerID:"",ocr2KeyBundleID:"",ocr2CommitPluginEnabled:!1,ocr2ExecutePluginEnabled:!1,ocr2MedianPluginEnabled:!1,ocr2MercuryPluginEnabled:!1,ocr2RebalancerPluginEnabled:!1,ocr2ForwarderAddress:""},d=a?a.chains.results.map(function(e){return e.id}):[],h=o?o.ethKeys.results:[],p=s?s.p2pKeys.results:[],b=u?u.ocrKeyBundles.results:[],m=c?c.ocr2KeyBundles.results:[];return l.createElement(aD.Z,{onClose:t,open:n,disableBackdropClick:!0},l.createElement(oO.Z,{disableTypography:!0},l.createElement(x.default,{variant:"body2"},"New Supported Chain")),l.createElement(oT.Z,null,l.createElement(xG,{innerRef:i,initialValues:f,onSubmit:r,chainIDs:d,accounts:h,p2pKeys:p,ocrKeys:b,ocr2Keys:m})),l.createElement(ox.Z,null,l.createElement(ok.default,{onClick:t},"Cancel"),l.createElement(ok.default,{color:"primary",type:"submit",form:"chain-configuration-form",variant:"contained"},"Submit")))},xK=function(e){var t=e.cfg,n=e.onClose,r=e.open,i=e.onSubmit,a=l.useRef(),o=i$({fetchPolicy:"network-only"}).data,s=_K({fetchPolicy:"cache-and-network"}).data,u=SV({fetchPolicy:"cache-and-network"}).data,c=EO({fetchPolicy:"cache-and-network"}).data,f=E2({fetchPolicy:"cache-and-network"}).data;if(!t)return null;var d={chainID:t.chainID,chainType:"EVM",accountAddr:t.accountAddr,adminAddr:t.adminAddr,accountAddrPubKey:t.accountAddrPubKey,fluxMonitorEnabled:t.fluxMonitorJobConfig.enabled,ocr1Enabled:t.ocr1JobConfig.enabled,ocr1IsBootstrap:t.ocr1JobConfig.isBootstrap,ocr1Multiaddr:t.ocr1JobConfig.multiaddr,ocr1P2PPeerID:t.ocr1JobConfig.p2pPeerID,ocr1KeyBundleID:t.ocr1JobConfig.keyBundleID,ocr2Enabled:t.ocr2JobConfig.enabled,ocr2IsBootstrap:t.ocr2JobConfig.isBootstrap,ocr2Multiaddr:t.ocr2JobConfig.multiaddr,ocr2P2PPeerID:t.ocr2JobConfig.p2pPeerID,ocr2KeyBundleID:t.ocr2JobConfig.keyBundleID,ocr2CommitPluginEnabled:t.ocr2JobConfig.plugins.commit,ocr2ExecutePluginEnabled:t.ocr2JobConfig.plugins.execute,ocr2MedianPluginEnabled:t.ocr2JobConfig.plugins.median,ocr2MercuryPluginEnabled:t.ocr2JobConfig.plugins.mercury,ocr2RebalancerPluginEnabled:t.ocr2JobConfig.plugins.rebalancer,ocr2ForwarderAddress:t.ocr2JobConfig.forwarderAddress},h=o?o.chains.results.map(function(e){return e.id}):[],p=s?s.ethKeys.results:[],b=u?u.p2pKeys.results:[],m=c?c.ocrKeyBundles.results:[],g=f?f.ocr2KeyBundles.results:[];return l.createElement(aD.Z,{onClose:n,open:r,disableBackdropClick:!0},l.createElement(oO.Z,{disableTypography:!0},l.createElement(x.default,{variant:"body2"},"Edit Supported Chain")),l.createElement(oT.Z,null,l.createElement(xG,{innerRef:a,initialValues:d,onSubmit:i,chainIDs:h,accounts:p,p2pKeys:b,ocrKeys:m,ocr2Keys:g,editing:!0})),l.createElement(ox.Z,null,l.createElement(ok.default,{onClick:n},"Cancel"),l.createElement(ok.default,{color:"primary",type:"submit",form:"chain-configuration-form",variant:"contained"},"Submit")))};function xV(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function xq(){var e=xV(["\n fragment FeedsManager_ChainConfigFields on FeedsManagerChainConfig {\n id\n chainID\n chainType\n accountAddr\n adminAddr\n accountAddrPubKey\n fluxMonitorJobConfig {\n enabled\n }\n ocr1JobConfig {\n enabled\n isBootstrap\n multiaddr\n p2pPeerID\n keyBundleID\n }\n ocr2JobConfig {\n enabled\n isBootstrap\n multiaddr\n forwarderAddress\n p2pPeerID\n keyBundleID\n plugins {\n commit\n execute\n median\n mercury\n rebalancer\n }\n }\n }\n"]);return xq=function(){return e},e}function xZ(){var e=xV(["\n ","\n fragment FeedsManagerFields on FeedsManager {\n id\n name\n uri\n publicKey\n isConnectionActive\n chainConfigs {\n ...FeedsManager_ChainConfigFields\n }\n }\n"]);return xZ=function(){return e},e}function xX(){var e=xV(["\n fragment FeedsManager_JobProposalsFields on JobProposal {\n id\n name\n externalJobID\n remoteUUID\n status\n pendingUpdate\n latestSpec {\n createdAt\n version\n }\n }\n"]);return xX=function(){return e},e}function xJ(){var e=xV(["\n ","\n ","\n fragment FeedsManagerPayload_ResultsFields on FeedsManager {\n ...FeedsManagerFields\n jobProposals {\n ...FeedsManager_JobProposalsFields\n }\n }\n"]);return xJ=function(){return e},e}function xQ(){var e=xV(["\n ","\n query FetchFeedManagersWithProposals {\n feedsManagers {\n results {\n ...FeedsManagerPayload_ResultsFields\n }\n }\n }\n"]);return xQ=function(){return e},e}var x1=n0(xq()),x0=n0(xZ(),x1),x2=n0(xX()),x3=n0(xJ(),x0,x2),x4=n0(xQ(),x3),x6=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return rv(x4,e)};function x5(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n0?n.feedsManagers.results[0]:void 0;return n&&a?l.createElement(TZ,{manager:a}):l.createElement(h.l_,{to:{pathname:"/feeds_manager/new",state:{from:e}}})},TJ={name:"Chainlink Feeds Manager",uri:"",publicKey:""},TQ=function(e){var t=e.onSubmit;return l.createElement(d.Z,{container:!0},l.createElement(d.Z,{item:!0,xs:12,md:11,lg:9},l.createElement(r5.Z,null,l.createElement(sl.Z,{title:"Register Feeds Manager"}),l.createElement(aW.Z,null,l.createElement(xl,{initialValues:TJ,onSubmit:t})))))};function T1(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);nt.version?e:t})},[o]),g=l.useMemo(function(){return ME(o).sort(function(e,t){return t.version-e.version})},[o]),v=function(e,t,n){switch(e){case"PENDING":return l.createElement(l.Fragment,null,l.createElement(ok.default,{variant:"text",color:"secondary",onClick:function(){return b("reject",t)}},"Reject"),m.id===t&&"DELETED"!==n.status&&"REVOKED"!==n.status&&l.createElement(ok.default,{variant:"contained",color:"primary",onClick:function(){return b("approve",t)}},"Approve"),m.id===t&&"DELETED"===n.status&&n.pendingUpdate&&l.createElement(l.Fragment,null,l.createElement(ok.default,{variant:"contained",color:"primary",onClick:function(){return b("cancel",t)}},"Cancel"),l.createElement(x.default,{color:"error"},"This proposal was deleted. Cancel the spec to delete any running jobs")));case"APPROVED":return l.createElement(l.Fragment,null,l.createElement(ok.default,{variant:"contained",onClick:function(){return b("cancel",t)}},"Cancel"),"DELETED"===n.status&&n.pendingUpdate&&l.createElement(x.default,{color:"error"},"This proposal was deleted. Cancel the spec to delete any running jobs"));case"CANCELLED":if(m.id===t&&"DELETED"!==n.status&&"REVOKED"!==n.status)return l.createElement(ok.default,{variant:"contained",color:"primary",onClick:function(){return b("approve",t)}},"Approve");return null;default:return null}};return l.createElement("div",null,g.map(function(e,n){return l.createElement(mP.Z,{defaultExpanded:0===n,key:n},l.createElement(mR.Z,{expandIcon:l.createElement(gh.Z,null)},l.createElement(x.default,{className:t.versionText},"Version ",e.version),l.createElement(Es.Z,{label:e.status,color:"APPROVED"===e.status?"primary":"default",variant:"REJECTED"===e.status||"CANCELLED"===e.status?"outlined":"default"}),l.createElement("div",{className:t.proposedAtContainer},l.createElement(x.default,null,"Proposed ",l.createElement(aO,{tooltip:!0},e.createdAt)))),l.createElement(mj.Z,{className:t.expansionPanelDetails},l.createElement("div",{className:t.actions},l.createElement("div",{className:t.editContainer},0===n&&("PENDING"===e.status||"CANCELLED"===e.status)&&"DELETED"!==s.status&&"REVOKED"!==s.status&&l.createElement(ok.default,{variant:"contained",onClick:function(){return p(!0)}},"Edit")),l.createElement("div",{className:t.actionsContainer},v(e.status,e.id,s))),l.createElement(gd,{language:"toml",style:gs,"data-testid":"codeblock"},e.definition)))}),l.createElement(oC,{open:null!=c,title:c?MM[c.action].title:"",body:c?MM[c.action].body:"",onConfirm:function(){if(c){switch(c.action){case"approve":n(c.id);break;case"cancel":r(c.id);break;case"reject":i(c.id)}f(null)}},cancelButtonText:"Cancel",onCancel:function(){return f(null)}}),l.createElement(Md,{open:h,onClose:function(){return p(!1)},initialValues:{definition:m.definition,id:m.id},onSubmit:a}))});function MA(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function ML(){var e=MA(["\n ","\n fragment JobProposalPayloadFields on JobProposal {\n id\n externalJobID\n remoteUUID\n jobID\n specs {\n ...JobProposal_SpecsFields\n }\n status\n pendingUpdate\n }\n"]);return ML=function(){return e},e}var MC=n0(ML(),Mx),MI=function(e){var t=e.onApprove,n=e.onCancel,r=e.onReject,i=e.onUpdateSpec,a=e.proposal;return l.createElement(ig,null,l.createElement(d.Z,{container:!0,spacing:32},l.createElement(d.Z,{item:!0,xs:9},l.createElement(iy,null,"Job Proposal #",a.id))),l.createElement(Mo,{proposal:a}),l.createElement(d.Z,{container:!0,spacing:32},l.createElement(d.Z,{item:!0,xs:9},l.createElement(Tq,null,"Specs"))),l.createElement(d.Z,{container:!0,spacing:32},l.createElement(d.Z,{item:!0,xs:12},l.createElement(MO,{proposal:a,specs:a.specs,onReject:r,onApprove:t,onCancel:n,onUpdateSpec:i}))))};function MD(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);nU,tA:()=>$,KL:()=>H,Iw:()=>V,DQ:()=>W,cB:()=>T,LO:()=>M,t5:()=>k,qt:()=>x,Jc:()=>C,L7:()=>Y,EO:()=>B});var r,i,a=n(66289),o=n(41800),s=n.n(o),u=n(67932);(i=r||(r={})).IN_PROGRESS="in_progress",i.PENDING_INCOMING_CONFIRMATIONS="pending_incoming_confirmations",i.PENDING_CONNECTION="pending_connection",i.PENDING_BRIDGE="pending_bridge",i.PENDING_SLEEP="pending_sleep",i.ERRORED="errored",i.COMPLETED="completed";var c=n(87013),l=n(19084),f=n(34823);function d(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]j,v2:()=>F});var r=n(66289);function i(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}var a="/sessions",o="/sessions",s=function e(t){var n=this;i(this,e),this.api=t,this.createSession=function(e){return n.create(e)},this.destroySession=function(){return n.destroy()},this.create=this.api.createResource(a),this.destroy=this.api.deleteResource(o)};function u(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}var c="/v2/bulk_delete_runs",l=function e(t){var n=this;u(this,e),this.api=t,this.bulkDeleteJobRuns=function(e){return n.destroy(e)},this.destroy=this.api.deleteResource(c)};function f(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}var d="/v2/chains/evm",h="".concat(d,"/:id"),p=function e(t){var n=this;f(this,e),this.api=t,this.getChains=function(){return n.index()},this.createChain=function(e){return n.create(e)},this.destroyChain=function(e){return n.destroy(void 0,{id:e})},this.updateChain=function(e,t){return n.update(t,{id:e})},this.index=this.api.fetchResource(d),this.create=this.api.createResource(d),this.destroy=this.api.deleteResource(h),this.update=this.api.updateResource(h)};function b(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}var m="/v2/keys/evm/chain",g=function e(t){var n=this;b(this,e),this.api=t,this.chain=function(e){var t=new URLSearchParams;t.append("address",e.address),t.append("evmChainID",e.evmChainID),null!==e.nextNonce&&t.append("nextNonce",e.nextNonce),null!==e.abandon&&t.append("abandon",String(e.abandon)),null!==e.enabled&&t.append("enabled",String(e.enabled));var r=m+"?"+t.toString();return n.api.createResource(r)()}};function v(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}var y="/v2/jobs",w="".concat(y,"/:specId/runs"),_=function e(t){var n=this;v(this,e),this.api=t,this.createJobRunV2=function(e,t){return n.post(t,{specId:e})},this.post=this.api.createResource(w,!0)};function E(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}var S="/v2/log",k=function e(t){var n=this;E(this,e),this.api=t,this.getLogConfig=function(){return n.show()},this.updateLogConfig=function(e){return n.update(e)},this.show=this.api.fetchResource(S),this.update=this.api.updateResource(S)};function x(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}var T="/v2/nodes",M=function e(t){var n=this;x(this,e),this.api=t,this.getNodes=function(){return n.index()},this.createNode=function(e){return n.create(e)},this.index=this.api.fetchResource(T),this.create=this.api.createResource(T)};function O(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}var A="/v2/enroll_webauthn",L=function e(t){var n=this;O(this,e),this.api=t,this.beginKeyRegistration=function(e){return n.create(e)},this.finishKeyRegistration=function(e){return n.put(e)},this.create=this.api.fetchResource(A),this.put=this.api.createResource(A)};function C(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}var I="/v2/build_info",D=function e(t){var n=this;C(this,e),this.api=t,this.show=function(){return n.api.GET(I)()}};function N(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}var P=function e(t){N(this,e),this.api=t,this.buildInfo=new D(this.api),this.bulkDeleteRuns=new l(this.api),this.chains=new p(this.api),this.logConfig=new k(this.api),this.nodes=new M(this.api),this.jobs=new _(this.api),this.webauthn=new L(this.api),this.evmKeys=new g(this.api)},R=new r.V0({base:void 0}),j=new s(R),F=new P(R)},1398(e,t,n){"use strict";n.d(t,{Z:()=>d});var r=n(67294),i=n(32316),a=n(83638),o=n(94184),s=n.n(o);function u(){return(u=Object.assign||function(e){for(var t=1;tc});var r=n(67294),i=n(32316);function a(){return(a=Object.assign||function(e){for(var t=1;tx,jK:()=>v});var r=n(67294),i=n(37703),a=n(45697),o=n.n(a),s=n(82204),u=n(71426),c=n(94184),l=n.n(c),f=n(32316),d=function(e){var t=e.palette.success||{},n=e.palette.warning||{};return{base:{paddingLeft:5*e.spacing.unit,paddingRight:5*e.spacing.unit},success:{backgroundColor:t.main,color:t.contrastText},error:{backgroundColor:e.palette.error.dark,color:e.palette.error.contrastText},warning:{backgroundColor:n.contrastText,color:n.main}}},h=function(e){var t,n=e.success,r=e.error,i=e.warning,a=e.classes,o=e.className;return n?t=a.success:r?t=a.error:i&&(t=a.warning),l()(a.base,o,t)},p=function(e){return r.createElement(s.Z,{className:h(e),square:!0},r.createElement(u.default,{variant:"body2",color:"inherit",component:"div"},e.children))};p.defaultProps={success:!1,error:!1,warning:!1},p.propTypes={success:o().bool,error:o().bool,warning:o().bool};let b=(0,f.withStyles)(d)(p);var m=function(){return r.createElement(r.Fragment,null,"Unhandled error. Please help us by opening a"," ",r.createElement("a",{href:"https://github.com/smartcontractkit/chainlink/issues/new"},"bug report"))};let g=m;function v(e){return"string"==typeof e?e:e.component?e.component(e.props):r.createElement(g,null)}function y(e,t){var n;return n="string"==typeof e?e:e.component?e.component(e.props):r.createElement(g,null),r.createElement("p",{key:t},n)}var w=function(e){var t=e.notifications;return r.createElement(b,{error:!0},t.map(y))},_=function(e){var t=e.notifications;return r.createElement(b,{success:!0},t.map(y))},E=function(e){var t=e.errors,n=e.successes;return r.createElement("div",null,(null==t?void 0:t.length)>0&&r.createElement(w,{notifications:t}),n.length>0&&r.createElement(_,{notifications:n}))},S=function(e){return{errors:e.notifications.errors,successes:e.notifications.successes}},k=(0,i.$j)(S)(E);let x=k},9409(e,t,n){"use strict";n.d(t,{ZP:()=>j});var r=n(67294),i=n(37703),a=n(5977),o=n(32316),s=n(1398),u=n(82204),c=n(30060),l=n(71426),f=n(60520),d=n(39814),h=n(57209),p=n(26842),b=n(3950),m=n(5536),g=n(45697),v=n.n(g);let y=n.p+"9f6d832ef97e8493764e.svg";function w(){return(w=Object.assign||function(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&_.map(function(e,t){return r.createElement(d.Z,{item:!0,xs:12,key:t},r.createElement(u.Z,{raised:!1,className:v.error},r.createElement(c.Z,null,r.createElement(l.default,{variant:"body1",className:v.errorText},(0,b.jK)(e)))))}),r.createElement(d.Z,{item:!0,xs:12},r.createElement(f.Z,{id:"email",label:"Email",margin:"normal",value:n,onChange:m("email"),error:_.length>0,variant:"outlined",fullWidth:!0})),r.createElement(d.Z,{item:!0,xs:12},r.createElement(f.Z,{id:"password",label:"Password",type:"password",autoComplete:"password",margin:"normal",value:h,onChange:m("password"),error:_.length>0,variant:"outlined",fullWidth:!0})),r.createElement(d.Z,{item:!0,xs:12},r.createElement(d.Z,{container:!0,spacing:0,justify:"center"},r.createElement(d.Z,{item:!0},r.createElement(s.Z,{type:"submit",variant:"primary"},"Access Account")))),y&&r.createElement(l.default,{variant:"body1",color:"textSecondary"},"Signing in...")))))))},P=function(e){return{fetching:e.authentication.fetching,authenticated:e.authentication.allowed,errors:e.notifications.errors}},R=(0,i.$j)(P,x({submitSignIn:p.L7}))(N);let j=(0,h.wU)(e)((0,o.withStyles)(D)(R))},16353(e,t,n){"use strict";n.d(t,{ZP:()=>H,rH:()=>U});var r,i=n(37703),a=n(97779),o=n(9541),s=n(19084);function u(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function c(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:h,t=arguments.length>1?arguments[1]:void 0;switch(t.type){case s.Mk.RECEIVE_SIGNOUT_SUCCESS:case s.Mk.RECEIVE_SIGNIN_SUCCESS:var n={allowed:t.authenticated};return o.Ks(n),f(c({},e,n),{errors:[]});case s.Mk.RECEIVE_SIGNIN_FAIL:var r={allowed:!1};return o.Ks(r),f(c({},e,r),{errors:[]});case s.Mk.RECEIVE_SIGNIN_ERROR:case s.Mk.RECEIVE_SIGNOUT_ERROR:var i={allowed:!1};return o.Ks(i),f(c({},e,i),{errors:t.errors||[]});default:return e}};let b=p;function m(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function g(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:_,t=arguments.length>1?arguments[1]:void 0;return t.type?t.type.startsWith(r.REQUEST)?y(g({},e),{count:e.count+1}):t.type.startsWith(r.RECEIVE)?y(g({},e),{count:Math.max(e.count-1,0)}):t.type.startsWith(r.RESPONSE)?y(g({},e),{count:Math.max(e.count-1,0)}):t.type===s.di.REDIRECT?y(g({},e),{count:0}):e:e};let S=E;function k(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function x(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:O,t=arguments.length>1?arguments[1]:void 0;switch(t.type){case s.di.MATCH_ROUTE:return M(x({},O),{currentUrl:t.pathname});case s.Ih.NOTIFY_SUCCESS:var n={component:t.component,props:t.props};return M(x({},e),{successes:[n],errors:[]});case s.Ih.NOTIFY_SUCCESS_MSG:return M(x({},e),{successes:[t.msg],errors:[]});case s.Ih.NOTIFY_ERROR:var r=t.error.errors,i=null==r?void 0:r.map(function(e){return L(t,e)});return M(x({},e),{successes:[],errors:i});case s.Ih.NOTIFY_ERROR_MSG:return M(x({},e),{successes:[],errors:[t.msg]});case s.Mk.RECEIVE_SIGNIN_FAIL:return M(x({},e),{successes:[],errors:["Your email or password is incorrect. Please try again"]});default:return e}};function L(e,t){return{component:e.component,props:{msg:t.detail}}}let C=A;function I(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function D(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:R,t=arguments.length>1?arguments[1]:void 0;switch(t.type){case s.di.REDIRECT:return P(D({},e),{to:t.to});case s.di.MATCH_ROUTE:return P(D({},e),{to:void 0});default:return e}};let F=j;var Y=n(87013),B=(0,a.UY)({authentication:b,fetching:S,notifications:C,redirect:F,buildInfo:Y.Z});B(void 0,{type:"INITIAL_STATE"});var U=i.v9;let H=B},19084(e,t,n){"use strict";var r,i,a,o,s,u,c,l,f,d;n.d(t,{Ih:()=>i,Mk:()=>a,Y0:()=>s,di:()=>r,jp:()=>o}),n(67294),(u=r||(r={})).REDIRECT="REDIRECT",u.MATCH_ROUTE="MATCH_ROUTE",(c=i||(i={})).NOTIFY_SUCCESS="NOTIFY_SUCCESS",c.NOTIFY_SUCCESS_MSG="NOTIFY_SUCCESS_MSG",c.NOTIFY_ERROR="NOTIFY_ERROR",c.NOTIFY_ERROR_MSG="NOTIFY_ERROR_MSG",(l=a||(a={})).REQUEST_SIGNIN="REQUEST_SIGNIN",l.RECEIVE_SIGNIN_SUCCESS="RECEIVE_SIGNIN_SUCCESS",l.RECEIVE_SIGNIN_FAIL="RECEIVE_SIGNIN_FAIL",l.RECEIVE_SIGNIN_ERROR="RECEIVE_SIGNIN_ERROR",l.RECEIVE_SIGNOUT_SUCCESS="RECEIVE_SIGNOUT_SUCCESS",l.RECEIVE_SIGNOUT_ERROR="RECEIVE_SIGNOUT_ERROR",(f=o||(o={})).RECEIVE_CREATE_ERROR="RECEIVE_CREATE_ERROR",f.RECEIVE_CREATE_SUCCESS="RECEIVE_CREATE_SUCCESS",f.RECEIVE_DELETE_ERROR="RECEIVE_DELETE_ERROR",f.RECEIVE_DELETE_SUCCESS="RECEIVE_DELETE_SUCCESS",f.RECEIVE_UPDATE_ERROR="RECEIVE_UPDATE_ERROR",f.RECEIVE_UPDATE_SUCCESS="RECEIVE_UPDATE_SUCCESS",f.REQUEST_CREATE="REQUEST_CREATE",f.REQUEST_DELETE="REQUEST_DELETE",f.REQUEST_UPDATE="REQUEST_UPDATE",f.UPSERT_CONFIGURATION="UPSERT_CONFIGURATION",f.UPSERT_JOB_RUN="UPSERT_JOB_RUN",f.UPSERT_JOB_RUNS="UPSERT_JOB_RUNS",f.UPSERT_TRANSACTION="UPSERT_TRANSACTION",f.UPSERT_TRANSACTIONS="UPSERT_TRANSACTIONS",f.UPSERT_BUILD_INFO="UPSERT_BUILD_INFO",(d=s||(s={})).FETCH_BUILD_INFO_REQUESTED="FETCH_BUILD_INFO_REQUESTED",d.FETCH_BUILD_INFO_SUCCEEDED="FETCH_BUILD_INFO_SUCCEEDED",d.FETCH_BUILD_INFO_FAILED="FETCH_BUILD_INFO_FAILED"},87013(e,t,n){"use strict";n.d(t,{Y:()=>o,Z:()=>u});var r=n(19084);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function a(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:o,t=arguments.length>1?arguments[1]:void 0;return t.type===r.Y0.FETCH_BUILD_INFO_SUCCEEDED?a({},t.buildInfo):e};let u=s},34823(e,t,n){"use strict";n.d(t,{N:()=>r});var r=function(e){return e.buildInfo}},73343(e,t,n){"use strict";n.d(t,{r:()=>u});var r=n(19350),i=n(32316),a=n(59114),o=n(5324),s={props:{MuiGrid:{spacing:3*o.default.unit},MuiCardHeader:{titleTypographyProps:{color:"secondary"}}},palette:{action:{hoverOpacity:.3},primary:{light:"#E5F1FF",main:"#3c40c6",contrastText:"#fff"},secondary:{main:"#3d5170"},success:{light:"#e8faf1",main:r.ek.A700,dark:r.ek[700],contrastText:r.y0.white},warning:{light:"#FFFBF1",main:"#fff6b6",contrastText:"#fad27a"},error:{light:"#ffdada",main:"#f44336",dark:"#d32f2f",contrastText:"#fff"},background:{default:"#f5f6f8",appBar:"#3c40c6"},text:{primary:(0,a.darken)(r.BA.A700,.7),secondary:"#818ea3"},listPendingStatus:{background:"#fef7e5",color:"#fecb4c"},listCompletedStatus:{background:"#e9faf2",color:"#4ed495"}},shape:{borderRadius:o.default.unit},overrides:{MuiButton:{root:{borderRadius:o.default.unit/2,textTransform:"none"},sizeLarge:{padding:void 0,fontSize:void 0,paddingTop:o.default.unit,paddingBottom:o.default.unit,paddingLeft:5*o.default.unit,paddingRight:5*o.default.unit}},MuiTableCell:{body:{fontSize:"1rem"},head:{fontSize:"1rem",fontWeight:400}},MuiCardHeader:{root:{borderBottom:"1px solid rgba(0, 0, 0, 0.12)"},action:{marginTop:-2,marginRight:0,"& >*":{marginLeft:2*o.default.unit}},subheader:{marginTop:.5*o.default.unit}}},typography:{useNextVariants:!0,fontFamily:"-apple-system,BlinkMacSystemFont,Roboto,Helvetica,Arial,sans-serif",button:{textTransform:"none",fontSize:"1.2em"},body1:{fontSize:"1.0rem",fontWeight:400,lineHeight:"1.46429em",color:"rgba(0, 0, 0, 0.87)",letterSpacing:-.4},body2:{fontSize:"1.0rem",fontWeight:500,lineHeight:"1.71429em",color:"rgba(0, 0, 0, 0.87)",letterSpacing:-.4},body1Next:{color:"rgb(29, 29, 29)",fontWeight:400,fontSize:"1rem",lineHeight:1.5,letterSpacing:-.4},body2Next:{color:"rgb(29, 29, 29)",fontWeight:400,fontSize:"0.875rem",lineHeight:1.5,letterSpacing:-.4},display1:{color:"#818ea3",fontSize:"2.125rem",fontWeight:400,lineHeight:"1.20588em",letterSpacing:-.4},display2:{color:"#818ea3",fontSize:"2.8125rem",fontWeight:400,lineHeight:"1.13333em",marginLeft:"-.02em",letterSpacing:-.4},display3:{color:"#818ea3",fontSize:"3.5rem",fontWeight:400,lineHeight:"1.30357em",marginLeft:"-.02em",letterSpacing:-.4},display4:{fontSize:14,fontWeightLight:300,fontWeightMedium:500,fontWeightRegular:400,letterSpacing:-.4},h1:{color:"rgb(29, 29, 29)",fontSize:"6rem",fontWeight:300,lineHeight:1},h2:{color:"rgb(29, 29, 29)",fontSize:"3.75rem",fontWeight:300,lineHeight:1},h3:{color:"rgb(29, 29, 29)",fontSize:"3rem",fontWeight:400,lineHeight:1.04},h4:{color:"rgb(29, 29, 29)",fontSize:"2.125rem",fontWeight:400,lineHeight:1.17},h5:{color:"rgb(29, 29, 29)",fontSize:"1.5rem",fontWeight:400,lineHeight:1.33,letterSpacing:-.4},h6:{fontSize:"0.8rem",fontWeight:450,lineHeight:"1.71429em",color:"rgba(0, 0, 0, 0.87)",letterSpacing:-.4},subheading:{color:"rgb(29, 29, 29)",fontSize:"1rem",fontWeight:400,lineHeight:"1.5em",letterSpacing:-.4},subtitle1:{color:"rgb(29, 29, 29)",fontSize:"1rem",fontWeight:400,lineHeight:1.75,letterSpacing:-.4},subtitle2:{color:"rgb(29, 29, 29)",fontSize:"0.875rem",fontWeight:500,lineHeight:1.57,letterSpacing:-.4}},shadows:["none","0px 1px 3px 0px rgba(0, 0, 0, 0.1),0px 1px 1px 0px rgba(0, 0, 0, 0.04),0px 2px 1px -1px rgba(0, 0, 0, 0.02)","0px 1px 5px 0px rgba(0, 0, 0, 0.1),0px 2px 2px 0px rgba(0, 0, 0, 0.04),0px 3px 1px -2px rgba(0, 0, 0, 0.02)","0px 1px 8px 0px rgba(0, 0, 0, 0.1),0px 3px 4px 0px rgba(0, 0, 0, 0.04),0px 3px 3px -2px rgba(0, 0, 0, 0.02)","0px 2px 4px -1px rgba(0, 0, 0, 0.1),0px 4px 5px 0px rgba(0, 0, 0, 0.04),0px 1px 10px 0px rgba(0, 0, 0, 0.02)","0px 3px 5px -1px rgba(0, 0, 0, 0.1),0px 5px 8px 0px rgba(0, 0, 0, 0.04),0px 1px 14px 0px rgba(0, 0, 0, 0.02)","0px 3px 5px -1px rgba(0, 0, 0, 0.1),0px 6px 10px 0px rgba(0, 0, 0, 0.04),0px 1px 18px 0px rgba(0, 0, 0, 0.02)","0px 4px 5px -2px rgba(0, 0, 0, 0.1),0px 7px 10px 1px rgba(0, 0, 0, 0.04),0px 2px 16px 1px rgba(0, 0, 0, 0.02)","0px 5px 5px -3px rgba(0, 0, 0, 0.1),0px 8px 10px 1px rgba(0, 0, 0, 0.04),0px 3px 14px 2px rgba(0, 0, 0, 0.02)","0px 5px 6px -3px rgba(0, 0, 0, 0.1),0px 9px 12px 1px rgba(0, 0, 0, 0.04),0px 3px 16px 2px rgba(0, 0, 0, 0.02)","0px 6px 6px -3px rgba(0, 0, 0, 0.1),0px 10px 14px 1px rgba(0, 0, 0, 0.04),0px 4px 18px 3px rgba(0, 0, 0, 0.02)","0px 6px 7px -4px rgba(0, 0, 0, 0.1),0px 11px 15px 1px rgba(0, 0, 0, 0.04),0px 4px 20px 3px rgba(0, 0, 0, 0.02)","0px 7px 8px -4px rgba(0, 0, 0, 0.1),0px 12px 17px 2px rgba(0, 0, 0, 0.04),0px 5px 22px 4px rgba(0, 0, 0, 0.02)","0px 7px 8px -4px rgba(0, 0, 0, 0.1),0px 13px 19px 2px rgba(0, 0, 0, 0.04),0px 5px 24px 4px rgba(0, 0, 0, 0.02)","0px 7px 9px -4px rgba(0, 0, 0, 0.1),0px 14px 21px 2px rgba(0, 0, 0, 0.04),0px 5px 26px 4px rgba(0, 0, 0, 0.02)","0px 8px 9px -5px rgba(0, 0, 0, 0.1),0px 15px 22px 2px rgba(0, 0, 0, 0.04),0px 6px 28px 5px rgba(0, 0, 0, 0.02)","0px 8px 10px -5px rgba(0, 0, 0, 0.1),0px 16px 24px 2px rgba(0, 0, 0, 0.04),0px 6px 30px 5px rgba(0, 0, 0, 0.02)","0px 8px 11px -5px rgba(0, 0, 0, 0.1),0px 17px 26px 2px rgba(0, 0, 0, 0.04),0px 6px 32px 5px rgba(0, 0, 0, 0.02)","0px 9px 11px -5px rgba(0, 0, 0, 0.1),0px 18px 28px 2px rgba(0, 0, 0, 0.04),0px 7px 34px 6px rgba(0, 0, 0, 0.02)","0px 9px 12px -6px rgba(0, 0, 0, 0.1),0px 19px 29px 2px rgba(0, 0, 0, 0.04),0px 7px 36px 6px rgba(0, 0, 0, 0.02)","0px 10px 13px -6px rgba(0, 0, 0, 0.1),0px 20px 31px 3px rgba(0, 0, 0, 0.04),0px 8px 38px 7px rgba(0, 0, 0, 0.02)","0px 10px 13px -6px rgba(0, 0, 0, 0.1),0px 21px 33px 3px rgba(0, 0, 0, 0.04),0px 8px 40px 7px rgba(0, 0, 0, 0.02)","0px 10px 14px -6px rgba(0, 0, 0, 0.1),0px 22px 35px 3px rgba(0, 0, 0, 0.04),0px 8px 42px 7px rgba(0, 0, 0, 0.02)","0px 11px 14px -7px rgba(0, 0, 0, 0.1),0px 23px 36px 3px rgba(0, 0, 0, 0.04),0px 9px 44px 8px rgba(0, 0, 0, 0.02)","0px 11px 15px -7px rgba(0, 0, 0, 0.1),0px 24px 38px 3px rgba(0, 0, 0, 0.04),0px 9px 46px 8px rgba(0, 0, 0, 0.02)",]},u=(0,i.createMuiTheme)(s)},66289(e,t,n){"use strict";function r(e){if(void 0===e)throw ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function i(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}function a(){if("undefined"==typeof Reflect||!Reflect.construct||Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],function(){})),!0}catch(e){return!1}}function o(e,t,n){return(o=a()?Reflect.construct:function(e,t,n){var r=[null];r.push.apply(r,t);var i=new(Function.bind.apply(e,r));return n&&f(i,n.prototype),i}).apply(null,arguments)}function s(e){return(s=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function u(e,t){if("function"!=typeof t&&null!==t)throw TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&f(e,t)}function c(e){return -1!==Function.toString.call(e).indexOf("[native code]")}function l(e,t){return t&&("object"===p(t)||"function"==typeof t)?t:r(e)}function f(e,t){return(f=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}n.d(t,{V0:()=>B,_7:()=>v});var d,h,p=function(e){return e&&"undefined"!=typeof Symbol&&e.constructor===Symbol?"symbol":typeof e};function b(e){var t="function"==typeof Map?new Map:void 0;return(b=function(e){if(null===e||!c(e))return e;if("function"!=typeof e)throw TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,n)}function n(){return o(e,arguments,s(this).constructor)}return n.prototype=Object.create(e.prototype,{constructor:{value:n,enumerable:!1,writable:!0,configurable:!0}}),f(n,e)})(e)}function m(){if("undefined"==typeof Reflect||!Reflect.construct||Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){})),!0}catch(e){return!1}}function g(e){var t=m();return function(){var n,r=s(e);if(t){var i=s(this).constructor;n=Reflect.construct(r,arguments,i)}else n=r.apply(this,arguments);return l(this,n)}}var v=function(e){u(n,e);var t=g(n);function n(e){var r;return i(this,n),(r=t.call(this,"AuthenticationError(".concat(e.statusText,")"))).errors=[{status:e.status,detail:e},],r}return n}(b(Error)),y=function(e){u(n,e);var t=g(n);function n(e){var r,a=e.errors;return i(this,n),(r=t.call(this,"BadRequestError")).errors=a,r}return n}(b(Error)),w=function(e){u(n,e);var t=g(n);function n(e){var r;return i(this,n),(r=t.call(this,"UnprocessableEntityError")).errors=e,r}return n}(b(Error)),_=function(e){u(n,e);var t=g(n);function n(e){var r;return i(this,n),(r=t.call(this,"ServerError")).errors=e,r}return n}(b(Error)),E=function(e){u(n,e);var t=g(n);function n(e){var r,a=e.errors;return i(this,n),(r=t.call(this,"ConflictError")).errors=a,r}return n}(b(Error)),S=function(e){u(n,e);var t=g(n);function n(e){var r;return i(this,n),(r=t.call(this,"UnknownResponseError(".concat(e.statusText,")"))).errors=[{status:e.status,detail:e.statusText},],r}return n}(b(Error));function k(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:2e4;return Promise.race([fetch(e,t),new Promise(function(e,t){return setTimeout(function(){return t(Error("timeout"))},n)}),])}function x(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]=200&&e.status<300))return[3,2];return[2,e.json()];case 2:if(400!==e.status)return[3,3];return[2,e.json().then(function(e){throw new y(e)})];case 3:if(401!==e.status)return[3,4];throw new v(e);case 4:if(422!==e.status)return[3,6];return[4,$(e)];case 5:throw n=i.sent(),new w(n);case 6:if(409!==e.status)return[3,7];return[2,e.json().then(function(e){throw new E(e)})];case 7:if(!(e.status>=500))return[3,9];return[4,$(e)];case 8:throw r=i.sent(),new _(r);case 9:throw new S(e);case 10:return[2]}})})).apply(this,arguments)}function $(e){return z.apply(this,arguments)}function z(){return(z=j(function(e){return Y(this,function(t){return[2,e.json().then(function(t){return t.errors?t.errors.map(function(t){return{status:e.status,detail:t.detail}}):G(e)}).catch(function(){return G(e)})]})})).apply(this,arguments)}function G(e){return[{status:e.status,detail:e.statusText},]}},50109(e,t,n){"use strict";n.d(t,{LK:()=>o,U2:()=>i,eT:()=>s,t8:()=>a});var r=n(12795);function i(e){return r.ZP.getItem("chainlink.".concat(e))}function a(e,t){r.ZP.setItem("chainlink.".concat(e),t)}function o(e){var t=i(e),n={};if(t)try{return JSON.parse(t)}catch(r){}return n}function s(e,t){a(e,JSON.stringify(t))}},9541(e,t,n){"use strict";n.d(t,{Ks:()=>u,Tp:()=>a,iR:()=>o,pm:()=>s});var r=n(50109),i="persistURL";function a(){return r.U2(i)||""}function o(e){r.t8(i,e)}function s(){return r.LK("authentication")}function u(e){r.eT("authentication",e)}},67121(e,t,n){"use strict";function r(e){var t,n=e.Symbol;return"function"==typeof n?n.observable?t=n.observable:(t=n("observable"),n.observable=t):t="@@observable",t}n.r(t),n.d(t,{default:()=>o}),e=n.hmd(e),i="undefined"!=typeof self?self:"undefined"!=typeof window?window:void 0!==n.g?n.g:e;var i,a=r(i);let o=a},2177(e,t,n){"use strict";n.d(t,{Z:()=>o});var r=!0,i="Invariant failed";function a(e,t){if(!e){if(r)throw Error(i);throw Error(i+": "+(t||""))}}let o=a},11742(e){e.exports=function(){var e=document.getSelection();if(!e.rangeCount)return function(){};for(var t=document.activeElement,n=[],r=0;ru,ZT:()=>i,_T:()=>o,ev:()=>c,mG:()=>s,pi:()=>a});var r=function(e,t){return(r=Object.setPrototypeOf||({__proto__:[]})instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])})(e,t)};function i(e,t){if("function"!=typeof t&&null!==t)throw TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}var a=function(){return(a=Object.assign||function(e){for(var t,n=1,r=arguments.length;nt.indexOf(r)&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var i=0,r=Object.getOwnPropertySymbols(e);it.indexOf(r[i])&&Object.prototype.propertyIsEnumerable.call(e,r[i])&&(n[r[i]]=e[r[i]]);return n}function s(e,t,n,r){function i(e){return e instanceof n?e:new n(function(t){t(e)})}return new(n||(n=Promise))(function(n,a){function o(e){try{u(r.next(e))}catch(t){a(t)}}function s(e){try{u(r.throw(e))}catch(t){a(t)}}function u(e){e.done?n(e.value):i(e.value).then(o,s)}u((r=r.apply(e,t||[])).next())})}function u(e,t){var n,r,i,a,o={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return a={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(a[Symbol.iterator]=function(){return this}),a;function s(e){return function(t){return u([e,t])}}function u(a){if(n)throw TypeError("Generator is already executing.");for(;o;)try{if(n=1,r&&(i=2&a[0]?r.return:a[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,a[1])).done)return i;switch(r=0,i&&(a=[2&a[0],i.value]),a[0]){case 0:case 1:i=a;break;case 4:return o.label++,{value:a[1],done:!1};case 5:o.label++,r=a[1],a=[0];continue;case 7:a=o.ops.pop(),o.trys.pop();continue;default:if(!(i=(i=o.trys).length>0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]r})},94927(e,t,n){function r(e,t){if(i("noDeprecation"))return e;var n=!1;function r(){if(!n){if(i("throwDeprecation"))throw Error(t);i("traceDeprecation")?console.trace(t):console.warn(t),n=!0}return e.apply(this,arguments)}return r}function i(e){try{if(!n.g.localStorage)return!1}catch(t){return!1}var r=n.g.localStorage[e];return null!=r&&"true"===String(r).toLowerCase()}e.exports=r},42473(e){"use strict";var t=function(){};e.exports=t},84763(e){e.exports=Worker},47529(e){e.exports=n;var t=Object.prototype.hasOwnProperty;function n(){for(var e={},n=0;ne.length)&&(t=e.length);for(var n=0,r=Array(t);n=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}e.exports=i,e.exports.__esModule=!0,e.exports.default=e.exports},7071(e){function t(e,t){if(null==e)return{};var n,r,i={},a=Object.keys(e);for(r=0;r=0||(i[n]=e[n]);return i}e.exports=t,e.exports.__esModule=!0,e.exports.default=e.exports},94993(e,t,n){var r=n(18698).default,i=n(66115);function a(e,t){if(t&&("object"===r(t)||"function"==typeof t))return t;if(void 0!==t)throw TypeError("Derived constructors may only return object or undefined");return i(e)}e.exports=a,e.exports.__esModule=!0,e.exports.default=e.exports},6015(e){function t(n,r){return e.exports=t=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e},e.exports.__esModule=!0,e.exports.default=e.exports,t(n,r)}e.exports=t,e.exports.__esModule=!0,e.exports.default=e.exports},861(e,t,n){var r=n(63405),i=n(79498),a=n(86116),o=n(42281);function s(e){return r(e)||i(e)||a(e)||o()}e.exports=s,e.exports.__esModule=!0,e.exports.default=e.exports},18698(e){function t(n){return e.exports=t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e.exports.__esModule=!0,e.exports.default=e.exports,t(n)}e.exports=t,e.exports.__esModule=!0,e.exports.default=e.exports},86116(e,t,n){var r=n(73897);function i(e,t){if(e){if("string"==typeof e)return r(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);if("Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return r(e,t)}}e.exports=i,e.exports.__esModule=!0,e.exports.default=e.exports},1644(e,t,n){"use strict";var r,i;function a(e){return!!e&&e<7}n.d(t,{I:()=>r,O:()=>a}),(i=r||(r={}))[i.loading=1]="loading",i[i.setVariables=2]="setVariables",i[i.fetchMore=3]="fetchMore",i[i.refetch=4]="refetch",i[i.poll=6]="poll",i[i.ready=7]="ready",i[i.error=8]="error"},30990(e,t,n){"use strict";n.d(t,{MS:()=>s,YG:()=>a,cA:()=>c,ls:()=>o});var r=n(70655);n(83952);var i=n(13154),a=Symbol();function o(e){return!!e.extensions&&Array.isArray(e.extensions[a])}function s(e){return e.hasOwnProperty("graphQLErrors")}var u=function(e){var t=(0,r.ev)((0,r.ev)((0,r.ev)([],e.graphQLErrors,!0),e.clientErrors,!0),e.protocolErrors,!0);return e.networkError&&t.push(e.networkError),t.map(function(e){return(0,i.s)(e)&&e.message||"Error message not found."}).join("\n")},c=function(e){function t(n){var r=n.graphQLErrors,i=n.protocolErrors,a=n.clientErrors,o=n.networkError,s=n.errorMessage,c=n.extraInfo,l=e.call(this,s)||this;return l.name="ApolloError",l.graphQLErrors=r||[],l.protocolErrors=i||[],l.clientErrors=a||[],l.networkError=o||null,l.message=s||u(l),l.extraInfo=c,l.__proto__=t.prototype,l}return(0,r.ZT)(t,e),t}(Error)},85317(e,t,n){"use strict";n.d(t,{K:()=>a});var r=n(67294),i=n(30320).aS?Symbol.for("__APOLLO_CONTEXT__"):"__APOLLO_CONTEXT__";function a(){var e=r.createContext[i];return e||(Object.defineProperty(r.createContext,i,{value:e=r.createContext({}),enumerable:!1,writable:!1,configurable:!0}),e.displayName="ApolloContext"),e}},21436(e,t,n){"use strict";n.d(t,{O:()=>i,k:()=>r});var r=Array.isArray;function i(e){return Array.isArray(e)&&e.length>0}},30320(e,t,n){"use strict";n.d(t,{DN:()=>s,JC:()=>l,aS:()=>o,mr:()=>i,sy:()=>a});var r=n(83952),i="function"==typeof WeakMap&&"ReactNative"!==(0,r.wY)(function(){return navigator.product}),a="function"==typeof WeakSet,o="function"==typeof Symbol&&"function"==typeof Symbol.for,s=o&&Symbol.asyncIterator,u="function"==typeof(0,r.wY)(function(){return window.document.createElement}),c=(0,r.wY)(function(){return navigator.userAgent.indexOf("jsdom")>=0})||!1,l=u&&!c},53712(e,t,n){"use strict";function r(){for(var e=[],t=0;tr})},10542(e,t,n){"use strict";n.d(t,{J:()=>o}),n(83952);var r=n(13154);function i(e){var t=new Set([e]);return t.forEach(function(e){(0,r.s)(e)&&a(e)===e&&Object.getOwnPropertyNames(e).forEach(function(n){(0,r.s)(e[n])&&t.add(e[n])})}),e}function a(e){if(__DEV__&&!Object.isFrozen(e))try{Object.freeze(e)}catch(t){if(t instanceof TypeError)return null;throw t}return e}function o(e){return __DEV__&&i(e),e}},14012(e,t,n){"use strict";n.d(t,{J:()=>a});var r=n(70655),i=n(53712);function a(e,t){return(0,i.o)(e,t,t.variables&&{variables:(0,r.pi)((0,r.pi)({},e&&e.variables),t.variables)})}},13154(e,t,n){"use strict";function r(e){return null!==e&&"object"==typeof e}n.d(t,{s:()=>r})},83952(e,t,n){"use strict";n.d(t,{ej:()=>u,kG:()=>c,wY:()=>h});var r,i=n(70655),a="Invariant Violation",o=Object.setPrototypeOf,s=void 0===o?function(e,t){return e.__proto__=t,e}:o,u=function(e){function t(n){void 0===n&&(n=a);var r=e.call(this,"number"==typeof n?a+": "+n+" (see https://github.com/apollographql/invariant-packages)":n)||this;return r.framesToPop=1,r.name=a,s(r,t.prototype),r}return(0,i.ZT)(t,e),t}(Error);function c(e,t){if(!e)throw new u(t)}var l=["debug","log","warn","error","silent"],f=l.indexOf("log");function d(e){return function(){if(l.indexOf(e)>=f)return(console[e]||console.log).apply(console,arguments)}}function h(e){try{return e()}catch(t){}}(r=c||(c={})).debug=d("debug"),r.log=d("log"),r.warn=d("warn"),r.error=d("error");let p=h(function(){return globalThis})||h(function(){return window})||h(function(){return self})||h(function(){return global})||h(function(){return h.constructor("return this")()});var b="__",m=[b,b].join("DEV");function g(){try{return Boolean(__DEV__)}catch(e){return Object.defineProperty(p,m,{value:"production"!==h(function(){return"production"}),enumerable:!1,configurable:!0,writable:!0}),p[m]}}let v=g();function y(e){try{return e()}catch(t){}}var w=y(function(){return globalThis})||y(function(){return window})||y(function(){return self})||y(function(){return global})||y(function(){return y.constructor("return this")()}),_=!1;function E(){!w||y(function(){return"production"})||y(function(){return process})||(Object.defineProperty(w,"process",{value:{env:{NODE_ENV:"production"}},configurable:!0,enumerable:!1,writable:!0}),_=!0)}function S(){_&&(delete w.process,_=!1)}E();var k=n(10143);function x(){return k.H,S()}function T(){__DEV__?c("boolean"==typeof v,v):c("boolean"==typeof v,39)}x(),T()},4942(e,t,n){"use strict";function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}n.d(t,{Z:()=>r})},87462(e,t,n){"use strict";function r(){return(r=Object.assign?Object.assign.bind():function(e){for(var t=1;tr})},51721(e,t,n){"use strict";function r(e,t){return(r=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e})(e,t)}function i(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,r(e,t)}n.d(t,{Z:()=>i})},63366(e,t,n){"use strict";function r(e,t){if(null==e)return{};var n,r,i={},a=Object.keys(e);for(r=0;r=0||(i[n]=e[n]);return i}n.d(t,{Z:()=>r})},25821(e,t,n){"use strict";n.d(t,{Z:()=>s});var r=n(45695);function i(e){return(i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var a=10,o=2;function s(e){return u(e,[])}function u(e,t){switch(i(e)){case"string":return JSON.stringify(e);case"function":return e.name?"[function ".concat(e.name,"]"):"[function]";case"object":if(null===e)return"null";return c(e,t);default:return String(e)}}function c(e,t){if(-1!==t.indexOf(e))return"[Circular]";var n=[].concat(t,[e]),r=d(e);if(void 0!==r){var i=r.call(e);if(i!==e)return"string"==typeof i?i:u(i,n)}else if(Array.isArray(e))return f(e,n);return l(e,n)}function l(e,t){var n=Object.keys(e);return 0===n.length?"{}":t.length>o?"["+h(e)+"]":"{ "+n.map(function(n){var r=u(e[n],t);return n+": "+r}).join(", ")+" }"}function f(e,t){if(0===e.length)return"[]";if(t.length>o)return"[Array]";for(var n=Math.min(a,e.length),r=e.length-n,i=[],s=0;s1&&i.push("... ".concat(r," more items")),"["+i.join(", ")+"]"}function d(e){var t=e[String(r.Z)];return"function"==typeof t?t:"function"==typeof e.inspect?e.inspect:void 0}function h(e){var t=Object.prototype.toString.call(e).replace(/^\[object /,"").replace(/]$/,"");if("Object"===t&&"function"==typeof e.constructor){var n=e.constructor.name;if("string"==typeof n&&""!==n)return n}return t}},45695(e,t,n){"use strict";n.d(t,{Z:()=>i});var r="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):void 0;let i=r},25217(e,t,n){"use strict";function r(e,t){if(!Boolean(e))throw Error(null!=t?t:"Unexpected invariant triggered.")}n.d(t,{Ye:()=>o,WU:()=>s,UG:()=>u});var i=n(45695);function a(e){var t=e.prototype.toJSON;"function"==typeof t||r(0),e.prototype.inspect=t,i.Z&&(e.prototype[i.Z]=t)}var o=function(){function e(e,t,n){this.start=e.start,this.end=t.end,this.startToken=e,this.endToken=t,this.source=n}return e.prototype.toJSON=function(){return{start:this.start,end:this.end}},e}();a(o);var s=function(){function e(e,t,n,r,i,a,o){this.kind=e,this.start=t,this.end=n,this.line=r,this.column=i,this.value=o,this.prev=a,this.next=null}return e.prototype.toJSON=function(){return{kind:this.kind,value:this.value,line:this.line,column:this.column}},e}();function u(e){return null!=e&&"string"==typeof e.kind}a(s)},87392(e,t,n){"use strict";function r(e){var t=e.split(/\r\n|[\n\r]/g),n=a(e);if(0!==n)for(var r=1;ro&&i(t[s-1]);)--s;return t.slice(o,s).join("\n")}function i(e){for(var t=0;t1&&void 0!==arguments[1]?arguments[1]:"",n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],r=-1===e.indexOf("\n"),i=" "===e[0]||" "===e[0],a='"'===e[e.length-1],o="\\"===e[e.length-1],s=!r||a||o||n,u="";return s&&!(r&&i)&&(u+="\n"+t),u+=t?e.replace(/\n/g,"\n"+t):e,s&&(u+="\n"),'"""'+u.replace(/"""/g,'\\"""')+'"""'}n.d(t,{LZ:()=>o,W7:()=>r})},97359(e,t,n){"use strict";n.d(t,{h:()=>r});var r=Object.freeze({NAME:"Name",DOCUMENT:"Document",OPERATION_DEFINITION:"OperationDefinition",VARIABLE_DEFINITION:"VariableDefinition",SELECTION_SET:"SelectionSet",FIELD:"Field",ARGUMENT:"Argument",FRAGMENT_SPREAD:"FragmentSpread",INLINE_FRAGMENT:"InlineFragment",FRAGMENT_DEFINITION:"FragmentDefinition",VARIABLE:"Variable",INT:"IntValue",FLOAT:"FloatValue",STRING:"StringValue",BOOLEAN:"BooleanValue",NULL:"NullValue",ENUM:"EnumValue",LIST:"ListValue",OBJECT:"ObjectValue",OBJECT_FIELD:"ObjectField",DIRECTIVE:"Directive",NAMED_TYPE:"NamedType",LIST_TYPE:"ListType",NON_NULL_TYPE:"NonNullType",SCHEMA_DEFINITION:"SchemaDefinition",OPERATION_TYPE_DEFINITION:"OperationTypeDefinition",SCALAR_TYPE_DEFINITION:"ScalarTypeDefinition",OBJECT_TYPE_DEFINITION:"ObjectTypeDefinition",FIELD_DEFINITION:"FieldDefinition",INPUT_VALUE_DEFINITION:"InputValueDefinition",INTERFACE_TYPE_DEFINITION:"InterfaceTypeDefinition",UNION_TYPE_DEFINITION:"UnionTypeDefinition",ENUM_TYPE_DEFINITION:"EnumTypeDefinition",ENUM_VALUE_DEFINITION:"EnumValueDefinition",INPUT_OBJECT_TYPE_DEFINITION:"InputObjectTypeDefinition",DIRECTIVE_DEFINITION:"DirectiveDefinition",SCHEMA_EXTENSION:"SchemaExtension",SCALAR_TYPE_EXTENSION:"ScalarTypeExtension",OBJECT_TYPE_EXTENSION:"ObjectTypeExtension",INTERFACE_TYPE_EXTENSION:"InterfaceTypeExtension",UNION_TYPE_EXTENSION:"UnionTypeExtension",ENUM_TYPE_EXTENSION:"EnumTypeExtension",INPUT_OBJECT_TYPE_EXTENSION:"InputObjectTypeExtension"})},10143(e,t,n){"use strict";n.d(t,{H:()=>c,T:()=>l});var r=n(99763),i=n(25821);function a(e,t){if(!Boolean(e))throw Error(t)}let o=function(e,t){return e instanceof t};function s(e,t){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:"GraphQL request",n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{line:1,column:1};"string"==typeof e||a(0,"Body must be a string. Received: ".concat((0,i.Z)(e),".")),this.body=e,this.name=t,this.locationOffset=n,this.locationOffset.line>0||a(0,"line in locationOffset is 1-indexed and must be positive."),this.locationOffset.column>0||a(0,"column in locationOffset is 1-indexed and must be positive.")}return u(e,[{key:r.YF,get:function(){return"Source"}}]),e}();function l(e){return o(e,c)}},99763(e,t,n){"use strict";n.d(t,{YF:()=>r});var r="function"==typeof Symbol&&null!=Symbol.toStringTag?Symbol.toStringTag:"@@toStringTag"},37452(e){"use strict";e.exports=JSON.parse('{"AElig":"\xc6","AMP":"&","Aacute":"\xc1","Acirc":"\xc2","Agrave":"\xc0","Aring":"\xc5","Atilde":"\xc3","Auml":"\xc4","COPY":"\xa9","Ccedil":"\xc7","ETH":"\xd0","Eacute":"\xc9","Ecirc":"\xca","Egrave":"\xc8","Euml":"\xcb","GT":">","Iacute":"\xcd","Icirc":"\xce","Igrave":"\xcc","Iuml":"\xcf","LT":"<","Ntilde":"\xd1","Oacute":"\xd3","Ocirc":"\xd4","Ograve":"\xd2","Oslash":"\xd8","Otilde":"\xd5","Ouml":"\xd6","QUOT":"\\"","REG":"\xae","THORN":"\xde","Uacute":"\xda","Ucirc":"\xdb","Ugrave":"\xd9","Uuml":"\xdc","Yacute":"\xdd","aacute":"\xe1","acirc":"\xe2","acute":"\xb4","aelig":"\xe6","agrave":"\xe0","amp":"&","aring":"\xe5","atilde":"\xe3","auml":"\xe4","brvbar":"\xa6","ccedil":"\xe7","cedil":"\xb8","cent":"\xa2","copy":"\xa9","curren":"\xa4","deg":"\xb0","divide":"\xf7","eacute":"\xe9","ecirc":"\xea","egrave":"\xe8","eth":"\xf0","euml":"\xeb","frac12":"\xbd","frac14":"\xbc","frac34":"\xbe","gt":">","iacute":"\xed","icirc":"\xee","iexcl":"\xa1","igrave":"\xec","iquest":"\xbf","iuml":"\xef","laquo":"\xab","lt":"<","macr":"\xaf","micro":"\xb5","middot":"\xb7","nbsp":"\xa0","not":"\xac","ntilde":"\xf1","oacute":"\xf3","ocirc":"\xf4","ograve":"\xf2","ordf":"\xaa","ordm":"\xba","oslash":"\xf8","otilde":"\xf5","ouml":"\xf6","para":"\xb6","plusmn":"\xb1","pound":"\xa3","quot":"\\"","raquo":"\xbb","reg":"\xae","sect":"\xa7","shy":"\xad","sup1":"\xb9","sup2":"\xb2","sup3":"\xb3","szlig":"\xdf","thorn":"\xfe","times":"\xd7","uacute":"\xfa","ucirc":"\xfb","ugrave":"\xf9","uml":"\xa8","uuml":"\xfc","yacute":"\xfd","yen":"\xa5","yuml":"\xff"}')},93580(e){"use strict";e.exports=JSON.parse('{"0":"�","128":"€","130":"‚","131":"ƒ","132":"„","133":"…","134":"†","135":"‡","136":"ˆ","137":"‰","138":"Š","139":"‹","140":"Œ","142":"Ž","145":"‘","146":"’","147":"“","148":"”","149":"•","150":"–","151":"—","152":"˜","153":"™","154":"š","155":"›","156":"œ","158":"ž","159":"Ÿ"}')},67946(e){"use strict";e.exports=JSON.parse('{"locale":"en","long":{"year":{"previous":"last year","current":"this year","next":"next year","past":{"one":"{0} year ago","other":"{0} years ago"},"future":{"one":"in {0} year","other":"in {0} years"}},"quarter":{"previous":"last quarter","current":"this quarter","next":"next quarter","past":{"one":"{0} quarter ago","other":"{0} quarters ago"},"future":{"one":"in {0} quarter","other":"in {0} quarters"}},"month":{"previous":"last month","current":"this month","next":"next month","past":{"one":"{0} month ago","other":"{0} months ago"},"future":{"one":"in {0} month","other":"in {0} months"}},"week":{"previous":"last week","current":"this week","next":"next week","past":{"one":"{0} week ago","other":"{0} weeks ago"},"future":{"one":"in {0} week","other":"in {0} weeks"}},"day":{"previous":"yesterday","current":"today","next":"tomorrow","past":{"one":"{0} day ago","other":"{0} days ago"},"future":{"one":"in {0} day","other":"in {0} days"}},"hour":{"current":"this hour","past":{"one":"{0} hour ago","other":"{0} hours ago"},"future":{"one":"in {0} hour","other":"in {0} hours"}},"minute":{"current":"this minute","past":{"one":"{0} minute ago","other":"{0} minutes ago"},"future":{"one":"in {0} minute","other":"in {0} minutes"}},"second":{"current":"now","past":{"one":"{0} second ago","other":"{0} seconds ago"},"future":{"one":"in {0} second","other":"in {0} seconds"}}},"short":{"year":{"previous":"last yr.","current":"this yr.","next":"next yr.","past":"{0} yr. ago","future":"in {0} yr."},"quarter":{"previous":"last qtr.","current":"this qtr.","next":"next qtr.","past":{"one":"{0} qtr. ago","other":"{0} qtrs. ago"},"future":{"one":"in {0} qtr.","other":"in {0} qtrs."}},"month":{"previous":"last mo.","current":"this mo.","next":"next mo.","past":"{0} mo. ago","future":"in {0} mo."},"week":{"previous":"last wk.","current":"this wk.","next":"next wk.","past":"{0} wk. ago","future":"in {0} wk."},"day":{"previous":"yesterday","current":"today","next":"tomorrow","past":{"one":"{0} day ago","other":"{0} days ago"},"future":{"one":"in {0} day","other":"in {0} days"}},"hour":{"current":"this hour","past":"{0} hr. ago","future":"in {0} hr."},"minute":{"current":"this minute","past":"{0} min. ago","future":"in {0} min."},"second":{"current":"now","past":"{0} sec. ago","future":"in {0} sec."}},"narrow":{"year":{"previous":"last yr.","current":"this yr.","next":"next yr.","past":"{0} yr. ago","future":"in {0} yr."},"quarter":{"previous":"last qtr.","current":"this qtr.","next":"next qtr.","past":{"one":"{0} qtr. ago","other":"{0} qtrs. ago"},"future":{"one":"in {0} qtr.","other":"in {0} qtrs."}},"month":{"previous":"last mo.","current":"this mo.","next":"next mo.","past":"{0} mo. ago","future":"in {0} mo."},"week":{"previous":"last wk.","current":"this wk.","next":"next wk.","past":"{0} wk. ago","future":"in {0} wk."},"day":{"previous":"yesterday","current":"today","next":"tomorrow","past":{"one":"{0} day ago","other":"{0} days ago"},"future":{"one":"in {0} day","other":"in {0} days"}},"hour":{"current":"this hour","past":"{0} hr. ago","future":"in {0} hr."},"minute":{"current":"this minute","past":"{0} min. ago","future":"in {0} min."},"second":{"current":"now","past":"{0} sec. ago","future":"in {0} sec."}},"now":{"now":{"current":"now","future":"in a moment","past":"just now"}},"mini":{"year":"{0}yr","month":"{0}mo","week":"{0}wk","day":"{0}d","hour":"{0}h","minute":"{0}m","second":"{0}s","now":"now"},"short-time":{"year":"{0} yr.","month":"{0} mo.","week":"{0} wk.","day":{"one":"{0} day","other":"{0} days"},"hour":"{0} hr.","minute":"{0} min.","second":"{0} sec."},"long-time":{"year":{"one":"{0} year","other":"{0} years"},"month":{"one":"{0} month","other":"{0} months"},"week":{"one":"{0} week","other":"{0} weeks"},"day":{"one":"{0} day","other":"{0} days"},"hour":{"one":"{0} hour","other":"{0} hours"},"minute":{"one":"{0} minute","other":"{0} minutes"},"second":{"one":"{0} second","other":"{0} seconds"}}}')}},__webpack_module_cache__={};function __webpack_require__(e){var t=__webpack_module_cache__[e];if(void 0!==t)return t.exports;var n=__webpack_module_cache__[e]={id:e,loaded:!1,exports:{}};return __webpack_modules__[e].call(n.exports,n,n.exports,__webpack_require__),n.loaded=!0,n.exports}__webpack_require__.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return __webpack_require__.d(t,{a:t}),t},(()=>{var e,t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__;__webpack_require__.t=function(n,r){if(1&r&&(n=this(n)),8&r||"object"==typeof n&&n&&(4&r&&n.__esModule||16&r&&"function"==typeof n.then))return n;var i=Object.create(null);__webpack_require__.r(i);var a={};e=e||[null,t({}),t([]),t(t)];for(var o=2&r&&n;"object"==typeof o&&!~e.indexOf(o);o=t(o))Object.getOwnPropertyNames(o).forEach(e=>a[e]=()=>n[e]);return a.default=()=>n,__webpack_require__.d(i,a),i}})(),__webpack_require__.d=(e,t)=>{for(var n in t)__webpack_require__.o(t,n)&&!__webpack_require__.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},__webpack_require__.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),__webpack_require__.hmd=e=>((e=Object.create(e)).children||(e.children=[]),Object.defineProperty(e,"exports",{enumerable:!0,set(){throw Error("ES Modules may not assign module.exports or exports.*, Use ESM export syntax, instead: "+e.id)}}),e),__webpack_require__.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),__webpack_require__.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},__webpack_require__.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),__webpack_require__.p="/assets/",__webpack_require__.nc=void 0;var __webpack_exports__={};(()=>{"use strict";var e,t,n,r,i=__webpack_require__(32316),a=__webpack_require__(8126),o=__webpack_require__(5690),s=__webpack_require__(30381),u=__webpack_require__.n(s),c=__webpack_require__(67294),l=__webpack_require__(73935),f=__webpack_require__.n(l),d=__webpack_require__(57209),h=__webpack_require__(37703),p=__webpack_require__(97779),b=__webpack_require__(28500);function m(e){return function(t){var n=t.dispatch,r=t.getState;return function(t){return function(i){return"function"==typeof i?i(n,r,e):t(i)}}}}var g=m();g.withExtraArgument=m;let v=g;var y=__webpack_require__(76489);function w(e){return function(t){return function(n){return function(r){n(r);var i=e||document&&document.cookie||"",a=t.getState();if("MATCH_ROUTE"===r.type&&"/signin"!==a.notifications.currentUrl){var o=(0,y.Q)(i);if(o.explorer)try{var s=JSON.parse(o.explorer);if("error"===s.status){var u=_(s.url);n({type:"NOTIFY_ERROR_MSG",msg:u})}}catch(c){n({type:"NOTIFY_ERROR_MSG",msg:"Invalid explorer status"})}}}}}}function _(e){var t="Can't connect to explorer: ".concat(e);return e.match(/^wss?:.+/)?t:"".concat(t,". You must use a websocket.")}var E=__webpack_require__(16353);function S(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function ei(e,t){if(e){if("string"==typeof e)return ea(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);if("Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return ea(e,t)}}function ea(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n1,i=!1,a=arguments[1],o=a;return new n(function(n){return t.subscribe({next:function(t){var a=!i;if(i=!0,!a||r)try{o=e(o,t)}catch(s){return n.error(s)}else o=t},error:function(e){n.error(e)},complete:function(){if(!i&&!r)return n.error(TypeError("Cannot reduce an empty sequence"));n.next(o),n.complete()}})})},t.concat=function(){for(var e=this,t=arguments.length,n=Array(t),r=0;r=0&&i.splice(e,1),o()}});i.push(s)},error:function(e){r.error(e)},complete:function(){o()}});function o(){a.closed&&0===i.length&&r.complete()}return function(){i.forEach(function(e){return e.unsubscribe()}),a.unsubscribe()}})},t[ed]=function(){return this},e.from=function(t){var n="function"==typeof this?this:e;if(null==t)throw TypeError(t+" is not an object");var r=ep(t,ed);if(r){var i=r.call(t);if(Object(i)!==i)throw TypeError(i+" is not an object");return em(i)&&i.constructor===n?i:new n(function(e){return i.subscribe(e)})}if(ec("iterator")&&(r=ep(t,ef)))return new n(function(e){ev(function(){if(!e.closed){for(var n,i=er(r.call(t));!(n=i()).done;){var a=n.value;if(e.next(a),e.closed)return}e.complete()}})});if(Array.isArray(t))return new n(function(e){ev(function(){if(!e.closed){for(var n=0;n0))return n.connection.key;var r=n.connection.filter?n.connection.filter:[];r.sort();var i={};return r.forEach(function(e){i[e]=t[e]}),"".concat(n.connection.key,"(").concat(eV(i),")")}var a=e;if(t){var o=eV(t);a+="(".concat(o,")")}return n&&Object.keys(n).forEach(function(e){-1===eW.indexOf(e)&&(n[e]&&Object.keys(n[e]).length?a+="@".concat(e,"(").concat(eV(n[e]),")"):a+="@".concat(e))}),a},{setStringify:function(e){var t=eV;return eV=e,t}}),eV=function(e){return JSON.stringify(e,eq)};function eq(e,t){return(0,eO.s)(t)&&!Array.isArray(t)&&(t=Object.keys(t).sort().reduce(function(e,n){return e[n]=t[n],e},{})),t}function eZ(e,t){if(e.arguments&&e.arguments.length){var n={};return e.arguments.forEach(function(e){var r;return ez(n,e.name,e.value,t)}),n}return null}function eX(e){return e.alias?e.alias.value:e.name.value}function eJ(e,t,n){for(var r,i=0,a=t.selections;it.indexOf(i))throw __DEV__?new Q.ej("illegal argument: ".concat(i)):new Q.ej(27)}return e}function tt(e,t){return t?t(e):eT.of()}function tn(e){return"function"==typeof e?new ta(e):e}function tr(e){return e.request.length<=1}var ti=function(e){function t(t,n){var r=e.call(this,t)||this;return r.link=n,r}return(0,en.ZT)(t,e),t}(Error),ta=function(){function e(e){e&&(this.request=e)}return e.empty=function(){return new e(function(){return eT.of()})},e.from=function(t){return 0===t.length?e.empty():t.map(tn).reduce(function(e,t){return e.concat(t)})},e.split=function(t,n,r){var i=tn(n),a=tn(r||new e(tt));return new e(tr(i)&&tr(a)?function(e){return t(e)?i.request(e)||eT.of():a.request(e)||eT.of()}:function(e,n){return t(e)?i.request(e,n)||eT.of():a.request(e,n)||eT.of()})},e.execute=function(e,t){return e.request(eM(t.context,e7(te(t))))||eT.of()},e.concat=function(t,n){var r=tn(t);if(tr(r))return __DEV__&&Q.kG.warn(new ti("You are calling concat on a terminating link, which will have no effect",r)),r;var i=tn(n);return new e(tr(i)?function(e){return r.request(e,function(e){return i.request(e)||eT.of()})||eT.of()}:function(e,t){return r.request(e,function(e){return i.request(e,t)||eT.of()})||eT.of()})},e.prototype.split=function(t,n,r){return this.concat(e.split(t,n,r||new e(tt)))},e.prototype.concat=function(t){return e.concat(this,t)},e.prototype.request=function(e,t){throw __DEV__?new Q.ej("request is not implemented"):new Q.ej(22)},e.prototype.onError=function(e,t){if(t&&t.error)return t.error(e),!1;throw e},e.prototype.setOnError=function(e){return this.onError=e,this},e}(),to=__webpack_require__(25821),ts=__webpack_require__(25217),tu={Name:[],Document:["definitions"],OperationDefinition:["name","variableDefinitions","directives","selectionSet"],VariableDefinition:["variable","type","defaultValue","directives"],Variable:["name"],SelectionSet:["selections"],Field:["alias","name","arguments","directives","selectionSet"],Argument:["name","value"],FragmentSpread:["name","directives"],InlineFragment:["typeCondition","directives","selectionSet"],FragmentDefinition:["name","variableDefinitions","typeCondition","directives","selectionSet"],IntValue:[],FloatValue:[],StringValue:[],BooleanValue:[],NullValue:[],EnumValue:[],ListValue:["values"],ObjectValue:["fields"],ObjectField:["name","value"],Directive:["name","arguments"],NamedType:["name"],ListType:["type"],NonNullType:["type"],SchemaDefinition:["description","directives","operationTypes"],OperationTypeDefinition:["type"],ScalarTypeDefinition:["description","name","directives"],ObjectTypeDefinition:["description","name","interfaces","directives","fields"],FieldDefinition:["description","name","arguments","type","directives"],InputValueDefinition:["description","name","type","defaultValue","directives"],InterfaceTypeDefinition:["description","name","interfaces","directives","fields"],UnionTypeDefinition:["description","name","directives","types"],EnumTypeDefinition:["description","name","directives","values"],EnumValueDefinition:["description","name","directives"],InputObjectTypeDefinition:["description","name","directives","fields"],DirectiveDefinition:["description","name","arguments","locations"],SchemaExtension:["directives","operationTypes"],ScalarTypeExtension:["name","directives"],ObjectTypeExtension:["name","interfaces","directives","fields"],InterfaceTypeExtension:["name","interfaces","directives","fields"],UnionTypeExtension:["name","directives","types"],EnumTypeExtension:["name","directives","values"],InputObjectTypeExtension:["name","directives","fields"]},tc=Object.freeze({});function tl(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:tu,r=void 0,i=Array.isArray(e),a=[e],o=-1,s=[],u=void 0,c=void 0,l=void 0,f=[],d=[],h=e;do{var p,b=++o===a.length,m=b&&0!==s.length;if(b){if(c=0===d.length?void 0:f[f.length-1],u=l,l=d.pop(),m){if(i)u=u.slice();else{for(var g={},v=0,y=Object.keys(u);v1)for(var r=new tB,i=1;i=0;--a){var o=i[a],s=isNaN(+o)?{}:[];s[o]=t,t=s}n=r.merge(n,t)}),n}var tW=Object.prototype.hasOwnProperty;function tK(e,t){var n,r,i,a,o;return(0,en.mG)(this,void 0,void 0,function(){var s,u,c,l,f,d,h,p,b,m,g,v,y,w,_,E,S,k,x,T,M,O,A;return(0,en.Jh)(this,function(L){switch(L.label){case 0:if(void 0===TextDecoder)throw Error("TextDecoder must be defined in the environment: please import a polyfill.");s=new TextDecoder("utf-8"),u=null===(n=e.headers)||void 0===n?void 0:n.get("content-type"),c="boundary=",l=(null==u?void 0:u.includes(c))?null==u?void 0:u.substring((null==u?void 0:u.indexOf(c))+c.length).replace(/['"]/g,"").replace(/\;(.*)/gm,"").trim():"-",f="\r\n--".concat(l),d="",h=tI(e),p=!0,L.label=1;case 1:if(!p)return[3,3];return[4,h.next()];case 2:for(m=(b=L.sent()).value,g=b.done,v="string"==typeof m?m:s.decode(m),y=d.length-f.length+1,p=!g,d+=v,w=d.indexOf(f,y);w>-1;){if(_=void 0,_=(O=[d.slice(0,w),d.slice(w+f.length),])[0],d=O[1],E=_.indexOf("\r\n\r\n"),(k=(S=tV(_.slice(0,E)))["content-type"])&&-1===k.toLowerCase().indexOf("application/json"))throw Error("Unsupported patch content type: application/json is required.");if(x=_.slice(E))try{T=tq(e,x),Object.keys(T).length>1||"data"in T||"incremental"in T||"errors"in T||"payload"in T?tz(T)?(M={},"payload"in T&&(M=(0,en.pi)({},T.payload)),"errors"in T&&(M=(0,en.pi)((0,en.pi)({},M),{extensions:(0,en.pi)((0,en.pi)({},"extensions"in M?M.extensions:null),((A={})[tN.YG]=T.errors,A))})),null===(r=t.next)||void 0===r||r.call(t,M)):null===(i=t.next)||void 0===i||i.call(t,T):1===Object.keys(T).length&&"hasNext"in T&&!T.hasNext&&(null===(a=t.complete)||void 0===a||a.call(t))}catch(C){tZ(C,t)}w=d.indexOf(f)}return[3,1];case 3:return null===(o=t.complete)||void 0===o||o.call(t),[2]}})})}function tV(e){var t={};return e.split("\n").forEach(function(e){var n=e.indexOf(":");if(n>-1){var r=e.slice(0,n).trim().toLowerCase(),i=e.slice(n+1).trim();t[r]=i}}),t}function tq(e,t){e.status>=300&&tD(e,function(){try{return JSON.parse(t)}catch(e){return t}}(),"Response not successful: Received status code ".concat(e.status));try{return JSON.parse(t)}catch(n){var r=n;throw r.name="ServerParseError",r.response=e,r.statusCode=e.status,r.bodyText=t,r}}function tZ(e,t){var n,r;"AbortError"!==e.name&&(e.result&&e.result.errors&&e.result.data&&(null===(n=t.next)||void 0===n||n.call(t,e.result)),null===(r=t.error)||void 0===r||r.call(t,e))}function tX(e,t,n){tJ(t)(e).then(function(e){var t,r;null===(t=n.next)||void 0===t||t.call(n,e),null===(r=n.complete)||void 0===r||r.call(n)}).catch(function(e){return tZ(e,n)})}function tJ(e){return function(t){return t.text().then(function(e){return tq(t,e)}).then(function(n){return t.status>=300&&tD(t,n,"Response not successful: Received status code ".concat(t.status)),Array.isArray(n)||tW.call(n,"data")||tW.call(n,"errors")||tD(t,n,"Server response was missing for query '".concat(Array.isArray(e)?e.map(function(e){return e.operationName}):e.operationName,"'.")),n})}}var tQ=function(e){if(!e&&"undefined"==typeof fetch)throw __DEV__?new Q.ej("\n\"fetch\" has not been found globally and no fetcher has been configured. To fix this, install a fetch package (like https://www.npmjs.com/package/cross-fetch), instantiate the fetcher, and pass it into your HttpLink constructor. For example:\n\nimport fetch from 'cross-fetch';\nimport { ApolloClient, HttpLink } from '@apollo/client';\nconst client = new ApolloClient({\n link: new HttpLink({ uri: '/graphql', fetch })\n});\n "):new Q.ej(23)},t1=__webpack_require__(87392);function t0(e){return tl(e,{leave:t3})}var t2=80,t3={Name:function(e){return e.value},Variable:function(e){return"$"+e.name},Document:function(e){return t6(e.definitions,"\n\n")+"\n"},OperationDefinition:function(e){var t=e.operation,n=e.name,r=t8("(",t6(e.variableDefinitions,", "),")"),i=t6(e.directives," "),a=e.selectionSet;return n||i||r||"query"!==t?t6([t,t6([n,r]),i,a]," "):a},VariableDefinition:function(e){var t=e.variable,n=e.type,r=e.defaultValue,i=e.directives;return t+": "+n+t8(" = ",r)+t8(" ",t6(i," "))},SelectionSet:function(e){return t5(e.selections)},Field:function(e){var t=e.alias,n=e.name,r=e.arguments,i=e.directives,a=e.selectionSet,o=t8("",t,": ")+n,s=o+t8("(",t6(r,", "),")");return s.length>t2&&(s=o+t8("(\n",t9(t6(r,"\n")),"\n)")),t6([s,t6(i," "),a]," ")},Argument:function(e){var t;return e.name+": "+e.value},FragmentSpread:function(e){var t;return"..."+e.name+t8(" ",t6(e.directives," "))},InlineFragment:function(e){var t=e.typeCondition,n=e.directives,r=e.selectionSet;return t6(["...",t8("on ",t),t6(n," "),r]," ")},FragmentDefinition:function(e){var t=e.name,n=e.typeCondition,r=e.variableDefinitions,i=e.directives,a=e.selectionSet;return"fragment ".concat(t).concat(t8("(",t6(r,", "),")")," ")+"on ".concat(n," ").concat(t8("",t6(i," ")," "))+a},IntValue:function(e){return e.value},FloatValue:function(e){return e.value},StringValue:function(e,t){var n=e.value;return e.block?(0,t1.LZ)(n,"description"===t?"":" "):JSON.stringify(n)},BooleanValue:function(e){return e.value?"true":"false"},NullValue:function(){return"null"},EnumValue:function(e){return e.value},ListValue:function(e){return"["+t6(e.values,", ")+"]"},ObjectValue:function(e){return"{"+t6(e.fields,", ")+"}"},ObjectField:function(e){var t;return e.name+": "+e.value},Directive:function(e){var t;return"@"+e.name+t8("(",t6(e.arguments,", "),")")},NamedType:function(e){return e.name},ListType:function(e){return"["+e.type+"]"},NonNullType:function(e){return e.type+"!"},SchemaDefinition:t4(function(e){var t=e.directives,n=e.operationTypes;return t6(["schema",t6(t," "),t5(n)]," ")}),OperationTypeDefinition:function(e){var t;return e.operation+": "+e.type},ScalarTypeDefinition:t4(function(e){var t;return t6(["scalar",e.name,t6(e.directives," ")]," ")}),ObjectTypeDefinition:t4(function(e){var t=e.name,n=e.interfaces,r=e.directives,i=e.fields;return t6(["type",t,t8("implements ",t6(n," & ")),t6(r," "),t5(i)]," ")}),FieldDefinition:t4(function(e){var t=e.name,n=e.arguments,r=e.type,i=e.directives;return t+(ne(n)?t8("(\n",t9(t6(n,"\n")),"\n)"):t8("(",t6(n,", "),")"))+": "+r+t8(" ",t6(i," "))}),InputValueDefinition:t4(function(e){var t=e.name,n=e.type,r=e.defaultValue,i=e.directives;return t6([t+": "+n,t8("= ",r),t6(i," ")]," ")}),InterfaceTypeDefinition:t4(function(e){var t=e.name,n=e.interfaces,r=e.directives,i=e.fields;return t6(["interface",t,t8("implements ",t6(n," & ")),t6(r," "),t5(i)]," ")}),UnionTypeDefinition:t4(function(e){var t=e.name,n=e.directives,r=e.types;return t6(["union",t,t6(n," "),r&&0!==r.length?"= "+t6(r," | "):""]," ")}),EnumTypeDefinition:t4(function(e){var t=e.name,n=e.directives,r=e.values;return t6(["enum",t,t6(n," "),t5(r)]," ")}),EnumValueDefinition:t4(function(e){var t;return t6([e.name,t6(e.directives," ")]," ")}),InputObjectTypeDefinition:t4(function(e){var t=e.name,n=e.directives,r=e.fields;return t6(["input",t,t6(n," "),t5(r)]," ")}),DirectiveDefinition:t4(function(e){var t=e.name,n=e.arguments,r=e.repeatable,i=e.locations;return"directive @"+t+(ne(n)?t8("(\n",t9(t6(n,"\n")),"\n)"):t8("(",t6(n,", "),")"))+(r?" repeatable":"")+" on "+t6(i," | ")}),SchemaExtension:function(e){var t=e.directives,n=e.operationTypes;return t6(["extend schema",t6(t," "),t5(n)]," ")},ScalarTypeExtension:function(e){var t;return t6(["extend scalar",e.name,t6(e.directives," ")]," ")},ObjectTypeExtension:function(e){var t=e.name,n=e.interfaces,r=e.directives,i=e.fields;return t6(["extend type",t,t8("implements ",t6(n," & ")),t6(r," "),t5(i)]," ")},InterfaceTypeExtension:function(e){var t=e.name,n=e.interfaces,r=e.directives,i=e.fields;return t6(["extend interface",t,t8("implements ",t6(n," & ")),t6(r," "),t5(i)]," ")},UnionTypeExtension:function(e){var t=e.name,n=e.directives,r=e.types;return t6(["extend union",t,t6(n," "),r&&0!==r.length?"= "+t6(r," | "):""]," ")},EnumTypeExtension:function(e){var t=e.name,n=e.directives,r=e.values;return t6(["extend enum",t,t6(n," "),t5(r)]," ")},InputObjectTypeExtension:function(e){var t=e.name,n=e.directives,r=e.fields;return t6(["extend input",t,t6(n," "),t5(r)]," ")}};function t4(e){return function(t){return t6([t.description,e(t)],"\n")}}function t6(e){var t,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return null!==(t=null==e?void 0:e.filter(function(e){return e}).join(n))&&void 0!==t?t:""}function t5(e){return t8("{\n",t9(t6(e,"\n")),"\n}")}function t8(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"";return null!=t&&""!==t?e+t+n:""}function t9(e){return t8(" ",e.replace(/\n/g,"\n "))}function t7(e){return -1!==e.indexOf("\n")}function ne(e){return null!=e&&e.some(t7)}var nt,nn,nr,ni={http:{includeQuery:!0,includeExtensions:!1,preserveHeaderCase:!1},headers:{accept:"*/*","content-type":"application/json"},options:{method:"POST"}},na=function(e,t){return t(e)};function no(e,t){for(var n=[],r=2;rObject.create(null),{forEach:nv,slice:ny}=Array.prototype,{hasOwnProperty:nw}=Object.prototype;class n_{constructor(e=!0,t=ng){this.weakness=e,this.makeData=t}lookup(...e){return this.lookupArray(e)}lookupArray(e){let t=this;return nv.call(e,e=>t=t.getChildTrie(e)),nw.call(t,"data")?t.data:t.data=this.makeData(ny.call(e))}peek(...e){return this.peekArray(e)}peekArray(e){let t=this;for(let n=0,r=e.length;t&&n=0;--o)t.definitions[o].kind===nL.h.OPERATION_DEFINITION&&++a;var s=nN(e),u=e.some(function(e){return e.remove}),c=function(e){return u&&e&&e.some(s)},l=new Map,f=!1,d={enter:function(e){if(c(e.directives))return f=!0,null}},h=tl(t,{Field:d,InlineFragment:d,VariableDefinition:{enter:function(){return!1}},Variable:{enter:function(e,t,n,r,a){var o=i(a);o&&o.variables.add(e.name.value)}},FragmentSpread:{enter:function(e,t,n,r,a){if(c(e.directives))return f=!0,null;var o=i(a);o&&o.fragmentSpreads.add(e.name.value)}},FragmentDefinition:{enter:function(e,t,n,r){l.set(JSON.stringify(r),e)},leave:function(e,t,n,i){return e===l.get(JSON.stringify(i))?e:a>0&&e.selectionSet.selections.every(function(e){return e.kind===nL.h.FIELD&&"__typename"===e.name.value})?(r(e.name.value).removed=!0,f=!0,null):void 0}},Directive:{leave:function(e){if(s(e))return f=!0,null}}});if(!f)return t;var p=function(e){return e.transitiveVars||(e.transitiveVars=new Set(e.variables),e.removed||e.fragmentSpreads.forEach(function(t){p(r(t)).transitiveVars.forEach(function(t){e.transitiveVars.add(t)})})),e},b=new Set;h.definitions.forEach(function(e){e.kind===nL.h.OPERATION_DEFINITION?p(n(e.name&&e.name.value)).fragmentSpreads.forEach(function(e){b.add(e)}):e.kind!==nL.h.FRAGMENT_DEFINITION||0!==a||r(e.name.value).removed||b.add(e.name.value)}),b.forEach(function(e){p(r(e)).fragmentSpreads.forEach(function(e){b.add(e)})});var m=function(e){return!!(!b.has(e)||r(e).removed)},g={enter:function(e){if(m(e.name.value))return null}};return nD(tl(h,{FragmentSpread:g,FragmentDefinition:g,OperationDefinition:{leave:function(e){if(e.variableDefinitions){var t=p(n(e.name&&e.name.value)).transitiveVars;if(t.size0},t.prototype.tearDownQuery=function(){this.isTornDown||(this.concast&&this.observer&&(this.concast.removeObserver(this.observer),delete this.concast,delete this.observer),this.stopPolling(),this.subscriptions.forEach(function(e){return e.unsubscribe()}),this.subscriptions.clear(),this.queryManager.stopQuery(this.queryId),this.observers.clear(),this.isTornDown=!0)},t}(eT);function n4(e){var t=e.options,n=t.fetchPolicy,r=t.nextFetchPolicy;return"cache-and-network"===n||"network-only"===n?e.reobserve({fetchPolicy:"cache-first",nextFetchPolicy:function(){return(this.nextFetchPolicy=r,"function"==typeof r)?r.apply(this,arguments):n}}):e.reobserve()}function n6(e){__DEV__&&Q.kG.error("Unhandled error",e.message,e.stack)}function n5(e){__DEV__&&e&&__DEV__&&Q.kG.debug("Missing cache result fields: ".concat(JSON.stringify(e)),e)}function n8(e){return"network-only"===e||"no-cache"===e||"standby"===e}nK(n3);function n9(e){return e.kind===nL.h.FIELD||e.kind===nL.h.FRAGMENT_SPREAD||e.kind===nL.h.INLINE_FRAGMENT}function n7(e){return e.kind===Kind.SCALAR_TYPE_DEFINITION||e.kind===Kind.OBJECT_TYPE_DEFINITION||e.kind===Kind.INTERFACE_TYPE_DEFINITION||e.kind===Kind.UNION_TYPE_DEFINITION||e.kind===Kind.ENUM_TYPE_DEFINITION||e.kind===Kind.INPUT_OBJECT_TYPE_DEFINITION}function re(e){return e.kind===Kind.SCALAR_TYPE_EXTENSION||e.kind===Kind.OBJECT_TYPE_EXTENSION||e.kind===Kind.INTERFACE_TYPE_EXTENSION||e.kind===Kind.UNION_TYPE_EXTENSION||e.kind===Kind.ENUM_TYPE_EXTENSION||e.kind===Kind.INPUT_OBJECT_TYPE_EXTENSION}var rt=function(){return Object.create(null)},rn=Array.prototype,rr=rn.forEach,ri=rn.slice,ra=function(){function e(e,t){void 0===e&&(e=!0),void 0===t&&(t=rt),this.weakness=e,this.makeData=t}return e.prototype.lookup=function(){for(var e=[],t=0;tclass{constructor(){this.id=["slot",rc++,Date.now(),Math.random().toString(36).slice(2),].join(":")}hasValue(){for(let e=rs;e;e=e.parent)if(this.id in e.slots){let t=e.slots[this.id];if(t===ru)break;return e!==rs&&(rs.slots[this.id]=t),!0}return rs&&(rs.slots[this.id]=ru),!1}getValue(){if(this.hasValue())return rs.slots[this.id]}withValue(e,t,n,r){let i={__proto__:null,[this.id]:e},a=rs;rs={parent:a,slots:i};try{return t.apply(r,n)}finally{rs=a}}static bind(e){let t=rs;return function(){let n=rs;try{return rs=t,e.apply(this,arguments)}finally{rs=n}}}static noContext(e,t,n){if(!rs)return e.apply(n,t);{let r=rs;try{return rs=null,e.apply(n,t)}finally{rs=r}}}};function rf(e){try{return e()}catch(t){}}let rd="@wry/context:Slot",rh=rf(()=>globalThis)||rf(()=>global)||Object.create(null),rp=rh,rb=rp[rd]||Array[rd]||function(e){try{Object.defineProperty(rp,rd,{value:e,enumerable:!1,writable:!1,configurable:!0})}finally{return e}}(rl()),{bind:rm,noContext:rg}=rb;function rv(){}var ry=function(){function e(e,t){void 0===e&&(e=1/0),void 0===t&&(t=rv),this.max=e,this.dispose=t,this.map=new Map,this.newest=null,this.oldest=null}return e.prototype.has=function(e){return this.map.has(e)},e.prototype.get=function(e){var t=this.getNode(e);return t&&t.value},e.prototype.getNode=function(e){var t=this.map.get(e);if(t&&t!==this.newest){var n=t.older,r=t.newer;r&&(r.older=n),n&&(n.newer=r),t.older=this.newest,t.older.newer=t,t.newer=null,this.newest=t,t===this.oldest&&(this.oldest=r)}return t},e.prototype.set=function(e,t){var n=this.getNode(e);return n?n.value=t:(n={key:e,value:t,newer:null,older:this.newest},this.newest&&(this.newest.newer=n),this.newest=n,this.oldest=this.oldest||n,this.map.set(e,n),n.value)},e.prototype.clean=function(){for(;this.oldest&&this.map.size>this.max;)this.delete(this.oldest.key)},e.prototype.delete=function(e){var t=this.map.get(e);return!!t&&(t===this.newest&&(this.newest=t.older),t===this.oldest&&(this.oldest=t.newer),t.newer&&(t.newer.older=t.older),t.older&&(t.older.newer=t.newer),this.map.delete(e),this.dispose(t.value,e),!0)},e}(),rw=new rb,r_=Object.prototype.hasOwnProperty,rE=void 0===(n=Array.from)?function(e){var t=[];return e.forEach(function(e){return t.push(e)}),t}:n;function rS(e){var t=e.unsubscribe;"function"==typeof t&&(e.unsubscribe=void 0,t())}var rk=[],rx=100;function rT(e,t){if(!e)throw Error(t||"assertion failure")}function rM(e,t){var n=e.length;return n>0&&n===t.length&&e[n-1]===t[n-1]}function rO(e){switch(e.length){case 0:throw Error("unknown value");case 1:return e[0];case 2:throw e[1]}}function rA(e){return e.slice(0)}var rL=function(){function e(t){this.fn=t,this.parents=new Set,this.childValues=new Map,this.dirtyChildren=null,this.dirty=!0,this.recomputing=!1,this.value=[],this.deps=null,++e.count}return e.prototype.peek=function(){if(1===this.value.length&&!rN(this))return rC(this),this.value[0]},e.prototype.recompute=function(e){return rT(!this.recomputing,"already recomputing"),rC(this),rN(this)?rI(this,e):rO(this.value)},e.prototype.setDirty=function(){this.dirty||(this.dirty=!0,this.value.length=0,rR(this),rS(this))},e.prototype.dispose=function(){var e=this;this.setDirty(),rH(this),rF(this,function(t,n){t.setDirty(),r$(t,e)})},e.prototype.forget=function(){this.dispose()},e.prototype.dependOn=function(e){e.add(this),this.deps||(this.deps=rk.pop()||new Set),this.deps.add(e)},e.prototype.forgetDeps=function(){var e=this;this.deps&&(rE(this.deps).forEach(function(t){return t.delete(e)}),this.deps.clear(),rk.push(this.deps),this.deps=null)},e.count=0,e}();function rC(e){var t=rw.getValue();if(t)return e.parents.add(t),t.childValues.has(e)||t.childValues.set(e,[]),rN(e)?rY(t,e):rB(t,e),t}function rI(e,t){return rH(e),rw.withValue(e,rD,[e,t]),rz(e,t)&&rP(e),rO(e.value)}function rD(e,t){e.recomputing=!0,e.value.length=0;try{e.value[0]=e.fn.apply(null,t)}catch(n){e.value[1]=n}e.recomputing=!1}function rN(e){return e.dirty||!!(e.dirtyChildren&&e.dirtyChildren.size)}function rP(e){e.dirty=!1,!rN(e)&&rj(e)}function rR(e){rF(e,rY)}function rj(e){rF(e,rB)}function rF(e,t){var n=e.parents.size;if(n)for(var r=rE(e.parents),i=0;i0&&e.childValues.forEach(function(t,n){r$(e,n)}),e.forgetDeps(),rT(null===e.dirtyChildren)}function r$(e,t){t.parents.delete(e),e.childValues.delete(t),rU(e,t)}function rz(e,t){if("function"==typeof e.subscribe)try{rS(e),e.unsubscribe=e.subscribe.apply(null,t)}catch(n){return e.setDirty(),!1}return!0}var rG={setDirty:!0,dispose:!0,forget:!0};function rW(e){var t=new Map,n=e&&e.subscribe;function r(e){var r=rw.getValue();if(r){var i=t.get(e);i||t.set(e,i=new Set),r.dependOn(i),"function"==typeof n&&(rS(i),i.unsubscribe=n(e))}}return r.dirty=function(e,n){var r=t.get(e);if(r){var i=n&&r_.call(rG,n)?n:"setDirty";rE(r).forEach(function(e){return e[i]()}),t.delete(e),rS(r)}},r}function rK(){var e=new ra("function"==typeof WeakMap);return function(){return e.lookupArray(arguments)}}var rV=rK(),rq=new Set;function rZ(e,t){void 0===t&&(t=Object.create(null));var n=new ry(t.max||65536,function(e){return e.dispose()}),r=t.keyArgs,i=t.makeCacheKey||rK(),a=function(){var a=i.apply(null,r?r.apply(null,arguments):arguments);if(void 0===a)return e.apply(null,arguments);var o=n.get(a);o||(n.set(a,o=new rL(e)),o.subscribe=t.subscribe,o.forget=function(){return n.delete(a)});var s=o.recompute(Array.prototype.slice.call(arguments));return n.set(a,o),rq.add(n),rw.hasValue()||(rq.forEach(function(e){return e.clean()}),rq.clear()),s};function o(e){var t=n.get(e);t&&t.setDirty()}function s(e){var t=n.get(e);if(t)return t.peek()}function u(e){return n.delete(e)}return Object.defineProperty(a,"size",{get:function(){return n.map.size},configurable:!1,enumerable:!1}),a.dirtyKey=o,a.dirty=function(){o(i.apply(null,arguments))},a.peekKey=s,a.peek=function(){return s(i.apply(null,arguments))},a.forgetKey=u,a.forget=function(){return u(i.apply(null,arguments))},a.makeCacheKey=i,a.getKey=r?function(){return i.apply(null,r.apply(null,arguments))}:i,Object.freeze(a)}var rX=new rb,rJ=new WeakMap;function rQ(e){var t=rJ.get(e);return t||rJ.set(e,t={vars:new Set,dep:rW()}),t}function r1(e){rQ(e).vars.forEach(function(t){return t.forgetCache(e)})}function r0(e){rQ(e).vars.forEach(function(t){return t.attachCache(e)})}function r2(e){var t=new Set,n=new Set,r=function(a){if(arguments.length>0){if(e!==a){e=a,t.forEach(function(e){rQ(e).dep.dirty(r),r3(e)});var o=Array.from(n);n.clear(),o.forEach(function(t){return t(e)})}}else{var s=rX.getValue();s&&(i(s),rQ(s).dep(r))}return e};r.onNextChange=function(e){return n.add(e),function(){n.delete(e)}};var i=r.attachCache=function(e){return t.add(e),rQ(e).vars.add(r),r};return r.forgetCache=function(e){return t.delete(e)},r}function r3(e){e.broadcastWatches&&e.broadcastWatches()}var r4=function(){function e(e){var t=e.cache,n=e.client,r=e.resolvers,i=e.fragmentMatcher;this.selectionsToResolveCache=new WeakMap,this.cache=t,n&&(this.client=n),r&&this.addResolvers(r),i&&this.setFragmentMatcher(i)}return e.prototype.addResolvers=function(e){var t=this;this.resolvers=this.resolvers||{},Array.isArray(e)?e.forEach(function(e){t.resolvers=tj(t.resolvers,e)}):this.resolvers=tj(this.resolvers,e)},e.prototype.setResolvers=function(e){this.resolvers={},this.addResolvers(e)},e.prototype.getResolvers=function(){return this.resolvers||{}},e.prototype.runResolvers=function(e){var t=e.document,n=e.remoteResult,r=e.context,i=e.variables,a=e.onlyRunForcedResolvers,o=void 0!==a&&a;return(0,en.mG)(this,void 0,void 0,function(){return(0,en.Jh)(this,function(e){return t?[2,this.resolveDocument(t,n.data,r,i,this.fragmentMatcher,o).then(function(e){return(0,en.pi)((0,en.pi)({},n),{data:e.result})})]:[2,n]})})},e.prototype.setFragmentMatcher=function(e){this.fragmentMatcher=e},e.prototype.getFragmentMatcher=function(){return this.fragmentMatcher},e.prototype.clientQuery=function(e){return tb(["client"],e)&&this.resolvers?e:null},e.prototype.serverQuery=function(e){return n$(e)},e.prototype.prepareContext=function(e){var t=this.cache;return(0,en.pi)((0,en.pi)({},e),{cache:t,getCacheKey:function(e){return t.identify(e)}})},e.prototype.addExportedVariables=function(e,t,n){return void 0===t&&(t={}),void 0===n&&(n={}),(0,en.mG)(this,void 0,void 0,function(){return(0,en.Jh)(this,function(r){return e?[2,this.resolveDocument(e,this.buildRootValueFromCache(e,t)||{},this.prepareContext(n),t).then(function(e){return(0,en.pi)((0,en.pi)({},t),e.exportedVariables)})]:[2,(0,en.pi)({},t)]})})},e.prototype.shouldForceResolvers=function(e){var t=!1;return tl(e,{Directive:{enter:function(e){if("client"===e.name.value&&e.arguments&&(t=e.arguments.some(function(e){return"always"===e.name.value&&"BooleanValue"===e.value.kind&&!0===e.value.value})))return tc}}}),t},e.prototype.buildRootValueFromCache=function(e,t){return this.cache.diff({query:nH(e),variables:t,returnPartialData:!0,optimistic:!1}).result},e.prototype.resolveDocument=function(e,t,n,r,i,a){return void 0===n&&(n={}),void 0===r&&(r={}),void 0===i&&(i=function(){return!0}),void 0===a&&(a=!1),(0,en.mG)(this,void 0,void 0,function(){var o,s,u,c,l,f,d,h,p,b,m;return(0,en.Jh)(this,function(g){return o=e8(e),s=e4(e),u=eL(s),c=this.collectSelectionsToResolve(o,u),f=(l=o.operation)?l.charAt(0).toUpperCase()+l.slice(1):"Query",d=this,h=d.cache,p=d.client,b={fragmentMap:u,context:(0,en.pi)((0,en.pi)({},n),{cache:h,client:p}),variables:r,fragmentMatcher:i,defaultOperationType:f,exportedVariables:{},selectionsToResolve:c,onlyRunForcedResolvers:a},m=!1,[2,this.resolveSelectionSet(o.selectionSet,m,t,b).then(function(e){return{result:e,exportedVariables:b.exportedVariables}})]})})},e.prototype.resolveSelectionSet=function(e,t,n,r){return(0,en.mG)(this,void 0,void 0,function(){var i,a,o,s,u,c=this;return(0,en.Jh)(this,function(l){return i=r.fragmentMap,a=r.context,o=r.variables,s=[n],u=function(e){return(0,en.mG)(c,void 0,void 0,function(){var u,c;return(0,en.Jh)(this,function(l){return(t||r.selectionsToResolve.has(e))&&td(e,o)?eQ(e)?[2,this.resolveField(e,t,n,r).then(function(t){var n;void 0!==t&&s.push(((n={})[eX(e)]=t,n))})]:(e1(e)?u=e:(u=i[e.name.value],__DEV__?(0,Q.kG)(u,"No fragment named ".concat(e.name.value)):(0,Q.kG)(u,11)),u&&u.typeCondition&&(c=u.typeCondition.name.value,r.fragmentMatcher(n,c,a)))?[2,this.resolveSelectionSet(u.selectionSet,t,n,r).then(function(e){s.push(e)})]:[2]:[2]})})},[2,Promise.all(e.selections.map(u)).then(function(){return tF(s)})]})})},e.prototype.resolveField=function(e,t,n,r){return(0,en.mG)(this,void 0,void 0,function(){var i,a,o,s,u,c,l,f,d,h=this;return(0,en.Jh)(this,function(p){return n?(i=r.variables,a=e.name.value,o=eX(e),s=a!==o,c=Promise.resolve(u=n[o]||n[a]),(!r.onlyRunForcedResolvers||this.shouldForceResolvers(e))&&(l=n.__typename||r.defaultOperationType,(f=this.resolvers&&this.resolvers[l])&&(d=f[s?a:o])&&(c=Promise.resolve(rX.withValue(this.cache,d,[n,eZ(e,i),r.context,{field:e,fragmentMap:r.fragmentMap},])))),[2,c.then(function(n){if(void 0===n&&(n=u),e.directives&&e.directives.forEach(function(e){"export"===e.name.value&&e.arguments&&e.arguments.forEach(function(e){"as"===e.name.value&&"StringValue"===e.value.kind&&(r.exportedVariables[e.value.value]=n)})}),!e.selectionSet||null==n)return n;var i,a,o=null!==(a=null===(i=e.directives)||void 0===i?void 0:i.some(function(e){return"client"===e.name.value}))&&void 0!==a&&a;return Array.isArray(n)?h.resolveSubSelectedArray(e,t||o,n,r):e.selectionSet?h.resolveSelectionSet(e.selectionSet,t||o,n,r):void 0})]):[2,null]})})},e.prototype.resolveSubSelectedArray=function(e,t,n,r){var i=this;return Promise.all(n.map(function(n){return null===n?null:Array.isArray(n)?i.resolveSubSelectedArray(e,t,n,r):e.selectionSet?i.resolveSelectionSet(e.selectionSet,t,n,r):void 0}))},e.prototype.collectSelectionsToResolve=function(e,t){var n=function(e){return!Array.isArray(e)},r=this.selectionsToResolveCache;function i(e){if(!r.has(e)){var a=new Set;r.set(e,a),tl(e,{Directive:function(e,t,r,i,o){"client"===e.name.value&&o.forEach(function(e){n(e)&&n9(e)&&a.add(e)})},FragmentSpread:function(e,r,o,s,u){var c=t[e.name.value];__DEV__?(0,Q.kG)(c,"No fragment named ".concat(e.name.value)):(0,Q.kG)(c,12);var l=i(c);l.size>0&&(u.forEach(function(e){n(e)&&n9(e)&&a.add(e)}),a.add(e),l.forEach(function(e){a.add(e)}))}})}return r.get(e)}return i(e)},e}(),r6=new(t_.mr?WeakMap:Map);function r5(e,t){var n=e[t];"function"==typeof n&&(e[t]=function(){return r6.set(e,(r6.get(e)+1)%1e15),n.apply(this,arguments)})}function r8(e){e.notifyTimeout&&(clearTimeout(e.notifyTimeout),e.notifyTimeout=void 0)}var r9=function(){function e(e,t){void 0===t&&(t=e.generateQueryId()),this.queryId=t,this.listeners=new Set,this.document=null,this.lastRequestId=1,this.subscriptions=new Set,this.stopped=!1,this.dirty=!1,this.observableQuery=null;var n=this.cache=e.cache;r6.has(n)||(r6.set(n,0),r5(n,"evict"),r5(n,"modify"),r5(n,"reset"))}return e.prototype.init=function(e){var t=e.networkStatus||nZ.I.loading;return this.variables&&this.networkStatus!==nZ.I.loading&&!(0,nm.D)(this.variables,e.variables)&&(t=nZ.I.setVariables),(0,nm.D)(e.variables,this.variables)||(this.lastDiff=void 0),Object.assign(this,{document:e.document,variables:e.variables,networkError:null,graphQLErrors:this.graphQLErrors||[],networkStatus:t}),e.observableQuery&&this.setObservableQuery(e.observableQuery),e.lastRequestId&&(this.lastRequestId=e.lastRequestId),this},e.prototype.reset=function(){r8(this),this.dirty=!1},e.prototype.getDiff=function(e){void 0===e&&(e=this.variables);var t=this.getDiffOptions(e);if(this.lastDiff&&(0,nm.D)(t,this.lastDiff.options))return this.lastDiff.diff;this.updateWatch(this.variables=e);var n=this.observableQuery;if(n&&"no-cache"===n.options.fetchPolicy)return{complete:!1};var r=this.cache.diff(t);return this.updateLastDiff(r,t),r},e.prototype.updateLastDiff=function(e,t){this.lastDiff=e?{diff:e,options:t||this.getDiffOptions()}:void 0},e.prototype.getDiffOptions=function(e){var t;return void 0===e&&(e=this.variables),{query:this.document,variables:e,returnPartialData:!0,optimistic:!0,canonizeResults:null===(t=this.observableQuery)||void 0===t?void 0:t.options.canonizeResults}},e.prototype.setDiff=function(e){var t=this,n=this.lastDiff&&this.lastDiff.diff;this.updateLastDiff(e),this.dirty||(0,nm.D)(n&&n.result,e&&e.result)||(this.dirty=!0,this.notifyTimeout||(this.notifyTimeout=setTimeout(function(){return t.notify()},0)))},e.prototype.setObservableQuery=function(e){var t=this;e!==this.observableQuery&&(this.oqListener&&this.listeners.delete(this.oqListener),this.observableQuery=e,e?(e.queryInfo=this,this.listeners.add(this.oqListener=function(){t.getDiff().fromOptimisticTransaction?e.observe():n4(e)})):delete this.oqListener)},e.prototype.notify=function(){var e=this;r8(this),this.shouldNotify()&&this.listeners.forEach(function(t){return t(e)}),this.dirty=!1},e.prototype.shouldNotify=function(){if(!this.dirty||!this.listeners.size)return!1;if((0,nZ.O)(this.networkStatus)&&this.observableQuery){var e=this.observableQuery.options.fetchPolicy;if("cache-only"!==e&&"cache-and-network"!==e)return!1}return!0},e.prototype.stop=function(){if(!this.stopped){this.stopped=!0,this.reset(),this.cancel(),this.cancel=e.prototype.cancel,this.subscriptions.forEach(function(e){return e.unsubscribe()});var t=this.observableQuery;t&&t.stopPolling()}},e.prototype.cancel=function(){},e.prototype.updateWatch=function(e){var t=this;void 0===e&&(e=this.variables);var n=this.observableQuery;if(!n||"no-cache"!==n.options.fetchPolicy){var r=(0,en.pi)((0,en.pi)({},this.getDiffOptions(e)),{watcher:this,callback:function(e){return t.setDiff(e)}});this.lastWatch&&(0,nm.D)(r,this.lastWatch)||(this.cancel(),this.cancel=this.cache.watch(this.lastWatch=r))}},e.prototype.resetLastWrite=function(){this.lastWrite=void 0},e.prototype.shouldWrite=function(e,t){var n=this.lastWrite;return!(n&&n.dmCount===r6.get(this.cache)&&(0,nm.D)(t,n.variables)&&(0,nm.D)(e.data,n.result.data))},e.prototype.markResult=function(e,t,n,r){var i=this,a=new tB,o=(0,tP.O)(e.errors)?e.errors.slice(0):[];if(this.reset(),"incremental"in e&&(0,tP.O)(e.incremental)){var s=tG(this.getDiff().result,e);e.data=s}else if("hasNext"in e&&e.hasNext){var u=this.getDiff();e.data=a.merge(u.result,e.data)}this.graphQLErrors=o,"no-cache"===n.fetchPolicy?this.updateLastDiff({result:e.data,complete:!0},this.getDiffOptions(n.variables)):0!==r&&(r7(e,n.errorPolicy)?this.cache.performTransaction(function(a){if(i.shouldWrite(e,n.variables))a.writeQuery({query:t,data:e.data,variables:n.variables,overwrite:1===r}),i.lastWrite={result:e,variables:n.variables,dmCount:r6.get(i.cache)};else if(i.lastDiff&&i.lastDiff.diff.complete){e.data=i.lastDiff.diff.result;return}var o=i.getDiffOptions(n.variables),s=a.diff(o);i.stopped||i.updateWatch(n.variables),i.updateLastDiff(s,o),s.complete&&(e.data=s.result)}):this.lastWrite=void 0)},e.prototype.markReady=function(){return this.networkError=null,this.networkStatus=nZ.I.ready},e.prototype.markError=function(e){return this.networkStatus=nZ.I.error,this.lastWrite=void 0,this.reset(),e.graphQLErrors&&(this.graphQLErrors=e.graphQLErrors),e.networkError&&(this.networkError=e.networkError),e},e}();function r7(e,t){void 0===t&&(t="none");var n="ignore"===t||"all"===t,r=!nO(e);return!r&&n&&e.data&&(r=!0),r}var ie=Object.prototype.hasOwnProperty,it=function(){function e(e){var t=e.cache,n=e.link,r=e.defaultOptions,i=e.queryDeduplication,a=void 0!==i&&i,o=e.onBroadcast,s=e.ssrMode,u=void 0!==s&&s,c=e.clientAwareness,l=void 0===c?{}:c,f=e.localState,d=e.assumeImmutableResults;this.clientAwareness={},this.queries=new Map,this.fetchCancelFns=new Map,this.transformCache=new(t_.mr?WeakMap:Map),this.queryIdCounter=1,this.requestIdCounter=1,this.mutationIdCounter=1,this.inFlightLinkObservables=new Map,this.cache=t,this.link=n,this.defaultOptions=r||Object.create(null),this.queryDeduplication=a,this.clientAwareness=l,this.localState=f||new r4({cache:t}),this.ssrMode=u,this.assumeImmutableResults=!!d,(this.onBroadcast=o)&&(this.mutationStore=Object.create(null))}return e.prototype.stop=function(){var e=this;this.queries.forEach(function(t,n){e.stopQueryNoBroadcast(n)}),this.cancelPendingFetches(__DEV__?new Q.ej("QueryManager stopped while query was in flight"):new Q.ej(14))},e.prototype.cancelPendingFetches=function(e){this.fetchCancelFns.forEach(function(t){return t(e)}),this.fetchCancelFns.clear()},e.prototype.mutate=function(e){var t,n,r=e.mutation,i=e.variables,a=e.optimisticResponse,o=e.updateQueries,s=e.refetchQueries,u=void 0===s?[]:s,c=e.awaitRefetchQueries,l=void 0!==c&&c,f=e.update,d=e.onQueryUpdated,h=e.fetchPolicy,p=void 0===h?(null===(t=this.defaultOptions.mutate)||void 0===t?void 0:t.fetchPolicy)||"network-only":h,b=e.errorPolicy,m=void 0===b?(null===(n=this.defaultOptions.mutate)||void 0===n?void 0:n.errorPolicy)||"none":b,g=e.keepRootFields,v=e.context;return(0,en.mG)(this,void 0,void 0,function(){var e,t,n,s,c,h;return(0,en.Jh)(this,function(b){switch(b.label){case 0:if(__DEV__?(0,Q.kG)(r,"mutation option is required. You must specify your GraphQL document in the mutation option."):(0,Q.kG)(r,15),__DEV__?(0,Q.kG)("network-only"===p||"no-cache"===p,"Mutations support only 'network-only' or 'no-cache' fetchPolicy strings. The default `network-only` behavior automatically writes mutation results to the cache. Passing `no-cache` skips the cache write."):(0,Q.kG)("network-only"===p||"no-cache"===p,16),e=this.generateMutationId(),n=(t=this.transform(r)).document,s=t.hasClientExports,r=this.cache.transformForLink(n),i=this.getVariables(r,i),!s)return[3,2];return[4,this.localState.addExportedVariables(r,i,v)];case 1:i=b.sent(),b.label=2;case 2:return c=this.mutationStore&&(this.mutationStore[e]={mutation:r,variables:i,loading:!0,error:null}),a&&this.markMutationOptimistic(a,{mutationId:e,document:r,variables:i,fetchPolicy:p,errorPolicy:m,context:v,updateQueries:o,update:f,keepRootFields:g}),this.broadcastQueries(),h=this,[2,new Promise(function(t,n){return nM(h.getObservableFromLink(r,(0,en.pi)((0,en.pi)({},v),{optimisticResponse:a}),i,!1),function(t){if(nO(t)&&"none"===m)throw new tN.cA({graphQLErrors:nA(t)});c&&(c.loading=!1,c.error=null);var n=(0,en.pi)({},t);return"function"==typeof u&&(u=u(n)),"ignore"===m&&nO(n)&&delete n.errors,h.markMutationResult({mutationId:e,result:n,document:r,variables:i,fetchPolicy:p,errorPolicy:m,context:v,update:f,updateQueries:o,awaitRefetchQueries:l,refetchQueries:u,removeOptimistic:a?e:void 0,onQueryUpdated:d,keepRootFields:g})}).subscribe({next:function(e){h.broadcastQueries(),"hasNext"in e&&!1!==e.hasNext||t(e)},error:function(t){c&&(c.loading=!1,c.error=t),a&&h.cache.removeOptimistic(e),h.broadcastQueries(),n(t instanceof tN.cA?t:new tN.cA({networkError:t}))}})})]}})})},e.prototype.markMutationResult=function(e,t){var n=this;void 0===t&&(t=this.cache);var r=e.result,i=[],a="no-cache"===e.fetchPolicy;if(!a&&r7(r,e.errorPolicy)){if(tU(r)||i.push({result:r.data,dataId:"ROOT_MUTATION",query:e.document,variables:e.variables}),tU(r)&&(0,tP.O)(r.incremental)){var o=t.diff({id:"ROOT_MUTATION",query:this.transform(e.document).asQuery,variables:e.variables,optimistic:!1,returnPartialData:!0}),s=void 0;o.result&&(s=tG(o.result,r)),void 0!==s&&(r.data=s,i.push({result:s,dataId:"ROOT_MUTATION",query:e.document,variables:e.variables}))}var u=e.updateQueries;u&&this.queries.forEach(function(e,a){var o=e.observableQuery,s=o&&o.queryName;if(s&&ie.call(u,s)){var c,l=u[s],f=n.queries.get(a),d=f.document,h=f.variables,p=t.diff({query:d,variables:h,returnPartialData:!0,optimistic:!1}),b=p.result;if(p.complete&&b){var m=l(b,{mutationResult:r,queryName:d&&e3(d)||void 0,queryVariables:h});m&&i.push({result:m,dataId:"ROOT_QUERY",query:d,variables:h})}}})}if(i.length>0||e.refetchQueries||e.update||e.onQueryUpdated||e.removeOptimistic){var c=[];if(this.refetchQueries({updateCache:function(t){a||i.forEach(function(e){return t.write(e)});var o=e.update,s=!t$(r)||tU(r)&&!r.hasNext;if(o){if(!a){var u=t.diff({id:"ROOT_MUTATION",query:n.transform(e.document).asQuery,variables:e.variables,optimistic:!1,returnPartialData:!0});u.complete&&("incremental"in(r=(0,en.pi)((0,en.pi)({},r),{data:u.result}))&&delete r.incremental,"hasNext"in r&&delete r.hasNext)}s&&o(t,r,{context:e.context,variables:e.variables})}a||e.keepRootFields||!s||t.modify({id:"ROOT_MUTATION",fields:function(e,t){var n=t.fieldName,r=t.DELETE;return"__typename"===n?e:r}})},include:e.refetchQueries,optimistic:!1,removeOptimistic:e.removeOptimistic,onQueryUpdated:e.onQueryUpdated||null}).forEach(function(e){return c.push(e)}),e.awaitRefetchQueries||e.onQueryUpdated)return Promise.all(c).then(function(){return r})}return Promise.resolve(r)},e.prototype.markMutationOptimistic=function(e,t){var n=this,r="function"==typeof e?e(t.variables):e;return this.cache.recordOptimisticTransaction(function(e){try{n.markMutationResult((0,en.pi)((0,en.pi)({},t),{result:{data:r}}),e)}catch(i){__DEV__&&Q.kG.error(i)}},t.mutationId)},e.prototype.fetchQuery=function(e,t,n){return this.fetchQueryObservable(e,t,n).promise},e.prototype.getQueryStore=function(){var e=Object.create(null);return this.queries.forEach(function(t,n){e[n]={variables:t.variables,networkStatus:t.networkStatus,networkError:t.networkError,graphQLErrors:t.graphQLErrors}}),e},e.prototype.resetErrors=function(e){var t=this.queries.get(e);t&&(t.networkError=void 0,t.graphQLErrors=[])},e.prototype.transform=function(e){var t=this.transformCache;if(!t.has(e)){var n=this.cache.transformDocument(e),r=nY(n),i=this.localState.clientQuery(n),a=r&&this.localState.serverQuery(r),o={document:n,hasClientExports:tm(n),hasForcedResolvers:this.localState.shouldForceResolvers(n),clientQuery:i,serverQuery:a,defaultVars:e9(e2(n)),asQuery:(0,en.pi)((0,en.pi)({},n),{definitions:n.definitions.map(function(e){return"OperationDefinition"===e.kind&&"query"!==e.operation?(0,en.pi)((0,en.pi)({},e),{operation:"query"}):e})})},s=function(e){e&&!t.has(e)&&t.set(e,o)};s(e),s(n),s(i),s(a)}return t.get(e)},e.prototype.getVariables=function(e,t){return(0,en.pi)((0,en.pi)({},this.transform(e).defaultVars),t)},e.prototype.watchQuery=function(e){void 0===(e=(0,en.pi)((0,en.pi)({},e),{variables:this.getVariables(e.query,e.variables)})).notifyOnNetworkStatusChange&&(e.notifyOnNetworkStatusChange=!1);var t=new r9(this),n=new n3({queryManager:this,queryInfo:t,options:e});return this.queries.set(n.queryId,t),t.init({document:n.query,observableQuery:n,variables:n.variables}),n},e.prototype.query=function(e,t){var n=this;return void 0===t&&(t=this.generateQueryId()),__DEV__?(0,Q.kG)(e.query,"query option is required. You must specify your GraphQL document in the query option."):(0,Q.kG)(e.query,17),__DEV__?(0,Q.kG)("Document"===e.query.kind,'You must wrap the query string in a "gql" tag.'):(0,Q.kG)("Document"===e.query.kind,18),__DEV__?(0,Q.kG)(!e.returnPartialData,"returnPartialData option only supported on watchQuery."):(0,Q.kG)(!e.returnPartialData,19),__DEV__?(0,Q.kG)(!e.pollInterval,"pollInterval option only supported on watchQuery."):(0,Q.kG)(!e.pollInterval,20),this.fetchQuery(t,e).finally(function(){return n.stopQuery(t)})},e.prototype.generateQueryId=function(){return String(this.queryIdCounter++)},e.prototype.generateRequestId=function(){return this.requestIdCounter++},e.prototype.generateMutationId=function(){return String(this.mutationIdCounter++)},e.prototype.stopQueryInStore=function(e){this.stopQueryInStoreNoBroadcast(e),this.broadcastQueries()},e.prototype.stopQueryInStoreNoBroadcast=function(e){var t=this.queries.get(e);t&&t.stop()},e.prototype.clearStore=function(e){return void 0===e&&(e={discardWatches:!0}),this.cancelPendingFetches(__DEV__?new Q.ej("Store reset while query was in flight (not completed in link chain)"):new Q.ej(21)),this.queries.forEach(function(e){e.observableQuery?e.networkStatus=nZ.I.loading:e.stop()}),this.mutationStore&&(this.mutationStore=Object.create(null)),this.cache.reset(e)},e.prototype.getObservableQueries=function(e){var t=this;void 0===e&&(e="active");var n=new Map,r=new Map,i=new Set;return Array.isArray(e)&&e.forEach(function(e){"string"==typeof e?r.set(e,!1):eN(e)?r.set(t.transform(e).document,!1):(0,eO.s)(e)&&e.query&&i.add(e)}),this.queries.forEach(function(t,i){var a=t.observableQuery,o=t.document;if(a){if("all"===e){n.set(i,a);return}var s=a.queryName;if("standby"===a.options.fetchPolicy||"active"===e&&!a.hasObservers())return;("active"===e||s&&r.has(s)||o&&r.has(o))&&(n.set(i,a),s&&r.set(s,!0),o&&r.set(o,!0))}}),i.size&&i.forEach(function(e){var r=nG("legacyOneTimeQuery"),i=t.getQuery(r).init({document:e.query,variables:e.variables}),a=new n3({queryManager:t,queryInfo:i,options:(0,en.pi)((0,en.pi)({},e),{fetchPolicy:"network-only"})});(0,Q.kG)(a.queryId===r),i.setObservableQuery(a),n.set(r,a)}),__DEV__&&r.size&&r.forEach(function(e,t){!e&&__DEV__&&Q.kG.warn("Unknown query ".concat("string"==typeof t?"named ":"").concat(JSON.stringify(t,null,2)," requested in refetchQueries options.include array"))}),n},e.prototype.reFetchObservableQueries=function(e){var t=this;void 0===e&&(e=!1);var n=[];return this.getObservableQueries(e?"all":"active").forEach(function(r,i){var a=r.options.fetchPolicy;r.resetLastResults(),(e||"standby"!==a&&"cache-only"!==a)&&n.push(r.refetch()),t.getQuery(i).setDiff(null)}),this.broadcastQueries(),Promise.all(n)},e.prototype.setObservableQuery=function(e){this.getQuery(e.queryId).setObservableQuery(e)},e.prototype.startGraphQLSubscription=function(e){var t=this,n=e.query,r=e.fetchPolicy,i=e.errorPolicy,a=e.variables,o=e.context,s=void 0===o?{}:o;n=this.transform(n).document,a=this.getVariables(n,a);var u=function(e){return t.getObservableFromLink(n,s,e).map(function(a){"no-cache"!==r&&(r7(a,i)&&t.cache.write({query:n,result:a.data,dataId:"ROOT_SUBSCRIPTION",variables:e}),t.broadcastQueries());var o=nO(a),s=(0,tN.ls)(a);if(o||s){var u={};throw o&&(u.graphQLErrors=a.errors),s&&(u.protocolErrors=a.extensions[tN.YG]),new tN.cA(u)}return a})};if(this.transform(n).hasClientExports){var c=this.localState.addExportedVariables(n,a,s).then(u);return new eT(function(e){var t=null;return c.then(function(n){return t=n.subscribe(e)},e.error),function(){return t&&t.unsubscribe()}})}return u(a)},e.prototype.stopQuery=function(e){this.stopQueryNoBroadcast(e),this.broadcastQueries()},e.prototype.stopQueryNoBroadcast=function(e){this.stopQueryInStoreNoBroadcast(e),this.removeQuery(e)},e.prototype.removeQuery=function(e){this.fetchCancelFns.delete(e),this.queries.has(e)&&(this.getQuery(e).stop(),this.queries.delete(e))},e.prototype.broadcastQueries=function(){this.onBroadcast&&this.onBroadcast(),this.queries.forEach(function(e){return e.notify()})},e.prototype.getLocalState=function(){return this.localState},e.prototype.getObservableFromLink=function(e,t,n,r){var i,a,o=this;void 0===r&&(r=null!==(i=null==t?void 0:t.queryDeduplication)&&void 0!==i?i:this.queryDeduplication);var s=this.transform(e).serverQuery;if(s){var u=this,c=u.inFlightLinkObservables,l=u.link,f={query:s,variables:n,operationName:e3(s)||void 0,context:this.prepareContext((0,en.pi)((0,en.pi)({},t),{forceFetch:!r}))};if(t=f.context,r){var d=c.get(s)||new Map;c.set(s,d);var h=nx(n);if(!(a=d.get(h))){var p=new nq([np(l,f)]);d.set(h,a=p),p.beforeNext(function(){d.delete(h)&&d.size<1&&c.delete(s)})}}else a=new nq([np(l,f)])}else a=new nq([eT.of({data:{}})]),t=this.prepareContext(t);var b=this.transform(e).clientQuery;return b&&(a=nM(a,function(e){return o.localState.runResolvers({document:b,remoteResult:e,context:t,variables:n})})),a},e.prototype.getResultsFromLink=function(e,t,n){var r=e.lastRequestId=this.generateRequestId(),i=this.cache.transformForLink(this.transform(e.document).document);return nM(this.getObservableFromLink(i,n.context,n.variables),function(a){var o=nA(a),s=o.length>0;if(r>=e.lastRequestId){if(s&&"none"===n.errorPolicy)throw e.markError(new tN.cA({graphQLErrors:o}));e.markResult(a,i,n,t),e.markReady()}var u={data:a.data,loading:!1,networkStatus:nZ.I.ready};return s&&"ignore"!==n.errorPolicy&&(u.errors=o,u.networkStatus=nZ.I.error),u},function(t){var n=(0,tN.MS)(t)?t:new tN.cA({networkError:t});throw r>=e.lastRequestId&&e.markError(n),n})},e.prototype.fetchQueryObservable=function(e,t,n){return this.fetchConcastWithInfo(e,t,n).concast},e.prototype.fetchConcastWithInfo=function(e,t,n){var r,i,a=this;void 0===n&&(n=nZ.I.loading);var o=this.transform(t.query).document,s=this.getVariables(o,t.variables),u=this.getQuery(e),c=this.defaultOptions.watchQuery,l=t.fetchPolicy,f=void 0===l?c&&c.fetchPolicy||"cache-first":l,d=t.errorPolicy,h=void 0===d?c&&c.errorPolicy||"none":d,p=t.returnPartialData,b=void 0!==p&&p,m=t.notifyOnNetworkStatusChange,g=void 0!==m&&m,v=t.context,y=void 0===v?{}:v,w=Object.assign({},t,{query:o,variables:s,fetchPolicy:f,errorPolicy:h,returnPartialData:b,notifyOnNetworkStatusChange:g,context:y}),_=function(e){w.variables=e;var r=a.fetchQueryByPolicy(u,w,n);return"standby"!==w.fetchPolicy&&r.sources.length>0&&u.observableQuery&&u.observableQuery.applyNextFetchPolicy("after-fetch",t),r},E=function(){return a.fetchCancelFns.delete(e)};if(this.fetchCancelFns.set(e,function(e){E(),setTimeout(function(){return r.cancel(e)})}),this.transform(w.query).hasClientExports)r=new nq(this.localState.addExportedVariables(w.query,w.variables,w.context).then(_).then(function(e){return e.sources})),i=!0;else{var S=_(w.variables);i=S.fromLink,r=new nq(S.sources)}return r.promise.then(E,E),{concast:r,fromLink:i}},e.prototype.refetchQueries=function(e){var t=this,n=e.updateCache,r=e.include,i=e.optimistic,a=void 0!==i&&i,o=e.removeOptimistic,s=void 0===o?a?nG("refetchQueries"):void 0:o,u=e.onQueryUpdated,c=new Map;r&&this.getObservableQueries(r).forEach(function(e,n){c.set(n,{oq:e,lastDiff:t.getQuery(n).getDiff()})});var l=new Map;return n&&this.cache.batch({update:n,optimistic:a&&s||!1,removeOptimistic:s,onWatchUpdated:function(e,t,n){var r=e.watcher instanceof r9&&e.watcher.observableQuery;if(r){if(u){c.delete(r.queryId);var i=u(r,t,n);return!0===i&&(i=r.refetch()),!1!==i&&l.set(r,i),i}null!==u&&c.set(r.queryId,{oq:r,lastDiff:n,diff:t})}}}),c.size&&c.forEach(function(e,n){var r,i=e.oq,a=e.lastDiff,o=e.diff;if(u){if(!o){var s=i.queryInfo;s.reset(),o=s.getDiff()}r=u(i,o,a)}u&&!0!==r||(r=i.refetch()),!1!==r&&l.set(i,r),n.indexOf("legacyOneTimeQuery")>=0&&t.stopQueryNoBroadcast(n)}),s&&this.cache.removeOptimistic(s),l},e.prototype.fetchQueryByPolicy=function(e,t,n){var r=this,i=t.query,a=t.variables,o=t.fetchPolicy,s=t.refetchWritePolicy,u=t.errorPolicy,c=t.returnPartialData,l=t.context,f=t.notifyOnNetworkStatusChange,d=e.networkStatus;e.init({document:this.transform(i).document,variables:a,networkStatus:n});var h=function(){return e.getDiff(a)},p=function(t,n){void 0===n&&(n=e.networkStatus||nZ.I.loading);var o=t.result;!__DEV__||c||(0,nm.D)(o,{})||n5(t.missing);var s=function(e){return eT.of((0,en.pi)({data:e,loading:(0,nZ.O)(n),networkStatus:n},t.complete?null:{partial:!0}))};return o&&r.transform(i).hasForcedResolvers?r.localState.runResolvers({document:i,remoteResult:{data:o},context:l,variables:a,onlyRunForcedResolvers:!0}).then(function(e){return s(e.data||void 0)}):"none"===u&&n===nZ.I.refetch&&Array.isArray(t.missing)?s(void 0):s(o)},b="no-cache"===o?0:n===nZ.I.refetch&&"merge"!==s?1:2,m=function(){return r.getResultsFromLink(e,b,{variables:a,context:l,fetchPolicy:o,errorPolicy:u})},g=f&&"number"==typeof d&&d!==n&&(0,nZ.O)(n);switch(o){default:case"cache-first":var v=h();if(v.complete)return{fromLink:!1,sources:[p(v,e.markReady())]};if(c||g)return{fromLink:!0,sources:[p(v),m()]};return{fromLink:!0,sources:[m()]};case"cache-and-network":var v=h();if(v.complete||c||g)return{fromLink:!0,sources:[p(v),m()]};return{fromLink:!0,sources:[m()]};case"cache-only":return{fromLink:!1,sources:[p(h(),e.markReady())]};case"network-only":if(g)return{fromLink:!0,sources:[p(h()),m()]};return{fromLink:!0,sources:[m()]};case"no-cache":if(g)return{fromLink:!0,sources:[p(e.getDiff()),m(),]};return{fromLink:!0,sources:[m()]};case"standby":return{fromLink:!1,sources:[]}}},e.prototype.getQuery=function(e){return e&&!this.queries.has(e)&&this.queries.set(e,new r9(this,e)),this.queries.get(e)},e.prototype.prepareContext=function(e){void 0===e&&(e={});var t=this.localState.prepareContext(e);return(0,en.pi)((0,en.pi)({},t),{clientAwareness:this.clientAwareness})},e}(),ir=__webpack_require__(14012),ii=!1,ia=function(){function e(e){var t=this;this.resetStoreCallbacks=[],this.clearStoreCallbacks=[];var n=e.uri,r=e.credentials,i=e.headers,a=e.cache,o=e.ssrMode,s=void 0!==o&&o,u=e.ssrForceFetchDelay,c=void 0===u?0:u,l=e.connectToDevTools,f=void 0===l?"object"==typeof window&&!window.__APOLLO_CLIENT__&&__DEV__:l,d=e.queryDeduplication,h=void 0===d||d,p=e.defaultOptions,b=e.assumeImmutableResults,m=void 0!==b&&b,g=e.resolvers,v=e.typeDefs,y=e.fragmentMatcher,w=e.name,_=e.version,E=e.link;if(E||(E=n?new nh({uri:n,credentials:r,headers:i}):ta.empty()),!a)throw __DEV__?new Q.ej("To initialize Apollo Client, you must specify a 'cache' property in the options object. \nFor more information, please visit: https://go.apollo.dev/c/docs"):new Q.ej(9);if(this.link=E,this.cache=a,this.disableNetworkFetches=s||c>0,this.queryDeduplication=h,this.defaultOptions=p||Object.create(null),this.typeDefs=v,c&&setTimeout(function(){return t.disableNetworkFetches=!1},c),this.watchQuery=this.watchQuery.bind(this),this.query=this.query.bind(this),this.mutate=this.mutate.bind(this),this.resetStore=this.resetStore.bind(this),this.reFetchObservableQueries=this.reFetchObservableQueries.bind(this),f&&"object"==typeof window&&(window.__APOLLO_CLIENT__=this),!ii&&f&&__DEV__&&(ii=!0,"undefined"!=typeof window&&window.document&&window.top===window.self&&!window.__APOLLO_DEVTOOLS_GLOBAL_HOOK__)){var S=window.navigator,k=S&&S.userAgent,x=void 0;"string"==typeof k&&(k.indexOf("Chrome/")>-1?x="https://chrome.google.com/webstore/detail/apollo-client-developer-t/jdkknkkbebbapilgoeccciglkfbmbnfm":k.indexOf("Firefox/")>-1&&(x="https://addons.mozilla.org/en-US/firefox/addon/apollo-developer-tools/")),x&&__DEV__&&Q.kG.log("Download the Apollo DevTools for a better development experience: "+x)}this.version=nb,this.localState=new r4({cache:a,client:this,resolvers:g,fragmentMatcher:y}),this.queryManager=new it({cache:this.cache,link:this.link,defaultOptions:this.defaultOptions,queryDeduplication:h,ssrMode:s,clientAwareness:{name:w,version:_},localState:this.localState,assumeImmutableResults:m,onBroadcast:f?function(){t.devToolsHookCb&&t.devToolsHookCb({action:{},state:{queries:t.queryManager.getQueryStore(),mutations:t.queryManager.mutationStore||{}},dataWithOptimisticResults:t.cache.extract(!0)})}:void 0})}return e.prototype.stop=function(){this.queryManager.stop()},e.prototype.watchQuery=function(e){return this.defaultOptions.watchQuery&&(e=(0,ir.J)(this.defaultOptions.watchQuery,e)),this.disableNetworkFetches&&("network-only"===e.fetchPolicy||"cache-and-network"===e.fetchPolicy)&&(e=(0,en.pi)((0,en.pi)({},e),{fetchPolicy:"cache-first"})),this.queryManager.watchQuery(e)},e.prototype.query=function(e){return this.defaultOptions.query&&(e=(0,ir.J)(this.defaultOptions.query,e)),__DEV__?(0,Q.kG)("cache-and-network"!==e.fetchPolicy,"The cache-and-network fetchPolicy does not work with client.query, because client.query can only return a single result. Please use client.watchQuery to receive multiple results from the cache and the network, or consider using a different fetchPolicy, such as cache-first or network-only."):(0,Q.kG)("cache-and-network"!==e.fetchPolicy,10),this.disableNetworkFetches&&"network-only"===e.fetchPolicy&&(e=(0,en.pi)((0,en.pi)({},e),{fetchPolicy:"cache-first"})),this.queryManager.query(e)},e.prototype.mutate=function(e){return this.defaultOptions.mutate&&(e=(0,ir.J)(this.defaultOptions.mutate,e)),this.queryManager.mutate(e)},e.prototype.subscribe=function(e){return this.queryManager.startGraphQLSubscription(e)},e.prototype.readQuery=function(e,t){return void 0===t&&(t=!1),this.cache.readQuery(e,t)},e.prototype.readFragment=function(e,t){return void 0===t&&(t=!1),this.cache.readFragment(e,t)},e.prototype.writeQuery=function(e){var t=this.cache.writeQuery(e);return!1!==e.broadcast&&this.queryManager.broadcastQueries(),t},e.prototype.writeFragment=function(e){var t=this.cache.writeFragment(e);return!1!==e.broadcast&&this.queryManager.broadcastQueries(),t},e.prototype.__actionHookForDevTools=function(e){this.devToolsHookCb=e},e.prototype.__requestRaw=function(e){return np(this.link,e)},e.prototype.resetStore=function(){var e=this;return Promise.resolve().then(function(){return e.queryManager.clearStore({discardWatches:!1})}).then(function(){return Promise.all(e.resetStoreCallbacks.map(function(e){return e()}))}).then(function(){return e.reFetchObservableQueries()})},e.prototype.clearStore=function(){var e=this;return Promise.resolve().then(function(){return e.queryManager.clearStore({discardWatches:!0})}).then(function(){return Promise.all(e.clearStoreCallbacks.map(function(e){return e()}))})},e.prototype.onResetStore=function(e){var t=this;return this.resetStoreCallbacks.push(e),function(){t.resetStoreCallbacks=t.resetStoreCallbacks.filter(function(t){return t!==e})}},e.prototype.onClearStore=function(e){var t=this;return this.clearStoreCallbacks.push(e),function(){t.clearStoreCallbacks=t.clearStoreCallbacks.filter(function(t){return t!==e})}},e.prototype.reFetchObservableQueries=function(e){return this.queryManager.reFetchObservableQueries(e)},e.prototype.refetchQueries=function(e){var t=this.queryManager.refetchQueries(e),n=[],r=[];t.forEach(function(e,t){n.push(t),r.push(e)});var i=Promise.all(r);return i.queries=n,i.results=r,i.catch(function(e){__DEV__&&Q.kG.debug("In client.refetchQueries, Promise.all promise rejected with error ".concat(e))}),i},e.prototype.getObservableQueries=function(e){return void 0===e&&(e="active"),this.queryManager.getObservableQueries(e)},e.prototype.extract=function(e){return this.cache.extract(e)},e.prototype.restore=function(e){return this.cache.restore(e)},e.prototype.addResolvers=function(e){this.localState.addResolvers(e)},e.prototype.setResolvers=function(e){this.localState.setResolvers(e)},e.prototype.getResolvers=function(){return this.localState.getResolvers()},e.prototype.setLocalStateFragmentMatcher=function(e){this.localState.setFragmentMatcher(e)},e.prototype.setLink=function(e){this.link=this.queryManager.link=e},e}(),io=function(){function e(){this.getFragmentDoc=rZ(eA)}return e.prototype.batch=function(e){var t,n=this,r="string"==typeof e.optimistic?e.optimistic:!1===e.optimistic?null:void 0;return this.performTransaction(function(){return t=e.update(n)},r),t},e.prototype.recordOptimisticTransaction=function(e,t){this.performTransaction(e,t)},e.prototype.transformDocument=function(e){return e},e.prototype.transformForLink=function(e){return e},e.prototype.identify=function(e){},e.prototype.gc=function(){return[]},e.prototype.modify=function(e){return!1},e.prototype.readQuery=function(e,t){return void 0===t&&(t=!!e.optimistic),this.read((0,en.pi)((0,en.pi)({},e),{rootId:e.id||"ROOT_QUERY",optimistic:t}))},e.prototype.readFragment=function(e,t){return void 0===t&&(t=!!e.optimistic),this.read((0,en.pi)((0,en.pi)({},e),{query:this.getFragmentDoc(e.fragment,e.fragmentName),rootId:e.id,optimistic:t}))},e.prototype.writeQuery=function(e){var t=e.id,n=e.data,r=(0,en._T)(e,["id","data"]);return this.write(Object.assign(r,{dataId:t||"ROOT_QUERY",result:n}))},e.prototype.writeFragment=function(e){var t=e.id,n=e.data,r=e.fragment,i=e.fragmentName,a=(0,en._T)(e,["id","data","fragment","fragmentName"]);return this.write(Object.assign(a,{query:this.getFragmentDoc(r,i),dataId:t,result:n}))},e.prototype.updateQuery=function(e,t){return this.batch({update:function(n){var r=n.readQuery(e),i=t(r);return null==i?r:(n.writeQuery((0,en.pi)((0,en.pi)({},e),{data:i})),i)}})},e.prototype.updateFragment=function(e,t){return this.batch({update:function(n){var r=n.readFragment(e),i=t(r);return null==i?r:(n.writeFragment((0,en.pi)((0,en.pi)({},e),{data:i})),i)}})},e}(),is=function(e){function t(n,r,i,a){var o,s=e.call(this,n)||this;if(s.message=n,s.path=r,s.query=i,s.variables=a,Array.isArray(s.path)){s.missing=s.message;for(var u=s.path.length-1;u>=0;--u)s.missing=((o={})[s.path[u]]=s.missing,o)}else s.missing=s.path;return s.__proto__=t.prototype,s}return(0,en.ZT)(t,e),t}(Error),iu=__webpack_require__(10542),ic=Object.prototype.hasOwnProperty;function il(e){return null==e}function id(e,t){var n=e.__typename,r=e.id,i=e._id;if("string"==typeof n&&(t&&(t.keyObject=il(r)?il(i)?void 0:{_id:i}:{id:r}),il(r)&&!il(i)&&(r=i),!il(r)))return"".concat(n,":").concat("number"==typeof r||"string"==typeof r?r:JSON.stringify(r))}var ih={dataIdFromObject:id,addTypename:!0,resultCaching:!0,canonizeResults:!1};function ip(e){return(0,n1.o)(ih,e)}function ib(e){var t=e.canonizeResults;return void 0===t?ih.canonizeResults:t}function im(e,t){return eD(t)?e.get(t.__ref,"__typename"):t&&t.__typename}var ig=/^[_a-z][_0-9a-z]*/i;function iv(e){var t=e.match(ig);return t?t[0]:e}function iy(e,t,n){return!!(0,eO.s)(t)&&((0,tP.k)(t)?t.every(function(t){return iy(e,t,n)}):e.selections.every(function(e){if(eQ(e)&&td(e,n)){var r=eX(e);return ic.call(t,r)&&(!e.selectionSet||iy(e.selectionSet,t[r],n))}return!0}))}function iw(e){return(0,eO.s)(e)&&!eD(e)&&!(0,tP.k)(e)}function i_(){return new tB}function iE(e,t){var n=eL(e4(e));return{fragmentMap:n,lookupFragment:function(e){var r=n[e];return!r&&t&&(r=t.lookup(e)),r||null}}}var iS=Object.create(null),ik=function(){return iS},ix=Object.create(null),iT=function(){function e(e,t){var n=this;this.policies=e,this.group=t,this.data=Object.create(null),this.rootIds=Object.create(null),this.refs=Object.create(null),this.getFieldValue=function(e,t){return(0,iu.J)(eD(e)?n.get(e.__ref,t):e&&e[t])},this.canRead=function(e){return eD(e)?n.has(e.__ref):"object"==typeof e},this.toReference=function(e,t){if("string"==typeof e)return eI(e);if(eD(e))return e;var r=n.policies.identify(e)[0];if(r){var i=eI(r);return t&&n.merge(r,e),i}}}return e.prototype.toObject=function(){return(0,en.pi)({},this.data)},e.prototype.has=function(e){return void 0!==this.lookup(e,!0)},e.prototype.get=function(e,t){if(this.group.depend(e,t),ic.call(this.data,e)){var n=this.data[e];if(n&&ic.call(n,t))return n[t]}return"__typename"===t&&ic.call(this.policies.rootTypenamesById,e)?this.policies.rootTypenamesById[e]:this instanceof iL?this.parent.get(e,t):void 0},e.prototype.lookup=function(e,t){return(t&&this.group.depend(e,"__exists"),ic.call(this.data,e))?this.data[e]:this instanceof iL?this.parent.lookup(e,t):this.policies.rootTypenamesById[e]?Object.create(null):void 0},e.prototype.merge=function(e,t){var n,r=this;eD(e)&&(e=e.__ref),eD(t)&&(t=t.__ref);var i="string"==typeof e?this.lookup(n=e):e,a="string"==typeof t?this.lookup(n=t):t;if(a){__DEV__?(0,Q.kG)("string"==typeof n,"store.merge expects a string ID"):(0,Q.kG)("string"==typeof n,1);var o=new tB(iI).merge(i,a);if(this.data[n]=o,o!==i&&(delete this.refs[n],this.group.caching)){var s=Object.create(null);i||(s.__exists=1),Object.keys(a).forEach(function(e){if(!i||i[e]!==o[e]){s[e]=1;var t=iv(e);t===e||r.policies.hasKeyArgs(o.__typename,t)||(s[t]=1),void 0!==o[e]||r instanceof iL||delete o[e]}}),s.__typename&&!(i&&i.__typename)&&this.policies.rootTypenamesById[n]===o.__typename&&delete s.__typename,Object.keys(s).forEach(function(e){return r.group.dirty(n,e)})}}},e.prototype.modify=function(e,t){var n=this,r=this.lookup(e);if(r){var i=Object.create(null),a=!1,o=!0,s={DELETE:iS,INVALIDATE:ix,isReference:eD,toReference:this.toReference,canRead:this.canRead,readField:function(t,r){return n.policies.readField("string"==typeof t?{fieldName:t,from:r||eI(e)}:t,{store:n})}};if(Object.keys(r).forEach(function(u){var c=iv(u),l=r[u];if(void 0!==l){var f="function"==typeof t?t:t[u]||t[c];if(f){var d=f===ik?iS:f((0,iu.J)(l),(0,en.pi)((0,en.pi)({},s),{fieldName:c,storeFieldName:u,storage:n.getStorage(e,u)}));d===ix?n.group.dirty(e,u):(d===iS&&(d=void 0),d!==l&&(i[u]=d,a=!0,l=d))}void 0!==l&&(o=!1)}}),a)return this.merge(e,i),o&&(this instanceof iL?this.data[e]=void 0:delete this.data[e],this.group.dirty(e,"__exists")),!0}return!1},e.prototype.delete=function(e,t,n){var r,i=this.lookup(e);if(i){var a=this.getFieldValue(i,"__typename"),o=t&&n?this.policies.getStoreFieldName({typename:a,fieldName:t,args:n}):t;return this.modify(e,o?((r={})[o]=ik,r):ik)}return!1},e.prototype.evict=function(e,t){var n=!1;return e.id&&(ic.call(this.data,e.id)&&(n=this.delete(e.id,e.fieldName,e.args)),this instanceof iL&&this!==t&&(n=this.parent.evict(e,t)||n),(e.fieldName||n)&&this.group.dirty(e.id,e.fieldName||"__exists")),n},e.prototype.clear=function(){this.replace(null)},e.prototype.extract=function(){var e=this,t=this.toObject(),n=[];return this.getRootIdSet().forEach(function(t){ic.call(e.policies.rootTypenamesById,t)||n.push(t)}),n.length&&(t.__META={extraRootIds:n.sort()}),t},e.prototype.replace=function(e){var t=this;if(Object.keys(this.data).forEach(function(n){e&&ic.call(e,n)||t.delete(n)}),e){var n=e.__META,r=(0,en._T)(e,["__META"]);Object.keys(r).forEach(function(e){t.merge(e,r[e])}),n&&n.extraRootIds.forEach(this.retain,this)}},e.prototype.retain=function(e){return this.rootIds[e]=(this.rootIds[e]||0)+1},e.prototype.release=function(e){if(this.rootIds[e]>0){var t=--this.rootIds[e];return t||delete this.rootIds[e],t}return 0},e.prototype.getRootIdSet=function(e){return void 0===e&&(e=new Set),Object.keys(this.rootIds).forEach(e.add,e),this instanceof iL?this.parent.getRootIdSet(e):Object.keys(this.policies.rootTypenamesById).forEach(e.add,e),e},e.prototype.gc=function(){var e=this,t=this.getRootIdSet(),n=this.toObject();t.forEach(function(r){ic.call(n,r)&&(Object.keys(e.findChildRefIds(r)).forEach(t.add,t),delete n[r])});var r=Object.keys(n);if(r.length){for(var i=this;i instanceof iL;)i=i.parent;r.forEach(function(e){return i.delete(e)})}return r},e.prototype.findChildRefIds=function(e){if(!ic.call(this.refs,e)){var t=this.refs[e]=Object.create(null),n=this.data[e];if(!n)return t;var r=new Set([n]);r.forEach(function(e){eD(e)&&(t[e.__ref]=!0),(0,eO.s)(e)&&Object.keys(e).forEach(function(t){var n=e[t];(0,eO.s)(n)&&r.add(n)})})}return this.refs[e]},e.prototype.makeCacheKey=function(){return this.group.keyMaker.lookupArray(arguments)},e}(),iM=function(){function e(e,t){void 0===t&&(t=null),this.caching=e,this.parent=t,this.d=null,this.resetCaching()}return e.prototype.resetCaching=function(){this.d=this.caching?rW():null,this.keyMaker=new n_(t_.mr)},e.prototype.depend=function(e,t){if(this.d){this.d(iO(e,t));var n=iv(t);n!==t&&this.d(iO(e,n)),this.parent&&this.parent.depend(e,t)}},e.prototype.dirty=function(e,t){this.d&&this.d.dirty(iO(e,t),"__exists"===t?"forget":"setDirty")},e}();function iO(e,t){return t+"#"+e}function iA(e,t){iD(e)&&e.group.depend(t,"__exists")}!function(e){var t=function(e){function t(t){var n=t.policies,r=t.resultCaching,i=void 0===r||r,a=t.seed,o=e.call(this,n,new iM(i))||this;return o.stump=new iC(o),o.storageTrie=new n_(t_.mr),a&&o.replace(a),o}return(0,en.ZT)(t,e),t.prototype.addLayer=function(e,t){return this.stump.addLayer(e,t)},t.prototype.removeLayer=function(){return this},t.prototype.getStorage=function(){return this.storageTrie.lookupArray(arguments)},t}(e);e.Root=t}(iT||(iT={}));var iL=function(e){function t(t,n,r,i){var a=e.call(this,n.policies,i)||this;return a.id=t,a.parent=n,a.replay=r,a.group=i,r(a),a}return(0,en.ZT)(t,e),t.prototype.addLayer=function(e,n){return new t(e,this,n,this.group)},t.prototype.removeLayer=function(e){var t=this,n=this.parent.removeLayer(e);return e===this.id?(this.group.caching&&Object.keys(this.data).forEach(function(e){var r=t.data[e],i=n.lookup(e);i?r?r!==i&&Object.keys(r).forEach(function(n){(0,nm.D)(r[n],i[n])||t.group.dirty(e,n)}):(t.group.dirty(e,"__exists"),Object.keys(i).forEach(function(n){t.group.dirty(e,n)})):t.delete(e)}),n):n===this.parent?this:n.addLayer(this.id,this.replay)},t.prototype.toObject=function(){return(0,en.pi)((0,en.pi)({},this.parent.toObject()),this.data)},t.prototype.findChildRefIds=function(t){var n=this.parent.findChildRefIds(t);return ic.call(this.data,t)?(0,en.pi)((0,en.pi)({},n),e.prototype.findChildRefIds.call(this,t)):n},t.prototype.getStorage=function(){for(var e=this.parent;e.parent;)e=e.parent;return e.getStorage.apply(e,arguments)},t}(iT),iC=function(e){function t(t){return e.call(this,"EntityStore.Stump",t,function(){},new iM(t.group.caching,t.group))||this}return(0,en.ZT)(t,e),t.prototype.removeLayer=function(){return this},t.prototype.merge=function(){return this.parent.merge.apply(this.parent,arguments)},t}(iL);function iI(e,t,n){var r=e[n],i=t[n];return(0,nm.D)(r,i)?r:i}function iD(e){return!!(e instanceof iT&&e.group.caching)}function iN(e){return[e.selectionSet,e.objectOrReference,e.context,e.context.canonizeResults,]}var iP=function(){function e(e){var t=this;this.knownResults=new(t_.mr?WeakMap:Map),this.config=(0,n1.o)(e,{addTypename:!1!==e.addTypename,canonizeResults:ib(e)}),this.canon=e.canon||new nk,this.executeSelectionSet=rZ(function(e){var n,r=e.context.canonizeResults,i=iN(e);i[3]=!r;var a=(n=t.executeSelectionSet).peek.apply(n,i);return a?r?(0,en.pi)((0,en.pi)({},a),{result:t.canon.admit(a.result)}):a:(iA(e.context.store,e.enclosingRef.__ref),t.execSelectionSetImpl(e))},{max:this.config.resultCacheMaxSize,keyArgs:iN,makeCacheKey:function(e,t,n,r){if(iD(n.store))return n.store.makeCacheKey(e,eD(t)?t.__ref:t,n.varString,r)}}),this.executeSubSelectedArray=rZ(function(e){return iA(e.context.store,e.enclosingRef.__ref),t.execSubSelectedArrayImpl(e)},{max:this.config.resultCacheMaxSize,makeCacheKey:function(e){var t=e.field,n=e.array,r=e.context;if(iD(r.store))return r.store.makeCacheKey(t,n,r.varString)}})}return e.prototype.resetCanon=function(){this.canon=new nk},e.prototype.diffQueryAgainstStore=function(e){var t,n=e.store,r=e.query,i=e.rootId,a=void 0===i?"ROOT_QUERY":i,o=e.variables,s=e.returnPartialData,u=void 0===s||s,c=e.canonizeResults,l=void 0===c?this.config.canonizeResults:c,f=this.config.cache.policies;o=(0,en.pi)((0,en.pi)({},e9(e6(r))),o);var d=eI(a),h=this.executeSelectionSet({selectionSet:e8(r).selectionSet,objectOrReference:d,enclosingRef:d,context:(0,en.pi)({store:n,query:r,policies:f,variables:o,varString:nx(o),canonizeResults:l},iE(r,this.config.fragments))});if(h.missing&&(t=[new is(iR(h.missing),h.missing,r,o)],!u))throw t[0];return{result:h.result,complete:!t,missing:t}},e.prototype.isFresh=function(e,t,n,r){if(iD(r.store)&&this.knownResults.get(e)===n){var i=this.executeSelectionSet.peek(n,t,r,this.canon.isKnown(e));if(i&&e===i.result)return!0}return!1},e.prototype.execSelectionSetImpl=function(e){var t,n=this,r=e.selectionSet,i=e.objectOrReference,a=e.enclosingRef,o=e.context;if(eD(i)&&!o.policies.rootTypenamesById[i.__ref]&&!o.store.has(i.__ref))return{result:this.canon.empty,missing:"Dangling reference to missing ".concat(i.__ref," object")};var s=o.variables,u=o.policies,c=o.store.getFieldValue(i,"__typename"),l=[],f=new tB;function d(e,n){var r;return e.missing&&(t=f.merge(t,((r={})[n]=e.missing,r))),e.result}this.config.addTypename&&"string"==typeof c&&!u.rootIdsByTypename[c]&&l.push({__typename:c});var h=new Set(r.selections);h.forEach(function(e){var r,p;if(td(e,s)){if(eQ(e)){var b=u.readField({fieldName:e.name.value,field:e,variables:o.variables,from:i},o),m=eX(e);void 0===b?nj.added(e)||(t=f.merge(t,((r={})[m]="Can't find field '".concat(e.name.value,"' on ").concat(eD(i)?i.__ref+" object":"object "+JSON.stringify(i,null,2)),r))):(0,tP.k)(b)?b=d(n.executeSubSelectedArray({field:e,array:b,enclosingRef:a,context:o}),m):e.selectionSet?null!=b&&(b=d(n.executeSelectionSet({selectionSet:e.selectionSet,objectOrReference:b,enclosingRef:eD(b)?b:a,context:o}),m)):o.canonizeResults&&(b=n.canon.pass(b)),void 0!==b&&l.push(((p={})[m]=b,p))}else{var g=eC(e,o.lookupFragment);if(!g&&e.kind===nL.h.FRAGMENT_SPREAD)throw __DEV__?new Q.ej("No fragment named ".concat(e.name.value)):new Q.ej(5);g&&u.fragmentMatches(g,c)&&g.selectionSet.selections.forEach(h.add,h)}}});var p={result:tF(l),missing:t},b=o.canonizeResults?this.canon.admit(p):(0,iu.J)(p);return b.result&&this.knownResults.set(b.result,r),b},e.prototype.execSubSelectedArrayImpl=function(e){var t,n=this,r=e.field,i=e.array,a=e.enclosingRef,o=e.context,s=new tB;function u(e,n){var r;return e.missing&&(t=s.merge(t,((r={})[n]=e.missing,r))),e.result}return r.selectionSet&&(i=i.filter(o.store.canRead)),i=i.map(function(e,t){return null===e?null:(0,tP.k)(e)?u(n.executeSubSelectedArray({field:r,array:e,enclosingRef:a,context:o}),t):r.selectionSet?u(n.executeSelectionSet({selectionSet:r.selectionSet,objectOrReference:e,enclosingRef:eD(e)?e:a,context:o}),t):(__DEV__&&ij(o.store,r,e),e)}),{result:o.canonizeResults?this.canon.admit(i):i,missing:t}},e}();function iR(e){try{JSON.stringify(e,function(e,t){if("string"==typeof t)throw t;return t})}catch(t){return t}}function ij(e,t,n){if(!t.selectionSet){var r=new Set([n]);r.forEach(function(n){(0,eO.s)(n)&&(__DEV__?(0,Q.kG)(!eD(n),"Missing selection set for object of type ".concat(im(e,n)," returned for query field ").concat(t.name.value)):(0,Q.kG)(!eD(n),6),Object.values(n).forEach(r.add,r))})}}function iF(e){var t=nG("stringifyForDisplay");return JSON.stringify(e,function(e,n){return void 0===n?t:n}).split(JSON.stringify(t)).join("")}var iY=Object.create(null);function iB(e){var t=JSON.stringify(e);return iY[t]||(iY[t]=Object.create(null))}function iU(e){var t=iB(e);return t.keyFieldsFn||(t.keyFieldsFn=function(t,n){var r=function(e,t){return n.readField(t,e)},i=n.keyObject=i$(e,function(e){var i=iW(n.storeObject,e,r);return void 0===i&&t!==n.storeObject&&ic.call(t,e[0])&&(i=iW(t,e,iG)),__DEV__?(0,Q.kG)(void 0!==i,"Missing field '".concat(e.join("."),"' while extracting keyFields from ").concat(JSON.stringify(t))):(0,Q.kG)(void 0!==i,2),i});return"".concat(n.typename,":").concat(JSON.stringify(i))})}function iH(e){var t=iB(e);return t.keyArgsFn||(t.keyArgsFn=function(t,n){var r=n.field,i=n.variables,a=n.fieldName,o=JSON.stringify(i$(e,function(e){var n=e[0],a=n.charAt(0);if("@"===a){if(r&&(0,tP.O)(r.directives)){var o=n.slice(1),s=r.directives.find(function(e){return e.name.value===o}),u=s&&eZ(s,i);return u&&iW(u,e.slice(1))}return}if("$"===a){var c=n.slice(1);if(i&&ic.call(i,c)){var l=e.slice(0);return l[0]=c,iW(i,l)}return}if(t)return iW(t,e)}));return(t||"{}"!==o)&&(a+=":"+o),a})}function i$(e,t){var n=new tB;return iz(e).reduce(function(e,r){var i,a=t(r);if(void 0!==a){for(var o=r.length-1;o>=0;--o)a=((i={})[r[o]]=a,i);e=n.merge(e,a)}return e},Object.create(null))}function iz(e){var t=iB(e);if(!t.paths){var n=t.paths=[],r=[];e.forEach(function(t,i){(0,tP.k)(t)?(iz(t).forEach(function(e){return n.push(r.concat(e))}),r.length=0):(r.push(t),(0,tP.k)(e[i+1])||(n.push(r.slice(0)),r.length=0))})}return t.paths}function iG(e,t){return e[t]}function iW(e,t,n){return n=n||iG,iK(t.reduce(function e(t,r){return(0,tP.k)(t)?t.map(function(t){return e(t,r)}):t&&n(t,r)},e))}function iK(e){return(0,eO.s)(e)?(0,tP.k)(e)?e.map(iK):i$(Object.keys(e).sort(),function(t){return iW(e,t)}):e}function iV(e){return void 0!==e.args?e.args:e.field?eZ(e.field,e.variables):null}eK.setStringify(nx);var iq=function(){},iZ=function(e,t){return t.fieldName},iX=function(e,t,n){return(0,n.mergeObjects)(e,t)},iJ=function(e,t){return t},iQ=function(){function e(e){this.config=e,this.typePolicies=Object.create(null),this.toBeAdded=Object.create(null),this.supertypeMap=new Map,this.fuzzySubtypes=new Map,this.rootIdsByTypename=Object.create(null),this.rootTypenamesById=Object.create(null),this.usingPossibleTypes=!1,this.config=(0,en.pi)({dataIdFromObject:id},e),this.cache=this.config.cache,this.setRootTypename("Query"),this.setRootTypename("Mutation"),this.setRootTypename("Subscription"),e.possibleTypes&&this.addPossibleTypes(e.possibleTypes),e.typePolicies&&this.addTypePolicies(e.typePolicies)}return e.prototype.identify=function(e,t){var n,r,i=this,a=t&&(t.typename||(null===(n=t.storeObject)||void 0===n?void 0:n.__typename))||e.__typename;if(a===this.rootTypenamesById.ROOT_QUERY)return["ROOT_QUERY"];for(var o=t&&t.storeObject||e,s=(0,en.pi)((0,en.pi)({},t),{typename:a,storeObject:o,readField:t&&t.readField||function(){var e=i0(arguments,o);return i.readField(e,{store:i.cache.data,variables:e.variables})}}),u=a&&this.getTypePolicy(a),c=u&&u.keyFn||this.config.dataIdFromObject;c;){var l=c((0,en.pi)((0,en.pi)({},e),o),s);if((0,tP.k)(l))c=iU(l);else{r=l;break}}return r=r?String(r):void 0,s.keyObject?[r,s.keyObject]:[r]},e.prototype.addTypePolicies=function(e){var t=this;Object.keys(e).forEach(function(n){var r=e[n],i=r.queryType,a=r.mutationType,o=r.subscriptionType,s=(0,en._T)(r,["queryType","mutationType","subscriptionType"]);i&&t.setRootTypename("Query",n),a&&t.setRootTypename("Mutation",n),o&&t.setRootTypename("Subscription",n),ic.call(t.toBeAdded,n)?t.toBeAdded[n].push(s):t.toBeAdded[n]=[s]})},e.prototype.updateTypePolicy=function(e,t){var n=this,r=this.getTypePolicy(e),i=t.keyFields,a=t.fields;function o(e,t){e.merge="function"==typeof t?t:!0===t?iX:!1===t?iJ:e.merge}o(r,t.merge),r.keyFn=!1===i?iq:(0,tP.k)(i)?iU(i):"function"==typeof i?i:r.keyFn,a&&Object.keys(a).forEach(function(t){var r=n.getFieldPolicy(e,t,!0),i=a[t];if("function"==typeof i)r.read=i;else{var s=i.keyArgs,u=i.read,c=i.merge;r.keyFn=!1===s?iZ:(0,tP.k)(s)?iH(s):"function"==typeof s?s:r.keyFn,"function"==typeof u&&(r.read=u),o(r,c)}r.read&&r.merge&&(r.keyFn=r.keyFn||iZ)})},e.prototype.setRootTypename=function(e,t){void 0===t&&(t=e);var n="ROOT_"+e.toUpperCase(),r=this.rootTypenamesById[n];t!==r&&(__DEV__?(0,Q.kG)(!r||r===e,"Cannot change root ".concat(e," __typename more than once")):(0,Q.kG)(!r||r===e,3),r&&delete this.rootIdsByTypename[r],this.rootIdsByTypename[t]=n,this.rootTypenamesById[n]=t)},e.prototype.addPossibleTypes=function(e){var t=this;this.usingPossibleTypes=!0,Object.keys(e).forEach(function(n){t.getSupertypeSet(n,!0),e[n].forEach(function(e){t.getSupertypeSet(e,!0).add(n);var r=e.match(ig);r&&r[0]===e||t.fuzzySubtypes.set(e,RegExp(e))})})},e.prototype.getTypePolicy=function(e){var t=this;if(!ic.call(this.typePolicies,e)){var n=this.typePolicies[e]=Object.create(null);n.fields=Object.create(null);var r=this.supertypeMap.get(e);r&&r.size&&r.forEach(function(e){var r=t.getTypePolicy(e),i=r.fields;Object.assign(n,(0,en._T)(r,["fields"])),Object.assign(n.fields,i)})}var i=this.toBeAdded[e];return i&&i.length&&i.splice(0).forEach(function(n){t.updateTypePolicy(e,n)}),this.typePolicies[e]},e.prototype.getFieldPolicy=function(e,t,n){if(e){var r=this.getTypePolicy(e).fields;return r[t]||n&&(r[t]=Object.create(null))}},e.prototype.getSupertypeSet=function(e,t){var n=this.supertypeMap.get(e);return!n&&t&&this.supertypeMap.set(e,n=new Set),n},e.prototype.fragmentMatches=function(e,t,n,r){var i=this;if(!e.typeCondition)return!0;if(!t)return!1;var a=e.typeCondition.name.value;if(t===a)return!0;if(this.usingPossibleTypes&&this.supertypeMap.has(a))for(var o=this.getSupertypeSet(t,!0),s=[o],u=function(e){var t=i.getSupertypeSet(e,!1);t&&t.size&&0>s.indexOf(t)&&s.push(t)},c=!!(n&&this.fuzzySubtypes.size),l=!1,f=0;f1?a:t}:(r=(0,en.pi)({},i),ic.call(r,"from")||(r.from=t)),__DEV__&&void 0===r.from&&__DEV__&&Q.kG.warn("Undefined 'from' passed to readField with arguments ".concat(iF(Array.from(e)))),void 0===r.variables&&(r.variables=n),r}function i2(e){return function(t,n){if((0,tP.k)(t)||(0,tP.k)(n))throw __DEV__?new Q.ej("Cannot automatically merge arrays"):new Q.ej(4);if((0,eO.s)(t)&&(0,eO.s)(n)){var r=e.getFieldValue(t,"__typename"),i=e.getFieldValue(n,"__typename");if(r&&i&&r!==i)return n;if(eD(t)&&iw(n))return e.merge(t.__ref,n),t;if(iw(t)&&eD(n))return e.merge(t,n.__ref),n;if(iw(t)&&iw(n))return(0,en.pi)((0,en.pi)({},t),n)}return n}}function i3(e,t,n){var r="".concat(t).concat(n),i=e.flavors.get(r);return i||e.flavors.set(r,i=e.clientOnly===t&&e.deferred===n?e:(0,en.pi)((0,en.pi)({},e),{clientOnly:t,deferred:n})),i}var i4=function(){function e(e,t,n){this.cache=e,this.reader=t,this.fragments=n}return e.prototype.writeToStore=function(e,t){var n=this,r=t.query,i=t.result,a=t.dataId,o=t.variables,s=t.overwrite,u=e2(r),c=i_();o=(0,en.pi)((0,en.pi)({},e9(u)),o);var l=(0,en.pi)((0,en.pi)({store:e,written:Object.create(null),merge:function(e,t){return c.merge(e,t)},variables:o,varString:nx(o)},iE(r,this.fragments)),{overwrite:!!s,incomingById:new Map,clientOnly:!1,deferred:!1,flavors:new Map}),f=this.processSelectionSet({result:i||Object.create(null),dataId:a,selectionSet:u.selectionSet,mergeTree:{map:new Map},context:l});if(!eD(f))throw __DEV__?new Q.ej("Could not identify object ".concat(JSON.stringify(i))):new Q.ej(7);return l.incomingById.forEach(function(t,r){var i=t.storeObject,a=t.mergeTree,o=t.fieldNodeSet,s=eI(r);if(a&&a.map.size){var u=n.applyMerges(a,s,i,l);if(eD(u))return;i=u}if(__DEV__&&!l.overwrite){var c=Object.create(null);o.forEach(function(e){e.selectionSet&&(c[e.name.value]=!0)});var f=function(e){return!0===c[iv(e)]},d=function(e){var t=a&&a.map.get(e);return Boolean(t&&t.info&&t.info.merge)};Object.keys(i).forEach(function(e){f(e)&&!d(e)&&at(s,i,e,l.store)})}e.merge(r,i)}),e.retain(f.__ref),f},e.prototype.processSelectionSet=function(e){var t=this,n=e.dataId,r=e.result,i=e.selectionSet,a=e.context,o=e.mergeTree,s=this.cache.policies,u=Object.create(null),c=n&&s.rootTypenamesById[n]||eJ(r,i,a.fragmentMap)||n&&a.store.get(n,"__typename");"string"==typeof c&&(u.__typename=c);var l=function(){var e=i0(arguments,u,a.variables);if(eD(e.from)){var t=a.incomingById.get(e.from.__ref);if(t){var n=s.readField((0,en.pi)((0,en.pi)({},e),{from:t.storeObject}),a);if(void 0!==n)return n}}return s.readField(e,a)},f=new Set;this.flattenFields(i,r,a,c).forEach(function(e,n){var i,a=r[eX(n)];if(f.add(n),void 0!==a){var d=s.getStoreFieldName({typename:c,fieldName:n.name.value,field:n,variables:e.variables}),h=i5(o,d),p=t.processFieldValue(a,n,n.selectionSet?i3(e,!1,!1):e,h),b=void 0;n.selectionSet&&(eD(p)||iw(p))&&(b=l("__typename",p));var m=s.getMergeFunction(c,n.name.value,b);m?h.info={field:n,typename:c,merge:m}:i7(o,d),u=e.merge(u,((i={})[d]=p,i))}else __DEV__&&!e.clientOnly&&!e.deferred&&!nj.added(n)&&!s.getReadFunction(c,n.name.value)&&__DEV__&&Q.kG.error("Missing field '".concat(eX(n),"' while writing result ").concat(JSON.stringify(r,null,2)).substring(0,1e3))});try{var d=s.identify(r,{typename:c,selectionSet:i,fragmentMap:a.fragmentMap,storeObject:u,readField:l}),h=d[0],p=d[1];n=n||h,p&&(u=a.merge(u,p))}catch(b){if(!n)throw b}if("string"==typeof n){var m=eI(n),g=a.written[n]||(a.written[n]=[]);if(g.indexOf(i)>=0||(g.push(i),this.reader&&this.reader.isFresh(r,m,i,a)))return m;var v=a.incomingById.get(n);return v?(v.storeObject=a.merge(v.storeObject,u),v.mergeTree=i8(v.mergeTree,o),f.forEach(function(e){return v.fieldNodeSet.add(e)})):a.incomingById.set(n,{storeObject:u,mergeTree:i9(o)?void 0:o,fieldNodeSet:f}),m}return u},e.prototype.processFieldValue=function(e,t,n,r){var i=this;return t.selectionSet&&null!==e?(0,tP.k)(e)?e.map(function(e,a){var o=i.processFieldValue(e,t,n,i5(r,a));return i7(r,a),o}):this.processSelectionSet({result:e,selectionSet:t.selectionSet,context:n,mergeTree:r}):__DEV__?nJ(e):e},e.prototype.flattenFields=function(e,t,n,r){void 0===r&&(r=eJ(t,e,n.fragmentMap));var i=new Map,a=this.cache.policies,o=new n_(!1);return function e(s,u){var c=o.lookup(s,u.clientOnly,u.deferred);c.visited||(c.visited=!0,s.selections.forEach(function(o){if(td(o,n.variables)){var s=u.clientOnly,c=u.deferred;if(!(s&&c)&&(0,tP.O)(o.directives)&&o.directives.forEach(function(e){var t=e.name.value;if("client"===t&&(s=!0),"defer"===t){var r=eZ(e,n.variables);r&&!1===r.if||(c=!0)}}),eQ(o)){var l=i.get(o);l&&(s=s&&l.clientOnly,c=c&&l.deferred),i.set(o,i3(n,s,c))}else{var f=eC(o,n.lookupFragment);if(!f&&o.kind===nL.h.FRAGMENT_SPREAD)throw __DEV__?new Q.ej("No fragment named ".concat(o.name.value)):new Q.ej(8);f&&a.fragmentMatches(f,r,t,n.variables)&&e(f.selectionSet,i3(n,s,c))}}}))}(e,n),i},e.prototype.applyMerges=function(e,t,n,r,i){var a=this;if(e.map.size&&!eD(n)){var o,s,u=!(0,tP.k)(n)&&(eD(t)||iw(t))?t:void 0,c=n;u&&!i&&(i=[eD(u)?u.__ref:u]);var l=function(e,t){return(0,tP.k)(e)?"number"==typeof t?e[t]:void 0:r.store.getFieldValue(e,String(t))};e.map.forEach(function(e,t){var n=l(u,t),o=l(c,t);if(void 0!==o){i&&i.push(t);var f=a.applyMerges(e,n,o,r,i);f!==o&&(s=s||new Map).set(t,f),i&&(0,Q.kG)(i.pop()===t)}}),s&&(n=(0,tP.k)(c)?c.slice(0):(0,en.pi)({},c),s.forEach(function(e,t){n[t]=e}))}return e.info?this.cache.policies.runMergeFunction(t,n,e.info,r,i&&(o=r.store).getStorage.apply(o,i)):n},e}(),i6=[];function i5(e,t){var n=e.map;return n.has(t)||n.set(t,i6.pop()||{map:new Map}),n.get(t)}function i8(e,t){if(e===t||!t||i9(t))return e;if(!e||i9(e))return t;var n=e.info&&t.info?(0,en.pi)((0,en.pi)({},e.info),t.info):e.info||t.info,r=e.map.size&&t.map.size,i=r?new Map:e.map.size?e.map:t.map,a={info:n,map:i};if(r){var o=new Set(t.map.keys());e.map.forEach(function(e,n){a.map.set(n,i8(e,t.map.get(n))),o.delete(n)}),o.forEach(function(n){a.map.set(n,i8(t.map.get(n),e.map.get(n)))})}return a}function i9(e){return!e||!(e.info||e.map.size)}function i7(e,t){var n=e.map,r=n.get(t);r&&i9(r)&&(i6.push(r),n.delete(t))}var ae=new Set;function at(e,t,n,r){var i=function(e){var t=r.getFieldValue(e,n);return"object"==typeof t&&t},a=i(e);if(a){var o=i(t);if(!(!o||eD(a)||(0,nm.D)(a,o)||Object.keys(a).every(function(e){return void 0!==r.getFieldValue(o,e)}))){var s=r.getFieldValue(e,"__typename")||r.getFieldValue(t,"__typename"),u=iv(n),c="".concat(s,".").concat(u);if(!ae.has(c)){ae.add(c);var l=[];(0,tP.k)(a)||(0,tP.k)(o)||[a,o].forEach(function(e){var t=r.getFieldValue(e,"__typename");"string"!=typeof t||l.includes(t)||l.push(t)}),__DEV__&&Q.kG.warn("Cache data may be lost when replacing the ".concat(u," field of a ").concat(s," object.\n\nThis could cause additional (usually avoidable) network requests to fetch data that were otherwise cached.\n\nTo address this problem (which is not a bug in Apollo Client), ").concat(l.length?"either ensure all objects of type "+l.join(" and ")+" have an ID or a custom merge function, or ":"","define a custom merge function for the ").concat(c," field, so InMemoryCache can safely merge these objects:\n\n existing: ").concat(JSON.stringify(a).slice(0,1e3),"\n incoming: ").concat(JSON.stringify(o).slice(0,1e3),"\n\nFor more information about these options, please refer to the documentation:\n\n * Ensuring entity objects have IDs: https://go.apollo.dev/c/generating-unique-identifiers\n * Defining custom merge functions: https://go.apollo.dev/c/merging-non-normalized-objects\n"))}}}}var an=function(e){function t(t){void 0===t&&(t={});var n=e.call(this)||this;return n.watches=new Set,n.typenameDocumentCache=new Map,n.makeVar=r2,n.txCount=0,n.config=ip(t),n.addTypename=!!n.config.addTypename,n.policies=new iQ({cache:n,dataIdFromObject:n.config.dataIdFromObject,possibleTypes:n.config.possibleTypes,typePolicies:n.config.typePolicies}),n.init(),n}return(0,en.ZT)(t,e),t.prototype.init=function(){var e=this.data=new iT.Root({policies:this.policies,resultCaching:this.config.resultCaching});this.optimisticData=e.stump,this.resetResultCache()},t.prototype.resetResultCache=function(e){var t=this,n=this.storeReader,r=this.config.fragments;this.storeWriter=new i4(this,this.storeReader=new iP({cache:this,addTypename:this.addTypename,resultCacheMaxSize:this.config.resultCacheMaxSize,canonizeResults:ib(this.config),canon:e?void 0:n&&n.canon,fragments:r}),r),this.maybeBroadcastWatch=rZ(function(e,n){return t.broadcastWatch(e,n)},{max:this.config.resultCacheMaxSize,makeCacheKey:function(e){var n=e.optimistic?t.optimisticData:t.data;if(iD(n)){var r=e.optimistic,i=e.id,a=e.variables;return n.makeCacheKey(e.query,e.callback,nx({optimistic:r,id:i,variables:a}))}}}),new Set([this.data.group,this.optimisticData.group,]).forEach(function(e){return e.resetCaching()})},t.prototype.restore=function(e){return this.init(),e&&this.data.replace(e),this},t.prototype.extract=function(e){return void 0===e&&(e=!1),(e?this.optimisticData:this.data).extract()},t.prototype.read=function(e){var t=e.returnPartialData,n=void 0!==t&&t;try{return this.storeReader.diffQueryAgainstStore((0,en.pi)((0,en.pi)({},e),{store:e.optimistic?this.optimisticData:this.data,config:this.config,returnPartialData:n})).result||null}catch(r){if(r instanceof is)return null;throw r}},t.prototype.write=function(e){try{return++this.txCount,this.storeWriter.writeToStore(this.data,e)}finally{--this.txCount||!1===e.broadcast||this.broadcastWatches()}},t.prototype.modify=function(e){if(ic.call(e,"id")&&!e.id)return!1;var t=e.optimistic?this.optimisticData:this.data;try{return++this.txCount,t.modify(e.id||"ROOT_QUERY",e.fields)}finally{--this.txCount||!1===e.broadcast||this.broadcastWatches()}},t.prototype.diff=function(e){return this.storeReader.diffQueryAgainstStore((0,en.pi)((0,en.pi)({},e),{store:e.optimistic?this.optimisticData:this.data,rootId:e.id||"ROOT_QUERY",config:this.config}))},t.prototype.watch=function(e){var t=this;return this.watches.size||r0(this),this.watches.add(e),e.immediate&&this.maybeBroadcastWatch(e),function(){t.watches.delete(e)&&!t.watches.size&&r1(t),t.maybeBroadcastWatch.forget(e)}},t.prototype.gc=function(e){nx.reset();var t=this.optimisticData.gc();return e&&!this.txCount&&(e.resetResultCache?this.resetResultCache(e.resetResultIdentities):e.resetResultIdentities&&this.storeReader.resetCanon()),t},t.prototype.retain=function(e,t){return(t?this.optimisticData:this.data).retain(e)},t.prototype.release=function(e,t){return(t?this.optimisticData:this.data).release(e)},t.prototype.identify=function(e){if(eD(e))return e.__ref;try{return this.policies.identify(e)[0]}catch(t){__DEV__&&Q.kG.warn(t)}},t.prototype.evict=function(e){if(!e.id){if(ic.call(e,"id"))return!1;e=(0,en.pi)((0,en.pi)({},e),{id:"ROOT_QUERY"})}try{return++this.txCount,this.optimisticData.evict(e,this.data)}finally{--this.txCount||!1===e.broadcast||this.broadcastWatches()}},t.prototype.reset=function(e){var t=this;return this.init(),nx.reset(),e&&e.discardWatches?(this.watches.forEach(function(e){return t.maybeBroadcastWatch.forget(e)}),this.watches.clear(),r1(this)):this.broadcastWatches(),Promise.resolve()},t.prototype.removeOptimistic=function(e){var t=this.optimisticData.removeLayer(e);t!==this.optimisticData&&(this.optimisticData=t,this.broadcastWatches())},t.prototype.batch=function(e){var t,n=this,r=e.update,i=e.optimistic,a=void 0===i||i,o=e.removeOptimistic,s=e.onWatchUpdated,u=function(e){var i=n,a=i.data,o=i.optimisticData;++n.txCount,e&&(n.data=n.optimisticData=e);try{return t=r(n)}finally{--n.txCount,n.data=a,n.optimisticData=o}},c=new Set;return s&&!this.txCount&&this.broadcastWatches((0,en.pi)((0,en.pi)({},e),{onWatchUpdated:function(e){return c.add(e),!1}})),"string"==typeof a?this.optimisticData=this.optimisticData.addLayer(a,u):!1===a?u(this.data):u(),"string"==typeof o&&(this.optimisticData=this.optimisticData.removeLayer(o)),s&&c.size?(this.broadcastWatches((0,en.pi)((0,en.pi)({},e),{onWatchUpdated:function(e,t){var n=s.call(this,e,t);return!1!==n&&c.delete(e),n}})),c.size&&c.forEach(function(e){return n.maybeBroadcastWatch.dirty(e)})):this.broadcastWatches(e),t},t.prototype.performTransaction=function(e,t){return this.batch({update:e,optimistic:t||null!==t})},t.prototype.transformDocument=function(e){if(this.addTypename){var t=this.typenameDocumentCache.get(e);return t||(t=nj(e),this.typenameDocumentCache.set(e,t),this.typenameDocumentCache.set(t,t)),t}return e},t.prototype.transformForLink=function(e){var t=this.config.fragments;return t?t.transform(e):e},t.prototype.broadcastWatches=function(e){var t=this;this.txCount||this.watches.forEach(function(n){return t.maybeBroadcastWatch(n,e)})},t.prototype.broadcastWatch=function(e,t){var n=e.lastDiff,r=this.diff(e);(!t||(e.optimistic&&"string"==typeof t.optimistic&&(r.fromOptimisticTransaction=!0),!t.onWatchUpdated||!1!==t.onWatchUpdated.call(this,e,r,n)))&&(n&&(0,nm.D)(n.result,r.result)||e.callback(e.lastDiff=r,n))},t}(io),ar={possibleTypes:{ApproveJobProposalSpecPayload:["ApproveJobProposalSpecSuccess","JobAlreadyExistsError","NotFoundError"],BridgePayload:["Bridge","NotFoundError"],CancelJobProposalSpecPayload:["CancelJobProposalSpecSuccess","NotFoundError"],ChainPayload:["Chain","NotFoundError"],CreateAPITokenPayload:["CreateAPITokenSuccess","InputErrors"],CreateBridgePayload:["CreateBridgeSuccess"],CreateCSAKeyPayload:["CSAKeyExistsError","CreateCSAKeySuccess"],CreateFeedsManagerChainConfigPayload:["CreateFeedsManagerChainConfigSuccess","InputErrors","NotFoundError"],CreateFeedsManagerPayload:["CreateFeedsManagerSuccess","InputErrors","NotFoundError","SingleFeedsManagerError"],CreateJobPayload:["CreateJobSuccess","InputErrors"],CreateOCR2KeyBundlePayload:["CreateOCR2KeyBundleSuccess"],CreateOCRKeyBundlePayload:["CreateOCRKeyBundleSuccess"],CreateP2PKeyPayload:["CreateP2PKeySuccess"],DeleteAPITokenPayload:["DeleteAPITokenSuccess","InputErrors"],DeleteBridgePayload:["DeleteBridgeConflictError","DeleteBridgeInvalidNameError","DeleteBridgeSuccess","NotFoundError"],DeleteCSAKeyPayload:["DeleteCSAKeySuccess","NotFoundError"],DeleteFeedsManagerChainConfigPayload:["DeleteFeedsManagerChainConfigSuccess","NotFoundError"],DeleteJobPayload:["DeleteJobSuccess","NotFoundError"],DeleteOCR2KeyBundlePayload:["DeleteOCR2KeyBundleSuccess","NotFoundError"],DeleteOCRKeyBundlePayload:["DeleteOCRKeyBundleSuccess","NotFoundError"],DeleteP2PKeyPayload:["DeleteP2PKeySuccess","NotFoundError"],DeleteVRFKeyPayload:["DeleteVRFKeySuccess","NotFoundError"],DismissJobErrorPayload:["DismissJobErrorSuccess","NotFoundError"],Error:["CSAKeyExistsError","DeleteBridgeConflictError","DeleteBridgeInvalidNameError","InputError","JobAlreadyExistsError","NotFoundError","RunJobCannotRunError","SingleFeedsManagerError"],EthTransactionPayload:["EthTransaction","NotFoundError"],FeaturesPayload:["Features"],FeedsManagerPayload:["FeedsManager","NotFoundError"],GetSQLLoggingPayload:["SQLLogging"],GlobalLogLevelPayload:["GlobalLogLevel"],JobPayload:["Job","NotFoundError"],JobProposalPayload:["JobProposal","NotFoundError"],JobRunPayload:["JobRun","NotFoundError"],JobSpec:["BlockHeaderFeederSpec","BlockhashStoreSpec","BootstrapSpec","CronSpec","DirectRequestSpec","FluxMonitorSpec","GatewaySpec","KeeperSpec","OCR2Spec","OCRSpec","StandardCapabilitiesSpec","VRFSpec","WebhookSpec","WorkflowSpec"],NodePayload:["Node","NotFoundError"],PaginatedPayload:["BridgesPayload","ChainsPayload","EthTransactionAttemptsPayload","EthTransactionsPayload","JobRunsPayload","JobsPayload","NodesPayload"],RejectJobProposalSpecPayload:["NotFoundError","RejectJobProposalSpecSuccess"],RunJobPayload:["NotFoundError","RunJobCannotRunError","RunJobSuccess"],SetGlobalLogLevelPayload:["InputErrors","SetGlobalLogLevelSuccess"],SetSQLLoggingPayload:["SetSQLLoggingSuccess"],SetServicesLogLevelsPayload:["InputErrors","SetServicesLogLevelsSuccess"],UpdateBridgePayload:["NotFoundError","UpdateBridgeSuccess"],UpdateFeedsManagerChainConfigPayload:["InputErrors","NotFoundError","UpdateFeedsManagerChainConfigSuccess"],UpdateFeedsManagerPayload:["InputErrors","NotFoundError","UpdateFeedsManagerSuccess"],UpdateJobProposalSpecDefinitionPayload:["NotFoundError","UpdateJobProposalSpecDefinitionSuccess"],UpdatePasswordPayload:["InputErrors","UpdatePasswordSuccess"],VRFKeyPayload:["NotFoundError","VRFKeySuccess"]}};let ai=ar;var aa=(r=void 0,location.origin),ao=new nh({uri:"".concat(aa,"/query"),credentials:"include"}),as=new ia({cache:new an({possibleTypes:ai.possibleTypes}),link:ao});if(a.Z.locale(o),u().defaultFormat="YYYY-MM-DD h:mm:ss A","undefined"!=typeof document){var au,ac,al=f().hydrate;ac=X,al(c.createElement(et,{client:as},c.createElement(d.zj,null,c.createElement(i.MuiThemeProvider,{theme:J.r},c.createElement(ac,null)))),document.getElementById("root"))}})()})(); \ No newline at end of file +`+(a!==i?`result of cast: ${a}`:""))}return r}_cast(e,t){let n=void 0===e?e:this.transforms.reduce((t,n)=>n.call(this,t,e,this),e);return void 0===n&&(n=this.getDefault()),n}_validate(e,t={},n){let{sync:r,path:i,from:a=[],originalValue:o=e,strict:s=this.spec.strict,abortEarly:u=this.spec.abortEarly}=t,c=e;s||(c=this._cast(c,pB({assert:!1},t)));let l={value:c,path:i,options:t,originalValue:o,schema:this,label:this.spec.label,sync:r,from:a},f=[];this._typeError&&f.push(this._typeError),this._whitelistError&&f.push(this._whitelistError),this._blacklistError&&f.push(this._blacklistError),pO({args:l,value:c,path:i,sync:r,tests:f,endEarly:u},e=>{if(e)return void n(e,c);pO({tests:this.tests,args:l,path:i,sync:r,value:c,endEarly:u},n)})}validate(e,t,n){let r=this.resolve(pB({},t,{value:e}));return"function"==typeof n?r._validate(e,t,n):new Promise((n,i)=>r._validate(e,t,(e,t)=>{e?i(e):n(t)}))}validateSync(e,t){let n;return this.resolve(pB({},t,{value:e}))._validate(e,pB({},t,{sync:!0}),(e,t)=>{if(e)throw e;n=t}),n}isValid(e,t){return this.validate(e,t).then(()=>!0,e=>{if(pT.isError(e))return!1;throw e})}isValidSync(e,t){try{return this.validateSync(e,t),!0}catch(n){if(pT.isError(n))return!1;throw n}}_getDefault(){let e=this.spec.default;return null==e?e:"function"==typeof e?e.call(this):pn(e)}getDefault(e){return this.resolve(e||{})._getDefault()}default(e){return 0===arguments.length?this._getDefault():this.clone({default:e})}strict(e=!0){var t=this.clone();return t.spec.strict=e,t}_isPresent(e){return null!=e}defined(e=pf.defined){return this.test({message:e,name:"defined",exclusive:!0,test:e=>void 0!==e})}required(e=pf.required){return this.clone({presence:"required"}).withMutation(t=>t.test({message:e,name:"required",exclusive:!0,test(e){return this.schema._isPresent(e)}}))}notRequired(){var e=this.clone({presence:"optional"});return e.tests=e.tests.filter(e=>"required"!==e.OPTIONS.name),e}nullable(e=!0){return this.clone({nullable:!1!==e})}transform(e){var t=this.clone();return t.transforms.push(e),t}test(...e){let t;if(void 0===(t=1===e.length?"function"==typeof e[0]?{test:e[0]}:e[0]:2===e.length?{name:e[0],test:e[1]}:{name:e[0],message:e[1],test:e[2]}).message&&(t.message=pf.default),"function"!=typeof t.test)throw TypeError("`test` is a required parameters");let n=this.clone(),r=pR(t),i=t.exclusive||t.name&&!0===n.exclusiveTests[t.name];if(t.exclusive&&!t.name)throw TypeError("Exclusive tests must provide a unique `name` identifying the test");return t.name&&(n.exclusiveTests[t.name]=!!t.exclusive),n.tests=n.tests.filter(e=>e.OPTIONS.name!==t.name||!i&&e.OPTIONS.test!==r.OPTIONS.test),n.tests.push(r),n}when(e,t){Array.isArray(e)||"string"==typeof e||(t=e,e=".");let n=this.clone(),r=pS(e).map(e=>new pD(e));return r.forEach(e=>{e.isSibling&&n.deps.push(e.key)}),n.conditions.push(new pE(r,t)),n}typeError(e){var t=this.clone();return t._typeError=pR({message:e,name:"typeError",test(e){return!!(void 0===e||this.schema.isType(e))||this.createError({params:{type:this.schema._type}})}}),t}oneOf(e,t=pf.oneOf){var n=this.clone();return e.forEach(e=>{n._whitelist.add(e),n._blacklist.delete(e)}),n._whitelistError=pR({message:t,name:"oneOf",test(e){if(void 0===e)return!0;let t=this.schema._whitelist;return!!t.has(e,this.resolve)||this.createError({params:{values:t.toArray().join(", ")}})}}),n}notOneOf(e,t=pf.notOneOf){var n=this.clone();return e.forEach(e=>{n._blacklist.add(e),n._whitelist.delete(e)}),n._blacklistError=pR({message:t,name:"notOneOf",test(e){let t=this.schema._blacklist;return!t.has(e,this.resolve)||this.createError({params:{values:t.toArray().join(", ")}})}}),n}strip(e=!0){let t=this.clone();return t.spec.strip=e,t}describe(){let e=this.clone(),{label:t,meta:n}=e.spec,r={meta:n,label:t,type:e.type,oneOf:e._whitelist.describe(),notOneOf:e._blacklist.describe(),tests:e.tests.map(e=>({name:e.OPTIONS.name,params:e.OPTIONS.params})).filter((e,t,n)=>n.findIndex(t=>t.name===e.name)===t)};return r}}for(let pH of(pU.prototype.__isYupSchema__=!0,["validate","validateSync"]))pU.prototype[`${pH}At`]=function(e,t,n={}){let{parent:r,parentPath:i,schema:a}=pF(this,e,t,n.context);return a[pH](r&&r[i],pB({},n,{parent:r,path:e}))};for(let p$ of["equals","is"])pU.prototype[p$]=pU.prototype.oneOf;for(let pz of["not","nope"])pU.prototype[pz]=pU.prototype.notOneOf;pU.prototype.optional=pU.prototype.notRequired;let pG=pU;function pW(){return new pG}pW.prototype=pG.prototype;let pK=e=>null==e;function pV(){return new pq}class pq extends pU{constructor(){super({type:"boolean"}),this.withMutation(()=>{this.transform(function(e){if(!this.isType(e)){if(/^(true|1)$/i.test(String(e)))return!0;if(/^(false|0)$/i.test(String(e)))return!1}return e})})}_typeCheck(e){return e instanceof Boolean&&(e=e.valueOf()),"boolean"==typeof e}isTrue(e=pb.isValue){return this.test({message:e,name:"is-value",exclusive:!0,params:{value:"true"},test:e=>pK(e)||!0===e})}isFalse(e=pb.isValue){return this.test({message:e,name:"is-value",exclusive:!0,params:{value:"false"},test:e=>pK(e)||!1===e})}}pV.prototype=pq.prototype;let pZ=/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i,pX=/^((https?|ftp):)?\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i,pJ=/^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i,pQ=e=>pK(e)||e===e.trim(),p1=({}).toString();function p0(){return new p2}class p2 extends pU{constructor(){super({type:"string"}),this.withMutation(()=>{this.transform(function(e){if(this.isType(e)||Array.isArray(e))return e;let t=null!=e&&e.toString?e.toString():e;return t===p1?e:t})})}_typeCheck(e){return e instanceof String&&(e=e.valueOf()),"string"==typeof e}_isPresent(e){return super._isPresent(e)&&!!e.length}length(e,t=pd.length){return this.test({message:t,name:"length",exclusive:!0,params:{length:e},test(t){return pK(t)||t.length===this.resolve(e)}})}min(e,t=pd.min){return this.test({message:t,name:"min",exclusive:!0,params:{min:e},test(t){return pK(t)||t.length>=this.resolve(e)}})}max(e,t=pd.max){return this.test({name:"max",exclusive:!0,message:t,params:{max:e},test(t){return pK(t)||t.length<=this.resolve(e)}})}matches(e,t){let n=!1,r,i;return t&&("object"==typeof t?{excludeEmptyString:n=!1,message:r,name:i}=t:r=t),this.test({name:i||"matches",message:r||pd.matches,params:{regex:e},test:t=>pK(t)||""===t&&n||-1!==t.search(e)})}email(e=pd.email){return this.matches(pZ,{name:"email",message:e,excludeEmptyString:!0})}url(e=pd.url){return this.matches(pX,{name:"url",message:e,excludeEmptyString:!0})}uuid(e=pd.uuid){return this.matches(pJ,{name:"uuid",message:e,excludeEmptyString:!1})}ensure(){return this.default("").transform(e=>null===e?"":e)}trim(e=pd.trim){return this.transform(e=>null!=e?e.trim():e).test({message:e,name:"trim",test:pQ})}lowercase(e=pd.lowercase){return this.transform(e=>pK(e)?e:e.toLowerCase()).test({message:e,name:"string_case",exclusive:!0,test:e=>pK(e)||e===e.toLowerCase()})}uppercase(e=pd.uppercase){return this.transform(e=>pK(e)?e:e.toUpperCase()).test({message:e,name:"string_case",exclusive:!0,test:e=>pK(e)||e===e.toUpperCase()})}}p0.prototype=p2.prototype;let p3=e=>e!=+e;function p4(){return new p6}class p6 extends pU{constructor(){super({type:"number"}),this.withMutation(()=>{this.transform(function(e){let t=e;if("string"==typeof t){if(""===(t=t.replace(/\s/g,"")))return NaN;t=+t}return this.isType(t)?t:parseFloat(t)})})}_typeCheck(e){return e instanceof Number&&(e=e.valueOf()),"number"==typeof e&&!p3(e)}min(e,t=ph.min){return this.test({message:t,name:"min",exclusive:!0,params:{min:e},test(t){return pK(t)||t>=this.resolve(e)}})}max(e,t=ph.max){return this.test({message:t,name:"max",exclusive:!0,params:{max:e},test(t){return pK(t)||t<=this.resolve(e)}})}lessThan(e,t=ph.lessThan){return this.test({message:t,name:"max",exclusive:!0,params:{less:e},test(t){return pK(t)||tthis.resolve(e)}})}positive(e=ph.positive){return this.moreThan(0,e)}negative(e=ph.negative){return this.lessThan(0,e)}integer(e=ph.integer){return this.test({name:"integer",message:e,test:e=>pK(e)||Number.isInteger(e)})}truncate(){return this.transform(e=>pK(e)?e:0|e)}round(e){var t,n=["ceil","floor","round","trunc"];if("trunc"===(e=(null==(t=e)?void 0:t.toLowerCase())||"round"))return this.truncate();if(-1===n.indexOf(e.toLowerCase()))throw TypeError("Only valid options for round() are: "+n.join(", "));return this.transform(t=>pK(t)?t:Math[e](t))}}p4.prototype=p6.prototype;var p5=/^(\d{4}|[+\-]\d{6})(?:-?(\d{2})(?:-?(\d{2}))?)?(?:[ T]?(\d{2}):?(\d{2})(?::?(\d{2})(?:[,\.](\d{1,}))?)?(?:(Z)|([+\-])(\d{2})(?::?(\d{2}))?)?)?$/;function p8(e){var t,n,r=[1,4,5,6,7,10,11],i=0;if(n=p5.exec(e)){for(var a,o=0;a=r[o];++o)n[a]=+n[a]||0;n[2]=(+n[2]||1)-1,n[3]=+n[3]||1,n[7]=n[7]?String(n[7]).substr(0,3):0,(void 0===n[8]||""===n[8])&&(void 0===n[9]||""===n[9])?t=+new Date(n[1],n[2],n[3],n[4],n[5],n[6],n[7]):("Z"!==n[8]&&void 0!==n[9]&&(i=60*n[10]+n[11],"+"===n[9]&&(i=0-i)),t=Date.UTC(n[1],n[2],n[3],n[4],n[5]+i,n[6],n[7]))}else t=Date.parse?Date.parse(e):NaN;return t}let p9=new Date(""),p7=e=>"[object Date]"===Object.prototype.toString.call(e);function be(){return new bt}class bt extends pU{constructor(){super({type:"date"}),this.withMutation(()=>{this.transform(function(e){return this.isType(e)?e:(e=p8(e),isNaN(e)?p9:new Date(e))})})}_typeCheck(e){return p7(e)&&!isNaN(e.getTime())}prepareParam(e,t){let n;if(pD.isRef(e))n=e;else{let r=this.cast(e);if(!this._typeCheck(r))throw TypeError(`\`${t}\` must be a Date or a value that can be \`cast()\` to a Date`);n=r}return n}min(e,t=pp.min){let n=this.prepareParam(e,"min");return this.test({message:t,name:"min",exclusive:!0,params:{min:e},test(e){return pK(e)||e>=this.resolve(n)}})}max(e,t=pp.max){var n=this.prepareParam(e,"max");return this.test({message:t,name:"max",exclusive:!0,params:{max:e},test(e){return pK(e)||e<=this.resolve(n)}})}}bt.INVALID_DATE=p9,be.prototype=bt.prototype,be.INVALID_DATE=p9;var bn=n(11865),br=n.n(bn),bi=n(68929),ba=n.n(bi),bo=n(67523),bs=n.n(bo),bu=n(94633),bc=n.n(bu);function bl(e,t=[]){let n=[],r=[];function i(e,i){var a=(0,pC.split)(e)[0];~r.indexOf(a)||r.push(a),~t.indexOf(`${i}-${a}`)||n.push([i,a])}for(let a in e)if(py()(e,a)){let o=e[a];~r.indexOf(a)||r.push(a),pD.isRef(o)&&o.isSibling?i(o.path,a):pw(o)&&"deps"in o&&o.deps.forEach(e=>i(e,a))}return bc().array(r,n).reverse()}function bf(e,t){let n=1/0;return e.some((e,r)=>{var i;if((null==(i=t.path)?void 0:i.indexOf(e))!==-1)return n=r,!0}),n}function bd(e){return(t,n)=>bf(e,t)-bf(e,n)}function bh(){return(bh=Object.assign||function(e){for(var t=1;t"[object Object]"===Object.prototype.toString.call(e);function bb(e,t){let n=Object.keys(e.fields);return Object.keys(t).filter(e=>-1===n.indexOf(e))}let bm=bd([]);class bg extends pU{constructor(e){super({type:"object"}),this.fields=Object.create(null),this._sortErrors=bm,this._nodes=[],this._excludedEdges=[],this.withMutation(()=>{this.transform(function(e){if("string"==typeof e)try{e=JSON.parse(e)}catch(t){e=null}return this.isType(e)?e:null}),e&&this.shape(e)})}_typeCheck(e){return bp(e)||"function"==typeof e}_cast(e,t={}){var n;let r=super._cast(e,t);if(void 0===r)return this.getDefault();if(!this._typeCheck(r))return r;let i=this.fields,a=null!=(n=t.stripUnknown)?n:this.spec.noUnknown,o=this._nodes.concat(Object.keys(r).filter(e=>-1===this._nodes.indexOf(e))),s={},u=bh({},t,{parent:s,__validating:t.__validating||!1}),c=!1;for(let l of o){let f=i[l],d=py()(r,l);if(f){let h,p=r[l];u.path=(t.path?`${t.path}.`:"")+l;let b="spec"in(f=f.resolve({value:p,context:t.context,parent:s}))?f.spec:void 0,m=null==b?void 0:b.strict;if(null==b?void 0:b.strip){c=c||l in r;continue}void 0!==(h=t.__validating&&m?r[l]:f.cast(r[l],u))&&(s[l]=h)}else d&&!a&&(s[l]=r[l]);s[l]!==r[l]&&(c=!0)}return c?s:r}_validate(e,t={},n){let r=[],{sync:i,from:a=[],originalValue:o=e,abortEarly:s=this.spec.abortEarly,recursive:u=this.spec.recursive}=t;a=[{schema:this,value:o},...a],t.__validating=!0,t.originalValue=o,t.from=a,super._validate(e,t,(e,c)=>{if(e){if(!pT.isError(e)||s)return void n(e,c);r.push(e)}if(!u||!bp(c)){n(r[0]||null,c);return}o=o||c;let l=this._nodes.map(e=>(n,r)=>{let i=-1===e.indexOf(".")?(t.path?`${t.path}.`:"")+e:`${t.path||""}["${e}"]`,s=this.fields[e];if(s&&"validate"in s){s.validate(c[e],bh({},t,{path:i,from:a,strict:!0,parent:c,originalValue:o[e]}),r);return}r(null)});pO({sync:i,tests:l,value:c,errors:r,endEarly:s,sort:this._sortErrors,path:t.path},n)})}clone(e){let t=super.clone(e);return t.fields=bh({},this.fields),t._nodes=this._nodes,t._excludedEdges=this._excludedEdges,t._sortErrors=this._sortErrors,t}concat(e){let t=super.concat(e),n=t.fields;for(let[r,i]of Object.entries(this.fields)){let a=n[r];void 0===a?n[r]=i:a instanceof pU&&i instanceof pU&&(n[r]=i.concat(a))}return t.withMutation(()=>t.shape(n))}getDefaultFromShape(){let e={};return this._nodes.forEach(t=>{let n=this.fields[t];e[t]="default"in n?n.getDefault():void 0}),e}_getDefault(){return"default"in this.spec?super._getDefault():this._nodes.length?this.getDefaultFromShape():void 0}shape(e,t=[]){let n=this.clone(),r=Object.assign(n.fields,e);if(n.fields=r,n._sortErrors=bd(Object.keys(r)),t.length){Array.isArray(t[0])||(t=[t]);let i=t.map(([e,t])=>`${e}-${t}`);n._excludedEdges=n._excludedEdges.concat(i)}return n._nodes=bl(r,n._excludedEdges),n}pick(e){let t={};for(let n of e)this.fields[n]&&(t[n]=this.fields[n]);return this.clone().withMutation(e=>(e.fields={},e.shape(t)))}omit(e){let t=this.clone(),n=t.fields;for(let r of(t.fields={},e))delete n[r];return t.withMutation(()=>t.shape(n))}from(e,t,n){let r=(0,pC.getter)(e,!0);return this.transform(i=>{if(null==i)return i;let a=i;return py()(i,e)&&(a=bh({},i),n||delete a[e],a[t]=r(i)),a})}noUnknown(e=!0,t=pm.noUnknown){"string"==typeof e&&(t=e,e=!0);let n=this.test({name:"noUnknown",exclusive:!0,message:t,test(t){if(null==t)return!0;let n=bb(this.schema,t);return!e||0===n.length||this.createError({params:{unknown:n.join(", ")}})}});return n.spec.noUnknown=e,n}unknown(e=!0,t=pm.noUnknown){return this.noUnknown(!e,t)}transformKeys(e){return this.transform(t=>t&&bs()(t,(t,n)=>e(n)))}camelCase(){return this.transformKeys(ba())}snakeCase(){return this.transformKeys(br())}constantCase(){return this.transformKeys(e=>br()(e).toUpperCase())}describe(){let e=super.describe();return e.fields=pL()(this.fields,e=>e.describe()),e}}function bv(e){return new bg(e)}function by(){return(by=Object.assign||function(e){for(var t=1;t{this.transform(function(e){if("string"==typeof e)try{e=JSON.parse(e)}catch(t){e=null}return this.isType(e)?e:null})})}_typeCheck(e){return Array.isArray(e)}get _subType(){return this.innerType}_cast(e,t){let n=super._cast(e,t);if(!this._typeCheck(n)||!this.innerType)return n;let r=!1,i=n.map((e,n)=>{let i=this.innerType.cast(e,by({},t,{path:`${t.path||""}[${n}]`}));return i!==e&&(r=!0),i});return r?i:n}_validate(e,t={},n){var r,i;let a=[],o=t.sync,s=t.path,u=this.innerType,c=null!=(r=t.abortEarly)?r:this.spec.abortEarly,l=null!=(i=t.recursive)?i:this.spec.recursive,f=null!=t.originalValue?t.originalValue:e;super._validate(e,t,(e,r)=>{if(e){if(!pT.isError(e)||c)return void n(e,r);a.push(e)}if(!l||!u||!this._typeCheck(r)){n(a[0]||null,r);return}f=f||r;let i=Array(r.length);for(let d=0;du.validate(h,b,t)}pO({sync:o,path:s,value:r,errors:a,endEarly:c,tests:i},n)})}clone(e){let t=super.clone(e);return t.innerType=this.innerType,t}concat(e){let t=super.concat(e);return t.innerType=this.innerType,e.innerType&&(t.innerType=t.innerType?t.innerType.concat(e.innerType):e.innerType),t}of(e){let t=this.clone();if(!pw(e))throw TypeError("`array.of()` sub-schema must be a valid yup schema not: "+pl(e));return t.innerType=e,t}length(e,t=pg.length){return this.test({message:t,name:"length",exclusive:!0,params:{length:e},test(t){return pK(t)||t.length===this.resolve(e)}})}min(e,t){return t=t||pg.min,this.test({message:t,name:"min",exclusive:!0,params:{min:e},test(t){return pK(t)||t.length>=this.resolve(e)}})}max(e,t){return t=t||pg.max,this.test({message:t,name:"max",exclusive:!0,params:{max:e},test(t){return pK(t)||t.length<=this.resolve(e)}})}ensure(){return this.default(()=>[]).transform((e,t)=>this._typeCheck(e)?e:null==t?[]:[].concat(t))}compact(e){let t=e?(t,n,r)=>!e(t,n,r):e=>!!e;return this.transform(e=>null!=e?e.filter(t):e)}describe(){let e=super.describe();return this.innerType&&(e.innerType=this.innerType.describe()),e}nullable(e=!0){return super.nullable(e)}defined(){return super.defined()}required(e){return super.required(e)}}bw.prototype=b_.prototype;var bE=bv().shape({name:p0().required("Required"),url:p0().required("Required")}),bS=function(e){var t=e.initialValues,n=e.onSubmit,r=e.submitButtonText,i=e.nameDisabled,a=void 0!==i&&i;return l.createElement(hT,{initialValues:t,validationSchema:bE,onSubmit:n},function(e){var t=e.isSubmitting;return l.createElement(l.Fragment,null,l.createElement(hR,{"data-testid":"bridge-form",noValidate:!0},l.createElement(d.Z,{container:!0,spacing:16},l.createElement(d.Z,{item:!0,xs:12,md:7},l.createElement(hP,{component:hX,id:"name",name:"name",label:"Name",disabled:a,required:!0,fullWidth:!0,FormHelperTextProps:{"data-testid":"name-helper-text"}})),l.createElement(d.Z,{item:!0,xs:12,md:7},l.createElement(hP,{component:hX,id:"url",name:"url",label:"Bridge URL",placeholder:"https://",required:!0,fullWidth:!0,FormHelperTextProps:{"data-testid":"url-helper-text"}})),l.createElement(d.Z,{item:!0,xs:12,md:7},l.createElement(d.Z,{container:!0,spacing:16},l.createElement(d.Z,{item:!0,xs:7},l.createElement(hP,{component:hX,id:"minimumContractPayment",name:"minimumContractPayment",label:"Minimum Contract Payment",placeholder:"0",fullWidth:!0,inputProps:{min:0},FormHelperTextProps:{"data-testid":"minimumContractPayment-helper-text"}})),l.createElement(d.Z,{item:!0,xs:7},l.createElement(hP,{component:hX,id:"confirmations",name:"confirmations",label:"Confirmations",placeholder:"0",type:"number",fullWidth:!0,inputProps:{min:0},FormHelperTextProps:{"data-testid":"confirmations-helper-text"}})))),l.createElement(d.Z,{item:!0,xs:12,md:7},l.createElement(ok.default,{variant:"contained",color:"primary",type:"submit",disabled:t,size:"large"},r)))))})},bk=function(e){var t=e.bridge,n=e.onSubmit,r={name:t.name,url:t.url,minimumContractPayment:t.minimumContractPayment,confirmations:t.confirmations};return l.createElement(ig,null,l.createElement(d.Z,{container:!0,spacing:40},l.createElement(d.Z,{item:!0,xs:12,md:11,lg:9},l.createElement(r5.Z,null,l.createElement(sl.Z,{title:"Edit Bridge",action:l.createElement(aA.Z,{component:tz,href:"/bridges/".concat(t.id)},"Cancel")}),l.createElement(aW.Z,null,l.createElement(bS,{nameDisabled:!0,initialValues:r,onSubmit:n,submitButtonText:"Save Bridge"}))))))};function bx(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&void 0!==arguments[0]&&arguments[0],t=e?function(){return l.createElement(x.default,{variant:"body1"},"Loading...")}:function(){return null};return{isLoading:e,LoadingPlaceholder:t}},mc=n(76023);function ml(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}function mB(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n=4?[e[0],e[1],e[2],e[3],"".concat(e[0],".").concat(e[1]),"".concat(e[0],".").concat(e[2]),"".concat(e[0],".").concat(e[3]),"".concat(e[1],".").concat(e[0]),"".concat(e[1],".").concat(e[2]),"".concat(e[1],".").concat(e[3]),"".concat(e[2],".").concat(e[0]),"".concat(e[2],".").concat(e[1]),"".concat(e[2],".").concat(e[3]),"".concat(e[3],".").concat(e[0]),"".concat(e[3],".").concat(e[1]),"".concat(e[3],".").concat(e[2]),"".concat(e[0],".").concat(e[1],".").concat(e[2]),"".concat(e[0],".").concat(e[1],".").concat(e[3]),"".concat(e[0],".").concat(e[2],".").concat(e[1]),"".concat(e[0],".").concat(e[2],".").concat(e[3]),"".concat(e[0],".").concat(e[3],".").concat(e[1]),"".concat(e[0],".").concat(e[3],".").concat(e[2]),"".concat(e[1],".").concat(e[0],".").concat(e[2]),"".concat(e[1],".").concat(e[0],".").concat(e[3]),"".concat(e[1],".").concat(e[2],".").concat(e[0]),"".concat(e[1],".").concat(e[2],".").concat(e[3]),"".concat(e[1],".").concat(e[3],".").concat(e[0]),"".concat(e[1],".").concat(e[3],".").concat(e[2]),"".concat(e[2],".").concat(e[0],".").concat(e[1]),"".concat(e[2],".").concat(e[0],".").concat(e[3]),"".concat(e[2],".").concat(e[1],".").concat(e[0]),"".concat(e[2],".").concat(e[1],".").concat(e[3]),"".concat(e[2],".").concat(e[3],".").concat(e[0]),"".concat(e[2],".").concat(e[3],".").concat(e[1]),"".concat(e[3],".").concat(e[0],".").concat(e[1]),"".concat(e[3],".").concat(e[0],".").concat(e[2]),"".concat(e[3],".").concat(e[1],".").concat(e[0]),"".concat(e[3],".").concat(e[1],".").concat(e[2]),"".concat(e[3],".").concat(e[2],".").concat(e[0]),"".concat(e[3],".").concat(e[2],".").concat(e[1]),"".concat(e[0],".").concat(e[1],".").concat(e[2],".").concat(e[3]),"".concat(e[0],".").concat(e[1],".").concat(e[3],".").concat(e[2]),"".concat(e[0],".").concat(e[2],".").concat(e[1],".").concat(e[3]),"".concat(e[0],".").concat(e[2],".").concat(e[3],".").concat(e[1]),"".concat(e[0],".").concat(e[3],".").concat(e[1],".").concat(e[2]),"".concat(e[0],".").concat(e[3],".").concat(e[2],".").concat(e[1]),"".concat(e[1],".").concat(e[0],".").concat(e[2],".").concat(e[3]),"".concat(e[1],".").concat(e[0],".").concat(e[3],".").concat(e[2]),"".concat(e[1],".").concat(e[2],".").concat(e[0],".").concat(e[3]),"".concat(e[1],".").concat(e[2],".").concat(e[3],".").concat(e[0]),"".concat(e[1],".").concat(e[3],".").concat(e[0],".").concat(e[2]),"".concat(e[1],".").concat(e[3],".").concat(e[2],".").concat(e[0]),"".concat(e[2],".").concat(e[0],".").concat(e[1],".").concat(e[3]),"".concat(e[2],".").concat(e[0],".").concat(e[3],".").concat(e[1]),"".concat(e[2],".").concat(e[1],".").concat(e[0],".").concat(e[3]),"".concat(e[2],".").concat(e[1],".").concat(e[3],".").concat(e[0]),"".concat(e[2],".").concat(e[3],".").concat(e[0],".").concat(e[1]),"".concat(e[2],".").concat(e[3],".").concat(e[1],".").concat(e[0]),"".concat(e[3],".").concat(e[0],".").concat(e[1],".").concat(e[2]),"".concat(e[3],".").concat(e[0],".").concat(e[2],".").concat(e[1]),"".concat(e[3],".").concat(e[1],".").concat(e[0],".").concat(e[2]),"".concat(e[3],".").concat(e[1],".").concat(e[2],".").concat(e[0]),"".concat(e[3],".").concat(e[2],".").concat(e[0],".").concat(e[1]),"".concat(e[3],".").concat(e[2],".").concat(e[1],".").concat(e[0])]:void 0}var mZ={};function mX(e){if(0===e.length||1===e.length)return e;var t=e.join(".");return mZ[t]||(mZ[t]=mq(e)),mZ[t]}function mJ(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2?arguments[2]:void 0;return mX(e.filter(function(e){return"token"!==e})).reduce(function(e,t){return mK({},e,n[t])},t)}function mQ(e){return e.join(" ")}function m1(e,t){var n=0;return function(r){return n+=1,r.map(function(r,i){return m0({node:r,stylesheet:e,useInlineStyles:t,key:"code-segment-".concat(n,"-").concat(i)})})}}function m0(e){var t=e.node,n=e.stylesheet,r=e.style,i=void 0===r?{}:r,a=e.useInlineStyles,o=e.key,s=t.properties,u=t.type,c=t.tagName,f=t.value;if("text"===u)return f;if(c){var d,h=m1(n,a);if(a){var p=Object.keys(n).reduce(function(e,t){return t.split(".").forEach(function(t){e.includes(t)||e.push(t)}),e},[]),b=s.className&&s.className.includes("token")?["token"]:[],m=s.className&&b.concat(s.className.filter(function(e){return!p.includes(e)}));d=mK({},s,{className:mQ(m)||void 0,style:mJ(s.className,Object.assign({},s.style,i),n)})}else d=mK({},s,{className:mQ(s.className)});var g=h(t.children);return l.createElement(c,(0,mV.Z)({key:o},d),g)}}let m2=function(e,t){return -1!==e.listLanguages().indexOf(t)};var m3=/\n/g;function m4(e){return e.match(m3)}function m6(e){var t=e.lines,n=e.startingLineNumber,r=e.style;return t.map(function(e,t){var i=t+n;return l.createElement("span",{key:"line-".concat(t),className:"react-syntax-highlighter-line-number",style:"function"==typeof r?r(i):r},"".concat(i,"\n"))})}function m5(e){var t=e.codeString,n=e.codeStyle,r=e.containerStyle,i=void 0===r?{float:"left",paddingRight:"10px"}:r,a=e.numberStyle,o=void 0===a?{}:a,s=e.startingLineNumber;return l.createElement("code",{style:Object.assign({},n,i)},m6({lines:t.replace(/\n$/,"").split("\n"),style:o,startingLineNumber:s}))}function m8(e){return"".concat(e.toString().length,".25em")}function m9(e,t){return{type:"element",tagName:"span",properties:{key:"line-number--".concat(e),className:["comment","linenumber","react-syntax-highlighter-line-number"],style:t},children:[{type:"text",value:e}]}}function m7(e,t,n){var r,i={display:"inline-block",minWidth:m8(n),paddingRight:"1em",textAlign:"right",userSelect:"none"};return mK({},i,"function"==typeof e?e(t):e)}function ge(e){var t=e.children,n=e.lineNumber,r=e.lineNumberStyle,i=e.largestLineNumber,a=e.showInlineLineNumbers,o=e.lineProps,s=void 0===o?{}:o,u=e.className,c=void 0===u?[]:u,l=e.showLineNumbers,f=e.wrapLongLines,d="function"==typeof s?s(n):s;if(d.className=c,n&&a){var h=m7(r,n,i);t.unshift(m9(n,h))}return f&l&&(d.style=mK({},d.style,{display:"flex"})),{type:"element",tagName:"span",properties:d,children:t}}function gt(e){for(var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],r=0;r2&&void 0!==arguments[2]?arguments[2]:[];return ge({children:e,lineNumber:t,lineNumberStyle:s,largestLineNumber:o,showInlineLineNumbers:i,lineProps:n,className:a,showLineNumbers:r,wrapLongLines:u})}function b(e,t){if(r&&t&&i){var n=m7(s,t,o);e.unshift(m9(t,n))}return e}function m(e,n){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[];return t||r.length>0?p(e,n,r):b(e,n)}for(var g=function(){var e=l[h],t=e.children[0].value;if(m4(t)){var n=t.split("\n");n.forEach(function(t,i){var o=r&&f.length+a,s={type:"text",value:"".concat(t,"\n")};if(0===i){var u=l.slice(d+1,h).concat(ge({children:[s],className:e.properties.className})),c=m(u,o);f.push(c)}else if(i===n.length-1){if(l[h+1]&&l[h+1].children&&l[h+1].children[0]){var p={type:"text",value:"".concat(t)},b=ge({children:[p],className:e.properties.className});l.splice(h+1,0,b)}else{var g=[s],v=m(g,o,e.properties.className);f.push(v)}}else{var y=[s],w=m(y,o,e.properties.className);f.push(w)}}),d=h}h++};h code[class*="language-"]':{background:"#f5f2f0",padding:".1em",borderRadius:".3em",whiteSpace:"normal"},comment:{color:"slategray"},prolog:{color:"slategray"},doctype:{color:"slategray"},cdata:{color:"slategray"},punctuation:{color:"#999"},namespace:{Opacity:".7"},property:{color:"#905"},tag:{color:"#905"},boolean:{color:"#905"},number:{color:"#905"},constant:{color:"#905"},symbol:{color:"#905"},deleted:{color:"#905"},selector:{color:"#690"},"attr-name":{color:"#690"},string:{color:"#690"},char:{color:"#690"},builtin:{color:"#690"},inserted:{color:"#690"},operator:{color:"#9a6e3a",background:"hsla(0, 0%, 100%, .5)"},entity:{color:"#9a6e3a",background:"hsla(0, 0%, 100%, .5)",cursor:"help"},url:{color:"#9a6e3a",background:"hsla(0, 0%, 100%, .5)"},".language-css .token.string":{color:"#9a6e3a",background:"hsla(0, 0%, 100%, .5)"},".style .token.string":{color:"#9a6e3a",background:"hsla(0, 0%, 100%, .5)"},atrule:{color:"#07a"},"attr-value":{color:"#07a"},keyword:{color:"#07a"},function:{color:"#DD4A68"},"class-name":{color:"#DD4A68"},regex:{color:"#e90"},important:{color:"#e90",fontWeight:"bold"},variable:{color:"#e90"},bold:{fontWeight:"bold"},italic:{fontStyle:"italic"}};var gu=n(98695),gc=n.n(gu);let gl=["abap","abnf","actionscript","ada","agda","al","antlr4","apacheconf","apl","applescript","aql","arduino","arff","asciidoc","asm6502","aspnet","autohotkey","autoit","bash","basic","batch","bbcode","birb","bison","bnf","brainfuck","brightscript","bro","bsl","c","cil","clike","clojure","cmake","coffeescript","concurnas","cpp","crystal","csharp","csp","css-extras","css","cypher","d","dart","dax","dhall","diff","django","dns-zone-file","docker","ebnf","editorconfig","eiffel","ejs","elixir","elm","erb","erlang","etlua","excel-formula","factor","firestore-security-rules","flow","fortran","fsharp","ftl","gcode","gdscript","gedcom","gherkin","git","glsl","gml","go","graphql","groovy","haml","handlebars","haskell","haxe","hcl","hlsl","hpkp","hsts","http","ichigojam","icon","iecst","ignore","inform7","ini","io","j","java","javadoc","javadoclike","javascript","javastacktrace","jolie","jq","js-extras","js-templates","jsdoc","json","json5","jsonp","jsstacktrace","jsx","julia","keyman","kotlin","latex","latte","less","lilypond","liquid","lisp","livescript","llvm","lolcode","lua","makefile","markdown","markup-templating","markup","matlab","mel","mizar","mongodb","monkey","moonscript","n1ql","n4js","nand2tetris-hdl","naniscript","nasm","neon","nginx","nim","nix","nsis","objectivec","ocaml","opencl","oz","parigp","parser","pascal","pascaligo","pcaxis","peoplecode","perl","php-extras","php","phpdoc","plsql","powerquery","powershell","processing","prolog","properties","protobuf","pug","puppet","pure","purebasic","purescript","python","q","qml","qore","r","racket","reason","regex","renpy","rest","rip","roboconf","robotframework","ruby","rust","sas","sass","scala","scheme","scss","shell-session","smali","smalltalk","smarty","sml","solidity","solution-file","soy","sparql","splunk-spl","sqf","sql","stan","stylus","swift","t4-cs","t4-templating","t4-vb","tap","tcl","textile","toml","tsx","tt2","turtle","twig","typescript","typoscript","unrealscript","vala","vbnet","velocity","verilog","vhdl","vim","visual-basic","warpscript","wasm","wiki","xeora","xml-doc","xojo","xquery","yaml","yang","zig"];var gf=go(gc(),gs);gf.supportedLanguages=gl;let gd=gf;var gh=n(64566);function gp(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function gb(){var e=gp(["\n query FetchConfigV2 {\n configv2 {\n user\n effective\n }\n }\n"]);return gb=function(){return e},e}var gm=n0(gb()),gg=function(e){var t=e.children;return l.createElement(ir.Z,null,l.createElement(r7.default,{component:"th",scope:"row",colSpan:3},t))},gv=function(){return l.createElement(gg,null,"...")},gy=function(e){var t=e.children;return l.createElement(gg,null,t)},gw=function(e){var t=e.loading,n=e.toml,r=e.error,i=void 0===r?"":r,a=e.title,o=e.expanded;if(i)return l.createElement(gy,null,i);if(t)return l.createElement(gv,null);a||(a="TOML");var s={display:"block"};return l.createElement(x.default,null,l.createElement(mP.Z,{defaultExpanded:o},l.createElement(mR.Z,{expandIcon:l.createElement(gh.Z,null)},a),l.createElement(mj.Z,{style:s},l.createElement(gd,{language:"toml",style:gs},n))))},g_=function(){var e=rv(gm,{fetchPolicy:"cache-and-network"}),t=e.data,n=e.loading,r=e.error;return(null==t?void 0:t.configv2.effective)=="N/A"?l.createElement(l.Fragment,null,l.createElement(d.Z,{item:!0,xs:12},l.createElement(r5.Z,null,l.createElement(sl.Z,{title:"TOML Configuration"}),l.createElement(gw,{title:"V2 config dump:",error:null==r?void 0:r.message,loading:n,toml:null==t?void 0:t.configv2.user,showHead:!0})))):l.createElement(l.Fragment,null,l.createElement(d.Z,{container:!0},l.createElement(d.Z,{item:!0,xs:12},l.createElement(r5.Z,null,l.createElement(sl.Z,{title:"TOML Configuration"}),l.createElement(gw,{title:"User specified:",error:null==r?void 0:r.message,loading:n,toml:null==t?void 0:t.configv2.user,showHead:!0,expanded:!0}),l.createElement(gw,{title:"Effective (with defaults):",error:null==r?void 0:r.message,loading:n,toml:null==t?void 0:t.configv2.effective,showHead:!0})))))},gE=n(34823),gS=function(e){return(0,b.createStyles)({cell:{paddingTop:1.5*e.spacing.unit,paddingBottom:1.5*e.spacing.unit}})},gk=(0,b.withStyles)(gS)(function(e){var t=e.classes,n=(0,A.I0)();(0,l.useEffect)(function(){n((0,ty.DQ)())});var r=(0,A.v9)(gE.N,A.wU);return l.createElement(r5.Z,null,l.createElement(sl.Z,{title:"Node"}),l.createElement(r8.Z,null,l.createElement(r9.Z,null,l.createElement(ir.Z,null,l.createElement(r7.default,{className:t.cell},l.createElement(x.default,null,"Version"),l.createElement(x.default,{variant:"subtitle1",color:"textSecondary"},r.version))),l.createElement(ir.Z,null,l.createElement(r7.default,{className:t.cell},l.createElement(x.default,null,"SHA"),l.createElement(x.default,{variant:"subtitle1",color:"textSecondary"},r.commitSHA))))))}),gx=function(){return l.createElement(ig,null,l.createElement(d.Z,{container:!0},l.createElement(d.Z,{item:!0,sm:12,md:8},l.createElement(d.Z,{container:!0},l.createElement(g_,null))),l.createElement(d.Z,{item:!0,sm:12,md:4},l.createElement(d.Z,{container:!0},l.createElement(d.Z,{item:!0,xs:12},l.createElement(gk,null)),l.createElement(d.Z,{item:!0,xs:12},l.createElement(mN,null)),l.createElement(d.Z,{item:!0,xs:12},l.createElement(mE,null))))))},gT=function(){return l.createElement(gx,null)},gM=function(){return l.createElement(gT,null)},gO=n(44431),gA=1e18,gL=function(e){return new gO.BigNumber(e).dividedBy(gA).toFixed(8)},gC=function(e){var t=e.keys,n=e.chainID,r=e.hideHeaderTitle;return l.createElement(l.Fragment,null,l.createElement(sl.Z,{title:!r&&"Account Balances",subheader:"Chain ID "+n}),l.createElement(aW.Z,null,l.createElement(w.default,{dense:!1,disablePadding:!0},t&&t.map(function(e,r){return l.createElement(l.Fragment,null,l.createElement(_.default,{disableGutters:!0,key:["acc-balance",n.toString(),r.toString()].join("-")},l.createElement(E.Z,{primary:l.createElement(l.Fragment,null,l.createElement(d.Z,{container:!0,spacing:16},l.createElement(d.Z,{item:!0,xs:12},l.createElement(op,{title:"Address"}),l.createElement(ob,{value:e.address})),l.createElement(d.Z,{item:!0,xs:6},l.createElement(op,{title:"Native Token Balance"}),l.createElement(ob,{value:e.ethBalance||"--"})),l.createElement(d.Z,{item:!0,xs:6},l.createElement(op,{title:"LINK Balance"}),l.createElement(ob,{value:e.linkBalance?gL(e.linkBalance):"--"}))))})),r+1s&&l.createElement(gB.Z,null,l.createElement(ir.Z,null,l.createElement(r7.default,{className:r.footer},l.createElement(aA.Z,{href:"/runs",component:tz},"View More"))))))});function vt(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function vn(){var e=vt(["\n ","\n query FetchRecentJobRuns($offset: Int, $limit: Int) {\n jobRuns(offset: $offset, limit: $limit) {\n results {\n ...RecentJobRunsPayload_ResultsFields\n }\n metadata {\n total\n }\n }\n }\n"]);return vn=function(){return e},e}var vr=5,vi=n0(vn(),g9),va=function(){var e=rv(vi,{variables:{offset:0,limit:vr},fetchPolicy:"cache-and-network"}),t=e.data,n=e.loading,r=e.error;return l.createElement(ve,{data:t,errorMsg:null==r?void 0:r.message,loading:n,maxRunsSize:vr})},vo=function(e){return(0,b.createStyles)({style:{textAlign:"center",padding:2.5*e.spacing.unit,position:"fixed",left:"0",bottom:"0",width:"100%",borderRadius:0},bareAnchor:{color:e.palette.common.black,textDecoration:"none"}})},vs=(0,b.withStyles)(vo)(function(e){var t=e.classes,n=(0,A.v9)(gE.N,A.wU),r=(0,A.I0)();return(0,l.useEffect)(function(){r((0,ty.DQ)())}),l.createElement(ii.default,{className:t.style},l.createElement(x.default,null,"Chainlink Node ",n.version," at commit"," ",l.createElement("a",{target:"_blank",rel:"noopener noreferrer",href:"https://github.com/smartcontractkit/chainlink/commit/".concat(n.commitSHA),className:t.bareAnchor},n.commitSHA)))}),vu=function(e){return(0,b.createStyles)({cell:{borderColor:e.palette.divider,borderTop:"1px solid",borderBottom:"none",paddingTop:2*e.spacing.unit,paddingBottom:2*e.spacing.unit,paddingLeft:2*e.spacing.unit},block:{display:"block"},overflowEllipsis:{textOverflow:"ellipsis",overflow:"hidden"}})},vc=(0,b.withStyles)(vu)(function(e){var t=e.classes,n=e.job;return l.createElement(ir.Z,null,l.createElement(r7.default,{scope:"row",className:t.cell},l.createElement(d.Z,{container:!0,spacing:0},l.createElement(d.Z,{item:!0,xs:12},l.createElement(ih,{href:"/jobs/".concat(n.id),classes:{linkContent:t.block}},l.createElement(x.default,{className:t.overflowEllipsis,variant:"body1",component:"span",color:"primary"},n.name||n.id))),l.createElement(d.Z,{item:!0,xs:12},l.createElement(x.default,{variant:"body1",color:"textSecondary"},"Created ",l.createElement(aO,{tooltip:!0},n.createdAt))))))});function vl(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function vf(){var e=vl(["\n fragment RecentJobsPayload_ResultsFields on Job {\n id\n name\n createdAt\n }\n"]);return vf=function(){return e},e}var vd=n0(vf()),vh=function(){return(0,b.createStyles)({cardHeader:{borderBottom:0},table:{tableLayout:"fixed"}})},vp=(0,b.withStyles)(vh)(function(e){var t,n,r=e.classes,i=e.data,a=e.errorMsg,o=e.loading;return l.createElement(r5.Z,null,l.createElement(sl.Z,{title:"Recent Jobs",className:r.cardHeader}),l.createElement(r8.Z,{className:r.table},l.createElement(r9.Z,null,l.createElement(g$,{visible:o}),l.createElement(gz,{visible:(null===(t=null==i?void 0:i.jobs.results)||void 0===t?void 0:t.length)===0},"No recently created jobs"),l.createElement(gU,{msg:a}),null===(n=null==i?void 0:i.jobs.results)||void 0===n?void 0:n.map(function(e,t){return l.createElement(vc,{job:e,key:t})}))))});function vb(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function vm(){var e=vb(["\n ","\n query FetchRecentJobs($offset: Int, $limit: Int) {\n jobs(offset: $offset, limit: $limit) {\n results {\n ...RecentJobsPayload_ResultsFields\n }\n }\n }\n"]);return vm=function(){return e},e}var vg=5,vv=n0(vm(),vd),vy=function(){var e=rv(vv,{variables:{offset:0,limit:vg},fetchPolicy:"cache-and-network"}),t=e.data,n=e.loading,r=e.error;return l.createElement(vp,{data:t,errorMsg:null==r?void 0:r.message,loading:n})},vw=function(){return l.createElement(ig,null,l.createElement(d.Z,{container:!0},l.createElement(d.Z,{item:!0,xs:8},l.createElement(va,null)),l.createElement(d.Z,{item:!0,xs:4},l.createElement(d.Z,{container:!0},l.createElement(d.Z,{item:!0,xs:12},l.createElement(gY,null)),l.createElement(d.Z,{item:!0,xs:12},l.createElement(vy,null))))),l.createElement(vs,null))},v_=function(){return l.createElement(vw,null)},vE=function(){return l.createElement(v_,null)},vS=n(87239),vk=function(e){switch(e){case"DirectRequestSpec":return"Direct Request";case"FluxMonitorSpec":return"Flux Monitor";default:return e.replace(/Spec$/,"")}},vx=n(5022),vT=n(78718),vM=n.n(vT);function vO(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n1?t-1:0),r=1;r1?t-1:0),r=1;re.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&n.map(function(e){return l.createElement(ir.Z,{key:e.id,style:{cursor:"pointer"},onClick:function(){return r.push("/runs/".concat(e.id))}},l.createElement(r7.default,{className:t.idCell,scope:"row"},l.createElement("div",{className:t.runDetails},l.createElement(x.default,{variant:"h5",color:"primary",component:"span"},e.id))),l.createElement(r7.default,{className:t.stampCell},l.createElement(x.default,{variant:"body1",color:"textSecondary",className:t.stamp},"Created ",l.createElement(aO,{tooltip:!0},e.createdAt))),l.createElement(r7.default,{className:t.statusCell,scope:"row"},l.createElement(x.default,{variant:"body1",className:O()(t.status,yh(t,e.status))},e.status.toLowerCase())))})))}),yb=n(16839),ym=n.n(yb);function yg(e){var t=e.replace(/\w+\s*=\s*<([^>]|[\r\n])*>/g,""),n=ym().read(t),r=n.edges();return n.nodes().map(function(e){var t={id:e,parentIds:r.filter(function(t){return t.w===e}).map(function(e){return e.v})};return Object.keys(n.node(e)).length>0&&(t.attributes=n.node(e)),t})}var yv=n(94164),yy=function(e){var t=e.data,n=[];return(null==t?void 0:t.attributes)&&Object.keys(t.attributes).forEach(function(e){var r;n.push(l.createElement("div",{key:e},l.createElement(x.default,{variant:"body1",color:"textSecondary",component:"div"},l.createElement("b",null,e,":")," ",null===(r=t.attributes)||void 0===r?void 0:r[e])))}),l.createElement("div",null,t&&l.createElement(x.default,{variant:"body1",color:"textPrimary"},l.createElement("b",null,t.id)),n)},yw=n(73343),y_=n(3379),yE=n.n(y_);function yS(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);nwindow.innerWidth?u-r.getBoundingClientRect().width-a:u+a,n=c+r.getBoundingClientRect().height+i>window.innerHeight?c-r.getBoundingClientRect().height-a:c+a,r.style.opacity=String(1),r.style.top="".concat(n,"px"),r.style.left="".concat(t,"px"),r.style.zIndex=String(1)}},h=function(e){var t=document.getElementById("tooltip-d3-chart-".concat(e));t&&(t.style.opacity=String(0),t.style.zIndex=String(-1))};return l.createElement("div",{style:{fontFamily:"sans-serif",fontWeight:"normal"}},l.createElement(yv.kJ,{id:"task-list-graph-d3",data:i,config:s,onMouseOverNode:d,onMouseOutNode:h},"D3 chart"),n.map(function(e){return l.createElement("div",{key:"d3-tooltip-key-".concat(e.id),id:"tooltip-d3-chart-".concat(e.id),style:{position:"absolute",opacity:"0",border:"1px solid rgba(0, 0, 0, 0.1)",padding:yw.r.spacing.unit,background:"white",borderRadius:5,zIndex:-1,inlineSize:"min-content"}},l.createElement(yy,{data:e}))}))};function yL(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);nyY&&l.createElement("div",{className:t.runDetails},l.createElement(aA.Z,{href:"/jobs/".concat(n.id,"/runs"),component:tz},"View more")))),l.createElement(d.Z,{item:!0,xs:12,sm:6},l.createElement(yF,{observationSource:n.observationSource})))});function yH(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&void 0!==arguments[0]?arguments[0]:"";try{return vx.parse(e),!0}catch(t){return!1}})}),wW=function(e){var t=e.initialValues,n=e.onSubmit,r=e.onTOMLChange;return l.createElement(hT,{initialValues:t,validationSchema:wG,onSubmit:n},function(e){var t=e.isSubmitting,n=e.values;return r&&r(n.toml),l.createElement(hR,{"data-testid":"job-form",noValidate:!0},l.createElement(d.Z,{container:!0,spacing:16},l.createElement(d.Z,{item:!0,xs:12},l.createElement(hP,{component:hX,id:"toml",name:"toml",label:"Job Spec (TOML)",required:!0,fullWidth:!0,multiline:!0,rows:10,rowsMax:25,variant:"outlined",autoComplete:"off",FormHelperTextProps:{"data-testid":"toml-helper-text"}})),l.createElement(d.Z,{item:!0,xs:12,md:7},l.createElement(ok.default,{variant:"contained",color:"primary",type:"submit",disabled:t,size:"large"},"Create Job"))))})},wK=n(50109),wV="persistSpec";function wq(e){var t=e.query,n=new URLSearchParams(t).get("definition");return n?(wK.t8(wV,n),{toml:n}):{toml:wK.U2(wV)||""}}var wZ=function(e){var t=e.onSubmit,n=e.onTOMLChange,r=wq({query:(0,h.TH)().search}),i=function(e){var t=e.replace(/[\u200B-\u200D\uFEFF]/g,"");wK.t8("".concat(wV),t),n&&n(t)};return l.createElement(r5.Z,null,l.createElement(sl.Z,{title:"New Job"}),l.createElement(aW.Z,null,l.createElement(wW,{initialValues:r,onSubmit:t,onTOMLChange:i})))};function wX(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);n1&&void 0!==arguments[1]?arguments[1]:{},n=t.start,r=void 0===n?6:n,i=t.end,a=void 0===i?4:i;return e.substring(0,r)+"..."+e.substring(e.length-a)}function _M(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]0&&void 0!==arguments[0]?arguments[0]:{};return rv(_W,e)},_V=function(){var e=_K({fetchPolicy:"cache-and-network"}),t=e.data,n=e.loading,r=e.error,i=e.refetch;return l.createElement(_U,{loading:n,data:t,errorMsg:null==r?void 0:r.message,refetch:i})},_q=function(e){var t=e.csaKey;return l.createElement(ir.Z,{hover:!0},l.createElement(r7.default,null,l.createElement(x.default,{variant:"body1"},t.publicKey," ",l.createElement(_x,{data:t.publicKey}))))};function _Z(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function _X(){var e=_Z(["\n fragment CSAKeysPayload_ResultsFields on CSAKey {\n id\n publicKey\n }\n"]);return _X=function(){return e},e}var _J=n0(_X()),_Q=function(e){var t,n,r,i=e.data,a=e.errorMsg,o=e.loading,s=e.onCreate;return l.createElement(r5.Z,null,l.createElement(sl.Z,{action:(null===(t=null==i?void 0:i.csaKeys.results)||void 0===t?void 0:t.length)===0&&l.createElement(ok.default,{variant:"outlined",color:"primary",onClick:s},"New CSA Key"),title:"CSA Key",subheader:"Manage your CSA Key"}),l.createElement(r8.Z,null,l.createElement(ie.Z,null,l.createElement(ir.Z,null,l.createElement(r7.default,null,"Public Key"))),l.createElement(r9.Z,null,l.createElement(g$,{visible:o}),l.createElement(gz,{visible:(null===(n=null==i?void 0:i.csaKeys.results)||void 0===n?void 0:n.length)===0}),l.createElement(gU,{msg:a}),null===(r=null==i?void 0:i.csaKeys.results)||void 0===r?void 0:r.map(function(e,t){return l.createElement(_q,{csaKey:e,key:t})}))))};function _1(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]0&&void 0!==arguments[0]?arguments[0]:{};return rv(EM,e)};function EA(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]0&&void 0!==arguments[0]?arguments[0]:{};return rv(EJ,e)},E3=function(){return oo(EQ)},E4=function(){return oo(E1)},E6=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return rv(E0,e)};function E5(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]0&&void 0!==arguments[0]?arguments[0]:{};return rv(SK,e)};function Sq(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);n=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}function kV(e,t){if(null==e)return{};var n,r,i={},a=Object.keys(e);for(r=0;r=0||(i[n]=e[n]);return i}var kq=function(e){var t=e.run,n=l.useMemo(function(){var e=t.inputs,n=t.outputs,r=t.taskRuns,i=kK(t,["inputs","outputs","taskRuns"]),a={};try{a=JSON.parse(e)}catch(o){a={}}return kW(kz({},i),{inputs:a,outputs:n,taskRuns:r})},[t]);return l.createElement(r5.Z,null,l.createElement(aW.Z,null,l.createElement(kH,{object:n})))};function kZ(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function kX(e){for(var t=1;t0&&l.createElement(kr,{errors:t.allErrors})),l.createElement(d.Z,{item:!0,xs:12},l.createElement(h.rs,null,l.createElement(h.AW,{path:"".concat(n,"/json")},l.createElement(kq,{run:t})),l.createElement(h.AW,{path:n},t.taskRuns.length>0&&l.createElement(kN,{taskRuns:t.taskRuns,observationSource:t.job.observationSource}))))))))};function k5(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function k8(){var e=k5(["\n ","\n query FetchJobRun($id: ID!) {\n jobRun(id: $id) {\n __typename\n ... on JobRun {\n ...JobRunPayload_Fields\n }\n ... on NotFoundError {\n message\n }\n }\n }\n"]);return k8=function(){return e},e}var k9=n0(k8(),k4),k7=function(){var e=rv(k9,{variables:{id:(0,h.UO)().id}}),t=e.data,n=e.loading,r=e.error;if(n)return l.createElement(iR,null);if(r)return l.createElement(iD,{error:r});var i=null==t?void 0:t.jobRun;switch(null==i?void 0:i.__typename){case"JobRun":return l.createElement(k6,{run:i});case"NotFoundError":return l.createElement(oa,null);default:return null}};function xe(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function xt(){var e=xe(["\n fragment JobRunsPayload_ResultsFields on JobRun {\n id\n allErrors\n createdAt\n finishedAt\n status\n job {\n id\n }\n }\n"]);return xt=function(){return e},e}var xn=n0(xt()),xr=function(e){var t=e.loading,n=e.data,r=e.page,i=e.pageSize,a=(0,h.k6)(),o=l.useMemo(function(){return null==n?void 0:n.jobRuns.results.map(function(e){var t,n=e.allErrors,r=e.id,i=e.createdAt;return{id:r,createdAt:i,errors:n,finishedAt:e.finishedAt,status:e.status}})},[n]);return l.createElement(ig,null,l.createElement(d.Z,{container:!0,spacing:32},l.createElement(d.Z,{item:!0,xs:12},l.createElement(iy,null,"Job Runs")),t&&l.createElement(iR,null),n&&o&&l.createElement(d.Z,{item:!0,xs:12},l.createElement(r5.Z,null,l.createElement(yp,{runs:o}),l.createElement(it.Z,{component:"div",count:n.jobRuns.metadata.total,rowsPerPage:i,rowsPerPageOptions:[i],page:r-1,onChangePage:function(e,t){a.push("/runs?page=".concat(t+1,"&per=").concat(i))},onChangeRowsPerPage:function(){},backIconButtonProps:{"aria-label":"prev-page"},nextIconButtonProps:{"aria-label":"next-page"}})))))};function xi(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function xa(){var e=xi(["\n ","\n query FetchJobRuns($offset: Int, $limit: Int) {\n jobRuns(offset: $offset, limit: $limit) {\n results {\n ...JobRunsPayload_ResultsFields\n }\n metadata {\n total\n }\n }\n }\n"]);return xa=function(){return e},e}var xo=n0(xa(),xn),xs=function(){var e=ij(),t=parseInt(e.get("page")||"1",10),n=parseInt(e.get("per")||"25",10),r=rv(xo,{variables:{offset:(t-1)*n,limit:n},fetchPolicy:"cache-and-network"}),i=r.data,a=r.loading,o=r.error;return o?l.createElement(iD,{error:o}):l.createElement(xr,{loading:a,data:i,page:t,pageSize:n})},xu=function(){var e=(0,h.$B)().path;return l.createElement(h.rs,null,l.createElement(h.AW,{exact:!0,path:e},l.createElement(xs,null)),l.createElement(h.AW,{path:"".concat(e,"/:id")},l.createElement(k7,null)))};function xc(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function xl(){var e=xc(["\n fragment FetchFeedsManagersPayload_ResultsFields on FeedsManager {\n __typename\n id\n name\n uri\n publicKey\n isConnectionActive\n createdAt\n }\n query FetchFeedsManagers {\n feedsManagers {\n results {\n ...FetchFeedsManagersPayload_ResultsFields\n }\n }\n }\n"]);return xl=function(){return e},e}var xf=n0(xl()),xd=function(e){return rv(xf,e)},xh=n(47559),xp=n(83165),xb=n(47298),xm=n(81395),xg=function(){return(0,b.createStyles)({root:{display:"flex"},connectedIcon:{color:xh.default[500]},disconnectedIcon:{color:xp.default[500]},text:{marginLeft:4}})},xv=(0,b.withStyles)(xg)(function(e){var t=e.isConnected,n=e.classes;return l.createElement("div",{className:n.root},t?l.createElement(xm.Z,{fontSize:"small",className:n.connectedIcon}):l.createElement(xb.Z,{fontSize:"small",className:n.disconnectedIcon}),l.createElement(x.default,{variant:"body1",inline:!0,className:n.text},t?"Connected":"Disconnected"))}),xy=(0,b.withStyles)(iu)(function(e){var t=e.jobDistributor,n=e.classes;return l.createElement(ir.Z,{className:n.row,hover:!0},l.createElement(r7.default,{className:n.cell,component:"th",scope:"row"},l.createElement(ih,{className:n.link,href:"/job_distributors/".concat(t.id)},t.name)),l.createElement(r7.default,null,l.createElement(xv,{isConnected:t.isConnectionActive})),l.createElement(r7.default,null,_T(t.publicKey,{start:6,end:6}),l.createElement(_x,{data:t.publicKey})),l.createElement(r7.default,null,t.uri))}),xw=function(e){var t=e.jobDistributors;return l.createElement(ig,null,l.createElement(d.Z,{container:!0},l.createElement(d.Z,{item:!0,xs:9},l.createElement(iy,null,"Job Distributors")),l.createElement(d.Z,{item:!0,xs:3},l.createElement(d.Z,{container:!0,justify:"flex-end"},l.createElement(d.Z,{item:!0},l.createElement(aA.Z,{variant:"secondary",component:tz,href:"/job_distributors/new"},"New Job Distributor")))),l.createElement(d.Z,{item:!0,xs:12},l.createElement(r5.Z,null,l.createElement(r8.Z,null,l.createElement(ie.Z,null,l.createElement(ir.Z,null,l.createElement(r7.default,null,"Name"),l.createElement(r7.default,null,"Status"),l.createElement(r7.default,null,"CSA Public Key"),l.createElement(r7.default,null,"RPC URL"))),l.createElement(r9.Z,null,0===t.length&&l.createElement(ir.Z,null,l.createElement(r7.default,{component:"th",scope:"row",colSpan:3},"Job Distributors have not been registered")),t.map(function(e){return l.createElement(xy,{key:e.id,jobDistributor:e})})))))))},x_=function(){var e,t=xd({fetchPolicy:"cache-and-network"}),n=t.data,r=t.loading,i=t.error;return r?l.createElement(iR,null):i?l.createElement(iD,{error:i}):l.createElement(xw,{jobDistributors:null!==(e=null==n?void 0:n.feedsManagers.results)&&void 0!==e?e:[]})},xE=bv().shape({name:p0().required("Required"),uri:p0().required("Required"),publicKey:p0().required("Required")}),xS=function(e){var t=e.initialValues,n=e.onSubmit;return l.createElement(hT,{initialValues:t,validationSchema:xE,onSubmit:n},function(e){var t=e.isSubmitting,n=e.submitForm;return l.createElement(hR,{"data-testid":"feeds-manager-form"},l.createElement(d.Z,{container:!0,spacing:16},l.createElement(d.Z,{item:!0,xs:12,md:6},l.createElement(hP,{component:hX,id:"name",name:"name",label:"Name",required:!0,fullWidth:!0,FormHelperTextProps:{"data-testid":"name-helper-text"}})),l.createElement(d.Z,{item:!0,xs:!1,md:6}),l.createElement(d.Z,{item:!0,xs:12,md:6},l.createElement(hP,{component:hX,id:"uri",name:"uri",label:"URI",required:!0,fullWidth:!0,helperText:"Provided by the Job Distributor operator",FormHelperTextProps:{"data-testid":"uri-helper-text"}})),l.createElement(d.Z,{item:!0,xs:12,md:6},l.createElement(hP,{component:hX,id:"publicKey",name:"publicKey",label:"Public Key",required:!0,fullWidth:!0,helperText:"Provided by the Job Distributor operator",FormHelperTextProps:{"data-testid":"publicKey-helper-text"}})),l.createElement(d.Z,{item:!0,xs:12},l.createElement(ok.default,{variant:"contained",color:"primary",disabled:t,onClick:n},"Submit"))))})},xk=function(e){var t=e.data,n=e.onSubmit,r={name:t.name,uri:t.uri,publicKey:t.publicKey};return l.createElement(d.Z,{container:!0},l.createElement(d.Z,{item:!0,xs:12,md:11,lg:9},l.createElement(r5.Z,null,l.createElement(sl.Z,{title:"Edit Job Distributor"}),l.createElement(aW.Z,null,l.createElement(xS,{initialValues:r,onSubmit:n})))))};function xx(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]0&&void 0!==arguments[0]?arguments[0]:{};return rv(xZ,e)},xJ=n(57234);function xQ(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}function x6(e,t){if(null==e)return{};var n,r,i={},a=Object.keys(e);for(r=0;r=0||(i[n]=e[n]);return i}function x5(e,t){return x1(e)||x2(e,t)||x8(e,t)||x3()}function x8(e,t){if(e){if("string"==typeof e)return xQ(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);if("Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n)return Array.from(n);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return xQ(e,t)}}var x9=function(e){return"SN_MAIN"===e||"SN_SEPOLIA"===e},x7=bv().shape({chainID:p0().required("Required"),chainType:p0().required("Required"),accountAddr:p0().required("Required"),accountAddrPubKey:p0().nullable(),adminAddr:p0().required("Required"),ocr1Multiaddr:p0().when(["ocr1Enabled","ocr1IsBootstrap"],{is:function(e,t){return e&&t},then:p0().required("Required").nullable()}).nullable(),ocr1P2PPeerID:p0().when(["ocr1Enabled","ocr1IsBootstrap"],{is:function(e,t){return e&&!t},then:p0().required("Required").nullable()}).nullable(),ocr1KeyBundleID:p0().when(["ocr1Enabled","ocr1IsBootstrap"],{is:function(e,t){return e&&!t},then:p0().required("Required").nullable()}).nullable(),ocr2Multiaddr:p0().when(["ocr2Enabled","ocr2IsBootstrap"],{is:function(e,t){return e&&t},then:p0().required("Required").nullable()}).nullable(),ocr2P2PPeerID:p0().when(["ocr2Enabled","ocr2IsBootstrap"],{is:function(e,t){return e&&!t},then:p0().required("Required").nullable()}).nullable(),ocr2KeyBundleID:p0().when(["ocr2Enabled","ocr2IsBootstrap"],{is:function(e,t){return e&&!t},then:p0().required("Required").nullable()}).nullable(),ocr2CommitPluginEnabled:pV().required("Required"),ocr2ExecutePluginEnabled:pV().required("Required"),ocr2MedianPluginEnabled:pV().required("Required"),ocr2MercuryPluginEnabled:pV().required("Required"),ocr2ForwarderAddress:p0().nullable()}),Te=function(e){return(0,b.createStyles)({supportedJobOptionsPaper:{padding:2*e.spacing.unit}})},Tt=function(e){var t=e.chainAccounts,n=x4(e,["chainAccounts"]),r=h_(),i=r.values,a=i.chainID,o=i.accountAddr,s=r.setFieldValue,u=x5(l.useState(!1),2),c=u[0],f=u[1],d=l.useRef();l.useEffect(function(){d.current=a},[a]),l.useEffect(function(){a!==d.current&&(s(n.name,""),f(!1))},[a,s,n.name]);var h=function(e){var t=e.target.value;"custom"===t?(f(!0),s(n.name,"")):(f(!1),s(n.name,t))};return l.createElement(l.Fragment,null,!x9(a)&&l.createElement(hP,x0({},n,{select:!0,value:c?"custom":o,onChange:h}),t.map(function(e){return l.createElement(tE.default,{key:e.address,value:e.address},e.address)})),x9(a)&&l.createElement(hP,{component:hX,id:"accountAddr",name:"accountAddr",label:"Enter your account address",inputProps:{"data-testid":"customAccountAddr-input"},helperText:"The account address used for this chain",required:!0,fullWidth:!0}),x9(a)&&l.createElement("div",null,l.createElement(hP,{component:hX,id:"accountAddrPubKey",name:"accountAddrPubKey",label:"Account Address Public Key",required:!0,fullWidth:!0,helperText:"The public key for your account address",FormHelperTextProps:{"data-testid":"accountAddrPubKey-helper-text"}})))},Tn=(0,b.withStyles)(Te)(function(e){var t=e.classes,n=e.editing,r=void 0!==n&&n,i=e.innerRef,a=e.initialValues,o=e.onSubmit,s=e.chainIDs,u=void 0===s?[]:s,c=e.accounts,f=void 0===c?[]:c,h=e.p2pKeys,p=void 0===h?[]:h,b=e.ocrKeys,m=void 0===b?[]:b,g=e.ocr2Keys,v=void 0===g?[]:g,y=e.showSubmit,w=void 0!==y&&y;return l.createElement(hT,{innerRef:i,initialValues:a,validationSchema:x7,onSubmit:o},function(e){var n=e.values,i=f.filter(function(e){return e.chain.id==n.chainID&&!e.isDisabled});return l.createElement(hR,{"data-testid":"feeds-manager-form",id:"chain-configuration-form",noValidate:!0},l.createElement(d.Z,{container:!0,spacing:16},l.createElement(d.Z,{item:!0,xs:12,md:6},l.createElement(hP,{component:hX,id:"chainType",name:"chainType",label:"Chain Type",select:!0,required:!0,fullWidth:!0,disabled:!0},l.createElement(tE.default,{key:"EVM",value:"EVM"},"EVM"))),l.createElement(d.Z,{item:!0,xs:12,md:6},l.createElement(hP,{component:hX,id:"chainID",name:"chainID",label:"Chain ID",required:!0,fullWidth:!0,select:!0,disabled:r,inputProps:{"data-testid":"chainID-input"},FormHelperTextProps:{"data-testid":"chainID-helper-text"}},u.map(function(e){return l.createElement(tE.default,{key:e,value:e},e)}))),l.createElement(d.Z,{item:!0,xs:12,md:6},l.createElement(Tt,{component:hX,id:"accountAddr",name:"accountAddr",label:"Account Address",inputProps:{"data-testid":"accountAddr-input"},required:!0,fullWidth:!0,select:!0,helperText:"The account address used for this chain",chainAccounts:i,FormHelperTextProps:{"data-testid":"accountAddr-helper-text"}})),l.createElement(d.Z,{item:!0,xs:12,md:6},l.createElement(hP,{component:hX,id:"adminAddr",name:"adminAddr",label:"Admin Address",required:!0,fullWidth:!0,helperText:"The address used for LINK payments",FormHelperTextProps:{"data-testid":"adminAddr-helper-text"}})),l.createElement(d.Z,{item:!0,xs:12},l.createElement(x.default,null,"Supported Job Types")),l.createElement(d.Z,{item:!0,xs:12},l.createElement(hP,{component:h2,name:"fluxMonitorEnabled",type:"checkbox",Label:{label:"Flux Monitor"}})),l.createElement(d.Z,{item:!0,xs:12},l.createElement(hP,{component:h2,name:"ocr1Enabled",type:"checkbox",Label:{label:"OCR"}}),n.ocr1Enabled&&l.createElement(ii.default,{className:t.supportedJobOptionsPaper},l.createElement(d.Z,{container:!0,spacing:8},l.createElement(l.Fragment,null,l.createElement(d.Z,{item:!0,xs:12},l.createElement(hP,{component:h2,name:"ocr1IsBootstrap",type:"checkbox",Label:{label:"Is this node running as a bootstrap peer?"}})),n.ocr1IsBootstrap?l.createElement(d.Z,{item:!0,xs:12},l.createElement(hP,{component:hX,id:"ocr1Multiaddr",name:"ocr1Multiaddr",label:"Multiaddr",required:!0,fullWidth:!0,helperText:"The OCR Multiaddr which oracles use to query for network information",FormHelperTextProps:{"data-testid":"ocr1Multiaddr-helper-text"}})):l.createElement(l.Fragment,null,l.createElement(d.Z,{item:!0,xs:12,md:6},l.createElement(hP,{component:hX,id:"ocr1P2PPeerID",name:"ocr1P2PPeerID",label:"Peer ID",select:!0,required:!0,fullWidth:!0,helperText:"The Peer ID used for this chain",FormHelperTextProps:{"data-testid":"ocr1P2PPeerID-helper-text"}},p.map(function(e){return l.createElement(tE.default,{key:e.peerID,value:e.peerID},e.peerID)}))),l.createElement(d.Z,{item:!0,xs:12,md:6},l.createElement(hP,{component:hX,id:"ocr1KeyBundleID",name:"ocr1KeyBundleID",label:"Key Bundle ID",select:!0,required:!0,fullWidth:!0,helperText:"The OCR Key Bundle ID used for this chain",FormHelperTextProps:{"data-testid":"ocr1KeyBundleID-helper-text"}},m.map(function(e){return l.createElement(tE.default,{key:e.id,value:e.id},e.id)})))))))),l.createElement(d.Z,{item:!0,xs:12},l.createElement(hP,{component:h2,name:"ocr2Enabled",type:"checkbox",Label:{label:"OCR2"}}),n.ocr2Enabled&&l.createElement(ii.default,{className:t.supportedJobOptionsPaper},l.createElement(d.Z,{container:!0,spacing:8},l.createElement(l.Fragment,null,l.createElement(d.Z,{item:!0,xs:12},l.createElement(hP,{component:h2,name:"ocr2IsBootstrap",type:"checkbox",Label:{label:"Is this node running as a bootstrap peer?"}})),l.createElement(d.Z,{item:!0,xs:12,md:6},l.createElement(hP,{component:hX,id:"ocr2P2PPeerID",name:"ocr2P2PPeerID",label:"Peer ID",select:!0,required:!n.ocr2IsBootstrap,fullWidth:!0,helperText:"The Peer ID used for this chain",FormHelperTextProps:{"data-testid":"ocr2P2PPeerID-helper-text"}},p.map(function(e){return l.createElement(tE.default,{key:e.peerID,value:e.peerID},e.peerID)}))),n.ocr2IsBootstrap?l.createElement(d.Z,{item:!0,xs:12},l.createElement(hP,{component:hX,id:"ocr2Multiaddr",name:"ocr2Multiaddr",label:"Multiaddr",required:!0,fullWidth:!0,helperText:"The OCR2 Multiaddr which oracles use to query for network information",FormHelperTextProps:{"data-testid":"ocr2Multiaddr-helper-text"}})):l.createElement(l.Fragment,null,l.createElement(d.Z,{item:!0,xs:12,md:6},l.createElement(hP,{component:hX,id:"ocr2KeyBundleID",name:"ocr2KeyBundleID",label:"Key Bundle ID",select:!0,required:!0,fullWidth:!0,helperText:"The OCR2 Key Bundle ID used for this chain",FormHelperTextProps:{"data-testid":"ocr2KeyBundleID-helper-text"}},v.map(function(e){return l.createElement(tE.default,{key:e.id,value:e.id},e.id)}))),l.createElement(d.Z,{item:!0,xs:12},l.createElement(x.default,null,"Supported Plugins")),l.createElement(d.Z,{item:!0,xs:6},l.createElement(hP,{component:h2,name:"ocr2CommitPluginEnabled",type:"checkbox",Label:{label:"Commit"}})),l.createElement(d.Z,{item:!0,xs:6},l.createElement(hP,{component:h2,name:"ocr2ExecutePluginEnabled",type:"checkbox",Label:{label:"Execute"}})),l.createElement(d.Z,{item:!0,xs:6},l.createElement(hP,{component:h2,name:"ocr2RebalancerPluginEnabled",type:"checkbox",Label:{label:"Rebalancer"}})),l.createElement(d.Z,{item:!0,xs:6},l.createElement(hP,{component:h2,name:"ocr2MedianPluginEnabled",type:"checkbox",Label:{label:"Median"}})),l.createElement(d.Z,{item:!0,xs:6},l.createElement(hP,{component:h2,name:"ocr2MercuryPluginEnabled",type:"checkbox",Label:{label:"Mercury"}})),l.createElement(d.Z,{item:!0,xs:12,md:12},l.createElement(hP,{component:hX,id:"ocr2ForwarderAddress",name:"ocr2ForwarderAddress",label:"Forwarder Address (optional)",fullWidth:!0,helperText:"The forwarder address from the Operator Forwarder Contract",FormHelperTextProps:{"data-testid":"ocr2ForwarderAddress-helper-text"}}))))))),w&&l.createElement(d.Z,{item:!0,xs:12,md:7},l.createElement(ok.default,{variant:"contained",color:"primary",type:"submit",size:"large"},"Submit"))))})}),Tr=function(e){var t=e.onClose,n=e.open,r=e.onSubmit,i=l.useRef(),a=i$({fetchPolicy:"network-only"}).data,o=_K({fetchPolicy:"cache-and-network"}).data,s=SV({fetchPolicy:"cache-and-network"}).data,u=EO({fetchPolicy:"cache-and-network"}).data,c=E2({fetchPolicy:"cache-and-network"}).data,f={chainID:"",chainType:"EVM",accountAddr:"",adminAddr:"",accountAddrPubKey:"",fluxMonitorEnabled:!1,ocr1Enabled:!1,ocr1IsBootstrap:!1,ocr1Multiaddr:"",ocr1P2PPeerID:"",ocr1KeyBundleID:"",ocr2Enabled:!1,ocr2IsBootstrap:!1,ocr2Multiaddr:"",ocr2P2PPeerID:"",ocr2KeyBundleID:"",ocr2CommitPluginEnabled:!1,ocr2ExecutePluginEnabled:!1,ocr2MedianPluginEnabled:!1,ocr2MercuryPluginEnabled:!1,ocr2RebalancerPluginEnabled:!1,ocr2ForwarderAddress:""},d=a?a.chains.results.map(function(e){return e.id}):[],h=o?o.ethKeys.results:[],p=s?s.p2pKeys.results:[],b=u?u.ocrKeyBundles.results:[],m=c?c.ocr2KeyBundles.results:[];return l.createElement(aD.Z,{onClose:t,open:n,disableBackdropClick:!0},l.createElement(oO.Z,{disableTypography:!0},l.createElement(x.default,{variant:"body2"},"New Supported Chain")),l.createElement(oT.Z,null,l.createElement(Tn,{innerRef:i,initialValues:f,onSubmit:r,chainIDs:d,accounts:h,p2pKeys:p,ocrKeys:b,ocr2Keys:m})),l.createElement(ox.Z,null,l.createElement(ok.default,{onClick:t},"Cancel"),l.createElement(ok.default,{color:"primary",type:"submit",form:"chain-configuration-form",variant:"contained"},"Submit")))},Ti=function(e){var t=e.cfg,n=e.onClose,r=e.open,i=e.onSubmit,a=l.useRef(),o=i$({fetchPolicy:"network-only"}).data,s=_K({fetchPolicy:"cache-and-network"}).data,u=SV({fetchPolicy:"cache-and-network"}).data,c=EO({fetchPolicy:"cache-and-network"}).data,f=E2({fetchPolicy:"cache-and-network"}).data;if(!t)return null;var d={chainID:t.chainID,chainType:"EVM",accountAddr:t.accountAddr,adminAddr:t.adminAddr,accountAddrPubKey:t.accountAddrPubKey,fluxMonitorEnabled:t.fluxMonitorJobConfig.enabled,ocr1Enabled:t.ocr1JobConfig.enabled,ocr1IsBootstrap:t.ocr1JobConfig.isBootstrap,ocr1Multiaddr:t.ocr1JobConfig.multiaddr,ocr1P2PPeerID:t.ocr1JobConfig.p2pPeerID,ocr1KeyBundleID:t.ocr1JobConfig.keyBundleID,ocr2Enabled:t.ocr2JobConfig.enabled,ocr2IsBootstrap:t.ocr2JobConfig.isBootstrap,ocr2Multiaddr:t.ocr2JobConfig.multiaddr,ocr2P2PPeerID:t.ocr2JobConfig.p2pPeerID,ocr2KeyBundleID:t.ocr2JobConfig.keyBundleID,ocr2CommitPluginEnabled:t.ocr2JobConfig.plugins.commit,ocr2ExecutePluginEnabled:t.ocr2JobConfig.plugins.execute,ocr2MedianPluginEnabled:t.ocr2JobConfig.plugins.median,ocr2MercuryPluginEnabled:t.ocr2JobConfig.plugins.mercury,ocr2RebalancerPluginEnabled:t.ocr2JobConfig.plugins.rebalancer,ocr2ForwarderAddress:t.ocr2JobConfig.forwarderAddress},h=o?o.chains.results.map(function(e){return e.id}):[],p=s?s.ethKeys.results:[],b=u?u.p2pKeys.results:[],m=c?c.ocrKeyBundles.results:[],g=f?f.ocr2KeyBundles.results:[];return l.createElement(aD.Z,{onClose:n,open:r,disableBackdropClick:!0},l.createElement(oO.Z,{disableTypography:!0},l.createElement(x.default,{variant:"body2"},"Edit Supported Chain")),l.createElement(oT.Z,null,l.createElement(Tn,{innerRef:a,initialValues:d,onSubmit:i,chainIDs:h,accounts:p,p2pKeys:b,ocrKeys:m,ocr2Keys:g,editing:!0})),l.createElement(ox.Z,null,l.createElement(ok.default,{onClick:n},"Cancel"),l.createElement(ok.default,{color:"primary",type:"submit",form:"chain-configuration-form",variant:"contained"},"Submit")))};function Ta(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);nt.version?e:t})},[o]),g=l.useMemo(function(){return Mx(o).sort(function(e,t){return t.version-e.version})},[o]),v=function(e,t,n){switch(e){case"PENDING":return l.createElement(l.Fragment,null,l.createElement(ok.default,{variant:"text",color:"secondary",onClick:function(){return b("reject",t)}},"Reject"),m.id===t&&"DELETED"!==n.status&&"REVOKED"!==n.status&&l.createElement(ok.default,{variant:"contained",color:"primary",onClick:function(){return b("approve",t)}},"Approve"),m.id===t&&"DELETED"===n.status&&n.pendingUpdate&&l.createElement(l.Fragment,null,l.createElement(ok.default,{variant:"contained",color:"primary",onClick:function(){return b("cancel",t)}},"Cancel"),l.createElement(x.default,{color:"error"},"This proposal was deleted. Cancel the spec to delete any running jobs")));case"APPROVED":return l.createElement(l.Fragment,null,l.createElement(ok.default,{variant:"contained",onClick:function(){return b("cancel",t)}},"Cancel"),"DELETED"===n.status&&n.pendingUpdate&&l.createElement(x.default,{color:"error"},"This proposal was deleted. Cancel the spec to delete any running jobs"));case"CANCELLED":if(m.id===t&&"DELETED"!==n.status&&"REVOKED"!==n.status)return l.createElement(ok.default,{variant:"contained",color:"primary",onClick:function(){return b("approve",t)}},"Approve");return null;default:return null}};return l.createElement("div",null,g.map(function(e,n){return l.createElement(mP.Z,{defaultExpanded:0===n,key:n},l.createElement(mR.Z,{expandIcon:l.createElement(gh.Z,null)},l.createElement(x.default,{className:t.versionText},"Version ",e.version),l.createElement(Es.Z,{label:e.status,color:"APPROVED"===e.status?"primary":"default",variant:"REJECTED"===e.status||"CANCELLED"===e.status?"outlined":"default"}),l.createElement("div",{className:t.proposedAtContainer},l.createElement(x.default,null,"Proposed ",l.createElement(aO,{tooltip:!0},e.createdAt)))),l.createElement(mj.Z,{className:t.expansionPanelDetails},l.createElement("div",{className:t.actions},l.createElement("div",{className:t.editContainer},0===n&&("PENDING"===e.status||"CANCELLED"===e.status)&&"DELETED"!==s.status&&"REVOKED"!==s.status&&l.createElement(ok.default,{variant:"contained",onClick:function(){return p(!0)}},"Edit")),l.createElement("div",{className:t.actionsContainer},v(e.status,e.id,s))),l.createElement(gd,{language:"toml",style:gs,"data-testid":"codeblock"},e.definition)))}),l.createElement(oC,{open:null!=c,title:c?ML[c.action].title:"",body:c?ML[c.action].body:"",onConfirm:function(){if(c){switch(c.action){case"approve":n(c.id);break;case"cancel":r(c.id);break;case"reject":i(c.id)}f(null)}},cancelButtonText:"Cancel",onCancel:function(){return f(null)}}),l.createElement(Mb,{open:h,onClose:function(){return p(!1)},initialValues:{definition:m.definition,id:m.id},onSubmit:a}))});function MI(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function MD(){var e=MI(["\n ","\n fragment JobProposalPayloadFields on JobProposal {\n id\n externalJobID\n remoteUUID\n jobID\n specs {\n ...JobProposal_SpecsFields\n }\n status\n pendingUpdate\n }\n"]);return MD=function(){return e},e}var MN=n0(MD(),MO),MP=function(e){var t=e.onApprove,n=e.onCancel,r=e.onReject,i=e.onUpdateSpec,a=e.proposal;return l.createElement(ig,null,l.createElement(d.Z,{container:!0,spacing:32},l.createElement(d.Z,{item:!0,xs:9},l.createElement(iy,null,"Job Proposal #",a.id))),l.createElement(Mc,{proposal:a}),l.createElement(d.Z,{container:!0,spacing:32},l.createElement(d.Z,{item:!0,xs:9},l.createElement(TJ,null,"Specs"))),l.createElement(d.Z,{container:!0,spacing:32},l.createElement(d.Z,{item:!0,xs:12},l.createElement(MC,{proposal:a,specs:a.specs,onReject:r,onApprove:t,onCancel:n,onUpdateSpec:i}))))};function MR(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]e.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);nU,tA:()=>$,KL:()=>H,Iw:()=>V,DQ:()=>W,cB:()=>T,LO:()=>M,t5:()=>k,qt:()=>x,Jc:()=>C,L7:()=>Y,EO:()=>B});var r,i,a=n(66289),o=n(41800),s=n.n(o),u=n(67932);(i=r||(r={})).IN_PROGRESS="in_progress",i.PENDING_INCOMING_CONFIRMATIONS="pending_incoming_confirmations",i.PENDING_CONNECTION="pending_connection",i.PENDING_BRIDGE="pending_bridge",i.PENDING_SLEEP="pending_sleep",i.ERRORED="errored",i.COMPLETED="completed";var c=n(87013),l=n(19084),f=n(34823);function d(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]j,v2:()=>F});var r=n(66289);function i(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}var a="/sessions",o="/sessions",s=function e(t){var n=this;i(this,e),this.api=t,this.createSession=function(e){return n.create(e)},this.destroySession=function(){return n.destroy()},this.create=this.api.createResource(a),this.destroy=this.api.deleteResource(o)};function u(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}var c="/v2/bulk_delete_runs",l=function e(t){var n=this;u(this,e),this.api=t,this.bulkDeleteJobRuns=function(e){return n.destroy(e)},this.destroy=this.api.deleteResource(c)};function f(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}var d="/v2/chains/evm",h="".concat(d,"/:id"),p=function e(t){var n=this;f(this,e),this.api=t,this.getChains=function(){return n.index()},this.createChain=function(e){return n.create(e)},this.destroyChain=function(e){return n.destroy(void 0,{id:e})},this.updateChain=function(e,t){return n.update(t,{id:e})},this.index=this.api.fetchResource(d),this.create=this.api.createResource(d),this.destroy=this.api.deleteResource(h),this.update=this.api.updateResource(h)};function b(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}var m="/v2/keys/evm/chain",g=function e(t){var n=this;b(this,e),this.api=t,this.chain=function(e){var t=new URLSearchParams;t.append("address",e.address),t.append("evmChainID",e.evmChainID),null!==e.nextNonce&&t.append("nextNonce",e.nextNonce),null!==e.abandon&&t.append("abandon",String(e.abandon)),null!==e.enabled&&t.append("enabled",String(e.enabled));var r=m+"?"+t.toString();return n.api.createResource(r)()}};function v(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}var y="/v2/jobs",w="".concat(y,"/:specId/runs"),_=function e(t){var n=this;v(this,e),this.api=t,this.createJobRunV2=function(e,t){return n.post(t,{specId:e})},this.post=this.api.createResource(w,!0)};function E(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}var S="/v2/log",k=function e(t){var n=this;E(this,e),this.api=t,this.getLogConfig=function(){return n.show()},this.updateLogConfig=function(e){return n.update(e)},this.show=this.api.fetchResource(S),this.update=this.api.updateResource(S)};function x(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}var T="/v2/nodes",M=function e(t){var n=this;x(this,e),this.api=t,this.getNodes=function(){return n.index()},this.createNode=function(e){return n.create(e)},this.index=this.api.fetchResource(T),this.create=this.api.createResource(T)};function O(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}var A="/v2/enroll_webauthn",L=function e(t){var n=this;O(this,e),this.api=t,this.beginKeyRegistration=function(e){return n.create(e)},this.finishKeyRegistration=function(e){return n.put(e)},this.create=this.api.fetchResource(A),this.put=this.api.createResource(A)};function C(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}var I="/v2/build_info",D=function e(t){var n=this;C(this,e),this.api=t,this.show=function(){return n.api.GET(I)()}};function N(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}var P=function e(t){N(this,e),this.api=t,this.buildInfo=new D(this.api),this.bulkDeleteRuns=new l(this.api),this.chains=new p(this.api),this.logConfig=new k(this.api),this.nodes=new M(this.api),this.jobs=new _(this.api),this.webauthn=new L(this.api),this.evmKeys=new g(this.api)},R=new r.V0({base:void 0}),j=new s(R),F=new P(R)},1398(e,t,n){"use strict";n.d(t,{Z:()=>d});var r=n(67294),i=n(32316),a=n(83638),o=n(94184),s=n.n(o);function u(){return(u=Object.assign||function(e){for(var t=1;tc});var r=n(67294),i=n(32316);function a(){return(a=Object.assign||function(e){for(var t=1;tx,jK:()=>v});var r=n(67294),i=n(37703),a=n(45697),o=n.n(a),s=n(82204),u=n(71426),c=n(94184),l=n.n(c),f=n(32316),d=function(e){var t=e.palette.success||{},n=e.palette.warning||{};return{base:{paddingLeft:5*e.spacing.unit,paddingRight:5*e.spacing.unit},success:{backgroundColor:t.main,color:t.contrastText},error:{backgroundColor:e.palette.error.dark,color:e.palette.error.contrastText},warning:{backgroundColor:n.contrastText,color:n.main}}},h=function(e){var t,n=e.success,r=e.error,i=e.warning,a=e.classes,o=e.className;return n?t=a.success:r?t=a.error:i&&(t=a.warning),l()(a.base,o,t)},p=function(e){return r.createElement(s.Z,{className:h(e),square:!0},r.createElement(u.default,{variant:"body2",color:"inherit",component:"div"},e.children))};p.defaultProps={success:!1,error:!1,warning:!1},p.propTypes={success:o().bool,error:o().bool,warning:o().bool};let b=(0,f.withStyles)(d)(p);var m=function(){return r.createElement(r.Fragment,null,"Unhandled error. Please help us by opening a"," ",r.createElement("a",{href:"https://github.com/smartcontractkit/chainlink/issues/new"},"bug report"))};let g=m;function v(e){return"string"==typeof e?e:e.component?e.component(e.props):r.createElement(g,null)}function y(e,t){var n;return n="string"==typeof e?e:e.component?e.component(e.props):r.createElement(g,null),r.createElement("p",{key:t},n)}var w=function(e){var t=e.notifications;return r.createElement(b,{error:!0},t.map(y))},_=function(e){var t=e.notifications;return r.createElement(b,{success:!0},t.map(y))},E=function(e){var t=e.errors,n=e.successes;return r.createElement("div",null,(null==t?void 0:t.length)>0&&r.createElement(w,{notifications:t}),n.length>0&&r.createElement(_,{notifications:n}))},S=function(e){return{errors:e.notifications.errors,successes:e.notifications.successes}},k=(0,i.$j)(S)(E);let x=k},9409(e,t,n){"use strict";n.d(t,{ZP:()=>j});var r=n(67294),i=n(37703),a=n(5977),o=n(32316),s=n(1398),u=n(82204),c=n(30060),l=n(71426),f=n(60520),d=n(39814),h=n(57209),p=n(26842),b=n(3950),m=n(5536),g=n(45697),v=n.n(g);let y=n.p+"9f6d832ef97e8493764e.svg";function w(){return(w=Object.assign||function(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&_.map(function(e,t){return r.createElement(d.Z,{item:!0,xs:12,key:t},r.createElement(u.Z,{raised:!1,className:v.error},r.createElement(c.Z,null,r.createElement(l.default,{variant:"body1",className:v.errorText},(0,b.jK)(e)))))}),r.createElement(d.Z,{item:!0,xs:12},r.createElement(f.Z,{id:"email",label:"Email",margin:"normal",value:n,onChange:m("email"),error:_.length>0,variant:"outlined",fullWidth:!0})),r.createElement(d.Z,{item:!0,xs:12},r.createElement(f.Z,{id:"password",label:"Password",type:"password",autoComplete:"password",margin:"normal",value:h,onChange:m("password"),error:_.length>0,variant:"outlined",fullWidth:!0})),r.createElement(d.Z,{item:!0,xs:12},r.createElement(d.Z,{container:!0,spacing:0,justify:"center"},r.createElement(d.Z,{item:!0},r.createElement(s.Z,{type:"submit",variant:"primary"},"Access Account")))),y&&r.createElement(l.default,{variant:"body1",color:"textSecondary"},"Signing in...")))))))},P=function(e){return{fetching:e.authentication.fetching,authenticated:e.authentication.allowed,errors:e.notifications.errors}},R=(0,i.$j)(P,x({submitSignIn:p.L7}))(N);let j=(0,h.wU)(e)((0,o.withStyles)(D)(R))},16353(e,t,n){"use strict";n.d(t,{ZP:()=>H,rH:()=>U});var r,i=n(37703),a=n(97779),o=n(9541),s=n(19084);function u(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function c(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:h,t=arguments.length>1?arguments[1]:void 0;switch(t.type){case s.Mk.RECEIVE_SIGNOUT_SUCCESS:case s.Mk.RECEIVE_SIGNIN_SUCCESS:var n={allowed:t.authenticated};return o.Ks(n),f(c({},e,n),{errors:[]});case s.Mk.RECEIVE_SIGNIN_FAIL:var r={allowed:!1};return o.Ks(r),f(c({},e,r),{errors:[]});case s.Mk.RECEIVE_SIGNIN_ERROR:case s.Mk.RECEIVE_SIGNOUT_ERROR:var i={allowed:!1};return o.Ks(i),f(c({},e,i),{errors:t.errors||[]});default:return e}};let b=p;function m(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function g(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:_,t=arguments.length>1?arguments[1]:void 0;return t.type?t.type.startsWith(r.REQUEST)?y(g({},e),{count:e.count+1}):t.type.startsWith(r.RECEIVE)?y(g({},e),{count:Math.max(e.count-1,0)}):t.type.startsWith(r.RESPONSE)?y(g({},e),{count:Math.max(e.count-1,0)}):t.type===s.di.REDIRECT?y(g({},e),{count:0}):e:e};let S=E;function k(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function x(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:O,t=arguments.length>1?arguments[1]:void 0;switch(t.type){case s.di.MATCH_ROUTE:return M(x({},O),{currentUrl:t.pathname});case s.Ih.NOTIFY_SUCCESS:var n={component:t.component,props:t.props};return M(x({},e),{successes:[n],errors:[]});case s.Ih.NOTIFY_SUCCESS_MSG:return M(x({},e),{successes:[t.msg],errors:[]});case s.Ih.NOTIFY_ERROR:var r=t.error.errors,i=null==r?void 0:r.map(function(e){return L(t,e)});return M(x({},e),{successes:[],errors:i});case s.Ih.NOTIFY_ERROR_MSG:return M(x({},e),{successes:[],errors:[t.msg]});case s.Mk.RECEIVE_SIGNIN_FAIL:return M(x({},e),{successes:[],errors:["Your email or password is incorrect. Please try again"]});default:return e}};function L(e,t){return{component:e.component,props:{msg:t.detail}}}let C=A;function I(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function D(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:R,t=arguments.length>1?arguments[1]:void 0;switch(t.type){case s.di.REDIRECT:return P(D({},e),{to:t.to});case s.di.MATCH_ROUTE:return P(D({},e),{to:void 0});default:return e}};let F=j;var Y=n(87013),B=(0,a.UY)({authentication:b,fetching:S,notifications:C,redirect:F,buildInfo:Y.Z});B(void 0,{type:"INITIAL_STATE"});var U=i.v9;let H=B},19084(e,t,n){"use strict";var r,i,a,o,s,u,c,l,f,d;n.d(t,{Ih:()=>i,Mk:()=>a,Y0:()=>s,di:()=>r,jp:()=>o}),n(67294),(u=r||(r={})).REDIRECT="REDIRECT",u.MATCH_ROUTE="MATCH_ROUTE",(c=i||(i={})).NOTIFY_SUCCESS="NOTIFY_SUCCESS",c.NOTIFY_SUCCESS_MSG="NOTIFY_SUCCESS_MSG",c.NOTIFY_ERROR="NOTIFY_ERROR",c.NOTIFY_ERROR_MSG="NOTIFY_ERROR_MSG",(l=a||(a={})).REQUEST_SIGNIN="REQUEST_SIGNIN",l.RECEIVE_SIGNIN_SUCCESS="RECEIVE_SIGNIN_SUCCESS",l.RECEIVE_SIGNIN_FAIL="RECEIVE_SIGNIN_FAIL",l.RECEIVE_SIGNIN_ERROR="RECEIVE_SIGNIN_ERROR",l.RECEIVE_SIGNOUT_SUCCESS="RECEIVE_SIGNOUT_SUCCESS",l.RECEIVE_SIGNOUT_ERROR="RECEIVE_SIGNOUT_ERROR",(f=o||(o={})).RECEIVE_CREATE_ERROR="RECEIVE_CREATE_ERROR",f.RECEIVE_CREATE_SUCCESS="RECEIVE_CREATE_SUCCESS",f.RECEIVE_DELETE_ERROR="RECEIVE_DELETE_ERROR",f.RECEIVE_DELETE_SUCCESS="RECEIVE_DELETE_SUCCESS",f.RECEIVE_UPDATE_ERROR="RECEIVE_UPDATE_ERROR",f.RECEIVE_UPDATE_SUCCESS="RECEIVE_UPDATE_SUCCESS",f.REQUEST_CREATE="REQUEST_CREATE",f.REQUEST_DELETE="REQUEST_DELETE",f.REQUEST_UPDATE="REQUEST_UPDATE",f.UPSERT_CONFIGURATION="UPSERT_CONFIGURATION",f.UPSERT_JOB_RUN="UPSERT_JOB_RUN",f.UPSERT_JOB_RUNS="UPSERT_JOB_RUNS",f.UPSERT_TRANSACTION="UPSERT_TRANSACTION",f.UPSERT_TRANSACTIONS="UPSERT_TRANSACTIONS",f.UPSERT_BUILD_INFO="UPSERT_BUILD_INFO",(d=s||(s={})).FETCH_BUILD_INFO_REQUESTED="FETCH_BUILD_INFO_REQUESTED",d.FETCH_BUILD_INFO_SUCCEEDED="FETCH_BUILD_INFO_SUCCEEDED",d.FETCH_BUILD_INFO_FAILED="FETCH_BUILD_INFO_FAILED"},87013(e,t,n){"use strict";n.d(t,{Y:()=>o,Z:()=>u});var r=n(19084);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function a(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:o,t=arguments.length>1?arguments[1]:void 0;return t.type===r.Y0.FETCH_BUILD_INFO_SUCCEEDED?a({},t.buildInfo):e};let u=s},34823(e,t,n){"use strict";n.d(t,{N:()=>r});var r=function(e){return e.buildInfo}},73343(e,t,n){"use strict";n.d(t,{r:()=>u});var r=n(19350),i=n(32316),a=n(59114),o=n(5324),s={props:{MuiGrid:{spacing:3*o.default.unit},MuiCardHeader:{titleTypographyProps:{color:"secondary"}}},palette:{action:{hoverOpacity:.3},primary:{light:"#E5F1FF",main:"#3c40c6",contrastText:"#fff"},secondary:{main:"#3d5170"},success:{light:"#e8faf1",main:r.ek.A700,dark:r.ek[700],contrastText:r.y0.white},warning:{light:"#FFFBF1",main:"#fff6b6",contrastText:"#fad27a"},error:{light:"#ffdada",main:"#f44336",dark:"#d32f2f",contrastText:"#fff"},background:{default:"#f5f6f8",appBar:"#3c40c6"},text:{primary:(0,a.darken)(r.BA.A700,.7),secondary:"#818ea3"},listPendingStatus:{background:"#fef7e5",color:"#fecb4c"},listCompletedStatus:{background:"#e9faf2",color:"#4ed495"}},shape:{borderRadius:o.default.unit},overrides:{MuiButton:{root:{borderRadius:o.default.unit/2,textTransform:"none"},sizeLarge:{padding:void 0,fontSize:void 0,paddingTop:o.default.unit,paddingBottom:o.default.unit,paddingLeft:5*o.default.unit,paddingRight:5*o.default.unit}},MuiTableCell:{body:{fontSize:"1rem"},head:{fontSize:"1rem",fontWeight:400}},MuiCardHeader:{root:{borderBottom:"1px solid rgba(0, 0, 0, 0.12)"},action:{marginTop:-2,marginRight:0,"& >*":{marginLeft:2*o.default.unit}},subheader:{marginTop:.5*o.default.unit}}},typography:{useNextVariants:!0,fontFamily:"-apple-system,BlinkMacSystemFont,Roboto,Helvetica,Arial,sans-serif",button:{textTransform:"none",fontSize:"1.2em"},body1:{fontSize:"1.0rem",fontWeight:400,lineHeight:"1.46429em",color:"rgba(0, 0, 0, 0.87)",letterSpacing:-.4},body2:{fontSize:"1.0rem",fontWeight:500,lineHeight:"1.71429em",color:"rgba(0, 0, 0, 0.87)",letterSpacing:-.4},body1Next:{color:"rgb(29, 29, 29)",fontWeight:400,fontSize:"1rem",lineHeight:1.5,letterSpacing:-.4},body2Next:{color:"rgb(29, 29, 29)",fontWeight:400,fontSize:"0.875rem",lineHeight:1.5,letterSpacing:-.4},display1:{color:"#818ea3",fontSize:"2.125rem",fontWeight:400,lineHeight:"1.20588em",letterSpacing:-.4},display2:{color:"#818ea3",fontSize:"2.8125rem",fontWeight:400,lineHeight:"1.13333em",marginLeft:"-.02em",letterSpacing:-.4},display3:{color:"#818ea3",fontSize:"3.5rem",fontWeight:400,lineHeight:"1.30357em",marginLeft:"-.02em",letterSpacing:-.4},display4:{fontSize:14,fontWeightLight:300,fontWeightMedium:500,fontWeightRegular:400,letterSpacing:-.4},h1:{color:"rgb(29, 29, 29)",fontSize:"6rem",fontWeight:300,lineHeight:1},h2:{color:"rgb(29, 29, 29)",fontSize:"3.75rem",fontWeight:300,lineHeight:1},h3:{color:"rgb(29, 29, 29)",fontSize:"3rem",fontWeight:400,lineHeight:1.04},h4:{color:"rgb(29, 29, 29)",fontSize:"2.125rem",fontWeight:400,lineHeight:1.17},h5:{color:"rgb(29, 29, 29)",fontSize:"1.5rem",fontWeight:400,lineHeight:1.33,letterSpacing:-.4},h6:{fontSize:"0.8rem",fontWeight:450,lineHeight:"1.71429em",color:"rgba(0, 0, 0, 0.87)",letterSpacing:-.4},subheading:{color:"rgb(29, 29, 29)",fontSize:"1rem",fontWeight:400,lineHeight:"1.5em",letterSpacing:-.4},subtitle1:{color:"rgb(29, 29, 29)",fontSize:"1rem",fontWeight:400,lineHeight:1.75,letterSpacing:-.4},subtitle2:{color:"rgb(29, 29, 29)",fontSize:"0.875rem",fontWeight:500,lineHeight:1.57,letterSpacing:-.4}},shadows:["none","0px 1px 3px 0px rgba(0, 0, 0, 0.1),0px 1px 1px 0px rgba(0, 0, 0, 0.04),0px 2px 1px -1px rgba(0, 0, 0, 0.02)","0px 1px 5px 0px rgba(0, 0, 0, 0.1),0px 2px 2px 0px rgba(0, 0, 0, 0.04),0px 3px 1px -2px rgba(0, 0, 0, 0.02)","0px 1px 8px 0px rgba(0, 0, 0, 0.1),0px 3px 4px 0px rgba(0, 0, 0, 0.04),0px 3px 3px -2px rgba(0, 0, 0, 0.02)","0px 2px 4px -1px rgba(0, 0, 0, 0.1),0px 4px 5px 0px rgba(0, 0, 0, 0.04),0px 1px 10px 0px rgba(0, 0, 0, 0.02)","0px 3px 5px -1px rgba(0, 0, 0, 0.1),0px 5px 8px 0px rgba(0, 0, 0, 0.04),0px 1px 14px 0px rgba(0, 0, 0, 0.02)","0px 3px 5px -1px rgba(0, 0, 0, 0.1),0px 6px 10px 0px rgba(0, 0, 0, 0.04),0px 1px 18px 0px rgba(0, 0, 0, 0.02)","0px 4px 5px -2px rgba(0, 0, 0, 0.1),0px 7px 10px 1px rgba(0, 0, 0, 0.04),0px 2px 16px 1px rgba(0, 0, 0, 0.02)","0px 5px 5px -3px rgba(0, 0, 0, 0.1),0px 8px 10px 1px rgba(0, 0, 0, 0.04),0px 3px 14px 2px rgba(0, 0, 0, 0.02)","0px 5px 6px -3px rgba(0, 0, 0, 0.1),0px 9px 12px 1px rgba(0, 0, 0, 0.04),0px 3px 16px 2px rgba(0, 0, 0, 0.02)","0px 6px 6px -3px rgba(0, 0, 0, 0.1),0px 10px 14px 1px rgba(0, 0, 0, 0.04),0px 4px 18px 3px rgba(0, 0, 0, 0.02)","0px 6px 7px -4px rgba(0, 0, 0, 0.1),0px 11px 15px 1px rgba(0, 0, 0, 0.04),0px 4px 20px 3px rgba(0, 0, 0, 0.02)","0px 7px 8px -4px rgba(0, 0, 0, 0.1),0px 12px 17px 2px rgba(0, 0, 0, 0.04),0px 5px 22px 4px rgba(0, 0, 0, 0.02)","0px 7px 8px -4px rgba(0, 0, 0, 0.1),0px 13px 19px 2px rgba(0, 0, 0, 0.04),0px 5px 24px 4px rgba(0, 0, 0, 0.02)","0px 7px 9px -4px rgba(0, 0, 0, 0.1),0px 14px 21px 2px rgba(0, 0, 0, 0.04),0px 5px 26px 4px rgba(0, 0, 0, 0.02)","0px 8px 9px -5px rgba(0, 0, 0, 0.1),0px 15px 22px 2px rgba(0, 0, 0, 0.04),0px 6px 28px 5px rgba(0, 0, 0, 0.02)","0px 8px 10px -5px rgba(0, 0, 0, 0.1),0px 16px 24px 2px rgba(0, 0, 0, 0.04),0px 6px 30px 5px rgba(0, 0, 0, 0.02)","0px 8px 11px -5px rgba(0, 0, 0, 0.1),0px 17px 26px 2px rgba(0, 0, 0, 0.04),0px 6px 32px 5px rgba(0, 0, 0, 0.02)","0px 9px 11px -5px rgba(0, 0, 0, 0.1),0px 18px 28px 2px rgba(0, 0, 0, 0.04),0px 7px 34px 6px rgba(0, 0, 0, 0.02)","0px 9px 12px -6px rgba(0, 0, 0, 0.1),0px 19px 29px 2px rgba(0, 0, 0, 0.04),0px 7px 36px 6px rgba(0, 0, 0, 0.02)","0px 10px 13px -6px rgba(0, 0, 0, 0.1),0px 20px 31px 3px rgba(0, 0, 0, 0.04),0px 8px 38px 7px rgba(0, 0, 0, 0.02)","0px 10px 13px -6px rgba(0, 0, 0, 0.1),0px 21px 33px 3px rgba(0, 0, 0, 0.04),0px 8px 40px 7px rgba(0, 0, 0, 0.02)","0px 10px 14px -6px rgba(0, 0, 0, 0.1),0px 22px 35px 3px rgba(0, 0, 0, 0.04),0px 8px 42px 7px rgba(0, 0, 0, 0.02)","0px 11px 14px -7px rgba(0, 0, 0, 0.1),0px 23px 36px 3px rgba(0, 0, 0, 0.04),0px 9px 44px 8px rgba(0, 0, 0, 0.02)","0px 11px 15px -7px rgba(0, 0, 0, 0.1),0px 24px 38px 3px rgba(0, 0, 0, 0.04),0px 9px 46px 8px rgba(0, 0, 0, 0.02)",]},u=(0,i.createMuiTheme)(s)},66289(e,t,n){"use strict";function r(e){if(void 0===e)throw ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function i(e,t){if(!(e instanceof t))throw TypeError("Cannot call a class as a function")}function a(){if("undefined"==typeof Reflect||!Reflect.construct||Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],function(){})),!0}catch(e){return!1}}function o(e,t,n){return(o=a()?Reflect.construct:function(e,t,n){var r=[null];r.push.apply(r,t);var i=new(Function.bind.apply(e,r));return n&&f(i,n.prototype),i}).apply(null,arguments)}function s(e){return(s=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function u(e,t){if("function"!=typeof t&&null!==t)throw TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&f(e,t)}function c(e){return -1!==Function.toString.call(e).indexOf("[native code]")}function l(e,t){return t&&("object"===p(t)||"function"==typeof t)?t:r(e)}function f(e,t){return(f=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}n.d(t,{V0:()=>B,_7:()=>v});var d,h,p=function(e){return e&&"undefined"!=typeof Symbol&&e.constructor===Symbol?"symbol":typeof e};function b(e){var t="function"==typeof Map?new Map:void 0;return(b=function(e){if(null===e||!c(e))return e;if("function"!=typeof e)throw TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,n)}function n(){return o(e,arguments,s(this).constructor)}return n.prototype=Object.create(e.prototype,{constructor:{value:n,enumerable:!1,writable:!0,configurable:!0}}),f(n,e)})(e)}function m(){if("undefined"==typeof Reflect||!Reflect.construct||Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){})),!0}catch(e){return!1}}function g(e){var t=m();return function(){var n,r=s(e);if(t){var i=s(this).constructor;n=Reflect.construct(r,arguments,i)}else n=r.apply(this,arguments);return l(this,n)}}var v=function(e){u(n,e);var t=g(n);function n(e){var r;return i(this,n),(r=t.call(this,"AuthenticationError(".concat(e.statusText,")"))).errors=[{status:e.status,detail:e},],r}return n}(b(Error)),y=function(e){u(n,e);var t=g(n);function n(e){var r,a=e.errors;return i(this,n),(r=t.call(this,"BadRequestError")).errors=a,r}return n}(b(Error)),w=function(e){u(n,e);var t=g(n);function n(e){var r;return i(this,n),(r=t.call(this,"UnprocessableEntityError")).errors=e,r}return n}(b(Error)),_=function(e){u(n,e);var t=g(n);function n(e){var r;return i(this,n),(r=t.call(this,"ServerError")).errors=e,r}return n}(b(Error)),E=function(e){u(n,e);var t=g(n);function n(e){var r,a=e.errors;return i(this,n),(r=t.call(this,"ConflictError")).errors=a,r}return n}(b(Error)),S=function(e){u(n,e);var t=g(n);function n(e){var r;return i(this,n),(r=t.call(this,"UnknownResponseError(".concat(e.statusText,")"))).errors=[{status:e.status,detail:e.statusText},],r}return n}(b(Error));function k(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:2e4;return Promise.race([fetch(e,t),new Promise(function(e,t){return setTimeout(function(){return t(Error("timeout"))},n)}),])}function x(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]=200&&e.status<300))return[3,2];return[2,e.json()];case 2:if(400!==e.status)return[3,3];return[2,e.json().then(function(e){throw new y(e)})];case 3:if(401!==e.status)return[3,4];throw new v(e);case 4:if(422!==e.status)return[3,6];return[4,$(e)];case 5:throw n=i.sent(),new w(n);case 6:if(409!==e.status)return[3,7];return[2,e.json().then(function(e){throw new E(e)})];case 7:if(!(e.status>=500))return[3,9];return[4,$(e)];case 8:throw r=i.sent(),new _(r);case 9:throw new S(e);case 10:return[2]}})})).apply(this,arguments)}function $(e){return z.apply(this,arguments)}function z(){return(z=j(function(e){return Y(this,function(t){return[2,e.json().then(function(t){return t.errors?t.errors.map(function(t){return{status:e.status,detail:t.detail}}):G(e)}).catch(function(){return G(e)})]})})).apply(this,arguments)}function G(e){return[{status:e.status,detail:e.statusText},]}},50109(e,t,n){"use strict";n.d(t,{LK:()=>o,U2:()=>i,eT:()=>s,t8:()=>a});var r=n(12795);function i(e){return r.ZP.getItem("chainlink.".concat(e))}function a(e,t){r.ZP.setItem("chainlink.".concat(e),t)}function o(e){var t=i(e),n={};if(t)try{return JSON.parse(t)}catch(r){}return n}function s(e,t){a(e,JSON.stringify(t))}},9541(e,t,n){"use strict";n.d(t,{Ks:()=>u,Tp:()=>a,iR:()=>o,pm:()=>s});var r=n(50109),i="persistURL";function a(){return r.U2(i)||""}function o(e){r.t8(i,e)}function s(){return r.LK("authentication")}function u(e){r.eT("authentication",e)}},67121(e,t,n){"use strict";function r(e){var t,n=e.Symbol;return"function"==typeof n?n.observable?t=n.observable:(t=n("observable"),n.observable=t):t="@@observable",t}n.r(t),n.d(t,{default:()=>o}),e=n.hmd(e),i="undefined"!=typeof self?self:"undefined"!=typeof window?window:void 0!==n.g?n.g:e;var i,a=r(i);let o=a},2177(e,t,n){"use strict";n.d(t,{Z:()=>o});var r=!0,i="Invariant failed";function a(e,t){if(!e){if(r)throw Error(i);throw Error(i+": "+(t||""))}}let o=a},11742(e){e.exports=function(){var e=document.getSelection();if(!e.rangeCount)return function(){};for(var t=document.activeElement,n=[],r=0;ru,ZT:()=>i,_T:()=>o,ev:()=>c,mG:()=>s,pi:()=>a});var r=function(e,t){return(r=Object.setPrototypeOf||({__proto__:[]})instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])})(e,t)};function i(e,t){if("function"!=typeof t&&null!==t)throw TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}var a=function(){return(a=Object.assign||function(e){for(var t,n=1,r=arguments.length;nt.indexOf(r)&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var i=0,r=Object.getOwnPropertySymbols(e);it.indexOf(r[i])&&Object.prototype.propertyIsEnumerable.call(e,r[i])&&(n[r[i]]=e[r[i]]);return n}function s(e,t,n,r){function i(e){return e instanceof n?e:new n(function(t){t(e)})}return new(n||(n=Promise))(function(n,a){function o(e){try{u(r.next(e))}catch(t){a(t)}}function s(e){try{u(r.throw(e))}catch(t){a(t)}}function u(e){e.done?n(e.value):i(e.value).then(o,s)}u((r=r.apply(e,t||[])).next())})}function u(e,t){var n,r,i,a,o={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return a={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(a[Symbol.iterator]=function(){return this}),a;function s(e){return function(t){return u([e,t])}}function u(a){if(n)throw TypeError("Generator is already executing.");for(;o;)try{if(n=1,r&&(i=2&a[0]?r.return:a[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,a[1])).done)return i;switch(r=0,i&&(a=[2&a[0],i.value]),a[0]){case 0:case 1:i=a;break;case 4:return o.label++,{value:a[1],done:!1};case 5:o.label++,r=a[1],a=[0];continue;case 7:a=o.ops.pop(),o.trys.pop();continue;default:if(!(i=(i=o.trys).length>0&&i[i.length-1])&&(6===a[0]||2===a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]r})},94927(e,t,n){function r(e,t){if(i("noDeprecation"))return e;var n=!1;function r(){if(!n){if(i("throwDeprecation"))throw Error(t);i("traceDeprecation")?console.trace(t):console.warn(t),n=!0}return e.apply(this,arguments)}return r}function i(e){try{if(!n.g.localStorage)return!1}catch(t){return!1}var r=n.g.localStorage[e];return null!=r&&"true"===String(r).toLowerCase()}e.exports=r},42473(e){"use strict";var t=function(){};e.exports=t},84763(e){e.exports=Worker},47529(e){e.exports=n;var t=Object.prototype.hasOwnProperty;function n(){for(var e={},n=0;ne.length)&&(t=e.length);for(var n=0,r=Array(t);n=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}e.exports=i,e.exports.__esModule=!0,e.exports.default=e.exports},7071(e){function t(e,t){if(null==e)return{};var n,r,i={},a=Object.keys(e);for(r=0;r=0||(i[n]=e[n]);return i}e.exports=t,e.exports.__esModule=!0,e.exports.default=e.exports},94993(e,t,n){var r=n(18698).default,i=n(66115);function a(e,t){if(t&&("object"===r(t)||"function"==typeof t))return t;if(void 0!==t)throw TypeError("Derived constructors may only return object or undefined");return i(e)}e.exports=a,e.exports.__esModule=!0,e.exports.default=e.exports},6015(e){function t(n,r){return e.exports=t=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e},e.exports.__esModule=!0,e.exports.default=e.exports,t(n,r)}e.exports=t,e.exports.__esModule=!0,e.exports.default=e.exports},861(e,t,n){var r=n(63405),i=n(79498),a=n(86116),o=n(42281);function s(e){return r(e)||i(e)||a(e)||o()}e.exports=s,e.exports.__esModule=!0,e.exports.default=e.exports},18698(e){function t(n){return e.exports=t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e.exports.__esModule=!0,e.exports.default=e.exports,t(n)}e.exports=t,e.exports.__esModule=!0,e.exports.default=e.exports},86116(e,t,n){var r=n(73897);function i(e,t){if(e){if("string"==typeof e)return r(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);if("Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return r(e,t)}}e.exports=i,e.exports.__esModule=!0,e.exports.default=e.exports},1644(e,t,n){"use strict";var r,i;function a(e){return!!e&&e<7}n.d(t,{I:()=>r,O:()=>a}),(i=r||(r={}))[i.loading=1]="loading",i[i.setVariables=2]="setVariables",i[i.fetchMore=3]="fetchMore",i[i.refetch=4]="refetch",i[i.poll=6]="poll",i[i.ready=7]="ready",i[i.error=8]="error"},30990(e,t,n){"use strict";n.d(t,{MS:()=>s,YG:()=>a,cA:()=>c,ls:()=>o});var r=n(70655);n(83952);var i=n(13154),a=Symbol();function o(e){return!!e.extensions&&Array.isArray(e.extensions[a])}function s(e){return e.hasOwnProperty("graphQLErrors")}var u=function(e){var t=(0,r.ev)((0,r.ev)((0,r.ev)([],e.graphQLErrors,!0),e.clientErrors,!0),e.protocolErrors,!0);return e.networkError&&t.push(e.networkError),t.map(function(e){return(0,i.s)(e)&&e.message||"Error message not found."}).join("\n")},c=function(e){function t(n){var r=n.graphQLErrors,i=n.protocolErrors,a=n.clientErrors,o=n.networkError,s=n.errorMessage,c=n.extraInfo,l=e.call(this,s)||this;return l.name="ApolloError",l.graphQLErrors=r||[],l.protocolErrors=i||[],l.clientErrors=a||[],l.networkError=o||null,l.message=s||u(l),l.extraInfo=c,l.__proto__=t.prototype,l}return(0,r.ZT)(t,e),t}(Error)},85317(e,t,n){"use strict";n.d(t,{K:()=>a});var r=n(67294),i=n(30320).aS?Symbol.for("__APOLLO_CONTEXT__"):"__APOLLO_CONTEXT__";function a(){var e=r.createContext[i];return e||(Object.defineProperty(r.createContext,i,{value:e=r.createContext({}),enumerable:!1,writable:!1,configurable:!0}),e.displayName="ApolloContext"),e}},21436(e,t,n){"use strict";n.d(t,{O:()=>i,k:()=>r});var r=Array.isArray;function i(e){return Array.isArray(e)&&e.length>0}},30320(e,t,n){"use strict";n.d(t,{DN:()=>s,JC:()=>l,aS:()=>o,mr:()=>i,sy:()=>a});var r=n(83952),i="function"==typeof WeakMap&&"ReactNative"!==(0,r.wY)(function(){return navigator.product}),a="function"==typeof WeakSet,o="function"==typeof Symbol&&"function"==typeof Symbol.for,s=o&&Symbol.asyncIterator,u="function"==typeof(0,r.wY)(function(){return window.document.createElement}),c=(0,r.wY)(function(){return navigator.userAgent.indexOf("jsdom")>=0})||!1,l=u&&!c},53712(e,t,n){"use strict";function r(){for(var e=[],t=0;tr})},10542(e,t,n){"use strict";n.d(t,{J:()=>o}),n(83952);var r=n(13154);function i(e){var t=new Set([e]);return t.forEach(function(e){(0,r.s)(e)&&a(e)===e&&Object.getOwnPropertyNames(e).forEach(function(n){(0,r.s)(e[n])&&t.add(e[n])})}),e}function a(e){if(__DEV__&&!Object.isFrozen(e))try{Object.freeze(e)}catch(t){if(t instanceof TypeError)return null;throw t}return e}function o(e){return __DEV__&&i(e),e}},14012(e,t,n){"use strict";n.d(t,{J:()=>a});var r=n(70655),i=n(53712);function a(e,t){return(0,i.o)(e,t,t.variables&&{variables:(0,r.pi)((0,r.pi)({},e&&e.variables),t.variables)})}},13154(e,t,n){"use strict";function r(e){return null!==e&&"object"==typeof e}n.d(t,{s:()=>r})},83952(e,t,n){"use strict";n.d(t,{ej:()=>u,kG:()=>c,wY:()=>h});var r,i=n(70655),a="Invariant Violation",o=Object.setPrototypeOf,s=void 0===o?function(e,t){return e.__proto__=t,e}:o,u=function(e){function t(n){void 0===n&&(n=a);var r=e.call(this,"number"==typeof n?a+": "+n+" (see https://github.com/apollographql/invariant-packages)":n)||this;return r.framesToPop=1,r.name=a,s(r,t.prototype),r}return(0,i.ZT)(t,e),t}(Error);function c(e,t){if(!e)throw new u(t)}var l=["debug","log","warn","error","silent"],f=l.indexOf("log");function d(e){return function(){if(l.indexOf(e)>=f)return(console[e]||console.log).apply(console,arguments)}}function h(e){try{return e()}catch(t){}}(r=c||(c={})).debug=d("debug"),r.log=d("log"),r.warn=d("warn"),r.error=d("error");let p=h(function(){return globalThis})||h(function(){return window})||h(function(){return self})||h(function(){return global})||h(function(){return h.constructor("return this")()});var b="__",m=[b,b].join("DEV");function g(){try{return Boolean(__DEV__)}catch(e){return Object.defineProperty(p,m,{value:"production"!==h(function(){return"production"}),enumerable:!1,configurable:!0,writable:!0}),p[m]}}let v=g();function y(e){try{return e()}catch(t){}}var w=y(function(){return globalThis})||y(function(){return window})||y(function(){return self})||y(function(){return global})||y(function(){return y.constructor("return this")()}),_=!1;function E(){!w||y(function(){return"production"})||y(function(){return process})||(Object.defineProperty(w,"process",{value:{env:{NODE_ENV:"production"}},configurable:!0,enumerable:!1,writable:!0}),_=!0)}function S(){_&&(delete w.process,_=!1)}E();var k=n(10143);function x(){return k.H,S()}function T(){__DEV__?c("boolean"==typeof v,v):c("boolean"==typeof v,39)}x(),T()},4942(e,t,n){"use strict";function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}n.d(t,{Z:()=>r})},87462(e,t,n){"use strict";function r(){return(r=Object.assign?Object.assign.bind():function(e){for(var t=1;tr})},51721(e,t,n){"use strict";function r(e,t){return(r=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e})(e,t)}function i(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,r(e,t)}n.d(t,{Z:()=>i})},63366(e,t,n){"use strict";function r(e,t){if(null==e)return{};var n,r,i={},a=Object.keys(e);for(r=0;r=0||(i[n]=e[n]);return i}n.d(t,{Z:()=>r})},25821(e,t,n){"use strict";n.d(t,{Z:()=>s});var r=n(45695);function i(e){return(i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var a=10,o=2;function s(e){return u(e,[])}function u(e,t){switch(i(e)){case"string":return JSON.stringify(e);case"function":return e.name?"[function ".concat(e.name,"]"):"[function]";case"object":if(null===e)return"null";return c(e,t);default:return String(e)}}function c(e,t){if(-1!==t.indexOf(e))return"[Circular]";var n=[].concat(t,[e]),r=d(e);if(void 0!==r){var i=r.call(e);if(i!==e)return"string"==typeof i?i:u(i,n)}else if(Array.isArray(e))return f(e,n);return l(e,n)}function l(e,t){var n=Object.keys(e);return 0===n.length?"{}":t.length>o?"["+h(e)+"]":"{ "+n.map(function(n){var r=u(e[n],t);return n+": "+r}).join(", ")+" }"}function f(e,t){if(0===e.length)return"[]";if(t.length>o)return"[Array]";for(var n=Math.min(a,e.length),r=e.length-n,i=[],s=0;s1&&i.push("... ".concat(r," more items")),"["+i.join(", ")+"]"}function d(e){var t=e[String(r.Z)];return"function"==typeof t?t:"function"==typeof e.inspect?e.inspect:void 0}function h(e){var t=Object.prototype.toString.call(e).replace(/^\[object /,"").replace(/]$/,"");if("Object"===t&&"function"==typeof e.constructor){var n=e.constructor.name;if("string"==typeof n&&""!==n)return n}return t}},45695(e,t,n){"use strict";n.d(t,{Z:()=>i});var r="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):void 0;let i=r},25217(e,t,n){"use strict";function r(e,t){if(!Boolean(e))throw Error(null!=t?t:"Unexpected invariant triggered.")}n.d(t,{Ye:()=>o,WU:()=>s,UG:()=>u});var i=n(45695);function a(e){var t=e.prototype.toJSON;"function"==typeof t||r(0),e.prototype.inspect=t,i.Z&&(e.prototype[i.Z]=t)}var o=function(){function e(e,t,n){this.start=e.start,this.end=t.end,this.startToken=e,this.endToken=t,this.source=n}return e.prototype.toJSON=function(){return{start:this.start,end:this.end}},e}();a(o);var s=function(){function e(e,t,n,r,i,a,o){this.kind=e,this.start=t,this.end=n,this.line=r,this.column=i,this.value=o,this.prev=a,this.next=null}return e.prototype.toJSON=function(){return{kind:this.kind,value:this.value,line:this.line,column:this.column}},e}();function u(e){return null!=e&&"string"==typeof e.kind}a(s)},87392(e,t,n){"use strict";function r(e){var t=e.split(/\r\n|[\n\r]/g),n=a(e);if(0!==n)for(var r=1;ro&&i(t[s-1]);)--s;return t.slice(o,s).join("\n")}function i(e){for(var t=0;t1&&void 0!==arguments[1]?arguments[1]:"",n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],r=-1===e.indexOf("\n"),i=" "===e[0]||" "===e[0],a='"'===e[e.length-1],o="\\"===e[e.length-1],s=!r||a||o||n,u="";return s&&!(r&&i)&&(u+="\n"+t),u+=t?e.replace(/\n/g,"\n"+t):e,s&&(u+="\n"),'"""'+u.replace(/"""/g,'\\"""')+'"""'}n.d(t,{LZ:()=>o,W7:()=>r})},97359(e,t,n){"use strict";n.d(t,{h:()=>r});var r=Object.freeze({NAME:"Name",DOCUMENT:"Document",OPERATION_DEFINITION:"OperationDefinition",VARIABLE_DEFINITION:"VariableDefinition",SELECTION_SET:"SelectionSet",FIELD:"Field",ARGUMENT:"Argument",FRAGMENT_SPREAD:"FragmentSpread",INLINE_FRAGMENT:"InlineFragment",FRAGMENT_DEFINITION:"FragmentDefinition",VARIABLE:"Variable",INT:"IntValue",FLOAT:"FloatValue",STRING:"StringValue",BOOLEAN:"BooleanValue",NULL:"NullValue",ENUM:"EnumValue",LIST:"ListValue",OBJECT:"ObjectValue",OBJECT_FIELD:"ObjectField",DIRECTIVE:"Directive",NAMED_TYPE:"NamedType",LIST_TYPE:"ListType",NON_NULL_TYPE:"NonNullType",SCHEMA_DEFINITION:"SchemaDefinition",OPERATION_TYPE_DEFINITION:"OperationTypeDefinition",SCALAR_TYPE_DEFINITION:"ScalarTypeDefinition",OBJECT_TYPE_DEFINITION:"ObjectTypeDefinition",FIELD_DEFINITION:"FieldDefinition",INPUT_VALUE_DEFINITION:"InputValueDefinition",INTERFACE_TYPE_DEFINITION:"InterfaceTypeDefinition",UNION_TYPE_DEFINITION:"UnionTypeDefinition",ENUM_TYPE_DEFINITION:"EnumTypeDefinition",ENUM_VALUE_DEFINITION:"EnumValueDefinition",INPUT_OBJECT_TYPE_DEFINITION:"InputObjectTypeDefinition",DIRECTIVE_DEFINITION:"DirectiveDefinition",SCHEMA_EXTENSION:"SchemaExtension",SCALAR_TYPE_EXTENSION:"ScalarTypeExtension",OBJECT_TYPE_EXTENSION:"ObjectTypeExtension",INTERFACE_TYPE_EXTENSION:"InterfaceTypeExtension",UNION_TYPE_EXTENSION:"UnionTypeExtension",ENUM_TYPE_EXTENSION:"EnumTypeExtension",INPUT_OBJECT_TYPE_EXTENSION:"InputObjectTypeExtension"})},10143(e,t,n){"use strict";n.d(t,{H:()=>c,T:()=>l});var r=n(99763),i=n(25821);function a(e,t){if(!Boolean(e))throw Error(t)}let o=function(e,t){return e instanceof t};function s(e,t){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:"GraphQL request",n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{line:1,column:1};"string"==typeof e||a(0,"Body must be a string. Received: ".concat((0,i.Z)(e),".")),this.body=e,this.name=t,this.locationOffset=n,this.locationOffset.line>0||a(0,"line in locationOffset is 1-indexed and must be positive."),this.locationOffset.column>0||a(0,"column in locationOffset is 1-indexed and must be positive.")}return u(e,[{key:r.YF,get:function(){return"Source"}}]),e}();function l(e){return o(e,c)}},99763(e,t,n){"use strict";n.d(t,{YF:()=>r});var r="function"==typeof Symbol&&null!=Symbol.toStringTag?Symbol.toStringTag:"@@toStringTag"},37452(e){"use strict";e.exports=JSON.parse('{"AElig":"\xc6","AMP":"&","Aacute":"\xc1","Acirc":"\xc2","Agrave":"\xc0","Aring":"\xc5","Atilde":"\xc3","Auml":"\xc4","COPY":"\xa9","Ccedil":"\xc7","ETH":"\xd0","Eacute":"\xc9","Ecirc":"\xca","Egrave":"\xc8","Euml":"\xcb","GT":">","Iacute":"\xcd","Icirc":"\xce","Igrave":"\xcc","Iuml":"\xcf","LT":"<","Ntilde":"\xd1","Oacute":"\xd3","Ocirc":"\xd4","Ograve":"\xd2","Oslash":"\xd8","Otilde":"\xd5","Ouml":"\xd6","QUOT":"\\"","REG":"\xae","THORN":"\xde","Uacute":"\xda","Ucirc":"\xdb","Ugrave":"\xd9","Uuml":"\xdc","Yacute":"\xdd","aacute":"\xe1","acirc":"\xe2","acute":"\xb4","aelig":"\xe6","agrave":"\xe0","amp":"&","aring":"\xe5","atilde":"\xe3","auml":"\xe4","brvbar":"\xa6","ccedil":"\xe7","cedil":"\xb8","cent":"\xa2","copy":"\xa9","curren":"\xa4","deg":"\xb0","divide":"\xf7","eacute":"\xe9","ecirc":"\xea","egrave":"\xe8","eth":"\xf0","euml":"\xeb","frac12":"\xbd","frac14":"\xbc","frac34":"\xbe","gt":">","iacute":"\xed","icirc":"\xee","iexcl":"\xa1","igrave":"\xec","iquest":"\xbf","iuml":"\xef","laquo":"\xab","lt":"<","macr":"\xaf","micro":"\xb5","middot":"\xb7","nbsp":"\xa0","not":"\xac","ntilde":"\xf1","oacute":"\xf3","ocirc":"\xf4","ograve":"\xf2","ordf":"\xaa","ordm":"\xba","oslash":"\xf8","otilde":"\xf5","ouml":"\xf6","para":"\xb6","plusmn":"\xb1","pound":"\xa3","quot":"\\"","raquo":"\xbb","reg":"\xae","sect":"\xa7","shy":"\xad","sup1":"\xb9","sup2":"\xb2","sup3":"\xb3","szlig":"\xdf","thorn":"\xfe","times":"\xd7","uacute":"\xfa","ucirc":"\xfb","ugrave":"\xf9","uml":"\xa8","uuml":"\xfc","yacute":"\xfd","yen":"\xa5","yuml":"\xff"}')},93580(e){"use strict";e.exports=JSON.parse('{"0":"�","128":"€","130":"‚","131":"ƒ","132":"„","133":"…","134":"†","135":"‡","136":"ˆ","137":"‰","138":"Š","139":"‹","140":"Œ","142":"Ž","145":"‘","146":"’","147":"“","148":"”","149":"•","150":"–","151":"—","152":"˜","153":"™","154":"š","155":"›","156":"œ","158":"ž","159":"Ÿ"}')},67946(e){"use strict";e.exports=JSON.parse('{"locale":"en","long":{"year":{"previous":"last year","current":"this year","next":"next year","past":{"one":"{0} year ago","other":"{0} years ago"},"future":{"one":"in {0} year","other":"in {0} years"}},"quarter":{"previous":"last quarter","current":"this quarter","next":"next quarter","past":{"one":"{0} quarter ago","other":"{0} quarters ago"},"future":{"one":"in {0} quarter","other":"in {0} quarters"}},"month":{"previous":"last month","current":"this month","next":"next month","past":{"one":"{0} month ago","other":"{0} months ago"},"future":{"one":"in {0} month","other":"in {0} months"}},"week":{"previous":"last week","current":"this week","next":"next week","past":{"one":"{0} week ago","other":"{0} weeks ago"},"future":{"one":"in {0} week","other":"in {0} weeks"}},"day":{"previous":"yesterday","current":"today","next":"tomorrow","past":{"one":"{0} day ago","other":"{0} days ago"},"future":{"one":"in {0} day","other":"in {0} days"}},"hour":{"current":"this hour","past":{"one":"{0} hour ago","other":"{0} hours ago"},"future":{"one":"in {0} hour","other":"in {0} hours"}},"minute":{"current":"this minute","past":{"one":"{0} minute ago","other":"{0} minutes ago"},"future":{"one":"in {0} minute","other":"in {0} minutes"}},"second":{"current":"now","past":{"one":"{0} second ago","other":"{0} seconds ago"},"future":{"one":"in {0} second","other":"in {0} seconds"}}},"short":{"year":{"previous":"last yr.","current":"this yr.","next":"next yr.","past":"{0} yr. ago","future":"in {0} yr."},"quarter":{"previous":"last qtr.","current":"this qtr.","next":"next qtr.","past":{"one":"{0} qtr. ago","other":"{0} qtrs. ago"},"future":{"one":"in {0} qtr.","other":"in {0} qtrs."}},"month":{"previous":"last mo.","current":"this mo.","next":"next mo.","past":"{0} mo. ago","future":"in {0} mo."},"week":{"previous":"last wk.","current":"this wk.","next":"next wk.","past":"{0} wk. ago","future":"in {0} wk."},"day":{"previous":"yesterday","current":"today","next":"tomorrow","past":{"one":"{0} day ago","other":"{0} days ago"},"future":{"one":"in {0} day","other":"in {0} days"}},"hour":{"current":"this hour","past":"{0} hr. ago","future":"in {0} hr."},"minute":{"current":"this minute","past":"{0} min. ago","future":"in {0} min."},"second":{"current":"now","past":"{0} sec. ago","future":"in {0} sec."}},"narrow":{"year":{"previous":"last yr.","current":"this yr.","next":"next yr.","past":"{0} yr. ago","future":"in {0} yr."},"quarter":{"previous":"last qtr.","current":"this qtr.","next":"next qtr.","past":{"one":"{0} qtr. ago","other":"{0} qtrs. ago"},"future":{"one":"in {0} qtr.","other":"in {0} qtrs."}},"month":{"previous":"last mo.","current":"this mo.","next":"next mo.","past":"{0} mo. ago","future":"in {0} mo."},"week":{"previous":"last wk.","current":"this wk.","next":"next wk.","past":"{0} wk. ago","future":"in {0} wk."},"day":{"previous":"yesterday","current":"today","next":"tomorrow","past":{"one":"{0} day ago","other":"{0} days ago"},"future":{"one":"in {0} day","other":"in {0} days"}},"hour":{"current":"this hour","past":"{0} hr. ago","future":"in {0} hr."},"minute":{"current":"this minute","past":"{0} min. ago","future":"in {0} min."},"second":{"current":"now","past":"{0} sec. ago","future":"in {0} sec."}},"now":{"now":{"current":"now","future":"in a moment","past":"just now"}},"mini":{"year":"{0}yr","month":"{0}mo","week":"{0}wk","day":"{0}d","hour":"{0}h","minute":"{0}m","second":"{0}s","now":"now"},"short-time":{"year":"{0} yr.","month":"{0} mo.","week":"{0} wk.","day":{"one":"{0} day","other":"{0} days"},"hour":"{0} hr.","minute":"{0} min.","second":"{0} sec."},"long-time":{"year":{"one":"{0} year","other":"{0} years"},"month":{"one":"{0} month","other":"{0} months"},"week":{"one":"{0} week","other":"{0} weeks"},"day":{"one":"{0} day","other":"{0} days"},"hour":{"one":"{0} hour","other":"{0} hours"},"minute":{"one":"{0} minute","other":"{0} minutes"},"second":{"one":"{0} second","other":"{0} seconds"}}}')}},__webpack_module_cache__={};function __webpack_require__(e){var t=__webpack_module_cache__[e];if(void 0!==t)return t.exports;var n=__webpack_module_cache__[e]={id:e,loaded:!1,exports:{}};return __webpack_modules__[e].call(n.exports,n,n.exports,__webpack_require__),n.loaded=!0,n.exports}__webpack_require__.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return __webpack_require__.d(t,{a:t}),t},(()=>{var e,t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__;__webpack_require__.t=function(n,r){if(1&r&&(n=this(n)),8&r||"object"==typeof n&&n&&(4&r&&n.__esModule||16&r&&"function"==typeof n.then))return n;var i=Object.create(null);__webpack_require__.r(i);var a={};e=e||[null,t({}),t([]),t(t)];for(var o=2&r&&n;"object"==typeof o&&!~e.indexOf(o);o=t(o))Object.getOwnPropertyNames(o).forEach(e=>a[e]=()=>n[e]);return a.default=()=>n,__webpack_require__.d(i,a),i}})(),__webpack_require__.d=(e,t)=>{for(var n in t)__webpack_require__.o(t,n)&&!__webpack_require__.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},__webpack_require__.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),__webpack_require__.hmd=e=>((e=Object.create(e)).children||(e.children=[]),Object.defineProperty(e,"exports",{enumerable:!0,set(){throw Error("ES Modules may not assign module.exports or exports.*, Use ESM export syntax, instead: "+e.id)}}),e),__webpack_require__.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),__webpack_require__.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},__webpack_require__.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),__webpack_require__.p="/assets/",__webpack_require__.nc=void 0;var __webpack_exports__={};(()=>{"use strict";var e,t,n,r,i=__webpack_require__(32316),a=__webpack_require__(8126),o=__webpack_require__(5690),s=__webpack_require__(30381),u=__webpack_require__.n(s),c=__webpack_require__(67294),l=__webpack_require__(73935),f=__webpack_require__.n(l),d=__webpack_require__(57209),h=__webpack_require__(37703),p=__webpack_require__(97779),b=__webpack_require__(28500);function m(e){return function(t){var n=t.dispatch,r=t.getState;return function(t){return function(i){return"function"==typeof i?i(n,r,e):t(i)}}}}var g=m();g.withExtraArgument=m;let v=g;var y=__webpack_require__(76489);function w(e){return function(t){return function(n){return function(r){n(r);var i=e||document&&document.cookie||"",a=t.getState();if("MATCH_ROUTE"===r.type&&"/signin"!==a.notifications.currentUrl){var o=(0,y.Q)(i);if(o.explorer)try{var s=JSON.parse(o.explorer);if("error"===s.status){var u=_(s.url);n({type:"NOTIFY_ERROR_MSG",msg:u})}}catch(c){n({type:"NOTIFY_ERROR_MSG",msg:"Invalid explorer status"})}}}}}}function _(e){var t="Can't connect to explorer: ".concat(e);return e.match(/^wss?:.+/)?t:"".concat(t,". You must use a websocket.")}var E=__webpack_require__(16353);function S(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function ei(e,t){if(e){if("string"==typeof e)return ea(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);if("Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return ea(e,t)}}function ea(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n1,i=!1,a=arguments[1],o=a;return new n(function(n){return t.subscribe({next:function(t){var a=!i;if(i=!0,!a||r)try{o=e(o,t)}catch(s){return n.error(s)}else o=t},error:function(e){n.error(e)},complete:function(){if(!i&&!r)return n.error(TypeError("Cannot reduce an empty sequence"));n.next(o),n.complete()}})})},t.concat=function(){for(var e=this,t=arguments.length,n=Array(t),r=0;r=0&&i.splice(e,1),o()}});i.push(s)},error:function(e){r.error(e)},complete:function(){o()}});function o(){a.closed&&0===i.length&&r.complete()}return function(){i.forEach(function(e){return e.unsubscribe()}),a.unsubscribe()}})},t[ed]=function(){return this},e.from=function(t){var n="function"==typeof this?this:e;if(null==t)throw TypeError(t+" is not an object");var r=ep(t,ed);if(r){var i=r.call(t);if(Object(i)!==i)throw TypeError(i+" is not an object");return em(i)&&i.constructor===n?i:new n(function(e){return i.subscribe(e)})}if(ec("iterator")&&(r=ep(t,ef)))return new n(function(e){ev(function(){if(!e.closed){for(var n,i=er(r.call(t));!(n=i()).done;){var a=n.value;if(e.next(a),e.closed)return}e.complete()}})});if(Array.isArray(t))return new n(function(e){ev(function(){if(!e.closed){for(var n=0;n0))return n.connection.key;var r=n.connection.filter?n.connection.filter:[];r.sort();var i={};return r.forEach(function(e){i[e]=t[e]}),"".concat(n.connection.key,"(").concat(eV(i),")")}var a=e;if(t){var o=eV(t);a+="(".concat(o,")")}return n&&Object.keys(n).forEach(function(e){-1===eW.indexOf(e)&&(n[e]&&Object.keys(n[e]).length?a+="@".concat(e,"(").concat(eV(n[e]),")"):a+="@".concat(e))}),a},{setStringify:function(e){var t=eV;return eV=e,t}}),eV=function(e){return JSON.stringify(e,eq)};function eq(e,t){return(0,eO.s)(t)&&!Array.isArray(t)&&(t=Object.keys(t).sort().reduce(function(e,n){return e[n]=t[n],e},{})),t}function eZ(e,t){if(e.arguments&&e.arguments.length){var n={};return e.arguments.forEach(function(e){var r;return ez(n,e.name,e.value,t)}),n}return null}function eX(e){return e.alias?e.alias.value:e.name.value}function eJ(e,t,n){for(var r,i=0,a=t.selections;it.indexOf(i))throw __DEV__?new Q.ej("illegal argument: ".concat(i)):new Q.ej(27)}return e}function tt(e,t){return t?t(e):eT.of()}function tn(e){return"function"==typeof e?new ta(e):e}function tr(e){return e.request.length<=1}var ti=function(e){function t(t,n){var r=e.call(this,t)||this;return r.link=n,r}return(0,en.ZT)(t,e),t}(Error),ta=function(){function e(e){e&&(this.request=e)}return e.empty=function(){return new e(function(){return eT.of()})},e.from=function(t){return 0===t.length?e.empty():t.map(tn).reduce(function(e,t){return e.concat(t)})},e.split=function(t,n,r){var i=tn(n),a=tn(r||new e(tt));return new e(tr(i)&&tr(a)?function(e){return t(e)?i.request(e)||eT.of():a.request(e)||eT.of()}:function(e,n){return t(e)?i.request(e,n)||eT.of():a.request(e,n)||eT.of()})},e.execute=function(e,t){return e.request(eM(t.context,e7(te(t))))||eT.of()},e.concat=function(t,n){var r=tn(t);if(tr(r))return __DEV__&&Q.kG.warn(new ti("You are calling concat on a terminating link, which will have no effect",r)),r;var i=tn(n);return new e(tr(i)?function(e){return r.request(e,function(e){return i.request(e)||eT.of()})||eT.of()}:function(e,t){return r.request(e,function(e){return i.request(e,t)||eT.of()})||eT.of()})},e.prototype.split=function(t,n,r){return this.concat(e.split(t,n,r||new e(tt)))},e.prototype.concat=function(t){return e.concat(this,t)},e.prototype.request=function(e,t){throw __DEV__?new Q.ej("request is not implemented"):new Q.ej(22)},e.prototype.onError=function(e,t){if(t&&t.error)return t.error(e),!1;throw e},e.prototype.setOnError=function(e){return this.onError=e,this},e}(),to=__webpack_require__(25821),ts=__webpack_require__(25217),tu={Name:[],Document:["definitions"],OperationDefinition:["name","variableDefinitions","directives","selectionSet"],VariableDefinition:["variable","type","defaultValue","directives"],Variable:["name"],SelectionSet:["selections"],Field:["alias","name","arguments","directives","selectionSet"],Argument:["name","value"],FragmentSpread:["name","directives"],InlineFragment:["typeCondition","directives","selectionSet"],FragmentDefinition:["name","variableDefinitions","typeCondition","directives","selectionSet"],IntValue:[],FloatValue:[],StringValue:[],BooleanValue:[],NullValue:[],EnumValue:[],ListValue:["values"],ObjectValue:["fields"],ObjectField:["name","value"],Directive:["name","arguments"],NamedType:["name"],ListType:["type"],NonNullType:["type"],SchemaDefinition:["description","directives","operationTypes"],OperationTypeDefinition:["type"],ScalarTypeDefinition:["description","name","directives"],ObjectTypeDefinition:["description","name","interfaces","directives","fields"],FieldDefinition:["description","name","arguments","type","directives"],InputValueDefinition:["description","name","type","defaultValue","directives"],InterfaceTypeDefinition:["description","name","interfaces","directives","fields"],UnionTypeDefinition:["description","name","directives","types"],EnumTypeDefinition:["description","name","directives","values"],EnumValueDefinition:["description","name","directives"],InputObjectTypeDefinition:["description","name","directives","fields"],DirectiveDefinition:["description","name","arguments","locations"],SchemaExtension:["directives","operationTypes"],ScalarTypeExtension:["name","directives"],ObjectTypeExtension:["name","interfaces","directives","fields"],InterfaceTypeExtension:["name","interfaces","directives","fields"],UnionTypeExtension:["name","directives","types"],EnumTypeExtension:["name","directives","values"],InputObjectTypeExtension:["name","directives","fields"]},tc=Object.freeze({});function tl(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:tu,r=void 0,i=Array.isArray(e),a=[e],o=-1,s=[],u=void 0,c=void 0,l=void 0,f=[],d=[],h=e;do{var p,b=++o===a.length,m=b&&0!==s.length;if(b){if(c=0===d.length?void 0:f[f.length-1],u=l,l=d.pop(),m){if(i)u=u.slice();else{for(var g={},v=0,y=Object.keys(u);v1)for(var r=new tB,i=1;i=0;--a){var o=i[a],s=isNaN(+o)?{}:[];s[o]=t,t=s}n=r.merge(n,t)}),n}var tW=Object.prototype.hasOwnProperty;function tK(e,t){var n,r,i,a,o;return(0,en.mG)(this,void 0,void 0,function(){var s,u,c,l,f,d,h,p,b,m,g,v,y,w,_,E,S,k,x,T,M,O,A;return(0,en.Jh)(this,function(L){switch(L.label){case 0:if(void 0===TextDecoder)throw Error("TextDecoder must be defined in the environment: please import a polyfill.");s=new TextDecoder("utf-8"),u=null===(n=e.headers)||void 0===n?void 0:n.get("content-type"),c="boundary=",l=(null==u?void 0:u.includes(c))?null==u?void 0:u.substring((null==u?void 0:u.indexOf(c))+c.length).replace(/['"]/g,"").replace(/\;(.*)/gm,"").trim():"-",f="\r\n--".concat(l),d="",h=tI(e),p=!0,L.label=1;case 1:if(!p)return[3,3];return[4,h.next()];case 2:for(m=(b=L.sent()).value,g=b.done,v="string"==typeof m?m:s.decode(m),y=d.length-f.length+1,p=!g,d+=v,w=d.indexOf(f,y);w>-1;){if(_=void 0,_=(O=[d.slice(0,w),d.slice(w+f.length),])[0],d=O[1],E=_.indexOf("\r\n\r\n"),(k=(S=tV(_.slice(0,E)))["content-type"])&&-1===k.toLowerCase().indexOf("application/json"))throw Error("Unsupported patch content type: application/json is required.");if(x=_.slice(E))try{T=tq(e,x),Object.keys(T).length>1||"data"in T||"incremental"in T||"errors"in T||"payload"in T?tz(T)?(M={},"payload"in T&&(M=(0,en.pi)({},T.payload)),"errors"in T&&(M=(0,en.pi)((0,en.pi)({},M),{extensions:(0,en.pi)((0,en.pi)({},"extensions"in M?M.extensions:null),((A={})[tN.YG]=T.errors,A))})),null===(r=t.next)||void 0===r||r.call(t,M)):null===(i=t.next)||void 0===i||i.call(t,T):1===Object.keys(T).length&&"hasNext"in T&&!T.hasNext&&(null===(a=t.complete)||void 0===a||a.call(t))}catch(C){tZ(C,t)}w=d.indexOf(f)}return[3,1];case 3:return null===(o=t.complete)||void 0===o||o.call(t),[2]}})})}function tV(e){var t={};return e.split("\n").forEach(function(e){var n=e.indexOf(":");if(n>-1){var r=e.slice(0,n).trim().toLowerCase(),i=e.slice(n+1).trim();t[r]=i}}),t}function tq(e,t){e.status>=300&&tD(e,function(){try{return JSON.parse(t)}catch(e){return t}}(),"Response not successful: Received status code ".concat(e.status));try{return JSON.parse(t)}catch(n){var r=n;throw r.name="ServerParseError",r.response=e,r.statusCode=e.status,r.bodyText=t,r}}function tZ(e,t){var n,r;"AbortError"!==e.name&&(e.result&&e.result.errors&&e.result.data&&(null===(n=t.next)||void 0===n||n.call(t,e.result)),null===(r=t.error)||void 0===r||r.call(t,e))}function tX(e,t,n){tJ(t)(e).then(function(e){var t,r;null===(t=n.next)||void 0===t||t.call(n,e),null===(r=n.complete)||void 0===r||r.call(n)}).catch(function(e){return tZ(e,n)})}function tJ(e){return function(t){return t.text().then(function(e){return tq(t,e)}).then(function(n){return t.status>=300&&tD(t,n,"Response not successful: Received status code ".concat(t.status)),Array.isArray(n)||tW.call(n,"data")||tW.call(n,"errors")||tD(t,n,"Server response was missing for query '".concat(Array.isArray(e)?e.map(function(e){return e.operationName}):e.operationName,"'.")),n})}}var tQ=function(e){if(!e&&"undefined"==typeof fetch)throw __DEV__?new Q.ej("\n\"fetch\" has not been found globally and no fetcher has been configured. To fix this, install a fetch package (like https://www.npmjs.com/package/cross-fetch), instantiate the fetcher, and pass it into your HttpLink constructor. For example:\n\nimport fetch from 'cross-fetch';\nimport { ApolloClient, HttpLink } from '@apollo/client';\nconst client = new ApolloClient({\n link: new HttpLink({ uri: '/graphql', fetch })\n});\n "):new Q.ej(23)},t1=__webpack_require__(87392);function t0(e){return tl(e,{leave:t3})}var t2=80,t3={Name:function(e){return e.value},Variable:function(e){return"$"+e.name},Document:function(e){return t6(e.definitions,"\n\n")+"\n"},OperationDefinition:function(e){var t=e.operation,n=e.name,r=t8("(",t6(e.variableDefinitions,", "),")"),i=t6(e.directives," "),a=e.selectionSet;return n||i||r||"query"!==t?t6([t,t6([n,r]),i,a]," "):a},VariableDefinition:function(e){var t=e.variable,n=e.type,r=e.defaultValue,i=e.directives;return t+": "+n+t8(" = ",r)+t8(" ",t6(i," "))},SelectionSet:function(e){return t5(e.selections)},Field:function(e){var t=e.alias,n=e.name,r=e.arguments,i=e.directives,a=e.selectionSet,o=t8("",t,": ")+n,s=o+t8("(",t6(r,", "),")");return s.length>t2&&(s=o+t8("(\n",t9(t6(r,"\n")),"\n)")),t6([s,t6(i," "),a]," ")},Argument:function(e){var t;return e.name+": "+e.value},FragmentSpread:function(e){var t;return"..."+e.name+t8(" ",t6(e.directives," "))},InlineFragment:function(e){var t=e.typeCondition,n=e.directives,r=e.selectionSet;return t6(["...",t8("on ",t),t6(n," "),r]," ")},FragmentDefinition:function(e){var t=e.name,n=e.typeCondition,r=e.variableDefinitions,i=e.directives,a=e.selectionSet;return"fragment ".concat(t).concat(t8("(",t6(r,", "),")")," ")+"on ".concat(n," ").concat(t8("",t6(i," ")," "))+a},IntValue:function(e){return e.value},FloatValue:function(e){return e.value},StringValue:function(e,t){var n=e.value;return e.block?(0,t1.LZ)(n,"description"===t?"":" "):JSON.stringify(n)},BooleanValue:function(e){return e.value?"true":"false"},NullValue:function(){return"null"},EnumValue:function(e){return e.value},ListValue:function(e){return"["+t6(e.values,", ")+"]"},ObjectValue:function(e){return"{"+t6(e.fields,", ")+"}"},ObjectField:function(e){var t;return e.name+": "+e.value},Directive:function(e){var t;return"@"+e.name+t8("(",t6(e.arguments,", "),")")},NamedType:function(e){return e.name},ListType:function(e){return"["+e.type+"]"},NonNullType:function(e){return e.type+"!"},SchemaDefinition:t4(function(e){var t=e.directives,n=e.operationTypes;return t6(["schema",t6(t," "),t5(n)]," ")}),OperationTypeDefinition:function(e){var t;return e.operation+": "+e.type},ScalarTypeDefinition:t4(function(e){var t;return t6(["scalar",e.name,t6(e.directives," ")]," ")}),ObjectTypeDefinition:t4(function(e){var t=e.name,n=e.interfaces,r=e.directives,i=e.fields;return t6(["type",t,t8("implements ",t6(n," & ")),t6(r," "),t5(i)]," ")}),FieldDefinition:t4(function(e){var t=e.name,n=e.arguments,r=e.type,i=e.directives;return t+(ne(n)?t8("(\n",t9(t6(n,"\n")),"\n)"):t8("(",t6(n,", "),")"))+": "+r+t8(" ",t6(i," "))}),InputValueDefinition:t4(function(e){var t=e.name,n=e.type,r=e.defaultValue,i=e.directives;return t6([t+": "+n,t8("= ",r),t6(i," ")]," ")}),InterfaceTypeDefinition:t4(function(e){var t=e.name,n=e.interfaces,r=e.directives,i=e.fields;return t6(["interface",t,t8("implements ",t6(n," & ")),t6(r," "),t5(i)]," ")}),UnionTypeDefinition:t4(function(e){var t=e.name,n=e.directives,r=e.types;return t6(["union",t,t6(n," "),r&&0!==r.length?"= "+t6(r," | "):""]," ")}),EnumTypeDefinition:t4(function(e){var t=e.name,n=e.directives,r=e.values;return t6(["enum",t,t6(n," "),t5(r)]," ")}),EnumValueDefinition:t4(function(e){var t;return t6([e.name,t6(e.directives," ")]," ")}),InputObjectTypeDefinition:t4(function(e){var t=e.name,n=e.directives,r=e.fields;return t6(["input",t,t6(n," "),t5(r)]," ")}),DirectiveDefinition:t4(function(e){var t=e.name,n=e.arguments,r=e.repeatable,i=e.locations;return"directive @"+t+(ne(n)?t8("(\n",t9(t6(n,"\n")),"\n)"):t8("(",t6(n,", "),")"))+(r?" repeatable":"")+" on "+t6(i," | ")}),SchemaExtension:function(e){var t=e.directives,n=e.operationTypes;return t6(["extend schema",t6(t," "),t5(n)]," ")},ScalarTypeExtension:function(e){var t;return t6(["extend scalar",e.name,t6(e.directives," ")]," ")},ObjectTypeExtension:function(e){var t=e.name,n=e.interfaces,r=e.directives,i=e.fields;return t6(["extend type",t,t8("implements ",t6(n," & ")),t6(r," "),t5(i)]," ")},InterfaceTypeExtension:function(e){var t=e.name,n=e.interfaces,r=e.directives,i=e.fields;return t6(["extend interface",t,t8("implements ",t6(n," & ")),t6(r," "),t5(i)]," ")},UnionTypeExtension:function(e){var t=e.name,n=e.directives,r=e.types;return t6(["extend union",t,t6(n," "),r&&0!==r.length?"= "+t6(r," | "):""]," ")},EnumTypeExtension:function(e){var t=e.name,n=e.directives,r=e.values;return t6(["extend enum",t,t6(n," "),t5(r)]," ")},InputObjectTypeExtension:function(e){var t=e.name,n=e.directives,r=e.fields;return t6(["extend input",t,t6(n," "),t5(r)]," ")}};function t4(e){return function(t){return t6([t.description,e(t)],"\n")}}function t6(e){var t,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return null!==(t=null==e?void 0:e.filter(function(e){return e}).join(n))&&void 0!==t?t:""}function t5(e){return t8("{\n",t9(t6(e,"\n")),"\n}")}function t8(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"";return null!=t&&""!==t?e+t+n:""}function t9(e){return t8(" ",e.replace(/\n/g,"\n "))}function t7(e){return -1!==e.indexOf("\n")}function ne(e){return null!=e&&e.some(t7)}var nt,nn,nr,ni={http:{includeQuery:!0,includeExtensions:!1,preserveHeaderCase:!1},headers:{accept:"*/*","content-type":"application/json"},options:{method:"POST"}},na=function(e,t){return t(e)};function no(e,t){for(var n=[],r=2;rObject.create(null),{forEach:nv,slice:ny}=Array.prototype,{hasOwnProperty:nw}=Object.prototype;class n_{constructor(e=!0,t=ng){this.weakness=e,this.makeData=t}lookup(...e){return this.lookupArray(e)}lookupArray(e){let t=this;return nv.call(e,e=>t=t.getChildTrie(e)),nw.call(t,"data")?t.data:t.data=this.makeData(ny.call(e))}peek(...e){return this.peekArray(e)}peekArray(e){let t=this;for(let n=0,r=e.length;t&&n=0;--o)t.definitions[o].kind===nL.h.OPERATION_DEFINITION&&++a;var s=nN(e),u=e.some(function(e){return e.remove}),c=function(e){return u&&e&&e.some(s)},l=new Map,f=!1,d={enter:function(e){if(c(e.directives))return f=!0,null}},h=tl(t,{Field:d,InlineFragment:d,VariableDefinition:{enter:function(){return!1}},Variable:{enter:function(e,t,n,r,a){var o=i(a);o&&o.variables.add(e.name.value)}},FragmentSpread:{enter:function(e,t,n,r,a){if(c(e.directives))return f=!0,null;var o=i(a);o&&o.fragmentSpreads.add(e.name.value)}},FragmentDefinition:{enter:function(e,t,n,r){l.set(JSON.stringify(r),e)},leave:function(e,t,n,i){return e===l.get(JSON.stringify(i))?e:a>0&&e.selectionSet.selections.every(function(e){return e.kind===nL.h.FIELD&&"__typename"===e.name.value})?(r(e.name.value).removed=!0,f=!0,null):void 0}},Directive:{leave:function(e){if(s(e))return f=!0,null}}});if(!f)return t;var p=function(e){return e.transitiveVars||(e.transitiveVars=new Set(e.variables),e.removed||e.fragmentSpreads.forEach(function(t){p(r(t)).transitiveVars.forEach(function(t){e.transitiveVars.add(t)})})),e},b=new Set;h.definitions.forEach(function(e){e.kind===nL.h.OPERATION_DEFINITION?p(n(e.name&&e.name.value)).fragmentSpreads.forEach(function(e){b.add(e)}):e.kind!==nL.h.FRAGMENT_DEFINITION||0!==a||r(e.name.value).removed||b.add(e.name.value)}),b.forEach(function(e){p(r(e)).fragmentSpreads.forEach(function(e){b.add(e)})});var m=function(e){return!!(!b.has(e)||r(e).removed)},g={enter:function(e){if(m(e.name.value))return null}};return nD(tl(h,{FragmentSpread:g,FragmentDefinition:g,OperationDefinition:{leave:function(e){if(e.variableDefinitions){var t=p(n(e.name&&e.name.value)).transitiveVars;if(t.size0},t.prototype.tearDownQuery=function(){this.isTornDown||(this.concast&&this.observer&&(this.concast.removeObserver(this.observer),delete this.concast,delete this.observer),this.stopPolling(),this.subscriptions.forEach(function(e){return e.unsubscribe()}),this.subscriptions.clear(),this.queryManager.stopQuery(this.queryId),this.observers.clear(),this.isTornDown=!0)},t}(eT);function n4(e){var t=e.options,n=t.fetchPolicy,r=t.nextFetchPolicy;return"cache-and-network"===n||"network-only"===n?e.reobserve({fetchPolicy:"cache-first",nextFetchPolicy:function(){return(this.nextFetchPolicy=r,"function"==typeof r)?r.apply(this,arguments):n}}):e.reobserve()}function n6(e){__DEV__&&Q.kG.error("Unhandled error",e.message,e.stack)}function n5(e){__DEV__&&e&&__DEV__&&Q.kG.debug("Missing cache result fields: ".concat(JSON.stringify(e)),e)}function n8(e){return"network-only"===e||"no-cache"===e||"standby"===e}nK(n3);function n9(e){return e.kind===nL.h.FIELD||e.kind===nL.h.FRAGMENT_SPREAD||e.kind===nL.h.INLINE_FRAGMENT}function n7(e){return e.kind===Kind.SCALAR_TYPE_DEFINITION||e.kind===Kind.OBJECT_TYPE_DEFINITION||e.kind===Kind.INTERFACE_TYPE_DEFINITION||e.kind===Kind.UNION_TYPE_DEFINITION||e.kind===Kind.ENUM_TYPE_DEFINITION||e.kind===Kind.INPUT_OBJECT_TYPE_DEFINITION}function re(e){return e.kind===Kind.SCALAR_TYPE_EXTENSION||e.kind===Kind.OBJECT_TYPE_EXTENSION||e.kind===Kind.INTERFACE_TYPE_EXTENSION||e.kind===Kind.UNION_TYPE_EXTENSION||e.kind===Kind.ENUM_TYPE_EXTENSION||e.kind===Kind.INPUT_OBJECT_TYPE_EXTENSION}var rt=function(){return Object.create(null)},rn=Array.prototype,rr=rn.forEach,ri=rn.slice,ra=function(){function e(e,t){void 0===e&&(e=!0),void 0===t&&(t=rt),this.weakness=e,this.makeData=t}return e.prototype.lookup=function(){for(var e=[],t=0;tclass{constructor(){this.id=["slot",rc++,Date.now(),Math.random().toString(36).slice(2),].join(":")}hasValue(){for(let e=rs;e;e=e.parent)if(this.id in e.slots){let t=e.slots[this.id];if(t===ru)break;return e!==rs&&(rs.slots[this.id]=t),!0}return rs&&(rs.slots[this.id]=ru),!1}getValue(){if(this.hasValue())return rs.slots[this.id]}withValue(e,t,n,r){let i={__proto__:null,[this.id]:e},a=rs;rs={parent:a,slots:i};try{return t.apply(r,n)}finally{rs=a}}static bind(e){let t=rs;return function(){let n=rs;try{return rs=t,e.apply(this,arguments)}finally{rs=n}}}static noContext(e,t,n){if(!rs)return e.apply(n,t);{let r=rs;try{return rs=null,e.apply(n,t)}finally{rs=r}}}};function rf(e){try{return e()}catch(t){}}let rd="@wry/context:Slot",rh=rf(()=>globalThis)||rf(()=>global)||Object.create(null),rp=rh,rb=rp[rd]||Array[rd]||function(e){try{Object.defineProperty(rp,rd,{value:e,enumerable:!1,writable:!1,configurable:!0})}finally{return e}}(rl()),{bind:rm,noContext:rg}=rb;function rv(){}var ry=function(){function e(e,t){void 0===e&&(e=1/0),void 0===t&&(t=rv),this.max=e,this.dispose=t,this.map=new Map,this.newest=null,this.oldest=null}return e.prototype.has=function(e){return this.map.has(e)},e.prototype.get=function(e){var t=this.getNode(e);return t&&t.value},e.prototype.getNode=function(e){var t=this.map.get(e);if(t&&t!==this.newest){var n=t.older,r=t.newer;r&&(r.older=n),n&&(n.newer=r),t.older=this.newest,t.older.newer=t,t.newer=null,this.newest=t,t===this.oldest&&(this.oldest=r)}return t},e.prototype.set=function(e,t){var n=this.getNode(e);return n?n.value=t:(n={key:e,value:t,newer:null,older:this.newest},this.newest&&(this.newest.newer=n),this.newest=n,this.oldest=this.oldest||n,this.map.set(e,n),n.value)},e.prototype.clean=function(){for(;this.oldest&&this.map.size>this.max;)this.delete(this.oldest.key)},e.prototype.delete=function(e){var t=this.map.get(e);return!!t&&(t===this.newest&&(this.newest=t.older),t===this.oldest&&(this.oldest=t.newer),t.newer&&(t.newer.older=t.older),t.older&&(t.older.newer=t.newer),this.map.delete(e),this.dispose(t.value,e),!0)},e}(),rw=new rb,r_=Object.prototype.hasOwnProperty,rE=void 0===(n=Array.from)?function(e){var t=[];return e.forEach(function(e){return t.push(e)}),t}:n;function rS(e){var t=e.unsubscribe;"function"==typeof t&&(e.unsubscribe=void 0,t())}var rk=[],rx=100;function rT(e,t){if(!e)throw Error(t||"assertion failure")}function rM(e,t){var n=e.length;return n>0&&n===t.length&&e[n-1]===t[n-1]}function rO(e){switch(e.length){case 0:throw Error("unknown value");case 1:return e[0];case 2:throw e[1]}}function rA(e){return e.slice(0)}var rL=function(){function e(t){this.fn=t,this.parents=new Set,this.childValues=new Map,this.dirtyChildren=null,this.dirty=!0,this.recomputing=!1,this.value=[],this.deps=null,++e.count}return e.prototype.peek=function(){if(1===this.value.length&&!rN(this))return rC(this),this.value[0]},e.prototype.recompute=function(e){return rT(!this.recomputing,"already recomputing"),rC(this),rN(this)?rI(this,e):rO(this.value)},e.prototype.setDirty=function(){this.dirty||(this.dirty=!0,this.value.length=0,rR(this),rS(this))},e.prototype.dispose=function(){var e=this;this.setDirty(),rH(this),rF(this,function(t,n){t.setDirty(),r$(t,e)})},e.prototype.forget=function(){this.dispose()},e.prototype.dependOn=function(e){e.add(this),this.deps||(this.deps=rk.pop()||new Set),this.deps.add(e)},e.prototype.forgetDeps=function(){var e=this;this.deps&&(rE(this.deps).forEach(function(t){return t.delete(e)}),this.deps.clear(),rk.push(this.deps),this.deps=null)},e.count=0,e}();function rC(e){var t=rw.getValue();if(t)return e.parents.add(t),t.childValues.has(e)||t.childValues.set(e,[]),rN(e)?rY(t,e):rB(t,e),t}function rI(e,t){return rH(e),rw.withValue(e,rD,[e,t]),rz(e,t)&&rP(e),rO(e.value)}function rD(e,t){e.recomputing=!0,e.value.length=0;try{e.value[0]=e.fn.apply(null,t)}catch(n){e.value[1]=n}e.recomputing=!1}function rN(e){return e.dirty||!!(e.dirtyChildren&&e.dirtyChildren.size)}function rP(e){e.dirty=!1,!rN(e)&&rj(e)}function rR(e){rF(e,rY)}function rj(e){rF(e,rB)}function rF(e,t){var n=e.parents.size;if(n)for(var r=rE(e.parents),i=0;i0&&e.childValues.forEach(function(t,n){r$(e,n)}),e.forgetDeps(),rT(null===e.dirtyChildren)}function r$(e,t){t.parents.delete(e),e.childValues.delete(t),rU(e,t)}function rz(e,t){if("function"==typeof e.subscribe)try{rS(e),e.unsubscribe=e.subscribe.apply(null,t)}catch(n){return e.setDirty(),!1}return!0}var rG={setDirty:!0,dispose:!0,forget:!0};function rW(e){var t=new Map,n=e&&e.subscribe;function r(e){var r=rw.getValue();if(r){var i=t.get(e);i||t.set(e,i=new Set),r.dependOn(i),"function"==typeof n&&(rS(i),i.unsubscribe=n(e))}}return r.dirty=function(e,n){var r=t.get(e);if(r){var i=n&&r_.call(rG,n)?n:"setDirty";rE(r).forEach(function(e){return e[i]()}),t.delete(e),rS(r)}},r}function rK(){var e=new ra("function"==typeof WeakMap);return function(){return e.lookupArray(arguments)}}var rV=rK(),rq=new Set;function rZ(e,t){void 0===t&&(t=Object.create(null));var n=new ry(t.max||65536,function(e){return e.dispose()}),r=t.keyArgs,i=t.makeCacheKey||rK(),a=function(){var a=i.apply(null,r?r.apply(null,arguments):arguments);if(void 0===a)return e.apply(null,arguments);var o=n.get(a);o||(n.set(a,o=new rL(e)),o.subscribe=t.subscribe,o.forget=function(){return n.delete(a)});var s=o.recompute(Array.prototype.slice.call(arguments));return n.set(a,o),rq.add(n),rw.hasValue()||(rq.forEach(function(e){return e.clean()}),rq.clear()),s};function o(e){var t=n.get(e);t&&t.setDirty()}function s(e){var t=n.get(e);if(t)return t.peek()}function u(e){return n.delete(e)}return Object.defineProperty(a,"size",{get:function(){return n.map.size},configurable:!1,enumerable:!1}),a.dirtyKey=o,a.dirty=function(){o(i.apply(null,arguments))},a.peekKey=s,a.peek=function(){return s(i.apply(null,arguments))},a.forgetKey=u,a.forget=function(){return u(i.apply(null,arguments))},a.makeCacheKey=i,a.getKey=r?function(){return i.apply(null,r.apply(null,arguments))}:i,Object.freeze(a)}var rX=new rb,rJ=new WeakMap;function rQ(e){var t=rJ.get(e);return t||rJ.set(e,t={vars:new Set,dep:rW()}),t}function r1(e){rQ(e).vars.forEach(function(t){return t.forgetCache(e)})}function r0(e){rQ(e).vars.forEach(function(t){return t.attachCache(e)})}function r2(e){var t=new Set,n=new Set,r=function(a){if(arguments.length>0){if(e!==a){e=a,t.forEach(function(e){rQ(e).dep.dirty(r),r3(e)});var o=Array.from(n);n.clear(),o.forEach(function(t){return t(e)})}}else{var s=rX.getValue();s&&(i(s),rQ(s).dep(r))}return e};r.onNextChange=function(e){return n.add(e),function(){n.delete(e)}};var i=r.attachCache=function(e){return t.add(e),rQ(e).vars.add(r),r};return r.forgetCache=function(e){return t.delete(e)},r}function r3(e){e.broadcastWatches&&e.broadcastWatches()}var r4=function(){function e(e){var t=e.cache,n=e.client,r=e.resolvers,i=e.fragmentMatcher;this.selectionsToResolveCache=new WeakMap,this.cache=t,n&&(this.client=n),r&&this.addResolvers(r),i&&this.setFragmentMatcher(i)}return e.prototype.addResolvers=function(e){var t=this;this.resolvers=this.resolvers||{},Array.isArray(e)?e.forEach(function(e){t.resolvers=tj(t.resolvers,e)}):this.resolvers=tj(this.resolvers,e)},e.prototype.setResolvers=function(e){this.resolvers={},this.addResolvers(e)},e.prototype.getResolvers=function(){return this.resolvers||{}},e.prototype.runResolvers=function(e){var t=e.document,n=e.remoteResult,r=e.context,i=e.variables,a=e.onlyRunForcedResolvers,o=void 0!==a&&a;return(0,en.mG)(this,void 0,void 0,function(){return(0,en.Jh)(this,function(e){return t?[2,this.resolveDocument(t,n.data,r,i,this.fragmentMatcher,o).then(function(e){return(0,en.pi)((0,en.pi)({},n),{data:e.result})})]:[2,n]})})},e.prototype.setFragmentMatcher=function(e){this.fragmentMatcher=e},e.prototype.getFragmentMatcher=function(){return this.fragmentMatcher},e.prototype.clientQuery=function(e){return tb(["client"],e)&&this.resolvers?e:null},e.prototype.serverQuery=function(e){return n$(e)},e.prototype.prepareContext=function(e){var t=this.cache;return(0,en.pi)((0,en.pi)({},e),{cache:t,getCacheKey:function(e){return t.identify(e)}})},e.prototype.addExportedVariables=function(e,t,n){return void 0===t&&(t={}),void 0===n&&(n={}),(0,en.mG)(this,void 0,void 0,function(){return(0,en.Jh)(this,function(r){return e?[2,this.resolveDocument(e,this.buildRootValueFromCache(e,t)||{},this.prepareContext(n),t).then(function(e){return(0,en.pi)((0,en.pi)({},t),e.exportedVariables)})]:[2,(0,en.pi)({},t)]})})},e.prototype.shouldForceResolvers=function(e){var t=!1;return tl(e,{Directive:{enter:function(e){if("client"===e.name.value&&e.arguments&&(t=e.arguments.some(function(e){return"always"===e.name.value&&"BooleanValue"===e.value.kind&&!0===e.value.value})))return tc}}}),t},e.prototype.buildRootValueFromCache=function(e,t){return this.cache.diff({query:nH(e),variables:t,returnPartialData:!0,optimistic:!1}).result},e.prototype.resolveDocument=function(e,t,n,r,i,a){return void 0===n&&(n={}),void 0===r&&(r={}),void 0===i&&(i=function(){return!0}),void 0===a&&(a=!1),(0,en.mG)(this,void 0,void 0,function(){var o,s,u,c,l,f,d,h,p,b,m;return(0,en.Jh)(this,function(g){return o=e8(e),s=e4(e),u=eL(s),c=this.collectSelectionsToResolve(o,u),f=(l=o.operation)?l.charAt(0).toUpperCase()+l.slice(1):"Query",d=this,h=d.cache,p=d.client,b={fragmentMap:u,context:(0,en.pi)((0,en.pi)({},n),{cache:h,client:p}),variables:r,fragmentMatcher:i,defaultOperationType:f,exportedVariables:{},selectionsToResolve:c,onlyRunForcedResolvers:a},m=!1,[2,this.resolveSelectionSet(o.selectionSet,m,t,b).then(function(e){return{result:e,exportedVariables:b.exportedVariables}})]})})},e.prototype.resolveSelectionSet=function(e,t,n,r){return(0,en.mG)(this,void 0,void 0,function(){var i,a,o,s,u,c=this;return(0,en.Jh)(this,function(l){return i=r.fragmentMap,a=r.context,o=r.variables,s=[n],u=function(e){return(0,en.mG)(c,void 0,void 0,function(){var u,c;return(0,en.Jh)(this,function(l){return(t||r.selectionsToResolve.has(e))&&td(e,o)?eQ(e)?[2,this.resolveField(e,t,n,r).then(function(t){var n;void 0!==t&&s.push(((n={})[eX(e)]=t,n))})]:(e1(e)?u=e:(u=i[e.name.value],__DEV__?(0,Q.kG)(u,"No fragment named ".concat(e.name.value)):(0,Q.kG)(u,11)),u&&u.typeCondition&&(c=u.typeCondition.name.value,r.fragmentMatcher(n,c,a)))?[2,this.resolveSelectionSet(u.selectionSet,t,n,r).then(function(e){s.push(e)})]:[2]:[2]})})},[2,Promise.all(e.selections.map(u)).then(function(){return tF(s)})]})})},e.prototype.resolveField=function(e,t,n,r){return(0,en.mG)(this,void 0,void 0,function(){var i,a,o,s,u,c,l,f,d,h=this;return(0,en.Jh)(this,function(p){return n?(i=r.variables,a=e.name.value,o=eX(e),s=a!==o,c=Promise.resolve(u=n[o]||n[a]),(!r.onlyRunForcedResolvers||this.shouldForceResolvers(e))&&(l=n.__typename||r.defaultOperationType,(f=this.resolvers&&this.resolvers[l])&&(d=f[s?a:o])&&(c=Promise.resolve(rX.withValue(this.cache,d,[n,eZ(e,i),r.context,{field:e,fragmentMap:r.fragmentMap},])))),[2,c.then(function(n){if(void 0===n&&(n=u),e.directives&&e.directives.forEach(function(e){"export"===e.name.value&&e.arguments&&e.arguments.forEach(function(e){"as"===e.name.value&&"StringValue"===e.value.kind&&(r.exportedVariables[e.value.value]=n)})}),!e.selectionSet||null==n)return n;var i,a,o=null!==(a=null===(i=e.directives)||void 0===i?void 0:i.some(function(e){return"client"===e.name.value}))&&void 0!==a&&a;return Array.isArray(n)?h.resolveSubSelectedArray(e,t||o,n,r):e.selectionSet?h.resolveSelectionSet(e.selectionSet,t||o,n,r):void 0})]):[2,null]})})},e.prototype.resolveSubSelectedArray=function(e,t,n,r){var i=this;return Promise.all(n.map(function(n){return null===n?null:Array.isArray(n)?i.resolveSubSelectedArray(e,t,n,r):e.selectionSet?i.resolveSelectionSet(e.selectionSet,t,n,r):void 0}))},e.prototype.collectSelectionsToResolve=function(e,t){var n=function(e){return!Array.isArray(e)},r=this.selectionsToResolveCache;function i(e){if(!r.has(e)){var a=new Set;r.set(e,a),tl(e,{Directive:function(e,t,r,i,o){"client"===e.name.value&&o.forEach(function(e){n(e)&&n9(e)&&a.add(e)})},FragmentSpread:function(e,r,o,s,u){var c=t[e.name.value];__DEV__?(0,Q.kG)(c,"No fragment named ".concat(e.name.value)):(0,Q.kG)(c,12);var l=i(c);l.size>0&&(u.forEach(function(e){n(e)&&n9(e)&&a.add(e)}),a.add(e),l.forEach(function(e){a.add(e)}))}})}return r.get(e)}return i(e)},e}(),r6=new(t_.mr?WeakMap:Map);function r5(e,t){var n=e[t];"function"==typeof n&&(e[t]=function(){return r6.set(e,(r6.get(e)+1)%1e15),n.apply(this,arguments)})}function r8(e){e.notifyTimeout&&(clearTimeout(e.notifyTimeout),e.notifyTimeout=void 0)}var r9=function(){function e(e,t){void 0===t&&(t=e.generateQueryId()),this.queryId=t,this.listeners=new Set,this.document=null,this.lastRequestId=1,this.subscriptions=new Set,this.stopped=!1,this.dirty=!1,this.observableQuery=null;var n=this.cache=e.cache;r6.has(n)||(r6.set(n,0),r5(n,"evict"),r5(n,"modify"),r5(n,"reset"))}return e.prototype.init=function(e){var t=e.networkStatus||nZ.I.loading;return this.variables&&this.networkStatus!==nZ.I.loading&&!(0,nm.D)(this.variables,e.variables)&&(t=nZ.I.setVariables),(0,nm.D)(e.variables,this.variables)||(this.lastDiff=void 0),Object.assign(this,{document:e.document,variables:e.variables,networkError:null,graphQLErrors:this.graphQLErrors||[],networkStatus:t}),e.observableQuery&&this.setObservableQuery(e.observableQuery),e.lastRequestId&&(this.lastRequestId=e.lastRequestId),this},e.prototype.reset=function(){r8(this),this.dirty=!1},e.prototype.getDiff=function(e){void 0===e&&(e=this.variables);var t=this.getDiffOptions(e);if(this.lastDiff&&(0,nm.D)(t,this.lastDiff.options))return this.lastDiff.diff;this.updateWatch(this.variables=e);var n=this.observableQuery;if(n&&"no-cache"===n.options.fetchPolicy)return{complete:!1};var r=this.cache.diff(t);return this.updateLastDiff(r,t),r},e.prototype.updateLastDiff=function(e,t){this.lastDiff=e?{diff:e,options:t||this.getDiffOptions()}:void 0},e.prototype.getDiffOptions=function(e){var t;return void 0===e&&(e=this.variables),{query:this.document,variables:e,returnPartialData:!0,optimistic:!0,canonizeResults:null===(t=this.observableQuery)||void 0===t?void 0:t.options.canonizeResults}},e.prototype.setDiff=function(e){var t=this,n=this.lastDiff&&this.lastDiff.diff;this.updateLastDiff(e),this.dirty||(0,nm.D)(n&&n.result,e&&e.result)||(this.dirty=!0,this.notifyTimeout||(this.notifyTimeout=setTimeout(function(){return t.notify()},0)))},e.prototype.setObservableQuery=function(e){var t=this;e!==this.observableQuery&&(this.oqListener&&this.listeners.delete(this.oqListener),this.observableQuery=e,e?(e.queryInfo=this,this.listeners.add(this.oqListener=function(){t.getDiff().fromOptimisticTransaction?e.observe():n4(e)})):delete this.oqListener)},e.prototype.notify=function(){var e=this;r8(this),this.shouldNotify()&&this.listeners.forEach(function(t){return t(e)}),this.dirty=!1},e.prototype.shouldNotify=function(){if(!this.dirty||!this.listeners.size)return!1;if((0,nZ.O)(this.networkStatus)&&this.observableQuery){var e=this.observableQuery.options.fetchPolicy;if("cache-only"!==e&&"cache-and-network"!==e)return!1}return!0},e.prototype.stop=function(){if(!this.stopped){this.stopped=!0,this.reset(),this.cancel(),this.cancel=e.prototype.cancel,this.subscriptions.forEach(function(e){return e.unsubscribe()});var t=this.observableQuery;t&&t.stopPolling()}},e.prototype.cancel=function(){},e.prototype.updateWatch=function(e){var t=this;void 0===e&&(e=this.variables);var n=this.observableQuery;if(!n||"no-cache"!==n.options.fetchPolicy){var r=(0,en.pi)((0,en.pi)({},this.getDiffOptions(e)),{watcher:this,callback:function(e){return t.setDiff(e)}});this.lastWatch&&(0,nm.D)(r,this.lastWatch)||(this.cancel(),this.cancel=this.cache.watch(this.lastWatch=r))}},e.prototype.resetLastWrite=function(){this.lastWrite=void 0},e.prototype.shouldWrite=function(e,t){var n=this.lastWrite;return!(n&&n.dmCount===r6.get(this.cache)&&(0,nm.D)(t,n.variables)&&(0,nm.D)(e.data,n.result.data))},e.prototype.markResult=function(e,t,n,r){var i=this,a=new tB,o=(0,tP.O)(e.errors)?e.errors.slice(0):[];if(this.reset(),"incremental"in e&&(0,tP.O)(e.incremental)){var s=tG(this.getDiff().result,e);e.data=s}else if("hasNext"in e&&e.hasNext){var u=this.getDiff();e.data=a.merge(u.result,e.data)}this.graphQLErrors=o,"no-cache"===n.fetchPolicy?this.updateLastDiff({result:e.data,complete:!0},this.getDiffOptions(n.variables)):0!==r&&(r7(e,n.errorPolicy)?this.cache.performTransaction(function(a){if(i.shouldWrite(e,n.variables))a.writeQuery({query:t,data:e.data,variables:n.variables,overwrite:1===r}),i.lastWrite={result:e,variables:n.variables,dmCount:r6.get(i.cache)};else if(i.lastDiff&&i.lastDiff.diff.complete){e.data=i.lastDiff.diff.result;return}var o=i.getDiffOptions(n.variables),s=a.diff(o);i.stopped||i.updateWatch(n.variables),i.updateLastDiff(s,o),s.complete&&(e.data=s.result)}):this.lastWrite=void 0)},e.prototype.markReady=function(){return this.networkError=null,this.networkStatus=nZ.I.ready},e.prototype.markError=function(e){return this.networkStatus=nZ.I.error,this.lastWrite=void 0,this.reset(),e.graphQLErrors&&(this.graphQLErrors=e.graphQLErrors),e.networkError&&(this.networkError=e.networkError),e},e}();function r7(e,t){void 0===t&&(t="none");var n="ignore"===t||"all"===t,r=!nO(e);return!r&&n&&e.data&&(r=!0),r}var ie=Object.prototype.hasOwnProperty,it=function(){function e(e){var t=e.cache,n=e.link,r=e.defaultOptions,i=e.queryDeduplication,a=void 0!==i&&i,o=e.onBroadcast,s=e.ssrMode,u=void 0!==s&&s,c=e.clientAwareness,l=void 0===c?{}:c,f=e.localState,d=e.assumeImmutableResults;this.clientAwareness={},this.queries=new Map,this.fetchCancelFns=new Map,this.transformCache=new(t_.mr?WeakMap:Map),this.queryIdCounter=1,this.requestIdCounter=1,this.mutationIdCounter=1,this.inFlightLinkObservables=new Map,this.cache=t,this.link=n,this.defaultOptions=r||Object.create(null),this.queryDeduplication=a,this.clientAwareness=l,this.localState=f||new r4({cache:t}),this.ssrMode=u,this.assumeImmutableResults=!!d,(this.onBroadcast=o)&&(this.mutationStore=Object.create(null))}return e.prototype.stop=function(){var e=this;this.queries.forEach(function(t,n){e.stopQueryNoBroadcast(n)}),this.cancelPendingFetches(__DEV__?new Q.ej("QueryManager stopped while query was in flight"):new Q.ej(14))},e.prototype.cancelPendingFetches=function(e){this.fetchCancelFns.forEach(function(t){return t(e)}),this.fetchCancelFns.clear()},e.prototype.mutate=function(e){var t,n,r=e.mutation,i=e.variables,a=e.optimisticResponse,o=e.updateQueries,s=e.refetchQueries,u=void 0===s?[]:s,c=e.awaitRefetchQueries,l=void 0!==c&&c,f=e.update,d=e.onQueryUpdated,h=e.fetchPolicy,p=void 0===h?(null===(t=this.defaultOptions.mutate)||void 0===t?void 0:t.fetchPolicy)||"network-only":h,b=e.errorPolicy,m=void 0===b?(null===(n=this.defaultOptions.mutate)||void 0===n?void 0:n.errorPolicy)||"none":b,g=e.keepRootFields,v=e.context;return(0,en.mG)(this,void 0,void 0,function(){var e,t,n,s,c,h;return(0,en.Jh)(this,function(b){switch(b.label){case 0:if(__DEV__?(0,Q.kG)(r,"mutation option is required. You must specify your GraphQL document in the mutation option."):(0,Q.kG)(r,15),__DEV__?(0,Q.kG)("network-only"===p||"no-cache"===p,"Mutations support only 'network-only' or 'no-cache' fetchPolicy strings. The default `network-only` behavior automatically writes mutation results to the cache. Passing `no-cache` skips the cache write."):(0,Q.kG)("network-only"===p||"no-cache"===p,16),e=this.generateMutationId(),n=(t=this.transform(r)).document,s=t.hasClientExports,r=this.cache.transformForLink(n),i=this.getVariables(r,i),!s)return[3,2];return[4,this.localState.addExportedVariables(r,i,v)];case 1:i=b.sent(),b.label=2;case 2:return c=this.mutationStore&&(this.mutationStore[e]={mutation:r,variables:i,loading:!0,error:null}),a&&this.markMutationOptimistic(a,{mutationId:e,document:r,variables:i,fetchPolicy:p,errorPolicy:m,context:v,updateQueries:o,update:f,keepRootFields:g}),this.broadcastQueries(),h=this,[2,new Promise(function(t,n){return nM(h.getObservableFromLink(r,(0,en.pi)((0,en.pi)({},v),{optimisticResponse:a}),i,!1),function(t){if(nO(t)&&"none"===m)throw new tN.cA({graphQLErrors:nA(t)});c&&(c.loading=!1,c.error=null);var n=(0,en.pi)({},t);return"function"==typeof u&&(u=u(n)),"ignore"===m&&nO(n)&&delete n.errors,h.markMutationResult({mutationId:e,result:n,document:r,variables:i,fetchPolicy:p,errorPolicy:m,context:v,update:f,updateQueries:o,awaitRefetchQueries:l,refetchQueries:u,removeOptimistic:a?e:void 0,onQueryUpdated:d,keepRootFields:g})}).subscribe({next:function(e){h.broadcastQueries(),"hasNext"in e&&!1!==e.hasNext||t(e)},error:function(t){c&&(c.loading=!1,c.error=t),a&&h.cache.removeOptimistic(e),h.broadcastQueries(),n(t instanceof tN.cA?t:new tN.cA({networkError:t}))}})})]}})})},e.prototype.markMutationResult=function(e,t){var n=this;void 0===t&&(t=this.cache);var r=e.result,i=[],a="no-cache"===e.fetchPolicy;if(!a&&r7(r,e.errorPolicy)){if(tU(r)||i.push({result:r.data,dataId:"ROOT_MUTATION",query:e.document,variables:e.variables}),tU(r)&&(0,tP.O)(r.incremental)){var o=t.diff({id:"ROOT_MUTATION",query:this.transform(e.document).asQuery,variables:e.variables,optimistic:!1,returnPartialData:!0}),s=void 0;o.result&&(s=tG(o.result,r)),void 0!==s&&(r.data=s,i.push({result:s,dataId:"ROOT_MUTATION",query:e.document,variables:e.variables}))}var u=e.updateQueries;u&&this.queries.forEach(function(e,a){var o=e.observableQuery,s=o&&o.queryName;if(s&&ie.call(u,s)){var c,l=u[s],f=n.queries.get(a),d=f.document,h=f.variables,p=t.diff({query:d,variables:h,returnPartialData:!0,optimistic:!1}),b=p.result;if(p.complete&&b){var m=l(b,{mutationResult:r,queryName:d&&e3(d)||void 0,queryVariables:h});m&&i.push({result:m,dataId:"ROOT_QUERY",query:d,variables:h})}}})}if(i.length>0||e.refetchQueries||e.update||e.onQueryUpdated||e.removeOptimistic){var c=[];if(this.refetchQueries({updateCache:function(t){a||i.forEach(function(e){return t.write(e)});var o=e.update,s=!t$(r)||tU(r)&&!r.hasNext;if(o){if(!a){var u=t.diff({id:"ROOT_MUTATION",query:n.transform(e.document).asQuery,variables:e.variables,optimistic:!1,returnPartialData:!0});u.complete&&("incremental"in(r=(0,en.pi)((0,en.pi)({},r),{data:u.result}))&&delete r.incremental,"hasNext"in r&&delete r.hasNext)}s&&o(t,r,{context:e.context,variables:e.variables})}a||e.keepRootFields||!s||t.modify({id:"ROOT_MUTATION",fields:function(e,t){var n=t.fieldName,r=t.DELETE;return"__typename"===n?e:r}})},include:e.refetchQueries,optimistic:!1,removeOptimistic:e.removeOptimistic,onQueryUpdated:e.onQueryUpdated||null}).forEach(function(e){return c.push(e)}),e.awaitRefetchQueries||e.onQueryUpdated)return Promise.all(c).then(function(){return r})}return Promise.resolve(r)},e.prototype.markMutationOptimistic=function(e,t){var n=this,r="function"==typeof e?e(t.variables):e;return this.cache.recordOptimisticTransaction(function(e){try{n.markMutationResult((0,en.pi)((0,en.pi)({},t),{result:{data:r}}),e)}catch(i){__DEV__&&Q.kG.error(i)}},t.mutationId)},e.prototype.fetchQuery=function(e,t,n){return this.fetchQueryObservable(e,t,n).promise},e.prototype.getQueryStore=function(){var e=Object.create(null);return this.queries.forEach(function(t,n){e[n]={variables:t.variables,networkStatus:t.networkStatus,networkError:t.networkError,graphQLErrors:t.graphQLErrors}}),e},e.prototype.resetErrors=function(e){var t=this.queries.get(e);t&&(t.networkError=void 0,t.graphQLErrors=[])},e.prototype.transform=function(e){var t=this.transformCache;if(!t.has(e)){var n=this.cache.transformDocument(e),r=nY(n),i=this.localState.clientQuery(n),a=r&&this.localState.serverQuery(r),o={document:n,hasClientExports:tm(n),hasForcedResolvers:this.localState.shouldForceResolvers(n),clientQuery:i,serverQuery:a,defaultVars:e9(e2(n)),asQuery:(0,en.pi)((0,en.pi)({},n),{definitions:n.definitions.map(function(e){return"OperationDefinition"===e.kind&&"query"!==e.operation?(0,en.pi)((0,en.pi)({},e),{operation:"query"}):e})})},s=function(e){e&&!t.has(e)&&t.set(e,o)};s(e),s(n),s(i),s(a)}return t.get(e)},e.prototype.getVariables=function(e,t){return(0,en.pi)((0,en.pi)({},this.transform(e).defaultVars),t)},e.prototype.watchQuery=function(e){void 0===(e=(0,en.pi)((0,en.pi)({},e),{variables:this.getVariables(e.query,e.variables)})).notifyOnNetworkStatusChange&&(e.notifyOnNetworkStatusChange=!1);var t=new r9(this),n=new n3({queryManager:this,queryInfo:t,options:e});return this.queries.set(n.queryId,t),t.init({document:n.query,observableQuery:n,variables:n.variables}),n},e.prototype.query=function(e,t){var n=this;return void 0===t&&(t=this.generateQueryId()),__DEV__?(0,Q.kG)(e.query,"query option is required. You must specify your GraphQL document in the query option."):(0,Q.kG)(e.query,17),__DEV__?(0,Q.kG)("Document"===e.query.kind,'You must wrap the query string in a "gql" tag.'):(0,Q.kG)("Document"===e.query.kind,18),__DEV__?(0,Q.kG)(!e.returnPartialData,"returnPartialData option only supported on watchQuery."):(0,Q.kG)(!e.returnPartialData,19),__DEV__?(0,Q.kG)(!e.pollInterval,"pollInterval option only supported on watchQuery."):(0,Q.kG)(!e.pollInterval,20),this.fetchQuery(t,e).finally(function(){return n.stopQuery(t)})},e.prototype.generateQueryId=function(){return String(this.queryIdCounter++)},e.prototype.generateRequestId=function(){return this.requestIdCounter++},e.prototype.generateMutationId=function(){return String(this.mutationIdCounter++)},e.prototype.stopQueryInStore=function(e){this.stopQueryInStoreNoBroadcast(e),this.broadcastQueries()},e.prototype.stopQueryInStoreNoBroadcast=function(e){var t=this.queries.get(e);t&&t.stop()},e.prototype.clearStore=function(e){return void 0===e&&(e={discardWatches:!0}),this.cancelPendingFetches(__DEV__?new Q.ej("Store reset while query was in flight (not completed in link chain)"):new Q.ej(21)),this.queries.forEach(function(e){e.observableQuery?e.networkStatus=nZ.I.loading:e.stop()}),this.mutationStore&&(this.mutationStore=Object.create(null)),this.cache.reset(e)},e.prototype.getObservableQueries=function(e){var t=this;void 0===e&&(e="active");var n=new Map,r=new Map,i=new Set;return Array.isArray(e)&&e.forEach(function(e){"string"==typeof e?r.set(e,!1):eN(e)?r.set(t.transform(e).document,!1):(0,eO.s)(e)&&e.query&&i.add(e)}),this.queries.forEach(function(t,i){var a=t.observableQuery,o=t.document;if(a){if("all"===e){n.set(i,a);return}var s=a.queryName;if("standby"===a.options.fetchPolicy||"active"===e&&!a.hasObservers())return;("active"===e||s&&r.has(s)||o&&r.has(o))&&(n.set(i,a),s&&r.set(s,!0),o&&r.set(o,!0))}}),i.size&&i.forEach(function(e){var r=nG("legacyOneTimeQuery"),i=t.getQuery(r).init({document:e.query,variables:e.variables}),a=new n3({queryManager:t,queryInfo:i,options:(0,en.pi)((0,en.pi)({},e),{fetchPolicy:"network-only"})});(0,Q.kG)(a.queryId===r),i.setObservableQuery(a),n.set(r,a)}),__DEV__&&r.size&&r.forEach(function(e,t){!e&&__DEV__&&Q.kG.warn("Unknown query ".concat("string"==typeof t?"named ":"").concat(JSON.stringify(t,null,2)," requested in refetchQueries options.include array"))}),n},e.prototype.reFetchObservableQueries=function(e){var t=this;void 0===e&&(e=!1);var n=[];return this.getObservableQueries(e?"all":"active").forEach(function(r,i){var a=r.options.fetchPolicy;r.resetLastResults(),(e||"standby"!==a&&"cache-only"!==a)&&n.push(r.refetch()),t.getQuery(i).setDiff(null)}),this.broadcastQueries(),Promise.all(n)},e.prototype.setObservableQuery=function(e){this.getQuery(e.queryId).setObservableQuery(e)},e.prototype.startGraphQLSubscription=function(e){var t=this,n=e.query,r=e.fetchPolicy,i=e.errorPolicy,a=e.variables,o=e.context,s=void 0===o?{}:o;n=this.transform(n).document,a=this.getVariables(n,a);var u=function(e){return t.getObservableFromLink(n,s,e).map(function(a){"no-cache"!==r&&(r7(a,i)&&t.cache.write({query:n,result:a.data,dataId:"ROOT_SUBSCRIPTION",variables:e}),t.broadcastQueries());var o=nO(a),s=(0,tN.ls)(a);if(o||s){var u={};throw o&&(u.graphQLErrors=a.errors),s&&(u.protocolErrors=a.extensions[tN.YG]),new tN.cA(u)}return a})};if(this.transform(n).hasClientExports){var c=this.localState.addExportedVariables(n,a,s).then(u);return new eT(function(e){var t=null;return c.then(function(n){return t=n.subscribe(e)},e.error),function(){return t&&t.unsubscribe()}})}return u(a)},e.prototype.stopQuery=function(e){this.stopQueryNoBroadcast(e),this.broadcastQueries()},e.prototype.stopQueryNoBroadcast=function(e){this.stopQueryInStoreNoBroadcast(e),this.removeQuery(e)},e.prototype.removeQuery=function(e){this.fetchCancelFns.delete(e),this.queries.has(e)&&(this.getQuery(e).stop(),this.queries.delete(e))},e.prototype.broadcastQueries=function(){this.onBroadcast&&this.onBroadcast(),this.queries.forEach(function(e){return e.notify()})},e.prototype.getLocalState=function(){return this.localState},e.prototype.getObservableFromLink=function(e,t,n,r){var i,a,o=this;void 0===r&&(r=null!==(i=null==t?void 0:t.queryDeduplication)&&void 0!==i?i:this.queryDeduplication);var s=this.transform(e).serverQuery;if(s){var u=this,c=u.inFlightLinkObservables,l=u.link,f={query:s,variables:n,operationName:e3(s)||void 0,context:this.prepareContext((0,en.pi)((0,en.pi)({},t),{forceFetch:!r}))};if(t=f.context,r){var d=c.get(s)||new Map;c.set(s,d);var h=nx(n);if(!(a=d.get(h))){var p=new nq([np(l,f)]);d.set(h,a=p),p.beforeNext(function(){d.delete(h)&&d.size<1&&c.delete(s)})}}else a=new nq([np(l,f)])}else a=new nq([eT.of({data:{}})]),t=this.prepareContext(t);var b=this.transform(e).clientQuery;return b&&(a=nM(a,function(e){return o.localState.runResolvers({document:b,remoteResult:e,context:t,variables:n})})),a},e.prototype.getResultsFromLink=function(e,t,n){var r=e.lastRequestId=this.generateRequestId(),i=this.cache.transformForLink(this.transform(e.document).document);return nM(this.getObservableFromLink(i,n.context,n.variables),function(a){var o=nA(a),s=o.length>0;if(r>=e.lastRequestId){if(s&&"none"===n.errorPolicy)throw e.markError(new tN.cA({graphQLErrors:o}));e.markResult(a,i,n,t),e.markReady()}var u={data:a.data,loading:!1,networkStatus:nZ.I.ready};return s&&"ignore"!==n.errorPolicy&&(u.errors=o,u.networkStatus=nZ.I.error),u},function(t){var n=(0,tN.MS)(t)?t:new tN.cA({networkError:t});throw r>=e.lastRequestId&&e.markError(n),n})},e.prototype.fetchQueryObservable=function(e,t,n){return this.fetchConcastWithInfo(e,t,n).concast},e.prototype.fetchConcastWithInfo=function(e,t,n){var r,i,a=this;void 0===n&&(n=nZ.I.loading);var o=this.transform(t.query).document,s=this.getVariables(o,t.variables),u=this.getQuery(e),c=this.defaultOptions.watchQuery,l=t.fetchPolicy,f=void 0===l?c&&c.fetchPolicy||"cache-first":l,d=t.errorPolicy,h=void 0===d?c&&c.errorPolicy||"none":d,p=t.returnPartialData,b=void 0!==p&&p,m=t.notifyOnNetworkStatusChange,g=void 0!==m&&m,v=t.context,y=void 0===v?{}:v,w=Object.assign({},t,{query:o,variables:s,fetchPolicy:f,errorPolicy:h,returnPartialData:b,notifyOnNetworkStatusChange:g,context:y}),_=function(e){w.variables=e;var r=a.fetchQueryByPolicy(u,w,n);return"standby"!==w.fetchPolicy&&r.sources.length>0&&u.observableQuery&&u.observableQuery.applyNextFetchPolicy("after-fetch",t),r},E=function(){return a.fetchCancelFns.delete(e)};if(this.fetchCancelFns.set(e,function(e){E(),setTimeout(function(){return r.cancel(e)})}),this.transform(w.query).hasClientExports)r=new nq(this.localState.addExportedVariables(w.query,w.variables,w.context).then(_).then(function(e){return e.sources})),i=!0;else{var S=_(w.variables);i=S.fromLink,r=new nq(S.sources)}return r.promise.then(E,E),{concast:r,fromLink:i}},e.prototype.refetchQueries=function(e){var t=this,n=e.updateCache,r=e.include,i=e.optimistic,a=void 0!==i&&i,o=e.removeOptimistic,s=void 0===o?a?nG("refetchQueries"):void 0:o,u=e.onQueryUpdated,c=new Map;r&&this.getObservableQueries(r).forEach(function(e,n){c.set(n,{oq:e,lastDiff:t.getQuery(n).getDiff()})});var l=new Map;return n&&this.cache.batch({update:n,optimistic:a&&s||!1,removeOptimistic:s,onWatchUpdated:function(e,t,n){var r=e.watcher instanceof r9&&e.watcher.observableQuery;if(r){if(u){c.delete(r.queryId);var i=u(r,t,n);return!0===i&&(i=r.refetch()),!1!==i&&l.set(r,i),i}null!==u&&c.set(r.queryId,{oq:r,lastDiff:n,diff:t})}}}),c.size&&c.forEach(function(e,n){var r,i=e.oq,a=e.lastDiff,o=e.diff;if(u){if(!o){var s=i.queryInfo;s.reset(),o=s.getDiff()}r=u(i,o,a)}u&&!0!==r||(r=i.refetch()),!1!==r&&l.set(i,r),n.indexOf("legacyOneTimeQuery")>=0&&t.stopQueryNoBroadcast(n)}),s&&this.cache.removeOptimistic(s),l},e.prototype.fetchQueryByPolicy=function(e,t,n){var r=this,i=t.query,a=t.variables,o=t.fetchPolicy,s=t.refetchWritePolicy,u=t.errorPolicy,c=t.returnPartialData,l=t.context,f=t.notifyOnNetworkStatusChange,d=e.networkStatus;e.init({document:this.transform(i).document,variables:a,networkStatus:n});var h=function(){return e.getDiff(a)},p=function(t,n){void 0===n&&(n=e.networkStatus||nZ.I.loading);var o=t.result;!__DEV__||c||(0,nm.D)(o,{})||n5(t.missing);var s=function(e){return eT.of((0,en.pi)({data:e,loading:(0,nZ.O)(n),networkStatus:n},t.complete?null:{partial:!0}))};return o&&r.transform(i).hasForcedResolvers?r.localState.runResolvers({document:i,remoteResult:{data:o},context:l,variables:a,onlyRunForcedResolvers:!0}).then(function(e){return s(e.data||void 0)}):"none"===u&&n===nZ.I.refetch&&Array.isArray(t.missing)?s(void 0):s(o)},b="no-cache"===o?0:n===nZ.I.refetch&&"merge"!==s?1:2,m=function(){return r.getResultsFromLink(e,b,{variables:a,context:l,fetchPolicy:o,errorPolicy:u})},g=f&&"number"==typeof d&&d!==n&&(0,nZ.O)(n);switch(o){default:case"cache-first":var v=h();if(v.complete)return{fromLink:!1,sources:[p(v,e.markReady())]};if(c||g)return{fromLink:!0,sources:[p(v),m()]};return{fromLink:!0,sources:[m()]};case"cache-and-network":var v=h();if(v.complete||c||g)return{fromLink:!0,sources:[p(v),m()]};return{fromLink:!0,sources:[m()]};case"cache-only":return{fromLink:!1,sources:[p(h(),e.markReady())]};case"network-only":if(g)return{fromLink:!0,sources:[p(h()),m()]};return{fromLink:!0,sources:[m()]};case"no-cache":if(g)return{fromLink:!0,sources:[p(e.getDiff()),m(),]};return{fromLink:!0,sources:[m()]};case"standby":return{fromLink:!1,sources:[]}}},e.prototype.getQuery=function(e){return e&&!this.queries.has(e)&&this.queries.set(e,new r9(this,e)),this.queries.get(e)},e.prototype.prepareContext=function(e){void 0===e&&(e={});var t=this.localState.prepareContext(e);return(0,en.pi)((0,en.pi)({},t),{clientAwareness:this.clientAwareness})},e}(),ir=__webpack_require__(14012),ii=!1,ia=function(){function e(e){var t=this;this.resetStoreCallbacks=[],this.clearStoreCallbacks=[];var n=e.uri,r=e.credentials,i=e.headers,a=e.cache,o=e.ssrMode,s=void 0!==o&&o,u=e.ssrForceFetchDelay,c=void 0===u?0:u,l=e.connectToDevTools,f=void 0===l?"object"==typeof window&&!window.__APOLLO_CLIENT__&&__DEV__:l,d=e.queryDeduplication,h=void 0===d||d,p=e.defaultOptions,b=e.assumeImmutableResults,m=void 0!==b&&b,g=e.resolvers,v=e.typeDefs,y=e.fragmentMatcher,w=e.name,_=e.version,E=e.link;if(E||(E=n?new nh({uri:n,credentials:r,headers:i}):ta.empty()),!a)throw __DEV__?new Q.ej("To initialize Apollo Client, you must specify a 'cache' property in the options object. \nFor more information, please visit: https://go.apollo.dev/c/docs"):new Q.ej(9);if(this.link=E,this.cache=a,this.disableNetworkFetches=s||c>0,this.queryDeduplication=h,this.defaultOptions=p||Object.create(null),this.typeDefs=v,c&&setTimeout(function(){return t.disableNetworkFetches=!1},c),this.watchQuery=this.watchQuery.bind(this),this.query=this.query.bind(this),this.mutate=this.mutate.bind(this),this.resetStore=this.resetStore.bind(this),this.reFetchObservableQueries=this.reFetchObservableQueries.bind(this),f&&"object"==typeof window&&(window.__APOLLO_CLIENT__=this),!ii&&f&&__DEV__&&(ii=!0,"undefined"!=typeof window&&window.document&&window.top===window.self&&!window.__APOLLO_DEVTOOLS_GLOBAL_HOOK__)){var S=window.navigator,k=S&&S.userAgent,x=void 0;"string"==typeof k&&(k.indexOf("Chrome/")>-1?x="https://chrome.google.com/webstore/detail/apollo-client-developer-t/jdkknkkbebbapilgoeccciglkfbmbnfm":k.indexOf("Firefox/")>-1&&(x="https://addons.mozilla.org/en-US/firefox/addon/apollo-developer-tools/")),x&&__DEV__&&Q.kG.log("Download the Apollo DevTools for a better development experience: "+x)}this.version=nb,this.localState=new r4({cache:a,client:this,resolvers:g,fragmentMatcher:y}),this.queryManager=new it({cache:this.cache,link:this.link,defaultOptions:this.defaultOptions,queryDeduplication:h,ssrMode:s,clientAwareness:{name:w,version:_},localState:this.localState,assumeImmutableResults:m,onBroadcast:f?function(){t.devToolsHookCb&&t.devToolsHookCb({action:{},state:{queries:t.queryManager.getQueryStore(),mutations:t.queryManager.mutationStore||{}},dataWithOptimisticResults:t.cache.extract(!0)})}:void 0})}return e.prototype.stop=function(){this.queryManager.stop()},e.prototype.watchQuery=function(e){return this.defaultOptions.watchQuery&&(e=(0,ir.J)(this.defaultOptions.watchQuery,e)),this.disableNetworkFetches&&("network-only"===e.fetchPolicy||"cache-and-network"===e.fetchPolicy)&&(e=(0,en.pi)((0,en.pi)({},e),{fetchPolicy:"cache-first"})),this.queryManager.watchQuery(e)},e.prototype.query=function(e){return this.defaultOptions.query&&(e=(0,ir.J)(this.defaultOptions.query,e)),__DEV__?(0,Q.kG)("cache-and-network"!==e.fetchPolicy,"The cache-and-network fetchPolicy does not work with client.query, because client.query can only return a single result. Please use client.watchQuery to receive multiple results from the cache and the network, or consider using a different fetchPolicy, such as cache-first or network-only."):(0,Q.kG)("cache-and-network"!==e.fetchPolicy,10),this.disableNetworkFetches&&"network-only"===e.fetchPolicy&&(e=(0,en.pi)((0,en.pi)({},e),{fetchPolicy:"cache-first"})),this.queryManager.query(e)},e.prototype.mutate=function(e){return this.defaultOptions.mutate&&(e=(0,ir.J)(this.defaultOptions.mutate,e)),this.queryManager.mutate(e)},e.prototype.subscribe=function(e){return this.queryManager.startGraphQLSubscription(e)},e.prototype.readQuery=function(e,t){return void 0===t&&(t=!1),this.cache.readQuery(e,t)},e.prototype.readFragment=function(e,t){return void 0===t&&(t=!1),this.cache.readFragment(e,t)},e.prototype.writeQuery=function(e){var t=this.cache.writeQuery(e);return!1!==e.broadcast&&this.queryManager.broadcastQueries(),t},e.prototype.writeFragment=function(e){var t=this.cache.writeFragment(e);return!1!==e.broadcast&&this.queryManager.broadcastQueries(),t},e.prototype.__actionHookForDevTools=function(e){this.devToolsHookCb=e},e.prototype.__requestRaw=function(e){return np(this.link,e)},e.prototype.resetStore=function(){var e=this;return Promise.resolve().then(function(){return e.queryManager.clearStore({discardWatches:!1})}).then(function(){return Promise.all(e.resetStoreCallbacks.map(function(e){return e()}))}).then(function(){return e.reFetchObservableQueries()})},e.prototype.clearStore=function(){var e=this;return Promise.resolve().then(function(){return e.queryManager.clearStore({discardWatches:!0})}).then(function(){return Promise.all(e.clearStoreCallbacks.map(function(e){return e()}))})},e.prototype.onResetStore=function(e){var t=this;return this.resetStoreCallbacks.push(e),function(){t.resetStoreCallbacks=t.resetStoreCallbacks.filter(function(t){return t!==e})}},e.prototype.onClearStore=function(e){var t=this;return this.clearStoreCallbacks.push(e),function(){t.clearStoreCallbacks=t.clearStoreCallbacks.filter(function(t){return t!==e})}},e.prototype.reFetchObservableQueries=function(e){return this.queryManager.reFetchObservableQueries(e)},e.prototype.refetchQueries=function(e){var t=this.queryManager.refetchQueries(e),n=[],r=[];t.forEach(function(e,t){n.push(t),r.push(e)});var i=Promise.all(r);return i.queries=n,i.results=r,i.catch(function(e){__DEV__&&Q.kG.debug("In client.refetchQueries, Promise.all promise rejected with error ".concat(e))}),i},e.prototype.getObservableQueries=function(e){return void 0===e&&(e="active"),this.queryManager.getObservableQueries(e)},e.prototype.extract=function(e){return this.cache.extract(e)},e.prototype.restore=function(e){return this.cache.restore(e)},e.prototype.addResolvers=function(e){this.localState.addResolvers(e)},e.prototype.setResolvers=function(e){this.localState.setResolvers(e)},e.prototype.getResolvers=function(){return this.localState.getResolvers()},e.prototype.setLocalStateFragmentMatcher=function(e){this.localState.setFragmentMatcher(e)},e.prototype.setLink=function(e){this.link=this.queryManager.link=e},e}(),io=function(){function e(){this.getFragmentDoc=rZ(eA)}return e.prototype.batch=function(e){var t,n=this,r="string"==typeof e.optimistic?e.optimistic:!1===e.optimistic?null:void 0;return this.performTransaction(function(){return t=e.update(n)},r),t},e.prototype.recordOptimisticTransaction=function(e,t){this.performTransaction(e,t)},e.prototype.transformDocument=function(e){return e},e.prototype.transformForLink=function(e){return e},e.prototype.identify=function(e){},e.prototype.gc=function(){return[]},e.prototype.modify=function(e){return!1},e.prototype.readQuery=function(e,t){return void 0===t&&(t=!!e.optimistic),this.read((0,en.pi)((0,en.pi)({},e),{rootId:e.id||"ROOT_QUERY",optimistic:t}))},e.prototype.readFragment=function(e,t){return void 0===t&&(t=!!e.optimistic),this.read((0,en.pi)((0,en.pi)({},e),{query:this.getFragmentDoc(e.fragment,e.fragmentName),rootId:e.id,optimistic:t}))},e.prototype.writeQuery=function(e){var t=e.id,n=e.data,r=(0,en._T)(e,["id","data"]);return this.write(Object.assign(r,{dataId:t||"ROOT_QUERY",result:n}))},e.prototype.writeFragment=function(e){var t=e.id,n=e.data,r=e.fragment,i=e.fragmentName,a=(0,en._T)(e,["id","data","fragment","fragmentName"]);return this.write(Object.assign(a,{query:this.getFragmentDoc(r,i),dataId:t,result:n}))},e.prototype.updateQuery=function(e,t){return this.batch({update:function(n){var r=n.readQuery(e),i=t(r);return null==i?r:(n.writeQuery((0,en.pi)((0,en.pi)({},e),{data:i})),i)}})},e.prototype.updateFragment=function(e,t){return this.batch({update:function(n){var r=n.readFragment(e),i=t(r);return null==i?r:(n.writeFragment((0,en.pi)((0,en.pi)({},e),{data:i})),i)}})},e}(),is=function(e){function t(n,r,i,a){var o,s=e.call(this,n)||this;if(s.message=n,s.path=r,s.query=i,s.variables=a,Array.isArray(s.path)){s.missing=s.message;for(var u=s.path.length-1;u>=0;--u)s.missing=((o={})[s.path[u]]=s.missing,o)}else s.missing=s.path;return s.__proto__=t.prototype,s}return(0,en.ZT)(t,e),t}(Error),iu=__webpack_require__(10542),ic=Object.prototype.hasOwnProperty;function il(e){return null==e}function id(e,t){var n=e.__typename,r=e.id,i=e._id;if("string"==typeof n&&(t&&(t.keyObject=il(r)?il(i)?void 0:{_id:i}:{id:r}),il(r)&&!il(i)&&(r=i),!il(r)))return"".concat(n,":").concat("number"==typeof r||"string"==typeof r?r:JSON.stringify(r))}var ih={dataIdFromObject:id,addTypename:!0,resultCaching:!0,canonizeResults:!1};function ip(e){return(0,n1.o)(ih,e)}function ib(e){var t=e.canonizeResults;return void 0===t?ih.canonizeResults:t}function im(e,t){return eD(t)?e.get(t.__ref,"__typename"):t&&t.__typename}var ig=/^[_a-z][_0-9a-z]*/i;function iv(e){var t=e.match(ig);return t?t[0]:e}function iy(e,t,n){return!!(0,eO.s)(t)&&((0,tP.k)(t)?t.every(function(t){return iy(e,t,n)}):e.selections.every(function(e){if(eQ(e)&&td(e,n)){var r=eX(e);return ic.call(t,r)&&(!e.selectionSet||iy(e.selectionSet,t[r],n))}return!0}))}function iw(e){return(0,eO.s)(e)&&!eD(e)&&!(0,tP.k)(e)}function i_(){return new tB}function iE(e,t){var n=eL(e4(e));return{fragmentMap:n,lookupFragment:function(e){var r=n[e];return!r&&t&&(r=t.lookup(e)),r||null}}}var iS=Object.create(null),ik=function(){return iS},ix=Object.create(null),iT=function(){function e(e,t){var n=this;this.policies=e,this.group=t,this.data=Object.create(null),this.rootIds=Object.create(null),this.refs=Object.create(null),this.getFieldValue=function(e,t){return(0,iu.J)(eD(e)?n.get(e.__ref,t):e&&e[t])},this.canRead=function(e){return eD(e)?n.has(e.__ref):"object"==typeof e},this.toReference=function(e,t){if("string"==typeof e)return eI(e);if(eD(e))return e;var r=n.policies.identify(e)[0];if(r){var i=eI(r);return t&&n.merge(r,e),i}}}return e.prototype.toObject=function(){return(0,en.pi)({},this.data)},e.prototype.has=function(e){return void 0!==this.lookup(e,!0)},e.prototype.get=function(e,t){if(this.group.depend(e,t),ic.call(this.data,e)){var n=this.data[e];if(n&&ic.call(n,t))return n[t]}return"__typename"===t&&ic.call(this.policies.rootTypenamesById,e)?this.policies.rootTypenamesById[e]:this instanceof iL?this.parent.get(e,t):void 0},e.prototype.lookup=function(e,t){return(t&&this.group.depend(e,"__exists"),ic.call(this.data,e))?this.data[e]:this instanceof iL?this.parent.lookup(e,t):this.policies.rootTypenamesById[e]?Object.create(null):void 0},e.prototype.merge=function(e,t){var n,r=this;eD(e)&&(e=e.__ref),eD(t)&&(t=t.__ref);var i="string"==typeof e?this.lookup(n=e):e,a="string"==typeof t?this.lookup(n=t):t;if(a){__DEV__?(0,Q.kG)("string"==typeof n,"store.merge expects a string ID"):(0,Q.kG)("string"==typeof n,1);var o=new tB(iI).merge(i,a);if(this.data[n]=o,o!==i&&(delete this.refs[n],this.group.caching)){var s=Object.create(null);i||(s.__exists=1),Object.keys(a).forEach(function(e){if(!i||i[e]!==o[e]){s[e]=1;var t=iv(e);t===e||r.policies.hasKeyArgs(o.__typename,t)||(s[t]=1),void 0!==o[e]||r instanceof iL||delete o[e]}}),s.__typename&&!(i&&i.__typename)&&this.policies.rootTypenamesById[n]===o.__typename&&delete s.__typename,Object.keys(s).forEach(function(e){return r.group.dirty(n,e)})}}},e.prototype.modify=function(e,t){var n=this,r=this.lookup(e);if(r){var i=Object.create(null),a=!1,o=!0,s={DELETE:iS,INVALIDATE:ix,isReference:eD,toReference:this.toReference,canRead:this.canRead,readField:function(t,r){return n.policies.readField("string"==typeof t?{fieldName:t,from:r||eI(e)}:t,{store:n})}};if(Object.keys(r).forEach(function(u){var c=iv(u),l=r[u];if(void 0!==l){var f="function"==typeof t?t:t[u]||t[c];if(f){var d=f===ik?iS:f((0,iu.J)(l),(0,en.pi)((0,en.pi)({},s),{fieldName:c,storeFieldName:u,storage:n.getStorage(e,u)}));d===ix?n.group.dirty(e,u):(d===iS&&(d=void 0),d!==l&&(i[u]=d,a=!0,l=d))}void 0!==l&&(o=!1)}}),a)return this.merge(e,i),o&&(this instanceof iL?this.data[e]=void 0:delete this.data[e],this.group.dirty(e,"__exists")),!0}return!1},e.prototype.delete=function(e,t,n){var r,i=this.lookup(e);if(i){var a=this.getFieldValue(i,"__typename"),o=t&&n?this.policies.getStoreFieldName({typename:a,fieldName:t,args:n}):t;return this.modify(e,o?((r={})[o]=ik,r):ik)}return!1},e.prototype.evict=function(e,t){var n=!1;return e.id&&(ic.call(this.data,e.id)&&(n=this.delete(e.id,e.fieldName,e.args)),this instanceof iL&&this!==t&&(n=this.parent.evict(e,t)||n),(e.fieldName||n)&&this.group.dirty(e.id,e.fieldName||"__exists")),n},e.prototype.clear=function(){this.replace(null)},e.prototype.extract=function(){var e=this,t=this.toObject(),n=[];return this.getRootIdSet().forEach(function(t){ic.call(e.policies.rootTypenamesById,t)||n.push(t)}),n.length&&(t.__META={extraRootIds:n.sort()}),t},e.prototype.replace=function(e){var t=this;if(Object.keys(this.data).forEach(function(n){e&&ic.call(e,n)||t.delete(n)}),e){var n=e.__META,r=(0,en._T)(e,["__META"]);Object.keys(r).forEach(function(e){t.merge(e,r[e])}),n&&n.extraRootIds.forEach(this.retain,this)}},e.prototype.retain=function(e){return this.rootIds[e]=(this.rootIds[e]||0)+1},e.prototype.release=function(e){if(this.rootIds[e]>0){var t=--this.rootIds[e];return t||delete this.rootIds[e],t}return 0},e.prototype.getRootIdSet=function(e){return void 0===e&&(e=new Set),Object.keys(this.rootIds).forEach(e.add,e),this instanceof iL?this.parent.getRootIdSet(e):Object.keys(this.policies.rootTypenamesById).forEach(e.add,e),e},e.prototype.gc=function(){var e=this,t=this.getRootIdSet(),n=this.toObject();t.forEach(function(r){ic.call(n,r)&&(Object.keys(e.findChildRefIds(r)).forEach(t.add,t),delete n[r])});var r=Object.keys(n);if(r.length){for(var i=this;i instanceof iL;)i=i.parent;r.forEach(function(e){return i.delete(e)})}return r},e.prototype.findChildRefIds=function(e){if(!ic.call(this.refs,e)){var t=this.refs[e]=Object.create(null),n=this.data[e];if(!n)return t;var r=new Set([n]);r.forEach(function(e){eD(e)&&(t[e.__ref]=!0),(0,eO.s)(e)&&Object.keys(e).forEach(function(t){var n=e[t];(0,eO.s)(n)&&r.add(n)})})}return this.refs[e]},e.prototype.makeCacheKey=function(){return this.group.keyMaker.lookupArray(arguments)},e}(),iM=function(){function e(e,t){void 0===t&&(t=null),this.caching=e,this.parent=t,this.d=null,this.resetCaching()}return e.prototype.resetCaching=function(){this.d=this.caching?rW():null,this.keyMaker=new n_(t_.mr)},e.prototype.depend=function(e,t){if(this.d){this.d(iO(e,t));var n=iv(t);n!==t&&this.d(iO(e,n)),this.parent&&this.parent.depend(e,t)}},e.prototype.dirty=function(e,t){this.d&&this.d.dirty(iO(e,t),"__exists"===t?"forget":"setDirty")},e}();function iO(e,t){return t+"#"+e}function iA(e,t){iD(e)&&e.group.depend(t,"__exists")}!function(e){var t=function(e){function t(t){var n=t.policies,r=t.resultCaching,i=void 0===r||r,a=t.seed,o=e.call(this,n,new iM(i))||this;return o.stump=new iC(o),o.storageTrie=new n_(t_.mr),a&&o.replace(a),o}return(0,en.ZT)(t,e),t.prototype.addLayer=function(e,t){return this.stump.addLayer(e,t)},t.prototype.removeLayer=function(){return this},t.prototype.getStorage=function(){return this.storageTrie.lookupArray(arguments)},t}(e);e.Root=t}(iT||(iT={}));var iL=function(e){function t(t,n,r,i){var a=e.call(this,n.policies,i)||this;return a.id=t,a.parent=n,a.replay=r,a.group=i,r(a),a}return(0,en.ZT)(t,e),t.prototype.addLayer=function(e,n){return new t(e,this,n,this.group)},t.prototype.removeLayer=function(e){var t=this,n=this.parent.removeLayer(e);return e===this.id?(this.group.caching&&Object.keys(this.data).forEach(function(e){var r=t.data[e],i=n.lookup(e);i?r?r!==i&&Object.keys(r).forEach(function(n){(0,nm.D)(r[n],i[n])||t.group.dirty(e,n)}):(t.group.dirty(e,"__exists"),Object.keys(i).forEach(function(n){t.group.dirty(e,n)})):t.delete(e)}),n):n===this.parent?this:n.addLayer(this.id,this.replay)},t.prototype.toObject=function(){return(0,en.pi)((0,en.pi)({},this.parent.toObject()),this.data)},t.prototype.findChildRefIds=function(t){var n=this.parent.findChildRefIds(t);return ic.call(this.data,t)?(0,en.pi)((0,en.pi)({},n),e.prototype.findChildRefIds.call(this,t)):n},t.prototype.getStorage=function(){for(var e=this.parent;e.parent;)e=e.parent;return e.getStorage.apply(e,arguments)},t}(iT),iC=function(e){function t(t){return e.call(this,"EntityStore.Stump",t,function(){},new iM(t.group.caching,t.group))||this}return(0,en.ZT)(t,e),t.prototype.removeLayer=function(){return this},t.prototype.merge=function(){return this.parent.merge.apply(this.parent,arguments)},t}(iL);function iI(e,t,n){var r=e[n],i=t[n];return(0,nm.D)(r,i)?r:i}function iD(e){return!!(e instanceof iT&&e.group.caching)}function iN(e){return[e.selectionSet,e.objectOrReference,e.context,e.context.canonizeResults,]}var iP=function(){function e(e){var t=this;this.knownResults=new(t_.mr?WeakMap:Map),this.config=(0,n1.o)(e,{addTypename:!1!==e.addTypename,canonizeResults:ib(e)}),this.canon=e.canon||new nk,this.executeSelectionSet=rZ(function(e){var n,r=e.context.canonizeResults,i=iN(e);i[3]=!r;var a=(n=t.executeSelectionSet).peek.apply(n,i);return a?r?(0,en.pi)((0,en.pi)({},a),{result:t.canon.admit(a.result)}):a:(iA(e.context.store,e.enclosingRef.__ref),t.execSelectionSetImpl(e))},{max:this.config.resultCacheMaxSize,keyArgs:iN,makeCacheKey:function(e,t,n,r){if(iD(n.store))return n.store.makeCacheKey(e,eD(t)?t.__ref:t,n.varString,r)}}),this.executeSubSelectedArray=rZ(function(e){return iA(e.context.store,e.enclosingRef.__ref),t.execSubSelectedArrayImpl(e)},{max:this.config.resultCacheMaxSize,makeCacheKey:function(e){var t=e.field,n=e.array,r=e.context;if(iD(r.store))return r.store.makeCacheKey(t,n,r.varString)}})}return e.prototype.resetCanon=function(){this.canon=new nk},e.prototype.diffQueryAgainstStore=function(e){var t,n=e.store,r=e.query,i=e.rootId,a=void 0===i?"ROOT_QUERY":i,o=e.variables,s=e.returnPartialData,u=void 0===s||s,c=e.canonizeResults,l=void 0===c?this.config.canonizeResults:c,f=this.config.cache.policies;o=(0,en.pi)((0,en.pi)({},e9(e6(r))),o);var d=eI(a),h=this.executeSelectionSet({selectionSet:e8(r).selectionSet,objectOrReference:d,enclosingRef:d,context:(0,en.pi)({store:n,query:r,policies:f,variables:o,varString:nx(o),canonizeResults:l},iE(r,this.config.fragments))});if(h.missing&&(t=[new is(iR(h.missing),h.missing,r,o)],!u))throw t[0];return{result:h.result,complete:!t,missing:t}},e.prototype.isFresh=function(e,t,n,r){if(iD(r.store)&&this.knownResults.get(e)===n){var i=this.executeSelectionSet.peek(n,t,r,this.canon.isKnown(e));if(i&&e===i.result)return!0}return!1},e.prototype.execSelectionSetImpl=function(e){var t,n=this,r=e.selectionSet,i=e.objectOrReference,a=e.enclosingRef,o=e.context;if(eD(i)&&!o.policies.rootTypenamesById[i.__ref]&&!o.store.has(i.__ref))return{result:this.canon.empty,missing:"Dangling reference to missing ".concat(i.__ref," object")};var s=o.variables,u=o.policies,c=o.store.getFieldValue(i,"__typename"),l=[],f=new tB;function d(e,n){var r;return e.missing&&(t=f.merge(t,((r={})[n]=e.missing,r))),e.result}this.config.addTypename&&"string"==typeof c&&!u.rootIdsByTypename[c]&&l.push({__typename:c});var h=new Set(r.selections);h.forEach(function(e){var r,p;if(td(e,s)){if(eQ(e)){var b=u.readField({fieldName:e.name.value,field:e,variables:o.variables,from:i},o),m=eX(e);void 0===b?nj.added(e)||(t=f.merge(t,((r={})[m]="Can't find field '".concat(e.name.value,"' on ").concat(eD(i)?i.__ref+" object":"object "+JSON.stringify(i,null,2)),r))):(0,tP.k)(b)?b=d(n.executeSubSelectedArray({field:e,array:b,enclosingRef:a,context:o}),m):e.selectionSet?null!=b&&(b=d(n.executeSelectionSet({selectionSet:e.selectionSet,objectOrReference:b,enclosingRef:eD(b)?b:a,context:o}),m)):o.canonizeResults&&(b=n.canon.pass(b)),void 0!==b&&l.push(((p={})[m]=b,p))}else{var g=eC(e,o.lookupFragment);if(!g&&e.kind===nL.h.FRAGMENT_SPREAD)throw __DEV__?new Q.ej("No fragment named ".concat(e.name.value)):new Q.ej(5);g&&u.fragmentMatches(g,c)&&g.selectionSet.selections.forEach(h.add,h)}}});var p={result:tF(l),missing:t},b=o.canonizeResults?this.canon.admit(p):(0,iu.J)(p);return b.result&&this.knownResults.set(b.result,r),b},e.prototype.execSubSelectedArrayImpl=function(e){var t,n=this,r=e.field,i=e.array,a=e.enclosingRef,o=e.context,s=new tB;function u(e,n){var r;return e.missing&&(t=s.merge(t,((r={})[n]=e.missing,r))),e.result}return r.selectionSet&&(i=i.filter(o.store.canRead)),i=i.map(function(e,t){return null===e?null:(0,tP.k)(e)?u(n.executeSubSelectedArray({field:r,array:e,enclosingRef:a,context:o}),t):r.selectionSet?u(n.executeSelectionSet({selectionSet:r.selectionSet,objectOrReference:e,enclosingRef:eD(e)?e:a,context:o}),t):(__DEV__&&ij(o.store,r,e),e)}),{result:o.canonizeResults?this.canon.admit(i):i,missing:t}},e}();function iR(e){try{JSON.stringify(e,function(e,t){if("string"==typeof t)throw t;return t})}catch(t){return t}}function ij(e,t,n){if(!t.selectionSet){var r=new Set([n]);r.forEach(function(n){(0,eO.s)(n)&&(__DEV__?(0,Q.kG)(!eD(n),"Missing selection set for object of type ".concat(im(e,n)," returned for query field ").concat(t.name.value)):(0,Q.kG)(!eD(n),6),Object.values(n).forEach(r.add,r))})}}function iF(e){var t=nG("stringifyForDisplay");return JSON.stringify(e,function(e,n){return void 0===n?t:n}).split(JSON.stringify(t)).join("")}var iY=Object.create(null);function iB(e){var t=JSON.stringify(e);return iY[t]||(iY[t]=Object.create(null))}function iU(e){var t=iB(e);return t.keyFieldsFn||(t.keyFieldsFn=function(t,n){var r=function(e,t){return n.readField(t,e)},i=n.keyObject=i$(e,function(e){var i=iW(n.storeObject,e,r);return void 0===i&&t!==n.storeObject&&ic.call(t,e[0])&&(i=iW(t,e,iG)),__DEV__?(0,Q.kG)(void 0!==i,"Missing field '".concat(e.join("."),"' while extracting keyFields from ").concat(JSON.stringify(t))):(0,Q.kG)(void 0!==i,2),i});return"".concat(n.typename,":").concat(JSON.stringify(i))})}function iH(e){var t=iB(e);return t.keyArgsFn||(t.keyArgsFn=function(t,n){var r=n.field,i=n.variables,a=n.fieldName,o=JSON.stringify(i$(e,function(e){var n=e[0],a=n.charAt(0);if("@"===a){if(r&&(0,tP.O)(r.directives)){var o=n.slice(1),s=r.directives.find(function(e){return e.name.value===o}),u=s&&eZ(s,i);return u&&iW(u,e.slice(1))}return}if("$"===a){var c=n.slice(1);if(i&&ic.call(i,c)){var l=e.slice(0);return l[0]=c,iW(i,l)}return}if(t)return iW(t,e)}));return(t||"{}"!==o)&&(a+=":"+o),a})}function i$(e,t){var n=new tB;return iz(e).reduce(function(e,r){var i,a=t(r);if(void 0!==a){for(var o=r.length-1;o>=0;--o)a=((i={})[r[o]]=a,i);e=n.merge(e,a)}return e},Object.create(null))}function iz(e){var t=iB(e);if(!t.paths){var n=t.paths=[],r=[];e.forEach(function(t,i){(0,tP.k)(t)?(iz(t).forEach(function(e){return n.push(r.concat(e))}),r.length=0):(r.push(t),(0,tP.k)(e[i+1])||(n.push(r.slice(0)),r.length=0))})}return t.paths}function iG(e,t){return e[t]}function iW(e,t,n){return n=n||iG,iK(t.reduce(function e(t,r){return(0,tP.k)(t)?t.map(function(t){return e(t,r)}):t&&n(t,r)},e))}function iK(e){return(0,eO.s)(e)?(0,tP.k)(e)?e.map(iK):i$(Object.keys(e).sort(),function(t){return iW(e,t)}):e}function iV(e){return void 0!==e.args?e.args:e.field?eZ(e.field,e.variables):null}eK.setStringify(nx);var iq=function(){},iZ=function(e,t){return t.fieldName},iX=function(e,t,n){return(0,n.mergeObjects)(e,t)},iJ=function(e,t){return t},iQ=function(){function e(e){this.config=e,this.typePolicies=Object.create(null),this.toBeAdded=Object.create(null),this.supertypeMap=new Map,this.fuzzySubtypes=new Map,this.rootIdsByTypename=Object.create(null),this.rootTypenamesById=Object.create(null),this.usingPossibleTypes=!1,this.config=(0,en.pi)({dataIdFromObject:id},e),this.cache=this.config.cache,this.setRootTypename("Query"),this.setRootTypename("Mutation"),this.setRootTypename("Subscription"),e.possibleTypes&&this.addPossibleTypes(e.possibleTypes),e.typePolicies&&this.addTypePolicies(e.typePolicies)}return e.prototype.identify=function(e,t){var n,r,i=this,a=t&&(t.typename||(null===(n=t.storeObject)||void 0===n?void 0:n.__typename))||e.__typename;if(a===this.rootTypenamesById.ROOT_QUERY)return["ROOT_QUERY"];for(var o=t&&t.storeObject||e,s=(0,en.pi)((0,en.pi)({},t),{typename:a,storeObject:o,readField:t&&t.readField||function(){var e=i0(arguments,o);return i.readField(e,{store:i.cache.data,variables:e.variables})}}),u=a&&this.getTypePolicy(a),c=u&&u.keyFn||this.config.dataIdFromObject;c;){var l=c((0,en.pi)((0,en.pi)({},e),o),s);if((0,tP.k)(l))c=iU(l);else{r=l;break}}return r=r?String(r):void 0,s.keyObject?[r,s.keyObject]:[r]},e.prototype.addTypePolicies=function(e){var t=this;Object.keys(e).forEach(function(n){var r=e[n],i=r.queryType,a=r.mutationType,o=r.subscriptionType,s=(0,en._T)(r,["queryType","mutationType","subscriptionType"]);i&&t.setRootTypename("Query",n),a&&t.setRootTypename("Mutation",n),o&&t.setRootTypename("Subscription",n),ic.call(t.toBeAdded,n)?t.toBeAdded[n].push(s):t.toBeAdded[n]=[s]})},e.prototype.updateTypePolicy=function(e,t){var n=this,r=this.getTypePolicy(e),i=t.keyFields,a=t.fields;function o(e,t){e.merge="function"==typeof t?t:!0===t?iX:!1===t?iJ:e.merge}o(r,t.merge),r.keyFn=!1===i?iq:(0,tP.k)(i)?iU(i):"function"==typeof i?i:r.keyFn,a&&Object.keys(a).forEach(function(t){var r=n.getFieldPolicy(e,t,!0),i=a[t];if("function"==typeof i)r.read=i;else{var s=i.keyArgs,u=i.read,c=i.merge;r.keyFn=!1===s?iZ:(0,tP.k)(s)?iH(s):"function"==typeof s?s:r.keyFn,"function"==typeof u&&(r.read=u),o(r,c)}r.read&&r.merge&&(r.keyFn=r.keyFn||iZ)})},e.prototype.setRootTypename=function(e,t){void 0===t&&(t=e);var n="ROOT_"+e.toUpperCase(),r=this.rootTypenamesById[n];t!==r&&(__DEV__?(0,Q.kG)(!r||r===e,"Cannot change root ".concat(e," __typename more than once")):(0,Q.kG)(!r||r===e,3),r&&delete this.rootIdsByTypename[r],this.rootIdsByTypename[t]=n,this.rootTypenamesById[n]=t)},e.prototype.addPossibleTypes=function(e){var t=this;this.usingPossibleTypes=!0,Object.keys(e).forEach(function(n){t.getSupertypeSet(n,!0),e[n].forEach(function(e){t.getSupertypeSet(e,!0).add(n);var r=e.match(ig);r&&r[0]===e||t.fuzzySubtypes.set(e,RegExp(e))})})},e.prototype.getTypePolicy=function(e){var t=this;if(!ic.call(this.typePolicies,e)){var n=this.typePolicies[e]=Object.create(null);n.fields=Object.create(null);var r=this.supertypeMap.get(e);r&&r.size&&r.forEach(function(e){var r=t.getTypePolicy(e),i=r.fields;Object.assign(n,(0,en._T)(r,["fields"])),Object.assign(n.fields,i)})}var i=this.toBeAdded[e];return i&&i.length&&i.splice(0).forEach(function(n){t.updateTypePolicy(e,n)}),this.typePolicies[e]},e.prototype.getFieldPolicy=function(e,t,n){if(e){var r=this.getTypePolicy(e).fields;return r[t]||n&&(r[t]=Object.create(null))}},e.prototype.getSupertypeSet=function(e,t){var n=this.supertypeMap.get(e);return!n&&t&&this.supertypeMap.set(e,n=new Set),n},e.prototype.fragmentMatches=function(e,t,n,r){var i=this;if(!e.typeCondition)return!0;if(!t)return!1;var a=e.typeCondition.name.value;if(t===a)return!0;if(this.usingPossibleTypes&&this.supertypeMap.has(a))for(var o=this.getSupertypeSet(t,!0),s=[o],u=function(e){var t=i.getSupertypeSet(e,!1);t&&t.size&&0>s.indexOf(t)&&s.push(t)},c=!!(n&&this.fuzzySubtypes.size),l=!1,f=0;f1?a:t}:(r=(0,en.pi)({},i),ic.call(r,"from")||(r.from=t)),__DEV__&&void 0===r.from&&__DEV__&&Q.kG.warn("Undefined 'from' passed to readField with arguments ".concat(iF(Array.from(e)))),void 0===r.variables&&(r.variables=n),r}function i2(e){return function(t,n){if((0,tP.k)(t)||(0,tP.k)(n))throw __DEV__?new Q.ej("Cannot automatically merge arrays"):new Q.ej(4);if((0,eO.s)(t)&&(0,eO.s)(n)){var r=e.getFieldValue(t,"__typename"),i=e.getFieldValue(n,"__typename");if(r&&i&&r!==i)return n;if(eD(t)&&iw(n))return e.merge(t.__ref,n),t;if(iw(t)&&eD(n))return e.merge(t,n.__ref),n;if(iw(t)&&iw(n))return(0,en.pi)((0,en.pi)({},t),n)}return n}}function i3(e,t,n){var r="".concat(t).concat(n),i=e.flavors.get(r);return i||e.flavors.set(r,i=e.clientOnly===t&&e.deferred===n?e:(0,en.pi)((0,en.pi)({},e),{clientOnly:t,deferred:n})),i}var i4=function(){function e(e,t,n){this.cache=e,this.reader=t,this.fragments=n}return e.prototype.writeToStore=function(e,t){var n=this,r=t.query,i=t.result,a=t.dataId,o=t.variables,s=t.overwrite,u=e2(r),c=i_();o=(0,en.pi)((0,en.pi)({},e9(u)),o);var l=(0,en.pi)((0,en.pi)({store:e,written:Object.create(null),merge:function(e,t){return c.merge(e,t)},variables:o,varString:nx(o)},iE(r,this.fragments)),{overwrite:!!s,incomingById:new Map,clientOnly:!1,deferred:!1,flavors:new Map}),f=this.processSelectionSet({result:i||Object.create(null),dataId:a,selectionSet:u.selectionSet,mergeTree:{map:new Map},context:l});if(!eD(f))throw __DEV__?new Q.ej("Could not identify object ".concat(JSON.stringify(i))):new Q.ej(7);return l.incomingById.forEach(function(t,r){var i=t.storeObject,a=t.mergeTree,o=t.fieldNodeSet,s=eI(r);if(a&&a.map.size){var u=n.applyMerges(a,s,i,l);if(eD(u))return;i=u}if(__DEV__&&!l.overwrite){var c=Object.create(null);o.forEach(function(e){e.selectionSet&&(c[e.name.value]=!0)});var f=function(e){return!0===c[iv(e)]},d=function(e){var t=a&&a.map.get(e);return Boolean(t&&t.info&&t.info.merge)};Object.keys(i).forEach(function(e){f(e)&&!d(e)&&at(s,i,e,l.store)})}e.merge(r,i)}),e.retain(f.__ref),f},e.prototype.processSelectionSet=function(e){var t=this,n=e.dataId,r=e.result,i=e.selectionSet,a=e.context,o=e.mergeTree,s=this.cache.policies,u=Object.create(null),c=n&&s.rootTypenamesById[n]||eJ(r,i,a.fragmentMap)||n&&a.store.get(n,"__typename");"string"==typeof c&&(u.__typename=c);var l=function(){var e=i0(arguments,u,a.variables);if(eD(e.from)){var t=a.incomingById.get(e.from.__ref);if(t){var n=s.readField((0,en.pi)((0,en.pi)({},e),{from:t.storeObject}),a);if(void 0!==n)return n}}return s.readField(e,a)},f=new Set;this.flattenFields(i,r,a,c).forEach(function(e,n){var i,a=r[eX(n)];if(f.add(n),void 0!==a){var d=s.getStoreFieldName({typename:c,fieldName:n.name.value,field:n,variables:e.variables}),h=i5(o,d),p=t.processFieldValue(a,n,n.selectionSet?i3(e,!1,!1):e,h),b=void 0;n.selectionSet&&(eD(p)||iw(p))&&(b=l("__typename",p));var m=s.getMergeFunction(c,n.name.value,b);m?h.info={field:n,typename:c,merge:m}:i7(o,d),u=e.merge(u,((i={})[d]=p,i))}else __DEV__&&!e.clientOnly&&!e.deferred&&!nj.added(n)&&!s.getReadFunction(c,n.name.value)&&__DEV__&&Q.kG.error("Missing field '".concat(eX(n),"' while writing result ").concat(JSON.stringify(r,null,2)).substring(0,1e3))});try{var d=s.identify(r,{typename:c,selectionSet:i,fragmentMap:a.fragmentMap,storeObject:u,readField:l}),h=d[0],p=d[1];n=n||h,p&&(u=a.merge(u,p))}catch(b){if(!n)throw b}if("string"==typeof n){var m=eI(n),g=a.written[n]||(a.written[n]=[]);if(g.indexOf(i)>=0||(g.push(i),this.reader&&this.reader.isFresh(r,m,i,a)))return m;var v=a.incomingById.get(n);return v?(v.storeObject=a.merge(v.storeObject,u),v.mergeTree=i8(v.mergeTree,o),f.forEach(function(e){return v.fieldNodeSet.add(e)})):a.incomingById.set(n,{storeObject:u,mergeTree:i9(o)?void 0:o,fieldNodeSet:f}),m}return u},e.prototype.processFieldValue=function(e,t,n,r){var i=this;return t.selectionSet&&null!==e?(0,tP.k)(e)?e.map(function(e,a){var o=i.processFieldValue(e,t,n,i5(r,a));return i7(r,a),o}):this.processSelectionSet({result:e,selectionSet:t.selectionSet,context:n,mergeTree:r}):__DEV__?nJ(e):e},e.prototype.flattenFields=function(e,t,n,r){void 0===r&&(r=eJ(t,e,n.fragmentMap));var i=new Map,a=this.cache.policies,o=new n_(!1);return function e(s,u){var c=o.lookup(s,u.clientOnly,u.deferred);c.visited||(c.visited=!0,s.selections.forEach(function(o){if(td(o,n.variables)){var s=u.clientOnly,c=u.deferred;if(!(s&&c)&&(0,tP.O)(o.directives)&&o.directives.forEach(function(e){var t=e.name.value;if("client"===t&&(s=!0),"defer"===t){var r=eZ(e,n.variables);r&&!1===r.if||(c=!0)}}),eQ(o)){var l=i.get(o);l&&(s=s&&l.clientOnly,c=c&&l.deferred),i.set(o,i3(n,s,c))}else{var f=eC(o,n.lookupFragment);if(!f&&o.kind===nL.h.FRAGMENT_SPREAD)throw __DEV__?new Q.ej("No fragment named ".concat(o.name.value)):new Q.ej(8);f&&a.fragmentMatches(f,r,t,n.variables)&&e(f.selectionSet,i3(n,s,c))}}}))}(e,n),i},e.prototype.applyMerges=function(e,t,n,r,i){var a=this;if(e.map.size&&!eD(n)){var o,s,u=!(0,tP.k)(n)&&(eD(t)||iw(t))?t:void 0,c=n;u&&!i&&(i=[eD(u)?u.__ref:u]);var l=function(e,t){return(0,tP.k)(e)?"number"==typeof t?e[t]:void 0:r.store.getFieldValue(e,String(t))};e.map.forEach(function(e,t){var n=l(u,t),o=l(c,t);if(void 0!==o){i&&i.push(t);var f=a.applyMerges(e,n,o,r,i);f!==o&&(s=s||new Map).set(t,f),i&&(0,Q.kG)(i.pop()===t)}}),s&&(n=(0,tP.k)(c)?c.slice(0):(0,en.pi)({},c),s.forEach(function(e,t){n[t]=e}))}return e.info?this.cache.policies.runMergeFunction(t,n,e.info,r,i&&(o=r.store).getStorage.apply(o,i)):n},e}(),i6=[];function i5(e,t){var n=e.map;return n.has(t)||n.set(t,i6.pop()||{map:new Map}),n.get(t)}function i8(e,t){if(e===t||!t||i9(t))return e;if(!e||i9(e))return t;var n=e.info&&t.info?(0,en.pi)((0,en.pi)({},e.info),t.info):e.info||t.info,r=e.map.size&&t.map.size,i=r?new Map:e.map.size?e.map:t.map,a={info:n,map:i};if(r){var o=new Set(t.map.keys());e.map.forEach(function(e,n){a.map.set(n,i8(e,t.map.get(n))),o.delete(n)}),o.forEach(function(n){a.map.set(n,i8(t.map.get(n),e.map.get(n)))})}return a}function i9(e){return!e||!(e.info||e.map.size)}function i7(e,t){var n=e.map,r=n.get(t);r&&i9(r)&&(i6.push(r),n.delete(t))}var ae=new Set;function at(e,t,n,r){var i=function(e){var t=r.getFieldValue(e,n);return"object"==typeof t&&t},a=i(e);if(a){var o=i(t);if(!(!o||eD(a)||(0,nm.D)(a,o)||Object.keys(a).every(function(e){return void 0!==r.getFieldValue(o,e)}))){var s=r.getFieldValue(e,"__typename")||r.getFieldValue(t,"__typename"),u=iv(n),c="".concat(s,".").concat(u);if(!ae.has(c)){ae.add(c);var l=[];(0,tP.k)(a)||(0,tP.k)(o)||[a,o].forEach(function(e){var t=r.getFieldValue(e,"__typename");"string"!=typeof t||l.includes(t)||l.push(t)}),__DEV__&&Q.kG.warn("Cache data may be lost when replacing the ".concat(u," field of a ").concat(s," object.\n\nThis could cause additional (usually avoidable) network requests to fetch data that were otherwise cached.\n\nTo address this problem (which is not a bug in Apollo Client), ").concat(l.length?"either ensure all objects of type "+l.join(" and ")+" have an ID or a custom merge function, or ":"","define a custom merge function for the ").concat(c," field, so InMemoryCache can safely merge these objects:\n\n existing: ").concat(JSON.stringify(a).slice(0,1e3),"\n incoming: ").concat(JSON.stringify(o).slice(0,1e3),"\n\nFor more information about these options, please refer to the documentation:\n\n * Ensuring entity objects have IDs: https://go.apollo.dev/c/generating-unique-identifiers\n * Defining custom merge functions: https://go.apollo.dev/c/merging-non-normalized-objects\n"))}}}}var an=function(e){function t(t){void 0===t&&(t={});var n=e.call(this)||this;return n.watches=new Set,n.typenameDocumentCache=new Map,n.makeVar=r2,n.txCount=0,n.config=ip(t),n.addTypename=!!n.config.addTypename,n.policies=new iQ({cache:n,dataIdFromObject:n.config.dataIdFromObject,possibleTypes:n.config.possibleTypes,typePolicies:n.config.typePolicies}),n.init(),n}return(0,en.ZT)(t,e),t.prototype.init=function(){var e=this.data=new iT.Root({policies:this.policies,resultCaching:this.config.resultCaching});this.optimisticData=e.stump,this.resetResultCache()},t.prototype.resetResultCache=function(e){var t=this,n=this.storeReader,r=this.config.fragments;this.storeWriter=new i4(this,this.storeReader=new iP({cache:this,addTypename:this.addTypename,resultCacheMaxSize:this.config.resultCacheMaxSize,canonizeResults:ib(this.config),canon:e?void 0:n&&n.canon,fragments:r}),r),this.maybeBroadcastWatch=rZ(function(e,n){return t.broadcastWatch(e,n)},{max:this.config.resultCacheMaxSize,makeCacheKey:function(e){var n=e.optimistic?t.optimisticData:t.data;if(iD(n)){var r=e.optimistic,i=e.id,a=e.variables;return n.makeCacheKey(e.query,e.callback,nx({optimistic:r,id:i,variables:a}))}}}),new Set([this.data.group,this.optimisticData.group,]).forEach(function(e){return e.resetCaching()})},t.prototype.restore=function(e){return this.init(),e&&this.data.replace(e),this},t.prototype.extract=function(e){return void 0===e&&(e=!1),(e?this.optimisticData:this.data).extract()},t.prototype.read=function(e){var t=e.returnPartialData,n=void 0!==t&&t;try{return this.storeReader.diffQueryAgainstStore((0,en.pi)((0,en.pi)({},e),{store:e.optimistic?this.optimisticData:this.data,config:this.config,returnPartialData:n})).result||null}catch(r){if(r instanceof is)return null;throw r}},t.prototype.write=function(e){try{return++this.txCount,this.storeWriter.writeToStore(this.data,e)}finally{--this.txCount||!1===e.broadcast||this.broadcastWatches()}},t.prototype.modify=function(e){if(ic.call(e,"id")&&!e.id)return!1;var t=e.optimistic?this.optimisticData:this.data;try{return++this.txCount,t.modify(e.id||"ROOT_QUERY",e.fields)}finally{--this.txCount||!1===e.broadcast||this.broadcastWatches()}},t.prototype.diff=function(e){return this.storeReader.diffQueryAgainstStore((0,en.pi)((0,en.pi)({},e),{store:e.optimistic?this.optimisticData:this.data,rootId:e.id||"ROOT_QUERY",config:this.config}))},t.prototype.watch=function(e){var t=this;return this.watches.size||r0(this),this.watches.add(e),e.immediate&&this.maybeBroadcastWatch(e),function(){t.watches.delete(e)&&!t.watches.size&&r1(t),t.maybeBroadcastWatch.forget(e)}},t.prototype.gc=function(e){nx.reset();var t=this.optimisticData.gc();return e&&!this.txCount&&(e.resetResultCache?this.resetResultCache(e.resetResultIdentities):e.resetResultIdentities&&this.storeReader.resetCanon()),t},t.prototype.retain=function(e,t){return(t?this.optimisticData:this.data).retain(e)},t.prototype.release=function(e,t){return(t?this.optimisticData:this.data).release(e)},t.prototype.identify=function(e){if(eD(e))return e.__ref;try{return this.policies.identify(e)[0]}catch(t){__DEV__&&Q.kG.warn(t)}},t.prototype.evict=function(e){if(!e.id){if(ic.call(e,"id"))return!1;e=(0,en.pi)((0,en.pi)({},e),{id:"ROOT_QUERY"})}try{return++this.txCount,this.optimisticData.evict(e,this.data)}finally{--this.txCount||!1===e.broadcast||this.broadcastWatches()}},t.prototype.reset=function(e){var t=this;return this.init(),nx.reset(),e&&e.discardWatches?(this.watches.forEach(function(e){return t.maybeBroadcastWatch.forget(e)}),this.watches.clear(),r1(this)):this.broadcastWatches(),Promise.resolve()},t.prototype.removeOptimistic=function(e){var t=this.optimisticData.removeLayer(e);t!==this.optimisticData&&(this.optimisticData=t,this.broadcastWatches())},t.prototype.batch=function(e){var t,n=this,r=e.update,i=e.optimistic,a=void 0===i||i,o=e.removeOptimistic,s=e.onWatchUpdated,u=function(e){var i=n,a=i.data,o=i.optimisticData;++n.txCount,e&&(n.data=n.optimisticData=e);try{return t=r(n)}finally{--n.txCount,n.data=a,n.optimisticData=o}},c=new Set;return s&&!this.txCount&&this.broadcastWatches((0,en.pi)((0,en.pi)({},e),{onWatchUpdated:function(e){return c.add(e),!1}})),"string"==typeof a?this.optimisticData=this.optimisticData.addLayer(a,u):!1===a?u(this.data):u(),"string"==typeof o&&(this.optimisticData=this.optimisticData.removeLayer(o)),s&&c.size?(this.broadcastWatches((0,en.pi)((0,en.pi)({},e),{onWatchUpdated:function(e,t){var n=s.call(this,e,t);return!1!==n&&c.delete(e),n}})),c.size&&c.forEach(function(e){return n.maybeBroadcastWatch.dirty(e)})):this.broadcastWatches(e),t},t.prototype.performTransaction=function(e,t){return this.batch({update:e,optimistic:t||null!==t})},t.prototype.transformDocument=function(e){if(this.addTypename){var t=this.typenameDocumentCache.get(e);return t||(t=nj(e),this.typenameDocumentCache.set(e,t),this.typenameDocumentCache.set(t,t)),t}return e},t.prototype.transformForLink=function(e){var t=this.config.fragments;return t?t.transform(e):e},t.prototype.broadcastWatches=function(e){var t=this;this.txCount||this.watches.forEach(function(n){return t.maybeBroadcastWatch(n,e)})},t.prototype.broadcastWatch=function(e,t){var n=e.lastDiff,r=this.diff(e);(!t||(e.optimistic&&"string"==typeof t.optimistic&&(r.fromOptimisticTransaction=!0),!t.onWatchUpdated||!1!==t.onWatchUpdated.call(this,e,r,n)))&&(n&&(0,nm.D)(n.result,r.result)||e.callback(e.lastDiff=r,n))},t}(io),ar={possibleTypes:{ApproveJobProposalSpecPayload:["ApproveJobProposalSpecSuccess","JobAlreadyExistsError","NotFoundError"],BridgePayload:["Bridge","NotFoundError"],CancelJobProposalSpecPayload:["CancelJobProposalSpecSuccess","NotFoundError"],ChainPayload:["Chain","NotFoundError"],CreateAPITokenPayload:["CreateAPITokenSuccess","InputErrors"],CreateBridgePayload:["CreateBridgeSuccess"],CreateCSAKeyPayload:["CSAKeyExistsError","CreateCSAKeySuccess"],CreateFeedsManagerChainConfigPayload:["CreateFeedsManagerChainConfigSuccess","InputErrors","NotFoundError"],CreateFeedsManagerPayload:["CreateFeedsManagerSuccess","DuplicateFeedsManagerError","InputErrors","NotFoundError","SingleFeedsManagerError"],CreateJobPayload:["CreateJobSuccess","InputErrors"],CreateOCR2KeyBundlePayload:["CreateOCR2KeyBundleSuccess"],CreateOCRKeyBundlePayload:["CreateOCRKeyBundleSuccess"],CreateP2PKeyPayload:["CreateP2PKeySuccess"],DeleteAPITokenPayload:["DeleteAPITokenSuccess","InputErrors"],DeleteBridgePayload:["DeleteBridgeConflictError","DeleteBridgeInvalidNameError","DeleteBridgeSuccess","NotFoundError"],DeleteCSAKeyPayload:["DeleteCSAKeySuccess","NotFoundError"],DeleteFeedsManagerChainConfigPayload:["DeleteFeedsManagerChainConfigSuccess","NotFoundError"],DeleteJobPayload:["DeleteJobSuccess","NotFoundError"],DeleteOCR2KeyBundlePayload:["DeleteOCR2KeyBundleSuccess","NotFoundError"],DeleteOCRKeyBundlePayload:["DeleteOCRKeyBundleSuccess","NotFoundError"],DeleteP2PKeyPayload:["DeleteP2PKeySuccess","NotFoundError"],DeleteVRFKeyPayload:["DeleteVRFKeySuccess","NotFoundError"],DismissJobErrorPayload:["DismissJobErrorSuccess","NotFoundError"],Error:["CSAKeyExistsError","DeleteBridgeConflictError","DeleteBridgeInvalidNameError","DuplicateFeedsManagerError","InputError","JobAlreadyExistsError","NotFoundError","RunJobCannotRunError","SingleFeedsManagerError"],EthTransactionPayload:["EthTransaction","NotFoundError"],FeaturesPayload:["Features"],FeedsManagerPayload:["FeedsManager","NotFoundError"],GetSQLLoggingPayload:["SQLLogging"],GlobalLogLevelPayload:["GlobalLogLevel"],JobPayload:["Job","NotFoundError"],JobProposalPayload:["JobProposal","NotFoundError"],JobRunPayload:["JobRun","NotFoundError"],JobSpec:["BlockHeaderFeederSpec","BlockhashStoreSpec","BootstrapSpec","CronSpec","DirectRequestSpec","FluxMonitorSpec","GatewaySpec","KeeperSpec","OCR2Spec","OCRSpec","StandardCapabilitiesSpec","VRFSpec","WebhookSpec","WorkflowSpec"],NodePayload:["Node","NotFoundError"],PaginatedPayload:["BridgesPayload","ChainsPayload","EthTransactionAttemptsPayload","EthTransactionsPayload","JobRunsPayload","JobsPayload","NodesPayload"],RejectJobProposalSpecPayload:["NotFoundError","RejectJobProposalSpecSuccess"],RunJobPayload:["NotFoundError","RunJobCannotRunError","RunJobSuccess"],SetGlobalLogLevelPayload:["InputErrors","SetGlobalLogLevelSuccess"],SetSQLLoggingPayload:["SetSQLLoggingSuccess"],SetServicesLogLevelsPayload:["InputErrors","SetServicesLogLevelsSuccess"],UpdateBridgePayload:["NotFoundError","UpdateBridgeSuccess"],UpdateFeedsManagerChainConfigPayload:["InputErrors","NotFoundError","UpdateFeedsManagerChainConfigSuccess"],UpdateFeedsManagerPayload:["InputErrors","NotFoundError","UpdateFeedsManagerSuccess"],UpdateJobProposalSpecDefinitionPayload:["NotFoundError","UpdateJobProposalSpecDefinitionSuccess"],UpdatePasswordPayload:["InputErrors","UpdatePasswordSuccess"],VRFKeyPayload:["NotFoundError","VRFKeySuccess"]}};let ai=ar;var aa=(r=void 0,location.origin),ao=new nh({uri:"".concat(aa,"/query"),credentials:"include"}),as=new ia({cache:new an({possibleTypes:ai.possibleTypes}),link:ao});if(a.Z.locale(o),u().defaultFormat="YYYY-MM-DD h:mm:ss A","undefined"!=typeof document){var au,ac,al=f().hydrate;ac=X,al(c.createElement(et,{client:as},c.createElement(d.zj,null,c.createElement(i.MuiThemeProvider,{theme:J.r},c.createElement(ac,null)))),document.getElementById("root"))}})()})(); \ No newline at end of file diff --git a/core/web/assets/main.6f07a88cfc748f57e21d.js.gz b/core/web/assets/main.84f90f8fc23465846aa7.js.gz similarity index 86% rename from core/web/assets/main.6f07a88cfc748f57e21d.js.gz rename to core/web/assets/main.84f90f8fc23465846aa7.js.gz index 1a83398229..7fcd5de913 100644 Binary files a/core/web/assets/main.6f07a88cfc748f57e21d.js.gz and b/core/web/assets/main.84f90f8fc23465846aa7.js.gz differ diff --git a/core/web/features_controller.go b/core/web/features_controller.go index 76f04fe262..66cd9e60c4 100644 --- a/core/web/features_controller.go +++ b/core/web/features_controller.go @@ -13,8 +13,9 @@ type FeaturesController struct { } const ( - FeatureKeyCSA string = "csa" - FeatureKeyFeedsManager string = "feeds_manager" + FeatureKeyCSA string = "csa" + FeatureKeyFeedsManager string = "feeds_manager" + FeatureKeyMultiFeedsManagers string = "multi_feeds_managers" ) // Index retrieves the features @@ -24,6 +25,7 @@ func (fc *FeaturesController) Index(c *gin.Context) { resources := []presenters.FeatureResource{ *presenters.NewFeatureResource(FeatureKeyCSA, fc.App.GetConfig().Feature().UICSAKeys()), *presenters.NewFeatureResource(FeatureKeyFeedsManager, fc.App.GetConfig().Feature().FeedsManager()), + *presenters.NewFeatureResource(FeatureKeyMultiFeedsManagers, fc.App.GetConfig().Feature().MultiFeedsManagers()), } jsonAPIResponse(c, resources, "features") diff --git a/core/web/features_controller_test.go b/core/web/features_controller_test.go index 727d7db547..02f75f14bc 100644 --- a/core/web/features_controller_test.go +++ b/core/web/features_controller_test.go @@ -19,6 +19,7 @@ func Test_FeaturesController_List(t *testing.T) { app := cltest.NewApplicationWithConfig(t, configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { csa := true c.Feature.UICSAKeys = &csa + c.Feature.MultiFeedsManagers = &csa })) require.NoError(t, app.Start(testutils.Context(t))) client := app.NewHTTPClient(nil) @@ -30,11 +31,14 @@ func Test_FeaturesController_List(t *testing.T) { resources := []presenters.FeatureResource{} err := web.ParseJSONAPIResponse(cltest.ParseResponseBody(t, resp), &resources) require.NoError(t, err) - require.Len(t, resources, 2) + require.Len(t, resources, 3) assert.Equal(t, "csa", resources[0].ID) assert.True(t, resources[0].Enabled) assert.Equal(t, "feeds_manager", resources[1].ID) assert.True(t, resources[1].Enabled) + + assert.Equal(t, "multi_feeds_managers", resources[2].ID) + assert.True(t, resources[2].Enabled) } diff --git a/core/web/health_controller.go b/core/web/health_controller.go index bd775671d7..ee08c39fcf 100644 --- a/core/web/health_controller.go +++ b/core/web/health_controller.go @@ -69,6 +69,8 @@ func (hc *HealthController) Readyz(c *gin.Context) { } func (hc *HealthController) Health(c *gin.Context) { + _, failing := c.GetQuery("failing") + status := http.StatusOK checker := hc.App.GetHealthChecker() @@ -89,6 +91,8 @@ func (hc *HealthController) Health(c *gin.Context) { if err != nil { status = HealthStatusFailing output = err.Error() + } else if failing { + continue // omit from returned data } checks = append(checks, presenters.Check{ diff --git a/core/web/health_controller_test.go b/core/web/health_controller_test.go index 21da1fb2e4..14367b1e4b 100644 --- a/core/web/health_controller_test.go +++ b/core/web/health_controller_test.go @@ -97,6 +97,12 @@ var ( bodyHTML string //go:embed testdata/body/health.txt bodyTXT string + //go:embed testdata/body/health-failing.json + bodyJSONFailing string + //go:embed testdata/body/health-failing.html + bodyHTMLFailing string + //go:embed testdata/body/health-failing.txt + bodyTXTFailing string ) func TestHealthController_Health_body(t *testing.T) { @@ -111,6 +117,12 @@ func TestHealthController_Health_body(t *testing.T) { {"html", "/health", map[string]string{"Accept": gin.MIMEHTML}, bodyHTML}, {"text", "/health", map[string]string{"Accept": gin.MIMEPlain}, bodyTXT}, {".txt", "/health.txt", nil, bodyTXT}, + + {"default-failing", "/health?failing", nil, bodyJSONFailing}, + {"json-failing", "/health?failing", map[string]string{"Accept": gin.MIMEJSON}, bodyJSONFailing}, + {"html-failing", "/health?failing", map[string]string{"Accept": gin.MIMEHTML}, bodyHTMLFailing}, + {"text-failing", "/health?failing", map[string]string{"Accept": gin.MIMEPlain}, bodyTXTFailing}, + {".txt-failing", "/health.txt?failing", nil, bodyTXTFailing}, } { t.Run(tc.name, func(t *testing.T) { app := cltest.NewApplicationWithKey(t) diff --git a/core/web/jobs_controller.go b/core/web/jobs_controller.go index 2e005d6d23..1cdc3e491c 100644 --- a/core/web/jobs_controller.go +++ b/core/web/jobs_controller.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "encoding/json" + "fmt" "net/http" "strings" "time" @@ -255,10 +256,11 @@ func (jc *JobsController) validateJobSpec(ctx context.Context, tomlString string case job.Stream: jb, err = streams.ValidatedStreamSpec(tomlString) case job.Workflow: - jb, err = workflows.ValidatedWorkflowJobSpec(tomlString) + jb, err = workflows.ValidatedWorkflowJobSpec(ctx, tomlString) case job.StandardCapabilities: jb, err = standardcapabilities.ValidatedStandardCapabilitiesSpec(tomlString) - + case job.CCIP: + return jb, http.StatusUnprocessableEntity, fmt.Errorf("CCIP jobs are not supported") default: return jb, http.StatusUnprocessableEntity, errors.Errorf("unknown job type: %s", jobType) } diff --git a/core/web/loop_registry_internal_test.go b/core/web/loop_registry_internal_test.go index 48cd75a5cf..a02fa20802 100644 --- a/core/web/loop_registry_internal_test.go +++ b/core/web/loop_registry_internal_test.go @@ -38,7 +38,7 @@ func TestLoopRegistryServer_CantWriteToResponse(t *testing.T) { l, o := logger.TestLoggerObserved(t, zap.ErrorLevel) s := &LoopRegistryServer{ exposedPromPort: 1, - registry: plugins.NewLoopRegistry(l, nil), + registry: plugins.NewLoopRegistry(l, nil, nil), logger: l.(logger.SugaredLogger), jsonMarshalFn: json.Marshal, } @@ -53,7 +53,7 @@ func TestLoopRegistryServer_CantMarshal(t *testing.T) { l, o := logger.TestLoggerObserved(t, zap.ErrorLevel) s := &LoopRegistryServer{ exposedPromPort: 1, - registry: plugins.NewLoopRegistry(l, nil), + registry: plugins.NewLoopRegistry(l, nil, nil), logger: l.(logger.SugaredLogger), jsonMarshalFn: func(any) ([]byte, error) { return []byte(""), errors.New("can't unmarshal") diff --git a/core/web/presenters/aptos_key.go b/core/web/presenters/aptos_key.go index 6460c325f9..8c0c09ed10 100644 --- a/core/web/presenters/aptos_key.go +++ b/core/web/presenters/aptos_key.go @@ -5,7 +5,8 @@ import "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/apt // AptosKeyResource represents a Aptos key JSONAPI resource. type AptosKeyResource struct { JAID - PubKey string `json:"publicKey"` + Account string `json:"account"` + PubKey string `json:"publicKey"` } // GetName implements the api2go EntityNamer interface @@ -15,8 +16,9 @@ func (AptosKeyResource) GetName() string { func NewAptosKeyResource(key aptoskey.Key) *AptosKeyResource { r := &AptosKeyResource{ - JAID: JAID{ID: key.ID()}, - PubKey: key.PublicKeyStr(), + JAID: JAID{ID: key.ID()}, + Account: key.Account(), + PubKey: key.PublicKeyStr(), } return r diff --git a/core/web/resolver/features.go b/core/web/resolver/features.go index 9ce39a773c..9d6c135a93 100644 --- a/core/web/resolver/features.go +++ b/core/web/resolver/features.go @@ -20,6 +20,11 @@ func (r *FeaturesResolver) FeedsManager() bool { return r.cfg.FeedsManager() } +// MultiFeedsManagers resolves to whether multiple feed managers support is enable. +func (r *FeaturesResolver) MultiFeedsManagers() bool { + return r.cfg.MultiFeedsManagers() +} + type FeaturesPayloadResolver struct { cfg config.Feature } diff --git a/core/web/resolver/features_test.go b/core/web/resolver/features_test.go index 76394f038b..4e8f9e8c26 100644 --- a/core/web/resolver/features_test.go +++ b/core/web/resolver/features_test.go @@ -15,6 +15,7 @@ func Test_ToFeatures(t *testing.T) { ... on Features { csa feedsManager + multiFeedsManagers } } }` @@ -29,6 +30,7 @@ func Test_ToFeatures(t *testing.T) { t, f := true, false c.Feature.UICSAKeys = &f c.Feature.FeedsManager = &t + c.Feature.MultiFeedsManagers = &f })) }, query: query, @@ -36,7 +38,8 @@ func Test_ToFeatures(t *testing.T) { { "features": { "csa": false, - "feedsManager": true + "feedsManager": true, + "multiFeedsManagers": false } }`, }, diff --git a/core/web/resolver/feeds_manager.go b/core/web/resolver/feeds_manager.go index 86705cf207..0711cb1354 100644 --- a/core/web/resolver/feeds_manager.go +++ b/core/web/resolver/feeds_manager.go @@ -145,6 +145,7 @@ func (r *CreateFeedsManagerPayloadResolver) ToCreateFeedsManagerSuccess() (*Crea return nil, false } +// TODO: delete once multiple feeds managers support is released func (r *CreateFeedsManagerPayloadResolver) ToSingleFeedsManagerError() (*SingleFeedsManagerErrorResolver, bool) { if r.err != nil && errors.Is(r.err, feeds.ErrSingleFeedsManager) { return NewSingleFeedsManagerError(r.err.Error()), true @@ -153,6 +154,14 @@ func (r *CreateFeedsManagerPayloadResolver) ToSingleFeedsManagerError() (*Single return nil, false } +func (r *CreateFeedsManagerPayloadResolver) ToDuplicateFeedsManagerError() (*DuplicateFeedsManagerErrorResolver, bool) { + if r.err != nil && errors.Is(r.err, feeds.ErrDuplicateFeedsManager) { + return NewDuplicateFeedsManagerError(r.err.Error()), true + } + + return nil, false +} + func (r *CreateFeedsManagerPayloadResolver) ToInputErrors() (*InputErrorsResolver, bool) { if r.inputErrs != nil { var errs []*InputErrorResolver @@ -182,6 +191,7 @@ func (r *CreateFeedsManagerSuccessResolver) FeedsManager() *FeedsManagerResolver } // SingleFeedsManagerErrorResolver - +// TODO: delete once multiple feeds managers support is released type SingleFeedsManagerErrorResolver struct { message string } @@ -200,6 +210,25 @@ func (r *SingleFeedsManagerErrorResolver) Code() ErrorCode { return ErrorCodeUnprocessable } +// DuplicateFeedsManagerErrorResolver - +type DuplicateFeedsManagerErrorResolver struct { + message string +} + +func NewDuplicateFeedsManagerError(message string) *DuplicateFeedsManagerErrorResolver { + return &DuplicateFeedsManagerErrorResolver{ + message: message, + } +} + +func (r *DuplicateFeedsManagerErrorResolver) Message() string { + return r.message +} + +func (r *DuplicateFeedsManagerErrorResolver) Code() ErrorCode { + return ErrorCodeUnprocessable +} + // -- UpdateFeedsManager Mutation -- // UpdateFeedsManagerPayloadResolver - diff --git a/core/web/resolver/feeds_manager_test.go b/core/web/resolver/feeds_manager_test.go index bafb50ab0d..4237c6a774 100644 --- a/core/web/resolver/feeds_manager_test.go +++ b/core/web/resolver/feeds_manager_test.go @@ -183,6 +183,10 @@ func Test_CreateFeedsManager(t *testing.T) { message code } + ... on DuplicateFeedsManagerError { + message + code + } ... on NotFoundError { message code @@ -264,6 +268,25 @@ func Test_CreateFeedsManager(t *testing.T) { } }`, }, + { + name: "register duplicate feeds manager error", + authenticated: true, + before: func(ctx context.Context, f *gqlTestFramework) { + f.App.On("GetFeedsService").Return(f.Mocks.feedsSvc) + f.Mocks.feedsSvc. + On("RegisterManager", mock.Anything, mock.IsType(feeds.RegisterManagerParams{})). + Return(int64(0), feeds.ErrDuplicateFeedsManager) + }, + query: mutation, + variables: variables, + result: ` + { + "createFeedsManager": { + "message": "manager was previously registered using the same public key", + "code": "UNPROCESSABLE" + } + }`, + }, { name: "not found", authenticated: true, diff --git a/core/web/resolver/mutation.go b/core/web/resolver/mutation.go index 4da5b1da65..f0f9cbcb0a 100644 --- a/core/web/resolver/mutation.go +++ b/core/web/resolver/mutation.go @@ -14,6 +14,7 @@ import ( "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/chainlink-common/pkg/assets" + "github.com/smartcontractkit/chainlink/v2/core/auth" "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/logger/audit" @@ -424,7 +425,7 @@ func (r *Resolver) CreateFeedsManager(ctx context.Context, args struct { id, err := feedsService.RegisterManager(ctx, params) if err != nil { - if errors.Is(err, feeds.ErrSingleFeedsManager) { + if errors.Is(err, feeds.ErrSingleFeedsManager) || errors.Is(err, feeds.ErrDuplicateFeedsManager) { return NewCreateFeedsManagerPayload(nil, err, nil), nil } return nil, err @@ -1061,11 +1062,13 @@ func (r *Resolver) CreateJob(ctx context.Context, args struct { case job.Gateway: jb, err = gateway.ValidatedGatewaySpec(args.Input.TOML) case job.Workflow: - jb, err = workflows.ValidatedWorkflowJobSpec(args.Input.TOML) + jb, err = workflows.ValidatedWorkflowJobSpec(ctx, args.Input.TOML) case job.StandardCapabilities: jb, err = standardcapabilities.ValidatedStandardCapabilitiesSpec(args.Input.TOML) case job.Stream: jb, err = streams.ValidatedStreamSpec(args.Input.TOML) + case job.CCIP: + return nil, errors.New("CCIP job type is not supported in this version") default: return NewCreateJobPayload(r.App, nil, map[string]string{ "Job Type": fmt.Sprintf("unknown job type: %s", jbt), diff --git a/core/web/resolver/testdata/config-empty-effective.toml b/core/web/resolver/testdata/config-empty-effective.toml index 16adff24a7..4cfe5e2086 100644 --- a/core/web/resolver/testdata/config-empty-effective.toml +++ b/core/web/resolver/testdata/config-empty-effective.toml @@ -6,7 +6,8 @@ ShutdownGracePeriod = '5s' FeedsManager = true LogPoller = false UICSAKeys = false -CCIP = false +CCIP = true +MultiFeedsManagers = false [Database] DefaultIdleInTxSessionTimeout = '1h0m0s' @@ -34,7 +35,7 @@ LeaseDuration = '10s' LeaseRefreshInterval = '1s' [TelemetryIngress] -UniConn = true +UniConn = false Logging = false BufferSize = 100 MaxBatchSize = 50 @@ -252,7 +253,36 @@ DeltaDial = '15s' DeltaReconcile = '1m0s' ListenAddresses = [] +[Capabilities.Dispatcher] +SupportedVersion = 1 +ReceiverBufferSize = 10000 + +[Capabilities.Dispatcher.RateLimit] +GlobalRPS = 800.0 +GlobalBurst = 1000 +PerSenderRPS = 10.0 +PerSenderBurst = 50 + [Capabilities.ExternalRegistry] Address = '' NetworkID = 'evm' ChainID = '1' + +[Capabilities.GatewayConnector] +ChainIDForNodeKey = '' +NodeAddress = '' +DonID = '' +WSHandshakeTimeoutMillis = 0 +AuthMinChallengeLen = 0 +AuthTimestampToleranceSec = 0 + +[[Capabilities.GatewayConnector.Gateways]] +ID = '' +URL = '' + +[Telemetry] +Enabled = false +CACertFile = '' +Endpoint = '' +InsecureConnection = false +TraceSampleRatio = 0.01 diff --git a/core/web/resolver/testdata/config-full.toml b/core/web/resolver/testdata/config-full.toml index b546042756..3179d13338 100644 --- a/core/web/resolver/testdata/config-full.toml +++ b/core/web/resolver/testdata/config-full.toml @@ -6,7 +6,8 @@ ShutdownGracePeriod = '10s' FeedsManager = true LogPoller = true UICSAKeys = true -CCIP = false +CCIP = true +MultiFeedsManagers = false [Database] DefaultIdleInTxSessionTimeout = '1m0s' @@ -34,7 +35,7 @@ LeaseDuration = '1m0s' LeaseRefreshInterval = '1s' [TelemetryIngress] -UniConn = true +UniConn = false Logging = true BufferSize = 1234 MaxBatchSize = 4321 @@ -262,11 +263,44 @@ DeltaDial = '1m0s' DeltaReconcile = '2s' ListenAddresses = ['foo', 'bar'] +[Capabilities.Dispatcher] +SupportedVersion = 1 +ReceiverBufferSize = 10000 + +[Capabilities.Dispatcher.RateLimit] +GlobalRPS = 800.0 +GlobalBurst = 1000 +PerSenderRPS = 10.0 +PerSenderBurst = 50 + [Capabilities.ExternalRegistry] Address = '' NetworkID = 'evm' ChainID = '1' +[Capabilities.GatewayConnector] +ChainIDForNodeKey = '11155111' +NodeAddress = '0x68902d681c28119f9b2531473a417088bf008e59' +DonID = 'example_don' +WSHandshakeTimeoutMillis = 100 +AuthMinChallengeLen = 10 +AuthTimestampToleranceSec = 10 + +[[Capabilities.GatewayConnector.Gateways]] +ID = 'example_gateway' +URL = 'wss://localhost:8081/node' + +[Telemetry] +Enabled = true +CACertFile = 'cert-file' +Endpoint = 'example.com/collector' +InsecureConnection = true +TraceSampleRatio = 0.01 + +[Telemetry.ResourceAttributes] +Baz = 'test' +Foo = 'bar' + [[EVM]] ChainID = '1' Enabled = false @@ -288,6 +322,7 @@ MinContractPayment = '9.223372036854775807 link' NonceAutoSync = true NoNewHeadsThreshold = '1m0s' OperatorFactoryAddress = '0xa5B85635Be42F21f94F28034B7DA440EeFF0F418' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 17 RPCBlockQueryDelay = 10 FinalizedBlockOffset = 0 @@ -367,6 +402,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [EVM.NodePool.Errors] NonceTooLow = '(: |^)nonce too low' @@ -383,6 +419,7 @@ L2Full = '(: |^)l2 full' TransactionAlreadyMined = '(: |^)transaction already mined' Fatal = '(: |^)fatal' ServiceUnavailable = '(: |^)service unavailable' +TooManyResults = '(: |^)too many results' [EVM.OCR] ContractConfirmations = 11 @@ -396,6 +433,9 @@ ObservationGracePeriod = '1s' [EVM.OCR2.Automation] GasLimit = 540 +[EVM.Workflow] +GasLimitDefault = 400000 + [[EVM.Nodes]] Name = 'foo' WSURL = 'wss://web.socket/test/foo' diff --git a/core/web/resolver/testdata/config-multi-chain-effective.toml b/core/web/resolver/testdata/config-multi-chain-effective.toml index ca9edca06f..7f29dc9704 100644 --- a/core/web/resolver/testdata/config-multi-chain-effective.toml +++ b/core/web/resolver/testdata/config-multi-chain-effective.toml @@ -6,7 +6,8 @@ ShutdownGracePeriod = '5s' FeedsManager = true LogPoller = false UICSAKeys = false -CCIP = false +CCIP = true +MultiFeedsManagers = false [Database] DefaultIdleInTxSessionTimeout = '1h0m0s' @@ -34,7 +35,7 @@ LeaseDuration = '10s' LeaseRefreshInterval = '1s' [TelemetryIngress] -UniConn = true +UniConn = false Logging = false BufferSize = 100 MaxBatchSize = 50 @@ -252,11 +253,40 @@ DeltaDial = '15s' DeltaReconcile = '1m0s' ListenAddresses = [] +[Capabilities.Dispatcher] +SupportedVersion = 1 +ReceiverBufferSize = 10000 + +[Capabilities.Dispatcher.RateLimit] +GlobalRPS = 800.0 +GlobalBurst = 1000 +PerSenderRPS = 10.0 +PerSenderBurst = 50 + [Capabilities.ExternalRegistry] Address = '' NetworkID = 'evm' ChainID = '1' +[Capabilities.GatewayConnector] +ChainIDForNodeKey = '' +NodeAddress = '' +DonID = '' +WSHandshakeTimeoutMillis = 0 +AuthMinChallengeLen = 0 +AuthTimestampToleranceSec = 0 + +[[Capabilities.GatewayConnector.Gateways]] +ID = '' +URL = '' + +[Telemetry] +Enabled = false +CACertFile = '' +Endpoint = '' +InsecureConnection = false +TraceSampleRatio = 0.01 + [[EVM]] ChainID = '1' AutoCreateKey = true @@ -275,6 +305,7 @@ MinContractPayment = '0.1 link' NonceAutoSync = true NoNewHeadsThreshold = '3m0s' OperatorFactoryAddress = '0x3E64Cd889482443324F91bFA9c84fE72A511f48A' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -339,6 +370,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [EVM.OCR] ContractConfirmations = 4 @@ -352,6 +384,9 @@ ObservationGracePeriod = '1s' [EVM.OCR2.Automation] GasLimit = 10500000 +[EVM.Workflow] +GasLimitDefault = 400000 + [[EVM.Nodes]] Name = 'primary' WSURL = 'wss://web.socket/mainnet' @@ -379,6 +414,7 @@ MinContractPayment = '0.1 link' NonceAutoSync = true NoNewHeadsThreshold = '3m0s' OperatorFactoryAddress = '0x8007e24251b1D2Fc518Eb843A701d9cD21fe0aA3' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -443,6 +479,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [EVM.OCR] ContractConfirmations = 4 @@ -456,6 +493,9 @@ ObservationGracePeriod = '1s' [EVM.OCR2.Automation] GasLimit = 5400000 +[EVM.Workflow] +GasLimitDefault = 400000 + [[EVM.Nodes]] Name = 'foo' WSURL = 'wss://web.socket/test/foo' @@ -477,6 +517,7 @@ MinIncomingConfirmations = 5 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '30s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 100 RPCBlockQueryDelay = 10 FinalizedBlockOffset = 0 @@ -541,6 +582,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [EVM.OCR] ContractConfirmations = 4 @@ -554,6 +596,9 @@ ObservationGracePeriod = '1s' [EVM.OCR2.Automation] GasLimit = 5400000 +[EVM.Workflow] +GasLimitDefault = 400000 + [[EVM.Nodes]] Name = 'bar' WSURL = 'wss://web.socket/test/bar' diff --git a/core/web/schema/type/features.graphql b/core/web/schema/type/features.graphql index 4254bdecb6..ff434ab4bd 100644 --- a/core/web/schema/type/features.graphql +++ b/core/web/schema/type/features.graphql @@ -1,6 +1,7 @@ type Features { csa: Boolean! feedsManager: Boolean! + multiFeedsManagers: Boolean! } # FeaturesPayload defines the response of fetching the features availability in the UI diff --git a/core/web/schema/type/feeds_manager.graphql b/core/web/schema/type/feeds_manager.graphql index 12e8732c8e..9da8f64e1c 100644 --- a/core/web/schema/type/feeds_manager.graphql +++ b/core/web/schema/type/feeds_manager.graphql @@ -77,6 +77,13 @@ type CreateFeedsManagerSuccess { feedsManager: FeedsManager! } +type DuplicateFeedsManagerError implements Error { + message: String! + code: ErrorCode! +} + +# DEPRECATED: No longer used since we now support multiple feeds manager. +# Keeping this to avoid breaking change. type SingleFeedsManagerError implements Error { message: String! code: ErrorCode! @@ -84,7 +91,8 @@ type SingleFeedsManagerError implements Error { # CreateFeedsManagerPayload defines the response when creating a feeds manager union CreateFeedsManagerPayload = CreateFeedsManagerSuccess - | SingleFeedsManagerError + | DuplicateFeedsManagerError + | SingleFeedsManagerError # // TODO: delete once multiple feeds managers support is released | NotFoundError | InputErrors diff --git a/core/web/testdata/body/health-failing.html b/core/web/testdata/body/health-failing.html new file mode 100644 index 0000000000..6b667a3ba6 --- /dev/null +++ b/core/web/testdata/body/health-failing.html @@ -0,0 +1,47 @@ + +
+ EVM +
+ 0 +
+ HeadTracker +
+ HeadListener +
Listener is not connected
+
+
+
+
diff --git a/core/web/testdata/body/health-failing.json b/core/web/testdata/body/health-failing.json new file mode 100644 index 0000000000..185b98b8da --- /dev/null +++ b/core/web/testdata/body/health-failing.json @@ -0,0 +1 @@ +{"data":[{"type":"checks","id":"EVM.0.HeadTracker.HeadListener","attributes":{"name":"EVM.0.HeadTracker.HeadListener","status":"failing","output":"Listener is not connected"}}]} diff --git a/core/web/testdata/body/health-failing.txt b/core/web/testdata/body/health-failing.txt new file mode 100644 index 0000000000..c6b948c3f9 --- /dev/null +++ b/core/web/testdata/body/health-failing.txt @@ -0,0 +1,2 @@ +! EVM.0.HeadTracker.HeadListener + Listener is not connected diff --git a/core/web/testdata/body/health.html b/core/web/testdata/body/health.html index 2a1b222753..d2b6db906b 100644 --- a/core/web/testdata/body/health.html +++ b/core/web/testdata/body/health.html @@ -63,12 +63,18 @@
Confirmer
+
+ Finalizer +
WrappedEvmEstimator
+
+ HeadReporter +
JobSpawner
@@ -96,9 +102,6 @@ BridgeCache -
- PromReporter -
TelemetryManager
diff --git a/core/web/testdata/body/health.json b/core/web/testdata/body/health.json index 10415c0abd..81ed7ff6d1 100644 --- a/core/web/testdata/body/health.json +++ b/core/web/testdata/body/health.json @@ -90,6 +90,15 @@ "output": "" } }, + { + "type": "checks", + "id": "EVM.0.Txm.Finalizer", + "attributes": { + "name": "EVM.0.Txm.Finalizer", + "status": "passing", + "output": "" + } + }, { "type": "checks", "id": "EVM.0.Txm.WrappedEvmEstimator", @@ -99,6 +108,15 @@ "output": "" } }, + { + "type": "checks", + "id": "HeadReporter", + "attributes": { + "name": "HeadReporter", + "status": "passing", + "output": "" + } + }, { "type": "checks", "id": "JobSpawner", @@ -162,15 +180,6 @@ "output": "" } }, - { - "type": "checks", - "id": "PromReporter", - "attributes": { - "name": "PromReporter", - "status": "passing", - "output": "" - } - }, { "type": "checks", "id": "TelemetryManager", diff --git a/core/web/testdata/body/health.txt b/core/web/testdata/body/health.txt index 09c8cff6c2..6b165d26d9 100644 --- a/core/web/testdata/body/health.txt +++ b/core/web/testdata/body/health.txt @@ -9,7 +9,9 @@ ok EVM.0.Txm ok EVM.0.Txm.BlockHistoryEstimator ok EVM.0.Txm.Broadcaster ok EVM.0.Txm.Confirmer +ok EVM.0.Txm.Finalizer ok EVM.0.Txm.WrappedEvmEstimator +ok HeadReporter ok JobSpawner ok Mailbox.Monitor ok Mercury.WSRPCPool @@ -17,5 +19,4 @@ ok Mercury.WSRPCPool.CacheSet ok PipelineORM ok PipelineRunner ok PipelineRunner.BridgeCache -ok PromReporter ok TelemetryManager diff --git a/core/web/testutils/mock_relayer.go b/core/web/testutils/mock_relayer.go index 4c2e5661af..4666f9da6a 100644 --- a/core/web/testutils/mock_relayer.go +++ b/core/web/testutils/mock_relayer.go @@ -8,6 +8,7 @@ import ( ) type MockRelayer struct { + Head commontypes.Head ChainStatus commontypes.ChainStatus NodeStatuses []commontypes.NodeStatus } @@ -40,6 +41,10 @@ func (m MockRelayer) NewContractReader(_ context.Context, _ []byte) (commontypes panic("not implemented") } +func (m MockRelayer) LatestHead(_ context.Context) (commontypes.Head, error) { + return m.Head, nil +} + func (m MockRelayer) GetChainStatus(ctx context.Context) (commontypes.ChainStatus, error) { return m.ChainStatus, nil } diff --git a/dashboard-lib/atlas-don/component.go b/dashboard-lib/atlas-don/component.go index 39218c7aea..beda6f48ec 100644 --- a/dashboard-lib/atlas-don/component.go +++ b/dashboard-lib/atlas-don/component.go @@ -2,6 +2,7 @@ package atlas_don import ( "fmt" + "github.com/K-Phoen/grabana/dashboard" "github.com/K-Phoen/grabana/row" "github.com/K-Phoen/grabana/stat" diff --git a/dashboard-lib/ccip-load-test-view/component.go b/dashboard-lib/ccip-load-test-view/component.go index 4fa2880d0e..700a0a5107 100644 --- a/dashboard-lib/ccip-load-test-view/component.go +++ b/dashboard-lib/ccip-load-test-view/component.go @@ -3,6 +3,7 @@ package ccip_load_test_view import ( "encoding/json" "fmt" + "github.com/K-Phoen/grabana/dashboard" "github.com/K-Phoen/grabana/logs" "github.com/K-Phoen/grabana/row" diff --git a/dashboard-lib/config.go b/dashboard-lib/config.go index 2e0b9cad99..386082dae0 100644 --- a/dashboard-lib/config.go +++ b/dashboard-lib/config.go @@ -2,9 +2,10 @@ package dashboard_lib import ( "encoding/base64" - "github.com/pkg/errors" "os" "strings" + + "github.com/pkg/errors" ) type EnvConfig struct { diff --git a/dashboard-lib/core-don/component.go b/dashboard-lib/core-don/component.go index 24173fb6cc..0589ab5cf8 100644 --- a/dashboard-lib/core-don/component.go +++ b/dashboard-lib/core-don/component.go @@ -2,6 +2,7 @@ package core_don import ( "fmt" + "github.com/K-Phoen/grabana/dashboard" "github.com/K-Phoen/grabana/gauge" "github.com/K-Phoen/grabana/row" diff --git a/dashboard-lib/core-ocrv2-ccip/component.go b/dashboard-lib/core-ocrv2-ccip/component.go index 837f693fcc..56b35587e1 100644 --- a/dashboard-lib/core-ocrv2-ccip/component.go +++ b/dashboard-lib/core-ocrv2-ccip/component.go @@ -2,6 +2,7 @@ package core_ocrv2_ccip import ( "fmt" + "github.com/K-Phoen/grabana/dashboard" "github.com/K-Phoen/grabana/row" "github.com/K-Phoen/grabana/target/prometheus" diff --git a/dashboard-lib/dashboard.go b/dashboard-lib/dashboard.go index 70892586bb..92c4691a5e 100644 --- a/dashboard-lib/dashboard.go +++ b/dashboard-lib/dashboard.go @@ -4,11 +4,12 @@ import ( "context" "encoding/json" "fmt" + "net/http" + "os" + "github.com/K-Phoen/grabana" "github.com/K-Phoen/grabana/dashboard" "github.com/pkg/errors" - "net/http" - "os" ) type Dashboard struct { diff --git a/dashboard-lib/go.mod b/dashboard-lib/go.mod index f115486231..0ab2b696db 100644 --- a/dashboard-lib/go.mod +++ b/dashboard-lib/go.mod @@ -17,11 +17,11 @@ require ( github.com/gosimple/slug v1.13.1 // indirect github.com/gosimple/unidecode v1.0.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/common v0.45.0 // indirect github.com/stretchr/testify v1.9.0 // indirect - golang.org/x/sys v0.16.0 // indirect + golang.org/x/sys v0.24.0 // indirect ) -exclude github.com/sourcegraph/sourcegraph/lib v0.0.0-20221216004406-749998a2ac74 +replace github.com/sourcegraph/sourcegraph/lib => github.com/sourcegraph/sourcegraph-public-snapshot/lib v0.0.0-20240822153003-c864f15af264 diff --git a/dashboard-lib/go.sum b/dashboard-lib/go.sum index 7eb74088f1..fd6985df0c 100644 --- a/dashboard-lib/go.sum +++ b/dashboard-lib/go.sum @@ -15,8 +15,9 @@ github.com/grafana/grafana-foundation-sdk/go v0.0.0-20240314112857-a7c9c6d0044c/ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= @@ -31,7 +32,7 @@ github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/docs/CONFIG.md b/docs/CONFIG.md index 196da084ab..059a0e3002 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -51,7 +51,8 @@ ShutdownGracePeriod is the maximum time allowed to shut down gracefully. If exce FeedsManager = true # Default LogPoller = false # Default UICSAKeys = false # Default -CCIP = false # Default +CCIP = true # Default +MultiFeedsManagers = false # Default ``` @@ -75,10 +76,16 @@ UICSAKeys enables CSA Keys in the UI. ### CCIP ```toml -CCIP = false # Default +CCIP = true # Default ``` CCIP enables the CCIP service. +### MultiFeedsManagers +```toml +MultiFeedsManagers = false # Default +``` +MultiFeedsManagers enables support for multiple feeds manager connections. + ## Database ```toml [Database] @@ -249,7 +256,7 @@ LeaseRefreshInterval determines how often to refresh the lease lock. Also contro ## TelemetryIngress ```toml [TelemetryIngress] -UniConn = true # Default +UniConn = false # Default Logging = false # Default BufferSize = 100 # Default MaxBatchSize = 50 # Default @@ -261,7 +268,7 @@ UseBatchSend = true # Default ### UniConn ```toml -UniConn = true # Default +UniConn = false # Default ``` UniConn toggles which ws connection style is used. @@ -1233,6 +1240,60 @@ ChainID = '1' # Default ``` ChainID identifies the target chain id where the remote registry is located. +## Capabilities.Dispatcher +```toml +[Capabilities.Dispatcher] +SupportedVersion = 1 # Default +ReceiverBufferSize = 10000 # Default +``` + + +### SupportedVersion +```toml +SupportedVersion = 1 # Default +``` +SupportedVersion is the version of the version of message schema. + +### ReceiverBufferSize +```toml +ReceiverBufferSize = 10000 # Default +``` +ReceiverBufferSize is the size of the buffer for incoming messages. + +## Capabilities.Dispatcher.RateLimit +```toml +[Capabilities.Dispatcher.RateLimit] +GlobalRPS = 800 # Default +GlobalBurst = 1000 # Default +PerSenderRPS = 10 # Default +PerSenderBurst = 50 # Default +``` + + +### GlobalRPS +```toml +GlobalRPS = 800 # Default +``` +GlobalRPS is the global rate limit for the dispatcher. + +### GlobalBurst +```toml +GlobalBurst = 1000 # Default +``` +GlobalBurst is the global burst limit for the dispatcher. + +### PerSenderRPS +```toml +PerSenderRPS = 10 # Default +``` +PerSenderRPS is the per-sender rate limit for the dispatcher. + +### PerSenderBurst +```toml +PerSenderBurst = 50 # Default +``` +PerSenderBurst is the per-sender burst limit for the dispatcher. + ## Capabilities.Peering ```toml [Capabilities.Peering] @@ -1332,6 +1393,74 @@ ListenAddresses = ['1.2.3.4:9999', '[a52d:0:a88:1274::abcd]:1337'] # Example ListenAddresses is the addresses the peer will listen to on the network in `host:port` form as accepted by `net.Listen()`, but the host and port must be fully specified and cannot be empty. You can specify `0.0.0.0` (IPv4) or `::` (IPv6) to listen on all interfaces, but that is not recommended. +## Capabilities.GatewayConnector +```toml +[Capabilities.GatewayConnector] +ChainIDForNodeKey = '11155111' # Example +NodeAddress = '0x68902d681c28119f9b2531473a417088bf008e59' # Example +DonID = 'example_don' # Example +WSHandshakeTimeoutMillis = 1000 # Example +AuthMinChallengeLen = 10 # Example +AuthTimestampToleranceSec = 10 # Example +``` + + +### ChainIDForNodeKey +```toml +ChainIDForNodeKey = '11155111' # Example +``` +ChainIDForNodeKey is the ChainID of the network associated with a private key to be used for authentication with Gateway nodes + +### NodeAddress +```toml +NodeAddress = '0x68902d681c28119f9b2531473a417088bf008e59' # Example +``` +NodeAddress is the address of the desired private key to be used for authentication with Gateway nodes + +### DonID +```toml +DonID = 'example_don' # Example +``` +DonID is the Id of the Don + +### WSHandshakeTimeoutMillis +```toml +WSHandshakeTimeoutMillis = 1000 # Example +``` +WSHandshakeTimeoutMillis is Websocket handshake timeout + +### AuthMinChallengeLen +```toml +AuthMinChallengeLen = 10 # Example +``` +AuthMinChallengeLen is the minimum number of bytes in authentication challenge payload + +### AuthTimestampToleranceSec +```toml +AuthTimestampToleranceSec = 10 # Example +``` +AuthTimestampToleranceSec is Authentication timestamp tolerance + +## Capabilities.GatewayConnector.Gateways +```toml +[[Capabilities.GatewayConnector.Gateways]] +ID = 'example_gateway' # Example +URL = 'wss://localhost:8081/node' # Example +``` + + +### ID +```toml +ID = 'example_gateway' # Example +``` +ID of the Gateway + +### URL +```toml +URL = 'wss://localhost:8081/node' # Example +``` +URL of the Gateway + ## Keeper ```toml [Keeper] @@ -1768,6 +1897,64 @@ TransmitTimeout controls how long the transmitter will wait for a response when sending a message to the mercury server, before aborting and considering the transmission to be failed. +## Telemetry +```toml +[Telemetry] +Enabled = false # Default +Endpoint = 'example.com/collector' # Example +CACertFile = 'cert-file' # Example +InsecureConnection = false # Default +TraceSampleRatio = 0.01 # Default +``` +Telemetry holds OTEL settings. +This data includes open telemetry metrics, traces, & logs. +It does not currently include prometheus metrics or standard out logs, but may in the future. + +### Enabled +```toml +Enabled = false # Default +``` +Enabled turns telemetry collection on or off. + +### Endpoint +```toml +Endpoint = 'example.com/collector' # Example +``` +Endpoint of the OTEL Collector. + +### CACertFile +```toml +CACertFile = 'cert-file' # Example +``` +CACertFile is the file path of the TLS certificate used for secure communication with the OTEL Collector. +Required unless InescureConnection is true. + +### InsecureConnection +```toml +InsecureConnection = false # Default +``` +InsecureConnection bypasses the TLS CACertFile requirement and uses an insecure connection instead. +Only available in dev mode. + +### TraceSampleRatio +```toml +TraceSampleRatio = 0.01 # Default +``` +TraceSampleRatio is the rate at which to sample traces. Must be between 0 and 1. + +## Telemetry.ResourceAttributes +```toml +[Telemetry.ResourceAttributes] +foo = "bar" # Example +``` +ResourceAttributes are global metadata to include with all telemetry. + +### foo +```toml +foo = "bar" # Example +``` +foo is an example resource attribute + ## EVM EVM defaults depend on ChainID: @@ -1790,6 +1977,7 @@ MinContractPayment = '0.1 link' NonceAutoSync = true NoNewHeadsThreshold = '3m0s' OperatorFactoryAddress = '0x3E64Cd889482443324F91bFA9c84fE72A511f48A' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -1854,6 +2042,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -1866,6 +2055,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 10500000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -1888,6 +2080,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.1 link' NonceAutoSync = true NoNewHeadsThreshold = '3m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -1952,6 +2145,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -1964,6 +2158,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -1986,6 +2183,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.1 link' NonceAutoSync = true NoNewHeadsThreshold = '3m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -2050,6 +2248,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -2062,6 +2261,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -2084,6 +2286,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.1 link' NonceAutoSync = true NoNewHeadsThreshold = '3m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -2148,6 +2351,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -2160,6 +2364,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -2183,6 +2390,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '40s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -2247,6 +2455,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -2259,6 +2468,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 6500000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -2281,6 +2493,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.001 link' NonceAutoSync = true NoNewHeadsThreshold = '3m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -2345,6 +2558,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -2357,6 +2571,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -2379,6 +2596,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.001 link' NonceAutoSync = true NoNewHeadsThreshold = '3m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -2443,6 +2661,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -2455,6 +2674,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -2478,6 +2700,7 @@ MinContractPayment = '0.1 link' NonceAutoSync = true NoNewHeadsThreshold = '3m0s' OperatorFactoryAddress = '0x8007e24251b1D2Fc518Eb843A701d9cD21fe0aA3' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -2542,6 +2765,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -2554,6 +2778,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -2576,6 +2803,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '30s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 2 FinalizedBlockOffset = 0 @@ -2599,7 +2827,7 @@ Enabled = true Mode = 'BlockHistory' PriceDefault = '5 gwei' PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' -PriceMin = '1 gwei' +PriceMin = '3 gwei' LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' @@ -2640,6 +2868,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -2652,6 +2881,9 @@ ObservationGracePeriod = '500ms' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -2673,6 +2905,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '3m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -2737,6 +2970,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -2749,6 +2983,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -2770,6 +3007,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '3m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -2834,6 +3072,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -2846,6 +3085,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -2867,6 +3109,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '3m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -2931,6 +3174,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -2943,6 +3187,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -2965,6 +3212,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '30s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 2 FinalizedBlockOffset = 0 @@ -3029,6 +3277,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -3041,6 +3290,9 @@ ObservationGracePeriod = '500ms' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -3064,6 +3316,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '3m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -3128,6 +3381,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -3140,6 +3394,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -3162,6 +3419,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '30s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 2 FinalizedBlockOffset = 0 @@ -3226,6 +3484,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -3238,6 +3497,9 @@ ObservationGracePeriod = '500ms' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -3260,6 +3522,7 @@ MinIncomingConfirmations = 5 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '30s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 100 RPCBlockQueryDelay = 10 FinalizedBlockOffset = 0 @@ -3324,6 +3587,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -3336,6 +3600,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -3358,6 +3625,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '12m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 100 RPCBlockQueryDelay = 15 FinalizedBlockOffset = 0 @@ -3422,6 +3690,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -3434,6 +3703,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -3456,6 +3728,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '6m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 100 RPCBlockQueryDelay = 15 FinalizedBlockOffset = 0 @@ -3520,6 +3793,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -3532,6 +3806,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -3554,6 +3831,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '30s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 2 FinalizedBlockOffset = 0 @@ -3618,6 +3896,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -3630,6 +3909,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 3800000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -3652,6 +3934,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '40s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -3716,6 +3999,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -3728,6 +4012,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -3750,6 +4037,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '1m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -3814,6 +4102,213 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' + +[OCR] +ContractConfirmations = 4 +ContractTransmitterTransmitTimeout = '10s' +DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' +ObservationGracePeriod = '1s' + +[OCR2] +[OCR2.Automation] +GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 +``` + +

+ +
Hedera Mainnet (295)

+ +```toml +AutoCreateKey = true +BlockBackfillDepth = 10 +BlockBackfillSkip = false +ChainType = 'hedera' +FinalityDepth = 10 +FinalityTagEnabled = false +LogBackfillBatchSize = 1000 +LogPollInterval = '10s' +LogKeepBlocksDepth = 100000 +LogPrunePageSize = 10000 +BackupLogPollerBlockDelay = 100 +MinIncomingConfirmations = 1 +MinContractPayment = '0.00001 link' +NonceAutoSync = true +NoNewHeadsThreshold = '3m0s' +LogBroadcasterEnabled = true +RPCDefaultBatchSize = 250 +RPCBlockQueryDelay = 1 +FinalizedBlockOffset = 0 +NoNewFinalizedHeadsThreshold = '0s' + +[Transactions] +ForwardersEnabled = false +MaxInFlight = 16 +MaxQueued = 250 +ReaperInterval = '1h0m0s' +ReaperThreshold = '168h0m0s' +ResendAfterThreshold = '2m0s' + +[Transactions.AutoPurge] +Enabled = false + +[BalanceMonitor] +Enabled = true + +[GasEstimator] +Mode = 'SuggestedPrice' +PriceDefault = '20 gwei' +PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' +PriceMin = '1 gwei' +LimitDefault = 8000000 +LimitMax = 8000000 +LimitMultiplier = '1' +LimitTransfer = 21000 +EstimateLimit = false +BumpMin = '10 gwei' +BumpPercent = 20 +BumpThreshold = 0 +EIP1559DynamicFees = false +FeeCapDefault = '100 gwei' +TipCapDefault = '1 wei' +TipCapMin = '1 wei' + +[GasEstimator.BlockHistory] +BatchSize = 25 +BlockHistorySize = 8 +CheckInclusionBlocks = 12 +CheckInclusionPercentile = 90 +TransactionPercentile = 60 + +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + +[HeadTracker] +HistoryDepth = 100 +MaxBufferSize = 3 +SamplingInterval = '1s' +MaxAllowedFinalityDepth = 10000 +FinalityTagBypass = true + +[NodePool] +PollFailureThreshold = 5 +PollInterval = '10s' +SelectionMode = 'HighestHead' +SyncThreshold = 10 +LeaseDuration = '0s' +NodeIsSyncingEnabled = false +FinalizedBlockPollInterval = '5s' +EnforceRepeatableRead = false +DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' + +[OCR] +ContractConfirmations = 4 +ContractTransmitterTransmitTimeout = '10s' +DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' +ObservationGracePeriod = '1s' + +[OCR2] +[OCR2.Automation] +GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 +``` + +

+ +
Hedera Testnet (296)

+ +```toml +AutoCreateKey = true +BlockBackfillDepth = 10 +BlockBackfillSkip = false +ChainType = 'hedera' +FinalityDepth = 10 +FinalityTagEnabled = false +LogBackfillBatchSize = 1000 +LogPollInterval = '10s' +LogKeepBlocksDepth = 100000 +LogPrunePageSize = 10000 +BackupLogPollerBlockDelay = 100 +MinIncomingConfirmations = 1 +MinContractPayment = '0.00001 link' +NonceAutoSync = true +NoNewHeadsThreshold = '3m0s' +LogBroadcasterEnabled = true +RPCDefaultBatchSize = 250 +RPCBlockQueryDelay = 1 +FinalizedBlockOffset = 0 +NoNewFinalizedHeadsThreshold = '0s' + +[Transactions] +ForwardersEnabled = false +MaxInFlight = 16 +MaxQueued = 250 +ReaperInterval = '1h0m0s' +ReaperThreshold = '168h0m0s' +ResendAfterThreshold = '2m0s' + +[Transactions.AutoPurge] +Enabled = false + +[BalanceMonitor] +Enabled = true + +[GasEstimator] +Mode = 'SuggestedPrice' +PriceDefault = '20 gwei' +PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' +PriceMin = '1 gwei' +LimitDefault = 8000000 +LimitMax = 8000000 +LimitMultiplier = '1' +LimitTransfer = 21000 +EstimateLimit = false +BumpMin = '10 gwei' +BumpPercent = 20 +BumpThreshold = 0 +EIP1559DynamicFees = false +FeeCapDefault = '100 gwei' +TipCapDefault = '1 wei' +TipCapMin = '1 wei' + +[GasEstimator.BlockHistory] +BatchSize = 25 +BlockHistorySize = 8 +CheckInclusionBlocks = 12 +CheckInclusionPercentile = 90 +TransactionPercentile = 60 + +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + +[HeadTracker] +HistoryDepth = 100 +MaxBufferSize = 3 +SamplingInterval = '1s' +MaxAllowedFinalityDepth = 10000 +FinalityTagBypass = true + +[NodePool] +PollFailureThreshold = 5 +PollInterval = '10s' +SelectionMode = 'HighestHead' +SyncThreshold = 10 +LeaseDuration = '0s' +NodeIsSyncingEnabled = false +FinalizedBlockPollInterval = '5s' +EnforceRepeatableRead = false +DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -3826,6 +4321,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -3848,6 +4346,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '1m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -3912,6 +4411,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -3924,6 +4424,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -3946,6 +4449,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '1m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -4010,6 +4514,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -4022,6 +4527,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -4045,6 +4553,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '40s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -4109,6 +4618,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -4121,6 +4631,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 6500000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -4143,6 +4656,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -4207,6 +4721,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -4219,6 +4734,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -4241,6 +4759,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '3m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -4305,6 +4824,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -4317,6 +4837,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -4339,6 +4862,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '3m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -4404,6 +4928,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -4416,6 +4941,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -4437,6 +4965,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '30s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -4501,6 +5030,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -4513,6 +5043,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -4535,6 +5068,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -4599,6 +5133,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -4611,6 +5146,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -4633,6 +5171,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '6m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 100 RPCBlockQueryDelay = 15 FinalizedBlockOffset = 0 @@ -4697,6 +5236,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -4709,6 +5249,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -4731,6 +5274,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '30s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -4795,6 +5339,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -4807,6 +5352,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -4829,6 +5377,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '30s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -4893,6 +5442,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -4905,6 +5455,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -4926,6 +5479,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '100' NonceAutoSync = true NoNewHeadsThreshold = '0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -4990,6 +5544,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -5002,6 +5557,113 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 +``` + +

+ +
Soneium Sepolia (1946)

+ +```toml +AutoCreateKey = true +BlockBackfillDepth = 10 +BlockBackfillSkip = false +ChainType = 'optimismBedrock' +FinalityDepth = 200 +FinalityTagEnabled = true +LinkContractAddress = '0x7ea13478Ea3961A0e8b538cb05a9DF0477c79Cd2' +LogBackfillBatchSize = 1000 +LogPollInterval = '2s' +LogKeepBlocksDepth = 100000 +LogPrunePageSize = 10000 +BackupLogPollerBlockDelay = 100 +MinIncomingConfirmations = 1 +MinContractPayment = '0.00001 link' +NonceAutoSync = true +NoNewHeadsThreshold = '40s' +LogBroadcasterEnabled = true +RPCDefaultBatchSize = 250 +RPCBlockQueryDelay = 1 +FinalizedBlockOffset = 0 +NoNewFinalizedHeadsThreshold = '2h0m0s' + +[Transactions] +ForwardersEnabled = false +MaxInFlight = 16 +MaxQueued = 250 +ReaperInterval = '1h0m0s' +ReaperThreshold = '168h0m0s' +ResendAfterThreshold = '30s' + +[Transactions.AutoPurge] +Enabled = false + +[BalanceMonitor] +Enabled = true + +[GasEstimator] +Mode = 'BlockHistory' +PriceDefault = '20 gwei' +PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' +PriceMin = '1 wei' +LimitDefault = 8000000 +LimitMax = 8000000 +LimitMultiplier = '1' +LimitTransfer = 21000 +EstimateLimit = false +BumpMin = '1 mwei' +BumpPercent = 20 +BumpThreshold = 3 +EIP1559DynamicFees = true +FeeCapDefault = '100 gwei' +TipCapDefault = '1 wei' +TipCapMin = '1 wei' + +[GasEstimator.BlockHistory] +BatchSize = 25 +BlockHistorySize = 60 +CheckInclusionBlocks = 12 +CheckInclusionPercentile = 90 +TransactionPercentile = 60 + +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + +[HeadTracker] +HistoryDepth = 300 +MaxBufferSize = 3 +SamplingInterval = '1s' +MaxAllowedFinalityDepth = 10000 +FinalityTagBypass = true + +[NodePool] +PollFailureThreshold = 5 +PollInterval = '10s' +SelectionMode = 'HighestHead' +SyncThreshold = 10 +LeaseDuration = '0s' +NodeIsSyncingEnabled = false +FinalizedBlockPollInterval = '5s' +EnforceRepeatableRead = false +DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' + +[OCR] +ContractConfirmations = 1 +ContractTransmitterTransmitTimeout = '10s' +DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' +ObservationGracePeriod = '1s' + +[OCR2] +[OCR2.Automation] +GasLimit = 6500000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -5024,6 +5686,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '40s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -5088,6 +5751,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -5100,6 +5764,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -5122,6 +5789,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '12m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 100 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -5186,6 +5854,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -5198,6 +5867,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -5220,6 +5892,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 2 FinalizedBlockOffset = 0 @@ -5284,6 +5957,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -5296,6 +5970,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 3800000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -5318,6 +5995,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -5383,6 +6061,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -5395,6 +6074,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -5416,6 +6098,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '30s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -5480,6 +6163,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -5492,6 +6176,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -5514,6 +6201,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '40s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -5578,6 +6266,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -5590,6 +6279,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 6500000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -5612,6 +6304,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '3m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -5676,6 +6369,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -5688,6 +6382,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -5711,6 +6408,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -5775,6 +6473,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -5787,6 +6486,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -5810,6 +6512,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -5874,6 +6577,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -5886,6 +6590,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -5908,6 +6615,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '3m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -5973,6 +6681,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -5985,6 +6694,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -6008,6 +6720,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -6072,6 +6785,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -6084,6 +6798,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 14500000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -6106,6 +6823,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '1m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -6170,6 +6888,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -6182,6 +6901,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -6204,6 +6926,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '30s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 2 FinalizedBlockOffset = 0 @@ -6268,6 +6991,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -6280,6 +7004,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -6302,6 +7029,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '30s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 2 FinalizedBlockOffset = 0 @@ -6366,6 +7094,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -6378,6 +7107,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -6400,6 +7132,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '1m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -6464,6 +7197,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -6476,6 +7210,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -6499,6 +7236,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '40s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -6565,6 +7303,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -6577,6 +7316,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 6500000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -6600,6 +7342,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '40s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -6666,6 +7409,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -6678,6 +7422,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 6500000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -6699,6 +7446,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -6763,6 +7511,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -6775,6 +7524,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -6796,6 +7548,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -6860,6 +7613,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -6872,6 +7626,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -6893,6 +7650,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -6957,6 +7715,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -6969,6 +7728,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -6991,6 +7753,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -7055,6 +7818,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -7067,6 +7831,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -7089,6 +7856,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 2 FinalizedBlockOffset = 0 @@ -7153,6 +7921,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -7165,6 +7934,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -7187,6 +7959,7 @@ MinIncomingConfirmations = 5 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '30s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 100 RPCBlockQueryDelay = 10 FinalizedBlockOffset = 0 @@ -7251,6 +8024,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -7263,6 +8037,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -7284,6 +8061,7 @@ MinIncomingConfirmations = 5 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '30s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 100 RPCBlockQueryDelay = 10 FinalizedBlockOffset = 0 @@ -7348,6 +8126,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -7360,6 +8139,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -7382,6 +8164,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '3m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -7447,6 +8230,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -7459,6 +8243,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -7481,6 +8268,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '40s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -7545,6 +8333,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -7557,6 +8346,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 6500000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -7580,6 +8372,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '40s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -7644,6 +8437,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -7656,6 +8450,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 6500000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -7679,6 +8476,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -7743,6 +8541,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -7755,6 +8554,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -7778,6 +8580,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -7842,6 +8645,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -7854,6 +8658,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 14500000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -7877,6 +8684,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -7941,6 +8749,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -7953,6 +8762,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 14500000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -7975,6 +8787,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -8039,6 +8852,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -8051,6 +8865,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -8073,6 +8890,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -8137,6 +8955,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -8149,6 +8968,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -8171,6 +8993,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.1 link' NonceAutoSync = true NoNewHeadsThreshold = '3m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -8235,6 +9058,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -8247,6 +9071,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 10500000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -8269,6 +9096,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '40s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -8333,6 +9161,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 1 @@ -8345,6 +9174,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 6500000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -8367,6 +9199,7 @@ MinIncomingConfirmations = 3 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '3m0s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -8432,6 +9265,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -8444,6 +9278,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -8466,6 +9303,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '30s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -8530,6 +9368,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -8542,6 +9381,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -8564,6 +9406,7 @@ MinIncomingConfirmations = 1 MinContractPayment = '0.00001 link' NonceAutoSync = true NoNewHeadsThreshold = '30s' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -8628,6 +9471,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [OCR] ContractConfirmations = 4 @@ -8640,6 +9484,9 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] GasLimit = 5400000 + +[Workflow] +GasLimitDefault = 400000 ```

@@ -8833,6 +9680,12 @@ The latest finalized block on chain is 64, so block 63 is the latest finalized f Block 64 will be treated as finalized by CL Node only when chain's latest finalized block is 65. As chain finalizes blocks in batches of 32, CL Node has to wait for a whole new batch to be finalized to treat block 64 as finalized. +### LogBroadcasterEnabled +```toml +LogBroadcasterEnabled = true # Default +``` +LogBroadcasterEnabled is a feature flag for LogBroadcaster, by default it's true. + ### NoNewFinalizedHeadsThreshold ```toml NoNewFinalizedHeadsThreshold = '0' # Default @@ -9376,6 +10229,7 @@ NodeIsSyncingEnabled = false # Default FinalizedBlockPollInterval = '5s' # Default EnforceRepeatableRead = false # Default DeathDeclarationDelay = '10s' # Default +NewHeadsPollInterval = '0s' # Default ``` The node pool manages multiple RPC endpoints. @@ -9468,6 +10322,14 @@ Larger values might be helpful to reduce the noisiness of health checks like `En trigger declaration of `FinalizedBlockOutOfSync` due to insignificant network delays in broadcasting of the finalized state among RPCs. RPC will not be picked to handle a request even if this option is set to a nonzero value. +### NewHeadsPollInterval +```toml +NewHeadsPollInterval = '0s' # Default +``` +NewHeadsPollInterval define an interval for polling new block periodically using http client rather than subscribe to ws feed + +Set to 0 to disable. + ## EVM.NodePool.Errors :warning: **_ADVANCED_**: _Do not change these settings unless you know what you are doing._ ```toml @@ -9486,6 +10348,7 @@ L2Full = '(: |^)l2 full' # Example TransactionAlreadyMined = '(: |^)transaction already mined' # Example Fatal = '(: |^)fatal' # Example ServiceUnavailable = '(: |^)service unavailable' # Example +TooManyResults = '(: |^)too many results' # Example ``` Errors enable the node to provide custom regex patterns to match against error messages from RPCs. @@ -9573,6 +10436,12 @@ ServiceUnavailable = '(: |^)service unavailable' # Example ``` ServiceUnavailable is a regex pattern to match against service unavailable errors. +### TooManyResults +```toml +TooManyResults = '(: |^)too many results' # Example +``` +TooManyResults is a regex pattern to match an eth_getLogs error indicating the result set is too large to return + ## EVM.OCR ```toml [EVM.OCR] @@ -9684,6 +10553,7 @@ GasLimit controls the gas limit for transmit transactions from ocr2automation jo [EVM.Workflow] FromAddress = '0x2a3e23c6f242F5345320814aC8a1b4E58707D292' # Example ForwarderAddress = '0x2a3e23c6f242F5345320814aC8a1b4E58707D292' # Example +GasLimitDefault = 400_000 # Default ``` @@ -9699,6 +10569,12 @@ ForwarderAddress = '0x2a3e23c6f242F5345320814aC8a1b4E58707D292' # Example ``` ForwarderAddress is the keystone forwarder contract address on chain. +### GasLimitDefault +```toml +GasLimitDefault = 400_000 # Default +``` +GasLimitDefault is the default gas limit for workflow transactions. + ## Cosmos ```toml [[Cosmos]] diff --git a/flake.lock b/flake.lock index c5097f5a47..77dddea406 100644 --- a/flake.lock +++ b/flake.lock @@ -26,11 +26,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1719997877, - "narHash": "sha256-/Edw+w0PiGgxwnCeJycM0VgH4HtlCi91v1d8xbi+REE=", + "lastModified": 1725354688, + "narHash": "sha256-KHHFemVt6C/hbGoMzIq7cpxmjdp+KZVZaqbvx02aliY=", "owner": "shazow", "repo": "foundry.nix", - "rev": "02febba4f1cf0606d790acdb24adcf7a64afb4e1", + "rev": "671672bd60a0d2e5f6757638fdf27e806df755a4", "type": "github" }, "original": { @@ -56,11 +56,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1720957393, - "narHash": "sha256-oedh2RwpjEa+TNxhg5Je9Ch6d3W1NKi7DbRO1ziHemA=", + "lastModified": 1725103162, + "narHash": "sha256-Ym04C5+qovuQDYL/rKWSR+WESseQBbNAe5DsXNx5trY=", "owner": "nixos", "repo": "nixpkgs", - "rev": "693bc46d169f5af9c992095736e82c3488bf7dbb", + "rev": "12228ff1752d7b7624a54e9c1af4b222b3c1073b", "type": "github" }, "original": { diff --git a/go.md b/go.md index d9ed0d0a66..02c01177b9 100644 --- a/go.md +++ b/go.md @@ -5,11 +5,7 @@ flowchart LR chainlink-cosmos chainlink-solana chainlink-starknet/relayer - subgraph chainlink-integrations - direction LR - chainlink-integrations/evm/relayer - chainlink-integrations/common - end + chainlink-evm end subgraph products @@ -21,8 +17,13 @@ flowchart LR chainlink-vrf end + subgraph tdh2 + tdh2/go/tdh2 + tdh2/go/ocr2/decryptionplugin + end + classDef outline stroke-dasharray:6,fill:none; - class chains,products outline + class chains,products,tdh2 outline chainlink/v2 --> chain-selectors click chain-selectors href "https://github.com/smartcontractkit/chain-selectors" @@ -40,6 +41,8 @@ flowchart LR click chainlink-solana href "https://github.com/smartcontractkit/chainlink-solana" chainlink/v2 --> chainlink-starknet/relayer click chainlink-starknet/relayer href "https://github.com/smartcontractkit/chainlink-starknet" + chainlink/v2 --> grpc-proxy + click grpc-proxy href "https://github.com/smartcontractkit/grpc-proxy" chainlink/v2 --> libocr click libocr href "https://github.com/smartcontractkit/libocr" chainlink/v2 --> tdh2/go/ocr2/decryptionplugin @@ -50,17 +53,23 @@ flowchart LR click wsrpc href "https://github.com/smartcontractkit/wsrpc" chainlink-automation --> chainlink-common chainlink-automation --> libocr + chainlink-common --> grpc-proxy chainlink-common --> libocr chainlink-cosmos --> chainlink-common chainlink-cosmos --> libocr + chainlink-cosmos --> grpc-proxy chainlink-data-streams --> chainlink-common chainlink-data-streams --> libocr + chainlink-data-streams --> grpc-proxy chainlink-feeds --> chainlink-common chainlink-feeds --> libocr + chainlink-feeds --> grpc-proxy chainlink-solana --> chainlink-common chainlink-solana --> libocr + chainlink-solana --> grpc-proxy chainlink-starknet/relayer --> chainlink-common chainlink-starknet/relayer --> libocr + chainlink-starknet/relayer --> grpc-proxy tdh2/go/ocr2/decryptionplugin --> libocr tdh2/go/ocr2/decryptionplugin --> tdh2/go/tdh2 ``` diff --git a/go.mod b/go.mod index 35494138c0..beb2354edf 100644 --- a/go.mod +++ b/go.mod @@ -19,8 +19,8 @@ require ( github.com/dominikbraun/graph v0.23.0 github.com/esote/minmaxheap v1.0.0 github.com/ethereum/go-ethereum v1.13.8 - github.com/fatih/color v1.16.0 - github.com/fxamacker/cbor/v2 v2.5.0 + github.com/fatih/color v1.17.0 + github.com/fxamacker/cbor/v2 v2.7.0 github.com/gagliardetto/solana-go v1.8.4 github.com/getsentry/sentry-go v0.23.0 github.com/gin-contrib/cors v1.5.0 @@ -29,18 +29,19 @@ require ( github.com/gin-contrib/size v0.0.0-20230212012657-e14a14094dc4 github.com/gin-gonic/gin v1.9.1 github.com/go-ldap/ldap/v3 v3.4.6 + github.com/go-viper/mapstructure/v2 v2.1.0 github.com/go-webauthn/webauthn v0.9.4 - github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b + github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 github.com/google/uuid v1.6.0 github.com/gorilla/securecookie v1.1.2 github.com/gorilla/sessions v1.2.2 github.com/gorilla/websocket v1.5.1 github.com/grafana/pyroscope-go v1.1.1 github.com/graph-gophers/dataloader v5.0.0+incompatible - github.com/graph-gophers/graphql-go v1.3.0 + github.com/graph-gophers/graphql-go v1.5.0 github.com/hashicorp/consul/sdk v0.16.0 github.com/hashicorp/go-envparse v0.1.0 - github.com/hashicorp/go-plugin v1.6.0 + github.com/hashicorp/go-plugin v1.6.2-0.20240829161738-06afb6d7ae99 github.com/hashicorp/go-retryablehttp v0.7.5 github.com/hdevalence/ed25519consensus v0.1.0 github.com/jackc/pgconn v1.14.3 @@ -54,18 +55,17 @@ require ( github.com/lib/pq v1.10.9 github.com/manyminds/api2go v0.0.0-20171030193247-e7b693844a6f github.com/mitchellh/go-homedir v1.1.0 - github.com/mitchellh/mapstructure v1.5.0 github.com/mr-tron/base58 v1.2.0 github.com/olekukonko/tablewriter v0.0.5 - github.com/onsi/gomega v1.30.0 + github.com/onsi/gomega v1.33.1 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pelletier/go-toml v1.9.5 - github.com/pelletier/go-toml/v2 v2.2.0 + github.com/pelletier/go-toml/v2 v2.2.2 github.com/pkg/errors v0.9.1 github.com/pressly/goose/v3 v3.21.1 - github.com/prometheus/client_golang v1.17.0 - github.com/prometheus/client_model v0.5.0 - github.com/prometheus/common v0.45.0 + github.com/prometheus/client_golang v1.20.0 + github.com/prometheus/client_model v0.6.1 + github.com/prometheus/common v0.59.1 github.com/prometheus/prometheus v0.48.1 github.com/robfig/cron/v3 v3.0.1 github.com/rogpeppe/go-internal v1.12.0 @@ -75,16 +75,16 @@ require ( github.com/shopspring/decimal v1.4.0 github.com/smartcontractkit/chain-selectors v1.0.27 github.com/smartcontractkit/chainlink-automation v1.0.4 - github.com/smartcontractkit/chainlink-common v0.2.2-0.20240723123524-e407ecd120b1 - github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 - github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240718160222-2dc0c8136bfa - github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 - github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e - github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 + github.com/smartcontractkit/chainlink-common v0.2.3-0.20240925085218-aded1b263ecc + github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 + github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240916152957-433914114bd2 + github.com/smartcontractkit/chainlink-feeds v0.0.0-20240910155501-42f20443189f + github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240911182932-3c609a6ac664 + github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240911194142-506bc469d8ae github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7 github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1 github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20230906073235-9e478e5e19f1 - github.com/smartcontractkit/wsrpc v0.7.3 + github.com/smartcontractkit/wsrpc v0.8.2 github.com/spf13/cast v1.6.0 github.com/stretchr/testify v1.9.0 github.com/test-go/testify v1.1.4 @@ -100,17 +100,18 @@ require ( go.dedis.ch/kyber/v3 v3.1.0 go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.49.0 go.opentelemetry.io/otel v1.28.0 + go.opentelemetry.io/otel/trace 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/time v0.5.0 - golang.org/x/tools v0.23.0 + golang.org/x/crypto v0.27.0 + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 + golang.org/x/mod v0.21.0 + golang.org/x/net v0.29.0 + golang.org/x/sync v0.8.0 + golang.org/x/term v0.24.0 + golang.org/x/text v0.18.0 + golang.org/x/time v0.6.0 + golang.org/x/tools v0.25.0 gonum.org/v1/gonum v0.15.0 google.golang.org/grpc v1.65.0 google.golang.org/protobuf v1.34.2 @@ -182,7 +183,7 @@ require ( github.com/cosmos/ibc-go/v7 v7.5.1 // indirect github.com/cosmos/ics23/go v0.10.0 // indirect github.com/cosmos/ledger-cosmos-go v0.12.4 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 // indirect github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect github.com/danieljoos/wincred v1.1.2 // indirect @@ -193,11 +194,12 @@ require ( github.com/dgraph-io/ristretto v0.1.1 // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect github.com/docker/distribution v2.8.2+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.7.0 // indirect github.com/ethereum/c-kzg-4844 v0.4.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gagliardetto/binary v0.7.7 // indirect github.com/gagliardetto/treeout v0.1.4 // indirect github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect @@ -214,7 +216,7 @@ require ( github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.15.5 // indirect + github.com/go-playground/validator/v10 v10.22.0 // indirect github.com/go-webauthn/x v0.1.5 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect @@ -230,12 +232,12 @@ require ( github.com/google/go-tpm v0.9.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/gorilla/context v1.1.1 // indirect - github.com/grafana/pyroscope-go/godeltaprof v0.1.6 // indirect + github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/gtank/merlin v0.1.1 // indirect github.com/gtank/ristretto255 v0.1.2 // indirect @@ -261,12 +263,13 @@ require ( github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jmhodges/levigo v1.0.0 // indirect + github.com/joho/godotenv v1.5.1 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.17.3 // indirect + github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect - github.com/leodido/go-urn v1.2.4 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/linxGnu/grocksdb v1.7.16 // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect @@ -274,25 +277,26 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/mfridman/interpolate v0.0.2 // indirect github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 // indirect github.com/mtibben/percent v0.2.1 // indirect - github.com/mwitkow/grpc-proxy v0.0.0-20230212185441-f345521cb9c9 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/oklog/run v1.1.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0-rc5 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect - github.com/prometheus/procfs v0.12.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect @@ -300,8 +304,9 @@ require ( github.com/sasha-s/go-deadlock v0.3.1 // indirect github.com/sethvargo/go-retry v0.2.4 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect + github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // indirect github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/cobra v1.8.0 // indirect + github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.18.2 // indirect github.com/status-im/keycard-go v0.2.0 // indirect @@ -331,21 +336,26 @@ require ( go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240823153156-2a54df7bffb9 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.4.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0 // indirect + go.opentelemetry.io/otel/log v0.4.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/sdk v1.28.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.4.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.28.0 // indirect - go.opentelemetry.io/otel/trace v1.28.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect 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.25.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 - google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect gopkg.in/guregu/null.v2 v2.1.2 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect @@ -355,17 +365,11 @@ require ( ) replace ( + // until merged upstream: https://github.com/omissis/go-jsonschema/pull/264 + github.com/atombender/go-jsonschema => github.com/nolag/go-jsonschema v0.16.0-rtinianov + // replicating the replace directive on cosmos SDK github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 - - // until merged upstream: https://github.com/hashicorp/go-plugin/pull/257 - github.com/hashicorp/go-plugin => github.com/smartcontractkit/go-plugin v0.0.0-20240208201424-b3b91517de16 - - // until nolag updates merged upstream - github.com/mitchellh/mapstructure => github.com/nolag/mapstructure v1.5.2-0.20240625151721-90ea83a3f479 - - // until merged upstream: https://github.com/mwitkow/grpc-proxy/pull/69 - github.com/mwitkow/grpc-proxy => github.com/smartcontractkit/grpc-proxy v0.0.0-20230731113816-f1be6620749f ) -exclude github.com/sourcegraph/sourcegraph/lib v0.0.0-20221216004406-749998a2ac74 +replace github.com/sourcegraph/sourcegraph/lib => github.com/sourcegraph/sourcegraph-public-snapshot/lib v0.0.0-20240822153003-c864f15af264 diff --git a/go.sum b/go.sum index 05fa904055..2dcd978d70 100644 --- a/go.sum +++ b/go.sum @@ -262,8 +262,8 @@ github.com/cosmos/rosetta-sdk-go v0.10.0/go.mod h1:SImAZkb96YbwvoRkzSMQB6noNJXFg 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.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -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= @@ -304,8 +304,8 @@ github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUn github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo= @@ -329,8 +329,8 @@ github.com/ethereum/go-ethereum v1.13.8/go.mod h1:sc48XYQxCzH3fG9BcrXCOOgQk2JfZz github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= @@ -344,10 +344,10 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= 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.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= -github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= -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/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.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gagliardetto/binary v0.7.7 h1:QZpT38+sgoPg+TIQjH94sLbl/vX+nlIRA37pEyOsjfY= github.com/gagliardetto/binary v0.7.7/go.mod h1:mUuay5LL8wFVnIlecHakSZMvcdqfs+CsotR5n77kyjM= github.com/gagliardetto/gofuzz v1.2.2 h1:XL/8qDMzcgvR4+CyRQW9UGdwPRPMHVJfqQ/uMvSUuQw= @@ -404,6 +404,7 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 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= @@ -420,14 +421,17 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= -github.com/go-playground/validator/v10 v10.15.5 h1:LEBecTWb/1j5TNY1YYG2RcOUN3R7NLylN+x8TTueE24= -github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= +github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/go-viper/mapstructure/v2 v2.1.0 h1:gHnMa2Y/pIxElCH2GlZZ1lZSsn6XMtufpGyP1XxdC/w= +github.com/go-viper/mapstructure/v2 v2.1.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-webauthn/webauthn v0.9.4 h1:YxvHSqgUyc5AK2pZbqkWWR55qKeDPhP8zLDr6lpIc2g= github.com/go-webauthn/webauthn v0.9.4/go.mod h1:LqupCtzSef38FcxzaklmOn7AykGKhAhr9xlRbdbgnTw= github.com/go-webauthn/x v0.1.5 h1:V2TCzDU2TGLd0kSZOXdrqDVV5JB9ILnKxA9S53CSBw0= @@ -502,6 +506,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -520,15 +525,14 @@ github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b h1:RMpPgZTSApbPf7xaVel+QkoGPRLFLrwFO89uDUHEGf0= -github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +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/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -555,12 +559,12 @@ github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/ github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/grafana/pyroscope-go v1.1.1 h1:PQoUU9oWtO3ve/fgIiklYuGilvsm8qaGhlY4Vw6MAcQ= github.com/grafana/pyroscope-go v1.1.1/go.mod h1:Mw26jU7jsL/KStNSGGuuVYdUq7Qghem5P8aXYXSXG88= -github.com/grafana/pyroscope-go/godeltaprof v0.1.6 h1:nEdZ8louGAplSvIJi1HVp7kWvFvdiiYg3COLlTwJiFo= -github.com/grafana/pyroscope-go/godeltaprof v0.1.6/go.mod h1:Tk376Nbldo4Cha9RgiU7ik8WKFkNpfds98aUzS8omLE= +github.com/grafana/pyroscope-go/godeltaprof v0.1.8 h1:iwOtYXeeVSAeYefJNaxDytgjKtUuKQbJqgAIjlnicKg= +github.com/grafana/pyroscope-go/godeltaprof v0.1.8/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= github.com/graph-gophers/dataloader v5.0.0+incompatible h1:R+yjsbrNq1Mo3aPG+Z/EKYrXrXXUNJHOgbRt+U6jOug= github.com/graph-gophers/dataloader v5.0.0+incompatible/go.mod h1:jk4jk0c5ZISbKaMe8WsVopGB5/15GvGHMdMdPtwlRp4= -github.com/graph-gophers/graphql-go v1.3.0 h1:Eb9x/q6MFpCLz7jBCiP/WTxjSDrYLR1QY41SORZyNJ0= -github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +github.com/graph-gophers/graphql-go v1.5.0 h1:fDqblo50TEpD0LY7RXk/LFVYEVqo3+tXMNMPSVXA1yc= +github.com/graph-gophers/graphql-go v1.5.0/go.mod h1:YtmJZDLbF1YYNrlNAuiO5zAStUWc3XZT07iGsVqe1Os= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= @@ -572,8 +576,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= @@ -604,6 +608,8 @@ github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJ github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-plugin v1.6.2-0.20240829161738-06afb6d7ae99 h1:OSQYEsRT3tRttZkk6zyC3aAaliwd7Loi/KgXgXxGtwA= +github.com/hashicorp/go-plugin v1.6.2-0.20240829161738-06afb6d7ae99/go.mod h1:CkgLQ5CZqNmdL9U9JzM532t8ZiYQ35+pj3b1FD37R0Q= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= @@ -723,8 +729,8 @@ github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= -github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= -github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= @@ -744,8 +750,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.17.3 h1:qkRjuerhUU1EmXLYGkSH6EZL+vPSxIrYjLNAK4slzwA= -github.com/klauspost/compress v1.17.3/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= @@ -768,8 +774,8 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+ github.com/leanovate/gopter v0.2.10-0.20210127095200-9abe2343507a h1:dHCfT5W7gghzPtfsW488uPmEOm85wewI+ypUwibyTdU= github.com/leanovate/gopter v0.2.10-0.20210127095200-9abe2343507a/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -819,8 +825,6 @@ github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxU github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= @@ -841,6 +845,10 @@ github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJ github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= @@ -863,13 +871,13 @@ github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= -github.com/nolag/mapstructure v1.5.2-0.20240625151721-90ea83a3f479 h1:1jCGDLFXDOHF2sdeTJYKrIuSLGMpQZpgXXHNGXR5Ouk= -github.com/nolag/mapstructure v1.5.2-0.20240625151721-90ea83a3f479/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/nsf/jsondiff v0.0.0-20210926074059-1e845ec5d249 h1:NHrXEjTNQY7P0Zfx1aMrNhpgxHmow66XQtm0aQLY0AE= github.com/nsf/jsondiff v0.0.0-20210926074059-1e845ec5d249/go.mod h1:mpRZBD8SJ55OIICQ3iWH0Yz3cjzA61JdqMLoWXeB2+8= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= @@ -886,18 +894,18 @@ 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.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= -github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= +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/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= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= -github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= -github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= +github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= +github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= github.com/opencontainers/runc v1.1.3 h1:vIXrkId+0/J2Ymu2m7VjGvbSlAId9XNRPhn2p4b+d8w= github.com/opencontainers/runc v1.1.3/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= @@ -914,8 +922,8 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9 github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= -github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= -github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 h1:hDSdbBuw3Lefr6R18ax0tZ2BJeNB3NehB3trOwYBsdU= github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= @@ -938,26 +946,26 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= -github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= -github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= +github.com/prometheus/client_golang v1.20.0 h1:jBzTZ7B099Rg24tny+qngoynol8LtVYlA2bqx3vEloI= +github.com/prometheus/client_golang v1.20.0/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= -github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0= +github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/prometheus/prometheus v0.48.1 h1:CTszphSNTXkuCG6O0IfpKdHcJkvvnAAE1GbELKS+NFk= github.com/prometheus/prometheus v0.48.1/go.mod h1:SRw624aMAxTfryAcP8rOjg4S/sHHaetx2lyJJ2nM83g= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= @@ -1030,30 +1038,28 @@ github.com/smartcontractkit/chain-selectors v1.0.27 h1:VE/ftX9Aae4gnw67yR1raKi+3 github.com/smartcontractkit/chain-selectors v1.0.27/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240723123524-e407ecd120b1 h1:pdEpjgbZ5w/Sd5lzg/XiuC5gVyrmSovOo+3nUD46SP8= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240723123524-e407ecd120b1/go.mod h1:Jg1sCTsbxg76YByI8ifpFby3FvVqISStHT8ypy9ocmY= -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-20240718160222-2dc0c8136bfa h1:g75H8oh2ws52s8BekwvGQ9XvBVu3E7WM1rfiA0PN0zk= -github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240718160222-2dc0c8136bfa/go.mod h1:wZvLHX/Sd9hskN51016cTFcT3G62KXVa6xbVDS7tRjc= -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.0.3-0.20240712132946-267a37c5ac6e h1:PzwzlHNv1YbJ6ZIdl/pIFRoOuOS4V4WLvjZvFUnZFL4= -github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e/go.mod h1:hsFhop+SlQHKD+DEFjZrMJmbauT1A/wvtZIeeo4PxFU= -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= -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/chainlink-common v0.2.3-0.20240925085218-aded1b263ecc h1:ALbyaoRzUSXQ2NhGFKVOyJqO22IB5yQjhjKWbIZGbrI= +github.com/smartcontractkit/chainlink-common v0.2.3-0.20240925085218-aded1b263ecc/go.mod h1:F6WUS6N4mP5ScwpwyTyAJc9/vjR+GXbMCRUOVekQi1g= +github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 h1:lTGIOQYLk1Ufn++X/AvZnt6VOcuhste5yp+C157No/Q= +github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7/go.mod h1:BMYE1vC/pGmdFSsOJdPrAA0/4gZ0Xo0SxTMdGspBtRo= +github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240916152957-433914114bd2 h1:yRk4ektpx/UxwarqAfgxUXLrsYXlaNeP1NOwzHGrK2Q= +github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240916152957-433914114bd2/go.mod h1:rNhNSrrRMvkgAm5SA6bNTdh2340bTQQZdUVNtZ2o2bk= +github.com/smartcontractkit/chainlink-feeds v0.0.0-20240910155501-42f20443189f h1:p4p3jBT91EQyLuAMvHD+zNJsuAYI/QjJbzuGUJ7wIgg= +github.com/smartcontractkit/chainlink-feeds v0.0.0-20240910155501-42f20443189f/go.mod h1:FLlWBt2hwiMVgt9AcSo6wBJYIRd/nsc8ENbV1Wir1bw= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240911182932-3c609a6ac664 h1:JPs35oSO07PK3Qv7Kyv0GJHVLacIE1IkrvefaPyBjKs= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240911182932-3c609a6ac664/go.mod h1:iJ9DKYo0F64ue7IogAIELwU2DfrhEAh76eSmZOilT8A= +github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240911194142-506bc469d8ae h1:d+B8y2Nd/PrnPMNoaSPn3eDgUgxcVcIqAxGrvYu/gGw= +github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240911194142-506bc469d8ae/go.mod h1:ec/a20UZ7YRK4oxJcnTBFzp1+DBcJcwqEaerUMsktMs= +github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12ijqMM9tvYVEm+nR826WsrNi6zCKpwBhuApq127wHs= +github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7/go.mod h1:FX7/bVdoep147QQhsOPkYsPEXhGZjeYx6lBSaSXtZOA= github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7 h1:e38V5FYE7DA1JfKXeD5Buo/7lczALuVXlJ8YNTAUxcw= github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7/go.mod h1:fb1ZDVXACvu4frX3APHZaEBp0xi1DIm34DcA0CwTsZM= github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1 h1:yiKnypAqP8l0OX0P3klzZ7SCcBUxy5KqTAKZmQOvSQE= github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1/go.mod h1:q6f4fe39oZPdsh1i57WznEZgxd8siidMaSFq3wdPmVg= github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20230906073235-9e478e5e19f1 h1:Dai1bn+Q5cpeGMQwRdjOdVjG8mmFFROVkSKuUgBErRQ= github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20230906073235-9e478e5e19f1/go.mod h1:G5Sd/yzHWf26rQ+X0nG9E0buKPqRGPMJAfk2gwCzOOw= -github.com/smartcontractkit/wsrpc v0.7.3 h1:CKYZfawZShZGfvsQep1F9oBansnFk9ByZPCdTMpLphw= -github.com/smartcontractkit/wsrpc v0.7.3/go.mod h1:sj7QX2NQibhkhxTfs3KOhAj/5xwgqMipTvJVSssT9i0= +github.com/smartcontractkit/wsrpc v0.8.2 h1:XB/xcn/MMseHW+8JE8+a/rceA86ck7Ur6cEa9LiUC8M= +github.com/smartcontractkit/wsrpc v0.8.2/go.mod h1:2u/wfnhl5R4RlSXseN4n6HHIWk8w1Am3AT6gWftQbNg= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -1071,8 +1077,8 @@ github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= 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/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -1103,7 +1109,6 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= @@ -1224,18 +1229,34 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIX go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= go.opentelemetry.io/contrib/propagators/b3 v1.24.0 h1:n4xwCdTx3pZqZs2CjS/CUZAs03y3dZcGhC/FepKtEUY= go.opentelemetry.io/contrib/propagators/b3 v1.24.0/go.mod h1:k5wRxKRU2uXx2F8uNJ4TaonuEO/V7/5xoz7kdsDACT8= +go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI= go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240823153156-2a54df7bffb9 h1:UiRNKd1OgqsLbFwE+wkAWTdiAxXtCBqKIHeBIse4FUA= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240823153156-2a54df7bffb9/go.mod h1:eqZlW3pJWhjyexnDPrdQxix1pn0wwhI4AO4GKpP/bMI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 h1:U2guen0GhqH8o/G2un8f/aG/y++OuW6MyCo6hT9prXk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0/go.mod h1:yeGZANgEcpdx/WK0IvvRFC+2oLiMS2u4L/0Rj2M2Qr0= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 h1:R3X6ZXmNPRR8ul6i3WgFURCHzaXjHdm0karRG/+dj3s= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0/go.mod h1:QWFXnDavXWwMx2EEcZsf3yxgEKAqsxQ+Syjp+seyInw= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.4.0 h1:0MH3f8lZrflbUWXVxyBg/zviDFdGE062uKh5+fu8Vv0= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.4.0/go.mod h1:Vh68vYiHY5mPdekTr0ox0sALsqjoVy0w3Os278yX5SQ= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.28.0 h1:BJee2iLkfRfl9lc7aFmBwkWxY/RI1RDdXepSF6y8TPE= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.28.0/go.mod h1:DIzlHs3DRscCIBU3Y9YSzPfScwnYnzfnCd4g8zA7bZc= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0 h1:EVSnY9JbEEW92bEkIYOVMw4q1WJxIAGoFTrtYOzWuRQ= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0/go.mod h1:Ea1N1QQryNXpCD0I1fdLibBAIpQuBkznMmkdKrapk1Y= +go.opentelemetry.io/otel/log v0.4.0 h1:/vZ+3Utqh18e8TPjuc3ecg284078KWrR8BRz+PQAj3o= +go.opentelemetry.io/otel/log v0.4.0/go.mod h1:DhGnQvky7pHy82MIRV43iXh3FlKN8UUKftn0KbLOq6I= go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/sdk/log v0.4.0 h1:1mMI22L82zLqf6KtkjrRy5BbagOTWdJsqMY/HSqILAA= +go.opentelemetry.io/otel/sdk/log v0.4.0/go.mod h1:AYJ9FVF0hNOgAVzUG/ybg/QttnXhUePWAupmCqtdESo= go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08= go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= +go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5fjhKeJGZPGFs= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= @@ -1266,7 +1287,6 @@ go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.14.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= @@ -1294,8 +1314,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.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.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= 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= @@ -1306,8 +1326,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-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= 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= @@ -1332,8 +1352,8 @@ golang.org/x/mod v0.3.0/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.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 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= @@ -1375,15 +1395,15 @@ 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.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= 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= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= -golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= +golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1395,8 +1415,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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= @@ -1466,8 +1486,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.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.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= @@ -1476,8 +1496,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.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= 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= @@ -1491,13 +1511,13 @@ 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.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.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= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -1544,8 +1564,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 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.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= +golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= 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= @@ -1602,10 +1622,10 @@ google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEY google.golang.org/genproto v0.0.0-20210401141331-865547bb08e2/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d h1:/hmn0Ku5kWij/kjGsrcJeC1T/MrJi2iNWwgAqrihFwc= google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d/go.mod h1:FfBgJBJg9GcpPvKIuHSZ/aE1g2ecGL74upMzGZjiGEY= -google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY= -google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d h1:JU0iKnSg02Gmb5ZdV8nYsKEKsP6o/FGVWTrw4i1DA9A= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= +google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= diff --git a/heroku.yml b/heroku.yml deleted file mode 100644 index bb95afa121..0000000000 --- a/heroku.yml +++ /dev/null @@ -1,6 +0,0 @@ -build: - docker: - web: Dockerfile.web - config: - REACT_APP_INFURA_KEY: - REACT_APP_GA_ID: diff --git a/integration-tests/.golangci.yml b/integration-tests/.golangci.yml index 8969110d98..304249d24c 100644 --- a/integration-tests/.golangci.yml +++ b/integration-tests/.golangci.yml @@ -70,6 +70,9 @@ linters-settings: - name: atomic issues: exclude-rules: + - path: deployment/memory/(.+)\.go + linters: + - revive - text: "^G404: Use of weak random number generator" linters: - gosec diff --git a/integration-tests/.tool-versions b/integration-tests/.tool-versions index d623afb283..7ca4249a79 100644 --- a/integration-tests/.tool-versions +++ b/integration-tests/.tool-versions @@ -3,3 +3,4 @@ k3d 5.4.6 kubectl 1.25.5 nodejs 20.13.1 golangci-lint 1.59.1 +task 3.35.1 diff --git a/integration-tests/README.md b/integration-tests/README.md index fcfefe97a7..5647a62316 100644 --- a/integration-tests/README.md +++ b/integration-tests/README.md @@ -27,6 +27,8 @@ version = "your tag" The `./testconfig/overrides.toml` file **should never be committed** and has been added to the [.gitignore](../.gitignore) file as it can often contain secrets like private keys and RPC URLs. +For more information on how to configure the tests, see the [testconfig README](./testconfig/README.md). + ## Build If you'd like to run the tests on a local build of Chainlink, you can point to your own docker image, or build a fresh one with `make`. @@ -51,7 +53,7 @@ It's generally recommended to run only one test at a time on a local machine as ### Configure Seth -Our new evm client is Seth. Detailed instructions on how to configure it can be found in the [Seth README](./README_SETH.md) in this repo as well as in [Seth repository](https://github.com/smartcontractkit/seth). +Our new evm client is Seth. Detailed instructions on how to configure it can be found in the [Seth README](./README_SETH.md) in this repo as well as in [Seth repository](https://github.com/smartcontractkit/chainlink-testing-framework/tree/main/seth). ## Analyze @@ -76,8 +78,7 @@ make test_soak_ocr_reorg_2 Run reorg/automation_reorg_test.go with reorg settings: -1. Use Simulated Geth network and put GethReorgConfig in overrides.toml - +1. Use Simulated Geth network and put GethReorgConfig in overrides.toml ```toml [Network] @@ -128,3 +129,9 @@ Run soak/ocr_test.go with RPC network chaos by bringing down network to RPC node ```bash make test_soak_ocr_rpc_down_half_cl_nodes ``` + +### Debugging HTTP and RPC clients +```bash +export SETH_LOG_LEVEL=debug +export RESTY_DEBUG=true +``` diff --git a/integration-tests/README_SETH.md b/integration-tests/README_SETH.md index 92302ab9ce..26fbfc1a79 100644 --- a/integration-tests/README_SETH.md +++ b/integration-tests/README_SETH.md @@ -41,7 +41,7 @@ ## Introduction -[Seth](https://github.com/smartcontractkit/seth) is the Ethereum client we use for integration tests. It is designed to be a thin wrapper over `go-ethereum` client that adds a couple of key features: +[Seth](https://github.com/smartcontractkit/chainlink-testing-framework/tree/main/seth) is the Ethereum client we use for integration tests. It is designed to be a thin wrapper over `go-ethereum` client that adds a couple of key features: * key management * transaction decoding and tracing * gas estimation @@ -65,7 +65,7 @@ tracing_level = "all" # trace all transactions regardless of whether they are re ``` ### Documentation and Further Details -For a comprehensive description of all available configuration options, refer to the `[Seth]` section of configuration documentation in the [default.toml](./testconfig/default.toml) file or consult the Seth [README.md on GitHub](https://github.com/smartcontractkit/seth/blob/master/README.md). +For a comprehensive description of all available configuration options, refer to the `[Seth]` section of configuration documentation in the [default.toml](./testconfig/default.toml) file or consult the Seth [README.md on GitHub](https://github.com/smartcontractkit/chainlink-testing-framework/tree/main/seth/blob/master/README.md). ## How to set Seth logging level ### Locally @@ -155,7 +155,7 @@ The most important thing to keep in mind that the CLI requires you to provide a * `keys` commands requires `SETH_KEYFILE_PATH`, `SETH_CONFIG_PATH` and `SETH_ROOT_PRIVATE_KEY` environment variables * `gas` and `stats` command requires `SETH_CONFIG_PATH` environment variable -You can find a sample `Seth.toml` file [here](https://github.com/smartcontractkit/seth/blob/master/seth.toml). Currently, you cannot use your test TOML file as a Seth configuration file, but we will add ability that in the future. +You can find a sample `Seth.toml` file [here](https://github.com/smartcontractkit/chainlink-testing-framework/tree/main/seth/blob/master/seth.toml). Currently, you cannot use your test TOML file as a Seth configuration file, but we will add ability that in the future. ## How to get Fallback (Hardcoded) Values There are two primary methods to obtain fallback values for network configuration: @@ -166,7 +166,7 @@ There are two primary methods to obtain fallback values for network configuratio 1. **Clone the Seth Repository:** Clone the repository from GitHub using: ```bash -git clone https://github.com/smartcontractkit/seth +git clone https://github.com/smartcontractkit/chainlink-testing-framework/tree/main/seth ``` 2. **Run Seth CLI:** diff --git a/integration-tests/actions/actions.go b/integration-tests/actions/actions.go index 6f7a301f2f..69a21081aa 100644 --- a/integration-tests/actions/actions.go +++ b/integration-tests/actions/actions.go @@ -30,6 +30,7 @@ import ( "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" @@ -51,6 +52,7 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" "github.com/smartcontractkit/chainlink/integration-tests/client" + "github.com/smartcontractkit/chainlink/integration-tests/testconfig/ocr" "github.com/smartcontractkit/chainlink/integration-tests/types/config/node" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/link_token_interface" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/operator_factory" @@ -287,7 +289,7 @@ func fundChainlinkNodesAtAnyKey( return err } - fromAddress, err := privateKeyToAddress(privateKey) + fromAddress, err := PrivateKeyToAddress(privateKey) if err != nil { return err } @@ -338,7 +340,7 @@ type FundsToSendPayload struct { // to given address. You can override any or none of the following: gas limit, gas price, gas fee cap, gas tip cap. // Values that are not set will be estimated or taken from config. func SendFunds(logger zerolog.Logger, client *seth.Client, payload FundsToSendPayload) (*types.Receipt, error) { - fromAddress, err := privateKeyToAddress(payload.PrivateKey) + fromAddress, err := PrivateKeyToAddress(payload.PrivateKey) if err != nil { return nil, err } @@ -492,7 +494,8 @@ func DeployForwarderContracts( operatorFactoryInstance = &instance for i := 0; i < numberOfOperatorForwarderPairs; i++ { - decodedTx, err := seth.Decode(operatorFactoryInstance.DeployNewOperatorAndForwarder()) + tx, deployErr := operatorFactoryInstance.DeployNewOperatorAndForwarder() + decodedTx, err := seth.Decode(tx, deployErr) require.NoError(t, err, "Deploying new operator with proposed ownership with forwarder shouldn't fail") for i, event := range decodedTx.Events { @@ -610,29 +613,48 @@ func TrackForwarder( Msg("Forwarder tracked") } -// DeployOCRv2Contracts deploys a number of OCRv2 contracts and configures them with defaults -func DeployOCRv2Contracts( +// SetupOCRv2Contracts deploys a number of OCRv2 contracts and configures them with defaults +func SetupOCRv2Contracts( l zerolog.Logger, seth *seth.Client, - numberOfContracts int, + ocrContractsConfig ocr.OffChainAggregatorsConfig, linkTokenAddress common.Address, transmitters []string, ocrOptions contracts.OffchainOptions, ) ([]contracts.OffchainAggregatorV2, error) { var ocrInstances []contracts.OffchainAggregatorV2 - for contractCount := 0; contractCount < numberOfContracts; contractCount++ { - ocrInstance, err := contracts.DeployOffchainAggregatorV2( - l, - seth, - linkTokenAddress, - ocrOptions, - ) - if err != nil { - return nil, fmt.Errorf("OCRv2 instance deployment have failed: %w", err) + + if ocrContractsConfig == nil { + return nil, fmt.Errorf("you need to pass non-nil OffChainAggregatorsConfig to setup OCR contracts") + } + + if !ocrContractsConfig.UseExistingOffChainAggregatorsContracts() { + for contractCount := 0; contractCount < ocrContractsConfig.NumberOfContractsToDeploy(); contractCount++ { + ocrInstance, err := contracts.DeployOffchainAggregatorV2( + l, + seth, + linkTokenAddress, + ocrOptions, + ) + if err != nil { + return nil, fmt.Errorf("OCRv2 instance deployment have failed: %w", err) + } + ocrInstances = append(ocrInstances, &ocrInstance) + if (contractCount+1)%ContractDeploymentInterval == 0 { // For large amounts of contract deployments, space things out some + time.Sleep(2 * time.Second) + } } - ocrInstances = append(ocrInstances, &ocrInstance) - if (contractCount+1)%ContractDeploymentInterval == 0 { // For large amounts of contract deployments, space things out some - time.Sleep(2 * time.Second) + } else { + for _, address := range ocrContractsConfig.OffChainAggregatorsContractsAddresses() { + ocrInstance, err := contracts.LoadOffchainAggregatorV2(l, seth, address) + if err != nil { + return nil, fmt.Errorf("OCRv2 instance loading have failed: %w", err) + } + ocrInstances = append(ocrInstances, &ocrInstance) + } + + if !ocrContractsConfig.ConfigureExistingOffChainAggregatorsContracts() { + return ocrInstances, nil } } @@ -844,7 +866,7 @@ func StartNewRound( func DeployOCRContractsForwarderFlow( logger zerolog.Logger, seth *seth.Client, - numberOfContracts int, + ocrContractsConfig ocr.OffChainAggregatorsConfig, linkTokenContractAddress common.Address, workerNodes []contracts.ChainlinkNodeWithKeysAndAddress, forwarderAddresses []common.Address, @@ -865,23 +887,23 @@ func DeployOCRContractsForwarderFlow( return forwarderAddresses, nil } - return deployAnyOCRv1Contracts(logger, seth, numberOfContracts, linkTokenContractAddress, workerNodes, transmitterPayeesFn, transmitterAddressesFn) + return setupAnyOCRv1Contracts(logger, seth, ocrContractsConfig, linkTokenContractAddress, workerNodes, transmitterPayeesFn, transmitterAddressesFn) } -// DeployOCRv1Contracts deploys and funds a certain number of offchain aggregator contracts -func DeployOCRv1Contracts( +// SetupOCRv1Contracts deploys and funds a certain number of offchain aggregator contracts or uses existing ones and returns a slice of contract wrappers. +func SetupOCRv1Contracts( logger zerolog.Logger, seth *seth.Client, - numberOfContracts int, + ocrContractsConfig ocr.OffChainAggregatorsConfig, linkTokenContractAddress common.Address, workerNodes []contracts.ChainlinkNodeWithKeysAndAddress, ) ([]contracts.OffchainAggregator, error) { transmitterPayeesFn := func() (transmitters []string, payees []string, err error) { transmitters = make([]string, 0) payees = make([]string, 0) - for _, node := range workerNodes { + for _, n := range workerNodes { var addr string - addr, err = node.PrimaryEthAddress() + addr, err = n.PrimaryEthAddress() if err != nil { err = fmt.Errorf("error getting node's primary ETH address: %w", err) return @@ -895,8 +917,8 @@ func DeployOCRv1Contracts( transmitterAddressesFn := func() ([]common.Address, error) { transmitterAddresses := make([]common.Address, 0) - for _, node := range workerNodes { - primaryAddress, err := node.PrimaryEthAddress() + for _, n := range workerNodes { + primaryAddress, err := n.PrimaryEthAddress() if err != nil { return nil, err } @@ -906,28 +928,48 @@ func DeployOCRv1Contracts( return transmitterAddresses, nil } - return deployAnyOCRv1Contracts(logger, seth, numberOfContracts, linkTokenContractAddress, workerNodes, transmitterPayeesFn, transmitterAddressesFn) + return setupAnyOCRv1Contracts(logger, seth, ocrContractsConfig, linkTokenContractAddress, workerNodes, transmitterPayeesFn, transmitterAddressesFn) } -func deployAnyOCRv1Contracts( +func setupAnyOCRv1Contracts( logger zerolog.Logger, seth *seth.Client, - numberOfContracts int, + ocrContractsConfig ocr.OffChainAggregatorsConfig, linkTokenContractAddress common.Address, workerNodes []contracts.ChainlinkNodeWithKeysAndAddress, getTransmitterAndPayeesFn func() ([]string, []string, error), getTransmitterAddressesFn func() ([]common.Address, error), ) ([]contracts.OffchainAggregator, error) { - // Deploy contracts var ocrInstances []contracts.OffchainAggregator - for contractCount := 0; contractCount < numberOfContracts; contractCount++ { - ocrInstance, err := contracts.DeployOffchainAggregator(logger, seth, linkTokenContractAddress, contracts.DefaultOffChainAggregatorOptions()) - if err != nil { - return nil, fmt.Errorf("OCR instance deployment have failed: %w", err) + + if ocrContractsConfig == nil { + return nil, fmt.Errorf("you need to pass non-nil OffChainAggregatorsConfig to setup OCR contracts") + } + + if !ocrContractsConfig.UseExistingOffChainAggregatorsContracts() { + // Deploy contracts + for contractCount := 0; contractCount < ocrContractsConfig.NumberOfContractsToDeploy(); contractCount++ { + ocrInstance, err := contracts.DeployOffchainAggregator(logger, seth, linkTokenContractAddress, contracts.DefaultOffChainAggregatorOptions()) + if err != nil { + return nil, fmt.Errorf("OCR instance deployment have failed: %w", err) + } + ocrInstances = append(ocrInstances, &ocrInstance) + if (contractCount+1)%ContractDeploymentInterval == 0 { // For large amounts of contract deployments, space things out some + time.Sleep(2 * time.Second) + } } - ocrInstances = append(ocrInstances, &ocrInstance) - if (contractCount+1)%ContractDeploymentInterval == 0 { // For large amounts of contract deployments, space things out some - time.Sleep(2 * time.Second) + } else { + // Load contract wrappers + for _, address := range ocrContractsConfig.OffChainAggregatorsContractsAddresses() { + ocrInstance, err := contracts.LoadOffChainAggregator(logger, seth, address) + if err != nil { + return nil, fmt.Errorf("OCR instance loading have failed: %w", err) + } + ocrInstances = append(ocrInstances, &ocrInstance) + } + + if !ocrContractsConfig.ConfigureExistingOffChainAggregatorsContracts() { + return ocrInstances, nil } } @@ -974,7 +1016,7 @@ func deployAnyOCRv1Contracts( return ocrInstances, nil } -func privateKeyToAddress(privateKey *ecdsa.PrivateKey) (common.Address, error) { +func PrivateKeyToAddress(privateKey *ecdsa.PrivateKey) (common.Address, error) { publicKey := privateKey.Public() publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) if !ok { @@ -1091,18 +1133,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 err + } + + tx, err := chainClient.Decode(linkInstance.Transfer(chainClient.NewTXOpts(), multicallAddress, toTransferToMultiCallContract)) if err != nil { - return errors.Wrapf(err, "Error transferring LINK to multicall contract") + return err } - balance, err := linkToken.BalanceOf(context.Background(), multicallAddress.Hex()) + 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 @@ -1127,18 +1193,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()) } } @@ -1279,11 +1351,16 @@ func IsOPStackChain(chainID int64) bool { chainID == 11155420 //OPTIMISM SEPOLIA } +func IsArbitrumChain(chainID int64) bool { + return chainID == 42161 || //Arbitrum MAINNET + chainID == 421614 //Arbitrum Sepolia +} + func RandBool() bool { return rand.Intn(2) == 1 } -func ContinuouslyGenerateTXsOnChain(sethClient *seth.Client, stopChannel chan bool, l zerolog.Logger) (bool, error) { +func ContinuouslyGenerateTXsOnChain(sethClient *seth.Client, stopChannel chan bool, wg *sync.WaitGroup, l zerolog.Logger) (bool, error) { counterContract, err := contracts.DeployCounterContract(sethClient) if err != nil { return false, err @@ -1297,6 +1374,10 @@ func ContinuouslyGenerateTXsOnChain(sethClient *seth.Client, stopChannel chan bo select { case <-stopChannel: l.Info().Str("Number of generated transactions on chain", count.String()).Msg("Stopping generating txs on chain. Desired block number reached.") + sleepDuration := time.Second * 10 + l.Info().Str("Waiting for", sleepDuration.String()).Msg("Waiting for transactions to be mined and avoid nonce issues") + time.Sleep(sleepDuration) + wg.Done() return true, nil default: err = counterContract.Increment() @@ -1311,3 +1392,12 @@ func ContinuouslyGenerateTXsOnChain(sethClient *seth.Client, stopChannel chan bo } } } + +func WithinTolerance(a, b, tolerance float64) (bool, float64) { + if a == b { + return true, 0 + } + diff := math.Abs(a - b) + isWithinTolerance := diff < tolerance + return isWithinTolerance, diff +} diff --git a/integration-tests/actions/automation_ocr_helpers.go b/integration-tests/actions/automation_ocr_helpers.go index 9157faf202..fb2fddb351 100644 --- a/integration-tests/actions/automation_ocr_helpers.go +++ b/integration-tests/actions/automation_ocr_helpers.go @@ -2,297 +2,24 @@ package actions //revive:disable:dot-imports import ( - "encoding/json" - "fmt" "math" "math/big" "testing" - "time" + + "github.com/ethereum/go-ethereum/common" + + tt "github.com/smartcontractkit/chainlink/integration-tests/types" "github.com/pkg/errors" "github.com/smartcontractkit/chainlink-testing-framework/seth" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_automation_registry_master_wrapper_2_3" - - "github.com/ethereum/go-ethereum/common" - "github.com/lib/pq" "github.com/stretchr/testify/require" - "gopkg.in/guregu/null.v4" - - ocr2 "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" - ocr3 "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3confighelper" - "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - - ocr2keepers20config "github.com/smartcontractkit/chainlink-automation/pkg/v2/config" - ocr2keepers30config "github.com/smartcontractkit/chainlink-automation/pkg/v3/config" - "github.com/smartcontractkit/chainlink-testing-framework/lib/logging" - "github.com/smartcontractkit/chainlink/integration-tests/client" "github.com/smartcontractkit/chainlink/integration-tests/contracts" "github.com/smartcontractkit/chainlink/integration-tests/contracts/ethereum" - "github.com/smartcontractkit/chainlink/v2/core/services/job" - "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" - "github.com/smartcontractkit/chainlink/v2/core/store/models" ) -func BuildAutoOCR2ConfigVars( - t *testing.T, - chainlinkNodes []*client.ChainlinkK8sClient, - registryConfig contracts.KeeperRegistrySettings, - registrar string, - deltaStage time.Duration, - chainModuleAddress common.Address, - reorgProtectionEnabled bool, - linkToken contracts.LinkToken, - wethToken contracts.WETHToken, - ethUSDFeed contracts.MockETHUSDFeed, - -) (contracts.OCRv2Config, error) { - return BuildAutoOCR2ConfigVarsWithKeyIndex(t, chainlinkNodes, registryConfig, registrar, deltaStage, 0, common.Address{}, chainModuleAddress, reorgProtectionEnabled, linkToken, wethToken, ethUSDFeed) -} - -func BuildAutoOCR2ConfigVarsWithKeyIndex( - t *testing.T, - chainlinkNodes []*client.ChainlinkK8sClient, - registryConfig contracts.KeeperRegistrySettings, - registrar string, - deltaStage time.Duration, - keyIndex int, - registryOwnerAddress common.Address, - chainModuleAddress common.Address, - reorgProtectionEnabled bool, - linkToken contracts.LinkToken, - wethToken contracts.WETHToken, - ethUSDFeed contracts.MockETHUSDFeed, -) (contracts.OCRv2Config, error) { - l := logging.GetTestLogger(t) - S, oracleIdentities, err := GetOracleIdentitiesWithKeyIndex(chainlinkNodes, keyIndex) - if err != nil { - return contracts.OCRv2Config{}, err - } - - var offC []byte - var signerOnchainPublicKeys []types.OnchainPublicKey - var transmitterAccounts []types.Account - var f uint8 - var offchainConfigVersion uint64 - var offchainConfig []byte - - if registryConfig.RegistryVersion == ethereum.RegistryVersion_2_1 || registryConfig.RegistryVersion == ethereum.RegistryVersion_2_2 || registryConfig.RegistryVersion == ethereum.RegistryVersion_2_3 { - offC, err = json.Marshal(ocr2keepers30config.OffchainConfig{ - TargetProbability: "0.999", - TargetInRounds: 1, - PerformLockoutWindow: 3600000, // Intentionally set to be higher than in prod for testing purpose - GasLimitPerReport: 5_300_000, - GasOverheadPerUpkeep: 300_000, - MinConfirmations: 0, - MaxUpkeepBatchSize: 10, - }) - if err != nil { - return contracts.OCRv2Config{}, err - } - - signerOnchainPublicKeys, transmitterAccounts, f, _, offchainConfigVersion, offchainConfig, err = ocr3.ContractSetConfigArgsForTests( - 10*time.Second, // deltaProgress time.Duration, - 15*time.Second, // deltaResend time.Duration, - 500*time.Millisecond, // deltaInitial time.Duration, - 1000*time.Millisecond, // deltaRound time.Duration, - 200*time.Millisecond, // deltaGrace time.Duration, - 300*time.Millisecond, // deltaCertifiedCommitRequest time.Duration - deltaStage, // deltaStage time.Duration, - 24, // rMax uint64, - S, // s []int, - oracleIdentities, // oracles []OracleIdentityExtra, - offC, // reportingPluginConfig []byte, - 20*time.Millisecond, // maxDurationQuery time.Duration, - 20*time.Millisecond, // maxDurationObservation time.Duration, // good to here - 1200*time.Millisecond, // maxDurationShouldAcceptAttestedReport time.Duration, - 20*time.Millisecond, // maxDurationShouldTransmitAcceptedReport time.Duration, - 1, // f int, - nil, // onchainConfig []byte, - ) - if err != nil { - return contracts.OCRv2Config{}, err - } - } else { - offC, err = json.Marshal(ocr2keepers20config.OffchainConfig{ - TargetProbability: "0.999", - TargetInRounds: 1, - PerformLockoutWindow: 3600000, // Intentionally set to be higher than in prod for testing purpose - GasLimitPerReport: 5_300_000, - GasOverheadPerUpkeep: 300_000, - SamplingJobDuration: 3000, - MinConfirmations: 0, - MaxUpkeepBatchSize: 1, - }) - if err != nil { - return contracts.OCRv2Config{}, err - } - - signerOnchainPublicKeys, transmitterAccounts, f, _, offchainConfigVersion, offchainConfig, err = ocr2.ContractSetConfigArgsForTests( - 10*time.Second, // deltaProgress time.Duration, - 15*time.Second, // deltaResend time.Duration, - 3000*time.Millisecond, // deltaRound time.Duration, - 200*time.Millisecond, // deltaGrace time.Duration, - deltaStage, // deltaStage time.Duration, - 24, // rMax uint8, - S, // s []int, - oracleIdentities, // oracles []OracleIdentityExtra, - offC, // reportingPluginConfig []byte, - 20*time.Millisecond, // maxDurationQuery time.Duration, - 20*time.Millisecond, // maxDurationObservation time.Duration, - 1200*time.Millisecond, // maxDurationReport time.Duration, - 20*time.Millisecond, // maxDurationShouldAcceptFinalizedReport time.Duration, - 20*time.Millisecond, // maxDurationShouldTransmitAcceptedReport time.Duration, - 1, // f int, - nil, // onchainConfig []byte, - ) - if err != nil { - return contracts.OCRv2Config{}, err - } - } - - var signers []common.Address - for _, signer := range signerOnchainPublicKeys { - require.Equal(t, 20, len(signer), "OnChainPublicKey '%v' has wrong length for address", signer) - signers = append(signers, common.BytesToAddress(signer)) - } - - var transmitters []common.Address - for _, transmitter := range transmitterAccounts { - require.True(t, common.IsHexAddress(string(transmitter)), "TransmitAccount '%s' is not a valid Ethereum address", string(transmitter)) - transmitters = append(transmitters, common.HexToAddress(string(transmitter))) - } - - ocrConfig := contracts.OCRv2Config{ - Signers: signers, - Transmitters: transmitters, - F: f, - OffchainConfigVersion: offchainConfigVersion, - OffchainConfig: offchainConfig, - } - - if registryConfig.RegistryVersion == ethereum.RegistryVersion_2_0 { - ocrConfig.OnchainConfig = registryConfig.Encode20OnchainConfig(registrar) - } else if registryConfig.RegistryVersion == ethereum.RegistryVersion_2_1 { - ocrConfig.TypedOnchainConfig21 = registryConfig.Create21OnchainConfig(registrar, registryOwnerAddress) - } else if registryConfig.RegistryVersion == ethereum.RegistryVersion_2_2 { - ocrConfig.TypedOnchainConfig22 = registryConfig.Create22OnchainConfig(registrar, registryOwnerAddress, chainModuleAddress, reorgProtectionEnabled) - } else if registryConfig.RegistryVersion == ethereum.RegistryVersion_2_3 { - ocrConfig.TypedOnchainConfig23 = registryConfig.Create23OnchainConfig(registrar, registryOwnerAddress, chainModuleAddress, reorgProtectionEnabled) - ocrConfig.BillingTokens = []common.Address{ - common.HexToAddress(linkToken.Address()), - common.HexToAddress(wethToken.Address()), - } - - ocrConfig.BillingConfigs = []i_automation_registry_master_wrapper_2_3.AutomationRegistryBase23BillingConfig{ - { - GasFeePPB: 100, - FlatFeeMilliCents: big.NewInt(500), - PriceFeed: common.HexToAddress(ethUSDFeed.Address()), // ETH/USD feed and LINK/USD feed are the same - Decimals: 18, - FallbackPrice: big.NewInt(1000), - MinSpend: big.NewInt(200), - }, - { - GasFeePPB: 100, - FlatFeeMilliCents: big.NewInt(500), - PriceFeed: common.HexToAddress(ethUSDFeed.Address()), // ETH/USD feed and LINK/USD feed are the same - Decimals: 18, - FallbackPrice: big.NewInt(1000), - MinSpend: big.NewInt(200), - }, - } - } - - l.Info().Msg("Done building OCR config") - return ocrConfig, nil -} - -// CreateOCRKeeperJobs bootstraps the first node and to the other nodes sends ocr jobs -func CreateOCRKeeperJobs( - t *testing.T, - chainlinkNodes []*client.ChainlinkK8sClient, - registryAddr string, - chainID int64, - keyIndex int, - registryVersion ethereum.KeeperRegistryVersion, -) { - l := logging.GetTestLogger(t) - bootstrapNode := chainlinkNodes[0] - bootstrapP2PIds, err := bootstrapNode.MustReadP2PKeys() - require.NoError(t, err, "Shouldn't fail reading P2P keys from bootstrap node") - bootstrapP2PId := bootstrapP2PIds.Data[0].Attributes.PeerID - - var contractVersion string - if registryVersion == ethereum.RegistryVersion_2_2 || registryVersion == ethereum.RegistryVersion_2_3 { - contractVersion = "v2.1+" - } else if registryVersion == ethereum.RegistryVersion_2_1 { - contractVersion = "v2.1" - } else if registryVersion == ethereum.RegistryVersion_2_0 { - contractVersion = "v2.0" - } else { - require.FailNow(t, fmt.Sprintf("v2.0, v2.1, v2.2 and v2.3 are the only supported versions, but got something else: %v (iota)", registryVersion)) - } - - bootstrapSpec := &client.OCR2TaskJobSpec{ - Name: "ocr2 bootstrap node " + registryAddr, - JobType: "bootstrap", - OCR2OracleSpec: job.OCR2OracleSpec{ - ContractID: registryAddr, - Relay: "evm", - RelayConfig: map[string]interface{}{ - "chainID": int(chainID), - }, - ContractConfigTrackerPollInterval: *models.NewInterval(time.Second * 15), - }, - } - _, err = bootstrapNode.MustCreateJob(bootstrapSpec) - require.NoError(t, err, "Shouldn't fail creating bootstrap job on bootstrap node") - // TODO: Use service name returned by chainlink-env once that is available - P2Pv2Bootstrapper := fmt.Sprintf("%s@%s-node-1:%d", bootstrapP2PId, bootstrapNode.Name(), 6690) - - for nodeIndex := 1; nodeIndex < len(chainlinkNodes); nodeIndex++ { - nodeTransmitterAddress, err := chainlinkNodes[nodeIndex].EthAddresses() - require.NoError(t, err, "Shouldn't fail getting primary ETH address from OCR node %d", nodeIndex+1) - nodeOCRKeys, err := chainlinkNodes[nodeIndex].MustReadOCR2Keys() - require.NoError(t, err, "Shouldn't fail getting OCR keys from OCR node %d", nodeIndex+1) - var nodeOCRKeyId []string - for _, key := range nodeOCRKeys.Data { - if key.Attributes.ChainType == string(chaintype.EVM) { - nodeOCRKeyId = append(nodeOCRKeyId, key.ID) - break - } - } - - autoOCR2JobSpec := client.OCR2TaskJobSpec{ - Name: "ocr2 " + registryAddr, - JobType: "offchainreporting2", - OCR2OracleSpec: job.OCR2OracleSpec{ - PluginType: "ocr2automation", - Relay: "evm", - RelayConfig: map[string]interface{}{ - "chainID": int(chainID), - }, - PluginConfig: map[string]interface{}{ - "mercuryCredentialName": "\"cred1\"", - "contractVersion": "\"" + contractVersion + "\"", - }, - ContractConfigTrackerPollInterval: *models.NewInterval(time.Second * 15), - ContractID: registryAddr, // registryAddr - OCRKeyBundleID: null.StringFrom(nodeOCRKeyId[0]), // get node ocr2config.ID - TransmitterID: null.StringFrom(nodeTransmitterAddress[keyIndex]), // node addr - P2PV2Bootstrappers: pq.StringArray{P2Pv2Bootstrapper}, // bootstrap node key and address @bootstrap:8000 - }, - } - - _, err = chainlinkNodes[nodeIndex].MustCreateJob(&autoOCR2JobSpec) - require.NoError(t, err, "Shouldn't fail creating OCR Task job on OCR node %d err: %+v", nodeIndex+1, err) - } - l.Info().Msg("Done creating OCR automation jobs") -} - // DeployAutoOCRRegistryAndRegistrar registry and registrar func DeployAutoOCRRegistryAndRegistrar( t *testing.T, @@ -309,9 +36,9 @@ func DeployAutoOCRRegistryAndRegistrar( return registry, registrar } -// DeployConsumers deploys and registers keeper consumers. If ephemeral addresses are enabled, it will deploy and register the consumers from ephemeral addresses, but each upkpeep will be registered with root key address as the admin. Which means +// DeployLegacyConsumers deploys and registers keeper consumers. If ephemeral addresses are enabled, it will deploy and register the consumers from ephemeral addresses, but each upkpeep will be registered with root key address as the admin. Which means // that functions like setting upkeep configuration, pausing, unpausing, etc. will be done by the root key address. It deploys multicall contract and sends link funds to each deployment address. -func DeployConsumers(t *testing.T, chainClient *seth.Client, registry contracts.KeeperRegistry, registrar contracts.KeeperRegistrar, linkToken contracts.LinkToken, numberOfUpkeeps int, linkFundsForEachUpkeep *big.Int, upkeepGasLimit uint32, isLogTrigger bool, isMercury bool, isBillingTokenNative bool, wethToken contracts.WETHToken) ([]contracts.KeeperConsumer, []*big.Int) { +func DeployLegacyConsumers(t *testing.T, chainClient *seth.Client, registry contracts.KeeperRegistry, registrar contracts.KeeperRegistrar, linkToken contracts.LinkToken, numberOfUpkeeps int, linkFundsForEachUpkeep *big.Int, upkeepGasLimit uint32, isLogTrigger bool, isMercury bool, isBillingTokenNative bool, wethToken contracts.WETHToken) ([]contracts.KeeperConsumer, []*big.Int) { // Fund deployers with LINK, no need to do this for Native token if !isBillingTokenNative { err := DeployMultiCallAndFundDeploymentAddresses(chainClient, linkToken, numberOfUpkeeps, linkFundsForEachUpkeep) @@ -331,6 +58,28 @@ func DeployConsumers(t *testing.T, chainClient *seth.Client, registry contracts. return upkeeps, upkeepIds } +// DeployConsumers deploys and registers keeper consumers. If ephemeral addresses are enabled, it will deploy and register the consumers from ephemeral addresses, but each upkpeep will be registered with root key address as the admin. Which means +// that functions like setting upkeep configuration, pausing, unpausing, etc. will be done by the root key address. It deploys multicall contract and sends link funds to each deployment address. +func DeployConsumers(t *testing.T, chainClient *seth.Client, registry contracts.KeeperRegistry, registrar contracts.KeeperRegistrar, linkToken contracts.LinkToken, numberOfUpkeeps int, linkFundsForEachUpkeep *big.Int, upkeepGasLimit uint32, isLogTrigger bool, isMercury bool, isBillingTokenNative bool, wethToken contracts.WETHToken, config tt.AutomationTestConfig) ([]contracts.KeeperConsumer, []*big.Int) { + // Fund deployers with LINK, no need to do this for Native token + if !isBillingTokenNative { + err := SetupMultiCallAndFundDeploymentAddresses(chainClient, linkToken, numberOfUpkeeps, linkFundsForEachUpkeep, config) + require.NoError(t, err, "Sending link funds to deployment addresses shouldn't fail") + } + + upkeeps := SetupKeeperConsumers(t, chainClient, numberOfUpkeeps, isLogTrigger, isMercury, config) + require.Equal(t, numberOfUpkeeps, len(upkeeps), "Number of upkeeps should match") + var upkeepsAddresses []string + for _, upkeep := range upkeeps { + upkeepsAddresses = append(upkeepsAddresses, upkeep.Address()) + } + upkeepIds := RegisterUpkeepContracts( + t, chainClient, linkToken, linkFundsForEachUpkeep, upkeepGasLimit, registry, registrar, numberOfUpkeeps, upkeepsAddresses, isLogTrigger, isMercury, isBillingTokenNative, wethToken, + ) + require.Equal(t, numberOfUpkeeps, len(upkeepIds), "Number of upkeepIds should match") + return upkeeps, upkeepIds +} + // DeployPerformanceConsumers deploys and registers keeper performance consumers. If ephemeral addresses are enabled, it will deploy and register the consumers from ephemeral addresses, but each upkeep will be registered with root key address as the admin. // that functions like setting upkeep configuration, pausing, unpausing, etc. will be done by the root key address. It deploys multicall contract and sends link funds to each deployment address. func DeployPerformanceConsumers( @@ -346,12 +95,13 @@ func DeployPerformanceConsumers( blockInterval, // Interval of blocks that upkeeps are expected to be performed checkGasToBurn, // How much gas should be burned on checkUpkeep() calls performGasToBurn int64, // How much gas should be burned on performUpkeep() calls + config tt.AutomationTestConfig, ) ([]contracts.KeeperConsumerPerformance, []*big.Int) { upkeeps := DeployKeeperConsumersPerformance( t, chainClient, numberOfUpkeeps, blockRange, blockInterval, checkGasToBurn, performGasToBurn, ) - err := DeployMultiCallAndFundDeploymentAddresses(chainClient, linkToken, numberOfUpkeeps, linkFundsForEachUpkeep) + err := SetupMultiCallAndFundDeploymentAddresses(chainClient, linkToken, numberOfUpkeeps, linkFundsForEachUpkeep, config) require.NoError(t, err, "Sending link funds to deployment addresses shouldn't fail") var upkeepsAddresses []string @@ -374,10 +124,11 @@ func DeployPerformDataCheckerConsumers( linkFundsForEachUpkeep *big.Int, upkeepGasLimit uint32, expectedData []byte, + config tt.AutomationTestConfig, ) ([]contracts.KeeperPerformDataChecker, []*big.Int) { upkeeps := DeployPerformDataChecker(t, chainClient, numberOfUpkeeps, expectedData) - err := DeployMultiCallAndFundDeploymentAddresses(chainClient, linkToken, numberOfUpkeeps, linkFundsForEachUpkeep) + err := SetupMultiCallAndFundDeploymentAddresses(chainClient, linkToken, numberOfUpkeeps, linkFundsForEachUpkeep, config) require.NoError(t, err, "Sending link funds to deployment addresses shouldn't fail") var upkeepsAddresses []string @@ -388,6 +139,45 @@ func DeployPerformDataCheckerConsumers( return upkeeps, upkeepIds } +func SetupMultiCallAddress(chainClient *seth.Client, testConfig tt.AutomationTestConfig) (common.Address, error) { + if testConfig.GetAutomationConfig().UseExistingMultiCallContract() { + multiCallAddress, err := testConfig.GetAutomationConfig().MultiCallContractAddress() + if err != nil { + return common.Address{}, errors.Wrap(err, "Error getting existing multicall contract address") + } + return multiCallAddress, nil + } + + multicallAddress, err := contracts.DeployMultiCallContract(chainClient) + if err != nil { + return common.Address{}, errors.Wrap(err, "Error deploying multicall contract") + } + return multicallAddress, nil +} + +// SetupMultiCallAndFundDeploymentAddresses setups multicall contract and sends link funds to each deployment address +func SetupMultiCallAndFundDeploymentAddresses( + chainClient *seth.Client, + linkToken contracts.LinkToken, + numberOfUpkeeps int, + linkFundsForEachUpkeep *big.Int, + testConfig tt.AutomationTestConfig, +) error { + concurrency, err := GetAndAssertCorrectConcurrency(chainClient, 1) + if err != nil { + return err + } + + operationsPerAddress := numberOfUpkeeps / concurrency + + multicallAddress, err := SetupMultiCallAddress(chainClient, testConfig) + if err != nil { + return errors.Wrap(err, "Error deploying multicall contract") + } + + return SendLinkFundsToDeploymentAddresses(chainClient, concurrency, numberOfUpkeeps, operationsPerAddress, multicallAddress, linkFundsForEachUpkeep, linkToken) +} + // DeployMultiCallAndFundDeploymentAddresses deploys multicall contract and sends link funds to each deployment address func DeployMultiCallAndFundDeploymentAddresses( chainClient *seth.Client, @@ -439,7 +229,7 @@ func deployRegistry( wethToken contracts.WETHToken, ethUSDFeed contracts.MockETHUSDFeed, ) contracts.KeeperRegistry { - ef, err := contracts.DeployMockETHLINKFeed(client, big.NewInt(2e18)) + ef, err := contracts.DeployMockLINKETHFeed(client, big.NewInt(2e18)) require.NoError(t, err, "Deploying mock ETH-Link feed shouldn't fail") gf, err := contracts.DeployMockGASFeed(client, big.NewInt(2e11)) require.NoError(t, err, "Deploying mock gas feed shouldn't fail") diff --git a/integration-tests/actions/automation_ocr_helpers_local.go b/integration-tests/actions/automation_ocr_helpers_local.go index d513f1875a..e4d5ad70ca 100644 --- a/integration-tests/actions/automation_ocr_helpers_local.go +++ b/integration-tests/actions/automation_ocr_helpers_local.go @@ -28,7 +28,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/store/models" ) -func AutomationDefaultRegistryConfig(c tc.AutomationTestConfig) contracts.KeeperRegistrySettings { +func ReadRegistryConfig(c tc.AutomationTestConfig) contracts.KeeperRegistrySettings { registrySettings := c.GetAutomationConfig().AutomationConfig.RegistrySettings return contracts.KeeperRegistrySettings{ PaymentPremiumPPB: *registrySettings.PaymentPremiumPPB, @@ -40,12 +40,49 @@ func AutomationDefaultRegistryConfig(c tc.AutomationTestConfig) contracts.Keeper MaxPerformGas: *registrySettings.MaxPerformGas, FallbackGasPrice: registrySettings.FallbackGasPrice, FallbackLinkPrice: registrySettings.FallbackLinkPrice, + FallbackNativePrice: registrySettings.FallbackNativePrice, MaxCheckDataSize: *registrySettings.MaxCheckDataSize, MaxPerformDataSize: *registrySettings.MaxPerformDataSize, MaxRevertDataSize: *registrySettings.MaxRevertDataSize, } } +func ReadPluginConfig(c tc.AutomationTestConfig) ocr2keepers30config.OffchainConfig { + plCfg := c.GetAutomationConfig().AutomationConfig.PluginConfig + return ocr2keepers30config.OffchainConfig{ + TargetProbability: *plCfg.TargetProbability, + TargetInRounds: *plCfg.TargetInRounds, + PerformLockoutWindow: *plCfg.PerformLockoutWindow, + GasLimitPerReport: *plCfg.GasLimitPerReport, + GasOverheadPerUpkeep: *plCfg.GasOverheadPerUpkeep, + MinConfirmations: *plCfg.MinConfirmations, + MaxUpkeepBatchSize: *plCfg.MaxUpkeepBatchSize, + LogProviderConfig: ocr2keepers30config.LogProviderConfig{ + BlockRate: *plCfg.LogProviderConfig.BlockRate, + LogLimit: *plCfg.LogProviderConfig.LogLimit, + }, + } +} + +func ReadPublicConfig(c tc.AutomationTestConfig) ocr3.PublicConfig { + pubCfg := c.GetAutomationConfig().AutomationConfig.PublicConfig + return ocr3.PublicConfig{ + DeltaProgress: *pubCfg.DeltaProgress, + DeltaResend: *pubCfg.DeltaResend, + DeltaInitial: *pubCfg.DeltaInitial, + DeltaRound: *pubCfg.DeltaRound, + DeltaGrace: *pubCfg.DeltaGrace, + DeltaCertifiedCommitRequest: *pubCfg.DeltaCertifiedCommitRequest, + DeltaStage: *pubCfg.DeltaStage, + RMax: *pubCfg.RMax, + MaxDurationQuery: *pubCfg.MaxDurationQuery, + MaxDurationObservation: *pubCfg.MaxDurationObservation, + MaxDurationShouldAcceptAttestedReport: *pubCfg.MaxDurationShouldAcceptAttestedReport, + MaxDurationShouldTransmitAcceptedReport: *pubCfg.MaxDurationShouldTransmitAcceptedReport, + F: *pubCfg.F, + } +} + func BuildAutoOCR2ConfigVarsLocal( l zerolog.Logger, chainlinkNodes []*client.ChainlinkClient, diff --git a/integration-tests/actions/automationv2/actions.go b/integration-tests/actions/automationv2/actions.go index d3504b8fb1..9b3013f778 100644 --- a/integration-tests/actions/automationv2/actions.go +++ b/integration-tests/actions/automationv2/actions.go @@ -12,6 +12,8 @@ import ( "testing" "time" + tt "github.com/smartcontractkit/chainlink/integration-tests/types" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/lib/pq" @@ -45,7 +47,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/store/models" ctf_concurrency "github.com/smartcontractkit/chainlink-testing-framework/lib/concurrency" - ctfTestEnv "github.com/smartcontractkit/chainlink-testing-framework/lib/docker/test_env" + ctftestenv "github.com/smartcontractkit/chainlink-testing-framework/lib/docker/test_env" "github.com/smartcontractkit/chainlink-testing-framework/lib/logging" ) @@ -61,10 +63,13 @@ type NodeDetails struct { type AutomationTest struct { ChainClient *seth.Client + TestConfig tt.AutomationTestConfig + LinkToken contracts.LinkToken Transcoder contracts.UpkeepTranscoder - EthLinkFeed contracts.MockETHLINKFeed - EthUSDFeed contracts.MockETHUSDFeed + LINKETHFeed contracts.MockLINKETHFeed + ETHUSDFeed contracts.MockETHUSDFeed + LINKUSDFeed contracts.MockETHUSDFeed WETHToken contracts.WETHToken GasFeed contracts.MockGasFeed Registry contracts.KeeperRegistry @@ -109,9 +114,11 @@ func NewAutomationTestK8s( l zerolog.Logger, chainClient *seth.Client, chainlinkNodes []*client.ChainlinkK8sClient, + config tt.AutomationTestConfig, ) *AutomationTest { return &AutomationTest{ ChainClient: chainClient, + TestConfig: config, ChainlinkNodesk8s: chainlinkNodes, IsOnk8s: true, TransmitterKeyIndex: 0, @@ -125,9 +132,11 @@ func NewAutomationTestDocker( l zerolog.Logger, chainClient *seth.Client, chainlinkNodes []*client.ChainlinkClient, + config tt.AutomationTestConfig, ) *AutomationTest { return &AutomationTest{ ChainClient: chainClient, + TestConfig: config, ChainlinkNodes: chainlinkNodes, IsOnk8s: false, TransmitterKeyIndex: 0, @@ -172,6 +181,7 @@ func (a *AutomationTest) LoadLINK(address string) error { return err } a.LinkToken = linkToken + a.Logger.Info().Str("LINK Token Address", a.LinkToken.Address()).Msg("Successfully loaded LINK Token") return nil } @@ -190,34 +200,35 @@ func (a *AutomationTest) LoadTranscoder(address string) error { return err } a.Transcoder = transcoder + a.Logger.Info().Str("Transcoder Address", a.Transcoder.Address()).Msg("Successfully loaded Transcoder") return nil } -func (a *AutomationTest) DeployEthLinkFeed() error { - ethLinkFeed, err := contracts.DeployMockETHLINKFeed(a.ChainClient, a.RegistrySettings.FallbackLinkPrice) +func (a *AutomationTest) DeployLinkEthFeed() error { + ethLinkFeed, err := contracts.DeployMockLINKETHFeed(a.ChainClient, a.RegistrySettings.FallbackLinkPrice) if err != nil { return err } - a.EthLinkFeed = ethLinkFeed + a.LINKETHFeed = ethLinkFeed return nil } -func (a *AutomationTest) LoadEthLinkFeed(address string) error { - ethLinkFeed, err := contracts.LoadMockETHLINKFeed(a.ChainClient, common.HexToAddress(address)) +func (a *AutomationTest) LoadLinkEthFeed(address string) error { + ethLinkFeed, err := contracts.LoadMockLINKETHFeed(a.ChainClient, common.HexToAddress(address)) if err != nil { return err } - a.EthLinkFeed = ethLinkFeed + a.LINKETHFeed = ethLinkFeed + a.Logger.Info().Str("LINK/ETH Feed Address", a.LINKETHFeed.Address()).Msg("Successfully loaded LINK/ETH Feed") return nil } func (a *AutomationTest) DeployEthUSDFeed() error { - // FallbackLinkPrice and FallbackETHPrice are the same ethUSDFeed, err := contracts.DeployMockETHUSDFeed(a.ChainClient, a.RegistrySettings.FallbackLinkPrice) if err != nil { return err } - a.EthUSDFeed = ethUSDFeed + a.ETHUSDFeed = ethUSDFeed return nil } @@ -226,7 +237,27 @@ func (a *AutomationTest) LoadEthUSDFeed(address string) error { if err != nil { return err } - a.EthUSDFeed = ethUSDFeed + a.ETHUSDFeed = ethUSDFeed + a.Logger.Info().Str("ETH/USD Feed Address", a.ETHUSDFeed.Address()).Msg("Successfully loaded ETH/USD Feed") + return nil +} + +func (a *AutomationTest) DeployLinkUSDFeed() error { + linkUSDFeed, err := contracts.DeployMockETHUSDFeed(a.ChainClient, a.RegistrySettings.FallbackLinkPrice) + if err != nil { + return err + } + a.LINKUSDFeed = linkUSDFeed + return nil +} + +func (a *AutomationTest) LoadLinkUSDFeed(address string) error { + linkUSDFeed, err := contracts.LoadMockETHUSDFeed(a.ChainClient, common.HexToAddress(address)) + if err != nil { + return err + } + a.LINKUSDFeed = linkUSDFeed + a.Logger.Info().Str("LINK/USD Feed Address", a.LINKUSDFeed.Address()).Msg("Successfully loaded LINK/USD Feed") return nil } @@ -245,6 +276,7 @@ func (a *AutomationTest) LoadWETH(address string) error { return err } a.WETHToken = wethToken + a.Logger.Info().Str("WETH Token Address", a.WETHToken.Address()).Msg("Successfully loaded WETH Token") return nil } @@ -263,6 +295,7 @@ func (a *AutomationTest) LoadEthGasFeed(address string) error { return err } a.GasFeed = gasFeed + a.Logger.Info().Str("Gas Feed Address", a.GasFeed.Address()).Msg("Successfully loaded Gas Feed") return nil } @@ -270,13 +303,13 @@ func (a *AutomationTest) DeployRegistry() error { registryOpts := &contracts.KeeperRegistryOpts{ RegistryVersion: a.RegistrySettings.RegistryVersion, LinkAddr: a.LinkToken.Address(), - ETHFeedAddr: a.EthLinkFeed.Address(), + ETHFeedAddr: a.LINKETHFeed.Address(), GasFeedAddr: a.GasFeed.Address(), TranscoderAddr: a.Transcoder.Address(), RegistrarAddr: utils.ZeroAddress.Hex(), Settings: a.RegistrySettings, - LinkUSDFeedAddr: a.EthUSDFeed.Address(), - NativeUSDFeedAddr: a.EthUSDFeed.Address(), + LinkUSDFeedAddr: a.ETHUSDFeed.Address(), + NativeUSDFeedAddr: a.LINKUSDFeed.Address(), WrappedNativeAddr: a.WETHToken.Address(), } registry, err := contracts.DeployKeeperRegistry(a.ChainClient, registryOpts) @@ -287,12 +320,13 @@ func (a *AutomationTest) DeployRegistry() error { return nil } -func (a *AutomationTest) LoadRegistry(address string) error { - registry, err := contracts.LoadKeeperRegistry(a.Logger, a.ChainClient, common.HexToAddress(address), a.RegistrySettings.RegistryVersion) +func (a *AutomationTest) LoadRegistry(registryAddress, chainModuleAddress string) error { + registry, err := contracts.LoadKeeperRegistry(a.Logger, a.ChainClient, common.HexToAddress(registryAddress), a.RegistrySettings.RegistryVersion, common.HexToAddress(chainModuleAddress)) if err != nil { return err } a.Registry = registry + a.Logger.Info().Str("ChainModule Address", chainModuleAddress).Str("Registry Address", a.Registry.Address()).Msg("Successfully loaded Registry") return nil } @@ -319,6 +353,7 @@ func (a *AutomationTest) LoadRegistrar(address string) error { if err != nil { return err } + a.Logger.Info().Str("Registrar Address", registrar.Address()).Msg("Successfully loaded Registrar") a.Registrar = registrar return nil } @@ -564,7 +599,7 @@ func (a *AutomationTest) SetConfigOnRegistry() error { { GasFeePPB: 100, FlatFeeMilliCents: big.NewInt(500), - PriceFeed: common.HexToAddress(a.EthUSDFeed.Address()), // ETH/USD feed and LINK/USD feed are the same + PriceFeed: common.HexToAddress(a.ETHUSDFeed.Address()), Decimals: 18, FallbackPrice: big.NewInt(1000), MinSpend: big.NewInt(200), @@ -572,13 +607,14 @@ func (a *AutomationTest) SetConfigOnRegistry() error { { GasFeePPB: 100, FlatFeeMilliCents: big.NewInt(500), - PriceFeed: common.HexToAddress(a.EthUSDFeed.Address()), // ETH/USD feed and LINK/USD feed are the same + PriceFeed: common.HexToAddress(a.LINKUSDFeed.Address()), Decimals: 18, FallbackPrice: big.NewInt(1000), MinSpend: big.NewInt(200), }, } } + a.Logger.Debug().Interface("ocrConfig", ocrConfig).Msg("Setting OCR3 config") err = a.Registry.SetConfigTypeSafe(ocrConfig) if err != nil { return errors.Join(err, fmt.Errorf("failed to set config on registry")) @@ -828,7 +864,7 @@ func (a *AutomationTest) AddJobsAndSetConfig(t *testing.T) { l.Info().Str("Registry Address", a.Registry.Address()).Msg("Successfully setConfig on registry") } -func (a *AutomationTest) SetupMercuryMock(t *testing.T, imposters []ctfTestEnv.KillgraveImposter) { +func (a *AutomationTest) SetupMercuryMock(t *testing.T, imposters []ctftestenv.KillgraveImposter) { if a.IsOnk8s { t.Error("mercury mock is not supported on k8s") } @@ -842,58 +878,118 @@ func (a *AutomationTest) SetupMercuryMock(t *testing.T, imposters []ctfTestEnv.K } func (a *AutomationTest) SetupAutomationDeployment(t *testing.T) { + a.setupDeployment(t, true) +} + +func (a *AutomationTest) SetupAutomationDeploymentWithoutJobs(t *testing.T) { + a.setupDeployment(t, false) +} + +func (a *AutomationTest) setupDeployment(t *testing.T, addJobs bool) { l := logging.GetTestLogger(t) err := a.CollectNodeDetails() require.NoError(t, err, "Error collecting node details") l.Info().Msg("Collected Node Details") l.Debug().Interface("Node Details", a.NodeDetails).Msg("Node Details") - err = a.DeployLINK() - require.NoError(t, err, "Error deploying link token contract") + if a.TestConfig.GetAutomationConfig().UseExistingLinkTokenContract() { + linkAddress, err := a.TestConfig.GetAutomationConfig().LinkTokenContractAddress() + require.NoError(t, err, "Error getting link token contract address") + err = a.LoadLINK(linkAddress.String()) + require.NoError(t, err, "Error loading link token contract") + } else { + err = a.DeployLINK() + require.NoError(t, err, "Error deploying link token contract") + } - err = a.DeployWETH() - require.NoError(t, err, "Error deploying weth token contract") + if a.TestConfig.GetAutomationConfig().UseExistingWethContract() { + wethAddress, err := a.TestConfig.GetAutomationConfig().WethContractAddress() + require.NoError(t, err, "Error getting weth token contract address") + err = a.LoadWETH(wethAddress.String()) + require.NoError(t, err, "Error loading weth token contract") + } else { + err = a.DeployWETH() + require.NoError(t, err, "Error deploying weth token contract") + } - err = a.DeployEthLinkFeed() - require.NoError(t, err, "Error deploying eth link feed contract") - err = a.DeployGasFeed() - require.NoError(t, err, "Error deploying gas feed contract") + if a.TestConfig.GetAutomationConfig().UseExistingLinkEthFeedContract() { + linkEthFeedAddress, err := a.TestConfig.GetAutomationConfig().LinkEthFeedContractAddress() + require.NoError(t, err, "Error getting link eth feed contract address") + err = a.LoadLinkEthFeed(linkEthFeedAddress.String()) + require.NoError(t, err, "Error loading link eth feed contract") + } else { + err = a.DeployLinkEthFeed() + require.NoError(t, err, "Error deploying link eth feed contract") + } - err = a.DeployEthUSDFeed() - require.NoError(t, err, "Error deploying eth usd feed contract") + if a.TestConfig.GetAutomationConfig().UseExistingEthGasFeedContract() { + gasFeedAddress, err := a.TestConfig.GetAutomationConfig().EthGasFeedContractAddress() + require.NoError(t, err, "Error getting gas feed contract address") + err = a.LoadEthGasFeed(gasFeedAddress.String()) + require.NoError(t, err, "Error loading gas feed contract") + } else { + err = a.DeployGasFeed() + require.NoError(t, err, "Error deploying gas feed contract") + } - err = a.DeployTranscoder() - require.NoError(t, err, "Error deploying transcoder contract") + if a.TestConfig.GetAutomationConfig().UseExistingEthUSDFeedContract() { + ethUsdFeedAddress, err := a.TestConfig.GetAutomationConfig().EthUSDFeedContractAddress() + require.NoError(t, err, "Error getting eth usd feed contract address") + err = a.LoadEthUSDFeed(ethUsdFeedAddress.String()) + require.NoError(t, err, "Error loading eth usd feed contract") + } else { + err = a.DeployEthUSDFeed() + require.NoError(t, err, "Error deploying eth usd feed contract") + } - err = a.DeployRegistry() - require.NoError(t, err, "Error deploying registry contract") - err = a.DeployRegistrar() - require.NoError(t, err, "Error deploying registrar contract") + if a.TestConfig.GetAutomationConfig().UseExistingLinkUSDFeedContract() { + linkUsdFeedAddress, err := a.TestConfig.GetAutomationConfig().LinkUSDFeedContractAddress() + require.NoError(t, err, "Error getting link usd feed contract address") + err = a.LoadLinkUSDFeed(linkUsdFeedAddress.String()) + require.NoError(t, err, "Error loading link usd feed contract") + } else { + err = a.DeployLinkUSDFeed() + require.NoError(t, err, "Error deploying link usd feed contract") + } - a.AddJobsAndSetConfig(t) -} + if a.TestConfig.GetAutomationConfig().UseExistingTranscoderContract() { + transcoderAddress, err := a.TestConfig.GetAutomationConfig().TranscoderContractAddress() + require.NoError(t, err, "Error getting transcoder contract address") + err = a.LoadTranscoder(transcoderAddress.String()) + require.NoError(t, err, "Error loading transcoder contract") + } else { + err = a.DeployTranscoder() + require.NoError(t, err, "Error deploying transcoder contract") + } + + if a.TestConfig.GetAutomationConfig().UseExistingRegistryContract() { + chainModuleAddress, err := a.TestConfig.GetAutomationConfig().ChainModuleContractAddress() + require.NoError(t, err, "Error getting chain module contract address") + registryAddress, err := a.TestConfig.GetAutomationConfig().RegistryContractAddress() + require.NoError(t, err, "Error getting registry contract address") + err = a.LoadRegistry(registryAddress.String(), chainModuleAddress.String()) + require.NoError(t, err, "Error loading registry contract") + if a.Registry.RegistryOwnerAddress().String() != a.ChainClient.MustGetRootKeyAddress().String() { + l.Debug().Str("RootKeyAddress", a.ChainClient.MustGetRootKeyAddress().String()).Str("Registry Owner Address", a.Registry.RegistryOwnerAddress().String()).Msg("Registry owner address is not the root key address") + t.Error("Registry owner address is not the root key address") + t.FailNow() + } + } else { + err = a.DeployRegistry() + require.NoError(t, err, "Error deploying registry contract") + } -func (a *AutomationTest) LoadAutomationDeployment(t *testing.T, linkTokenAddress, - ethLinkFeedAddress, gasFeedAddress, transcoderAddress, registryAddress, registrarAddress string) { - l := logging.GetTestLogger(t) - err := a.CollectNodeDetails() - require.NoError(t, err, "Error collecting node details") - l.Info().Msg("Collected Node Details") - l.Debug().Interface("Node Details", a.NodeDetails).Msg("Node Details") + if a.TestConfig.GetAutomationConfig().UseExistingRegistrarContract() { + registrarAddress, err := a.TestConfig.GetAutomationConfig().RegistrarContractAddress() + require.NoError(t, err, "Error getting registrar contract address") + err = a.LoadRegistrar(registrarAddress.String()) + require.NoError(t, err, "Error loading registrar contract") + } else { + err = a.DeployRegistrar() + require.NoError(t, err, "Error deploying registrar contract") + } - err = a.LoadLINK(linkTokenAddress) - require.NoError(t, err, "Error loading link token contract") - - err = a.LoadEthLinkFeed(ethLinkFeedAddress) - require.NoError(t, err, "Error loading eth link feed contract") - err = a.LoadEthGasFeed(gasFeedAddress) - require.NoError(t, err, "Error loading gas feed contract") - err = a.LoadTranscoder(transcoderAddress) - require.NoError(t, err, "Error loading transcoder contract") - err = a.LoadRegistry(registryAddress) - require.NoError(t, err, "Error loading registry contract") - err = a.LoadRegistrar(registrarAddress) - require.NoError(t, err, "Error loading registrar contract") - - a.AddJobsAndSetConfig(t) + if addJobs { + a.AddJobsAndSetConfig(t) + } } diff --git a/integration-tests/actions/contracts.go b/integration-tests/actions/contracts.go new file mode 100644 index 0000000000..1a50c4d7ba --- /dev/null +++ b/integration-tests/actions/contracts.go @@ -0,0 +1,23 @@ +package actions + +import ( + "github.com/rs/zerolog" + + "github.com/smartcontractkit/chainlink-testing-framework/seth" + + "github.com/smartcontractkit/chainlink/integration-tests/contracts" + tc "github.com/smartcontractkit/chainlink/integration-tests/testconfig" +) + +// LinkTokenContract returns a link token contract instance. Depending on test configuration, it either deploys a new one or uses an existing one. +func LinkTokenContract(l zerolog.Logger, sethClient *seth.Client, configWithLinkToken tc.LinkTokenContractConfig) (*contracts.EthereumLinkToken, error) { + if configWithLinkToken != nil && configWithLinkToken.UseExistingLinkTokenContract() { + linkAddress, err := configWithLinkToken.LinkTokenContractAddress() + if err != nil { + return nil, err + } + + return contracts.LoadLinkTokenContract(l, sethClient, linkAddress) + } + return contracts.DeployLinkTokenContract(l, sethClient) +} diff --git a/integration-tests/actions/keeper_helpers.go b/integration-tests/actions/keeper_helpers.go index d5bb3462f1..1cf468d06f 100644 --- a/integration-tests/actions/keeper_helpers.go +++ b/integration-tests/actions/keeper_helpers.go @@ -8,6 +8,8 @@ import ( "strconv" "testing" + tt "github.com/smartcontractkit/chainlink/integration-tests/types" + "github.com/ethereum/go-ethereum/core/types" "github.com/google/uuid" "github.com/pkg/errors" @@ -82,7 +84,7 @@ func DeployKeeperContracts( client *seth.Client, linkFundsForEachUpkeep *big.Int, ) (contracts.KeeperRegistry, contracts.KeeperRegistrar, []contracts.KeeperConsumer, []*big.Int) { - ef, err := contracts.DeployMockETHLINKFeed(client, big.NewInt(2e18)) + ef, err := contracts.DeployMockLINKETHFeed(client, big.NewInt(2e18)) require.NoError(t, err, "Deploying mock ETH-Link feed shouldn't fail") gf, err := contracts.DeployMockGASFeed(client, big.NewInt(2e11)) require.NoError(t, err, "Deploying mock gas feed shouldn't fail") @@ -117,7 +119,7 @@ func DeployKeeperContracts( } registrar := DeployKeeperRegistrar(t, client, registryVersion, linkToken, registrarSettings, registry) - upkeeps, upkeepIds := DeployConsumers(t, client, registry, registrar, linkToken, numberOfUpkeeps, linkFundsForEachUpkeep, upkeepGasLimit, false, false, false, nil) + upkeeps, upkeepIds := DeployLegacyConsumers(t, client, registry, registrar, linkToken, numberOfUpkeeps, linkFundsForEachUpkeep, upkeepGasLimit, false, false, false, nil) return registry, registrar, upkeeps, upkeepIds } @@ -137,7 +139,7 @@ func DeployPerformanceKeeperContracts( checkGasToBurn, // How much gas should be burned on checkUpkeep() calls performGasToBurn int64, // How much gas should be burned on performUpkeep() calls ) (contracts.KeeperRegistry, contracts.KeeperRegistrar, []contracts.KeeperConsumerPerformance, []*big.Int) { - ef, err := contracts.DeployMockETHLINKFeed(chainClient, big.NewInt(2e18)) + ef, err := contracts.DeployMockLINKETHFeed(chainClient, big.NewInt(2e18)) require.NoError(t, err, "Deploying mock ETH-Link feed shouldn't fail") gf, err := contracts.DeployMockGASFeed(chainClient, big.NewInt(2e11)) require.NoError(t, err, "Deploying mock gas feed shouldn't fail") @@ -197,7 +199,7 @@ func DeployPerformDataCheckerContracts( linkFundsForEachUpkeep *big.Int, expectedData []byte, ) (contracts.KeeperRegistry, contracts.KeeperRegistrar, []contracts.KeeperPerformDataChecker, []*big.Int) { - ef, err := contracts.DeployMockETHLINKFeed(chainClient, big.NewInt(2e18)) + ef, err := contracts.DeployMockLINKETHFeed(chainClient, big.NewInt(2e18)) require.NoError(t, err, "Deploying mock ETH-Link feed shouldn't fail") gf, err := contracts.DeployMockGASFeed(chainClient, big.NewInt(2e11)) require.NoError(t, err, "Deploying mock gas feed shouldn't fail") @@ -456,6 +458,33 @@ func DeployKeeperConsumers(t *testing.T, client *seth.Client, numberOfContracts return results } +// SetupKeeperConsumers concurrently loads or deploys keeper consumer contracts. It requires at least 1 ephemeral key to be present in Seth config. +func SetupKeeperConsumers(t *testing.T, client *seth.Client, numberOfContracts int, isLogTrigger bool, isMercury bool, config tt.AutomationTestConfig) []contracts.KeeperConsumer { + l := logging.GetTestLogger(t) + + var results []contracts.KeeperConsumer + + if config.GetAutomationConfig().UseExistingUpkeepContracts() { + contractsLoaded, err := config.GetAutomationConfig().UpkeepContractAddresses() + require.NoError(t, err, "Failed to get upkeep contract addresses") + require.Equal(t, numberOfContracts, len(contractsLoaded), "Incorrect number of Keeper Consumer Contracts loaded") + l.Info().Int("Number of Contracts", numberOfContracts).Msg("Loading upkeep contracts from config") + // Load existing contracts + for i := 0; i < numberOfContracts; i++ { + require.NoError(t, err, "Failed to get upkeep contract addresses") + contract, err := contracts.LoadKeeperConsumer(client, contractsLoaded[i]) + require.NoError(t, err, "Failed to load keeper consumer contract") + l.Info().Str("Contract Address", contract.Address()).Int("Number", i+1).Int("Out Of", numberOfContracts).Msg("Loaded Keeper Consumer Contract") + results = append(results, contract) + } + } else { + // Deploy new contracts + return DeployKeeperConsumers(t, client, numberOfContracts, isLogTrigger, isMercury) + } + + return results +} + // DeployKeeperConsumersPerformance sequentially deploys keeper performance consumer contracts. func DeployKeeperConsumersPerformance( t *testing.T, diff --git a/integration-tests/actions/ocr2_helpers_local.go b/integration-tests/actions/ocr2_helpers_local.go index 692d9502e1..3667aa1ef0 100644 --- a/integration-tests/actions/ocr2_helpers_local.go +++ b/integration-tests/actions/ocr2_helpers_local.go @@ -20,7 +20,6 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/codec" "github.com/smartcontractkit/chainlink-testing-framework/lib/docker/test_env" - evmtypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" "github.com/smartcontractkit/chainlink/v2/core/services/job" diff --git a/integration-tests/actions/ocr_helpers.go b/integration-tests/actions/ocr_helpers.go index 19cad817b7..0f6f65d128 100644 --- a/integration-tests/actions/ocr_helpers.go +++ b/integration-tests/actions/ocr_helpers.go @@ -21,6 +21,8 @@ import ( "github.com/smartcontractkit/chainlink/integration-tests/client" "github.com/smartcontractkit/chainlink/integration-tests/contracts" + tc "github.com/smartcontractkit/chainlink/integration-tests/testconfig" + "github.com/smartcontractkit/chainlink/integration-tests/testconfig/ocr" ) // This actions file often returns functions, rather than just values. These are used as common test helpers, and are @@ -229,13 +231,14 @@ func BuildNodeContractPairID(node contracts.ChainlinkNodeWithKeysAndAddress, ocr func SetupOCRv1Cluster( l zerolog.Logger, seth *seth.Client, + configWithLinkToken tc.LinkTokenContractConfig, workerNodes []*client.ChainlinkK8sClient, ) (common.Address, error) { err := FundChainlinkNodesFromRootAddress(l, seth, contracts.ChainlinkK8sClientToChainlinkNodeWithKeysAndAddress(workerNodes), big.NewFloat(3)) if err != nil { return common.Address{}, err } - linkContract, err := contracts.DeployLinkTokenContract(l, seth) + linkContract, err := LinkTokenContract(l, seth, configWithLinkToken) if err != nil { return common.Address{}, err } @@ -246,11 +249,12 @@ func SetupOCRv1Feed( l zerolog.Logger, seth *seth.Client, lta common.Address, + ocrContractsConfig ocr.OffChainAggregatorsConfig, msClient *ctfClient.MockserverClient, bootstrapNode *client.ChainlinkK8sClient, workerNodes []*client.ChainlinkK8sClient, ) ([]contracts.OffchainAggregator, error) { - ocrInstances, err := DeployOCRv1Contracts(l, seth, 1, lta, contracts.ChainlinkK8sClientToChainlinkNodeWithKeysAndAddress(workerNodes)) + ocrInstances, err := SetupOCRv1Contracts(l, seth, ocrContractsConfig, lta, contracts.ChainlinkK8sClientToChainlinkNodeWithKeysAndAddress(workerNodes)) if err != nil { return nil, err } diff --git a/integration-tests/actions/refund.go b/integration-tests/actions/refund.go index 1835d9a04a..bcdf6a380d 100644 --- a/integration-tests/actions/refund.go +++ b/integration-tests/actions/refund.go @@ -234,6 +234,14 @@ func (r *OvershotTransferRetrier) Retry(ctx context.Context, logger zerolog.Logg // of strategies to attempt to return funds, including retrying with less funds if the transaction fails due to // insufficient funds, and retrying with a higher gas limit if the transaction fails due to gas too low. func ReturnFundsFromNodes(log zerolog.Logger, client *seth.Client, chainlinkNodes []contracts.ChainlinkNodeWithKeysAndAddress) error { + var keyExporters []contracts.ChainlinkKeyExporter + for _, node := range chainlinkNodes { + keyExporters = append(keyExporters, node) + } + return ReturnFundsFromKeyExporterNodes(log, client, keyExporters) +} + +func ReturnFundsFromKeyExporterNodes(log zerolog.Logger, client *seth.Client, chainlinkNodes []contracts.ChainlinkKeyExporter) error { if client == nil { return fmt.Errorf("seth client is nil, unable to return funds from chainlink nodes") } diff --git a/integration-tests/actions/vrf/common/actions.go b/integration-tests/actions/vrf/common/actions.go index 7d9feb275c..67d13264c1 100644 --- a/integration-tests/actions/vrf/common/actions.go +++ b/integration-tests/actions/vrf/common/actions.go @@ -10,6 +10,8 @@ import ( "testing" "time" + "github.com/smartcontractkit/chainlink/integration-tests/utils" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/go-resty/resty/v2" @@ -20,7 +22,7 @@ import ( ctf_test_env "github.com/smartcontractkit/chainlink-testing-framework/lib/docker/test_env" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/conversions" - seth_utils "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/seth" + "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/client" @@ -367,7 +369,7 @@ func BuildNewCLEnvForVRF(l zerolog.Logger, t *testing.T, envConfig VRFEnvConfig, if err != nil { return nil, nil, fmt.Errorf("%s, err: %w", "error getting first evm network", err) } - sethClient, err := seth_utils.GetChainClient(envConfig.TestConfig, *evmNetwork) + sethClient, err := utils.TestAwareSethClient(t, envConfig.TestConfig, evmNetwork) if err != nil { return nil, nil, fmt.Errorf("%s, err: %w", "error getting seth client", err) } @@ -385,6 +387,35 @@ func BuildNewCLEnvForVRF(l zerolog.Logger, t *testing.T, envConfig VRFEnvConfig, return env, sethClient, nil } +func LoadExistingCLEnvForVRF( + t *testing.T, + envConfig VRFEnvConfig, + commonExistingEnvConfig *vrf_common_config.ExistingEnvConfig, + l zerolog.Logger, +) (*test_env.CLClusterTestEnv, *seth.Client, error) { + env, err := test_env.NewCLTestEnvBuilder(). + WithTestInstance(t). + WithTestConfig(&envConfig.TestConfig). + WithCustomCleanup(envConfig.CleanupFn). + Build() + if err != nil { + return nil, nil, fmt.Errorf("%s, err: %w", "error creating test env", err) + } + evmNetwork, err := env.GetFirstEvmNetwork() + if err != nil { + return nil, nil, err + } + sethClient, err := utils.TestAwareSethClient(t, envConfig.TestConfig, evmNetwork) + if err != nil { + return nil, nil, err + } + err = FundNodesIfNeeded(testcontext.Get(t), commonExistingEnvConfig, sethClient, l) + if err != nil { + return nil, nil, err + } + return env, sethClient, nil +} + func GetRPCUrl(env *test_env.CLClusterTestEnv, chainID int64) (string, error) { provider, err := env.GetRpcProvider(chainID) if err != nil { @@ -400,7 +431,7 @@ type RPCRawClient struct { } func NewRPCRawClient(url string) *RPCRawClient { - isDebug := os.Getenv("DEBUG_RESTY") == "true" + isDebug := os.Getenv("RESTY_DEBUG") == "true" restyClient := resty.New().SetDebug(isDebug).SetBaseURL(url) return &RPCRawClient{ resty: restyClient, diff --git a/integration-tests/actions/vrf/common/models.go b/integration-tests/actions/vrf/common/models.go index 9baa5c96e1..f51fd84ba0 100644 --- a/integration-tests/actions/vrf/common/models.go +++ b/integration-tests/actions/vrf/common/models.go @@ -55,6 +55,7 @@ type VRFContracts struct { VRFV2PlusConsumer []contracts.VRFv2PlusLoadTestConsumer LinkToken contracts.LinkToken MockETHLINKFeed contracts.VRFMockETHLINKFeed + LinkNativeFeedAddress string } type VRFOwnerConfig struct { diff --git a/integration-tests/actions/vrf/vrfv2/contract_steps.go b/integration-tests/actions/vrf/vrfv2/contract_steps.go index bab4de8624..25239537f4 100644 --- a/integration-tests/actions/vrf/vrfv2/contract_steps.go +++ b/integration-tests/actions/vrf/vrfv2/contract_steps.go @@ -635,7 +635,7 @@ func SetupNewConsumersAndSubs( ) ([]contracts.VRFv2LoadTestConsumer, []uint64, error) { consumers, err := DeployVRFV2Consumers(sethClient, coordinator.Address(), numberOfConsumerContractsToDeployAndAddToSub) if err != nil { - return nil, nil, fmt.Errorf("err: %w", err) + return nil, nil, err } l.Info(). Str("Coordinator", *testConfig.VRFv2.ExistingEnvConfig.ExistingEnvConfig.CoordinatorAddress). @@ -649,7 +649,7 @@ func SetupNewConsumersAndSubs( numberOfSubToCreate, ) if err != nil { - return nil, nil, fmt.Errorf("err: %w", err) + return nil, nil, err } return consumers, subIDs, nil } diff --git a/integration-tests/actions/vrf/vrfv2/setup_steps.go b/integration-tests/actions/vrf/vrfv2/setup_steps.go index 6f1edc6b04..f252f79ffc 100644 --- a/integration-tests/actions/vrf/vrfv2/setup_steps.go +++ b/integration-tests/actions/vrf/vrfv2/setup_steps.go @@ -8,8 +8,6 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/seth" - seth_utils "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/seth" - "github.com/ethereum/go-ethereum/common" "github.com/rs/zerolog" "golang.org/x/sync/errgroup" @@ -44,7 +42,7 @@ func CreateVRFV2Job( } ost, err := os.String() if err != nil { - return nil, fmt.Errorf("%s, err %w", vrfcommon.ErrParseJob, err) + return nil, fmt.Errorf(vrfcommon.ErrGenericFormat, vrfcommon.ErrParseJob, err) } spec := &client.VRFV2JobSpec{ @@ -71,7 +69,7 @@ func CreateVRFV2Job( } job, err := chainlinkNode.MustCreateJob(spec) if err != nil { - return nil, fmt.Errorf("%s, err %w", ErrCreatingVRFv2Job, err) + return nil, fmt.Errorf(vrfcommon.ErrGenericFormat, ErrCreatingVRFv2Job, err) } return job, nil } @@ -115,15 +113,14 @@ func SetupVRFV2Environment( if err != nil { return nil, nil, nil, err } - l.Info().Str("Coordinator", vrfContracts.CoordinatorV2.Address()).Msg("Registering Proving Key") provingKey, err := VRFV2RegisterProvingKey(vrfKey, registerProvingKeyAgainstAddress, vrfContracts.CoordinatorV2) if err != nil { - return nil, nil, nil, fmt.Errorf("%s, err %w", vrfcommon.ErrRegisteringProvingKey, err) + return nil, nil, nil, fmt.Errorf(vrfcommon.ErrGenericFormat, vrfcommon.ErrRegisteringProvingKey, err) } keyHash, err := vrfContracts.CoordinatorV2.HashOfKey(ctx, provingKey) if err != nil { - return nil, nil, nil, fmt.Errorf("%s, err %w", vrfcommon.ErrCreatingProvingKeyHash, err) + return nil, nil, nil, fmt.Errorf(vrfcommon.ErrGenericFormat, vrfcommon.ErrCreatingProvingKeyHash, err) } vrfTXKeyAddressStrings, vrfTXKeyAddresses, err := vrfcommon.CreateFundAndGetSendingKeys( @@ -191,44 +188,47 @@ func SetupVRFV2Environment( return vrfContracts, &vrfKeyData, nodeTypeToNodeMap, nil } -func setupVRFNode(contracts *vrfcommon.VRFContracts, chainID *big.Int, vrfv2Config *testconfig.General, pubKeyCompressed string, vrfOwnerConfig *vrfcommon.VRFOwnerConfig, l zerolog.Logger, vrfNode *vrfcommon.VRFNode) error { +func setupVRFNode(contracts *vrfcommon.VRFContracts, chainID *big.Int, config *testconfig.General, pubKeyCompressed string, vrfOwnerConfig *vrfcommon.VRFOwnerConfig, l zerolog.Logger, vrfNode *vrfcommon.VRFNode) error { vrfJobSpecConfig := vrfcommon.VRFJobSpecConfig{ - ForwardingAllowed: *vrfv2Config.VRFJobForwardingAllowed, + ForwardingAllowed: *config.VRFJobForwardingAllowed, CoordinatorAddress: contracts.CoordinatorV2.Address(), BatchCoordinatorAddress: contracts.BatchCoordinatorV2.Address(), FromAddresses: vrfNode.TXKeyAddressStrings, EVMChainID: chainID.String(), - MinIncomingConfirmations: int(*vrfv2Config.MinimumConfirmations), + MinIncomingConfirmations: int(*config.MinimumConfirmations), PublicKey: pubKeyCompressed, - EstimateGasMultiplier: *vrfv2Config.VRFJobEstimateGasMultiplier, - BatchFulfillmentEnabled: *vrfv2Config.VRFJobBatchFulfillmentEnabled, - BatchFulfillmentGasMultiplier: *vrfv2Config.VRFJobBatchFulfillmentGasMultiplier, - PollPeriod: vrfv2Config.VRFJobPollPeriod.Duration, - RequestTimeout: vrfv2Config.VRFJobRequestTimeout.Duration, - SimulationBlock: vrfv2Config.VRFJobSimulationBlock, + EstimateGasMultiplier: *config.VRFJobEstimateGasMultiplier, + BatchFulfillmentEnabled: *config.VRFJobBatchFulfillmentEnabled, + BatchFulfillmentGasMultiplier: *config.VRFJobBatchFulfillmentGasMultiplier, + PollPeriod: config.VRFJobPollPeriod.Duration, + RequestTimeout: config.VRFJobRequestTimeout.Duration, + SimulationBlock: config.VRFJobSimulationBlock, VRFOwnerConfig: vrfOwnerConfig, } l.Info().Msg("Creating VRFV2 Job") - vrfV2job, err := CreateVRFV2Job( + job, err := CreateVRFV2Job( vrfNode.CLNode.API, vrfJobSpecConfig, ) if err != nil { return fmt.Errorf("%s, err %w", ErrCreateVRFV2Jobs, err) } - vrfNode.Job = vrfV2job + vrfNode.Job = job // this part is here because VRFv2 can work with only a specific key // [[EVM.KeySpecific]] // Key = '...' nodeConfig := node.NewConfig(vrfNode.CLNode.NodeConfig, - node.WithKeySpecificMaxGasPrice(vrfNode.TXKeyAddressStrings, *vrfv2Config.CLNodeMaxGasPriceGWei), + node.WithKeySpecificMaxGasPrice(vrfNode.TXKeyAddressStrings, *config.CLNodeMaxGasPriceGWei), ) - l.Info().Msg("Restarting Node with new sending key PriceMax configuration") + l.Info(). + Strs("Sending Keys", vrfNode.TXKeyAddressStrings). + Int64("Price Max Setting", *config.CLNodeMaxGasPriceGWei). + Msg("Restarting Node with new sending key PriceMax configuration") err = vrfNode.CLNode.Restart(nodeConfig) if err != nil { - return fmt.Errorf("%s, err %w", vrfcommon.ErrRestartCLNode, err) + return fmt.Errorf(vrfcommon.ErrGenericFormat, vrfcommon.ErrRestartCLNode, err) } return nil } @@ -371,38 +371,30 @@ func SetupVRFV2ForNewEnv( func SetupVRFV2ForExistingEnv(t *testing.T, envConfig vrfcommon.VRFEnvConfig, l zerolog.Logger) (*vrfcommon.VRFContracts, *vrfcommon.VRFKeyData, *test_env.CLClusterTestEnv, *seth.Client, error) { commonExistingEnvConfig := envConfig.TestConfig.VRFv2.ExistingEnvConfig.ExistingEnvConfig - env, err := test_env.NewCLTestEnvBuilder(). - WithTestInstance(t). - WithTestConfig(&envConfig.TestConfig). - WithCustomCleanup(envConfig.CleanupFn). - Build() - if err != nil { - return nil, nil, nil, nil, fmt.Errorf("%s, err: %w", "error creating test env", err) - } - evmNetwork, err := env.GetFirstEvmNetwork() - if err != nil { - return nil, nil, nil, nil, err - } - sethClient, err := seth_utils.GetChainClient(envConfig.TestConfig, *evmNetwork) + env, sethClient, err := vrfcommon.LoadExistingCLEnvForVRF( + t, + envConfig, + commonExistingEnvConfig, + l, + ) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, fmt.Errorf("%s, err: %w", "error loading existing CL env", err) } - coordinator, err := contracts.LoadVRFCoordinatorV2(sethClient, *commonExistingEnvConfig.ConsumerAddress) + coordinator, err := contracts.LoadVRFCoordinatorV2(sethClient, *commonExistingEnvConfig.CoordinatorAddress) if err != nil { return nil, nil, nil, nil, fmt.Errorf("%s, err: %w", "error loading VRFCoordinator2", err) } - linkAddr := common.HexToAddress(*commonExistingEnvConfig.LinkAddress) - linkToken, err := contracts.LoadLinkTokenContract(l, sethClient, linkAddr) + linkAddress, err := coordinator.GetLinkAddress(testcontext.Get(t)) if err != nil { - return nil, nil, nil, nil, fmt.Errorf("%s, err: %w", "error loading LinkToken", err) + return nil, nil, nil, nil, fmt.Errorf("%s, err: %w", "error getting Link address from Coordinator", err) } - err = vrfcommon.FundNodesIfNeeded(testcontext.Get(t), commonExistingEnvConfig, sethClient, l) + linkToken, err := contracts.LoadLinkTokenContract(l, sethClient, common.HexToAddress(linkAddress.String())) if err != nil { - return nil, nil, nil, nil, fmt.Errorf("err: %w", err) + return nil, nil, nil, nil, fmt.Errorf("%s, err: %w", "error loading LinkToken", err) } blockHashStoreAddress, err := coordinator.GetBlockHashStoreAddress(testcontext.Get(t)) if err != nil { - return nil, nil, nil, nil, fmt.Errorf("err: %w", err) + return nil, nil, nil, nil, err } blockHashStore, err := contracts.LoadBlockHashStore(sethClient, blockHashStoreAddress.String()) if err != nil { @@ -449,13 +441,13 @@ func SetupSubsAndConsumersForExistingEnv( l, ) if err != nil { - return nil, nil, fmt.Errorf("err: %w", err) + return nil, nil, err } } else { addr := common.HexToAddress(*commonExistingEnvConfig.ConsumerAddress) consumer, err := contracts.LoadVRFv2LoadTestConsumer(sethClient, addr) if err != nil { - return nil, nil, fmt.Errorf("err: %w", err) + return nil, nil, err } consumers = append(consumers, consumer) subIDs = append(subIDs, *testConfig.VRFv2.ExistingEnvConfig.SubID) @@ -471,7 +463,7 @@ func SetupSubsAndConsumersForExistingEnv( l, ) if err != nil { - return nil, nil, fmt.Errorf("err: %w", err) + return nil, nil, err } } return subIDs, consumers, nil diff --git a/integration-tests/actions/vrf/vrfv2plus/contract_steps.go b/integration-tests/actions/vrf/vrfv2plus/contract_steps.go index 6aa9ef6c70..4dced9edf7 100644 --- a/integration-tests/actions/vrf/vrfv2plus/contract_steps.go +++ b/integration-tests/actions/vrf/vrfv2plus/contract_steps.go @@ -41,7 +41,7 @@ func DeployVRFV2_5Contracts( if err != nil { return nil, fmt.Errorf(vrfcommon.ErrGenericFormat, ErrDeployCoordinatorV2Plus, err) } - err = opStackCoordinator.SetL1FeeCalculation(configGeneral.L1FeeCalculationMode, configGeneral.L1FeeCoefficient) + err = opStackCoordinator.SetL1FeeCalculation(*configGeneral.L1FeeCalculationMode, *configGeneral.L1FeeCoefficient) if err != nil { return nil, fmt.Errorf(vrfcommon.ErrGenericFormat, ErrSetL1FeeCalculation, err) } @@ -49,6 +49,24 @@ func DeployVRFV2_5Contracts( if err != nil { return nil, fmt.Errorf(vrfcommon.ErrGenericFormat, vrfcommon.ErrLoadingCoordinator, err) } + } else if actions.IsArbitrumChain(chainClient.ChainID) { + arbitrumCoordinator, err := contracts.DeployVRFCoordinatorV2_5_Arbitrum(chainClient, bhs.Address()) + if err != nil { + return nil, fmt.Errorf(vrfcommon.ErrGenericFormat, ErrDeployCoordinatorV2Plus, err) + } + coordinator, err = contracts.LoadVRFCoordinatorV2_5(chainClient, arbitrumCoordinator.Address.String()) + if err != nil { + return nil, fmt.Errorf(vrfcommon.ErrGenericFormat, vrfcommon.ErrLoadingCoordinator, err) + } + } else if *configGeneral.UseTestCoordinator { + testCoordinator, err := contracts.DeployVRFCoordinatorTestV2_5(chainClient, bhs.Address()) + if err != nil { + return nil, fmt.Errorf(vrfcommon.ErrGenericFormat, ErrDeployCoordinatorV2Plus, err) + } + coordinator, err = contracts.LoadVRFCoordinatorV2_5(chainClient, testCoordinator.Address.String()) + if err != nil { + return nil, fmt.Errorf(vrfcommon.ErrGenericFormat, vrfcommon.ErrLoadingCoordinator, err) + } } else { coordinator, err = contracts.DeployVRFCoordinatorV2_5(chainClient, bhs.Address()) if err != nil { @@ -57,7 +75,7 @@ func DeployVRFV2_5Contracts( } batchCoordinator, err := contracts.DeployBatchVRFCoordinatorV2Plus(chainClient, coordinator.Address()) if err != nil { - return nil, fmt.Errorf("%s, err %w", ErrDeployBatchCoordinatorV2Plus, err) + return nil, fmt.Errorf(vrfcommon.ErrGenericFormat, ErrDeployBatchCoordinatorV2Plus, err) } return &vrfcommon.VRFContracts{ CoordinatorV2Plus: coordinator, @@ -408,7 +426,7 @@ func DeployVRFV2PlusDirectFundingContracts( linkTokenAddress string, linkEthFeedAddress string, coordinator contracts.VRFCoordinatorV2_5, - consumerContractsAmount int, + numberOfConsumerContracts int, wrapperSubId *big.Int, configGeneral *vrfv2plusconfig.General, ) (*VRFV2PlusWrapperContracts, error) { @@ -419,7 +437,7 @@ func DeployVRFV2PlusDirectFundingContracts( if err != nil { return nil, fmt.Errorf(vrfcommon.ErrGenericFormat, ErrDeployWrapper, err) } - err = opStackWrapper.SetL1FeeCalculation(configGeneral.L1FeeCalculationMode, configGeneral.L1FeeCoefficient) + err = opStackWrapper.SetL1FeeCalculation(*configGeneral.L1FeeCalculationMode, *configGeneral.L1FeeCoefficient) if err != nil { return nil, fmt.Errorf(vrfcommon.ErrGenericFormat, ErrSetL1FeeCalculation, err) } @@ -427,13 +445,22 @@ func DeployVRFV2PlusDirectFundingContracts( if err != nil { return nil, fmt.Errorf(vrfcommon.ErrGenericFormat, vrfcommon.ErrLoadingCoordinator, err) } + } else if actions.IsArbitrumChain(sethClient.ChainID) { + arbitrumWrapper, err := contracts.DeployVRFV2PlusWrapperArbitrum(sethClient, linkTokenAddress, linkEthFeedAddress, coordinator.Address(), wrapperSubId) + if err != nil { + return nil, fmt.Errorf(vrfcommon.ErrGenericFormat, ErrDeployCoordinatorV2Plus, err) + } + vrfv2PlusWrapper, err = contracts.LoadVRFV2PlusWrapper(sethClient, arbitrumWrapper.Address.String()) + if err != nil { + return nil, fmt.Errorf(vrfcommon.ErrGenericFormat, vrfcommon.ErrLoadingCoordinator, err) + } } else { vrfv2PlusWrapper, err = contracts.DeployVRFV2PlusWrapper(sethClient, linkTokenAddress, linkEthFeedAddress, coordinator.Address(), wrapperSubId) if err != nil { return nil, fmt.Errorf(vrfcommon.ErrGenericFormat, ErrDeployWrapper, err) } } - consumers, err := DeployVRFV2PlusWrapperConsumers(sethClient, vrfv2PlusWrapper, consumerContractsAmount) + consumers, err := DeployVRFV2PlusWrapperConsumers(sethClient, vrfv2PlusWrapper, numberOfConsumerContracts) if err != nil { return nil, err } @@ -546,9 +573,9 @@ func WaitRandomWordsFulfilledEvent( return randomWordsFulfilledEvent, err } -func DeployVRFV2PlusWrapperConsumers(client *seth.Client, vrfV2PlusWrapper contracts.VRFV2PlusWrapper, consumerContractsAmount int) ([]contracts.VRFv2PlusWrapperLoadTestConsumer, error) { +func DeployVRFV2PlusWrapperConsumers(client *seth.Client, vrfV2PlusWrapper contracts.VRFV2PlusWrapper, numberOfConsumerContracts int) ([]contracts.VRFv2PlusWrapperLoadTestConsumer, error) { var consumers []contracts.VRFv2PlusWrapperLoadTestConsumer - for i := 1; i <= consumerContractsAmount; i++ { + for i := 1; i <= numberOfConsumerContracts; i++ { loadTestConsumer, err := contracts.DeployVRFV2PlusWrapperLoadTestConsumer(client, vrfV2PlusWrapper.Address()) if err != nil { return nil, fmt.Errorf(vrfcommon.ErrGenericFormat, ErrAdvancedConsumer, err) @@ -610,7 +637,7 @@ func SetupNewConsumersAndSubs( ) ([]contracts.VRFv2PlusLoadTestConsumer, []*big.Int, error) { consumers, err := DeployVRFV2PlusConsumers(sethClient, coordinator, consumerContractsAmount) if err != nil { - return nil, nil, fmt.Errorf("err: %w", err) + return nil, nil, err } l.Info(). Str("Coordinator", *testConfig.VRFv2Plus.ExistingEnvConfig.ExistingEnvConfig.CoordinatorAddress). @@ -628,7 +655,7 @@ func SetupNewConsumersAndSubs( *testConfig.VRFv2Plus.General.SubscriptionBillingType, ) if err != nil { - return nil, nil, fmt.Errorf("err: %w", err) + return nil, nil, err } return consumers, subIDs, nil } @@ -653,3 +680,57 @@ func CancelSubsAndReturnFunds(ctx context.Context, vrfContracts *vrfcommon.VRFCo } } } + +func FundWrapperConsumer( + sethClient *seth.Client, + subFundingType string, + linkToken contracts.LinkToken, + wrapperConsumer contracts.VRFv2PlusWrapperLoadTestConsumer, + vrfv2PlusConfig *vrfv2plusconfig.General, + l zerolog.Logger, +) error { + fundConsumerWithLink := func() error { + //fund consumer with Link + linkAmount := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(*vrfv2PlusConfig.WrapperConsumerFundingAmountLink)) + l.Info(). + Str("Link Amount", linkAmount.String()). + Str("WrapperConsumerAddress", wrapperConsumer.Address()).Msg("Funding WrapperConsumer with Link") + return linkToken.Transfer( + wrapperConsumer.Address(), + linkAmount, + ) + } + fundConsumerWithNative := func() error { + //fund consumer with Eth (native token) + _, err := actions.SendFunds(l, sethClient, actions.FundsToSendPayload{ + ToAddress: common.HexToAddress(wrapperConsumer.Address()), + Amount: conversions.EtherToWei(big.NewFloat(*vrfv2PlusConfig.WrapperConsumerFundingAmountNativeToken)), + PrivateKey: sethClient.PrivateKeys[0], + }) + return err + } + switch vrfv2plusconfig.BillingType(subFundingType) { + case vrfv2plusconfig.BillingType_Link: + err := fundConsumerWithLink() + if err != nil { + return err + } + case vrfv2plusconfig.BillingType_Native: + err := fundConsumerWithNative() + if err != nil { + return err + } + case vrfv2plusconfig.BillingType_Link_and_Native: + err := fundConsumerWithLink() + if err != nil { + return err + } + err = fundConsumerWithNative() + if err != nil { + return err + } + default: + return fmt.Errorf("invalid billing type: %s", subFundingType) + } + return nil +} diff --git a/integration-tests/actions/vrf/vrfv2plus/logging_helpers.go b/integration-tests/actions/vrf/vrfv2plus/logging_helpers.go index 1c6e2edaa0..758200f86f 100644 --- a/integration-tests/actions/vrf/vrfv2plus/logging_helpers.go +++ b/integration-tests/actions/vrf/vrfv2plus/logging_helpers.go @@ -4,9 +4,11 @@ import ( "fmt" "math/big" + "github.com/ethereum/go-ethereum/core/types" "github.com/rs/zerolog" "github.com/smartcontractkit/chainlink/integration-tests/contracts" + tc "github.com/smartcontractkit/chainlink/integration-tests/testconfig" vrfv2plus_config "github.com/smartcontractkit/chainlink/integration-tests/testconfig/vrfv2plus" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_coordinator_v2_5" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_v2plus_upgraded_version" @@ -55,3 +57,18 @@ func LogSubDetailsAfterMigration(l zerolog.Logger, newCoordinator contracts.Coor Interface("Subscription Consumers", migratedSubscription.Consumers). Msg("Subscription Data After Migration to New Coordinator") } + +func LogPaymentDetails(l zerolog.Logger, fulfillmentTxFeeWei *big.Int, fulfillmentTxReceipt *types.Receipt, actualSubPaymentWei *big.Int, expectedSubPaymentWei *big.Float, configCopy tc.TestConfig) { + l.Info(). + Str("Tx Fee in Wei", fulfillmentTxFeeWei.String()). + Str("Effective Gas Price", fulfillmentTxReceipt.EffectiveGasPrice.String()). + Uint64("Gas Used", fulfillmentTxReceipt.GasUsed). + Str("Actual Subscription Payment in Wei", actualSubPaymentWei.String()). + Str("Expected Subscription Payment in Wei", expectedSubPaymentWei.String()). + Uint8("Native Premium Percentage", *configCopy.VRFv2Plus.General.NativePremiumPercentage). + Uint8("Link Premium Percentage", *configCopy.VRFv2Plus.General.LinkPremiumPercentage). + Uint32("FulfillmentFlatFeeNativePPM", *configCopy.VRFv2Plus.General.FulfillmentFlatFeeNativePPM). + Uint32("FulfillmentFlatFeeLinkDiscountPPM", *configCopy.VRFv2Plus.General.FulfillmentFlatFeeLinkDiscountPPM). + Uint32("GasAfterPaymentCalculation", *configCopy.VRFv2Plus.General.GasAfterPaymentCalculation). + Msg("Randomness Fulfillment Payment Details") +} diff --git a/integration-tests/actions/vrf/vrfv2plus/models.go b/integration-tests/actions/vrf/vrfv2plus/models.go index a2ca8ec582..5198439c05 100644 --- a/integration-tests/actions/vrf/vrfv2plus/models.go +++ b/integration-tests/actions/vrf/vrfv2plus/models.go @@ -5,6 +5,6 @@ import ( ) type VRFV2PlusWrapperContracts struct { - VRFV2PlusWrapper contracts.VRFV2PlusWrapper - LoadTestConsumers []contracts.VRFv2PlusWrapperLoadTestConsumer + VRFV2PlusWrapper contracts.VRFV2PlusWrapper + WrapperConsumers []contracts.VRFv2PlusWrapperLoadTestConsumer } diff --git a/integration-tests/actions/vrf/vrfv2plus/setup_steps.go b/integration-tests/actions/vrf/vrfv2plus/setup_steps.go index caa5e26a69..c88c7fdb12 100644 --- a/integration-tests/actions/vrf/vrfv2plus/setup_steps.go +++ b/integration-tests/actions/vrf/vrfv2plus/setup_steps.go @@ -8,8 +8,6 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/seth" - seth_utils "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/seth" - "github.com/shopspring/decimal" "golang.org/x/sync/errgroup" @@ -17,7 +15,6 @@ import ( "github.com/google/uuid" "github.com/rs/zerolog" - "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/conversions" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" "github.com/smartcontractkit/chainlink/integration-tests/actions" vrfcommon "github.com/smartcontractkit/chainlink/integration-tests/actions/vrf/common" @@ -28,7 +25,7 @@ import ( "github.com/smartcontractkit/chainlink/integration-tests/client" "github.com/smartcontractkit/chainlink/integration-tests/contracts" "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" - vrfv2plus_config "github.com/smartcontractkit/chainlink/integration-tests/testconfig/vrfv2plus" + vrfv2plusconfig "github.com/smartcontractkit/chainlink/integration-tests/testconfig/vrfv2plus" "github.com/smartcontractkit/chainlink/integration-tests/types" ) @@ -66,12 +63,10 @@ func CreateVRFV2PlusJob( if vrfJobSpecConfig.BatchFulfillmentEnabled { jobSpec.BatchCoordinatorAddress = vrfJobSpecConfig.BatchCoordinatorAddress } - job, err := chainlinkNode.MustCreateJob(&jobSpec) if err != nil { return nil, fmt.Errorf(vrfcommon.ErrGenericFormat, ErrCreatingVRFv2PlusJob, err) } - return job, nil } @@ -90,7 +85,6 @@ func SetupVRFV2_5Environment( ) (*vrfcommon.VRFContracts, *vrfcommon.VRFKeyData, map[vrfcommon.VRFNodeType]*vrfcommon.VRFNode, error) { l.Info().Msg("Starting VRFV2 Plus environment setup") configGeneral := vrfv2PlusTestConfig.GetVRFv2PlusConfig().General - vrfContracts, err := SetupVRFV2PlusContracts( sethClient, linkToken, @@ -201,7 +195,7 @@ func SetupVRFV2_5Environment( return vrfContracts, &vrfKeyData, nodeTypeToNodeMap, nil } -func setupVRFNode(contracts *vrfcommon.VRFContracts, chainID *big.Int, config *vrfv2plus_config.General, pubKeyCompressed string, l zerolog.Logger, vrfNode *vrfcommon.VRFNode) error { +func setupVRFNode(contracts *vrfcommon.VRFContracts, chainID *big.Int, config *vrfv2plusconfig.General, pubKeyCompressed string, l zerolog.Logger, vrfNode *vrfcommon.VRFNode) error { vrfJobSpecConfig := vrfcommon.VRFJobSpecConfig{ ForwardingAllowed: *config.VRFJobForwardingAllowed, CoordinatorAddress: contracts.CoordinatorV2Plus.Address(), @@ -235,7 +229,10 @@ func setupVRFNode(contracts *vrfcommon.VRFContracts, chainID *big.Int, config *v nodeConfig := node.NewConfig(vrfNode.CLNode.NodeConfig, node.WithKeySpecificMaxGasPrice(vrfNode.TXKeyAddressStrings, *config.CLNodeMaxGasPriceGWei), ) - l.Info().Msg("Restarting Node with new sending key PriceMax configuration") + l.Info(). + Strs("Sending Keys", vrfNode.TXKeyAddressStrings). + Int64("Price Max Setting", *config.CLNodeMaxGasPriceGWei). + Msg("Restarting Node with new sending key PriceMax configuration") err = vrfNode.CLNode.Restart(nodeConfig) if err != nil { return fmt.Errorf(vrfcommon.ErrGenericFormat, vrfcommon.ErrRestartCLNode, err) @@ -243,43 +240,131 @@ func setupVRFNode(contracts *vrfcommon.VRFContracts, chainID *big.Int, config *v return nil } -func SetupVRFV2PlusWrapperEnvironment( +func SetupVRFV2PlusWrapperForExistingEnv( ctx context.Context, + sethClient *seth.Client, + vrfContracts *vrfcommon.VRFContracts, + keyHash [32]byte, + vrfv2PlusTestConfig types.VRFv2PlusTestConfig, + numberOfConsumerContracts int, l zerolog.Logger, +) (*VRFV2PlusWrapperContracts, *big.Int, error) { + config := *vrfv2PlusTestConfig.GetVRFv2PlusConfig() + var wrapper contracts.VRFV2PlusWrapper + var err error + if *config.ExistingEnvConfig.UseExistingWrapper { + wrapper, err = contracts.LoadVRFV2PlusWrapper(sethClient, *config.ExistingEnvConfig.WrapperAddress) + if err != nil { + return nil, nil, fmt.Errorf(vrfcommon.ErrGenericFormat, "error loading VRFV2PlusWrapper", err) + } + } else { + wrapperSubId, err := CreateSubAndFindSubID(ctx, sethClient, vrfContracts.CoordinatorV2Plus) + if err != nil { + return nil, nil, err + } + wrapper, err = contracts.DeployVRFV2PlusWrapper(sethClient, vrfContracts.LinkToken.Address(), vrfContracts.LinkNativeFeedAddress, vrfContracts.CoordinatorV2Plus.Address(), wrapperSubId) + if err != nil { + return nil, nil, fmt.Errorf(vrfcommon.ErrGenericFormat, ErrDeployWrapper, err) + } + err = FundSubscriptions( + big.NewFloat(*config.General.SubscriptionFundingAmountNative), + big.NewFloat(*config.General.SubscriptionFundingAmountLink), + vrfContracts.LinkToken, + vrfContracts.CoordinatorV2Plus, + []*big.Int{wrapperSubId}, + *config.General.SubscriptionBillingType, + ) + if err != nil { + return nil, nil, err + } + err = vrfContracts.CoordinatorV2Plus.AddConsumer(wrapperSubId, wrapper.Address()) + if err != nil { + return nil, nil, err + } + err = wrapper.SetConfig( + *config.General.WrapperGasOverhead, + *config.General.CoordinatorGasOverheadNative, + *config.General.CoordinatorGasOverheadLink, + *config.General.CoordinatorGasOverheadPerWord, + *config.General.CoordinatorNativePremiumPercentage, + *config.General.CoordinatorLinkPremiumPercentage, + keyHash, + *config.General.WrapperMaxNumberOfWords, + *config.General.StalenessSeconds, + decimal.RequireFromString(*config.General.FallbackWeiPerUnitLink).BigInt(), + *config.General.FulfillmentFlatFeeNativePPM, + *config.General.FulfillmentFlatFeeLinkDiscountPPM, + ) + if err != nil { + return nil, nil, err + } + } + wrapperSubID, err := wrapper.GetSubID(ctx) + if err != nil { + return nil, nil, fmt.Errorf(vrfcommon.ErrGenericFormat, "error getting subID", err) + } + var wrapperConsumers []contracts.VRFv2PlusWrapperLoadTestConsumer + if *config.ExistingEnvConfig.CreateFundAddWrapperConsumers { + wrapperConsumers, err = DeployVRFV2PlusWrapperConsumers(sethClient, wrapper, numberOfConsumerContracts) + if err != nil { + return nil, nil, err + } + } else { + wrapperConsumer, err := contracts.LoadVRFV2WrapperLoadTestConsumer(sethClient, *config.ExistingEnvConfig.WrapperConsumerAddress) + if err != nil { + return nil, nil, fmt.Errorf(vrfcommon.ErrGenericFormat, "error loading VRFV2WrapperLoadTestConsumer", err) + } + wrapperConsumers = append(wrapperConsumers, wrapperConsumer) + } + wrapperContracts := &VRFV2PlusWrapperContracts{wrapper, wrapperConsumers} + for _, consumer := range wrapperConsumers { + err = FundWrapperConsumer( + sethClient, + *config.General.SubscriptionBillingType, + vrfContracts.LinkToken, + consumer, + config.General, + l, + ) + if err != nil { + return nil, nil, err + } + } + return wrapperContracts, wrapperSubID, nil +} + +func SetupVRFV2PlusWrapperForNewEnv( + ctx context.Context, sethClient *seth.Client, vrfv2PlusTestConfig types.VRFv2PlusTestConfig, - linkToken contracts.LinkToken, - mockNativeLINKFeed contracts.MockETHLINKFeed, - coordinator contracts.VRFCoordinatorV2_5, + vrfContracts *vrfcommon.VRFContracts, keyHash [32]byte, wrapperConsumerContractsAmount int, + l zerolog.Logger, ) (*VRFV2PlusWrapperContracts, *big.Int, error) { // external EOA has to create a subscription for the wrapper first - wrapperSubId, err := CreateSubAndFindSubID(ctx, sethClient, coordinator) + wrapperSubId, err := CreateSubAndFindSubID(ctx, sethClient, vrfContracts.CoordinatorV2Plus) if err != nil { return nil, nil, err } - vrfv2PlusConfig := vrfv2PlusTestConfig.GetVRFv2PlusConfig().General wrapperContracts, err := DeployVRFV2PlusDirectFundingContracts( sethClient, - linkToken.Address(), - mockNativeLINKFeed.Address(), - coordinator, + vrfContracts.LinkToken.Address(), + vrfContracts.MockETHLINKFeed.Address(), + vrfContracts.CoordinatorV2Plus, wrapperConsumerContractsAmount, wrapperSubId, vrfv2PlusConfig, ) if err != nil { - return nil, nil, fmt.Errorf(vrfcommon.ErrGenericFormat, vrfcommon.ErrWaitTXsComplete, err) + return nil, nil, err } - // once the wrapper is deployed, wrapper address will become consumer of external EOA subscription - err = coordinator.AddConsumer(wrapperSubId, wrapperContracts.VRFV2PlusWrapper.Address()) + err = vrfContracts.CoordinatorV2Plus.AddConsumer(wrapperSubId, wrapperContracts.VRFV2PlusWrapper.Address()) if err != nil { return nil, nil, err } - err = wrapperContracts.VRFV2PlusWrapper.SetConfig( *vrfv2PlusConfig.WrapperGasOverhead, *vrfv2PlusConfig.CoordinatorGasOverheadNative, @@ -297,53 +382,35 @@ func SetupVRFV2PlusWrapperEnvironment( if err != nil { return nil, nil, err } - //fund sub wrapperSubID, err := wrapperContracts.VRFV2PlusWrapper.GetSubID(ctx) if err != nil { return nil, nil, err } - err = FundSubscriptions( big.NewFloat(*vrfv2PlusTestConfig.GetVRFv2PlusConfig().General.SubscriptionFundingAmountNative), big.NewFloat(*vrfv2PlusTestConfig.GetVRFv2PlusConfig().General.SubscriptionFundingAmountLink), - linkToken, - coordinator, + vrfContracts.LinkToken, + vrfContracts.CoordinatorV2Plus, []*big.Int{wrapperSubID}, *vrfv2PlusConfig.SubscriptionBillingType, ) if err != nil { return nil, nil, err } - - //fund consumer with Link - err = linkToken.Transfer( - wrapperContracts.LoadTestConsumers[0].Address(), - big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(*vrfv2PlusConfig.WrapperConsumerFundingAmountLink)), - ) - if err != nil { - return nil, nil, err - } - - //fund consumer with Eth (native token) - _, err = actions.SendFunds(l, sethClient, actions.FundsToSendPayload{ - ToAddress: common.HexToAddress(wrapperContracts.LoadTestConsumers[0].Address()), - Amount: conversions.EtherToWei(big.NewFloat(*vrfv2PlusConfig.WrapperConsumerFundingAmountNativeToken)), - PrivateKey: sethClient.PrivateKeys[0], - }) - if err != nil { - return nil, nil, err - } - - wrapperConsumerBalanceBeforeRequestWei, err := sethClient.Client.BalanceAt(ctx, common.HexToAddress(wrapperContracts.LoadTestConsumers[0].Address()), nil) - if err != nil { - return nil, nil, err + for _, consumer := range wrapperContracts.WrapperConsumers { + err = FundWrapperConsumer( + sethClient, + *vrfv2PlusConfig.SubscriptionBillingType, + vrfContracts.LinkToken, + consumer, + vrfv2PlusConfig, + l, + ) + if err != nil { + return nil, nil, err + } } - l.Info(). - Str("WrapperConsumerBalanceBeforeRequestWei", wrapperConsumerBalanceBeforeRequestWei.String()). - Str("WrapperConsumerAddress", wrapperContracts.LoadTestConsumers[0].Address()). - Msg("WrapperConsumerBalanceBeforeRequestWei") - return wrapperContracts, wrapperSubID, nil } @@ -421,47 +488,45 @@ func SetupVRFV2PlusForNewEnv( func SetupVRFV2PlusForExistingEnv(t *testing.T, envConfig vrfcommon.VRFEnvConfig, l zerolog.Logger) (*vrfcommon.VRFContracts, *vrfcommon.VRFKeyData, *test_env.CLClusterTestEnv, *seth.Client, error) { commonExistingEnvConfig := envConfig.TestConfig.VRFv2Plus.ExistingEnvConfig.ExistingEnvConfig - env, err := test_env.NewCLTestEnvBuilder(). - WithTestInstance(t). - WithTestConfig(&envConfig.TestConfig). - WithCustomCleanup(envConfig.CleanupFn). - Build() - if err != nil { - return nil, nil, nil, nil, fmt.Errorf("%s, err: %w", "error creating test env", err) - } - evmNetwork, err := env.GetFirstEvmNetwork() - if err != nil { - return nil, nil, nil, nil, err - } - sethClient, err := seth_utils.GetChainClient(envConfig.TestConfig, *evmNetwork) + env, sethClient, err := vrfcommon.LoadExistingCLEnvForVRF( + t, + envConfig, + commonExistingEnvConfig, + l, + ) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, fmt.Errorf("%s, err: %w", "error loading existing CL env", err) } coordinator, err := contracts.LoadVRFCoordinatorV2_5(sethClient, *commonExistingEnvConfig.CoordinatorAddress) if err != nil { return nil, nil, nil, nil, fmt.Errorf("%s, err: %w", "error loading VRFCoordinator2_5", err) } - linkToken, err := contracts.LoadLinkTokenContract(l, sethClient, common.HexToAddress(*commonExistingEnvConfig.LinkAddress)) + linkAddress, err := coordinator.GetLinkAddress(testcontext.Get(t)) + if err != nil { + return nil, nil, nil, nil, fmt.Errorf("%s, err: %w", "error getting Link address from Coordinator", err) + } + linkToken, err := contracts.LoadLinkTokenContract(l, sethClient, common.HexToAddress(linkAddress.String())) if err != nil { return nil, nil, nil, nil, fmt.Errorf("%s, err: %w", "error loading LinkToken", err) } - err = vrfcommon.FundNodesIfNeeded(testcontext.Get(t), commonExistingEnvConfig, sethClient, l) + linkNativeFeedAddress, err := coordinator.GetLinkNativeFeed(testcontext.Get(t)) if err != nil { - return nil, nil, nil, nil, fmt.Errorf("err: %w", err) + return nil, nil, nil, nil, fmt.Errorf("%s, err: %w", "error getting Link address from Coordinator", err) } blockHashStoreAddress, err := coordinator.GetBlockHashStoreAddress(testcontext.Get(t)) if err != nil { - return nil, nil, nil, nil, fmt.Errorf("err: %w", err) + return nil, nil, nil, nil, err } blockHashStore, err := contracts.LoadBlockHashStore(sethClient, blockHashStoreAddress.String()) if err != nil { return nil, nil, nil, nil, fmt.Errorf("%s, err: %w", "error loading BlockHashStore", err) } vrfContracts := &vrfcommon.VRFContracts{ - CoordinatorV2Plus: coordinator, - VRFV2PlusConsumer: nil, - LinkToken: linkToken, - BHS: blockHashStore, + CoordinatorV2Plus: coordinator, + VRFV2PlusConsumer: nil, + LinkToken: linkToken, + BHS: blockHashStore, + LinkNativeFeedAddress: linkNativeFeedAddress.String(), } vrfKey := &vrfcommon.VRFKeyData{ VRFKey: nil, @@ -500,12 +565,12 @@ func SetupSubsAndConsumersForExistingEnv( l, ) if err != nil { - return nil, nil, fmt.Errorf("err: %w", err) + return nil, nil, err } } else { consumer, err := contracts.LoadVRFv2PlusLoadTestConsumer(sethClient, *commonExistingEnvConfig.ConsumerAddress) if err != nil { - return nil, nil, fmt.Errorf("err: %w", err) + return nil, nil, err } consumers = append(consumers, consumer) var ok bool @@ -527,21 +592,65 @@ func SetupSubsAndConsumersForExistingEnv( l, ) if err != nil { - return nil, nil, fmt.Errorf("err: %w", err) + return nil, nil, err } } return subIDs, consumers, nil } func SelectBillingTypeWithDistribution(billingType string, distributionFn func() bool) (bool, error) { - switch vrfv2plus_config.BillingType(billingType) { - case vrfv2plus_config.BillingType_Link: + switch vrfv2plusconfig.BillingType(billingType) { + case vrfv2plusconfig.BillingType_Link: return false, nil - case vrfv2plus_config.BillingType_Native: + case vrfv2plusconfig.BillingType_Native: return true, nil - case vrfv2plus_config.BillingType_Link_and_Native: + case vrfv2plusconfig.BillingType_Link_and_Native: return distributionFn(), nil default: return false, fmt.Errorf("invalid billing type: %s", billingType) } } + +func SetupVRFV2PlusWrapperUniverse( + ctx context.Context, + sethClient *seth.Client, + vrfContracts *vrfcommon.VRFContracts, + config *tc.TestConfig, + keyHash [32]byte, + numberOfConsumerContracts int, + l zerolog.Logger, +) (*VRFV2PlusWrapperContracts, *big.Int, error) { + var ( + wrapperContracts *VRFV2PlusWrapperContracts + wrapperSubID *big.Int + err error + ) + if *config.VRFv2Plus.General.UseExistingEnv { + wrapperContracts, wrapperSubID, err = SetupVRFV2PlusWrapperForExistingEnv( + ctx, + sethClient, + vrfContracts, + keyHash, + config, + numberOfConsumerContracts, + l, + ) + if err != nil { + return nil, nil, err + } + } else { + wrapperContracts, wrapperSubID, err = SetupVRFV2PlusWrapperForNewEnv( + ctx, + sethClient, + config, + vrfContracts, + keyHash, + numberOfConsumerContracts, + l, + ) + if err != nil { + return nil, nil, err + } + } + return wrapperContracts, wrapperSubID, nil +} diff --git a/integration-tests/benchmark/keeper_test.go b/integration-tests/benchmark/automation_test.go similarity index 60% rename from integration-tests/benchmark/keeper_test.go rename to integration-tests/benchmark/automation_test.go index 7c947d0b64..0a63ff2c27 100644 --- a/integration-tests/benchmark/keeper_test.go +++ b/integration-tests/benchmark/automation_test.go @@ -10,26 +10,25 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-testing-framework/lib/blockchain" - ctf_config "github.com/smartcontractkit/chainlink-testing-framework/lib/config" - env_client "github.com/smartcontractkit/chainlink-testing-framework/lib/k8s/client" + ctfconfig "github.com/smartcontractkit/chainlink-testing-framework/lib/config" + envclient "github.com/smartcontractkit/chainlink-testing-framework/lib/k8s/client" "github.com/smartcontractkit/chainlink-testing-framework/lib/k8s/environment" "github.com/smartcontractkit/chainlink-testing-framework/lib/k8s/pkg/helm/chainlink" "github.com/smartcontractkit/chainlink-testing-framework/lib/k8s/pkg/helm/ethereum" "github.com/smartcontractkit/chainlink-testing-framework/lib/k8s/pkg/helm/reorg" "github.com/smartcontractkit/chainlink-testing-framework/lib/logging" "github.com/smartcontractkit/chainlink-testing-framework/lib/networks" - seth_utils "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/seth" + sethutils "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/seth" "github.com/smartcontractkit/chainlink/integration-tests/actions" - "github.com/smartcontractkit/chainlink/integration-tests/contracts" - eth_contracts "github.com/smartcontractkit/chainlink/integration-tests/contracts/ethereum" + ethcontracts "github.com/smartcontractkit/chainlink/integration-tests/contracts/ethereum" tc "github.com/smartcontractkit/chainlink/integration-tests/testconfig" "github.com/smartcontractkit/chainlink/integration-tests/testsetups" "github.com/smartcontractkit/chainlink/integration-tests/types" ) var ( - performanceChainlinkResources = map[string]interface{}{ + chainlinkResources = map[string]interface{}{ "resources": map[string]interface{}{ "requests": map[string]interface{}{ "cpu": "1000m", @@ -41,7 +40,7 @@ var ( }, }, } - performanceDbResources = map[string]interface{}{ + dbResources = map[string]interface{}{ "resources": map[string]interface{}{ "requests": map[string]interface{}{ "cpu": "1000m", @@ -55,33 +54,6 @@ var ( "stateful": true, "capacity": "10Gi", } - - soakChainlinkResources = map[string]interface{}{ - "resources": map[string]interface{}{ - "requests": map[string]interface{}{ - "cpu": "350m", - "memory": "1Gi", - }, - "limits": map[string]interface{}{ - "cpu": "350m", - "memory": "1Gi", - }, - }, - } - soakDbResources = map[string]interface{}{ - "resources": map[string]interface{}{ - "requests": map[string]interface{}{ - "cpu": "250m", - "memory": "256Mi", - }, - "limits": map[string]interface{}{ - "cpu": "250m", - "memory": "256Mi", - }, - }, - "stateful": true, - "capacity": "10Gi", - } ) type NetworkConfig struct { @@ -102,7 +74,7 @@ func TestAutomationBenchmark(t *testing.T) { testType, err := tc.GetConfigurationNameFromEnv() require.NoError(t, err, "Error getting test type") - config, err := tc.GetConfig([]string{testType}, tc.Keeper) + config, err := tc.GetConfig([]string{testType}, tc.Automation) require.NoError(t, err, "Error getting test config") testEnvironment, benchmarkNetwork := SetupAutomationBenchmarkEnv(t, &config) @@ -110,103 +82,81 @@ func TestAutomationBenchmark(t *testing.T) { return } networkName := strings.ReplaceAll(benchmarkNetwork.Name, " ", "") - testName := fmt.Sprintf("%s%s", networkName, *config.Keeper.Common.RegistryToTest) + testName := fmt.Sprintf("%s%s", networkName, *config.Automation.Benchmark.RegistryToTest) l.Info().Str("Test Name", testName).Msg("Running Benchmark Test") benchmarkTestNetwork := getNetworkConfig(&config) l.Info().Str("Namespace", testEnvironment.Cfg.Namespace).Msg("Connected to Keepers Benchmark Environment") - testNetwork := seth_utils.MustReplaceSimulatedNetworkUrlWithK8(l, benchmarkNetwork, *testEnvironment) + testNetwork := sethutils.MustReplaceSimulatedNetworkUrlWithK8(l, benchmarkNetwork, *testEnvironment) - chainClient, err := seth_utils.GetChainClientWithConfigFunction(&config, testNetwork, seth_utils.OneEphemeralKeysLiveTestnetAutoFixFn) + chainClient, err := sethutils.GetChainClientWithConfigFunction(&config, testNetwork, sethutils.OneEphemeralKeysLiveTestnetAutoFixFn) require.NoError(t, err, "Error getting Seth client") registryVersions := addRegistry(&config) + registrySettings := actions.ReadRegistryConfig(config) keeperBenchmarkTest := testsetups.NewKeeperBenchmarkTest(t, testsetups.KeeperBenchmarkTestInputs{ - BlockchainClient: chainClient, - RegistryVersions: registryVersions, - KeeperRegistrySettings: &contracts.KeeperRegistrySettings{ - PaymentPremiumPPB: uint32(0), - FlatFeeMicroLINK: uint32(40000), - BlockCountPerTurn: big.NewInt(100), - CheckGasLimit: uint32(45_000_000), //45M - StalenessSeconds: big.NewInt(90_000), - GasCeilingMultiplier: uint16(2), - MaxPerformGas: uint32(*config.Keeper.Common.MaxPerformGas), - MinUpkeepSpend: big.NewInt(0), - FallbackGasPrice: big.NewInt(2e11), - FallbackLinkPrice: big.NewInt(2e18), - MaxCheckDataSize: uint32(5_000), - MaxPerformDataSize: uint32(5_000), - MaxRevertDataSize: uint32(5_000), - }, + BlockchainClient: chainClient, + RegistryVersions: registryVersions, + KeeperRegistrySettings: ®istrySettings, Upkeeps: &testsetups.UpkeepConfig{ - NumberOfUpkeeps: *config.Keeper.Common.NumberOfUpkeeps, - CheckGasToBurn: *config.Keeper.Common.CheckGasToBurn, - PerformGasToBurn: *config.Keeper.Common.PerformGasToBurn, - BlockRange: *config.Keeper.Common.BlockRange, - BlockInterval: *config.Keeper.Common.BlockInterval, - UpkeepGasLimit: *config.Keeper.Common.UpkeepGasLimit, + NumberOfUpkeeps: *config.Automation.Benchmark.NumberOfUpkeeps, + CheckGasToBurn: *config.Automation.Benchmark.CheckGasToBurn, + PerformGasToBurn: *config.Automation.Benchmark.PerformGasToBurn, + BlockRange: *config.Automation.Benchmark.BlockRange, + BlockInterval: *config.Automation.Benchmark.BlockInterval, + UpkeepGasLimit: *config.Automation.Benchmark.UpkeepGasLimit, FirstEligibleBuffer: 1, }, - Contracts: &testsetups.PreDeployedContracts{ - RegistrarAddress: *config.Keeper.Common.RegistrarAddress, - RegistryAddress: *config.Keeper.Common.RegistryAddress, - LinkTokenAddress: *config.Keeper.Common.LinkTokenAddress, - EthFeedAddress: *config.Keeper.Common.EthFeedAddress, - GasFeedAddress: *config.Keeper.Common.GasFeedAddress, - }, ChainlinkNodeFunding: benchmarkTestNetwork.funding, UpkeepSLA: benchmarkTestNetwork.upkeepSLA, BlockTime: benchmarkTestNetwork.blockTime, DeltaStage: benchmarkTestNetwork.deltaStage, - ForceSingleTxnKey: *config.Keeper.Common.ForceSingleTxKey, - DeleteJobsOnEnd: *config.Keeper.Common.DeleteJobsOnEnd, + ForceSingleTxnKey: *config.Automation.Benchmark.ForceSingleTxKey, + DeleteJobsOnEnd: *config.Automation.Benchmark.DeleteJobsOnEnd, }, ) t.Cleanup(func() { if err = actions.TeardownRemoteSuite(keeperBenchmarkTest.TearDownVals(t)); err != nil { l.Error().Err(err).Msg("Error when tearing down remote suite") + } else { + if *config.GetAutomationConfig().Benchmark.DeleteJobsOnEnd { + err := testEnvironment.Client.RemoveNamespace(testEnvironment.Cfg.Namespace) + if err != nil { + l.Error().Err(err).Msg("Error removing namespace") + } + } } }) - keeperBenchmarkTest.Setup(testEnvironment, &config) + keeperBenchmarkTest.Setup(testEnvironment, config) keeperBenchmarkTest.Run() } -func addRegistry(config *tc.TestConfig) []eth_contracts.KeeperRegistryVersion { - switch *config.Keeper.Common.RegistryToTest { - case "1_1": - return []eth_contracts.KeeperRegistryVersion{eth_contracts.RegistryVersion_1_1} - case "1_2": - return []eth_contracts.KeeperRegistryVersion{eth_contracts.RegistryVersion_1_2} - case "1_3": - return []eth_contracts.KeeperRegistryVersion{eth_contracts.RegistryVersion_1_3} +func addRegistry(config *tc.TestConfig) []ethcontracts.KeeperRegistryVersion { + switch *config.Automation.Benchmark.RegistryToTest { case "2_0": - return []eth_contracts.KeeperRegistryVersion{eth_contracts.RegistryVersion_2_0} + return []ethcontracts.KeeperRegistryVersion{ethcontracts.RegistryVersion_2_0} case "2_1": - return []eth_contracts.KeeperRegistryVersion{eth_contracts.RegistryVersion_2_1} + return []ethcontracts.KeeperRegistryVersion{ethcontracts.RegistryVersion_2_1} case "2_2": - return []eth_contracts.KeeperRegistryVersion{eth_contracts.RegistryVersion_2_2} - case "2_0-1_3": - return []eth_contracts.KeeperRegistryVersion{eth_contracts.RegistryVersion_2_0, eth_contracts.RegistryVersion_1_3} - case "2_1-2_0-1_3": - return []eth_contracts.KeeperRegistryVersion{eth_contracts.RegistryVersion_2_1, - eth_contracts.RegistryVersion_2_0, eth_contracts.RegistryVersion_1_3} + return []ethcontracts.KeeperRegistryVersion{ethcontracts.RegistryVersion_2_2} + case "2_3": + return []ethcontracts.KeeperRegistryVersion{ethcontracts.RegistryVersion_2_3} case "2_2-2_1": - return []eth_contracts.KeeperRegistryVersion{eth_contracts.RegistryVersion_2_2, eth_contracts.RegistryVersion_2_1} + return []ethcontracts.KeeperRegistryVersion{ethcontracts.RegistryVersion_2_2, ethcontracts.RegistryVersion_2_1} case "2_0-Multiple": - return repeatRegistries(eth_contracts.RegistryVersion_2_0, *config.Keeper.Common.NumberOfRegistries) + return repeatRegistries(ethcontracts.RegistryVersion_2_0, *config.Automation.Benchmark.NumberOfRegistries) case "2_1-Multiple": - return repeatRegistries(eth_contracts.RegistryVersion_2_1, *config.Keeper.Common.NumberOfRegistries) + return repeatRegistries(ethcontracts.RegistryVersion_2_1, *config.Automation.Benchmark.NumberOfRegistries) case "2_2-Multiple": - return repeatRegistries(eth_contracts.RegistryVersion_2_2, *config.Keeper.Common.NumberOfRegistries) + return repeatRegistries(ethcontracts.RegistryVersion_2_2, *config.Automation.Benchmark.NumberOfRegistries) default: - return []eth_contracts.KeeperRegistryVersion{eth_contracts.RegistryVersion_2_0} + return []ethcontracts.KeeperRegistryVersion{ethcontracts.RegistryVersion_2_0} } } -func repeatRegistries(registryVersion eth_contracts.KeeperRegistryVersion, numberOfRegistries int) []eth_contracts.KeeperRegistryVersion { - repeatedRegistries := make([]eth_contracts.KeeperRegistryVersion, 0) +func repeatRegistries(registryVersion ethcontracts.KeeperRegistryVersion, numberOfRegistries int) []ethcontracts.KeeperRegistryVersion { + repeatedRegistries := make([]ethcontracts.KeeperRegistryVersion, 0) for i := 0; i < numberOfRegistries; i++ { repeatedRegistries = append(repeatedRegistries, registryVersion) } @@ -288,15 +238,20 @@ var networkConfig = map[string]NetworkConfig{ blockTime: time.Second, deltaStage: 20 * time.Second, }, + networks.ScrollSepolia.Name: { + upkeepSLA: int64(120), + blockTime: 3 * time.Second, + deltaStage: 20 * time.Second, + }, } -func SetupAutomationBenchmarkEnv(t *testing.T, keeperTestConfig types.KeeperBenchmarkTestConfig) (*environment.Environment, blockchain.EVMNetwork) { +func SetupAutomationBenchmarkEnv(t *testing.T, keeperTestConfig types.AutomationBenchmarkTestConfig) (*environment.Environment, blockchain.EVMNetwork) { l := logging.GetTestLogger(t) testNetwork := networks.MustGetSelectedNetworkConfig(keeperTestConfig.GetNetworkConfig())[0] // Environment currently being used to run benchmark test on blockTime := "1" - numberOfNodes := *keeperTestConfig.GetKeeperConfig().Common.NumberOfNodes + numberOfNodes := *keeperTestConfig.GetAutomationConfig().General.NumberOfNodes - if strings.Contains(*keeperTestConfig.GetKeeperConfig().Common.RegistryToTest, "2_") { + if strings.Contains(*keeperTestConfig.GetAutomationConfig().Benchmark.RegistryToTest, "2_") { numberOfNodes++ } @@ -310,18 +265,14 @@ func SetupAutomationBenchmarkEnv(t *testing.T, keeperTestConfig types.KeeperBenc "automation-%s-%s-%s", strings.ToLower(strings.Join(keeperTestConfig.GetConfigurationNames(), "")), strings.ReplaceAll(strings.ToLower(testNetwork.Name), " ", "-"), - strings.ReplaceAll(strings.ToLower(*keeperTestConfig.GetKeeperConfig().Common.RegistryToTest), "_", "-"), + strings.ReplaceAll(strings.ToLower(*keeperTestConfig.GetAutomationConfig().Benchmark.RegistryToTest), "_", "-"), ), Test: t, PreventPodEviction: true, }) - dbResources := performanceDbResources - chainlinkResources := performanceChainlinkResources - if strings.Contains(strings.ToLower(strings.Join(keeperTestConfig.GetConfigurationNames(), ",")), "soak") { - chainlinkResources = soakChainlinkResources - dbResources = soakDbResources - } + dbResources := dbResources + chainlinkResources := chainlinkResources // Test can run on simulated, simulated-non-dev, testnets if testNetwork.Name == networks.SimulatedEVMNonDev.Name { @@ -365,18 +316,19 @@ func SetupAutomationBenchmarkEnv(t *testing.T, keeperTestConfig types.KeeperBenc }, })) } - - // TODO we need to update the image in CTF, the old one is not available anymore - // deploy blockscout if running on simulated - // if testNetwork.Simulated { - // testEnvironment. - // AddChart(blockscout.New(&blockscout.Props{ - // Name: "geth-blockscout", - // WsURL: testNetwork.URLs[0], - // HttpURL: testNetwork.HTTPURLs[0]})) - // } - err := testEnvironment.Run() - require.NoError(t, err, "Error launching test environment") + var err error + if testNetwork.Simulated { + // TODO we need to update the image in CTF, the old one is not available anymore + // deploy blockscout if running on simulated + //testEnvironment. + // AddChart(blockscout.New(&blockscout.Props{ + // Name: "geth-blockscout", + // WsURL: testNetwork.URLs[0], + // HttpURL: testNetwork.HTTPURLs[0]})) + // Need to setup geth node before setting up chainlink nodes + err = testEnvironment.Run() + require.NoError(t, err, "Error launching test environment") + } if testEnvironment.WillUseRemoteRunner() { return testEnvironment, testNetwork @@ -389,10 +341,10 @@ func SetupAutomationBenchmarkEnv(t *testing.T, keeperTestConfig types.KeeperBenc // for simulated-nod-dev each CL node gets its own RPC node if testNetwork.Name == networks.SimulatedEVMNonDev.Name { podName := fmt.Sprintf("%s-ethereum-geth:%d", testNetwork.Name, i) - txNodeInternalWs, err := testEnvironment.Fwd.FindPort(podName, "geth", "ws-rpc").As(env_client.RemoteConnection, env_client.WS) + txNodeInternalWs, err := testEnvironment.Fwd.FindPort(podName, "geth", "ws-rpc").As(envclient.RemoteConnection, envclient.WS) require.NoError(t, err, "Error finding WS ports") internalWsURLs = append(internalWsURLs, txNodeInternalWs) - txNodeInternalHttp, err := testEnvironment.Fwd.FindPort(podName, "geth", "http-rpc").As(env_client.RemoteConnection, env_client.HTTP) + txNodeInternalHttp, err := testEnvironment.Fwd.FindPort(podName, "geth", "http-rpc").As(envclient.RemoteConnection, envclient.HTTP) require.NoError(t, err, "Error finding HTTP ports") internalHttpURLs = append(internalHttpURLs, txNodeInternalHttp) // for testnets with more than 1 RPC nodes @@ -412,8 +364,8 @@ func SetupAutomationBenchmarkEnv(t *testing.T, keeperTestConfig types.KeeperBenc testNetwork.URLs = []string{internalWsURLs[i]} var overrideFn = func(_ interface{}, target interface{}) { - ctf_config.MustConfigOverrideChainlinkVersion(keeperTestConfig.GetChainlinkImageConfig(), target) - ctf_config.MightConfigOverridePyroscopeKey(keeperTestConfig.GetPyroscopeConfig(), target) + ctfconfig.MustConfigOverrideChainlinkVersion(keeperTestConfig.GetChainlinkImageConfig(), target) + ctfconfig.MightConfigOverridePyroscopeKey(keeperTestConfig.GetPyroscopeConfig(), target) } tomlConfig, err := actions.BuildTOMLNodeConfigForK8s(keeperTestConfig, testNetwork) diff --git a/integration-tests/ccip-tests/Makefile b/integration-tests/ccip-tests/Makefile index 40b4cc8338..4aebd4c460 100644 --- a/integration-tests/ccip-tests/Makefile +++ b/integration-tests/ccip-tests/Makefile @@ -4,10 +4,10 @@ set_config: if [ -s "$(override_toml)" ]; then \ echo "Overriding config with $(override_toml)"; \ - echo "export BASE64_CCIP_CONFIG_OVERRIDE=$$(base64 -i $(override_toml))" > ./testconfig/override/.env; \ - echo "export TEST_BASE64_CCIP_CONFIG_OVERRIDE=$$(base64 -i $(override_toml))" >> ./testconfig/override/.env; \ - echo "BASE64_CCIP_CONFIG_OVERRIDE=$$(base64 -i $(override_toml))" > ./testconfig/override/debug.env; \ - echo "TEST_BASE64_CCIP_CONFIG_OVERRIDE=$$(base64 -i $(override_toml))" >> ./testconfig/override/debug.env; \ + echo "export BASE64_CONFIG_OVERRIDE=$$(base64 -i $(override_toml))" > ./testconfig/override/.env; \ + echo "export TEST_BASE64_CONFIG_OVERRIDE=$$(base64 -i $(override_toml))" >> ./testconfig/override/.env; \ + echo "BASE64_CONFIG_OVERRIDE=$$(base64 -i $(override_toml))" > ./testconfig/override/debug.env; \ + echo "TEST_BASE64_CONFIG_OVERRIDE=$$(base64 -i $(override_toml))" >> ./testconfig/override/debug.env; \ else \ echo "No override config found, using default config"; \ echo > ./testconfig/override/.env; \ @@ -52,8 +52,8 @@ test_smoke_ccip: set_config test_smoke_ccip_default: set_config source ./testconfig/override/.env && \ DATABASE_URL=postgresql://postgres:node@localhost:5432/chainlink_test?sslmode=disable \ - BASE64_CCIP_CONFIG_OVERRIDE="" \ - TEST_BASE64_CCIP_CONFIG_OVERRIDE="" \ + BASE64_CONFIG_OVERRIDE="" \ + TEST_BASE64_CONFIG_OVERRIDE="" \ ENV_JOB_IMAGE="" \ TEST_SUITE=smoke \ TEST_ARGS="-test.timeout 900h" \ diff --git a/integration-tests/ccip-tests/README.md b/integration-tests/ccip-tests/README.md index 9c4da98dcb..855cfcde69 100644 --- a/integration-tests/ccip-tests/README.md +++ b/integration-tests/ccip-tests/README.md @@ -8,7 +8,7 @@ CCIP tests are designed to be highly configurable. Instead of writing many tests 1. Default test input - set via TOML - If no specific input is set; the tests will run with default inputs mentioned in [default.toml](./testconfig/tomls/ccip-default.toml). Please refer to the [testconfig README](../testconfig/README.md) for a more detailed look at how testconfig works. -2. If you want to run your test with a different config, you can override the default inputs. You can either write an [overrides.toml](../testconfig/README.md#configuration-and-overrides) file, or set the env var `BASE64_CCIP_CONFIG_OVERRIDE` containing the base64 encoded TOML file content with updated test input parameters. +2. If you want to run your test with a different config, you can override the default inputs. You can either write an [overrides.toml](../testconfig/README.md#configuration-and-overrides) file, or set the env var `BASE64_CONFIG_OVERRIDE` containing the base64 encoded TOML file content with updated test input parameters. For example, if you want to override the `Network` input in test and want to run your test on `avalanche testnet` and `arbitrum goerli` network, you need to: 1. Create a TOML file with the following content: @@ -20,10 +20,10 @@ For example, if you want to override the `Network` input in test and want to run ``` 2. Encode it using the `base64` command - 3. Set the env var `BASE64_CCIP_CONFIG_OVERRIDE` with the encoded content. + 3. Set the env var `BASE64_CONFIG_OVERRIDE` with the encoded content. ```bash - export BASE64_CCIP_CONFIG_OVERRIDE=$(base64 -i ) + export BASE64_CONFIG_OVERRIDE=$(base64 -i ) ``` [mainnet.toml](./testconfig/override/mainnet.toml), [override.toml](./testconfig/examples/override.toml.example) are some of the sample override TOML files. @@ -31,7 +31,7 @@ For example, if you want to override the `Network` input in test and want to run For example - In order to run the smoke test (TestSmokeCCIPForBidirectionalLane) on mainnet, run the test with following env var set: ```bash - export BASE64_CCIP_CONFIG_OVERRIDE=$(base64 -i ./testconfig/override/mainnet.toml) + export BASE64_CONFIG_OVERRIDE=$(base64 -i ./testconfig/override/mainnet.toml) ``` 3. Secrets - You also need to set some secrets. This is a mandatory step needed to run the tests. Please refer to [.testsecrets.example](./examples/.testsecrets.example) for the list of secrets and instruction how to set them up. @@ -40,6 +40,27 @@ For example, if you want to override the `Network` input in test and want to run **Please note that the secrets should NOT be checked in to the repo and should be kept locally.** +======= + export BASE64_CONFIG_OVERRIDE=$(base64 -i ./testconfig/override/mainnet.toml) + ``` + +3. Secrets - You also need to set some secrets. This is a mandatory step needed to run the tests. Please refer to [.testsecrets.example](./examples/.testsecrets.example) for the list of secrets and instruction how to set them up. + - The chainlink image and tag are required secrets for all the tests. + - If you are running tests in live networks like testnet and mainnet, you need to set the secrets (rpc urls and private keys) for the respective networks. + - If you are running tests in simulated networks no network specific secrets are required. + here is a sample secrets.toml file, for running the tests in simulated networks, with the chainlink image and tag set as secrets: + +**Please note that the secrets should NOT be checked in to the repo and should be kept locally.** + +We recommend against changing the content of [secrets.toml.example](./testconfig/examples/secrets.toml.example). Please create a new file and set it as the secrets file. + +You can run this command to ignore any changes to the file. + +```bash +git update-index --skip-worktree +``` + +>>>>>>> v2.17.0 ## Running the Tests There are two ways to run the tests: diff --git a/integration-tests/ccip-tests/actions/ccip_helpers.go b/integration-tests/ccip-tests/actions/ccip_helpers.go index eb34db40de..49d6ed3fd1 100644 --- a/integration-tests/ccip-tests/actions/ccip_helpers.go +++ b/integration-tests/ccip-tests/actions/ccip_helpers.go @@ -1816,7 +1816,7 @@ func (sourceCCIP *SourceCCIPModule) CCIPMsg( extraArgs, err = testhelpers.GetEVMExtraArgsV2(gasLimit, allowOutOfOrder) } if err != nil { - return router.ClientEVM2AnyMessage{}, fmt.Errorf("failed encoding the options field: %w", err) + return router.ClientEVM2AnyMessage{}, fmt.Errorf("failed getting extra args: %w", err) } // form the message for transfer return router.ClientEVM2AnyMessage{ @@ -1829,7 +1829,10 @@ func (sourceCCIP *SourceCCIPModule) CCIPMsg( } // SendRequest sends a CCIP request to the source chain's router contract -func (sourceCCIP *SourceCCIPModule) SendRequest(receiver common.Address, gasLimit *big.Int) (common.Hash, time.Duration, *big.Int, error) { +func (sourceCCIP *SourceCCIPModule) SendRequest( + receiver common.Address, + gasLimit *big.Int, +) (common.Hash, time.Duration, *big.Int, error) { var d time.Duration destChainSelector, err := chainselectors.SelectorFromChainId(sourceCCIP.DestinationChainId) if err != nil { @@ -2854,8 +2857,11 @@ func (lane *CCIPLane) AddToSentReqs(txHash common.Hash, reqStats []*testreporter func (lane *CCIPLane) Multicall(noOfRequests int, multiSendAddr common.Address) error { var ccipMultipleMsg []contracts.CCIPMsgData feeToken := common.HexToAddress(lane.Source.Common.FeeToken.Address()) - genericMsg, err := lane.Source.CCIPMsg(lane.Dest.ReceiverDapp.EthAddress, lane.Source.Common.AllowOutOfOrder, - big.NewInt(DefaultDestinationGasLimit)) + genericMsg, err := lane.Source.CCIPMsg( + lane.Dest.ReceiverDapp.EthAddress, + lane.Source.Common.AllowOutOfOrder, + big.NewInt(DefaultDestinationGasLimit), + ) if err != nil { return fmt.Errorf("failed to form the ccip message: %w", err) } @@ -3278,6 +3284,7 @@ func (lane *CCIPLane) ValidateRequestByTxHash(txHash common.Hash, opts validatio if opts.expectAnyPhaseToFail { return fmt.Errorf("expected at least any one phase to fail but no phase got failed") } + return nil } @@ -3597,9 +3604,9 @@ func (lane *CCIPLane) DeployNewCCIPLane( return fmt.Errorf("failed to create source module: %w", err) } - // If AllowOutOfOrder is set globally in test config, then assumption is to set it for every lane. - //However, if this is set as false, and set as true for specific chain in lane_configs then apply it - //only for a lane where source network is of that chain. + // If AllowOutOfOrder is set globally in test config, the assumption is to set it for every lane. + // However, if this is set as false, and set as true for specific chain in lane_configs, then apply it + // only for a lane where source network is of that chain. if allowOutOfOrder { lane.Source.Common.AllowOutOfOrder = true } @@ -4150,8 +4157,7 @@ func (c *CCIPTestEnv) ConnectToDeployedNodes() error { return fmt.Errorf("no CL node found") } - for i := range chainlinkK8sNodes { - chainlinkK8sNodes[i].ChainlinkClient.WithRetryCount(3) + for range chainlinkK8sNodes { c.nodeMutexes = append(c.nodeMutexes, &sync.Mutex{}) } c.CLNodes = chainlinkK8sNodes diff --git a/integration-tests/ccip-tests/load/helper.go b/integration-tests/ccip-tests/load/helper.go index 2c150bbc5a..f89aadc248 100644 --- a/integration-tests/ccip-tests/load/helper.go +++ b/integration-tests/ccip-tests/load/helper.go @@ -312,7 +312,7 @@ func (l *LoadArgs) TriggerLoadByLane() { } waspCfg.LokiConfig.Timeout = time.Minute loadRunner, err := wasp.NewGenerator(waspCfg) - require.NoError(l.TestCfg.Test, err, "initiating loadgen for lane %s --> %s", + require.NoError(l.TestCfg.Test, err, "error while initiating loadgen for lane %s --> %s", lane.SourceNetworkName, lane.DestNetworkName) loadRunner.Run(false) l.AddToRunnerGroup(loadRunner) diff --git a/integration-tests/ccip-tests/smoke/ccip_test.go b/integration-tests/ccip-tests/smoke/ccip_test.go index bf2533f02a..cc8cc9b9a4 100644 --- a/integration-tests/ccip-tests/smoke/ccip_test.go +++ b/integration-tests/ccip-tests/smoke/ccip_test.go @@ -16,6 +16,7 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/osutil" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/ptr" "github.com/smartcontractkit/chainlink/integration-tests/ccip-tests/actions" + "github.com/smartcontractkit/chainlink/integration-tests/ccip-tests/testsetups" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -23,7 +24,7 @@ import ( "github.com/smartcontractkit/chainlink/integration-tests/ccip-tests/contracts" "github.com/smartcontractkit/chainlink/integration-tests/ccip-tests/testconfig" "github.com/smartcontractkit/chainlink/integration-tests/ccip-tests/testreporters" - "github.com/smartcontractkit/chainlink/integration-tests/ccip-tests/testsetups" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_onramp" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/lock_release_token_pool" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/token_pool" @@ -38,6 +39,7 @@ func TestSmokeCCIPForBidirectionalLane(t *testing.T) { t.Parallel() log := logging.GetTestLogger(t) TestCfg := testsetups.NewCCIPTestConfig(t, log, testconfig.Smoke) + require.NotNil(t, TestCfg.TestGroupInput.MsgDetails.DestGasLimit) gasLimit := big.NewInt(*TestCfg.TestGroupInput.MsgDetails.DestGasLimit) setUpOutput := testsetups.CCIPDefaultTestSetUp(t, &log, "smoke-ccip", nil, TestCfg) if len(setUpOutput.Lanes) == 0 { diff --git a/integration-tests/ccip-tests/testconfig/ccip.go b/integration-tests/ccip-tests/testconfig/ccip.go index 7925ffa71f..f0b63c8fae 100644 --- a/integration-tests/ccip-tests/testconfig/ccip.go +++ b/integration-tests/ccip-tests/testconfig/ccip.go @@ -241,7 +241,6 @@ func (l *LoadProfile) Validate() error { if l.TestDuration == nil || l.TestDuration.Duration().Minutes() == 0 { return fmt.Errorf("test duration should be set") } - return nil } diff --git a/integration-tests/ccip-tests/testconfig/examples/.testsecrets.example b/integration-tests/ccip-tests/testconfig/examples/.testsecrets.example index e69ef9e854..52afa77015 100644 --- a/integration-tests/ccip-tests/testconfig/examples/.testsecrets.example +++ b/integration-tests/ccip-tests/testconfig/examples/.testsecrets.example @@ -36,4 +36,4 @@ E2E_TEST_LOKI_ENDPOINT="" # Grafana secrets E2E_TEST_GRAFANA_BASE_URL="" -E2E_TEST_GRAFANA_DASHBOARD_URL="/d/6vjVx-1V8/ccip-long-running-tests" +E2E_TEST_GRAFANA_DASHBOARD_URL="/d/6vjVx-1V8/ccip-long-running-tests" \ No newline at end of file diff --git a/integration-tests/ccip-tests/testconfig/examples/network_config.toml.example b/integration-tests/ccip-tests/testconfig/examples/network_config.toml.example new file mode 100644 index 0000000000..ffed99a771 --- /dev/null +++ b/integration-tests/ccip-tests/testconfig/examples/network_config.toml.example @@ -0,0 +1,168 @@ +[RpcHttpUrls] +ETHEREUM_MAINNET = [ + 'https:....', + 'https:......', +] +AVALANCHE_MAINNET = [ + 'https:....', + 'https:......', +] +BASE_MAINNET = [ + 'https:....', + 'https:......', +] +ARBITRUM_MAINNET = [ + 'https:....', + 'https:......', +] +BSC_MAINNET = [ + 'https:....', + 'https:......', +] +OPTIMISM_MAINNET = [ + 'https:....', + 'https:......', +] +POLYGON_MAINNET = [ + 'https:....', + 'https:......', +] +WEMIX_MAINNET = [ + 'https:....', + 'https:......', +] +KROMA_MAINNET = [ + 'https:....', + 'https:......', +] + +OPTIMISM_SEPOLIA = [ + 'https:....', + 'https:......', +] +SEPOLIA = [ + 'https:....', + 'https:......', +] +AVALANCHE_FUJI = [ + 'https:....', + 'https:......', +] +ARBITRUM_SEPOLIA = [ + 'https:....', + 'https:......', +] +POLYGON_MUMBAI = [ + 'https:....', + 'https:......', +] +BASE_SEPOLIA = [ + 'https:....', + 'https:......', +] +BSC_TESTNET = [ + 'https:....', + 'https:......', +] +KROMA_SEPOLIA = [ + 'https:....', + 'https:......', +] +WEMIX_TESTNET = [ + 'https:....', + 'https:......', +] + +[RpcWsUrls] +ETHEREUM_MAINNET = [ + 'wss://......', + 'wss://.........' +] +AVALANCHE_MAINNET = [ + 'wss://......', + 'wss://.........' +] +BASE_MAINNET = [ + 'wss://......', + 'wss://.........' +] +ARBITRUM_MAINNET = [ + 'wss://......', + 'wss://.........' +] +BSC_MAINNET = [ + 'wss://......', + 'wss://.........' +] +POLYGON_MAINNET = [ + 'wss://......', + 'wss://.........' +] +OPTIMISM_MAINNET = [ + 'wss://......', + 'wss://.........' +] +WEMIX_MAINNET = [ + 'wss://......', + 'wss://.........' +] +KROMA_MAINNET = [ + 'wss://......', + 'wss://.........' +] +OPTIMISM_SEPOLIA = [ + 'wss://......', + 'wss://.........' +] +SEPOLIA = [ + 'wss://......', + 'wss://.........' +] +AVALANCHE_FUJI = [ + 'wss://......', + 'wss://.........' +] +ARBITRUM_SEPOLIA = [ + 'wss://......', + 'wss://.........' +] +POLYGON_MUMBAI = [ + 'wss://......', + 'wss://.........' +] +BASE_SEPOLIA = [ + 'wss://......', + 'wss://.........' +] +BSC_TESTNET = [ + 'wss://......', + 'wss://.........' +] +KROMA_SEPOLIA = [ + 'wss://......', + 'wss://.........' +] +WEMIX_TESTNET = [ + 'wss://......', + 'wss://.........' +] + +[WalletKeys] +ETHEREUM_MAINNET = [''] +AVALANCHE_MAINNET = [''] +BASE_MAINNET = [''] +ARBITRUM_MAINNET = [''] +BSC_MAINNET = [''] +POLYGON_MAINNET = [''] +OPTIMISM_MAINNET = [''] +WEMIX_MAINNET = [''] +KROMA_MAINNET = [''] +OPTIMISM_SEPOLIA = [''] +SEPOLIA = [''] +AVALANCHE_FUJI = [''] +ARBITRUM_SEPOLIA = [''] +POLYGON_MUMBAI = [''] +BASE_SEPOLIA = [''] +BSC_TESTNET = [''] +KROMA_SEPOLIA = [''] +WEMIX_TESTNET = [''] \ No newline at end of file diff --git a/integration-tests/ccip-tests/testconfig/global.go b/integration-tests/ccip-tests/testconfig/global.go index 2545bb83ca..d2b3f599b1 100644 --- a/integration-tests/ccip-tests/testconfig/global.go +++ b/integration-tests/ccip-tests/testconfig/global.go @@ -29,8 +29,7 @@ import ( ) const ( - OVERIDECONFIG = "BASE64_CCIP_CONFIG_OVERRIDE" - + OVERIDECONFIG = "BASE64_CONFIG_OVERRIDE" ErrReadConfig = "failed to read TOML config" ErrUnmarshalConfig = "failed to unmarshal TOML config" Load string = "load" @@ -137,6 +136,7 @@ func NewConfig() (*Config, error) { } } } + // read secrets for all products if cfg.CCIP != nil { err := ctfconfig.LoadSecretEnvsFromFiles() @@ -175,6 +175,36 @@ type Common struct { func (p *Common) ReadFromEnvVar() error { logger := logging.GetTestLogger(nil) + testLogCollect := ctfconfig.MustReadEnvVar_Boolean(ctfconfig.E2E_TEST_LOG_COLLECT_ENV) + if testLogCollect != nil { + if p.Logging == nil { + p.Logging = &ctfconfig.LoggingConfig{} + } + logger.Debug().Msgf("Using %s env var to override Logging.TestLogCollect", ctfconfig.E2E_TEST_LOG_COLLECT_ENV) + p.Logging.TestLogCollect = testLogCollect + } + + loggingRunID := ctfconfig.MustReadEnvVar_String(ctfconfig.E2E_TEST_LOGGING_RUN_ID_ENV) + if loggingRunID != "" { + if p.Logging == nil { + p.Logging = &ctfconfig.LoggingConfig{} + } + logger.Debug().Msgf("Using %s env var to override Logging.RunID", ctfconfig.E2E_TEST_LOGGING_RUN_ID_ENV) + p.Logging.RunId = &loggingRunID + } + + logstreamLogTargets := ctfconfig.MustReadEnvVar_Strings(ctfconfig.E2E_TEST_LOG_STREAM_LOG_TARGETS_ENV, ",") + if len(logstreamLogTargets) > 0 { + if p.Logging == nil { + p.Logging = &ctfconfig.LoggingConfig{} + } + if p.Logging.LogStream == nil { + p.Logging.LogStream = &ctfconfig.LogStreamConfig{} + } + logger.Debug().Msgf("Using %s env var to override Logging.LogStream.LogTargets", ctfconfig.E2E_TEST_LOG_STREAM_LOG_TARGETS_ENV) + p.Logging.LogStream.LogTargets = logstreamLogTargets + } + lokiTenantID := ctfconfig.MustReadEnvVar_String(ctfconfig.E2E_TEST_LOKI_TENANT_ID_ENV) if lokiTenantID != "" { if p.Logging == nil { @@ -259,6 +289,15 @@ func (p *Common) ReadFromEnvVar() error { p.Logging.Grafana.BearerToken = &grafanaBearerToken } + selectedNetworks := ctfconfig.MustReadEnvVar_Strings(ctfconfig.E2E_TEST_SELECTED_NETWORK_ENV, ",") + if len(selectedNetworks) > 0 { + if p.Network == nil { + p.Network = &ctfconfig.NetworkConfig{} + } + logger.Debug().Msgf("Using %s env var to override Network.SelectedNetworks", ctfconfig.E2E_TEST_SELECTED_NETWORK_ENV) + p.Network.SelectedNetworks = selectedNetworks + } + walletKeys := ctfconfig.ReadEnvVarGroupedMap(ctfconfig.E2E_TEST_WALLET_KEY_ENV, ctfconfig.E2E_TEST_WALLET_KEYS_ENV) if len(walletKeys) > 0 { if p.Network == nil { @@ -302,6 +341,38 @@ func (p *Common) ReadFromEnvVar() error { p.NewCLCluster.Common.ChainlinkImage.Image = &chainlinkImage } + chainlinkVersion := ctfconfig.MustReadEnvVar_String(ctfconfig.E2E_TEST_CHAINLINK_VERSION_ENV) + if chainlinkVersion != "" { + if p.NewCLCluster == nil { + p.NewCLCluster = &ChainlinkDeployment{} + } + if p.NewCLCluster.Common == nil { + p.NewCLCluster.Common = &Node{} + } + if p.NewCLCluster.Common.ChainlinkImage == nil { + p.NewCLCluster.Common.ChainlinkImage = &ctfconfig.ChainlinkImageConfig{} + } + + logger.Debug().Msgf("Using %s env var to override NewCLCluster.Common.ChainlinkImage.Version", ctfconfig.E2E_TEST_CHAINLINK_VERSION_ENV) + p.NewCLCluster.Common.ChainlinkImage.Version = &chainlinkVersion + } + + chainlinkPostgresVersion := ctfconfig.MustReadEnvVar_String(ctfconfig.E2E_TEST_CHAINLINK_POSTGRES_VERSION_ENV) + if chainlinkPostgresVersion != "" { + if p.NewCLCluster == nil { + p.NewCLCluster = &ChainlinkDeployment{} + } + if p.NewCLCluster.Common == nil { + p.NewCLCluster.Common = &Node{} + } + if p.NewCLCluster.Common.ChainlinkImage == nil { + p.NewCLCluster.Common.ChainlinkImage = &ctfconfig.ChainlinkImageConfig{} + } + + logger.Debug().Msgf("Using %s env var to override NewCLCluster.Common.ChainlinkImage.PostgresVersion", ctfconfig.E2E_TEST_CHAINLINK_POSTGRES_VERSION_ENV) + p.NewCLCluster.Common.ChainlinkImage.PostgresVersion = &chainlinkPostgresVersion + } + chainlinkUpgradeImage := ctfconfig.MustReadEnvVar_String(ctfconfig.E2E_TEST_CHAINLINK_UPGRADE_IMAGE_ENV) if chainlinkUpgradeImage != "" { if p.NewCLCluster == nil { @@ -318,6 +389,22 @@ func (p *Common) ReadFromEnvVar() error { p.NewCLCluster.Common.ChainlinkUpgradeImage.Image = &chainlinkUpgradeImage } + chainlinkUpgradeVersion := ctfconfig.MustReadEnvVar_String(ctfconfig.E2E_TEST_CHAINLINK_UPGRADE_VERSION_ENV) + if chainlinkUpgradeVersion != "" { + if p.NewCLCluster == nil { + p.NewCLCluster = &ChainlinkDeployment{} + } + if p.NewCLCluster.Common == nil { + p.NewCLCluster.Common = &Node{} + } + if p.NewCLCluster.Common.ChainlinkImage == nil { + p.NewCLCluster.Common.ChainlinkImage = &ctfconfig.ChainlinkImageConfig{} + } + + logger.Debug().Msgf("Using %s env var to override NewCLCluster.Common.ChainlinkUpgradeImage.Version", ctfconfig.E2E_TEST_CHAINLINK_UPGRADE_VERSION_ENV) + p.NewCLCluster.Common.ChainlinkUpgradeImage.Version = &chainlinkUpgradeVersion + } + return nil } diff --git a/integration-tests/ccip-tests/testconfig/tomls/ccip1.4-stress/baseline.toml b/integration-tests/ccip-tests/testconfig/tomls/ccip1.4-stress/baseline.toml index c404d3a0c0..d78cd12595 100644 --- a/integration-tests/ccip-tests/testconfig/tomls/ccip1.4-stress/baseline.toml +++ b/integration-tests/ccip-tests/testconfig/tomls/ccip1.4-stress/baseline.toml @@ -195,4 +195,4 @@ DataLength = 10000 [[CCIP.Groups.load.LoadProfile.MsgProfile.MsgDetails]] MsgType = 'Data' DestGasLimit = 2500000 -DataLength = 10000 +DataLength = 10000 \ No newline at end of file diff --git a/integration-tests/ccip-tests/testsetups/ccip.go b/integration-tests/ccip-tests/testsetups/ccip.go index 75a32f8b9e..eee424d50d 100644 --- a/integration-tests/ccip-tests/testsetups/ccip.go +++ b/integration-tests/ccip-tests/testsetups/ccip.go @@ -29,8 +29,8 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/lib/blockchain" ctfClient "github.com/smartcontractkit/chainlink-testing-framework/lib/client" - ctfconfig "github.com/smartcontractkit/chainlink-testing-framework/lib/config" - ctfconfigtypes "github.com/smartcontractkit/chainlink-testing-framework/lib/config/types" + ctf_config "github.com/smartcontractkit/chainlink-testing-framework/lib/config" + ctf_config_types "github.com/smartcontractkit/chainlink-testing-framework/lib/config/types" ctftestenv "github.com/smartcontractkit/chainlink-testing-framework/lib/docker/test_env" "github.com/smartcontractkit/chainlink-testing-framework/lib/k8s/config" "github.com/smartcontractkit/chainlink-testing-framework/lib/k8s/environment" @@ -271,16 +271,16 @@ func (c *CCIPTestConfig) SetNetworkPairs(lggr zerolog.Logger) error { c.EnvInput.Network.AnvilConfigs[strings.ToUpper(name)] = existing } - chainConfig := &ctfconfig.EthereumChainConfig{} + chainConfig := &ctf_config.EthereumChainConfig{} err := chainConfig.Default() if err != nil { allError = multierr.Append(allError, fmt.Errorf("failed to get default chain config: %w", err)) } else { chainConfig.ChainID = int(chainID) - eth1 := ctfconfigtypes.EthereumVersion_Eth1 - geth := ctfconfigtypes.ExecutionLayer_Geth + eth1 := ctf_config_types.EthereumVersion_Eth1 + geth := ctf_config_types.ExecutionLayer_Geth - c.EnvInput.PrivateEthereumNetworks[fmt.Sprint(chainID)] = &ctfconfig.EthereumNetworkConfig{ + c.EnvInput.PrivateEthereumNetworks[fmt.Sprint(chainID)] = &ctf_config.EthereumNetworkConfig{ EthereumVersion: ð1, ExecutionLayer: &geth, EthereumChainConfig: chainConfig, diff --git a/integration-tests/ccip-tests/testsetups/test_env.go b/integration-tests/ccip-tests/testsetups/test_env.go index e7c77fbf41..effd6e61bf 100644 --- a/integration-tests/ccip-tests/testsetups/test_env.go +++ b/integration-tests/ccip-tests/testsetups/test_env.go @@ -13,8 +13,8 @@ import ( "github.com/rs/zerolog" "github.com/stretchr/testify/require" - ctfconfig "github.com/smartcontractkit/chainlink-testing-framework/lib/config" - ctfconfigtypes "github.com/smartcontractkit/chainlink-testing-framework/lib/config/types" + ctf_config "github.com/smartcontractkit/chainlink-testing-framework/lib/config" + ctf_config_types "github.com/smartcontractkit/chainlink-testing-framework/lib/config/types" "github.com/smartcontractkit/chainlink-testing-framework/lib/networks" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/conversions" @@ -53,7 +53,7 @@ func SetResourceProfile(cpu, mem string) map[string]interface{} { } } -func setNodeConfig(nets []blockchain.EVMNetwork, nodeConfig, commonChain string, configByChain map[string]string) (*corechainlink.Config, string, error) { +func SetNodeConfig(nets []blockchain.EVMNetwork, nodeConfig, commonChain string, configByChain map[string]string) (*corechainlink.Config, string, error) { var tomlCfg *corechainlink.Config var err error var commonChainConfig *evmcfg.Chain @@ -122,7 +122,7 @@ func ChainlinkPropsForUpdate( chainConfigByChain = testInputs.EnvInput.NewCLCluster.Common.ChainConfigTOMLByChain } - _, tomlStr, err := setNodeConfig( + _, tomlStr, err := SetNodeConfig( testInputs.SelectedNetworks, nodeConfig, commonChainConfig, chainConfigByChain, ) @@ -150,7 +150,7 @@ func ChainlinkPropsForUpdate( "version": upgradeTag, }, } - _, tomlStr, err := setNodeConfig( + _, tomlStr, err := SetNodeConfig( testInputs.SelectedNetworks, testInputs.EnvInput.NewCLCluster.Common.BaseConfigTOML, testInputs.EnvInput.NewCLCluster.Common.CommonChainConfigTOML, @@ -216,7 +216,7 @@ func ChainlinkChart( chainConfigByChain = testInputs.EnvInput.NewCLCluster.Common.ChainConfigTOMLByChain } - _, tomlStr, err := setNodeConfig(nets, nodeConfig, commonChainConfig, chainConfigByChain) + _, tomlStr, err := SetNodeConfig(nets, nodeConfig, commonChainConfig, chainConfigByChain) require.NoError(t, err) nodesMap = append(nodesMap, map[string]any{ "name": clNode.Name, @@ -240,7 +240,8 @@ func ChainlinkChart( return chainlink.New(0, clProps) } clProps["replicas"] = pointer.GetInt(testInputs.EnvInput.NewCLCluster.NoOfNodes) - _, tomlStr, err := setNodeConfig( + + _, tomlStr, err := SetNodeConfig( nets, testInputs.EnvInput.NewCLCluster.Common.BaseConfigTOML, testInputs.EnvInput.NewCLCluster.Common.CommonChainConfigTOML, @@ -257,7 +258,7 @@ func DeployLocalCluster( ) (*test_env.CLClusterTestEnv, func() error) { selectedNetworks := testInputs.SelectedNetworks - privateEthereumNetworks := []*ctfconfig.EthereumNetworkConfig{} + privateEthereumNetworks := []*ctf_config.EthereumNetworkConfig{} for _, network := range testInputs.EnvInput.PrivateEthereumNetworks { privateEthereumNetworks = append(privateEthereumNetworks, network) @@ -287,16 +288,17 @@ func DeployLocalCluster( } for _, network := range missing { - chainConfig := &ctfconfig.EthereumChainConfig{} + chainConfig := &ctf_config.EthereumChainConfig{} err := chainConfig.Default() if err != nil { require.NoError(t, err, "failed to get default chain config: %w", err) } else { chainConfig.ChainID = int(network.ChainID) - eth1 := ctfconfigtypes.EthereumVersion_Eth1 - geth := ctfconfigtypes.ExecutionLayer_Geth - privateEthereumNetworks = append(privateEthereumNetworks, &ctfconfig.EthereumNetworkConfig{ + eth1 := ctf_config_types.EthereumVersion_Eth1 + geth := ctf_config_types.ExecutionLayer_Geth + + privateEthereumNetworks = append(privateEthereumNetworks, &ctf_config.EthereumNetworkConfig{ EthereumVersion: ð1, ExecutionLayer: &geth, EthereumChainConfig: chainConfig, @@ -332,10 +334,13 @@ func DeployLocalCluster( // a func to start the CL nodes asynchronously deployCL := func() error { noOfNodes := pointer.GetInt(testInputs.EnvInput.NewCLCluster.NoOfNodes) + if env.ClCluster == nil { + env.ClCluster = &test_env.ClCluster{} + } // if individual nodes are specified, then deploy them with specified configs if len(testInputs.EnvInput.NewCLCluster.Nodes) > 0 { for _, clNode := range testInputs.EnvInput.NewCLCluster.Nodes { - toml, _, err := setNodeConfig( + toml, _, err := SetNodeConfig( selectedNetworks, clNode.BaseConfigTOML, clNode.CommonChainConfigTOML, @@ -364,7 +369,7 @@ func DeployLocalCluster( } else { // if no individual nodes are specified, then deploy the number of nodes specified in the env input with common config for i := 0; i < noOfNodes; i++ { - toml, _, err := setNodeConfig( + toml, _, err := SetNodeConfig( selectedNetworks, testInputs.EnvInput.NewCLCluster.Common.BaseConfigTOML, testInputs.EnvInput.NewCLCluster.Common.CommonChainConfigTOML, @@ -490,7 +495,8 @@ func DeployEnvironments( "fullnameOverride": actions.NetworkName(network.Name), "image": map[string]interface{}{ "repository": "ghcr.io/foundry-rs/foundry", - "tag": "nightly-2442e7a5fc165d7d0b022aa8b9f09dcdf675157b", + "tag": "nightly-5ac78a9cd4b94dc53d1fe5e0f42372b28b5a7559", + // "tag": "nightly-ea2eff95b5c17edd3ffbdfc6daab5ce5cc80afc0", }, "anvil": map[string]interface{}{ "chainId": fmt.Sprintf("%d", network.ChainID), diff --git a/integration-tests/chaos/automation_chaos_test.go b/integration-tests/chaos/automation_chaos_test.go index 59b52f8e0f..c0823f482a 100644 --- a/integration-tests/chaos/automation_chaos_test.go +++ b/integration-tests/chaos/automation_chaos_test.go @@ -6,9 +6,6 @@ import ( "testing" "time" - ocr3 "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3confighelper" - - ocr2keepers30config "github.com/smartcontractkit/chainlink-automation/pkg/v3/config" "github.com/smartcontractkit/chainlink/integration-tests/actions/automationv2" seth_utils "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/seth" @@ -243,55 +240,17 @@ func TestAutomationChaos(t *testing.T) { require.NoError(t, err, "Error tearing down environment") }) - a := automationv2.NewAutomationTestK8s(l, chainClient, chainlinkNodes) + a := automationv2.NewAutomationTestK8s(l, chainClient, chainlinkNodes, &config) a.SetMercuryCredentialName("cred1") - conf := config.Automation.AutomationConfig - a.RegistrySettings = contracts.KeeperRegistrySettings{ - PaymentPremiumPPB: *conf.RegistrySettings.PaymentPremiumPPB, - FlatFeeMicroLINK: *conf.RegistrySettings.FlatFeeMicroLINK, - CheckGasLimit: *conf.RegistrySettings.CheckGasLimit, - StalenessSeconds: conf.RegistrySettings.StalenessSeconds, - GasCeilingMultiplier: *conf.RegistrySettings.GasCeilingMultiplier, - MaxPerformGas: *conf.RegistrySettings.MaxPerformGas, - MinUpkeepSpend: conf.RegistrySettings.MinUpkeepSpend, - FallbackGasPrice: conf.RegistrySettings.FallbackGasPrice, - FallbackLinkPrice: conf.RegistrySettings.FallbackLinkPrice, - MaxCheckDataSize: *conf.RegistrySettings.MaxCheckDataSize, - MaxPerformDataSize: *conf.RegistrySettings.MaxPerformDataSize, - MaxRevertDataSize: *conf.RegistrySettings.MaxRevertDataSize, - RegistryVersion: rv, - } + a.RegistrySettings = actions.ReadRegistryConfig(config) + a.RegistrySettings.RegistryVersion = rv + a.PluginConfig = actions.ReadPluginConfig(config) + a.PublicConfig = actions.ReadPublicConfig(config) a.RegistrarSettings = contracts.KeeperRegistrarSettings{ AutoApproveConfigType: uint8(2), AutoApproveMaxAllowed: 1000, MinLinkJuels: big.NewInt(0), } - plCfg := config.GetAutomationConfig().AutomationConfig.PluginConfig - a.PluginConfig = ocr2keepers30config.OffchainConfig{ - TargetProbability: *plCfg.TargetProbability, - TargetInRounds: *plCfg.TargetInRounds, - PerformLockoutWindow: *plCfg.PerformLockoutWindow, - GasLimitPerReport: *plCfg.GasLimitPerReport, - GasOverheadPerUpkeep: *plCfg.GasOverheadPerUpkeep, - MinConfirmations: *plCfg.MinConfirmations, - MaxUpkeepBatchSize: *plCfg.MaxUpkeepBatchSize, - } - pubCfg := config.GetAutomationConfig().AutomationConfig.PublicConfig - a.PublicConfig = ocr3.PublicConfig{ - DeltaProgress: *pubCfg.DeltaProgress, - DeltaResend: *pubCfg.DeltaResend, - DeltaInitial: *pubCfg.DeltaInitial, - DeltaRound: *pubCfg.DeltaRound, - DeltaGrace: *pubCfg.DeltaGrace, - DeltaCertifiedCommitRequest: *pubCfg.DeltaCertifiedCommitRequest, - DeltaStage: *pubCfg.DeltaStage, - RMax: *pubCfg.RMax, - MaxDurationQuery: *pubCfg.MaxDurationQuery, - MaxDurationObservation: *pubCfg.MaxDurationObservation, - MaxDurationShouldAcceptAttestedReport: *pubCfg.MaxDurationShouldAcceptAttestedReport, - MaxDurationShouldTransmitAcceptedReport: *pubCfg.MaxDurationShouldTransmitAcceptedReport, - F: *pubCfg.F, - } a.SetupAutomationDeployment(t) @@ -300,11 +259,11 @@ func TestAutomationChaos(t *testing.T) { var consumersLogTrigger, consumersConditional []contracts.KeeperConsumer var upkeepidsConditional, upkeepidsLogTrigger []*big.Int - consumersConditional, upkeepidsConditional = actions.DeployConsumers(t, a.ChainClient, a.Registry, a.Registrar, a.LinkToken, numberOfUpkeeps, big.NewInt(defaultLinkFunds), defaultUpkeepGasLimit, false, false, false, nil) + consumersConditional, upkeepidsConditional = actions.DeployConsumers(t, a.ChainClient, a.Registry, a.Registrar, a.LinkToken, numberOfUpkeeps, big.NewInt(defaultLinkFunds), defaultUpkeepGasLimit, false, false, false, nil, &config) consumers := consumersConditional upkeepIDs := upkeepidsConditional if rv >= eth_contracts.RegistryVersion_2_1 { - consumersLogTrigger, upkeepidsLogTrigger = actions.DeployConsumers(t, a.ChainClient, a.Registry, a.Registrar, a.LinkToken, numberOfUpkeeps, big.NewInt(defaultLinkFunds), defaultUpkeepGasLimit, true, false, false, nil) + consumersLogTrigger, upkeepidsLogTrigger = actions.DeployConsumers(t, a.ChainClient, a.Registry, a.Registrar, a.LinkToken, numberOfUpkeeps, big.NewInt(defaultLinkFunds), defaultUpkeepGasLimit, true, false, false, nil, &config) consumers = append(consumersConditional, consumersLogTrigger...) upkeepIDs = append(upkeepidsConditional, upkeepidsLogTrigger...) diff --git a/integration-tests/chaos/ocr_chaos_test.go b/integration-tests/chaos/ocr_chaos_test.go index 89417d782d..821d3cc48c 100644 --- a/integration-tests/chaos/ocr_chaos_test.go +++ b/integration-tests/chaos/ocr_chaos_test.go @@ -179,14 +179,13 @@ func TestOCRChaos(t *testing.T) { }) ms := ctfClient.ConnectMockServer(testEnvironment) - - linkContract, err := contracts.DeployLinkTokenContract(l, seth) + linkContract, err := actions.LinkTokenContract(l, seth, config.OCR) require.NoError(t, err, "Error deploying link token contract") err = actions.FundChainlinkNodesFromRootAddress(l, seth, contracts.ChainlinkK8sClientToChainlinkNodeWithKeysAndAddress(chainlinkNodes), big.NewFloat(10)) require.NoError(t, err) - ocrInstances, err := actions.DeployOCRv1Contracts(l, seth, 1, common.HexToAddress(linkContract.Address()), contracts.ChainlinkK8sClientToChainlinkNodeWithKeysAndAddress(workerNodes)) + ocrInstances, err := actions.SetupOCRv1Contracts(l, seth, config.OCR, common.HexToAddress(linkContract.Address()), contracts.ChainlinkK8sClientToChainlinkNodeWithKeysAndAddress(workerNodes)) require.NoError(t, err) err = actions.CreateOCRJobs(ocrInstances, bootstrapNode, workerNodes, 5, ms, fmt.Sprint(seth.ChainID)) require.NoError(t, err) diff --git a/integration-tests/citool/cmd/check_tests_cmd.go b/integration-tests/citool/cmd/check_tests_cmd.go deleted file mode 100644 index 3ef3712a57..0000000000 --- a/integration-tests/citool/cmd/check_tests_cmd.go +++ /dev/null @@ -1,166 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - "path/filepath" - "regexp" - "strings" - - "github.com/spf13/cobra" - "gopkg.in/yaml.v3" -) - -type JobConfig struct { - Jobs map[string]struct { - Strategy struct { - Matrix struct { - Test []struct { - Path string `yaml:"path"` - TestOpts string `yaml:"testOpts"` - } `yaml:"test"` - } `yaml:"matrix"` - } `yaml:"strategy"` - } `yaml:"jobs"` -} - -var checkTestsCmd = &cobra.Command{ - Use: "check-tests [directory] [yaml file]", - Short: "Check if all tests in a directory are included in the test configurations YAML file", - Args: cobra.ExactArgs(2), - Run: func(_ *cobra.Command, args []string) { - directory := args[0] - yamlFile := args[1] - excludedDirs := []string{"../../citool"} - tests, err := extractTests(directory, excludedDirs) - if err != nil { - fmt.Println("Error extracting tests:", err) - os.Exit(1) - } - - checkTestsInPipeline(yamlFile, tests) - }, -} - -// extractTests scans the given directory and subdirectories (except the excluded ones) -// for Go test files, extracts test function names, and returns a slice of Test. -func extractTests(dir string, excludeDirs []string) ([]Test, error) { - var tests []Test - - // Resolve to absolute path - absDir, err := filepath.Abs(dir) - if err != nil { - return nil, err - } - - // filepath.WalkDir provides more control and is more efficient for skipping directories - err = filepath.WalkDir(absDir, func(path string, d os.DirEntry, err error) error { - if err != nil { - return err - } - - // Check if the current path is one of the excluded directories - for _, exclude := range excludeDirs { - absExclude, _ := filepath.Abs(exclude) - if strings.HasPrefix(path, absExclude) { - if d.IsDir() { - return filepath.SkipDir // Skip this directory - } - return nil // Skip this file - } - } - - if !d.IsDir() && strings.HasSuffix(d.Name(), "_test.go") { - content, err := os.ReadFile(path) - if err != nil { - return err - } - re := regexp.MustCompile(`func (Test\w+)`) - matches := re.FindAllSubmatch(content, -1) - for _, match := range matches { - funcName := string(match[1]) - if funcName == "TestMain" { // Skip "TestMain" - continue - } - tests = append(tests, Test{ - Name: funcName, - Path: mustExtractSubpath(path, "integration-tests"), - }) - } - } - return nil - }) - - return tests, err -} - -// ExtractSubpath extracts a specific subpath from a given full path. -// If the subpath is not found, it returns an error. -func mustExtractSubpath(fullPath, subPath string) string { - index := strings.Index(fullPath, subPath) - if index == -1 { - panic("subpath not found in the provided full path") - } - return fullPath[index:] -} - -func checkTestsInPipeline(yamlFile string, tests []Test) { - data, err := os.ReadFile(yamlFile) - if err != nil { - fmt.Printf("Error reading YAML file: %s\n", err) - return - } - - var config Config - err = yaml.Unmarshal(data, &config) - if err != nil { - fmt.Printf("Error parsing YAML: %s\n", err) - return - } - - missingTests := []string{} // Track missing tests - - for _, test := range tests { - found := false - for _, item := range config.Tests { - if item.Path == test.Path { - if strings.Contains(item.TestCmd, "-test.run") { - if matchTestNameInCmd(item.TestCmd, test.Name) { - found = true - break - } - } else { - found = true - break - } - } - } - if !found { - missingTests = append(missingTests, fmt.Sprintf("ERROR: Test '%s' in file '%s' does not have CI configuration in '%s'", test.Name, test.Path, yamlFile)) - } - } - - if len(missingTests) > 0 { - for _, missing := range missingTests { - fmt.Println(missing) - } - os.Exit(1) // Exit with a failure status - } -} - -// matchTestNameInCmd checks if the given test name matches the -test.run pattern in the command string. -func matchTestNameInCmd(cmd string, testName string) bool { - testRunRegex := regexp.MustCompile(`-test\.run ([^\s]+)`) - matches := testRunRegex.FindStringSubmatch(cmd) - if len(matches) > 1 { - // Extract the regex pattern used in the -test.run command - pattern := matches[1] - - // Escape regex metacharacters in the testName before matching - escapedTestName := regexp.QuoteMeta(testName) - - // Check if the escaped test name matches the extracted pattern - return regexp.MustCompile(pattern).MatchString(escapedTestName) - } - return false -} diff --git a/integration-tests/citool/cmd/check_tests_cmd_test.go b/integration-tests/citool/cmd/check_tests_cmd_test.go deleted file mode 100644 index 4b7f50e7f0..0000000000 --- a/integration-tests/citool/cmd/check_tests_cmd_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package cmd - -import ( - "testing" -) - -func TestMatchTestNameInCmd(t *testing.T) { - tests := []struct { - cmd string - testName string - expected bool - }{ - {"go test -test.run ^TestExample$", "TestExample", true}, - {"go test -test.run ^TestExample$", "TestAnother", false}, - {"go test -test.run ^TestExample$ -v", "TestExample", true}, - {"go test -test.run ^TestExamplePart$", "TestExample", false}, - {"go test -test.run ^TestWithNumbers123$", "TestWithNumbers123", true}, - {"go test -test.run ^Test_With_Underscores$", "Test_With_Underscores", true}, - {"go test -test.run ^Test-With-Dash$", "Test-With-Dash", true}, - {"go test -test.run ^TestWithSpace Space$", "TestWithSpace Space", true}, - {"go test -test.run ^TestWithNewline\nNewline$", "TestWithNewline\nNewline", true}, - {"go test -test.run ^TestOne$|^TestTwo$", "TestOne", true}, - {"go test -test.run ^TestOne$|^TestTwo$", "TestTwo", true}, - {"go test -test.run ^TestOne$|^TestTwo$", "TestThree", false}, - {"go test -test.run TestOne|TestTwo", "TestTwo", true}, - {"go test -test.run TestOne|TestTwo", "TestOne", true}, - {"go test -test.run TestOne|TestTwo|TestThree", "TestFour", false}, - {"go test -test.run ^TestOne$|TestTwo$", "TestTwo", true}, - {"go test -test.run ^TestOne$|TestTwo|TestThree$", "TestThree", true}, - {"go test -test.run TestOne|TestTwo|TestThree", "TestOne", true}, - {"go test -test.run TestOne|TestTwo|TestThree", "TestThree", true}, - {"go test -test.run ^TestA$|^TestB$|^TestC$", "TestA", true}, - {"go test -test.run ^TestA$|^TestB$|^TestC$", "TestB", true}, - {"go test -test.run ^TestA$|^TestB$|^TestC$", "TestD", false}, - {"go test -test.run TestA|^TestB$|TestC", "TestB", true}, - {"go test -test.run ^TestA|^TestB|TestC$", "TestA", true}, - {"go test -test.run ^TestA|^TestB|TestC$", "TestC", true}, - {"go test -test.run ^TestA|^TestB|TestC$", "TestD", false}, - } - - for _, tt := range tests { - result := matchTestNameInCmd(tt.cmd, tt.testName) - if result != tt.expected { - t.Errorf("matchTestNameInCmd(%s, %s) = %t; expected %t", tt.cmd, tt.testName, result, tt.expected) - } - } -} diff --git a/integration-tests/citool/cmd/create_test_config_cmd.go b/integration-tests/citool/cmd/create_test_config_cmd.go deleted file mode 100644 index 3a285a4e98..0000000000 --- a/integration-tests/citool/cmd/create_test_config_cmd.go +++ /dev/null @@ -1,179 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - - "github.com/pelletier/go-toml/v2" - "github.com/spf13/cobra" - - ctfconfig "github.com/smartcontractkit/chainlink-testing-framework/lib/config" - ctfconfigtypes "github.com/smartcontractkit/chainlink-testing-framework/lib/config/types" -) - -var createTestConfigCmd = &cobra.Command{ - Use: "create", - Short: "Create a test config from the provided flags", - Run: func(cmd *cobra.Command, _ []string) { - var tc ctfconfig.TestConfig - - var version, postgresVersion *string - if cmd.Flags().Changed(ChainlinkVersionFlag) { - version = &oc.ChainlinkVersion - } - if cmd.Flags().Changed(ChainlinkPostgresVersionFlag) { - version = &oc.ChainlinkPostgresVersion - } - if version != nil || postgresVersion != nil { - tc.ChainlinkImage = &ctfconfig.ChainlinkImageConfig{ - Version: version, - PostgresVersion: postgresVersion, - } - } - - var upgradeVersion *string - if cmd.Flags().Changed(ChainlinkUpgradeVersionFlag) { - upgradeVersion = &oc.ChainlinkUpgradeVersion - } - if upgradeVersion != nil { - tc.ChainlinkUpgradeImage = &ctfconfig.ChainlinkImageConfig{ - Version: upgradeVersion, - } - } - - var selectedNetworks *[]string - if cmd.Flags().Changed(SelectedNetworksFlag) { - selectedNetworks = &oc.SelectedNetworks - } - if selectedNetworks != nil { - tc.Network = &ctfconfig.NetworkConfig{ - SelectedNetworks: oc.SelectedNetworks, - } - } - - var peryscopeEnabled *bool - var pyroscopeServerURL, pyroscopeEnvironment, pyroscopeKey *string - if cmd.Flags().Changed(PyroscopeEnabledFlag) { - peryscopeEnabled = &oc.PyroscopeEnabled - } - if cmd.Flags().Changed(PyroscopeServerURLFlag) { - pyroscopeServerURL = &oc.PyroscopeServerURL - } - if cmd.Flags().Changed(PyroscopeKeyFlag) { - pyroscopeKey = &oc.PyroscopeKey - } - if cmd.Flags().Changed(PyroscopeEnvironmentFlag) { - pyroscopeEnvironment = &oc.PyroscopeEnvironment - } - if peryscopeEnabled != nil { - tc.Pyroscope = &ctfconfig.PyroscopeConfig{ - Enabled: peryscopeEnabled, - ServerUrl: pyroscopeServerURL, - Environment: pyroscopeEnvironment, - Key: pyroscopeKey, - } - } - - var testLogCollect *bool - if cmd.Flags().Changed(LoggingTestLogCollectFlag) { - testLogCollect = &oc.LoggingTestLogCollect - } - var loggingRunID *string - if cmd.Flags().Changed(LoggingRunIDFlag) { - loggingRunID = &oc.LoggingRunID - } - var loggingLogTargets []string - if cmd.Flags().Changed(LoggingLogTargetsFlag) { - loggingLogTargets = oc.LoggingLogTargets - } - var loggingLokiTenantID *string - if cmd.Flags().Changed(LoggingLokiTenantIDFlag) { - loggingLokiTenantID = &oc.LoggingLokiTenantID - } - var loggingLokiBasicAuth *string - if cmd.Flags().Changed(LoggingLokiBasicAuthFlag) { - loggingLokiBasicAuth = &oc.LoggingLokiBasicAuth - } - var loggingLokiEndpoint *string - if cmd.Flags().Changed(LoggingLokiEndpointFlag) { - loggingLokiEndpoint = &oc.LoggingLokiEndpoint - } - var loggingGrafanaBaseURL *string - if cmd.Flags().Changed(LoggingGrafanaBaseURLFlag) { - loggingGrafanaBaseURL = &oc.LoggingGrafanaBaseURL - } - var loggingGrafanaDashboardURL *string - if cmd.Flags().Changed(LoggingGrafanaDashboardURLFlag) { - loggingGrafanaDashboardURL = &oc.LoggingGrafanaDashboardURL - } - var loggingGrafanaBearerToken *string - if cmd.Flags().Changed(LoggingGrafanaBearerTokenFlag) { - loggingGrafanaBearerToken = &oc.LoggingGrafanaBearerToken - } - - if testLogCollect != nil || loggingRunID != nil || loggingLogTargets != nil || loggingLokiEndpoint != nil || loggingLokiTenantID != nil || loggingLokiBasicAuth != nil || loggingGrafanaBaseURL != nil || loggingGrafanaDashboardURL != nil || loggingGrafanaBearerToken != nil { - tc.Logging = &ctfconfig.LoggingConfig{} - tc.Logging.TestLogCollect = testLogCollect - tc.Logging.RunId = loggingRunID - if loggingLogTargets != nil { - tc.Logging.LogStream = &ctfconfig.LogStreamConfig{ - LogTargets: loggingLogTargets, - } - } - if loggingLokiTenantID != nil || loggingLokiBasicAuth != nil || loggingLokiEndpoint != nil { - tc.Logging.Loki = &ctfconfig.LokiConfig{ - TenantId: loggingLokiTenantID, - BasicAuth: loggingLokiBasicAuth, - Endpoint: loggingLokiEndpoint, - } - } - if loggingGrafanaBaseURL != nil || loggingGrafanaDashboardURL != nil || loggingGrafanaBearerToken != nil { - tc.Logging.Grafana = &ctfconfig.GrafanaConfig{ - BaseUrl: loggingGrafanaBaseURL, - DashboardUrl: loggingGrafanaDashboardURL, - BearerToken: loggingGrafanaBearerToken, - } - } - } - - var privateEthereumNetworkExecutionLayer *string - if cmd.Flags().Changed(PrivateEthereumNetworkExecutionLayerFlag) { - privateEthereumNetworkExecutionLayer = &oc.PrivateEthereumNetworkExecutionLayer - } - var privateEthereumNetworkEthereumVersion *string - if cmd.Flags().Changed(PrivateEthereumNetworkEthereumVersionFlag) { - privateEthereumNetworkEthereumVersion = &oc.PrivateEthereumNetworkEthereumVersion - } - var privateEthereumNetworkCustomDockerImage *string - if cmd.Flags().Changed(PrivateEthereumNetworkCustomDockerImageFlag) { - privateEthereumNetworkCustomDockerImage = &oc.PrivateEthereumNetworkCustomDockerImages - } - if privateEthereumNetworkExecutionLayer != nil || privateEthereumNetworkEthereumVersion != nil || privateEthereumNetworkCustomDockerImage != nil { - var el ctfconfigtypes.ExecutionLayer - if privateEthereumNetworkExecutionLayer != nil { - el = ctfconfigtypes.ExecutionLayer(*privateEthereumNetworkExecutionLayer) - } - var ev ctfconfigtypes.EthereumVersion - if privateEthereumNetworkEthereumVersion != nil { - ev = ctfconfigtypes.EthereumVersion(*privateEthereumNetworkEthereumVersion) - } - var customImages map[ctfconfig.ContainerType]string - if privateEthereumNetworkCustomDockerImage != nil { - customImages = map[ctfconfig.ContainerType]string{"execution_layer": *privateEthereumNetworkCustomDockerImage} - } - tc.PrivateEthereumNetwork = &ctfconfig.EthereumNetworkConfig{ - ExecutionLayer: &el, - EthereumVersion: &ev, - CustomDockerImages: customImages, - } - } - - configToml, err := toml.Marshal(tc) - if err != nil { - fmt.Fprintf(os.Stderr, "Error marshalling TestConfig to TOML: %v\n", err) - os.Exit(1) - } - - fmt.Fprintln(cmd.OutOrStdout(), string(configToml)) - }, -} diff --git a/integration-tests/citool/cmd/csv_export_cmd.go b/integration-tests/citool/cmd/csv_export_cmd.go deleted file mode 100644 index 8fe13440c8..0000000000 --- a/integration-tests/citool/cmd/csv_export_cmd.go +++ /dev/null @@ -1,96 +0,0 @@ -package cmd - -import ( - "encoding/csv" - "fmt" - "os" - "strings" - - "github.com/spf13/cobra" - "gopkg.in/yaml.v3" -) - -var csvExportCmd = &cobra.Command{ - Use: "csvexport", - Short: "Export tests to CSV format", - Run: func(cmd *cobra.Command, _ []string) { - configFile, _ := cmd.Flags().GetString("file") - if err := exportConfigToCSV(configFile); err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } - }, -} - -func init() { - csvExportCmd.Flags().StringP("file", "f", "", "Path to YML file") - err := csvExportCmd.MarkFlagRequired("file") - if err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } -} - -func exportConfigToCSV(configFile string) error { - // Read the YAML file - bytes, err := os.ReadFile(configFile) - if err != nil { - return err - } - - // Unmarshal the YAML into the Config struct - var config Config - if err := yaml.Unmarshal(bytes, &config); err != nil { - return err - } - - // Create a CSV file - file, err := os.Create("output.csv") - if err != nil { - return err - } - defer file.Close() - - writer := csv.NewWriter(file) - defer writer.Flush() - - // Write CSV headers - headers := []string{"ID", "Test Path", "Test Env Type", "Runs On", "Test Cmd", "Test Config Override Required", "Test Secrets Required", "Remote Runner Memory", "Pyroscope Env", "Workflows", "Test Inputs"} - if err := writer.Write(headers); err != nil { - return err - } - - // Iterate over Tests and write data to CSV - for _, test := range config.Tests { - workflows := strings.Join(test.Workflows, ", ") // Combine workflows into a single CSV field - // Serialize TestInputs - testInputs := serializeMap(test.TestInputs) - - record := []string{ - test.ID, - test.Path, - test.TestEnvType, - test.RunsOn, - test.TestCmd, - fmt.Sprintf("%t", test.TestConfigOverrideRequired), - fmt.Sprintf("%t", test.TestSecretsRequired), - test.RemoteRunnerMemory, - test.PyroscopeEnv, - workflows, - testInputs, - } - if err := writer.Write(record); err != nil { - return err - } - } - - return nil -} - -func serializeMap(inputs map[string]string) string { - pairs := make([]string, 0, len(inputs)) - for key, value := range inputs { - pairs = append(pairs, fmt.Sprintf("%s=%s", key, value)) - } - return strings.Join(pairs, ", ") -} diff --git a/integration-tests/citool/cmd/filter_cmd.go b/integration-tests/citool/cmd/filter_cmd.go deleted file mode 100644 index 5d1d41cb9d..0000000000 --- a/integration-tests/citool/cmd/filter_cmd.go +++ /dev/null @@ -1,165 +0,0 @@ -package cmd - -import ( - "encoding/base64" - "encoding/json" - "fmt" - "log" - "os" - "regexp" - "strings" - - "github.com/spf13/cobra" - "gopkg.in/yaml.v2" - - "github.com/smartcontractkit/chainlink-testing-framework/lib/utils" -) - -// Filter tests based on workflow, test type, and test IDs. -func filterTests(allTests []CITestConf, workflow, testType, ids string, envresolve bool) []CITestConf { - workflowFilter := workflow - typeFilter := testType - idFilter := strings.Split(ids, ",") - - var filteredTests []CITestConf - - for _, test := range allTests { - workflowMatch := workflow == "" || contains(test.Workflows, workflowFilter) - typeMatch := testType == "" || test.TestEnvType == typeFilter - idMatch := ids == "*" || ids == "" || contains(idFilter, test.ID) - - if workflowMatch && typeMatch && idMatch { - test.IDSanitized = sanitizeTestID(test.ID) - filteredTests = append(filteredTests, test) - } - if envresolve { - for k, v := range test.TestInputs { - test.TestInputs[k] = utils.MustResolveEnvPlaceholder(v) - } - } - } - - return filteredTests -} - -func filterAndMergeTests(allTests []CITestConf, workflow, testType, base64Tests string, envresolve bool) ([]CITestConf, error) { - decodedBytes, err := base64.StdEncoding.DecodeString(base64Tests) - if err != nil { - return nil, err - } - var decodedTests []CITestConf - err = yaml.Unmarshal(decodedBytes, &decodedTests) - if err != nil { - return nil, err - } - - idFilter := make(map[string]CITestConf) - for _, dt := range decodedTests { - idFilter[dt.ID] = dt - } - - var filteredTests []CITestConf - for _, test := range allTests { - workflowMatch := workflow == "" || contains(test.Workflows, workflow) - typeMatch := testType == "" || test.TestEnvType == testType - - if decodedTest, exists := idFilter[test.ID]; exists && workflowMatch && typeMatch { - // Override test inputs from the base64 encoded tests - for k, v := range decodedTest.TestInputs { - if test.TestInputs == nil { - test.TestInputs = make(map[string]string) - } - test.TestInputs[k] = v - } - test.IDSanitized = sanitizeTestID(test.ID) - filteredTests = append(filteredTests, test) - } - if envresolve { - for k, v := range test.TestInputs { - test.TestInputs[k] = utils.MustResolveEnvPlaceholder(v) - } - } - } - - return filteredTests, nil -} - -func sanitizeTestID(id string) string { - // Define a regular expression that matches any character not a letter, digit, hyphen - re := regexp.MustCompile(`[^a-zA-Z0-9-_]+`) - // Replace all occurrences of disallowed characters with "_" - return re.ReplaceAllString(id, "_") -} - -// Utility function to check if a slice contains a string. -func contains(slice []string, element string) bool { - for _, s := range slice { - if s == element { - return true - } - } - return false -} - -// filterCmd represents the filter command -var filterCmd = &cobra.Command{ - Use: "filter", - Short: "Filter test configurations based on specified criteria", - Long: `Filters tests from a YAML configuration based on name, workflow, test type, and test IDs. -Example usage: -./e2e_tests_tool filter --file .github/e2e-tests.yml --workflow "Run Nightly E2E Tests" --test-env-type "docker" --test-ids "test1,test2"`, - Run: func(cmd *cobra.Command, _ []string) { - yamlFile, _ := cmd.Flags().GetString("file") - workflow, _ := cmd.Flags().GetString("workflow") - testType, _ := cmd.Flags().GetString("test-env-type") - testIDs, _ := cmd.Flags().GetString("test-ids") - testMap, _ := cmd.Flags().GetString("test-list") - envresolve, _ := cmd.Flags().GetBool("envresolve") - - data, err := os.ReadFile(yamlFile) - if err != nil { - fmt.Fprintf(os.Stderr, "Error reading YAML file: %v\n", err) - os.Exit(1) - } - - var config Config - err = yaml.Unmarshal(data, &config) - if err != nil { - fmt.Fprintf(os.Stderr, "Error parsing YAML file %s data: %v\n", yamlFile, err) - os.Exit(1) - } - - var filteredTests []CITestConf - if testMap == "" { - filteredTests = filterTests(config.Tests, workflow, testType, testIDs, envresolve) - } else { - filteredTests, err = filterAndMergeTests(config.Tests, workflow, testType, testMap, envresolve) - if err != nil { - log.Fatalf("Error filtering and merging tests: %v", err) - } - } - matrix := map[string][]CITestConf{"tests": filteredTests} - matrixJSON, err := json.Marshal(matrix) - if err != nil { - fmt.Fprintf(os.Stderr, "Error marshaling matrix to JSON: %v\n", err) - os.Exit(1) - } - - fmt.Printf("%s", matrixJSON) - }, -} - -func init() { - filterCmd.Flags().StringP("file", "f", "", "Path to the YAML file") - filterCmd.Flags().String("test-list", "", "Base64 encoded list of tests (YML objects) to filter by. Can include test_inputs for each test.") - filterCmd.Flags().StringP("test-ids", "i", "*", "Comma-separated list of test IDs to filter by") - filterCmd.Flags().StringP("test-env-type", "y", "", "Type of test to filter by") - filterCmd.Flags().StringP("workflow", "t", "", "Workflow filter") - filterCmd.Flags().Bool("envresolve", false, "Resolve environment variables in test inputs") - - err := filterCmd.MarkFlagRequired("file") - if err != nil { - fmt.Fprintf(os.Stderr, "Error marking flag as required: %v\n", err) - os.Exit(1) - } -} diff --git a/integration-tests/citool/cmd/filter_cmd_test.go b/integration-tests/citool/cmd/filter_cmd_test.go deleted file mode 100644 index ff6e9c981d..0000000000 --- a/integration-tests/citool/cmd/filter_cmd_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package cmd - -import ( - "testing" -) - -func TestFilterTestsByID(t *testing.T) { - tests := []CITestConf{ - {ID: "run_all_in_ocr_tests_go", TestEnvType: "docker"}, - {ID: "run_all_in_ocr2_tests_go", TestEnvType: "docker"}, - {ID: "run_all_in_ocr3_tests_go", TestEnvType: "k8s_remote_runner"}, - } - - cases := []struct { - description string - inputIDs string - expectedLen int - }{ - {"Filter by single ID", "run_all_in_ocr_tests_go", 1}, - {"Filter by multiple IDs", "run_all_in_ocr_tests_go,run_all_in_ocr2_tests_go", 2}, - {"Wildcard to include all", "*", 3}, - {"Empty ID string to include all", "", 3}, - } - - for _, c := range cases { - t.Run(c.description, func(t *testing.T) { - filtered := filterTests(tests, "", "", c.inputIDs, false) - if len(filtered) != c.expectedLen { - t.Errorf("FilterTests(%s) returned %d tests, expected %d", c.description, len(filtered), c.expectedLen) - } - }) - } -} - -func TestFilterTestsIntegration(t *testing.T) { - tests := []CITestConf{ - {ID: "run_all_in_ocr_tests_go", TestEnvType: "docker", Workflows: []string{"Run Nightly E2E Tests"}}, - {ID: "run_all_in_ocr2_tests_go", TestEnvType: "docker", Workflows: []string{"Run PR E2E Tests"}}, - {ID: "run_all_in_ocr3_tests_go", TestEnvType: "k8s_remote_runner", Workflows: []string{"Run PR E2E Tests"}}, - } - - cases := []struct { - description string - inputNames string - inputWorkflow string - inputTestType string - inputIDs string - expectedLen int - }{ - {"Filter by test type and ID", "", "", "docker", "run_all_in_ocr2_tests_go", 1}, - {"Filter by trigger and test type", "", "Run PR E2E Tests", "docker", "*", 1}, - {"No filters applied", "", "", "", "*", 3}, - {"Filter mismatching all criteria", "", "Run Nightly E2E Tests", "", "", 1}, - } - - for _, c := range cases { - t.Run(c.description, func(t *testing.T) { - filtered := filterTests(tests, c.inputWorkflow, c.inputTestType, c.inputIDs, false) - if len(filtered) != c.expectedLen { - t.Errorf("FilterTests(%s) returned %d tests, expected %d", c.description, len(filtered), c.expectedLen) - } - }) - } -} diff --git a/integration-tests/citool/cmd/root_cmd.go b/integration-tests/citool/cmd/root_cmd.go deleted file mode 100644 index fdb4efd572..0000000000 --- a/integration-tests/citool/cmd/root_cmd.go +++ /dev/null @@ -1,32 +0,0 @@ -package cmd - -import ( - "os" - - "github.com/spf13/cobra" -) - -// rootCmd represents the base command when called without any subcommands -var rootCmd = &cobra.Command{ - Use: "citool", - Short: "A tool to manage E2E tests on Github CI", -} - -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { - err := rootCmd.Execute() - if err != nil { - os.Exit(1) - } -} - -func init() { - rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") - - rootCmd.AddCommand(checkTestsCmd) - rootCmd.AddCommand(filterCmd) - rootCmd.AddCommand(csvExportCmd) - rootCmd.AddCommand(testConfigCmd) - testConfigCmd.AddCommand(createTestConfigCmd) -} diff --git a/integration-tests/citool/cmd/test_config_cmd.go b/integration-tests/citool/cmd/test_config_cmd.go deleted file mode 100644 index 3d0766ca0c..0000000000 --- a/integration-tests/citool/cmd/test_config_cmd.go +++ /dev/null @@ -1,123 +0,0 @@ -package cmd - -import ( - "strings" - - "github.com/spf13/cobra" - - "github.com/smartcontractkit/chainlink-testing-framework/lib/utils" -) - -var testConfigCmd = &cobra.Command{ - Use: "test-config", - Short: "Manage test config", -} - -// OverrideConfig holds the configuration data for overrides -type OverrideConfig struct { - ChainlinkImage string - ChainlinkVersion string - ChainlinkUpgradeImage string - ChainlinkUpgradeVersion string - ChainlinkPostgresVersion string - SelectedNetworks []string - PyroscopeEnabled bool - PyroscopeServerURL string - PyroscopeEnvironment string - PyroscopeKey string - LoggingTestLogCollect bool - LoggingRunID string - LoggingLogTargets []string - LoggingLokiTenantID string - LoggingLokiEndpoint string - LoggingLokiBasicAuth string - LoggingGrafanaBaseURL string - LoggingGrafanaDashboardURL string - LoggingGrafanaBearerToken string - PrivateEthereumNetworkExecutionLayer string - PrivateEthereumNetworkEthereumVersion string - PrivateEthereumNetworkCustomDockerImages string -} - -const ( - ChainlinkVersionFlag = "chainlink-version" - ChainlinkUpgradeVersionFlag = "chainlink-upgrade-version" - ChainlinkPostgresVersionFlag = "chainlink-postgres-version" - SelectedNetworksFlag = "selected-networks" - FromBase64ConfigFlag = "from-base64-config" - LoggingLokiBasicAuthFlag = "logging-loki-basic-auth" - LoggingLokiEndpointFlag = "logging-loki-endpoint" - LoggingRunIDFlag = "logging-run-id" - LoggingLokiTenantIDFlag = "logging-loki-tenant-id" - LoggingGrafanaBaseURLFlag = "logging-grafana-base-url" - LoggingGrafanaDashboardURLFlag = "logging-grafana-dashboard-url" - LoggingGrafanaBearerTokenFlag = "logging-grafana-bearer-token" - LoggingLogTargetsFlag = "logging-log-targets" - LoggingTestLogCollectFlag = "logging-test-log-collect" - PyroscopeEnabledFlag = "pyroscope-enabled" - PyroscopeServerURLFlag = "pyroscope-server-url" - PyroscopeKeyFlag = "pyroscope-key" - PyroscopeEnvironmentFlag = "pyroscope-environment" - PrivateEthereumNetworkExecutionLayerFlag = "private-ethereum-network-execution-layer" - PrivateEthereumNetworkEthereumVersionFlag = "private-ethereum-network-ethereum-version" - PrivateEthereumNetworkCustomDockerImageFlag = "private-ethereum-network-custom-docker-image" -) - -var oc OverrideConfig - -func init() { - cmds := []*cobra.Command{createTestConfigCmd} - for _, c := range cmds { - c.Flags().StringArrayVar(&oc.SelectedNetworks, SelectedNetworksFlag, nil, "Selected networks") - c.Flags().StringVar(&oc.ChainlinkVersion, ChainlinkVersionFlag, "", "Chainlink version") - c.Flags().StringVar(&oc.ChainlinkUpgradeVersion, ChainlinkUpgradeVersionFlag, "", "Chainlink upgrade version") - c.Flags().StringVar(&oc.ChainlinkPostgresVersion, ChainlinkPostgresVersionFlag, "", "Chainlink Postgres version") - c.Flags().BoolVar(&oc.PyroscopeEnabled, PyroscopeEnabledFlag, false, "Pyroscope enabled") - c.Flags().StringVar(&oc.PyroscopeServerURL, PyroscopeServerURLFlag, "", "Pyroscope server URL") - c.Flags().StringVar(&oc.PyroscopeKey, PyroscopeKeyFlag, "", "Pyroscope key") - c.Flags().StringVar(&oc.PyroscopeEnvironment, PyroscopeEnvironmentFlag, "", "Pyroscope environment") - c.Flags().BoolVar(&oc.LoggingTestLogCollect, LoggingTestLogCollectFlag, false, "Test log collect") - c.Flags().StringVar(&oc.LoggingRunID, LoggingRunIDFlag, "", "Run ID") - c.Flags().StringArrayVar(&oc.LoggingLogTargets, LoggingLogTargetsFlag, nil, "Logging.LogStream.LogTargets") - c.Flags().StringVar(&oc.LoggingLokiEndpoint, LoggingLokiEndpointFlag, "", "") - c.Flags().StringVar(&oc.LoggingLokiTenantID, LoggingLokiTenantIDFlag, "", "") - c.Flags().StringVar(&oc.LoggingLokiBasicAuth, LoggingLokiBasicAuthFlag, "", "") - c.Flags().StringVar(&oc.LoggingGrafanaBaseURL, LoggingGrafanaBaseURLFlag, "", "") - c.Flags().StringVar(&oc.LoggingGrafanaDashboardURL, LoggingGrafanaDashboardURLFlag, "", "") - c.Flags().StringVar(&oc.LoggingGrafanaBearerToken, LoggingGrafanaBearerTokenFlag, "", "") - c.Flags().StringVar(&oc.PrivateEthereumNetworkExecutionLayer, PrivateEthereumNetworkExecutionLayerFlag, "", "") - c.Flags().StringVar(&oc.PrivateEthereumNetworkEthereumVersion, PrivateEthereumNetworkEthereumVersionFlag, "", "") - c.Flags().StringVar(&oc.PrivateEthereumNetworkCustomDockerImages, PrivateEthereumNetworkCustomDockerImageFlag, "", "") - - c.PreRun = func(_ *cobra.Command, _ []string) { - // Resolve selected networks environment variable if set - if len(oc.SelectedNetworks) > 0 { - _, hasEnvVar := utils.LookupEnvVarName(oc.SelectedNetworks[0]) - if hasEnvVar { - selectedNetworks := utils.MustResolveEnvPlaceholder(oc.SelectedNetworks[0]) - oc.SelectedNetworks = strings.Split(selectedNetworks, ",") - } - } - - // Resolve all other environment variables - oc.ChainlinkImage = utils.MustResolveEnvPlaceholder(oc.ChainlinkImage) - oc.ChainlinkVersion = utils.MustResolveEnvPlaceholder(oc.ChainlinkVersion) - oc.ChainlinkUpgradeImage = utils.MustResolveEnvPlaceholder(oc.ChainlinkUpgradeImage) - oc.ChainlinkUpgradeVersion = utils.MustResolveEnvPlaceholder(oc.ChainlinkUpgradeVersion) - oc.ChainlinkPostgresVersion = utils.MustResolveEnvPlaceholder(oc.ChainlinkPostgresVersion) - oc.PyroscopeServerURL = utils.MustResolveEnvPlaceholder(oc.PyroscopeServerURL) - oc.PyroscopeKey = utils.MustResolveEnvPlaceholder(oc.PyroscopeKey) - oc.PyroscopeEnvironment = utils.MustResolveEnvPlaceholder(oc.PyroscopeEnvironment) - oc.LoggingRunID = utils.MustResolveEnvPlaceholder(oc.LoggingRunID) - oc.LoggingLokiTenantID = utils.MustResolveEnvPlaceholder(oc.LoggingLokiTenantID) - oc.LoggingLokiEndpoint = utils.MustResolveEnvPlaceholder(oc.LoggingLokiEndpoint) - oc.LoggingLokiBasicAuth = utils.MustResolveEnvPlaceholder(oc.LoggingLokiBasicAuth) - oc.LoggingGrafanaBaseURL = utils.MustResolveEnvPlaceholder(oc.LoggingGrafanaBaseURL) - oc.LoggingGrafanaDashboardURL = utils.MustResolveEnvPlaceholder(oc.LoggingGrafanaDashboardURL) - oc.LoggingGrafanaBearerToken = utils.MustResolveEnvPlaceholder(oc.LoggingGrafanaBearerToken) - oc.PrivateEthereumNetworkExecutionLayer = utils.MustResolveEnvPlaceholder(oc.PrivateEthereumNetworkExecutionLayer) - oc.PrivateEthereumNetworkEthereumVersion = utils.MustResolveEnvPlaceholder(oc.PrivateEthereumNetworkEthereumVersion) - oc.PrivateEthereumNetworkCustomDockerImages = utils.MustResolveEnvPlaceholder(oc.PrivateEthereumNetworkCustomDockerImages) - } - } -} diff --git a/integration-tests/citool/cmd/test_config_cmd_test.go b/integration-tests/citool/cmd/test_config_cmd_test.go deleted file mode 100644 index 4e710d06f6..0000000000 --- a/integration-tests/citool/cmd/test_config_cmd_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package cmd - -import ( - "bytes" - "testing" - - "github.com/pelletier/go-toml/v2" - "github.com/spf13/cobra" - "github.com/stretchr/testify/assert" - - ctf_config "github.com/smartcontractkit/chainlink-testing-framework/lib/config" - ctfconfigtypes "github.com/smartcontractkit/chainlink-testing-framework/lib/config/types" -) - -func TestCreateTestConfigCmd(t *testing.T) { - tests := []struct { - name string - args []string - want interface{} - check func(t *testing.T, tc *ctf_config.TestConfig) - wantErr bool - }{ - { - name: "LoggingLogTargets", - args: []string{"create", "--logging-log-targets=target1", "--logging-log-targets=target2"}, - check: func(t *testing.T, tc *ctf_config.TestConfig) { - assert.NotNil(t, tc.Logging) - assert.NotNil(t, tc.Logging.LogStream) - assert.Equal(t, []string{"target1", "target2"}, tc.Logging.LogStream.LogTargets) - }, - }, - { - name: "PrivateEthereumNetworkExecutionLayerFlag", - args: []string{"create", "--private-ethereum-network-execution-layer=geth", "--private-ethereum-network-ethereum-version=1.10.0"}, - check: func(t *testing.T, tc *ctf_config.TestConfig) { - assert.NotNil(t, tc.PrivateEthereumNetwork) - assert.NotNil(t, tc.PrivateEthereumNetwork.ExecutionLayer) - assert.Equal(t, ctfconfigtypes.ExecutionLayer("geth"), *tc.PrivateEthereumNetwork.ExecutionLayer) - assert.Equal(t, ctfconfigtypes.EthereumVersion("1.10.0"), *tc.PrivateEthereumNetwork.EthereumVersion) - }, - }, - { - name: "PrivateEthereumNetworkCustomDockerImageFlag", - args: []string{"create", "--private-ethereum-network-execution-layer=geth", "--private-ethereum-network-ethereum-version=1.10.0", "--private-ethereum-network-custom-docker-image=custom-image:v1.0"}, - check: func(t *testing.T, tc *ctf_config.TestConfig) { - assert.NotNil(t, tc.PrivateEthereumNetwork) - assert.NotNil(t, tc.PrivateEthereumNetwork.ExecutionLayer) - assert.Equal(t, map[ctf_config.ContainerType]string{"execution_layer": "custom-image:v1.0"}, tc.PrivateEthereumNetwork.CustomDockerImages) - }, - }, - } - - rootCmd := &cobra.Command{} - rootCmd.AddCommand(createTestConfigCmd) - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - rootCmd.SetArgs(tt.args) - var out bytes.Buffer - rootCmd.SetOutput(&out) - err := rootCmd.Execute() - if (err != nil) != tt.wantErr { - t.Fatalf("Execute() error = %v, wantErr %v", err, tt.wantErr) - } - var tc ctf_config.TestConfig - err = toml.Unmarshal(out.Bytes(), &tc) - if err != nil { - t.Fatalf("Failed to unmarshal output: %v", err) - } - if tt.check != nil { - tt.check(t, &tc) - } - }) - } -} diff --git a/integration-tests/citool/cmd/types.go b/integration-tests/citool/cmd/types.go deleted file mode 100644 index 3c347e9406..0000000000 --- a/integration-tests/citool/cmd/types.go +++ /dev/null @@ -1,26 +0,0 @@ -package cmd - -type Test struct { - Name string - Path string -} - -// CITestConf defines the configuration for running a test in a CI environment, specifying details like test ID, path, type, runner settings, command, and associated workflows. -type CITestConf struct { - ID string `yaml:"id" json:"id"` - IDSanitized string `json:"id_sanitized"` - Path string `yaml:"path" json:"path"` - TestEnvType string `yaml:"test_env_type" json:"test_env_type"` - RunsOn string `yaml:"runs_on" json:"runs_on"` - TestCmd string `yaml:"test_cmd" json:"test_cmd"` - TestConfigOverrideRequired bool `yaml:"test_config_override_required" json:"testConfigOverrideRequired"` - TestSecretsRequired bool `yaml:"test_secrets_required" json:"testSecretsRequired"` - TestInputs map[string]string `yaml:"test_inputs" json:"test_inputs"` - RemoteRunnerMemory string `yaml:"remote_runner_memory" json:"remoteRunnerMemory"` - PyroscopeEnv string `yaml:"pyroscope_env" json:"pyroscopeEnv"` - Workflows []string `yaml:"workflows" json:"workflows"` -} - -type Config struct { - Tests []CITestConf `yaml:"runner-test-matrix"` -} diff --git a/integration-tests/citool/main.go b/integration-tests/citool/main.go deleted file mode 100644 index 4fa6cac56e..0000000000 --- a/integration-tests/citool/main.go +++ /dev/null @@ -1,9 +0,0 @@ -package main - -import ( - "github.com/smartcontractkit/chainlink/integration-tests/citool/cmd" -) - -func main() { - cmd.Execute() -} diff --git a/integration-tests/client/chainlink.go b/integration-tests/client/chainlink.go index 5d9b6a3d08..4cc015d74a 100644 --- a/integration-tests/client/chainlink.go +++ b/integration-tests/client/chainlink.go @@ -2,6 +2,7 @@ package client import ( + "crypto/tls" "fmt" "math/big" "net/http" @@ -45,14 +46,10 @@ type ChainlinkClient struct { // NewChainlinkClient creates a new Chainlink model using a provided config func NewChainlinkClient(c *ChainlinkConfig, logger zerolog.Logger) (*ChainlinkClient, error) { - rc, err := initRestyClient(c.URL, c.Email, c.Password, c.HTTPTimeout) + rc, err := initRestyClient(c.URL, c.Email, c.Password, c.Headers, c.HTTPTimeout) if err != nil { return nil, err } - _, isSet := os.LookupEnv("CL_CLIENT_DEBUG") - if isSet { - rc.SetDebug(true) - } return &ChainlinkClient{ Config: c, APIClient: rc, @@ -61,8 +58,11 @@ func NewChainlinkClient(c *ChainlinkConfig, logger zerolog.Logger) (*ChainlinkCl }, nil } -func initRestyClient(url string, email string, password string, timeout *time.Duration) (*resty.Client, error) { - rc := resty.New().SetBaseURL(url) +func initRestyClient(url string, email string, password string, headers map[string]string, timeout *time.Duration) (*resty.Client, error) { + isDebug := os.Getenv("RESTY_DEBUG") == "true" + // G402 - TODO: certificates + //nolint + rc := resty.New().SetBaseURL(url).SetHeaders(headers).SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}).SetDebug(isDebug) if timeout != nil { rc.SetTimeout(*timeout) } @@ -74,7 +74,7 @@ func initRestyClient(url string, email string, password string, timeout *time.Du for i := 0; i < retryCount; i++ { resp, err = rc.R().SetBody(session).Post("/sessions") if err != nil { - log.Debug().Err(err).Str("URL", url).Interface("Session Details", session).Msg("Error connecting to Chainlink node, retrying") + log.Warn().Err(err).Str("URL", url).Interface("Session Details", session).Msg("Error connecting to Chainlink node, retrying") time.Sleep(5 * time.Second) } else { break @@ -1117,7 +1117,9 @@ func CreateNodeKeysBundle(nodes []*ChainlinkClient, chainName string, chainId st if err != nil { return nil, nil, err } - + if len(p2pkeys.Data) == 0 { + return nil, nil, fmt.Errorf("found no P2P Keys on the Chainlink node. Node URL: %s", n.URL()) + } peerID := p2pkeys.Data[0].Attributes.PeerID // If there is already a txkey present for the chain skip creating a new one // otherwise the test logic will need multiple key management (like funding all the keys, diff --git a/integration-tests/client/chainlink_k8s.go b/integration-tests/client/chainlink_k8s.go index 969e060ca1..4cdfa96c53 100644 --- a/integration-tests/client/chainlink_k8s.go +++ b/integration-tests/client/chainlink_k8s.go @@ -2,7 +2,6 @@ package client import ( - "os" "regexp" "github.com/rs/zerolog/log" @@ -23,14 +22,10 @@ type ChainlinkK8sClient struct { // NewChainlink creates a new Chainlink model using a provided config func NewChainlinkK8sClient(c *ChainlinkConfig, podName, chartName string) (*ChainlinkK8sClient, error) { - rc, err := initRestyClient(c.URL, c.Email, c.Password, c.HTTPTimeout) + rc, err := initRestyClient(c.URL, c.Email, c.Password, c.Headers, c.HTTPTimeout) if err != nil { return nil, err } - _, isSet := os.LookupEnv("CL_CLIENT_DEBUG") - if isSet { - rc.SetDebug(true) - } return &ChainlinkK8sClient{ ChainlinkClient: &ChainlinkClient{ APIClient: rc, diff --git a/integration-tests/client/chainlink_models.go b/integration-tests/client/chainlink_models.go index a0435d5336..86e9f75902 100644 --- a/integration-tests/client/chainlink_models.go +++ b/integration-tests/client/chainlink_models.go @@ -20,11 +20,12 @@ type EIServiceConfig struct { // ChainlinkConfig represents the variables needed to connect to a Chainlink node type ChainlinkConfig struct { - URL string `toml:",omitempty"` - Email string `toml:",omitempty"` - Password string `toml:",omitempty"` - InternalIP string `toml:",omitempty"` - HTTPTimeout *time.Duration `toml:"-"` + URL string `toml:",omitempty"` + Email string `toml:",omitempty"` + Password string `toml:",omitempty"` + InternalIP string `toml:",omitempty"` + Headers map[string]string `toml:",omitempty"` + HTTPTimeout *time.Duration `toml:"-"` } // ResponseSlice is the generic model that can be used for all Chainlink API responses that are an slice diff --git a/integration-tests/contracts/contract_models.go b/integration-tests/contracts/contract_models.go index 853ce8e86b..006ee5db6a 100644 --- a/integration-tests/contracts/contract_models.go +++ b/integration-tests/contracts/contract_models.go @@ -151,9 +151,13 @@ type OffchainAggregatorData struct { type ChainlinkNodeWithKeysAndAddress interface { MustReadOCRKeys() (*client.OCRKeys, error) MustReadP2PKeys() (*client.P2PKeys, error) - ExportEVMKeysForChain(string) ([]*client.ExportedEVMKey, error) PrimaryEthAddress() (string, error) EthAddresses() ([]string, error) + ChainlinkKeyExporter +} + +type ChainlinkKeyExporter interface { + ExportEVMKeysForChain(string) ([]*client.ExportedEVMKey, error) } type ChainlinkNodeWithForwarder interface { @@ -226,6 +230,12 @@ type JobByInstance struct { Instance string } +type MockLINKETHFeed interface { + Address() string + LatestRoundData() (*big.Int, error) + LatestRoundDataUpdatedAt() (*big.Int, error) +} + type MockETHLINKFeed interface { Address() string LatestRoundData() (*big.Int, error) diff --git a/integration-tests/contracts/contract_vrf_models.go b/integration-tests/contracts/contract_vrf_models.go index 3aca32a66f..8d1424cbfd 100644 --- a/integration-tests/contracts/contract_vrf_models.go +++ b/integration-tests/contracts/contract_vrf_models.go @@ -76,6 +76,8 @@ type VRFCoordinatorV2 interface { WaitForConfigSetEvent(timeout time.Duration) (*CoordinatorConfigSet, error) OracleWithdraw(recipient common.Address, amount *big.Int) error GetBlockHashStoreAddress(ctx context.Context) (common.Address, error) + GetLinkAddress(ctx context.Context) (common.Address, error) + GetLinkNativeFeed(ctx context.Context) (common.Address, error) } type VRFCoordinatorV2_5 interface { @@ -121,6 +123,9 @@ type VRFCoordinatorV2_5 interface { ParseRandomWordsFulfilled(log types.Log) (*CoordinatorRandomWordsFulfilled, error) WaitForConfigSetEvent(timeout time.Duration) (*CoordinatorConfigSet, error) GetBlockHashStoreAddress(ctx context.Context) (common.Address, error) + GetLinkAddress(ctx context.Context) (common.Address, error) + GetLinkNativeFeed(ctx context.Context) (common.Address, error) + GetConfig(ctx context.Context) (vrf_coordinator_v2_5.SConfig, error) } type VRFCoordinatorV2PlusUpgradedVersion interface { diff --git a/integration-tests/contracts/ethereum_contracts.go b/integration-tests/contracts/ethereum_contracts.go index 9ce118b556..288a36978a 100644 --- a/integration-tests/contracts/ethereum_contracts.go +++ b/integration-tests/contracts/ethereum_contracts.go @@ -14,6 +14,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/rs/zerolog" "github.com/rs/zerolog/log" + "github.com/smartcontractkit/libocr/gethwrappers/offchainaggregator" "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" ocrConfigHelper "github.com/smartcontractkit/libocr/offchainreporting/confighelper" @@ -203,22 +204,17 @@ type EthereumOffchainAggregator struct { l zerolog.Logger } -func LoadOffchainAggregator(l zerolog.Logger, seth *seth.Client, contractAddress common.Address) (EthereumOffchainAggregator, error) { - abi, err := offchainaggregator.OffchainAggregatorMetaData.GetAbi() - if err != nil { - return EthereumOffchainAggregator{}, fmt.Errorf("failed to get OffChain Aggregator ABI: %w", err) - } - seth.ContractStore.AddABI("OffChainAggregator", *abi) - seth.ContractStore.AddBIN("OffChainAggregator", common.FromHex(offchainaggregator.OffchainAggregatorMetaData.Bin)) +func LoadOffChainAggregator(l zerolog.Logger, sethClient *seth.Client, contractAddress common.Address) (EthereumOffchainAggregator, error) { + loader := seth.NewContractLoader[offchainaggregator.OffchainAggregator](sethClient) + instance, err := loader.LoadContract("LinkToken", contractAddress, offchainaggregator.OffchainAggregatorMetaData.GetAbi, offchainaggregator.NewOffchainAggregator) - ocr, err := offchainaggregator.NewOffchainAggregator(contractAddress, wrappers.MustNewWrappedContractBackend(nil, seth)) if err != nil { - return EthereumOffchainAggregator{}, fmt.Errorf("failed to instantiate OCR instance: %w", err) + return EthereumOffchainAggregator{}, fmt.Errorf("failed to instantiate OCR v2 instance: %w", err) } return EthereumOffchainAggregator{ - client: seth, - ocr: ocr, + client: sethClient, + ocr: instance, address: &contractAddress, l: l, }, nil @@ -356,10 +352,6 @@ func (o *EthereumOffchainAggregator) SetConfig( return err } - // fails with error setting OCR config for contract '0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82': both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified - // but we only have gasPrice set... It also fails with the same error when we enable EIP-1559 - // fails when we wait for it to be minted, inside the wrapper there's no error when we call it, so it must be something inside smart contract - // that's reverting it and maybe the error message is completely off _, err = o.client.Decode(o.ocr.SetConfig(o.client.NewTXOpts(), signers, transmitters, threshold, encodedConfigVersion, encodedConfig)) return err } @@ -583,36 +575,36 @@ type EthereumOffchainAggregatorV2 struct { l zerolog.Logger } -func LoadOffChainAggregatorV2(l zerolog.Logger, seth *seth.Client, contractAddress common.Address) (EthereumOffchainAggregatorV2, error) { - oAbi, err := ocr2aggregator.OCR2AggregatorMetaData.GetAbi() +func LoadOffchainAggregatorV2(l zerolog.Logger, seth *seth.Client, address common.Address) (EthereumOffchainAggregatorV2, error) { + contractAbi, err := ocr2aggregator.OCR2AggregatorMetaData.GetAbi() if err != nil { - return EthereumOffchainAggregatorV2{}, fmt.Errorf("failed to get OffChain Aggregator ABI: %w", err) + return EthereumOffchainAggregatorV2{}, fmt.Errorf("failed to get OffChain Aggregator v2 ABI: %w", err) } - seth.ContractStore.AddABI("OffChainAggregatorV2", *oAbi) + seth.ContractStore.AddABI("OffChainAggregatorV2", *contractAbi) seth.ContractStore.AddBIN("OffChainAggregatorV2", common.FromHex(ocr2aggregator.OCR2AggregatorMetaData.Bin)) - ocr2, err := ocr2aggregator.NewOCR2Aggregator(contractAddress, seth.Client) + ocr2, err := ocr2aggregator.NewOCR2Aggregator(address, seth.Client) if err != nil { - return EthereumOffchainAggregatorV2{}, fmt.Errorf("failed to instantiate OCR instance: %w", err) + return EthereumOffchainAggregatorV2{}, fmt.Errorf("failed to instantiate OCRv2 instance: %w", err) } return EthereumOffchainAggregatorV2{ client: seth, contract: ocr2, - address: &contractAddress, + address: &address, l: l, }, nil } func DeployOffchainAggregatorV2(l zerolog.Logger, seth *seth.Client, linkTokenAddress common.Address, offchainOptions OffchainOptions) (EthereumOffchainAggregatorV2, error) { - oAbi, err := ocr2aggregator.OCR2AggregatorMetaData.GetAbi() + contractAbi, err := ocr2aggregator.OCR2AggregatorMetaData.GetAbi() if err != nil { - return EthereumOffchainAggregatorV2{}, fmt.Errorf("failed to get OffChain Aggregator ABI: %w", err) + return EthereumOffchainAggregatorV2{}, fmt.Errorf("failed to get OffChain Aggregator v2 ABI: %w", err) } - seth.ContractStore.AddABI("OffChainAggregatorV2", *oAbi) + seth.ContractStore.AddABI("OffChainAggregatorV2", *contractAbi) seth.ContractStore.AddBIN("OffChainAggregatorV2", common.FromHex(ocr2aggregator.OCR2AggregatorMetaData.Bin)) - ocrDeploymentData2, err := seth.DeployContract(seth.NewTXOpts(), "OffChainAggregatorV2", *oAbi, common.FromHex(ocr2aggregator.OCR2AggregatorMetaData.Bin), + ocrDeploymentData2, err := seth.DeployContract(seth.NewTXOpts(), "OffChainAggregatorV2", *contractAbi, common.FromHex(ocr2aggregator.OCR2AggregatorMetaData.Bin), linkTokenAddress, offchainOptions.MinimumAnswer, offchainOptions.MaximumAnswer, @@ -623,12 +615,12 @@ func DeployOffchainAggregatorV2(l zerolog.Logger, seth *seth.Client, linkTokenAd ) if err != nil { - return EthereumOffchainAggregatorV2{}, fmt.Errorf("OCR instance deployment have failed: %w", err) + return EthereumOffchainAggregatorV2{}, fmt.Errorf("OCRv2 instance deployment have failed: %w", err) } ocr2, err := ocr2aggregator.NewOCR2Aggregator(ocrDeploymentData2.Address, wrappers.MustNewWrappedContractBackend(nil, seth)) if err != nil { - return EthereumOffchainAggregatorV2{}, fmt.Errorf("failed to instantiate OCR instance: %w", err) + return EthereumOffchainAggregatorV2{}, fmt.Errorf("failed to instantiate OCRv2 instance: %w", err) } return EthereumOffchainAggregatorV2{ @@ -714,9 +706,9 @@ func (e *EthereumOffchainAggregatorV2) SetConfig(ocrConfig *OCRv2Config) error { Interface("Signers", ocrConfig.Signers). Interface("Transmitters", ocrConfig.Transmitters). Uint8("F", ocrConfig.F). - Bytes("OnchainConfig", ocrConfig.OnchainConfig). + Str("OnchainConfig", string(ocrConfig.OnchainConfig)). Uint64("OffchainConfigVersion", ocrConfig.OffchainConfigVersion). - Bytes("OffchainConfig", ocrConfig.OffchainConfig). + Str("OffchainConfig", string(ocrConfig.OffchainConfig)). Msg("Setting OCRv2 Config") _, err := e.client.Decode(e.contract.SetConfig( @@ -771,22 +763,16 @@ func DeployLinkTokenContract(l zerolog.Logger, client *seth.Client) (*EthereumLi } func LoadLinkTokenContract(l zerolog.Logger, client *seth.Client, address common.Address) (*EthereumLinkToken, error) { - abi, err := link_token_interface.LinkTokenMetaData.GetAbi() - if err != nil { - return &EthereumLinkToken{}, fmt.Errorf("failed to get LinkToken ABI: %w", err) - } + loader := seth.NewContractLoader[link_token_interface.LinkToken](client) + instance, err := loader.LoadContract("LinkToken", address, link_token_interface.LinkTokenMetaData.GetAbi, link_token_interface.NewLinkToken) - client.ContractStore.AddABI("LinkToken", *abi) - client.ContractStore.AddBIN("LinkToken", common.FromHex(link_token_interface.LinkTokenMetaData.Bin)) - - linkToken, err := link_token_interface.NewLinkToken(address, wrappers.MustNewWrappedContractBackend(nil, client)) if err != nil { return &EthereumLinkToken{}, fmt.Errorf("failed to instantiate LinkToken instance: %w", err) } return &EthereumLinkToken{ client: client, - instance: linkToken, + instance: instance, address: address, l: l, }, nil @@ -1196,19 +1182,19 @@ func (v *EthereumMockETHLINKFeed) LatestRoundDataUpdatedAt() (*big.Int, error) { return data.UpdatedAt, nil } -func DeployMockETHLINKFeed(client *seth.Client, answer *big.Int) (MockETHLINKFeed, error) { +func DeployMockLINKETHFeed(client *seth.Client, answer *big.Int) (MockLINKETHFeed, error) { abi, err := mock_ethlink_aggregator_wrapper.MockETHLINKAggregatorMetaData.GetAbi() if err != nil { - return &EthereumMockETHLINKFeed{}, fmt.Errorf("failed to get MockETHLINKFeed ABI: %w", err) + return &EthereumMockETHLINKFeed{}, fmt.Errorf("failed to get MockLINKETHFeed ABI: %w", err) } - data, err := client.DeployContract(client.NewTXOpts(), "MockETHLINKFeed", *abi, common.FromHex(mock_ethlink_aggregator_wrapper.MockETHLINKAggregatorMetaData.Bin), answer) + data, err := client.DeployContract(client.NewTXOpts(), "MockLINKETHFeed", *abi, common.FromHex(mock_ethlink_aggregator_wrapper.MockETHLINKAggregatorMetaData.Bin), answer) if err != nil { - return &EthereumMockETHLINKFeed{}, fmt.Errorf("MockETHLINKFeed instance deployment have failed: %w", err) + return &EthereumMockETHLINKFeed{}, fmt.Errorf("MockLINKETHFeed instance deployment have failed: %w", err) } instance, err := mock_ethlink_aggregator_wrapper.NewMockETHLINKAggregator(data.Address, wrappers.MustNewWrappedContractBackend(nil, client)) if err != nil { - return &EthereumMockETHLINKFeed{}, fmt.Errorf("failed to instantiate MockETHLINKFeed instance: %w", err) + return &EthereumMockETHLINKFeed{}, fmt.Errorf("failed to instantiate MockLINKETHFeed instance: %w", err) } return &EthereumMockETHLINKFeed{ @@ -1218,17 +1204,17 @@ func DeployMockETHLINKFeed(client *seth.Client, answer *big.Int) (MockETHLINKFee }, nil } -func LoadMockETHLINKFeed(client *seth.Client, address common.Address) (MockETHLINKFeed, error) { +func LoadMockLINKETHFeed(client *seth.Client, address common.Address) (MockLINKETHFeed, error) { abi, err := mock_ethlink_aggregator_wrapper.MockETHLINKAggregatorMetaData.GetAbi() if err != nil { - return &EthereumMockETHLINKFeed{}, fmt.Errorf("failed to get MockETHLINKFeed ABI: %w", err) + return &EthereumMockETHLINKFeed{}, fmt.Errorf("failed to get MockLINKETHFeed ABI: %w", err) } - client.ContractStore.AddABI("MockETHLINKFeed", *abi) - client.ContractStore.AddBIN("MockETHLINKFeed", common.FromHex(mock_ethlink_aggregator_wrapper.MockETHLINKAggregatorMetaData.Bin)) + client.ContractStore.AddABI("MockLINKETHFeed", *abi) + client.ContractStore.AddBIN("MockLINKETHFeed", common.FromHex(mock_ethlink_aggregator_wrapper.MockETHLINKAggregatorMetaData.Bin)) instance, err := mock_ethlink_aggregator_wrapper.NewMockETHLINKAggregator(address, wrappers.MustNewWrappedContractBackend(nil, client)) if err != nil { - return &EthereumMockETHLINKFeed{}, fmt.Errorf("failed to instantiate MockETHLINKFeed instance: %w", err) + return &EthereumMockETHLINKFeed{}, fmt.Errorf("failed to instantiate MockLINKETHFeed instance: %w", err) } return &EthereumMockETHLINKFeed{ diff --git a/integration-tests/contracts/ethereum_contracts_automation.go b/integration-tests/contracts/ethereum_contracts_automation.go index 02132b1a12..3e18fe177f 100644 --- a/integration-tests/contracts/ethereum_contracts_automation.go +++ b/integration-tests/contracts/ethereum_contracts_automation.go @@ -148,7 +148,7 @@ type EthereumKeeperRegistry struct { func (v *EthereumKeeperRegistry) ReorgProtectionEnabled() bool { chainId := v.client.ChainID // reorg protection is disabled in polygon zkEVM and Scroll bc currently there is no way to get the block hash onchain - return v.version != ethereum.RegistryVersion_2_2 || (chainId != 1101 && chainId != 1442 && chainId != 2442 && chainId != 534352 && chainId != 534351) + return v.version < ethereum.RegistryVersion_2_2 || (chainId != 1101 && chainId != 1442 && chainId != 2442 && chainId != 534352 && chainId != 534351) } func (v *EthereumKeeperRegistry) ChainModuleAddress() common.Address { @@ -194,10 +194,11 @@ func (v *EthereumKeeperRegistry) RegistryOwnerAddress() common.Address { func (v *EthereumKeeperRegistry) SetConfigTypeSafe(ocrConfig OCRv2Config) error { txOpts := v.client.NewTXOpts() var err error + var decodedTx *seth.DecodedTransaction switch v.version { case ethereum.RegistryVersion_2_1: - _, err = v.client.Decode(v.registry2_1.SetConfigTypeSafe(txOpts, + decodedTx, err = v.client.Decode(v.registry2_1.SetConfigTypeSafe(txOpts, ocrConfig.Signers, ocrConfig.Transmitters, ocrConfig.F, @@ -206,7 +207,7 @@ func (v *EthereumKeeperRegistry) SetConfigTypeSafe(ocrConfig OCRv2Config) error ocrConfig.OffchainConfig, )) case ethereum.RegistryVersion_2_2: - _, err = v.client.Decode(v.registry2_2.SetConfigTypeSafe(txOpts, + decodedTx, err = v.client.Decode(v.registry2_2.SetConfigTypeSafe(txOpts, ocrConfig.Signers, ocrConfig.Transmitters, ocrConfig.F, @@ -215,7 +216,7 @@ func (v *EthereumKeeperRegistry) SetConfigTypeSafe(ocrConfig OCRv2Config) error ocrConfig.OffchainConfig, )) case ethereum.RegistryVersion_2_3: - _, err = v.client.Decode(v.registry2_3.SetConfigTypeSafe(txOpts, + decodedTx, err = v.client.Decode(v.registry2_3.SetConfigTypeSafe(txOpts, ocrConfig.Signers, ocrConfig.Transmitters, ocrConfig.F, @@ -228,7 +229,7 @@ func (v *EthereumKeeperRegistry) SetConfigTypeSafe(ocrConfig OCRv2Config) error default: return fmt.Errorf("SetConfigTypeSafe is not supported in keeper registry version %d", v.version) } - + v.l.Debug().Interface("decodedTx", decodedTx).Msg("SetConfigTypeSafe") return err } @@ -1369,12 +1370,7 @@ func deployRegistry22(client *seth.Client, opts *KeeperRegistryOpts) (KeeperRegi return nil, err } - var allowedReadOnlyAddress common.Address - if chainId == networks.PolygonZkEvmMainnet.ChainID || chainId == networks.PolygonZkEvmCardona.ChainID { - allowedReadOnlyAddress = common.HexToAddress("0x1111111111111111111111111111111111111111") - } else { - allowedReadOnlyAddress = common.HexToAddress("0x0000000000000000000000000000000000000000") - } + allowedReadOnlyAddress := common.HexToAddress("0x0000000000000000000000000000000000000000") logicBAbi, err := registrylogicb22.AutomationRegistryLogicBMetaData.GetAbi() if err != nil { @@ -1457,12 +1453,7 @@ func deployRegistry23(client *seth.Client, opts *KeeperRegistryOpts) (KeeperRegi return nil, err } - var allowedReadOnlyAddress common.Address - if chainId == networks.PolygonZkEvmMainnet.ChainID || chainId == networks.PolygonZkEvmCardona.ChainID { - allowedReadOnlyAddress = common.HexToAddress("0x1111111111111111111111111111111111111111") - } else { - allowedReadOnlyAddress = common.HexToAddress("0x0000000000000000000000000000000000000000") - } + allowedReadOnlyAddress := common.HexToAddress("0x0000000000000000000000000000000000000000") logicCAbi, err := registrylogicc23.AutomationRegistryLogicCMetaData.GetAbi() if err != nil { @@ -1538,7 +1529,7 @@ func deployRegistry23(client *seth.Client, opts *KeeperRegistryOpts) (KeeperRegi } // LoadKeeperRegistry returns deployed on given address EthereumKeeperRegistry -func LoadKeeperRegistry(l zerolog.Logger, client *seth.Client, address common.Address, registryVersion eth_contracts.KeeperRegistryVersion) (KeeperRegistry, error) { +func LoadKeeperRegistry(l zerolog.Logger, client *seth.Client, address common.Address, registryVersion eth_contracts.KeeperRegistryVersion, chainModuleAddress common.Address) (KeeperRegistry, error) { var keeper *EthereumKeeperRegistry var err error switch registryVersion { @@ -1555,7 +1546,7 @@ func LoadKeeperRegistry(l zerolog.Logger, client *seth.Client, address common.Ad case eth_contracts.RegistryVersion_2_2: // why the contract name is not the same as the actual contract name? keeper, err = loadRegistry2_2(client, address) case eth_contracts.RegistryVersion_2_3: - keeper, err = loadRegistry2_3(client, address) + keeper, err = loadRegistry2_3(client, address, chainModuleAddress) default: return nil, fmt.Errorf("keeper registry version %d is not supported", registryVersion) } @@ -1695,24 +1686,24 @@ func loadRegistry2_2(client *seth.Client, address common.Address) (*EthereumKeep }, nil } -func loadRegistry2_3(client *seth.Client, address common.Address) (*EthereumKeeperRegistry, error) { - abi, err := iregistry23.IAutomationRegistryMaster23MetaData.GetAbi() +func loadRegistry2_3(client *seth.Client, address, chainModuleAddress common.Address) (*EthereumKeeperRegistry, error) { + + loader := seth.NewContractLoader[iregistry23.IAutomationRegistryMaster23](client) + instance, err := loader.LoadContract("AutomationRegistry2_3", address, iregistry23.IAutomationRegistryMaster23MetaData.GetAbi, iregistry23.NewIAutomationRegistryMaster23) if err != nil { - return &EthereumKeeperRegistry{}, fmt.Errorf("failed to get AutomationRegistry2_3 ABI: %w", err) + return &EthereumKeeperRegistry{}, fmt.Errorf("failed to load AutomationRegistry2_3 instance: %w", err) } - client.ContractStore.AddABI("AutomationRegistry2_3", *abi) - client.ContractStore.AddBIN("AutomationRegistry2_3", common.FromHex(iregistry23.IAutomationRegistryMaster23MetaData.Bin)) - - instance, err := iregistry23.NewIAutomationRegistryMaster23(address, wrappers.MustNewWrappedContractBackend(nil, client)) + chainModule, err := loadChainModule(client, chainModuleAddress) if err != nil { - return &EthereumKeeperRegistry{}, fmt.Errorf("failed to instantiate AutomationRegistry2_3 instance: %w", err) + return &EthereumKeeperRegistry{}, fmt.Errorf("failed to load chain module: %w", err) } return &EthereumKeeperRegistry{ address: &address, client: client, registry2_3: instance, + chainModule: chainModule, }, nil } @@ -1768,6 +1759,26 @@ func deployOptimismModule(client *seth.Client) (common.Address, error) { return data.Address, nil } +func loadChainModule(client *seth.Client, address common.Address) (*i_chain_module.IChainModule, error) { + abi, err := i_chain_module.IChainModuleMetaData.GetAbi() + if err != nil { + return &i_chain_module.IChainModule{}, fmt.Errorf("failed to get IChainModule ABI: %w", err) + } + + client.ContractStore.AddABI("IChainModule", *abi) + client.ContractStore.AddBIN("IChainModule", common.FromHex(i_chain_module.IChainModuleMetaData.Bin)) + + chainModule, err := i_chain_module.NewIChainModule( + address, + wrappers.MustNewWrappedContractBackend(nil, client), + ) + if err != nil { + return &i_chain_module.IChainModule{}, fmt.Errorf("failed to instantiate IChainModule instance: %w", err) + } + + return chainModule, nil +} + func deployBaseModule(client *seth.Client) (common.Address, error) { abi, err := chain_module_base.ChainModuleBaseMetaData.GetAbi() if err != nil { @@ -2203,17 +2214,10 @@ func LoadKeeperRegistrar(client *seth.Client, address common.Address, registryVe if registryVersion == eth_contracts.RegistryVersion_1_1 || registryVersion == eth_contracts.RegistryVersion_1_2 || registryVersion == eth_contracts.RegistryVersion_1_3 { - abi, err := keeper_registrar_wrapper1_2.KeeperRegistrarMetaData.GetAbi() + loader := seth.NewContractLoader[keeper_registrar_wrapper1_2.KeeperRegistrar](client) + instance, err := loader.LoadContract("KeeperRegistrar1_2", address, keeper_registrar_wrapper1_2.KeeperRegistrarMetaData.GetAbi, keeper_registrar_wrapper1_2.NewKeeperRegistrar) if err != nil { - return &EthereumKeeperRegistrar{}, fmt.Errorf("failed to get KeeperRegistrar1_2 ABI: %w", err) - } - - client.ContractStore.AddABI("KeeperRegistrar1_2", *abi) - client.ContractStore.AddBIN("KeeperRegistrar1_2", common.FromHex(keeper_registrar_wrapper1_2.KeeperRegistrarMetaData.Bin)) - - instance, err := keeper_registrar_wrapper1_2.NewKeeperRegistrar(address, wrappers.MustNewWrappedContractBackend(nil, client)) - if err != nil { - return &EthereumKeeperRegistrar{}, fmt.Errorf("failed to instantiate KeeperRegistrar1_2 instance: %w", err) + return &EthereumKeeperRegistrar{}, fmt.Errorf("failed to load KeeperRegistrar1_2 instance: %w", err) } return &EthereumKeeperRegistrar{ @@ -2222,43 +2226,78 @@ func LoadKeeperRegistrar(client *seth.Client, address common.Address, registryVe registrar: instance, }, err } else if registryVersion == eth_contracts.RegistryVersion_2_0 { - abi, err := keeper_registrar_wrapper2_0.KeeperRegistrarMetaData.GetAbi() + loader := seth.NewContractLoader[keeper_registrar_wrapper2_0.KeeperRegistrar](client) + instance, err := loader.LoadContract("KeeperRegistrar2_0", address, keeper_registrar_wrapper2_0.KeeperRegistrarMetaData.GetAbi, keeper_registrar_wrapper2_0.NewKeeperRegistrar) if err != nil { - return &EthereumKeeperRegistrar{}, fmt.Errorf("failed to get KeeperRegistrar2_0 ABI: %w", err) + return &EthereumKeeperRegistrar{}, fmt.Errorf("failed to load KeeperRegistrar2_0 instance: %w", err) } - client.ContractStore.AddABI("KeeperRegistrar2_0", *abi) - client.ContractStore.AddBIN("KeeperRegistrar2_0", common.FromHex(keeper_registrar_wrapper2_0.KeeperRegistrarMetaData.Bin)) + return &EthereumKeeperRegistrar{ + address: &address, + client: client, + registrar20: instance, + }, nil + } else if registryVersion == eth_contracts.RegistryVersion_2_1 || registryVersion == eth_contracts.RegistryVersion_2_2 { + loader := seth.NewContractLoader[registrar21.AutomationRegistrar](client) + instance, err := loader.LoadContract("KeeperRegistrar2_1", address, registrar21.AutomationRegistrarMetaData.GetAbi, registrar21.NewAutomationRegistrar) + if err != nil { + return &EthereumKeeperRegistrar{}, fmt.Errorf("failed to load KeeperRegistrar2_1 instance: %w", err) + } - instance, err := keeper_registrar_wrapper2_0.NewKeeperRegistrar(address, wrappers.MustNewWrappedContractBackend(nil, client)) + return &EthereumKeeperRegistrar{ + address: &address, + client: client, + registrar21: instance, + }, nil + } else if registryVersion == eth_contracts.RegistryVersion_2_3 { + loader := seth.NewContractLoader[registrar23.AutomationRegistrar](client) + instance, err := loader.LoadContract("KeeperRegistrar2_3", address, registrar23.AutomationRegistrarMetaData.GetAbi, registrar23.NewAutomationRegistrar) if err != nil { - return &EthereumKeeperRegistrar{}, fmt.Errorf("failed to instantiate KeeperRegistrar2_0 instance: %w", err) + return &EthereumKeeperRegistrar{}, fmt.Errorf("failed to load KeeperRegistrar2_3 instance: %w", err) } return &EthereumKeeperRegistrar{ address: &address, client: client, - registrar20: instance, + registrar23: instance, }, nil } + return &EthereumKeeperRegistrar{}, fmt.Errorf("unsupported registry version: %v", registryVersion) +} - abi, err := registrar21.AutomationRegistrarMetaData.GetAbi() - if err != nil { - return &EthereumKeeperRegistrar{}, fmt.Errorf("failed to get KeeperRegistrar2_1 ABI: %w", err) - } +type EthereumAutomationKeeperConsumer struct { + client *seth.Client + consumer *log_upkeep_counter_wrapper.LogUpkeepCounter + address *common.Address +} + +func (e EthereumAutomationKeeperConsumer) Address() string { + return e.address.Hex() +} + +func (e EthereumAutomationKeeperConsumer) Counter(ctx context.Context) (*big.Int, error) { + return e.consumer.Counter(&bind.CallOpts{ + From: e.client.MustGetRootKeyAddress(), + Context: ctx, + }) +} - client.ContractStore.AddABI("KeeperRegistrar2_1", *abi) - client.ContractStore.AddBIN("KeeperRegistrar2_1", common.FromHex(registrar21.AutomationRegistrarMetaData.Bin)) +func (e EthereumAutomationKeeperConsumer) Start() error { + _, err := e.client.Decode(e.consumer.Start(e.client.NewTXOpts())) + return err +} - instance, err := registrar21.NewAutomationRegistrar(address, wrappers.MustNewWrappedContractBackend(nil, client)) +func LoadKeeperConsumer(client *seth.Client, address common.Address) (*EthereumAutomationKeeperConsumer, error) { + loader := seth.NewContractLoader[log_upkeep_counter_wrapper.LogUpkeepCounter](client) + instance, err := loader.LoadContract("KeeperConsumer", address, log_upkeep_counter_wrapper.LogUpkeepCounterMetaData.GetAbi, log_upkeep_counter_wrapper.NewLogUpkeepCounter) if err != nil { - return &EthereumKeeperRegistrar{}, fmt.Errorf("failed to instantiate KeeperRegistrar2_1 instance: %w", err) + return &EthereumAutomationKeeperConsumer{}, fmt.Errorf("failed to load KeeperConsumerMetaData instance: %w", err) } - return &EthereumKeeperRegistrar{ - address: &address, - client: client, - registrar21: instance, + return &EthereumAutomationKeeperConsumer{ + client: client, + consumer: instance, + address: &address, }, nil } @@ -2712,21 +2751,35 @@ func (v *EthereumAutomationConsumerBenchmark) GetUpkeepCount(ctx context.Context }, id) } -// DeployKeeperConsumerBenchmark deploys a keeper consumer benchmark contract with a standard contract backend -func DeployKeeperConsumerBenchmark(client *seth.Client) (AutomationConsumerBenchmark, error) { - return deployKeeperConsumerBenchmarkWithWrapperFn(client, func(client *seth.Client) *wrappers.WrappedContractBackend { +// DeployAutomationConsumerBenchmark deploys a keeper consumer benchmark contract with a standard contract backend +func DeployAutomationConsumerBenchmark(client *seth.Client) (AutomationConsumerBenchmark, error) { + return deployAutomationConsumerBenchmarkWithWrapperFn(client, func(client *seth.Client) *wrappers.WrappedContractBackend { return wrappers.MustNewWrappedContractBackend(nil, client) }) } -// DeployKeeperConsumerBenchmarkWithRetry deploys a keeper consumer benchmark contract with a read-only operations retrying contract backend -func DeployKeeperConsumerBenchmarkWithRetry(client *seth.Client, logger zerolog.Logger, maxAttempts uint, retryDelay time.Duration) (AutomationConsumerBenchmark, error) { - return deployKeeperConsumerBenchmarkWithWrapperFn(client, func(client *seth.Client) *wrappers.WrappedContractBackend { +func LoadAutomationConsumerBenchmark(client *seth.Client, address common.Address) (*EthereumAutomationConsumerBenchmark, error) { + loader := seth.NewContractLoader[automation_consumer_benchmark.AutomationConsumerBenchmark](client) + instance, err := loader.LoadContract("AutomationConsumerBenchmark", address, automation_consumer_benchmark.AutomationConsumerBenchmarkMetaData.GetAbi, automation_consumer_benchmark.NewAutomationConsumerBenchmark) + if err != nil { + return &EthereumAutomationConsumerBenchmark{}, fmt.Errorf("failed to load AutomationConsumerBenchmark instance: %w", err) + } + + return &EthereumAutomationConsumerBenchmark{ + client: client, + consumer: instance, + address: &address, + }, nil +} + +// DeployAutomationConsumerBenchmarkWithRetry deploys a keeper consumer benchmark contract with a read-only operations retrying contract backend +func DeployAutomationConsumerBenchmarkWithRetry(client *seth.Client, logger zerolog.Logger, maxAttempts uint, retryDelay time.Duration) (AutomationConsumerBenchmark, error) { + return deployAutomationConsumerBenchmarkWithWrapperFn(client, func(client *seth.Client) *wrappers.WrappedContractBackend { return wrappers.MustNewRetryingWrappedContractBackend(client, logger, maxAttempts, retryDelay) }) } -func deployKeeperConsumerBenchmarkWithWrapperFn(client *seth.Client, wrapperConstrFn func(client *seth.Client) *wrappers.WrappedContractBackend) (AutomationConsumerBenchmark, error) { +func deployAutomationConsumerBenchmarkWithWrapperFn(client *seth.Client, wrapperConstrFn func(client *seth.Client) *wrappers.WrappedContractBackend) (AutomationConsumerBenchmark, error) { abi, err := automation_consumer_benchmark.AutomationConsumerBenchmarkMetaData.GetAbi() if err != nil { return &EthereumAutomationConsumerBenchmark{}, fmt.Errorf("failed to get AutomationConsumerBenchmark ABI: %w", err) @@ -2748,8 +2801,8 @@ func deployKeeperConsumerBenchmarkWithWrapperFn(client *seth.Client, wrapperCons }, nil } -// KeeperConsumerBenchmarkUpkeepObserver is a header subscription that awaits for a round of upkeeps -type KeeperConsumerBenchmarkUpkeepObserver struct { +// AutomationConsumerBenchmarkUpkeepObserver is a header subscription that awaits for a round of upkeeps +type AutomationConsumerBenchmarkUpkeepObserver struct { instance AutomationConsumerBenchmark registry KeeperRegistry upkeepID *big.Int @@ -2773,9 +2826,9 @@ type KeeperConsumerBenchmarkUpkeepObserver struct { l zerolog.Logger } -// NewKeeperConsumerBenchmarkUpkeepObserver provides a new instance of a NewKeeperConsumerBenchmarkUpkeepObserver +// NewAutomationConsumerBenchmarkUpkeepObserver provides a new instance of a NewAutomationConsumerBenchmarkUpkeepObserver // Used to track and log benchmark test results for keepers -func NewKeeperConsumerBenchmarkUpkeepObserver( +func NewAutomationConsumerBenchmarkUpkeepObserver( contract AutomationConsumerBenchmark, registry KeeperRegistry, upkeepID *big.Int, @@ -2785,8 +2838,8 @@ func NewKeeperConsumerBenchmarkUpkeepObserver( upkeepIndex int64, firstEligibleBuffer int64, logger zerolog.Logger, -) *KeeperConsumerBenchmarkUpkeepObserver { - return &KeeperConsumerBenchmarkUpkeepObserver{ +) *AutomationConsumerBenchmarkUpkeepObserver { + return &AutomationConsumerBenchmarkUpkeepObserver{ instance: contract, registry: registry, upkeepID: upkeepID, @@ -2808,7 +2861,7 @@ func NewKeeperConsumerBenchmarkUpkeepObserver( // ReceiveHeader will query the latest Keeper round and check to see whether upkeep was performed, it returns // true when observation has finished. -func (o *KeeperConsumerBenchmarkUpkeepObserver) ReceiveHeader(receivedHeader *blockchain.SafeEVMHeader) (bool, error) { +func (o *AutomationConsumerBenchmarkUpkeepObserver) ReceiveHeader(receivedHeader *blockchain.SafeEVMHeader) (bool, error) { if receivedHeader.Number.Uint64() <= o.lastBlockNum { // Uncle / reorg we won't count return false, nil } @@ -2911,12 +2964,12 @@ func (o *KeeperConsumerBenchmarkUpkeepObserver) ReceiveHeader(receivedHeader *bl } // Complete returns whether watching for upkeeps has completed -func (o *KeeperConsumerBenchmarkUpkeepObserver) Complete() bool { +func (o *AutomationConsumerBenchmarkUpkeepObserver) Complete() bool { return o.complete } // LogDetails logs the results of the benchmark test to testreporter -func (o *KeeperConsumerBenchmarkUpkeepObserver) LogDetails() { +func (o *AutomationConsumerBenchmarkUpkeepObserver) LogDetails() { report := testreporters.KeeperBenchmarkTestReport{ ContractAddress: o.instance.Address(), TotalEligibleCount: o.countEligible, diff --git a/integration-tests/contracts/ethereum_keeper_contracts.go b/integration-tests/contracts/ethereum_keeper_contracts.go index 28fdd15b13..90f1af0319 100644 --- a/integration-tests/contracts/ethereum_keeper_contracts.go +++ b/integration-tests/contracts/ethereum_keeper_contracts.go @@ -150,6 +150,7 @@ type KeeperRegistrySettings struct { MaxPerformGas uint32 // max gas allowed for an upkeep within perform FallbackGasPrice *big.Int // gas price used if the gas price feed is stale FallbackLinkPrice *big.Int // LINK price used if the LINK price feed is stale + FallbackNativePrice *big.Int // Native price used if the Native price feed is stale MaxCheckDataSize uint32 MaxPerformDataSize uint32 MaxRevertDataSize uint32 @@ -173,7 +174,7 @@ func (rcs *KeeperRegistrySettings) Create23OnchainConfig(registrar string, regis ChainModule: chainModuleAddress, ReorgProtectionEnabled: reorgProtectionEnabled, FinanceAdmin: registryOwnerAddress, - FallbackNativePrice: rcs.FallbackLinkPrice, // Just use the LINK price + FallbackNativePrice: rcs.FallbackNativePrice, } } diff --git a/integration-tests/contracts/ethereum_vrf_contracts.go b/integration-tests/contracts/ethereum_vrf_contracts.go index 71da9baa36..144de176ee 100644 --- a/integration-tests/contracts/ethereum_vrf_contracts.go +++ b/integration-tests/contracts/ethereum_vrf_contracts.go @@ -10,9 +10,11 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/rs/zerolog/log" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/mock_ethlink_aggregator_wrapper" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_coordinator_v2_5" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_v2plus_load_test_with_metrics" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrfv2plus_wrapper" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrfv2plus_wrapper_load_test_consumer" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrfv2plus_wrapper_optimism" "github.com/smartcontractkit/chainlink-testing-framework/seth" @@ -352,6 +354,26 @@ func DeployVRFMockETHLINKFeed(seth *seth.Client, answer *big.Int) (VRFMockETHLIN }, err } +func LoadVRFMockETHLINKFeed(client *seth.Client, address common.Address) (VRFMockETHLINKFeed, error) { + abi, err := mock_ethlink_aggregator_wrapper.MockETHLINKAggregatorMetaData.GetAbi() + if err != nil { + return &EthereumVRFMockETHLINKFeed{}, fmt.Errorf("failed to get VRFMockETHLINKAggregator ABI: %w", err) + } + client.ContractStore.AddABI("VRFMockETHLINKAggregator", *abi) + client.ContractStore.AddBIN("VRFMockETHLINKAggregator", common.FromHex(mock_ethlink_aggregator_wrapper.MockETHLINKAggregatorMetaData.Bin)) + + instance, err := vrf_mock_ethlink_aggregator.NewVRFMockETHLINKAggregator(address, wrappers.MustNewWrappedContractBackend(nil, client)) + if err != nil { + return &EthereumVRFMockETHLINKFeed{}, fmt.Errorf("failed to instantiate VRFMockETHLINKAggregator instance: %w", err) + } + + return &EthereumVRFMockETHLINKFeed{ + address: address, + client: client, + feed: instance, + }, nil +} + func (v *EthereumBlockhashStore) Address() string { return v.address.Hex() } @@ -546,3 +568,22 @@ func LoadVRFV2PlusWrapperOptimism(seth *seth.Client, addr string) (*EthereumVRFV wrapper: contract, }, nil } + +func LoadVRFV2WrapperLoadTestConsumer(seth *seth.Client, addr string) (*EthereumVRFV2PlusWrapperLoadTestConsumer, error) { + address := common.HexToAddress(addr) + abi, err := vrfv2plus_wrapper_load_test_consumer.VRFV2PlusWrapperLoadTestConsumerMetaData.GetAbi() + if err != nil { + return nil, fmt.Errorf("failed to get VRFV2PlusWrapperLoadTestConsumer ABI: %w", err) + } + seth.ContractStore.AddABI("VRFV2PlusWrapperLoadTestConsumer", *abi) + seth.ContractStore.AddBIN("VRFV2PlusWrapperLoadTestConsumer", common.FromHex(vrfv2plus_wrapper_load_test_consumer.VRFV2PlusWrapperLoadTestConsumerMetaData.Bin)) + contract, err := vrfv2plus_wrapper_load_test_consumer.NewVRFV2PlusWrapperLoadTestConsumer(address, wrappers.MustNewWrappedContractBackend(nil, seth)) + if err != nil { + return nil, fmt.Errorf("failed to instantiate VRFV2PlusWrapperLoadTestConsumer instance: %w", err) + } + return &EthereumVRFV2PlusWrapperLoadTestConsumer{ + client: seth, + address: address, + consumer: contract, + }, nil +} diff --git a/integration-tests/contracts/ethereum_vrfv2_contracts.go b/integration-tests/contracts/ethereum_vrfv2_contracts.go index aa9237ea7c..708fdb211e 100644 --- a/integration-tests/contracts/ethereum_vrfv2_contracts.go +++ b/integration-tests/contracts/ethereum_vrfv2_contracts.go @@ -594,6 +594,30 @@ func (v *EthereumVRFCoordinatorV2) ParseLog(log types.Log) (generated.AbigenLog, return v.coordinator.ParseLog(log) } +func (v *EthereumVRFCoordinatorV2) GetLinkAddress(ctx context.Context) (common.Address, error) { + opts := &bind.CallOpts{ + From: v.client.MustGetRootKeyAddress(), + Context: ctx, + } + address, err := v.coordinator.LINK(opts) + if err != nil { + return common.Address{}, err + } + return address, nil +} + +func (v *EthereumVRFCoordinatorV2) GetLinkNativeFeed(ctx context.Context) (common.Address, error) { + opts := &bind.CallOpts{ + From: v.client.MustGetRootKeyAddress(), + Context: ctx, + } + address, err := v.coordinator.LINKETHFEED(opts) + if err != nil { + return common.Address{}, err + } + return address, nil +} + // CancelSubscription cancels subscription by Sub owner, // return funds to specified address, // checks if pending requests for a sub exist diff --git a/integration-tests/contracts/ethereum_vrfv2plus_contracts.go b/integration-tests/contracts/ethereum_vrfv2plus_contracts.go index 58044b36c6..35d22e8ef5 100644 --- a/integration-tests/contracts/ethereum_vrfv2plus_contracts.go +++ b/integration-tests/contracts/ethereum_vrfv2plus_contracts.go @@ -16,11 +16,14 @@ import ( "github.com/smartcontractkit/chainlink/integration-tests/wrappers" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/batch_blockhash_store" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/batch_vrf_coordinator_v2plus" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_coordinator_test_v2_5" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_coordinator_v2_5" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_coordinator_v2_5_arbitrum" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_coordinator_v2_5_optimism" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_v2plus_load_test_with_metrics" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrf_v2plus_upgraded_version" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrfv2plus_wrapper" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrfv2plus_wrapper_arbitrum" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrfv2plus_wrapper_load_test_consumer" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/vrfv2plus_wrapper_optimism" ) @@ -37,6 +40,18 @@ type EthereumVRFCoordinatorV2_5_Optimism struct { coordinator vrf_coordinator_v2_5_optimism.VRFCoordinatorV25Optimism } +type EthereumVRFCoordinatorV2_5_Arbitrum struct { + Address common.Address + client *seth.Client + coordinator vrf_coordinator_v2_5_arbitrum.VRFCoordinatorV25Arbitrum +} + +type EthereumVRFCoordinatorTestV2_5 struct { + Address common.Address + client *seth.Client + coordinator vrf_coordinator_test_v2_5.VRFCoordinatorTestV25 +} + type EthereumBatchVRFCoordinatorV2Plus struct { address common.Address client *seth.Client @@ -74,6 +89,12 @@ type EthereumVRFV2PlusWrapperOptimism struct { wrapper *vrfv2plus_wrapper_optimism.VRFV2PlusWrapperOptimism } +type EthereumVRFV2PlusWrapperArbitrum struct { + Address common.Address + client *seth.Client + wrapper *vrfv2plus_wrapper_arbitrum.VRFV2PlusWrapperArbitrum +} + func (v *EthereumVRFV2PlusWrapper) Address() string { return v.address.Hex() } @@ -178,6 +199,56 @@ func DeployVRFCoordinatorV2_5_Optimism(seth *seth.Client, bhsAddr string) (*Ethe }, err } +func DeployVRFCoordinatorV2_5_Arbitrum(seth *seth.Client, bhsAddr string) (*EthereumVRFCoordinatorV2_5_Arbitrum, error) { + abi, err := vrf_coordinator_v2_5_arbitrum.VRFCoordinatorV25ArbitrumMetaData.GetAbi() + if err != nil { + return nil, fmt.Errorf("failed to get VRFCoordinatorV2_5_Arbitrum ABI: %w", err) + } + coordinatorDeploymentData, err := seth.DeployContract( + seth.NewTXOpts(), + "VRFCoordinatorV2_5_Arbitrum", + *abi, + common.FromHex(vrf_coordinator_v2_5_arbitrum.VRFCoordinatorV25ArbitrumMetaData.Bin), + common.HexToAddress(bhsAddr)) + if err != nil { + return nil, fmt.Errorf("VRFCoordinatorV2_5_Arbitrum instance deployment have failed: %w", err) + } + contract, err := vrf_coordinator_v2_5_arbitrum.NewVRFCoordinatorV25Arbitrum(coordinatorDeploymentData.Address, wrappers.MustNewWrappedContractBackend(nil, seth)) + if err != nil { + return nil, fmt.Errorf("failed to instantiate VRFCoordinatorV2_5_Arbitrum instance: %w", err) + } + return &EthereumVRFCoordinatorV2_5_Arbitrum{ + client: seth, + coordinator: *contract, + Address: coordinatorDeploymentData.Address, + }, err +} + +func DeployVRFCoordinatorTestV2_5(seth *seth.Client, bhsAddr string) (*EthereumVRFCoordinatorTestV2_5, error) { + abi, err := vrf_coordinator_test_v2_5.VRFCoordinatorTestV25MetaData.GetAbi() + if err != nil { + return nil, fmt.Errorf("failed to get VRFCoordinatorTestV2_5 ABI: %w", err) + } + coordinatorDeploymentData, err := seth.DeployContract( + seth.NewTXOpts(), + "VRFCoordinatorTestV2_5", + *abi, + common.FromHex(vrf_coordinator_test_v2_5.VRFCoordinatorTestV25MetaData.Bin), + common.HexToAddress(bhsAddr)) + if err != nil { + return nil, fmt.Errorf("VRFCoordinatorTestV2_5 instance deployment have failed: %w", err) + } + contract, err := vrf_coordinator_test_v2_5.NewVRFCoordinatorTestV25(coordinatorDeploymentData.Address, wrappers.MustNewWrappedContractBackend(nil, seth)) + if err != nil { + return nil, fmt.Errorf("failed to instantiate VRFCoordinatorTestV2_5 instance: %w", err) + } + return &EthereumVRFCoordinatorTestV2_5{ + client: seth, + coordinator: *contract, + Address: coordinatorDeploymentData.Address, + }, err +} + func DeployBatchVRFCoordinatorV2Plus(seth *seth.Client, coordinatorAddress string) (BatchVRFCoordinatorV2Plus, error) { abi, err := batch_vrf_coordinator_v2plus.BatchVRFCoordinatorV2PlusMetaData.GetAbi() if err != nil { @@ -340,6 +411,42 @@ func (v *EthereumVRFCoordinatorV2_5) GetBlockHashStoreAddress(ctx context.Contex return blockHashStoreAddress, nil } +func (v *EthereumVRFCoordinatorV2_5) GetLinkAddress(ctx context.Context) (common.Address, error) { + opts := &bind.CallOpts{ + From: v.client.MustGetRootKeyAddress(), + Context: ctx, + } + address, err := v.coordinator.LINK(opts) + if err != nil { + return common.Address{}, err + } + return address, nil +} + +func (v *EthereumVRFCoordinatorV2_5) GetLinkNativeFeed(ctx context.Context) (common.Address, error) { + opts := &bind.CallOpts{ + From: v.client.MustGetRootKeyAddress(), + Context: ctx, + } + address, err := v.coordinator.LINKNATIVEFEED(opts) + if err != nil { + return common.Address{}, err + } + return address, nil +} + +func (v *EthereumVRFCoordinatorV2_5) GetConfig(ctx context.Context) (vrf_coordinator_v2_5.SConfig, error) { + opts := &bind.CallOpts{ + From: v.client.MustGetRootKeyAddress(), + Context: ctx, + } + config, err := v.coordinator.SConfig(opts) + if err != nil { + return vrf_coordinator_v2_5.SConfig{}, err + } + return config, nil +} + // OwnerCancelSubscription cancels subscription by Coordinator owner // return funds to sub owner, // does not check if pending requests for a sub exist @@ -1161,24 +1268,50 @@ func DeployVRFV2PlusWrapper(seth *seth.Client, linkAddr string, linkEthFeedAddr }, err } +func DeployVRFV2PlusWrapperArbitrum(seth *seth.Client, linkAddr string, linkEthFeedAddr string, coordinatorAddr string, subId *big.Int) (*EthereumVRFV2PlusWrapperArbitrum, error) { + abi, err := vrfv2plus_wrapper_arbitrum.VRFV2PlusWrapperArbitrumMetaData.GetAbi() + if err != nil { + return nil, fmt.Errorf("failed to get VRFV2PlusWrapper_Arbitrum ABI: %w", err) + } + data, err := seth.DeployContract( + seth.NewTXOpts(), + "VRFV2PlusWrapper_Arbitrum", + *abi, + common.FromHex(vrfv2plus_wrapper_arbitrum.VRFV2PlusWrapperArbitrumMetaData.Bin), + common.HexToAddress(linkAddr), common.HexToAddress(linkEthFeedAddr), + common.HexToAddress(coordinatorAddr), subId) + if err != nil { + return nil, fmt.Errorf("VRFV2PlusWrapper_Arbitrum instance deployment have failed: %w", err) + } + contract, err := vrfv2plus_wrapper_arbitrum.NewVRFV2PlusWrapperArbitrum(data.Address, wrappers.MustNewWrappedContractBackend(nil, seth)) + if err != nil { + return nil, fmt.Errorf("failed to instantiate VRFV2PlusWrapper_Arbitrum instance: %w", err) + } + return &EthereumVRFV2PlusWrapperArbitrum{ + client: seth, + wrapper: contract, + Address: data.Address, + }, err +} + func DeployVRFV2PlusWrapperOptimism(seth *seth.Client, linkAddr string, linkEthFeedAddr string, coordinatorAddr string, subId *big.Int) (*EthereumVRFV2PlusWrapperOptimism, error) { abi, err := vrfv2plus_wrapper_optimism.VRFV2PlusWrapperOptimismMetaData.GetAbi() if err != nil { - return nil, fmt.Errorf("failed to get VRFV2PlusWrapperOptimism ABI: %w", err) + return nil, fmt.Errorf("failed to get VRFV2PlusWrapper_Optimism ABI: %w", err) } data, err := seth.DeployContract( seth.NewTXOpts(), - "VRFV2PlusWrapperOptimism", + "VRFV2PlusWrapper_Optimism", *abi, common.FromHex(vrfv2plus_wrapper_optimism.VRFV2PlusWrapperOptimismMetaData.Bin), common.HexToAddress(linkAddr), common.HexToAddress(linkEthFeedAddr), common.HexToAddress(coordinatorAddr), subId) if err != nil { - return nil, fmt.Errorf("VRFV2PlusWrapperOptimism instance deployment have failed: %w", err) + return nil, fmt.Errorf("VRFV2PlusWrapper_Optimism instance deployment have failed: %w", err) } contract, err := vrfv2plus_wrapper_optimism.NewVRFV2PlusWrapperOptimism(data.Address, wrappers.MustNewWrappedContractBackend(nil, seth)) if err != nil { - return nil, fmt.Errorf("failed to instantiate VRFV2PlusWrapperOptimism instance: %w", err) + return nil, fmt.Errorf("failed to instantiate VRFV2PlusWrapper_Optimism instance: %w", err) } return &EthereumVRFV2PlusWrapperOptimism{ client: seth, diff --git a/integration-tests/crib/README.md b/integration-tests/crib/README.md index ecf393f780..c37cbfec9c 100644 --- a/integration-tests/crib/README.md +++ b/integration-tests/crib/README.md @@ -1,4 +1,4 @@ -### CRIB Health Check Test +### Example e2e product test using CRIB ## Setup CRIB This is a simple smoke + chaos test for CRIB deployment. @@ -12,8 +12,15 @@ devspace deploy --debug --profile local-dev-simulated-core-ocr1 ## Run the tests ```shell -CRIB_NAMESPACE=crib-oh-my-crib -CRIB_NETWORK=geth # only "geth" is supported for now -CRIB_NODES=5 # min 5 nodes -go test -v -run TestCRIB -``` \ No newline at end of file +export CRIB_NAMESPACE=crib-oh-my-crib +export CRIB_NETWORK=geth # only "geth" is supported for now +export CRIB_NODES=5 # min 5 nodes +#export SETH_LOG_LEVEL=debug # these two can be enabled to debug connection issues +#export RESTY_DEBUG=true +#export TEST_PERSISTENCE=true # to run the chaos test +export GAP_URL=https://localhost:8080/primary # only applicable in CI, unset the var to connect locally +go test -v -run TestCRIBChaos +``` + +## Configuring CI workflow +We are using GAP and GATI to access the infrastructure, please follow [configuration guide](https://smartcontract-it.atlassian.net/wiki/spaces/CRIB/pages/909967436/CRIB+CI+Integration) \ No newline at end of file diff --git a/integration-tests/crib/chaos.go b/integration-tests/crib/chaos.go index 985ca2dc2d..463ced483f 100644 --- a/integration-tests/crib/chaos.go +++ b/integration-tests/crib/chaos.go @@ -3,10 +3,10 @@ package crib import ( "time" - "github.com/smartcontractkit/chainlink-testing-framework/havoc" - "github.com/chaos-mesh/chaos-mesh/api/v1alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/smartcontractkit/chainlink-testing-framework/havoc" ) func rebootCLNamespace(delay time.Duration, namespace string) (*havoc.Chaos, error) { diff --git a/integration-tests/crib/connect.go b/integration-tests/crib/connect.go index 3289ed52c4..acc16b74f9 100644 --- a/integration-tests/crib/connect.go +++ b/integration-tests/crib/connect.go @@ -1,11 +1,11 @@ package crib import ( - "fmt" - "os" - "strconv" + "net/http" "time" + "github.com/smartcontractkit/chainlink-testing-framework/lib/crib" + "github.com/pkg/errors" "github.com/smartcontractkit/chainlink-testing-framework/seth" @@ -19,17 +19,7 @@ import ( "github.com/smartcontractkit/chainlink/integration-tests/client" ) -const ( - // these are constants for simulated CRIB that should never change - // CRIB: https://github.com/smartcontractkit/crib/tree/main/core - // Core Chart: https://github.com/smartcontractkit/infra-charts/tree/main/chainlink-cluster - mockserverCRIBTemplate = "https://%s-mockserver%s" - internalNodeDNSTemplate = "app-node%d" - ingressNetworkWSURLTemplate = "wss://%s-geth-1337-ws%s" - ingressNetworkHTTPURLTemplate = "https://%s-geth-1337-http%s" -) - -func setSethConfig(cfg tc.TestConfig, netWSURL string, netHTTPURL string) { +func setSethConfig(cfg tc.TestConfig, netWSURL string, netHTTPURL string, headers http.Header) { netName := "CRIB_SIMULATED" cfg.Network.SelectedNetworks = []string{netName} cfg.Network.RpcHttpUrls = map[string][]string{} @@ -37,6 +27,7 @@ func setSethConfig(cfg tc.TestConfig, netWSURL string, netHTTPURL string) { cfg.Network.RpcWsUrls = map[string][]string{} cfg.Network.RpcWsUrls[netName] = []string{netWSURL} cfg.Seth.EphemeralAddrs = ptr.Ptr(int64(0)) + cfg.Seth.RPCHeaders = headers } // ConnectRemote connects to a local environment, see https://github.com/smartcontractkit/crib/tree/main/core @@ -46,92 +37,77 @@ func ConnectRemote() ( *msClient.MockserverClient, *client.ChainlinkK8sClient, []*client.ChainlinkK8sClient, + *crib.CoreDONConnectionConfig, error, ) { - ingressSuffix := os.Getenv("K8S_STAGING_INGRESS_SUFFIX") - if ingressSuffix == "" { - return nil, nil, nil, nil, errors.New("K8S_STAGING_INGRESS_SUFFIX must be set to connect to k8s ingresses") - } - cribNamespace := os.Getenv("CRIB_NAMESPACE") - if cribNamespace == "" { - return nil, nil, nil, nil, errors.New("CRIB_NAMESPACE must be set to connect") - } - cribNetwork := os.Getenv("CRIB_NETWORK") - if cribNetwork == "" { - return nil, nil, nil, nil, errors.New("CRIB_NETWORK must be set to connect, only 'geth' is supported for now") - } - cribNodes := os.Getenv("CRIB_NODES") - nodes, err := strconv.Atoi(cribNodes) + vars, err := crib.CoreDONSimulatedConnection() if err != nil { - return nil, nil, nil, nil, errors.New("CRIB_NODES must be a number, 5-19 nodes") + return nil, nil, nil, nil, nil, err } + // TODO: move all the parts of ConnectRemote() to CTF when Seth config refactor is finalized config, err := tc.GetConfig([]string{"CRIB"}, tc.OCR) if err != nil { - return nil, nil, nil, nil, err - } - if nodes < 2 { - return nil, nil, nil, nil, fmt.Errorf("not enough chainlink nodes, need at least 2, TOML key: [CRIB.nodes]") + return nil, nil, nil, nil, nil, err } - mockserverURL := fmt.Sprintf(mockserverCRIBTemplate, cribNamespace, ingressSuffix) var sethClient *seth.Client - switch cribNetwork { + switch vars.Network { case "geth": - netWSURL := fmt.Sprintf(ingressNetworkWSURLTemplate, cribNamespace, ingressSuffix) - netHTTPURL := fmt.Sprintf(ingressNetworkHTTPURLTemplate, cribNamespace, ingressSuffix) - setSethConfig(config, netWSURL, netHTTPURL) + setSethConfig(config, vars.NetworkWSURL, vars.NetworkHTTPURL, vars.BlockchainNodeHeaders) net := blockchain.EVMNetwork{ - Name: cribNetwork, - Simulated: true, - SupportsEIP1559: true, - ClientImplementation: blockchain.EthereumClientImplementation, - ChainID: 1337, - PrivateKeys: []string{ - "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", - }, - URLs: []string{netWSURL}, - HTTPURLs: []string{netHTTPURL}, + Name: vars.Network, + Simulated: true, + SupportsEIP1559: true, + ClientImplementation: blockchain.EthereumClientImplementation, + ChainID: vars.ChainID, + PrivateKeys: vars.PrivateKeys, + URLs: []string{vars.NetworkWSURL}, + HTTPURLs: []string{vars.NetworkHTTPURL}, ChainlinkTransactionLimit: 500000, Timeout: blockchain.StrDuration{Duration: 2 * time.Minute}, MinimumConfirmations: 1, GasEstimationBuffer: 10000, + Headers: vars.BlockchainNodeHeaders, } sethClient, err = seth_utils.GetChainClient(config, net) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, err } default: - return nil, nil, nil, nil, errors.New("CRIB network is not supported") + return nil, nil, nil, nil, nil, errors.New("CRIB network is not supported") } // bootstrap node clClients := make([]*client.ChainlinkK8sClient, 0) c, err := client.NewChainlinkK8sClient(&client.ChainlinkConfig{ - URL: fmt.Sprintf("https://%s-node%d%s", cribNamespace, 1, ingressSuffix), Email: client.CLNodeTestEmail, - InternalIP: fmt.Sprintf(internalNodeDNSTemplate, 1), Password: client.CLNodeTestPassword, - }, fmt.Sprintf(internalNodeDNSTemplate, 1), cribNamespace) + URL: vars.NodeURLs[0], + InternalIP: vars.NodeInternalDNS[0], + Headers: vars.NodeHeaders[0], + }, vars.NodeInternalDNS[0], vars.Namespace) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, err } clClients = append(clClients, c) // all the other nodes, indices of nodes in CRIB starts with 1 - for i := 2; i <= nodes; i++ { + for i := 1; i < vars.Nodes; i++ { cl, err := client.NewChainlinkK8sClient(&client.ChainlinkConfig{ - URL: fmt.Sprintf("https://%s-node%d%s", cribNamespace, i, ingressSuffix), Email: client.CLNodeTestEmail, - InternalIP: fmt.Sprintf(internalNodeDNSTemplate, i), Password: client.CLNodeTestPassword, - }, fmt.Sprintf(internalNodeDNSTemplate, i), cribNamespace) + URL: vars.NodeURLs[i], + InternalIP: vars.NodeInternalDNS[i], + Headers: vars.NodeHeaders[i], + }, vars.NodeInternalDNS[i], vars.Namespace) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, err } clClients = append(clClients, cl) } mockServerClient := msClient.NewMockserverClient(&msClient.MockserverConfig{ - LocalURL: mockserverURL, - ClusterURL: mockserverURL, + LocalURL: vars.MockserverURL, + ClusterURL: "http://mockserver:1080", + Headers: vars.MockserverHeaders, }) //nolint:gosec // G602 - false positive https://github.com/securego/gosec/issues/1005 - return sethClient, mockServerClient, clClients[0], clClients[1:], nil + return sethClient, mockServerClient, clClients[0], clClients[1:], vars, nil } diff --git a/integration-tests/crib/ocr_test.go b/integration-tests/crib/ocr_test.go index 327283c150..f2119dc7f4 100644 --- a/integration-tests/crib/ocr_test.go +++ b/integration-tests/crib/ocr_test.go @@ -6,6 +6,10 @@ import ( "testing" "time" + "github.com/smartcontractkit/chainlink-testing-framework/lib/client" + "github.com/smartcontractkit/chainlink-testing-framework/lib/logging" + "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/ptr" + "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-testing-framework/havoc" @@ -13,18 +17,70 @@ import ( "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/contracts" - "github.com/smartcontractkit/chainlink-testing-framework/lib/logging" + tc "github.com/smartcontractkit/chainlink/integration-tests/testconfig" + ocr_config "github.com/smartcontractkit/chainlink/integration-tests/testconfig/ocr" ) -func TestCRIB(t *testing.T) { +// TestCRIBChaos an example of how we can run chaos tests with havoc and core CRIB +func TestCRIBChaos(t *testing.T) { + l := logging.GetTestLogger(t) + config, err := tc.GetConfig([]string{"Crib"}, tc.OCR) + require.NoError(t, err) + + sethClient, msClient, bootstrapNode, workerNodes, _, err := ConnectRemote() + require.NoError(t, err) + + lta, err := actions.SetupOCRv1Cluster(l, sethClient, config.OCR, workerNodes) + require.NoError(t, err) + ocrInstances, err := actions.SetupOCRv1Feed(l, sethClient, lta, config.OCR, msClient, bootstrapNode, workerNodes) + require.NoError(t, err) + + err = actions.SetAllAdapterResponsesToTheSameValue(10, ocrInstances, workerNodes, msClient) + require.NoError(t, err) + actions.SimulateOCRv1EAActivity(l, 3*time.Second, ocrInstances, workerNodes, msClient) + + err = actions.WatchNewOCRRound(l, sethClient, 1, contracts.V1OffChainAgrregatorToOffChainAggregatorWithRounds(ocrInstances), 5*time.Minute) + require.NoError(t, err, "Error watching for new OCR round") + + if os.Getenv("TEST_PERSISTENCE") != "" { + ch, err := rebootCLNamespace( + 1*time.Second, + os.Getenv("CRIB_NAMESPACE"), + ) + require.NoError(t, err, "Error rebooting CL namespace") + ch.Create(context.Background()) + ch.AddListener(havoc.NewChaosLogger(l)) + t.Cleanup(func() { + err := ch.Delete(context.Background()) + require.NoError(t, err, "Error deleting chaos") + }) + require.Eventually(t, func() bool { + err = actions.WatchNewOCRRound(l, sethClient, 3, contracts.V1OffChainAgrregatorToOffChainAggregatorWithRounds(ocrInstances), 5*time.Minute) + if err != nil { + l.Info().Err(err).Msg("OCR round is not there yet") + return false + } + return true + }, 20*time.Minute, 5*time.Second) + } +} + +// TestCRIBRPCChaos and example of how we can run RPC chaos with Geth or Anvil +func TestCRIBRPCChaos(t *testing.T) { l := logging.GetTestLogger(t) - sethClient, msClient, bootstrapNode, workerNodes, err := ConnectRemote() + sethClient, msClient, bootstrapNode, workerNodes, vars, err := ConnectRemote() require.NoError(t, err) - lta, err := actions.SetupOCRv1Cluster(l, sethClient, workerNodes) + ocrConfig := &ocr_config.Config{ + Contracts: &ocr_config.Contracts{ + ShouldBeUsed: ptr.Ptr(false), + }, + } + + lta, err := actions.SetupOCRv1Cluster(l, sethClient, ocrConfig, workerNodes) require.NoError(t, err) - ocrInstances, err := actions.SetupOCRv1Feed(l, sethClient, lta, msClient, bootstrapNode, workerNodes) + ocrInstances, err := actions.SetupOCRv1Feed(l, sethClient, lta, ocrConfig, msClient, bootstrapNode, workerNodes) require.NoError(t, err) err = actions.SetAllAdapterResponsesToTheSameValue(10, ocrInstances, workerNodes, msClient) @@ -34,22 +90,10 @@ func TestCRIB(t *testing.T) { err = actions.WatchNewOCRRound(l, sethClient, 1, contracts.V1OffChainAgrregatorToOffChainAggregatorWithRounds(ocrInstances), 5*time.Minute) require.NoError(t, err, "Error watching for new OCR round") - ch, err := rebootCLNamespace( - 1*time.Second, - os.Getenv("CRIB_NAMESPACE"), - ) - ch.Create(context.Background()) - ch.AddListener(havoc.NewChaosLogger(l)) - t.Cleanup(func() { - err := ch.Delete(context.Background()) - require.NoError(t, err) - }) - require.Eventually(t, func() bool { - err = actions.WatchNewOCRRound(l, sethClient, 3, contracts.V1OffChainAgrregatorToOffChainAggregatorWithRounds(ocrInstances), 5*time.Minute) - if err != nil { - l.Info().Err(err).Msg("OCR round is not there yet") - return false - } - return true - }, 3*time.Minute, 5*time.Second) + ac := client.NewRPCClient(sethClient.URL, vars.BlockchainNodeHeaders) + err = ac.GethSetHead(10) + require.NoError(t, err) + + err = actions.WatchNewOCRRound(l, sethClient, 3, contracts.V1OffChainAgrregatorToOffChainAggregatorWithRounds(ocrInstances), 5*time.Minute) + require.NoError(t, err, "Error watching for new OCR round") } diff --git a/integration-tests/docker/test_env/cl_node.go b/integration-tests/docker/test_env/cl_node.go index a37a6204f6..02179403ac 100644 --- a/integration-tests/docker/test_env/cl_node.go +++ b/integration-tests/docker/test_env/cl_node.go @@ -33,11 +33,13 @@ import ( "github.com/smartcontractkit/chainlink/integration-tests/client" it_utils "github.com/smartcontractkit/chainlink/integration-tests/utils" "github.com/smartcontractkit/chainlink/integration-tests/utils/templates" + grapqlClient "github.com/smartcontractkit/chainlink/integration-tests/web/sdk/client" ) var ( - ErrConnectNodeClient = "could not connect Node HTTP Client" - ErrStartCLNodeContainer = "failed to start CL node container" + ErrConnectNodeClient = "could not connect Node HTTP Client" + ErrConnectNodeGraphqlClient = "could not connect Node Graphql Client" + ErrStartCLNodeContainer = "failed to start CL node container" ) const ( @@ -54,6 +56,7 @@ type ClNode struct { UserEmail string `json:"userEmail"` UserPassword string `json:"userPassword"` AlwaysPullImage bool `json:"-"` + GraphqlAPI grapqlClient.Client `json:"-"` t *testing.T l zerolog.Logger } @@ -339,19 +342,25 @@ func (n *ClNode) containerStartOrRestart(restartDb bool) error { Str("userEmail", n.UserEmail). Str("userPassword", n.UserPassword). Msg("Started Chainlink Node container") - clClient, err := client.NewChainlinkClient(&client.ChainlinkConfig{ + config := &client.ChainlinkConfig{ URL: clEndpoint, Email: n.UserEmail, Password: n.UserPassword, InternalIP: ip, - }, - n.l) + } + clClient, err := client.NewChainlinkClient(config, n.l) if err != nil { return fmt.Errorf("%s err: %w", ErrConnectNodeClient, err) } + graphqlClient, err := newChainLinkGraphqlClient(config) + if err != nil { + return fmt.Errorf("%s err: %w", ErrConnectNodeGraphqlClient, err) + } + n.Container = container n.API = clClient + n.GraphqlAPI = graphqlClient return nil } @@ -491,3 +500,11 @@ func (n *ClNode) getContainerRequest(secrets string) ( }, }, nil } + +func newChainLinkGraphqlClient(c *client.ChainlinkConfig) (grapqlClient.Client, error) { + nodeClient, err := grapqlClient.New(c.URL, grapqlClient.Credentials{Email: c.Email, Password: c.Password}) + if err != nil { + return nil, err + } + return nodeClient, nil +} diff --git a/integration-tests/docker/test_env/test_env.go b/integration-tests/docker/test_env/test_env.go index ff891f1b7d..57f3c09cb0 100644 --- a/integration-tests/docker/test_env/test_env.go +++ b/integration-tests/docker/test_env/test_env.go @@ -20,7 +20,6 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/lib/logging" "github.com/smartcontractkit/chainlink-testing-framework/lib/logstream" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/runid" - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" d "github.com/smartcontractkit/chainlink/integration-tests/docker" diff --git a/integration-tests/docker/test_env/test_env_builder.go b/integration-tests/docker/test_env/test_env_builder.go index 249bb8c39a..aabbf1d2f1 100644 --- a/integration-tests/docker/test_env/test_env_builder.go +++ b/integration-tests/docker/test_env/test_env_builder.go @@ -3,15 +3,18 @@ package test_env import ( "fmt" "os" + "path/filepath" "slices" "strings" "testing" + "time" "github.com/rs/zerolog" "github.com/rs/zerolog/log" - "go.uber.org/zap/zapcore" + "github.com/smartcontractkit/chainlink-testing-framework/seth" + "github.com/smartcontractkit/chainlink-testing-framework/lib/blockchain" ctf_config "github.com/smartcontractkit/chainlink-testing-framework/lib/config" "github.com/smartcontractkit/chainlink-testing-framework/lib/docker/test_env" @@ -19,10 +22,11 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/lib/logstream" "github.com/smartcontractkit/chainlink-testing-framework/lib/networks" "github.com/smartcontractkit/chainlink-testing-framework/lib/testreporters" + "github.com/smartcontractkit/chainlink-testing-framework/lib/testsummary" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/osutil" - "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/integration-tests/types/config/node" + "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" ) type CleanUpType string @@ -256,7 +260,7 @@ func (b *CLTestEnvBuilder) Build() (*CLClusterTestEnv, error) { b.t.Cleanup(func() { b.l.Info().Msg("Shutting down LogStream") logPath, err := osutil.GetAbsoluteFolderPath("logs") - if err != nil { + if err == nil { b.l.Info().Str("Absolute path", logPath).Msg("LogStream logs folder location") } @@ -281,7 +285,8 @@ func (b *CLTestEnvBuilder) Build() (*CLClusterTestEnv, error) { // new logs can be added to the log stream, so parallel processing would get stuck on waiting for it to be unlocked LogScanningLoop: for i := 0; i < b.clNodesCount; i++ { - if b == nil || b.te == nil || b.te.ClCluster == nil || b.te.ClCluster.Nodes == nil || b.te.ClCluster.Nodes[i] == nil || len(b.te.ClCluster.Nodes)-1 < i { + // if something went wrong during environment setup we might not have all nodes, and we don't want an NPE + if b == nil || b.te == nil || b.te.ClCluster == nil || b.te.ClCluster.Nodes == nil || len(b.te.ClCluster.Nodes)-1 < i || b.te.ClCluster.Nodes[i] == nil { continue } // ignore count return, because we are only interested in the error @@ -308,6 +313,47 @@ func (b *CLTestEnvBuilder) Build() (*CLClusterTestEnv, error) { b.te.LogStream.SaveLogLocationInTestSummary() } b.l.Info().Msg("Finished shutting down LogStream") + + if b.t.Failed() || *b.testConfig.GetLoggingConfig().TestLogCollect { + b.l.Info().Msg("Dump state of all Postgres DBs used by Chainlink Nodes") + + dbDumpFolder := "db_dumps" + dbDumpPath := fmt.Sprintf("%s/%s-%s", dbDumpFolder, b.t.Name(), time.Now().Format("2006-01-02T15-04-05")) + if err := os.MkdirAll(dbDumpPath, os.ModePerm); err != nil { + b.l.Error().Err(err).Msg("Error creating folder for Postgres DB dump") + return + } + + absDbDumpPath, err := osutil.GetAbsoluteFolderPath(dbDumpFolder) + if err == nil { + b.l.Info().Str("Absolute path", absDbDumpPath).Msg("PostgresDB dump folder location") + } + + for i := 0; i < b.clNodesCount; i++ { + // if something went wrong during environment setup we might not have all nodes, and we don't want an NPE + if b == nil || b.te == nil || b.te.ClCluster == nil || b.te.ClCluster.Nodes == nil || len(b.te.ClCluster.Nodes)-1 < i || b.te.ClCluster.Nodes[i] == nil || b.te.ClCluster.Nodes[i].PostgresDb == nil { + continue + } + + filePath := filepath.Join(dbDumpPath, fmt.Sprintf("postgres_db_dump_%s.sql", b.te.ClCluster.Nodes[i].ContainerName)) + localDbDumpFile, err := os.Create(filePath) + if err != nil { + b.l.Error().Err(err).Msg("Error creating localDbDumpFile for Postgres DB dump") + _ = localDbDumpFile.Close() + continue + } + + if err := b.te.ClCluster.Nodes[i].PostgresDb.ExecPgDumpFromContainer(localDbDumpFile); err != nil { + b.l.Error().Err(err).Msg("Error dumping Postgres DB") + } + _ = localDbDumpFile.Close() + } + b.l.Info().Msg("Finished dumping state of all Postgres DBs used by Chainlink Nodes") + } + + if b.testConfig.GetSethConfig() != nil && ((b.t.Failed() && slices.Contains(b.testConfig.GetSethConfig().TraceOutputs, seth.TraceOutput_DOT) && b.testConfig.GetSethConfig().TracingLevel != seth.TracingLevel_None) || (!b.t.Failed() && slices.Contains(b.testConfig.GetSethConfig().TraceOutputs, seth.TraceOutput_DOT) && b.testConfig.GetSethConfig().TracingLevel == seth.TracingLevel_All)) { + _ = testsummary.AddEntry(b.t.Name(), "dot_graphs", "true") + } }) } else { b.l.Warn().Msg("LogStream won't be cleaned up, because either test instance is not set or cleanup type is set to none") @@ -365,29 +411,29 @@ func (b *CLTestEnvBuilder) Build() (*CLClusterTestEnv, error) { b.te.rpcProviders[networkConfig.ChainID] = &rpcProvider b.te.EVMNetworks = append(b.te.EVMNetworks, &networkConfig) } + if b.clNodesCount > 0 { + dereferrencedEvms := make([]blockchain.EVMNetwork, 0) + for _, en := range b.te.EVMNetworks { + dereferrencedEvms = append(dereferrencedEvms, *en) + } - dereferrencedEvms := make([]blockchain.EVMNetwork, 0) - for _, en := range b.te.EVMNetworks { - dereferrencedEvms = append(dereferrencedEvms, *en) - } - - nodeConfigInToml := b.testConfig.GetNodeConfig() + nodeConfigInToml := b.testConfig.GetNodeConfig() - nodeConfig, _, err := node.BuildChainlinkNodeConfig( - dereferrencedEvms, - nodeConfigInToml.BaseConfigTOML, - nodeConfigInToml.CommonChainConfigTOML, - nodeConfigInToml.ChainConfigTOMLByChainID, - ) - if err != nil { - return nil, err - } + nodeConfig, _, err := node.BuildChainlinkNodeConfig( + dereferrencedEvms, + nodeConfigInToml.BaseConfigTOML, + nodeConfigInToml.CommonChainConfigTOML, + nodeConfigInToml.ChainConfigTOMLByChainID, + ) + if err != nil { + return nil, err + } - err = b.te.StartClCluster(nodeConfig, b.clNodesCount, b.secretsConfig, b.testConfig, b.clNodesOpts...) - if err != nil { - return nil, err + err = b.te.StartClCluster(nodeConfig, b.clNodesCount, b.secretsConfig, b.testConfig, b.clNodesOpts...) + if err != nil { + return nil, err + } } - b.te.isSimulatedNetwork = true return b.te, nil diff --git a/integration-tests/docs/VRF.md b/integration-tests/docs/VRF.md new file mode 100644 index 0000000000..745bec0586 --- /dev/null +++ b/integration-tests/docs/VRF.md @@ -0,0 +1,65 @@ +# How To Run VRF Tests +* All test configs should be placed in the [integration-tests/testconfig](../testconfig) folder +* All test configs for running tests in live testnets should be under [integration-tests/testconfig/vrfv2plus/overrides](../testconfig/vrfv2plus/overrides) folder + + +## Functional Tests +### In CI - using On Demand Workflows +```bash +gh workflow run "on-demand-vrfv2plus-smoke-tests.yml" \ +--ref develop \ +-f=test_secrets_override_key= \ +-f test_config_override_path= \ +-f test_suite="Selected Tests" \ # Optional, Options - "All Tests", "Selected Tests". Default is "All Tests". If "Selected Tests" is selected, then `test_list_regex` should be provided +-f test_list_regex="" \ # Optional, default is "TestVRFv2Plus$/(Link_Billing|Native_Billing|Direct_Funding)|TestVRFV2PlusWithBHS" which are P0 tests +-f chainlink_version="<>" # Optional, default is image created from develop branch. Not needed if you run tests against existing environment +-f notify_user_id_on_failure= # Optional, default is empty. If provided, will notify the user on slack if the tests fail +``` + +#### Examples: +Run P0 tests against existing environment (Staging) on Arbitrum Sepolia +```bash +gh workflow run "on-demand-vrfv2plus-smoke-tests.yml" \ +--ref develop \ +-f=test_secrets_override_key= \ +-f test_config_override_path=integration-tests/testconfig/vrfv2plus/overrides/staging/arbitrum_sepolia_staging_test_config.toml \ +-f test_suite="Selected Tests" +``` + +Run all tests deploying all contracts, CL nodes with `2.15.0` version on Base Sepolia +```bash +gh workflow run "on-demand-vrfv2plus-smoke-tests.yml" \ +--ref develop \ +-f=test_secrets_override_key= \ +-f test_config_override_path=integration-tests/testconfig/vrfv2plus/overrides/new_env/base_sepolia_new_env_test_config.toml \ +-f test_suite="All Tests" \ +-f chainlink_version="2.15.0" +``` + +### Locally +```bash +cd integration-tests +TEST_LOG_LEVEL=debug \ +BASE64_CONFIG_OVERRIDE=$(cat | base64) \ +go test -v -timeout 15m -run "" ./smoke +``` + +## Performance Tests +```bash +gh workflow run "on-demand-vrfv2plus-performance-test.yml" \ +--ref develop \ +-f=test_secrets_override_key= \ +-f test_config_override_path= \ +-f performanceTestType=“Smoke” # Options - "Smoke", "Soak", "Stress", "Load". +-f test_list_regex="" # Optional, default is "TestVRFV2PlusPerformance" +``` + +#### Examples: +Run SOAK tests against existing environment (Staging) on Base Sepolia +```bash +gh workflow run "on-demand-vrfv2plus-performance-test.yml" \ +--ref develop \ +-f=test_secrets_override_key= \ +-f test_config_override_path=integration-tests/testconfig/vrfv2plus/overrides/staging/base_sepolia_staging_test_config.toml \ +-f performanceTestType=“Soak” +``` diff --git a/integration-tests/go.mod b/integration-tests/go.mod index cf4b5ce8aa..c40ac63013 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -26,7 +26,7 @@ require ( github.com/onsi/gomega v1.33.1 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/prometheus/common v0.59.1 github.com/rs/zerolog v1.33.0 github.com/scylladb/go-reflectx v1.0.1 github.com/segmentio/ksuid v1.0.4 @@ -34,9 +34,9 @@ require ( github.com/slack-go/slack v0.12.2 github.com/smartcontractkit/chain-selectors v1.0.27 github.com/smartcontractkit/chainlink-automation v1.0.4 - github.com/smartcontractkit/chainlink-common v0.2.2-0.20240723123524-e407ecd120b1 + github.com/smartcontractkit/chainlink-common v0.2.3-0.20240925085218-aded1b263ecc github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.0 - github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.5 + github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.9 github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0 github.com/smartcontractkit/chainlink-testing-framework/seth v1.50.2 github.com/smartcontractkit/chainlink-testing-framework/wasp v1.50.0 @@ -51,12 +51,10 @@ require ( go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 golang.org/x/crypto v0.27.0 - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 golang.org/x/sync v0.8.0 golang.org/x/text v0.18.0 gopkg.in/guregu/null.v4 v4.0.0 - gopkg.in/yaml.v2 v2.4.0 - gopkg.in/yaml.v3 v3.0.1 k8s.io/apimachinery v0.31.0 ) @@ -192,7 +190,7 @@ require ( 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/fatih/color v1.17.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect @@ -228,6 +226,7 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.22.0 // indirect github.com/go-redis/redis/v8 v8.11.5 // indirect + github.com/go-viper/mapstructure/v2 v2.1.0 // indirect github.com/go-webauthn/webauthn v0.9.4 // indirect github.com/go-webauthn/x v0.1.5 // indirect github.com/goccy/go-json v0.10.2 // indirect @@ -267,7 +266,7 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/gtank/merlin v0.1.1 // indirect github.com/gtank/ristretto255 v0.1.2 // indirect @@ -280,7 +279,7 @@ require ( github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-msgpack v0.5.5 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-plugin v1.6.0 // indirect + github.com/hashicorp/go-plugin v1.6.2-0.20240829161738-06afb6d7ae99 // indirect github.com/hashicorp/go-retryablehttp v0.7.5 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-sockaddr v1.0.2 // indirect @@ -317,7 +316,7 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/julienschmidt/httprouter v1.3.0 // indirect github.com/kelseyhightower/envconfig v1.4.0 // indirect - github.com/klauspost/compress v1.17.8 // indirect + github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect @@ -358,13 +357,13 @@ require ( github.com/mtibben/percent v0.2.1 // indirect 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 github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/opencontainers/runc v1.1.7 // indirect github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e // indirect github.com/opentracing-contrib/go-stdlib v1.0.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect @@ -378,7 +377,7 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/prometheus/alertmanager v0.26.0 // indirect - github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_golang v1.20.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common/sigv4 v0.1.0 // indirect github.com/prometheus/exporter-toolkit v0.10.1-0.20230714054209-2f4150c63f97 // indirect @@ -399,14 +398,15 @@ require ( github.com/shirou/gopsutil/v3 v3.24.3 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 // indirect - github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240718160222-2dc0c8136bfa // indirect - github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 // indirect - github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e // indirect - github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 // indirect + github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 // indirect + github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240916152957-433914114bd2 // indirect + github.com/smartcontractkit/chainlink-feeds v0.0.0-20240910155501-42f20443189f // indirect + github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240911182932-3c609a6ac664 // indirect + github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240911194142-506bc469d8ae // indirect + github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // 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 - github.com/smartcontractkit/wsrpc v0.8.1 // indirect + github.com/smartcontractkit/wsrpc v0.8.2 // indirect github.com/soheilhy/cmux v0.1.5 // indirect github.com/sony/gobreaker v0.5.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect @@ -456,10 +456,18 @@ require ( go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240823153156-2a54df7bffb9 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.4.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0 // indirect + go.opentelemetry.io/otel/log v0.4.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/sdk v1.28.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.4.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect @@ -467,24 +475,26 @@ 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/mod v0.21.0 // indirect golang.org/x/net v0.29.0 // indirect - golang.org/x/oauth2 v0.21.0 // indirect + golang.org/x/oauth2 v0.22.0 // indirect golang.org/x/sys v0.25.0 // indirect golang.org/x/term v0.24.0 // indirect golang.org/x/time v0.6.0 // indirect - golang.org/x/tools v0.23.0 // indirect + golang.org/x/tools v0.25.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 - google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // 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.31.0 // indirect k8s.io/apiextensions-apiserver v0.31.0 // indirect k8s.io/cli-runtime v0.31.0 // indirect @@ -508,25 +518,22 @@ require ( exclude github.com/chaos-mesh/chaos-mesh/api/v1alpha1 v0.0.0-20220226050744-799408773657 replace ( - github.com/prometheus/client_golang => github.com/prometheus/client_golang v1.14.0 - github.com/prometheus/common => github.com/prometheus/common v0.42.0 -) + // until merged upstream: https://github.com/omissis/go-jsonschema/pull/264 + github.com/atombender/go-jsonschema => github.com/nolag/go-jsonschema v0.16.0-rtinianov -replace ( github.com/go-kit/log => github.com/go-kit/log v0.2.1 // replicating the replace directive on cosmos SDK github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 - // until merged upstream: https://github.com/hashicorp/go-plugin/pull/257 - github.com/hashicorp/go-plugin => github.com/smartcontractkit/go-plugin v0.0.0-20240208201424-b3b91517de16 - - // until merged upstream: https://github.com/mwitkow/grpc-proxy/pull/69 - github.com/mwitkow/grpc-proxy => github.com/smartcontractkit/grpc-proxy v0.0.0-20230731113816-f1be6620749f - // type func(a Label, b Label) bool of func(a, b Label) bool {…} does not match inferred type func(a Label, b Label) int for func(a E, b E) int github.com/prometheus/prometheus => github.com/prometheus/prometheus v0.47.2-0.20231010075449-4b9c19fe5510 ) -exclude github.com/sourcegraph/sourcegraph/lib v0.0.0-20221216004406-749998a2ac74 +replace ( + github.com/prometheus/client_golang => github.com/prometheus/client_golang v1.14.0 + github.com/prometheus/common => github.com/prometheus/common v0.42.0 +) + +replace github.com/sourcegraph/sourcegraph/lib => github.com/sourcegraph/sourcegraph-public-snapshot/lib v0.0.0-20240822153003-c864f15af264 diff --git a/integration-tests/go.sum b/integration-tests/go.sum index e4013c5803..d10606f1d4 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -512,8 +512,8 @@ github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwo github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= @@ -646,6 +646,8 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/go-viper/mapstructure/v2 v2.1.0 h1:gHnMa2Y/pIxElCH2GlZZ1lZSsn6XMtufpGyP1XxdC/w= +github.com/go-viper/mapstructure/v2 v2.1.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-webauthn/webauthn v0.9.4 h1:YxvHSqgUyc5AK2pZbqkWWR55qKeDPhP8zLDr6lpIc2g= github.com/go-webauthn/webauthn v0.9.4/go.mod h1:LqupCtzSef38FcxzaklmOn7AykGKhAhr9xlRbdbgnTw= github.com/go-webauthn/x v0.1.5 h1:V2TCzDU2TGLd0kSZOXdrqDVV5JB9ILnKxA9S53CSBw0= @@ -836,8 +838,8 @@ github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd h1:PpuIBO5P3e9hpqBD github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A= github.com/graph-gophers/dataloader v5.0.0+incompatible h1:R+yjsbrNq1Mo3aPG+Z/EKYrXrXXUNJHOgbRt+U6jOug= github.com/graph-gophers/dataloader v5.0.0+incompatible/go.mod h1:jk4jk0c5ZISbKaMe8WsVopGB5/15GvGHMdMdPtwlRp4= -github.com/graph-gophers/graphql-go v1.3.0 h1:Eb9x/q6MFpCLz7jBCiP/WTxjSDrYLR1QY41SORZyNJ0= -github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +github.com/graph-gophers/graphql-go v1.5.0 h1:fDqblo50TEpD0LY7RXk/LFVYEVqo3+tXMNMPSVXA1yc= +github.com/graph-gophers/graphql-go v1.5.0/go.mod h1:YtmJZDLbF1YYNrlNAuiO5zAStUWc3XZT07iGsVqe1Os= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -851,8 +853,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= @@ -895,6 +897,8 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-plugin v1.6.2-0.20240829161738-06afb6d7ae99 h1:OSQYEsRT3tRttZkk6zyC3aAaliwd7Loi/KgXgXxGtwA= +github.com/hashicorp/go-plugin v1.6.2-0.20240829161738-06afb6d7ae99/go.mod h1:CkgLQ5CZqNmdL9U9JzM532t8ZiYQ35+pj3b1FD37R0Q= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= @@ -1058,8 +1062,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= -github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= @@ -1261,8 +1265,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs= -github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= +github.com/opencontainers/runc v1.1.7 h1:y2EZDS8sNng4Ksf0GUYNhKbTShZJPJg1FiXJNH/uoCk= +github.com/opencontainers/runc v1.1.7/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50= github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e h1:4cPxUYdgaGzZIT5/j0IfqOrrXmq6bG8AwvwisMXpdrg= github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e/go.mod h1:DYR5Eij8rJl8h7gblRrOZ8g0kW1umSpKqYIBTgeDtLo= github.com/opentracing-contrib/go-stdlib v1.0.0 h1:TBS7YuVotp8myLon4Pv7BtCBzOTo1DeZCld0Z63mW2w= @@ -1417,40 +1421,38 @@ github.com/smartcontractkit/chain-selectors v1.0.27 h1:VE/ftX9Aae4gnw67yR1raKi+3 github.com/smartcontractkit/chain-selectors v1.0.27/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240723123524-e407ecd120b1 h1:pdEpjgbZ5w/Sd5lzg/XiuC5gVyrmSovOo+3nUD46SP8= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240723123524-e407ecd120b1/go.mod h1:Jg1sCTsbxg76YByI8ifpFby3FvVqISStHT8ypy9ocmY= -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-20240718160222-2dc0c8136bfa h1:g75H8oh2ws52s8BekwvGQ9XvBVu3E7WM1rfiA0PN0zk= -github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240718160222-2dc0c8136bfa/go.mod h1:wZvLHX/Sd9hskN51016cTFcT3G62KXVa6xbVDS7tRjc= -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.0.3-0.20240712132946-267a37c5ac6e h1:PzwzlHNv1YbJ6ZIdl/pIFRoOuOS4V4WLvjZvFUnZFL4= -github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e/go.mod h1:hsFhop+SlQHKD+DEFjZrMJmbauT1A/wvtZIeeo4PxFU= -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-common v0.2.3-0.20240925085218-aded1b263ecc h1:ALbyaoRzUSXQ2NhGFKVOyJqO22IB5yQjhjKWbIZGbrI= +github.com/smartcontractkit/chainlink-common v0.2.3-0.20240925085218-aded1b263ecc/go.mod h1:F6WUS6N4mP5ScwpwyTyAJc9/vjR+GXbMCRUOVekQi1g= +github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 h1:lTGIOQYLk1Ufn++X/AvZnt6VOcuhste5yp+C157No/Q= +github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7/go.mod h1:BMYE1vC/pGmdFSsOJdPrAA0/4gZ0Xo0SxTMdGspBtRo= +github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240916152957-433914114bd2 h1:yRk4ektpx/UxwarqAfgxUXLrsYXlaNeP1NOwzHGrK2Q= +github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240916152957-433914114bd2/go.mod h1:rNhNSrrRMvkgAm5SA6bNTdh2340bTQQZdUVNtZ2o2bk= +github.com/smartcontractkit/chainlink-feeds v0.0.0-20240910155501-42f20443189f h1:p4p3jBT91EQyLuAMvHD+zNJsuAYI/QjJbzuGUJ7wIgg= +github.com/smartcontractkit/chainlink-feeds v0.0.0-20240910155501-42f20443189f/go.mod h1:FLlWBt2hwiMVgt9AcSo6wBJYIRd/nsc8ENbV1Wir1bw= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240911182932-3c609a6ac664 h1:JPs35oSO07PK3Qv7Kyv0GJHVLacIE1IkrvefaPyBjKs= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240911182932-3c609a6ac664/go.mod h1:iJ9DKYo0F64ue7IogAIELwU2DfrhEAh76eSmZOilT8A= +github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240911194142-506bc469d8ae h1:d+B8y2Nd/PrnPMNoaSPn3eDgUgxcVcIqAxGrvYu/gGw= +github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240911194142-506bc469d8ae/go.mod h1:ec/a20UZ7YRK4oxJcnTBFzp1+DBcJcwqEaerUMsktMs= github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.0 h1:mgjBQIEy+3V3G6K8e+6by3xndgsXdYYsdy+7kzQZwSk= github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.0/go.mod h1:pdIxrooP5CFGmC0p5NTOBiZAFtMw+5pTT4de5GY3ywA= -github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.5 h1:Owb1MQZn0NZHwtZAnPZE6TVoTx6xLrfPaUdeOYswE9M= -github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.5/go.mod h1:hS4yNF94C1lkS9gvtFXW8Km8K9NzGeR20aNfkqo5qbE= +github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.9 h1:/2kAb6y854viKigkdFMWDNNbaz3zD0gAkbZoSHC8Rrg= +github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.9/go.mod h1:7R5wGWWJi0dr5Y5cXbLQ4vSeIj0ElvhBaymcfvqqUmo= github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0 h1:VIxK8u0Jd0Q/VuhmsNm6Bls6Tb31H/sA3A/rbc5hnhg= github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0/go.mod h1:lyAu+oMXdNUzEDScj2DXB2IueY+SDXPPfyl/kb63tMM= github.com/smartcontractkit/chainlink-testing-framework/seth v1.50.2 h1:sA4bnFVMwzhR/uxjHcMc5ccFLIv3Qr9XUsUS7aY+Yyc= github.com/smartcontractkit/chainlink-testing-framework/seth v1.50.2/go.mod h1:afY3QmNgeR/VI1pRbGH8g3YXGy7C2RrFOwUzEFvL3L8= github.com/smartcontractkit/chainlink-testing-framework/wasp v1.50.0 h1:gfhfTn7HkbUHNooSF3c9vzQyN8meWJVGt6G/pNUbpYk= github.com/smartcontractkit/chainlink-testing-framework/wasp v1.50.0/go.mod h1:tqajhpUJA/9OaMCLitghBXjAgqYO4i27St0F4TUO3+M= -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/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12ijqMM9tvYVEm+nR826WsrNi6zCKpwBhuApq127wHs= +github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7/go.mod h1:FX7/bVdoep147QQhsOPkYsPEXhGZjeYx6lBSaSXtZOA= github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7 h1:e38V5FYE7DA1JfKXeD5Buo/7lczALuVXlJ8YNTAUxcw= github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7/go.mod h1:fb1ZDVXACvu4frX3APHZaEBp0xi1DIm34DcA0CwTsZM= github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1 h1:yiKnypAqP8l0OX0P3klzZ7SCcBUxy5KqTAKZmQOvSQE= github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1/go.mod h1:q6f4fe39oZPdsh1i57WznEZgxd8siidMaSFq3wdPmVg= github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20230906073235-9e478e5e19f1 h1:Dai1bn+Q5cpeGMQwRdjOdVjG8mmFFROVkSKuUgBErRQ= github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20230906073235-9e478e5e19f1/go.mod h1:G5Sd/yzHWf26rQ+X0nG9E0buKPqRGPMJAfk2gwCzOOw= -github.com/smartcontractkit/wsrpc v0.8.1 h1:kk0SXLqWrWaZ3J6c7n8D0NZ2uTMBBBpG5dZZXZX8UGE= -github.com/smartcontractkit/wsrpc v0.8.1/go.mod h1:yfg8v8fPLXkb6Mcnx6Pm/snP6jJ0r5Kf762Yd1a/KpA= +github.com/smartcontractkit/wsrpc v0.8.2 h1:XB/xcn/MMseHW+8JE8+a/rceA86ck7Ur6cEa9LiUC8M= +github.com/smartcontractkit/wsrpc v0.8.2/go.mod h1:2u/wfnhl5R4RlSXseN4n6HHIWk8w1Am3AT6gWftQbNg= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -1654,16 +1656,30 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIX go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240823153156-2a54df7bffb9 h1:UiRNKd1OgqsLbFwE+wkAWTdiAxXtCBqKIHeBIse4FUA= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240823153156-2a54df7bffb9/go.mod h1:eqZlW3pJWhjyexnDPrdQxix1pn0wwhI4AO4GKpP/bMI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 h1:U2guen0GhqH8o/G2un8f/aG/y++OuW6MyCo6hT9prXk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0/go.mod h1:yeGZANgEcpdx/WK0IvvRFC+2oLiMS2u4L/0Rj2M2Qr0= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 h1:R3X6ZXmNPRR8ul6i3WgFURCHzaXjHdm0karRG/+dj3s= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0/go.mod h1:QWFXnDavXWwMx2EEcZsf3yxgEKAqsxQ+Syjp+seyInw= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.4.0 h1:0MH3f8lZrflbUWXVxyBg/zviDFdGE062uKh5+fu8Vv0= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.4.0/go.mod h1:Vh68vYiHY5mPdekTr0ox0sALsqjoVy0w3Os278yX5SQ= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.28.0 h1:BJee2iLkfRfl9lc7aFmBwkWxY/RI1RDdXepSF6y8TPE= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.28.0/go.mod h1:DIzlHs3DRscCIBU3Y9YSzPfScwnYnzfnCd4g8zA7bZc= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0 h1:EVSnY9JbEEW92bEkIYOVMw4q1WJxIAGoFTrtYOzWuRQ= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0/go.mod h1:Ea1N1QQryNXpCD0I1fdLibBAIpQuBkznMmkdKrapk1Y= +go.opentelemetry.io/otel/log v0.4.0 h1:/vZ+3Utqh18e8TPjuc3ecg284078KWrR8BRz+PQAj3o= +go.opentelemetry.io/otel/log v0.4.0/go.mod h1:DhGnQvky7pHy82MIRV43iXh3FlKN8UUKftn0KbLOq6I= go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/sdk/log v0.4.0 h1:1mMI22L82zLqf6KtkjrRy5BbagOTWdJsqMY/HSqILAA= +go.opentelemetry.io/otel/sdk/log v0.4.0/go.mod h1:AYJ9FVF0hNOgAVzUG/ybg/QttnXhUePWAupmCqtdESo= go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08= go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= @@ -1744,8 +1760,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-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= 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= @@ -1770,8 +1786,8 @@ golang.org/x/mod v0.3.0/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.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 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= @@ -1836,8 +1852,8 @@ golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= -golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= -golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= +golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -2038,8 +2054,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 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.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= +golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= 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= @@ -2116,10 +2132,10 @@ google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210401141331-865547bb08e2/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d h1:/hmn0Ku5kWij/kjGsrcJeC1T/MrJi2iNWwgAqrihFwc= google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d/go.mod h1:FfBgJBJg9GcpPvKIuHSZ/aE1g2ecGL74upMzGZjiGEY= -google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY= -google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d h1:JU0iKnSg02Gmb5ZdV8nYsKEKsP6o/FGVWTrw4i1DA9A= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= +google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= diff --git a/integration-tests/load/README.md b/integration-tests/load/README.md index afcf633e5c..359de3c6d4 100644 --- a/integration-tests/load/README.md +++ b/integration-tests/load/README.md @@ -70,8 +70,10 @@ Gun should be working with one instance of your product. VU(Virtual user) creates a new instance of your product and works with it in `Call()` ### Cluster mode (k8s) + Add configuration to `overrides.toml` -``` + +```toml [WaspAutoBuild] namespace = "wasp" update_image = true diff --git a/integration-tests/load/automationv2_1/automationv2_1_test.go b/integration-tests/load/automationv2_1/automationv2_1_test.go index cb2606f00b..3cd931ecef 100644 --- a/integration-tests/load/automationv2_1/automationv2_1_test.go +++ b/integration-tests/load/automationv2_1/automationv2_1_test.go @@ -22,12 +22,8 @@ import ( "github.com/slack-go/slack" "github.com/stretchr/testify/require" - ocr3 "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3confighelper" - "github.com/smartcontractkit/chainlink-testing-framework/wasp" - ocr2keepers30config "github.com/smartcontractkit/chainlink-automation/pkg/v3/config" - "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" "github.com/smartcontractkit/chainlink-testing-framework/lib/k8s/environment" "github.com/smartcontractkit/chainlink-testing-framework/lib/k8s/pkg/helm/chainlink" @@ -154,6 +150,7 @@ func setUpDataStreamsWireMock(url string) error { func TestLogTrigger(t *testing.T) { ctx := tests.Context(t) l := logging.GetTestLogger(t) + registryVersion := contractseth.RegistryVersion_2_1 loadedTestConfig, err := tc.GetConfig([]string{"Load"}, tc.Automation) if err != nil { @@ -314,56 +311,16 @@ Load Config: multicallAddress, err := contracts.DeployMultiCallContract(chainClient) require.NoError(t, err, "Error deploying multicall contract") - a := automationv2.NewAutomationTestK8s(l, chainClient, chainlinkNodes) - conf := loadedTestConfig.Automation.AutomationConfig - a.RegistrySettings = contracts.KeeperRegistrySettings{ - PaymentPremiumPPB: *conf.RegistrySettings.PaymentPremiumPPB, - FlatFeeMicroLINK: *conf.RegistrySettings.FlatFeeMicroLINK, - CheckGasLimit: *conf.RegistrySettings.CheckGasLimit, - StalenessSeconds: conf.RegistrySettings.StalenessSeconds, - GasCeilingMultiplier: *conf.RegistrySettings.GasCeilingMultiplier, - MaxPerformGas: *conf.RegistrySettings.MaxPerformGas, - MinUpkeepSpend: conf.RegistrySettings.MinUpkeepSpend, - FallbackGasPrice: conf.RegistrySettings.FallbackGasPrice, - FallbackLinkPrice: conf.RegistrySettings.FallbackLinkPrice, - MaxCheckDataSize: *conf.RegistrySettings.MaxCheckDataSize, - MaxPerformDataSize: *conf.RegistrySettings.MaxPerformDataSize, - MaxRevertDataSize: *conf.RegistrySettings.MaxRevertDataSize, - RegistryVersion: contractseth.RegistryVersion_2_1, - } + a := automationv2.NewAutomationTestK8s(l, chainClient, chainlinkNodes, &loadedTestConfig) + a.RegistrySettings = actions.ReadRegistryConfig(loadedTestConfig) + a.RegistrySettings.RegistryVersion = registryVersion + a.PluginConfig = actions.ReadPluginConfig(loadedTestConfig) + a.PublicConfig = actions.ReadPublicConfig(loadedTestConfig) a.RegistrarSettings = contracts.KeeperRegistrarSettings{ AutoApproveConfigType: uint8(2), - AutoApproveMaxAllowed: math.MaxUint16, + AutoApproveMaxAllowed: 1000, MinLinkJuels: big.NewInt(0), } - a.PluginConfig = ocr2keepers30config.OffchainConfig{ - TargetProbability: *conf.PluginConfig.TargetProbability, - TargetInRounds: *conf.PluginConfig.TargetInRounds, - PerformLockoutWindow: *conf.PluginConfig.PerformLockoutWindow, - GasLimitPerReport: *conf.PluginConfig.GasLimitPerReport, - GasOverheadPerUpkeep: *conf.PluginConfig.GasOverheadPerUpkeep, - MinConfirmations: *conf.PluginConfig.MinConfirmations, - MaxUpkeepBatchSize: *conf.PluginConfig.MaxUpkeepBatchSize, - LogProviderConfig: ocr2keepers30config.LogProviderConfig{ - BlockRate: *conf.PluginConfig.LogProviderConfig.BlockRate, - LogLimit: *conf.PluginConfig.LogProviderConfig.LogLimit, - }, - } - a.PublicConfig = ocr3.PublicConfig{ - DeltaProgress: *conf.PublicConfig.DeltaProgress, - DeltaResend: *conf.PublicConfig.DeltaResend, - DeltaInitial: *conf.PublicConfig.DeltaInitial, - DeltaRound: *conf.PublicConfig.DeltaRound, - DeltaGrace: *conf.PublicConfig.DeltaGrace, - DeltaCertifiedCommitRequest: *conf.PublicConfig.DeltaCertifiedCommitRequest, - DeltaStage: *conf.PublicConfig.DeltaStage, - RMax: *conf.PublicConfig.RMax, - MaxDurationQuery: *conf.PublicConfig.MaxDurationQuery, - MaxDurationObservation: *conf.PublicConfig.MaxDurationObservation, - MaxDurationShouldAcceptAttestedReport: *conf.PublicConfig.MaxDurationShouldAcceptAttestedReport, - MaxDurationShouldTransmitAcceptedReport: *conf.PublicConfig.MaxDurationShouldTransmitAcceptedReport, - F: *conf.PublicConfig.F, - } if *loadedTestConfig.Automation.DataStreams.Enabled { a.SetMercuryCredentialName("cred1") diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index 8b04590c4d..0503ea6a4d 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -15,14 +15,12 @@ require ( github.com/pkg/errors v0.9.1 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.20240723123524-e407ecd120b1 - github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.5 + github.com/smartcontractkit/chainlink-common v0.2.3-0.20240925085218-aded1b263ecc + github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.9 github.com/smartcontractkit/chainlink-testing-framework/seth v1.50.2 github.com/smartcontractkit/chainlink-testing-framework/wasp v1.50.0 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 github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20230906073235-9e478e5e19f1 github.com/stretchr/testify v1.9.0 github.com/wiremock/go-wiremock v1.9.0 @@ -30,47 +28,19 @@ require ( ) require ( - cosmossdk.io/api v0.3.1 // indirect - cosmossdk.io/core v0.5.1 // indirect - cosmossdk.io/depinject v1.0.0-alpha.4 // indirect - cosmossdk.io/errors v1.0.1 // indirect - cosmossdk.io/math v1.3.0 // 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/containerd/errdefs v0.1.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/sagikazarmark/locafero v0.4.0 // indirect - github.com/sagikazarmark/slog-shim v0.1.0 // indirect - github.com/sergi/go-diff v1.3.1 // indirect - github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 // indirect - github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.0 // indirect - github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0 // indirect + github.com/AlekSi/pointer v1.1.0 // indirect + github.com/smartcontractkit/chainlink-automation v1.0.4 // indirect + github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7 // indirect github.com/testcontainers/testcontainers-go v0.28.0 // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect - k8s.io/apimachinery v0.31.0 // indirect ) -// avoids ambigious imports of indirect dependencies -exclude github.com/hashicorp/consul v1.2.1 - require ( contrib.go.opencensus.io/exporter/stackdriver v0.13.5 // indirect + cosmossdk.io/api v0.3.1 // indirect + cosmossdk.io/core v0.5.1 // indirect + cosmossdk.io/depinject v1.0.0-alpha.4 // indirect + cosmossdk.io/errors v1.0.1 // indirect + cosmossdk.io/math v1.3.0 // indirect dario.cat/mergo v1.0.1 // indirect filippo.io/edwards25519 v1.1.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect @@ -86,6 +56,7 @@ require ( github.com/CosmWasm/wasmvm v1.2.4 // indirect github.com/DataDog/zstd v1.5.2 // indirect github.com/K-Phoen/sdk v0.12.4 // indirect + github.com/Khan/genqlient v0.7.0 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.1 // indirect @@ -101,15 +72,31 @@ require ( github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/avast/retry-go v3.0.0+incompatible // indirect github.com/avast/retry-go/v4 v4.6.0 // 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/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df // 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 @@ -137,6 +124,7 @@ require ( github.com/consensys/gnark-crypto v0.12.1 // indirect github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/continuity v0.4.3 // indirect + github.com/containerd/errdefs v0.1.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect @@ -179,7 +167,7 @@ require ( 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/fatih/color v1.17.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fvbommel/sortorder v1.1.0 // indirect @@ -217,6 +205,7 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.22.0 // indirect github.com/go-redis/redis/v8 v8.11.5 // indirect + github.com/go-viper/mapstructure/v2 v2.1.0 // indirect github.com/go-webauthn/webauthn v0.9.4 // indirect github.com/go-webauthn/x v0.1.5 // indirect github.com/goccy/go-json v0.10.2 // indirect @@ -231,6 +220,7 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/google/btree v1.1.2 // indirect + github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-github/v41 v41.0.0 // indirect github.com/google/go-querystring v1.1.0 // indirect @@ -272,7 +262,7 @@ require ( github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-msgpack v0.5.5 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-plugin v1.6.0 // indirect + github.com/hashicorp/go-plugin v1.6.2-0.20240829161738-06afb6d7ae99 // indirect github.com/hashicorp/go-retryablehttp v0.7.5 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-sockaddr v1.0.2 // indirect @@ -309,7 +299,7 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/julienschmidt/httprouter v1.3.0 // indirect github.com/kelseyhightower/envconfig v1.4.0 // indirect - github.com/klauspost/compress v1.17.8 // indirect + github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect @@ -327,6 +317,7 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/miekg/dns v1.1.56 // indirect github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect @@ -351,7 +342,6 @@ require ( github.com/mtibben/percent v0.2.1 // indirect 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/oklog/run v1.1.0 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect @@ -371,9 +361,9 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/prometheus/alertmanager v0.26.0 // indirect - github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_golang v1.20.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/common v0.59.1 // indirect github.com/prometheus/common/sigv4 v0.1.0 // indirect github.com/prometheus/exporter-toolkit v0.10.1-0.20230714054209-2f4150c63f97 // indirect github.com/prometheus/procfs v0.15.1 // indirect @@ -382,6 +372,9 @@ require ( github.com/rivo/uniseg v0.4.4 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect github.com/scylladb/go-reflectx v1.0.1 // indirect @@ -394,12 +387,16 @@ require ( github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/smartcontractkit/chain-selectors v1.0.27 // indirect - github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240718160222-2dc0c8136bfa // indirect - github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 // indirect - github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e // indirect - github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 // indirect + github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 // indirect + github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240916152957-433914114bd2 // indirect + github.com/smartcontractkit/chainlink-feeds v0.0.0-20240910155501-42f20443189f // indirect + github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240911182932-3c609a6ac664 // indirect + github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240911194142-506bc469d8ae // indirect + github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.0 // indirect + github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0 // indirect + github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // indirect github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1 // indirect - github.com/smartcontractkit/wsrpc v0.8.1 // indirect + github.com/smartcontractkit/wsrpc v0.8.2 // indirect github.com/soheilhy/cmux v0.1.5 // indirect github.com/sony/gobreaker v0.5.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect @@ -432,6 +429,7 @@ require ( github.com/umbracle/ethgo v0.1.3 // indirect github.com/umbracle/fastrlp v0.0.0-20220527094140-59d5dd30e722 // indirect github.com/valyala/fastjson v1.4.1 // indirect + github.com/vektah/gqlparser/v2 v2.5.11 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xlab/treeprint v1.2.0 // indirect @@ -451,10 +449,18 @@ require ( go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240823153156-2a54df7bffb9 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.4.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0 // indirect + go.opentelemetry.io/otel/log v0.4.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/sdk v1.28.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.4.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect @@ -465,8 +471,8 @@ require ( go4.org/netipx v0.0.0-20230125063823-8449b0a6169f // indirect golang.org/x/arch v0.8.0 // indirect golang.org/x/crypto v0.27.0 // indirect - golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect - golang.org/x/mod v0.20.0 // indirect + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect + golang.org/x/mod v0.21.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/oauth2 v0.22.0 // indirect golang.org/x/sync v0.8.0 // indirect @@ -474,7 +480,7 @@ require ( golang.org/x/term v0.24.0 // indirect golang.org/x/text v0.18.0 // indirect golang.org/x/time v0.6.0 // indirect - golang.org/x/tools v0.24.0 // indirect + golang.org/x/tools v0.25.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 @@ -482,6 +488,7 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // 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/guregu/null.v4 v4.0.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect @@ -490,6 +497,7 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.31.0 // indirect k8s.io/apiextensions-apiserver v0.31.0 // indirect + k8s.io/apimachinery 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.31.0 // indirect @@ -508,18 +516,18 @@ require ( sigs.k8s.io/yaml v1.4.0 // indirect; indirect nhooyr.io/websocket v1.8.7 // indirect ) +// avoids ambigious imports of indirect dependencies +exclude github.com/hashicorp/consul v1.2.1 + replace ( + // until merged upstream: https://github.com/omissis/go-jsonschema/pull/264 + github.com/atombender/go-jsonschema => github.com/nolag/go-jsonschema v0.16.0-rtinianov + github.com/go-kit/log => github.com/go-kit/log v0.2.1 // replicating the replace directive on cosmos SDK github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 - // until merged upstream: https://github.com/hashicorp/go-plugin/pull/257 - github.com/hashicorp/go-plugin => github.com/smartcontractkit/go-plugin v0.0.0-20240208201424-b3b91517de16 - - // until merged upstream: https://github.com/mwitkow/grpc-proxy/pull/69 - github.com/mwitkow/grpc-proxy => github.com/smartcontractkit/grpc-proxy v0.0.0-20230731113816-f1be6620749f - // type func(a Label, b Label) bool of func(a, b Label) bool {…} does not match inferred type func(a Label, b Label) int for func(a E, b E) int github.com/prometheus/prometheus => github.com/prometheus/prometheus v0.47.2-0.20231010075449-4b9c19fe5510 ) @@ -565,4 +573,4 @@ replace ( sigs.k8s.io/controller-runtime => sigs.k8s.io/controller-runtime v0.16.2 ) -exclude github.com/sourcegraph/sourcegraph/lib v0.0.0-20221216004406-749998a2ac74 +replace github.com/sourcegraph/sourcegraph/lib => github.com/sourcegraph/sourcegraph-public-snapshot/lib v0.0.0-20240822153003-c864f15af264 diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index 4135a49d67..ea10d96011 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -129,6 +129,8 @@ github.com/K-Phoen/grabana v0.22.1 h1:b/O+C3H2H6VNYSeMCYUO4X4wYuwFXgBcRkvYa+fjpQ github.com/K-Phoen/grabana v0.22.1/go.mod h1:3LTXrTzQzTKTgvKSXdRjlsJbizSOW/V23Q3iX00R5bU= github.com/K-Phoen/sdk v0.12.4 h1:j2EYuBJm3zDTD0fGKACVFWxAXtkR0q5QzfVqxmHSeGQ= github.com/K-Phoen/sdk v0.12.4/go.mod h1:qmM0wO23CtoDux528MXPpYvS4XkRWkWX6rvX9Za8EVU= +github.com/Khan/genqlient v0.7.0 h1:GZ1meyRnzcDTK48EjqB8t3bcfYvHArCUUvgOwpz1D4w= +github.com/Khan/genqlient v0.7.0/go.mod h1:HNyy3wZvuYwmW3Y7mkoQLZsa/R5n5yIRajS1kPBvSFM= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= @@ -179,6 +181,8 @@ github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc= github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -490,8 +494,8 @@ github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwo github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= @@ -626,6 +630,8 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/go-viper/mapstructure/v2 v2.1.0 h1:gHnMa2Y/pIxElCH2GlZZ1lZSsn6XMtufpGyP1XxdC/w= +github.com/go-viper/mapstructure/v2 v2.1.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-webauthn/webauthn v0.9.4 h1:YxvHSqgUyc5AK2pZbqkWWR55qKeDPhP8zLDr6lpIc2g= github.com/go-webauthn/webauthn v0.9.4/go.mod h1:LqupCtzSef38FcxzaklmOn7AykGKhAhr9xlRbdbgnTw= github.com/go-webauthn/x v0.1.5 h1:V2TCzDU2TGLd0kSZOXdrqDVV5JB9ILnKxA9S53CSBw0= @@ -820,8 +826,8 @@ github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd h1:PpuIBO5P3e9hpqBD github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A= github.com/graph-gophers/dataloader v5.0.0+incompatible h1:R+yjsbrNq1Mo3aPG+Z/EKYrXrXXUNJHOgbRt+U6jOug= github.com/graph-gophers/dataloader v5.0.0+incompatible/go.mod h1:jk4jk0c5ZISbKaMe8WsVopGB5/15GvGHMdMdPtwlRp4= -github.com/graph-gophers/graphql-go v1.3.0 h1:Eb9x/q6MFpCLz7jBCiP/WTxjSDrYLR1QY41SORZyNJ0= -github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +github.com/graph-gophers/graphql-go v1.5.0 h1:fDqblo50TEpD0LY7RXk/LFVYEVqo3+tXMNMPSVXA1yc= +github.com/graph-gophers/graphql-go v1.5.0/go.mod h1:YtmJZDLbF1YYNrlNAuiO5zAStUWc3XZT07iGsVqe1Os= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -879,6 +885,8 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-plugin v1.6.2-0.20240829161738-06afb6d7ae99 h1:OSQYEsRT3tRttZkk6zyC3aAaliwd7Loi/KgXgXxGtwA= +github.com/hashicorp/go-plugin v1.6.2-0.20240829161738-06afb6d7ae99/go.mod h1:CkgLQ5CZqNmdL9U9JzM532t8ZiYQ35+pj3b1FD37R0Q= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= @@ -1040,8 +1048,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= -github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= @@ -1389,40 +1397,38 @@ github.com/smartcontractkit/chain-selectors v1.0.27 h1:VE/ftX9Aae4gnw67yR1raKi+3 github.com/smartcontractkit/chain-selectors v1.0.27/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240723123524-e407ecd120b1 h1:pdEpjgbZ5w/Sd5lzg/XiuC5gVyrmSovOo+3nUD46SP8= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240723123524-e407ecd120b1/go.mod h1:Jg1sCTsbxg76YByI8ifpFby3FvVqISStHT8ypy9ocmY= -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-20240718160222-2dc0c8136bfa h1:g75H8oh2ws52s8BekwvGQ9XvBVu3E7WM1rfiA0PN0zk= -github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240718160222-2dc0c8136bfa/go.mod h1:wZvLHX/Sd9hskN51016cTFcT3G62KXVa6xbVDS7tRjc= -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.0.3-0.20240712132946-267a37c5ac6e h1:PzwzlHNv1YbJ6ZIdl/pIFRoOuOS4V4WLvjZvFUnZFL4= -github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e/go.mod h1:hsFhop+SlQHKD+DEFjZrMJmbauT1A/wvtZIeeo4PxFU= -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-common v0.2.3-0.20240925085218-aded1b263ecc h1:ALbyaoRzUSXQ2NhGFKVOyJqO22IB5yQjhjKWbIZGbrI= +github.com/smartcontractkit/chainlink-common v0.2.3-0.20240925085218-aded1b263ecc/go.mod h1:F6WUS6N4mP5ScwpwyTyAJc9/vjR+GXbMCRUOVekQi1g= +github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 h1:lTGIOQYLk1Ufn++X/AvZnt6VOcuhste5yp+C157No/Q= +github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7/go.mod h1:BMYE1vC/pGmdFSsOJdPrAA0/4gZ0Xo0SxTMdGspBtRo= +github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240916152957-433914114bd2 h1:yRk4ektpx/UxwarqAfgxUXLrsYXlaNeP1NOwzHGrK2Q= +github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240916152957-433914114bd2/go.mod h1:rNhNSrrRMvkgAm5SA6bNTdh2340bTQQZdUVNtZ2o2bk= +github.com/smartcontractkit/chainlink-feeds v0.0.0-20240910155501-42f20443189f h1:p4p3jBT91EQyLuAMvHD+zNJsuAYI/QjJbzuGUJ7wIgg= +github.com/smartcontractkit/chainlink-feeds v0.0.0-20240910155501-42f20443189f/go.mod h1:FLlWBt2hwiMVgt9AcSo6wBJYIRd/nsc8ENbV1Wir1bw= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240911182932-3c609a6ac664 h1:JPs35oSO07PK3Qv7Kyv0GJHVLacIE1IkrvefaPyBjKs= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240911182932-3c609a6ac664/go.mod h1:iJ9DKYo0F64ue7IogAIELwU2DfrhEAh76eSmZOilT8A= +github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240911194142-506bc469d8ae h1:d+B8y2Nd/PrnPMNoaSPn3eDgUgxcVcIqAxGrvYu/gGw= +github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240911194142-506bc469d8ae/go.mod h1:ec/a20UZ7YRK4oxJcnTBFzp1+DBcJcwqEaerUMsktMs= github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.0 h1:mgjBQIEy+3V3G6K8e+6by3xndgsXdYYsdy+7kzQZwSk= github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.0/go.mod h1:pdIxrooP5CFGmC0p5NTOBiZAFtMw+5pTT4de5GY3ywA= -github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.5 h1:Owb1MQZn0NZHwtZAnPZE6TVoTx6xLrfPaUdeOYswE9M= -github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.5/go.mod h1:hS4yNF94C1lkS9gvtFXW8Km8K9NzGeR20aNfkqo5qbE= +github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.9 h1:/2kAb6y854viKigkdFMWDNNbaz3zD0gAkbZoSHC8Rrg= +github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.9/go.mod h1:7R5wGWWJi0dr5Y5cXbLQ4vSeIj0ElvhBaymcfvqqUmo= github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0 h1:VIxK8u0Jd0Q/VuhmsNm6Bls6Tb31H/sA3A/rbc5hnhg= github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0/go.mod h1:lyAu+oMXdNUzEDScj2DXB2IueY+SDXPPfyl/kb63tMM= github.com/smartcontractkit/chainlink-testing-framework/seth v1.50.2 h1:sA4bnFVMwzhR/uxjHcMc5ccFLIv3Qr9XUsUS7aY+Yyc= github.com/smartcontractkit/chainlink-testing-framework/seth v1.50.2/go.mod h1:afY3QmNgeR/VI1pRbGH8g3YXGy7C2RrFOwUzEFvL3L8= github.com/smartcontractkit/chainlink-testing-framework/wasp v1.50.0 h1:gfhfTn7HkbUHNooSF3c9vzQyN8meWJVGt6G/pNUbpYk= github.com/smartcontractkit/chainlink-testing-framework/wasp v1.50.0/go.mod h1:tqajhpUJA/9OaMCLitghBXjAgqYO4i27St0F4TUO3+M= -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/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12ijqMM9tvYVEm+nR826WsrNi6zCKpwBhuApq127wHs= +github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7/go.mod h1:FX7/bVdoep147QQhsOPkYsPEXhGZjeYx6lBSaSXtZOA= github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7 h1:e38V5FYE7DA1JfKXeD5Buo/7lczALuVXlJ8YNTAUxcw= github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7/go.mod h1:fb1ZDVXACvu4frX3APHZaEBp0xi1DIm34DcA0CwTsZM= github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1 h1:yiKnypAqP8l0OX0P3klzZ7SCcBUxy5KqTAKZmQOvSQE= github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1/go.mod h1:q6f4fe39oZPdsh1i57WznEZgxd8siidMaSFq3wdPmVg= github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20230906073235-9e478e5e19f1 h1:Dai1bn+Q5cpeGMQwRdjOdVjG8mmFFROVkSKuUgBErRQ= github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20230906073235-9e478e5e19f1/go.mod h1:G5Sd/yzHWf26rQ+X0nG9E0buKPqRGPMJAfk2gwCzOOw= -github.com/smartcontractkit/wsrpc v0.8.1 h1:kk0SXLqWrWaZ3J6c7n8D0NZ2uTMBBBpG5dZZXZX8UGE= -github.com/smartcontractkit/wsrpc v0.8.1/go.mod h1:yfg8v8fPLXkb6Mcnx6Pm/snP6jJ0r5Kf762Yd1a/KpA= +github.com/smartcontractkit/wsrpc v0.8.2 h1:XB/xcn/MMseHW+8JE8+a/rceA86ck7Ur6cEa9LiUC8M= +github.com/smartcontractkit/wsrpc v0.8.2/go.mod h1:2u/wfnhl5R4RlSXseN4n6HHIWk8w1Am3AT6gWftQbNg= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -1543,6 +1549,8 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC github.com/valyala/fastjson v1.4.1 h1:hrltpHpIpkaxll8QltMU8c3QZ5+qIiCL8yKqPFJI/yE= github.com/valyala/fastjson v1.4.1/go.mod h1:nV6MsjxL2IMJQUoHDIrjEI7oLyeqK6aBD7EFWPsvP8o= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/vektah/gqlparser/v2 v2.5.11 h1:JJxLtXIoN7+3x6MBdtIP59TP1RANnY7pXOaDnADQSf8= +github.com/vektah/gqlparser/v2 v2.5.11/go.mod h1:1rCcfwB2ekJofmluGWXMSEnPMZgbxzwj6FaZ/4OT8Cc= github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs= github.com/vultr/govultr/v2 v2.17.2/go.mod h1:ZFOKGWmgjytfyjeyAdhQlSWwTjh2ig+X49cAp50dzXI= github.com/wiremock/go-wiremock v1.9.0 h1:9xcU4/IoEfgCaH4TGhQTtiQyBh2eMtu9JB6ppWduK+E= @@ -1624,16 +1632,30 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIX go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240823153156-2a54df7bffb9 h1:UiRNKd1OgqsLbFwE+wkAWTdiAxXtCBqKIHeBIse4FUA= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240823153156-2a54df7bffb9/go.mod h1:eqZlW3pJWhjyexnDPrdQxix1pn0wwhI4AO4GKpP/bMI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 h1:U2guen0GhqH8o/G2un8f/aG/y++OuW6MyCo6hT9prXk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0/go.mod h1:yeGZANgEcpdx/WK0IvvRFC+2oLiMS2u4L/0Rj2M2Qr0= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 h1:R3X6ZXmNPRR8ul6i3WgFURCHzaXjHdm0karRG/+dj3s= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0/go.mod h1:QWFXnDavXWwMx2EEcZsf3yxgEKAqsxQ+Syjp+seyInw= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.4.0 h1:0MH3f8lZrflbUWXVxyBg/zviDFdGE062uKh5+fu8Vv0= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.4.0/go.mod h1:Vh68vYiHY5mPdekTr0ox0sALsqjoVy0w3Os278yX5SQ= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.28.0 h1:BJee2iLkfRfl9lc7aFmBwkWxY/RI1RDdXepSF6y8TPE= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.28.0/go.mod h1:DIzlHs3DRscCIBU3Y9YSzPfScwnYnzfnCd4g8zA7bZc= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0 h1:EVSnY9JbEEW92bEkIYOVMw4q1WJxIAGoFTrtYOzWuRQ= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0/go.mod h1:Ea1N1QQryNXpCD0I1fdLibBAIpQuBkznMmkdKrapk1Y= +go.opentelemetry.io/otel/log v0.4.0 h1:/vZ+3Utqh18e8TPjuc3ecg284078KWrR8BRz+PQAj3o= +go.opentelemetry.io/otel/log v0.4.0/go.mod h1:DhGnQvky7pHy82MIRV43iXh3FlKN8UUKftn0KbLOq6I= go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/sdk/log v0.4.0 h1:1mMI22L82zLqf6KtkjrRy5BbagOTWdJsqMY/HSqILAA= +go.opentelemetry.io/otel/sdk/log v0.4.0/go.mod h1:AYJ9FVF0hNOgAVzUG/ybg/QttnXhUePWAupmCqtdESo= go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08= go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= @@ -1714,8 +1736,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-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= 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= @@ -1740,8 +1762,8 @@ golang.org/x/mod v0.3.0/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.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 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= @@ -2006,8 +2028,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 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.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= +golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= 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/load/ocr/ocr_test.go b/integration-tests/load/ocr/ocr_test.go index 71caa9d222..55b683a0a7 100644 --- a/integration-tests/load/ocr/ocr_test.go +++ b/integration-tests/load/ocr/ocr_test.go @@ -28,12 +28,12 @@ func TestOCRLoad(t *testing.T) { config, err := tc.GetConfig([]string{"Load"}, tc.OCR) require.NoError(t, err) - sethClient, msClient, bootstrapNode, workerNodes, err := crib.ConnectRemote() + sethClient, msClient, bootstrapNode, workerNodes, _, err := crib.ConnectRemote() require.NoError(t, err) - lta, err := actions.SetupOCRv1Cluster(l, sethClient, workerNodes) + lta, err := actions.SetupOCRv1Cluster(l, sethClient, config.OCR, workerNodes) require.NoError(t, err) - ocrInstances, err := actions.SetupOCRv1Feed(l, sethClient, lta, msClient, bootstrapNode, workerNodes) + ocrInstances, err := actions.SetupOCRv1Feed(l, sethClient, lta, config.OCR, msClient, bootstrapNode, workerNodes) require.NoError(t, err) cfg := config.OCR @@ -61,10 +61,10 @@ func TestOCRVolume(t *testing.T) { config, err := tc.GetConfig([]string{"Volume"}, tc.OCR) require.NoError(t, err) - sethClient, msClient, bootstrapNode, workerNodes, err := crib.ConnectRemote() + sethClient, msClient, bootstrapNode, workerNodes, _, err := crib.ConnectRemote() require.NoError(t, err) - lta, err := actions.SetupOCRv1Cluster(l, sethClient, workerNodes) + lta, err := actions.SetupOCRv1Cluster(l, sethClient, config.OCR, workerNodes) require.NoError(t, err) cfg := config.OCR @@ -77,7 +77,7 @@ func TestOCRVolume(t *testing.T) { LoadType: wasp.VU, CallTimeout: cfg.Volume.VerificationTimeout.Duration, Schedule: wasp.Plain(*cfg.Volume.Rate, cfg.Volume.TestDuration.Duration), - VU: NewVU(l, sethClient, *cfg.Volume.VURequestsPerUnit, cfg.Volume.RateLimitUnitDuration.Duration, lta, bootstrapNode, workerNodes, msClient), + VU: NewVU(l, sethClient, cfg, *cfg.Volume.VURequestsPerUnit, cfg.Volume.RateLimitUnitDuration.Duration, lta, bootstrapNode, workerNodes, msClient), Labels: CommonTestLabels, LokiConfig: wasp.NewLokiConfig(cfgl.Endpoint, cfgl.TenantId, cfgl.BasicAuth, cfgl.BearerToken), })) diff --git a/integration-tests/load/ocr/vu.go b/integration-tests/load/ocr/vu.go index aece9cb74b..c337e338a8 100644 --- a/integration-tests/load/ocr/vu.go +++ b/integration-tests/load/ocr/vu.go @@ -8,18 +8,16 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/rs/zerolog" - - "github.com/smartcontractkit/chainlink-testing-framework/seth" - "go.uber.org/ratelimit" - "github.com/smartcontractkit/chainlink-testing-framework/wasp" - client2 "github.com/smartcontractkit/chainlink-testing-framework/lib/client" + "github.com/smartcontractkit/chainlink-testing-framework/seth" + "github.com/smartcontractkit/chainlink-testing-framework/wasp" "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/client" "github.com/smartcontractkit/chainlink/integration-tests/contracts" + "github.com/smartcontractkit/chainlink/integration-tests/testconfig/ocr" ) // VU is a virtual user for the OCR load test @@ -37,11 +35,13 @@ type VU struct { msClient *client2.MockserverClient l zerolog.Logger ocrInstances []contracts.OffchainAggregator + config ocr.OffChainAggregatorsConfig } func NewVU( l zerolog.Logger, seth *seth.Client, + config ocr.OffChainAggregatorsConfig, rate int, rateUnit time.Duration, lta common.Address, @@ -60,6 +60,7 @@ func NewVU( msClient: msClient, bootstrapNode: bootstrapNode, workerNodes: workerNodes, + config: config, } } @@ -75,11 +76,12 @@ func (m *VU) Clone(_ *wasp.Generator) wasp.VirtualUser { msClient: m.msClient, bootstrapNode: m.bootstrapNode, workerNodes: m.workerNodes, + config: m.config, } } func (m *VU) Setup(_ *wasp.Generator) error { - ocrInstances, err := actions.DeployOCRv1Contracts(m.l, m.seth, 1, m.lta, contracts.ChainlinkK8sClientToChainlinkNodeWithKeysAndAddress(m.workerNodes)) + ocrInstances, err := actions.SetupOCRv1Contracts(m.l, m.seth, m.config, m.lta, contracts.ChainlinkK8sClientToChainlinkNodeWithKeysAndAddress(m.workerNodes)) if err != nil { return err } diff --git a/integration-tests/load/vrfv2/vrfv2_test.go b/integration-tests/load/vrfv2/vrfv2_test.go index 49bd2dd9c0..b59093a8ea 100644 --- a/integration-tests/load/vrfv2/vrfv2_test.go +++ b/integration-tests/load/vrfv2/vrfv2_test.go @@ -17,7 +17,6 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/lib/networks" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/ptr" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" - "github.com/smartcontractkit/chainlink/integration-tests/actions" vrfcommon "github.com/smartcontractkit/chainlink/integration-tests/actions/vrf/common" "github.com/smartcontractkit/chainlink/integration-tests/actions/vrf/vrfv2" @@ -72,9 +71,9 @@ func TestVRFV2Performance(t *testing.T) { Uint16("RandomnessRequestCountPerRequestDeviation", *vrfv2Config.General.RandomnessRequestCountPerRequestDeviation). Bool("UseExistingEnv", *vrfv2Config.General.UseExistingEnv). Msg("Performance Test Configuration") + cleanupFn := func() { teardown(t, vrfContracts.VRFV2Consumers[0], lc, updatedLabels, testReporter, testType, &testConfig) - require.NoError(t, err, "Getting Seth client shouldn't fail") if sethClient.Cfg.IsSimulatedNetwork() { l.Info(). @@ -82,6 +81,8 @@ func TestVRFV2Performance(t *testing.T) { Msg("Network is a simulated network. Skipping fund return for Coordinator Subscriptions.") } else { if *vrfv2Config.General.CancelSubsAfterTestRun { + // wait for all txs to be mined in order to avoid nonce issues + time.Sleep(10 * time.Second) //cancel subs and return funds to sub owner vrfv2.CancelSubsAndReturnFunds(testcontext.Get(t), vrfContracts, sethClient.MustGetRootKeyAddress().Hex(), subIDsForCancellingAfterTest, l) } @@ -307,7 +308,7 @@ func TestVRFV2BHSPerformance(t *testing.T) { wgBlockNumberTobe.Add(1) //Wait at least 256 blocks latestBlockNumber, err := sethClient.Client.BlockNumber(testcontext.Get(t)) - require.NoError(t, err) + require.NoError(t, err, "error getting latest block number") _, err = actions.WaitForBlockNumberToBe( testcontext.Get(t), latestBlockNumber+uint64(257), diff --git a/integration-tests/load/vrfv2plus/vrfv2plus_test.go b/integration-tests/load/vrfv2plus/vrfv2plus_test.go index bcefe510a6..29111f434c 100644 --- a/integration-tests/load/vrfv2plus/vrfv2plus_test.go +++ b/integration-tests/load/vrfv2plus/vrfv2plus_test.go @@ -80,6 +80,8 @@ func TestVRFV2PlusPerformance(t *testing.T) { Msg("Network is a simulated network. Skipping fund return for Coordinator Subscriptions.") } else { if *testConfig.VRFv2Plus.General.CancelSubsAfterTestRun { + // wait for all txs to be mined in order to avoid nonce issues + time.Sleep(10 * time.Second) //cancel subs and return funds to sub owner vrfv2plus.CancelSubsAndReturnFunds(testcontext.Get(t), vrfContracts, sethClient.MustGetRootKeyAddress().Hex(), subIDsForCancellingAfterTest, l) } diff --git a/integration-tests/reorg/automation_reorg_test.go b/integration-tests/reorg/automation_reorg_test.go index 22cfaa7ca6..fde37d9f06 100644 --- a/integration-tests/reorg/automation_reorg_test.go +++ b/integration-tests/reorg/automation_reorg_test.go @@ -10,10 +10,8 @@ import ( "strings" "testing" - ocr3 "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3confighelper" "go.uber.org/zap/zapcore" - ocr2keepers30config "github.com/smartcontractkit/chainlink-automation/pkg/v3/config" "github.com/smartcontractkit/chainlink-testing-framework/lib/testreporters" sethUtils "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/seth" "github.com/smartcontractkit/chainlink/integration-tests/actions/automationv2" @@ -43,7 +41,7 @@ var ( ) var logScannerSettings = test_env.GetDefaultChainlinkNodeLogScannerSettingsWithExtraAllowedMessages(testreporters.NewAllowedLogMessage( - "Got very old block with number", + "Got very old block.", "It is expected, because we are causing reorgs", zapcore.DPanicLevel, testreporters.WarnAboutAllowedMsgs_No, @@ -134,43 +132,17 @@ func TestAutomationReorg(t *testing.T) { gethRPCClient := ctfClient.NewRPCClient(evmNetwork.HTTPURLs[0], nil) - registryConfig := actions.AutomationDefaultRegistryConfig(config) - registryConfig.RegistryVersion = registryVersion - - a := automationv2.NewAutomationTestDocker(l, sethClient, nodeClients) + a := automationv2.NewAutomationTestDocker(l, sethClient, nodeClients, &config) a.SetMercuryCredentialName("cred1") - a.RegistrySettings = registryConfig + a.RegistrySettings = actions.ReadRegistryConfig(config) + a.RegistrySettings.RegistryVersion = registryVersion + a.PluginConfig = actions.ReadPluginConfig(config) + a.PublicConfig = actions.ReadPublicConfig(config) a.RegistrarSettings = contracts.KeeperRegistrarSettings{ AutoApproveConfigType: uint8(2), AutoApproveMaxAllowed: 1000, MinLinkJuels: big.NewInt(0), } - plCfg := config.GetAutomationConfig().AutomationConfig.PluginConfig - a.PluginConfig = ocr2keepers30config.OffchainConfig{ - TargetProbability: *plCfg.TargetProbability, - TargetInRounds: *plCfg.TargetInRounds, - PerformLockoutWindow: *plCfg.PerformLockoutWindow, - GasLimitPerReport: *plCfg.GasLimitPerReport, - GasOverheadPerUpkeep: *plCfg.GasOverheadPerUpkeep, - MinConfirmations: *plCfg.MinConfirmations, - MaxUpkeepBatchSize: *plCfg.MaxUpkeepBatchSize, - } - pubCfg := config.GetAutomationConfig().AutomationConfig.PublicConfig - a.PublicConfig = ocr3.PublicConfig{ - DeltaProgress: *pubCfg.DeltaProgress, - DeltaResend: *pubCfg.DeltaResend, - DeltaInitial: *pubCfg.DeltaInitial, - DeltaRound: *pubCfg.DeltaRound, - DeltaGrace: *pubCfg.DeltaGrace, - DeltaCertifiedCommitRequest: *pubCfg.DeltaCertifiedCommitRequest, - DeltaStage: *pubCfg.DeltaStage, - RMax: *pubCfg.RMax, - MaxDurationQuery: *pubCfg.MaxDurationQuery, - MaxDurationObservation: *pubCfg.MaxDurationObservation, - MaxDurationShouldAcceptAttestedReport: *pubCfg.MaxDurationShouldAcceptAttestedReport, - MaxDurationShouldTransmitAcceptedReport: *pubCfg.MaxDurationShouldTransmitAcceptedReport, - F: *pubCfg.F, - } a.SetupAutomationDeployment(t) a.SetDockerEnv(env) @@ -197,6 +169,7 @@ func TestAutomationReorg(t *testing.T) { false, false, a.WETHToken, + &config, ) if isLogTrigger { diff --git a/integration-tests/scripts/buildTests b/integration-tests/scripts/buildTests index b6033c1c41..7be2d0c5a7 100755 --- a/integration-tests/scripts/buildTests +++ b/integration-tests/scripts/buildTests @@ -8,7 +8,7 @@ set -ex # get this scripts directory SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) -cd "$SCRIPT_DIR"/../ || exit 1 +cd "$SCRIPT_DIR"/../ || { echo "Error: Failed to change directory to $SCRIPT_DIR/../"; exit 1; } helm repo update @@ -23,10 +23,16 @@ for x in $tosplit do if [ "$x" = "load" ]; then echo "Changing directory and executing go test -c ./... for 'load' package" - pushd "./load" && go test -c -tags embed -o .. ./... + pushd "./load" && go test -c -tags embed -o ../ ./... + popd + elif [ "$x" = "ccip-load" ]; then + echo "Changing directory and executing go test -c ./... for 'ccip-load' package" + pushd "./ccip-tests/load" && go test -c -tags embed -o ../ ./... + mv ../load.test ../ccip-load.test # rename the binary to match the suite name popd else go test -c -tags embed ./"${x}" fi + echo "Built ${x}.test" done IFS=$OIFS diff --git a/integration-tests/scripts/entrypoint b/integration-tests/scripts/entrypoint index d4ebe722a1..dc4ff1d0db 100755 --- a/integration-tests/scripts/entrypoint +++ b/integration-tests/scripts/entrypoint @@ -14,6 +14,11 @@ cd "$SCRIPT_DIR"/../ || exit 1 # SUITE=${SUITE:=} the suite of tests you want to run # TEST_NAME=${TEST_NAME:=} The specific test to run +if [ -z "${SUITE}" ]; then + echo "SUITE is not set. You likely need to set the TEST_SUITE env var in your workflow. Exiting." + exit 1 +fi + # run the tests ./${SUITE}.test -test.v -test.count 1 ${ARGS} -test.run ^${TEST_NAME}$ diff --git a/integration-tests/smoke/README.md b/integration-tests/smoke/README.md index 266720c7bc..c4aa2b91a1 100644 --- a/integration-tests/smoke/README.md +++ b/integration-tests/smoke/README.md @@ -75,9 +75,3 @@ Then execute: go test -v -run ${TestName} ``` - - -### Debugging CL client API calls -```bash -export CL_CLIENT_DEBUG=true -``` \ No newline at end of file diff --git a/integration-tests/smoke/automation_test.go b/integration-tests/smoke/automation_test.go index 92cbc95a9d..2e56237e9c 100644 --- a/integration-tests/smoke/automation_test.go +++ b/integration-tests/smoke/automation_test.go @@ -12,7 +12,7 @@ import ( "testing" "time" - seth_utils "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/seth" + "github.com/smartcontractkit/chainlink/integration-tests/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -20,10 +20,7 @@ import ( "github.com/onsi/gomega" "github.com/stretchr/testify/require" - ocr3 "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3confighelper" - - ocr2keepers30config "github.com/smartcontractkit/chainlink-automation/pkg/v3/config" - ctfTestEnv "github.com/smartcontractkit/chainlink-testing-framework/lib/docker/test_env" + ctftestenv "github.com/smartcontractkit/chainlink-testing-framework/lib/docker/test_env" "github.com/smartcontractkit/chainlink-testing-framework/lib/logging" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" @@ -126,7 +123,7 @@ func SetupAutomationBasic(t *testing.T, nodeUpgrade bool) { isMercury := isMercuryV02 || isMercuryV03 a := setupAutomationTestDocker( - t, registryVersion, actions.AutomationDefaultRegistryConfig(cfg), isMercuryV02, isMercuryV03, &cfg, + t, registryVersion, actions.ReadRegistryConfig(cfg), isMercuryV02, isMercuryV03, &cfg, ) sb, err := a.ChainClient.Client.BlockNumber(context.Background()) @@ -145,6 +142,7 @@ func SetupAutomationBasic(t *testing.T, nodeUpgrade bool) { isMercury, isBillingTokenNative, a.WETHToken, + &cfg, ) // Do it in two separate loops, so we don't end up setting up one upkeep, but starting the consumer for another one @@ -265,26 +263,13 @@ func TestSetUpkeepTriggerConfig(t *testing.T) { require.NoError(t, err, "Failed to get config") a := setupAutomationTestDocker( - t, registryVersion, actions.AutomationDefaultRegistryConfig(config), false, false, &config, + t, registryVersion, actions.ReadRegistryConfig(config), false, false, &config, ) sb, err := a.ChainClient.Client.BlockNumber(context.Background()) require.NoError(t, err, "Failed to get start block") - consumers, upkeepIDs := actions.DeployConsumers( - t, - a.ChainClient, - a.Registry, - a.Registrar, - a.LinkToken, - defaultAmountOfUpkeeps, - big.NewInt(automationDefaultLinkFunds), - automationDefaultUpkeepGasLimit, - true, - false, - false, - nil, - ) + consumers, upkeepIDs := actions.DeployConsumers(t, a.ChainClient, a.Registry, a.Registrar, a.LinkToken, defaultAmountOfUpkeeps, big.NewInt(automationDefaultLinkFunds), automationDefaultUpkeepGasLimit, true, false, false, nil, &config) // Start log trigger based upkeeps for all consumers for i := 0; i < len(consumers); i++ { @@ -448,26 +433,13 @@ func TestAutomationAddFunds(t *testing.T) { config, err := tc.GetConfig([]string{"Smoke"}, tc.Automation) require.NoError(t, err, "Failed to get config") a := setupAutomationTestDocker( - t, registryVersion, actions.AutomationDefaultRegistryConfig(config), false, false, &config, + t, registryVersion, actions.ReadRegistryConfig(config), false, false, &config, ) sb, err := a.ChainClient.Client.BlockNumber(context.Background()) require.NoError(t, err, "Failed to get start block") - consumers, upkeepIDs := actions.DeployConsumers( - t, - a.ChainClient, - a.Registry, - a.Registrar, - a.LinkToken, - defaultAmountOfUpkeeps, - big.NewInt(1), - automationDefaultUpkeepGasLimit, - false, - false, - false, - nil, - ) + consumers, upkeepIDs := actions.DeployConsumers(t, a.ChainClient, a.Registry, a.Registrar, a.LinkToken, defaultAmountOfUpkeeps, big.NewInt(1), automationDefaultUpkeepGasLimit, false, false, false, nil, &config) t.Cleanup(func() { actions.GetStalenessReportCleanupFn(t, a.Logger, a.ChainClient, sb, a.Registry, registryVersion)() @@ -528,26 +500,13 @@ func TestAutomationPauseUnPause(t *testing.T) { require.NoError(t, err, "Failed to get config") a := setupAutomationTestDocker( - t, registryVersion, actions.AutomationDefaultRegistryConfig(config), false, false, &config, + t, registryVersion, actions.ReadRegistryConfig(config), false, false, &config, ) sb, err := a.ChainClient.Client.BlockNumber(context.Background()) require.NoError(t, err, "Failed to get start block") - consumers, upkeepIDs := actions.DeployConsumers( - t, - a.ChainClient, - a.Registry, - a.Registrar, - a.LinkToken, - defaultAmountOfUpkeeps, - big.NewInt(automationDefaultLinkFunds), - automationDefaultUpkeepGasLimit, - false, - false, - false, - nil, - ) + consumers, upkeepIDs := actions.DeployConsumers(t, a.ChainClient, a.Registry, a.Registrar, a.LinkToken, defaultAmountOfUpkeeps, big.NewInt(automationDefaultLinkFunds), automationDefaultUpkeepGasLimit, false, false, false, nil, &config) t.Cleanup(func() { actions.GetStalenessReportCleanupFn(t, a.Logger, a.ChainClient, sb, a.Registry, registryVersion)() @@ -629,26 +588,13 @@ func TestAutomationRegisterUpkeep(t *testing.T) { require.NoError(t, err, "Failed to get config") a := setupAutomationTestDocker( - t, registryVersion, actions.AutomationDefaultRegistryConfig(config), false, false, &config, + t, registryVersion, actions.ReadRegistryConfig(config), false, false, &config, ) sb, err := a.ChainClient.Client.BlockNumber(context.Background()) require.NoError(t, err, "Failed to get start block") - consumers, upkeepIDs := actions.DeployConsumers( - t, - a.ChainClient, - a.Registry, - a.Registrar, - a.LinkToken, - defaultAmountOfUpkeeps, - big.NewInt(automationDefaultLinkFunds), - automationDefaultUpkeepGasLimit, - false, - false, - false, - nil, - ) + consumers, upkeepIDs := actions.DeployConsumers(t, a.ChainClient, a.Registry, a.Registrar, a.LinkToken, defaultAmountOfUpkeeps, big.NewInt(automationDefaultLinkFunds), automationDefaultUpkeepGasLimit, false, false, false, nil, &config) t.Cleanup(func() { actions.GetStalenessReportCleanupFn(t, a.Logger, a.ChainClient, sb, a.Registry, registryVersion)() @@ -725,26 +671,13 @@ func TestAutomationPauseRegistry(t *testing.T) { require.NoError(t, err, "Failed to get config") a := setupAutomationTestDocker( - t, registryVersion, actions.AutomationDefaultRegistryConfig(config), false, false, &config, + t, registryVersion, actions.ReadRegistryConfig(config), false, false, &config, ) sb, err := a.ChainClient.Client.BlockNumber(context.Background()) require.NoError(t, err, "Failed to get start block") - consumers, upkeepIDs := actions.DeployConsumers( - t, - a.ChainClient, - a.Registry, - a.Registrar, - a.LinkToken, - defaultAmountOfUpkeeps, - big.NewInt(automationDefaultLinkFunds), - automationDefaultUpkeepGasLimit, - false, - false, - false, - nil, - ) + consumers, upkeepIDs := actions.DeployConsumers(t, a.ChainClient, a.Registry, a.Registrar, a.LinkToken, defaultAmountOfUpkeeps, big.NewInt(automationDefaultLinkFunds), automationDefaultUpkeepGasLimit, false, false, false, nil, &config) t.Cleanup(func() { actions.GetStalenessReportCleanupFn(t, a.Logger, a.ChainClient, sb, a.Registry, registryVersion)() @@ -805,26 +738,13 @@ func TestAutomationKeeperNodesDown(t *testing.T) { require.NoError(t, err, "Failed to get config") a := setupAutomationTestDocker( - t, registryVersion, actions.AutomationDefaultRegistryConfig(config), false, false, &config, + t, registryVersion, actions.ReadRegistryConfig(config), false, false, &config, ) sb, err := a.ChainClient.Client.BlockNumber(context.Background()) require.NoError(t, err, "Failed to get start block") - consumers, upkeepIDs := actions.DeployConsumers( - t, - a.ChainClient, - a.Registry, - a.Registrar, - a.LinkToken, - defaultAmountOfUpkeeps, - big.NewInt(automationDefaultLinkFunds), - automationDefaultUpkeepGasLimit, - false, - false, - false, - nil, - ) + consumers, upkeepIDs := actions.DeployConsumers(t, a.ChainClient, a.Registry, a.Registrar, a.LinkToken, defaultAmountOfUpkeeps, big.NewInt(automationDefaultLinkFunds), automationDefaultUpkeepGasLimit, false, false, false, nil, &config) t.Cleanup(func() { actions.GetStalenessReportCleanupFn(t, a.Logger, a.ChainClient, sb, a.Registry, registryVersion)() @@ -913,7 +833,7 @@ func TestAutomationPerformSimulation(t *testing.T) { require.NoError(t, err, "Failed to get config") a := setupAutomationTestDocker( - t, registryVersion, actions.AutomationDefaultRegistryConfig(config), false, false, &config, + t, registryVersion, actions.ReadRegistryConfig(config), false, false, &config, ) sb, err := a.ChainClient.Client.BlockNumber(context.Background()) @@ -932,6 +852,7 @@ func TestAutomationPerformSimulation(t *testing.T) { 5, // Interval of blocks that upkeeps are expected to be performed 100000, // How much gas should be burned on checkUpkeep() calls 4000000, // How much gas should be burned on performUpkeep() calls. Initially set higher than defaultUpkeepGasLimit + &config, ) t.Cleanup(func() { @@ -984,7 +905,7 @@ func TestAutomationCheckPerformGasLimit(t *testing.T) { config, err := tc.GetConfig([]string{"Smoke"}, tc.Automation) require.NoError(t, err, "Failed to get config") a := setupAutomationTestDocker( - t, registryVersion, actions.AutomationDefaultRegistryConfig(config), false, false, &config, + t, registryVersion, actions.ReadRegistryConfig(config), false, false, &config, ) sb, err := a.ChainClient.Client.BlockNumber(context.Background()) @@ -1003,6 +924,7 @@ func TestAutomationCheckPerformGasLimit(t *testing.T) { 5, // Interval of blocks that upkeeps are expected to be performed 100000, // How much gas should be burned on checkUpkeep() calls 4000000, // How much gas should be burned on performUpkeep() calls. Initially set higher than defaultUpkeepGasLimit + &config, ) t.Cleanup(func() { @@ -1091,7 +1013,7 @@ func TestAutomationCheckPerformGasLimit(t *testing.T) { } // Now increase checkGasLimit on registry - highCheckGasLimit := actions.AutomationDefaultRegistryConfig(config) + highCheckGasLimit := actions.ReadRegistryConfig(config) highCheckGasLimit.CheckGasLimit = uint32(5000000) highCheckGasLimit.RegistryVersion = registryVersion @@ -1139,7 +1061,7 @@ func TestUpdateCheckData(t *testing.T) { require.NoError(t, err, "Failed to get config") a := setupAutomationTestDocker( - t, registryVersion, actions.AutomationDefaultRegistryConfig(config), false, false, &config, + t, registryVersion, actions.ReadRegistryConfig(config), false, false, &config, ) sb, err := a.ChainClient.Client.BlockNumber(context.Background()) @@ -1155,6 +1077,7 @@ func TestUpdateCheckData(t *testing.T) { big.NewInt(automationDefaultLinkFunds), automationDefaultUpkeepGasLimit, []byte(automationExpectedData), + &config, ) t.Cleanup(func() { @@ -1220,26 +1143,13 @@ func TestSetOffchainConfigWithMaxGasPrice(t *testing.T) { t.Fatal(err) } a := setupAutomationTestDocker( - t, registryVersion, actions.AutomationDefaultRegistryConfig(config), false, false, &config, + t, registryVersion, actions.ReadRegistryConfig(config), false, false, &config, ) sb, err := a.ChainClient.Client.BlockNumber(context.Background()) require.NoError(t, err, "Failed to get start block") - consumers, upkeepIDs := actions.DeployConsumers( - t, - a.ChainClient, - a.Registry, - a.Registrar, - a.LinkToken, - defaultAmountOfUpkeeps, - big.NewInt(automationDefaultLinkFunds), - automationDefaultUpkeepGasLimit, - false, - false, - false, - nil, - ) + consumers, upkeepIDs := actions.DeployConsumers(t, a.ChainClient, a.Registry, a.Registrar, a.LinkToken, defaultAmountOfUpkeeps, big.NewInt(automationDefaultLinkFunds), automationDefaultUpkeepGasLimit, false, false, false, nil, &config) t.Cleanup(func() { actions.GetStalenessReportCleanupFn(t, a.Logger, a.ChainClient, sb, a.Registry, registryVersion)() @@ -1427,7 +1337,7 @@ func setupAutomationTestDocker( evmNetwork, err := env.GetFirstEvmNetwork() require.NoError(t, err, "Error getting first evm network") - sethClient, err := seth_utils.GetChainClient(automationTestConfig, *evmNetwork) + sethClient, err := utils.TestAwareSethClient(t, automationTestConfig, evmNetwork) require.NoError(t, err, "Error getting seth client") err = actions.FundChainlinkNodesFromRootAddress(l, sethClient, contracts.ChainlinkClientToChainlinkNodeWithKeysAndAddress(env.ClCluster.NodeAPIs()), big.NewFloat(*automationTestConfig.GetCommonConfig().ChainlinkNodeFunding)) @@ -1438,7 +1348,7 @@ func setupAutomationTestDocker( _ = actions.ReturnFundsFromNodes(l, sethClient, contracts.ChainlinkClientToChainlinkNodeWithKeysAndAddress(env.ClCluster.NodeAPIs())) }) - a := automationv2.NewAutomationTestDocker(l, sethClient, nodeClients) + a := automationv2.NewAutomationTestDocker(l, sethClient, nodeClients, automationTestConfig) a.SetMercuryCredentialName("cred1") a.RegistrySettings = registryConfig a.RegistrarSettings = contracts.KeeperRegistrarSettings{ @@ -1446,47 +1356,23 @@ func setupAutomationTestDocker( AutoApproveMaxAllowed: 1000, MinLinkJuels: big.NewInt(0), } - plCfg := automationTestConfig.GetAutomationConfig().AutomationConfig.PluginConfig - a.PluginConfig = ocr2keepers30config.OffchainConfig{ - TargetProbability: *plCfg.TargetProbability, - TargetInRounds: *plCfg.TargetInRounds, - PerformLockoutWindow: *plCfg.PerformLockoutWindow, - GasLimitPerReport: *plCfg.GasLimitPerReport, - GasOverheadPerUpkeep: *plCfg.GasOverheadPerUpkeep, - MinConfirmations: *plCfg.MinConfirmations, - MaxUpkeepBatchSize: *plCfg.MaxUpkeepBatchSize, - } - pubCfg := automationTestConfig.GetAutomationConfig().AutomationConfig.PublicConfig - a.PublicConfig = ocr3.PublicConfig{ - DeltaProgress: *pubCfg.DeltaProgress, - DeltaResend: *pubCfg.DeltaResend, - DeltaInitial: *pubCfg.DeltaInitial, - DeltaRound: *pubCfg.DeltaRound, - DeltaGrace: *pubCfg.DeltaGrace, - DeltaCertifiedCommitRequest: *pubCfg.DeltaCertifiedCommitRequest, - DeltaStage: *pubCfg.DeltaStage, - RMax: *pubCfg.RMax, - MaxDurationQuery: *pubCfg.MaxDurationQuery, - MaxDurationObservation: *pubCfg.MaxDurationObservation, - MaxDurationShouldAcceptAttestedReport: *pubCfg.MaxDurationShouldAcceptAttestedReport, - MaxDurationShouldTransmitAcceptedReport: *pubCfg.MaxDurationShouldTransmitAcceptedReport, - F: *pubCfg.F, - } + a.PluginConfig = actions.ReadPluginConfig(automationTestConfig) + a.PublicConfig = actions.ReadPublicConfig(automationTestConfig) a.SetupAutomationDeployment(t) a.SetDockerEnv(env) if isMercuryV02 || isMercuryV03 { - var imposters []ctfTestEnv.KillgraveImposter - mercuryv03Mock200 := ctfTestEnv.KillgraveImposter{ - Request: ctfTestEnv.KillgraveRequest{ + var imposters []ctftestenv.KillgraveImposter + mercuryv03Mock200 := ctftestenv.KillgraveImposter{ + Request: ctftestenv.KillgraveRequest{ Method: http.MethodGet, Endpoint: "/api/v1/reports/bulk", SchemaFile: nil, Params: &map[string]string{"feedIDs": "0x00028c915d6af0fd66bba2d0fc9405226bca8d6806333121a7d9832103d1563c", "timestamp": "{[\\d+]}"}, Headers: nil, }, - Response: ctfTestEnv.KillgraveResponse{ + Response: ctftestenv.KillgraveResponse{ Status: 200, Body: `{"reports":[{"feedID":"0x00028c915d6af0fd66bba2d0fc9405226bca8d6806333121a7d9832103d1563c","validFromTimestamp":0,"observationsTimestamp":0,"fullReport":"0x00066dfcd1ed2d95b18c948dbc5bd64c687afe93e4ca7d663ddec14c20090ad80000000000000000000000000000000000000000000000000000000000081401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001204554482d5553442d415242495452554d2d544553544e455400000000000000000000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000289ad8d367000000000000000000000000000000000000000000000000000000289acf0b38000000000000000000000000000000000000000000000000000000289b3da40000000000000000000000000000000000000000000000000000000000018ae7ce74d9fa252a8983976eab600dc7590c778d04813430841bc6e765c34cd81a168d00000000000000000000000000000000000000000000000000000000018ae7cb0000000000000000000000000000000000000000000000000000000064891c98000000000000000000000000000000000000000000000000000000000000000260412b94e525ca6cedc9f544fd86f77606d52fe731a5d069dbe836a8bfc0fb8c911963b0ae7a14971f3b4621bffb802ef0605392b9a6c89c7fab1df8633a5ade00000000000000000000000000000000000000000000000000000000000000024500c2f521f83fba5efc2bf3effaaedde43d0a4adff785c1213b712a3aed0d8157642a84324db0cf9695ebd27708d4608eb0337e0dd87b0e43f0fa70c700d911"}]}`, BodyFile: nil, @@ -1495,15 +1381,15 @@ func setupAutomationTestDocker( }, } - mercuryv02Mock200 := ctfTestEnv.KillgraveImposter{ - Request: ctfTestEnv.KillgraveRequest{ + mercuryv02Mock200 := ctftestenv.KillgraveImposter{ + Request: ctftestenv.KillgraveRequest{ Method: http.MethodGet, Endpoint: "/client", SchemaFile: nil, Params: &map[string]string{"feedIdHex": "{0x00028c915d6af0fd66bba2d0fc9405226bca8d6806333121a7d9832103d1563c|0x4554482d5553442d415242495452554d2d544553544e45540000000000000000}", "blockNumber": "{[\\d+]}"}, Headers: nil, }, - Response: ctfTestEnv.KillgraveResponse{ + Response: ctftestenv.KillgraveResponse{ Status: 200, Body: `{"chainlinkBlob":"0x0001c38d71fed6c320b90e84b6f559459814d068e2a1700adc931ca9717d4fe70000000000000000000000000000000000000000000000000000000001a80b52b4bf1233f9cb71144a253a1791b202113c4ab4a92fa1b176d684b4959666ff8200000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001004254432d5553442d415242495452554d2d544553544e4554000000000000000000000000000000000000000000000000000000000000000000000000645570be000000000000000000000000000000000000000000000000000002af2b818dc5000000000000000000000000000000000000000000000000000002af2426faf3000000000000000000000000000000000000000000000000000002af32dc209700000000000000000000000000000000000000000000000000000000012130f8df0a9745bb6ad5e2df605e158ba8ad8a33ef8a0acf9851f0f01668a3a3f2b68600000000000000000000000000000000000000000000000000000000012130f60000000000000000000000000000000000000000000000000000000000000002c4a7958dce105089cf5edb68dad7dcfe8618d7784eb397f97d5a5fade78c11a58275aebda478968e545f7e3657aba9dcbe8d44605e4c6fde3e24edd5e22c94270000000000000000000000000000000000000000000000000000000000000002459c12d33986018a8959566d145225f0c4a4e61a9a3f50361ccff397899314f0018162cf10cd89897635a0bb62a822355bd199d09f4abe76e4d05261bb44733d"}`, BodyFile: nil, diff --git a/integration-tests/smoke/flux_test.go b/integration-tests/smoke/flux_test.go index 6f007166b1..259752a534 100644 --- a/integration-tests/smoke/flux_test.go +++ b/integration-tests/smoke/flux_test.go @@ -8,12 +8,13 @@ import ( "testing" "time" + "github.com/smartcontractkit/chainlink/integration-tests/utils" + "github.com/ethereum/go-ethereum/common" "github.com/google/uuid" "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-testing-framework/lib/logging" - seth_utils "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/seth" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" "github.com/smartcontractkit/chainlink/integration-tests/actions" @@ -49,7 +50,7 @@ func TestFluxBasic(t *testing.T) { evmNetwork, err := env.GetFirstEvmNetwork() require.NoError(t, err, "Error getting first evm network") - sethClient, err := seth_utils.GetChainClient(config, *evmNetwork) + sethClient, err := utils.TestAwareSethClient(t, config, evmNetwork) require.NoError(t, err, "Error getting seth client") adapterUUID := uuid.NewString() diff --git a/integration-tests/smoke/forwarder_ocr_test.go b/integration-tests/smoke/forwarder_ocr_test.go index e7c550e1d3..5e5fb1d40b 100644 --- a/integration-tests/smoke/forwarder_ocr_test.go +++ b/integration-tests/smoke/forwarder_ocr_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - seth_utils "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/seth" + "github.com/smartcontractkit/chainlink/integration-tests/utils" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" @@ -49,7 +49,7 @@ func TestForwarderOCRBasic(t *testing.T) { evmNetwork, err := env.GetFirstEvmNetwork() require.NoError(t, err, "Error getting first evm network") - sethClient, err := seth_utils.GetChainClient(config, *evmNetwork) + sethClient, err := utils.TestAwareSethClient(t, config, evmNetwork) require.NoError(t, err, "Error getting seth client") err = actions.FundChainlinkNodesFromRootAddress(l, sethClient, contracts.ChainlinkClientToChainlinkNodeWithKeysAndAddress(env.ClCluster.NodeAPIs()), big.NewFloat(*config.Common.ChainlinkNodeFunding)) @@ -60,8 +60,8 @@ func TestForwarderOCRBasic(t *testing.T) { _ = actions.ReturnFundsFromNodes(l, sethClient, contracts.ChainlinkClientToChainlinkNodeWithKeysAndAddress(env.ClCluster.NodeAPIs())) }) - lt, err := contracts.DeployLinkTokenContract(l, sethClient) - require.NoError(t, err, "Deploying Link Token Contract shouldn't fail") + linkContract, err := actions.LinkTokenContract(l, sethClient, config.OCR) + require.NoError(t, err, "Error loading/deploying link token contract") fundingAmount := big.NewFloat(.05) l.Info().Str("ETH amount per node", fundingAmount.String()).Msg("Funding Chainlink nodes") @@ -69,7 +69,7 @@ func TestForwarderOCRBasic(t *testing.T) { require.NoError(t, err, "Error funding Chainlink nodes") operators, authorizedForwarders, _ := actions.DeployForwarderContracts( - t, sethClient, common.HexToAddress(lt.Address()), len(workerNodes), + t, sethClient, common.HexToAddress(linkContract.Address()), len(workerNodes), ) require.Equal(t, len(workerNodes), len(operators), "Number of operators should match number of worker nodes") @@ -81,11 +81,12 @@ func TestForwarderOCRBasic(t *testing.T) { require.NoError(t, err, "Accepting Authorize Receivers on Operator shouldn't fail") actions.TrackForwarder(t, sethClient, authorizedForwarders[i], workerNodes[i]) } + ocrInstances, err := actions.DeployOCRContractsForwarderFlow( l, sethClient, - 1, - common.HexToAddress(lt.Address()), + config.OCR, + common.HexToAddress(linkContract.Address()), contracts.ChainlinkClientToChainlinkNodeWithKeysAndAddress(workerNodes), authorizedForwarders, ) diff --git a/integration-tests/smoke/forwarders_ocr2_test.go b/integration-tests/smoke/forwarders_ocr2_test.go index 58de6d7b11..0cc7d9fafe 100644 --- a/integration-tests/smoke/forwarders_ocr2_test.go +++ b/integration-tests/smoke/forwarders_ocr2_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - seth_utils "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/seth" + "github.com/smartcontractkit/chainlink/integration-tests/utils" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" @@ -50,7 +50,7 @@ func TestForwarderOCR2Basic(t *testing.T) { evmNetwork, err := env.GetFirstEvmNetwork() require.NoError(t, err, "Error getting first evm network") - sethClient, err := seth_utils.GetChainClient(config, *evmNetwork) + sethClient, err := utils.TestAwareSethClient(t, config, evmNetwork) require.NoError(t, err, "Error getting seth client") err = actions.FundChainlinkNodesFromRootAddress(l, sethClient, contracts.ChainlinkClientToChainlinkNodeWithKeysAndAddress(env.ClCluster.NodeAPIs()), big.NewFloat(*config.Common.ChainlinkNodeFunding)) @@ -61,8 +61,8 @@ func TestForwarderOCR2Basic(t *testing.T) { _ = actions.ReturnFundsFromNodes(l, sethClient, contracts.ChainlinkClientToChainlinkNodeWithKeysAndAddress(env.ClCluster.NodeAPIs())) }) - lt, err := contracts.DeployLinkTokenContract(l, sethClient) - require.NoError(t, err, "Deploying Link Token Contract shouldn't fail") + linkContract, err := actions.LinkTokenContract(l, sethClient, config.OCR2) + require.NoError(t, err, "Error loading/deploying link token contract") fundingAmount := big.NewFloat(.05) l.Info().Str("ETH amount per node", fundingAmount.String()).Msg("Funding Chainlink nodes") @@ -70,7 +70,7 @@ func TestForwarderOCR2Basic(t *testing.T) { require.NoError(t, err, "Error funding Chainlink nodes") operators, authorizedForwarders, _ := actions.DeployForwarderContracts( - t, sethClient, common.HexToAddress(lt.Address()), len(workerNodes), + t, sethClient, common.HexToAddress(linkContract.Address()), len(workerNodes), ) require.Equal(t, len(workerNodes), len(operators), "Number of operators should match number of worker nodes") @@ -90,7 +90,7 @@ func TestForwarderOCR2Basic(t *testing.T) { } ocrOffchainOptions := contracts.DefaultOffChainAggregatorOptions() - ocrInstances, err := actions.DeployOCRv2Contracts(l, sethClient, 1, common.HexToAddress(lt.Address()), transmitters, ocrOffchainOptions) + ocrInstances, err := actions.SetupOCRv2Contracts(l, sethClient, config.OCR2, common.HexToAddress(linkContract.Address()), transmitters, ocrOffchainOptions) require.NoError(t, err, "Error deploying OCRv2 contracts with forwarders") ocrv2Config, err := actions.BuildMedianOCR2ConfigLocal(workerNodes, ocrOffchainOptions) diff --git a/integration-tests/smoke/job_distributor_test.go b/integration-tests/smoke/job_distributor_test.go new file mode 100644 index 0000000000..2d1657faf7 --- /dev/null +++ b/integration-tests/smoke/job_distributor_test.go @@ -0,0 +1,60 @@ +package smoke + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-testing-framework/lib/logging" + + "github.com/smartcontractkit/chainlink/integration-tests/actions" + "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" + tc "github.com/smartcontractkit/chainlink/integration-tests/testconfig" + graphqlClient "github.com/smartcontractkit/chainlink/integration-tests/web/sdk/client" +) + +func TestRegisteringMultipleJobDistributor(t *testing.T) { + t.Parallel() + + l := logging.GetTestLogger(t) + + config, err := tc.GetConfig([]string{"Smoke"}, "job_distributor") + require.NoError(t, err, "Error getting config") + + privateNetwork, err := actions.EthereumNetworkConfigFromConfig(l, &config) + require.NoError(t, err, "Error building ethereum network config") + + env, err := test_env.NewCLTestEnvBuilder(). + WithTestConfig(&config). + WithTestInstance(t). + WithStandardCleanup(). + WithPrivateEthereumNetwork(privateNetwork.EthereumNetworkConfig). + WithCLNodes(1). + WithStandardCleanup(). + Build() + require.NoError(t, err) + + ctx := context.Background() + _, err = env.ClCluster.Nodes[0].GraphqlAPI.CreateJobDistributor(ctx, graphqlClient.JobDistributorInput{ + Name: "job-distributor-1", + Uri: "http://job-distributor-1:8080", + PublicKey: "54227538d9352e0a24550a80ab6a7af6e4f1ffbb8a604e913cbb81c484a7f97d", + }) + require.NoError(t, err, "Creating first job distributor in chainlink node shouldn't fail") + + _, err = env.ClCluster.Nodes[0].GraphqlAPI.CreateJobDistributor(ctx, graphqlClient.JobDistributorInput{ + Name: "job-distributor-2", + Uri: "http://job-distributor-2:8080", + PublicKey: "37346b7ea98af21e1309847e00f772826ac3689fe990b1920d01efc58ad2f250", + }) + require.NoError(t, err, "Creating second job distributor in chainlink node shouldn't fail") + + distributors, err := env.ClCluster.Nodes[0].GraphqlAPI.ListJobDistributors(ctx) + require.NoError(t, err, "Listing job distributors in chainlink node shouldn't fail") + require.Len(t, distributors.FeedsManagers.Results, 2, "There should be 2 job distributors") + + assert.Equal(t, "job-distributor-1", distributors.FeedsManagers.Results[0].Name) + assert.Equal(t, "job-distributor-2", distributors.FeedsManagers.Results[1].Name) +} diff --git a/integration-tests/smoke/keeper_test.go b/integration-tests/smoke/keeper_test.go index 21eaf23122..d9875b8db7 100644 --- a/integration-tests/smoke/keeper_test.go +++ b/integration-tests/smoke/keeper_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - seth_utils "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/seth" + "github.com/smartcontractkit/chainlink/integration-tests/utils" "github.com/ethereum/go-ethereum/common" "github.com/onsi/gomega" @@ -1244,7 +1244,7 @@ func setupKeeperTest(l zerolog.Logger, t *testing.T, config *tc.TestConfig) ( evmNetwork, err := env.GetFirstEvmNetwork() require.NoError(t, err, "Error getting first evm network") - sethClient, err := seth_utils.GetChainClient(config, *evmNetwork) + sethClient, err := utils.TestAwareSethClient(t, config, evmNetwork) require.NoError(t, err, "Error getting seth client") err = actions.FundChainlinkNodesFromRootAddress(l, sethClient, contracts.ChainlinkClientToChainlinkNodeWithKeysAndAddress(env.ClCluster.NodeAPIs()), big.NewFloat(*config.Common.ChainlinkNodeFunding)) diff --git a/integration-tests/smoke/log_poller_test.go b/integration-tests/smoke/log_poller_test.go index c7f5483ac1..b5891e7a3e 100644 --- a/integration-tests/smoke/log_poller_test.go +++ b/integration-tests/smoke/log_poller_test.go @@ -306,20 +306,7 @@ func prepareEnvironment(l zerolog.Logger, t *testing.T, testConfig *tc.TestConfi logScannerSettings, ) - _, upkeepIDs := actions.DeployConsumers( - t, - chainClient, - registry, - registrar, - linkToken, - upKeepsNeeded, - big.NewInt(int64(9e18)), - uint32(2500000), - true, - false, - false, - nil, - ) + _, upkeepIDs := actions.DeployLegacyConsumers(t, chainClient, registry, registrar, linkToken, upKeepsNeeded, big.NewInt(int64(9e18)), uint32(2500000), true, false, false, nil) err = logpoller.AssertUpkeepIdsUniqueness(upkeepIDs) require.NoError(t, err, "Error asserting upkeep ids uniqueness") diff --git a/integration-tests/smoke/ocr2_test.go b/integration-tests/smoke/ocr2_test.go index adcc99ae09..325c88f979 100644 --- a/integration-tests/smoke/ocr2_test.go +++ b/integration-tests/smoke/ocr2_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - seth_utils "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/seth" + "github.com/smartcontractkit/chainlink/integration-tests/utils" "github.com/ethereum/go-ethereum/common" "github.com/rs/zerolog" @@ -103,11 +103,11 @@ func TestOCRv2JobReplacement(t *testing.T) { t.Parallel() l := logging.GetTestLogger(t) - env, aggregatorContracts, sethClient := prepareORCv2SmokeTestEnv(t, defaultTestData(), l, 5) - nodeClients := env.ClCluster.NodeAPIs() + testEnv, aggregatorContracts, sethClient := prepareORCv2SmokeTestEnv(t, defaultTestData(), l, 5) + nodeClients := testEnv.ClCluster.NodeAPIs() bootstrapNode, workerNodes := nodeClients[0], nodeClients[1:] - err := env.MockAdapter.SetAdapterBasedIntValuePath("ocr2", []string{http.MethodGet, http.MethodPost}, 10) + err := testEnv.MockAdapter.SetAdapterBasedIntValuePath("ocr2", []string{http.MethodGet, http.MethodPost}, 10) require.NoError(t, err) err = actions.WatchNewOCRRound(l, sethClient, 2, contracts.V2OffChainAgrregatorToOffChainAggregatorWithRounds(aggregatorContracts), time.Minute*5) require.NoError(t, err, "Error watching for new OCR2 round") @@ -125,7 +125,7 @@ func TestOCRv2JobReplacement(t *testing.T) { err = actions.DeleteBridges(nodeClients) require.NoError(t, err) - err = actions.CreateOCRv2JobsLocal(aggregatorContracts, bootstrapNode, workerNodes, env.MockAdapter, "ocr2", 15, uint64(sethClient.ChainID), false, false) + err = actions.CreateOCRv2JobsLocal(aggregatorContracts, bootstrapNode, workerNodes, testEnv.MockAdapter, "ocr2", 15, uint64(sethClient.ChainID), false, false) require.NoError(t, err, "Error creating OCRv2 jobs") err = actions.WatchNewOCRRound(l, sethClient, 3, contracts.V2OffChainAgrregatorToOffChainAggregatorWithRounds(aggregatorContracts), time.Minute*3) @@ -164,14 +164,14 @@ func prepareORCv2SmokeTestEnv(t *testing.T, testData ocr2test, l zerolog.Logger, evmNetwork, err := testEnv.GetFirstEvmNetwork() require.NoError(t, err, "Error getting first evm network") - sethClient, err := seth_utils.GetChainClient(config, *evmNetwork) + sethClient, err := utils.TestAwareSethClient(t, config, evmNetwork) require.NoError(t, err, "Error getting seth client") nodeClients := testEnv.ClCluster.NodeAPIs() bootstrapNode, workerNodes := nodeClients[0], nodeClients[1:] - linkContract, err := contracts.DeployLinkTokenContract(l, sethClient) - require.NoError(t, err, "Error deploying link token contract") + linkContract, err := actions.LinkTokenContract(l, sethClient, config.OCR2) + require.NoError(t, err, "Error loading/deploying link token contract") err = actions.FundChainlinkNodesFromRootAddress(l, sethClient, contracts.ChainlinkClientToChainlinkNodeWithKeysAndAddress(workerNodes), big.NewFloat(*config.Common.ChainlinkNodeFunding)) require.NoError(t, err, "Error funding Chainlink nodes") @@ -191,18 +191,20 @@ func prepareORCv2SmokeTestEnv(t *testing.T, testData ocr2test, l zerolog.Logger, transmitters = append(transmitters, addr) } - ocrOffchainOptions := contracts.DefaultOffChainAggregatorOptions() - aggregatorContracts, err := actions.DeployOCRv2Contracts(l, sethClient, 1, common.HexToAddress(linkContract.Address()), transmitters, ocrOffchainOptions) + ocrOffChainOptions := contracts.DefaultOffChainAggregatorOptions() + aggregatorContracts, err := actions.SetupOCRv2Contracts(l, sethClient, config.OCR2, common.HexToAddress(linkContract.Address()), transmitters, ocrOffChainOptions) require.NoError(t, err, "Error deploying OCRv2 aggregator contracts") err = actions.CreateOCRv2JobsLocal(aggregatorContracts, bootstrapNode, workerNodes, testEnv.MockAdapter, "ocr2", 5, uint64(sethClient.ChainID), false, testData.chainReaderAndCodec) require.NoError(t, err, "Error creating OCRv2 jobs") - ocrv2Config, err := actions.BuildMedianOCR2ConfigLocal(workerNodes, ocrOffchainOptions) - require.NoError(t, err, "Error building OCRv2 config") + if !config.OCR2.UseExistingOffChainAggregatorsContracts() || (config.OCR2.UseExistingOffChainAggregatorsContracts() && config.OCR2.ConfigureExistingOffChainAggregatorsContracts()) { + ocrV2Config, err := actions.BuildMedianOCR2ConfigLocal(workerNodes, ocrOffChainOptions) + require.NoError(t, err, "Error building OCRv2 config") - err = actions.ConfigureOCRv2AggregatorContracts(ocrv2Config, aggregatorContracts) - require.NoError(t, err, "Error configuring OCRv2 aggregator contracts") + err = actions.ConfigureOCRv2AggregatorContracts(ocrV2Config, aggregatorContracts) + require.NoError(t, err, "Error configuring OCRv2 aggregator contracts") + } assertCorrectNodeConfiguration(t, l, clNodeCount, testData, testEnv) diff --git a/integration-tests/smoke/ocr_test.go b/integration-tests/smoke/ocr_test.go index cd6954ac2c..a19adb5c02 100644 --- a/integration-tests/smoke/ocr_test.go +++ b/integration-tests/smoke/ocr_test.go @@ -5,8 +5,6 @@ import ( "testing" "time" - seth_utils "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/seth" - "github.com/ethereum/go-ethereum/common" "github.com/rs/zerolog" "github.com/stretchr/testify/require" @@ -20,6 +18,7 @@ import ( "github.com/smartcontractkit/chainlink/integration-tests/contracts" "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" tc "github.com/smartcontractkit/chainlink/integration-tests/testconfig" + "github.com/smartcontractkit/chainlink/integration-tests/utils" ) const ( @@ -100,7 +99,7 @@ func prepareORCv1SmokeTestEnv(t *testing.T, l zerolog.Logger, firstRoundResult i evmNetwork, err := env.GetFirstEvmNetwork() require.NoError(t, err, "Error getting first evm network") - sethClient, err := seth_utils.GetChainClient(config, *evmNetwork) + sethClient, err := utils.TestAwareSethClient(t, config, evmNetwork) require.NoError(t, err, "Error getting seth client") nodeClients := env.ClCluster.NodeAPIs() @@ -114,10 +113,10 @@ func prepareORCv1SmokeTestEnv(t *testing.T, l zerolog.Logger, firstRoundResult i _ = actions.ReturnFundsFromNodes(l, sethClient, contracts.ChainlinkClientToChainlinkNodeWithKeysAndAddress(env.ClCluster.NodeAPIs())) }) - linkContract, err := contracts.DeployLinkTokenContract(l, sethClient) - require.NoError(t, err, "Error deploying link token contract") + linkContract, err := actions.LinkTokenContract(l, sethClient, config.OCR) + require.NoError(t, err, "Error loading/deploying link token contract") - ocrInstances, err := actions.DeployOCRv1Contracts(l, sethClient, 1, common.HexToAddress(linkContract.Address()), contracts.ChainlinkClientToChainlinkNodeWithKeysAndAddress(workerNodes)) + ocrInstances, err := actions.SetupOCRv1Contracts(l, sethClient, config.OCR, common.HexToAddress(linkContract.Address()), contracts.ChainlinkClientToChainlinkNodeWithKeysAndAddress(workerNodes)) require.NoError(t, err, "Error deploying OCR contracts") err = actions.CreateOCRJobsLocal(ocrInstances, bootstrapNode, workerNodes, 5, env.MockAdapter, big.NewInt(sethClient.ChainID)) diff --git a/integration-tests/smoke/runlog_test.go b/integration-tests/smoke/runlog_test.go index 922cc9c372..d081619fe6 100644 --- a/integration-tests/smoke/runlog_test.go +++ b/integration-tests/smoke/runlog_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - seth_utils "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/seth" + "github.com/smartcontractkit/chainlink/integration-tests/utils" "github.com/google/uuid" "github.com/onsi/gomega" @@ -47,7 +47,7 @@ func TestRunLogBasic(t *testing.T) { evmNetwork, err := env.GetFirstEvmNetwork() require.NoError(t, err, "Error getting first evm network") - sethClient, err := seth_utils.GetChainClient(config, *evmNetwork) + sethClient, err := utils.TestAwareSethClient(t, config, evmNetwork) require.NoError(t, err, "Error getting seth client") err = actions.FundChainlinkNodesFromRootAddress(l, sethClient, contracts.ChainlinkClientToChainlinkNodeWithKeysAndAddress(env.ClCluster.NodeAPIs()), big.NewFloat(*config.Common.ChainlinkNodeFunding)) diff --git a/integration-tests/smoke/vrf_test.go b/integration-tests/smoke/vrf_test.go index afef09841b..822276222a 100644 --- a/integration-tests/smoke/vrf_test.go +++ b/integration-tests/smoke/vrf_test.go @@ -11,11 +11,9 @@ import ( "github.com/rs/zerolog" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink-testing-framework/seth" - "github.com/smartcontractkit/chainlink-testing-framework/lib/logging" - seth_utils "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/seth" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" + "github.com/smartcontractkit/chainlink-testing-framework/seth" "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/actions/vrf/vrfv1" @@ -24,6 +22,7 @@ import ( ethcontracts "github.com/smartcontractkit/chainlink/integration-tests/contracts" "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" tc "github.com/smartcontractkit/chainlink/integration-tests/testconfig" + "github.com/smartcontractkit/chainlink/integration-tests/utils" ) func TestVRFBasic(t *testing.T) { @@ -205,7 +204,7 @@ func prepareVRFtestEnv(t *testing.T, l zerolog.Logger) (*test_env.CLClusterTestE evmNetwork, err := env.GetFirstEvmNetwork() require.NoError(t, err, "Error getting first evm network") - sethClient, err := seth_utils.GetChainClient(config, *evmNetwork) + sethClient, err := utils.TestAwareSethClient(t, config, evmNetwork) require.NoError(t, err, "Error getting seth client") err = actions.FundChainlinkNodesFromRootAddress(l, sethClient, contracts.ChainlinkClientToChainlinkNodeWithKeysAndAddress(env.ClCluster.NodeAPIs()), big.NewFloat(*config.Common.ChainlinkNodeFunding)) diff --git a/integration-tests/smoke/vrfv2_test.go b/integration-tests/smoke/vrfv2_test.go index 4ae2553fa0..8390c27a4b 100644 --- a/integration-tests/smoke/vrfv2_test.go +++ b/integration-tests/smoke/vrfv2_test.go @@ -26,7 +26,6 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/conversions" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/ptr" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" - "github.com/smartcontractkit/chainlink/integration-tests/actions" vrfcommon "github.com/smartcontractkit/chainlink/integration-tests/actions/vrf/common" "github.com/smartcontractkit/chainlink/integration-tests/actions/vrf/vrfv2" @@ -41,6 +40,53 @@ const ( SethRootKeyIndex = 0 ) +// vrfv2CleanUpFn is a cleanup function that captures pointers from context, in which it's called and uses them to clean up the test environment +var vrfv2CleanUpFn = func( + t **testing.T, + sethClient **seth.Client, + config **tc.TestConfig, + testEnv **test_env.CLClusterTestEnv, + vrfContracts **vrfcommon.VRFContracts, + subIDsForCancellingAfterTest *[]uint64, + walletAddress **string, +) func() { + return func() { + logger := logging.GetTestLogger(*t) + testConfig := **config + network := networks.MustGetSelectedNetworkConfig(testConfig.GetNetworkConfig())[0] + if network.Simulated { + logger.Info(). + Str("Network Name", network.Name). + Msg("Network is a simulated network. Skipping fund return for Coordinator Subscriptions.") + } else { + if *vrfContracts != nil && *sethClient != nil { + if *testConfig.VRFv2.General.CancelSubsAfterTestRun { + client := *sethClient + var returnToAddress string + if walletAddress == nil || *walletAddress == nil { + returnToAddress = client.MustGetRootKeyAddress().Hex() + } else { + returnToAddress = **walletAddress + } + //cancel subs and return funds to sub owner + vrfv2.CancelSubsAndReturnFunds(testcontext.Get(*t), *vrfContracts, returnToAddress, *subIDsForCancellingAfterTest, logger) + } + } else { + logger.Error().Msg("VRF Contracts and/or Seth client are nil. Cannot execute cleanup") + } + } + if !*testConfig.VRFv2.General.UseExistingEnv { + if *testEnv == nil { + logger.Error().Msg("Test environment is nil. Cannot execute cleanup") + return + } + if err := (*testEnv).Cleanup(test_env.CleanupOpts{TestName: (*t).Name()}); err != nil { + logger.Error().Err(err).Msg("Error cleaning up test environment") + } + } + } +} + func TestVRFv2Basic(t *testing.T) { t.Parallel() var ( @@ -55,30 +101,13 @@ func TestVRFv2Basic(t *testing.T) { config, err := tc.GetChainAndTestTypeSpecificConfig("Smoke", tc.VRFv2) require.NoError(t, err, "Error getting config") - vrfv2Config := config.VRFv2 chainID := networks.MustGetSelectedNetworkConfig(config.GetNetworkConfig())[0].ChainID - cleanupFn := func() { - if sethClient.Cfg.IsSimulatedNetwork() { - l.Info(). - Str("Network Name", sethClient.Cfg.Network.Name). - Msg("Network is a simulated network. Skipping fund return for Coordinator Subscriptions.") - } else { - if *vrfv2Config.General.CancelSubsAfterTestRun { - //cancel subs and return funds to sub owner - vrfv2.CancelSubsAndReturnFunds(testcontext.Get(t), vrfContracts, sethClient.MustGetRootKeyAddress().Hex(), subIDsForCancellingAfterTest, l) - } - } - if !*vrfv2Config.General.UseExistingEnv { - if err := testEnv.Cleanup(test_env.CleanupOpts{TestName: t.Name()}); err != nil { - l.Error().Err(err).Msg("Error cleaning up test environment") - } - } - } + configPtr := &config vrfEnvConfig := vrfcommon.VRFEnvConfig{ TestConfig: config, ChainID: chainID, - CleanupFn: cleanupFn, + CleanupFn: vrfv2CleanUpFn(&t, &sethClient, &configPtr, &testEnv, &vrfContracts, &subIDsForCancellingAfterTest, nil), } newEnvConfig := vrfcommon.NewEnvConfig{ NodesToCreate: []vrfcommon.VRFNodeType{vrfcommon.VRF}, @@ -575,29 +604,12 @@ func TestVRFv2MultipleSendingKeys(t *testing.T) { t.Fatal(err) } chainID := networks.MustGetSelectedNetworkConfig(config.GetNetworkConfig())[0].ChainID - vrfv2Config := config.VRFv2 - cleanupFn := func() { - if sethClient.Cfg.IsSimulatedNetwork() { - l.Info(). - Str("Network Name", sethClient.Cfg.Network.Name). - Msg("Network is a simulated network. Skipping fund return for Coordinator Subscriptions.") - } else { - if *vrfv2Config.General.CancelSubsAfterTestRun { - //cancel subs and return funds to sub owner - vrfv2.CancelSubsAndReturnFunds(testcontext.Get(t), vrfContracts, sethClient.MustGetRootKeyAddress().Hex(), subIDsForCancellingAfterTest, l) - } - } - if !*vrfv2Config.General.UseExistingEnv { - if err := testEnv.Cleanup(test_env.CleanupOpts{TestName: t.Name()}); err != nil { - l.Error().Err(err).Msg("Error cleaning up test environment") - } - } - } + configPtr := &config vrfEnvConfig := vrfcommon.VRFEnvConfig{ TestConfig: config, ChainID: chainID, - CleanupFn: cleanupFn, + CleanupFn: vrfv2CleanUpFn(&t, &sethClient, &configPtr, &testEnv, &vrfContracts, &subIDsForCancellingAfterTest, nil), } newEnvConfig := vrfcommon.NewEnvConfig{ NodesToCreate: []vrfcommon.VRFNodeType{vrfcommon.VRF}, @@ -681,29 +693,12 @@ func TestVRFOwner(t *testing.T) { config, err := tc.GetChainAndTestTypeSpecificConfig("Smoke", tc.VRFv2) require.NoError(t, err, "Error getting config") chainID := networks.MustGetSelectedNetworkConfig(config.GetNetworkConfig())[0].ChainID - vrfv2Config := config.VRFv2 - cleanupFn := func() { - if sethClient.Cfg.IsSimulatedNetwork() { - l.Info(). - Str("Network Name", sethClient.Cfg.Network.Name). - Msg("Network is a simulated network. Skipping fund return for Coordinator Subscriptions.") - } else { - if *vrfv2Config.General.CancelSubsAfterTestRun { - //cancel subs and return funds to sub owner - vrfv2.CancelSubsAndReturnFunds(testcontext.Get(t), vrfContracts, sethClient.MustGetRootKeyAddress().Hex(), subIDsForCancellingAfterTest, l) - } - } - if !*vrfv2Config.General.UseExistingEnv { - if err := testEnv.Cleanup(test_env.CleanupOpts{TestName: t.Name()}); err != nil { - l.Error().Err(err).Msg("Error cleaning up test environment") - } - } - } + configPtr := &config vrfEnvConfig := vrfcommon.VRFEnvConfig{ TestConfig: config, ChainID: chainID, - CleanupFn: cleanupFn, + CleanupFn: vrfv2CleanUpFn(&t, &sethClient, &configPtr, &testEnv, &vrfContracts, &subIDsForCancellingAfterTest, nil), } newEnvConfig := vrfcommon.NewEnvConfig{ NodesToCreate: []vrfcommon.VRFNodeType{vrfcommon.VRF}, @@ -818,24 +813,7 @@ func TestVRFV2WithBHS(t *testing.T) { require.NoError(t, err, "Error getting config") vrfv2Config := config.VRFv2 chainID := networks.MustGetSelectedNetworkConfig(config.GetNetworkConfig())[0].ChainID - - cleanupFn := func() { - if sethClient.Cfg.IsSimulatedNetwork() { - l.Info(). - Str("Network Name", sethClient.Cfg.Network.Name). - Msg("Network is a simulated network. Skipping fund return for Coordinator Subscriptions.") - } else { - if *vrfv2Config.General.CancelSubsAfterTestRun { - //cancel subs and return funds to sub owner - vrfv2.CancelSubsAndReturnFunds(testcontext.Get(t), vrfContracts, sethClient.MustGetRootKeyAddress().Hex(), subIDsForCancellingAfterTest, l) - } - } - if !*vrfv2Config.General.UseExistingEnv { - if err := testEnv.Cleanup(test_env.CleanupOpts{TestName: t.Name()}); err != nil { - l.Error().Err(err).Msg("Error cleaning up test environment") - } - } - } + configPtr := &config //decrease default span for checking blockhashes for unfulfilled requests vrfv2Config.General.BHSJobWaitBlocks = ptr.Ptr(2) @@ -843,7 +821,7 @@ func TestVRFV2WithBHS(t *testing.T) { vrfEnvConfig := vrfcommon.VRFEnvConfig{ TestConfig: config, ChainID: chainID, - CleanupFn: cleanupFn, + CleanupFn: vrfv2CleanUpFn(&t, &sethClient, &configPtr, &testEnv, &vrfContracts, &subIDsForCancellingAfterTest, nil), } newEnvConfig := vrfcommon.NewEnvConfig{ NodesToCreate: []vrfcommon.VRFNodeType{vrfcommon.VRF, vrfcommon.BHS}, @@ -861,7 +839,6 @@ func TestVRFV2WithBHS(t *testing.T) { } //BHS node should fill in blockhashes into BHS contract depending on the waitBlocks and lookBackBlocks settings configCopy := config.MustCopy().(tc.TestConfig) - //Underfund Subscription configCopy.VRFv2.General.SubscriptionFundingAmountLink = ptr.Ptr(float64(0)) consumers, subIDsForBHS, err := vrfv2.SetupNewConsumersAndSubs( @@ -965,9 +942,7 @@ func TestVRFV2WithBHS(t *testing.T) { SethRootKeyIndex, ) require.NoError(t, err, "error requesting randomness") - randRequestBlockNumber := randomWordsRequestedEvent.Raw.BlockNumber - _, err = vrfContracts.BHS.GetBlockHash(testcontext.Get(t), big.NewInt(int64(randRequestBlockNumber))) require.Error(t, err, "error not occurred when getting blockhash for a blocknumber which was not stored in BHS contract") @@ -988,29 +963,37 @@ func TestVRFV2WithBHS(t *testing.T) { metrics, err := consumers[0].GetLoadTestMetrics(testcontext.Get(t)) require.Equal(t, 0, metrics.RequestCount.Cmp(big.NewInt(1))) require.Equal(t, 0, metrics.FulfilmentCount.Cmp(big.NewInt(0))) - - var clNodeTxs *client.TransactionsData - var txHash string gom := gomega.NewGomegaWithT(t) - gom.Eventually(func(g gomega.Gomega) { - clNodeTxs, _, err = nodeTypeToNodeMap[vrfcommon.BHS].CLNode.API.ReadTransactions() - g.Expect(err).ShouldNot(gomega.HaveOccurred(), "error getting CL Node transactions") - l.Info().Int("Number of TXs", len(clNodeTxs.Data)).Msg("BHS Node txs") - g.Expect(len(clNodeTxs.Data)).Should(gomega.BeNumerically("==", 1), "Expected 1 tx posted by BHS Node, but found %d", len(clNodeTxs.Data)) - txHash = clNodeTxs.Data[0].Attributes.Hash - }, "2m", "1s").Should(gomega.Succeed()) - - require.Equal(t, strings.ToLower(vrfContracts.BHS.Address()), strings.ToLower(clNodeTxs.Data[0].Attributes.To)) - - bhsStoreTx, _, err := sethClient.Client.TransactionByHash(testcontext.Get(t), common.HexToHash(txHash)) - require.NoError(t, err, "error getting tx from hash") - bhsStoreTxInputData, err := actions.DecodeTxInputData(blockhash_store.BlockhashStoreABI, bhsStoreTx.Data()) - l.Info(). - Str("Block Number", bhsStoreTxInputData["n"].(*big.Int).String()). - Msg("BHS Node's Store Blockhash for Blocknumber Method TX") - require.Equal(t, randRequestBlockNumber, bhsStoreTxInputData["n"].(*big.Int).Uint64()) + if !*configCopy.VRFv2.General.UseExistingEnv { + l.Info().Msg("Checking BHS Node's transactions") + var clNodeTxs *client.TransactionsData + var txHash string + gom.Eventually(func(g gomega.Gomega) { + clNodeTxs, _, err = nodeTypeToNodeMap[vrfcommon.BHS].CLNode.API.ReadTransactions() + g.Expect(err).ShouldNot(gomega.HaveOccurred(), "error getting CL Node transactions") + g.Expect(len(clNodeTxs.Data)).Should(gomega.BeNumerically("==", 1), "Expected 1 tx posted by BHS Node, but found %d", len(clNodeTxs.Data)) + txHash = clNodeTxs.Data[0].Attributes.Hash + l.Info(). + Str("TX Hash", txHash). + Int("Number of TXs", len(clNodeTxs.Data)). + Msg("BHS Node txs") + }, "2m", "1s").Should(gomega.Succeed()) + + require.Equal(t, strings.ToLower(vrfContracts.BHS.Address()), strings.ToLower(clNodeTxs.Data[0].Attributes.To)) + + bhsStoreTx, _, err := sethClient.Client.TransactionByHash(testcontext.Get(t), common.HexToHash(txHash)) + require.NoError(t, err, "error getting tx from hash") + bhsStoreTxInputData, err := actions.DecodeTxInputData(blockhash_store.BlockhashStoreABI, bhsStoreTx.Data()) + require.NoError(t, err, "error decoding tx input data") + l.Info(). + Str("Block Number", bhsStoreTxInputData["n"].(*big.Int).String()). + Msg("BHS Node's Store Blockhash for Blocknumber Method TX") + require.Equal(t, randRequestBlockNumber, bhsStoreTxInputData["n"].(*big.Int).Uint64()) + } else { + l.Warn().Msg("Skipping BHS Node's transactions check as existing env is used") + } var randRequestBlockHash [32]byte gom.Eventually(func(g gomega.Gomega) { randRequestBlockHash, err = vrfContracts.BHS.GetBlockHash(testcontext.Get(t), big.NewInt(int64(randRequestBlockNumber))) @@ -1030,7 +1013,6 @@ func TestVRFV2NodeReorg(t *testing.T) { env *test_env.CLClusterTestEnv vrfContracts *vrfcommon.VRFContracts subIDsForCancellingAfterTest []uint64 - defaultWalletAddress string vrfKey *vrfcommon.VRFKeyData sethClient *seth.Client ) @@ -1038,33 +1020,16 @@ func TestVRFV2NodeReorg(t *testing.T) { config, err := tc.GetChainAndTestTypeSpecificConfig("Smoke", tc.VRFv2) require.NoError(t, err, "Error getting config") - vrfv2Config := config.VRFv2 network := networks.MustGetSelectedNetworkConfig(config.GetNetworkConfig())[0] if !network.Simulated { t.Skip("Skipped since Reorg test could only be run on Simulated chain.") } chainID := network.ChainID - cleanupFn := func() { - if sethClient.Cfg.IsSimulatedNetwork() { - l.Info(). - Str("Network Name", sethClient.Cfg.Network.Name). - Msg("Network is a simulated network. Skipping fund return for Coordinator Subscriptions.") - } else { - if *vrfv2Config.General.CancelSubsAfterTestRun { - //cancel subs and return funds to sub owner - vrfv2.CancelSubsAndReturnFunds(testcontext.Get(t), vrfContracts, defaultWalletAddress, subIDsForCancellingAfterTest, l) - } - } - if !*vrfv2Config.General.UseExistingEnv { - if err := env.Cleanup(test_env.CleanupOpts{TestName: t.Name()}); err != nil { - l.Error().Err(err).Msg("Error cleaning up test environment") - } - } - } + configPtr := &config chainlinkNodeLogScannerSettings := test_env.GetDefaultChainlinkNodeLogScannerSettingsWithExtraAllowedMessages( testreporters.NewAllowedLogMessage( - "This is a problem and either means a very deep re-org occurred", + "Got very old block.", "Test is expecting a reorg to occur", zapcore.DPanicLevel, testreporters.WarnAboutAllowedMsgs_No), @@ -1077,7 +1042,7 @@ func TestVRFV2NodeReorg(t *testing.T) { vrfEnvConfig := vrfcommon.VRFEnvConfig{ TestConfig: config, ChainID: chainID, - CleanupFn: cleanupFn, + CleanupFn: vrfv2CleanUpFn(&t, &sethClient, &configPtr, &env, &vrfContracts, &subIDsForCancellingAfterTest, nil), } newEnvConfig := vrfcommon.NewEnvConfig{ NodesToCreate: []vrfcommon.VRFNodeType{vrfcommon.VRF}, @@ -1209,7 +1174,6 @@ func TestVRFv2BatchFulfillmentEnabledDisabled(t *testing.T) { env *test_env.CLClusterTestEnv vrfContracts *vrfcommon.VRFContracts subIDsForCancellingAfterTest []uint64 - defaultWalletAddress string vrfKey *vrfcommon.VRFKeyData nodeTypeToNodeMap map[vrfcommon.VRFNodeType]*vrfcommon.VRFNode sethClient *seth.Client @@ -1218,30 +1182,14 @@ func TestVRFv2BatchFulfillmentEnabledDisabled(t *testing.T) { config, err := tc.GetChainAndTestTypeSpecificConfig("Smoke", tc.VRFv2) require.NoError(t, err, "Error getting config") - vrfv2Config := config.VRFv2 network := networks.MustGetSelectedNetworkConfig(config.GetNetworkConfig())[0] chainID := network.ChainID - cleanupFn := func() { - if sethClient.Cfg.IsSimulatedNetwork() { - l.Info(). - Str("Network Name", sethClient.Cfg.Network.Name). - Msg("Network is a simulated network. Skipping fund return for Coordinator Subscriptions.") - } else { - if *vrfv2Config.General.CancelSubsAfterTestRun { - //cancel subs and return funds to sub owner - vrfv2.CancelSubsAndReturnFunds(testcontext.Get(t), vrfContracts, defaultWalletAddress, subIDsForCancellingAfterTest, l) - } - } - if !*vrfv2Config.General.UseExistingEnv { - if err := env.Cleanup(test_env.CleanupOpts{TestName: t.Name()}); err != nil { - l.Error().Err(err).Msg("Error cleaning up test environment") - } - } - } + + configPtr := &config vrfEnvConfig := vrfcommon.VRFEnvConfig{ TestConfig: config, ChainID: chainID, - CleanupFn: cleanupFn, + CleanupFn: vrfv2CleanUpFn(&t, &sethClient, &configPtr, &env, &vrfContracts, &subIDsForCancellingAfterTest, nil), } newEnvConfig := vrfcommon.NewEnvConfig{ NodesToCreate: []vrfcommon.VRFNodeType{vrfcommon.VRF}, @@ -1495,5 +1443,4 @@ func TestVRFv2BatchFulfillmentEnabledDisabled(t *testing.T) { // verify that all fulfillments should be in separate txs require.Equal(t, int(randRequestCount), len(singleFulfillmentTxs)) }) - } diff --git a/integration-tests/smoke/vrfv2plus_test.go b/integration-tests/smoke/vrfv2plus_test.go index 36c61ee91b..d0a19c75d2 100644 --- a/integration-tests/smoke/vrfv2plus_test.go +++ b/integration-tests/smoke/vrfv2plus_test.go @@ -38,6 +38,47 @@ import ( it_utils "github.com/smartcontractkit/chainlink/integration-tests/utils" ) +// vrfv2PlusCleanUpFn is a cleanup function that captures pointers from context, in which it's called and uses them to clean up the test environment +var vrfv2PlusCleanUpFn = func( + t **testing.T, + sethClient **seth.Client, + config **tc.TestConfig, + testEnv **test_env.CLClusterTestEnv, + vrfContracts **vrfcommon.VRFContracts, + subIDsForCancellingAfterTest *[]*big.Int, +) func() { + return func() { + l := logging.GetTestLogger(*t) + testConfig := **config + network := networks.MustGetSelectedNetworkConfig(testConfig.GetNetworkConfig())[0] + if network.Simulated { + l.Info(). + Str("Network Name", network.Name). + Msg("Network is a simulated network. Skipping fund return for Coordinator Subscriptions.") + } else { + if *vrfContracts != nil && *sethClient != nil { + if *testConfig.VRFv2Plus.General.CancelSubsAfterTestRun { + client := *sethClient + returnToAddress := client.MustGetRootKeyAddress().Hex() + //cancel subs and return funds to sub owner + vrfv2plus.CancelSubsAndReturnFunds(testcontext.Get(*t), *vrfContracts, returnToAddress, *subIDsForCancellingAfterTest, l) + } + } else { + l.Error().Msg("VRF Contracts and/or Seth client are nil. Cannot execute cleanup") + } + } + if !*testConfig.VRFv2Plus.General.UseExistingEnv { + if *testEnv == nil { + l.Error().Msg("Test environment is nil. Cannot execute cleanup") + return + } + if err := (*testEnv).Cleanup(test_env.CleanupOpts{TestName: (*t).Name()}); err != nil { + l.Error().Err(err).Msg("Error cleaning up test environment") + } + } + } +} + func TestVRFv2Plus(t *testing.T) { t.Parallel() var ( @@ -53,30 +94,13 @@ func TestVRFv2Plus(t *testing.T) { config, err := tc.GetChainAndTestTypeSpecificConfig("Smoke", tc.VRFv2Plus) require.NoError(t, err, "Error getting config") - vrfv2PlusConfig := config.VRFv2Plus chainID := networks.MustGetSelectedNetworkConfig(config.GetNetworkConfig())[0].ChainID + configPtr := &config - cleanupFn := func() { - if sethClient.Cfg.IsSimulatedNetwork() { - l.Info(). - Str("Network Name", sethClient.Cfg.Network.Name). - Msg("Network is a simulated network. Skipping fund return for Coordinator Subscriptions.") - } else { - if *vrfv2PlusConfig.General.CancelSubsAfterTestRun { - //cancel subs and return funds to sub owner - vrfv2plus.CancelSubsAndReturnFunds(testcontext.Get(t), vrfContracts, sethClient.MustGetRootKeyAddress().Hex(), subIDsForCancellingAfterTest, l) - } - } - if !*vrfv2PlusConfig.General.UseExistingEnv { - if err := env.Cleanup(test_env.CleanupOpts{TestName: t.Name()}); err != nil { - l.Error().Err(err).Msg("Error cleaning up test environment") - } - } - } vrfEnvConfig := vrfcommon.VRFEnvConfig{ TestConfig: config, ChainID: chainID, - CleanupFn: cleanupFn, + CleanupFn: vrfv2PlusCleanUpFn(&t, &sethClient, &configPtr, &env, &vrfContracts, &subIDsForCancellingAfterTest), } newEnvConfig := vrfcommon.NewEnvConfig{ NodesToCreate: []vrfcommon.VRFNodeType{vrfcommon.VRF}, @@ -128,12 +152,6 @@ func TestVRFv2Plus(t *testing.T) { require.Equal(t, isNativeBilling, randomWordsFulfilledEvent.NativePayment, "RandomWordsFulfilled Event's `NativePayment` field should be false") require.True(t, randomWordsFulfilledEvent.Success, "RandomWordsFulfilled Event's `Success` field should be true") - expectedSubBalanceJuels := new(big.Int).Sub(subBalanceBeforeRequest, randomWordsFulfilledEvent.Payment) - subscription, err = vrfContracts.CoordinatorV2Plus.GetSubscription(testcontext.Get(t), subIDForRequestRandomness) - require.NoError(t, err, "error getting subscription information") - subBalanceAfterRequest := subscription.Balance - require.Equal(t, expectedSubBalanceJuels, subBalanceAfterRequest) - status, err := consumers[0].GetRequestStatus(testcontext.Get(t), randomWordsFulfilledEvent.RequestId) require.NoError(t, err, "error getting rand request status") require.True(t, status.Fulfilled) @@ -144,6 +162,19 @@ func TestVRFv2Plus(t *testing.T) { l.Info().Str("Output", w.String()).Msg("Randomness fulfilled") require.Equal(t, 1, w.Cmp(big.NewInt(0)), "Expected the VRF job give an answer bigger than 0") } + t.Run("Verify Billing", func(t *testing.T) { + actualSubPaymentJuels := randomWordsFulfilledEvent.Payment + + expectedSubBalanceJuels := new(big.Int).Sub(subBalanceBeforeRequest, actualSubPaymentJuels) + subscription, err = vrfContracts.CoordinatorV2Plus.GetSubscription(testcontext.Get(t), subIDForRequestRandomness) + require.NoError(t, err, "error getting subscription information") + subBalanceAfterRequest := subscription.Balance + require.Equal(t, expectedSubBalanceJuels, subBalanceAfterRequest) + + //todo - need to refactor the test so that when running on live testnet and deploying a new environment, + // we would load real Link Token and Link/ETH feed addresses from the config + }) + }) t.Run("Native Billing", func(t *testing.T) { configCopy := config.MustCopy().(tc.TestConfig) @@ -184,12 +215,6 @@ func TestVRFv2Plus(t *testing.T) { require.False(t, randomWordsFulfilledEvent.OnlyPremium) require.Equal(t, isNativeBilling, randomWordsFulfilledEvent.NativePayment) require.True(t, randomWordsFulfilledEvent.Success) - expectedSubBalanceWei := new(big.Int).Sub(subNativeTokenBalanceBeforeRequest, randomWordsFulfilledEvent.Payment) - subscription, err = vrfContracts.CoordinatorV2Plus.GetSubscription(testcontext.Get(t), subID) - require.NoError(t, err) - subBalanceAfterRequest := subscription.NativeBalance - require.Equal(t, expectedSubBalanceWei, subBalanceAfterRequest) - status, err := consumers[0].GetRequestStatus(testcontext.Get(t), randomWordsFulfilledEvent.RequestId) require.NoError(t, err, "error getting rand request status") require.True(t, status.Fulfilled) @@ -200,115 +225,72 @@ func TestVRFv2Plus(t *testing.T) { l.Info().Str("Output", w.String()).Msg("Randomness fulfilled") require.Equal(t, 1, w.Cmp(big.NewInt(0)), "Expected the VRF job give an answer bigger than 0") } - }) - t.Run("VRF Node waits block confirmation number specified by the consumer before sending fulfilment on-chain", func(t *testing.T) { - configCopy := config.MustCopy().(tc.TestConfig) - testConfig := configCopy.VRFv2Plus.General - isNativeBilling := true - - consumers, subIDs, err := vrfv2plus.SetupNewConsumersAndSubs( - testcontext.Get(t), - sethClient, - vrfContracts.CoordinatorV2Plus, - configCopy, - vrfContracts.LinkToken, - 1, - 1, - l, - ) - require.NoError(t, err, "error setting up new consumers and subs") - subID := subIDs[0] - subscription, err := vrfContracts.CoordinatorV2Plus.GetSubscription(testcontext.Get(t), subID) - require.NoError(t, err, "error getting subscription information") - vrfcommon.LogSubDetails(l, subscription, subID.String(), vrfContracts.CoordinatorV2Plus) - subIDsForCancellingAfterTest = append(subIDsForCancellingAfterTest, subIDs...) - - expectedBlockNumberWait := uint16(10) - testConfig.MinimumConfirmations = ptr.Ptr[uint16](expectedBlockNumberWait) - randomWordsRequestedEvent, randomWordsFulfilledEvent, err := vrfv2plus.RequestRandomnessAndWaitForFulfillment( - consumers[0], - vrfContracts.CoordinatorV2Plus, - vrfKey, - subID, - isNativeBilling, - testConfig, - l, - 0, - ) - require.NoError(t, err, "error requesting randomness and waiting for fulfilment") - - // check that VRF node waited at least the number of blocks specified by the consumer in the rand request min confs field - blockNumberWait := randomWordsRequestedEvent.Raw.BlockNumber - randomWordsFulfilledEvent.Raw.BlockNumber - require.GreaterOrEqual(t, blockNumberWait, uint64(expectedBlockNumberWait)) - - status, err := consumers[0].GetRequestStatus(testcontext.Get(t), randomWordsFulfilledEvent.RequestId) - require.NoError(t, err, "error getting rand request status") - require.True(t, status.Fulfilled) - l.Info().Bool("Fulfilment Status", status.Fulfilled).Msg("Random Words Request Fulfilment Status") - }) - t.Run("CL Node VRF Job Runs", func(t *testing.T) { - configCopy := config.MustCopy().(tc.TestConfig) - var isNativeBilling = false - - consumers, subIDsForRequestRandomness, err := vrfv2plus.SetupNewConsumersAndSubs( - testcontext.Get(t), - sethClient, - vrfContracts.CoordinatorV2Plus, - configCopy, - vrfContracts.LinkToken, - 1, - 1, - l, - ) - require.NoError(t, err, "error setting up new consumers and subs") - subIDForRequestRandomness := subIDsForRequestRandomness[0] - subscription, err := vrfContracts.CoordinatorV2Plus.GetSubscription(testcontext.Get(t), subIDForRequestRandomness) - require.NoError(t, err, "error getting subscription information") - vrfcommon.LogSubDetails(l, subscription, subIDForRequestRandomness.String(), vrfContracts.CoordinatorV2Plus) - subIDsForCancellingAfterTest = append(subIDsForCancellingAfterTest, subIDsForRequestRandomness...) - - jobRunsBeforeTest, err := nodeTypeToNodeMap[vrfcommon.VRF].CLNode.API.MustReadRunsByJob(nodeTypeToNodeMap[vrfcommon.VRF].Job.Data.ID) - require.NoError(t, err, "error reading job runs") - - // test and assert - _, _, err = vrfv2plus.RequestRandomnessAndWaitForFulfillment( - consumers[0], - vrfContracts.CoordinatorV2Plus, - vrfKey, - subIDForRequestRandomness, - isNativeBilling, - configCopy.VRFv2Plus.General, - l, - 0, - ) - require.NoError(t, err, "error requesting randomness and waiting for fulfilment") + t.Run("Verify Billing", func(t *testing.T) { + actualSubPaymentWei := randomWordsFulfilledEvent.Payment + expectedSubBalanceWei := new(big.Int).Sub(subNativeTokenBalanceBeforeRequest, actualSubPaymentWei) + subscription, err = vrfContracts.CoordinatorV2Plus.GetSubscription(testcontext.Get(t), subID) + require.NoError(t, err) + subBalanceAfterRequest := subscription.NativeBalance + require.Equal(t, expectedSubBalanceWei, subBalanceAfterRequest) - jobRuns, err := nodeTypeToNodeMap[vrfcommon.VRF].CLNode.API.MustReadRunsByJob(nodeTypeToNodeMap[vrfcommon.VRF].Job.Data.ID) - require.NoError(t, err, "error reading job runs") - require.Equal(t, len(jobRunsBeforeTest.Data)+1, len(jobRuns.Data)) + // verify that the actual sub payment is within the expected range - this cannot be checked on SIMULATED env + coordinatorConfig, err := vrfContracts.CoordinatorV2Plus.GetConfig(testcontext.Get(t)) + require.NoError(t, err, "error getting coordinator config") + //check that Coordinator config is correct + require.Equal(t, *configCopy.VRFv2Plus.General.FulfillmentFlatFeeNativePPM, coordinatorConfig.FulfillmentFlatFeeNativePPM) + require.Equal(t, *configCopy.VRFv2Plus.General.NativePremiumPercentage, coordinatorConfig.NativePremiumPercentage) + require.Equal(t, *configCopy.VRFv2Plus.General.GasAfterPaymentCalculation, coordinatorConfig.GasAfterPaymentCalculation) + // check that the actual sub payment is within the expected range and is according to the coordinator config's billing settings + fulfillmentTxHash := randomWordsFulfilledEvent.Raw.TxHash + fulfillmentTxReceipt, err := sethClient.Client.TransactionReceipt(testcontext.Get(t), fulfillmentTxHash) + require.NoError(t, err, "error getting fulfilment tx receipt") + + txGasUsed := new(big.Int).SetUint64(fulfillmentTxReceipt.GasUsed) + // we don't have that information for older Geth versions + if fulfillmentTxReceipt.EffectiveGasPrice == nil { + fulfillmentTxReceipt.EffectiveGasPrice = new(big.Int).SetUint64(0) + } + fulfillmentTxFeeWei := new(big.Int).Mul(txGasUsed, fulfillmentTxReceipt.EffectiveGasPrice) + + premiumFeeInDecimal := new(big.Float).Quo(big.NewFloat(float64(*configCopy.VRFv2Plus.General.CoordinatorNativePremiumPercentage)), big.NewFloat(100)) + premiumFeeRatio := new(big.Float).Add(premiumFeeInDecimal, big.NewFloat(1)) + // since - tx fee * (premium fee in decimal + 1) = expected sub payment + fulfillmentTxFeeWeiFloat64, _ := fulfillmentTxFeeWei.Float64() + expectedSubPaymentWeiWithoutFlatFee := new(big.Float).Mul(big.NewFloat(fulfillmentTxFeeWeiFloat64), premiumFeeRatio) + flatFee := new(big.Float).Mul(big.NewFloat(float64(*configCopy.VRFv2Plus.General.FulfillmentFlatFeeNativePPM)), big.NewFloat(1e12)) + expectedSubPaymentWeiWitFlatFee := new(big.Float).Add(expectedSubPaymentWeiWithoutFlatFee, flatFee) + vrfv2plus.LogPaymentDetails(l, fulfillmentTxFeeWei, fulfillmentTxReceipt, actualSubPaymentWei, expectedSubPaymentWeiWitFlatFee, configCopy) + actualSubPaymentWeiFloat, _ := actualSubPaymentWei.Float64() + expectedSubPaymentWeiFloat, _ := expectedSubPaymentWeiWitFlatFee.Float64() + + //verify that the actual sub payment is more than TX fee + require.Equal(t, 1, actualSubPaymentWei.Cmp(fulfillmentTxFeeWei), "the actual sub payment has to be more than the TX fee") + + tolerance := *testConfig.SubBillingTolerance + isWithinTolerance, diff := actions.WithinTolerance(actualSubPaymentWeiFloat, expectedSubPaymentWeiFloat, tolerance) + require.True(t, isWithinTolerance, fmt.Sprintf("Expected the actual sub payment to be within %f tolerance of the expected sub payment. Diff: %f", tolerance, diff)) + }) }) t.Run("Direct Funding", func(t *testing.T) { configCopy := config.MustCopy().(tc.TestConfig) - wrapperContracts, wrapperSubID, err := vrfv2plus.SetupVRFV2PlusWrapperEnvironment( + wrapperContracts, wrapperSubID, err := vrfv2plus.SetupVRFV2PlusWrapperUniverse( testcontext.Get(t), - l, sethClient, + vrfContracts, &configCopy, - vrfContracts.LinkToken, - vrfContracts.MockETHLINKFeed, - vrfContracts.CoordinatorV2Plus, vrfKey.KeyHash, 1, + l, ) require.NoError(t, err) t.Run("Link Billing", func(t *testing.T) { configCopy := config.MustCopy().(tc.TestConfig) testConfig := configCopy.VRFv2Plus.General - isNativeBilling := false + var isNativeBilling = false - wrapperConsumerJuelsBalanceBeforeRequest, err := vrfContracts.LinkToken.BalanceOf(testcontext.Get(t), wrapperContracts.LoadTestConsumers[0].Address()) + wrapperConsumerJuelsBalanceBeforeRequest, err := vrfContracts.LinkToken.BalanceOf(testcontext.Get(t), wrapperContracts.WrapperConsumers[0].Address()) require.NoError(t, err, "error getting wrapper consumer balance") wrapperSubscription, err := vrfContracts.CoordinatorV2Plus.GetSubscription(testcontext.Get(t), wrapperSubID) @@ -316,7 +298,7 @@ func TestVRFv2Plus(t *testing.T) { subBalanceBeforeRequest := wrapperSubscription.Balance randomWordsFulfilledEvent, err := vrfv2plus.DirectFundingRequestRandomnessAndWaitForFulfillment( - wrapperContracts.LoadTestConsumers[0], + wrapperContracts.WrapperConsumers[0], vrfContracts.CoordinatorV2Plus, vrfKey, wrapperSubID, @@ -332,18 +314,18 @@ func TestVRFv2Plus(t *testing.T) { subBalanceAfterRequest := wrapperSubscription.Balance require.Equal(t, expectedSubBalanceJuels, subBalanceAfterRequest) - consumerStatus, err := wrapperContracts.LoadTestConsumers[0].GetRequestStatus(testcontext.Get(t), randomWordsFulfilledEvent.RequestId) + consumerStatus, err := wrapperContracts.WrapperConsumers[0].GetRequestStatus(testcontext.Get(t), randomWordsFulfilledEvent.RequestId) require.NoError(t, err, "error getting rand request status") require.True(t, consumerStatus.Fulfilled) expectedWrapperConsumerJuelsBalance := new(big.Int).Sub(wrapperConsumerJuelsBalanceBeforeRequest, consumerStatus.Paid) - wrapperConsumerJuelsBalanceAfterRequest, err := vrfContracts.LinkToken.BalanceOf(testcontext.Get(t), wrapperContracts.LoadTestConsumers[0].Address()) + wrapperConsumerJuelsBalanceAfterRequest, err := vrfContracts.LinkToken.BalanceOf(testcontext.Get(t), wrapperContracts.WrapperConsumers[0].Address()) require.NoError(t, err, "error getting wrapper consumer balance") require.Equal(t, expectedWrapperConsumerJuelsBalance, wrapperConsumerJuelsBalanceAfterRequest) - // todo: uncomment when VRF-651 will be fixed - // require.Equal(t, 1, consumerStatus.Paid.Cmp(randomWordsFulfilledEvent.Payment), "Expected Consumer contract pay more than the Coordinator Sub") + //todo: uncomment when VRF-651 will be fixed + //require.Equal(t, 1, consumerStatus.Paid.Cmp(randomWordsFulfilledEvent.Payment), "Expected Consumer contract pay more than the Coordinator Sub") vrfcommon.LogFulfillmentDetailsLinkBilling(l, wrapperConsumerJuelsBalanceBeforeRequest, wrapperConsumerJuelsBalanceAfterRequest, consumerStatus, randomWordsFulfilledEvent) require.Equal(t, *testConfig.NumberOfWords, uint32(len(consumerStatus.RandomWords))) @@ -355,9 +337,9 @@ func TestVRFv2Plus(t *testing.T) { t.Run("Native Billing", func(t *testing.T) { configCopy := config.MustCopy().(tc.TestConfig) testConfig := configCopy.VRFv2Plus.General - isNativeBilling := true + var isNativeBilling = true - wrapperConsumerBalanceBeforeRequestWei, err := sethClient.Client.BalanceAt(testcontext.Get(t), common.HexToAddress(wrapperContracts.LoadTestConsumers[0].Address()), nil) + wrapperConsumerBalanceBeforeRequestWei, err := sethClient.Client.BalanceAt(testcontext.Get(t), common.HexToAddress(wrapperContracts.WrapperConsumers[0].Address()), nil) require.NoError(t, err, "error getting wrapper consumer balance") wrapperSubscription, err := vrfContracts.CoordinatorV2Plus.GetSubscription(testcontext.Get(t), wrapperSubID) @@ -365,7 +347,7 @@ func TestVRFv2Plus(t *testing.T) { subBalanceBeforeRequest := wrapperSubscription.NativeBalance randomWordsFulfilledEvent, err := vrfv2plus.DirectFundingRequestRandomnessAndWaitForFulfillment( - wrapperContracts.LoadTestConsumers[0], + wrapperContracts.WrapperConsumers[0], vrfContracts.CoordinatorV2Plus, vrfKey, wrapperSubID, @@ -381,18 +363,18 @@ func TestVRFv2Plus(t *testing.T) { subBalanceAfterRequest := wrapperSubscription.NativeBalance require.Equal(t, expectedSubBalanceWei, subBalanceAfterRequest) - consumerStatus, err := wrapperContracts.LoadTestConsumers[0].GetRequestStatus(testcontext.Get(t), randomWordsFulfilledEvent.RequestId) + consumerStatus, err := wrapperContracts.WrapperConsumers[0].GetRequestStatus(testcontext.Get(t), randomWordsFulfilledEvent.RequestId) require.NoError(t, err, "error getting rand request status") require.True(t, consumerStatus.Fulfilled) expectedWrapperConsumerWeiBalance := new(big.Int).Sub(wrapperConsumerBalanceBeforeRequestWei, consumerStatus.Paid) - wrapperConsumerBalanceAfterRequestWei, err := sethClient.Client.BalanceAt(testcontext.Get(t), common.HexToAddress(wrapperContracts.LoadTestConsumers[0].Address()), nil) + wrapperConsumerBalanceAfterRequestWei, err := sethClient.Client.BalanceAt(testcontext.Get(t), common.HexToAddress(wrapperContracts.WrapperConsumers[0].Address()), nil) require.NoError(t, err, "error getting wrapper consumer balance") require.Equal(t, expectedWrapperConsumerWeiBalance, wrapperConsumerBalanceAfterRequestWei) - // todo: uncomment when VRF-651 will be fixed - // require.Equal(t, 1, consumerStatus.Paid.Cmp(randomWordsFulfilledEvent.Payment), "Expected Consumer contract pay more than the Coordinator Sub") + //todo: uncomment when VRF-651 will be fixed + //require.Equal(t, 1, consumerStatus.Paid.Cmp(randomWordsFulfilledEvent.Payment), "Expected Consumer contract pay more than the Coordinator Sub") vrfcommon.LogFulfillmentDetailsNativeBilling(l, wrapperConsumerBalanceBeforeRequestWei, wrapperConsumerBalanceAfterRequestWei, consumerStatus, randomWordsFulfilledEvent) require.Equal(t, *testConfig.NumberOfWords, uint32(len(consumerStatus.RandomWords))) @@ -402,6 +384,92 @@ func TestVRFv2Plus(t *testing.T) { } }) }) + t.Run("VRF Node waits block confirmation number specified by the consumer before sending fulfilment on-chain", func(t *testing.T) { + configCopy := config.MustCopy().(tc.TestConfig) + testConfig := configCopy.VRFv2Plus.General + isNativeBilling := true + + consumers, subIDs, err := vrfv2plus.SetupNewConsumersAndSubs( + testcontext.Get(t), + sethClient, + vrfContracts.CoordinatorV2Plus, + configCopy, + vrfContracts.LinkToken, + 1, + 1, + l, + ) + require.NoError(t, err, "error setting up new consumers and subs") + subID := subIDs[0] + subscription, err := vrfContracts.CoordinatorV2Plus.GetSubscription(testcontext.Get(t), subID) + require.NoError(t, err, "error getting subscription information") + vrfcommon.LogSubDetails(l, subscription, subID.String(), vrfContracts.CoordinatorV2Plus) + subIDsForCancellingAfterTest = append(subIDsForCancellingAfterTest, subIDs...) + + expectedBlockNumberWait := uint16(10) + testConfig.MinimumConfirmations = ptr.Ptr[uint16](expectedBlockNumberWait) + randomWordsRequestedEvent, randomWordsFulfilledEvent, err := vrfv2plus.RequestRandomnessAndWaitForFulfillment( + consumers[0], + vrfContracts.CoordinatorV2Plus, + vrfKey, + subID, + isNativeBilling, + testConfig, + l, + 0, + ) + require.NoError(t, err, "error requesting randomness and waiting for fulfilment") + + // check that VRF node waited at least the number of blocks specified by the consumer in the rand request min confs field + blockNumberWait := randomWordsRequestedEvent.Raw.BlockNumber - randomWordsFulfilledEvent.Raw.BlockNumber + require.GreaterOrEqual(t, blockNumberWait, uint64(expectedBlockNumberWait)) + + status, err := consumers[0].GetRequestStatus(testcontext.Get(t), randomWordsFulfilledEvent.RequestId) + require.NoError(t, err, "error getting rand request status") + require.True(t, status.Fulfilled) + l.Info().Bool("Fulfilment Status", status.Fulfilled).Msg("Random Words Request Fulfilment Status") + }) + t.Run("CL Node VRF Job Runs", func(t *testing.T) { + configCopy := config.MustCopy().(tc.TestConfig) + var isNativeBilling = false + + consumers, subIDsForRequestRandomness, err := vrfv2plus.SetupNewConsumersAndSubs( + testcontext.Get(t), + sethClient, + vrfContracts.CoordinatorV2Plus, + configCopy, + vrfContracts.LinkToken, + 1, + 1, + l, + ) + require.NoError(t, err, "error setting up new consumers and subs") + subIDForRequestRandomness := subIDsForRequestRandomness[0] + subscription, err := vrfContracts.CoordinatorV2Plus.GetSubscription(testcontext.Get(t), subIDForRequestRandomness) + require.NoError(t, err, "error getting subscription information") + vrfcommon.LogSubDetails(l, subscription, subIDForRequestRandomness.String(), vrfContracts.CoordinatorV2Plus) + subIDsForCancellingAfterTest = append(subIDsForCancellingAfterTest, subIDsForRequestRandomness...) + + jobRunsBeforeTest, err := nodeTypeToNodeMap[vrfcommon.VRF].CLNode.API.MustReadRunsByJob(nodeTypeToNodeMap[vrfcommon.VRF].Job.Data.ID) + require.NoError(t, err, "error reading job runs") + + // test and assert + _, _, err = vrfv2plus.RequestRandomnessAndWaitForFulfillment( + consumers[0], + vrfContracts.CoordinatorV2Plus, + vrfKey, + subIDForRequestRandomness, + isNativeBilling, + configCopy.VRFv2Plus.General, + l, + 0, + ) + require.NoError(t, err, "error requesting randomness and waiting for fulfilment") + + jobRuns, err := nodeTypeToNodeMap[vrfcommon.VRF].CLNode.API.MustReadRunsByJob(nodeTypeToNodeMap[vrfcommon.VRF].Job.Data.ID) + require.NoError(t, err, "error reading job runs") + require.Equal(t, len(jobRunsBeforeTest.Data)+1, len(jobRuns.Data)) + }) t.Run("Canceling Sub And Returning Funds", func(t *testing.T) { configCopy := config.MustCopy().(tc.TestConfig) @@ -749,30 +817,13 @@ func TestVRFv2PlusMultipleSendingKeys(t *testing.T) { config, err := tc.GetChainAndTestTypeSpecificConfig("Smoke", tc.VRFv2Plus) require.NoError(t, err, "Error getting config") - vrfv2PlusConfig := config.VRFv2Plus chainID := networks.MustGetSelectedNetworkConfig(config.GetNetworkConfig())[0].ChainID + configPtr := &config - cleanupFn := func() { - if sethClient.Cfg.IsSimulatedNetwork() { - l.Info(). - Str("Network Name", sethClient.Cfg.Network.Name). - Msg("Network is a simulated network. Skipping fund return for Coordinator Subscriptions.") - } else { - if *vrfv2PlusConfig.General.CancelSubsAfterTestRun { - //cancel subs and return funds to sub owner - vrfv2plus.CancelSubsAndReturnFunds(testcontext.Get(t), vrfContracts, sethClient.MustGetRootKeyAddress().Hex(), subIDsForCancellingAfterTest, l) - } - } - if !*vrfv2PlusConfig.General.UseExistingEnv { - if err := env.Cleanup(test_env.CleanupOpts{TestName: t.Name()}); err != nil { - l.Error().Err(err).Msg("Error cleaning up test environment") - } - } - } vrfEnvConfig := vrfcommon.VRFEnvConfig{ TestConfig: config, ChainID: chainID, - CleanupFn: cleanupFn, + CleanupFn: vrfv2PlusCleanUpFn(&t, &sethClient, &configPtr, &env, &vrfContracts, &subIDsForCancellingAfterTest), } newEnvConfig := vrfcommon.NewEnvConfig{ NodesToCreate: []vrfcommon.VRFNodeType{vrfcommon.VRF}, @@ -854,30 +905,13 @@ func TestVRFv2PlusMigration(t *testing.T) { config, err := tc.GetChainAndTestTypeSpecificConfig("Smoke", tc.VRFv2Plus) require.NoError(t, err, "Error getting config") - vrfv2PlusConfig := config.VRFv2Plus chainID := networks.MustGetSelectedNetworkConfig(config.GetNetworkConfig())[0].ChainID + configPtr := &config - cleanupFn := func() { - if sethClient.Cfg.IsSimulatedNetwork() { - l.Info(). - Str("Network Name", sethClient.Cfg.Network.Name). - Msg("Network is a simulated network. Skipping fund return for Coordinator Subscriptions.") - } else { - if *vrfv2PlusConfig.General.CancelSubsAfterTestRun { - //cancel subs and return funds to sub owner - vrfv2plus.CancelSubsAndReturnFunds(testcontext.Get(t), vrfContracts, sethClient.MustGetRootKeyAddress().Hex(), subIDsForCancellingAfterTest, l) - } - } - if !*vrfv2PlusConfig.General.UseExistingEnv { - if err := env.Cleanup(test_env.CleanupOpts{TestName: t.Name()}); err != nil { - l.Error().Err(err).Msg("Error cleaning up test environment") - } - } - } vrfEnvConfig := vrfcommon.VRFEnvConfig{ TestConfig: config, ChainID: chainID, - CleanupFn: cleanupFn, + CleanupFn: vrfv2PlusCleanUpFn(&t, &sethClient, &configPtr, &env, &vrfContracts, &subIDsForCancellingAfterTest), } newEnvConfig := vrfcommon.NewEnvConfig{ NodesToCreate: []vrfcommon.VRFNodeType{vrfcommon.VRF}, @@ -1063,16 +1097,14 @@ func TestVRFv2PlusMigration(t *testing.T) { t.Run("Test migration of direct billing using VRFV2PlusWrapper subID", func(t *testing.T) { configCopy := config.MustCopy().(tc.TestConfig) - wrapperContracts, wrapperSubID, err := vrfv2plus.SetupVRFV2PlusWrapperEnvironment( + wrapperContracts, wrapperSubID, err := vrfv2plus.SetupVRFV2PlusWrapperUniverse( testcontext.Get(t), - l, sethClient, + vrfContracts, &configCopy, - vrfContracts.LinkToken, - vrfContracts.MockETHLINKFeed, - vrfContracts.CoordinatorV2Plus, vrfKey.KeyHash, 1, + l, ) require.NoError(t, err) subID := wrapperSubID @@ -1203,7 +1235,7 @@ func TestVRFv2PlusMigration(t *testing.T) { // Verify rand requests fulfills with Link Token billing isNativeBilling := false randomWordsFulfilledEvent, err := vrfv2plus.DirectFundingRequestRandomnessAndWaitForFulfillment( - wrapperContracts.LoadTestConsumers[0], + wrapperContracts.WrapperConsumers[0], newCoordinator, vrfKey, subID, @@ -1212,14 +1244,14 @@ func TestVRFv2PlusMigration(t *testing.T) { l, ) require.NoError(t, err, "error requesting randomness and waiting for fulfilment") - consumerStatus, err := wrapperContracts.LoadTestConsumers[0].GetRequestStatus(testcontext.Get(t), randomWordsFulfilledEvent.RequestId) + consumerStatus, err := wrapperContracts.WrapperConsumers[0].GetRequestStatus(testcontext.Get(t), randomWordsFulfilledEvent.RequestId) require.NoError(t, err, "error getting rand request status") require.True(t, consumerStatus.Fulfilled) // Verify rand requests fulfills with Native Token billing isNativeBilling = true randomWordsFulfilledEvent, err = vrfv2plus.DirectFundingRequestRandomnessAndWaitForFulfillment( - wrapperContracts.LoadTestConsumers[0], + wrapperContracts.WrapperConsumers[0], newCoordinator, vrfKey, subID, @@ -1228,7 +1260,7 @@ func TestVRFv2PlusMigration(t *testing.T) { l, ) require.NoError(t, err, "error requesting randomness and waiting for fulfilment") - consumerStatus, err = wrapperContracts.LoadTestConsumers[0].GetRequestStatus(testcontext.Get(t), randomWordsFulfilledEvent.RequestId) + consumerStatus, err = wrapperContracts.WrapperConsumers[0].GetRequestStatus(testcontext.Get(t), randomWordsFulfilledEvent.RequestId) require.NoError(t, err, "error getting rand request status") require.True(t, consumerStatus.Fulfilled) }) @@ -1250,24 +1282,7 @@ func TestVRFV2PlusWithBHS(t *testing.T) { require.NoError(t, err, "Error getting config") vrfv2PlusConfig := config.VRFv2Plus chainID := networks.MustGetSelectedNetworkConfig(config.GetNetworkConfig())[0].ChainID - - cleanupFn := func() { - if sethClient.Cfg.IsSimulatedNetwork() { - l.Info(). - Str("Network Name", sethClient.Cfg.Network.Name). - Msg("Network is a simulated network. Skipping fund return for Coordinator Subscriptions.") - } else { - if *vrfv2PlusConfig.General.CancelSubsAfterTestRun { - //cancel subs and return funds to sub owner - vrfv2plus.CancelSubsAndReturnFunds(testcontext.Get(t), vrfContracts, sethClient.MustGetRootKeyAddress().Hex(), subIDsForCancellingAfterTest, l) - } - } - if !*vrfv2PlusConfig.General.UseExistingEnv { - if err := env.Cleanup(test_env.CleanupOpts{TestName: t.Name()}); err != nil { - l.Error().Err(err).Msg("Error cleaning up test environment") - } - } - } + configPtr := &config // decrease default span for checking blockhashes for unfulfilled requests vrfv2PlusConfig.General.BHSJobWaitBlocks = ptr.Ptr(2) @@ -1275,7 +1290,7 @@ func TestVRFV2PlusWithBHS(t *testing.T) { vrfEnvConfig := vrfcommon.VRFEnvConfig{ TestConfig: config, ChainID: chainID, - CleanupFn: cleanupFn, + CleanupFn: vrfv2PlusCleanUpFn(&t, &sethClient, &configPtr, &env, &vrfContracts, &subIDsForCancellingAfterTest), } newEnvConfig := vrfcommon.NewEnvConfig{ NodesToCreate: []vrfcommon.VRFNodeType{vrfcommon.VRF, vrfcommon.BHS}, @@ -1347,11 +1362,10 @@ func TestVRFV2PlusWithBHS(t *testing.T) { }() if *configCopy.VRFv2Plus.General.GenerateTXsOnChain { + wg.Add(1) go func() { - _, err := actions.ContinuouslyGenerateTXsOnChain(sethClient, desiredBlockNumberReached, l) + _, err := actions.ContinuouslyGenerateTXsOnChain(sethClient, desiredBlockNumberReached, &wg, l) require.NoError(t, err) - // Wait to let the transactions be mined and avoid nonce issues - time.Sleep(time.Second * 5) }() } wg.Wait() @@ -1449,28 +1463,37 @@ func TestVRFV2PlusWithBHS(t *testing.T) { metrics, err := consumers[0].GetLoadTestMetrics(testcontext.Get(t)) require.Equal(t, 0, metrics.RequestCount.Cmp(big.NewInt(1))) require.Equal(t, 0, metrics.FulfilmentCount.Cmp(big.NewInt(0))) - - var clNodeTxs *client.TransactionsData - var txHash string gom := gomega.NewGomegaWithT(t) - gom.Eventually(func(g gomega.Gomega) { - clNodeTxs, _, err = nodeTypeToNodeMap[vrfcommon.BHS].CLNode.API.ReadTransactions() - g.Expect(err).ShouldNot(gomega.HaveOccurred(), "error getting CL Node transactions") - l.Info().Int("Number of TXs", len(clNodeTxs.Data)).Msg("BHS Node txs") - g.Expect(len(clNodeTxs.Data)).Should(gomega.BeNumerically("==", 1), "Expected 1 tx posted by BHS Node, but found %d", len(clNodeTxs.Data)) - txHash = clNodeTxs.Data[0].Attributes.Hash - }, "2m", "1s").Should(gomega.Succeed()) - - require.Equal(t, strings.ToLower(vrfContracts.BHS.Address()), strings.ToLower(clNodeTxs.Data[0].Attributes.To)) - bhsStoreTx, _, err := sethClient.Client.TransactionByHash(testcontext.Get(t), common.HexToHash(txHash)) - require.NoError(t, err, "error getting tx from hash") + if !*configCopy.VRFv2Plus.General.UseExistingEnv { + l.Info().Msg("Checking BHS Node's transactions") + var clNodeTxs *client.TransactionsData + var txHash string + gom.Eventually(func(g gomega.Gomega) { + clNodeTxs, _, err = nodeTypeToNodeMap[vrfcommon.BHS].CLNode.API.ReadTransactions() + g.Expect(err).ShouldNot(gomega.HaveOccurred(), "error getting CL Node transactions") + g.Expect(len(clNodeTxs.Data)).Should(gomega.BeNumerically("==", 1), "Expected 1 tx posted by BHS Node, but found %d", len(clNodeTxs.Data)) + txHash = clNodeTxs.Data[0].Attributes.Hash + l.Info(). + Str("TX Hash", txHash). + Int("Number of TXs", len(clNodeTxs.Data)). + Msg("BHS Node txs") + }, "2m", "1s").Should(gomega.Succeed()) + + require.Equal(t, strings.ToLower(vrfContracts.BHS.Address()), strings.ToLower(clNodeTxs.Data[0].Attributes.To)) + + bhsStoreTx, _, err := sethClient.Client.TransactionByHash(testcontext.Get(t), common.HexToHash(txHash)) + require.NoError(t, err, "error getting tx from hash") - bhsStoreTxInputData, err := actions.DecodeTxInputData(blockhash_store.BlockhashStoreABI, bhsStoreTx.Data()) - l.Info(). - Str("Block Number", bhsStoreTxInputData["n"].(*big.Int).String()). - Msg("BHS Node's Store Blockhash for Blocknumber Method TX") - require.Equal(t, randRequestBlockNumber, bhsStoreTxInputData["n"].(*big.Int).Uint64()) + bhsStoreTxInputData, err := actions.DecodeTxInputData(blockhash_store.BlockhashStoreABI, bhsStoreTx.Data()) + require.NoError(t, err, "error decoding tx input data") + l.Info(). + Str("Block Number", bhsStoreTxInputData["n"].(*big.Int).String()). + Msg("BHS Node's Store Blockhash for Blocknumber Method TX") + require.Equal(t, randRequestBlockNumber, bhsStoreTxInputData["n"].(*big.Int).Uint64()) + } else { + l.Warn().Msg("Skipping BHS Node's transactions check as existing env is used") + } var randRequestBlockHash [32]byte gom.Eventually(func(g gomega.Gomega) { @@ -1499,28 +1522,10 @@ func TestVRFV2PlusWithBHF(t *testing.T) { config, err := tc.GetChainAndTestTypeSpecificConfig("Smoke", tc.VRFv2Plus) require.NoError(t, err, "Error getting config") - vrfv2PlusConfig := config.VRFv2Plus chainID := networks.MustGetSelectedNetworkConfig(config.GetNetworkConfig())[0].ChainID + configPtr := &config - cleanupFn := func() { - if sethClient.Cfg.IsSimulatedNetwork() { - l.Info(). - Str("Network Name", sethClient.Cfg.Network.Name). - Msg("Network is a simulated network. Skipping fund return for Coordinator Subscriptions.") - } else { - if *vrfv2PlusConfig.General.CancelSubsAfterTestRun { - //cancel subs and return funds to sub owner - vrfv2plus.CancelSubsAndReturnFunds(testcontext.Get(t), vrfContracts, sethClient.MustGetRootKeyAddress().Hex(), subIDsForCancellingAfterTest, l) - } - } - if !*vrfv2PlusConfig.General.UseExistingEnv { - if err := env.Cleanup(test_env.CleanupOpts{TestName: t.Name()}); err != nil { - l.Error().Err(err).Msg("Error cleaning up test environment") - } - } - } - - // BHF job config + // BHF job config - NOTE - not possible to create BHF job spec with waitBlocks being less than 256 blocks config.VRFv2Plus.General.BHFJobWaitBlocks = ptr.Ptr(260) config.VRFv2Plus.General.BHFJobLookBackBlocks = ptr.Ptr(500) config.VRFv2Plus.General.BHFJobPollPeriod = ptr.Ptr(blockchain.StrDuration{Duration: time.Second * 30}) @@ -1528,7 +1533,7 @@ func TestVRFV2PlusWithBHF(t *testing.T) { vrfEnvConfig := vrfcommon.VRFEnvConfig{ TestConfig: config, ChainID: chainID, - CleanupFn: cleanupFn, + CleanupFn: vrfv2PlusCleanUpFn(&t, &sethClient, &configPtr, &env, &vrfContracts, &subIDsForCancellingAfterTest), } chainlinkNodeLogScannerSettings := test_env.GetDefaultChainlinkNodeLogScannerSettingsWithExtraAllowedMessages(testreporters.NewAllowedLogMessage( "Pipeline error", @@ -1662,30 +1667,13 @@ func TestVRFv2PlusReplayAfterTimeout(t *testing.T) { config, err := tc.GetChainAndTestTypeSpecificConfig("Smoke", tc.VRFv2Plus) require.NoError(t, err, "Error getting config") - vrfv2PlusConfig := config.VRFv2Plus chainID := networks.MustGetSelectedNetworkConfig(config.GetNetworkConfig())[0].ChainID + configPtr := &config - cleanupFn := func() { - if sethClient.Cfg.IsSimulatedNetwork() { - l.Info(). - Str("Network Name", sethClient.Cfg.Network.Name). - Msg("Network is a simulated network. Skipping fund return for Coordinator Subscriptions.") - } else { - if *vrfv2PlusConfig.General.CancelSubsAfterTestRun { - //cancel subs and return funds to sub owner - vrfv2plus.CancelSubsAndReturnFunds(testcontext.Get(t), vrfContracts, sethClient.MustGetRootKeyAddress().Hex(), subIDsForCancellingAfterTest, l) - } - } - if !*vrfv2PlusConfig.General.UseExistingEnv { - if err := env.Cleanup(test_env.CleanupOpts{TestName: t.Name()}); err != nil { - l.Error().Err(err).Msg("Error cleaning up test environment") - } - } - } vrfEnvConfig := vrfcommon.VRFEnvConfig{ TestConfig: config, ChainID: chainID, - CleanupFn: cleanupFn, + CleanupFn: vrfv2PlusCleanUpFn(&t, &sethClient, &configPtr, &env, &vrfContracts, &subIDsForCancellingAfterTest), } newEnvConfig := vrfcommon.NewEnvConfig{ NodesToCreate: []vrfcommon.VRFNodeType{vrfcommon.VRF}, @@ -1835,30 +1823,13 @@ func TestVRFv2PlusPendingBlockSimulationAndZeroConfirmationDelays(t *testing.T) config, err := tc.GetChainAndTestTypeSpecificConfig("Smoke", tc.VRFv2Plus) require.NoError(t, err, "Error getting config") - vrfv2PlusConfig := config.VRFv2Plus chainID := networks.MustGetSelectedNetworkConfig(config.GetNetworkConfig())[0].ChainID + configPtr := &config - cleanupFn := func() { - if sethClient.Cfg.IsSimulatedNetwork() { - l.Info(). - Str("Network Name", sethClient.Cfg.Network.Name). - Msg("Network is a simulated network. Skipping fund return for Coordinator Subscriptions.") - } else { - if *vrfv2PlusConfig.General.CancelSubsAfterTestRun { - //cancel subs and return funds to sub owner - vrfv2plus.CancelSubsAndReturnFunds(testcontext.Get(t), vrfContracts, sethClient.MustGetRootKeyAddress().Hex(), subIDsForCancellingAfterTest, l) - } - } - if !*vrfv2PlusConfig.General.UseExistingEnv { - if err := env.Cleanup(test_env.CleanupOpts{TestName: t.Name()}); err != nil { - l.Error().Err(err).Msg("Error cleaning up test environment") - } - } - } vrfEnvConfig := vrfcommon.VRFEnvConfig{ TestConfig: config, ChainID: chainID, - CleanupFn: cleanupFn, + CleanupFn: vrfv2PlusCleanUpFn(&t, &sethClient, &configPtr, &env, &vrfContracts, &subIDsForCancellingAfterTest), } newEnvConfig := vrfcommon.NewEnvConfig{ NodesToCreate: []vrfcommon.VRFNodeType{vrfcommon.VRF}, @@ -1921,7 +1892,6 @@ func TestVRFv2PlusNodeReorg(t *testing.T) { env *test_env.CLClusterTestEnv vrfContracts *vrfcommon.VRFContracts subIDsForCancellingAfterTest []*big.Int - defaultWalletAddress string vrfKey *vrfcommon.VRFKeyData sethClient *seth.Client ) @@ -1929,37 +1899,21 @@ func TestVRFv2PlusNodeReorg(t *testing.T) { config, err := tc.GetChainAndTestTypeSpecificConfig("Smoke", tc.VRFv2Plus) require.NoError(t, err, "Error getting config") - vrfv2PlusConfig := config.VRFv2Plus network := networks.MustGetSelectedNetworkConfig(config.GetNetworkConfig())[0] if !network.Simulated { t.Skip("Skipped since Reorg test could only be run on Simulated chain.") } chainID := network.ChainID - cleanupFn := func() { - if sethClient.Cfg.IsSimulatedNetwork() { - l.Info(). - Str("Network Name", sethClient.Cfg.Network.Name). - Msg("Network is a simulated network. Skipping fund return for Coordinator Subscriptions.") - } else { - if *vrfv2PlusConfig.General.CancelSubsAfterTestRun { - // cancel subs and return funds to sub owner - vrfv2plus.CancelSubsAndReturnFunds(testcontext.Get(t), vrfContracts, defaultWalletAddress, subIDsForCancellingAfterTest, l) - } - } - if !*vrfv2PlusConfig.General.UseExistingEnv { - if err := env.Cleanup(test_env.CleanupOpts{TestName: t.Name()}); err != nil { - l.Error().Err(err).Msg("Error cleaning up test environment") - } - } - } + configPtr := &config + vrfEnvConfig := vrfcommon.VRFEnvConfig{ TestConfig: config, ChainID: chainID, - CleanupFn: cleanupFn, + CleanupFn: vrfv2PlusCleanUpFn(&t, &sethClient, &configPtr, &env, &vrfContracts, &subIDsForCancellingAfterTest), } chainlinkNodeLogScannerSettings := test_env.GetDefaultChainlinkNodeLogScannerSettingsWithExtraAllowedMessages( testreporters.NewAllowedLogMessage( - "This is a problem and either means a very deep re-org occurred", + "Got very old block.", "Test is expecting a reorg to occur", zapcore.DPanicLevel, testreporters.WarnAboutAllowedMsgs_No), @@ -2099,7 +2053,6 @@ func TestVRFv2PlusBatchFulfillmentEnabledDisabled(t *testing.T) { env *test_env.CLClusterTestEnv vrfContracts *vrfcommon.VRFContracts subIDsForCancellingAfterTest []*big.Int - defaultWalletAddress string vrfKey *vrfcommon.VRFKeyData nodeTypeToNodeMap map[vrfcommon.VRFNodeType]*vrfcommon.VRFNode sethClient *seth.Client @@ -2108,30 +2061,14 @@ func TestVRFv2PlusBatchFulfillmentEnabledDisabled(t *testing.T) { config, err := tc.GetChainAndTestTypeSpecificConfig("Smoke", tc.VRFv2Plus) require.NoError(t, err, "Error getting config") - vrfv2PlusConfig := config.VRFv2Plus network := networks.MustGetSelectedNetworkConfig(config.GetNetworkConfig())[0] chainID := network.ChainID - cleanupFn := func() { - if sethClient.Cfg.IsSimulatedNetwork() { - l.Info(). - Str("Network Name", sethClient.Cfg.Network.Name). - Msg("Network is a simulated network. Skipping fund return for Coordinator Subscriptions.") - } else { - if *vrfv2PlusConfig.General.CancelSubsAfterTestRun { - //cancel subs and return funds to sub owner - vrfv2plus.CancelSubsAndReturnFunds(testcontext.Get(t), vrfContracts, defaultWalletAddress, subIDsForCancellingAfterTest, l) - } - } - if !*vrfv2PlusConfig.General.UseExistingEnv { - if err := env.Cleanup(test_env.CleanupOpts{TestName: t.Name()}); err != nil { - l.Error().Err(err).Msg("Error cleaning up test environment") - } - } - } + configPtr := &config + vrfEnvConfig := vrfcommon.VRFEnvConfig{ TestConfig: config, ChainID: chainID, - CleanupFn: cleanupFn, + CleanupFn: vrfv2PlusCleanUpFn(&t, &sethClient, &configPtr, &env, &vrfContracts, &subIDsForCancellingAfterTest), } newEnvConfig := vrfcommon.NewEnvConfig{ NodesToCreate: []vrfcommon.VRFNodeType{vrfcommon.VRF}, diff --git a/integration-tests/soak/forwarder_ocr_test.go b/integration-tests/soak/forwarder_ocr_test.go index 3017a1cef2..292693d509 100644 --- a/integration-tests/soak/forwarder_ocr_test.go +++ b/integration-tests/soak/forwarder_ocr_test.go @@ -42,6 +42,11 @@ func executeForwarderOCRSoakTest(t *testing.T, config *tc.TestConfig) { t.Cleanup(func() { if err := actions.TeardownRemoteSuite(ocrSoakTest.TearDownVals(t)); err != nil { l.Error().Err(err).Msg("Error tearing down environment") + } else { + err := ocrSoakTest.Environment().Client.RemoveNamespace(ocrSoakTest.Environment().Cfg.Namespace) + if err != nil { + l.Error().Err(err).Msg("Error removing namespace") + } } }) ocrSoakTest.Setup(config) diff --git a/integration-tests/soak/ocr_test.go b/integration-tests/soak/ocr_test.go index 5e53c7d098..56e462ef46 100644 --- a/integration-tests/soak/ocr_test.go +++ b/integration-tests/soak/ocr_test.go @@ -4,8 +4,6 @@ import ( "fmt" "testing" - "github.com/smartcontractkit/chainlink-testing-framework/havoc" - seth_utils "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/seth" "github.com/stretchr/testify/require" @@ -18,6 +16,8 @@ import ( "github.com/google/uuid" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/smartcontractkit/chainlink-testing-framework/havoc" + "github.com/smartcontractkit/chainlink-testing-framework/lib/networks" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/ptr" "github.com/smartcontractkit/chainlink/integration-tests/actions" @@ -161,6 +161,11 @@ func executeOCRSoakTest(t *testing.T, test *testsetups.OCRSoakTest, config *tc.T t.Cleanup(func() { if err := actions.TeardownRemoteSuite(test.TearDownVals(t)); err != nil { l.Error().Err(err).Msg("Error tearing down environment") + } else { + err := test.Environment().Client.RemoveNamespace(test.Environment().Cfg.Namespace) + if err != nil { + l.Error().Err(err).Msg("Error removing namespace") + } } }) if test.Interrupted() { diff --git a/integration-tests/test.Dockerfile b/integration-tests/test.Dockerfile index fc6eefd650..6252cfdd42 100644 --- a/integration-tests/test.Dockerfile +++ b/integration-tests/test.Dockerfile @@ -1,10 +1,31 @@ ARG BASE_IMAGE ARG IMAGE_VERSION=latest -FROM ${BASE_IMAGE}:${IMAGE_VERSION} - -ARG SUITES=chaos migration performance reorg smoke soak benchmark +FROM ${BASE_IMAGE}:${IMAGE_VERSION} AS build-env -COPY . testdir/ WORKDIR /go/testdir +RUN mkdir -p /go/testdir/integration-tests/load +COPY go.mod go.sum ./ +COPY integration-tests/go.mod integration-tests/go.sum ./integration-tests/ +COPY integration-tests/load/go.mod integration-tests/load/go.sum ./integration-tests/load/ +RUN cd integration-tests && go mod download +RUN cd integration-tests/load && go mod download + +COPY . . + +ARG SUITES=chaos soak benchmark load ccip-load + RUN /go/testdir/integration-tests/scripts/buildTests "${SUITES}" + +FROM ${BASE_IMAGE}:${IMAGE_VERSION} + +RUN mkdir -p /go/testdir/integration-tests/scripts +# Dependency of CosmWasm/wasmd +COPY --from=build-env /go/pkg/mod/github.com/\!cosm\!wasm/wasmvm@v*/internal/api/libwasmvm.*.so /usr/lib/ +RUN chmod 755 /usr/lib/libwasmvm.*.so +COPY --from=build-env /go/testdir/integration-tests/*.test /go/testdir/integration-tests/ +COPY --from=build-env /go/testdir/integration-tests/ccip-tests/*.test /go/testdir/integration-tests/ +COPY --from=build-env /go/testdir/integration-tests/scripts /go/testdir/integration-tests/scripts/ +RUN echo "All tests" +RUN ls -l /go/testdir/integration-tests/*.test + ENTRYPOINT ["/go/testdir/integration-tests/scripts/entrypoint"] diff --git a/integration-tests/testconfig/README.md b/integration-tests/testconfig/README.md index 7ff6cedd24..f618658055 100644 --- a/integration-tests/testconfig/README.md +++ b/integration-tests/testconfig/README.md @@ -23,9 +23,11 @@ The order of precedence for overrides is as follows: * [Environment variable `BASE64_CONFIG_OVERRIDE`](#base64_config_override) ### default.toml + That file is envisioned to contain fundamental and universally applicable settings, such as logging configurations, private Ethereum network settings or Seth networks settings for known networks. ### Product-specific configurations + Product-specific configurations, such as those in `[product_name].toml`, house the bulk of default and variant settings, supporting default configurations like the following in `log_poller.toml`, which should be used by all Log Poller tests: ```toml @@ -47,15 +49,16 @@ max_emit_wait_time_ms = 500 ``` ### overrides.toml + This file is recommended for local use to adjust dynamic variables or modify predefined settings. At the very minimum it should contain the Chainlink image and version, as shown in the example below: ```toml [ChainlinkImage] -image = "your image name" version = "your tag" ``` ### `BASE64_CONFIG_OVERRIDE` + This environment variable is primarily intended for use in continuous integration environments, enabling the substitution of default settings with confidential or user-specific parameters. For instance: ```bash @@ -64,7 +67,6 @@ cat << EOF > config.toml selected_networks=["$SELECTED_NETWORKS"] [ChainlinkImage] -image="" version="$CHAINLINK_VERSION" postgres_version="$CHAINLINK_POSTGRES_VERSION" @@ -80,6 +82,7 @@ BASE64_CONFIG_OVERRIDE=$(cat config.toml | base64 -w 0) echo ::add-mask::$BASE64_CONFIG_OVERRIDE echo "BASE64_CONFIG_OVERRIDE=$BASE64_CONFIG_OVERRIDE" >> $GITHUB_ENV ``` + **It is highly recommended to use reusable GHA actions present in [.actions](../../../.github/.actions) to generate and apply the base64-encoded configuration.** Own implementation of `BASE64_CONFIG_OVERRIDE` generation is discouraged and should be used only if existing actions do not cover the use case. But even in that case it might be a better idea to extend existing actions. This variable is automatically relayed to Kubernetes-based tests, eliminating the need for manual intervention in test scripts. @@ -89,6 +92,18 @@ Test secrets are not stored directly within the `TestConfig` TOML for security r For detailed instructions on how to set test secrets both locally and within CI environments, please visit: [Test Secrets Guide in CTF](https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/config/README.md#test-secrets) +### All test secrets + +See [All E2E Test Secrets in CTF](https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/config/README.md#all-e2e-test-secrets). + +### Core repo specific test secrets + +| Secret | Env Var | Example | Description | +| ----------------------------- | ------------------------------------------------------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------ | +| Data Streams Url | `E2E_TEST_DATA_STREAMS_URL` | `E2E_TEST_DATA_STREAMS_URL=url` | Required by some automation tests to connect to data streams. | +| Data Streams Username | `E2E_TEST_DATA_STREAMS_USERNAME` | `E2E_TEST_DATA_STREAMS_USERNAME=username` | Required by some automation tests to connect to data streams. | +| Data Streams Password | `E2E_TEST_DATA_STREAMS_PASSWORD` | `E2E_TEST_DATA_STREAMS_PASSWORD=password` | Required by some automation tests to connect to data streams. | + ## Named Configurations Named configurations allow for the customization of settings through unique identifiers, such as a test name or type, acting as specific overrides. Here's how you can define and use these configurations: @@ -116,6 +131,7 @@ When processing TOML files, the system initially searches for a general (unnamed Find default node config in `testconfig/default.toml` To set custom config for Chainlink Node use `NodeConfig.BaseConfigTOML` in TOML. Example: + ```toml [NodeConfig] BaseConfigTOML = """ @@ -136,10 +152,14 @@ Enabled = true DefaultTransactionQueueDepth = 0 """ ``` + Note that you cannot override individual values in BaseConfigTOML. You must provide the entire configuration. +This corresponds to [Config struct](../../core/services/chainlink/config.go) in Chainlink Node that excludes all chain-specific configuration, which is built based on selected_networks and either Chainlink Node's defaults for each network, or `ChainConfigTOMLByChainID` (if an entry with matching chain id is defined) or `CommonChainConfigTOML` (if no entry with matching chain id is defined). +If BaseConfigTOML is empty, then default base config provided by the Chainlink Node is used. If tracing is enabled unique id will be generated and shared between all Chainlink nodes in the same test. To set base config for EVM chains use `NodeConfig.CommonChainConfigTOML`. Example: + ```toml CommonChainConfigTOML = """ AutoCreateKey = true @@ -153,12 +173,13 @@ FeeCapDefault = '200 gwei' """ ``` -This is the default configuration used for all EVM chains unless ChainConfigTOMLByChainID is specified. +This is the default configuration used for all EVM chains unless `ChainConfigTOMLByChainID` is specified. Do remember that if either `ChainConfigTOMLByChainID` or `CommonChainConfigTOML` is defined, it will override any defaults that Chainlink Node might have for the given network. Part of the configuration that defines blockchain node URLs is always dynamically generated based on the EVMNetwork configuration. To set custom per-chain config use `[NodeConfig.ChainConfigTOMLByChainID]`. Example: + ```toml [NodeConfig.ChainConfigTOMLByChainID] -# applicable for arbitrum-goerli chain +# applicable only to arbitrum-goerli chain 421613 = """ [GasEstimator] PriceMax = '400 gwei' @@ -170,7 +191,98 @@ BumpMin = '100 gwei' """ ``` -For more examples see `example.toml` in product TOML configs like `testconfig/automation/example.toml`. +For more examples see `example.toml` in product TOML configs like `testconfig/automation/example.toml`. If either ChainConfigTOMLByChainID or CommonChainConfigTOML is defined, it will override any defaults that Chainlink Node might have for the given network. Part of the configuration that defines blockchain node URLs is always dynamically generated based on the EVMNetwork configuration. +Currently, all networks are treated as EVM networks. There's no way to provide Solana, Starknet, Cosmos or Aptos configuration yet. + +### OCR tests contract config +In order to allow running OCR soak/load/smoke tests with already deployed contracts, we have provided an experimental feature for providing addresses of LINK token and OCR contracts in the TOML config. Additionally, user can choose, whether existing OCR contracts should be configured or not. +If no contract addresses are provided, the tests will deploy new contracts. + +The feature is highly configurable and it possible to use existing LINK token contract, but deploy new OCR contracts or vice versa. Both OCRv1 and OCRv2 contracts are supported. + +To use existing LINK and OCRv1 contracts, provide the following configuration in the TOML file: +```toml +[OCR.Contracts] +link_token = "0x88d1239894D9582f5849E5b5a964da9e5730f1E6" +offchain_aggregators = ["0xc1ce3815d6e7f3705265c2577F1342344752A5Eb"] +``` + +For OCRv2, provide the following configuration: +```toml +[OCR2.Contracts] +link_token = "0x88d1239894D9582f5849E5b5a964da9e5730f1E6" +offchain_aggregators = ["0xc1ce3815d6e7f3705265c2577F1342344752A5Eb"] +``` + +If you want to disable them, you can set `use = false` or remove the addresses from the configuration. + +If you want to use existing OCRv1 contract, without configuring it, you can set `configure = false` in the configuration: +```toml +[OCR.Contracts] +link_token = "0x88d1239894D9582f5849E5b5a964da9e5730f1E6" +offchain_aggregators = ["0xc1ce3815d6e7f3705265c2577F1342344752A5Eb"] + +# notice that this address needs to match the one in offchain_aggregators +[OCR.Contracts.Settings."0xc1ce3815d6e7f3705265c2577F1342344752A5Eb"] +configure = false +``` + +Be aware that using multiple existing OCR contracts, but configuring only some of them is not supported. This is not a valid configuration: +```toml +[OCR.Contracts] +link_token = "0x88d1239894D9582f5849E5b5a964da9e5730f1E6" +offchain_aggregators = ["0xc1ce3815d6e7f3705265c2577F1342344752A5Eb", "0x2f4FA21fCd917C448C160caafEC874032F404c08"] + +# notice that this address needs to match the one in offchain_aggregators +[OCR.Contracts.Settings."0xc1ce3815d6e7f3705265c2577F1342344752A5Eb"] +configure = false + +# if setting for a given address is not present, we assume it should be used and configured +# so in this case "0x2f4FA21fCd917C448C160caafEC874032F404c08" will be evaluated as configure = true, +# but "0xc1ce3815d6e7f3705265c2577F1342344752A5Eb" is set to configure = false. +# this will fail configuration validation +``` + +This, more explicit version is also invalid: +```toml +[OCR.Contracts] +link_token = "0x88d1239894D9582f5849E5b5a964da9e5730f1E6" +offchain_aggregators = ["0xc1ce3815d6e7f3705265c2577F1342344752A5Eb", "0x2f4FA21fCd917C448C160caafEC874032F404c08"] + +# notice that this address needs to match the one in offchain_aggregators +[OCR.Contracts.Settings."0xc1ce3815d6e7f3705265c2577F1342344752A5Eb"] +configure = false + +[OCR.Contracts.Settings."0x2f4FA21fCd917C448C160caafEC874032F404c08"] +configure = true +``` + +Similarly, this one is also invalid: +```toml +[OCR.Contracts] +link_token = "0x88d1239894D9582f5849E5b5a964da9e5730f1E6" +offchain_aggregators = ["0xc1ce3815d6e7f3705265c2577F1342344752A5Eb", "0x2f4FA21fCd917C448C160caafEC874032F404c08"] + +# notice that this address needs to match the one in offchain_aggregators +[OCR.Contracts.Settings."0xc1ce3815d6e7f3705265c2577F1342344752A5Eb"] +use = false + +[OCR.Contracts.Settings."0x2f4FA21fCd917C448C160caafEC874032F404c08"] +use = true +``` + +There are no settings available for LINK token contract. + +Last, but not least, when deploying new OCR contracts you need to provide their number. For example: +```toml +# for OCRv1 +[OCR.Common] +number_of_contracts=2 + +# for OCRv2 +[OCR2.Common] +number_of_contracts=2 +``` ### Setting env vars for Chainlink Node @@ -208,7 +320,7 @@ For local testing, it is advisable to place these variables in the `overrides.to ## Embedded config -Because Go automatically excludes TOML files during the compilation of binaries, we must take deliberate steps to include our configuration files in the compiled binary. This can be accomplished by using a custom build tag `-o embed`. Implementing this tag will incorporate all the default configurations located in the `./testconfig` folder directly into the binary. Therefore, when executing tests from the binary, you'll only need to supply the `overrides.toml` file. This file should list only the settings you wish to modify; all other configurations will be sourced from the embedded configurations. You can access these embedded configurations [here](.integration-tests/testconfig/configs_embed.go). +Because Go automatically excludes TOML files during the compilation of binaries, we must take deliberate steps to include our configuration files in the compiled binary. This can be accomplished by using a custom build tag `-o embed`. Implementing this tag will incorporate all the default configurations located in the `./testconfig` folder directly into the binary. Therefore, when executing tests from the binary, you'll only need to supply the `overrides.toml` file. This file should list only the settings you wish to modify; all other configurations will be sourced from the embedded configurations. You can access these embedded configurations [here](./configs_embed.go). ## To bear in mind diff --git a/integration-tests/testconfig/automation/automation.toml b/integration-tests/testconfig/automation/automation.toml index 26b9f05597..b2e87fa34f 100644 --- a/integration-tests/testconfig/automation/automation.toml +++ b/integration-tests/testconfig/automation/automation.toml @@ -2,6 +2,9 @@ [Common] chainlink_node_funding = 2.0 +[Pyroscope] +enabled=false + [NodeConfig] BaseConfigTOML = """ [Feature] @@ -82,6 +85,7 @@ max_perform_gas=5_000_000 min_upkeep_spend=0 fallback_gas_price=200_000_000_000 fallback_link_price=2_000_000_000_000_000_000 +fallback_native_price=2_000_000_000_000_000_000 max_check_data_size=5_000 max_perform_data_size=5_000 max_revert_data_size=5_000 @@ -148,6 +152,7 @@ max_perform_gas=5_000_000 min_upkeep_spend=0 fallback_gas_price=200_000_000_000 fallback_link_price=2_000_000_000_000_000_000 +fallback_native_price=2_000_000_000_000_000_000 max_check_data_size=5_000 max_perform_data_size=5_000 max_revert_data_size=5_000 @@ -199,11 +204,16 @@ max_perform_gas=5_000_000 min_upkeep_spend=0 fallback_gas_price=200_000_000_000 fallback_link_price=2_000_000_000_000_000_000 +fallback_native_price=2_000_000_000_000_000_000 max_check_data_size=5_000 max_perform_data_size=5_000 max_revert_data_size=5_000 # load test specific overrides +[Load.Logging.Grafana] +base_url="https://grafana.ops.prod.cldev.sh" +dashboard_url="/d/a4899f53-f709-430a-aec2-24f32198dcc9/chainlink-automation-v2-load-test" + [Load.Seth] ephemeral_addresses_number = 100 root_key_funds_buffer = 1_000_000 @@ -289,9 +299,254 @@ max_perform_gas=5_000_000 min_upkeep_spend=0 fallback_gas_price=200_000_000_000 fallback_link_price=2_000_000_000_000_000_000 +fallback_native_price=2_000_000_000_000_000_000 max_check_data_size=5_000 max_perform_data_size=5_000 max_revert_data_size=5_000 [Load.Pyroscope] -enabled=false \ No newline at end of file +enabled=false + +# automation benchmark test specific overrides +[Benchmark.Logging.Grafana] +base_url="https://grafana.ops.prod.cldev.sh" +dashboard_url="/d/Q8n6m1unz/chainlink-automation-benchmark-test" + +# will retry roughly for 1h before giving up (900 * 4s) +[Benchmark.Automation.Resiliency] +# number of retries before giving up +contract_call_limit = 900 +# static interval between retries +contract_call_interval = "4s" + +[Benchmark.Seth] +# keeper benchmark running on simulated network requires 100k per node +root_key_funds_buffer = 1_000_000 + +[Benchmark.Automation] +[Benchmark.Automation.General] +number_of_nodes=6 +duration=3600 +block_time=1 +spec_type="minimum" +chainlink_node_log_level="info" +use_prometheus=false +remove_namespace = true + +[Benchmark.Automation.Benchmark] +registry_to_test = "2_1" +number_of_registries = 1 +number_of_nodes = 6 +number_of_upkeeps = 1000 +upkeep_gas_limit = 1500000 +check_gas_to_burn = 10000 +perform_gas_to_burn = 1000 +block_range = 3600 +block_interval = 60 +forces_single_tx_key = false +delete_jobs_on_end = true + +[Benchmark.NodeConfig] +BaseConfigTOML = """ +[Feature] +LogPoller = true + +[OCR2] +Enabled = true + +[P2P] +[P2P.V2] +Enabled = true +AnnounceAddresses = ["0.0.0.0:6690"] +ListenAddresses = ["0.0.0.0:6690"] +[Keeper] +TurnLookBack = 0 +[WebServer] +HTTPWriteTimeout = '1h' +""" + +CommonChainConfigTOML = """ +""" + +[Benchmark.NodeConfig.ChainConfigTOMLByChainID] +# applicable for simulated chain +1337 = """ +FinalityDepth = 50 +LogPollInterval = '1s' +MinIncomingConfirmations = 1 + +[HeadTracker] +HistoryDepth = 100 + +[GasEstimator] +Mode = 'FixedPrice' +LimitDefault = 5_000_000 +""" + +[Benchmark.Automation.AutomationConfig] +use_log_buffer_v1=false + +[Benchmark.Automation.AutomationConfig.PublicConfig] +delta_progress=10_000_000_000 +delta_resend=15_000_000_000 +delta_initial=500_000_000 +delta_round=1_000_000_000 +delta_grace=200_000_000 +delta_certified_commit_request=300_000_000 +delta_stage=30_000_000_000 +r_max=24 +f=1 +max_duration_query=20_000_000 +max_duration_observation=20_000_000 +max_duration_should_accept_attested_report=1_200_000_000 +max_duration_should_transmit_accepted_report=20_000_000 + +[Benchmark.Automation.AutomationConfig.PluginConfig] +perform_lockout_window=3_600_000 +target_probability="0.999" +target_in_rounds=1 +min_confirmations=0 +gas_limit_per_report=10_300_000 +gas_overhead_per_upkeep=300_000 +max_upkeep_batch_size=10 + +[Benchmark.Automation.AutomationConfig.PluginConfig.LogProviderConfig] +block_rate=1 +log_limit=2 + +[Benchmark.Automation.AutomationConfig.RegistrySettings] +payment_premium_ppb=0 +flat_fee_micro_link=40000 +check_gas_limit=45_000_000 +staleness_seconds=90_000 +gas_ceiling_multiplier=2 +max_perform_gas=5_000_000 +min_upkeep_spend=0 +fallback_gas_price=200_000_000_000 +fallback_link_price=2_000_000_000_000_000_000 +fallback_native_price=2_000_000_000_000_000_000 +max_check_data_size=5_000 +max_perform_data_size=5_000 +max_revert_data_size=5_000 + +# automation soak test specific overrides +[Soak.Logging.Grafana] +base_url="https://grafana.ops.prod.cldev.sh" +dashboard_url="/d/Q8n6m1unz/chainlink-automation-benchmark-test" + +# will retry roughly for 1h before giving up (900 * 4s) +[Soak.Automation.Resiliency] +# number of retries before giving up +contract_call_limit = 900 +# static interval between retries +contract_call_interval = "4s" + +[Soak.Seth] +# keeper benchmark running on simulated network requires 100k per node +root_key_funds_buffer = 1_000_000 + +[Soak.Automation] +[Soak.Automation.General] +number_of_nodes=6 +duration=3600 +block_time=1 +spec_type="minimum" +chainlink_node_log_level="info" +use_prometheus=false +remove_namespace = true + +[Soak.Automation.Benchmark] +registry_to_test = "2_1" +number_of_registries = 1 +number_of_nodes = 6 +number_of_upkeeps = 50 +upkeep_gas_limit = 1500000 +check_gas_to_burn = 10000 +perform_gas_to_burn = 1000 +block_range = 28800 +block_interval = 300 +forces_single_tx_key = false +delete_jobs_on_end = true + +[Soak.NodeConfig] +BaseConfigTOML = """ +[Feature] +LogPoller = true + +[OCR2] +Enabled = true + +[P2P] +[P2P.V2] +Enabled = true +AnnounceAddresses = ["0.0.0.0:6690"] +ListenAddresses = ["0.0.0.0:6690"] +[Keeper] +TurnLookBack = 0 +[WebServer] +HTTPWriteTimeout = '1h' +""" + +CommonChainConfigTOML = """ +""" + +[Soak.NodeConfig.ChainConfigTOMLByChainID] +# applicable for simulated chain +1337 = """ +FinalityDepth = 50 +LogPollInterval = '1s' +MinIncomingConfirmations = 1 + +[HeadTracker] +HistoryDepth = 100 + +[GasEstimator] +Mode = 'FixedPrice' +LimitDefault = 5_000_000 +""" + +[Soak.Automation.AutomationConfig] +use_log_buffer_v1=false + +[Soak.Automation.AutomationConfig.PublicConfig] +delta_progress=10_000_000_000 +delta_resend=15_000_000_000 +delta_initial=500_000_000 +delta_round=1_000_000_000 +delta_grace=200_000_000 +delta_certified_commit_request=300_000_000 +delta_stage=30_000_000_000 +r_max=24 +f=1 +max_duration_query=20_000_000 +max_duration_observation=20_000_000 +max_duration_should_accept_attested_report=1_200_000_000 +max_duration_should_transmit_accepted_report=20_000_000 + +[Soak.Automation.AutomationConfig.PluginConfig] +perform_lockout_window=3_600_000 +target_probability="0.999" +target_in_rounds=1 +min_confirmations=0 +gas_limit_per_report=10_300_000 +gas_overhead_per_upkeep=300_000 +max_upkeep_batch_size=10 + +[Soak.Automation.AutomationConfig.PluginConfig.LogProviderConfig] +block_rate=1 +log_limit=2 + +[Soak.Automation.AutomationConfig.RegistrySettings] +payment_premium_ppb=200_000_000 +flat_fee_micro_link=0 +check_gas_limit=2_500_000 +staleness_seconds=90000 +gas_ceiling_multiplier=1 +max_perform_gas=5_000_000 +min_upkeep_spend=0 +fallback_gas_price=200_000_000_000 +fallback_link_price=2_000_000_000_000_000_000 +fallback_native_price=2_000_000_000_000_000_000 +max_check_data_size=5_000 +max_perform_data_size=5_000 +max_revert_data_size=5_000 diff --git a/integration-tests/testconfig/automation/config.go b/integration-tests/testconfig/automation/config.go index e6df7714af..e462ff15c1 100644 --- a/integration-tests/testconfig/automation/config.go +++ b/integration-tests/testconfig/automation/config.go @@ -2,8 +2,13 @@ package automation import ( "errors" + "fmt" "math/big" "time" + + "github.com/ethereum/go-ethereum/common" + + "github.com/smartcontractkit/chainlink-testing-framework/lib/blockchain" ) type Config struct { @@ -11,6 +16,9 @@ type Config struct { Load []Load `toml:"Load"` DataStreams *DataStreams `toml:"DataStreams"` AutomationConfig *AutomationConfig `toml:"AutomationConfig"` + Resiliency *ResiliencyConfig `toml:"Resiliency"` + Benchmark *Benchmark `toml:"Benchmark"` + Contracts *Contracts `toml:"Contracts"` } func (c *Config) Validate() error { @@ -37,6 +45,57 @@ func (c *Config) Validate() error { return err } } + if c.Resiliency != nil { + if err := c.Resiliency.Validate(); err != nil { + return err + } + } + if c.Benchmark != nil { + if err := c.Benchmark.Validate(); err != nil { + return err + } + } + return nil +} + +type Benchmark struct { + RegistryToTest *string `toml:"registry_to_test"` + NumberOfRegistries *int `toml:"number_of_registries"` + NumberOfUpkeeps *int `toml:"number_of_upkeeps"` + UpkeepGasLimit *int64 `toml:"upkeep_gas_limit"` + CheckGasToBurn *int64 `toml:"check_gas_to_burn"` + PerformGasToBurn *int64 `toml:"perform_gas_to_burn"` + BlockRange *int64 `toml:"block_range"` + BlockInterval *int64 `toml:"block_interval"` + ForceSingleTxKey *bool `toml:"forces_single_tx_key"` + DeleteJobsOnEnd *bool `toml:"delete_jobs_on_end"` +} + +func (c *Benchmark) Validate() error { + if c.RegistryToTest == nil || *c.RegistryToTest == "" { + return errors.New("registry_to_test must be set") + } + if c.NumberOfRegistries == nil || *c.NumberOfRegistries <= 0 { + return errors.New("number_of_registries must be a positive integer") + } + if c.NumberOfUpkeeps == nil || *c.NumberOfUpkeeps <= 0 { + return errors.New("number_of_upkeeps must be a positive integer") + } + if c.UpkeepGasLimit == nil || *c.UpkeepGasLimit <= 0 { + return errors.New("upkeep_gas_limit must be a positive integer") + } + if c.CheckGasToBurn == nil || *c.CheckGasToBurn <= 0 { + return errors.New("check_gas_to_burn must be a positive integer") + } + if c.PerformGasToBurn == nil || *c.PerformGasToBurn <= 0 { + return errors.New("perform_gas_to_burn must be a positive integer") + } + if c.BlockRange == nil || *c.BlockRange <= 0 { + return errors.New("block_range must be a positive integer") + } + if c.BlockInterval == nil || *c.BlockInterval <= 0 { + return errors.New("block_interval must be a positive integer") + } return nil } @@ -129,9 +188,9 @@ func (c *Load) Validate() error { type DataStreams struct { Enabled *bool `toml:"enabled"` - URL *string `toml:"url"` - Username *string `toml:"username"` - Password *string `toml:"password"` + URL *string `toml:"-"` + Username *string `toml:"-"` + Password *string `toml:"-"` DefaultFeedID *string `toml:"default_feed_id"` } @@ -298,6 +357,7 @@ type RegistrySettings struct { MinUpkeepSpend *big.Int `toml:"min_upkeep_spend"` FallbackGasPrice *big.Int `toml:"fallback_gas_price"` FallbackLinkPrice *big.Int `toml:"fallback_link_price"` + FallbackNativePrice *big.Int `toml:"fallback_native_price"` MaxCheckDataSize *uint32 `toml:"max_check_data_size"` MaxPerformDataSize *uint32 `toml:"max_perform_data_size"` MaxRevertDataSize *uint32 `toml:"max_revert_data_size"` @@ -331,6 +391,9 @@ func (c *RegistrySettings) Validate() error { if c.FallbackLinkPrice == nil || c.FallbackLinkPrice.Cmp(big.NewInt(0)) < 0 { return errors.New("fallback_link_price must be set to a non-negative integer") } + if c.FallbackNativePrice == nil || c.FallbackNativePrice.Cmp(big.NewInt(0)) < 0 { + return errors.New("fallback_native_price must be set to a non-negative integer") + } if c.MaxCheckDataSize == nil || *c.MaxCheckDataSize < 1 { return errors.New("max_check_data_size must be set to a positive integer") } @@ -342,3 +405,445 @@ func (c *RegistrySettings) Validate() error { } return nil } + +type ResiliencyConfig struct { + ContractCallLimit *uint `toml:"contract_call_limit"` + ContractCallInterval *blockchain.StrDuration `toml:"contract_call_interval"` +} + +func (c *ResiliencyConfig) Validate() error { + if c.ContractCallLimit == nil { + return errors.New("contract_call_limit must be set") + } + if c.ContractCallInterval == nil { + return errors.New("contract_call_interval must be set") + } + + return nil +} + +type Contracts struct { + ShouldBeUsed *bool `toml:"use"` + LinkTokenAddress *string `toml:"link_token"` + WethAddress *string `toml:"weth"` + TranscoderAddress *string `toml:"transcoder"` + ChainModuleAddress *string `toml:"chain_module"` + RegistryAddress *string `toml:"registry"` + RegistrarAddress *string `toml:"registrar"` + LinkEthFeedAddress *string `toml:"link_eth_feed"` + EthGasFeedAddress *string `toml:"eth_gas_feed"` + EthUSDFeedAddress *string `toml:"eth_usd_feed"` + LinkUSDFeedAddress *string `toml:"link_usd_feed"` + UpkeepContractAddresses []string `toml:"upkeep_contracts"` + MultiCallAddress *string `toml:"multicall"` + Settings map[string]ContractSetting `toml:"Settings"` +} + +func (o *Contracts) Validate() error { + if o.LinkTokenAddress != nil && !common.IsHexAddress(*o.LinkTokenAddress) { + return errors.New("link_token must be a valid ethereum address") + } + if o.WethAddress != nil && !common.IsHexAddress(*o.WethAddress) { + return errors.New("weth must be a valid ethereum address") + } + if o.TranscoderAddress != nil && !common.IsHexAddress(*o.TranscoderAddress) { + return errors.New("transcoder must be a valid ethereum address") + } + if o.ChainModuleAddress != nil && !common.IsHexAddress(*o.ChainModuleAddress) { + return errors.New("chain_module must be a valid ethereum address") + } + if o.RegistryAddress != nil && !common.IsHexAddress(*o.RegistryAddress) { + return errors.New("registry must be a valid ethereum address") + } + if o.RegistrarAddress != nil && !common.IsHexAddress(*o.RegistrarAddress) { + return errors.New("registrar must be a valid ethereum address") + } + if o.LinkEthFeedAddress != nil && !common.IsHexAddress(*o.LinkEthFeedAddress) { + return errors.New("link_eth_feed must be a valid ethereum address") + } + if o.EthGasFeedAddress != nil && !common.IsHexAddress(*o.EthGasFeedAddress) { + return errors.New("eth_gas_feed must be a valid ethereum address") + } + if o.EthUSDFeedAddress != nil && !common.IsHexAddress(*o.EthUSDFeedAddress) { + return errors.New("eth_usd_feed must be a valid ethereum address") + } + if o.LinkUSDFeedAddress != nil && !common.IsHexAddress(*o.LinkUSDFeedAddress) { + return errors.New("link_usd_feed must be a valid ethereum address") + } + if o.MultiCallAddress != nil && !common.IsHexAddress(*o.MultiCallAddress) { + return errors.New("multicall must be a valid ethereum address") + } + if o.UpkeepContractAddresses != nil { + allEnabled := make(map[bool]int) + allConfigure := make(map[bool]int) + for _, address := range o.UpkeepContractAddresses { + if !common.IsHexAddress(address) { + return fmt.Errorf("upkeep_contracts must be valid ethereum addresses, but %s is not", address) + } + + if v, ok := o.Settings[address]; ok { + if v.ShouldBeUsed != nil { + allEnabled[*v.ShouldBeUsed]++ + } else { + allEnabled[true]++ + } + if v.Configure != nil { + allConfigure[*v.Configure]++ + } else { + allConfigure[true]++ + } + } + } + + if allEnabled[true] > 0 && allEnabled[false] > 0 { + return errors.New("either all or none offchain_aggregators must be used") + } + + if allConfigure[true] > 0 && allConfigure[false] > 0 { + return errors.New("either all or none offchain_aggregators must be configured") + } + } + + return nil +} + +func (c *Config) UseExistingContracts() bool { + if c.Contracts == nil { + return false + } + + if c.Contracts.ShouldBeUsed != nil { + return *c.Contracts.ShouldBeUsed + } + + return false +} + +func (c *Config) LinkTokenContractAddress() (common.Address, error) { + if c.Contracts != nil && c.Contracts.LinkTokenAddress != nil { + return common.HexToAddress(*c.Contracts.LinkTokenAddress), nil + } + + return common.Address{}, errors.New("link token address must be set") +} + +func (c *Config) WethContractAddress() (common.Address, error) { + if c.Contracts != nil && c.Contracts.WethAddress != nil { + return common.HexToAddress(*c.Contracts.WethAddress), nil + } + + return common.Address{}, errors.New("weth address must be set") +} + +func (c *Config) TranscoderContractAddress() (common.Address, error) { + if c.Contracts != nil && c.Contracts.TranscoderAddress != nil { + return common.HexToAddress(*c.Contracts.TranscoderAddress), nil + } + + return common.Address{}, errors.New("transcoder address must be set") +} + +func (c *Config) ChainModuleContractAddress() (common.Address, error) { + if c.Contracts != nil && c.Contracts.ChainModuleAddress != nil { + return common.HexToAddress(*c.Contracts.ChainModuleAddress), nil + } + + return common.Address{}, errors.New("chain module address must be set") +} + +func (c *Config) RegistryContractAddress() (common.Address, error) { + if c.Contracts != nil && c.Contracts.RegistryAddress != nil { + return common.HexToAddress(*c.Contracts.RegistryAddress), nil + } + + return common.Address{}, errors.New("registry address must be set") +} + +func (c *Config) RegistrarContractAddress() (common.Address, error) { + if c.Contracts != nil && c.Contracts.RegistrarAddress != nil { + return common.HexToAddress(*c.Contracts.RegistrarAddress), nil + } + + return common.Address{}, errors.New("registrar address must be set") +} + +func (c *Config) LinkEthFeedContractAddress() (common.Address, error) { + if c.Contracts != nil && c.Contracts.LinkEthFeedAddress != nil { + return common.HexToAddress(*c.Contracts.LinkEthFeedAddress), nil + } + + return common.Address{}, errors.New("link eth feed address must be set") +} + +func (c *Config) EthGasFeedContractAddress() (common.Address, error) { + if c.Contracts != nil && c.Contracts.EthGasFeedAddress != nil { + return common.HexToAddress(*c.Contracts.EthGasFeedAddress), nil + } + + return common.Address{}, errors.New("eth gas feed address must be set") +} + +func (c *Config) EthUSDFeedContractAddress() (common.Address, error) { + if c.Contracts != nil && c.Contracts.EthUSDFeedAddress != nil { + return common.HexToAddress(*c.Contracts.EthUSDFeedAddress), nil + } + + return common.Address{}, errors.New("eth usd feed address must be set") +} + +func (c *Config) LinkUSDFeedContractAddress() (common.Address, error) { + if c.Contracts != nil && c.Contracts.LinkUSDFeedAddress != nil { + return common.HexToAddress(*c.Contracts.LinkUSDFeedAddress), nil + } + + return common.Address{}, errors.New("link usd feed address must be set") +} + +func (c *Config) UpkeepContractAddresses() ([]common.Address, error) { + if c.Contracts != nil && c.Contracts.UpkeepContractAddresses != nil { + addresses := make([]common.Address, len(c.Contracts.UpkeepContractAddresses)) + for i, address := range c.Contracts.UpkeepContractAddresses { + addresses[i] = common.HexToAddress(address) + } + return addresses, nil + } + + return nil, errors.New("upkeep contract addresses must be set") +} + +func (c *Config) MultiCallContractAddress() (common.Address, error) { + if c.Contracts != nil && c.Contracts.MultiCallAddress != nil { + return common.HexToAddress(*c.Contracts.MultiCallAddress), nil + } + + return common.Address{}, errors.New("multicall address must be set") +} + +func (c *Config) UseExistingLinkTokenContract() bool { + if !c.UseExistingContracts() { + return false + } + + if c.Contracts.LinkTokenAddress == nil { + return false + } + + if len(c.Contracts.Settings) == 0 { + return true + } + + if v, ok := c.Contracts.Settings[*c.Contracts.LinkTokenAddress]; ok { + return v.ShouldBeUsed != nil && *v.ShouldBeUsed + } + + return true +} + +func (c *Config) UseExistingWethContract() bool { + if !c.UseExistingContracts() { + return false + } + + if c.Contracts.WethAddress == nil { + return false + } + + if len(c.Contracts.Settings) == 0 { + return true + } + + if v, ok := c.Contracts.Settings[*c.Contracts.WethAddress]; ok { + return v.ShouldBeUsed != nil && *v.ShouldBeUsed + } + + return true +} + +func (c *Config) UseExistingTranscoderContract() bool { + if !c.UseExistingContracts() { + return false + } + + if c.Contracts.TranscoderAddress == nil { + return false + } + + if len(c.Contracts.Settings) == 0 { + return true + } + + if v, ok := c.Contracts.Settings[*c.Contracts.TranscoderAddress]; ok { + return v.ShouldBeUsed != nil && *v.ShouldBeUsed + } + + return true +} + +func (c *Config) UseExistingRegistryContract() bool { + if !c.UseExistingContracts() { + return false + } + + if c.Contracts.RegistryAddress == nil { + return false + } + + if len(c.Contracts.Settings) == 0 { + return true + } + + if v, ok := c.Contracts.Settings[*c.Contracts.RegistryAddress]; ok { + return v.ShouldBeUsed != nil && *v.ShouldBeUsed + } + + return true +} + +func (c *Config) UseExistingRegistrarContract() bool { + if !c.UseExistingContracts() { + return false + } + + if c.Contracts.RegistrarAddress == nil { + return false + } + + if len(c.Contracts.Settings) == 0 { + return true + } + + if v, ok := c.Contracts.Settings[*c.Contracts.RegistrarAddress]; ok { + return v.ShouldBeUsed != nil && *v.ShouldBeUsed + } + + return true +} + +func (c *Config) UseExistingLinkEthFeedContract() bool { + if !c.UseExistingContracts() { + return false + } + + if c.Contracts.LinkEthFeedAddress == nil { + return false + } + + if len(c.Contracts.Settings) == 0 { + return true + } + + if v, ok := c.Contracts.Settings[*c.Contracts.LinkEthFeedAddress]; ok { + return v.ShouldBeUsed != nil && *v.ShouldBeUsed + } + + return true +} + +func (c *Config) UseExistingEthGasFeedContract() bool { + if !c.UseExistingContracts() { + return false + } + + if c.Contracts.EthGasFeedAddress == nil { + return false + } + + if len(c.Contracts.Settings) == 0 { + return true + } + + if v, ok := c.Contracts.Settings[*c.Contracts.EthGasFeedAddress]; ok { + return v.ShouldBeUsed != nil && *v.ShouldBeUsed + } + + return true +} + +func (c *Config) UseExistingEthUSDFeedContract() bool { + if !c.UseExistingContracts() { + return false + } + + if c.Contracts.EthUSDFeedAddress == nil { + return false + } + + if len(c.Contracts.Settings) == 0 { + return true + } + + if v, ok := c.Contracts.Settings[*c.Contracts.EthUSDFeedAddress]; ok { + return v.ShouldBeUsed != nil && *v.ShouldBeUsed + } + + return true +} + +func (c *Config) UseExistingLinkUSDFeedContract() bool { + if !c.UseExistingContracts() { + return false + } + + if c.Contracts.LinkUSDFeedAddress == nil { + return false + } + + if len(c.Contracts.Settings) == 0 { + return true + } + + if v, ok := c.Contracts.Settings[*c.Contracts.LinkUSDFeedAddress]; ok { + return v.ShouldBeUsed != nil && *v.ShouldBeUsed + } + + return true +} + +func (c *Config) UseExistingUpkeepContracts() bool { + if !c.UseExistingContracts() { + return false + } + + if c.Contracts.UpkeepContractAddresses == nil { + return false + } + + if len(c.Contracts.Settings) == 0 { + return true + } + + for _, address := range c.Contracts.UpkeepContractAddresses { + if v, ok := c.Contracts.Settings[address]; ok { + if v.ShouldBeUsed != nil && *v.ShouldBeUsed { + return true + } + } + } + + return false +} + +func (c *Config) UseExistingMultiCallContract() bool { + if !c.UseExistingContracts() { + return false + } + + if c.Contracts.MultiCallAddress == nil { + return false + } + + if len(c.Contracts.Settings) == 0 { + return true + } + + if v, ok := c.Contracts.Settings[*c.Contracts.MultiCallAddress]; ok { + return v.ShouldBeUsed != nil && *v.ShouldBeUsed + } + + return true +} + +type ContractSetting struct { + ShouldBeUsed *bool `toml:"use"` + Configure *bool `toml:"configure"` +} diff --git a/integration-tests/testconfig/automation/example.toml b/integration-tests/testconfig/automation/example.toml index 3b48e89a54..3bbe78d693 100644 --- a/integration-tests/testconfig/automation/example.toml +++ b/integration-tests/testconfig/automation/example.toml @@ -1,7 +1,6 @@ # Example of full config with all fields # General part [ChainlinkImage] -image="public.ecr.aws/chainlink/chainlink" version="2.7.0" [Logging] @@ -16,38 +15,10 @@ log_producer_timeout="10s" # number of retries before log producer gives up and stops listening to logs log_producer_retry_limit=10 -[Logging.Loki] -tenant_id="tenant_id" -# full URL of Loki ingest endpoint -endpoint="https://loki.url/api/v3/push" -# currently only needed when using public instance -basic_auth_secret="loki-basic-auth" -# only needed for cloud grafana -bearer_token_secret="bearer_token" - -# LogStream will try to shorten Grafana URLs by default (if all 3 variables are set) -[Logging.Grafana] -# grafana url (trailing "/" will be stripped) -base_url="http://grafana.url" -# url of your grafana dashboard (prefix and suffix "/" are stirpped), example: /d/ad61652-2712-1722/my-dashboard -dashboard_url="/d/your-dashboard" -# Grafana dashboard uid to annotate. Find it in Dashboard Settings -> JSON Model -dashboard_uid="dashboard-uid-to-annotate" -bearer_token_secret="my-awesome-token" - # if you want to use polygon_mumbial [Network] selected_networks=["polygon_mumbai"] -[Network.RpcHttpUrls] -polygon_mumbai = ["https://my-rpc-endpoint.io"] - -[Network.RpcWsUrls] -polygon_mumbai = ["https://my-rpc-endpoint.io"] - -[Network.WalletKeys] -polygon_mumbai = ["change-me-to-your-PK"] - [PrivateEthereumNetwork] # pos or pow consensus_type="pos" @@ -163,5 +134,4 @@ use_prometheus=false # upgrade test specific override [TestAutomationNodeUpgrade.ChainlinkUpgradeImage] -image="public.ecr.aws/chainlink/chainlink" version="2.8.0" \ No newline at end of file diff --git a/integration-tests/testconfig/automation/overrides/benchmark/1000Upkeeps-1h-2_1.toml b/integration-tests/testconfig/automation/overrides/benchmark/1000Upkeeps-1h-2_1.toml new file mode 100644 index 0000000000..0b704524e9 --- /dev/null +++ b/integration-tests/testconfig/automation/overrides/benchmark/1000Upkeeps-1h-2_1.toml @@ -0,0 +1,15 @@ +[ChainlinkImage] +version="latest" + +[Benchmark.Automation.Benchmark] +registry_to_test = "2_1" +number_of_registries = 1 +number_of_nodes = 6 +number_of_upkeeps = 1000 +upkeep_gas_limit = 1500000 +check_gas_to_burn = 10000 +perform_gas_to_burn = 1000 +block_range = 3600 +block_interval = 60 +forces_single_tx_key = false +delete_jobs_on_end = true \ No newline at end of file diff --git a/integration-tests/testconfig/automation/overrides/benchmark/1000Upkeeps-1h-2_3.toml b/integration-tests/testconfig/automation/overrides/benchmark/1000Upkeeps-1h-2_3.toml new file mode 100644 index 0000000000..f00bb5ed47 --- /dev/null +++ b/integration-tests/testconfig/automation/overrides/benchmark/1000Upkeeps-1h-2_3.toml @@ -0,0 +1,15 @@ +[ChainlinkImage] +version="latest" + +[Benchmark.Automation.Benchmark] +registry_to_test = "2_3" +number_of_registries = 1 +number_of_nodes = 6 +number_of_upkeeps = 1000 +upkeep_gas_limit = 1500000 +check_gas_to_burn = 10000 +perform_gas_to_burn = 1000 +block_range = 3600 +block_interval = 60 +forces_single_tx_key = false +delete_jobs_on_end = true \ No newline at end of file diff --git a/integration-tests/testconfig/automation/overrides/load/500Upkeeps-1x-1h.toml b/integration-tests/testconfig/automation/overrides/load/500Upkeeps-1x-1h.toml new file mode 100644 index 0000000000..6d58253f52 --- /dev/null +++ b/integration-tests/testconfig/automation/overrides/load/500Upkeeps-1x-1h.toml @@ -0,0 +1,47 @@ +[ChainlinkImage] +version="latest" + +[Load.Seth] +root_key_funds_buffer = 1_000_000 +ephemeral_addresses_number = 300 + +[Load.Seth.nonce_manager] +key_sync_timeout = "100s" + +[Load.Common] +chainlink_node_funding = 1000 + +[Load.Automation.AutomationConfig] +use_log_buffer_v1=false + +[Load.Automation.AutomationConfig.PluginConfig.LogProviderConfig] +block_rate=1 +log_limit=2 + +[Load.Automation] +[Load.Automation.General] +number_of_nodes=6 +duration=3600 +block_time=1 +spec_type="recommended" +chainlink_node_log_level="debug" +use_prometheus=true +remove_namespace = true + +[Load.Automation.DataStreams] +enabled=false + +[[Load.Automation.Load]] +number_of_upkeeps=500 +number_of_events = 1 +number_of_spam_matching_events = 0 +number_of_spam_non_matching_events = 0 +check_burn_amount = 0 +perform_burn_amount = 0 +upkeep_gas_limit = 1000000 +shared_trigger = false +is_streams_lookup = false +feeds = [] + +[Pyroscope] +enabled=false \ No newline at end of file diff --git a/integration-tests/testconfig/automation/overrides/load/50Upkeeps-1x-12h.toml b/integration-tests/testconfig/automation/overrides/load/50Upkeeps-1x-12h.toml new file mode 100644 index 0000000000..8991a6eb90 --- /dev/null +++ b/integration-tests/testconfig/automation/overrides/load/50Upkeeps-1x-12h.toml @@ -0,0 +1,43 @@ +[ChainlinkImage] +version="latest" + +[Load.Seth] +root_key_funds_buffer = 1_000_000 + +[Load.Common] +chainlink_node_funding = 10000 + +[Load.Automation.AutomationConfig] +use_log_buffer_v1=false + +[Load.Automation.AutomationConfig.PluginConfig.LogProviderConfig] +block_rate=1 +log_limit=2 + +[Load.Automation] +[Load.Automation.General] +number_of_nodes=6 +duration=43200 +block_time=1 +spec_type="recommended" +chainlink_node_log_level="debug" +use_prometheus=true +remove_namespace = true + +[Load.Automation.DataStreams] +enabled=false + +[[Load.Automation.Load]] +number_of_upkeeps=50 +number_of_events = 1 +number_of_spam_matching_events = 0 +number_of_spam_non_matching_events = 0 +check_burn_amount = 0 +perform_burn_amount = 0 +upkeep_gas_limit = 1000000 +shared_trigger = false +is_streams_lookup = false +feeds = [] + +[Pyroscope] +enabled=false \ No newline at end of file diff --git a/integration-tests/testconfig/automation/overrides/load/50Upkeeps-1x-1h.toml b/integration-tests/testconfig/automation/overrides/load/50Upkeeps-1x-1h.toml new file mode 100644 index 0000000000..a133fce6dc --- /dev/null +++ b/integration-tests/testconfig/automation/overrides/load/50Upkeeps-1x-1h.toml @@ -0,0 +1,43 @@ +[ChainlinkImage] +version="latest" + +[Load.Seth] +root_key_funds_buffer = 1_000_000 + +[Load.Common] +chainlink_node_funding = 1000 + +[Load.Automation.AutomationConfig] +use_log_buffer_v1=false + +[Load.Automation.AutomationConfig.PluginConfig.LogProviderConfig] +block_rate=1 +log_limit=2 + +[Load.Automation] +[Load.Automation.General] +number_of_nodes=6 +duration=3600 +block_time=1 +spec_type="recommended" +chainlink_node_log_level="debug" +use_prometheus=true +remove_namespace = true + +[Load.Automation.DataStreams] +enabled=false + +[[Load.Automation.Load]] +number_of_upkeeps=50 +number_of_events = 1 +number_of_spam_matching_events = 0 +number_of_spam_non_matching_events = 0 +check_burn_amount = 0 +perform_burn_amount = 0 +upkeep_gas_limit = 1000000 +shared_trigger = false +is_streams_lookup = false +feeds = [] + +[Pyroscope] +enabled=false \ No newline at end of file diff --git a/integration-tests/testconfig/automation/overrides/soak/50Upkeeps-8h-2_1.toml b/integration-tests/testconfig/automation/overrides/soak/50Upkeeps-8h-2_1.toml new file mode 100644 index 0000000000..fda5cb6ea2 --- /dev/null +++ b/integration-tests/testconfig/automation/overrides/soak/50Upkeeps-8h-2_1.toml @@ -0,0 +1,15 @@ +[ChainlinkImage] +version="latest" + +[Soak.Automation.Benchmark] +registry_to_test = "2_1" +number_of_registries = 1 +number_of_nodes = 6 +number_of_upkeeps = 50 +upkeep_gas_limit = 1500000 +check_gas_to_burn = 10000 +perform_gas_to_burn = 1000 +block_range = 28800 +block_interval = 300 +forces_single_tx_key = false +delete_jobs_on_end = true \ No newline at end of file diff --git a/integration-tests/testconfig/automation/overrides/soak/50Upkeeps-8h-2_3.toml b/integration-tests/testconfig/automation/overrides/soak/50Upkeeps-8h-2_3.toml new file mode 100644 index 0000000000..46ba1ad3e8 --- /dev/null +++ b/integration-tests/testconfig/automation/overrides/soak/50Upkeeps-8h-2_3.toml @@ -0,0 +1,15 @@ +[ChainlinkImage] +version="latest" + +[Soak.Automation.Benchmark] +registry_to_test = "2_3" +number_of_registries = 1 +number_of_nodes = 6 +number_of_upkeeps = 50 +upkeep_gas_limit = 1500000 +check_gas_to_burn = 10000 +perform_gas_to_burn = 1000 +block_range = 28800 +block_interval = 300 +forces_single_tx_key = false +delete_jobs_on_end = true \ No newline at end of file diff --git a/integration-tests/testconfig/ccip/ccip.toml b/integration-tests/testconfig/ccip/ccip.toml new file mode 100644 index 0000000000..3a27d0cd54 --- /dev/null +++ b/integration-tests/testconfig/ccip/ccip.toml @@ -0,0 +1,149 @@ +[Common] +# chainlink node funding in native token +chainlink_node_funding = 1 + +[Network] +selected_networks = ['SIMULATED_1', 'SIMULATED_2'] + +[Network.EVMNetworks.SIMULATED_1] +evm_name = 'chain-1337' +evm_chain_id = 1337 +evm_keys = [ + "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", +] +evm_simulated = true +client_implementation = 'Ethereum' +evm_chainlink_transaction_limit = 50000 +evm_transaction_timeout = '2m' +evm_minimum_confirmations = 1 +evm_gas_estimation_buffer = 1000 +evm_supports_eip1559 = true +evm_default_gas_limit = 6000000 +evm_finality_depth = 1 + +[Network.EVMNetworks.SIMULATED_2] +evm_name = 'chain-2337' +evm_chain_id = 2337 +evm_keys = [ + "59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", + "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", +] +evm_simulated = true +client_implementation = 'Ethereum' +evm_chainlink_transaction_limit = 50000 +evm_transaction_timeout = '2m' +evm_minimum_confirmations = 1 +evm_gas_estimation_buffer = 1000 +evm_supports_eip1559 = true +evm_default_gas_limit = 6000000 +evm_finality_depth = 1 + +[NodeConfig] +BaseConfigTOML = """ +[Feature] +FeedsManager = true +LogPoller = true +UICSAKeys = true + +[Log] +Level = 'debug' +JSONConsole = true + +[Log.File] +MaxSize = '0b' + +[WebServer] +AllowOrigins = '*' +HTTPPort = 6688 +SecureCookies = false +HTTPWriteTimeout = '3m' +SessionTimeout = '999h0m0s' + +[WebServer.RateLimit] +Authenticated = 2000 +Unauthenticated = 1000 + +[WebServer.TLS] +HTTPSPort = 0 + +[Database] +MaxIdleConns = 20 +MaxOpenConns = 40 +MigrateOnStartup = true + +[OCR2] +Enabled = true +ContractPollInterval = '5s' + +[OCR] +Enabled = false +DefaultTransactionQueueDepth = 200 + +[P2P] +[P2P.V2] +Enabled = true +ListenAddresses = ['0.0.0.0:6690'] +AnnounceAddresses = ['0.0.0.0:6690'] +DeltaDial = '500ms' +DeltaReconcile = '5s' +""" + +[CCIP] +HomeChainSelector = '12922642891491394802' # for chain-2337 + +[CCIP.CLNode] +NoOfPluginNodes = 4 +NoOfBootstraps = 1 + +[CCIP.PrivateEthereumNetworks.SIMULATED_1] +# either eth1 or eth2 (for post-Merge); for eth2 Prysm is used for consensus layer. +ethereum_version = "eth1" +# geth, besu, erigon or nethermind +execution_layer = "geth" +# eth2-only, if set to true environment startup will wait until at least 1 epoch has been finalised +wait_for_finalization=false + +[CCIP.PrivateEthereumNetworks.SIMULATED_1.EthereumChainConfig] +# eth2-only, the lower the value the faster the block production (3 is minimum) +seconds_per_slot = 3 +# eth2-only, the lower the value the faster the epoch finalisation (2 is minimum) +slots_per_epoch = 2 +# eht2-only, the lower tha value the faster the chain starts (10 is minimum) +genesis_delay = 15 +# eth2-only, number of validators +validator_count = 4 +chain_id = 1337 +# address that should be founded in genesis wih ETH +addresses_to_fund = [ + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", +] + +[CCIP.PrivateEthereumNetworks.SIMULATED_1.EthereumChainConfig.HardForkEpochs] +# eth2-only, epoch at which chain will upgrade do Dencun or Deneb/Cancun (1 is minimum) +Deneb = 500 + +#[CCIP.Env.PrivateEthereumNetworks.SIMULATED_1.CustomDockerImages] +# custom docker image that will be used for execution layer client. It has to be one of: hyperledger/besu, nethermind/nethermind, thorax/erigon or ethereum/client-go. +# instead of using a specific tag you can also use "latest_available" to use latest published tag in Github or "latest_stable" to use latest stable release from Github +# (if corresponding Docker image on Docker Hub has not been published environment creation will fail). +#execution_layer="hyperledger/besu:latest_stable" + +[CCIP.PrivateEthereumNetworks.SIMULATED_2] +ethereum_version = "eth1" +execution_layer = "geth" + +[CCIP.PrivateEthereumNetworks.SIMULATED_2.EthereumChainConfig] +seconds_per_slot = 3 +slots_per_epoch = 2 +genesis_delay = 15 +validator_count = 4 +chain_id = 2337 +addresses_to_fund = [ + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", +] + +[CCIP.PrivateEthereumNetworks.SIMULATED_2.EthereumChainConfig.HardForkEpochs] +Deneb = 500 \ No newline at end of file diff --git a/integration-tests/testconfig/ccip/config.go b/integration-tests/testconfig/ccip/config.go new file mode 100644 index 0000000000..b9d969fed1 --- /dev/null +++ b/integration-tests/testconfig/ccip/config.go @@ -0,0 +1,99 @@ +package ccip + +import ( + "strconv" + + "github.com/AlekSi/pointer" + + ctfconfig "github.com/smartcontractkit/chainlink-testing-framework/lib/config" + + "github.com/smartcontractkit/chainlink/integration-tests/client" +) + +const ( + E2E_JD_IMAGE = "E2E_JD_IMAGE" + E2E_JD_VERSION = "E2E_JD_VERSION" + E2E_JD_GRPC = "E2E_JD_GRPC" + E2E_JD_WSRPC = "E2E_JD_WSRPC" + DEFAULT_DB_NAME = "JD_DB" + DEFAULT_DB_VERSION = "14.1" +) + +type Config struct { + PrivateEthereumNetworks map[string]*ctfconfig.EthereumNetworkConfig `toml:",omitempty"` + CLNode *NodeConfig `toml:",omitempty"` + JobDistributorConfig JDConfig `toml:",omitempty"` + HomeChainSelector *string `toml:",omitempty"` +} + +type NodeConfig struct { + NoOfPluginNodes *int `toml:",omitempty"` + NoOfBootstraps *int `toml:",omitempty"` + ClientConfig *client.ChainlinkConfig `toml:",omitempty"` +} + +type JDConfig struct { + Image *string `toml:",omitempty"` + Version *string `toml:",omitempty"` + DBName *string `toml:",omitempty"` + DBVersion *string `toml:",omitempty"` + JDGRPC *string `toml:",omitempty"` + JDWSRPC *string `toml:",omitempty"` +} + +func (o *Config) Validate() error { + return nil +} + +// TODO: include all JD specific input in generic secret handling +func (o *Config) GetJDGRPC() string { + grpc := pointer.GetString(o.JobDistributorConfig.JDGRPC) + if grpc == "" { + return ctfconfig.MustReadEnvVar_String(E2E_JD_GRPC) + } + return grpc +} + +func (o *Config) GetJDWSRPC() string { + wsrpc := pointer.GetString(o.JobDistributorConfig.JDWSRPC) + if wsrpc == "" { + return ctfconfig.MustReadEnvVar_String(E2E_JD_WSRPC) + } + return wsrpc +} + +func (o *Config) GetJDImage() string { + image := pointer.GetString(o.JobDistributorConfig.Image) + if image == "" { + return ctfconfig.MustReadEnvVar_String(E2E_JD_IMAGE) + } + return image +} + +func (o *Config) GetJDVersion() string { + version := pointer.GetString(o.JobDistributorConfig.Version) + if version == "" { + return ctfconfig.MustReadEnvVar_String(E2E_JD_VERSION) + } + return version +} + +func (o *Config) GetJDDBName() string { + dbname := pointer.GetString(o.JobDistributorConfig.DBName) + if dbname == "" { + return DEFAULT_DB_NAME + } + return dbname +} + +func (o *Config) GetJDDBVersion() string { + dbversion := pointer.GetString(o.JobDistributorConfig.DBVersion) + if dbversion == "" { + return DEFAULT_DB_VERSION + } + return dbversion +} + +func (o *Config) GetHomeChainSelector() (uint64, error) { + return strconv.ParseUint(pointer.GetString(o.HomeChainSelector), 10, 64) +} diff --git a/integration-tests/testconfig/ccip/overrides/sepolia_avax_binance.toml b/integration-tests/testconfig/ccip/overrides/sepolia_avax_binance.toml new file mode 100644 index 0000000000..06af64d5d9 --- /dev/null +++ b/integration-tests/testconfig/ccip/overrides/sepolia_avax_binance.toml @@ -0,0 +1,55 @@ +[Common] +# chainlink node funding in native token +chainlink_node_funding = 2 + +[Logging] +test_log_collect = true + +[Logging.LogStream] +# supported targets: file, loki, in-memory. if empty no logs will be persisted +log_targets = ["loki"] + +[Network] +selected_networks = ['SEPOLIA', 'AVALANCHE_FUJI', 'BSC_TESTNET'] + +[Network.EVMNetworks.SEPOLIA] +evm_name = 'Sepolia Testnet' +evm_chain_id = 11155111 +evm_simulated = false +client_implementation = 'Ethereum' +evm_chainlink_transaction_limit = 5000 +evm_transaction_timeout = '5m' +evm_minimum_confirmations = 1 +evm_gas_estimation_buffer = 1000 +evm_supports_eip1559 = true +evm_default_gas_limit = 6000000 +evm_finality_tag = true + +[Network.EVMNetworks.AVALANCHE_FUJI] +evm_name = 'Avalanche Fuji' +evm_chain_id = 43113 +evm_simulated = false +client_implementation = 'Ethereum' +evm_chainlink_transaction_limit = 5000 +evm_transaction_timeout = '2m' +evm_minimum_confirmations = 1 +evm_gas_estimation_buffer = 1000 +evm_supports_eip1559 = true +evm_default_gas_limit = 6000000 +evm_finality_tag = true + +[Network.EVMNetworks.BSC_TESTNET] +evm_name = 'BSC Testnet' +evm_chain_id = 97 +evm_simulated = false +client_implementation = 'BSC' +evm_chainlink_transaction_limit = 5000 +evm_transaction_timeout = '2m' +evm_minimum_confirmations = 3 +evm_gas_estimation_buffer = 0 +evm_supports_eip1559 = true +evm_default_gas_limit = 6000000 +evm_finality_tag = true + +[CCIP] +HomeChainSelector = '16015286601757825753' # for sepolia \ No newline at end of file diff --git a/integration-tests/testconfig/common/vrf/common.go b/integration-tests/testconfig/common/vrf/common.go index 120bd5c864..49cb7c4f25 100644 --- a/integration-tests/testconfig/common/vrf/common.go +++ b/integration-tests/testconfig/common/vrf/common.go @@ -71,10 +71,13 @@ func (c *PerformanceConfig) Validate() error { type ExistingEnvConfig struct { CoordinatorAddress *string `toml:"coordinator_address"` + UseExistingWrapper *bool `toml:"use_existing_wrapper"` + WrapperAddress *string `toml:"wrapper_address"` ConsumerAddress *string `toml:"consumer_address"` - LinkAddress *string `toml:"link_address"` + WrapperConsumerAddress *string `toml:"wrapper_consumer_address"` KeyHash *string `toml:"key_hash"` CreateFundSubsAndAddConsumers *bool `toml:"create_fund_subs_and_add_consumers"` + CreateFundAddWrapperConsumers *bool `toml:"create_fund_add_wrapper_consumers"` NodeSendingKeys []string `toml:"node_sending_keys"` Funding } @@ -83,23 +86,33 @@ func (c *ExistingEnvConfig) Validate() error { if c.CreateFundSubsAndAddConsumers == nil { return errors.New("create_fund_subs_and_add_consumers must be set ") } + if c.CreateFundAddWrapperConsumers == nil { + return errors.New("create_fund_add_wrapper_consumers must be set ") + } if c.CoordinatorAddress == nil { return errors.New("coordinator_address must be set when using existing environment") } if !common.IsHexAddress(*c.CoordinatorAddress) { return errors.New("coordinator_address must be a valid hex address") } + if c.UseExistingWrapper == nil { + return errors.New("use_existing_wrapper must be set ") + } + if *c.UseExistingWrapper { + if c.WrapperAddress == nil { + return errors.New("wrapper_address must be set when using `use_existing_wrapper=true`") + } + if !common.IsHexAddress(*c.WrapperAddress) { + return errors.New("wrapper_address must be a valid hex address") + } + } if c.KeyHash == nil { return errors.New("key_hash must be set when using existing environment") } if *c.KeyHash == "" { return errors.New("key_hash must be a non-empty string") } - if *c.CreateFundSubsAndAddConsumers { - if err := c.Funding.Validate(); err != nil { - return err - } - } else { + if !*c.CreateFundSubsAndAddConsumers { if c.ConsumerAddress == nil || *c.ConsumerAddress == "" { return errors.New("consumer_address must be set when using existing environment") } @@ -107,7 +120,14 @@ func (c *ExistingEnvConfig) Validate() error { return errors.New("consumer_address must be a valid hex address") } } - + if !*c.CreateFundAddWrapperConsumers { + if c.WrapperConsumerAddress == nil || *c.WrapperConsumerAddress == "" { + return errors.New("wrapper_consumer_address must be set when using existing environment") + } + if !common.IsHexAddress(*c.WrapperConsumerAddress) { + return errors.New("wrapper_consumer_address must be a valid hex address") + } + } if c.NodeSendingKeys != nil { for _, key := range c.NodeSendingKeys { if !common.IsHexAddress(key) { @@ -115,7 +135,6 @@ func (c *ExistingEnvConfig) Validate() error { } } } - return nil } @@ -127,7 +146,6 @@ func (c *Funding) Validate() error { if c.NodeSendingKeyFundingMin != nil && *c.NodeSendingKeyFundingMin <= 0 { return errors.New("when set node_sending_key_funding_min must be a positive value") } - return nil } diff --git a/integration-tests/testconfig/configs_embed.go b/integration-tests/testconfig/configs_embed.go index 31303357a4..5de81acb7d 100644 --- a/integration-tests/testconfig/configs_embed.go +++ b/integration-tests/testconfig/configs_embed.go @@ -18,6 +18,8 @@ import "embed" //go:embed vrf/vrf.toml //go:embed vrfv2/vrfv2.toml //go:embed vrfv2plus/vrfv2plus.toml +//go:embed ccip/ccip.toml + var embeddedConfigsFs embed.FS func init() { diff --git a/integration-tests/testconfig/default.toml b/integration-tests/testconfig/default.toml index 63de9bc7fd..be0a31a18f 100644 --- a/integration-tests/testconfig/default.toml +++ b/integration-tests/testconfig/default.toml @@ -1,37 +1,76 @@ [Logging] +# set to true to flush logs to selected target regardless of test result; otherwise logs are only flushed if test failed test_log_collect = false +[Logging.Grafana] +base_url="https://grafana.ops.prod.cldev.sh" +base_url_github_ci="http://localhost:8080/primary" +dashboard_url="/d/ddf75041-1e39-42af-aa46-361fe4c36e9e/ci-e2e-tests-logs" + [Logging.LogStream] +# supported targets: file, loki, in-memory. if empty no logs will be persisted log_targets = ["file"] +# context timeout for starting log producer and also time-frame for requesting logs log_producer_timeout = "10s" +# number of retries before log producer gives up and stops listening to logs log_producer_retry_limit = 10 [ChainlinkImage] +# postgres version to use postgres_version = "15.6" -image = "public.ecr.aws/chainlink/chainlink" +# chainlink image tag to use version = "2.12.0" +# Set chainlink image using E2E_TEST_CHAINLINK_IMAGE env, as it is a test secret [Common] -chainlink_node_funding = 4 +# chainlink node funding in native token +chainlink_node_funding = 0.5 [Network] +# slice of networks to use; at lesat one network must be selected; each selected network must either be already defined in the CTF as a known network, or be defined in +# TOML test files as a new network selected_networks = ["simulated"] [PrivateEthereumNetwork] +# ethereum version to use; eth1 or eth2 (post-merge) ethereum_version = "eth1" +# execution layer to use; geth, besu, nethermind, erigon or reth execution_layer = "geth" [PrivateEthereumNetwork.EthereumChainConfig] +# duration of single slot, lower => faster block production, must be >= 3 seconds_per_slot = 3 +# number of slots in epoch, lower => faster epoch finalisation, must be >= 2 slots_per_epoch = 2 +# extra genesis delay, no need to modify, but it should be after all validators/beacon chain starts genesis_delay = 15 +# number of validators in the network validator_count = 4 +# chain id to use chain_id = 1337 +# slice of addresses that will be funded with native token in genesis addresses_to_fund = ["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"] +# map of hard fork epochs for each network; key is fork name, value is hard fork epoch +# keep in mind that this depends on the specific version of eth2 client you are using +# this configuration is fault-tolerant and incorrect forks will be ignored [PrivateEthereumNetwork.EthereumChainConfig.HardForkEpochs] Deneb = 500 +# General config of the Chainklink node corresponding to core/services/chainlink/config.go (Config struct) that excludes +# all chain-specific configuration, which is built based on selected_networks and either Chainlink Node's defaults for +# each network, or ChainConfigTOMLByChainID (if an entry with matching chain id is defined) or CommonChainConfigTOML (if no +# entry with matching chain id is defined). +# +# Please remember that if either ChainConfigTOMLByChainID or CommonChainConfigTOML is defined, it will override any defaults +# that Chainlink Node might have for the given network. Part of the configuration that defines blockchain node URLs is always +# dynamically generated based on the EVMNetwork configuration. +# +# Last, but not least, currently all selected networks are treated as EVM networks. There's no way to provide Solana, Starknet, +# Cosmos or Aptos configuration yet. +# +# If BaseConfigTOML is empty, then default base config provided by the Chainlink Node is used. +# Also, if tracing is enabled unique id will be generated and shared between all Chainlink nodes in the same test. [NodeConfig] BaseConfigTOML = """ [Feature] @@ -78,12 +117,14 @@ DeltaDial = '500ms' DeltaReconcile = '5s' """ -# override config toml related to EVMNode configs for chainlink nodes; applicable to all EVM node configs in chainlink toml +# Overrides default config TOML related to EVMNode configs for chainlink nodes; applicable to all EVM node configs in chainlink TOML. +# Do not use it, if you want the default values to be used. Passing blockchain nodes URLs here will have no effect. CommonChainConfigTOML = """ """ [NodeConfig.ChainConfigTOMLByChainID] -# applicable for simulated chain +# Chain-specific EVMNode config TOML for chainlink nodes; applicable to all EVM node configs in chainlink TOML. It takes precedence +# over CommonChainConfigTOML and Chainlink Node's defaults. Passing blockchain nodes URLs here will have no effect. 1337 = """ AutoCreateKey = true FinalityDepth = 1 @@ -97,13 +138,13 @@ FeeCapDefault = '200 gwei' [Seth] # controls which transactions are decoded/traced. Possbile values are: none, all, reverted (default). # if transaction level doesn't match, then calling Decode() does nothing. It's advised to keep it set -# to 'reverted' to limit noise. If you combine it with 'trace_to_json' it will save all possible data -# in JSON files for reverted transactions. +# to 'reverted' to limit noise. tracing_level = "reverted" -# saves each decoding/tracing results to JSON files; what exactly is saved depends on what we +# saves each decoding/tracing results to DOT files; what exactly is saved depends on what we # were able te decode, we try to save maximum information possible. It can either be: # just tx hash, decoded transaction or call trace. Which transactions traces are saved depends # on 'tracing_level'. +trace_outputs = ["dot", "console"] # number of addresses to be generated and runtime, if set to 0, no addresses will be generated # each generated address will receive a proportion of native tokens from root private key's balance @@ -218,66 +259,14 @@ gas_tip_cap = 1_800_000_000 [[Seth.networks]] name = "Sepolia Testnet" transaction_timeout = "3m" -eip_1559_dynamic_fees = false - -# automated gas estimation for live networks -# if set to true we will dynamically estimate gas for every transaction (based on suggested values, priority and congestion rate for last X blocks) -# gas_price_estimation_enabled = true -# number of blocks to use for congestion rate estimation (it will determine buffer added on top of suggested values) -# gas_price_estimation_blocks = 100 -# transaction priority, which determines adjustment factor multiplier applied to suggested values (fast - 1.2x, standard - 1x, slow - 0.8x) -# gas_price_estimation_tx_priority = "standard" - -# URLs -# if set they will overwrite URLs from EVMNetwork that Seth uses, can be either WS(S) or HTTP(S) -# urls_secret = ["ws://your-ws-url:8546"] - -# gas_limits -# gas limit should be explicitly set only if you are connecting to a node that's incapable of estimating gas limit itself (should only happen for very old versions) -# gas_limit = 14_000_000 -# transfer_gas_fee is gas limit that will be used, when funding CL nodes and returning funds from there and when funding and returning funds from ephemeral keys -# we use hardcoded value in order to be estimate how much funds are available for sending or returning after tx costs have been paid -transfer_gas_fee = 21_000 - -# manual settings, used when gas_price_estimation_enabled is false or when it fails -# legacy transactions -gas_price = 50_000_000_000 - -# EIP-1559 transactions -gas_fee_cap = 45_000_000_000 -gas_tip_cap = 10_000_000_000 - -[[Seth.networks]] -name = "Polygon Mumbai" -transaction_timeout = "3m" eip_1559_dynamic_fees = true - -# automated gas estimation for live networks -# if set to true we will dynamically estimate gas for every transaction (based on suggested values, priority and congestion rate for last X blocks) -# gas_price_estimation_enabled = true -# number of blocks to use for congestion rate estimation (it will determine buffer added on top of suggested values) -# gas_price_estimation_blocks = 100 -# transaction priority, which determines adjustment factor multiplier applied to suggested values (fast - 1.2x, standard - 1x, slow - 0.8x) -# gas_price_estimation_tx_priority = "standard" - -# URLs -# if set they will overwrite URLs from EVMNetwork that Seth uses, can be either WS(S) or HTTP(S) -# urls_secret = ["ws://your-ws-url:8546"] - -# gas_limits -# gas limit should be explicitly set only if you are connecting to a node that's incapable of estimating gas limit itself (should only happen for very old versions) -# gas_limit = 6_000_000 -# transfer_gas_fee is gas limit that will be used, when funding CL nodes and returning funds from there and when funding and returning funds from ephemeral keys -# we use hardcoded value in order to be estimate how much funds are available for sending or returning after tx costs have been paid transfer_gas_fee = 21_000 - -# manual settings, used when gas_price_estimation_enabled is false or when it fails -# legacy transactions -gas_price = 1_800_000_000 - -# EIP-1559 transactions -gas_fee_cap = 3_800_000_000 -gas_tip_cap = 1_800_000_000 +gas_price = 105_000_000_000 +gas_fee_cap = 150_312_843_059 +gas_tip_cap = 40_416_094 +gas_price_estimation_enabled = true +gas_price_estimation_blocks = 1000 +gas_price_estimation_tx_priority = "standard" [[Seth.networks]] name = "Polygon Amoy" @@ -286,11 +275,11 @@ eip_1559_dynamic_fees = true # automated gas estimation for live networks # if set to true we will dynamically estimate gas for every transaction (based on suggested values, priority and congestion rate for last X blocks) -# gas_price_estimation_enabled = true +gas_price_estimation_enabled = true # number of blocks to use for congestion rate estimation (it will determine buffer added on top of suggested values) -# gas_price_estimation_blocks = 100 +gas_price_estimation_blocks = 100 # transaction priority, which determines adjustment factor multiplier applied to suggested values (fast - 1.2x, standard - 1x, slow - 0.8x) -# gas_price_estimation_tx_priority = "standard" +gas_price_estimation_tx_priority = "standard" # URLs # if set they will overwrite URLs from EVMNetwork that Seth uses, can be either WS(S) or HTTP(S) @@ -309,41 +298,8 @@ gas_price = 200_000_000_000 # EIP-1559 transactions gas_fee_cap = 200_000_000_000 -gas_tip_cap = 2_000_000_000 - -[[Seth.networks]] -name = "Polygon zkEVM Goerli" -transaction_timeout = "3m" -eip_1559_dynamic_fees = false - -# automated gas estimation for live networks -# if set to true we will dynamically estimate gas for every transaction (based on suggested values, priority and congestion rate for last X blocks) -# gas_price_estimation_enabled = true -# number of blocks to use for congestion rate estimation (it will determine buffer added on top of suggested values) -# gas_price_estimation_blocks = 100 -# transaction priority, which determines adjustment factor multiplier applied to suggested values (fast - 1.2x, standard - 1x, slow - 0.8x) -# gas_price_estimation_tx_priority = "standard" - -# URLs -# if set they will overwrite URLs from EVMNetwork that Seth uses, can be either WS(S) or HTTP(S) -# urls_secret = ["ws://your-ws-url:8546"] +gas_tip_cap = 25_000_000_000 -# gas_limits -# gas limit should be explicitly set only if you are connecting to a node that's incapable of estimating gas limit itself (should only happen for very old versions) -# gas_limit = 9_000_000 -# transfer_gas_fee is gas limit that will be used, when funding CL nodes and returning funds from there and when funding and returning funds from ephemeral keys -# we use hardcoded value in order to be estimate how much funds are available for sending or returning after tx costs have been paid -transfer_gas_fee = 21_000 - -# manual settings, used when gas_price_estimation_enabled is false or when it fails -# legacy transactions -gas_price = 50_000_000 - -# EIP-1559 transactions - - -gas_fee_cap = 3_800_000_000 -gas_tip_cap = 1_800_000_000 [[Seth.networks]] name = "Optimism Sepolia" transaction_timeout = "3m" @@ -415,3 +371,300 @@ gas_price_estimation_blocks = 100 # priority of the transaction, can be "fast", "standard" or "slow" (the higher the priority, the higher adjustment factor will be used for gas estimation) [default: "standard"] gas_price_estimation_tx_priority = "standard" + + +[[Seth.networks]] +name = "Nexon Mainnet" +transaction_timeout = "3m" +eip_1559_dynamic_fees = true +transfer_gas_fee = 21_000 + +# manual settings, used when gas_price_estimation_enabled is false or when it fails +# legacy transactions +gas_price = 30_000_000_000 + +# EIP-1559 transactions +gas_fee_cap = 30_000_000_000 +gas_tip_cap = 1_800_000_000 + + +[Network.EVMNetworks.NEXON_MAINNET] +evm_name = "NEXON_MAINNET" +#evm_urls = ["rpc ws endpoint"] +#evm_http_urls = ["rpc http endpoint"] +client_implementation = "Ethereum" +#evm_keys = ["private keys you want to use"] +evm_simulated = false +evm_chainlink_transaction_limit = 5000 +evm_minimum_confirmations = 1 +evm_gas_estimation_buffer = 10000 +evm_supports_eip1559 = true +evm_default_gas_limit = 6000000 +evm_chain_id = 60118 + +[[Seth.networks]] +name = "Nexon Stage" +transaction_timeout = "3m" +eip_1559_dynamic_fees = true +transfer_gas_fee = 21_000 + +# manual settings, used when gas_price_estimation_enabled is false or when it fails +# legacy transactions +gas_price = 30_000_000_000 + +# EIP-1559 transactions +gas_fee_cap = 30_000_000_000 +gas_tip_cap = 1_800_000_000 + + +[Network.EVMNetworks.NEXON_STAGE] +evm_name = "NEXON_STAGE" +#evm_urls = ["rpc ws endpoint"] +#evm_http_urls = ["rpc http endpoint"] +client_implementation = "Ethereum" +#evm_keys = ["private keys you want to use"] +evm_simulated = false +evm_chainlink_transaction_limit = 5000 +evm_minimum_confirmations = 1 +evm_gas_estimation_buffer = 10000 +evm_supports_eip1559 = true +evm_default_gas_limit = 6000000 +evm_chain_id = 847799 + + +#### + +[[Seth.networks]] +name = "Nexon QA" +transaction_timeout = "3m" +eip_1559_dynamic_fees = true +transfer_gas_fee = 21_000 + +# manual settings, used when gas_price_estimation_enabled is false or when it fails +# legacy transactions +gas_price = 30_000_000_000 + +# EIP-1559 transactions +gas_fee_cap = 30_000_000_000 +gas_tip_cap = 1_800_000_000 + + +[Network.EVMNetworks.NEXON_QA] +evm_name = "NEXON_QA" +#evm_urls = ["rpc ws endpoint"] +#evm_http_urls = ["rpc http endpoint"] +client_implementation = "Ethereum" +#evm_keys = ["private keys you want to use"] +evm_simulated = false +evm_chainlink_transaction_limit = 5000 +evm_minimum_confirmations = 1 +evm_gas_estimation_buffer = 10000 +evm_supports_eip1559 = true +evm_default_gas_limit = 6000000 +evm_chain_id = 807424 + +##### + +[[Seth.networks]] +name = "Nexon Test" +transaction_timeout = "3m" +eip_1559_dynamic_fees = true +transfer_gas_fee = 21_000 + +# manual settings, used when gas_price_estimation_enabled is false or when it fails +# legacy transactions +gas_price = 30_000_000_000 + +# EIP-1559 transactions +gas_fee_cap = 30_000_000_000 +gas_tip_cap = 1_800_000_000 + + +[Network.EVMNetworks.NEXON_TEST] +evm_name = "NEXON_TEST" +#evm_urls = ["rpc ws endpoint"] +#evm_http_urls = ["rpc http endpoint"] +client_implementation = "Ethereum" +#evm_keys = ["private keys you want to use"] +evm_simulated = false +evm_chainlink_transaction_limit = 5000 +evm_minimum_confirmations = 1 +evm_gas_estimation_buffer = 10000 +evm_supports_eip1559 = true +evm_default_gas_limit = 6000000 +evm_chain_id = 595581 + +##### +[[Seth.networks]] +name = "Nexon Dev" +transaction_timeout = "3m" +eip_1559_dynamic_fees = true +transfer_gas_fee = 21_000 + +# manual settings, used when gas_price_estimation_enabled is false or when it fails +# legacy transactions +gas_price = 30_000_000_000 + +# EIP-1559 transactions +gas_fee_cap = 30_000_000_000 +gas_tip_cap = 1_800_000_000 + + +[Network.EVMNetworks.NEXON_DEV] +evm_name = "NEXON_DEV" +#evm_urls = ["rpc ws endpoint"] +#evm_http_urls = ["rpc http endpoint"] +client_implementation = "Ethereum" +#evm_keys = ["private keys you want to use"] +evm_simulated = false +evm_chainlink_transaction_limit = 5000 +evm_minimum_confirmations = 1 +evm_gas_estimation_buffer = 10000 +evm_supports_eip1559 = true +evm_default_gas_limit = 6000000 +evm_chain_id = 5668 + +[[Seth.networks]] +name = "LINEA_SEPOLIA" +chain_id = "59141" +transaction_timeout = "10m" +transfer_gas_fee = 21_000 +gas_price = 200_000_000_000_000 +eip_1559_dynamic_fees = false +gas_fee_cap = 109_694_825_437 +gas_tip_cap = 30_000_000_000 +gas_price_estimation_enabled = true +gas_price_estimation_blocks = 1000 +gas_price_estimation_tx_priority = "standard" + +[[Seth.networks]] +name = "LINEA_MAINNET" +chain_id = "59144" +transaction_timeout = "10m" +transfer_gas_fee = 21_000 +gas_price = 200_000_000_000_000 +eip_1559_dynamic_fees = false +gas_fee_cap = 109_694_825_437 +gas_tip_cap = 30_000_000_000 +gas_price_estimation_enabled = true +gas_price_estimation_blocks = 1000 +gas_price_estimation_tx_priority = "standard" + +[[Seth.networks]] +name = "ZKSYNC_SEPOLIA" +chain_id = "300" +transaction_timeout = "3m" +transfer_gas_fee = 21_000 +gas_price = 200_000_000_000 +eip_1559_dynamic_fees = true +gas_fee_cap = 109_694_825_437 +gas_tip_cap = 30_000_000_000 +gas_price_estimation_enabled = true +gas_price_estimation_blocks = 1000 +gas_price_estimation_tx_priority = "standard" + +[[Seth.networks]] +name = "ZKSYNC_MAINNET" +chain_id = "324" +transaction_timeout = "3m" +transfer_gas_fee = 21_000 +gas_price = 200_000_000_000 +eip_1559_dynamic_fees = true +gas_fee_cap = 109_694_825_437 +gas_tip_cap = 30_000_000_000 +gas_price_estimation_enabled = true +gas_price_estimation_blocks = 1000 +gas_price_estimation_tx_priority = "standard" + +[[Seth.networks]] +name = "POLYGON_ZKEVM_CARDONA" +transaction_timeout = "3m" +transfer_gas_fee = 21_000 +gas_price = 743_000_000 +eip_1559_dynamic_fees = false +gas_fee_cap = 1_725_800_000 +gas_tip_cap = 822_800_000 +gas_price_estimation_enabled = true +gas_price_estimation_blocks = 1000 +gas_price_estimation_tx_priority = "standard" + +[[Seth.networks]] +name = "HEDERA_TESTNET" +transaction_timeout = "3m" +transfer_gas_fee = 800_000 +gas_limit = 2_000_000 +gas_price = 2_500_000_000_000 +eip_1559_dynamic_fees = false +gas_fee_cap = 109_694_825_437 +gas_tip_cap = 30_000_000_000 +gas_price_estimation_enabled = true +gas_price_estimation_blocks = 1000 +gas_price_estimation_tx_priority = "standard" + +[[Seth.networks]] +name = "TREASURE_RUBY" +transaction_timeout = "10m" +transfer_gas_fee = 21_000 +gas_price = 100_000_000 +eip_1559_dynamic_fees = true +gas_fee_cap = 200_000_000 +gas_tip_cap = 100_000_000 +gas_price_estimation_enabled = true +gas_price_estimation_blocks = 1000 +gas_price_estimation_tx_priority = "standard" + +[[Seth.networks]] +name = "XLAYER_MAINNET" +transaction_timeout = "10m" +transfer_gas_fee = 21_000 +gas_price = 13_400_000_000 +eip_1559_dynamic_fees = false +gas_fee_cap = 109_694_825_437 +gas_tip_cap = 30_000_000_000 +gas_price_estimation_enabled = true +gas_price_estimation_blocks = 500 +gas_price_estimation_tx_priority = "standard" + +[[Seth.networks]] +name = "XLAYER_SEPOLIA" +transaction_timeout = "10m" +transfer_gas_fee = 21_000 +gas_price = 200_000_000_000 +eip_1559_dynamic_fees = false +gas_fee_cap = 109_694_825_437 +gas_tip_cap = 30_000_000_000 +gas_price_estimation_enabled = true +gas_price_estimation_blocks = 500 +gas_price_estimation_tx_priority = "standard" + +[[Seth.networks]] +name = "POLYGON_MAINNET" +transaction_timeout = "3m" +transfer_gas_fee = 21_000 +gas_price = 31_000_000_000 +eip_1559_dynamic_fees = true +gas_fee_cap = 61_000_000_000 +gas_tip_cap = 30_000_000_000 +gas_price_estimation_enabled = true +gas_price_estimation_blocks = 1000 + +[[Seth.networks]] +name = "ARBITRUM_MAINNET" +transaction_timeout = "10m" +transfer_gas_fee = 21_000 +gas_price = 10_000_000 +eip_1559_dynamic_fees = true +gas_fee_cap = 10_000_000 +gas_tip_cap = 0 +gas_price_estimation_enabled = true +gas_price_estimation_blocks = 1000 + +#### + +[Network.EVMNetworks.SONEIUM_SEPOLIA] +evm_name = "SONEIUM_SEPOLIA" +evm_chain_id = 1946 +client_implementation = "Optimism" +evm_simulated = false + +#### diff --git a/integration-tests/testconfig/forwarder_ocr/example.toml b/integration-tests/testconfig/forwarder_ocr/example.toml index 0b762299af..517a341f80 100644 --- a/integration-tests/testconfig/forwarder_ocr/example.toml +++ b/integration-tests/testconfig/forwarder_ocr/example.toml @@ -1,7 +1,6 @@ # Example of full config with all fields # General part [ChainlinkImage] -image="public.ecr.aws/chainlink/chainlink" version="2.7.0" [Logging] @@ -31,7 +30,7 @@ bearer_token_secret="bearer_token" base_url="http://grafana.url" # url of your grafana dashboard (prefix and suffix "/" are stirpped), example: /d/ad61652-2712-1722/my-dashboard dashboard_url="/d/your-dashboard" -# Grafana dashboard uid to annotate. Find it in Dashboard Settings -> JSON Model +# Grafana dashboard uid to annotate. Find it in Dashboard Settings -> JSON Model dashboard_uid="dashboard-uid-to-annotate" bearer_token_secret="my-awesome-token" @@ -39,15 +38,6 @@ bearer_token_secret="my-awesome-token" [Network] selected_networks=["polygon_mumbai"] -[Network.RpcHttpUrls] -polygon_mumbai = ["https://my-rpc-endpoint.io"] - -[Network.RpcWsUrls] -polygon_mumbai = ["https://my-rpc-endpoint.io"] - -[Network.WalletKeys] -polygon_mumbai = ["change-me-to-your-PK"] - [PrivateEthereumNetwork] # pos or pow consensus_type="pos" @@ -143,6 +133,9 @@ BumpPercent = 20 BumpMin = '100 gwei' """ +[OCR.Common] +number_of_contracts=1 + # load test specific configuration [Load.OCR] [Load.OCR.Common] @@ -162,9 +155,8 @@ chainlink_node_funding = 100 [Soak.OCR] [Soak.OCR.Common] +number_of_contracts=2 test_duration="15m" [Soak.OCR.Soak] -ocr_version="1" -number_of_contracts=2 -time_between_rounds="1m" \ No newline at end of file +time_between_rounds="1m" diff --git a/integration-tests/testconfig/forwarder_ocr/forwarder_ocr.toml b/integration-tests/testconfig/forwarder_ocr/forwarder_ocr.toml index 8fa0fa5db2..68ed21404f 100644 --- a/integration-tests/testconfig/forwarder_ocr/forwarder_ocr.toml +++ b/integration-tests/testconfig/forwarder_ocr/forwarder_ocr.toml @@ -57,7 +57,10 @@ MinContractPayment = 0 [Transactions] ForwardersEnabled = true - """ +""" + +[OCR.Common] +number_of_contracts=1 # load test specific configuration [Load.OCR] @@ -92,8 +95,8 @@ chainlink_node_funding = 0.5 [Soak.OCR] [Soak.OCR.Common] +number_of_contracts=2 test_duration = "15m" [Soak.OCR.Soak] -number_of_contracts = 2 -time_between_rounds = "1m" +time_between_rounds="1m" diff --git a/integration-tests/testconfig/forwarder_ocr2/example.toml b/integration-tests/testconfig/forwarder_ocr2/example.toml index b3bc45d270..3ec3e4c690 100644 --- a/integration-tests/testconfig/forwarder_ocr2/example.toml +++ b/integration-tests/testconfig/forwarder_ocr2/example.toml @@ -31,7 +31,7 @@ bearer_token_secret="bearer_token" base_url="http://grafana.url" # url of your grafana dashboard (prefix and suffix "/" are stirpped), example: /d/ad61652-2712-1722/my-dashboard dashboard_url="/d/your-dashboard" -# Grafana dashboard uid to annotate. Find it in Dashboard Settings -> JSON Model +# Grafana dashboard uid to annotate. Find it in Dashboard Settings -> JSON Model dashboard_uid="dashboard-uid-to-annotate" bearer_token_secret="my-awesome-token" @@ -39,15 +39,6 @@ bearer_token_secret="my-awesome-token" [Network] selected_networks=["polygon_mumbai"] -[Network.RpcHttpUrls] -polygon_mumbai = ["https://my-rpc-endpoint.io"] - -[Network.RpcWsUrls] -polygon_mumbai = ["https://my-rpc-endpoint.io"] - -[Network.WalletKeys] -polygon_mumbai = ["change-me-to-your-PK"] - [PrivateEthereumNetwork] # pos or pow consensus_type="pos" @@ -143,12 +134,15 @@ BumpPercent = 20 BumpMin = '100 gwei' """ +[OCR2.Common] +number_of_contracts=1 + # load test specific configuration -[Load.OCR] -[Load.OCR.Common] +[Load.OCR2] +[Load.OCR2.Common] eth_funds = 3 -[Load.OCR.Load] +[Load.OCR2.Load] test_duration = "3m" rate_limit_unit_duration = "1m" rate = 3 @@ -160,11 +154,10 @@ ea_change_interval = "5s" [Soak.Common] chainlink_node_funding = 100 -[Soak.OCR] -[Soak.OCR.Common] +[Soak.OCR2] +[Soak.OCR2.Common] +number_of_contracts=2 test_duration="15m" -[Soak.OCR.Soak] -ocr_version="1" -number_of_contracts=2 -time_between_rounds="1m" \ No newline at end of file +[Soak.OCR2.Soak] +time_between_rounds="1m" diff --git a/integration-tests/testconfig/forwarder_ocr2/forwarder_ocr2.toml b/integration-tests/testconfig/forwarder_ocr2/forwarder_ocr2.toml index 3f2a8610a8..76d5695a8b 100644 --- a/integration-tests/testconfig/forwarder_ocr2/forwarder_ocr2.toml +++ b/integration-tests/testconfig/forwarder_ocr2/forwarder_ocr2.toml @@ -53,7 +53,10 @@ MinContractPayment = 0 [Transactions] ForwardersEnabled = true - """ +""" + +[OCR2.Common] +number_of_contracts=1 # load test specific configuration [Load.OCR2.Common] @@ -85,10 +88,10 @@ ea_change_interval = "5s" chainlink_node_funding = 1 [Soak.OCR2.Common] +number_of_contracts=2 test_duration="15m" [Soak.OCR2.Soak] -number_of_contracts=2 time_between_rounds="1m" diff --git a/integration-tests/testconfig/functions/example.toml b/integration-tests/testconfig/functions/example.toml index 7502a6fc44..74d931632a 100644 --- a/integration-tests/testconfig/functions/example.toml +++ b/integration-tests/testconfig/functions/example.toml @@ -1,7 +1,6 @@ # Example of full config with all fields # General part [ChainlinkImage] -image="public.ecr.aws/chainlink/chainlink" version="2.7.0" [Logging] @@ -16,38 +15,10 @@ log_producer_timeout="10s" # number of retries before log producer gives up and stops listening to logs log_producer_retry_limit=10 -[Logging.Loki] -tenant_id="tenant_id" -# full URL of Loki ingest endpoint -endpoint="https://loki.url/api/v3/push" -# currently only needed when using public instance -basic_auth_secret="loki-basic-auth" -# only needed for cloud grafana -bearer_token_secret="bearer_token" - -# LogStream will try to shorten Grafana URLs by default (if all 3 variables are set) -[Logging.Grafana] -# grafana url (trailing "/" will be stripped) -base_url="http://grafana.url" -# url of your grafana dashboard (prefix and suffix "/" are stirpped), example: /d/ad61652-2712-1722/my-dashboard -dashboard_url="/d/your-dashboard" -# Grafana dashboard uid to annotate. Find it in Dashboard Settings -> JSON Model -dashboard_uid="dashboard-uid-to-annotate" -bearer_token_secret="my-awesome-token" - # if you want to use simulated network [Network] selected_networks=["polygon_mumbai"] -[Network.RpcHttpUrls] -polygon_mumbai = ["https://my-rpc-endpoint.io"] - -[Network.RpcWsUrls] -polygon_mumbai = ["https://my-rpc-endpoint.io"] - -[Network.WalletKeys] -polygon_mumbai = ["change-me-to-your-PK"] - [PrivateEthereumNetwork] # pos or pow consensus_type="pos" diff --git a/integration-tests/testconfig/job_distributor/job_distributor.toml b/integration-tests/testconfig/job_distributor/job_distributor.toml new file mode 100644 index 0000000000..6dae45fb14 --- /dev/null +++ b/integration-tests/testconfig/job_distributor/job_distributor.toml @@ -0,0 +1,46 @@ +[NodeConfig] +BaseConfigTOML = """ +[Feature] +FeedsManager = true +LogPoller = true +UICSAKeys = true +MultiFeedsManagers = true + +[Log] +Level = 'debug' +JSONConsole = true + +[Log.File] +MaxSize = '0b' + +[WebServer] +AllowOrigins = '*' +HTTPPort = 6688 +SecureCookies = false +HTTPWriteTimeout = '3m' +SessionTimeout = '999h0m0s' + +[WebServer.RateLimit] +Authenticated = 2000 +Unauthenticated = 1000 + +[WebServer.TLS] +HTTPSPort = 0 + +[Database] +MaxIdleConns = 20 +MaxOpenConns = 40 +MigrateOnStartup = true + +[OCR] +Enabled = true +DefaultTransactionQueueDepth = 0 + +[P2P] +[P2P.V2] +Enabled = true +ListenAddresses = ['0.0.0.0:6690'] +AnnounceAddresses = ['0.0.0.0:6690'] +DeltaDial = '500ms' +DeltaReconcile = '5s' +""" diff --git a/integration-tests/testconfig/keeper/config.go b/integration-tests/testconfig/keeper/config.go index 60bdfd8697..81a88ef67e 100644 --- a/integration-tests/testconfig/keeper/config.go +++ b/integration-tests/testconfig/keeper/config.go @@ -1,110 +1,8 @@ package keeper -import ( - "errors" - - "github.com/smartcontractkit/chainlink-testing-framework/lib/blockchain" -) - type Config struct { - Common *Common `toml:"Common"` - Resiliency *ResiliencyConfig `toml:"Resiliency"` } func (c *Config) Validate() error { - if c.Common == nil { - return nil - } - if err := c.Common.Validate(); err != nil { - return err - } - if c.Resiliency == nil { - return nil - } - return c.Resiliency.Validate() -} - -type Common struct { - RegistryToTest *string `toml:"registry_to_test"` - NumberOfRegistries *int `toml:"number_of_registries"` - NumberOfNodes *int `toml:"number_of_nodes"` - NumberOfUpkeeps *int `toml:"number_of_upkeeps"` - UpkeepGasLimit *int64 `toml:"upkeep_gas_limit"` - CheckGasToBurn *int64 `toml:"check_gas_to_burn"` - PerformGasToBurn *int64 `toml:"perform_gas_to_burn"` - MaxPerformGas *int64 `toml:"max_perform_gas"` - BlockRange *int64 `toml:"block_range"` - BlockInterval *int64 `toml:"block_interval"` - ForceSingleTxKey *bool `toml:"forces_single_tx_key"` - DeleteJobsOnEnd *bool `toml:"delete_jobs_on_end"` - RegistryAddress *string `toml:"registry_address"` - RegistrarAddress *string `toml:"registrar_address"` - LinkTokenAddress *string `toml:"link_token_address"` - EthFeedAddress *string `toml:"eth_feed_address"` - GasFeedAddress *string `toml:"gas_feed_address"` -} - -func (c *Common) Validate() error { - if c.RegistryToTest == nil || *c.RegistryToTest == "" { - return errors.New("registry_to_test must be set") - } - if c.NumberOfRegistries == nil || *c.NumberOfRegistries <= 0 { - return errors.New("number_of_registries must be a positive integer") - } - if c.NumberOfNodes == nil || *c.NumberOfNodes <= 0 { - return errors.New("number_of_nodes must be a positive integer") - } - if c.NumberOfUpkeeps == nil || *c.NumberOfUpkeeps <= 0 { - return errors.New("number_of_upkeeps must be a positive integer") - } - if c.UpkeepGasLimit == nil || *c.UpkeepGasLimit <= 0 { - return errors.New("upkeep_gas_limit must be a positive integer") - } - if c.CheckGasToBurn == nil || *c.CheckGasToBurn <= 0 { - return errors.New("check_gas_to_burn must be a positive integer") - } - if c.PerformGasToBurn == nil || *c.PerformGasToBurn <= 0 { - return errors.New("perform_gas_to_burn must be a positive integer") - } - if c.MaxPerformGas == nil || *c.MaxPerformGas <= 0 { - return errors.New("max_perform_gas must be a positive integer") - } - if c.BlockRange == nil || *c.BlockRange <= 0 { - return errors.New("block_range must be a positive integer") - } - if c.BlockInterval == nil || *c.BlockInterval <= 0 { - return errors.New("block_interval must be a positive integer") - } - if c.RegistryAddress == nil { - c.RegistryAddress = new(string) - } - if c.RegistrarAddress == nil { - c.RegistrarAddress = new(string) - } - if c.LinkTokenAddress == nil { - c.LinkTokenAddress = new(string) - } - if c.EthFeedAddress == nil { - c.EthFeedAddress = new(string) - } - if c.GasFeedAddress == nil { - c.GasFeedAddress = new(string) - } - return nil -} - -type ResiliencyConfig struct { - ContractCallLimit *uint `toml:"contract_call_limit"` - ContractCallInterval *blockchain.StrDuration `toml:"contract_call_interval"` -} - -func (c *ResiliencyConfig) Validate() error { - if c.ContractCallLimit == nil { - return errors.New("contract_call_limit must be set") - } - if c.ContractCallInterval == nil { - return errors.New("contract_call_interval must be set") - } - return nil } diff --git a/integration-tests/testconfig/keeper/example.toml b/integration-tests/testconfig/keeper/example.toml index 5abb583562..4efbf97482 100644 --- a/integration-tests/testconfig/keeper/example.toml +++ b/integration-tests/testconfig/keeper/example.toml @@ -1,7 +1,6 @@ # Example of full config with all fields # General part [ChainlinkImage] -image="public.ecr.aws/chainlink/chainlink" version="2.7.0" [Logging] @@ -16,38 +15,10 @@ log_producer_timeout="10s" # number of retries before log producer gives up and stops listening to logs log_producer_retry_limit=10 -[Logging.Loki] -tenant_id="tenant_id" -# full URL of Loki ingest endpoint -endpoint="https://loki.url/api/v3/push" -# currently only needed when using public instance -basic_auth_secret="loki-basic-auth" -# only needed for cloud grafana -bearer_token_secret="bearer_token" - -# LogStream will try to shorten Grafana URLs by default (if all 3 variables are set) -[Logging.Grafana] -# grafana url (trailing "/" will be stripped) -base_url="http://grafana.url" -# url of your grafana dashboard (prefix and suffix "/" are stirpped), example: /d/ad61652-2712-1722/my-dashboard -dashboard_url="/d/your-dashboard" -# Grafana dashboard uid to annotate. Find it in Dashboard Settings -> JSON Model -dashboard_uid="dashboard-uid-to-annotate" -bearer_token_secret="my-awesome-token" - # if you want to use polygon_mumbial [Network] selected_networks=["polygon_mumbai"] -[Network.RpcHttpUrls] -polygon_mumbai = ["https://my-rpc-endpoint.io"] - -[Network.RpcWsUrls] -polygon_mumbai = ["https://my-rpc-endpoint.io"] - -[Network.WalletKeys] -polygon_mumbai = ["change-me-to-your-PK"] - [PrivateEthereumNetwork] # pos or pow consensus_type="pos" diff --git a/integration-tests/testconfig/keeper/keeper.toml b/integration-tests/testconfig/keeper/keeper.toml index b4a6a3b2c0..d483d6df49 100644 --- a/integration-tests/testconfig/keeper/keeper.toml +++ b/integration-tests/testconfig/keeper/keeper.toml @@ -32,66 +32,4 @@ HTTPSPort = 0 [Keeper] TurnLookBack = 0 -""" - -[Keeper.Common] -registry_to_test = "2_1" -number_of_registries = 1 -number_of_nodes = 6 -number_of_upkeeps = 500 -upkeep_gas_limit = 1500000 -check_gas_to_burn = 100000 -perform_gas_to_burn = 50000 -max_perform_gas = 5000000 -block_range = 100 -block_interval = 20 -forces_single_tx_key = false -delete_jobs_on_end = true - -# will retry roughly for 1h before giving up (900 * 4s) -[Keeper.Resiliency] -# number of retries before giving up -contract_call_limit = 900 -# static interval between retries -contract_call_interval = "4s" - -[Seth] -# keeper benchmark running on simulated network requires 100k per node -root_key_funds_buffer = 700_000 - -[Benchmark.NodeConfig] -BaseConfigTOML = """ -[Feature] -LogPoller = true - -[OCR2] -Enabled = true - -[P2P] -[P2P.V2] -Enabled = true -AnnounceAddresses = ["0.0.0.0:6690"] -ListenAddresses = ["0.0.0.0:6690"] -[Keeper] -TurnLookBack = 0 -[WebServer] -HTTPWriteTimeout = '1h' -""" - -CommonChainConfigTOML = """ -""" - -[Benchmark.NodeConfig.ChainConfigTOMLByChainID] -# applicable for simulated chain -1337 = """ -FinalityDepth = 50 -LogPollInterval = '1s' -MinIncomingConfirmations = 1 - -[HeadTracker] -HistoryDepth = 100 - -[GasEstimator] -Mode = 'FixedPrice' -LimitDefault = 5_000_000 -""" +""" \ No newline at end of file diff --git a/integration-tests/testconfig/log_poller/example.toml b/integration-tests/testconfig/log_poller/example.toml index c28d36ae12..78f3b5482d 100644 --- a/integration-tests/testconfig/log_poller/example.toml +++ b/integration-tests/testconfig/log_poller/example.toml @@ -1,7 +1,6 @@ # Example of full config with all fields # General part [ChainlinkImage] -image="public.ecr.aws/chainlink/chainlink" version="2.7.0" [Logging] @@ -16,38 +15,10 @@ log_producer_timeout="10s" # number of retries before log producer gives up and stops listening to logs log_producer_retry_limit=10 -[Logging.Loki] -tenant_id="tenant_id" -# full URL of Loki ingest endpoint -endpoint="https://loki.url/api/v3/push" -# currently only needed when using public instance -basic_auth_secret="loki-basic-auth" -# only needed for cloud grafana -bearer_token_secret="bearer_token" - -# LogStream will try to shorten Grafana URLs by default (if all 3 variables are set) -[Logging.Grafana] -# grafana url (trailing "/" will be stripped) -base_url="http://grafana.url" -# url of your grafana dashboard (prefix and suffix "/" are stirpped), example: /d/ad61652-2712-1722/my-dashboard -dashboard_url="/d/your-dashboard" -# Grafana dashboard uid to annotate. Find it in Dashboard Settings -> JSON Model -dashboard_uid="dashboard-uid-to-annotate" -bearer_token_secret="my-awesome-token" - # if you want to use polygon_mumbial [Network] selected_networks=["polygon_mumbai"] -[Network.RpcHttpUrls] -polygon_mumbai = ["https://my-rpc-endpoint.io"] - -[Network.RpcWsUrls] -polygon_mumbai = ["https://my-rpc-endpoint.io"] - -[Network.WalletKeys] -polygon_mumbai = ["change-me-to-your-PK"] - [PrivateEthereumNetwork] # pos or pow consensus_type="pos" diff --git a/integration-tests/testconfig/node/example.toml b/integration-tests/testconfig/node/example.toml index 510379b4f0..bc5628e46b 100644 --- a/integration-tests/testconfig/node/example.toml +++ b/integration-tests/testconfig/node/example.toml @@ -1,7 +1,6 @@ # Example of full config with all fields # General part [ChainlinkImage] -image="public.ecr.aws/chainlink/chainlink" version="2.7.0" [Logging] @@ -16,38 +15,10 @@ log_producer_timeout="10s" # number of retries before log producer gives up and stops listening to logs log_producer_retry_limit=10 -[Logging.Loki] -tenant_id="tenant_id" -# full URL of Loki ingest endpoint -endpoint="https://loki.url/api/v3/push" -# currently only needed when using public instance -basic_auth_secret="loki-basic-auth" -# only needed for cloud grafana -bearer_token_secret="bearer_token" - -# LogStream will try to shorten Grafana URLs by default (if all 3 variables are set) -[Logging.Grafana] -# grafana url (trailing "/" will be stripped) -base_url="http://grafana.url" -# url of your grafana dashboard (prefix and suffix "/" are stirpped), example: /d/ad61652-2712-1722/my-dashboard -dashboard_url="/d/your-dashboard" -# Grafana dashboard uid to annotate. Find it in Dashboard Settings -> JSON Model -dashboard_uid="dashboard-uid-to-annotate" -bearer_token_secret="my-awesome-token" - # if you want to use polygon_mumbial [Network] selected_networks=["polygon_mumbai"] -[Network.RpcHttpUrls] -polygon_mumbai = ["https://my-rpc-endpoint.io"] - -[Network.RpcWsUrls] -polygon_mumbai = ["https://my-rpc-endpoint.io"] - -[Network.WalletKeys] -polygon_mumbai = ["change-me-to-your-PK"] - [PrivateEthereumNetwork] # pos or pow consensus_type="pos" @@ -77,5 +48,4 @@ chainlink_node_funding = 0.5 # Test-specific part [ChainlinkUpgradeImage] -image="public.ecr.aws/chainlink/chainlink" version="2.8.0" \ No newline at end of file diff --git a/integration-tests/testconfig/ocr/example.toml b/integration-tests/testconfig/ocr/example.toml index 92262241df..7c1c755567 100644 --- a/integration-tests/testconfig/ocr/example.toml +++ b/integration-tests/testconfig/ocr/example.toml @@ -1,7 +1,6 @@ # Example of full config with all fields # General part [ChainlinkImage] -image="public.ecr.aws/chainlink/chainlink" version="2.7.0" [Logging] @@ -31,7 +30,7 @@ bearer_token_secret="bearer_token" base_url="http://grafana.url" # url of your grafana dashboard (prefix and suffix "/" are stirpped), example: /d/ad61652-2712-1722/my-dashboard dashboard_url="/d/your-dashboard" -# Grafana dashboard uid to annotate. Find it in Dashboard Settings -> JSON Model +# Grafana dashboard uid to annotate. Find it in Dashboard Settings -> JSON Model dashboard_uid="dashboard-uid-to-annotate" bearer_token_secret="my-awesome-token" @@ -39,15 +38,6 @@ bearer_token_secret="my-awesome-token" [Network] selected_networks=["polygon_mumbai"] -[Network.RpcHttpUrls] -polygon_mumbai = ["https://my-rpc-endpoint.io"] - -[Network.RpcWsUrls] -polygon_mumbai = ["https://my-rpc-endpoint.io"] - -[Network.WalletKeys] -polygon_mumbai = ["change-me-to-your-PK"] - [PrivateEthereumNetwork] # pos or pow consensus_type="pos" @@ -142,6 +132,9 @@ BumpPercent = 20 BumpMin = '100 gwei' """ +[OCR.Common] +number_of_contracts=1 + # load test specific configuration [Load.OCR] [Load.OCR.Common] @@ -161,8 +154,8 @@ chainlink_node_funding = 100 [Soak.OCR] [Soak.OCR.Common] +number_of_contracts=2 test_duration="15m" [Soak.OCR.Soak] -number_of_contracts=2 time_between_rounds="1m" diff --git a/integration-tests/testconfig/ocr/ocr.go b/integration-tests/testconfig/ocr/ocr.go index d8250d407f..240fd2afea 100644 --- a/integration-tests/testconfig/ocr/ocr.go +++ b/integration-tests/testconfig/ocr/ocr.go @@ -2,15 +2,19 @@ package ocr import ( "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/chainlink-testing-framework/lib/blockchain" ) type Config struct { - Soak *SoakConfig `toml:"Soak"` - Load *Load `toml:"Load"` - Volume *Volume `toml:"Volume"` - Common *Common `toml:"Common"` + Soak *SoakConfig `toml:"Soak"` + Load *Load `toml:"Load"` + Volume *Volume `toml:"Volume"` + Common *Common `toml:"Common"` + Contracts *Contracts `toml:"Contracts"` } func (o *Config) Validate() error { @@ -29,15 +33,24 @@ func (o *Config) Validate() error { return err } } + if o.Contracts != nil { + if err := o.Contracts.Validate(); err != nil { + return err + } + } return nil } type Common struct { - ETHFunds *int `toml:"eth_funds"` - TestDuration *blockchain.StrDuration `toml:"test_duration"` + NumberOfContracts *int `toml:"number_of_contracts"` + ETHFunds *int `toml:"eth_funds"` + TestDuration *blockchain.StrDuration `toml:"test_duration"` } func (o *Common) Validate() error { + if o.NumberOfContracts != nil && *o.NumberOfContracts < 1 { + return errors.New("when number_of_contracts is set, it must be greater than 0") + } if o.ETHFunds != nil && *o.ETHFunds < 0 { return errors.New("eth_funds must be set and cannot be negative") } @@ -117,16 +130,169 @@ func (o *Volume) Validate() error { } type SoakConfig struct { - NumberOfContracts *int `toml:"number_of_contracts"` TimeBetweenRounds *blockchain.StrDuration `toml:"time_between_rounds"` } func (o *SoakConfig) Validate() error { - if o.NumberOfContracts == nil || *o.NumberOfContracts <= 1 { - return errors.New("number_of_contracts must be set and be greater than 1") - } if o.TimeBetweenRounds == nil || o.TimeBetweenRounds.Duration == 0 { return errors.New("time_between_rounds must be set and be a positive integer") } return nil } + +// For more information on the configuration of contracts, see https://smartcontract-it.atlassian.net/wiki/spaces/TT/pages/828407894/Contracts+addresses+in+TOML+convention +type Contracts struct { + ShouldBeUsed *bool `toml:"use"` + LinkTokenAddress *string `toml:"link_token"` + OffChainAggregatorAddresses []string `toml:"offchain_aggregators"` + Settings map[string]ContractSetting `toml:"Settings"` +} + +func (o *Contracts) Validate() error { + if o.LinkTokenAddress != nil && !common.IsHexAddress(*o.LinkTokenAddress) { + return errors.New("link_token must be a valid ethereum address") + } + if o.OffChainAggregatorAddresses != nil { + allEnabled := make(map[bool]int) + allConfigure := make(map[bool]int) + for _, address := range o.OffChainAggregatorAddresses { + if !common.IsHexAddress(address) { + return fmt.Errorf("offchain_aggregators must be valid ethereum addresses, but %s is not", address) + } + + if v, ok := o.Settings[address]; ok { + if v.ShouldBeUsed != nil { + allEnabled[*v.ShouldBeUsed]++ + } else { + allEnabled[true]++ + } + if v.Configure != nil { + allConfigure[*v.Configure]++ + } else { + allConfigure[true]++ + } + } + } + + if allEnabled[true] > 0 && allEnabled[false] > 0 { + return errors.New("either all or none offchain_aggregators must be used") + } + + if allConfigure[true] > 0 && allConfigure[false] > 0 { + return errors.New("either all or none offchain_aggregators must be configured") + } + } + + return nil +} + +func (o *Config) UseExistingContracts() bool { + if o.Contracts == nil { + return false + } + + if o.Contracts.ShouldBeUsed != nil { + return *o.Contracts.ShouldBeUsed + } + + return false +} + +func (o *Config) LinkTokenContractAddress() (common.Address, error) { + if o.Contracts != nil && o.Contracts.LinkTokenAddress != nil { + return common.HexToAddress(*o.Contracts.LinkTokenAddress), nil + } + + return common.Address{}, errors.New("link token address must be set") +} + +func (o *Config) UseExistingLinkTokenContract() bool { + if !o.UseExistingContracts() { + return false + } + + if o.Contracts.LinkTokenAddress == nil { + return false + } + + if len(o.Contracts.Settings) == 0 { + return true + } + + if v, ok := o.Contracts.Settings[*o.Contracts.LinkTokenAddress]; ok { + return v.ShouldBeUsed != nil && *v.ShouldBeUsed + } + + return true +} + +type ContractSetting struct { + ShouldBeUsed *bool `toml:"use"` + Configure *bool `toml:"configure"` +} + +type OffChainAggregatorsConfig interface { + OffChainAggregatorsContractsAddresses() []common.Address + UseExistingOffChainAggregatorsContracts() bool + ConfigureExistingOffChainAggregatorsContracts() bool + NumberOfContractsToDeploy() int +} + +func (o *Config) UseExistingOffChainAggregatorsContracts() bool { + if !o.UseExistingContracts() { + return false + } + + if len(o.Contracts.OffChainAggregatorAddresses) == 0 { + return false + } + + if len(o.Contracts.Settings) == 0 { + return true + } + + for _, address := range o.Contracts.OffChainAggregatorAddresses { + if v, ok := o.Contracts.Settings[address]; ok { + return v.ShouldBeUsed != nil && *v.ShouldBeUsed + } + } + + return true +} + +func (o *Config) OffChainAggregatorsContractsAddresses() []common.Address { + var ocrInstanceAddresses []common.Address + if !o.UseExistingOffChainAggregatorsContracts() { + return ocrInstanceAddresses + } + + for _, address := range o.Contracts.OffChainAggregatorAddresses { + ocrInstanceAddresses = append(ocrInstanceAddresses, common.HexToAddress(address)) + } + + return ocrInstanceAddresses +} + +func (o *Config) ConfigureExistingOffChainAggregatorsContracts() bool { + if !o.UseExistingOffChainAggregatorsContracts() { + return true + } + + for _, address := range o.Contracts.OffChainAggregatorAddresses { + for maybeOcrAddress, setting := range o.Contracts.Settings { + if maybeOcrAddress == address { + return setting.Configure != nil && *setting.Configure + } + } + } + + return true +} + +func (o *Config) NumberOfContractsToDeploy() int { + if o.Common != nil && o.Common.NumberOfContracts != nil { + return *o.Common.NumberOfContracts + } + + return 0 +} diff --git a/integration-tests/testconfig/ocr/ocr.toml b/integration-tests/testconfig/ocr/ocr.toml index 17ee4d7b68..36cded0b85 100644 --- a/integration-tests/testconfig/ocr/ocr.toml +++ b/integration-tests/testconfig/ocr/ocr.toml @@ -43,6 +43,9 @@ Enabled = true ListenAddresses = ['0.0.0.0:6690'] """ +[OCR.Common] +number_of_contracts=1 + # load test specific configuration [Load.OCR] [Load.OCR.Common] @@ -77,9 +80,9 @@ chainlink_node_funding = 0.5 [Soak.OCR] [Soak.OCR.Common] test_duration="15m" +number_of_contracts=2 [Soak.OCR.Soak] -number_of_contracts=2 time_between_rounds="1m" # Soak test configuration with Geth reorg below finality with FinalityTagEnabled=false diff --git a/integration-tests/testconfig/ocr/overrides/arbitrum_mainnet.toml b/integration-tests/testconfig/ocr/overrides/arbitrum_mainnet.toml new file mode 100644 index 0000000000..953d9f351a --- /dev/null +++ b/integration-tests/testconfig/ocr/overrides/arbitrum_mainnet.toml @@ -0,0 +1,18 @@ +[Network] +selected_networks = ["ARBITRUM_MAINNET"] + +[Soak.Common] +chainlink_node_funding = 0.1 + +[Soak.OCR] +[Soak.OCR.Common] +test_duration = "24h" + +[Soak.OCR.Soak] +time_between_rounds = "10m" + +[OCR.Common] +number_of_contracts = 2 + +[Seth] +experiments_enabled = ["slow_funds_return"] diff --git a/integration-tests/testconfig/ocr/overrides/arbitrum_sepolia.toml b/integration-tests/testconfig/ocr/overrides/arbitrum_sepolia.toml new file mode 100644 index 0000000000..1428e50b0e --- /dev/null +++ b/integration-tests/testconfig/ocr/overrides/arbitrum_sepolia.toml @@ -0,0 +1,15 @@ +[Network] +selected_networks = ["ARBITRUM_SEPOLIA"] + +[Soak.Common] +chainlink_node_funding = 1 + +[Soak.OCR] +[Soak.OCR.Common] +test_duration = "24h" + +[Soak.OCR.Soak] +time_between_rounds = "2m" + +[OCR.Common] +number_of_contracts = 2 diff --git a/integration-tests/testconfig/ocr/overrides/base_mainnet.toml b/integration-tests/testconfig/ocr/overrides/base_mainnet.toml new file mode 100644 index 0000000000..a285c23758 --- /dev/null +++ b/integration-tests/testconfig/ocr/overrides/base_mainnet.toml @@ -0,0 +1,18 @@ +[Network] +selected_networks = ["BASE_MAINNET"] + +[Soak.Common] +chainlink_node_funding = 5 + +[Soak.OCR] +[Soak.OCR.Common] +test_duration = "24h" + +[Soak.OCR.Soak] +time_between_rounds = "10m" + +[OCR.Common] +number_of_contracts = 2 + +[Seth] +experiments_enabled = ["slow_funds_return"] diff --git a/integration-tests/testconfig/ocr/overrides/base_sepolia.toml b/integration-tests/testconfig/ocr/overrides/base_sepolia.toml new file mode 100644 index 0000000000..3dbbadcbef --- /dev/null +++ b/integration-tests/testconfig/ocr/overrides/base_sepolia.toml @@ -0,0 +1,15 @@ +[Network] +selected_networks = ["BASE_SEPOLIA"] + +[Soak.Common] +chainlink_node_funding = 1 + +[Soak.OCR] +[Soak.OCR.Common] +test_duration = "24h" + +[Soak.OCR.Soak] +time_between_rounds = "2m" + +[OCR.Common] +number_of_contracts = 2 diff --git a/integration-tests/testconfig/ocr/overrides/celo_alfajores.toml b/integration-tests/testconfig/ocr/overrides/celo_alfajores.toml new file mode 100644 index 0000000000..37c4a8cf16 --- /dev/null +++ b/integration-tests/testconfig/ocr/overrides/celo_alfajores.toml @@ -0,0 +1,15 @@ +[Network] +selected_networks = ["CELO_ALFAJORES"] + +[Soak.Common] +chainlink_node_funding = 10 + +[Soak.OCR] +[Soak.OCR.Common] +test_duration = "24h" + +[Soak.OCR.Soak] +time_between_rounds = "2m" + +[OCR.Common] +number_of_contracts = 2 diff --git a/integration-tests/testconfig/ocr/overrides/ethereum_sepolia.toml b/integration-tests/testconfig/ocr/overrides/ethereum_sepolia.toml new file mode 100644 index 0000000000..612e47506a --- /dev/null +++ b/integration-tests/testconfig/ocr/overrides/ethereum_sepolia.toml @@ -0,0 +1,15 @@ +[Network] +selected_networks = ["SEPOLIA"] + +[Soak.Common] +chainlink_node_funding = 1 + +[Soak.OCR] +[Soak.OCR.Common] +test_duration = "24h" + +[Soak.OCR.Soak] +time_between_rounds = "2m" + +[OCR.Common] +number_of_contracts = 2 diff --git a/integration-tests/testconfig/ocr/overrides/linea_sepolia.toml b/integration-tests/testconfig/ocr/overrides/linea_sepolia.toml new file mode 100644 index 0000000000..6fa6218d54 --- /dev/null +++ b/integration-tests/testconfig/ocr/overrides/linea_sepolia.toml @@ -0,0 +1,15 @@ +[Network] +selected_networks = ["Linea_Sepolia"] + +[Soak.Common] +chainlink_node_funding = 1 + +[Soak.OCR] +[Soak.OCR.Common] +test_duration = "24h" + +[Soak.OCR.Soak] +time_between_rounds = "2m" + +[OCR.Common] +number_of_contracts = 2 diff --git a/integration-tests/testconfig/ocr/overrides/optimism_mainnet.toml b/integration-tests/testconfig/ocr/overrides/optimism_mainnet.toml new file mode 100644 index 0000000000..eec0640baa --- /dev/null +++ b/integration-tests/testconfig/ocr/overrides/optimism_mainnet.toml @@ -0,0 +1,18 @@ +[Network] +selected_networks = ["OPTIMISM_MAINNET"] + +[Soak.Common] +chainlink_node_funding = 0.1 + +[Soak.OCR] +[Soak.OCR.Common] +test_duration = "24h" + +[Soak.OCR.Soak] +time_between_rounds = "10m" + +[OCR.Common] +number_of_contracts = 2 + +[Seth] +experiments_enabled = ["slow_funds_return"] diff --git a/integration-tests/testconfig/ocr/overrides/optimism_sepolia.toml b/integration-tests/testconfig/ocr/overrides/optimism_sepolia.toml new file mode 100644 index 0000000000..fe666b6aa9 --- /dev/null +++ b/integration-tests/testconfig/ocr/overrides/optimism_sepolia.toml @@ -0,0 +1,15 @@ +[Network] +selected_networks = ["OPTIMISM_SEPOLIA"] + +[Soak.Common] +chainlink_node_funding = 1 + +[Soak.OCR] +[Soak.OCR.Common] +test_duration = "24h" + +[Soak.OCR.Soak] +time_between_rounds = "2m" + +[OCR.Common] +number_of_contracts = 2 diff --git a/integration-tests/testconfig/ocr/overrides/scroll_sepolia.toml b/integration-tests/testconfig/ocr/overrides/scroll_sepolia.toml new file mode 100644 index 0000000000..0beedfd05e --- /dev/null +++ b/integration-tests/testconfig/ocr/overrides/scroll_sepolia.toml @@ -0,0 +1,15 @@ +[Network] +selected_networks = ["SCROLL_SEPOLIA"] + +[Soak.Common] +chainlink_node_funding = 1 + +[Soak.OCR] +[Soak.OCR.Common] +test_duration = "24h" + +[Soak.OCR.Soak] +time_between_rounds = "2m" + +[OCR.Common] +number_of_contracts = 2 diff --git a/integration-tests/testconfig/ocr/overrides/wemix_mainnet.toml b/integration-tests/testconfig/ocr/overrides/wemix_mainnet.toml new file mode 100644 index 0000000000..4a7859db3c --- /dev/null +++ b/integration-tests/testconfig/ocr/overrides/wemix_mainnet.toml @@ -0,0 +1,18 @@ +[Network] +selected_networks = ["WEMIX_MAINNET"] + +[Soak.Common] +chainlink_node_funding = 5 + +[Soak.OCR] +[Soak.OCR.Common] +test_duration = "24h" + +[Soak.OCR.Soak] +time_between_rounds = "10m" + +[OCR.Common] +number_of_contracts = 2 + +[Seth] +experiments_enabled = ["slow_funds_return"] diff --git a/integration-tests/testconfig/ocr2/example.toml b/integration-tests/testconfig/ocr2/example.toml index 36e3105f21..319f64d258 100644 --- a/integration-tests/testconfig/ocr2/example.toml +++ b/integration-tests/testconfig/ocr2/example.toml @@ -1,7 +1,6 @@ # Example of full config with all fields # General part [ChainlinkImage] -image="public.ecr.aws/chainlink/chainlink" version="2.7.0" [Logging] @@ -31,7 +30,7 @@ bearer_token_secret="bearer_token" base_url="http://grafana.url" # url of your grafana dashboard (prefix and suffix "/" are stirpped), example: /d/ad61652-2712-1722/my-dashboard dashboard_url="/d/your-dashboard" -# Grafana dashboard uid to annotate. Find it in Dashboard Settings -> JSON Model +# Grafana dashboard uid to annotate. Find it in Dashboard Settings -> JSON Model dashboard_uid="dashboard-uid-to-annotate" bearer_token_secret="my-awesome-token" @@ -39,15 +38,6 @@ bearer_token_secret="my-awesome-token" [Network] selected_networks=["polygon_mumbai"] -[Network.RpcHttpUrls] -polygon_mumbai = ["https://my-rpc-endpoint.io"] - -[Network.RpcWsUrls] -polygon_mumbai = ["https://my-rpc-endpoint.io"] - -[Network.WalletKeys] -polygon_mumbai = ["change-me-to-your-PK"] - [PrivateEthereumNetwork] # pos or pow consensus_type="pos" @@ -142,6 +132,9 @@ BumpPercent = 20 BumpMin = '100 gwei' """ +[OCR2.Common] +number_of_contracts=1 + # load test specific configuration [Load.OCR2] [Load.OCR2.Common] @@ -161,8 +154,8 @@ chainlink_node_funding = 100 [Soak.OCR2] [Soak.OCR.Common] +number_of_contracts=2 test_duration="15m" [Soak.OCR2.Soak] -number_of_contracts=2 time_between_rounds="1m" diff --git a/integration-tests/testconfig/ocr2/ocr2.go b/integration-tests/testconfig/ocr2/ocr2.go index 1e7f034e04..60169e944f 100644 --- a/integration-tests/testconfig/ocr2/ocr2.go +++ b/integration-tests/testconfig/ocr2/ocr2.go @@ -1,14 +1,13 @@ package ocr2 import ( - "errors" - - "github.com/smartcontractkit/chainlink-testing-framework/lib/blockchain" + "github.com/smartcontractkit/chainlink/integration-tests/testconfig/ocr" ) type Config struct { - Soak *SoakConfig `toml:"Soak"` - Common *Common `toml:"Common"` + Soak *ocr.SoakConfig `toml:"Soak"` + Common *ocr.Common `toml:"Common"` + Contracts *ocr.Contracts `toml:"Contracts"` } func (o *Config) Validate() error { @@ -22,32 +21,10 @@ func (o *Config) Validate() error { return err } } - return nil -} - -type Common struct { - ETHFunds *int `toml:"eth_funds"` - TestDuration *blockchain.StrDuration `toml:"test_duration"` -} - -func (o *Common) Validate() error { - if o.ETHFunds != nil && *o.ETHFunds < 0 { - return errors.New("eth_funds must be set and cannot be negative") - } - return nil -} - -type SoakConfig struct { - NumberOfContracts *int `toml:"number_of_contracts"` - TimeBetweenRounds *blockchain.StrDuration `toml:"time_between_rounds"` -} - -func (o *SoakConfig) Validate() error { - if o.NumberOfContracts == nil || *o.NumberOfContracts <= 1 { - return errors.New("number_of_contracts must be set and be greater than 1") - } - if o.TimeBetweenRounds == nil || o.TimeBetweenRounds.Duration == 0 { - return errors.New("time_between_rounds must be set and be a positive integer") + if o.Contracts != nil { + if err := o.Contracts.Validate(); err != nil { + return err + } } return nil } diff --git a/integration-tests/testconfig/ocr2/ocr2.toml b/integration-tests/testconfig/ocr2/ocr2.toml index ad195913bd..62d92574ea 100644 --- a/integration-tests/testconfig/ocr2/ocr2.toml +++ b/integration-tests/testconfig/ocr2/ocr2.toml @@ -43,6 +43,9 @@ Enabled = true ListenAddresses = ['0.0.0.0:6690'] """ +[OCR2.Common] +number_of_contracts=1 + # load test specific configuration [Load.OCR2] [Load.OCR2.Common] @@ -76,8 +79,8 @@ chainlink_node_funding = 0.5 [Soak.OCR2] [Soak.OCR2.Common] +number_of_contracts=2 test_duration="15m" [Soak.OCR2.Soak] -number_of_contracts=2 time_between_rounds="1m" diff --git a/integration-tests/testconfig/ocr2/overrides/base_sepolia.toml b/integration-tests/testconfig/ocr2/overrides/base_sepolia.toml new file mode 100644 index 0000000000..76e4fc4722 --- /dev/null +++ b/integration-tests/testconfig/ocr2/overrides/base_sepolia.toml @@ -0,0 +1,18 @@ +[Network] +selected_networks = ["BASE_SEPOLIA"] + +[Soak.Common] +chainlink_node_funding = 1 + +[Soak.OCR2] +[Soak.OCR2.Common] +test_duration = "24h" + +[Soak.OCR2.Soak] +time_between_rounds = "2m" + +[OCR2.Common] +number_of_contracts = 2 + +[Seth] +experiments_enabled = ["slow_funds_return"] diff --git a/integration-tests/testconfig/ocr2/overrides/ethereum_sepolia.toml b/integration-tests/testconfig/ocr2/overrides/ethereum_sepolia.toml new file mode 100644 index 0000000000..f7e0240780 --- /dev/null +++ b/integration-tests/testconfig/ocr2/overrides/ethereum_sepolia.toml @@ -0,0 +1,15 @@ +[Network] +selected_networks = ["SEPOLIA"] + +[Soak.Common] +chainlink_node_funding = 1 + +[Soak.OCR2] +[Soak.OCR2.Common] +test_duration = "24h" + +[Soak.OCR2.Soak] +time_between_rounds = "2m" + +[OCR2.Common] +number_of_contracts = 2 diff --git a/integration-tests/testconfig/ocr2/overrides/polygon_amoy.toml b/integration-tests/testconfig/ocr2/overrides/polygon_amoy.toml new file mode 100644 index 0000000000..41a31897c5 --- /dev/null +++ b/integration-tests/testconfig/ocr2/overrides/polygon_amoy.toml @@ -0,0 +1,15 @@ +[Network] +selected_networks = ["POLYGON_AMOY"] + +[Soak.Common] +chainlink_node_funding = 20 + +[Soak.OCR2] +[Soak.OCR2.Common] +test_duration = "24h" + +[Soak.OCR2.Soak] +time_between_rounds = "2m" + +[OCR2.Common] +number_of_contracts = 2 diff --git a/integration-tests/testconfig/ocr2/overrides/polygon_mainnet.toml b/integration-tests/testconfig/ocr2/overrides/polygon_mainnet.toml new file mode 100644 index 0000000000..51da279313 --- /dev/null +++ b/integration-tests/testconfig/ocr2/overrides/polygon_mainnet.toml @@ -0,0 +1,18 @@ +[Network] +selected_networks = ["POLYGON_MAINNET"] + +[Soak.Common] +chainlink_node_funding = 5 + +[Soak.OCR2] +[Soak.OCR2.Common] +test_duration = "24h" + +[Soak.OCR2.Soak] +time_between_rounds = "10m" + +[OCR2.Common] +number_of_contracts = 2 + +[Seth] +experiments_enabled = ["slow_funds_return"] diff --git a/integration-tests/testconfig/ocr2/overrides/wemix_testnet.toml b/integration-tests/testconfig/ocr2/overrides/wemix_testnet.toml new file mode 100644 index 0000000000..82bc06c17e --- /dev/null +++ b/integration-tests/testconfig/ocr2/overrides/wemix_testnet.toml @@ -0,0 +1,15 @@ +[Network] +selected_networks = ["WEMIX_TESTNET"] + +[Soak.Common] +chainlink_node_funding = 10 + +[Soak.OCR2] +[Soak.OCR2.Common] +test_duration = "24h" + +[Soak.OCR2.Soak] +time_between_rounds = "2m" + +[OCR2.Common] +number_of_contracts = 2 diff --git a/integration-tests/testconfig/ocr2/overrides/xlayer_sepolia.toml b/integration-tests/testconfig/ocr2/overrides/xlayer_sepolia.toml new file mode 100644 index 0000000000..d58098c5b0 --- /dev/null +++ b/integration-tests/testconfig/ocr2/overrides/xlayer_sepolia.toml @@ -0,0 +1,15 @@ +[Network] +selected_networks = ["xlayer_sepolia"] + +[Soak.Common] +chainlink_node_funding = 1 + +[Soak.OCR2] +[Soak.OCR2.Common] +test_duration = "24h" + +[Soak.OCR2.Soak] +time_between_rounds = "2m" + +[OCR2.Common] +number_of_contracts = 2 diff --git a/integration-tests/testconfig/testconfig.go b/integration-tests/testconfig/testconfig.go index 9a910b7e8c..f80d05ae62 100644 --- a/integration-tests/testconfig/testconfig.go +++ b/integration-tests/testconfig/testconfig.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/barkimedes/go-deepcopy" + "github.com/ethereum/go-ethereum/common" "github.com/google/uuid" "github.com/pelletier/go-toml/v2" "github.com/pkg/errors" @@ -27,6 +28,7 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/osutil" a_config "github.com/smartcontractkit/chainlink/integration-tests/testconfig/automation" + ccip_config "github.com/smartcontractkit/chainlink/integration-tests/testconfig/ccip" f_config "github.com/smartcontractkit/chainlink/integration-tests/testconfig/functions" keeper_config "github.com/smartcontractkit/chainlink/integration-tests/testconfig/keeper" lp_config "github.com/smartcontractkit/chainlink/integration-tests/testconfig/log_poller" @@ -72,6 +74,21 @@ type Ocr2TestConfig interface { GetOCR2Config() *ocr_config.Config } +type CCIPTestConfig interface { + GetCCIPConfig() *ccip_config.Config +} + +type LinkTokenContractConfig interface { + LinkTokenContractAddress() (common.Address, error) + UseExistingLinkTokenContract() bool +} + +const ( + E2E_TEST_DATA_STREAMS_URL_ENV = "E2E_TEST_DATA_STREAMS_URL" + E2E_TEST_DATA_STREAMS_USERNAME_ENV = "E2E_TEST_DATA_STREAMS_USERNAME" + E2E_TEST_DATA_STREAMS_PASSWORD_ENV = "E2E_TEST_DATA_STREAMS_PASSWORD" +) + type TestConfig struct { ctf_config.TestConfig @@ -85,6 +102,7 @@ type TestConfig struct { VRF *vrf_config.Config `toml:"VRF"` VRFv2 *vrfv2_config.Config `toml:"VRFv2"` VRFv2Plus *vrfv2plus_config.Config `toml:"VRFv2Plus"` + CCIP *ccip_config.Config `toml:"CCIP"` ConfigurationNames []string `toml:"-"` } @@ -198,6 +216,10 @@ func (c TestConfig) GetOCRConfig() *ocr_config.Config { return c.OCR } +func (c TestConfig) GetCCIPConfig() *ccip_config.Config { + return c.CCIP +} + func (c TestConfig) GetConfigurationNames() []string { return c.ConfigurationNames } @@ -253,6 +275,8 @@ const ( VRF Product = "vrf" VRFv2 Product = "vrfv2" VRFv2Plus Product = "vrfv2plus" + + CCIP Product = "ccip" ) const TestTypeEnvVarName = "TEST_TYPE" @@ -347,13 +371,19 @@ func GetConfig(configurationNames []string, product Product) (TestConfig, error) } } - // it needs some custom logic, so we do it separately - err := testConfig.readNetworkConfiguration() + logger.Info().Msg("Setting env vars from testsecrets dot-env files") + err := ctf_config.LoadSecretEnvsFromFiles() if err != nil { - return TestConfig{}, errors.Wrapf(err, "error reading network config") + return TestConfig{}, errors.Wrapf(err, "error reading test config values from ~/.testsecrets file") + } + + logger.Info().Msg("Reading config values from existing env vars") + err = testConfig.ReadFromEnvVar() + if err != nil { + return TestConfig{}, errors.Wrapf(err, "error reading test config values from env vars") } - logger.Info().Msg("Reading configs from Base64 override env var") + logger.Info().Msgf("Overriding config from %s env var", Base64OverrideEnvVarName) configEncoded, isSet := os.LookupEnv(Base64OverrideEnvVarName) if isSet && configEncoded != "" { logger.Debug().Msgf("Found base64 config override environment variable '%s' found", Base64OverrideEnvVarName) @@ -374,16 +404,9 @@ func GetConfig(configurationNames []string, product Product) (TestConfig, error) logger.Debug().Msg("Base64 config override from environment variable not found") } - logger.Info().Msg("Loading config values from default ~/.testsecrets env file") - err = ctf_config.LoadSecretEnvsFromFiles() - if err != nil { - return TestConfig{}, errors.Wrapf(err, "error reading test config values from ~/.testsecrets file") - } - - logger.Info().Msg("Reading values from environment variables") - err = testConfig.ReadFromEnvVar() + err = testConfig.readNetworkConfiguration() if err != nil { - return TestConfig{}, errors.Wrapf(err, "error reading test config values from env vars") + return TestConfig{}, errors.Wrapf(err, "error reading network config") } logger.Debug().Msg("Validating test config") @@ -404,6 +427,55 @@ func GetConfig(configurationNames []string, product Product) (TestConfig, error) return testConfig, nil } +// Read config values from environment variables +func (c *TestConfig) ReadFromEnvVar() error { + logger := logging.GetTestLogger(nil) + + // Read values for base config from env vars + err := c.TestConfig.ReadFromEnvVar() + if err != nil { + return errors.Wrapf(err, "error reading test config values from env vars") + } + + dsURL := ctf_config.MustReadEnvVar_String(E2E_TEST_DATA_STREAMS_URL_ENV) + if dsURL != "" { + if c.Automation == nil { + c.Automation = &a_config.Config{} + } + if c.Automation.DataStreams == nil { + c.Automation.DataStreams = &a_config.DataStreams{} + } + logger.Debug().Msgf("Using %s env var to override Automation.DataStreams.URL", E2E_TEST_DATA_STREAMS_URL_ENV) + c.Automation.DataStreams.URL = &dsURL + } + + dsUsername := ctf_config.MustReadEnvVar_String(E2E_TEST_DATA_STREAMS_USERNAME_ENV) + if dsUsername != "" { + if c.Automation == nil { + c.Automation = &a_config.Config{} + } + if c.Automation.DataStreams == nil { + c.Automation.DataStreams = &a_config.DataStreams{} + } + logger.Debug().Msgf("Using %s env var to override Automation.DataStreams.Username", E2E_TEST_DATA_STREAMS_USERNAME_ENV) + c.Automation.DataStreams.Username = &dsUsername + } + + dsPassword := ctf_config.MustReadEnvVar_String(E2E_TEST_DATA_STREAMS_PASSWORD_ENV) + if dsPassword != "" { + if c.Automation == nil { + c.Automation = &a_config.Config{} + } + if c.Automation.DataStreams == nil { + c.Automation.DataStreams = &a_config.DataStreams{} + } + logger.Debug().Msgf("Using %s env var to override Automation.DataStreams.Password", E2E_TEST_DATA_STREAMS_PASSWORD_ENV) + c.Automation.DataStreams.Password = &dsPassword + } + + return nil +} + func (c *TestConfig) logRiskySettings(logger zerolog.Logger) { isAnySimulated := false for _, network := range c.Network.SelectedNetworks { diff --git a/integration-tests/testconfig/vrfv2/config.go b/integration-tests/testconfig/vrfv2/config.go index 76d54a45d5..5e94040396 100644 --- a/integration-tests/testconfig/vrfv2/config.go +++ b/integration-tests/testconfig/vrfv2/config.go @@ -3,8 +3,6 @@ package testconfig import ( "errors" - "github.com/ethereum/go-ethereum/common" - vrf_common_config "github.com/smartcontractkit/chainlink/integration-tests/testconfig/common/vrf" ) @@ -52,10 +50,6 @@ func (c *ExistingEnvConfig) Validate() error { if *c.SubID == 0 { return errors.New("sub_id must be positive value") } - - if c.LinkAddress != nil && !common.IsHexAddress(*c.LinkAddress) { - return errors.New("link_address must be a valid hex address") - } } return c.Funding.Validate() diff --git a/integration-tests/testconfig/vrfv2/example.toml b/integration-tests/testconfig/vrfv2/example.toml index 9417a422cf..13af6dee62 100644 --- a/integration-tests/testconfig/vrfv2/example.toml +++ b/integration-tests/testconfig/vrfv2/example.toml @@ -1,7 +1,6 @@ # Example of full config with all fields # General part [ChainlinkImage] -image="public.ecr.aws/chainlink/chainlink" version="2.7.0" [Logging] @@ -16,38 +15,10 @@ log_producer_timeout="10s" # number of retries before log producer gives up and stops listening to logs log_producer_retry_limit=10 -[Logging.Loki] -tenant_id="tenant_id" -# full URL of Loki ingest endpoint -endpoint="https://loki.url/api/v3/push" -# currently only needed when using public instance -basic_auth_secret="loki-basic-auth" -# only needed for cloud grafana -bearer_token_secret="bearer_token" - -# LogStream will try to shorten Grafana URLs by default (if all 3 variables are set) -[Logging.Grafana] -# grafana url (trailing "/" will be stripped) -base_url="http://grafana.url" -# url of your grafana dashboard (prefix and suffix "/" are stirpped), example: /d/ad61652-2712-1722/my-dashboard -dashboard_url="/d/your-dashboard" -# Grafana dashboard uid to annotate. Find it in Dashboard Settings -> JSON Model -dashboard_uid="dashboard-uid-to-annotate" -bearer_token_secret="my-awesome-token" - # if you want to use polygon_mumbial [Network] selected_networks=["polygon_mumbai"] -[Network.RpcHttpUrls] -polygon_mumbai = ["https://my-rpc-endpoint.io"] - -[Network.RpcWsUrls] -polygon_mumbai = ["https://my-rpc-endpoint.io"] - -[Network.WalletKeys] -polygon_mumbai = ["change-me-to-your-PK"] - [PrivateEthereumNetwork] # pos or pow consensus_type="pos" diff --git a/integration-tests/testconfig/vrfv2/overrides/new_env/arbitrum_sepolia_new_env_test_config.toml b/integration-tests/testconfig/vrfv2/overrides/new_env/arbitrum_sepolia_new_env_test_config.toml new file mode 100644 index 0000000000..9181c0f51f --- /dev/null +++ b/integration-tests/testconfig/vrfv2/overrides/new_env/arbitrum_sepolia_new_env_test_config.toml @@ -0,0 +1,8 @@ +[Network] +selected_networks = ["ARBITRUM_SEPOLIA"] + +[ARBITRUM_SEPOLIA.VRFv2.General] +use_existing_env = false + +[Logging] +test_log_collect = true diff --git a/integration-tests/testconfig/vrfv2/overrides/new_env/avalanche_fuji_new_env_test_config.toml b/integration-tests/testconfig/vrfv2/overrides/new_env/avalanche_fuji_new_env_test_config.toml new file mode 100644 index 0000000000..cfeaa5a797 --- /dev/null +++ b/integration-tests/testconfig/vrfv2/overrides/new_env/avalanche_fuji_new_env_test_config.toml @@ -0,0 +1,8 @@ +[Network] +selected_networks = ["AVALANCHE_FUJI"] + +[AVALANCHE_FUJI.VRFv2.General] +use_existing_env = false + +[Logging] +test_log_collect = true diff --git a/integration-tests/testconfig/vrfv2/overrides/new_env/bsc_testnet_new_env_test_config.toml b/integration-tests/testconfig/vrfv2/overrides/new_env/bsc_testnet_new_env_test_config.toml new file mode 100644 index 0000000000..ec938767ec --- /dev/null +++ b/integration-tests/testconfig/vrfv2/overrides/new_env/bsc_testnet_new_env_test_config.toml @@ -0,0 +1,8 @@ +[Network] +selected_networks = ["BSC_TESTNET"] + +[BSC_TESTNET.VRFv2.General] +use_existing_env = false + +[Logging] +test_log_collect = true diff --git a/integration-tests/testconfig/vrfv2/overrides/new_env/polygon_amoy_new_env_test_config.toml b/integration-tests/testconfig/vrfv2/overrides/new_env/polygon_amoy_new_env_test_config.toml new file mode 100644 index 0000000000..6331647a88 --- /dev/null +++ b/integration-tests/testconfig/vrfv2/overrides/new_env/polygon_amoy_new_env_test_config.toml @@ -0,0 +1,8 @@ +[Network] +selected_networks = ["POLYGON_AMOY"] + +[POLYGON_AMOY.VRFv2.General] +use_existing_env = false + +[Logging] +test_log_collect = true diff --git a/integration-tests/testconfig/vrfv2/overrides/new_env/sepolia_new_env_test_config.toml b/integration-tests/testconfig/vrfv2/overrides/new_env/sepolia_new_env_test_config.toml new file mode 100644 index 0000000000..591f07e662 --- /dev/null +++ b/integration-tests/testconfig/vrfv2/overrides/new_env/sepolia_new_env_test_config.toml @@ -0,0 +1,8 @@ +[Network] +selected_networks = ["SEPOLIA"] + +[SEPOLIA.VRFv2.General] +use_existing_env = false + +[Logging] +test_log_collect = true diff --git a/integration-tests/testconfig/vrfv2/overrides/staging/polygon_amoy_staging_test_config.toml b/integration-tests/testconfig/vrfv2/overrides/staging/polygon_amoy_staging_test_config.toml new file mode 100644 index 0000000000..acdff9ff4e --- /dev/null +++ b/integration-tests/testconfig/vrfv2/overrides/staging/polygon_amoy_staging_test_config.toml @@ -0,0 +1,24 @@ +[Network] +selected_networks = ["POLYGON_AMOY"] + +[POLYGON_AMOY.VRFv2.General] +use_existing_env = true + +[POLYGON_AMOY.VRFv2.ExistingEnv] +coordinator_address = "0x919BEF67CE94604A7Cd5747F2c0088C8EB8A93aa" +consumer_address = "" +sub_id = "" +key_hash = "0xcd9c54d52db91522b69fe8ddc40d1f78156795de0efb2adbc053a438b9ee14a6" +create_fund_subs_and_add_consumers = true +node_sending_key_funding_min = 10 +node_sending_keys = [ + "0xD4B787C4ce6E04c16b5FDce2c25956b8F4432195", + # BHS + "0x638372de870eF0F8E675A3f67F18D5bd4A2fd804", + "0xF9eF03816411D037202d5ed4457dC1613e3bd729", + "0xCD66973f8fbaE787211EC20228c6bd90D83562A8", + "0x242ea1F4Bb72EF643B2D8EF22e18a89f00742F40", + "0xaA09B4F9B5710b239fdbf1D0f535dd7f86F91219", + "0xe6b72B647B8B45C5562F7a5259E187889C747d3b", + "0x2c1185C4d3B0B4a577d4079Ee193A4e293164D9d" +] diff --git a/integration-tests/testconfig/vrfv2/vrfv2.toml b/integration-tests/testconfig/vrfv2/vrfv2.toml index 011e90c15f..634e3074fe 100644 --- a/integration-tests/testconfig/vrfv2/vrfv2.toml +++ b/integration-tests/testconfig/vrfv2/vrfv2.toml @@ -19,6 +19,7 @@ MaxSize = '0b' [WebServer] AllowOrigins = '*' HTTPPort = 6688 +HTTPWriteTimeout = '1m0s' SecureCookies = false [WebServer.RateLimit] @@ -113,11 +114,17 @@ bhf_job_run_timeout = "1h" [VRFv2.ExistingEnv] coordinator_address = "" -consumer_address = "" -sub_id = 1 key_hash = "" + +use_existing_wrapper = false +wrapper_address = "" create_fund_subs_and_add_consumers = true -link_address = "" +sub_id = 1 +consumer_address = "" + +create_fund_add_wrapper_consumers = true +wrapper_consumer_address = "" + node_sending_key_funding_min = 10 node_sending_keys = [ "", diff --git a/integration-tests/testconfig/vrfv2plus/config.go b/integration-tests/testconfig/vrfv2plus/config.go index 9d863afdd1..4ae5ee5c35 100644 --- a/integration-tests/testconfig/vrfv2plus/config.go +++ b/integration-tests/testconfig/vrfv2plus/config.go @@ -57,8 +57,12 @@ type General struct { CoordinatorLinkPremiumPercentage *uint8 `toml:"coordinator_link_premium_percentage"` //OP Stack chains settings - L1FeeCalculationMode uint8 `toml:"l1_fee_calculation_mode"` - L1FeeCoefficient uint8 `toml:"l1_fee_coefficient"` + L1FeeCalculationMode *uint8 `toml:"l1_fee_calculation_mode"` + L1FeeCoefficient *uint8 `toml:"l1_fee_coefficient"` + + UseTestCoordinator *bool `toml:"use_test_coordinator"` + + SubBillingTolerance *float64 `toml:"sub_billing_tolerance_wei"` } func (c *General) Validate() error { @@ -101,6 +105,9 @@ func (c *General) Validate() error { if c.CoordinatorLinkPremiumPercentage == nil { return errors.New("coordinator_link_premium_percentage must not be nil") } + if c.UseTestCoordinator == nil { + return errors.New("use_test_coordinator must not be nil") + } return nil } diff --git a/integration-tests/testconfig/vrfv2plus/example.toml b/integration-tests/testconfig/vrfv2plus/example.toml index d6e7a3f28d..160e9ba03a 100644 --- a/integration-tests/testconfig/vrfv2plus/example.toml +++ b/integration-tests/testconfig/vrfv2plus/example.toml @@ -1,7 +1,6 @@ # Example of full config with all fields # General part [ChainlinkImage] -image="public.ecr.aws/chainlink/chainlink" version="2.7.0" [Logging] @@ -16,38 +15,10 @@ log_producer_timeout="10s" # number of retries before log producer gives up and stops listening to logs log_producer_retry_limit=10 -[Logging.Loki] -tenant_id="tenant_id" -# full URL of Loki ingest endpoint -endpoint="https://loki.url/api/v3/push" -# currently only needed when using public instance -basic_auth_secret="loki-basic-auth" -# only needed for cloud grafana -bearer_token_secret="bearer_token" - -# LogStream will try to shorten Grafana URLs by default (if all 3 variables are set) -[Logging.Grafana] -# grafana url (trailing "/" will be stripped) -base_url="http://grafana.url" -# url of your grafana dashboard (prefix and suffix "/" are stirpped), example: /d/ad61652-2712-1722/my-dashboard -dashboard_url="/d/your-dashboard" -# Grafana dashboard uid to annotate. Find it in Dashboard Settings -> JSON Model -dashboard_uid="dashboard-uid-to-annotate" -bearer_token_secret="my-awesome-token" - # if you want to use polygon_mumbial [Network] selected_networks=["polygon_mumbai"] -[Network.RpcHttpUrls] -polygon_mumbai = ["https://my-rpc-endpoint.io"] - -[Network.RpcWsUrls] -polygon_mumbai = ["https://my-rpc-endpoint.io"] - -[Network.WalletKeys] -polygon_mumbai = ["change-me-to-your-PK"] - [PrivateEthereumNetwork] # pos or pow consensus_type="pos" diff --git a/integration-tests/testconfig/vrfv2plus/overrides/new_env/arbitrum_sepolia_new_env_test_config.toml b/integration-tests/testconfig/vrfv2plus/overrides/new_env/arbitrum_sepolia_new_env_test_config.toml new file mode 100644 index 0000000000..271ef037bd --- /dev/null +++ b/integration-tests/testconfig/vrfv2plus/overrides/new_env/arbitrum_sepolia_new_env_test_config.toml @@ -0,0 +1,8 @@ +[Network] +selected_networks = ["ARBITRUM_SEPOLIA"] + +[ARBITRUM_SEPOLIA.VRFv2Plus.General] +use_existing_env = false + +[Logging] +test_log_collect = true diff --git a/integration-tests/testconfig/vrfv2plus/overrides/new_env/avalanche_fuji_new_env_test_config.toml b/integration-tests/testconfig/vrfv2plus/overrides/new_env/avalanche_fuji_new_env_test_config.toml new file mode 100644 index 0000000000..273d796ba6 --- /dev/null +++ b/integration-tests/testconfig/vrfv2plus/overrides/new_env/avalanche_fuji_new_env_test_config.toml @@ -0,0 +1,8 @@ +[Network] +selected_networks = ["AVALANCHE_FUJI"] + +[AVALANCHE_FUJI.VRFv2Plus.General] +use_existing_env = false + +[Logging] +test_log_collect = true diff --git a/integration-tests/testconfig/vrfv2plus/overrides/new_env/base_sepolia_new_env_test_config.toml b/integration-tests/testconfig/vrfv2plus/overrides/new_env/base_sepolia_new_env_test_config.toml new file mode 100644 index 0000000000..f93c75cc38 --- /dev/null +++ b/integration-tests/testconfig/vrfv2plus/overrides/new_env/base_sepolia_new_env_test_config.toml @@ -0,0 +1,8 @@ +[Network] +selected_networks = ["BASE_SEPOLIA"] + +[BASE_SEPOLIA.VRFv2Plus.General] +use_existing_env = false + +[Logging] +test_log_collect = true diff --git a/integration-tests/testconfig/vrfv2plus/overrides/new_env/bsc_testnet_new_env_test_config.toml b/integration-tests/testconfig/vrfv2plus/overrides/new_env/bsc_testnet_new_env_test_config.toml new file mode 100644 index 0000000000..16cc9a6e53 --- /dev/null +++ b/integration-tests/testconfig/vrfv2plus/overrides/new_env/bsc_testnet_new_env_test_config.toml @@ -0,0 +1,8 @@ +[Network] +selected_networks = ["BSC_TESTNET"] + +[BSC_TESTNET.VRFv2Plus.General] +use_existing_env = false + +[Logging] +test_log_collect = true diff --git a/integration-tests/testconfig/vrfv2plus/overrides/new_env/nexon_dev_new_env_test_config.toml b/integration-tests/testconfig/vrfv2plus/overrides/new_env/nexon_dev_new_env_test_config.toml new file mode 100644 index 0000000000..5d7b774108 --- /dev/null +++ b/integration-tests/testconfig/vrfv2plus/overrides/new_env/nexon_dev_new_env_test_config.toml @@ -0,0 +1,8 @@ +[Network] +selected_networks = ["NEXON_DEV"] + +[NEXON_DEV.VRFv2Plus.General] +use_existing_env = false + +[Logging] +test_log_collect = true diff --git a/integration-tests/testconfig/vrfv2plus/overrides/new_env/nexon_qa_new_env_test_config.toml b/integration-tests/testconfig/vrfv2plus/overrides/new_env/nexon_qa_new_env_test_config.toml new file mode 100644 index 0000000000..0f761128af --- /dev/null +++ b/integration-tests/testconfig/vrfv2plus/overrides/new_env/nexon_qa_new_env_test_config.toml @@ -0,0 +1,8 @@ +[Network] +selected_networks = ["NEXON_QA"] + +[NEXON_QA.VRFv2Plus.General] +use_existing_env = false + +[Logging] +test_log_collect = true diff --git a/integration-tests/testconfig/vrfv2plus/overrides/new_env/nexon_stage_new_env_test_config.toml b/integration-tests/testconfig/vrfv2plus/overrides/new_env/nexon_stage_new_env_test_config.toml new file mode 100644 index 0000000000..5dcd771e9d --- /dev/null +++ b/integration-tests/testconfig/vrfv2plus/overrides/new_env/nexon_stage_new_env_test_config.toml @@ -0,0 +1,8 @@ +[Network] +selected_networks = ["NEXON_STAGE"] + +[NEXON_STAGE.VRFv2Plus.General] +use_existing_env = false + +[Logging] +test_log_collect = true diff --git a/integration-tests/testconfig/vrfv2plus/overrides/new_env/nexon_test_new_env_test_config.toml b/integration-tests/testconfig/vrfv2plus/overrides/new_env/nexon_test_new_env_test_config.toml new file mode 100644 index 0000000000..d1263df34d --- /dev/null +++ b/integration-tests/testconfig/vrfv2plus/overrides/new_env/nexon_test_new_env_test_config.toml @@ -0,0 +1,8 @@ +[Network] +selected_networks = ["NEXON_TEST"] + +[NEXON_TEST.VRFv2Plus.General] +use_existing_env = false + +[Logging] +test_log_collect = true diff --git a/integration-tests/testconfig/vrfv2plus/overrides/new_env/optimism_sepolia_new_env_test_config.toml b/integration-tests/testconfig/vrfv2plus/overrides/new_env/optimism_sepolia_new_env_test_config.toml new file mode 100644 index 0000000000..8fd5b76379 --- /dev/null +++ b/integration-tests/testconfig/vrfv2plus/overrides/new_env/optimism_sepolia_new_env_test_config.toml @@ -0,0 +1,8 @@ +[Network] +selected_networks = ["OPTIMISM_SEPOLIA"] + +[OPTIMISM_SEPOLIA.VRFv2Plus.General] +use_existing_env = false + +[Logging] +test_log_collect = true diff --git a/integration-tests/testconfig/vrfv2plus/overrides/new_env/polygon_amoy_new_env_test_config.toml b/integration-tests/testconfig/vrfv2plus/overrides/new_env/polygon_amoy_new_env_test_config.toml new file mode 100644 index 0000000000..10abe34bb5 --- /dev/null +++ b/integration-tests/testconfig/vrfv2plus/overrides/new_env/polygon_amoy_new_env_test_config.toml @@ -0,0 +1,8 @@ +[Network] +selected_networks = ["POLYGON_AMOY"] + +[POLYGON_AMOY.VRFv2Plus.General] +use_existing_env = false + +[Logging] +test_log_collect = true diff --git a/integration-tests/testconfig/vrfv2plus/overrides/new_env/sepolia_new_env_test_config.toml b/integration-tests/testconfig/vrfv2plus/overrides/new_env/sepolia_new_env_test_config.toml new file mode 100644 index 0000000000..7df808c505 --- /dev/null +++ b/integration-tests/testconfig/vrfv2plus/overrides/new_env/sepolia_new_env_test_config.toml @@ -0,0 +1,8 @@ +[Network] +selected_networks = ["SEPOLIA"] + +[SEPOLIA.VRFv2Plus.General] +use_existing_env = false + +[Logging] +test_log_collect = true diff --git a/integration-tests/testconfig/vrfv2plus/overrides/new_env/soneium_sepolia_new_env_test_config.toml b/integration-tests/testconfig/vrfv2plus/overrides/new_env/soneium_sepolia_new_env_test_config.toml new file mode 100644 index 0000000000..beacf9c5f7 --- /dev/null +++ b/integration-tests/testconfig/vrfv2plus/overrides/new_env/soneium_sepolia_new_env_test_config.toml @@ -0,0 +1,8 @@ +[Network] +selected_networks = ["SONEIUM_SEPOLIA"] + +[SONEIUM_SEPOLIA.VRFv2Plus.General] +use_existing_env = false + +[Logging] +test_log_collect = true diff --git a/integration-tests/testconfig/vrfv2plus/overrides/staging/arbitrum_sepolia_staging_test_config.toml b/integration-tests/testconfig/vrfv2plus/overrides/staging/arbitrum_sepolia_staging_test_config.toml new file mode 100644 index 0000000000..66529bbed3 --- /dev/null +++ b/integration-tests/testconfig/vrfv2plus/overrides/staging/arbitrum_sepolia_staging_test_config.toml @@ -0,0 +1,36 @@ +[Network] +selected_networks = ["ARBITRUM_SEPOLIA"] + +[ARBITRUM_SEPOLIA.VRFv2Plus.General] +use_existing_env = true + +[ARBITRUM_SEPOLIA.VRFv2Plus.ExistingEnv] +coordinator_address = "0xF7ba1Cf141F9729abC43c146dc2bf86EbcfD8603" +consumer_address = "" +sub_id = "" +key_hash = "0xe13aa26fe94bfcd2ae055911f4d3bf1aed54ca6cf77af34e17f918802fd69ba1" +create_fund_subs_and_add_consumers = true +node_sending_key_funding_min = 20 +node_sending_keys = [ + "0xbE21ae371FcA1aC2d8A152e707D21e68d7d99252", + "0xb13e9BA0aE94FD3b89B13b092e6d41614c805134", + "0x27aa703e585Ee165B7c977EAA652eCFa13b08294", + "0x9324643ACD2ec5b0813488E5EdAb64C3758ae4Ee", + "0x7CBA8c8e86f23f23363051650Fe5AE4DE78c3652", + "0x9A0143a4BAB55A826331A8ef82462557633aA016", + "0xD4259633F8e87949F683433a17e1fFcCE27865AC", + "0x5e47B71d6F95f68cd5538907ec6A9635b1Fe30Fa", + "0xa850a1a257FDF439c8f854ce3b89dd5b6F411827", + "0x7c82D56087c10aF2c3970f9a9Be7786B2850ce91", + "0x9545CB59956347d3debf27f5029754bBE1d398FA", + "0xEb8C69ac19709f27A97FB4A561f51AD2F9b34c5B", + # BHS + "0xf0e8cF7Fbd28Fc4D412B76B744CDA269df671F8e", + "0x317A02A658d12E5Bb4A6270171E7385928dD2d53", + "0x480f1dbcEc118Bd91e4dbb7e45bFa4A73180d21f", + "0x500a2662FaF981EC4669f791349D37Cbf1bE7d85", + "0x2ad350374B904c10B47c64ECdBD9e70BB0833Be2", + "0x0b946F0bF4e63C12b5157137f1c130f0788bC1b1", + # BHF + "0x571BBF4a5b07fc3F47Bd3B65CE2FE73739f86623" +] diff --git a/integration-tests/testconfig/vrfv2plus/overrides/staging/avalanche_fuji_staging_test_config.toml b/integration-tests/testconfig/vrfv2plus/overrides/staging/avalanche_fuji_staging_test_config.toml new file mode 100644 index 0000000000..c8566e59a7 --- /dev/null +++ b/integration-tests/testconfig/vrfv2plus/overrides/staging/avalanche_fuji_staging_test_config.toml @@ -0,0 +1,18 @@ +[Network] +selected_networks = ["AVALANCHE_FUJI"] + +[AVALANCHE_FUJI.VRFv2Plus.General] +use_existing_env = true + +[AVALANCHE_FUJI.VRFv2Plus.ExistingEnv] +coordinator_address = "0xE122bf3Badd6545bDec5D4627a6DAd16352A1b36" +consumer_address = "" +sub_id = "" +key_hash = "0x5b03254a80ea3eb72139ff0423cb88be42612780c3dd25f1d95a5ba7708a4be1" +create_fund_subs_and_add_consumers = true +node_sending_key_funding_min = 30 +node_sending_keys = [ + "0x3D7Da5D6A23CA2240CE576C8638C1798a023920a", + # BHS + "0x72c8565279430F5179b0090d51ab8BB53Da323B5" +] diff --git a/integration-tests/testconfig/vrfv2plus/overrides/staging/base_sepolia_staging_test_config.toml b/integration-tests/testconfig/vrfv2plus/overrides/staging/base_sepolia_staging_test_config.toml new file mode 100644 index 0000000000..ad125ae46b --- /dev/null +++ b/integration-tests/testconfig/vrfv2plus/overrides/staging/base_sepolia_staging_test_config.toml @@ -0,0 +1,20 @@ +[Network] +selected_networks = ["BASE_SEPOLIA"] + +[BASE_SEPOLIA.VRFv2Plus.General] +use_existing_env = true + +[BASE_SEPOLIA.VRFv2Plus.ExistingEnv] +coordinator_address = "0x2Cf7Bb5923FA4dBdf92981fDBbEe27d13A896705" +consumer_address = "" +sub_id = "" +key_hash = "0x01d1eb450e0271ac86d3b78c7cc799f80e7f80863a4875f6fc7b66629419c951" +create_fund_subs_and_add_consumers = true +node_sending_key_funding_min = 30 +node_sending_keys = [ + "0x5d621FF993B1a990d189936E70021c585e3B0880", + "0x87F701AD21BfAF99eF64596c5CE8762a5Cb9ED13", + "0xe8B0865e9Aae9DE628BE14965Bbd1C0c9C80d245", + # BHS + "0x65C853683beB6363869DDda8534b2aD45786d380", +] diff --git a/integration-tests/testconfig/vrfv2plus/overrides/staging/bsc_testnet_staging_test_config.toml b/integration-tests/testconfig/vrfv2plus/overrides/staging/bsc_testnet_staging_test_config.toml new file mode 100644 index 0000000000..48060a88cf --- /dev/null +++ b/integration-tests/testconfig/vrfv2plus/overrides/staging/bsc_testnet_staging_test_config.toml @@ -0,0 +1,18 @@ +[Network] +selected_networks = ["BSC_TESTNET"] + +[BSC_TESTNET.VRFv2Plus.General] +use_existing_env = true + +[BSC_TESTNET.VRFv2Plus.ExistingEnv] +coordinator_address = "0x84A477F6ebF33501eE3ACA86fEcB980b1fC99AC2" +consumer_address = "" +sub_id = "" +key_hash = "0x4d43763d3eff849a89cf578a42787baa32132d7a80032125710e95b3972cd214" +create_fund_subs_and_add_consumers = true +node_sending_key_funding_min = 30 +node_sending_keys = [ + "0x4EE2Cc6D50E8acb6BaEf673B03559525a6c92fB8", + # BHS + "0xAFB44568f7DAc218EA6e1C71c366692ED4758A07" +] diff --git a/integration-tests/testconfig/vrfv2plus/overrides/staging/nexon_dev_staging_test_config.toml b/integration-tests/testconfig/vrfv2plus/overrides/staging/nexon_dev_staging_test_config.toml new file mode 100644 index 0000000000..0121c02552 --- /dev/null +++ b/integration-tests/testconfig/vrfv2plus/overrides/staging/nexon_dev_staging_test_config.toml @@ -0,0 +1,18 @@ +[Network] +selected_networks = ["NEXON_DEV"] + +[NEXON_DEV.VRFv2Plus.General] +use_existing_env = true + +[NEXON_DEV.VRFv2Plus.ExistingEnv] +coordinator_address = "0x6901d7236A823E7B7911d90FBe46E6FA770CC823" +consumer_address = "" +sub_id = "" +key_hash = "0xdc023892a41e5fe74ec7c4c2e8c0a808b01aea7acaf2b2ae30f4e08df877c48b" +create_fund_subs_and_add_consumers = true +node_sending_key_funding_min = 30 +node_sending_keys = [ + "0xF3d9879a75BBD85890056D7c6cB37C555F9b41A3", + # BHS + "0xb544f9D7c16a30af0EEd0afcC4132D1c63bAF8AC", +] diff --git a/integration-tests/testconfig/vrfv2plus/overrides/staging/nexon_qa_staging_test_config.toml b/integration-tests/testconfig/vrfv2plus/overrides/staging/nexon_qa_staging_test_config.toml new file mode 100644 index 0000000000..f45ecde0ed --- /dev/null +++ b/integration-tests/testconfig/vrfv2plus/overrides/staging/nexon_qa_staging_test_config.toml @@ -0,0 +1,20 @@ +[Network] +selected_networks = ["NEXON_QA"] + +[NEXON_QA.VRFv2Plus.General] +use_existing_env = true + +[NEXON_QA.VRFv2Plus.ExistingEnv] +coordinator_address = "0xF1F0beBcc284591FCD28d8f2BAc9f30efdA3E0ea" +consumer_address = "" +sub_id = "" +key_hash = "0x7d5692e71807c4c02f5a109627a9ad2b12a361a346790a306983af9a5e3a186f" +create_fund_subs_and_add_consumers = true +node_sending_key_funding_min = 30 +node_sending_keys = [ + "0xB97c0C52A2B957b45DA213e652c76090DDd0FEc6", + "0xe205F5d4a99ca0f474d0b4d12f60a0153c786B4E", + # BHS + "0xf85E291edF0352435f2fD5e817030f6542375a99", +] + diff --git a/integration-tests/testconfig/vrfv2plus/overrides/staging/nexon_stage_staging_test_config.toml b/integration-tests/testconfig/vrfv2plus/overrides/staging/nexon_stage_staging_test_config.toml new file mode 100644 index 0000000000..1a50876a73 --- /dev/null +++ b/integration-tests/testconfig/vrfv2plus/overrides/staging/nexon_stage_staging_test_config.toml @@ -0,0 +1,15 @@ +[Network] +selected_networks = ["NEXON_STAGE"] + +[NEXON_STAGE.VRFv2Plus.General] +use_existing_env = true + +[NEXON_STAGE.VRFv2Plus.ExistingEnv] +coordinator_address = "0xF705dD3e7E717F32de0Cc5F833f8009f16122AD1" +consumer_address = "" +sub_id = "" +key_hash = "0xbc9f525e3e1d9e2336f7c77d5f33f5b60aab3765944617fed7f66a6afecac616" +create_fund_subs_and_add_consumers = true +node_sending_key_funding_min = 30 +node_sending_keys = [ +] diff --git a/integration-tests/testconfig/vrfv2plus/overrides/staging/nexon_test_staging_test_config.toml b/integration-tests/testconfig/vrfv2plus/overrides/staging/nexon_test_staging_test_config.toml new file mode 100644 index 0000000000..4f897f4e1d --- /dev/null +++ b/integration-tests/testconfig/vrfv2plus/overrides/staging/nexon_test_staging_test_config.toml @@ -0,0 +1,19 @@ +[Network] +selected_networks = ["NEXON_TEST"] + +[NEXON_TEST.VRFv2Plus.General] +use_existing_env = true + +[NEXON_TEST.VRFv2Plus.ExistingEnv] +coordinator_address = "0xAa92Ba21168B48195cAdB87cfaB3eB70B2499F55" +consumer_address = "" +sub_id = "" +key_hash = "0x0cb2a18e8b762cb4c8f7b17a6cc02ac7b9d2a3346f048cfd2f5d37677f8747d8" +create_fund_subs_and_add_consumers = true +node_sending_key_funding_min = 30 +node_sending_keys = [ + "0xBFD780Af421e98C35918e10B9d6da7389C3e1D10", + "0xbf6c76024672F233aB8164EC00683e1AE774F6b0", + # BHS + "0x2a3900Ac77de110670E060DBFf4fCbe36c6f8170", +] diff --git a/integration-tests/testconfig/vrfv2plus/overrides/staging/optimism_sepolia_staging_test_config.toml b/integration-tests/testconfig/vrfv2plus/overrides/staging/optimism_sepolia_staging_test_config.toml new file mode 100644 index 0000000000..e44085067c --- /dev/null +++ b/integration-tests/testconfig/vrfv2plus/overrides/staging/optimism_sepolia_staging_test_config.toml @@ -0,0 +1,20 @@ +[Network] +selected_networks = ["OPTIMISM_SEPOLIA"] + +[OPTIMISM_SEPOLIA.VRFv2Plus.General] +use_existing_env = true + +[OPTIMISM_SEPOLIA.VRFv2Plus.ExistingEnv] +coordinator_address = "0xA4a64A217bE85680e0ebB454CD5BF4A1c274Fc7B" +consumer_address = "" +sub_id = "" +key_hash = "0x4b4838bbf22a7c5a871ada8ceab6ded3c2de6cadc037371146ee70f6435325c1" +create_fund_subs_and_add_consumers = true +node_sending_key_funding_min = 30 +node_sending_keys = [ + "0x17509D615052DE3A81a684755Ec118d62C4e1Cd1", + "0x5118ece61294f4dFf4a1fb63b3f036969516dF0a", + "0x89554391652616ea06a408263b9B2b9a70E87204", + # BHS + "0x8DE6446b5022C68F38CD32d04AA0E3b8F4C1aaB6", +] diff --git a/integration-tests/testconfig/vrfv2plus/overrides/staging/polygon_amoy_staging_test_config.toml b/integration-tests/testconfig/vrfv2plus/overrides/staging/polygon_amoy_staging_test_config.toml new file mode 100644 index 0000000000..d2f9727483 --- /dev/null +++ b/integration-tests/testconfig/vrfv2plus/overrides/staging/polygon_amoy_staging_test_config.toml @@ -0,0 +1,32 @@ +[Network] +selected_networks = ["POLYGON_AMOY"] + +[POLYGON_AMOY.VRFv2Plus.General] +use_existing_env = true + +[POLYGON_AMOY.VRFv2Plus.ExistingEnv] +coordinator_address = "0x7541EbaE23f32B4A1A2e7a8Cbf9da9582767A9B4" +consumer_address = "" +sub_id = "" +key_hash = "0xd360445bacd26df47086ccf255c4f932d297ed8d5c7334b51eed32f61c541601" +#key_hash = "0x2328cbee29e32d0b6662d6df82ff0fea7be300bd310561c92f515c9ee19464f1" +#key_hash = "0x25f4e2d0509f42ec77db5380f3433a89fe623fa75f65d5b398d5f498327be4dd" +create_fund_subs_and_add_consumers = true +node_sending_key_funding_min = 10 +node_sending_keys = [ + "0xD96013C241f1741C35a135321969f92Aae02A12F", + "0x0580E61a5523F5CAAC4968E4f8FE63b59596BdD7", + "0xd15FcEa6a6AA17085930Fbd5647A9F7fD2Ff58b8", + "0xB7277cBb6E7028AE65235b8ee9201AcBb14B11d4", + "0x6D36a1dC1eEd25C75961E989c4d01Cd4453bE465", + "0xd299Cd7C0073b71e620bf8A3bfD50F75c0b49af8", + "0x48BE7BAED0b65776D85DF971fA901c637cFC5e87", + # BHS + "0x638372de870eF0F8E675A3f67F18D5bd4A2fd804", + "0xF9eF03816411D037202d5ed4457dC1613e3bd729", + "0xCD66973f8fbaE787211EC20228c6bd90D83562A8", + "0x242ea1F4Bb72EF643B2D8EF22e18a89f00742F40", + "0xaA09B4F9B5710b239fdbf1D0f535dd7f86F91219", + "0xe6b72B647B8B45C5562F7a5259E187889C747d3b", + "0x2c1185C4d3B0B4a577d4079Ee193A4e293164D9d" +] diff --git a/integration-tests/testconfig/vrfv2plus/overrides/staging/sepolia_staging_test_config.toml b/integration-tests/testconfig/vrfv2plus/overrides/staging/sepolia_staging_test_config.toml new file mode 100644 index 0000000000..fd2e6f0bc1 --- /dev/null +++ b/integration-tests/testconfig/vrfv2plus/overrides/staging/sepolia_staging_test_config.toml @@ -0,0 +1,18 @@ +[Network] +selected_networks = ["SEPOLIA"] + +[SEPOLIA.VRFv2Plus.General] +use_existing_env = true + +[SEPOLIA.VRFv2Plus.ExistingEnv] +coordinator_address = "0x2F3b892710523Ee9A85c3155a42089fFe99Ca31e" +consumer_address = "" +sub_id = "" +key_hash = "0xf5b4a359df0598eef89872ea2170f2afa844dbf74b417e6d44d4bda9420aceb2" +create_fund_subs_and_add_consumers = true +node_sending_key_funding_min = 30 +node_sending_keys = [ + "0x0c0DC7f33A1256f0247c5ea75861d385fa5FED31", + # BHS + "0xEd8A4b792d16484f6c9B4df1e721e8280925Db80", +] diff --git a/integration-tests/testconfig/vrfv2plus/overrides/staging/soneium_sepolia_staging_test_config.toml b/integration-tests/testconfig/vrfv2plus/overrides/staging/soneium_sepolia_staging_test_config.toml new file mode 100644 index 0000000000..df973d8702 --- /dev/null +++ b/integration-tests/testconfig/vrfv2plus/overrides/staging/soneium_sepolia_staging_test_config.toml @@ -0,0 +1,20 @@ +[Network] +selected_networks = ["SONEIUM_SEPOLIA"] + +[SONEIUM_SEPOLIA.VRFv2Plus.General] +use_existing_env = true + +[SONEIUM_SEPOLIA.VRFv2Plus.ExistingEnv] +coordinator_address = "0x81e211D679231615C2601E82B658Bde449AF240f" +consumer_address = "" +use_existing_wrapper = true +wrapper_address = "0xD06CfcDAa6f32BB131e693F99f502ac31588CBC8" +sub_id = "" +key_hash = "0x9552c9542c079db4f8c6867e27f6c4780e3e24d895cb0890ca9984764dbe7200" +create_fund_subs_and_add_consumers = true +node_sending_key_funding_min = 1 +node_sending_keys = [ + "0x22643FcF2018ac636477CFE19Ed17071FF7A5cA0", + # BHS + "0xD012B272E8ec6eA7A3373E340Ead52FA2C7352ea", +] diff --git a/integration-tests/testconfig/vrfv2plus/vrfv2plus.toml b/integration-tests/testconfig/vrfv2plus/vrfv2plus.toml index cd089013db..c9f5ac12a8 100644 --- a/integration-tests/testconfig/vrfv2plus/vrfv2plus.toml +++ b/integration-tests/testconfig/vrfv2plus/vrfv2plus.toml @@ -1,4 +1,5 @@ # default config + [NodeConfig] BaseConfigTOML = """ [Feature] @@ -30,7 +31,78 @@ Unauthenticated = 100 HTTPSPort = 0 """ +# Node TOML config depending on the chain [NodeConfig.ChainConfigTOMLByChainID] +# ETHEREUM SEPOLIA +11155111 = """ +BlockBackfillDepth = 500 +MinIncomingConfirmations = 3 + +[GasEstimator] +LimitDefault = 3500000 +""" + +# BNB TESTNET +97 = """ +BlockBackfillDepth = 500 +RPCDefaultBatchSize = 25 +LogBackfillBatchSize = 1000 + +[GasEstimator] +LimitDefault = 3500000 + +[GasEstimator.BlockHistory] +BatchSize = 100 +""" + +# Polygon Amoy +80002 = """ +BlockBackfillDepth = 500 +RPCDefaultBatchSize = 25 +LogBackfillBatchSize = 1000 + +[Transactions] +MaxInFlight = 128 +MaxQueued = 0 + +[GasEstimator] +LimitDefault = 3500000 + +[GasEstimator.BlockHistory] +BatchSize = 100 +""" + +# Avalanche Fuji +43113 = """ +BlockBackfillDepth = 500 +RPCDefaultBatchSize = 25 +LogBackfillBatchSize = 1000 + +[GasEstimator] +LimitDefault = 3500000 + +[GasEstimator.BlockHistory] +BatchSize = 100 +""" + +# Arbitrum Sepolia +# NOTE: PROD env has `LimitDefault = 100_000_000`, but it is decreased in order not to over spend testnet tokens +421614 = """ +BlockBackfillDepth = 15000 +LogBackfillBatchSize = 1000 +RPCDefaultBatchSize = 25 + +[Transactions] +MaxInFlight = 128 +MaxQueued = 0 + +[GasEstimator] +LimitDefault = 3_500_000 + +[GasEstimator.BlockHistory] +BatchSize = 100 +""" + # OPTIMISM SEPOLIA 11155420 = """ BlockBackfillDepth = 500 @@ -53,15 +125,73 @@ RPCDefaultBatchSize = 25 [GasEstimator] LimitDefault = 3500000 +[GasEstimator.BlockHistory] +BatchSize = 100 +""" +# Nexon Staging +847799 = """ +BlockBackfillDepth = 500 +RPCDefaultBatchSize = 25 +LogBackfillBatchSize = 1000 +NoNewHeadsThreshold = '0s' + +[GasEstimator] +LimitDefault = 3500000 + +[GasEstimator.BlockHistory] +BatchSize = 100 +""" + +# Nexon QA +807424 = """ +BlockBackfillDepth = 500 +RPCDefaultBatchSize = 25 +LogBackfillBatchSize = 1000 +NoNewHeadsThreshold = '0s' + +[GasEstimator] +LimitDefault = 3500000 + +[GasEstimator.BlockHistory] +BatchSize = 100 +""" + +# Nexon TEST +595581 = """ +BlockBackfillDepth = 500 +RPCDefaultBatchSize = 25 +LogBackfillBatchSize = 1000 +NoNewHeadsThreshold = '0s' + +[GasEstimator] +LimitDefault = 3500000 + +[GasEstimator.BlockHistory] +BatchSize = 100 +""" + +# Nexon DEV +5668 = """ +BlockBackfillDepth = 500 +RPCDefaultBatchSize = 25 +LogBackfillBatchSize = 1000 +NoNewHeadsThreshold = '0s' + +[GasEstimator] +LimitDefault = 3500000 + [GasEstimator.BlockHistory] BatchSize = 100 """ + [Common] -chainlink_node_funding = 0.5 +chainlink_node_funding = 0.7 [VRFv2Plus] [VRFv2Plus.General] +sub_billing_tolerance_wei = 1e15 +use_test_coordinator = false cancel_subs_after_test_run = true use_existing_env = false #todo - need to have separate minimum_confirmations config for Coordinator, CL Node and Consumer request @@ -139,11 +269,17 @@ bhf_job_run_timeout = "1h" [VRFv2Plus.ExistingEnv] coordinator_address = "" -consumer_address = "" -sub_id = "" key_hash = "" + +use_existing_wrapper = false +wrapper_address = "" create_fund_subs_and_add_consumers = true -link_address = "" +sub_id = "" +consumer_address = "" + +create_fund_add_wrapper_consumers = true +wrapper_consumer_address = "" + node_sending_key_funding_min = 1 node_sending_keys = [] @@ -156,19 +292,17 @@ bhs_test_rate_limit_unit_duration = "3s" bhs_test_rps = 1 #SOAK TEST CONFIG -[Soak.Common] -chainlink_node_funding = 0.1 [Soak.VRFv2Plus.General] randomness_request_count_per_request = 4 # amount of randomness requests to make per one TX request randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting number_of_sub_to_create = 1 number_of_sending_keys_to_create = 0 -subscription_funding_amount_link = 50 -subscription_funding_amount_native=10 +subscription_funding_amount_link = 500 +subscription_funding_amount_native = 100 [Soak.VRFv2Plus.Performance] -test_duration = "2m" +test_duration = "2h" rate_limit_unit_duration = "10s" rps = 1 bhs_test_duration = "1m" @@ -200,24 +334,26 @@ bhs_test_rps = 1 chainlink_node_funding = 0.1 [Stress.VRFv2Plus.General] -randomness_request_count_per_request = 3 # amount of randomness requests to make per one TX request -randomness_request_count_per_request_deviation = 2 #NOTE - deviation should be less than randomness_request_count_per_request setting +randomness_request_count_per_request = 30 # amount of randomness requests to make per one TX request +randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting number_of_sub_to_create = 1 number_of_sending_keys_to_create = 0 -subscription_funding_amount_link = 5.0 -subscription_funding_amount_native=1 +subscription_funding_amount_link = 500 +subscription_funding_amount_native=100 [Stress.VRFv2Plus.Performance] -test_duration = "2m" -rate_limit_unit_duration = "3s" +test_duration = "10m" +rate_limit_unit_duration = "10s" rps = 1 bhs_test_duration = "1m" bhs_test_rate_limit_unit_duration = "3s" bhs_test_rps = 1 ### POLYGON AMOY Config - +[POLYGON_AMOY.Common] +chainlink_node_funding = 5 [POLYGON_AMOY.VRFv2Plus.General] +use_test_coordinator = true #todo - need to have separate minimum_confirmations config for Coordinator, CL Node and Consumer request minimum_confirmations = 3 @@ -227,7 +363,7 @@ callback_gas_limit = 1000000 # NEW ENV CONFIG # CL Node config -cl_node_max_gas_price_gwei = 200 +cl_node_max_gas_price_gwei = 500 number_of_sending_keys_to_create = 0 # Coordinator config @@ -265,109 +401,64 @@ bhs_job_poll_period = "2s" bhs_job_run_timeout = "30s" # NEW ENV CONFIG END -[POLYGON_AMOY.VRFv2Plus.ExistingEnv] -coordinator_address = "0x7541EbaE23f32B4A1A2e7a8Cbf9da9582767A9B4" -consumer_address = "" -sub_id = "" -key_hash = "0xd360445bacd26df47086ccf255c4f932d297ed8d5c7334b51eed32f61c541601" -#key_hash = "0x2328cbee29e32d0b6662d6df82ff0fea7be300bd310561c92f515c9ee19464f1" -#key_hash = "0x25f4e2d0509f42ec77db5380f3433a89fe623fa75f65d5b398d5f498327be4dd" -create_fund_subs_and_add_consumers = true -link_address = "0x0Fd9e8d3aF1aaee056EB9e802c3A762a667b1904" -node_sending_key_funding_min = 10 -node_sending_keys = [ - "0xD96013C241f1741C35a135321969f92Aae02A12F", - "0x0580E61a5523F5CAAC4968E4f8FE63b59596BdD7", - "0xd15FcEa6a6AA17085930Fbd5647A9F7fD2Ff58b8", - "0xB7277cBb6E7028AE65235b8ee9201AcBb14B11d4", - "0x6D36a1dC1eEd25C75961E989c4d01Cd4453bE465", - "0xd299Cd7C0073b71e620bf8A3bfD50F75c0b49af8", - "0x48BE7BAED0b65776D85DF971fA901c637cFC5e87", - # BHS - "0x638372de870eF0F8E675A3f67F18D5bd4A2fd804", - "0xF9eF03816411D037202d5ed4457dC1613e3bd729", - "0xCD66973f8fbaE787211EC20228c6bd90D83562A8", - "0x242ea1F4Bb72EF643B2D8EF22e18a89f00742F40", - "0xaA09B4F9B5710b239fdbf1D0f535dd7f86F91219", - "0xe6b72B647B8B45C5562F7a5259E187889C747d3b", - "0x2c1185C4d3B0B4a577d4079Ee193A4e293164D9d" -] - #SMOKE TEST CONFIG [POLYGON_AMOY-Smoke.VRFv2Plus.General] randomness_request_count_per_request = 1 # amount of randomness requests to make per one TX request randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting number_of_sub_to_create = 1 -subscription_funding_amount_link = 5 +subscription_funding_amount_link = 3 subscription_funding_amount_native = 1 -subscription_refunding_amount_link = 5 +subscription_refunding_amount_link = 3 subscription_refunding_amount_native = 1 number_of_words = 1 random_words_fulfilled_event_timeout = "1m30s" wait_for_256_blocks_timeout = "15m" wrapper_consumer_funding_amount_native_token = 1.0 -wrapper_consumer_funding_amount_link = 5 - - -[POLYGON_AMOY-Smoke.VRFv2Plus.Performance] -test_duration = "2s" -rate_limit_unit_duration = "15s" -rps = 1 - +wrapper_consumer_funding_amount_link = 3 -#SOAK TEST CONFIG [POLYGON_AMOY-Soak.VRFv2Plus.General] -randomness_request_count_per_request = 1 # amount of randomness requests to make per one TX request +randomness_request_count_per_request = 4 # amount of randomness requests to make per one TX request randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting number_of_sub_to_create = 1 +number_of_sending_keys_to_create = 0 subscription_funding_amount_link = 500 -subscription_funding_amount_native = 200 +subscription_funding_amount_native = 100 [POLYGON_AMOY-Soak.VRFv2Plus.Performance] -test_duration = "5h" -rate_limit_unit_duration = "3s" -rps = 1 - -# LOAD TEST CONFIG -[POLYGON_AMOY-Load.VRFv2Plus.General] -randomness_request_count_per_request = 3 # amount of randomness requests to make per one TX request -randomness_request_count_per_request_deviation = 2 #NOTE - deviation should be less than randomness_request_count_per_request setting -number_of_sub_to_create = 1 -subscription_funding_amount_link = 300 -subscription_funding_amount_native = 300 - -[POLYGON_AMOY-Load.VRFv2Plus.Performance] -test_duration = "2m" -rate_limit_unit_duration = "3s" +test_duration = "2h" +rate_limit_unit_duration = "10s" rps = 1 - -# STRESS TEST CONFIG [POLYGON_AMOY-Stress.VRFv2Plus.General] -randomness_request_count_per_request = 3 # amount of randomness requests to make per one TX request -randomness_request_count_per_request_deviation = 2 #NOTE - deviation should be less than randomness_request_count_per_request setting +randomness_request_count_per_request = 150 # amount of randomness requests to make per one TX request +randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting number_of_sub_to_create = 1 -subscription_funding_amount_link = 5.0 -subscription_funding_amount_native = 0.1 +number_of_sending_keys_to_create = 0 +subscription_funding_amount_link = 500 +subscription_funding_amount_native=100 [POLYGON_AMOY-Stress.VRFv2Plus.Performance] -test_duration = "2m" -rate_limit_unit_duration = "3s" +test_duration = "10m" +rate_limit_unit_duration = "1m" rps = 1 ### ARBITRUM SEPOLIA Config - [ARBITRUM_SEPOLIA.VRFv2Plus.General] +use_test_coordinator = true #todo - need to have separate minimum_confirmations config for Coordinator, CL Node and Consumer request minimum_confirmations = 0 -subscription_billing_type = "LINK_AND_NATIVE" +## NEW ENV CONFIG +## CL Node config cl_node_max_gas_price_gwei = 50 number_of_sending_keys_to_create = 0 -# Coordinator config +# Consumer Request config +subscription_billing_type = "LINK_AND_NATIVE" callback_gas_limit = 1000000 + +# Coordinator config max_gas_limit_coordinator_config = 2500000 fallback_wei_per_unit_link = "5352799651145251" staleness_seconds = 172_800 @@ -402,37 +493,7 @@ bhs_job_poll_period = "300ms" bhs_job_run_timeout = "30s" # NEW ENV CONFIG END -[ARBITRUM_SEPOLIA.VRFv2Plus.ExistingEnv] -coordinator_address = "0xF7ba1Cf141F9729abC43c146dc2bf86EbcfD8603" -consumer_address = "" -sub_id = "" -key_hash = "0xe13aa26fe94bfcd2ae055911f4d3bf1aed54ca6cf77af34e17f918802fd69ba1" -create_fund_subs_and_add_consumers = true -link_address = "0xb1D4538B4571d411F07960EF2838Ce337FE1E80E" -node_sending_key_funding_min = 20 -node_sending_keys = [ - "0xbE21ae371FcA1aC2d8A152e707D21e68d7d99252", - "0xb13e9BA0aE94FD3b89B13b092e6d41614c805134", - "0x27aa703e585Ee165B7c977EAA652eCFa13b08294", - "0x9324643ACD2ec5b0813488E5EdAb64C3758ae4Ee", - "0x7CBA8c8e86f23f23363051650Fe5AE4DE78c3652", - "0x9A0143a4BAB55A826331A8ef82462557633aA016", - "0xD4259633F8e87949F683433a17e1fFcCE27865AC", - "0x5e47B71d6F95f68cd5538907ec6A9635b1Fe30Fa", - "0xa850a1a257FDF439c8f854ce3b89dd5b6F411827", - "0x7c82D56087c10aF2c3970f9a9Be7786B2850ce91", - "0x9545CB59956347d3debf27f5029754bBE1d398FA", - "0xEb8C69ac19709f27A97FB4A561f51AD2F9b34c5B", - # BHS - "0xf0e8cF7Fbd28Fc4D412B76B744CDA269df671F8e", - "0x317A02A658d12E5Bb4A6270171E7385928dD2d53", - "0x480f1dbcEc118Bd91e4dbb7e45bFa4A73180d21f", - "0x500a2662FaF981EC4669f791349D37Cbf1bE7d85", - "0x2ad350374B904c10B47c64ECdBD9e70BB0833Be2", - "0x0b946F0bF4e63C12b5157137f1c130f0788bC1b1", - # BHF - "0x571BBF4a5b07fc3F47Bd3B65CE2FE73739f86623" -] + #SMOKE TEST CONFIG [ARBITRUM_SEPOLIA-Smoke.VRFv2Plus.General] @@ -441,8 +502,8 @@ randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be l number_of_sub_to_create = 1 subscription_funding_amount_link = 5 subscription_funding_amount_native = 1 -subscription_refunding_amount_link = 20 -subscription_refunding_amount_native = 10 +subscription_refunding_amount_link = 5 +subscription_refunding_amount_native = 1 number_of_words = 1 random_words_fulfilled_event_timeout = "1m30s" wait_for_256_blocks_timeout = "100s" @@ -450,55 +511,37 @@ wait_for_256_blocks_timeout = "100s" wrapper_consumer_funding_amount_native_token = 1.0 wrapper_consumer_funding_amount_link = 5 -[ARBITRUM_SEPOLIA-Smoke.VRFv2Plus.Performance] -test_duration = "1s" -rate_limit_unit_duration = "10s" -rps = 1 - - -#SOAK TEST CONFIG [ARBITRUM_SEPOLIA-Soak.VRFv2Plus.General] randomness_request_count_per_request = 4 # amount of randomness requests to make per one TX request randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting number_of_sub_to_create = 1 -subscription_funding_amount_link = 300 +number_of_sending_keys_to_create = 0 +subscription_funding_amount_link = 500 subscription_funding_amount_native = 100 [ARBITRUM_SEPOLIA-Soak.VRFv2Plus.Performance] -test_duration = "1h" +test_duration = "2h" rate_limit_unit_duration = "10s" rps = 1 -# LOAD TEST CONFIG -[ARBITRUM_SEPOLIA-Load.VRFv2Plus.General] -randomness_request_count_per_request = 1 # amount of randomness requests to make per one TX request -randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting -number_of_sub_to_create = 1 -subscription_funding_amount_link = 100 -subscription_funding_amount_native = 60 - -[ARBITRUM_SEPOLIA-Load.VRFv2Plus.Performance] -test_duration = "30m" -rate_limit_unit_duration = "3s" -rps = 1 - - -# STRESS TEST CONFIG [ARBITRUM_SEPOLIA-Stress.VRFv2Plus.General] -randomness_request_count_per_request = 3 # amount of randomness requests to make per one TX request -randomness_request_count_per_request_deviation = 2 #NOTE - deviation should be less than randomness_request_count_per_request setting +randomness_request_count_per_request = 150 # amount of randomness requests to make per one TX request +randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting number_of_sub_to_create = 1 -subscription_funding_amount_link = 5.0 -subscription_funding_amount_native = 0.1 +number_of_sending_keys_to_create = 0 +subscription_funding_amount_link = 500 +subscription_funding_amount_native=100 [ARBITRUM_SEPOLIA-Stress.VRFv2Plus.Performance] -test_duration = "2m" -rate_limit_unit_duration = "3s" +test_duration = "10m" +rate_limit_unit_duration = "1m" rps = 1 - ### AVALANCHE FUJI Config +[AVALANCHE_FUJI.Common] +chainlink_node_funding = 3 [AVALANCHE_FUJI.VRFv2Plus.General] +use_test_coordinator = true #todo - need to have separate minimum_confirmations config for Coordinator, CL Node and Consumer request minimum_confirmations = 0 @@ -546,29 +589,15 @@ bhs_job_poll_period = "2s" bhs_job_run_timeout = "30s" # NEW ENV CONFIG END -[AVALANCHE_FUJI.VRFv2Plus.ExistingEnv] -coordinator_address = "0xE122bf3Badd6545bDec5D4627a6DAd16352A1b36" -consumer_address = "" -sub_id = "" -key_hash = "0x5b03254a80ea3eb72139ff0423cb88be42612780c3dd25f1d95a5ba7708a4be1" -create_fund_subs_and_add_consumers = true -link_address = "0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846" -node_sending_key_funding_min = 50 -node_sending_keys = [ - "0x3D7Da5D6A23CA2240CE576C8638C1798a023920a", - # BHS - "0x72c8565279430F5179b0090d51ab8BB53Da323B5" -] - #SMOKE TEST CONFIG [AVALANCHE_FUJI-Smoke.VRFv2Plus.General] randomness_request_count_per_request = 1 # amount of randomness requests to make per one TX request randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting number_of_sub_to_create = 1 -subscription_funding_amount_link = 20 -subscription_funding_amount_native = 20 -subscription_refunding_amount_link = 20 -subscription_refunding_amount_native = 20 +subscription_funding_amount_link = 3 +subscription_funding_amount_native = 1 +subscription_refunding_amount_link = 3 +subscription_refunding_amount_native = 1 number_of_words = 1 random_words_fulfilled_event_timeout = "1m30s" wait_for_256_blocks_timeout = "10m" @@ -576,56 +605,39 @@ wait_for_256_blocks_timeout = "10m" wrapper_consumer_funding_amount_native_token = 1.0 wrapper_consumer_funding_amount_link = 5 -[AVALANCHE_FUJI-Smoke.VRFv2Plus.Performance] -test_duration = "2s" -rate_limit_unit_duration = "10s" -rps = 1 - - -#SOAK TEST CONFIG [AVALANCHE_FUJI-Soak.VRFv2Plus.General] -randomness_request_count_per_request = 1 # amount of randomness requests to make per one TX request +randomness_request_count_per_request = 4 # amount of randomness requests to make per one TX request randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting number_of_sub_to_create = 1 -subscription_funding_amount_link = 400 -subscription_funding_amount_native = 200 +number_of_sending_keys_to_create = 0 +subscription_funding_amount_link = 500 +subscription_funding_amount_native = 100 [AVALANCHE_FUJI-Soak.VRFv2Plus.Performance] -test_duration = "5h" -rate_limit_unit_duration = "3s" -rps = 1 - -# LOAD TEST CONFIG -[AVALANCHE_FUJI-Load.VRFv2Plus.General] -randomness_request_count_per_request = 3 # amount of randomness requests to make per one TX request -randomness_request_count_per_request_deviation = 2 #NOTE - deviation should be less than randomness_request_count_per_request setting -number_of_sub_to_create = 1 -subscription_funding_amount_link = 300 -subscription_funding_amount_native = 300 - -[AVALANCHE_FUJI-Load.VRFv2Plus.Performance] -test_duration = "2m" -rate_limit_unit_duration = "3s" +test_duration = "2h" +rate_limit_unit_duration = "10s" rps = 1 - -# STRESS TEST CONFIG [AVALANCHE_FUJI-Stress.VRFv2Plus.General] -randomness_request_count_per_request = 3 # amount of randomness requests to make per one TX request -randomness_request_count_per_request_deviation = 2 #NOTE - deviation should be less than randomness_request_count_per_request setting +randomness_request_count_per_request = 60 # amount of randomness requests to make per one TX request +randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting number_of_sub_to_create = 1 -subscription_funding_amount_link = 5.0 -subscription_funding_amount_native = 0.1 +number_of_sending_keys_to_create = 0 +subscription_funding_amount_link = 500 +subscription_funding_amount_native=100 [AVALANCHE_FUJI-Stress.VRFv2Plus.Performance] -test_duration = "2m" -rate_limit_unit_duration = "3s" +test_duration = "10m" +rate_limit_unit_duration = "1m" rps = 1 -### ETH SEPOLIA Config -[SEPOLIA.VRFv2Plus.General] +### BASE SEPOLIA Config +[BASE_SEPOLIA.Common] +chainlink_node_funding = 5 +[BASE_SEPOLIA.VRFv2Plus.General] +use_test_coordinator = false #todo - need to have separate minimum_confirmations config for Coordinator, CL Node and Consumer request -minimum_confirmations = 3 +minimum_confirmations = 0 # Consumer Request config subscription_billing_type = "LINK_AND_NATIVE" @@ -633,120 +645,373 @@ callback_gas_limit = 1000000 # NEW ENV CONFIG # CL Node config -cl_node_max_gas_price_gwei = 100 +cl_node_max_gas_price_gwei = 30 number_of_sending_keys_to_create = 0 # Coordinator config max_gas_limit_coordinator_config = 2500000 -fallback_wei_per_unit_link = "5354747932930759" +fallback_wei_per_unit_link = "3962147213857640" staleness_seconds = 172_800 -gas_after_payment_calculation = 38_900 +gas_after_payment_calculation = 42_500 fulfillment_flat_fee_native_ppm = 0 fulfillment_flat_fee_link_discount_ppm = 0 -native_premium_percentage = 24 -link_premium_percentage = 20 +native_premium_percentage = 60 +link_premium_percentage = 50 # Wrapper config wrapped_gas_overhead = 13_400 -coordinator_gas_overhead_native = 90_000 -coordinator_gas_overhead_link = 112_000 +coordinator_gas_overhead_native = 128_500 +coordinator_gas_overhead_link = 150_400 coordinator_gas_overhead_per_word = 435 -coordinator_native_premium_percentage = 24 -coordinator_link_premium_percentage = 20 +coordinator_native_premium_percentage = 60 +coordinator_link_premium_percentage = 50 wrapper_max_number_of_words = 10 # VRF Job config vrf_job_forwarding_allowed = false vrf_job_estimate_gas_multiplier = 1.15 -vrf_job_batch_fulfillment_enabled = false +vrf_job_batch_fulfillment_enabled = true vrf_job_batch_fulfillment_gas_multiplier = 1.1 -vrf_job_poll_period = "5s" +vrf_job_poll_period = "2s" vrf_job_request_timeout = "2h0m0s" -vrf_job_simulation_block = "latest" +vrf_job_simulation_block = "pending" # BHS Job config bhs_job_wait_blocks = 30 bhs_job_lookback_blocks = 200 -bhs_job_poll_period = "30s" -bhs_job_run_timeout = "1m0s" +bhs_job_poll_period = "2s" +bhs_job_run_timeout = "30s" # NEW ENV CONFIG END -[SEPOLIA.VRFv2Plus.ExistingEnv] -coordinator_address = "0x2F3b892710523Ee9A85c3155a42089fFe99Ca31e" -consumer_address = "" -sub_id = "" -key_hash = "0xf5b4a359df0598eef89872ea2170f2afa844dbf74b417e6d44d4bda9420aceb2" -create_fund_subs_and_add_consumers = true -link_address = "0x779877A7B0D9E8603169DdbD7836e478b4624789" -node_sending_key_funding_min = 50 -node_sending_keys = [ - "0x0c0DC7f33A1256f0247c5ea75861d385fa5FED31", - # BHS - "0xEd8A4b792d16484f6c9B4df1e721e8280925Db80", -] +#SMOKE TEST CONFIG +[BASE_SEPOLIA-Smoke.VRFv2Plus.General] +randomness_request_count_per_request = 1 # amount of randomness requests to make per one TX request +randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting +number_of_sub_to_create = 1 +subscription_funding_amount_link = 3 +subscription_funding_amount_native = 1 +subscription_refunding_amount_link = 3 +subscription_refunding_amount_native = 1 +number_of_words = 1 +random_words_fulfilled_event_timeout = "1m30s" +wait_for_256_blocks_timeout = "10m" + +wrapper_consumer_funding_amount_native_token = 1.0 +wrapper_consumer_funding_amount_link = 5 + +[BASE_SEPOLIA-Soak.VRFv2Plus.General] +randomness_request_count_per_request = 4 # amount of randomness requests to make per one TX request +randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting +number_of_sub_to_create = 1 +number_of_sending_keys_to_create = 0 +subscription_funding_amount_link = 500 +subscription_funding_amount_native = 100 + +[BASE_SEPOLIA-Soak.VRFv2Plus.Performance] +test_duration = "2h" +rate_limit_unit_duration = "10s" +rps = 1 + +[BASE_SEPOLIA-Stress.VRFv2Plus.General] +randomness_request_count_per_request = 60 # amount of randomness requests to make per one TX request +randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting +number_of_sub_to_create = 1 +number_of_sending_keys_to_create = 0 +subscription_funding_amount_link = 500 +subscription_funding_amount_native=100 + +[BASE_SEPOLIA-Stress.VRFv2Plus.Performance] +test_duration = "10m" +rate_limit_unit_duration = "1m" +rps = 1 + + +### OPTIMISM SEPOLIA Config +[OPTIMISM_SEPOLIA.Common] +chainlink_node_funding = 5 +[OPTIMISM_SEPOLIA.VRFv2Plus.General] +sub_billing_tolerance_wei = 1e16 +use_test_coordinator = false +#todo - need to have separate minimum_confirmations config for Coordinator, CL Node and Consumer request +minimum_confirmations = 0 + +# Consumer Request config +subscription_billing_type = "LINK_AND_NATIVE" +callback_gas_limit = 1000000 + +# NEW ENV CONFIG +# CL Node config +cl_node_max_gas_price_gwei = 30 +number_of_sending_keys_to_create = 0 + +# Coordinator config +max_gas_limit_coordinator_config = 2500000 +fallback_wei_per_unit_link = "3896881047706782" +staleness_seconds = 172_800 +gas_after_payment_calculation = 42_500 +fulfillment_flat_fee_native_ppm = 0 +fulfillment_flat_fee_link_discount_ppm = 0 +native_premium_percentage = 60 +link_premium_percentage = 50 + +# Wrapper config +wrapped_gas_overhead = 13_400 +coordinator_gas_overhead_native = 128_500 +coordinator_gas_overhead_link = 150_400 +coordinator_gas_overhead_per_word = 435 +coordinator_native_premium_percentage = 60 +coordinator_link_premium_percentage = 50 +wrapper_max_number_of_words = 10 + +# VRF Job config +vrf_job_forwarding_allowed = false +vrf_job_estimate_gas_multiplier = 1.15 +vrf_job_batch_fulfillment_enabled = true +vrf_job_batch_fulfillment_gas_multiplier = 1.1 +vrf_job_poll_period = "2s" +vrf_job_request_timeout = "2h0m0s" +vrf_job_simulation_block = "pending" + +# BHS Job config +bhs_job_wait_blocks = 30 +bhs_job_lookback_blocks = 200 +bhs_job_poll_period = "2s" +bhs_job_run_timeout = "30s" +# NEW ENV CONFIG END #SMOKE TEST CONFIG -[SEPOLIA-Smoke.VRFv2Plus.General] +[OPTIMISM_SEPOLIA-Smoke.VRFv2Plus.General] randomness_request_count_per_request = 1 # amount of randomness requests to make per one TX request randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting number_of_sub_to_create = 1 -subscription_funding_amount_link = 5 +subscription_funding_amount_link = 10 subscription_funding_amount_native = 1 -subscription_refunding_amount_link = 5 +subscription_refunding_amount_link = 10 subscription_refunding_amount_native = 1 number_of_words = 1 random_words_fulfilled_event_timeout = "1m30s" -wait_for_256_blocks_timeout = "70m" +wait_for_256_blocks_timeout = "10m" wrapper_consumer_funding_amount_native_token = 1.0 wrapper_consumer_funding_amount_link = 5 -[SEPOLIA-Smoke.VRFv2Plus.Performance] -test_duration = "2s" -rate_limit_unit_duration = "3s" +[OPTIMISM_SEPOLIA-Soak.VRFv2Plus.General] +randomness_request_count_per_request = 4 # amount of randomness requests to make per one TX request +randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting +number_of_sub_to_create = 1 +number_of_sending_keys_to_create = 0 +subscription_funding_amount_link = 500 +subscription_funding_amount_native = 100 + +[OPTIMISM_SEPOLIA-Soak.VRFv2Plus.Performance] +test_duration = "2h" +rate_limit_unit_duration = "10s" rps = 1 -#SOAK TEST CONFIG -[SEPOLIA-Soak.VRFv2Plus.General] +[OPTIMISM_SEPOLIA-Stress.VRFv2Plus.General] +randomness_request_count_per_request = 60 # amount of randomness requests to make per one TX request +randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting +number_of_sub_to_create = 1 +number_of_sending_keys_to_create = 0 +subscription_funding_amount_link = 500 +subscription_funding_amount_native=100 + +[OPTIMISM_SEPOLIA-Stress.VRFv2Plus.Performance] +test_duration = "10m" +rate_limit_unit_duration = "1m" +rps = 1 + + +### SONEIUM SEPOLIA Config +[SONEIUM_SEPOLIA.Common] +chainlink_node_funding = 5 +[SONEIUM_SEPOLIA.VRFv2Plus.General] +use_test_coordinator = false +#todo - need to have separate minimum_confirmations config for Coordinator, CL Node and Consumer request +minimum_confirmations = 0 + +# Consumer Request config +subscription_billing_type = "LINK_AND_NATIVE" +callback_gas_limit = 1000000 + +# NEW ENV CONFIG +# CL Node config +cl_node_max_gas_price_gwei = 30 +number_of_sending_keys_to_create = 0 + +# Coordinator config +max_gas_limit_coordinator_config = 2500000 +fallback_wei_per_unit_link = "4619667900000000" +staleness_seconds = 172_800 +gas_after_payment_calculation = 42_500 +fulfillment_flat_fee_native_ppm = 0 +fulfillment_flat_fee_link_discount_ppm = 0 +native_premium_percentage = 60 +link_premium_percentage = 50 + +# Wrapper config +wrapped_gas_overhead = 13_400 +coordinator_gas_overhead_native = 128_500 +coordinator_gas_overhead_link = 150_400 +coordinator_gas_overhead_per_word = 435 +coordinator_native_premium_percentage = 60 +coordinator_link_premium_percentage = 50 +wrapper_max_number_of_words = 10 + +# VRF Job config +vrf_job_forwarding_allowed = false +vrf_job_estimate_gas_multiplier = 1.15 +vrf_job_batch_fulfillment_enabled = true +vrf_job_batch_fulfillment_gas_multiplier = 1.1 +vrf_job_poll_period = "2s" +vrf_job_request_timeout = "2h0m0s" +vrf_job_simulation_block = "pending" + +# BHS Job config +bhs_job_wait_blocks = 30 +bhs_job_lookback_blocks = 200 +bhs_job_poll_period = "2s" +bhs_job_run_timeout = "30s" +# NEW ENV CONFIG END + +#SMOKE TEST CONFIG +[SONEIUM_SEPOLIA-Smoke.VRFv2Plus.General] randomness_request_count_per_request = 1 # amount of randomness requests to make per one TX request randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting number_of_sub_to_create = 1 +subscription_funding_amount_link = 3 +subscription_funding_amount_native = 1 +subscription_refunding_amount_link = 3 +subscription_refunding_amount_native = 1 +number_of_words = 1 +random_words_fulfilled_event_timeout = "1m30s" +wait_for_256_blocks_timeout = "10m" + +wrapper_consumer_funding_amount_native_token = 1.0 +wrapper_consumer_funding_amount_link = 5 + +[SONEIUM_SEPOLIA-Soak.VRFv2Plus.General] +randomness_request_count_per_request = 4 # amount of randomness requests to make per one TX request +randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting +number_of_sub_to_create = 1 +number_of_sending_keys_to_create = 0 subscription_funding_amount_link = 500 -subscription_funding_amount_native = 200 +subscription_funding_amount_native = 100 -[SEPOLIA-Soak.VRFv2Plus.Performance] +[SONEIUM_SEPOLIA-Soak.VRFv2Plus.Performance] test_duration = "2h" -rate_limit_unit_duration = "3s" +rate_limit_unit_duration = "10s" rps = 1 -# LOAD TEST CONFIG -[SEPOLIA-Load.VRFv2Plus.General] -randomness_request_count_per_request = 3 # amount of randomness requests to make per one TX request -randomness_request_count_per_request_deviation = 2 #NOTE - deviation should be less than randomness_request_count_per_request setting +[SONEIUM_SEPOLIA-Stress.VRFv2Plus.General] +randomness_request_count_per_request = 60 # amount of randomness requests to make per one TX request +randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting number_of_sub_to_create = 1 -subscription_funding_amount_link = 5.0 -subscription_funding_amount_native = 30 +number_of_sending_keys_to_create = 0 +subscription_funding_amount_link = 500 +subscription_funding_amount_native=100 -[SEPOLIA-Load.VRFv2Plus.Performance] -test_duration = "2m" -rate_limit_unit_duration = "3s" +[SONEIUM_SEPOLIA-Stress.VRFv2Plus.Performance] +test_duration = "10m" +rate_limit_unit_duration = "1m" +rps = 1 + + +### ETH SEPOLIA Config +[SEPOLIA.VRFv2Plus.General] +use_test_coordinator = true +#todo - need to have separate minimum_confirmations config for Coordinator, CL Node and Consumer request +minimum_confirmations = 3 + +# Consumer Request config +subscription_billing_type = "LINK_AND_NATIVE" +callback_gas_limit = 1000000 + +# NEW ENV CONFIG +# CL Node config +cl_node_max_gas_price_gwei = 100 +number_of_sending_keys_to_create = 0 + +# Coordinator config +max_gas_limit_coordinator_config = 2500000 +fallback_wei_per_unit_link = "5354747932930759" +staleness_seconds = 172_800 +gas_after_payment_calculation = 38_900 +fulfillment_flat_fee_native_ppm = 0 +fulfillment_flat_fee_link_discount_ppm = 0 +native_premium_percentage = 24 +link_premium_percentage = 20 + +# Wrapper config +wrapped_gas_overhead = 13_400 +coordinator_gas_overhead_native = 90_000 +coordinator_gas_overhead_link = 112_000 +coordinator_gas_overhead_per_word = 435 +coordinator_native_premium_percentage = 24 +coordinator_link_premium_percentage = 20 +wrapper_max_number_of_words = 10 + +# VRF Job config +vrf_job_forwarding_allowed = false +vrf_job_estimate_gas_multiplier = 1.15 +vrf_job_batch_fulfillment_enabled = false +vrf_job_batch_fulfillment_gas_multiplier = 1.1 +vrf_job_poll_period = "5s" +vrf_job_request_timeout = "2h0m0s" +vrf_job_simulation_block = "latest" + +# BHS Job config +bhs_job_wait_blocks = 30 +bhs_job_lookback_blocks = 200 +bhs_job_poll_period = "30s" +bhs_job_run_timeout = "1m0s" +# NEW ENV CONFIG END + +#SMOKE TEST CONFIG +[SEPOLIA-Smoke.VRFv2Plus.General] +randomness_request_count_per_request = 1 # amount of randomness requests to make per one TX request +randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting +number_of_sub_to_create = 1 +subscription_funding_amount_link = 3 +subscription_funding_amount_native = 1 +subscription_refunding_amount_link = 3 +subscription_refunding_amount_native = 1 +number_of_words = 1 +random_words_fulfilled_event_timeout = "1m30s" +wait_for_256_blocks_timeout = "70m" + +wrapper_consumer_funding_amount_native_token = 1.0 +wrapper_consumer_funding_amount_link = 3 + +[SEPOLIA-Soak.VRFv2Plus.General] +randomness_request_count_per_request = 4 # amount of randomness requests to make per one TX request +randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting +number_of_sub_to_create = 1 +number_of_sending_keys_to_create = 0 +subscription_funding_amount_link = 500 +subscription_funding_amount_native = 100 + +[SEPOLIA-Soak.VRFv2Plus.Performance] +test_duration = "2h" +rate_limit_unit_duration = "10s" rps = 1 -# STRESS TEST CONFIG [SEPOLIA-Stress.VRFv2Plus.General] -randomness_request_count_per_request = 3 # amount of randomness requests to make per one TX request -randomness_request_count_per_request_deviation = 2 #NOTE - deviation should be less than randomness_request_count_per_request setting +randomness_request_count_per_request = 30 # amount of randomness requests to make per one TX request +randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting number_of_sub_to_create = 1 -subscription_funding_amount_link = 5.0 -subscription_funding_amount_native = 0.1 +number_of_sending_keys_to_create = 0 +subscription_funding_amount_link = 500 +subscription_funding_amount_native=100 [SEPOLIA-Stress.VRFv2Plus.Performance] -test_duration = "2m" -rate_limit_unit_duration = "3s" +test_duration = "10m" +rate_limit_unit_duration = "1m" rps = 1 -### BSC SEPOLIA Config +### BSC TESTNET Config [BSC_TESTNET.VRFv2Plus.General] +use_test_coordinator = true #todo - need to have separate minimum_confirmations config for Coordinator, CL Node and Consumer request minimum_confirmations = 3 @@ -794,77 +1059,51 @@ bhs_job_poll_period = "2s" bhs_job_run_timeout = "30s" # NEW ENV CONFIG END -[BSC_TESTNET.VRFv2Plus.ExistingEnv] -coordinator_address = "0x84A477F6ebF33501eE3ACA86fEcB980b1fC99AC2" -consumer_address = "" -sub_id = "" -key_hash = "0x4d43763d3eff849a89cf578a42787baa32132d7a80032125710e95b3972cd214" -create_fund_subs_and_add_consumers = true -link_address = "0x84b9B910527Ad5C03A9Ca831909E21e236EA7b06" -node_sending_key_funding_min = 150 -node_sending_keys = [ - "0x4EE2Cc6D50E8acb6BaEf673B03559525a6c92fB8", - # BHS - "0xAFB44568f7DAc218EA6e1C71c366692ED4758A07" -] - #SMOKE TEST CONFIG [BSC_TESTNET-Smoke.VRFv2Plus.General] randomness_request_count_per_request = 1 # amount of randomness requests to make per one TX request randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting number_of_sub_to_create = 1 -subscription_funding_amount_link = 10 -subscription_funding_amount_native = 10 - -[BSC_TESTNET-Smoke.VRFv2Plus.Performance] -test_duration = "15s" -rate_limit_unit_duration = "3s" -rps = 1 +subscription_funding_amount_link = 3 +subscription_funding_amount_native = 1 +subscription_refunding_amount_link = 3 +subscription_refunding_amount_native = 1 +number_of_words = 1 +random_words_fulfilled_event_timeout = "1m30s" +wait_for_256_blocks_timeout = "15m" +wrapper_consumer_funding_amount_native_token = 1.0 +wrapper_consumer_funding_amount_link = 5 -#SOAK TEST CONFIG [BSC_TESTNET-Soak.VRFv2Plus.General] -randomness_request_count_per_request = 1 # amount of randomness requests to make per one TX request +randomness_request_count_per_request = 4 # amount of randomness requests to make per one TX request randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting number_of_sub_to_create = 1 -subscription_funding_amount_link = 400 -subscription_funding_amount_native = 200 +number_of_sending_keys_to_create = 0 +subscription_funding_amount_link = 500 +subscription_funding_amount_native = 100 [BSC_TESTNET-Soak.VRFv2Plus.Performance] -test_duration = "5h" -rate_limit_unit_duration = "3s" -rps = 1 - -# LOAD TEST CONFIG -[BSC_TESTNET-Load.VRFv2Plus.General] -randomness_request_count_per_request = 3 # amount of randomness requests to make per one TX request -randomness_request_count_per_request_deviation = 2 #NOTE - deviation should be less than randomness_request_count_per_request setting -number_of_sub_to_create = 1 -subscription_funding_amount_link = 300 -subscription_funding_amount_native = 300 - -[BSC_TESTNET-Load.VRFv2Plus.Performance] -test_duration = "2m" -rate_limit_unit_duration = "3s" +test_duration = "2h" +rate_limit_unit_duration = "10s" rps = 1 - -# STRESS TEST CONFIG [BSC_TESTNET-Stress.VRFv2Plus.General] -randomness_request_count_per_request = 3 # amount of randomness requests to make per one TX request -randomness_request_count_per_request_deviation = 2 #NOTE - deviation should be less than randomness_request_count_per_request setting +randomness_request_count_per_request = 60 # amount of randomness requests to make per one TX request +randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting number_of_sub_to_create = 1 -subscription_funding_amount_link = 5.0 -subscription_funding_amount_native = 0.1 +number_of_sending_keys_to_create = 0 +subscription_funding_amount_link = 500 +subscription_funding_amount_native=100 [BSC_TESTNET-Stress.VRFv2Plus.Performance] -test_duration = "2m" -rate_limit_unit_duration = "3s" +test_duration = "10m" +rate_limit_unit_duration = "1m" rps = 1 - ### NEXON QA Config [NEXON_QA.VRFv2Plus.General] +use_test_coordinator = true #todo - need to have separate minimum_confirmations config for Coordinator, CL Node and Consumer request minimum_confirmations = 0 generate_txs_on_chain = true @@ -873,21 +1112,6 @@ generate_txs_on_chain = true subscription_billing_type = "LINK_AND_NATIVE" callback_gas_limit = 1000000 -[NEXON_QA.VRFv2Plus.ExistingEnv] -coordinator_address = "0xF1F0beBcc284591FCD28d8f2BAc9f30efdA3E0ea" -consumer_address = "" -sub_id = "" -key_hash = "0x7d5692e71807c4c02f5a109627a9ad2b12a361a346790a306983af9a5e3a186f" -create_fund_subs_and_add_consumers = true -link_address = "0x92Bd61014c5BDc4A43BBbaAEa63d0694BE43ECDd" -node_sending_key_funding_min = 30 -node_sending_keys = [ - "0xB97c0C52A2B957b45DA213e652c76090DDd0FEc6", - "0xe205F5d4a99ca0f474d0b4d12f60a0153c786B4E", - # BHS - "0xf85E291edF0352435f2fD5e817030f6542375a99", -] - #SMOKE TEST CONFIG [NEXON_QA-Smoke.VRFv2Plus.General] randomness_request_count_per_request = 1 # amount of randomness requests to make per one TX request @@ -903,55 +1127,35 @@ wait_for_256_blocks_timeout = "25m" wrapper_consumer_funding_amount_link = 21 wrapper_consumer_funding_amount_native_token = 3 - -[NEXON_QA-Smoke.VRFv2Plus.Performance] -test_duration = "2s" -rate_limit_unit_duration = "3s" -rps = 1 - - -#SOAK TEST CONFIG [NEXON_QA-Soak.VRFv2Plus.General] -randomness_request_count_per_request = 1 # amount of randomness requests to make per one TX request +randomness_request_count_per_request = 4 # amount of randomness requests to make per one TX request randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting number_of_sub_to_create = 1 -subscription_funding_amount_link = 400 -subscription_funding_amount_native = 200 +number_of_sending_keys_to_create = 0 +subscription_funding_amount_link = 500 +subscription_funding_amount_native = 100 [NEXON_QA-Soak.VRFv2Plus.Performance] -test_duration = "5h" -rate_limit_unit_duration = "3s" -rps = 1 - -# LOAD TEST CONFIG -[NEXON_QA-Load.VRFv2Plus.General] -randomness_request_count_per_request = 3 # amount of randomness requests to make per one TX request -randomness_request_count_per_request_deviation = 2 #NOTE - deviation should be less than randomness_request_count_per_request setting -number_of_sub_to_create = 1 -subscription_funding_amount_link = 300 -subscription_funding_amount_native = 300 - -[NEXON_QA-Load.VRFv2Plus.Performance] -test_duration = "2m" -rate_limit_unit_duration = "3s" +test_duration = "2h" +rate_limit_unit_duration = "10s" rps = 1 - -# STRESS TEST CONFIG [NEXON_QA-Stress.VRFv2Plus.General] -randomness_request_count_per_request = 3 # amount of randomness requests to make per one TX request -randomness_request_count_per_request_deviation = 2 #NOTE - deviation should be less than randomness_request_count_per_request setting +randomness_request_count_per_request = 60 # amount of randomness requests to make per one TX request +randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting number_of_sub_to_create = 1 -subscription_funding_amount_link = 5.0 -subscription_funding_amount_native = 2 +number_of_sending_keys_to_create = 0 +subscription_funding_amount_link = 500 +subscription_funding_amount_native=100 [NEXON_QA-Stress.VRFv2Plus.Performance] -test_duration = "2m" -rate_limit_unit_duration = "3s" +test_duration = "10m" +rate_limit_unit_duration = "1m" rps = 1 ### NEXON DEV Config [NEXON_DEV.VRFv2Plus.General] +use_test_coordinator = true #todo - need to have separate minimum_confirmations config for Coordinator, CL Node and Consumer request minimum_confirmations = 0 generate_txs_on_chain = true @@ -960,20 +1164,6 @@ generate_txs_on_chain = true subscription_billing_type = "LINK_AND_NATIVE" callback_gas_limit = 1000000 -[NEXON_DEV.VRFv2Plus.ExistingEnv] -coordinator_address = "0x6901d7236A823E7B7911d90FBe46E6FA770CC823" -consumer_address = "" -sub_id = "" -key_hash = "0xdc023892a41e5fe74ec7c4c2e8c0a808b01aea7acaf2b2ae30f4e08df877c48b" -create_fund_subs_and_add_consumers = true -link_address = "0xE4DDEDb5A220eC218791dC35b1b4D737ba813EE7" -node_sending_key_funding_min = 30 -node_sending_keys = [ - "0xF3d9879a75BBD85890056D7c6cB37C555F9b41A3", - # BHS - "0xb544f9D7c16a30af0EEd0afcC4132D1c63bAF8AC", -] - #SMOKE TEST CONFIG [NEXON_DEV-Smoke.VRFv2Plus.General] randomness_request_count_per_request = 1 # amount of randomness requests to make per one TX request @@ -989,55 +1179,35 @@ wait_for_256_blocks_timeout = "25m" wrapper_consumer_funding_amount_link = 21 wrapper_consumer_funding_amount_native_token = 3 -[NEXON_DEV-Smoke.VRFv2Plus.Performance] -test_duration = "2s" -rate_limit_unit_duration = "3s" -rps = 1 - - -#SOAK TEST CONFIG [NEXON_DEV-Soak.VRFv2Plus.General] -randomness_request_count_per_request = 1 # amount of randomness requests to make per one TX request +randomness_request_count_per_request = 4 # amount of randomness requests to make per one TX request randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting number_of_sub_to_create = 1 -subscription_funding_amount_link = 400 -subscription_funding_amount_native = 200 +number_of_sending_keys_to_create = 0 +subscription_funding_amount_link = 500 +subscription_funding_amount_native = 100 [NEXON_DEV-Soak.VRFv2Plus.Performance] -test_duration = "5h" -rate_limit_unit_duration = "3s" -rps = 1 - -# LOAD TEST CONFIG -[NEXON_DEV-Load.VRFv2Plus.General] -randomness_request_count_per_request = 3 # amount of randomness requests to make per one TX request -randomness_request_count_per_request_deviation = 2 #NOTE - deviation should be less than randomness_request_count_per_request setting -number_of_sub_to_create = 1 -subscription_funding_amount_link = 300 -subscription_funding_amount_native = 300 - -[NEXON_DEV-Load.VRFv2Plus.Performance] -test_duration = "2m" -rate_limit_unit_duration = "3s" +test_duration = "2h" +rate_limit_unit_duration = "10s" rps = 1 - -# STRESS TEST CONFIG [NEXON_DEV-Stress.VRFv2Plus.General] -randomness_request_count_per_request = 3 # amount of randomness requests to make per one TX request -randomness_request_count_per_request_deviation = 2 #NOTE - deviation should be less than randomness_request_count_per_request setting +randomness_request_count_per_request = 60 # amount of randomness requests to make per one TX request +randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting number_of_sub_to_create = 1 -subscription_funding_amount_link = 5.0 -subscription_funding_amount_native = 0.1 +number_of_sending_keys_to_create = 0 +subscription_funding_amount_link = 500 +subscription_funding_amount_native=100 [NEXON_DEV-Stress.VRFv2Plus.Performance] -test_duration = "2m" -rate_limit_unit_duration = "3s" +test_duration = "10m" +rate_limit_unit_duration = "1m" rps = 1 - ### NEXON TEST Config [NEXON_TEST.VRFv2Plus.General] +use_test_coordinator = true #todo - need to have separate minimum_confirmations config for Coordinator, CL Node and Consumer request minimum_confirmations = 0 generate_txs_on_chain = true @@ -1046,20 +1216,6 @@ generate_txs_on_chain = true subscription_billing_type = "LINK_AND_NATIVE" callback_gas_limit = 1000000 -[NEXON_TEST.VRFv2Plus.ExistingEnv] -coordinator_address = "0xAa92Ba21168B48195cAdB87cfaB3eB70B2499F55" -consumer_address = "" -sub_id = "" -key_hash = "0x0cb2a18e8b762cb4c8f7b17a6cc02ac7b9d2a3346f048cfd2f5d37677f8747d8" -create_fund_subs_and_add_consumers = true -link_address = "0xD694472F1CD02E1f3fc3534386bda6802fCFe0f7" -node_sending_key_funding_min = 30 -node_sending_keys = [ - "0xBFD780Af421e98C35918e10B9d6da7389C3e1D10", - "0xbf6c76024672F233aB8164EC00683e1AE774F6b0", - # BHS - "0x2a3900Ac77de110670E060DBFf4fCbe36c6f8170", -] #SMOKE TEST CONFIG [NEXON_TEST-Smoke.VRFv2Plus.General] @@ -1076,55 +1232,36 @@ wait_for_256_blocks_timeout = "25m" wrapper_consumer_funding_amount_link = 5 wrapper_consumer_funding_amount_native_token = 3 -[NEXON_TEST-Smoke.VRFv2Plus.Performance] -test_duration = "2s" -rate_limit_unit_duration = "3s" -rps = 1 - - -#SOAK TEST CONFIG [NEXON_TEST-Soak.VRFv2Plus.General] -randomness_request_count_per_request = 1 # amount of randomness requests to make per one TX request +randomness_request_count_per_request = 4 # amount of randomness requests to make per one TX request randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting number_of_sub_to_create = 1 -subscription_funding_amount_link = 400 -subscription_funding_amount_native = 200 +number_of_sending_keys_to_create = 0 +subscription_funding_amount_link = 500 +subscription_funding_amount_native = 100 [NEXON_TEST-Soak.VRFv2Plus.Performance] -test_duration = "5h" -rate_limit_unit_duration = "3s" -rps = 1 - -# LOAD TEST CONFIG -[NEXON_TEST-Load.VRFv2Plus.General] -randomness_request_count_per_request = 3 # amount of randomness requests to make per one TX request -randomness_request_count_per_request_deviation = 2 #NOTE - deviation should be less than randomness_request_count_per_request setting -number_of_sub_to_create = 1 -subscription_funding_amount_link = 300 -subscription_funding_amount_native = 300 - -[NEXON_TEST-Load.VRFv2Plus.Performance] -test_duration = "2m" -rate_limit_unit_duration = "3s" +test_duration = "2h" +rate_limit_unit_duration = "10s" rps = 1 - -# STRESS TEST CONFIG [NEXON_TEST-Stress.VRFv2Plus.General] -randomness_request_count_per_request = 3 # amount of randomness requests to make per one TX request -randomness_request_count_per_request_deviation = 2 #NOTE - deviation should be less than randomness_request_count_per_request setting +randomness_request_count_per_request = 60 # amount of randomness requests to make per one TX request +randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting number_of_sub_to_create = 1 -subscription_funding_amount_link = 5.0 -subscription_funding_amount_native = 0.1 +number_of_sending_keys_to_create = 0 +subscription_funding_amount_link = 500 +subscription_funding_amount_native=100 [NEXON_TEST-Stress.VRFv2Plus.Performance] -test_duration = "2m" -rate_limit_unit_duration = "3s" +test_duration = "10m" +rate_limit_unit_duration = "1m" rps = 1 ### NEXON STAGE Config [NEXON_STAGE.VRFv2Plus.General] +use_test_coordinator = true #todo - need to have separate minimum_confirmations config for Coordinator, CL Node and Consumer request minimum_confirmations = 0 generate_txs_on_chain = true @@ -1133,16 +1270,6 @@ generate_txs_on_chain = true subscription_billing_type = "LINK_AND_NATIVE" callback_gas_limit = 1000000 -[NEXON_STAGE.VRFv2Plus.ExistingEnv] -coordinator_address = "0xF705dD3e7E717F32de0Cc5F833f8009f16122AD1" -consumer_address = "" -sub_id = "" -key_hash = "0xbc9f525e3e1d9e2336f7c77d5f33f5b60aab3765944617fed7f66a6afecac616" -create_fund_subs_and_add_consumers = true -link_address = "0x8E3f5E6dFeb4498437149b0d347ef51427dB1DE2" -node_sending_key_funding_min = 30 -node_sending_keys = [ -] #SMOKE TEST CONFIG [NEXON_STAGE-Smoke.VRFv2Plus.General] @@ -1159,48 +1286,28 @@ wait_for_256_blocks_timeout = "25m" wrapper_consumer_funding_amount_link = 21 wrapper_consumer_funding_amount_native_token = 3 -[NEXON_STAGE-Smoke.VRFv2Plus.Performance] -test_duration = "2s" -rate_limit_unit_duration = "3s" -rps = 1 - - -#SOAK TEST CONFIG [NEXON_STAGE-Soak.VRFv2Plus.General] -randomness_request_count_per_request = 1 # amount of randomness requests to make per one TX request +randomness_request_count_per_request = 4 # amount of randomness requests to make per one TX request randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting number_of_sub_to_create = 1 -subscription_funding_amount_link = 400 -subscription_funding_amount_native = 200 +number_of_sending_keys_to_create = 0 +subscription_funding_amount_link = 500 +subscription_funding_amount_native = 100 [NEXON_STAGE-Soak.VRFv2Plus.Performance] -test_duration = "5h" -rate_limit_unit_duration = "3s" -rps = 1 - -# LOAD TEST CONFIG -[NEXON_STAGE-Load.VRFv2Plus.General] -randomness_request_count_per_request = 3 # amount of randomness requests to make per one TX request -randomness_request_count_per_request_deviation = 2 #NOTE - deviation should be less than randomness_request_count_per_request setting -number_of_sub_to_create = 1 -subscription_funding_amount_link = 300 -subscription_funding_amount_native = 300 - -[NEXON_STAGE-Load.VRFv2Plus.Performance] -test_duration = "2m" -rate_limit_unit_duration = "3s" +test_duration = "2h" +rate_limit_unit_duration = "10s" rps = 1 - -# STRESS TEST CONFIG [NEXON_STAGE-Stress.VRFv2Plus.General] -randomness_request_count_per_request = 3 # amount of randomness requests to make per one TX request -randomness_request_count_per_request_deviation = 2 #NOTE - deviation should be less than randomness_request_count_per_request setting +randomness_request_count_per_request = 60 # amount of randomness requests to make per one TX request +randomness_request_count_per_request_deviation = 0 #NOTE - deviation should be less than randomness_request_count_per_request setting number_of_sub_to_create = 1 -subscription_funding_amount_link = 5.0 -subscription_funding_amount_native = 0.1 +number_of_sending_keys_to_create = 0 +subscription_funding_amount_link = 500 +subscription_funding_amount_native=100 [NEXON_STAGE-Stress.VRFv2Plus.Performance] -test_duration = "2m" -rate_limit_unit_duration = "3s" +test_duration = "10m" +rate_limit_unit_duration = "1m" rps = 1 diff --git a/integration-tests/testreporters/keeper_benchmark.go b/integration-tests/testreporters/keeper_benchmark.go index 4c00c99251..c85de92493 100644 --- a/integration-tests/testreporters/keeper_benchmark.go +++ b/integration-tests/testreporters/keeper_benchmark.go @@ -15,7 +15,6 @@ import ( "github.com/slack-go/slack" "github.com/smartcontractkit/chainlink-testing-framework/lib/testreporters" - "github.com/smartcontractkit/chainlink/integration-tests/client" ) diff --git a/integration-tests/testsetups/keeper_benchmark.go b/integration-tests/testsetups/automation_benchmark.go similarity index 78% rename from integration-tests/testsetups/keeper_benchmark.go rename to integration-tests/testsetups/automation_benchmark.go index af1e76b1d7..ff8100ea43 100644 --- a/integration-tests/testsetups/keeper_benchmark.go +++ b/integration-tests/testsetups/automation_benchmark.go @@ -12,6 +12,10 @@ import ( "testing" "time" + "github.com/smartcontractkit/chainlink/integration-tests/testconfig" + + "github.com/smartcontractkit/chainlink/integration-tests/actions/automationv2" + geth "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -37,7 +41,7 @@ import ( "github.com/smartcontractkit/chainlink/integration-tests/client" "github.com/smartcontractkit/chainlink/integration-tests/contracts" "github.com/smartcontractkit/chainlink/integration-tests/contracts/ethereum" - keepertestconfig "github.com/smartcontractkit/chainlink/integration-tests/testconfig/keeper" + autotestconfig "github.com/smartcontractkit/chainlink/integration-tests/testconfig/automation" "github.com/smartcontractkit/chainlink/integration-tests/testreporters" tt "github.com/smartcontractkit/chainlink/integration-tests/types" ) @@ -52,6 +56,7 @@ type KeeperBenchmarkTest struct { log zerolog.Logger startingBlock *big.Int + automationTests []automationv2.AutomationTest keeperRegistries []contracts.KeeperRegistry keeperRegistrars []contracts.KeeperRegistrar keeperConsumerContracts []contracts.AutomationConsumerBenchmark @@ -61,11 +66,9 @@ type KeeperBenchmarkTest struct { namespace string chainlinkNodes []*client.ChainlinkK8sClient chainClient *seth.Client - testConfig tt.KeeperBenchmarkTestConfig + testConfig tt.AutomationBenchmarkTestConfig linkToken contracts.LinkToken - ethFeed contracts.MockETHLINKFeed - gasFeed contracts.MockGasFeed } // UpkeepConfig dictates details of how the test's upkeep contracts should be called and configured @@ -79,22 +82,11 @@ type UpkeepConfig struct { FirstEligibleBuffer int64 // How many blocks to add to randomised first eligible block, set to 0 to disable randomised first eligible block } -// PreDeployedContracts are contracts that are already deployed on a (usually) live testnet chain, so re-deployment -// in unnecessary -type PreDeployedContracts struct { - RegistryAddress string - RegistrarAddress string - LinkTokenAddress string - EthFeedAddress string - GasFeedAddress string -} - // KeeperBenchmarkTestInputs are all the required inputs for a Keeper Benchmark Test type KeeperBenchmarkTestInputs struct { BlockchainClient *seth.Client // Client for the test to connect to the blockchain with KeeperRegistrySettings *contracts.KeeperRegistrySettings // Settings of each keeper contract Upkeeps *UpkeepConfig - Contracts *PreDeployedContracts Timeout time.Duration // Timeout for the test ChainlinkNodeFunding *big.Float // Amount of ETH to fund each chainlink node with UpkeepSLA int64 // SLA in number of blocks for an upkeep to be performed once it becomes eligible @@ -115,15 +107,16 @@ func NewKeeperBenchmarkTest(t *testing.T, inputs KeeperBenchmarkTestInputs) *Kee } // Setup prepares contracts for the test -func (k *KeeperBenchmarkTest) Setup(env *environment.Environment, config tt.KeeperBenchmarkTestConfig) { +func (k *KeeperBenchmarkTest) Setup(env *environment.Environment, config testconfig.TestConfig) { startTime := time.Now() k.TestReporter.Summary.StartTime = startTime.UnixMilli() k.ensureInputValues() k.env = env k.namespace = k.env.Cfg.Namespace inputs := k.Inputs - k.testConfig = config + k.testConfig = &config + k.automationTests = make([]automationv2.AutomationTest, len(inputs.RegistryVersions)) k.keeperRegistries = make([]contracts.KeeperRegistry, len(inputs.RegistryVersions)) k.keeperRegistrars = make([]contracts.KeeperRegistrar, len(inputs.RegistryVersions)) k.keeperConsumerContracts = make([]contracts.AutomationConsumerBenchmark, len(inputs.RegistryVersions)) @@ -131,8 +124,8 @@ func (k *KeeperBenchmarkTest) Setup(env *environment.Environment, config tt.Keep k.log.Debug().Interface("TestInputs", inputs).Msg("Setting up benchmark test") // if not present disable it - if k.testConfig.GetKeeperConfig().Resiliency == nil { - k.testConfig.GetKeeperConfig().Resiliency = &keepertestconfig.ResiliencyConfig{ + if k.testConfig.GetAutomationConfig().Resiliency == nil { + k.testConfig.GetAutomationConfig().Resiliency = &autotestconfig.ResiliencyConfig{ ContractCallLimit: ptr.Ptr(uint(0)), ContractCallInterval: ptr.Ptr(blockchain.StrDuration{Duration: 0 * time.Second}), } @@ -153,35 +146,22 @@ func (k *KeeperBenchmarkTest) Setup(env *environment.Environment, config tt.Keep } } - c := inputs.Contracts - - if common.IsHexAddress(c.LinkTokenAddress) { - _, err = contracts.LoadLinkTokenContract(k.log, k.chainClient, common.HexToAddress(c.LinkTokenAddress)) - require.NoError(k.t, err, "Loading Link Token Contract shouldn't fail") - } else { - k.linkToken, err = contracts.DeployLinkTokenContract(k.log, k.chainClient) - require.NoError(k.t, err, "Deploying mock Link Token Contract feed shouldn't fail") - } - - if common.IsHexAddress(c.EthFeedAddress) { - _, err = contracts.LoadMockETHLINKFeed(k.chainClient, common.HexToAddress(c.EthFeedAddress)) - require.NoError(k.t, err, "Loading ETH-Link feed Contract shouldn't fail") - } else { - k.ethFeed, err = contracts.DeployMockETHLINKFeed(k.chainClient, big.NewInt(2e18)) - require.NoError(k.t, err, "Deploying mock ETH-Link feed shouldn't fail") - } - - if common.IsHexAddress(c.GasFeedAddress) { - k.gasFeed, err = contracts.LoadMockGASFeed(k.chainClient, common.HexToAddress(c.GasFeedAddress)) - require.NoError(k.t, err, "Loading Gas feed Contract shouldn't fail") - } else { - k.gasFeed, err = contracts.DeployMockGASFeed(k.chainClient, big.NewInt(2e11)) - require.NoError(k.t, err, "Deploying mock gas feed shouldn't fail") - } - for index := range inputs.RegistryVersions { k.log.Info().Int("Index", index).Msg("Starting Test Setup") - k.DeployBenchmarkKeeperContracts(index) + a := automationv2.NewAutomationTestK8s(k.log, k.chainClient, k.chainlinkNodes, &config) + a.RegistrySettings = *k.Inputs.KeeperRegistrySettings + a.RegistrySettings.RegistryVersion = inputs.RegistryVersions[index] + a.RegistrarSettings = contracts.KeeperRegistrarSettings{ + AutoApproveConfigType: uint8(2), + AutoApproveMaxAllowed: math.MaxUint16, + MinLinkJuels: big.NewInt(0), + } + a.PluginConfig = actions.ReadPluginConfig(config) + a.PublicConfig = actions.ReadPublicConfig(config) + a.SetupAutomationDeploymentWithoutJobs(k.t) + err = a.SetConfigOnRegistry() + require.NoError(k.t, err, "Setting initial config on registry shouldn't fail") + k.SetupBenchmarkKeeperContracts(index, a) } var keysToFund = inputs.RegistryVersions @@ -192,7 +172,7 @@ func (k *KeeperBenchmarkTest) Setup(env *environment.Environment, config tt.Keep for index := range keysToFund { // Fund chainlink nodes nodesToFund := k.chainlinkNodes - if inputs.RegistryVersions[index] == ethereum.RegistryVersion_2_0 || inputs.RegistryVersions[index] == ethereum.RegistryVersion_2_1 || inputs.RegistryVersions[index] == ethereum.RegistryVersion_2_2 { + if inputs.RegistryVersions[index] >= ethereum.RegistryVersion_2_0 { nodesToFund = k.chainlinkNodes[1:] } err = actions.FundChainlinkNodesAtKeyIndexFromRootAddress(k.log, k.chainClient, contracts.ChainlinkK8sClientToChainlinkNodeWithKeysAndAddress(nodesToFund), k.Inputs.ChainlinkNodeFunding, index) @@ -200,7 +180,7 @@ func (k *KeeperBenchmarkTest) Setup(env *environment.Environment, config tt.Keep } k.log.Info().Str("Setup Time", time.Since(startTime).String()).Msg("Finished Keeper Benchmark Test Setup") - err = k.SendSlackNotification(nil, config) + err = k.SendSlackNotification(nil, &config) if err != nil { k.log.Warn().Msg("Sending test start slack notification failed") } @@ -216,7 +196,6 @@ func (k *KeeperBenchmarkTest) Run() { float64(u.BlockInterval) k.TestReporter.Summary.TestInputs = map[string]interface{}{ "NumberOfUpkeeps": u.NumberOfUpkeeps, - "BlockCountPerTurn": k.Inputs.KeeperRegistrySettings.BlockCountPerTurn, "CheckGasLimit": k.Inputs.KeeperRegistrySettings.CheckGasLimit, "MaxPerformGas": k.Inputs.KeeperRegistrySettings.MaxPerformGas, "CheckGasToBurn": u.CheckGasToBurn, @@ -233,35 +212,15 @@ func (k *KeeperBenchmarkTest) Run() { k.startingBlock = big.NewInt(0).SetUint64(startingBlock) startTime := time.Now() - nodesWithoutBootstrap := k.chainlinkNodes[1:] - for rIndex := range k.keeperRegistries { var txKeyId = rIndex if inputs.ForceSingleTxnKey { txKeyId = 0 } - kr := k.keeperRegistries[rIndex] - // TODO: need to add the LINK, WETH and WETH/USD feed to support v23 - ocrConfig, err := actions.BuildAutoOCR2ConfigVarsWithKeyIndex( - k.t, nodesWithoutBootstrap, *inputs.KeeperRegistrySettings, kr.Address(), k.Inputs.DeltaStage, txKeyId, common.Address{}, kr.ChainModuleAddress(), kr.ReorgProtectionEnabled(), nil, nil, nil, - ) - require.NoError(k.t, err, "Building OCR config shouldn't fail") - - rv := inputs.RegistryVersions[rIndex] - // Send keeper jobs to registry and chainlink nodes - if rv == ethereum.RegistryVersion_2_0 || rv == ethereum.RegistryVersion_2_1 || rv == ethereum.RegistryVersion_2_2 { - actions.CreateOCRKeeperJobs(k.t, k.chainlinkNodes, kr.Address(), k.chainClient.ChainID, txKeyId, rv) - if rv == ethereum.RegistryVersion_2_0 { - err = kr.SetConfig(*inputs.KeeperRegistrySettings, ocrConfig) - } else { - err = kr.SetConfigTypeSafe(ocrConfig) - } - require.NoError(k.t, err, "Registry config should be be set successfully") - // Give time for OCR nodes to bootstrap - time.Sleep(1 * time.Minute) - } else { - actions.CreateKeeperJobsWithKeyIndex(k.t, k.chainlinkNodes, kr, txKeyId, ocrConfig, fmt.Sprint(k.chainClient.ChainID)) - } + k.automationTests[rIndex].SetTransmitterKeyIndex(txKeyId) + k.automationTests[rIndex].AddJobsAndSetConfig(k.t) + // Give time for OCR nodes to bootstrap + time.Sleep(1 * time.Minute) } k.log.Info().Msgf("Waiting for %d blocks for all upkeeps to be performed", inputs.Upkeeps.BlockRange+inputs.UpkeepSLA) @@ -344,7 +303,7 @@ func (k *KeeperBenchmarkTest) Run() { startedObservations.Add(1) k.log.Info().Int("Channel index", chIndex).Str("UpkeepID", upkeepIDCopy.String()).Msg("Starting upkeep observation") - confirmer := contracts.NewKeeperConsumerBenchmarkUpkeepObserver( + confirmer := contracts.NewAutomationConsumerBenchmarkUpkeepObserver( k.keeperConsumerContracts[registryIndex], k.keeperRegistries[registryIndex], upkeepIDCopy, @@ -659,7 +618,7 @@ func (k *KeeperBenchmarkTest) ensureInputValues() { } } -func (k *KeeperBenchmarkTest) SendSlackNotification(slackClient *slack.Client, config tt.KeeperBenchmarkTestConfig) error { +func (k *KeeperBenchmarkTest) SendSlackNotification(slackClient *slack.Client, config tt.AutomationBenchmarkTestConfig) error { if slackClient == nil { slackClient = slack.New(reportModel.SlackAPIKey) } @@ -693,65 +652,25 @@ func (k *KeeperBenchmarkTest) SendSlackNotification(slackClient *slack.Client, c return err } -// DeployBenchmarkKeeperContracts deploys a set amount of keeper Benchmark contracts registered to a single registry -func (k *KeeperBenchmarkTest) DeployBenchmarkKeeperContracts(index int) { +// SetupBenchmarkKeeperContracts deploys a set amount of keeper Benchmark contracts registered to a single registry +func (k *KeeperBenchmarkTest) SetupBenchmarkKeeperContracts(index int, a *automationv2.AutomationTest) { registryVersion := k.Inputs.RegistryVersions[index] k.Inputs.KeeperRegistrySettings.RegistryVersion = registryVersion upkeep := k.Inputs.Upkeeps var ( - registry contracts.KeeperRegistry - registrar contracts.KeeperRegistrar - err error + err error ) - // Contract deployment is different for legacy keepers and OCR automation - if registryVersion <= ethereum.RegistryVersion_1_3 { // Legacy keeper - v1.X - registry, err = contracts.DeployKeeperRegistry(k.chainClient, &contracts.KeeperRegistryOpts{ - RegistryVersion: registryVersion, - LinkAddr: k.linkToken.Address(), - ETHFeedAddr: k.ethFeed.Address(), - GasFeedAddr: k.gasFeed.Address(), - TranscoderAddr: actions.ZeroAddress.Hex(), - RegistrarAddr: actions.ZeroAddress.Hex(), - Settings: *k.Inputs.KeeperRegistrySettings, - }) - require.NoError(k.t, err, "Deploying registry contract shouldn't fail") - - // Fund the registry with 1 LINK * amount of AutomationConsumerBenchmark contracts - err := k.linkToken.Transfer(registry.Address(), big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(int64(k.Inputs.Upkeeps.NumberOfUpkeeps)))) - require.NoError(k.t, err, "Funding keeper registry contract shouldn't fail") - - registrarSettings := contracts.KeeperRegistrarSettings{ - AutoApproveConfigType: 2, - AutoApproveMaxAllowed: math.MaxUint16, - RegistryAddr: registry.Address(), - MinLinkJuels: big.NewInt(0), - } - - registrar, err = contracts.DeployKeeperRegistrar(k.chainClient, registryVersion, k.linkToken.Address(), registrarSettings) - require.NoError(k.t, err, "Funding keeper registrar contract shouldn't fail") - } else { // OCR automation - v2.X - registry, registrar = actions.DeployAutoOCRRegistryAndRegistrar( - k.t, k.chainClient, registryVersion, *k.Inputs.KeeperRegistrySettings, k.linkToken, nil, nil, - ) - - // Fund the registry with LINK - err := k.linkToken.Transfer(registry.Address(), big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(int64(k.Inputs.Upkeeps.NumberOfUpkeeps)))) - require.NoError(k.t, err, "Funding keeper registry contract shouldn't fail") - ocrConfig, err := actions.BuildAutoOCR2ConfigVars(k.t, k.chainlinkNodes[1:], *k.Inputs.KeeperRegistrySettings, registrar.Address(), k.Inputs.DeltaStage, registry.ChainModuleAddress(), registry.ReorgProtectionEnabled(), nil, nil, nil) - require.NoError(k.t, err, "Building OCR config shouldn't fail") - k.log.Debug().Interface("KeeperRegistrySettings", *k.Inputs.KeeperRegistrySettings).Interface("OCRConfig", ocrConfig).Msg("Config") - require.NoError(k.t, err, "Error building OCR config vars") - if registryVersion == ethereum.RegistryVersion_2_0 { - err = registry.SetConfig(*k.Inputs.KeeperRegistrySettings, ocrConfig) - } else { - err = registry.SetConfigTypeSafe(ocrConfig) - } - require.NoError(k.t, err, "Registry config should be be set successfully") + var consumer contracts.AutomationConsumerBenchmark + if a.TestConfig.GetAutomationConfig().UseExistingUpkeepContracts() { + benchmarkAddresses, err := a.TestConfig.GetAutomationConfig().UpkeepContractAddresses() + require.NoError(k.t, err, "Getting upkeep contract addresses shouldn't fail") + consumer, err = contracts.LoadAutomationConsumerBenchmark(k.chainClient, benchmarkAddresses[0]) + require.NoError(k.t, err, "Loading KeeperConsumerBenchmark shouldn't fail") + } else { + consumer = k.DeployKeeperConsumersBenchmark() } - consumer := k.DeployKeeperConsumersBenchmark() - var upkeepAddresses []string checkData := make([][]byte, 0) @@ -799,14 +718,16 @@ func (k *KeeperBenchmarkTest) DeployBenchmarkKeeperContracts(index int) { big.NewInt(0)) linkFunds = big.NewInt(0).Add(linkFunds, minLinkBalance) + k.linkToken = a.LinkToken - err = actions.DeployMultiCallAndFundDeploymentAddresses(k.chainClient, k.linkToken, upkeep.NumberOfUpkeeps, linkFunds) + err = actions.SetupMultiCallAndFundDeploymentAddresses(k.chainClient, k.linkToken, upkeep.NumberOfUpkeeps, linkFunds, a.TestConfig) require.NoError(k.t, err, "Sending link funds to deployment addresses shouldn't fail") - upkeepIds := actions.RegisterUpkeepContractsWithCheckData(k.t, k.chainClient, k.linkToken, linkFunds, uint32(upkeep.UpkeepGasLimit), registry, registrar, upkeep.NumberOfUpkeeps, upkeepAddresses, checkData, false, false, false, nil) + upkeepIds := actions.RegisterUpkeepContractsWithCheckData(k.t, k.chainClient, k.linkToken, linkFunds, uint32(upkeep.UpkeepGasLimit), a.Registry, a.Registrar, upkeep.NumberOfUpkeeps, upkeepAddresses, checkData, false, false, false, nil) - k.keeperRegistries[index] = registry - k.keeperRegistrars[index] = registrar + k.automationTests[index] = *a + k.keeperRegistries[index] = a.Registry + k.keeperRegistrars[index] = a.Registrar k.upkeepIDs[index] = upkeepIds k.keeperConsumerContracts[index] = consumer } @@ -815,20 +736,20 @@ func (k *KeeperBenchmarkTest) DeployKeeperConsumersBenchmark() contracts.Automat // Deploy consumer var err error var keeperConsumerInstance contracts.AutomationConsumerBenchmark - if *k.testConfig.GetKeeperConfig().Resiliency.ContractCallLimit != 0 && k.testConfig.GetKeeperConfig().Resiliency.ContractCallInterval.Duration != 0 { - maxRetryAttempts := *k.testConfig.GetKeeperConfig().Resiliency.ContractCallLimit - callRetryDelay := k.testConfig.GetKeeperConfig().Resiliency.ContractCallInterval.Duration - keeperConsumerInstance, err = contracts.DeployKeeperConsumerBenchmarkWithRetry(k.chainClient, k.log, maxRetryAttempts, callRetryDelay) + if *k.testConfig.GetAutomationConfig().Resiliency.ContractCallLimit != 0 && k.testConfig.GetAutomationConfig().Resiliency.ContractCallInterval.Duration != 0 { + maxRetryAttempts := *k.testConfig.GetAutomationConfig().Resiliency.ContractCallLimit + callRetryDelay := k.testConfig.GetAutomationConfig().Resiliency.ContractCallInterval.Duration + keeperConsumerInstance, err = contracts.DeployAutomationConsumerBenchmarkWithRetry(k.chainClient, k.log, maxRetryAttempts, callRetryDelay) if err != nil { k.log.Error().Err(err).Msg("Deploying AutomationConsumerBenchmark instance shouldn't fail") - keeperConsumerInstance, err = contracts.DeployKeeperConsumerBenchmarkWithRetry(k.chainClient, k.log, maxRetryAttempts, callRetryDelay) + keeperConsumerInstance, err = contracts.DeployAutomationConsumerBenchmarkWithRetry(k.chainClient, k.log, maxRetryAttempts, callRetryDelay) require.NoError(k.t, err, "Error deploying AutomationConsumerBenchmark") } } else { - keeperConsumerInstance, err = contracts.DeployKeeperConsumerBenchmark(k.chainClient) + keeperConsumerInstance, err = contracts.DeployAutomationConsumerBenchmark(k.chainClient) if err != nil { k.log.Error().Err(err).Msg("Deploying AutomationConsumerBenchmark instance %d shouldn't fail") - keeperConsumerInstance, err = contracts.DeployKeeperConsumerBenchmark(k.chainClient) + keeperConsumerInstance, err = contracts.DeployAutomationConsumerBenchmark(k.chainClient) require.NoError(k.t, err, "Error deploying AutomationConsumerBenchmark") } } diff --git a/integration-tests/testsetups/ocr.go b/integration-tests/testsetups/ocr.go index 34b57558cb..73b142b629 100644 --- a/integration-tests/testsetups/ocr.go +++ b/integration-tests/testsetups/ocr.go @@ -15,8 +15,6 @@ import ( "testing" "time" - "github.com/smartcontractkit/chainlink-testing-framework/havoc" - "github.com/smartcontractkit/chainlink-testing-framework/lib/grafana" seth_utils "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/seth" @@ -32,6 +30,8 @@ import ( "github.com/smartcontractkit/libocr/gethwrappers/offchainaggregator" "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" + "github.com/smartcontractkit/chainlink-testing-framework/havoc" + "github.com/smartcontractkit/chainlink-testing-framework/lib/blockchain" ctf_client "github.com/smartcontractkit/chainlink-testing-framework/lib/client" ctf_config "github.com/smartcontractkit/chainlink-testing-framework/lib/config" @@ -279,9 +279,10 @@ func (o *OCRSoakTest) Setup(ocrTestConfig tt.OcrTestConfig) { require.NoError(o.t, err, "Connecting to chainlink nodes shouldn't fail") o.bootstrapNode, o.workerNodes = nodes[0], nodes[1:] o.mockServer = ctf_client.ConnectMockServer(o.testEnvironment) + require.NoError(o.t, err, "Creating mockserver clients shouldn't fail") - linkContract, err := contracts.DeployLinkTokenContract(o.log, sethClient) - require.NoError(o.t, err, "Error deploying LINK contract") + linkContract, err := actions.LinkTokenContract(o.log, sethClient, ocrTestConfig.GetActiveOCRConfig()) + require.NoError(o.t, err, "Error loading/deploying link token contract") // Fund Chainlink nodes, excluding the bootstrap node o.log.Info().Float64("ETH amount per node", *o.Config.Common.ChainlinkNodeFunding).Msg("Funding Chainlink nodes") @@ -289,7 +290,6 @@ func (o *OCRSoakTest) Setup(ocrTestConfig tt.OcrTestConfig) { require.NoError(o.t, err, "Error funding Chainlink nodes") var forwarders []common.Address - if o.OperatorForwarderFlow { var operators []common.Address operators, forwarders, _ = actions.DeployForwarderContracts( @@ -310,17 +310,17 @@ func (o *OCRSoakTest) Setup(ocrTestConfig tt.OcrTestConfig) { o.ocrV1Instances, err = actions.DeployOCRContractsForwarderFlow( o.log, o.seth, - *o.Config.GetActiveOCRConfig().Soak.NumberOfContracts, + o.Config.GetActiveOCRConfig(), common.HexToAddress(linkContract.Address()), contracts.ChainlinkK8sClientToChainlinkNodeWithKeysAndAddress(o.workerNodes), forwarders, ) require.NoError(o.t, err, "Error deploying OCR Forwarder contracts") } else { - o.ocrV1Instances, err = actions.DeployOCRv1Contracts( + o.ocrV1Instances, err = actions.SetupOCRv1Contracts( o.log, sethClient, - *o.Config.GetActiveOCRConfig().Soak.NumberOfContracts, + o.Config.GetActiveOCRConfig(), common.HexToAddress(linkContract.Address()), contracts.ChainlinkK8sClientToChainlinkNodeWithKeysAndAddress(o.workerNodes), ) @@ -342,19 +342,22 @@ func (o *OCRSoakTest) Setup(ocrTestConfig tt.OcrTestConfig) { } ocrOffchainOptions := contracts.DefaultOffChainAggregatorOptions() - o.ocrV2Instances, err = actions.DeployOCRv2Contracts( + o.ocrV2Instances, err = actions.SetupOCRv2Contracts( o.log, o.seth, - *ocrTestConfig.GetActiveOCRConfig().Soak.NumberOfContracts, + ocrTestConfig.GetActiveOCRConfig(), common.HexToAddress(linkContract.Address()), transmitters, ocrOffchainOptions, ) require.NoError(o.t, err, "Error deploying OCRv2 contracts") - contractConfig, err := actions.BuildMedianOCR2Config(o.workerNodes, ocrOffchainOptions) - require.NoError(o.t, err, "Error building median config") - err = actions.ConfigureOCRv2AggregatorContracts(contractConfig, o.ocrV2Instances) - require.NoError(o.t, err, "Error configuring OCRv2 aggregator contracts") + + if !ocrTestConfig.GetActiveOCRConfig().UseExistingOffChainAggregatorsContracts() || (ocrTestConfig.GetActiveOCRConfig().UseExistingOffChainAggregatorsContracts() && ocrTestConfig.GetActiveOCRConfig().ConfigureExistingOffChainAggregatorsContracts()) { + contractConfig, err := actions.BuildMedianOCR2Config(o.workerNodes, ocrOffchainOptions) + require.NoError(o.t, err, "Error building median config") + err = actions.ConfigureOCRv2AggregatorContracts(contractConfig, o.ocrV2Instances) + require.NoError(o.t, err, "Error configuring OCRv2 aggregator contracts") + } } if o.OCRVersion == "1" { @@ -398,7 +401,7 @@ func (o *OCRSoakTest) Run() { o.log.Info(). Str("Test Duration", o.Config.GetActiveOCRConfig().Common.TestDuration.Duration.Truncate(time.Second).String()). - Int("Number of OCR Contracts", *config.GetActiveOCRConfig().Soak.NumberOfContracts). + Int("Number of OCR Contracts", *config.GetActiveOCRConfig().Common.NumberOfContracts). Str("OCR Version", o.OCRVersion). Msg("Starting OCR Soak Test") @@ -529,7 +532,7 @@ func (o *OCRSoakTest) LoadState() error { if testState.OCRVersion == "1" { o.ocrV1Instances = make([]contracts.OffchainAggregator, len(testState.OCRContractAddresses)) for i, addr := range testState.OCRContractAddresses { - instance, err := contracts.LoadOffchainAggregator(o.log, o.seth, common.HexToAddress(addr)) + instance, err := contracts.LoadOffChainAggregator(o.log, o.seth, common.HexToAddress(addr)) if err != nil { return fmt.Errorf("failed to instantiate OCR instance: %w", err) } @@ -538,7 +541,7 @@ func (o *OCRSoakTest) LoadState() error { } else if testState.OCRVersion == "2" { o.ocrV2Instances = make([]contracts.OffchainAggregatorV2, len(testState.OCRContractAddresses)) for i, addr := range testState.OCRContractAddresses { - instance, err := contracts.LoadOffChainAggregatorV2(o.log, o.seth, common.HexToAddress(addr)) + instance, err := contracts.LoadOffchainAggregatorV2(o.log, o.seth, common.HexToAddress(addr)) if err != nil { return err } @@ -547,8 +550,7 @@ func (o *OCRSoakTest) LoadState() error { } o.mockServer = ctf_client.ConnectMockServerURL(testState.MockServerURL) - - return nil + return err } func (o *OCRSoakTest) Resume() { @@ -561,7 +563,7 @@ func (o *OCRSoakTest) Resume() { Str("Time Left", o.timeLeft.String()). Msg("Resuming OCR Soak Test") - ocrAddresses := make([]common.Address, *o.Config.GetActiveOCRConfig().Soak.NumberOfContracts) + ocrAddresses := make([]common.Address, *o.Config.GetActiveOCRConfig().Common.NumberOfContracts) if o.OCRVersion == "1" { for i, ocrInstance := range o.ocrV1Instances { @@ -1021,12 +1023,12 @@ func (o *OCRSoakTest) collectEvents() error { // ensureValues ensures that all values needed to run the test are present func (o *OCRSoakTest) ensureInputValues() error { - ocrConfig := o.Config.GetActiveOCRConfig().Soak + ocrConfig := o.Config.GetActiveOCRConfig() if o.OCRVersion != "1" && o.OCRVersion != "2" { return fmt.Errorf("OCR version must be 1 or 2, found %s", o.OCRVersion) } - if ocrConfig.NumberOfContracts != nil && *ocrConfig.NumberOfContracts <= 0 { - return fmt.Errorf("number of OCR contracts must be set and greater than 0, found %d", ocrConfig.NumberOfContracts) + if ocrConfig.Common.NumberOfContracts != nil && *ocrConfig.Common.NumberOfContracts <= 0 { + return fmt.Errorf("number of OCR contracts must be set and greater than 0, found %d", ocrConfig.Common.NumberOfContracts) } if o.Config.Common.ChainlinkNodeFunding != nil && *o.Config.Common.ChainlinkNodeFunding <= 0 { return fmt.Errorf("chainlink node funding must be greater than 0, found %f", *o.Config.Common.ChainlinkNodeFunding) @@ -1034,11 +1036,12 @@ func (o *OCRSoakTest) ensureInputValues() error { if o.Config.GetActiveOCRConfig().Common.TestDuration != nil && o.Config.GetActiveOCRConfig().Common.TestDuration.Duration <= time.Minute { return fmt.Errorf("test duration must be greater than 1 minute, found %s", o.Config.GetActiveOCRConfig().Common.TestDuration) } - if ocrConfig.TimeBetweenRounds != nil && ocrConfig.TimeBetweenRounds.Duration >= time.Hour { - return fmt.Errorf("time between rounds must be less than 1 hour, found %s", ocrConfig.TimeBetweenRounds) + soakConfig := ocrConfig.Soak + if soakConfig.TimeBetweenRounds != nil && soakConfig.TimeBetweenRounds.Duration >= time.Hour { + return fmt.Errorf("time between rounds must be less than 1 hour, found %s", soakConfig.TimeBetweenRounds) } - if ocrConfig.TimeBetweenRounds != nil && ocrConfig.TimeBetweenRounds.Duration < time.Second*30 { - return fmt.Errorf("time between rounds must be greater or equal to 30 seconds, found %s", ocrConfig.TimeBetweenRounds) + if soakConfig.TimeBetweenRounds != nil && soakConfig.TimeBetweenRounds.Duration < time.Second*30 { + return fmt.Errorf("time between rounds must be greater or equal to 30 seconds, found %s", soakConfig.TimeBetweenRounds) } return nil diff --git a/integration-tests/types/testconfigs.go b/integration-tests/types/testconfigs.go index ee9183589c..16f7253a2e 100644 --- a/integration-tests/types/testconfigs.go +++ b/integration-tests/types/testconfigs.go @@ -31,10 +31,10 @@ type AutomationTestConfig interface { tc.AutomationTestConfig } -type KeeperBenchmarkTestConfig interface { +type AutomationBenchmarkTestConfig interface { ctf_config.GlobalTestConfig tc.CommonTestConfig - tc.KeeperTestConfig + tc.AutomationTestConfig ctf_config.NamedConfigurations testreporters.GrafanaURLProvider } @@ -51,3 +51,9 @@ type Ocr2TestConfig interface { tc.CommonTestConfig tc.Ocr2TestConfig } + +type CCIPTestConfig interface { + ctf_config.GlobalTestConfig + tc.CommonTestConfig + tc.CCIPTestConfig +} diff --git a/integration-tests/universal/log_poller/helpers.go b/integration-tests/universal/log_poller/helpers.go index d13f40025b..ec40348947 100644 --- a/integration-tests/universal/log_poller/helpers.go +++ b/integration-tests/universal/log_poller/helpers.go @@ -13,6 +13,8 @@ import ( "testing" "time" + "github.com/smartcontractkit/chainlink/integration-tests/utils" + "github.com/jmoiron/sqlx" "github.com/smartcontractkit/chainlink-testing-framework/wasp" @@ -32,7 +34,6 @@ import ( ctf_test_env "github.com/smartcontractkit/chainlink-testing-framework/lib/docker/test_env" "github.com/smartcontractkit/chainlink-testing-framework/lib/logging" "github.com/smartcontractkit/chainlink-testing-framework/lib/networks" - seth_utils "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/seth" "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/client" "github.com/smartcontractkit/chainlink/integration-tests/contracts" @@ -1061,7 +1062,7 @@ func SetupLogPollerTestDocker( evmNetwork, err := env.GetFirstEvmNetwork() require.NoError(t, err, "Error getting first evm network") - chainClient, err := seth_utils.GetChainClient(testConfig, *evmNetwork) + chainClient, err := utils.TestAwareSethClient(t, testConfig, evmNetwork) require.NoError(t, err, "Error getting seth client") err = actions.FundChainlinkNodesFromRootAddress(l, chainClient, contracts.ChainlinkClientToChainlinkNodeWithKeysAndAddress(env.ClCluster.NodeAPIs()), big.NewFloat(chainlinkNodeFunding)) diff --git a/integration-tests/utils/seth.go b/integration-tests/utils/seth.go new file mode 100644 index 0000000000..704d954772 --- /dev/null +++ b/integration-tests/utils/seth.go @@ -0,0 +1,24 @@ +package utils + +import ( + "fmt" + "testing" + + "github.com/smartcontractkit/chainlink-testing-framework/lib/blockchain" + ctf_config "github.com/smartcontractkit/chainlink-testing-framework/lib/config" + seth_utils "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/seth" + pkg_seth "github.com/smartcontractkit/chainlink-testing-framework/seth" +) + +// DynamicArtifactDirConfigFn returns a function that sets Seth's artifacts directory to a unique directory for the test +func DynamicArtifactDirConfigFn(t *testing.T) func(*pkg_seth.Config) error { + return func(cfg *pkg_seth.Config) error { + cfg.ArtifactsDir = fmt.Sprintf("seth_artifacts/%s", t.Name()) + return nil + } +} + +// TestAwareSethClient returns a Seth client with the artifacts directory set to a unique directory for the test +func TestAwareSethClient(t *testing.T, sethConfig ctf_config.SethConfig, evmNetwork *blockchain.EVMNetwork) (*pkg_seth.Client, error) { + return seth_utils.GetChainClientWithConfigFunction(sethConfig, *evmNetwork, DynamicArtifactDirConfigFn(t)) +} diff --git a/integration-tests/web/sdk/client/client.go b/integration-tests/web/sdk/client/client.go index 454e18a234..783a8a8856 100644 --- a/integration-tests/web/sdk/client/client.go +++ b/integration-tests/web/sdk/client/client.go @@ -7,24 +7,27 @@ import ( "net/http" "strings" + "github.com/AlekSi/pointer" "github.com/Khan/genqlient/graphql" - "github.com/smartcontractkit/chainlink/integration-tests/web/sdk/client/internal/doer" + "github.com/smartcontractkit/chainlink/integration-tests/web/sdk/client/doer" "github.com/smartcontractkit/chainlink/integration-tests/web/sdk/internal/generated" ) type Client interface { - GetCSAKeys(ctx context.Context) (*generated.GetCSAKeysResponse, error) + FetchCSAPublicKey(ctx context.Context) (*string, error) + FetchP2PPeerID(ctx context.Context) (*string, error) + FetchAccountAddress(ctx context.Context, chainID string) (*string, error) + FetchOCR2KeyBundleID(ctx context.Context, chainType string) (string, error) GetJob(ctx context.Context, id string) (*generated.GetJobResponse, error) ListJobs(ctx context.Context, offset, limit int) (*generated.ListJobsResponse, error) - GetBridge(ctx context.Context, id string) (*generated.GetBridgeResponse, error) - ListBridges(ctx context.Context, offset, limit int) (*generated.ListBridgesResponse, error) - GetFeedsManager(ctx context.Context, id string) (*generated.GetFeedsManagerResponse, error) - ListFeedsManagers(ctx context.Context) (*generated.ListFeedsManagersResponse, error) - CreateFeedsManager(ctx context.Context, cmd generated.CreateFeedsManagerInput) (*generated.CreateFeedsManagerResponse, error) - UpdateFeedsManager(ctx context.Context, id string, cmd generated.UpdateFeedsManagerInput) (*generated.UpdateFeedsManagerResponse, error) + GetJobDistributor(ctx context.Context, id string) (*generated.GetFeedsManagerResponse, error) + ListJobDistributors(ctx context.Context) (*generated.ListFeedsManagersResponse, error) + CreateJobDistributor(ctx context.Context, cmd JobDistributorInput) (string, error) + UpdateJobDistributor(ctx context.Context, id string, cmd JobDistributorInput) error + CreateJobDistributorChainConfig(ctx context.Context, in JobDistributorChainConfigInput) error GetJobProposal(ctx context.Context, id string) (*generated.GetJobProposalResponse, error) - ApproveJobProposalSpec(ctx context.Context, id string, force bool) (*generated.ApproveJobProposalSpecResponse, error) + ApproveJobProposalSpec(ctx context.Context, id string, force bool) (*JobProposalApprovalSuccessSpec, error) CancelJobProposalSpec(ctx context.Context, id string) (*generated.CancelJobProposalSpecResponse, error) RejectJobProposalSpec(ctx context.Context, id string) (*generated.RejectJobProposalSpecResponse, error) UpdateJobProposalSpecDefinition(ctx context.Context, id string, cmd generated.UpdateJobProposalSpecDefinitionInput) (*generated.UpdateJobProposalSpecDefinitionResponse, error) @@ -69,8 +72,58 @@ func New(baseURI string, creds Credentials) (Client, error) { return c, nil } -func (c *client) GetCSAKeys(ctx context.Context) (*generated.GetCSAKeysResponse, error) { - return generated.GetCSAKeys(ctx, c.gqlClient) +func (c *client) FetchCSAPublicKey(ctx context.Context) (*string, error) { + keys, err := generated.FetchCSAKeys(ctx, c.gqlClient) + if err != nil { + return nil, err + } + if keys == nil || len(keys.CsaKeys.GetResults()) == 0 { + return nil, fmt.Errorf("no CSA keys found") + } + return &keys.CsaKeys.GetResults()[0].PublicKey, nil +} + +func (c *client) FetchP2PPeerID(ctx context.Context) (*string, error) { + keys, err := generated.FetchP2PKeys(ctx, c.gqlClient) + if err != nil { + return nil, err + } + if keys == nil || len(keys.P2pKeys.GetResults()) == 0 { + return nil, fmt.Errorf("no P2P keys found") + } + return &keys.P2pKeys.GetResults()[0].PeerID, nil +} + +func (c *client) FetchOCR2KeyBundleID(ctx context.Context, chainType string) (string, error) { + keyBundles, err := generated.FetchOCR2KeyBundles(ctx, c.gqlClient) + if err != nil { + return "", err + } + if keyBundles == nil || len(keyBundles.GetOcr2KeyBundles().Results) == 0 { + return "", fmt.Errorf("no ocr2 keybundle found, check if ocr2 is enabled") + } + for _, keyBundle := range keyBundles.GetOcr2KeyBundles().Results { + if keyBundle.ChainType == generated.OCR2ChainType(chainType) { + return keyBundle.GetId(), nil + } + } + return "", fmt.Errorf("no ocr2 keybundle found for chain type %s", chainType) +} + +func (c *client) FetchAccountAddress(ctx context.Context, chainID string) (*string, error) { + keys, err := generated.FetchAccounts(ctx, c.gqlClient) + if err != nil { + return nil, err + } + if keys == nil || len(keys.EthKeys.GetResults()) == 0 { + return nil, fmt.Errorf("no accounts found") + } + for _, keyDetail := range keys.EthKeys.GetResults() { + if keyDetail.GetChain().Enabled && keyDetail.GetChain().Id == chainID { + return pointer.ToString(keyDetail.Address), nil + } + } + return nil, fmt.Errorf("no account found for chain %s", chainID) } func (c *client) GetJob(ctx context.Context, id string) (*generated.GetJobResponse, error) { @@ -89,28 +142,72 @@ func (c *client) ListBridges(ctx context.Context, offset, limit int) (*generated return generated.ListBridges(ctx, c.gqlClient, offset, limit) } -func (c *client) GetFeedsManager(ctx context.Context, id string) (*generated.GetFeedsManagerResponse, error) { +func (c *client) GetJobDistributor(ctx context.Context, id string) (*generated.GetFeedsManagerResponse, error) { return generated.GetFeedsManager(ctx, c.gqlClient, id) } -func (c *client) ListFeedsManagers(ctx context.Context) (*generated.ListFeedsManagersResponse, error) { +func (c *client) ListJobDistributors(ctx context.Context) (*generated.ListFeedsManagersResponse, error) { return generated.ListFeedsManagers(ctx, c.gqlClient) } -func (c *client) CreateFeedsManager(ctx context.Context, cmd generated.CreateFeedsManagerInput) (*generated.CreateFeedsManagerResponse, error) { - return generated.CreateFeedsManager(ctx, c.gqlClient, cmd) +func (c *client) CreateJobDistributor(ctx context.Context, in JobDistributorInput) (string, error) { + var cmd generated.CreateFeedsManagerInput + err := DecodeInput(in, &cmd) + if err != nil { + return "", err + } + response, err := generated.CreateFeedsManager(ctx, c.gqlClient, cmd) + if err != nil { + return "", err + } + // Access the FeedsManager ID + if success, ok := response.GetCreateFeedsManager().(*generated.CreateFeedsManagerCreateFeedsManagerCreateFeedsManagerSuccess); ok { + feedsManager := success.GetFeedsManager() + return feedsManager.GetId(), nil + } + return "", fmt.Errorf("failed to create feeds manager") +} + +func (c *client) UpdateJobDistributor(ctx context.Context, id string, in JobDistributorInput) error { + var cmd generated.UpdateFeedsManagerInput + err := DecodeInput(in, &cmd) + if err != nil { + return err + } + _, err = generated.UpdateFeedsManager(ctx, c.gqlClient, id, cmd) + return err } -func (c *client) UpdateFeedsManager(ctx context.Context, id string, cmd generated.UpdateFeedsManagerInput) (*generated.UpdateFeedsManagerResponse, error) { - return generated.UpdateFeedsManager(ctx, c.gqlClient, id, cmd) +func (c *client) CreateJobDistributorChainConfig(ctx context.Context, in JobDistributorChainConfigInput) error { + var cmd generated.CreateFeedsManagerChainConfigInput + err := DecodeInput(in, &cmd) + if err != nil { + return err + } + _, err = generated.CreateFeedsManagerChainConfig(ctx, c.gqlClient, cmd) + return err } func (c *client) GetJobProposal(ctx context.Context, id string) (*generated.GetJobProposalResponse, error) { return generated.GetJobProposal(ctx, c.gqlClient, id) } -func (c *client) ApproveJobProposalSpec(ctx context.Context, id string, force bool) (*generated.ApproveJobProposalSpecResponse, error) { - return generated.ApproveJobProposalSpec(ctx, c.gqlClient, id, force) +func (c *client) ApproveJobProposalSpec(ctx context.Context, id string, force bool) (*JobProposalApprovalSuccessSpec, error) { + res, err := generated.ApproveJobProposalSpec(ctx, c.gqlClient, id, force) + if err != nil { + return nil, err + } + if success, ok := res.GetApproveJobProposalSpec().(*generated.ApproveJobProposalSpecApproveJobProposalSpecApproveJobProposalSpecSuccess); ok { + var cmd JobProposalApprovalSuccessSpec + if success.Spec.Status == generated.SpecStatusApproved { + err := DecodeInput(success.Spec, &cmd) + if err != nil { + return nil, fmt.Errorf("failed to decode job proposal spec: %w ; and job proposal spec not approved", err) + } + return &cmd, nil + } + } + return nil, fmt.Errorf("failed to approve job proposal spec") } func (c *client) CancelJobProposalSpec(ctx context.Context, id string) (*generated.CancelJobProposalSpecResponse, error) { diff --git a/integration-tests/web/sdk/client/doer/doer.go b/integration-tests/web/sdk/client/doer/doer.go new file mode 100644 index 0000000000..dde842bae5 --- /dev/null +++ b/integration-tests/web/sdk/client/doer/doer.go @@ -0,0 +1,20 @@ +package doer + +import "net/http" + +type Authed struct { + cookie string + wrapped *http.Client +} + +func NewAuthed(cookie string) *Authed { + return &Authed{ + cookie: cookie, + wrapped: http.DefaultClient, + } +} + +func (a *Authed) Do(req *http.Request) (*http.Response, error) { + req.Header.Set("cookie", a.cookie) + return a.wrapped.Do(req) +} diff --git a/integration-tests/web/sdk/client/types.go b/integration-tests/web/sdk/client/types.go new file mode 100644 index 0000000000..d213ee161c --- /dev/null +++ b/integration-tests/web/sdk/client/types.go @@ -0,0 +1,60 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" +) + +type JobDistributorInput struct { + Name string `json:"name"` + Uri string `json:"uri"` + PublicKey string `json:"publicKey"` +} + +type JobDistributorChainConfigInput struct { + JobDistributorID string `json:"feedsManagerID"` + ChainID string `json:"chainID"` + ChainType string `json:"chainType"` + AccountAddr string `json:"accountAddr"` + AccountAddrPubKey string `json:"accountAddrPubKey"` + AdminAddr string `json:"adminAddr"` + FluxMonitorEnabled bool `json:"fluxMonitorEnabled"` + Ocr1Enabled bool `json:"ocr1Enabled"` + Ocr1IsBootstrap bool `json:"ocr1IsBootstrap"` + Ocr1Multiaddr string `json:"ocr1Multiaddr"` + Ocr1P2PPeerID string `json:"ocr1P2PPeerID"` + Ocr1KeyBundleID string `json:"ocr1KeyBundleID"` + Ocr2Enabled bool `json:"ocr2Enabled"` + Ocr2IsBootstrap bool `json:"ocr2IsBootstrap"` + Ocr2Multiaddr string `json:"ocr2Multiaddr"` + Ocr2ForwarderAddress string `json:"ocr2ForwarderAddress"` + Ocr2P2PPeerID string `json:"ocr2P2PPeerID"` + Ocr2KeyBundleID string `json:"ocr2KeyBundleID"` + Ocr2Plugins string `json:"ocr2Plugins"` +} + +type JobProposalApprovalSuccessSpec struct { + Id string `json:"id"` + Definition string `json:"definition"` + Version int `json:"version"` + Status string `json:"status"` + StatusUpdatedAt string `json:"statusUpdatedAt"` + CreatedAt string `json:"createdAt"` + UpdatedAt string `json:"updatedAt"` +} + +func DecodeInput(in, out any) error { + if reflect.TypeOf(out).Kind() != reflect.Ptr || reflect.ValueOf(out).IsNil() { + return fmt.Errorf("out type must be a non-nil pointer") + } + jsonBytes, err := json.Marshal(in) + if err != nil { + return err + } + + decoder := json.NewDecoder(bytes.NewReader(jsonBytes)) + decoder.DisallowUnknownFields() + return decoder.Decode(out) +} diff --git a/integration-tests/web/sdk/client/types_test.go b/integration-tests/web/sdk/client/types_test.go new file mode 100644 index 0000000000..dd9b48274d --- /dev/null +++ b/integration-tests/web/sdk/client/types_test.go @@ -0,0 +1,67 @@ +package client + +import ( + "testing" + + "github.com/smartcontractkit/chainlink/integration-tests/web/sdk/internal/generated" +) + +func TestDecodeInput(t *testing.T) { + type args struct { + in any + out any + } + tests := []struct { + name string + args args + wantErr bool + errMessage string + }{ + { + name: "success", + args: args{&JobDistributorInput{ + Name: "name", + Uri: "uri", + PublicKey: "publicKey", + }, &generated.CreateFeedsManagerInput{}}, + wantErr: false, + errMessage: "", + }, + { + name: "non-pointer", + args: args{&JobDistributorInput{ + Name: "name", + Uri: "uri", + PublicKey: "publicKey", + }, generated.CreateFeedsManagerInput{}}, + wantErr: true, + errMessage: "out type must be a non-nil pointer", + }, + { + name: "incorrect type", + args: args{&JobDistributorInput{ + Name: "name", + Uri: "uri", + PublicKey: "publicKey", + }, generated.CreateFeedsManagerChainConfigInput{}}, + wantErr: true, + errMessage: "json: cannot unmarshal object into Go value of type generated.CreateFeedsManagerChainConfigInput", + }, + { + name: "success", + args: args{&JobDistributorInput{ + Name: "name", + Uri: "uri", + PublicKey: "publicKey", + }, &generated.UpdateFeedsManagerInput{}}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := DecodeInput(tt.args.in, tt.args.out); (err != nil) != tt.wantErr { + t.Errorf("DecodeInput() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/integration-tests/web/sdk/internal/generated/generated.go b/integration-tests/web/sdk/internal/generated/generated.go index 6fada0aaa0..8efde4c453 100644 --- a/integration-tests/web/sdk/internal/generated/generated.go +++ b/integration-tests/web/sdk/internal/generated/generated.go @@ -8,7 +8,6 @@ import ( "fmt" "github.com/Khan/genqlient/graphql" - "github.com/smartcontractkit/chainlink/v2/core/web/gqlscalar" ) @@ -538,10 +537,502 @@ func (v *CancelJobProposalSpecResponse) __premarshalJSON() (*__premarshalCancelJ return &retval, nil } +// CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigPayload includes the requested fields of the GraphQL interface CreateFeedsManagerChainConfigPayload. +// +// CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigPayload is implemented by the following types: +// CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccess +// CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigInputErrors +// CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigNotFoundError +type CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigPayload interface { + implementsGraphQLInterfaceCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigPayload() + // GetTypename returns the receiver's concrete GraphQL type-name (see interface doc for possible values). + GetTypename() string +} + +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccess) implementsGraphQLInterfaceCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigPayload() { +} +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigInputErrors) implementsGraphQLInterfaceCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigPayload() { +} +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigNotFoundError) implementsGraphQLInterfaceCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigPayload() { +} + +func __unmarshalCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigPayload(b []byte, v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigPayload) error { + if string(b) == "null" { + return nil + } + + var tn struct { + TypeName string `json:"__typename"` + } + err := json.Unmarshal(b, &tn) + if err != nil { + return err + } + + switch tn.TypeName { + case "CreateFeedsManagerChainConfigSuccess": + *v = new(CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccess) + return json.Unmarshal(b, *v) + case "InputErrors": + *v = new(CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigInputErrors) + return json.Unmarshal(b, *v) + case "NotFoundError": + *v = new(CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigNotFoundError) + return json.Unmarshal(b, *v) + case "": + return fmt.Errorf( + "response was missing CreateFeedsManagerChainConfigPayload.__typename") + default: + return fmt.Errorf( + `unexpected concrete type for CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigPayload: "%v"`, tn.TypeName) + } +} + +func __marshalCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigPayload(v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigPayload) ([]byte, error) { + + var typename string + switch v := (*v).(type) { + case *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccess: + typename = "CreateFeedsManagerChainConfigSuccess" + + result := struct { + TypeName string `json:"__typename"` + *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccess + }{typename, v} + return json.Marshal(result) + case *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigInputErrors: + typename = "InputErrors" + + result := struct { + TypeName string `json:"__typename"` + *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigInputErrors + }{typename, v} + return json.Marshal(result) + case *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigNotFoundError: + typename = "NotFoundError" + + result := struct { + TypeName string `json:"__typename"` + *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigNotFoundError + }{typename, v} + return json.Marshal(result) + case nil: + return []byte("null"), nil + default: + return nil, fmt.Errorf( + `unexpected concrete type for CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigPayload: "%T"`, v) + } +} + +// CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccess includes the requested fields of the GraphQL type CreateFeedsManagerChainConfigSuccess. +type CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccess struct { + Typename string `json:"__typename"` + ChainConfig CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfig `json:"chainConfig"` +} + +// GetTypename returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccess.Typename, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccess) GetTypename() string { + return v.Typename +} + +// GetChainConfig returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccess.ChainConfig, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccess) GetChainConfig() CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfig { + return v.ChainConfig +} + +// CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfig includes the requested fields of the GraphQL type FeedsManagerChainConfig. +type CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfig struct { + Id string `json:"id"` + ChainID string `json:"chainID"` + ChainType string `json:"chainType"` + AccountAddr string `json:"accountAddr"` + AdminAddr string `json:"adminAddr"` + FluxMonitorJobConfig CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigFluxMonitorJobConfig `json:"fluxMonitorJobConfig"` + Ocr1JobConfig CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr1JobConfigOCR1JobConfig `json:"ocr1JobConfig"` + Ocr2JobConfig CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfig `json:"ocr2JobConfig"` +} + +// GetId returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfig.Id, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfig) GetId() string { + return v.Id +} + +// GetChainID returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfig.ChainID, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfig) GetChainID() string { + return v.ChainID +} + +// GetChainType returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfig.ChainType, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfig) GetChainType() string { + return v.ChainType +} + +// GetAccountAddr returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfig.AccountAddr, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfig) GetAccountAddr() string { + return v.AccountAddr +} + +// GetAdminAddr returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfig.AdminAddr, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfig) GetAdminAddr() string { + return v.AdminAddr +} + +// GetFluxMonitorJobConfig returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfig.FluxMonitorJobConfig, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfig) GetFluxMonitorJobConfig() CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigFluxMonitorJobConfig { + return v.FluxMonitorJobConfig +} + +// GetOcr1JobConfig returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfig.Ocr1JobConfig, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfig) GetOcr1JobConfig() CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr1JobConfigOCR1JobConfig { + return v.Ocr1JobConfig +} + +// GetOcr2JobConfig returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfig.Ocr2JobConfig, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfig) GetOcr2JobConfig() CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfig { + return v.Ocr2JobConfig +} + +// CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigFluxMonitorJobConfig includes the requested fields of the GraphQL type FluxMonitorJobConfig. +type CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigFluxMonitorJobConfig struct { + Enabled bool `json:"enabled"` +} + +// GetEnabled returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigFluxMonitorJobConfig.Enabled, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigFluxMonitorJobConfig) GetEnabled() bool { + return v.Enabled +} + +// CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr1JobConfigOCR1JobConfig includes the requested fields of the GraphQL type OCR1JobConfig. +type CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr1JobConfigOCR1JobConfig struct { + Enabled bool `json:"enabled"` + IsBootstrap bool `json:"isBootstrap"` + Multiaddr string `json:"multiaddr"` + P2pPeerID string `json:"p2pPeerID"` + KeyBundleID string `json:"keyBundleID"` +} + +// GetEnabled returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr1JobConfigOCR1JobConfig.Enabled, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr1JobConfigOCR1JobConfig) GetEnabled() bool { + return v.Enabled +} + +// GetIsBootstrap returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr1JobConfigOCR1JobConfig.IsBootstrap, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr1JobConfigOCR1JobConfig) GetIsBootstrap() bool { + return v.IsBootstrap +} + +// GetMultiaddr returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr1JobConfigOCR1JobConfig.Multiaddr, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr1JobConfigOCR1JobConfig) GetMultiaddr() string { + return v.Multiaddr +} + +// GetP2pPeerID returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr1JobConfigOCR1JobConfig.P2pPeerID, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr1JobConfigOCR1JobConfig) GetP2pPeerID() string { + return v.P2pPeerID +} + +// GetKeyBundleID returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr1JobConfigOCR1JobConfig.KeyBundleID, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr1JobConfigOCR1JobConfig) GetKeyBundleID() string { + return v.KeyBundleID +} + +// CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfig includes the requested fields of the GraphQL type OCR2JobConfig. +type CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfig struct { + Enabled bool `json:"enabled"` + IsBootstrap bool `json:"isBootstrap"` + Multiaddr string `json:"multiaddr"` + ForwarderAddress string `json:"forwarderAddress"` + P2pPeerID string `json:"p2pPeerID"` + KeyBundleID string `json:"keyBundleID"` + Plugins CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfigPlugins `json:"plugins"` +} + +// GetEnabled returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfig.Enabled, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfig) GetEnabled() bool { + return v.Enabled +} + +// GetIsBootstrap returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfig.IsBootstrap, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfig) GetIsBootstrap() bool { + return v.IsBootstrap +} + +// GetMultiaddr returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfig.Multiaddr, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfig) GetMultiaddr() string { + return v.Multiaddr +} + +// GetForwarderAddress returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfig.ForwarderAddress, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfig) GetForwarderAddress() string { + return v.ForwarderAddress +} + +// GetP2pPeerID returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfig.P2pPeerID, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfig) GetP2pPeerID() string { + return v.P2pPeerID +} + +// GetKeyBundleID returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfig.KeyBundleID, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfig) GetKeyBundleID() string { + return v.KeyBundleID +} + +// GetPlugins returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfig.Plugins, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfig) GetPlugins() CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfigPlugins { + return v.Plugins +} + +// CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfigPlugins includes the requested fields of the GraphQL type Plugins. +type CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfigPlugins struct { + Commit bool `json:"commit"` + Execute bool `json:"execute"` + Median bool `json:"median"` + Mercury bool `json:"mercury"` + Rebalancer bool `json:"rebalancer"` +} + +// GetCommit returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfigPlugins.Commit, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfigPlugins) GetCommit() bool { + return v.Commit +} + +// GetExecute returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfigPlugins.Execute, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfigPlugins) GetExecute() bool { + return v.Execute +} + +// GetMedian returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfigPlugins.Median, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfigPlugins) GetMedian() bool { + return v.Median +} + +// GetMercury returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfigPlugins.Mercury, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfigPlugins) GetMercury() bool { + return v.Mercury +} + +// GetRebalancer returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfigPlugins.Rebalancer, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfigOcr2JobConfigOCR2JobConfigPlugins) GetRebalancer() bool { + return v.Rebalancer +} + +// CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigInputErrors includes the requested fields of the GraphQL type InputErrors. +type CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigInputErrors struct { + Typename string `json:"__typename"` + Errors []CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigInputErrorsErrorsInputError `json:"errors"` +} + +// GetTypename returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigInputErrors.Typename, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigInputErrors) GetTypename() string { + return v.Typename +} + +// GetErrors returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigInputErrors.Errors, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigInputErrors) GetErrors() []CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigInputErrorsErrorsInputError { + return v.Errors +} + +// CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigInputErrorsErrorsInputError includes the requested fields of the GraphQL type InputError. +type CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigInputErrorsErrorsInputError struct { + Message string `json:"message"` + Path string `json:"path"` +} + +// GetMessage returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigInputErrorsErrorsInputError.Message, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigInputErrorsErrorsInputError) GetMessage() string { + return v.Message +} + +// GetPath returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigInputErrorsErrorsInputError.Path, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigInputErrorsErrorsInputError) GetPath() string { + return v.Path +} + +// CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigNotFoundError includes the requested fields of the GraphQL type NotFoundError. +type CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigNotFoundError struct { + Typename string `json:"__typename"` + Message string `json:"message"` + Code ErrorCode `json:"code"` +} + +// GetTypename returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigNotFoundError.Typename, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigNotFoundError) GetTypename() string { + return v.Typename +} + +// GetMessage returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigNotFoundError.Message, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigNotFoundError) GetMessage() string { + return v.Message +} + +// GetCode returns CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigNotFoundError.Code, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigNotFoundError) GetCode() ErrorCode { + return v.Code +} + +type CreateFeedsManagerChainConfigInput struct { + FeedsManagerID string `json:"feedsManagerID"` + ChainID string `json:"chainID"` + ChainType string `json:"chainType"` + AccountAddr string `json:"accountAddr"` + AccountAddrPubKey string `json:"accountAddrPubKey"` + AdminAddr string `json:"adminAddr"` + FluxMonitorEnabled bool `json:"fluxMonitorEnabled"` + Ocr1Enabled bool `json:"ocr1Enabled"` + Ocr1IsBootstrap bool `json:"ocr1IsBootstrap"` + Ocr1Multiaddr string `json:"ocr1Multiaddr"` + Ocr1P2PPeerID string `json:"ocr1P2PPeerID"` + Ocr1KeyBundleID string `json:"ocr1KeyBundleID"` + Ocr2Enabled bool `json:"ocr2Enabled"` + Ocr2IsBootstrap bool `json:"ocr2IsBootstrap"` + Ocr2Multiaddr string `json:"ocr2Multiaddr"` + Ocr2ForwarderAddress string `json:"ocr2ForwarderAddress"` + Ocr2P2PPeerID string `json:"ocr2P2PPeerID"` + Ocr2KeyBundleID string `json:"ocr2KeyBundleID"` + Ocr2Plugins string `json:"ocr2Plugins"` +} + +// GetFeedsManagerID returns CreateFeedsManagerChainConfigInput.FeedsManagerID, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigInput) GetFeedsManagerID() string { return v.FeedsManagerID } + +// GetChainID returns CreateFeedsManagerChainConfigInput.ChainID, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigInput) GetChainID() string { return v.ChainID } + +// GetChainType returns CreateFeedsManagerChainConfigInput.ChainType, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigInput) GetChainType() string { return v.ChainType } + +// GetAccountAddr returns CreateFeedsManagerChainConfigInput.AccountAddr, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigInput) GetAccountAddr() string { return v.AccountAddr } + +// GetAccountAddrPubKey returns CreateFeedsManagerChainConfigInput.AccountAddrPubKey, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigInput) GetAccountAddrPubKey() string { + return v.AccountAddrPubKey +} + +// GetAdminAddr returns CreateFeedsManagerChainConfigInput.AdminAddr, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigInput) GetAdminAddr() string { return v.AdminAddr } + +// GetFluxMonitorEnabled returns CreateFeedsManagerChainConfigInput.FluxMonitorEnabled, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigInput) GetFluxMonitorEnabled() bool { + return v.FluxMonitorEnabled +} + +// GetOcr1Enabled returns CreateFeedsManagerChainConfigInput.Ocr1Enabled, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigInput) GetOcr1Enabled() bool { return v.Ocr1Enabled } + +// GetOcr1IsBootstrap returns CreateFeedsManagerChainConfigInput.Ocr1IsBootstrap, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigInput) GetOcr1IsBootstrap() bool { return v.Ocr1IsBootstrap } + +// GetOcr1Multiaddr returns CreateFeedsManagerChainConfigInput.Ocr1Multiaddr, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigInput) GetOcr1Multiaddr() string { return v.Ocr1Multiaddr } + +// GetOcr1P2PPeerID returns CreateFeedsManagerChainConfigInput.Ocr1P2PPeerID, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigInput) GetOcr1P2PPeerID() string { return v.Ocr1P2PPeerID } + +// GetOcr1KeyBundleID returns CreateFeedsManagerChainConfigInput.Ocr1KeyBundleID, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigInput) GetOcr1KeyBundleID() string { return v.Ocr1KeyBundleID } + +// GetOcr2Enabled returns CreateFeedsManagerChainConfigInput.Ocr2Enabled, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigInput) GetOcr2Enabled() bool { return v.Ocr2Enabled } + +// GetOcr2IsBootstrap returns CreateFeedsManagerChainConfigInput.Ocr2IsBootstrap, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigInput) GetOcr2IsBootstrap() bool { return v.Ocr2IsBootstrap } + +// GetOcr2Multiaddr returns CreateFeedsManagerChainConfigInput.Ocr2Multiaddr, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigInput) GetOcr2Multiaddr() string { return v.Ocr2Multiaddr } + +// GetOcr2ForwarderAddress returns CreateFeedsManagerChainConfigInput.Ocr2ForwarderAddress, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigInput) GetOcr2ForwarderAddress() string { + return v.Ocr2ForwarderAddress +} + +// GetOcr2P2PPeerID returns CreateFeedsManagerChainConfigInput.Ocr2P2PPeerID, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigInput) GetOcr2P2PPeerID() string { return v.Ocr2P2PPeerID } + +// GetOcr2KeyBundleID returns CreateFeedsManagerChainConfigInput.Ocr2KeyBundleID, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigInput) GetOcr2KeyBundleID() string { return v.Ocr2KeyBundleID } + +// GetOcr2Plugins returns CreateFeedsManagerChainConfigInput.Ocr2Plugins, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigInput) GetOcr2Plugins() string { return v.Ocr2Plugins } + +// CreateFeedsManagerChainConfigResponse is returned by CreateFeedsManagerChainConfig on success. +type CreateFeedsManagerChainConfigResponse struct { + CreateFeedsManagerChainConfig CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigPayload `json:"-"` +} + +// GetCreateFeedsManagerChainConfig returns CreateFeedsManagerChainConfigResponse.CreateFeedsManagerChainConfig, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerChainConfigResponse) GetCreateFeedsManagerChainConfig() CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigPayload { + return v.CreateFeedsManagerChainConfig +} + +func (v *CreateFeedsManagerChainConfigResponse) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *CreateFeedsManagerChainConfigResponse + CreateFeedsManagerChainConfig json.RawMessage `json:"createFeedsManagerChainConfig"` + graphql.NoUnmarshalJSON + } + firstPass.CreateFeedsManagerChainConfigResponse = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + { + dst := &v.CreateFeedsManagerChainConfig + src := firstPass.CreateFeedsManagerChainConfig + if len(src) != 0 && string(src) != "null" { + err = __unmarshalCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigPayload( + src, dst) + if err != nil { + return fmt.Errorf( + "unable to unmarshal CreateFeedsManagerChainConfigResponse.CreateFeedsManagerChainConfig: %w", err) + } + } + } + return nil +} + +type __premarshalCreateFeedsManagerChainConfigResponse struct { + CreateFeedsManagerChainConfig json.RawMessage `json:"createFeedsManagerChainConfig"` +} + +func (v *CreateFeedsManagerChainConfigResponse) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *CreateFeedsManagerChainConfigResponse) __premarshalJSON() (*__premarshalCreateFeedsManagerChainConfigResponse, error) { + var retval __premarshalCreateFeedsManagerChainConfigResponse + + { + + dst := &retval.CreateFeedsManagerChainConfig + src := v.CreateFeedsManagerChainConfig + var err error + *dst, err = __marshalCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigPayload( + &src) + if err != nil { + return nil, fmt.Errorf( + "unable to marshal CreateFeedsManagerChainConfigResponse.CreateFeedsManagerChainConfig: %w", err) + } + } + return &retval, nil +} + // CreateFeedsManagerCreateFeedsManagerCreateFeedsManagerPayload includes the requested fields of the GraphQL interface CreateFeedsManagerPayload. // // CreateFeedsManagerCreateFeedsManagerCreateFeedsManagerPayload is implemented by the following types: // CreateFeedsManagerCreateFeedsManagerCreateFeedsManagerSuccess +// CreateFeedsManagerCreateFeedsManagerDuplicateFeedsManagerError // CreateFeedsManagerCreateFeedsManagerInputErrors // CreateFeedsManagerCreateFeedsManagerNotFoundError // CreateFeedsManagerCreateFeedsManagerSingleFeedsManagerError @@ -553,6 +1044,8 @@ type CreateFeedsManagerCreateFeedsManagerCreateFeedsManagerPayload interface { func (v *CreateFeedsManagerCreateFeedsManagerCreateFeedsManagerSuccess) implementsGraphQLInterfaceCreateFeedsManagerCreateFeedsManagerCreateFeedsManagerPayload() { } +func (v *CreateFeedsManagerCreateFeedsManagerDuplicateFeedsManagerError) implementsGraphQLInterfaceCreateFeedsManagerCreateFeedsManagerCreateFeedsManagerPayload() { +} func (v *CreateFeedsManagerCreateFeedsManagerInputErrors) implementsGraphQLInterfaceCreateFeedsManagerCreateFeedsManagerCreateFeedsManagerPayload() { } func (v *CreateFeedsManagerCreateFeedsManagerNotFoundError) implementsGraphQLInterfaceCreateFeedsManagerCreateFeedsManagerCreateFeedsManagerPayload() { @@ -577,6 +1070,9 @@ func __unmarshalCreateFeedsManagerCreateFeedsManagerCreateFeedsManagerPayload(b case "CreateFeedsManagerSuccess": *v = new(CreateFeedsManagerCreateFeedsManagerCreateFeedsManagerSuccess) return json.Unmarshal(b, *v) + case "DuplicateFeedsManagerError": + *v = new(CreateFeedsManagerCreateFeedsManagerDuplicateFeedsManagerError) + return json.Unmarshal(b, *v) case "InputErrors": *v = new(CreateFeedsManagerCreateFeedsManagerInputErrors) return json.Unmarshal(b, *v) @@ -607,6 +1103,14 @@ func __marshalCreateFeedsManagerCreateFeedsManagerCreateFeedsManagerPayload(v *C *CreateFeedsManagerCreateFeedsManagerCreateFeedsManagerSuccess }{typename, v} return json.Marshal(result) + case *CreateFeedsManagerCreateFeedsManagerDuplicateFeedsManagerError: + typename = "DuplicateFeedsManagerError" + + result := struct { + TypeName string `json:"__typename"` + *CreateFeedsManagerCreateFeedsManagerDuplicateFeedsManagerError + }{typename, v} + return json.Marshal(result) case *CreateFeedsManagerCreateFeedsManagerInputErrors: typename = "InputErrors" @@ -749,6 +1253,16 @@ func (v *CreateFeedsManagerCreateFeedsManagerCreateFeedsManagerSuccessFeedsManag return &retval, nil } +// CreateFeedsManagerCreateFeedsManagerDuplicateFeedsManagerError includes the requested fields of the GraphQL type DuplicateFeedsManagerError. +type CreateFeedsManagerCreateFeedsManagerDuplicateFeedsManagerError struct { + Typename string `json:"__typename"` +} + +// GetTypename returns CreateFeedsManagerCreateFeedsManagerDuplicateFeedsManagerError.Typename, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerCreateFeedsManagerDuplicateFeedsManagerError) GetTypename() string { + return v.Typename +} + // CreateFeedsManagerCreateFeedsManagerInputErrors includes the requested fields of the GraphQL type InputErrors. type CreateFeedsManagerCreateFeedsManagerInputErrors struct { Typename string `json:"__typename"` @@ -947,6 +1461,186 @@ func (v *FeedsManagerParts) GetIsConnectionActive() bool { return v.IsConnection // GetCreatedAt returns FeedsManagerParts.CreatedAt, and is useful for accessing the field via an interface. func (v *FeedsManagerParts) GetCreatedAt() string { return v.CreatedAt } +// FetchAccountsEthKeysEthKeysPayload includes the requested fields of the GraphQL type EthKeysPayload. +type FetchAccountsEthKeysEthKeysPayload struct { + Results []FetchAccountsEthKeysEthKeysPayloadResultsEthKey `json:"results"` +} + +// GetResults returns FetchAccountsEthKeysEthKeysPayload.Results, and is useful for accessing the field via an interface. +func (v *FetchAccountsEthKeysEthKeysPayload) GetResults() []FetchAccountsEthKeysEthKeysPayloadResultsEthKey { + return v.Results +} + +// FetchAccountsEthKeysEthKeysPayloadResultsEthKey includes the requested fields of the GraphQL type EthKey. +type FetchAccountsEthKeysEthKeysPayloadResultsEthKey struct { + Address string `json:"address"` + IsDisabled bool `json:"isDisabled"` + Chain FetchAccountsEthKeysEthKeysPayloadResultsEthKeyChain `json:"chain"` + EthBalance string `json:"ethBalance"` + LinkBalance string `json:"linkBalance"` +} + +// GetAddress returns FetchAccountsEthKeysEthKeysPayloadResultsEthKey.Address, and is useful for accessing the field via an interface. +func (v *FetchAccountsEthKeysEthKeysPayloadResultsEthKey) GetAddress() string { return v.Address } + +// GetIsDisabled returns FetchAccountsEthKeysEthKeysPayloadResultsEthKey.IsDisabled, and is useful for accessing the field via an interface. +func (v *FetchAccountsEthKeysEthKeysPayloadResultsEthKey) GetIsDisabled() bool { return v.IsDisabled } + +// GetChain returns FetchAccountsEthKeysEthKeysPayloadResultsEthKey.Chain, and is useful for accessing the field via an interface. +func (v *FetchAccountsEthKeysEthKeysPayloadResultsEthKey) GetChain() FetchAccountsEthKeysEthKeysPayloadResultsEthKeyChain { + return v.Chain +} + +// GetEthBalance returns FetchAccountsEthKeysEthKeysPayloadResultsEthKey.EthBalance, and is useful for accessing the field via an interface. +func (v *FetchAccountsEthKeysEthKeysPayloadResultsEthKey) GetEthBalance() string { return v.EthBalance } + +// GetLinkBalance returns FetchAccountsEthKeysEthKeysPayloadResultsEthKey.LinkBalance, and is useful for accessing the field via an interface. +func (v *FetchAccountsEthKeysEthKeysPayloadResultsEthKey) GetLinkBalance() string { + return v.LinkBalance +} + +// FetchAccountsEthKeysEthKeysPayloadResultsEthKeyChain includes the requested fields of the GraphQL type Chain. +type FetchAccountsEthKeysEthKeysPayloadResultsEthKeyChain struct { + Id string `json:"id"` + Enabled bool `json:"enabled"` +} + +// GetId returns FetchAccountsEthKeysEthKeysPayloadResultsEthKeyChain.Id, and is useful for accessing the field via an interface. +func (v *FetchAccountsEthKeysEthKeysPayloadResultsEthKeyChain) GetId() string { return v.Id } + +// GetEnabled returns FetchAccountsEthKeysEthKeysPayloadResultsEthKeyChain.Enabled, and is useful for accessing the field via an interface. +func (v *FetchAccountsEthKeysEthKeysPayloadResultsEthKeyChain) GetEnabled() bool { return v.Enabled } + +// FetchAccountsResponse is returned by FetchAccounts on success. +type FetchAccountsResponse struct { + EthKeys FetchAccountsEthKeysEthKeysPayload `json:"ethKeys"` +} + +// GetEthKeys returns FetchAccountsResponse.EthKeys, and is useful for accessing the field via an interface. +func (v *FetchAccountsResponse) GetEthKeys() FetchAccountsEthKeysEthKeysPayload { return v.EthKeys } + +// FetchCSAKeysCsaKeysCSAKeysPayload includes the requested fields of the GraphQL type CSAKeysPayload. +type FetchCSAKeysCsaKeysCSAKeysPayload struct { + Results []FetchCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey `json:"results"` +} + +// GetResults returns FetchCSAKeysCsaKeysCSAKeysPayload.Results, and is useful for accessing the field via an interface. +func (v *FetchCSAKeysCsaKeysCSAKeysPayload) GetResults() []FetchCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey { + return v.Results +} + +// FetchCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey includes the requested fields of the GraphQL type CSAKey. +type FetchCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey struct { + Id string `json:"id"` + PublicKey string `json:"publicKey"` + Version int `json:"version"` +} + +// GetId returns FetchCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey.Id, and is useful for accessing the field via an interface. +func (v *FetchCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey) GetId() string { return v.Id } + +// GetPublicKey returns FetchCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey.PublicKey, and is useful for accessing the field via an interface. +func (v *FetchCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey) GetPublicKey() string { return v.PublicKey } + +// GetVersion returns FetchCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey.Version, and is useful for accessing the field via an interface. +func (v *FetchCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey) GetVersion() int { return v.Version } + +// FetchCSAKeysResponse is returned by FetchCSAKeys on success. +type FetchCSAKeysResponse struct { + CsaKeys FetchCSAKeysCsaKeysCSAKeysPayload `json:"csaKeys"` +} + +// GetCsaKeys returns FetchCSAKeysResponse.CsaKeys, and is useful for accessing the field via an interface. +func (v *FetchCSAKeysResponse) GetCsaKeys() FetchCSAKeysCsaKeysCSAKeysPayload { return v.CsaKeys } + +// FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayload includes the requested fields of the GraphQL type OCR2KeyBundlesPayload. +type FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayload struct { + Results []FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayloadResultsOCR2KeyBundle `json:"results"` +} + +// GetResults returns FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayload.Results, and is useful for accessing the field via an interface. +func (v *FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayload) GetResults() []FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayloadResultsOCR2KeyBundle { + return v.Results +} + +// FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayloadResultsOCR2KeyBundle includes the requested fields of the GraphQL type OCR2KeyBundle. +type FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayloadResultsOCR2KeyBundle struct { + Id string `json:"id"` + ChainType OCR2ChainType `json:"chainType"` + ConfigPublicKey string `json:"configPublicKey"` + OnChainPublicKey string `json:"onChainPublicKey"` + OffChainPublicKey string `json:"offChainPublicKey"` +} + +// GetId returns FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayloadResultsOCR2KeyBundle.Id, and is useful for accessing the field via an interface. +func (v *FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayloadResultsOCR2KeyBundle) GetId() string { + return v.Id +} + +// GetChainType returns FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayloadResultsOCR2KeyBundle.ChainType, and is useful for accessing the field via an interface. +func (v *FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayloadResultsOCR2KeyBundle) GetChainType() OCR2ChainType { + return v.ChainType +} + +// GetConfigPublicKey returns FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayloadResultsOCR2KeyBundle.ConfigPublicKey, and is useful for accessing the field via an interface. +func (v *FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayloadResultsOCR2KeyBundle) GetConfigPublicKey() string { + return v.ConfigPublicKey +} + +// GetOnChainPublicKey returns FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayloadResultsOCR2KeyBundle.OnChainPublicKey, and is useful for accessing the field via an interface. +func (v *FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayloadResultsOCR2KeyBundle) GetOnChainPublicKey() string { + return v.OnChainPublicKey +} + +// GetOffChainPublicKey returns FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayloadResultsOCR2KeyBundle.OffChainPublicKey, and is useful for accessing the field via an interface. +func (v *FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayloadResultsOCR2KeyBundle) GetOffChainPublicKey() string { + return v.OffChainPublicKey +} + +// FetchOCR2KeyBundlesResponse is returned by FetchOCR2KeyBundles on success. +type FetchOCR2KeyBundlesResponse struct { + Ocr2KeyBundles FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayload `json:"ocr2KeyBundles"` +} + +// GetOcr2KeyBundles returns FetchOCR2KeyBundlesResponse.Ocr2KeyBundles, and is useful for accessing the field via an interface. +func (v *FetchOCR2KeyBundlesResponse) GetOcr2KeyBundles() FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayload { + return v.Ocr2KeyBundles +} + +// FetchP2PKeysP2pKeysP2PKeysPayload includes the requested fields of the GraphQL type P2PKeysPayload. +type FetchP2PKeysP2pKeysP2PKeysPayload struct { + Results []FetchP2PKeysP2pKeysP2PKeysPayloadResultsP2PKey `json:"results"` +} + +// GetResults returns FetchP2PKeysP2pKeysP2PKeysPayload.Results, and is useful for accessing the field via an interface. +func (v *FetchP2PKeysP2pKeysP2PKeysPayload) GetResults() []FetchP2PKeysP2pKeysP2PKeysPayloadResultsP2PKey { + return v.Results +} + +// FetchP2PKeysP2pKeysP2PKeysPayloadResultsP2PKey includes the requested fields of the GraphQL type P2PKey. +type FetchP2PKeysP2pKeysP2PKeysPayloadResultsP2PKey struct { + Id string `json:"id"` + PeerID string `json:"peerID"` + PublicKey string `json:"publicKey"` +} + +// GetId returns FetchP2PKeysP2pKeysP2PKeysPayloadResultsP2PKey.Id, and is useful for accessing the field via an interface. +func (v *FetchP2PKeysP2pKeysP2PKeysPayloadResultsP2PKey) GetId() string { return v.Id } + +// GetPeerID returns FetchP2PKeysP2pKeysP2PKeysPayloadResultsP2PKey.PeerID, and is useful for accessing the field via an interface. +func (v *FetchP2PKeysP2pKeysP2PKeysPayloadResultsP2PKey) GetPeerID() string { return v.PeerID } + +// GetPublicKey returns FetchP2PKeysP2pKeysP2PKeysPayloadResultsP2PKey.PublicKey, and is useful for accessing the field via an interface. +func (v *FetchP2PKeysP2pKeysP2PKeysPayloadResultsP2PKey) GetPublicKey() string { return v.PublicKey } + +// FetchP2PKeysResponse is returned by FetchP2PKeys on success. +type FetchP2PKeysResponse struct { + P2pKeys FetchP2PKeysP2pKeysP2PKeysPayload `json:"p2pKeys"` +} + +// GetP2pKeys returns FetchP2PKeysResponse.P2pKeys, and is useful for accessing the field via an interface. +func (v *FetchP2PKeysResponse) GetP2pKeys() FetchP2PKeysP2pKeysP2PKeysPayload { return v.P2pKeys } + // GetBridgeBridge includes the requested fields of the GraphQL type Bridge. type GetBridgeBridge struct { Typename string `json:"__typename"` @@ -1206,40 +1900,6 @@ func (v *GetBridgeResponse) __premarshalJSON() (*__premarshalGetBridgeResponse, return &retval, nil } -// GetCSAKeysCsaKeysCSAKeysPayload includes the requested fields of the GraphQL type CSAKeysPayload. -type GetCSAKeysCsaKeysCSAKeysPayload struct { - Results []GetCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey `json:"results"` -} - -// GetResults returns GetCSAKeysCsaKeysCSAKeysPayload.Results, and is useful for accessing the field via an interface. -func (v *GetCSAKeysCsaKeysCSAKeysPayload) GetResults() []GetCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey { - return v.Results -} - -// GetCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey includes the requested fields of the GraphQL type CSAKey. -type GetCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey struct { - Id string `json:"id"` - PublicKey string `json:"publicKey"` - Version int `json:"version"` -} - -// GetId returns GetCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey.Id, and is useful for accessing the field via an interface. -func (v *GetCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey) GetId() string { return v.Id } - -// GetPublicKey returns GetCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey.PublicKey, and is useful for accessing the field via an interface. -func (v *GetCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey) GetPublicKey() string { return v.PublicKey } - -// GetVersion returns GetCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey.Version, and is useful for accessing the field via an interface. -func (v *GetCSAKeysCsaKeysCSAKeysPayloadResultsCSAKey) GetVersion() int { return v.Version } - -// GetCSAKeysResponse is returned by GetCSAKeys on success. -type GetCSAKeysResponse struct { - CsaKeys GetCSAKeysCsaKeysCSAKeysPayload `json:"csaKeys"` -} - -// GetCsaKeys returns GetCSAKeysResponse.CsaKeys, and is useful for accessing the field via an interface. -func (v *GetCSAKeysResponse) GetCsaKeys() GetCSAKeysCsaKeysCSAKeysPayload { return v.CsaKeys } - // GetFeedsManagerFeedsManager includes the requested fields of the GraphQL type FeedsManager. type GetFeedsManagerFeedsManager struct { Typename string `json:"__typename"` @@ -3222,6 +3882,16 @@ type ListJobsResponse struct { // GetJobs returns ListJobsResponse.Jobs, and is useful for accessing the field via an interface. func (v *ListJobsResponse) GetJobs() ListJobsJobsJobsPayload { return v.Jobs } +type OCR2ChainType string + +const ( + OCR2ChainTypeEvm OCR2ChainType = "EVM" + OCR2ChainTypeCosmos OCR2ChainType = "COSMOS" + OCR2ChainTypeSolana OCR2ChainType = "SOLANA" + OCR2ChainTypeStarknet OCR2ChainType = "STARKNET" + OCR2ChainTypeAptos OCR2ChainType = "APTOS" +) + // #################### // Jobs and Job Proposals // #################### @@ -4115,6 +4785,16 @@ type __CancelJobProposalSpecInput struct { // GetId returns __CancelJobProposalSpecInput.Id, and is useful for accessing the field via an interface. func (v *__CancelJobProposalSpecInput) GetId() string { return v.Id } +// __CreateFeedsManagerChainConfigInput is used internally by genqlient +type __CreateFeedsManagerChainConfigInput struct { + Input CreateFeedsManagerChainConfigInput `json:"input"` +} + +// GetInput returns __CreateFeedsManagerChainConfigInput.Input, and is useful for accessing the field via an interface. +func (v *__CreateFeedsManagerChainConfigInput) GetInput() CreateFeedsManagerChainConfigInput { + return v.Input +} + // __CreateFeedsManagerInput is used internally by genqlient type __CreateFeedsManagerInput struct { Input CreateFeedsManagerInput `json:"input"` @@ -4382,44 +5062,115 @@ func CreateFeedsManager( return &data_, err_ } -// The query or mutation executed by GetBridge. -const GetBridge_Operation = ` -query GetBridge ($id: ID!) { - bridge(id: $id) { +// The query or mutation executed by CreateFeedsManagerChainConfig. +const CreateFeedsManagerChainConfig_Operation = ` +mutation CreateFeedsManagerChainConfig ($input: CreateFeedsManagerChainConfigInput!) { + createFeedsManagerChainConfig(input: $input) { __typename - ... BridgeParts + ... on CreateFeedsManagerChainConfigSuccess { + chainConfig { + id + chainID + chainType + accountAddr + adminAddr + fluxMonitorJobConfig { + enabled + } + ocr1JobConfig { + enabled + isBootstrap + multiaddr + p2pPeerID + keyBundleID + } + ocr2JobConfig { + enabled + isBootstrap + multiaddr + forwarderAddress + p2pPeerID + keyBundleID + plugins { + commit + execute + median + mercury + rebalancer + } + } + } + } ... on NotFoundError { message code } + ... on InputErrors { + errors { + message + path + } + } } } -fragment BridgeParts on Bridge { - id - name - url - confirmations - outgoingToken - minimumContractPayment - createdAt -} ` -func GetBridge( +// createFeedsManagerChainConfig.graphql +func CreateFeedsManagerChainConfig( ctx_ context.Context, client_ graphql.Client, - id string, -) (*GetBridgeResponse, error) { + input CreateFeedsManagerChainConfigInput, +) (*CreateFeedsManagerChainConfigResponse, error) { req_ := &graphql.Request{ - OpName: "GetBridge", - Query: GetBridge_Operation, - Variables: &__GetBridgeInput{ - Id: id, + OpName: "CreateFeedsManagerChainConfig", + Query: CreateFeedsManagerChainConfig_Operation, + Variables: &__CreateFeedsManagerChainConfigInput{ + Input: input, }, } var err_ error - var data_ GetBridgeResponse + var data_ CreateFeedsManagerChainConfigResponse + resp_ := &graphql.Response{Data: &data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return &data_, err_ +} + +// The query or mutation executed by FetchAccounts. +const FetchAccounts_Operation = ` +query FetchAccounts { + ethKeys { + results { + address + isDisabled + chain { + id + enabled + } + ethBalance + linkBalance + } + } +} +` + +func FetchAccounts( + ctx_ context.Context, + client_ graphql.Client, +) (*FetchAccountsResponse, error) { + req_ := &graphql.Request{ + OpName: "FetchAccounts", + Query: FetchAccounts_Operation, + } + var err_ error + + var data_ FetchAccountsResponse resp_ := &graphql.Response{Data: &data_} err_ = client_.MakeRequest( @@ -4431,9 +5182,9 @@ func GetBridge( return &data_, err_ } -// The query or mutation executed by GetCSAKeys. -const GetCSAKeys_Operation = ` -query GetCSAKeys { +// The query or mutation executed by FetchCSAKeys. +const FetchCSAKeys_Operation = ` +query FetchCSAKeys { csaKeys { results { id @@ -4444,17 +5195,138 @@ query GetCSAKeys { } ` -func GetCSAKeys( +func FetchCSAKeys( ctx_ context.Context, client_ graphql.Client, -) (*GetCSAKeysResponse, error) { +) (*FetchCSAKeysResponse, error) { req_ := &graphql.Request{ - OpName: "GetCSAKeys", - Query: GetCSAKeys_Operation, + OpName: "FetchCSAKeys", + Query: FetchCSAKeys_Operation, } var err_ error - var data_ GetCSAKeysResponse + var data_ FetchCSAKeysResponse + resp_ := &graphql.Response{Data: &data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return &data_, err_ +} + +// The query or mutation executed by FetchOCR2KeyBundles. +const FetchOCR2KeyBundles_Operation = ` +query FetchOCR2KeyBundles { + ocr2KeyBundles { + results { + id + chainType + configPublicKey + onChainPublicKey + offChainPublicKey + } + } +} +` + +func FetchOCR2KeyBundles( + ctx_ context.Context, + client_ graphql.Client, +) (*FetchOCR2KeyBundlesResponse, error) { + req_ := &graphql.Request{ + OpName: "FetchOCR2KeyBundles", + Query: FetchOCR2KeyBundles_Operation, + } + var err_ error + + var data_ FetchOCR2KeyBundlesResponse + resp_ := &graphql.Response{Data: &data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return &data_, err_ +} + +// The query or mutation executed by FetchP2PKeys. +const FetchP2PKeys_Operation = ` +query FetchP2PKeys { + p2pKeys { + results { + id + peerID + publicKey + } + } +} +` + +func FetchP2PKeys( + ctx_ context.Context, + client_ graphql.Client, +) (*FetchP2PKeysResponse, error) { + req_ := &graphql.Request{ + OpName: "FetchP2PKeys", + Query: FetchP2PKeys_Operation, + } + var err_ error + + var data_ FetchP2PKeysResponse + resp_ := &graphql.Response{Data: &data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return &data_, err_ +} + +// The query or mutation executed by GetBridge. +const GetBridge_Operation = ` +query GetBridge ($id: ID!) { + bridge(id: $id) { + __typename + ... BridgeParts + ... on NotFoundError { + message + code + } + } +} +fragment BridgeParts on Bridge { + id + name + url + confirmations + outgoingToken + minimumContractPayment + createdAt +} +` + +func GetBridge( + ctx_ context.Context, + client_ graphql.Client, + id string, +) (*GetBridgeResponse, error) { + req_ := &graphql.Request{ + OpName: "GetBridge", + Query: GetBridge_Operation, + Variables: &__GetBridgeInput{ + Id: id, + }, + } + var err_ error + + var data_ GetBridgeResponse resp_ := &graphql.Response{Data: &data_} err_ = client_.MakeRequest( diff --git a/integration-tests/web/sdk/internal/genqlient.graphql b/integration-tests/web/sdk/internal/genqlient.graphql index f9e8fc1843..cd1912b88c 100644 --- a/integration-tests/web/sdk/internal/genqlient.graphql +++ b/integration-tests/web/sdk/internal/genqlient.graphql @@ -2,118 +2,168 @@ # CSA Keys ##################### -query GetCSAKeys { - csaKeys { - results { - id - publicKey - version +query FetchCSAKeys { + csaKeys { + results { + id + publicKey + version + } } - } } +##################### +# P2P Keys +##################### + +query FetchP2PKeys { + p2pKeys { + results { + id + peerID + publicKey + } + } +} + +##################### +# ethKeys +##################### + +query FetchAccounts { + ethKeys { + results { + address + isDisabled + chain { + id + enabled + } + ethBalance + linkBalance + } + } +} + +##################### +# ocr2KeyBundles +##################### + +query FetchOCR2KeyBundles { + ocr2KeyBundles { + results { + id + chainType + configPublicKey + onChainPublicKey + offChainPublicKey + } + } +} + + ##################### # Jobs and Job Proposals ##################### fragment OCR2Spec on OCR2Spec { - blockchainTimeout - contractID - contractConfigConfirmations - contractConfigTrackerPollInterval - createdAt - ocrKeyBundleID - monitoringEndpoint - p2pv2Bootstrappers - relay - relayConfig - transmitterID - pluginType - pluginConfig + blockchainTimeout + contractID + contractConfigConfirmations + contractConfigTrackerPollInterval + createdAt + ocrKeyBundleID + monitoringEndpoint + p2pv2Bootstrappers + relay + relayConfig + transmitterID + pluginType + pluginConfig } fragment JobParts on Job { - id - name - schemaVersion - gasLimit - forwardingAllowed - maxTaskDuration - externalJobID - type - spec { - ... on OCR2Spec { - ...OCR2Spec - } - } - observationSource - errors { id - description - occurrences - createdAt - updatedAt - } + name + schemaVersion + gasLimit + forwardingAllowed + maxTaskDuration + externalJobID + type + spec { + ... on OCR2Spec { + ...OCR2Spec + } + } + observationSource + errors { + id + description + occurrences + createdAt + updatedAt + } } query GetJob($id: ID!) { - job(id: $id) { - ...JobParts - ... on NotFoundError { - message - code + job(id: $id) { + ...JobParts + ... on NotFoundError { + message + code + } } - } } query ListJobs($offset: Int, $limit: Int) { - jobs(offset: $offset, limit: $limit) { - results { - ...JobParts - } - metadata { - total + jobs(offset: $offset, limit: $limit) { + results { + ...JobParts + } + metadata { + total + } } - } } query GetJobProposal($id: ID!) { - jobProposal(id: $id) { - ... on JobProposal { - id - name - status - remoteUUID - externalJobID - jobID - feedsManager { - ...FeedsManagerParts - } - multiAddrs - pendingUpdate - specs { - id - definition - version - status - statusUpdatedAt - createdAt - updatedAt - } - latestSpec { - id - definition - version - status - statusUpdatedAt - createdAt - updatedAt - } - } - ... on NotFoundError { - message - code + jobProposal(id: $id) { + ... on JobProposal { + id + name + status + remoteUUID + externalJobID + jobID + feedsManager { + ...FeedsManagerParts + } + multiAddrs + pendingUpdate + specs { + id + definition + version + status + statusUpdatedAt + createdAt + updatedAt + } + latestSpec { + id + definition + version + status + statusUpdatedAt + createdAt + updatedAt + } + } + ... on NotFoundError { + message + code + } } - } } ##################### @@ -121,34 +171,34 @@ query GetJobProposal($id: ID!) { ##################### fragment BridgeParts on Bridge { - id - name - url - confirmations - outgoingToken - minimumContractPayment - createdAt + id + name + url + confirmations + outgoingToken + minimumContractPayment + createdAt } query ListBridges($offset: Int, $limit: Int) { - bridges(offset: $offset, limit: $limit) { - results { - ...BridgeParts - } - metadata { - total + bridges(offset: $offset, limit: $limit) { + results { + ...BridgeParts + } + metadata { + total + } } - } } query GetBridge($id: ID!) { - bridge(id: $id) { - ...BridgeParts - ... on NotFoundError { - message - code + bridge(id: $id) { + ...BridgeParts + ... on NotFoundError { + message + code + } } - } } ##################### @@ -156,165 +206,216 @@ query GetBridge($id: ID!) { ##################### fragment FeedsManagerParts on FeedsManager { - id - name - uri - publicKey - isConnectionActive - createdAt + id + name + uri + publicKey + isConnectionActive + createdAt } query GetFeedsManager($id: ID!) { - feedsManager(id: $id) { - ...FeedsManagerParts - ... on NotFoundError { - message - code + feedsManager(id: $id) { + ...FeedsManagerParts + ... on NotFoundError { + message + code + } } - } } query ListFeedsManagers { - feedsManagers { - results { - ...FeedsManagerParts + feedsManagers { + results { + ...FeedsManagerParts + } } - } } mutation CreateFeedsManager($input: CreateFeedsManagerInput!) { - createFeedsManager(input: $input) { - ... on CreateFeedsManagerSuccess { - feedsManager { - ...FeedsManagerParts - } + createFeedsManager(input: $input) { + ... on CreateFeedsManagerSuccess { + feedsManager { + ...FeedsManagerParts + } + } + ... on SingleFeedsManagerError { + message + code + } + ... on NotFoundError { + message + code + } + ... on InputErrors { + errors { + message + code + path + } + } } - ... on SingleFeedsManagerError { - message - code - } - ... on NotFoundError { - message - code - } - ... on InputErrors { - errors { - message - code - path - } - } - } } mutation UpdateFeedsManager($id: ID!, $input: UpdateFeedsManagerInput!) { - updateFeedsManager(id: $id, input: $input) { - ... on UpdateFeedsManagerSuccess { - feedsManager { - ...FeedsManagerParts - } + updateFeedsManager(id: $id, input: $input) { + ... on UpdateFeedsManagerSuccess { + feedsManager { + ...FeedsManagerParts + } + } + ... on NotFoundError { + message + code + } + ... on InputErrors { + errors { + message + code + path + } + } } - ... on NotFoundError { - message - code - } - ... on InputErrors { - errors { - message - code - path - } +} + +# createFeedsManagerChainConfig.graphql +mutation CreateFeedsManagerChainConfig($input: CreateFeedsManagerChainConfigInput!) { + createFeedsManagerChainConfig(input: $input) { + ... on CreateFeedsManagerChainConfigSuccess { + chainConfig { + id + chainID + chainType + accountAddr + adminAddr + fluxMonitorJobConfig { + enabled + } + ocr1JobConfig { + enabled + isBootstrap + multiaddr + p2pPeerID + keyBundleID + } + ocr2JobConfig { + enabled + isBootstrap + multiaddr + forwarderAddress + p2pPeerID + keyBundleID + plugins { + commit + execute + median + mercury + rebalancer + } + } + } + } + ... on NotFoundError { + message + code + } + ... on InputErrors { + errors { + message + path + } + } } - } } + ##################### # Job Proposals ##################### mutation ApproveJobProposalSpec($id: ID!, $force: Boolean) { - approveJobProposalSpec(id: $id, force: $force) { - ... on ApproveJobProposalSpecSuccess { - spec { - id - definition - version - status - statusUpdatedAt - createdAt - updatedAt - } - } - ... on JobAlreadyExistsError { - message - code - } - ... on NotFoundError { - message - code + approveJobProposalSpec(id: $id, force: $force) { + ... on ApproveJobProposalSpecSuccess { + spec { + id + definition + version + status + statusUpdatedAt + createdAt + updatedAt + } + } + ... on JobAlreadyExistsError { + message + code + } + ... on NotFoundError { + message + code + } } - } } mutation CancelJobProposalSpec($id: ID!) { - cancelJobProposalSpec(id: $id) { - ... on CancelJobProposalSpecSuccess { - spec { - id - definition - version - status - statusUpdatedAt - createdAt - updatedAt - } - } - ... on NotFoundError { - message - code + cancelJobProposalSpec(id: $id) { + ... on CancelJobProposalSpecSuccess { + spec { + id + definition + version + status + statusUpdatedAt + createdAt + updatedAt + } + } + ... on NotFoundError { + message + code + } } - } } mutation RejectJobProposalSpec($id: ID!) { - rejectJobProposalSpec(id: $id) { - ... on RejectJobProposalSpecSuccess { - spec { - id - definition - version - status - statusUpdatedAt - createdAt - updatedAt - } - } - ... on NotFoundError { - message - code + rejectJobProposalSpec(id: $id) { + ... on RejectJobProposalSpecSuccess { + spec { + id + definition + version + status + statusUpdatedAt + createdAt + updatedAt + } + } + ... on NotFoundError { + message + code + } } - } } mutation UpdateJobProposalSpecDefinition( - $id: ID! - $input: UpdateJobProposalSpecDefinitionInput! + $id: ID! + $input: UpdateJobProposalSpecDefinitionInput! ) { - updateJobProposalSpecDefinition(id: $id, input: $input) { - ... on UpdateJobProposalSpecDefinitionSuccess { - spec { - id - definition - version - status - statusUpdatedAt - createdAt - updatedAt - } - } - ... on NotFoundError { - message - code + updateJobProposalSpecDefinition(id: $id, input: $input) { + ... on UpdateJobProposalSpecDefinitionSuccess { + spec { + id + definition + version + status + statusUpdatedAt + createdAt + updatedAt + } + } + ... on NotFoundError { + message + code + } } - } } diff --git a/integration-tests/web/sdk/internal/schema.graphql b/integration-tests/web/sdk/internal/schema.graphql index 64b7e479ab..be09d61ec7 100644 --- a/integration-tests/web/sdk/internal/schema.graphql +++ b/integration-tests/web/sdk/internal/schema.graphql @@ -287,6 +287,7 @@ type EthTransactionAttemptsPayload implements PaginatedPayload { type Features { csa: Boolean! feedsManager: Boolean! + multiFeedsManagers: Boolean! } # FeaturesPayload defines the response of fetching the features availability in the UI @@ -370,6 +371,13 @@ type CreateFeedsManagerSuccess { feedsManager: FeedsManager! } +type DuplicateFeedsManagerError implements Error { + message: String! + code: ErrorCode! +} + +# DEPRECATED: No longer used since we now support multiple feeds manager. +# Keeping this to avoid breaking change. type SingleFeedsManagerError implements Error { message: String! code: ErrorCode! @@ -377,7 +385,8 @@ type SingleFeedsManagerError implements Error { # CreateFeedsManagerPayload defines the response when creating a feeds manager union CreateFeedsManagerPayload = CreateFeedsManagerSuccess - | SingleFeedsManagerError + | DuplicateFeedsManagerError + | SingleFeedsManagerError # // TODO: delete once multiple feeds managers support is released | NotFoundError | InputErrors diff --git a/integration-tests/wrappers/contract_caller.go b/integration-tests/wrappers/contract_caller.go index 8669b11e49..14170c7dee 100644 --- a/integration-tests/wrappers/contract_caller.go +++ b/integration-tests/wrappers/contract_caller.go @@ -17,10 +17,8 @@ import ( "github.com/pkg/errors" "github.com/rs/zerolog" - "github.com/smartcontractkit/chainlink-testing-framework/seth" - "github.com/smartcontractkit/chainlink-testing-framework/lib/blockchain" - + "github.com/smartcontractkit/chainlink-testing-framework/seth" evmClient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" ) diff --git a/main_test.go b/main_test.go index 81e056e3b8..419a4af051 100644 --- a/main_test.go +++ b/main_test.go @@ -42,6 +42,10 @@ func TestMain(m *testing.M) { })) } +// TestScripts walks through the testdata/scripts directory and runs all tests that end in +// .txt or .txtar with the testscripts library. To run an individual test, specify it in the +// -run param of go test without the txtar or txt suffix, like so: +// go test . -run TestScripts/node/validate/default func TestScripts(t *testing.T) { if testing.Short() { t.Skip("skipping testscript") diff --git a/operator_ui/TAG b/operator_ui/TAG index bd6882b0f5..b085cda408 100644 --- a/operator_ui/TAG +++ b/operator_ui/TAG @@ -1 +1 @@ -v0.8.0-094d250 +v0.8.0-b940c8d \ No newline at end of file diff --git a/operator_ui/install.go b/operator_ui/install.go index 1e09783db6..14d920ddd4 100644 --- a/operator_ui/install.go +++ b/operator_ui/install.go @@ -22,7 +22,7 @@ func main() { fullRepo = owner + "/" + repo tagPath = "operator_ui/TAG" unpackDir = "core/web/assets" - downloadTimeoutSeconds = 10 + downloadTimeoutSeconds = 30 ) // Grab first argument as root directory if len(os.Args) < 2 { diff --git a/package.json b/package.json index f32ffe9e5a..2a6927dc61 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ccip", - "version": "2.15.0-ccip1.5.0", + "version": "2.17.0-ccip1.5.0", "description": "node of the decentralized oracle network, bridging on and off-chain computation", "main": "index.js", "scripts": { @@ -25,6 +25,6 @@ "@changesets/changelog-github": "^0.4.8", "@changesets/cli": "~2.26.2", "husky": "^9.0.11", - "semver": "^7.6.1" + "semver": "^7.6.3" } } diff --git a/plugins/loop_registry.go b/plugins/loop_registry.go index b796ddf87e..51c6310ffa 100644 --- a/plugins/loop_registry.go +++ b/plugins/loop_registry.go @@ -27,15 +27,17 @@ type LoopRegistry struct { mu sync.Mutex registry map[string]*RegisteredLoop - lggr logger.Logger - cfgTracing config.Tracing + lggr logger.Logger + cfgTracing config.Tracing + cfgTelemetry config.Telemetry } -func NewLoopRegistry(lggr logger.Logger, tracingConfig config.Tracing) *LoopRegistry { +func NewLoopRegistry(lggr logger.Logger, tracing config.Tracing, telemetry config.Telemetry) *LoopRegistry { return &LoopRegistry{ - registry: map[string]*RegisteredLoop{}, - lggr: logger.Named(lggr, "LoopRegistry"), - cfgTracing: tracingConfig, + registry: map[string]*RegisteredLoop{}, + lggr: logger.Named(lggr, "LoopRegistry"), + cfgTracing: tracing, + cfgTelemetry: telemetry, } } @@ -65,6 +67,15 @@ func (m *LoopRegistry) Register(id string) (*RegisteredLoop, error) { envCfg.TracingAttributes = m.cfgTracing.Attributes() } + if m.cfgTelemetry != nil { + envCfg.TelemetryEnabled = m.cfgTelemetry.Enabled() + envCfg.TelemetryEndpoint = m.cfgTelemetry.OtelExporterGRPCEndpoint() + envCfg.TelemetryInsecureConnection = m.cfgTelemetry.InsecureConnection() + envCfg.TelemetryCACertFile = m.cfgTelemetry.CACertFile() + envCfg.TelemetryAttributes = m.cfgTelemetry.ResourceAttributes() + envCfg.TelemetryTraceSampleRatio = m.cfgTelemetry.TraceSampleRatio() + } + m.registry[id] = &RegisteredLoop{Name: id, EnvCfg: envCfg} m.lggr.Debugf("Registered loopp %q with config %v, port %d", id, envCfg, envCfg.PrometheusPort) return m.registry[id], nil diff --git a/plugins/loop_registry_test.go b/plugins/loop_registry_test.go index b307469e09..84b6b0cefc 100644 --- a/plugins/loop_registry_test.go +++ b/plugins/loop_registry_test.go @@ -5,12 +5,13 @@ import ( "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-common/pkg/loop" "github.com/smartcontractkit/chainlink/v2/core/logger" ) func TestPluginPortManager(t *testing.T) { // register one - m := NewLoopRegistry(logger.TestLogger(t), nil) + m := NewLoopRegistry(logger.TestLogger(t), nil, nil) pFoo, err := m.Register("foo") require.NoError(t, err) require.Equal(t, "foo", pFoo.Name) @@ -26,37 +27,63 @@ func TestPluginPortManager(t *testing.T) { require.Equal(t, pFoo.EnvCfg.PrometheusPort+1, pBar.EnvCfg.PrometheusPort) } -// Mock tracing config -type MockCfgTracing struct{} +type mockCfgTracing struct{} -func (m *MockCfgTracing) Attributes() map[string]string { +func (m *mockCfgTracing) Attributes() map[string]string { return map[string]string{"attribute": "value"} } -func (m *MockCfgTracing) Enabled() bool { return true } -func (m *MockCfgTracing) NodeID() string { return "" } -func (m *MockCfgTracing) CollectorTarget() string { return "http://localhost:9000" } -func (m *MockCfgTracing) SamplingRatio() float64 { return 0.1 } -func (m *MockCfgTracing) TLSCertPath() string { return "/path/to/cert.pem" } -func (m *MockCfgTracing) Mode() string { return "tls" } +func (m *mockCfgTracing) Enabled() bool { return true } +func (m *mockCfgTracing) NodeID() string { return "" } +func (m *mockCfgTracing) CollectorTarget() string { return "http://localhost:9000" } +func (m *mockCfgTracing) SamplingRatio() float64 { return 0.1 } +func (m *mockCfgTracing) TLSCertPath() string { return "/path/to/cert.pem" } +func (m *mockCfgTracing) Mode() string { return "tls" } + +type mockCfgTelemetry struct{} + +func (m mockCfgTelemetry) Enabled() bool { return true } + +func (m mockCfgTelemetry) InsecureConnection() bool { return true } + +func (m mockCfgTelemetry) CACertFile() string { return "path/to/cert.pem" } + +func (m mockCfgTelemetry) OtelExporterGRPCEndpoint() string { return "http://localhost:9001" } + +func (m mockCfgTelemetry) ResourceAttributes() map[string]string { + return map[string]string{"foo": "bar"} +} + +func (m mockCfgTelemetry) TraceSampleRatio() float64 { return 0.42 } func TestLoopRegistry_Register(t *testing.T) { - mockCfgTracing := &MockCfgTracing{} + mockCfgTracing := &mockCfgTracing{} + mockCfgTelemetry := &mockCfgTelemetry{} registry := make(map[string]*RegisteredLoop) // Create a LoopRegistry instance with mockCfgTracing loopRegistry := &LoopRegistry{ - lggr: logger.TestLogger(t), - registry: registry, - cfgTracing: mockCfgTracing, + lggr: logger.TestLogger(t), + registry: registry, + cfgTracing: mockCfgTracing, + cfgTelemetry: mockCfgTelemetry, } // Test case 1: Register new loop registeredLoop, err := loopRegistry.Register("testID") require.Nil(t, err) require.Equal(t, "testID", registeredLoop.Name) - require.True(t, registeredLoop.EnvCfg.TracingEnabled) - require.Equal(t, "http://localhost:9000", registeredLoop.EnvCfg.TracingCollectorTarget) - require.Equal(t, map[string]string{"attribute": "value"}, registeredLoop.EnvCfg.TracingAttributes) - require.Equal(t, 0.1, registeredLoop.EnvCfg.TracingSamplingRatio) - require.Equal(t, "/path/to/cert.pem", registeredLoop.EnvCfg.TracingTLSCertPath) + + envCfg := registeredLoop.EnvCfg + require.True(t, envCfg.TracingEnabled) + require.Equal(t, "http://localhost:9000", envCfg.TracingCollectorTarget) + require.Equal(t, map[string]string{"attribute": "value"}, envCfg.TracingAttributes) + require.Equal(t, 0.1, envCfg.TracingSamplingRatio) + require.Equal(t, "/path/to/cert.pem", envCfg.TracingTLSCertPath) + + require.True(t, envCfg.TelemetryEnabled) + require.True(t, envCfg.TelemetryInsecureConnection) + require.Equal(t, "path/to/cert.pem", envCfg.TelemetryCACertFile) + require.Equal(t, "http://localhost:9001", envCfg.TelemetryEndpoint) + require.Equal(t, loop.OtelAttributes{"foo": "bar"}, envCfg.TelemetryAttributes) + require.Equal(t, 0.42, envCfg.TelemetryTraceSampleRatio) } diff --git a/plugins/medianpoc/plugin.go b/plugins/medianpoc/plugin.go index 41580afe49..e6ada1bec8 100644 --- a/plugins/medianpoc/plugin.go +++ b/plugins/medianpoc/plugin.go @@ -40,7 +40,7 @@ func (e *PipelineNotFoundError) Error() string { } func (p *Plugin) NewValidationService(ctx context.Context) (core.ValidationService, error) { - s := &reportingPluginValidationService{lggr: p.Logger} + s := &reportingPluginValidationService{Service: services.Config{Name: "ValidationService"}.NewService(p.Logger)} p.SubService(s) return s, nil } @@ -81,7 +81,10 @@ func (p *Plugin) NewReportingPluginFactory( if err != nil { return nil, err } - s := &reportingPluginFactoryService{lggr: p.Logger, ReportingPluginFactory: f} + s := &reportingPluginFactoryService{ + Service: services.Config{Name: "ReportingPluginFactory"}.NewService(p.Logger), + ReportingPluginFactory: f, + } p.SubService(s) return s, nil } @@ -157,28 +160,12 @@ func (p *Plugin) newFactory(ctx context.Context, config core.ReportingPluginServ } type reportingPluginFactoryService struct { - services.StateMachine - lggr logger.Logger + services.Service ocrtypes.ReportingPluginFactory } -func (r *reportingPluginFactoryService) Name() string { return r.lggr.Name() } - -func (r *reportingPluginFactoryService) Start(ctx context.Context) error { - return r.StartOnce("ReportingPluginFactory", func() error { return nil }) -} - -func (r *reportingPluginFactoryService) Close() error { - return r.StopOnce("ReportingPluginFactory", func() error { return nil }) -} - -func (r *reportingPluginFactoryService) HealthReport() map[string]error { - return map[string]error{r.Name(): r.Healthy()} -} - type reportingPluginValidationService struct { - services.StateMachine - lggr logger.Logger + services.Service } func (r *reportingPluginValidationService) ValidateConfig(ctx context.Context, config map[string]interface{}) error { @@ -196,16 +183,3 @@ func (r *reportingPluginValidationService) ValidateConfig(ctx context.Context, c return nil } -func (r *reportingPluginValidationService) Name() string { return r.lggr.Name() } - -func (r *reportingPluginValidationService) Start(ctx context.Context) error { - return r.StartOnce("ValidationService", func() error { return nil }) -} - -func (r *reportingPluginValidationService) Close() error { - return r.StopOnce("ValidationService", func() error { return nil }) -} - -func (r *reportingPluginValidationService) HealthReport() map[string]error { - return map[string]error{r.Name(): r.Healthy()} -} diff --git a/plugins/medianpoc/plugin_test.go b/plugins/medianpoc/plugin_test.go index 22e4c095d6..d2d17db32a 100644 --- a/plugins/medianpoc/plugin_test.go +++ b/plugins/medianpoc/plugin_test.go @@ -73,7 +73,7 @@ func (p provider) OnchainConfigCodec() median.OnchainConfigCodec { return mockOnchainConfigCodec{} } -func (p provider) ChainReader() types.ContractReader { +func (p provider) ContractReader() types.ContractReader { return nil } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2eb9f6c0aa..874c04248c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,20 +20,20 @@ devDependencies: packages: - '@babel/code-frame@7.23.5': - resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==} + '@babel/code-frame@7.24.7': + resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.22.20': - resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} + '@babel/helper-validator-identifier@7.24.7': + resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} engines: {node: '>=6.9.0'} - '@babel/highlight@7.23.4': - resolution: {integrity: sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==} + '@babel/highlight@7.24.7': + resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} engines: {node: '>=6.9.0'} - '@babel/runtime@7.23.9': - resolution: {integrity: sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==} + '@babel/runtime@7.25.6': + resolution: {integrity: sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==} engines: {node: '>=6.9.0'} '@changesets/apply-release-plan@6.1.4': @@ -174,8 +174,8 @@ packages: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} - braces@3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} breakword@1.0.6: @@ -248,6 +248,18 @@ packages: resolution: {integrity: sha512-QTaY0XjjhTQOdguARF0lGKm5/mEq9PD9/VhZZegHDIBq2tQwgNpHc3dneD4mGo2iJs+fTKv5Bp0fZ+BRuY3Z0g==} engines: {node: '>= 0.1.90'} + data-view-buffer@1.0.1: + resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.1: + resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.0: + resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} + engines: {node: '>= 0.4'} + dataloader@1.4.0: resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==} @@ -292,8 +304,8 @@ packages: error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - es-abstract@1.22.4: - resolution: {integrity: sha512-vZYJlk2u6qHYxBOTjAeg7qUxHdNfih64Uu2J8QqWgXZ2cri0ZpJAkzDUK/q593+mvKwlxyaxr6F1Q+3LKoQRgg==} + es-abstract@1.23.3: + resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} engines: {node: '>= 0.4'} es-define-property@1.0.0: @@ -304,6 +316,10 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} + es-object-atoms@1.0.0: + resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} + engines: {node: '>= 0.4'} + es-set-tostringtag@2.0.3: resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} engines: {node: '>= 0.4'} @@ -315,8 +331,8 @@ packages: resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} engines: {node: '>= 0.4'} - escalade@3.1.2: - resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} escape-string-regexp@1.0.5: @@ -342,8 +358,8 @@ packages: fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} - fill-range@7.0.1: - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} find-up@4.1.0: @@ -394,8 +410,8 @@ packages: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} - globalthis@1.0.3: - resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} globby@11.1.0: @@ -441,8 +457,8 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} - hasown@2.0.1: - resolution: {integrity: sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==} + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} hosted-git-info@2.8.9: @@ -455,8 +471,8 @@ packages: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} - ignore@5.3.1: - resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} indent-string@4.0.0: @@ -489,8 +505,13 @@ packages: resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} hasBin: true - is-core-module@2.13.1: - resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} + is-core-module@2.15.1: + resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.1: + resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} + engines: {node: '>= 0.4'} is-date-object@1.0.5: resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} @@ -619,8 +640,8 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - micromatch@4.0.5: - resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} min-indent@1.0.1: @@ -647,8 +668,9 @@ packages: normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} - object-inspect@1.13.1: - resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} + object-inspect@1.13.2: + resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} + engines: {node: '>= 0.4'} object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} @@ -708,6 +730,9 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + picocolors@1.1.0: + resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -724,8 +749,8 @@ packages: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} - preferred-pm@3.1.3: - resolution: {integrity: sha512-MkXsENfftWSRpzCzImcp4FRsCc3y1opwB73CfCNWyzMqArju2CrlMHlqB7VexKiPEOjGMbttv1r9fSCn5S610w==} + preferred-pm@3.1.4: + resolution: {integrity: sha512-lEHd+yEm22jXdCphDrkvIJQU66EuLojPPtvZkpKIkiD+l0DMThF/niqZKJSoU8Vl7iuvtmzyMhir9LdVy5WMnA==} engines: {node: '>=10'} prettier@2.8.8: @@ -788,8 +813,8 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - safe-array-concat@1.1.0: - resolution: {integrity: sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==} + safe-array-concat@1.1.2: + resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} engines: {node: '>=0.4'} safe-regex-test@1.0.3: @@ -803,16 +828,16 @@ packages: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true - semver@7.6.1: - resolution: {integrity: sha512-f/vbBsu+fOiYt+lmwZV0rVwJScl46HppnOA1ZvIuBWKOTlllpyJ3bfVax76/OrhCH38dyxoDIA8K7uB963IYgA==} + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} engines: {node: '>=10'} hasBin: true set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - set-function-length@1.2.1: - resolution: {integrity: sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==} + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} set-function-name@2.0.2: @@ -827,8 +852,8 @@ packages: resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} engines: {node: '>=0.10.0'} - side-channel@1.0.5: - resolution: {integrity: sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==} + side-channel@1.0.6: + resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} engines: {node: '>= 0.4'} signal-exit@3.0.7: @@ -855,8 +880,8 @@ packages: spdx-expression-parse@3.0.1: resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - spdx-license-ids@3.0.17: - resolution: {integrity: sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==} + spdx-license-ids@3.0.20: + resolution: {integrity: sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==} sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} @@ -868,15 +893,16 @@ packages: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} - string.prototype.trim@1.2.8: - resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==} + string.prototype.trim@1.2.9: + resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} engines: {node: '>= 0.4'} - string.prototype.trimend@1.0.7: - resolution: {integrity: sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==} + string.prototype.trimend@1.0.8: + resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} - string.prototype.trimstart@1.0.7: - resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==} + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} @@ -950,8 +976,8 @@ packages: resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} engines: {node: '>= 0.4'} - typed-array-length@1.0.5: - resolution: {integrity: sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==} + typed-array-length@1.0.6: + resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} engines: {node: '>= 0.4'} unbox-primitive@1.0.2: @@ -979,12 +1005,12 @@ packages: which-module@2.0.1: resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} - which-pm@2.0.0: - resolution: {integrity: sha512-Lhs9Pmyph0p5n5Z3mVnN0yWcbQYUAD7rbQUiMsQxOJ3T57k7RFe35SUwWMf7dsbDZks1uOmw4AecB/JMDj3v/w==} + which-pm@2.2.0: + resolution: {integrity: sha512-MOiaDbA5ZZgUjkeMWM5EkJp4loW5ZRoa5bc3/aeMox/PJelMhE6t7S/mLuiY43DBupyxH+S0U1bTui9kWUlmsw==} engines: {node: '>=8.15'} - which-typed-array@1.1.14: - resolution: {integrity: sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==} + which-typed-array@1.1.15: + resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} engines: {node: '>= 0.4'} which@1.3.1: @@ -1031,28 +1057,27 @@ packages: snapshots: - '@babel/code-frame@7.23.5': + '@babel/code-frame@7.24.7': dependencies: - '@babel/highlight': 7.23.4 - chalk: 2.4.2 + '@babel/highlight': 7.24.7 + picocolors: 1.1.0 - '@babel/helper-validator-identifier@7.22.20': {} + '@babel/helper-validator-identifier@7.24.7': {} - '@babel/highlight@7.23.4': + '@babel/highlight@7.24.7': dependencies: - '@babel/helper-validator-identifier': 7.22.20 + '@babel/helper-validator-identifier': 7.24.7 chalk: 2.4.2 js-tokens: 4.0.0 + picocolors: 1.1.0 - /@babel/runtime@7.24.0: - resolution: {integrity: sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==} - engines: {node: '>=6.9.0'} + '@babel/runtime@7.25.6': dependencies: regenerator-runtime: 0.14.1 '@changesets/apply-release-plan@6.1.4': dependencies: - '@babel/runtime': 7.24.0 + '@babel/runtime': 7.25.6 '@changesets/config': 2.3.1 '@changesets/get-version-range-type': 0.3.2 '@changesets/git': 2.0.0 @@ -1064,16 +1089,16 @@ snapshots: outdent: 0.5.0 prettier: 2.8.8 resolve-from: 5.0.0 - semver: 7.6.1 + semver: 7.6.3 '@changesets/assemble-release-plan@5.2.4': dependencies: - '@babel/runtime': 7.24.0 + '@babel/runtime': 7.25.6 '@changesets/errors': 0.1.4 '@changesets/get-dependents-graph': 1.3.6 '@changesets/types': 5.2.1 '@manypkg/get-packages': 1.1.3 - semver: 7.6.1 + semver: 7.6.3 '@changesets/changelog-git@0.1.14': dependencies: @@ -1089,7 +1114,7 @@ snapshots: '@changesets/cli@2.26.2': dependencies: - '@babel/runtime': 7.24.0 + '@babel/runtime': 7.25.6 '@changesets/apply-release-plan': 6.1.4 '@changesets/assemble-release-plan': 5.2.4 '@changesets/changelog-git': 0.1.14 @@ -1116,9 +1141,9 @@ snapshots: meow: 6.1.1 outdent: 0.5.0 p-limit: 2.3.0 - preferred-pm: 3.1.3 + preferred-pm: 3.1.4 resolve-from: 5.0.0 - semver: 7.6.1 + semver: 7.6.3 spawndamnit: 2.0.0 term-size: 2.2.1 tty-table: 4.2.3 @@ -1131,7 +1156,7 @@ snapshots: '@changesets/types': 5.2.1 '@manypkg/get-packages': 1.1.3 fs-extra: 7.0.1 - micromatch: 4.0.5 + micromatch: 4.0.8 '@changesets/errors@0.1.4': dependencies: @@ -1143,7 +1168,7 @@ snapshots: '@manypkg/get-packages': 1.1.3 chalk: 2.4.2 fs-extra: 7.0.1 - semver: 7.6.1 + semver: 7.6.3 '@changesets/get-github-info@0.5.2': dependencies: @@ -1154,7 +1179,7 @@ snapshots: '@changesets/get-release-plan@3.0.17': dependencies: - '@babel/runtime': 7.24.0 + '@babel/runtime': 7.25.6 '@changesets/assemble-release-plan': 5.2.4 '@changesets/config': 2.3.1 '@changesets/pre': 1.0.14 @@ -1166,12 +1191,12 @@ snapshots: '@changesets/git@2.0.0': dependencies: - '@babel/runtime': 7.24.0 + '@babel/runtime': 7.25.6 '@changesets/errors': 0.1.4 '@changesets/types': 5.2.1 '@manypkg/get-packages': 1.1.3 is-subdir: 1.2.0 - micromatch: 4.0.5 + micromatch: 4.0.8 spawndamnit: 2.0.0 '@changesets/logger@0.0.5': @@ -1185,7 +1210,7 @@ snapshots: '@changesets/pre@1.0.14': dependencies: - '@babel/runtime': 7.24.0 + '@babel/runtime': 7.25.6 '@changesets/errors': 0.1.4 '@changesets/types': 5.2.1 '@manypkg/get-packages': 1.1.3 @@ -1193,7 +1218,7 @@ snapshots: '@changesets/read@0.5.9': dependencies: - '@babel/runtime': 7.24.0 + '@babel/runtime': 7.25.6 '@changesets/git': 2.0.0 '@changesets/logger': 0.0.5 '@changesets/parse': 0.3.16 @@ -1208,7 +1233,7 @@ snapshots: '@changesets/write@0.2.3': dependencies: - '@babel/runtime': 7.24.0 + '@babel/runtime': 7.25.6 '@changesets/types': 5.2.1 fs-extra: 7.0.1 human-id: 1.0.2 @@ -1216,14 +1241,14 @@ snapshots: '@manypkg/find-root@1.1.0': dependencies: - '@babel/runtime': 7.24.0 + '@babel/runtime': 7.25.6 '@types/node': 12.20.55 find-up: 4.1.0 fs-extra: 8.1.0 '@manypkg/get-packages@1.1.3': dependencies: - '@babel/runtime': 7.24.0 + '@babel/runtime': 7.25.6 '@changesets/types': 4.1.0 '@manypkg/find-root': 1.1.0 fs-extra: 8.1.0 @@ -1281,7 +1306,7 @@ snapshots: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.5 + es-abstract: 1.23.3 es-shim-unscopables: 1.0.2 arraybuffer.prototype.slice@1.0.3: @@ -1289,7 +1314,7 @@ snapshots: array-buffer-byte-length: 1.0.1 call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.5 + es-abstract: 1.23.3 es-errors: 1.3.0 get-intrinsic: 1.2.4 is-array-buffer: 3.0.4 @@ -1305,9 +1330,9 @@ snapshots: dependencies: is-windows: 1.0.2 - braces@3.0.2: + braces@3.0.3: dependencies: - fill-range: 7.0.1 + fill-range: 7.1.1 breakword@1.0.6: dependencies: @@ -1319,7 +1344,7 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 get-intrinsic: 1.2.4 - set-function-length: 1.2.1 + set-function-length: 1.2.2 camelcase-keys@6.2.2: dependencies: @@ -1389,6 +1414,24 @@ snapshots: csv-stringify: 5.6.5 stream-transform: 2.1.3 + data-view-buffer@1.0.1: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + data-view-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + data-view-byte-offset@1.0.0: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + dataloader@1.4.0: {} decamelize-keys@1.1.1: @@ -1433,51 +1476,54 @@ snapshots: dependencies: is-arrayish: 0.2.1 - /es-abstract@1.22.5: - resolution: {integrity: sha512-oW69R+4q2wG+Hc3KZePPZxOiisRIqfKBVo/HLx94QcJeWGU/8sZhCvc829rd1kS366vlJbzBfXf9yWwf0+Ko7w==} - engines: {node: '>= 0.4'} + es-abstract@1.23.3: dependencies: array-buffer-byte-length: 1.0.1 arraybuffer.prototype.slice: 1.0.3 available-typed-arrays: 1.0.7 call-bind: 1.0.7 + data-view-buffer: 1.0.1 + data-view-byte-length: 1.0.1 + data-view-byte-offset: 1.0.0 es-define-property: 1.0.0 es-errors: 1.3.0 + es-object-atoms: 1.0.0 es-set-tostringtag: 2.0.3 es-to-primitive: 1.2.1 function.prototype.name: 1.1.6 get-intrinsic: 1.2.4 get-symbol-description: 1.0.2 - globalthis: 1.0.3 + globalthis: 1.0.4 gopd: 1.0.1 has-property-descriptors: 1.0.2 has-proto: 1.0.3 has-symbols: 1.0.3 - hasown: 2.0.1 + hasown: 2.0.2 internal-slot: 1.0.7 is-array-buffer: 3.0.4 is-callable: 1.2.7 + is-data-view: 1.0.1 is-negative-zero: 2.0.3 is-regex: 1.1.4 is-shared-array-buffer: 1.0.3 is-string: 1.0.7 is-typed-array: 1.1.13 is-weakref: 1.0.2 - object-inspect: 1.13.1 + object-inspect: 1.13.2 object-keys: 1.1.1 object.assign: 4.1.5 regexp.prototype.flags: 1.5.2 - safe-array-concat: 1.1.0 + safe-array-concat: 1.1.2 safe-regex-test: 1.0.3 - string.prototype.trim: 1.2.8 - string.prototype.trimend: 1.0.7 - string.prototype.trimstart: 1.0.7 + string.prototype.trim: 1.2.9 + string.prototype.trimend: 1.0.8 + string.prototype.trimstart: 1.0.8 typed-array-buffer: 1.0.2 typed-array-byte-length: 1.0.1 typed-array-byte-offset: 1.0.2 - typed-array-length: 1.0.5 + typed-array-length: 1.0.6 unbox-primitive: 1.0.2 - which-typed-array: 1.1.14 + which-typed-array: 1.1.15 es-define-property@1.0.0: dependencies: @@ -1485,15 +1531,19 @@ snapshots: es-errors@1.3.0: {} + es-object-atoms@1.0.0: + dependencies: + es-errors: 1.3.0 + es-set-tostringtag@2.0.3: dependencies: get-intrinsic: 1.2.4 has-tostringtag: 1.0.2 - hasown: 2.0.1 + hasown: 2.0.2 es-shim-unscopables@1.0.2: dependencies: - hasown: 2.0.1 + hasown: 2.0.2 es-to-primitive@1.2.1: dependencies: @@ -1501,7 +1551,7 @@ snapshots: is-date-object: 1.0.5 is-symbol: 1.0.4 - escalade@3.1.2: {} + escalade@3.2.0: {} escape-string-regexp@1.0.5: {} @@ -1521,13 +1571,13 @@ snapshots: '@nodelib/fs.walk': 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 - micromatch: 4.0.5 + micromatch: 4.0.8 fastq@1.17.1: dependencies: reusify: 1.0.4 - fill-range@7.0.1: + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -1543,7 +1593,7 @@ snapshots: find-yarn-workspace-root2@1.2.16: dependencies: - micromatch: 4.0.5 + micromatch: 4.0.8 pkg-dir: 4.2.0 for-each@0.3.3: @@ -1568,7 +1618,7 @@ snapshots: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.5 + es-abstract: 1.23.3 functions-have-names: 1.2.3 functions-have-names@1.2.3: {} @@ -1581,7 +1631,7 @@ snapshots: function-bind: 1.1.2 has-proto: 1.0.3 has-symbols: 1.0.3 - hasown: 2.0.1 + hasown: 2.0.2 get-symbol-description@1.0.2: dependencies: @@ -1593,16 +1643,17 @@ snapshots: dependencies: is-glob: 4.0.3 - globalthis@1.0.3: + globalthis@1.0.4: dependencies: define-properties: 1.2.1 + gopd: 1.0.1 globby@11.1.0: dependencies: array-union: 2.1.0 dir-glob: 3.0.1 fast-glob: 3.3.2 - ignore: 5.3.1 + ignore: 5.3.2 merge2: 1.4.1 slash: 3.0.0 @@ -1634,7 +1685,7 @@ snapshots: dependencies: has-symbols: 1.0.3 - hasown@2.0.1: + hasown@2.0.2: dependencies: function-bind: 1.1.2 @@ -1654,16 +1705,15 @@ snapshots: dependencies: safer-buffer: 2.1.2 - ignore@5.3.1: {} + ignore@5.3.2: {} indent-string@4.0.0: {} internal-slot@1.0.7: dependencies: es-errors: 1.3.0 - hasown: 2.0.1 + hasown: 2.0.2 side-channel: 1.0.6 - dev: true is-array-buffer@3.0.4: dependencies: @@ -1687,9 +1737,13 @@ snapshots: dependencies: ci-info: 3.9.0 - is-core-module@2.13.1: + is-core-module@2.15.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.1: dependencies: - hasown: 2.0.1 + is-typed-array: 1.1.13 is-date-object@1.0.5: dependencies: @@ -1736,7 +1790,7 @@ snapshots: is-typed-array@1.1.13: dependencies: - which-typed-array: 1.1.14 + which-typed-array: 1.1.15 is-weakref@1.0.2: dependencies: @@ -1809,9 +1863,9 @@ snapshots: merge2@1.4.1: {} - micromatch@4.0.5: + micromatch@4.0.8: dependencies: - braces: 3.0.2 + braces: 3.0.3 picomatch: 2.3.1 min-indent@1.0.1: {} @@ -1835,7 +1889,7 @@ snapshots: semver: 5.7.2 validate-npm-package-license: 3.0.4 - object-inspect@1.13.1: {} + object-inspect@1.13.2: {} object-keys@1.1.1: {} @@ -1876,7 +1930,7 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.23.5 + '@babel/code-frame': 7.24.7 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -1887,6 +1941,8 @@ snapshots: path-type@4.0.0: {} + picocolors@1.1.0: {} + picomatch@2.3.1: {} pify@4.0.1: {} @@ -1897,12 +1953,12 @@ snapshots: possible-typed-array-names@1.0.0: {} - preferred-pm@3.1.3: + preferred-pm@3.1.4: dependencies: find-up: 5.0.0 find-yarn-workspace-root2: 1.2.16 path-exists: 4.0.0 - which-pm: 2.0.0 + which-pm: 2.2.0 prettier@2.8.8: {} @@ -1954,7 +2010,7 @@ snapshots: resolve@1.22.8: dependencies: - is-core-module: 2.13.1 + is-core-module: 2.15.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -1964,7 +2020,7 @@ snapshots: dependencies: queue-microtask: 1.2.3 - safe-array-concat@1.1.0: + safe-array-concat@1.1.2: dependencies: call-bind: 1.0.7 get-intrinsic: 1.2.4 @@ -1981,11 +2037,11 @@ snapshots: semver@5.7.2: {} - semver@7.6.1: {} + semver@7.6.3: {} set-blocking@2.0.0: {} - set-function-length@1.2.1: + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 es-errors: 1.3.0 @@ -2007,14 +2063,12 @@ snapshots: shebang-regex@1.0.0: {} - /side-channel@1.0.6: - resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} - engines: {node: '>= 0.4'} + side-channel@1.0.6: dependencies: call-bind: 1.0.7 es-errors: 1.3.0 get-intrinsic: 1.2.4 - object-inspect: 1.13.1 + object-inspect: 1.13.2 signal-exit@3.0.7: {} @@ -2037,16 +2091,16 @@ snapshots: spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.17 + spdx-license-ids: 3.0.20 spdx-exceptions@2.5.0: {} spdx-expression-parse@3.0.1: dependencies: spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.17 + spdx-license-ids: 3.0.20 - spdx-license-ids@3.0.17: {} + spdx-license-ids@3.0.20: {} sprintf-js@1.0.3: {} @@ -2060,26 +2114,24 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - string.prototype.trim@1.2.8: + string.prototype.trim@1.2.9: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.5 - dev: true + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 - string.prototype.trimend@1.0.7: + string.prototype.trimend@1.0.8: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.5 - dev: true + es-object-atoms: 1.0.0 - string.prototype.trimstart@1.0.7: + string.prototype.trimstart@1.0.8: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.5 - dev: true + es-object-atoms: 1.0.0 strip-ansi@6.0.1: dependencies: @@ -2154,7 +2206,7 @@ snapshots: has-proto: 1.0.3 is-typed-array: 1.1.13 - typed-array-length@1.0.5: + typed-array-length@1.0.6: dependencies: call-bind: 1.0.7 for-each: 0.3.3 @@ -2198,12 +2250,12 @@ snapshots: which-module@2.0.1: {} - which-pm@2.0.0: + which-pm@2.2.0: dependencies: load-yaml-file: 0.2.0 path-exists: 4.0.0 - which-typed-array@1.1.14: + which-typed-array@1.1.15: dependencies: available-typed-arrays: 1.0.7 call-bind: 1.0.7 @@ -2257,7 +2309,7 @@ snapshots: yargs@17.7.2: dependencies: cliui: 8.0.1 - escalade: 3.1.2 + escalade: 3.2.0 get-caller-file: 2.0.5 require-directory: 2.1.1 string-width: 4.2.3 diff --git a/testdata/scripts/health/default.txtar b/testdata/scripts/health/default.txtar index 1dbf6b8eb9..8480345e27 100644 --- a/testdata/scripts/health/default.txtar +++ b/testdata/scripts/health/default.txtar @@ -31,6 +31,7 @@ fj293fbBnlQ!f9vNs HTTPPort = $PORT -- out.txt -- +ok HeadReporter ok JobSpawner ok Mailbox.Monitor ok Mercury.WSRPCPool @@ -38,12 +39,20 @@ ok Mercury.WSRPCPool.CacheSet ok PipelineORM ok PipelineRunner ok PipelineRunner.BridgeCache -ok PromReporter ok TelemetryManager -- out.json -- { "data": [ + { + "type": "checks", + "id": "HeadReporter", + "attributes": { + "name": "HeadReporter", + "status": "passing", + "output": "" + } + }, { "type": "checks", "id": "JobSpawner", @@ -107,15 +116,6 @@ ok TelemetryManager "output": "" } }, - { - "type": "checks", - "id": "PromReporter", - "attributes": { - "name": "PromReporter", - "status": "passing", - "output": "" - } - }, { "type": "checks", "id": "TelemetryManager", @@ -126,4 +126,4 @@ ok TelemetryManager } } ] -} +} \ No newline at end of file diff --git a/testdata/scripts/health/help.txtar b/testdata/scripts/health/help.txtar index 07eb0509e7..68176f05a4 100644 --- a/testdata/scripts/health/help.txtar +++ b/testdata/scripts/health/help.txtar @@ -9,5 +9,6 @@ USAGE: chainlink health [command options] [arguments...] OPTIONS: - --json, -j json output + --failing, -f filter for failing services + --json, -j json output diff --git a/testdata/scripts/health/multi-chain.txtar b/testdata/scripts/health/multi-chain.txtar index 8178f8e821..bba3b3e111 100644 --- a/testdata/scripts/health/multi-chain.txtar +++ b/testdata/scripts/health/multi-chain.txtar @@ -15,6 +15,14 @@ cp stdout compact.json exec jq . compact.json cmp stdout out.json +exec chainlink --remote-node-url $NODEURL health -failing +cmp stdout out-unhealthy.txt + +exec chainlink --remote-node-url $NODEURL health -f -json +cp stdout compact.json +exec jq . compact.json +cmp stdout out-unhealthy.json + -- testdb.txt -- CL_DATABASE_URL -- testport.txt -- @@ -74,7 +82,9 @@ ok EVM.1.Txm ok EVM.1.Txm.BlockHistoryEstimator ok EVM.1.Txm.Broadcaster ok EVM.1.Txm.Confirmer +ok EVM.1.Txm.Finalizer ok EVM.1.Txm.WrappedEvmEstimator +ok HeadReporter ok JobSpawner ok Mailbox.Monitor ok Mercury.WSRPCPool @@ -82,11 +92,14 @@ ok Mercury.WSRPCPool.CacheSet ok PipelineORM ok PipelineRunner ok PipelineRunner.BridgeCache -ok PromReporter ok Solana.Bar ok StarkNet.Baz ok TelemetryManager +-- out-unhealthy.txt -- +! EVM.1.HeadTracker.HeadListener + Listener is not connected + -- out.json -- { "data": [ @@ -207,6 +220,15 @@ ok TelemetryManager "output": "" } }, + { + "type": "checks", + "id": "EVM.1.Txm.Finalizer", + "attributes": { + "name": "EVM.1.Txm.Finalizer", + "status": "passing", + "output": "" + } + }, { "type": "checks", "id": "EVM.1.Txm.WrappedEvmEstimator", @@ -216,6 +238,15 @@ ok TelemetryManager "output": "" } }, + { + "type": "checks", + "id": "HeadReporter", + "attributes": { + "name": "HeadReporter", + "status": "passing", + "output": "" + } + }, { "type": "checks", "id": "JobSpawner", @@ -279,15 +310,6 @@ ok TelemetryManager "output": "" } }, - { - "type": "checks", - "id": "PromReporter", - "attributes": { - "name": "PromReporter", - "status": "passing", - "output": "" - } - }, { "type": "checks", "id": "Solana.Bar", @@ -317,3 +339,17 @@ ok TelemetryManager } ] } +-- out-unhealthy.json -- +{ + "data": [ + { + "type": "checks", + "id": "EVM.1.HeadTracker.HeadListener", + "attributes": { + "name": "EVM.1.HeadTracker.HeadListener", + "status": "failing", + "output": "Listener is not connected" + } + } + ] +} diff --git a/testdata/scripts/node/db/help.txtar b/testdata/scripts/node/db/help.txtar index 4f2fe3e076..ccdf343178 100644 --- a/testdata/scripts/node/db/help.txtar +++ b/testdata/scripts/node/db/help.txtar @@ -20,4 +20,4 @@ COMMANDS: OPTIONS: --help, -h show help - \ No newline at end of file + diff --git a/testdata/scripts/node/validate/default.txtar b/testdata/scripts/node/validate/default.txtar index a2dea94e02..ad923919e0 100644 --- a/testdata/scripts/node/validate/default.txtar +++ b/testdata/scripts/node/validate/default.txtar @@ -18,7 +18,8 @@ ShutdownGracePeriod = '5s' FeedsManager = true LogPoller = false UICSAKeys = false -CCIP = false +CCIP = true +MultiFeedsManagers = false [Database] DefaultIdleInTxSessionTimeout = '1h0m0s' @@ -46,7 +47,7 @@ LeaseDuration = '10s' LeaseRefreshInterval = '1s' [TelemetryIngress] -UniConn = true +UniConn = false Logging = false BufferSize = 100 MaxBatchSize = 50 @@ -264,11 +265,40 @@ DeltaDial = '15s' DeltaReconcile = '1m0s' ListenAddresses = [] +[Capabilities.Dispatcher] +SupportedVersion = 1 +ReceiverBufferSize = 10000 + +[Capabilities.Dispatcher.RateLimit] +GlobalRPS = 800.0 +GlobalBurst = 1000 +PerSenderRPS = 10.0 +PerSenderBurst = 50 + [Capabilities.ExternalRegistry] Address = '' NetworkID = 'evm' ChainID = '1' +[Capabilities.GatewayConnector] +ChainIDForNodeKey = '' +NodeAddress = '' +DonID = '' +WSHandshakeTimeoutMillis = 0 +AuthMinChallengeLen = 0 +AuthTimestampToleranceSec = 0 + +[[Capabilities.GatewayConnector.Gateways]] +ID = '' +URL = '' + +[Telemetry] +Enabled = false +CACertFile = '' +Endpoint = '' +InsecureConnection = false +TraceSampleRatio = 0.01 + Invalid configuration: invalid secrets: 2 errors: - Database.URL: empty: must be provided and non-empty - Password.Keystore: empty: must be provided and non-empty diff --git a/testdata/scripts/node/validate/disk-based-logging-disabled.txtar b/testdata/scripts/node/validate/disk-based-logging-disabled.txtar index a6612cc8f1..2dd96b540c 100644 --- a/testdata/scripts/node/validate/disk-based-logging-disabled.txtar +++ b/testdata/scripts/node/validate/disk-based-logging-disabled.txtar @@ -62,7 +62,8 @@ ShutdownGracePeriod = '5s' FeedsManager = true LogPoller = false UICSAKeys = false -CCIP = false +CCIP = true +MultiFeedsManagers = false [Database] DefaultIdleInTxSessionTimeout = '1h0m0s' @@ -90,7 +91,7 @@ LeaseDuration = '10s' LeaseRefreshInterval = '1s' [TelemetryIngress] -UniConn = true +UniConn = false Logging = false BufferSize = 100 MaxBatchSize = 50 @@ -308,11 +309,40 @@ DeltaDial = '15s' DeltaReconcile = '1m0s' ListenAddresses = [] +[Capabilities.Dispatcher] +SupportedVersion = 1 +ReceiverBufferSize = 10000 + +[Capabilities.Dispatcher.RateLimit] +GlobalRPS = 800.0 +GlobalBurst = 1000 +PerSenderRPS = 10.0 +PerSenderBurst = 50 + [Capabilities.ExternalRegistry] Address = '' NetworkID = 'evm' ChainID = '1' +[Capabilities.GatewayConnector] +ChainIDForNodeKey = '' +NodeAddress = '' +DonID = '' +WSHandshakeTimeoutMillis = 0 +AuthMinChallengeLen = 0 +AuthTimestampToleranceSec = 0 + +[[Capabilities.GatewayConnector.Gateways]] +ID = '' +URL = '' + +[Telemetry] +Enabled = false +CACertFile = '' +Endpoint = '' +InsecureConnection = false +TraceSampleRatio = 0.01 + [[EVM]] ChainID = '1' AutoCreateKey = true @@ -331,6 +361,7 @@ MinContractPayment = '0.1 link' NonceAutoSync = true NoNewHeadsThreshold = '3m0s' OperatorFactoryAddress = '0x3E64Cd889482443324F91bFA9c84fE72A511f48A' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -395,6 +426,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [EVM.OCR] ContractConfirmations = 4 @@ -408,6 +440,9 @@ ObservationGracePeriod = '1s' [EVM.OCR2.Automation] GasLimit = 10500000 +[EVM.Workflow] +GasLimitDefault = 400000 + [[EVM.Nodes]] Name = 'fake' WSURL = 'wss://foo.bar/ws' diff --git a/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar b/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar index 79dcfa776d..bc097abbea 100644 --- a/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar +++ b/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar @@ -62,7 +62,8 @@ ShutdownGracePeriod = '5s' FeedsManager = true LogPoller = false UICSAKeys = false -CCIP = false +CCIP = true +MultiFeedsManagers = false [Database] DefaultIdleInTxSessionTimeout = '1h0m0s' @@ -90,7 +91,7 @@ LeaseDuration = '10s' LeaseRefreshInterval = '1s' [TelemetryIngress] -UniConn = true +UniConn = false Logging = false BufferSize = 100 MaxBatchSize = 50 @@ -308,11 +309,40 @@ DeltaDial = '15s' DeltaReconcile = '1m0s' ListenAddresses = [] +[Capabilities.Dispatcher] +SupportedVersion = 1 +ReceiverBufferSize = 10000 + +[Capabilities.Dispatcher.RateLimit] +GlobalRPS = 800.0 +GlobalBurst = 1000 +PerSenderRPS = 10.0 +PerSenderBurst = 50 + [Capabilities.ExternalRegistry] Address = '' NetworkID = 'evm' ChainID = '1' +[Capabilities.GatewayConnector] +ChainIDForNodeKey = '' +NodeAddress = '' +DonID = '' +WSHandshakeTimeoutMillis = 0 +AuthMinChallengeLen = 0 +AuthTimestampToleranceSec = 0 + +[[Capabilities.GatewayConnector.Gateways]] +ID = '' +URL = '' + +[Telemetry] +Enabled = false +CACertFile = '' +Endpoint = '' +InsecureConnection = false +TraceSampleRatio = 0.01 + [[EVM]] ChainID = '1' AutoCreateKey = true @@ -331,6 +361,7 @@ MinContractPayment = '0.1 link' NonceAutoSync = true NoNewHeadsThreshold = '3m0s' OperatorFactoryAddress = '0x3E64Cd889482443324F91bFA9c84fE72A511f48A' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -395,6 +426,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [EVM.OCR] ContractConfirmations = 4 @@ -408,6 +440,9 @@ ObservationGracePeriod = '1s' [EVM.OCR2.Automation] GasLimit = 10500000 +[EVM.Workflow] +GasLimitDefault = 400000 + [[EVM.Nodes]] Name = 'fake' WSURL = 'wss://foo.bar/ws' diff --git a/testdata/scripts/node/validate/disk-based-logging.txtar b/testdata/scripts/node/validate/disk-based-logging.txtar index efa27eec11..81872443b4 100644 --- a/testdata/scripts/node/validate/disk-based-logging.txtar +++ b/testdata/scripts/node/validate/disk-based-logging.txtar @@ -62,7 +62,8 @@ ShutdownGracePeriod = '5s' FeedsManager = true LogPoller = false UICSAKeys = false -CCIP = false +CCIP = true +MultiFeedsManagers = false [Database] DefaultIdleInTxSessionTimeout = '1h0m0s' @@ -90,7 +91,7 @@ LeaseDuration = '10s' LeaseRefreshInterval = '1s' [TelemetryIngress] -UniConn = true +UniConn = false Logging = false BufferSize = 100 MaxBatchSize = 50 @@ -308,11 +309,40 @@ DeltaDial = '15s' DeltaReconcile = '1m0s' ListenAddresses = [] +[Capabilities.Dispatcher] +SupportedVersion = 1 +ReceiverBufferSize = 10000 + +[Capabilities.Dispatcher.RateLimit] +GlobalRPS = 800.0 +GlobalBurst = 1000 +PerSenderRPS = 10.0 +PerSenderBurst = 50 + [Capabilities.ExternalRegistry] Address = '' NetworkID = 'evm' ChainID = '1' +[Capabilities.GatewayConnector] +ChainIDForNodeKey = '' +NodeAddress = '' +DonID = '' +WSHandshakeTimeoutMillis = 0 +AuthMinChallengeLen = 0 +AuthTimestampToleranceSec = 0 + +[[Capabilities.GatewayConnector.Gateways]] +ID = '' +URL = '' + +[Telemetry] +Enabled = false +CACertFile = '' +Endpoint = '' +InsecureConnection = false +TraceSampleRatio = 0.01 + [[EVM]] ChainID = '1' AutoCreateKey = true @@ -331,6 +361,7 @@ MinContractPayment = '0.1 link' NonceAutoSync = true NoNewHeadsThreshold = '3m0s' OperatorFactoryAddress = '0x3E64Cd889482443324F91bFA9c84fE72A511f48A' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -395,6 +426,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [EVM.OCR] ContractConfirmations = 4 @@ -408,6 +440,9 @@ ObservationGracePeriod = '1s' [EVM.OCR2.Automation] GasLimit = 10500000 +[EVM.Workflow] +GasLimitDefault = 400000 + [[EVM.Nodes]] Name = 'fake' WSURL = 'wss://foo.bar/ws' diff --git a/testdata/scripts/node/validate/invalid-ocr-p2p.txtar b/testdata/scripts/node/validate/invalid-ocr-p2p.txtar index 36e3324126..d3e56f97fb 100644 --- a/testdata/scripts/node/validate/invalid-ocr-p2p.txtar +++ b/testdata/scripts/node/validate/invalid-ocr-p2p.txtar @@ -47,7 +47,8 @@ ShutdownGracePeriod = '5s' FeedsManager = true LogPoller = false UICSAKeys = false -CCIP = false +CCIP = true +MultiFeedsManagers = false [Database] DefaultIdleInTxSessionTimeout = '1h0m0s' @@ -75,7 +76,7 @@ LeaseDuration = '10s' LeaseRefreshInterval = '1s' [TelemetryIngress] -UniConn = true +UniConn = false Logging = false BufferSize = 100 MaxBatchSize = 50 @@ -293,11 +294,40 @@ DeltaDial = '15s' DeltaReconcile = '1m0s' ListenAddresses = [] +[Capabilities.Dispatcher] +SupportedVersion = 1 +ReceiverBufferSize = 10000 + +[Capabilities.Dispatcher.RateLimit] +GlobalRPS = 800.0 +GlobalBurst = 1000 +PerSenderRPS = 10.0 +PerSenderBurst = 50 + [Capabilities.ExternalRegistry] Address = '' NetworkID = 'evm' ChainID = '1' +[Capabilities.GatewayConnector] +ChainIDForNodeKey = '' +NodeAddress = '' +DonID = '' +WSHandshakeTimeoutMillis = 0 +AuthMinChallengeLen = 0 +AuthTimestampToleranceSec = 0 + +[[Capabilities.GatewayConnector.Gateways]] +ID = '' +URL = '' + +[Telemetry] +Enabled = false +CACertFile = '' +Endpoint = '' +InsecureConnection = false +TraceSampleRatio = 0.01 + Invalid configuration: invalid configuration: P2P.V2.Enabled: invalid value (false): P2P required for OCR or OCR2. Please enable P2P or disable OCR/OCR2. -- err.txt -- diff --git a/testdata/scripts/node/validate/invalid.txtar b/testdata/scripts/node/validate/invalid.txtar index 6932eb5038..1ff74c2bd7 100644 --- a/testdata/scripts/node/validate/invalid.txtar +++ b/testdata/scripts/node/validate/invalid.txtar @@ -52,7 +52,8 @@ ShutdownGracePeriod = '5s' FeedsManager = true LogPoller = false UICSAKeys = false -CCIP = false +CCIP = true +MultiFeedsManagers = false [Database] DefaultIdleInTxSessionTimeout = '1h0m0s' @@ -80,7 +81,7 @@ LeaseDuration = '10s' LeaseRefreshInterval = '1s' [TelemetryIngress] -UniConn = true +UniConn = false Logging = false BufferSize = 100 MaxBatchSize = 50 @@ -298,11 +299,40 @@ DeltaDial = '15s' DeltaReconcile = '1m0s' ListenAddresses = [] +[Capabilities.Dispatcher] +SupportedVersion = 1 +ReceiverBufferSize = 10000 + +[Capabilities.Dispatcher.RateLimit] +GlobalRPS = 800.0 +GlobalBurst = 1000 +PerSenderRPS = 10.0 +PerSenderBurst = 50 + [Capabilities.ExternalRegistry] Address = '' NetworkID = 'evm' ChainID = '1' +[Capabilities.GatewayConnector] +ChainIDForNodeKey = '' +NodeAddress = '' +DonID = '' +WSHandshakeTimeoutMillis = 0 +AuthMinChallengeLen = 0 +AuthTimestampToleranceSec = 0 + +[[Capabilities.GatewayConnector.Gateways]] +ID = '' +URL = '' + +[Telemetry] +Enabled = false +CACertFile = '' +Endpoint = '' +InsecureConnection = false +TraceSampleRatio = 0.01 + [[EVM]] ChainID = '1' AutoCreateKey = true @@ -321,6 +351,7 @@ MinContractPayment = '0.1 link' NonceAutoSync = true NoNewHeadsThreshold = '3m0s' OperatorFactoryAddress = '0x3E64Cd889482443324F91bFA9c84fE72A511f48A' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -385,6 +416,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [EVM.OCR] ContractConfirmations = 4 @@ -398,6 +430,9 @@ ObservationGracePeriod = '1s' [EVM.OCR2.Automation] GasLimit = 10500000 +[EVM.Workflow] +GasLimitDefault = 400000 + [[EVM.Nodes]] Name = 'fake' WSURL = 'wss://foo.bar/ws' diff --git a/testdata/scripts/node/validate/valid.txtar b/testdata/scripts/node/validate/valid.txtar index 7074c82bf5..7ff9c4abc8 100644 --- a/testdata/scripts/node/validate/valid.txtar +++ b/testdata/scripts/node/validate/valid.txtar @@ -59,7 +59,8 @@ ShutdownGracePeriod = '5s' FeedsManager = true LogPoller = false UICSAKeys = false -CCIP = false +CCIP = true +MultiFeedsManagers = false [Database] DefaultIdleInTxSessionTimeout = '1h0m0s' @@ -87,7 +88,7 @@ LeaseDuration = '10s' LeaseRefreshInterval = '1s' [TelemetryIngress] -UniConn = true +UniConn = false Logging = false BufferSize = 100 MaxBatchSize = 50 @@ -305,11 +306,40 @@ DeltaDial = '15s' DeltaReconcile = '1m0s' ListenAddresses = [] +[Capabilities.Dispatcher] +SupportedVersion = 1 +ReceiverBufferSize = 10000 + +[Capabilities.Dispatcher.RateLimit] +GlobalRPS = 800.0 +GlobalBurst = 1000 +PerSenderRPS = 10.0 +PerSenderBurst = 50 + [Capabilities.ExternalRegistry] Address = '' NetworkID = 'evm' ChainID = '1' +[Capabilities.GatewayConnector] +ChainIDForNodeKey = '' +NodeAddress = '' +DonID = '' +WSHandshakeTimeoutMillis = 0 +AuthMinChallengeLen = 0 +AuthTimestampToleranceSec = 0 + +[[Capabilities.GatewayConnector.Gateways]] +ID = '' +URL = '' + +[Telemetry] +Enabled = false +CACertFile = '' +Endpoint = '' +InsecureConnection = false +TraceSampleRatio = 0.01 + [[EVM]] ChainID = '1' AutoCreateKey = true @@ -328,6 +358,7 @@ MinContractPayment = '0.1 link' NonceAutoSync = true NoNewHeadsThreshold = '3m0s' OperatorFactoryAddress = '0x3E64Cd889482443324F91bFA9c84fE72A511f48A' +LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 FinalizedBlockOffset = 0 @@ -392,6 +423,7 @@ NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' EnforceRepeatableRead = false DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' [EVM.OCR] ContractConfirmations = 4 @@ -405,6 +437,9 @@ ObservationGracePeriod = '1s' [EVM.OCR2.Automation] GasLimit = 10500000 +[EVM.Workflow] +GasLimitDefault = 400000 + [[EVM.Nodes]] Name = 'fake' WSURL = 'wss://foo.bar/ws' diff --git a/testdata/scripts/node/validate/warnings.txtar b/testdata/scripts/node/validate/warnings.txtar index 683ef2519b..ac8489f324 100644 --- a/testdata/scripts/node/validate/warnings.txtar +++ b/testdata/scripts/node/validate/warnings.txtar @@ -41,7 +41,8 @@ ShutdownGracePeriod = '5s' FeedsManager = true LogPoller = false UICSAKeys = false -CCIP = false +CCIP = true +MultiFeedsManagers = false [Database] DefaultIdleInTxSessionTimeout = '1h0m0s' @@ -69,7 +70,7 @@ LeaseDuration = '10s' LeaseRefreshInterval = '1s' [TelemetryIngress] -UniConn = true +UniConn = false Logging = false BufferSize = 100 MaxBatchSize = 50 @@ -287,11 +288,40 @@ DeltaDial = '15s' DeltaReconcile = '1m0s' ListenAddresses = [] +[Capabilities.Dispatcher] +SupportedVersion = 1 +ReceiverBufferSize = 10000 + +[Capabilities.Dispatcher.RateLimit] +GlobalRPS = 800.0 +GlobalBurst = 1000 +PerSenderRPS = 10.0 +PerSenderBurst = 50 + [Capabilities.ExternalRegistry] Address = '' NetworkID = 'evm' ChainID = '1' +[Capabilities.GatewayConnector] +ChainIDForNodeKey = '' +NodeAddress = '' +DonID = '' +WSHandshakeTimeoutMillis = 0 +AuthMinChallengeLen = 0 +AuthTimestampToleranceSec = 0 + +[[Capabilities.GatewayConnector.Gateways]] +ID = '' +URL = '' + +[Telemetry] +Enabled = false +CACertFile = '' +Endpoint = '' +InsecureConnection = false +TraceSampleRatio = 0.01 + # Configuration warning: Tracing.TLSCertPath: invalid value (something): must be empty when Tracing.Mode is 'unencrypted' Valid configuration. diff --git a/tools/bin/goreleaser_utils b/tools/bin/goreleaser_utils index 4e1b3ffc4d..979204d1e3 100755 --- a/tools/bin/goreleaser_utils +++ b/tools/bin/goreleaser_utils @@ -25,7 +25,7 @@ _get_arch() { _get_wasmvm_lib_path() { local -r platform="$1" local -r arch="$2" - wasmvm_dir=$(go list -json -m all | jq -r '. | select(.Path == "github.com/CosmWasm/wasmvm") | .Dir') + wasmvm_dir=$(go list -json -m github.com/CosmWasm/wasmvm | jq -r '.Dir') shared_lib_dir="$wasmvm_dir/internal/api" lib_name="libwasmvm" if [ "$platform" == "darwin" ]; then @@ -68,11 +68,13 @@ before_hook() { install_remote_plugins "linux" "amd64" "$gobin"/linux_amd64/ mkdir -p "$lib_path/linux_amd64/plugins" cp "$gobin"/linux_amd64/chainlink* "$lib_path/linux_amd64/plugins" + cp "$gobin"/chainlink* "$lib_path/linux_amd64/plugins" install_local_plugins "linux" "arm64" "$gobin"/linux_arm64/ install_remote_plugins "linux" "arm64" "$gobin"/linux_arm64/ mkdir -p "$lib_path/linux_arm64/plugins" cp "$gobin"/linux_arm64/chainlink* "$lib_path/linux_arm64/plugins" + cp "$gobin"/chainlink* "$lib_path/linux_arm64/plugins" } install_local_plugins() { @@ -94,10 +96,10 @@ get_remote_plugin_paths() { ) for plugin in "${plugins[@]}"; do - plugin_dep_name=$(echo "$plugin" | cut -d"|" -f1) - plugin_main=$(echo "$plugin" | cut -d"|" -f2) + plugin_dep_name=$(echo "$plugin" | cut -d"|" -f1) + plugin_main=$(echo "$plugin" | cut -d"|" -f2) - full_plugin_path=$(go list -m -f "{{.Dir}}" "$plugin_dep_name")"$plugin_main" + full_plugin_path=$(go list -m -f "{{.Dir}}" "$plugin_dep_name")"$plugin_main" echo "$full_plugin_path" done } diff --git a/tools/bin/modgraph b/tools/bin/modgraph index 4d8ad108c6..4080e53abe 100755 --- a/tools/bin/modgraph +++ b/tools/bin/modgraph @@ -11,11 +11,7 @@ flowchart LR chainlink-cosmos chainlink-solana chainlink-starknet/relayer - subgraph chainlink-integrations - direction LR - chainlink-integrations/evm/relayer - chainlink-integrations/common - end + chainlink-evm end subgraph products @@ -27,8 +23,13 @@ flowchart LR chainlink-vrf end + subgraph tdh2 + tdh2/go/tdh2 + tdh2/go/ocr2/decryptionplugin + end + classDef outline stroke-dasharray:6,fill:none; - class chains,products outline + class chains,products,tdh2 outline " go mod graph | \ # org only diff --git a/tools/ci/gorace_test b/tools/ci/gorace_test deleted file mode 100755 index ebf0b89281..0000000000 --- a/tools/ci/gorace_test +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash - -set -e -GORACE="halt_on_error=1" go test -v -race -parallel 2 -p 1 chainlink/core/internal chainlink/core/services diff --git a/tools/ci/init_gcloud b/tools/ci/init_gcloud deleted file mode 100755 index f1ebb12b59..0000000000 --- a/tools/ci/init_gcloud +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -if [ -z "$GCLOUD_SERVICE_KEY" ] -then - echo "Skipping gcloud initiation because no service key is set" - exit 0 -else - echo $GCLOUD_SERVICE_KEY > ${HOME}/gcloud-service-key.json - gcloud auth activate-service-account --key-file=${HOME}/gcloud-service-key.json - gcloud --quiet config set project ${GOOGLE_PROJECT_ID} - gcloud --quiet config set compute/zone ${GOOGLE_COMPUTE_ZONE} -fi diff --git a/tools/docker/README.md b/tools/docker/README.md index e0ccadd68c..527951e64c 100644 --- a/tools/docker/README.md +++ b/tools/docker/README.md @@ -23,6 +23,7 @@ cd tools/docker ### Compose script env vars The following env vars are used for the compose script : +- `WITH_OBSERVABILITY=true` to enable grafana, prometheus and alertmanager - `GETH_MODE=true` to use geth instead of parity - `CHAIN_ID=` to specify the chainID (default is 34055 for parity and 1337 for geth) - `HTTPURL=` to specify the RPC node HTTP url (default is set if you use geth or parity) @@ -35,6 +36,10 @@ for example : CHAIN_ID=11155111 WSURL=wss://eth.sepolia HTTPURL=https://eth.sepolia ./compose dev ``` +```sh +WITH_OBSERVABILITY=true ./compose up +``` + ## Dev Will run one node with a postgres database and by default a devnet RPC node that can be either geth or parity. diff --git a/tools/docker/alertmanager/alertmanager.yml b/tools/docker/alertmanager/alertmanager.yml new file mode 100644 index 0000000000..7521fd768b --- /dev/null +++ b/tools/docker/alertmanager/alertmanager.yml @@ -0,0 +1,15 @@ + +route: + receiver: 'mail' + repeat_interval: 4h + group_by: [ alertname ] + + +receivers: + - name: 'mail' + email_configs: + - smarthost: 'smtp.gmail.com:465' + auth_username: 'your_mail@gmail.com' + auth_password: "" + from: 'your_mail@gmail.com' + to: 'some_mail@gmail.com' \ No newline at end of file diff --git a/tools/docker/compose b/tools/docker/compose index 0e754a5ffc..abaf377333 100755 --- a/tools/docker/compose +++ b/tools/docker/compose @@ -16,7 +16,14 @@ if [ -z "$WSURL" ] && [ -z "$HTTPURL" ]; then fi fi -base="docker-compose $base_files" +args="node" + +if [ "$WITH_OBSERVABILITY" ]; then + base_files="$base_files -f docker-compose.observability.yaml" + args="$args grafana" +fi + +base="docker compose $base_files" dev="$base -f docker-compose.dev.yaml" configure() { @@ -41,7 +48,7 @@ configure() { clean_docker() { $base down -v --remove-orphans $dev down -v --remove-orphans - rm -f config.toml + rm -rf config.toml } usage() { @@ -71,7 +78,7 @@ logs) dev) configure $dev build - $dev up -d node + $dev up -d $args $dev watch --no-up node ;; connect) diff --git a/tools/docker/docker-compose.observability.yaml b/tools/docker/docker-compose.observability.yaml new file mode 100644 index 0000000000..c82c050d58 --- /dev/null +++ b/tools/docker/docker-compose.observability.yaml @@ -0,0 +1,44 @@ +services: + prometheus: + image: prom/prometheus:main + container_name: chainlink-prometheus + volumes: + - ./prometheus/:/etc/prometheus/ + - prometheus_data:/prometheus + command: + - '--config.file=/etc/prometheus/prometheus.yaml' + - '--storage.tsdb.path=/prometheus' + - '--web.console.libraries=/usr/share/prometheus/console_libraries' + - '--web.console.templates=/usr/share/prometheus/consoles' + restart: always + ports: + - 9090:9090 + grafana: + image: grafana/grafana:10.4.3 + user: "472" + depends_on: + - prometheus + - alertmanager + ports: + - 3000:3000 + volumes: + - grafana_data:/var/lib/grafana + - ./grafana/provisioning/:/etc/grafana/provisioning/ + env_file: + - ./grafana/config.monitoring + restart: always + alertmanager: + image: prom/alertmanager:main + container_name: chainlink-alertmanager + volumes: + - "./alertmanager:/config" + - alertmanager-data:/data + command: --config.file=/config/alertmanager.yml --log.level=debug + restart: always + ports: + - 9093:9093 + +volumes: + alertmanager-data: {} + prometheus_data: {} + grafana_data: {} diff --git a/tools/docker/grafana/config.monitoring b/tools/docker/grafana/config.monitoring new file mode 100644 index 0000000000..a2b009166b --- /dev/null +++ b/tools/docker/grafana/config.monitoring @@ -0,0 +1,2 @@ +GF_SECURITY_ADMIN_PASSWORD=foobar +GF_USERS_ALLOW_SIGN_UP=false \ No newline at end of file diff --git a/tools/docker/grafana/provisioning/datasources/datasource.yml b/tools/docker/grafana/provisioning/datasources/datasource.yml new file mode 100644 index 0000000000..f57418b29f --- /dev/null +++ b/tools/docker/grafana/provisioning/datasources/datasource.yml @@ -0,0 +1,59 @@ +# config file version +apiVersion: 1 + +# list of datasources that should be deleted from the database +deleteDatasources: + - name: Prometheus + orgId: 1 + +# list of datasources to insert/update depending +# whats available in the database +datasources: + # name of the datasource. Required + - name: Prometheus + # datasource type. Required + type: prometheus + # access mode. direct or proxy. Required + access: proxy + # org id. will default to orgId 1 if not specified + orgId: 1 + # url + url: http://prometheus:9090 + # database password, if used + password: + # database user, if used + user: + # database name, if used + database: + # enable/disable basic auth + basicAuth: false + # basic auth username, if used + basicAuthUser: + # basic auth password, if used + basicAuthPassword: + # enable/disable with credentials headers + withCredentials: + # mark as default datasource. Max one per org + isDefault: true + # fields that will be converted to json and stored in json_data + jsonData: + graphiteVersion: "1.1" + tlsAuth: false + tlsAuthWithCACert: false + # json object of data that will be encrypted. + secureJsonData: + tlsCACert: "..." + tlsClientCert: "..." + tlsClientKey: "..." + version: 1 + # allow users to edit datasources from the UI. + editable: true + - name: Alertmanager + type: alertmanager + url: http://alertmanager:9093 + access: proxy + jsonData: + # Valid options for implementation include mimir, cortex and prometheus + implementation: prometheus + # Whether or not Grafana should send alert instances to this Alertmanager + handleGrafanaManagedAlerts: false \ No newline at end of file diff --git a/tools/docker/prometheus/prometheus.yaml b/tools/docker/prometheus/prometheus.yaml new file mode 100644 index 0000000000..96ca106493 --- /dev/null +++ b/tools/docker/prometheus/prometheus.yaml @@ -0,0 +1,13 @@ +global: + scrape_interval: 5s +scrape_configs: + - job_name: 'local_scrape' + scrape_interval: 1s + static_configs: + - targets: ['chainlink-node:6688', 'chainlink-node-2:6688'] + metrics_path: '/metrics' +alerting: + alertmanagers: + - scheme: http + static_configs: + - targets: ['alertmanager:9093'] \ No newline at end of file diff --git a/tools/flakeytests/cmd/runner/main.go b/tools/flakeytests/cmd/runner/main.go index 0e1be5e7d3..c20fef17b1 100644 --- a/tools/flakeytests/cmd/runner/main.go +++ b/tools/flakeytests/cmd/runner/main.go @@ -24,7 +24,6 @@ func main() { grafanaHost := flag.String("grafana_host", "", "grafana host URL") grafanaAuth := flag.String("grafana_auth", "", "grafana basic auth for Loki API") - grafanaOrgID := flag.String("grafana_org_id", "", "grafana org ID") command := flag.String("command", "", "test command being rerun; used to tag metrics") ghSHA := flag.String("gh_sha", "", "commit sha for which we're rerunning tests") ghEventPath := flag.String("gh_event_path", "", "path to associated gh event") @@ -46,10 +45,6 @@ func main() { log.Fatal("Error re-running flakey tests: `grafana_auth` is required") } - if *grafanaOrgID == "" { - log.Fatal("Error re-running flakey tests: `grafana_org_id` is required") - } - if *command == "" { log.Fatal("Error re-running flakey tests: `command` is required") } @@ -68,7 +63,7 @@ func main() { } meta := flakeytests.GetGithubMetadata(*ghRepo, *ghEventName, *ghSHA, *ghEventPath, *ghRunID, runAttempt) - rep := flakeytests.NewLokiReporter(*grafanaHost, *grafanaAuth, *grafanaOrgID, *command, meta) + rep := flakeytests.NewLokiReporter(*grafanaHost, *grafanaAuth, *command, meta) r := flakeytests.NewRunner(readers, rep, numReruns) err := r.Run(ctx) if err != nil { diff --git a/tools/flakeytests/reporter.go b/tools/flakeytests/reporter.go index b3c8ad6da0..b7c7f66698 100644 --- a/tools/flakeytests/reporter.go +++ b/tools/flakeytests/reporter.go @@ -62,7 +62,6 @@ type Context struct { type LokiReporter struct { host string auth string - orgId string command string now func() time.Time ctx Context @@ -156,7 +155,6 @@ func (l *LokiReporter) makeRequest(ctx context.Context, pushReq pushRequest) err fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(l.auth))), ) req.Header.Add("Content-Type", "application/json") - req.Header.Add("X-Scope-OrgID", l.orgId) resp, err := http.DefaultClient.Do(req) if err != nil { return err @@ -179,6 +177,6 @@ func (l *LokiReporter) Report(ctx context.Context, report *Report) error { return l.makeRequest(ctx, pushReq) } -func NewLokiReporter(host, auth, orgId, command string, ctx Context) *LokiReporter { - return &LokiReporter{host: host, auth: auth, orgId: orgId, command: command, now: time.Now, ctx: ctx} +func NewLokiReporter(host, auth, command string, ctx Context) *LokiReporter { + return &LokiReporter{host: host, auth: auth, command: command, now: time.Now, ctx: ctx} } diff --git a/tools/flakeytests/reporter_test.go b/tools/flakeytests/reporter_test.go index c2b03e26dc..15650fc7bd 100644 --- a/tools/flakeytests/reporter_test.go +++ b/tools/flakeytests/reporter_test.go @@ -19,7 +19,7 @@ func TestMakeRequest_SingleTest(t *testing.T) { }, }, } - lr := &LokiReporter{auth: "bla", host: "bla", orgId: "bla", command: "go_core_tests", now: func() time.Time { return now }} + lr := &LokiReporter{auth: "bla", host: "bla", command: "go_core_tests", now: func() time.Time { return now }} pr, err := lr.createRequest(r) require.NoError(t, err) assert.Len(t, pr.Streams, 1) @@ -41,7 +41,7 @@ func TestMakeRequest_MultipleTests(t *testing.T) { }, }, } - lr := &LokiReporter{auth: "bla", host: "bla", orgId: "bla", command: "go_core_tests", now: func() time.Time { return now }} + lr := &LokiReporter{auth: "bla", host: "bla", command: "go_core_tests", now: func() time.Time { return now }} pr, err := lr.createRequest(r) require.NoError(t, err) assert.Len(t, pr.Streams, 1) @@ -58,7 +58,7 @@ func TestMakeRequest_NoTests(t *testing.T) { now := time.Now() ts := fmt.Sprintf("%d", now.UnixNano()) r := NewReport() - lr := &LokiReporter{auth: "bla", host: "bla", orgId: "bla", command: "go_core_tests", now: func() time.Time { return now }} + lr := &LokiReporter{auth: "bla", host: "bla", command: "go_core_tests", now: func() time.Time { return now }} pr, err := lr.createRequest(r) require.NoError(t, err) assert.Len(t, pr.Streams, 1) @@ -72,7 +72,7 @@ func TestMakeRequest_WithContext(t *testing.T) { now := time.Now() ts := fmt.Sprintf("%d", now.UnixNano()) r := NewReport() - lr := &LokiReporter{auth: "bla", host: "bla", orgId: "bla", command: "go_core_tests", now: func() time.Time { return now }, ctx: Context{CommitSHA: "42"}} + lr := &LokiReporter{auth: "bla", host: "bla", command: "go_core_tests", now: func() time.Time { return now }, ctx: Context{CommitSHA: "42"}} pr, err := lr.createRequest(r) require.NoError(t, err) assert.Len(t, pr.Streams, 1) @@ -95,7 +95,7 @@ func TestMakeRequest_Panics(t *testing.T) { "core/assets": 1, }, } - lr := &LokiReporter{auth: "bla", host: "bla", orgId: "bla", command: "go_core_tests", now: func() time.Time { return now }} + lr := &LokiReporter{auth: "bla", host: "bla", command: "go_core_tests", now: func() time.Time { return now }} pr, err := lr.createRequest(r) require.NoError(t, err) assert.Len(t, pr.Streams, 1) diff --git a/tools/txtar/visitor.go b/tools/txtar/visitor.go index f945ac3523..d96cdc6f0a 100644 --- a/tools/txtar/visitor.go +++ b/tools/txtar/visitor.go @@ -13,13 +13,13 @@ const ( NoRecurse RecurseOpt = false ) -type TxtarDirVisitor struct { +type DirVisitor struct { rootDir string cb func(path string) error recurse RecurseOpt } -func (d *TxtarDirVisitor) Walk() error { +func (d *DirVisitor) Walk() error { return filepath.WalkDir(d.rootDir, func(path string, de fs.DirEntry, err error) error { if err != nil { return err @@ -52,7 +52,7 @@ func (d *TxtarDirVisitor) Walk() error { }) } -func (d *TxtarDirVisitor) isRootDir(de fs.DirEntry) (bool, error) { +func (d *DirVisitor) isRootDir(de fs.DirEntry) (bool, error) { fi, err := os.Stat(d.rootDir) if err != nil { return false, err @@ -65,8 +65,8 @@ func (d *TxtarDirVisitor) isRootDir(de fs.DirEntry) (bool, error) { return os.SameFile(fi, fi2), nil } -func NewDirVisitor(rootDir string, recurse RecurseOpt, cb func(path string) error) *TxtarDirVisitor { - return &TxtarDirVisitor{ +func NewDirVisitor(rootDir string, recurse RecurseOpt, cb func(path string) error) *DirVisitor { + return &DirVisitor{ rootDir: rootDir, cb: cb, recurse: recurse,