diff --git a/.changeset/brave-frogs-greet.md b/.changeset/brave-frogs-greet.md new file mode 100644 index 00000000000..907e8f1ee75 --- /dev/null +++ b/.changeset/brave-frogs-greet.md @@ -0,0 +1,5 @@ +--- +"chainlink": minor +--- + +update plugin and evm chainwriter to remove evmConfig dependency #updated diff --git a/.changeset/small-gifts-play.md b/.changeset/small-gifts-play.md new file mode 100644 index 00000000000..a9486a033ef --- /dev/null +++ b/.changeset/small-gifts-play.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +#updated Operator UI to support StreamSpec job definition diff --git a/.changeset/strange-radios-teach.md b/.changeset/strange-radios-teach.md new file mode 100644 index 00000000000..ea84bfbf359 --- /dev/null +++ b/.changeset/strange-radios-teach.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +Merge raw configs correctly #bugfix diff --git a/.changeset/swift-fireants-compare.md b/.changeset/swift-fireants-compare.md new file mode 100644 index 00000000000..b11c516e7c3 --- /dev/null +++ b/.changeset/swift-fireants-compare.md @@ -0,0 +1,5 @@ +--- +"chainlink": minor +--- + +Add CSA authentication support to Beholder #added diff --git a/.changeset/tender-colts-return.md b/.changeset/tender-colts-return.md new file mode 100644 index 00000000000..16c0b2da1ff --- /dev/null +++ b/.changeset/tender-colts-return.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +#updated celo alfajores config diff --git a/.changeset/thick-chefs-deliver.md b/.changeset/thick-chefs-deliver.md new file mode 100644 index 00000000000..67d68d22792 --- /dev/null +++ b/.changeset/thick-chefs-deliver.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +Enable AutoPurge feature on all affected chains #nops diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 85cf5d7f7ed..d6b47cef2ae 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -35,6 +35,10 @@ /core/services/ccip @smartcontractkit/ccip /core/services/ocr2/plugins/ccip @smartcontractkit/ccip +# CCIP +/core/services/ccip @smartcontractkit/ccip +/core/services/ocr2/plugins/ccip @smartcontractkit/ccip + # VRF-related services /core/services/vrf @smartcontractkit/dev-services @smartcontractkit/core /core/services/blockhashstore @smartcontractkit/dev-services @smartcontractkit/core diff --git a/.github/actions/setup-go/action.yml b/.github/actions/setup-go/action.yml index bc52b5498ab..b5519fbad0e 100644 --- a/.github/actions/setup-go/action.yml +++ b/.github/actions/setup-go/action.yml @@ -85,7 +85,7 @@ runs: - uses: actions/cache/restore@v4.1.1 name: Cache Go Build Outputs (restore) # For certain events, we don't necessarily want to create a build cache, but we will benefit from restoring from one. - if: ${{ inputs.only-modules == 'false' && (github.event == 'merge_group' || inputs.restore-build-cache-only == 'true') }} + if: ${{ inputs.only-modules == 'false' && (github.event_name == 'merge_group' || inputs.restore-build-cache-only == 'true') }} with: path: | ${{ steps.go-cache-dir.outputs.gobuildcache }} @@ -98,7 +98,7 @@ runs: - uses: actions/cache@v4.1.1 # don't save cache on merge queue events - if: ${{ inputs.only-modules == 'false' && (github.event != 'merge_group' && inputs.restore-build-cache-only == 'false') }} + if: ${{ inputs.only-modules == 'false' && (github.event_name != 'merge_group' && inputs.restore-build-cache-only == 'false') }} name: Cache Go Build Outputs with: path: | diff --git a/.github/workflows/ci-core.yml b/.github/workflows/ci-core.yml index 3d7050197a6..3a32d7e12c7 100644 --- a/.github/workflows/ci-core.yml +++ b/.github/workflows/ci-core.yml @@ -166,7 +166,7 @@ jobs: uses: ./.github/actions/setup-go with: # race/fuzz tests don't benefit repeated caching, so restore from develop's build cache - restore-build-cache-only: ${{ matrix.type.cmd == 'go_core_race_tests' || matrix.type.cmd == 'go_core_fuzz' }} + restore-build-cache-only: ${{ matrix.type.cmd == 'go_core_fuzz' }} build-cache-version: ${{ matrix.type.cmd }} - name: Replace chainlink-evm deps @@ -220,12 +220,13 @@ jobs: go install ./pkg/chainlink/cmd/chainlink-starknet popd - - name: Increase Race Timeout - # Increase race timeout for scheduled runs only + - name: Increase Timeouts for Fuzz/Race + # Increase timeouts for scheduled runs only if: ${{ github.event.schedule != '' && needs.filter.outputs.should-run-ci-core == 'true' }} run: | echo "TIMEOUT=10m" >> $GITHUB_ENV echo "COUNT=50" >> $GITHUB_ENV + echo "FUZZ_TIMEOUT_MINUTES=10">> $GITHUB_ENV - name: Install gotestloghelper if: ${{ needs.filter.outputs.should-run-ci-core == 'true' }} diff --git a/.github/workflows/delete-caches.yml b/.github/workflows/delete-caches.yml new file mode 100644 index 00000000000..870b6b6d086 --- /dev/null +++ b/.github/workflows/delete-caches.yml @@ -0,0 +1,42 @@ +name: Cleanup Caches + +# See: +# https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows#force-deleting-cache-entries + +on: + pull_request: + types: + - closed + +jobs: + cleanup-branch-caches: + runs-on: ubuntu-latest + permissions: + # `actions:write` permission is required to delete caches + # See also: https://docs.github.com/en/rest/actions/cache?apiVersion=2022-11-28#delete-a-github-actions-cache-for-a-repository-using-a-cache-id + actions: write + contents: read + steps: + - name: Check out code + uses: actions/checkout@v4.1.2 + + - name: Cleanup Branch Caches + run: | + gh extension install actions/gh-actions-cache + + REPO=${{ github.repository }} + BRANCH=refs/pull/${{ github.event.pull_request.number }}/merge + + echo "Fetching list of cache key" + cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH | cut -f 1 ) + + ## Setting this to not fail the workflow while deleting cache keys. + set +e + echo "Deleting caches..." + for cacheKey in $cacheKeysForPR + do + gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm + done + echo "Done" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 5d9f8221d4d..52c8e8f71b2 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -210,7 +210,7 @@ jobs: contents: read 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@ca50645120f0f07ed8c0df08175a5d6b3e4ac454 #ctf-run-tests@0.1.2 + uses: smartcontractkit/.github/.github/workflows/run-e2e-tests.yml@5412507526722a7b1c5d719fa686eed5a1bc4035 #ctf-run-tests@0.2.0 with: workflow_name: Run Core E2E Tests For PR chainlink_version: ${{ inputs.evm-ref || github.sha }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 52b5cd967ce..544cf09e5b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog Chainlink Core -## 2.18.0 - UNRELEASED +## 2.18.0 - 2024-11-01 ### Minor Changes @@ -90,12 +90,10 @@ - [#14693](https://github.com/smartcontractkit/chainlink/pull/14693) [`03df8989e8`](https://github.com/smartcontractkit/chainlink/commit/03df8989e8e7549afb05bb49c765c0c07db8669e) Thanks [@ChrisAmora](https://github.com/ChrisAmora)! - #updated Consume Feeds Manager WSRPC protos from Chainlink Protos Repository. - [#14694](https://github.com/smartcontractkit/chainlink/pull/14694) [`e9b3397f46`](https://github.com/smartcontractkit/chainlink/commit/e9b3397f465e26209c8c8ccfed66aa9595f8246b) Thanks [@mateusz-sekara](https://github.com/mateusz-sekara)! - Adjustments for usdc reader tests #internal - [#14624](https://github.com/smartcontractkit/chainlink/pull/14624) [`be774f00a9`](https://github.com/smartcontractkit/chainlink/commit/be774f00a961d9a7361d9ae5b10c97996f7ab164) Thanks [@mateusz-sekara](https://github.com/mateusz-sekara)! - Registering USDC/CCTP events in the ChainReader during oracle creation #internal -- [#14534](https://github.com/smartcontractkit/chainlink/pull/14534) [`de268e98b8`](https://github.com/smartcontractkit/chainlink/commit/de268e98b8d68a284e1260297925b91c5d2411bc) Thanks [@huangzhen1997](https://github.com/huangzhen1997)! - - register polling subscription to avoid subscription leaking when rpc client gets closed. - +- [#14534](https://github.com/smartcontractkit/chainlink/pull/14534) [`de268e98b8`](https://github.com/smartcontractkit/chainlink/commit/de268e98b8d68a284e1260297925b91c5d2411bc) Thanks [@huangzhen1997](https://github.com/huangzhen1997)! + - register polling subscription to avoid subscription leaking when rpc client gets closed. - add a temporary special treatment for SubscribeNewHead before we replace it with SubscribeToHeads. Add a goroutine that forwards new head from poller to caller channel. - - fix a deadlock in poller, by using a new lock for subs slice in rpc client. - #bugfix - + - fix a deadlock in poller, by using a new lock for subs slice in rpc client. #bugfix - [#14656](https://github.com/smartcontractkit/chainlink/pull/14656) [`004a0de233`](https://github.com/smartcontractkit/chainlink/commit/004a0de2337b0312558ae7c045e7fc2fb4a05916) Thanks [@dimkouv](https://github.com/dimkouv)! - #added graceful shutdown for ccip oracles - [#14720](https://github.com/smartcontractkit/chainlink/pull/14720) [`4f8c55eb01`](https://github.com/smartcontractkit/chainlink/commit/4f8c55eb01b5823f43f49761344e92dc37ec0114) Thanks [@vyzaldysanchez](https://github.com/vyzaldysanchez)! - #updated Refactors store_db - [#14530](https://github.com/smartcontractkit/chainlink/pull/14530) [`2c16f46311`](https://github.com/smartcontractkit/chainlink/commit/2c16f4631184a6e3da7f2f3957173500e2c4837b) Thanks [@Madalosso](https://github.com/Madalosso)! - #updated default config values for FinalityTagEnabled to match CCIP configs @@ -108,6 +106,7 @@ - [#14668](https://github.com/smartcontractkit/chainlink/pull/14668) [`dacb6a8c70`](https://github.com/smartcontractkit/chainlink/commit/dacb6a8c708e8d7cb94aa63ae7463f58a38d0e59) Thanks [@winder](https://github.com/winder)! - #internal ccip contract reader config. - [#14814](https://github.com/smartcontractkit/chainlink/pull/14814) [`f708ebb094`](https://github.com/smartcontractkit/chainlink/commit/f708ebb094ecd6f4f77e9c480ceacd250fc1fadc) Thanks [@DylanTinianov](https://github.com/DylanTinianov)! - Fix testWSServer issue causing panic in testing #internal - [#14635](https://github.com/smartcontractkit/chainlink/pull/14635) [`ee1d6e3b1a`](https://github.com/smartcontractkit/chainlink/commit/ee1d6e3b1a60dc657a5cab869aac0897e33dc76d) Thanks [@dhaidashenko](https://github.com/dhaidashenko)! - Hedera chain type: broadcast transactions only to a single healthy RPC instead of all healthy RPCs to avoid redundant relay fees. #changed +- [#15031](https://github.com/smartcontractkit/chainlink/pull/15031) [`6951f9e74a`](https://github.com/smartcontractkit/chainlink/commit/6951f9e74ae016fab1548b682d4fd8ed5b818d3c) Thanks [@ogtownsend](https://github.com/ogtownsend)! - #bugfix Update DA oracle config struct members to pointers ## 2.17.0 - 2024-10-10 diff --git a/GNUmakefile b/GNUmakefile index 6be577c3db3..592183923e2 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -178,6 +178,7 @@ golangci-lint: ## Run golangci-lint for all issues. .PHONY: modgraph modgraph: + go install github.com/jmank88/modgraph@v0.1.0 ./tools/bin/modgraph > go.md .PHONY: test-short diff --git a/ccip/config/evm/Celo_Testnet.toml b/ccip/config/evm/Celo_Testnet.toml index 4f457b103e0..0e4594150dd 100644 --- a/ccip/config/evm/Celo_Testnet.toml +++ b/ccip/config/evm/Celo_Testnet.toml @@ -1,21 +1,30 @@ ChainID = '44787' ChainType = 'celo' -FinalityDepth = 10 -LogPollInterval = '5s' -MinIncomingConfirmations = 1 +FinalityTagEnabled = true +FinalityDepth = 2750 # mean finality time of ~37 minutes + 500 block buffer +LogPollInterval = '1s' # 1 sec block rate NoNewHeadsThreshold = '1m' -OCR.ContractConfirmations = 1 -NoNewFinalizedHeadsThreshold = '1m' +MinIncomingConfirmations = 1 +NoNewFinalizedHeadsThreshold = '45m' # Set slightly higher than mean finality time [GasEstimator] -PriceDefault = '5 gwei' -PriceMax = '500 gwei' -PriceMin = '5 gwei' -BumpMin = '2 gwei' +EIP1559DynamicFees = true +PriceMin = '5 gwei' # Mean gas price around 5 gwei and celo txns are extremely cheap at ~0.00088 CELO per txn ($0.000058) +PriceMax = '1000 gwei' # DS&A recommendation [GasEstimator.BlockHistory] -BlockHistorySize = 24 +# Default is 8, which leads to bumpy gas prices. In CCIP +# we want to smooth out the gas prices, so we increase the sample size. +BlockHistorySize = 200 + +[Transactions] +ResendAfterThreshold = '30s' [HeadTracker] -HistoryDepth = 50 -PersistenceEnabled = false +HistoryDepth = 300 + +[NodePool] +SyncThreshold = 10 # recommended for OP stack chains + +[OCR] +ContractConfirmations = 1 # recommended for OP stack chains \ No newline at end of file diff --git a/ccip/config/evm/Linea_Mainnet.toml b/ccip/config/evm/Linea_Mainnet.toml index 94d8bedc44b..5a89873acae 100644 --- a/ccip/config/evm/Linea_Mainnet.toml +++ b/ccip/config/evm/Linea_Mainnet.toml @@ -15,3 +15,8 @@ ResendAfterThreshold = '3m' # set greater than finality depth [HeadTracker] HistoryDepth = 350 + +[Transactions.AutoPurge] +Enabled = true +Threshold = 50 # 50 blocks at 3s block time ~2.5 minutes +MinAttempts = 3 diff --git a/ccip/config/evm/Linea_Sepolia.toml b/ccip/config/evm/Linea_Sepolia.toml index ac5e18a09b6..8f168ee93a6 100644 --- a/ccip/config/evm/Linea_Sepolia.toml +++ b/ccip/config/evm/Linea_Sepolia.toml @@ -10,4 +10,9 @@ PriceMin = '1 wei' ResendAfterThreshold = '3m' [HeadTracker] -HistoryDepth = 1000 \ No newline at end of file +HistoryDepth = 1000 + +[Transactions.AutoPurge] +Enabled = true +Threshold = 50 # 50 blocks at 3s block time ~2.5 minutes +MinAttempts = 3 diff --git a/ccip/config/evm/Polygon_Zkevm_Cardona.toml b/ccip/config/evm/Polygon_Zkevm_Cardona.toml index cd91465dae6..5e4861f9d44 100644 --- a/ccip/config/evm/Polygon_Zkevm_Cardona.toml +++ b/ccip/config/evm/Polygon_Zkevm_Cardona.toml @@ -22,3 +22,7 @@ BlockHistorySize = 12 [HeadTracker] HistoryDepth = 2000 + +[Transactions.AutoPurge] +Enabled = true +MinAttempts = 3 diff --git a/ccip/config/evm/Polygon_Zkevm_Mainnet.toml b/ccip/config/evm/Polygon_Zkevm_Mainnet.toml index 79e0cb0fce5..b38a483ff35 100644 --- a/ccip/config/evm/Polygon_Zkevm_Mainnet.toml +++ b/ccip/config/evm/Polygon_Zkevm_Mainnet.toml @@ -24,3 +24,7 @@ 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 + +[Transactions.AutoPurge] +Enabled = true +MinAttempts = 3 diff --git a/ccip/config/evm/Scroll_Mainnet.toml b/ccip/config/evm/Scroll_Mainnet.toml index 4a887b504df..b8e7bd09e80 100644 --- a/ccip/config/evm/Scroll_Mainnet.toml +++ b/ccip/config/evm/Scroll_Mainnet.toml @@ -20,3 +20,7 @@ HistoryDepth = 50 [OCR] ContractConfirmations = 1 + +[Transactions.AutoPurge] +Enabled = true +DetectionApiUrl = 'https://venus.scroll.io' diff --git a/ccip/config/evm/Scroll_Sepolia.toml b/ccip/config/evm/Scroll_Sepolia.toml index b2e1cfbd733..baee2080d96 100644 --- a/ccip/config/evm/Scroll_Sepolia.toml +++ b/ccip/config/evm/Scroll_Sepolia.toml @@ -20,3 +20,7 @@ HistoryDepth = 50 [OCR] ContractConfirmations = 1 + +[Transactions.AutoPurge] +Enabled = true +DetectionApiUrl = 'https://sepolia-venus.scroll.io' diff --git a/ccip/config/evm/XLayer_Mainnet.toml b/ccip/config/evm/XLayer_Mainnet.toml index 4096a4db244..a39a9231ae2 100644 --- a/ccip/config/evm/XLayer_Mainnet.toml +++ b/ccip/config/evm/XLayer_Mainnet.toml @@ -23,3 +23,7 @@ BlockHistorySize = 12 [HeadTracker] HistoryDepth = 2000 + +[Transactions.AutoPurge] +Enabled = true +MinAttempts = 3 diff --git a/ccip/config/evm/XLayer_Sepolia.toml b/ccip/config/evm/XLayer_Sepolia.toml index 62e2c1e8ad0..2aa6e58469b 100644 --- a/ccip/config/evm/XLayer_Sepolia.toml +++ b/ccip/config/evm/XLayer_Sepolia.toml @@ -23,3 +23,7 @@ BlockHistorySize = 12 [HeadTracker] HistoryDepth = 2000 + +[Transactions.AutoPurge] +Enabled = true +MinAttempts = 3 diff --git a/ccip/config/evm/Zircuit_Mainnet.toml b/ccip/config/evm/Zircuit_Mainnet.toml new file mode 100644 index 00000000000..9ec1dd0d6a7 --- /dev/null +++ b/ccip/config/evm/Zircuit_Mainnet.toml @@ -0,0 +1,36 @@ +ChainID = '48900' +ChainType = 'optimismBedrock' +FinalityTagEnabled = true +FinalityDepth = 1000 +LinkContractAddress = '0x5D6d033B4FbD2190D99D930719fAbAcB64d2439a' +LogPollInterval = "2s" +NoNewHeadsThreshold = "40s" +NoNewFinalizedHeadsThreshold = "15m" + +[GasEstimator] +EIP1559DynamicFees = true +PriceMin = '1 wei' +BumpMin = '100 wei' + +[GasEstimator.BlockHistory] +BlockHistorySize = 24 + +[Transactions] +ResendAfterThreshold = '30s' + +[HeadTracker] +HistoryDepth = 2000 + +[NodePool] +SyncThreshold = 10 + +[OCR] +ContractConfirmations = 1 + +[OCR2.Automation] +GasLimit = 6500000 + +[Transactions.AutoPurge] +Enabled = true +Threshold = 90 # 90 blocks at 2s block time ~3 minutes +MinAttempts = 3 diff --git a/ccip/config/evm/Zircuit_Sepolia.toml b/ccip/config/evm/Zircuit_Sepolia.toml new file mode 100644 index 00000000000..d439d98162b --- /dev/null +++ b/ccip/config/evm/Zircuit_Sepolia.toml @@ -0,0 +1,36 @@ +ChainID = '48899' +ChainType = 'optimismBedrock' +FinalityTagEnabled = true +FinalityDepth = 1000 +LinkContractAddress = '0xDEE94506570cA186BC1e3516fCf4fd719C312cCD' +LogPollInterval = "2s" +NoNewHeadsThreshold = "40s" +NoNewFinalizedHeadsThreshold = "15m" + +[GasEstimator] +EIP1559DynamicFees = true +PriceMin = '1 wei' +BumpMin = '100 wei' + +[GasEstimator.BlockHistory] +BlockHistorySize = 60 + +[Transactions] +ResendAfterThreshold = '30s' + +[HeadTracker] +HistoryDepth = 2000 + +[NodePool] +SyncThreshold = 10 + +[OCR] +ContractConfirmations = 1 + +[OCR2.Automation] +GasLimit = 6500000 + +[Transactions.AutoPurge] +Enabled = true +Threshold = 90 # 90 blocks at 2s block time ~3 minutes +MinAttempts = 3 diff --git a/common/txmgr/confirmer.go b/common/txmgr/confirmer.go index a42b5985226..e6fd5b61f68 100644 --- a/common/txmgr/confirmer.go +++ b/common/txmgr/confirmer.go @@ -1078,12 +1078,12 @@ func (ec *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Ens } // Here, we rely on the finalized block provided in the chain instead of the one - //provided via a dedicated method to avoid the false warning of the chain being - //too short. When `FinalityTagBypass = true,` HeadTracker tracks `finality depth - //+ history depth` to prevent excessive CPU usage. Thus, the provided chain may - //be shorter than the chain from the latest to the latest finalized, marked with - //a tag. A proper fix of this issue and complete switch to finality tag support - //will be introduced in BCFR-620 + // provided via a dedicated method to avoid the false warning of the chain being + // too short. When `FinalityTagBypass = true,` HeadTracker tracks `finality depth + // + history depth` to prevent excessive CPU usage. Thus, the provided chain may + // be shorter than the chain from the latest to the latest finalized, marked with + // a tag. A proper fix of this issue and complete switch to finality tag support + // will be introduced in BCFR-620 latestFinalized := head.LatestFinalizedHead() if latestFinalized == nil || !latestFinalized.IsValid() { if ec.nConsecutiveBlocksChainTooShort > logAfterNConsecutiveBlocksChainTooShort { diff --git a/contracts/.changeset/six-games-drum.md b/contracts/.changeset/six-games-drum.md new file mode 100644 index 00000000000..00e5ca9dc5b --- /dev/null +++ b/contracts/.changeset/six-games-drum.md @@ -0,0 +1,9 @@ +--- +'@chainlink/contracts': minor +--- + +#internal CCIP test restructuring + +PR issue: CCIP-4116 + +Solidity Review issue: CCIP-3966 \ No newline at end of file diff --git a/contracts/gas-snapshots/ccip.gas-snapshot b/contracts/gas-snapshots/ccip.gas-snapshot index 4182f6597fe..18ea50101d4 100644 --- a/contracts/gas-snapshots/ccip.gas-snapshot +++ b/contracts/gas-snapshots/ccip.gas-snapshot @@ -1,9 +1,9 @@ -ARMProxyStandaloneTest:test_ARMCallEmptyContractRevert() (gas: 19709) -ARMProxyStandaloneTest:test_Constructor() (gas: 302231) -ARMProxyStandaloneTest:test_SetARM() (gas: 16599) -ARMProxyStandaloneTest:test_SetARMzero() (gas: 11319) -ARMProxyTest:test_ARMCallRevertReasonForwarded() (gas: 45153) -ARMProxyTest:test_ARMIsCursed_Success() (gas: 47082) +ARMProxy_constructor:test_Constructor() (gas: 302231) +ARMProxy_isCursed:test_IsCursed_Success() (gas: 47209) +ARMProxy_isCursed:test_call_ARMCallEmptyContract_Revert() (gas: 19412) +ARMProxy_isCursed:test_isCursed_RevertReasonForwarded_Revert() (gas: 45210) +ARMProxy_setARM:test_SetARM() (gas: 16599) +ARMProxy_setARM:test_SetARMzero() (gas: 11275) BurnFromMintTokenPool_lockOrBurn:test_ChainNotAllowed_Revert() (gas: 28962) BurnFromMintTokenPool_lockOrBurn:test_PoolBurnRevertNotHealthy_Revert() (gas: 55341) BurnFromMintTokenPool_lockOrBurn:test_PoolBurn_Success() (gas: 244152) @@ -49,11 +49,10 @@ CCIPHome_beforeCapabilityConfigSet:test_beforeCapabilityConfigSet_InnerCallRever CCIPHome_beforeCapabilityConfigSet:test_beforeCapabilityConfigSet_InvalidSelector_reverts() (gas: 11015) CCIPHome_beforeCapabilityConfigSet:test_beforeCapabilityConfigSet_OnlyCapabilitiesRegistryCanCall_reverts() (gas: 37072) CCIPHome_beforeCapabilityConfigSet:test_beforeCapabilityConfigSet_success() (gas: 1455716) -CCIPHome_constructor:test_constructor_CapabilitiesRegistryAddressZero_reverts() (gas: 63789) -CCIPHome_constructor:test_constructor_success() (gas: 3455842) -CCIPHome_constructor:test_getCapabilityConfiguration_success() (gas: 9150) -CCIPHome_constructor:test_supportsInterface_success() (gas: 9907) +CCIPHome_constructor:test_constructor_CapabilitiesRegistryAddressZero_reverts() (gas: 63767) +CCIPHome_constructor:test_constructor_success() (gas: 3455841) CCIPHome_getAllConfigs:test_getAllConfigs_success() (gas: 2773000) +CCIPHome_getCapabilityConfiguration:test_getCapabilityConfiguration_success() (gas: 9138) CCIPHome_getConfigDigests:test_getConfigDigests_success() (gas: 2547397) CCIPHome_promoteCandidateAndRevokeActive:test_promoteCandidateAndRevokeActive_CanOnlySelfCall_reverts() (gas: 9087) CCIPHome_promoteCandidateAndRevokeActive:test_promoteCandidateAndRevokeActive_ConfigDigestMismatch_reverts() (gas: 23005) @@ -66,6 +65,7 @@ CCIPHome_revokeCandidate:test_revokeCandidate_success() (gas: 30674) CCIPHome_setCandidate:test_setCandidate_CanOnlySelfCall_reverts() (gas: 29383) CCIPHome_setCandidate:test_setCandidate_ConfigDigestMismatch_reverts() (gas: 1395154) CCIPHome_setCandidate:test_setCandidate_success() (gas: 1365439) +CCIPHome_supportsInterface:test_supportsInterface_success() (gas: 9885) DefensiveExampleTest:test_HappyPath_Success() (gas: 200473) DefensiveExampleTest:test_Recovery() (gas: 424876) E2E:test_E2E_3MessagesMMultiOffRampSuccess_gas() (gas: 1519829) @@ -90,35 +90,35 @@ EtherSenderReceiverTest_validatedMessage:test_validatedMessage_emptyDataOverwrit EtherSenderReceiverTest_validatedMessage:test_validatedMessage_invalidTokenAmounts() (gas: 17969) EtherSenderReceiverTest_validatedMessage:test_validatedMessage_tokenOverwrittenToWeth() (gas: 25328) EtherSenderReceiverTest_validatedMessage:test_validatedMessage_validMessage_extraArgs() (gas: 26392) -FactoryBurnMintERC20approve:test_Approve_Success() (gas: 55819) -FactoryBurnMintERC20approve:test_InvalidAddress_Reverts() (gas: 10703) -FactoryBurnMintERC20burn:test_BasicBurn_Success() (gas: 172464) -FactoryBurnMintERC20burn:test_BurnFromZeroAddress_Reverts() (gas: 47338) -FactoryBurnMintERC20burn:test_ExceedsBalance_Reverts() (gas: 22005) -FactoryBurnMintERC20burn:test_SenderNotBurner_Reverts() (gas: 13520) -FactoryBurnMintERC20burnFrom:test_BurnFrom_Success() (gas: 58274) -FactoryBurnMintERC20burnFrom:test_ExceedsBalance_Reverts() (gas: 36191) -FactoryBurnMintERC20burnFrom:test_InsufficientAllowance_Reverts() (gas: 22113) -FactoryBurnMintERC20burnFrom:test_SenderNotBurner_Reverts() (gas: 13487) -FactoryBurnMintERC20burnFromAlias:test_BurnFrom_Success() (gas: 58248) -FactoryBurnMintERC20burnFromAlias:test_ExceedsBalance_Reverts() (gas: 36155) -FactoryBurnMintERC20burnFromAlias:test_InsufficientAllowance_Reverts() (gas: 22068) -FactoryBurnMintERC20burnFromAlias:test_SenderNotBurner_Reverts() (gas: 13442) -FactoryBurnMintERC20constructor:test_Constructor_Success() (gas: 1450638) -FactoryBurnMintERC20decreaseApproval:test_DecreaseApproval_Success() (gas: 31419) -FactoryBurnMintERC20getCCIPAdmin:test_getCCIPAdmin_Success() (gas: 12717) -FactoryBurnMintERC20getCCIPAdmin:test_setCCIPAdmin_Success() (gas: 23874) -FactoryBurnMintERC20grantMintAndBurnRoles:test_GrantMintAndBurnRoles_Success() (gas: 121194) -FactoryBurnMintERC20grantRole:test_GrantBurnAccess_Success() (gas: 53403) -FactoryBurnMintERC20grantRole:test_GrantMany_Success() (gas: 961486) -FactoryBurnMintERC20grantRole:test_GrantMintAccess_Success() (gas: 94165) -FactoryBurnMintERC20increaseApproval:test_IncreaseApproval_Success() (gas: 44398) -FactoryBurnMintERC20mint:test_BasicMint_Success() (gas: 149804) -FactoryBurnMintERC20mint:test_MaxSupplyExceeded_Reverts() (gas: 50679) -FactoryBurnMintERC20mint:test_SenderNotMinter_Reverts() (gas: 11405) -FactoryBurnMintERC20supportsInterface:test_SupportsInterface_Success() (gas: 11538) -FactoryBurnMintERC20transfer:test_InvalidAddress_Reverts() (gas: 10701) -FactoryBurnMintERC20transfer:test_Transfer_Success() (gas: 42482) +FactoryBurnMintERC20_approve:test_Approve_Success() (gas: 55819) +FactoryBurnMintERC20_approve:test_InvalidAddress_Reverts() (gas: 10703) +FactoryBurnMintERC20_burn:test_BasicBurn_Success() (gas: 172464) +FactoryBurnMintERC20_burn:test_BurnFromZeroAddress_Reverts() (gas: 47338) +FactoryBurnMintERC20_burn:test_ExceedsBalance_Reverts() (gas: 22005) +FactoryBurnMintERC20_burn:test_SenderNotBurner_Reverts() (gas: 13520) +FactoryBurnMintERC20_burnFrom:test_BurnFrom_Success() (gas: 58274) +FactoryBurnMintERC20_burnFrom:test_ExceedsBalance_Reverts() (gas: 36191) +FactoryBurnMintERC20_burnFrom:test_InsufficientAllowance_Reverts() (gas: 22113) +FactoryBurnMintERC20_burnFrom:test_SenderNotBurner_Reverts() (gas: 13487) +FactoryBurnMintERC20_burnFromAlias:test_BurnFrom_Success() (gas: 58248) +FactoryBurnMintERC20_burnFromAlias:test_ExceedsBalance_Reverts() (gas: 36155) +FactoryBurnMintERC20_burnFromAlias:test_InsufficientAllowance_Reverts() (gas: 22068) +FactoryBurnMintERC20_burnFromAlias:test_SenderNotBurner_Reverts() (gas: 13442) +FactoryBurnMintERC20_constructor:test_Constructor_Success() (gas: 1450638) +FactoryBurnMintERC20_decreaseApproval:test_DecreaseApproval_Success() (gas: 31419) +FactoryBurnMintERC20_getCCIPAdmin:test_getCCIPAdmin_Success() (gas: 12717) +FactoryBurnMintERC20_getCCIPAdmin:test_setCCIPAdmin_Success() (gas: 23874) +FactoryBurnMintERC20_grantMintAndBurnRoles:test_GrantMintAndBurnRoles_Success() (gas: 121194) +FactoryBurnMintERC20_grantRole:test_GrantBurnAccess_Success() (gas: 53403) +FactoryBurnMintERC20_grantRole:test_GrantMany_Success() (gas: 961486) +FactoryBurnMintERC20_grantRole:test_GrantMintAccess_Success() (gas: 94165) +FactoryBurnMintERC20_increaseApproval:test_IncreaseApproval_Success() (gas: 44398) +FactoryBurnMintERC20_mint:test_BasicMint_Success() (gas: 149804) +FactoryBurnMintERC20_mint:test_MaxSupplyExceeded_Reverts() (gas: 50679) +FactoryBurnMintERC20_mint:test_SenderNotMinter_Reverts() (gas: 11405) +FactoryBurnMintERC20_supportsInterface:test_SupportsInterface_Success() (gas: 11538) +FactoryBurnMintERC20_transfer:test_InvalidAddress_Reverts() (gas: 10701) +FactoryBurnMintERC20_transfer:test_Transfer_Success() (gas: 42482) FeeQuoter_applyDestChainConfigUpdates:test_InvalidChainFamilySelector_Revert() (gas: 16846) FeeQuoter_applyDestChainConfigUpdates:test_InvalidDestChainConfigDestChainSelectorEqZero_Revert() (gas: 16759) FeeQuoter_applyDestChainConfigUpdates:test_applyDestChainConfigUpdatesDefaultTxGasLimitEqZero_Revert() (gas: 16813) @@ -554,16 +554,10 @@ OnRamp_setDynamicConfig:test_setDynamicConfig_InvalidConfigReentrancyGuardEntere OnRamp_setDynamicConfig:test_setDynamicConfig_Success() (gas: 56440) OnRamp_withdrawFeeTokens:test_WithdrawFeeTokens_Success() (gas: 125867) PingPong_ccipReceive:test_CcipReceive_Success() (gas: 172841) -PingPong_plumbing:test_OutOfOrderExecution_Success() (gas: 20283) -PingPong_plumbing:test_Pausing_Success() (gas: 17738) +PingPong_setOutOfOrderExecution:test_OutOfOrderExecution_Success() (gas: 20283) +PingPong_setPaused:test_Pausing_Success() (gas: 17738) PingPong_startPingPong:test_StartPingPong_With_OOO_Success() (gas: 151954) PingPong_startPingPong:test_StartPingPong_With_Sequenced_Ordered_Success() (gas: 177569) -RMNHome__validateStaticAndDynamicConfig:test_validateStaticAndDynamicConfig_DuplicateOffchainPublicKey_reverts() (gas: 18850) -RMNHome__validateStaticAndDynamicConfig:test_validateStaticAndDynamicConfig_DuplicatePeerId_reverts() (gas: 18710) -RMNHome__validateStaticAndDynamicConfig:test_validateStaticAndDynamicConfig_DuplicateSourceChain_reverts() (gas: 20387) -RMNHome__validateStaticAndDynamicConfig:test_validateStaticAndDynamicConfig_NotEnoughObservers_reverts() (gas: 21405) -RMNHome__validateStaticAndDynamicConfig:test_validateStaticAndDynamicConfig_OutOfBoundsNodesLength_reverts() (gas: 137318) -RMNHome__validateStaticAndDynamicConfig:test_validateStaticAndDynamicConfig_OutOfBoundsObserverNodeIndex_reverts() (gas: 20522) RMNHome_getConfigDigests:test_getConfigDigests_success() (gas: 1079685) RMNHome_promoteCandidateAndRevokeActive:test_promoteCandidateAndRevokeActive_ConfigDigestMismatch_reverts() (gas: 23879) RMNHome_promoteCandidateAndRevokeActive:test_promoteCandidateAndRevokeActive_NoOpStateTransitionNotAllowed_reverts() (gas: 10597) @@ -580,6 +574,12 @@ RMNHome_setDynamicConfig:test_setDynamicConfig_DigestNotFound_reverts() (gas: 30 RMNHome_setDynamicConfig:test_setDynamicConfig_MinObserversTooHigh_reverts() (gas: 18854) RMNHome_setDynamicConfig:test_setDynamicConfig_OnlyOwner_reverts() (gas: 14009) RMNHome_setDynamicConfig:test_setDynamicConfig_success() (gas: 104862) +RMNHome_validateStaticAndDynamicConfig:test_validateStaticAndDynamicConfig_DuplicateOffchainPublicKey_reverts() (gas: 18850) +RMNHome_validateStaticAndDynamicConfig:test_validateStaticAndDynamicConfig_DuplicatePeerId_reverts() (gas: 18710) +RMNHome_validateStaticAndDynamicConfig:test_validateStaticAndDynamicConfig_DuplicateSourceChain_reverts() (gas: 20387) +RMNHome_validateStaticAndDynamicConfig:test_validateStaticAndDynamicConfig_NotEnoughObservers_reverts() (gas: 21405) +RMNHome_validateStaticAndDynamicConfig:test_validateStaticAndDynamicConfig_OutOfBoundsNodesLength_reverts() (gas: 137318) +RMNHome_validateStaticAndDynamicConfig:test_validateStaticAndDynamicConfig_OutOfBoundsObserverNodeIndex_reverts() (gas: 20522) RMNRemote_constructor:test_constructor_success() (gas: 8334) RMNRemote_constructor:test_constructor_zeroChainSelector_reverts() (gas: 59184) RMNRemote_curse:test_curse_AlreadyCursed_duplicateSubject_reverts() (gas: 154479) @@ -678,13 +678,13 @@ TokenAdminRegistry_setPool:test_setPool_Success() (gas: 36267) TokenAdminRegistry_setPool:test_setPool_ZeroAddressRemovesPool_Success() (gas: 30875) TokenAdminRegistry_transferAdminRole:test_transferAdminRole_OnlyAdministrator_Revert() (gas: 18202) TokenAdminRegistry_transferAdminRole:test_transferAdminRole_Success() (gas: 49592) -TokenPoolFactoryTests:test_TokenPoolFactory_Constructor_Revert() (gas: 1039441) -TokenPoolFactoryTests:test_createTokenPoolLockRelease_ExistingToken_predict_Success() (gas: 11591893) -TokenPoolFactoryTests:test_createTokenPool_BurnFromMintTokenPool_Success() (gas: 5848501) -TokenPoolFactoryTests:test_createTokenPool_ExistingRemoteToken_AndPredictPool_Success() (gas: 12227697) -TokenPoolFactoryTests:test_createTokenPool_WithNoExistingRemoteContracts_predict_Success() (gas: 12564414) -TokenPoolFactoryTests:test_createTokenPool_WithNoExistingTokenOnRemoteChain_Success() (gas: 5701802) -TokenPoolFactoryTests:test_createTokenPool_WithRemoteTokenAndRemotePool_Success() (gas: 5845024) +TokenPoolFactory_constructor:test_constructor_Revert() (gas: 1039441) +TokenPoolFactory_createTokenPool:test_createTokenPoolLockRelease_ExistingToken_predict_Success() (gas: 11591871) +TokenPoolFactory_createTokenPool:test_createTokenPool_BurnFromMintTokenPool_Success() (gas: 5848479) +TokenPoolFactory_createTokenPool:test_createTokenPool_ExistingRemoteToken_AndPredictPool_Success() (gas: 12227675) +TokenPoolFactory_createTokenPool:test_createTokenPool_WithNoExistingRemoteContracts_predict_Success() (gas: 12564436) +TokenPoolFactory_createTokenPool:test_createTokenPool_WithNoExistingTokenOnRemoteChain_Success() (gas: 5701824) +TokenPoolFactory_createTokenPool:test_createTokenPool_WithRemoteTokenAndRemotePool_Success() (gas: 5845002) TokenPoolWithAllowList_applyAllowListUpdates:test_AllowListNotEnabled_Revert() (gas: 1944108) TokenPoolWithAllowList_applyAllowListUpdates:test_OnlyOwner_Revert() (gas: 12119) TokenPoolWithAllowList_applyAllowListUpdates:test_SetAllowListSkipsZero_Success() (gas: 23567) diff --git a/contracts/src/v0.8/ccip/test/NonceManager.t.sol b/contracts/src/v0.8/ccip/test/NonceManager.t.sol index f560b5be593..b5c3ee6bd99 100644 --- a/contracts/src/v0.8/ccip/test/NonceManager.t.sol +++ b/contracts/src/v0.8/ccip/test/NonceManager.t.sol @@ -11,8 +11,8 @@ import {OnRamp} from "../onRamp/OnRamp.sol"; import {BaseTest} from "./BaseTest.t.sol"; import {EVM2EVMOffRampHelper} from "./helpers/EVM2EVMOffRampHelper.sol"; import {OnRampHelper} from "./helpers/OnRampHelper.sol"; -import {OffRampSetup} from "./offRamp/offRamp/OffRampSetup.t.sol"; -import {OnRampSetup} from "./onRamp/onRamp/OnRampSetup.t.sol"; +import {OffRampSetup} from "./offRamp/OffRamp/OffRampSetup.t.sol"; +import {OnRampSetup} from "./onRamp/OnRamp/OnRampSetup.t.sol"; import {Test} from "forge-std/Test.sol"; diff --git a/contracts/src/v0.8/ccip/test/TokenSetup.t.sol b/contracts/src/v0.8/ccip/test/TokenSetup.t.sol index 42d10190f1e..2077bc94deb 100644 --- a/contracts/src/v0.8/ccip/test/TokenSetup.t.sol +++ b/contracts/src/v0.8/ccip/test/TokenSetup.t.sol @@ -7,7 +7,7 @@ import {LockReleaseTokenPool} from "../pools/LockReleaseTokenPool.sol"; import {TokenPool} from "../pools/TokenPool.sol"; import {TokenAdminRegistry} from "../tokenAdminRegistry/TokenAdminRegistry.sol"; import {MaybeRevertingBurnMintTokenPool} from "./helpers/MaybeRevertingBurnMintTokenPool.sol"; -import {RouterSetup} from "./router/RouterSetup.t.sol"; +import {RouterSetup} from "./router/Router/RouterSetup.t.sol"; import {IERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; diff --git a/contracts/src/v0.8/ccip/test/applications/DefensiveExample.t.sol b/contracts/src/v0.8/ccip/test/applications/DefensiveExample/DefensiveExample.t.sol similarity index 91% rename from contracts/src/v0.8/ccip/test/applications/DefensiveExample.t.sol rename to contracts/src/v0.8/ccip/test/applications/DefensiveExample/DefensiveExample.t.sol index b4829668ce3..ddaadbac801 100644 --- a/contracts/src/v0.8/ccip/test/applications/DefensiveExample.t.sol +++ b/contracts/src/v0.8/ccip/test/applications/DefensiveExample/DefensiveExample.t.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {DefensiveExample} from "../../applications/DefensiveExample.sol"; -import {Client} from "../../libraries/Client.sol"; -import {OnRampSetup} from "../onRamp/onRamp/OnRampSetup.t.sol"; +import {DefensiveExample} from "../../../applications/DefensiveExample.sol"; +import {Client} from "../../../libraries/Client.sol"; +import {OnRampSetup} from "../../onRamp/OnRamp/OnRampSetup.t.sol"; -import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; contract DefensiveExampleTest is OnRampSetup { event MessageFailed(bytes32 indexed messageId, bytes reason); diff --git a/contracts/src/v0.8/ccip/test/applications/EtherSenderReceiver.t.sol b/contracts/src/v0.8/ccip/test/applications/EtherSenderReceiver.t.sol deleted file mode 100644 index 6da86a06c7e..00000000000 --- a/contracts/src/v0.8/ccip/test/applications/EtherSenderReceiver.t.sol +++ /dev/null @@ -1,719 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {Test} from "forge-std/Test.sol"; - -import {ICCIPRouter} from "../../applications/EtherSenderReceiver.sol"; - -import {IRouterClient} from "../../interfaces/IRouterClient.sol"; -import {Client} from "../../libraries/Client.sol"; -import {WETH9} from "../WETH9.sol"; -import {EtherSenderReceiverHelper} from "./../helpers/EtherSenderReceiverHelper.sol"; - -import {ERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/ERC20.sol"; - -contract EtherSenderReceiverTest is Test { - EtherSenderReceiverHelper internal s_etherSenderReceiver; - WETH9 internal s_weth; - WETH9 internal s_someOtherWeth; - ERC20 internal s_linkToken; - - address internal constant OWNER = 0x00007e64E1fB0C487F25dd6D3601ff6aF8d32e4e; - address internal constant ROUTER = 0x0F3779ee3a832D10158073ae2F5e61ac7FBBF880; - address internal constant XCHAIN_RECEIVER = 0xBd91b2073218AF872BF73b65e2e5950ea356d147; - uint256 internal constant AMOUNT = 100; - - function setUp() public { - vm.startPrank(OWNER); - - s_linkToken = new ERC20("Chainlink Token", "LINK"); - s_someOtherWeth = new WETH9(); - s_weth = new WETH9(); - vm.mockCall(ROUTER, abi.encodeWithSelector(ICCIPRouter.getWrappedNative.selector), abi.encode(address(s_weth))); - s_etherSenderReceiver = new EtherSenderReceiverHelper(ROUTER); - - deal(OWNER, 1_000_000 ether); - deal(address(s_linkToken), OWNER, 1_000_000 ether); - - // deposit some eth into the weth contract. - s_weth.deposit{value: 10 ether}(); - uint256 wethSupply = s_weth.totalSupply(); - assertEq(wethSupply, 10 ether, "total weth supply must be 10 ether"); - } -} - -contract EtherSenderReceiverTest_constructor is EtherSenderReceiverTest { - function test_constructor() public view { - assertEq(s_etherSenderReceiver.getRouter(), ROUTER, "router must be set correctly"); - uint256 allowance = s_weth.allowance(address(s_etherSenderReceiver), ROUTER); - assertEq(allowance, type(uint256).max, "allowance must be set infinite"); - } -} - -contract EtherSenderReceiverTest_validateFeeToken is EtherSenderReceiverTest { - error InsufficientMsgValue(uint256 gotAmount, uint256 msgValue); - error TokenAmountNotEqualToMsgValue(uint256 gotAmount, uint256 msgValue); - - function test_validateFeeToken_valid_native() public { - Client.EVMTokenAmount[] memory tokenAmount = new Client.EVMTokenAmount[](1); - tokenAmount[0] = Client.EVMTokenAmount({token: address(s_weth), amount: AMOUNT}); - Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(XCHAIN_RECEIVER), - data: "", - tokenAmounts: tokenAmount, - feeToken: address(0), - extraArgs: "" - }); - - s_etherSenderReceiver.validateFeeToken{value: AMOUNT + 1}(message); - } - - function test_validateFeeToken_valid_feeToken() public { - Client.EVMTokenAmount[] memory tokenAmount = new Client.EVMTokenAmount[](1); - tokenAmount[0] = Client.EVMTokenAmount({token: address(s_weth), amount: AMOUNT}); - Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(XCHAIN_RECEIVER), - data: "", - tokenAmounts: tokenAmount, - feeToken: address(s_weth), - extraArgs: "" - }); - - s_etherSenderReceiver.validateFeeToken{value: AMOUNT}(message); - } - - function test_validateFeeToken_reverts_feeToken_tokenAmountNotEqualToMsgValue() public { - Client.EVMTokenAmount[] memory tokenAmount = new Client.EVMTokenAmount[](1); - tokenAmount[0] = Client.EVMTokenAmount({token: address(s_weth), amount: AMOUNT}); - Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(XCHAIN_RECEIVER), - data: "", - tokenAmounts: tokenAmount, - feeToken: address(s_weth), - extraArgs: "" - }); - - vm.expectRevert(abi.encodeWithSelector(TokenAmountNotEqualToMsgValue.selector, AMOUNT, AMOUNT + 1)); - s_etherSenderReceiver.validateFeeToken{value: AMOUNT + 1}(message); - } -} - -contract EtherSenderReceiverTest_validatedMessage is EtherSenderReceiverTest { - error InvalidDestinationReceiver(bytes destReceiver); - error InvalidTokenAmounts(uint256 gotAmounts); - error InvalidWethAddress(address want, address got); - error GasLimitTooLow(uint256 minLimit, uint256 gotLimit); - - function test_Fuzz_validatedMessage_msgSenderOverwrite( - bytes memory data - ) public view { - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); - tokenAmounts[0] = Client.EVMTokenAmount({ - token: address(0), // callers may not specify this. - amount: AMOUNT - }); - Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(XCHAIN_RECEIVER), - data: data, - tokenAmounts: tokenAmounts, - feeToken: address(0), - extraArgs: "" - }); - - Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); - assertEq(validatedMessage.receiver, abi.encode(XCHAIN_RECEIVER), "receiver must be XCHAIN_RECEIVER"); - assertEq(validatedMessage.data, abi.encode(OWNER), "data must be msg.sender"); - assertEq(validatedMessage.tokenAmounts[0].token, address(s_weth), "token must be weth"); - assertEq(validatedMessage.tokenAmounts[0].amount, AMOUNT, "amount must be correct"); - assertEq(validatedMessage.feeToken, address(0), "feeToken must be 0"); - assertEq(validatedMessage.extraArgs, bytes(""), "extraArgs must be empty"); - } - - function test_Fuzz_validatedMessage_tokenAddressOverwrite( - address token - ) public view { - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); - tokenAmounts[0] = Client.EVMTokenAmount({token: token, amount: AMOUNT}); - Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(XCHAIN_RECEIVER), - data: "", - tokenAmounts: tokenAmounts, - feeToken: address(0), - extraArgs: "" - }); - - Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); - assertEq(validatedMessage.receiver, abi.encode(XCHAIN_RECEIVER), "receiver must be XCHAIN_RECEIVER"); - assertEq(validatedMessage.data, abi.encode(OWNER), "data must be msg.sender"); - assertEq(validatedMessage.tokenAmounts[0].token, address(s_weth), "token must be weth"); - assertEq(validatedMessage.tokenAmounts[0].amount, AMOUNT, "amount must be correct"); - assertEq(validatedMessage.feeToken, address(0), "feeToken must be 0"); - assertEq(validatedMessage.extraArgs, bytes(""), "extraArgs must be empty"); - } - - function test_validatedMessage_emptyDataOverwrittenToMsgSender() public view { - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); - tokenAmounts[0] = Client.EVMTokenAmount({ - token: address(0), // callers may not specify this. - amount: AMOUNT - }); - Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(XCHAIN_RECEIVER), - data: "", - tokenAmounts: tokenAmounts, - feeToken: address(0), - extraArgs: "" - }); - - Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); - assertEq(validatedMessage.receiver, abi.encode(XCHAIN_RECEIVER), "receiver must be XCHAIN_RECEIVER"); - assertEq(validatedMessage.data, abi.encode(OWNER), "data must be msg.sender"); - assertEq(validatedMessage.tokenAmounts[0].token, address(s_weth), "token must be weth"); - assertEq(validatedMessage.tokenAmounts[0].amount, AMOUNT, "amount must be correct"); - assertEq(validatedMessage.feeToken, address(0), "feeToken must be 0"); - assertEq(validatedMessage.extraArgs, bytes(""), "extraArgs must be empty"); - } - - function test_validatedMessage_dataOverwrittenToMsgSender() public view { - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); - tokenAmounts[0] = Client.EVMTokenAmount({ - token: address(0), // callers may not specify this. - amount: AMOUNT - }); - Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(XCHAIN_RECEIVER), - data: abi.encode(address(42)), - tokenAmounts: tokenAmounts, - feeToken: address(0), - extraArgs: "" - }); - - Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); - assertEq(validatedMessage.receiver, abi.encode(XCHAIN_RECEIVER), "receiver must be XCHAIN_RECEIVER"); - assertEq(validatedMessage.data, abi.encode(OWNER), "data must be msg.sender"); - assertEq(validatedMessage.tokenAmounts[0].token, address(s_weth), "token must be weth"); - assertEq(validatedMessage.tokenAmounts[0].amount, AMOUNT, "amount must be correct"); - assertEq(validatedMessage.feeToken, address(0), "feeToken must be 0"); - assertEq(validatedMessage.extraArgs, bytes(""), "extraArgs must be empty"); - } - - function test_validatedMessage_tokenOverwrittenToWeth() public view { - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); - tokenAmounts[0] = Client.EVMTokenAmount({ - token: address(42), // incorrect token. - amount: AMOUNT - }); - Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(XCHAIN_RECEIVER), - data: "", - tokenAmounts: tokenAmounts, - feeToken: address(0), - extraArgs: "" - }); - - Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); - assertEq(validatedMessage.receiver, abi.encode(XCHAIN_RECEIVER), "receiver must be XCHAIN_RECEIVER"); - assertEq(validatedMessage.data, abi.encode(OWNER), "data must be msg.sender"); - assertEq(validatedMessage.tokenAmounts[0].token, address(s_weth), "token must be weth"); - assertEq(validatedMessage.tokenAmounts[0].amount, AMOUNT, "amount must be correct"); - assertEq(validatedMessage.feeToken, address(0), "feeToken must be 0"); - assertEq(validatedMessage.extraArgs, bytes(""), "extraArgs must be empty"); - } - - function test_validatedMessage_validMessage_extraArgs() public view { - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); - tokenAmounts[0] = Client.EVMTokenAmount({ - token: address(0), // callers may not specify this. - amount: AMOUNT - }); - Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(XCHAIN_RECEIVER), - data: "", - tokenAmounts: tokenAmounts, - feeToken: address(0), - extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: 200_000})) - }); - - Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); - assertEq(validatedMessage.receiver, abi.encode(XCHAIN_RECEIVER), "receiver must be XCHAIN_RECEIVER"); - assertEq(validatedMessage.data, abi.encode(OWNER), "data must be msg.sender"); - assertEq(validatedMessage.tokenAmounts[0].token, address(s_weth), "token must be weth"); - assertEq(validatedMessage.tokenAmounts[0].amount, AMOUNT, "amount must be correct"); - assertEq(validatedMessage.feeToken, address(0), "feeToken must be 0"); - assertEq( - validatedMessage.extraArgs, - Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: 200_000})), - "extraArgs must be correct" - ); - } - - function test_validatedMessage_invalidTokenAmounts() public { - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); - tokenAmounts[0] = Client.EVMTokenAmount({token: address(0), amount: AMOUNT}); - tokenAmounts[1] = Client.EVMTokenAmount({token: address(0), amount: AMOUNT}); - Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(XCHAIN_RECEIVER), - data: "", - tokenAmounts: tokenAmounts, - feeToken: address(0), - extraArgs: "" - }); - - vm.expectRevert(abi.encodeWithSelector(InvalidTokenAmounts.selector, uint256(2))); - s_etherSenderReceiver.validatedMessage(message); - } -} - -contract EtherSenderReceiverTest_getFee is EtherSenderReceiverTest { - uint64 internal constant DESTINATION_CHAIN_SELECTOR = 424242; - uint256 internal constant FEE_WEI = 121212; - - function test_getFee() public { - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); - tokenAmounts[0] = Client.EVMTokenAmount({token: address(0), amount: AMOUNT}); - Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(XCHAIN_RECEIVER), - data: "", - tokenAmounts: tokenAmounts, - feeToken: address(0), - extraArgs: "" - }); - - Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); - - vm.mockCall( - ROUTER, - abi.encodeWithSelector(IRouterClient.getFee.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), - abi.encode(FEE_WEI) - ); - - uint256 fee = s_etherSenderReceiver.getFee(DESTINATION_CHAIN_SELECTOR, message); - assertEq(fee, FEE_WEI, "fee must be feeWei"); - } -} - -contract EtherSenderReceiverTest_ccipReceive is EtherSenderReceiverTest { - uint64 internal constant SOURCE_CHAIN_SELECTOR = 424242; - address internal constant XCHAIN_SENDER = 0x9951529C13B01E542f7eE3b6D6665D292e9BA2E0; - - error InvalidTokenAmounts(uint256 gotAmounts); - error InvalidToken(address gotToken, address expectedToken); - - function test_Fuzz_ccipReceive( - uint256 tokenAmount - ) public { - // cap to 10 ether because OWNER only has 10 ether. - if (tokenAmount > 10 ether) { - return; - } - - Client.EVMTokenAmount[] memory destTokenAmounts = new Client.EVMTokenAmount[](1); - destTokenAmounts[0] = Client.EVMTokenAmount({token: address(s_weth), amount: tokenAmount}); - Client.Any2EVMMessage memory message = Client.Any2EVMMessage({ - messageId: keccak256(abi.encode("ccip send")), - sourceChainSelector: SOURCE_CHAIN_SELECTOR, - sender: abi.encode(XCHAIN_SENDER), - data: abi.encode(OWNER), - destTokenAmounts: destTokenAmounts - }); - - // simulate a cross-chain token transfer, just transfer the weth to s_etherSenderReceiver. - s_weth.transfer(address(s_etherSenderReceiver), tokenAmount); - - uint256 balanceBefore = OWNER.balance; - s_etherSenderReceiver.publicCcipReceive(message); - uint256 balanceAfter = OWNER.balance; - assertEq(balanceAfter, balanceBefore + tokenAmount, "balance must be correct"); - } - - function test_ccipReceive_happyPath() public { - Client.EVMTokenAmount[] memory destTokenAmounts = new Client.EVMTokenAmount[](1); - destTokenAmounts[0] = Client.EVMTokenAmount({token: address(s_weth), amount: AMOUNT}); - Client.Any2EVMMessage memory message = Client.Any2EVMMessage({ - messageId: keccak256(abi.encode("ccip send")), - sourceChainSelector: 424242, - sender: abi.encode(XCHAIN_SENDER), - data: abi.encode(OWNER), - destTokenAmounts: destTokenAmounts - }); - - // simulate a cross-chain token transfer, just transfer the weth to s_etherSenderReceiver. - s_weth.transfer(address(s_etherSenderReceiver), AMOUNT); - - uint256 balanceBefore = OWNER.balance; - s_etherSenderReceiver.publicCcipReceive(message); - uint256 balanceAfter = OWNER.balance; - assertEq(balanceAfter, balanceBefore + AMOUNT, "balance must be correct"); - } - - function test_ccipReceive_fallbackToWethTransfer() public { - Client.EVMTokenAmount[] memory destTokenAmounts = new Client.EVMTokenAmount[](1); - destTokenAmounts[0] = Client.EVMTokenAmount({token: address(s_weth), amount: AMOUNT}); - Client.Any2EVMMessage memory message = Client.Any2EVMMessage({ - messageId: keccak256(abi.encode("ccip send")), - sourceChainSelector: 424242, - sender: abi.encode(XCHAIN_SENDER), - data: abi.encode(address(s_linkToken)), // ERC20 cannot receive() ether. - destTokenAmounts: destTokenAmounts - }); - - // simulate a cross-chain token transfer, just transfer the weth to s_etherSenderReceiver. - s_weth.transfer(address(s_etherSenderReceiver), AMOUNT); - - uint256 balanceBefore = address(s_linkToken).balance; - s_etherSenderReceiver.publicCcipReceive(message); - uint256 balanceAfter = address(s_linkToken).balance; - assertEq(balanceAfter, balanceBefore, "balance must be unchanged"); - uint256 wethBalance = s_weth.balanceOf(address(s_linkToken)); - assertEq(wethBalance, AMOUNT, "weth balance must be correct"); - } - - function test_ccipReceive_wrongTokenAmount() public { - Client.EVMTokenAmount[] memory destTokenAmounts = new Client.EVMTokenAmount[](2); - destTokenAmounts[0] = Client.EVMTokenAmount({token: address(s_weth), amount: AMOUNT}); - destTokenAmounts[1] = Client.EVMTokenAmount({token: address(s_weth), amount: AMOUNT}); - Client.Any2EVMMessage memory message = Client.Any2EVMMessage({ - messageId: keccak256(abi.encode("ccip send")), - sourceChainSelector: 424242, - sender: abi.encode(XCHAIN_SENDER), - data: abi.encode(OWNER), - destTokenAmounts: destTokenAmounts - }); - - vm.expectRevert(abi.encodeWithSelector(InvalidTokenAmounts.selector, uint256(2))); - s_etherSenderReceiver.publicCcipReceive(message); - } - - function test_ccipReceive_wrongToken() public { - Client.EVMTokenAmount[] memory destTokenAmounts = new Client.EVMTokenAmount[](1); - destTokenAmounts[0] = Client.EVMTokenAmount({token: address(s_someOtherWeth), amount: AMOUNT}); - Client.Any2EVMMessage memory message = Client.Any2EVMMessage({ - messageId: keccak256(abi.encode("ccip send")), - sourceChainSelector: 424242, - sender: abi.encode(XCHAIN_SENDER), - data: abi.encode(OWNER), - destTokenAmounts: destTokenAmounts - }); - - vm.expectRevert(abi.encodeWithSelector(InvalidToken.selector, address(s_someOtherWeth), address(s_weth))); - s_etherSenderReceiver.publicCcipReceive(message); - } -} - -contract EtherSenderReceiverTest_ccipSend is EtherSenderReceiverTest { - error InsufficientFee(uint256 gotFee, uint256 fee); - - uint64 internal constant DESTINATION_CHAIN_SELECTOR = 424242; - uint256 internal constant FEE_WEI = 121212; - uint256 internal constant FEE_JUELS = 232323; - - function test_Fuzz_ccipSend(uint256 feeFromRouter, uint256 feeSupplied) public { - // cap the fuzzer because OWNER only has a million ether. - vm.assume(feeSupplied < 1_000_000 ether - AMOUNT); - - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); - tokenAmounts[0] = Client.EVMTokenAmount({ - token: address(0), // callers may not specify this. - amount: AMOUNT - }); - Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(XCHAIN_RECEIVER), - data: "", - tokenAmounts: tokenAmounts, - feeToken: address(0), - extraArgs: "" - }); - - Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); - - vm.mockCall( - ROUTER, - abi.encodeWithSelector(IRouterClient.getFee.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), - abi.encode(feeFromRouter) - ); - - if (feeSupplied < feeFromRouter) { - vm.expectRevert(); - s_etherSenderReceiver.ccipSend{value: AMOUNT + feeSupplied}(DESTINATION_CHAIN_SELECTOR, message); - } else { - bytes32 expectedMsgId = keccak256(abi.encode("ccip send")); - vm.mockCall( - ROUTER, - feeSupplied, - abi.encodeWithSelector(IRouterClient.ccipSend.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), - abi.encode(expectedMsgId) - ); - - bytes32 actualMsgId = - s_etherSenderReceiver.ccipSend{value: AMOUNT + feeSupplied}(DESTINATION_CHAIN_SELECTOR, message); - assertEq(actualMsgId, expectedMsgId, "message id must be correct"); - } - } - - function test_Fuzz_ccipSend_feeToken(uint256 feeFromRouter, uint256 feeSupplied) public { - // cap the fuzzer because OWNER only has a million LINK. - vm.assume(feeSupplied < 1_000_000 ether - AMOUNT); - - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); - tokenAmounts[0] = Client.EVMTokenAmount({ - token: address(0), // callers may not specify this. - amount: AMOUNT - }); - Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(XCHAIN_RECEIVER), - data: "", - tokenAmounts: tokenAmounts, - feeToken: address(s_linkToken), - extraArgs: "" - }); - - Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); - - vm.mockCall( - ROUTER, - abi.encodeWithSelector(IRouterClient.getFee.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), - abi.encode(feeFromRouter) - ); - - s_linkToken.approve(address(s_etherSenderReceiver), feeSupplied); - - if (feeSupplied < feeFromRouter) { - vm.expectRevert(); - s_etherSenderReceiver.ccipSend{value: AMOUNT}(DESTINATION_CHAIN_SELECTOR, message); - } else { - bytes32 expectedMsgId = keccak256(abi.encode("ccip send")); - vm.mockCall( - ROUTER, - abi.encodeWithSelector(IRouterClient.ccipSend.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), - abi.encode(expectedMsgId) - ); - - bytes32 actualMsgId = s_etherSenderReceiver.ccipSend{value: AMOUNT}(DESTINATION_CHAIN_SELECTOR, message); - assertEq(actualMsgId, expectedMsgId, "message id must be correct"); - } - } - - function test_ccipSend_reverts_insufficientFee_weth() public { - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); - tokenAmounts[0] = Client.EVMTokenAmount({ - token: address(0), // callers may not specify this. - amount: AMOUNT - }); - Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(XCHAIN_RECEIVER), - data: "", - tokenAmounts: tokenAmounts, - feeToken: address(s_weth), - extraArgs: "" - }); - - Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); - - vm.mockCall( - ROUTER, - abi.encodeWithSelector(IRouterClient.getFee.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), - abi.encode(FEE_WEI) - ); - - s_weth.approve(address(s_etherSenderReceiver), FEE_WEI - 1); - - vm.expectRevert("SafeERC20: low-level call failed"); - s_etherSenderReceiver.ccipSend{value: AMOUNT}(DESTINATION_CHAIN_SELECTOR, message); - } - - function test_ccipSend_reverts_insufficientFee_feeToken() public { - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); - tokenAmounts[0] = Client.EVMTokenAmount({ - token: address(0), // callers may not specify this. - amount: AMOUNT - }); - Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(XCHAIN_RECEIVER), - data: "", - tokenAmounts: tokenAmounts, - feeToken: address(s_linkToken), - extraArgs: "" - }); - - Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); - - vm.mockCall( - ROUTER, - abi.encodeWithSelector(IRouterClient.getFee.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), - abi.encode(FEE_JUELS) - ); - - s_linkToken.approve(address(s_etherSenderReceiver), FEE_JUELS - 1); - - vm.expectRevert("ERC20: insufficient allowance"); - s_etherSenderReceiver.ccipSend{value: AMOUNT}(DESTINATION_CHAIN_SELECTOR, message); - } - - function test_ccipSend_reverts_insufficientFee_native() public { - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); - tokenAmounts[0] = Client.EVMTokenAmount({ - token: address(0), // callers may not specify this. - amount: AMOUNT - }); - Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(XCHAIN_RECEIVER), - data: "", - tokenAmounts: tokenAmounts, - feeToken: address(0), - extraArgs: "" - }); - - Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); - - vm.mockCall( - ROUTER, - abi.encodeWithSelector(IRouterClient.getFee.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), - abi.encode(FEE_WEI) - ); - - vm.expectRevert(); - s_etherSenderReceiver.ccipSend{value: AMOUNT + FEE_WEI - 1}(DESTINATION_CHAIN_SELECTOR, message); - } - - function test_ccipSend_success_nativeExcess() public { - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); - tokenAmounts[0] = Client.EVMTokenAmount({ - token: address(0), // callers may not specify this. - amount: AMOUNT - }); - Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(XCHAIN_RECEIVER), - data: "", - tokenAmounts: tokenAmounts, - feeToken: address(0), - extraArgs: "" - }); - - Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); - - bytes32 expectedMsgId = keccak256(abi.encode("ccip send")); - vm.mockCall( - ROUTER, - abi.encodeWithSelector(IRouterClient.getFee.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), - abi.encode(FEE_WEI) - ); - - // we assert that the correct value is sent to the router call, which should be - // the msg.value - feeWei. - vm.mockCall( - ROUTER, - FEE_WEI + 1, - abi.encodeWithSelector(IRouterClient.ccipSend.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), - abi.encode(expectedMsgId) - ); - - bytes32 actualMsgId = - s_etherSenderReceiver.ccipSend{value: AMOUNT + FEE_WEI + 1}(DESTINATION_CHAIN_SELECTOR, message); - assertEq(actualMsgId, expectedMsgId, "message id must be correct"); - } - - function test_ccipSend_success_native() public { - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); - tokenAmounts[0] = Client.EVMTokenAmount({ - token: address(0), // callers may not specify this. - amount: AMOUNT - }); - Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(XCHAIN_RECEIVER), - data: "", - tokenAmounts: tokenAmounts, - feeToken: address(0), - extraArgs: "" - }); - - Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); - - bytes32 expectedMsgId = keccak256(abi.encode("ccip send")); - vm.mockCall( - ROUTER, - abi.encodeWithSelector(IRouterClient.getFee.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), - abi.encode(FEE_WEI) - ); - vm.mockCall( - ROUTER, - FEE_WEI, - abi.encodeWithSelector(IRouterClient.ccipSend.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), - abi.encode(expectedMsgId) - ); - - bytes32 actualMsgId = s_etherSenderReceiver.ccipSend{value: AMOUNT + FEE_WEI}(DESTINATION_CHAIN_SELECTOR, message); - assertEq(actualMsgId, expectedMsgId, "message id must be correct"); - } - - function test_ccipSend_success_feeToken() public { - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); - tokenAmounts[0] = Client.EVMTokenAmount({ - token: address(0), // callers may not specify this. - amount: AMOUNT - }); - Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(XCHAIN_RECEIVER), - data: "", - tokenAmounts: tokenAmounts, - feeToken: address(s_linkToken), - extraArgs: "" - }); - - Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); - - bytes32 expectedMsgId = keccak256(abi.encode("ccip send")); - vm.mockCall( - ROUTER, - abi.encodeWithSelector(IRouterClient.getFee.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), - abi.encode(FEE_JUELS) - ); - vm.mockCall( - ROUTER, - abi.encodeWithSelector(IRouterClient.ccipSend.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), - abi.encode(expectedMsgId) - ); - - s_linkToken.approve(address(s_etherSenderReceiver), FEE_JUELS); - - bytes32 actualMsgId = s_etherSenderReceiver.ccipSend{value: AMOUNT}(DESTINATION_CHAIN_SELECTOR, message); - assertEq(actualMsgId, expectedMsgId, "message id must be correct"); - uint256 routerAllowance = s_linkToken.allowance(address(s_etherSenderReceiver), ROUTER); - assertEq(routerAllowance, FEE_JUELS, "router allowance must be feeJuels"); - } - - function test_ccipSend_success_weth() public { - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); - tokenAmounts[0] = Client.EVMTokenAmount({ - token: address(0), // callers may not specify this. - amount: AMOUNT - }); - Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(XCHAIN_RECEIVER), - data: "", - tokenAmounts: tokenAmounts, - feeToken: address(s_weth), - extraArgs: "" - }); - - Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); - - bytes32 expectedMsgId = keccak256(abi.encode("ccip send")); - vm.mockCall( - ROUTER, - abi.encodeWithSelector(IRouterClient.getFee.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), - abi.encode(FEE_WEI) - ); - vm.mockCall( - ROUTER, - abi.encodeWithSelector(IRouterClient.ccipSend.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), - abi.encode(expectedMsgId) - ); - - s_weth.approve(address(s_etherSenderReceiver), FEE_WEI); - - bytes32 actualMsgId = s_etherSenderReceiver.ccipSend{value: AMOUNT}(DESTINATION_CHAIN_SELECTOR, message); - assertEq(actualMsgId, expectedMsgId, "message id must be correct"); - uint256 routerAllowance = s_weth.allowance(address(s_etherSenderReceiver), ROUTER); - assertEq(routerAllowance, type(uint256).max, "router allowance must be max for weth"); - } -} diff --git a/contracts/src/v0.8/ccip/test/applications/EtherSenderReceiver/EtherSenderReceiverTest.ccipReceive.t.sol b/contracts/src/v0.8/ccip/test/applications/EtherSenderReceiver/EtherSenderReceiverTest.ccipReceive.t.sol new file mode 100644 index 00000000000..4df7e23b3d7 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/applications/EtherSenderReceiver/EtherSenderReceiverTest.ccipReceive.t.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Client} from "../../../libraries/Client.sol"; + +import {EtherSenderReceiverTestSetup} from "./EtherSenderReceiverTestSetup.t.sol"; + +contract EtherSenderReceiverTest_ccipReceive is EtherSenderReceiverTestSetup { + uint64 internal constant SOURCE_CHAIN_SELECTOR = 424242; + address internal constant XCHAIN_SENDER = 0x9951529C13B01E542f7eE3b6D6665D292e9BA2E0; + + error InvalidTokenAmounts(uint256 gotAmounts); + error InvalidToken(address gotToken, address expectedToken); + + function test_Fuzz_ccipReceive( + uint256 tokenAmount + ) public { + // cap to 10 ether because OWNER only has 10 ether. + if (tokenAmount > 10 ether) { + return; + } + + Client.EVMTokenAmount[] memory destTokenAmounts = new Client.EVMTokenAmount[](1); + destTokenAmounts[0] = Client.EVMTokenAmount({token: address(s_weth), amount: tokenAmount}); + Client.Any2EVMMessage memory message = Client.Any2EVMMessage({ + messageId: keccak256(abi.encode("ccip send")), + sourceChainSelector: SOURCE_CHAIN_SELECTOR, + sender: abi.encode(XCHAIN_SENDER), + data: abi.encode(OWNER), + destTokenAmounts: destTokenAmounts + }); + + // simulate a cross-chain token transfer, just transfer the weth to s_etherSenderReceiver. + s_weth.transfer(address(s_etherSenderReceiver), tokenAmount); + + uint256 balanceBefore = OWNER.balance; + s_etherSenderReceiver.publicCcipReceive(message); + uint256 balanceAfter = OWNER.balance; + assertEq(balanceAfter, balanceBefore + tokenAmount, "balance must be correct"); + } + + function test_ccipReceive_happyPath() public { + Client.EVMTokenAmount[] memory destTokenAmounts = new Client.EVMTokenAmount[](1); + destTokenAmounts[0] = Client.EVMTokenAmount({token: address(s_weth), amount: AMOUNT}); + Client.Any2EVMMessage memory message = Client.Any2EVMMessage({ + messageId: keccak256(abi.encode("ccip send")), + sourceChainSelector: 424242, + sender: abi.encode(XCHAIN_SENDER), + data: abi.encode(OWNER), + destTokenAmounts: destTokenAmounts + }); + + // simulate a cross-chain token transfer, just transfer the weth to s_etherSenderReceiver. + s_weth.transfer(address(s_etherSenderReceiver), AMOUNT); + + uint256 balanceBefore = OWNER.balance; + s_etherSenderReceiver.publicCcipReceive(message); + uint256 balanceAfter = OWNER.balance; + assertEq(balanceAfter, balanceBefore + AMOUNT, "balance must be correct"); + } + + function test_ccipReceive_fallbackToWethTransfer() public { + Client.EVMTokenAmount[] memory destTokenAmounts = new Client.EVMTokenAmount[](1); + destTokenAmounts[0] = Client.EVMTokenAmount({token: address(s_weth), amount: AMOUNT}); + Client.Any2EVMMessage memory message = Client.Any2EVMMessage({ + messageId: keccak256(abi.encode("ccip send")), + sourceChainSelector: 424242, + sender: abi.encode(XCHAIN_SENDER), + data: abi.encode(address(s_linkToken)), // ERC20 cannot receive() ether. + destTokenAmounts: destTokenAmounts + }); + + // simulate a cross-chain token transfer, just transfer the weth to s_etherSenderReceiver. + s_weth.transfer(address(s_etherSenderReceiver), AMOUNT); + + uint256 balanceBefore = address(s_linkToken).balance; + s_etherSenderReceiver.publicCcipReceive(message); + uint256 balanceAfter = address(s_linkToken).balance; + assertEq(balanceAfter, balanceBefore, "balance must be unchanged"); + uint256 wethBalance = s_weth.balanceOf(address(s_linkToken)); + assertEq(wethBalance, AMOUNT, "weth balance must be correct"); + } + + function test_ccipReceive_wrongTokenAmount() public { + Client.EVMTokenAmount[] memory destTokenAmounts = new Client.EVMTokenAmount[](2); + destTokenAmounts[0] = Client.EVMTokenAmount({token: address(s_weth), amount: AMOUNT}); + destTokenAmounts[1] = Client.EVMTokenAmount({token: address(s_weth), amount: AMOUNT}); + Client.Any2EVMMessage memory message = Client.Any2EVMMessage({ + messageId: keccak256(abi.encode("ccip send")), + sourceChainSelector: 424242, + sender: abi.encode(XCHAIN_SENDER), + data: abi.encode(OWNER), + destTokenAmounts: destTokenAmounts + }); + + vm.expectRevert(abi.encodeWithSelector(InvalidTokenAmounts.selector, uint256(2))); + s_etherSenderReceiver.publicCcipReceive(message); + } + + function test_ccipReceive_wrongToken() public { + Client.EVMTokenAmount[] memory destTokenAmounts = new Client.EVMTokenAmount[](1); + destTokenAmounts[0] = Client.EVMTokenAmount({token: address(s_someOtherWeth), amount: AMOUNT}); + Client.Any2EVMMessage memory message = Client.Any2EVMMessage({ + messageId: keccak256(abi.encode("ccip send")), + sourceChainSelector: 424242, + sender: abi.encode(XCHAIN_SENDER), + data: abi.encode(OWNER), + destTokenAmounts: destTokenAmounts + }); + + vm.expectRevert(abi.encodeWithSelector(InvalidToken.selector, address(s_someOtherWeth), address(s_weth))); + s_etherSenderReceiver.publicCcipReceive(message); + } +} diff --git a/contracts/src/v0.8/ccip/test/applications/EtherSenderReceiver/EtherSenderReceiverTest.ccipSend.t.sol b/contracts/src/v0.8/ccip/test/applications/EtherSenderReceiver/EtherSenderReceiverTest.ccipSend.t.sol new file mode 100644 index 00000000000..a64ce7c2120 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/applications/EtherSenderReceiver/EtherSenderReceiverTest.ccipSend.t.sol @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IRouterClient} from "../../../interfaces/IRouterClient.sol"; +import {Client} from "../../../libraries/Client.sol"; + +import {EtherSenderReceiverTestSetup} from "./EtherSenderReceiverTestSetup.t.sol"; + +contract EtherSenderReceiverTest_ccipSend is EtherSenderReceiverTestSetup { + error InsufficientFee(uint256 gotFee, uint256 fee); + + uint64 internal constant DESTINATION_CHAIN_SELECTOR = 424242; + uint256 internal constant FEE_WEI = 121212; + uint256 internal constant FEE_JUELS = 232323; + + function test_Fuzz_ccipSend(uint256 feeFromRouter, uint256 feeSupplied) public { + // cap the fuzzer because OWNER only has a million ether. + vm.assume(feeSupplied < 1_000_000 ether - AMOUNT); + + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); + tokenAmounts[0] = Client.EVMTokenAmount({ + token: address(0), // callers may not specify this. + amount: AMOUNT + }); + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(XCHAIN_RECEIVER), + data: "", + tokenAmounts: tokenAmounts, + feeToken: address(0), + extraArgs: "" + }); + + Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); + + vm.mockCall( + ROUTER, + abi.encodeWithSelector(IRouterClient.getFee.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), + abi.encode(feeFromRouter) + ); + + if (feeSupplied < feeFromRouter) { + vm.expectRevert(); + s_etherSenderReceiver.ccipSend{value: AMOUNT + feeSupplied}(DESTINATION_CHAIN_SELECTOR, message); + } else { + bytes32 expectedMsgId = keccak256(abi.encode("ccip send")); + vm.mockCall( + ROUTER, + feeSupplied, + abi.encodeWithSelector(IRouterClient.ccipSend.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), + abi.encode(expectedMsgId) + ); + + bytes32 actualMsgId = + s_etherSenderReceiver.ccipSend{value: AMOUNT + feeSupplied}(DESTINATION_CHAIN_SELECTOR, message); + assertEq(actualMsgId, expectedMsgId, "message id must be correct"); + } + } + + function test_Fuzz_ccipSend_feeToken(uint256 feeFromRouter, uint256 feeSupplied) public { + // cap the fuzzer because OWNER only has a million LINK. + vm.assume(feeSupplied < 1_000_000 ether - AMOUNT); + + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); + tokenAmounts[0] = Client.EVMTokenAmount({ + token: address(0), // callers may not specify this. + amount: AMOUNT + }); + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(XCHAIN_RECEIVER), + data: "", + tokenAmounts: tokenAmounts, + feeToken: address(s_linkToken), + extraArgs: "" + }); + + Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); + + vm.mockCall( + ROUTER, + abi.encodeWithSelector(IRouterClient.getFee.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), + abi.encode(feeFromRouter) + ); + + s_linkToken.approve(address(s_etherSenderReceiver), feeSupplied); + + if (feeSupplied < feeFromRouter) { + vm.expectRevert(); + s_etherSenderReceiver.ccipSend{value: AMOUNT}(DESTINATION_CHAIN_SELECTOR, message); + } else { + bytes32 expectedMsgId = keccak256(abi.encode("ccip send")); + vm.mockCall( + ROUTER, + abi.encodeWithSelector(IRouterClient.ccipSend.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), + abi.encode(expectedMsgId) + ); + + bytes32 actualMsgId = s_etherSenderReceiver.ccipSend{value: AMOUNT}(DESTINATION_CHAIN_SELECTOR, message); + assertEq(actualMsgId, expectedMsgId, "message id must be correct"); + } + } + + function test_ccipSend_reverts_insufficientFee_weth() public { + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); + tokenAmounts[0] = Client.EVMTokenAmount({ + token: address(0), // callers may not specify this. + amount: AMOUNT + }); + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(XCHAIN_RECEIVER), + data: "", + tokenAmounts: tokenAmounts, + feeToken: address(s_weth), + extraArgs: "" + }); + + Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); + + vm.mockCall( + ROUTER, + abi.encodeWithSelector(IRouterClient.getFee.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), + abi.encode(FEE_WEI) + ); + + s_weth.approve(address(s_etherSenderReceiver), FEE_WEI - 1); + + vm.expectRevert("SafeERC20: low-level call failed"); + s_etherSenderReceiver.ccipSend{value: AMOUNT}(DESTINATION_CHAIN_SELECTOR, message); + } + + function test_ccipSend_reverts_insufficientFee_feeToken() public { + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); + tokenAmounts[0] = Client.EVMTokenAmount({ + token: address(0), // callers may not specify this. + amount: AMOUNT + }); + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(XCHAIN_RECEIVER), + data: "", + tokenAmounts: tokenAmounts, + feeToken: address(s_linkToken), + extraArgs: "" + }); + + Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); + + vm.mockCall( + ROUTER, + abi.encodeWithSelector(IRouterClient.getFee.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), + abi.encode(FEE_JUELS) + ); + + s_linkToken.approve(address(s_etherSenderReceiver), FEE_JUELS - 1); + + vm.expectRevert("ERC20: insufficient allowance"); + s_etherSenderReceiver.ccipSend{value: AMOUNT}(DESTINATION_CHAIN_SELECTOR, message); + } + + function test_ccipSend_reverts_insufficientFee_native() public { + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); + tokenAmounts[0] = Client.EVMTokenAmount({ + token: address(0), // callers may not specify this. + amount: AMOUNT + }); + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(XCHAIN_RECEIVER), + data: "", + tokenAmounts: tokenAmounts, + feeToken: address(0), + extraArgs: "" + }); + + Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); + + vm.mockCall( + ROUTER, + abi.encodeWithSelector(IRouterClient.getFee.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), + abi.encode(FEE_WEI) + ); + + vm.expectRevert(); + s_etherSenderReceiver.ccipSend{value: AMOUNT + FEE_WEI - 1}(DESTINATION_CHAIN_SELECTOR, message); + } + + function test_ccipSend_success_nativeExcess() public { + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); + tokenAmounts[0] = Client.EVMTokenAmount({ + token: address(0), // callers may not specify this. + amount: AMOUNT + }); + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(XCHAIN_RECEIVER), + data: "", + tokenAmounts: tokenAmounts, + feeToken: address(0), + extraArgs: "" + }); + + Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); + + bytes32 expectedMsgId = keccak256(abi.encode("ccip send")); + vm.mockCall( + ROUTER, + abi.encodeWithSelector(IRouterClient.getFee.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), + abi.encode(FEE_WEI) + ); + + // we assert that the correct value is sent to the router call, which should be + // the msg.value - feeWei. + vm.mockCall( + ROUTER, + FEE_WEI + 1, + abi.encodeWithSelector(IRouterClient.ccipSend.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), + abi.encode(expectedMsgId) + ); + + bytes32 actualMsgId = + s_etherSenderReceiver.ccipSend{value: AMOUNT + FEE_WEI + 1}(DESTINATION_CHAIN_SELECTOR, message); + assertEq(actualMsgId, expectedMsgId, "message id must be correct"); + } + + function test_ccipSend_success_native() public { + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); + tokenAmounts[0] = Client.EVMTokenAmount({ + token: address(0), // callers may not specify this. + amount: AMOUNT + }); + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(XCHAIN_RECEIVER), + data: "", + tokenAmounts: tokenAmounts, + feeToken: address(0), + extraArgs: "" + }); + + Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); + + bytes32 expectedMsgId = keccak256(abi.encode("ccip send")); + vm.mockCall( + ROUTER, + abi.encodeWithSelector(IRouterClient.getFee.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), + abi.encode(FEE_WEI) + ); + vm.mockCall( + ROUTER, + FEE_WEI, + abi.encodeWithSelector(IRouterClient.ccipSend.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), + abi.encode(expectedMsgId) + ); + + bytes32 actualMsgId = s_etherSenderReceiver.ccipSend{value: AMOUNT + FEE_WEI}(DESTINATION_CHAIN_SELECTOR, message); + assertEq(actualMsgId, expectedMsgId, "message id must be correct"); + } + + function test_ccipSend_success_feeToken() public { + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); + tokenAmounts[0] = Client.EVMTokenAmount({ + token: address(0), // callers may not specify this. + amount: AMOUNT + }); + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(XCHAIN_RECEIVER), + data: "", + tokenAmounts: tokenAmounts, + feeToken: address(s_linkToken), + extraArgs: "" + }); + + Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); + + bytes32 expectedMsgId = keccak256(abi.encode("ccip send")); + vm.mockCall( + ROUTER, + abi.encodeWithSelector(IRouterClient.getFee.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), + abi.encode(FEE_JUELS) + ); + vm.mockCall( + ROUTER, + abi.encodeWithSelector(IRouterClient.ccipSend.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), + abi.encode(expectedMsgId) + ); + + s_linkToken.approve(address(s_etherSenderReceiver), FEE_JUELS); + + bytes32 actualMsgId = s_etherSenderReceiver.ccipSend{value: AMOUNT}(DESTINATION_CHAIN_SELECTOR, message); + assertEq(actualMsgId, expectedMsgId, "message id must be correct"); + uint256 routerAllowance = s_linkToken.allowance(address(s_etherSenderReceiver), ROUTER); + assertEq(routerAllowance, FEE_JUELS, "router allowance must be feeJuels"); + } + + function test_ccipSend_success_weth() public { + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); + tokenAmounts[0] = Client.EVMTokenAmount({ + token: address(0), // callers may not specify this. + amount: AMOUNT + }); + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(XCHAIN_RECEIVER), + data: "", + tokenAmounts: tokenAmounts, + feeToken: address(s_weth), + extraArgs: "" + }); + + Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); + + bytes32 expectedMsgId = keccak256(abi.encode("ccip send")); + vm.mockCall( + ROUTER, + abi.encodeWithSelector(IRouterClient.getFee.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), + abi.encode(FEE_WEI) + ); + vm.mockCall( + ROUTER, + abi.encodeWithSelector(IRouterClient.ccipSend.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), + abi.encode(expectedMsgId) + ); + + s_weth.approve(address(s_etherSenderReceiver), FEE_WEI); + + bytes32 actualMsgId = s_etherSenderReceiver.ccipSend{value: AMOUNT}(DESTINATION_CHAIN_SELECTOR, message); + assertEq(actualMsgId, expectedMsgId, "message id must be correct"); + uint256 routerAllowance = s_weth.allowance(address(s_etherSenderReceiver), ROUTER); + assertEq(routerAllowance, type(uint256).max, "router allowance must be max for weth"); + } +} diff --git a/contracts/src/v0.8/ccip/test/applications/EtherSenderReceiver/EtherSenderReceiverTest.constructor.t.sol b/contracts/src/v0.8/ccip/test/applications/EtherSenderReceiver/EtherSenderReceiverTest.constructor.t.sol new file mode 100644 index 00000000000..7dbf668820f --- /dev/null +++ b/contracts/src/v0.8/ccip/test/applications/EtherSenderReceiver/EtherSenderReceiverTest.constructor.t.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {EtherSenderReceiverTestSetup} from "./EtherSenderReceiverTestSetup.t.sol"; + +contract EtherSenderReceiverTest_constructor is EtherSenderReceiverTestSetup { + function test_constructor() public view { + assertEq(s_etherSenderReceiver.getRouter(), ROUTER, "router must be set correctly"); + uint256 allowance = s_weth.allowance(address(s_etherSenderReceiver), ROUTER); + assertEq(allowance, type(uint256).max, "allowance must be set infinite"); + } +} diff --git a/contracts/src/v0.8/ccip/test/applications/EtherSenderReceiver/EtherSenderReceiverTest.getFee.t.sol b/contracts/src/v0.8/ccip/test/applications/EtherSenderReceiver/EtherSenderReceiverTest.getFee.t.sol new file mode 100644 index 00000000000..78a62449b98 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/applications/EtherSenderReceiver/EtherSenderReceiverTest.getFee.t.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IRouterClient} from "../../../interfaces/IRouterClient.sol"; +import {Client} from "../../../libraries/Client.sol"; +import {EtherSenderReceiverTestSetup} from "./EtherSenderReceiverTestSetup.t.sol"; + +contract EtherSenderReceiverTest_getFee is EtherSenderReceiverTestSetup { + uint64 internal constant DESTINATION_CHAIN_SELECTOR = 424242; + uint256 internal constant FEE_WEI = 121212; + + function test_getFee() public { + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); + tokenAmounts[0] = Client.EVMTokenAmount({token: address(0), amount: AMOUNT}); + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(XCHAIN_RECEIVER), + data: "", + tokenAmounts: tokenAmounts, + feeToken: address(0), + extraArgs: "" + }); + + Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); + + vm.mockCall( + ROUTER, + abi.encodeWithSelector(IRouterClient.getFee.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), + abi.encode(FEE_WEI) + ); + + uint256 fee = s_etherSenderReceiver.getFee(DESTINATION_CHAIN_SELECTOR, message); + assertEq(fee, FEE_WEI, "fee must be feeWei"); + } +} diff --git a/contracts/src/v0.8/ccip/test/applications/EtherSenderReceiver/EtherSenderReceiverTest.validateFeeToken.t.sol b/contracts/src/v0.8/ccip/test/applications/EtherSenderReceiver/EtherSenderReceiverTest.validateFeeToken.t.sol new file mode 100644 index 00000000000..ae24ca3deae --- /dev/null +++ b/contracts/src/v0.8/ccip/test/applications/EtherSenderReceiver/EtherSenderReceiverTest.validateFeeToken.t.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Client} from "../../../libraries/Client.sol"; +import {EtherSenderReceiverTestSetup} from "./EtherSenderReceiverTestSetup.t.sol"; + +contract EtherSenderReceiverTest_validateFeeToken is EtherSenderReceiverTestSetup { + error InsufficientMsgValue(uint256 gotAmount, uint256 msgValue); + error TokenAmountNotEqualToMsgValue(uint256 gotAmount, uint256 msgValue); + + function test_validateFeeToken_valid_native() public { + Client.EVMTokenAmount[] memory tokenAmount = new Client.EVMTokenAmount[](1); + tokenAmount[0] = Client.EVMTokenAmount({token: address(s_weth), amount: AMOUNT}); + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(XCHAIN_RECEIVER), + data: "", + tokenAmounts: tokenAmount, + feeToken: address(0), + extraArgs: "" + }); + + s_etherSenderReceiver.validateFeeToken{value: AMOUNT + 1}(message); + } + + function test_validateFeeToken_valid_feeToken() public { + Client.EVMTokenAmount[] memory tokenAmount = new Client.EVMTokenAmount[](1); + tokenAmount[0] = Client.EVMTokenAmount({token: address(s_weth), amount: AMOUNT}); + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(XCHAIN_RECEIVER), + data: "", + tokenAmounts: tokenAmount, + feeToken: address(s_weth), + extraArgs: "" + }); + + s_etherSenderReceiver.validateFeeToken{value: AMOUNT}(message); + } + + function test_validateFeeToken_reverts_feeToken_tokenAmountNotEqualToMsgValue() public { + Client.EVMTokenAmount[] memory tokenAmount = new Client.EVMTokenAmount[](1); + tokenAmount[0] = Client.EVMTokenAmount({token: address(s_weth), amount: AMOUNT}); + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(XCHAIN_RECEIVER), + data: "", + tokenAmounts: tokenAmount, + feeToken: address(s_weth), + extraArgs: "" + }); + + vm.expectRevert(abi.encodeWithSelector(TokenAmountNotEqualToMsgValue.selector, AMOUNT, AMOUNT + 1)); + s_etherSenderReceiver.validateFeeToken{value: AMOUNT + 1}(message); + } +} diff --git a/contracts/src/v0.8/ccip/test/applications/EtherSenderReceiver/EtherSenderReceiverTest.validatedMessage.t.sol b/contracts/src/v0.8/ccip/test/applications/EtherSenderReceiver/EtherSenderReceiverTest.validatedMessage.t.sol new file mode 100644 index 00000000000..91ccf2034d9 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/applications/EtherSenderReceiver/EtherSenderReceiverTest.validatedMessage.t.sol @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Client} from "../../../libraries/Client.sol"; +import {EtherSenderReceiverTestSetup} from "./EtherSenderReceiverTestSetup.t.sol"; + +contract EtherSenderReceiverTest_validatedMessage is EtherSenderReceiverTestSetup { + error InvalidDestinationReceiver(bytes destReceiver); + error InvalidTokenAmounts(uint256 gotAmounts); + error InvalidWethAddress(address want, address got); + error GasLimitTooLow(uint256 minLimit, uint256 gotLimit); + + function test_Fuzz_validatedMessage_msgSenderOverwrite( + bytes memory data + ) public view { + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); + tokenAmounts[0] = Client.EVMTokenAmount({ + token: address(0), // callers may not specify this. + amount: AMOUNT + }); + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(XCHAIN_RECEIVER), + data: data, + tokenAmounts: tokenAmounts, + feeToken: address(0), + extraArgs: "" + }); + + Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); + assertEq(validatedMessage.receiver, abi.encode(XCHAIN_RECEIVER), "receiver must be XCHAIN_RECEIVER"); + assertEq(validatedMessage.data, abi.encode(OWNER), "data must be msg.sender"); + assertEq(validatedMessage.tokenAmounts[0].token, address(s_weth), "token must be weth"); + assertEq(validatedMessage.tokenAmounts[0].amount, AMOUNT, "amount must be correct"); + assertEq(validatedMessage.feeToken, address(0), "feeToken must be 0"); + assertEq(validatedMessage.extraArgs, bytes(""), "extraArgs must be empty"); + } + + function test_Fuzz_validatedMessage_tokenAddressOverwrite( + address token + ) public view { + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); + tokenAmounts[0] = Client.EVMTokenAmount({token: token, amount: AMOUNT}); + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(XCHAIN_RECEIVER), + data: "", + tokenAmounts: tokenAmounts, + feeToken: address(0), + extraArgs: "" + }); + + Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); + assertEq(validatedMessage.receiver, abi.encode(XCHAIN_RECEIVER), "receiver must be XCHAIN_RECEIVER"); + assertEq(validatedMessage.data, abi.encode(OWNER), "data must be msg.sender"); + assertEq(validatedMessage.tokenAmounts[0].token, address(s_weth), "token must be weth"); + assertEq(validatedMessage.tokenAmounts[0].amount, AMOUNT, "amount must be correct"); + assertEq(validatedMessage.feeToken, address(0), "feeToken must be 0"); + assertEq(validatedMessage.extraArgs, bytes(""), "extraArgs must be empty"); + } + + function test_validatedMessage_emptyDataOverwrittenToMsgSender() public view { + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); + tokenAmounts[0] = Client.EVMTokenAmount({ + token: address(0), // callers may not specify this. + amount: AMOUNT + }); + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(XCHAIN_RECEIVER), + data: "", + tokenAmounts: tokenAmounts, + feeToken: address(0), + extraArgs: "" + }); + + Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); + assertEq(validatedMessage.receiver, abi.encode(XCHAIN_RECEIVER), "receiver must be XCHAIN_RECEIVER"); + assertEq(validatedMessage.data, abi.encode(OWNER), "data must be msg.sender"); + assertEq(validatedMessage.tokenAmounts[0].token, address(s_weth), "token must be weth"); + assertEq(validatedMessage.tokenAmounts[0].amount, AMOUNT, "amount must be correct"); + assertEq(validatedMessage.feeToken, address(0), "feeToken must be 0"); + assertEq(validatedMessage.extraArgs, bytes(""), "extraArgs must be empty"); + } + + function test_validatedMessage_dataOverwrittenToMsgSender() public view { + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); + tokenAmounts[0] = Client.EVMTokenAmount({ + token: address(0), // callers may not specify this. + amount: AMOUNT + }); + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(XCHAIN_RECEIVER), + data: abi.encode(address(42)), + tokenAmounts: tokenAmounts, + feeToken: address(0), + extraArgs: "" + }); + + Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); + assertEq(validatedMessage.receiver, abi.encode(XCHAIN_RECEIVER), "receiver must be XCHAIN_RECEIVER"); + assertEq(validatedMessage.data, abi.encode(OWNER), "data must be msg.sender"); + assertEq(validatedMessage.tokenAmounts[0].token, address(s_weth), "token must be weth"); + assertEq(validatedMessage.tokenAmounts[0].amount, AMOUNT, "amount must be correct"); + assertEq(validatedMessage.feeToken, address(0), "feeToken must be 0"); + assertEq(validatedMessage.extraArgs, bytes(""), "extraArgs must be empty"); + } + + function test_validatedMessage_tokenOverwrittenToWeth() public view { + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); + tokenAmounts[0] = Client.EVMTokenAmount({ + token: address(42), // incorrect token. + amount: AMOUNT + }); + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(XCHAIN_RECEIVER), + data: "", + tokenAmounts: tokenAmounts, + feeToken: address(0), + extraArgs: "" + }); + + Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); + assertEq(validatedMessage.receiver, abi.encode(XCHAIN_RECEIVER), "receiver must be XCHAIN_RECEIVER"); + assertEq(validatedMessage.data, abi.encode(OWNER), "data must be msg.sender"); + assertEq(validatedMessage.tokenAmounts[0].token, address(s_weth), "token must be weth"); + assertEq(validatedMessage.tokenAmounts[0].amount, AMOUNT, "amount must be correct"); + assertEq(validatedMessage.feeToken, address(0), "feeToken must be 0"); + assertEq(validatedMessage.extraArgs, bytes(""), "extraArgs must be empty"); + } + + function test_validatedMessage_validMessage_extraArgs() public view { + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); + tokenAmounts[0] = Client.EVMTokenAmount({ + token: address(0), // callers may not specify this. + amount: AMOUNT + }); + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(XCHAIN_RECEIVER), + data: "", + tokenAmounts: tokenAmounts, + feeToken: address(0), + extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: 200_000})) + }); + + Client.EVM2AnyMessage memory validatedMessage = s_etherSenderReceiver.validatedMessage(message); + assertEq(validatedMessage.receiver, abi.encode(XCHAIN_RECEIVER), "receiver must be XCHAIN_RECEIVER"); + assertEq(validatedMessage.data, abi.encode(OWNER), "data must be msg.sender"); + assertEq(validatedMessage.tokenAmounts[0].token, address(s_weth), "token must be weth"); + assertEq(validatedMessage.tokenAmounts[0].amount, AMOUNT, "amount must be correct"); + assertEq(validatedMessage.feeToken, address(0), "feeToken must be 0"); + assertEq( + validatedMessage.extraArgs, + Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: 200_000})), + "extraArgs must be correct" + ); + } + + function test_validatedMessage_invalidTokenAmounts() public { + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); + tokenAmounts[0] = Client.EVMTokenAmount({token: address(0), amount: AMOUNT}); + tokenAmounts[1] = Client.EVMTokenAmount({token: address(0), amount: AMOUNT}); + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(XCHAIN_RECEIVER), + data: "", + tokenAmounts: tokenAmounts, + feeToken: address(0), + extraArgs: "" + }); + + vm.expectRevert(abi.encodeWithSelector(InvalidTokenAmounts.selector, uint256(2))); + s_etherSenderReceiver.validatedMessage(message); + } +} diff --git a/contracts/src/v0.8/ccip/test/applications/EtherSenderReceiver/EtherSenderReceiverTestSetup.t.sol b/contracts/src/v0.8/ccip/test/applications/EtherSenderReceiver/EtherSenderReceiverTestSetup.t.sol new file mode 100644 index 00000000000..b989a11b3ef --- /dev/null +++ b/contracts/src/v0.8/ccip/test/applications/EtherSenderReceiver/EtherSenderReceiverTestSetup.t.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Test} from "forge-std/Test.sol"; + +import {ICCIPRouter} from "../../../applications/EtherSenderReceiver.sol"; + +import {WETH9} from "../../WETH9.sol"; +import {EtherSenderReceiverHelper} from "../../helpers/EtherSenderReceiverHelper.sol"; + +import {ERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/ERC20.sol"; + +contract EtherSenderReceiverTestSetup is Test { + EtherSenderReceiverHelper internal s_etherSenderReceiver; + WETH9 internal s_weth; + WETH9 internal s_someOtherWeth; + ERC20 internal s_linkToken; + + address internal constant OWNER = 0x00007e64E1fB0C487F25dd6D3601ff6aF8d32e4e; + address internal constant ROUTER = 0x0F3779ee3a832D10158073ae2F5e61ac7FBBF880; + address internal constant XCHAIN_RECEIVER = 0xBd91b2073218AF872BF73b65e2e5950ea356d147; + uint256 internal constant AMOUNT = 100; + + function setUp() public { + vm.startPrank(OWNER); + + s_linkToken = new ERC20("Chainlink Token", "LINK"); + s_someOtherWeth = new WETH9(); + s_weth = new WETH9(); + vm.mockCall(ROUTER, abi.encodeWithSelector(ICCIPRouter.getWrappedNative.selector), abi.encode(address(s_weth))); + s_etherSenderReceiver = new EtherSenderReceiverHelper(ROUTER); + + deal(OWNER, 1_000_000 ether); + deal(address(s_linkToken), OWNER, 1_000_000 ether); + + // deposit some eth into the weth contract. + s_weth.deposit{value: 10 ether}(); + uint256 wethSupply = s_weth.totalSupply(); + assertEq(wethSupply, 10 ether, "total weth supply must be 10 ether"); + } +} diff --git a/contracts/src/v0.8/ccip/test/applications/ImmutableExample.t.sol b/contracts/src/v0.8/ccip/test/applications/ImmutableExample/ImmutableExample.t.sol similarity index 82% rename from contracts/src/v0.8/ccip/test/applications/ImmutableExample.t.sol rename to contracts/src/v0.8/ccip/test/applications/ImmutableExample/ImmutableExample.t.sol index f3f09ecc78c..2eb9b736ad4 100644 --- a/contracts/src/v0.8/ccip/test/applications/ImmutableExample.t.sol +++ b/contracts/src/v0.8/ccip/test/applications/ImmutableExample/ImmutableExample.t.sol @@ -1,14 +1,14 @@ pragma solidity ^0.8.0; -import {IAny2EVMMessageReceiver} from "../../interfaces/IAny2EVMMessageReceiver.sol"; +import {IAny2EVMMessageReceiver} from "../../../interfaces/IAny2EVMMessageReceiver.sol"; -import {CCIPClientExample} from "../../applications/CCIPClientExample.sol"; -import {Client} from "../../libraries/Client.sol"; -import {OnRampSetup} from "../onRamp/onRamp/OnRampSetup.t.sol"; +import {CCIPClientExample} from "../../../applications/CCIPClientExample.sol"; +import {Client} from "../../../libraries/Client.sol"; +import {OnRampSetup} from "../../onRamp/OnRamp/OnRampSetup.t.sol"; -import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; import {ERC165Checker} from - "../../../vendor/openzeppelin-solidity/v5.0.2/contracts/utils/introspection/ERC165Checker.sol"; + "../../../../vendor/openzeppelin-solidity/v5.0.2/contracts/utils/introspection/ERC165Checker.sol"; contract CCIPClientExample_sanity is OnRampSetup { function test_ImmutableExamples_Success() public { diff --git a/contracts/src/v0.8/ccip/test/applications/PingPong/PingPong.ccipReceive.t.sol b/contracts/src/v0.8/ccip/test/applications/PingPong/PingPong.ccipReceive.t.sol new file mode 100644 index 00000000000..a7559b6dea2 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/applications/PingPong/PingPong.ccipReceive.t.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {PingPongDemo} from "../../../applications/PingPongDemo.sol"; +import {Client} from "../../../libraries/Client.sol"; + +import {PingPongDappSetup} from "./PingPongDappSetup.t.sol"; + +contract PingPong_ccipReceive is PingPongDappSetup { + function test_CcipReceive_Success() public { + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](0); + + uint256 pingPongNumber = 5; + + Client.Any2EVMMessage memory message = Client.Any2EVMMessage({ + messageId: bytes32("a"), + sourceChainSelector: DEST_CHAIN_SELECTOR, + sender: abi.encode(i_pongContract), + data: abi.encode(pingPongNumber), + destTokenAmounts: tokenAmounts + }); + + vm.startPrank(address(s_sourceRouter)); + + vm.expectEmit(); + emit PingPongDemo.Pong(pingPongNumber + 1); + + s_pingPong.ccipReceive(message); + } +} diff --git a/contracts/src/v0.8/ccip/test/applications/PingPong/PingPong.setCounterpart.t.sol b/contracts/src/v0.8/ccip/test/applications/PingPong/PingPong.setCounterpart.t.sol new file mode 100644 index 00000000000..365ccf33e71 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/applications/PingPong/PingPong.setCounterpart.t.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {PingPongDappSetup} from "./PingPongDappSetup.t.sol"; + +contract PingPong_setCounterpart is PingPongDappSetup { + function test_Fuzz_CounterPartAddress_Success(uint64 chainSelector, address counterpartAddress) public { + s_pingPong.setCounterpartChainSelector(chainSelector); + + s_pingPong.setCounterpart(chainSelector, counterpartAddress); + + assertEq(s_pingPong.getCounterpartAddress(), counterpartAddress); + assertEq(s_pingPong.getCounterpartChainSelector(), chainSelector); + } +} diff --git a/contracts/src/v0.8/ccip/test/applications/PingPong/PingPong.setCounterpartAddress.t.sol b/contracts/src/v0.8/ccip/test/applications/PingPong/PingPong.setCounterpartAddress.t.sol new file mode 100644 index 00000000000..6762d8aab85 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/applications/PingPong/PingPong.setCounterpartAddress.t.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {PingPongDappSetup} from "./PingPongDappSetup.t.sol"; + +contract PingPong_setCounterpartAddress is PingPongDappSetup { + function test_Fuzz_CounterPartAddress_Success( + address counterpartAddress + ) public { + s_pingPong.setCounterpartAddress(counterpartAddress); + + assertEq(s_pingPong.getCounterpartAddress(), counterpartAddress); + } +} diff --git a/contracts/src/v0.8/ccip/test/applications/PingPong/PingPong.setCounterpartChainSelector.t.sol b/contracts/src/v0.8/ccip/test/applications/PingPong/PingPong.setCounterpartChainSelector.t.sol new file mode 100644 index 00000000000..a6fea957753 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/applications/PingPong/PingPong.setCounterpartChainSelector.t.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {PingPongDappSetup} from "./PingPongDappSetup.t.sol"; + +contract PingPong_setCounterpartChainSelector is PingPongDappSetup { + function test_Fuzz_CounterPartChainSelector_Success( + uint64 chainSelector + ) public { + s_pingPong.setCounterpartChainSelector(chainSelector); + + assertEq(s_pingPong.getCounterpartChainSelector(), chainSelector); + } +} diff --git a/contracts/src/v0.8/ccip/test/applications/PingPong/PingPong.setOutOfOrderExecution.t.sol b/contracts/src/v0.8/ccip/test/applications/PingPong/PingPong.setOutOfOrderExecution.t.sol new file mode 100644 index 00000000000..0e09e67f7cb --- /dev/null +++ b/contracts/src/v0.8/ccip/test/applications/PingPong/PingPong.setOutOfOrderExecution.t.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {PingPongDemo} from "../../../applications/PingPongDemo.sol"; +import {PingPongDappSetup} from "./PingPongDappSetup.t.sol"; + +contract PingPong_setOutOfOrderExecution is PingPongDappSetup { + function test_OutOfOrderExecution_Success() public { + assertFalse(s_pingPong.getOutOfOrderExecution()); + + vm.expectEmit(); + emit PingPongDemo.OutOfOrderExecutionChange(true); + + s_pingPong.setOutOfOrderExecution(true); + + assertTrue(s_pingPong.getOutOfOrderExecution()); + } +} diff --git a/contracts/src/v0.8/ccip/test/applications/PingPong/PingPong.setPaused.t.sol b/contracts/src/v0.8/ccip/test/applications/PingPong/PingPong.setPaused.t.sol new file mode 100644 index 00000000000..82cd22199ec --- /dev/null +++ b/contracts/src/v0.8/ccip/test/applications/PingPong/PingPong.setPaused.t.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {PingPongDappSetup} from "./PingPongDappSetup.t.sol"; + +contract PingPong_setPaused is PingPongDappSetup { + function test_Pausing_Success() public { + assertFalse(s_pingPong.isPaused()); + + s_pingPong.setPaused(true); + + assertTrue(s_pingPong.isPaused()); + } +} diff --git a/contracts/src/v0.8/ccip/test/applications/PingPong/PingPong.startPingPong.t.sol b/contracts/src/v0.8/ccip/test/applications/PingPong/PingPong.startPingPong.t.sol new file mode 100644 index 00000000000..d9dfc980154 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/applications/PingPong/PingPong.startPingPong.t.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {PingPongDemo} from "../../../applications/PingPongDemo.sol"; +import {Internal} from "../../../libraries/Internal.sol"; +import {OnRamp} from "../../../onRamp/OnRamp.sol"; + +import {PingPongDappSetup} from "./PingPongDappSetup.t.sol"; + +contract PingPong_startPingPong is PingPongDappSetup { + uint256 internal s_pingPongNumber = 1; + + function test_StartPingPong_With_Sequenced_Ordered_Success() public { + _assertPingPongSuccess(); + } + + function test_StartPingPong_With_OOO_Success() public { + s_pingPong.setOutOfOrderExecution(true); + + _assertPingPongSuccess(); + } + + function _assertPingPongSuccess() internal { + vm.expectEmit(); + emit PingPongDemo.Ping(s_pingPongNumber); + + Internal.EVM2AnyRampMessage memory message; + + vm.expectEmit(false, false, false, false); + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, message); + + s_pingPong.startPingPong(); + } +} diff --git a/contracts/src/v0.8/ccip/test/applications/PingPong/PingPongDappSetup.t.sol b/contracts/src/v0.8/ccip/test/applications/PingPong/PingPongDappSetup.t.sol new file mode 100644 index 00000000000..8c009a0660d --- /dev/null +++ b/contracts/src/v0.8/ccip/test/applications/PingPong/PingPongDappSetup.t.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {PingPongDemo} from "../../../applications/PingPongDemo.sol"; +import {OnRampSetup} from "../../onRamp/OnRamp/OnRampSetup.t.sol"; + +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; + +contract PingPongDappSetup is OnRampSetup { + PingPongDemo internal s_pingPong; + IERC20 internal s_feeToken; + + address internal immutable i_pongContract = makeAddr("ping_pong_counterpart"); + + function setUp() public virtual override { + super.setUp(); + + s_feeToken = IERC20(s_sourceTokens[0]); + s_pingPong = new PingPongDemo(address(s_sourceRouter), s_feeToken); + s_pingPong.setCounterpart(DEST_CHAIN_SELECTOR, i_pongContract); + + uint256 fundingAmount = 1e18; + + // Fund the contract with LINK tokens + s_feeToken.transfer(address(s_pingPong), fundingAmount); + } +} diff --git a/contracts/src/v0.8/ccip/test/applications/PingPongDemo.t.sol b/contracts/src/v0.8/ccip/test/applications/PingPongDemo.t.sol deleted file mode 100644 index f645bd88cb5..00000000000 --- a/contracts/src/v0.8/ccip/test/applications/PingPongDemo.t.sol +++ /dev/null @@ -1,125 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.24; - -import {PingPongDemo} from "../../applications/PingPongDemo.sol"; -import {Client} from "../../libraries/Client.sol"; -import {Internal} from "../../libraries/Internal.sol"; -import {OnRamp} from "../../onRamp/OnRamp.sol"; -import {OnRampSetup} from "../onRamp/onRamp/OnRampSetup.t.sol"; - -import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; - -contract PingPongDappSetup is OnRampSetup { - PingPongDemo internal s_pingPong; - IERC20 internal s_feeToken; - - address internal immutable i_pongContract = makeAddr("ping_pong_counterpart"); - - function setUp() public virtual override { - super.setUp(); - - s_feeToken = IERC20(s_sourceTokens[0]); - s_pingPong = new PingPongDemo(address(s_sourceRouter), s_feeToken); - s_pingPong.setCounterpart(DEST_CHAIN_SELECTOR, i_pongContract); - - uint256 fundingAmount = 1e18; - - // Fund the contract with LINK tokens - s_feeToken.transfer(address(s_pingPong), fundingAmount); - } -} - -contract PingPong_startPingPong is PingPongDappSetup { - uint256 internal s_pingPongNumber = 1; - - function test_StartPingPong_With_Sequenced_Ordered_Success() public { - _assertPingPongSuccess(); - } - - function test_StartPingPong_With_OOO_Success() public { - s_pingPong.setOutOfOrderExecution(true); - - _assertPingPongSuccess(); - } - - function _assertPingPongSuccess() internal { - vm.expectEmit(); - emit PingPongDemo.Ping(s_pingPongNumber); - - Internal.EVM2AnyRampMessage memory message; - - vm.expectEmit(false, false, false, false); - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, message); - - s_pingPong.startPingPong(); - } -} - -contract PingPong_ccipReceive is PingPongDappSetup { - function test_CcipReceive_Success() public { - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](0); - - uint256 pingPongNumber = 5; - - Client.Any2EVMMessage memory message = Client.Any2EVMMessage({ - messageId: bytes32("a"), - sourceChainSelector: DEST_CHAIN_SELECTOR, - sender: abi.encode(i_pongContract), - data: abi.encode(pingPongNumber), - destTokenAmounts: tokenAmounts - }); - - vm.startPrank(address(s_sourceRouter)); - - vm.expectEmit(); - emit PingPongDemo.Pong(pingPongNumber + 1); - - s_pingPong.ccipReceive(message); - } -} - -contract PingPong_plumbing is PingPongDappSetup { - function test_Fuzz_CounterPartChainSelector_Success( - uint64 chainSelector - ) public { - s_pingPong.setCounterpartChainSelector(chainSelector); - - assertEq(s_pingPong.getCounterpartChainSelector(), chainSelector); - } - - function test_Fuzz_CounterPartAddress_Success( - address counterpartAddress - ) public { - s_pingPong.setCounterpartAddress(counterpartAddress); - - assertEq(s_pingPong.getCounterpartAddress(), counterpartAddress); - } - - function test_Fuzz_CounterPartAddress_Success(uint64 chainSelector, address counterpartAddress) public { - s_pingPong.setCounterpartChainSelector(chainSelector); - - s_pingPong.setCounterpart(chainSelector, counterpartAddress); - - assertEq(s_pingPong.getCounterpartAddress(), counterpartAddress); - assertEq(s_pingPong.getCounterpartChainSelector(), chainSelector); - } - - function test_Pausing_Success() public { - assertFalse(s_pingPong.isPaused()); - - s_pingPong.setPaused(true); - - assertTrue(s_pingPong.isPaused()); - } - - function test_OutOfOrderExecution_Success() public { - assertFalse(s_pingPong.getOutOfOrderExecution()); - - vm.expectEmit(); - emit PingPongDemo.OutOfOrderExecutionChange(true); - - s_pingPong.setOutOfOrderExecution(true); - - assertTrue(s_pingPong.getOutOfOrderExecution()); - } -} diff --git a/contracts/src/v0.8/ccip/test/attacks/onRamp/FacadeClient.sol b/contracts/src/v0.8/ccip/test/attacks/OnRamp/FacadeClient.sol similarity index 100% rename from contracts/src/v0.8/ccip/test/attacks/onRamp/FacadeClient.sol rename to contracts/src/v0.8/ccip/test/attacks/OnRamp/FacadeClient.sol diff --git a/contracts/src/v0.8/ccip/test/attacks/onRamp/OnRampTokenPoolReentrancy.t.sol b/contracts/src/v0.8/ccip/test/attacks/OnRamp/OnRampTokenPoolReentrancy.t.sol similarity index 98% rename from contracts/src/v0.8/ccip/test/attacks/onRamp/OnRampTokenPoolReentrancy.t.sol rename to contracts/src/v0.8/ccip/test/attacks/OnRamp/OnRampTokenPoolReentrancy.t.sol index c50d86cad7d..cd3baf1747a 100644 --- a/contracts/src/v0.8/ccip/test/attacks/onRamp/OnRampTokenPoolReentrancy.t.sol +++ b/contracts/src/v0.8/ccip/test/attacks/OnRamp/OnRampTokenPoolReentrancy.t.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.24; import {Client} from "../../../libraries/Client.sol"; import {OnRamp} from "../../../onRamp/OnRamp.sol"; import {TokenPool} from "../../../pools/TokenPool.sol"; -import {OnRampSetup} from "../../onRamp/onRamp/OnRampSetup.t.sol"; +import {OnRampSetup} from "../../onRamp/OnRamp/OnRampSetup.t.sol"; import {FacadeClient} from "./FacadeClient.sol"; import {ReentrantMaliciousTokenPool} from "./ReentrantMaliciousTokenPool.sol"; diff --git a/contracts/src/v0.8/ccip/test/attacks/onRamp/ReentrantMaliciousTokenPool.sol b/contracts/src/v0.8/ccip/test/attacks/OnRamp/ReentrantMaliciousTokenPool.sol similarity index 100% rename from contracts/src/v0.8/ccip/test/attacks/onRamp/ReentrantMaliciousTokenPool.sol rename to contracts/src/v0.8/ccip/test/attacks/OnRamp/ReentrantMaliciousTokenPool.sol diff --git a/contracts/src/v0.8/ccip/test/capability/CCIPHome.t.sol b/contracts/src/v0.8/ccip/test/capability/CCIPHome.t.sol deleted file mode 100644 index 259506dd64a..00000000000 --- a/contracts/src/v0.8/ccip/test/capability/CCIPHome.t.sol +++ /dev/null @@ -1,920 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.24; - -import {ICapabilityConfiguration} from "../../../keystone/interfaces/ICapabilityConfiguration.sol"; -import {INodeInfoProvider} from "../../../keystone/interfaces/INodeInfoProvider.sol"; - -import {CCIPHome} from "../../capability/CCIPHome.sol"; -import {Internal} from "../../libraries/Internal.sol"; -import {CCIPHomeHelper} from "../helpers/CCIPHomeHelper.sol"; -import {Test} from "forge-std/Test.sol"; - -import {IERC165} from "../../../vendor/openzeppelin-solidity/v5.0.2/contracts/interfaces/IERC165.sol"; - -contract CCIPHomeTest is Test { - // address internal constant OWNER = address(0x0000000123123123123); - bytes32 internal constant ZERO_DIGEST = bytes32(uint256(0)); - address internal constant CAPABILITIES_REGISTRY = address(0x0000000123123123123); - Internal.OCRPluginType internal constant DEFAULT_PLUGIN_TYPE = Internal.OCRPluginType.Commit; - uint32 internal constant DEFAULT_DON_ID = 78978987; - - CCIPHomeHelper public s_ccipHome; - - uint256 private constant PREFIX_MASK = type(uint256).max << (256 - 16); // 0xFFFF00..00 - uint256 private constant PREFIX = 0x000a << (256 - 16); // 0x000b00..00 - - uint64 private constant DEFAULT_CHAIN_SELECTOR = 9381579735; - - function setUp() public virtual { - s_ccipHome = new CCIPHomeHelper(CAPABILITIES_REGISTRY); - s_ccipHome.applyChainConfigUpdates(new uint64[](0), _getBaseChainConfigs()); - vm.startPrank(address(s_ccipHome)); - } - - function _getBaseChainConfigs() internal pure returns (CCIPHome.ChainConfigArgs[] memory) { - CCIPHome.ChainConfigArgs[] memory configs = new CCIPHome.ChainConfigArgs[](1); - CCIPHome.ChainConfig memory chainConfig = - CCIPHome.ChainConfig({readers: new bytes32[](0), fChain: 1, config: abi.encode("chainConfig")}); - configs[0] = CCIPHome.ChainConfigArgs({chainSelector: DEFAULT_CHAIN_SELECTOR, chainConfig: chainConfig}); - - return configs; - } - - function _getConfigDigest( - uint32 donId, - Internal.OCRPluginType pluginType, - bytes memory config, - uint32 version - ) internal view returns (bytes32) { - return bytes32( - (PREFIX & PREFIX_MASK) - | ( - uint256( - keccak256( - bytes.concat( - abi.encode(bytes32("EVM"), block.chainid, address(s_ccipHome), donId, pluginType, version), config - ) - ) - ) & ~PREFIX_MASK - ) - ); - } - - function _getBaseConfig( - Internal.OCRPluginType pluginType - ) internal returns (CCIPHome.OCR3Config memory) { - CCIPHome.OCR3Node[] memory nodes = new CCIPHome.OCR3Node[](4); - bytes32[] memory p2pIds = new bytes32[](4); - for (uint256 i = 0; i < nodes.length; i++) { - p2pIds[i] = keccak256(abi.encode("p2pId", i)); - nodes[i] = CCIPHome.OCR3Node({ - p2pId: keccak256(abi.encode("p2pId", i)), - signerKey: abi.encode("signerKey"), - transmitterKey: abi.encode("transmitterKey") - }); - } - - // This is a work-around for not calling mockCall / expectCall with each scenario using _getBaseConfig - INodeInfoProvider.NodeInfo[] memory nodeInfos = new INodeInfoProvider.NodeInfo[](4); - vm.mockCall( - CAPABILITIES_REGISTRY, - abi.encodeWithSelector(INodeInfoProvider.getNodesByP2PIds.selector, p2pIds), - abi.encode(nodeInfos) - ); - - return CCIPHome.OCR3Config({ - pluginType: pluginType, - chainSelector: DEFAULT_CHAIN_SELECTOR, - FRoleDON: 1, - offchainConfigVersion: 98765, - offrampAddress: abi.encode("offrampAddress"), - rmnHomeAddress: abi.encode("rmnHomeAddress"), - nodes: nodes, - offchainConfig: abi.encode("offchainConfig") - }); - } -} - -contract CCIPHome_constructor is CCIPHomeTest { - function test_constructor_success() public { - CCIPHome ccipHome = new CCIPHome(CAPABILITIES_REGISTRY); - - assertEq(address(ccipHome.getCapabilityRegistry()), CAPABILITIES_REGISTRY); - } - - function test_supportsInterface_success() public view { - assertTrue(s_ccipHome.supportsInterface(type(IERC165).interfaceId)); - assertTrue(s_ccipHome.supportsInterface(type(ICapabilityConfiguration).interfaceId)); - } - - function test_getCapabilityConfiguration_success() public view { - bytes memory config = s_ccipHome.getCapabilityConfiguration(DEFAULT_DON_ID); - assertEq(config.length, 0); - } - - function test_constructor_CapabilitiesRegistryAddressZero_reverts() public { - vm.expectRevert(CCIPHome.ZeroAddressNotAllowed.selector); - new CCIPHome(address(0)); - } -} - -contract CCIPHome_beforeCapabilityConfigSet is CCIPHomeTest { - function setUp() public virtual override { - super.setUp(); - vm.stopPrank(); - vm.startPrank(address(CAPABILITIES_REGISTRY)); - } - - function test_beforeCapabilityConfigSet_success() public { - // first set a config - bytes memory callData = abi.encodeCall( - CCIPHome.setCandidate, - (DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, _getBaseConfig(Internal.OCRPluginType.Commit), ZERO_DIGEST) - ); - - vm.expectCall(address(s_ccipHome), callData); - - s_ccipHome.beforeCapabilityConfigSet(new bytes32[](0), callData, 0, DEFAULT_DON_ID); - - // Then revoke the config - bytes32 candidateDigest = s_ccipHome.getCandidateDigest(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE); - assertNotEq(candidateDigest, ZERO_DIGEST); - - callData = abi.encodeCall(CCIPHome.revokeCandidate, (DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, candidateDigest)); - - vm.expectCall(address(s_ccipHome), callData); - - s_ccipHome.beforeCapabilityConfigSet(new bytes32[](0), callData, 0, DEFAULT_DON_ID); - - // Then set a new config - callData = abi.encodeCall( - CCIPHome.setCandidate, - (DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, _getBaseConfig(Internal.OCRPluginType.Commit), ZERO_DIGEST) - ); - - vm.expectCall(address(s_ccipHome), callData); - - s_ccipHome.beforeCapabilityConfigSet(new bytes32[](0), callData, 0, DEFAULT_DON_ID); - - // Then promote the new config - - bytes32 newCandidateDigest = s_ccipHome.getCandidateDigest(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE); - assertNotEq(newCandidateDigest, ZERO_DIGEST); - - callData = abi.encodeCall( - CCIPHome.promoteCandidateAndRevokeActive, (DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, newCandidateDigest, ZERO_DIGEST) - ); - - vm.expectCall(address(s_ccipHome), callData); - - s_ccipHome.beforeCapabilityConfigSet(new bytes32[](0), callData, 0, DEFAULT_DON_ID); - - bytes32 activeDigest = s_ccipHome.getActiveDigest(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE); - assertEq(activeDigest, newCandidateDigest); - } - - function test_beforeCapabilityConfigSet_OnlyCapabilitiesRegistryCanCall_reverts() public { - bytes memory callData = abi.encodeCall( - CCIPHome.setCandidate, - (DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, _getBaseConfig(Internal.OCRPluginType.Commit), ZERO_DIGEST) - ); - - vm.stopPrank(); - - vm.expectRevert(CCIPHome.OnlyCapabilitiesRegistryCanCall.selector); - - s_ccipHome.beforeCapabilityConfigSet(new bytes32[](0), callData, 0, DEFAULT_DON_ID); - } - - function test_beforeCapabilityConfigSet_InvalidSelector_reverts() public { - bytes memory callData = abi.encodeCall(CCIPHome.getConfigDigests, (DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE)); - - vm.expectRevert(abi.encodeWithSelector(CCIPHome.InvalidSelector.selector, CCIPHome.getConfigDigests.selector)); - s_ccipHome.beforeCapabilityConfigSet(new bytes32[](0), callData, 0, DEFAULT_DON_ID); - } - - function test_beforeCapabilityConfigSet_DONIdMismatch_reverts() public { - uint32 wrongDonId = DEFAULT_DON_ID + 1; - - bytes memory callData = abi.encodeCall( - CCIPHome.setCandidate, - (DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, _getBaseConfig(Internal.OCRPluginType.Commit), ZERO_DIGEST) - ); - - vm.expectRevert(abi.encodeWithSelector(CCIPHome.DONIdMismatch.selector, DEFAULT_DON_ID, wrongDonId)); - s_ccipHome.beforeCapabilityConfigSet(new bytes32[](0), callData, 0, wrongDonId); - } - - function test_beforeCapabilityConfigSet_InnerCallReverts_reverts() public { - bytes memory callData = abi.encodeCall(CCIPHome.revokeCandidate, (DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, ZERO_DIGEST)); - - vm.expectRevert(CCIPHome.RevokingZeroDigestNotAllowed.selector); - s_ccipHome.beforeCapabilityConfigSet(new bytes32[](0), callData, 0, DEFAULT_DON_ID); - } -} - -contract CCIPHome_getConfigDigests is CCIPHomeTest { - function test_getConfigDigests_success() public { - (bytes32 activeDigest, bytes32 candidateDigest) = s_ccipHome.getConfigDigests(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE); - assertEq(activeDigest, ZERO_DIGEST); - assertEq(candidateDigest, ZERO_DIGEST); - - CCIPHome.OCR3Config memory config = _getBaseConfig(Internal.OCRPluginType.Commit); - bytes32 firstDigest = s_ccipHome.setCandidate(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, config, ZERO_DIGEST); - - (activeDigest, candidateDigest) = s_ccipHome.getConfigDigests(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE); - assertEq(activeDigest, ZERO_DIGEST); - assertEq(candidateDigest, firstDigest); - - s_ccipHome.promoteCandidateAndRevokeActive(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, firstDigest, ZERO_DIGEST); - - (activeDigest, candidateDigest) = s_ccipHome.getConfigDigests(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE); - assertEq(activeDigest, firstDigest); - assertEq(candidateDigest, ZERO_DIGEST); - - bytes32 secondDigest = s_ccipHome.setCandidate(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, config, ZERO_DIGEST); - - (activeDigest, candidateDigest) = s_ccipHome.getConfigDigests(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE); - assertEq(activeDigest, firstDigest); - assertEq(candidateDigest, secondDigest); - - assertEq(activeDigest, s_ccipHome.getActiveDigest(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE)); - assertEq(candidateDigest, s_ccipHome.getCandidateDigest(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE)); - } -} - -contract CCIPHome_getAllConfigs is CCIPHomeTest { - function test_getAllConfigs_success() public { - CCIPHome.OCR3Config memory config = _getBaseConfig(Internal.OCRPluginType.Commit); - bytes32 firstDigest = s_ccipHome.setCandidate(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, config, ZERO_DIGEST); - - (CCIPHome.VersionedConfig memory activeConfig, CCIPHome.VersionedConfig memory candidateConfig) = - s_ccipHome.getAllConfigs(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE); - assertEq(activeConfig.configDigest, ZERO_DIGEST); - assertEq(candidateConfig.configDigest, firstDigest); - - s_ccipHome.promoteCandidateAndRevokeActive(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, firstDigest, ZERO_DIGEST); - - (activeConfig, candidateConfig) = s_ccipHome.getAllConfigs(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE); - assertEq(activeConfig.configDigest, firstDigest); - assertEq(candidateConfig.configDigest, ZERO_DIGEST); - - bytes32 secondDigest = s_ccipHome.setCandidate(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, config, ZERO_DIGEST); - - (activeConfig, candidateConfig) = s_ccipHome.getAllConfigs(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE); - assertEq(activeConfig.configDigest, firstDigest); - assertEq(candidateConfig.configDigest, secondDigest); - - (activeConfig, candidateConfig) = s_ccipHome.getAllConfigs(DEFAULT_DON_ID + 1, DEFAULT_PLUGIN_TYPE); - assertEq(activeConfig.configDigest, ZERO_DIGEST); - assertEq(candidateConfig.configDigest, ZERO_DIGEST); - - (activeConfig, candidateConfig) = s_ccipHome.getAllConfigs(DEFAULT_DON_ID, Internal.OCRPluginType.Execution); - assertEq(activeConfig.configDigest, ZERO_DIGEST); - assertEq(candidateConfig.configDigest, ZERO_DIGEST); - } -} - -contract CCIPHome_setCandidate is CCIPHomeTest { - function test_setCandidate_success() public { - CCIPHome.OCR3Config memory config = _getBaseConfig(Internal.OCRPluginType.Commit); - CCIPHome.VersionedConfig memory versionedConfig = - CCIPHome.VersionedConfig({version: 1, config: config, configDigest: ZERO_DIGEST}); - - versionedConfig.configDigest = - _getConfigDigest(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, abi.encode(versionedConfig.config), versionedConfig.version); - - vm.expectEmit(); - emit CCIPHome.ConfigSet(versionedConfig.configDigest, versionedConfig.version, versionedConfig.config); - - s_ccipHome.setCandidate(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, versionedConfig.config, ZERO_DIGEST); - - (CCIPHome.VersionedConfig memory storedVersionedConfig, bool ok) = - s_ccipHome.getConfig(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, versionedConfig.configDigest); - assertTrue(ok); - assertEq(storedVersionedConfig.version, versionedConfig.version); - assertEq(storedVersionedConfig.configDigest, versionedConfig.configDigest); - assertEq(keccak256(abi.encode(storedVersionedConfig.config)), keccak256(abi.encode(versionedConfig.config))); - } - - function test_setCandidate_ConfigDigestMismatch_reverts() public { - CCIPHome.OCR3Config memory config = _getBaseConfig(Internal.OCRPluginType.Commit); - - bytes32 digest = s_ccipHome.setCandidate(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, config, ZERO_DIGEST); - - vm.expectRevert(abi.encodeWithSelector(CCIPHome.ConfigDigestMismatch.selector, digest, ZERO_DIGEST)); - s_ccipHome.setCandidate(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, config, ZERO_DIGEST); - - vm.expectEmit(); - emit CCIPHome.CandidateConfigRevoked(digest); - - s_ccipHome.setCandidate(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, config, digest); - } - - function test_setCandidate_CanOnlySelfCall_reverts() public { - vm.stopPrank(); - - vm.expectRevert(CCIPHome.CanOnlySelfCall.selector); - s_ccipHome.setCandidate( - DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, _getBaseConfig(Internal.OCRPluginType.Commit), ZERO_DIGEST - ); - } -} - -contract CCIPHome_revokeCandidate is CCIPHomeTest { - // Sets two configs - function setUp() public virtual override { - super.setUp(); - CCIPHome.OCR3Config memory config = _getBaseConfig(Internal.OCRPluginType.Commit); - bytes32 digest = s_ccipHome.setCandidate(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, config, ZERO_DIGEST); - s_ccipHome.promoteCandidateAndRevokeActive(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, digest, ZERO_DIGEST); - - config.offrampAddress = abi.encode("new_offrampAddress"); - s_ccipHome.setCandidate(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, config, ZERO_DIGEST); - } - - function test_revokeCandidate_success() public { - (bytes32 priorActiveDigest, bytes32 priorCandidateDigest) = - s_ccipHome.getConfigDigests(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE); - - vm.expectEmit(); - emit CCIPHome.CandidateConfigRevoked(priorCandidateDigest); - - s_ccipHome.revokeCandidate(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, priorCandidateDigest); - - (CCIPHome.VersionedConfig memory storedVersionedConfig, bool ok) = - s_ccipHome.getConfig(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, priorCandidateDigest); - assertFalse(ok); - // Ensure no old data is returned, even though it's still in storage - assertEq(storedVersionedConfig.version, 0); - assertEq(storedVersionedConfig.config.chainSelector, 0); - assertEq(storedVersionedConfig.config.FRoleDON, 0); - - // Asser the active digest is unaffected but the candidate digest is set to zero - (bytes32 activeDigest, bytes32 candidateDigest) = s_ccipHome.getConfigDigests(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE); - assertEq(activeDigest, priorActiveDigest); - assertEq(candidateDigest, ZERO_DIGEST); - assertTrue(candidateDigest != priorCandidateDigest); - } - - function test_revokeCandidate_ConfigDigestMismatch_reverts() public { - (, bytes32 priorCandidateDigest) = s_ccipHome.getConfigDigests(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE); - - bytes32 wrongDigest = keccak256("wrong_digest"); - vm.expectRevert(abi.encodeWithSelector(CCIPHome.ConfigDigestMismatch.selector, priorCandidateDigest, wrongDigest)); - s_ccipHome.revokeCandidate(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, wrongDigest); - } - - function test_revokeCandidate_RevokingZeroDigestNotAllowed_reverts() public { - vm.expectRevert(CCIPHome.RevokingZeroDigestNotAllowed.selector); - s_ccipHome.revokeCandidate(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, ZERO_DIGEST); - } - - function test_revokeCandidate_CanOnlySelfCall_reverts() public { - vm.startPrank(address(0)); - - vm.expectRevert(CCIPHome.CanOnlySelfCall.selector); - s_ccipHome.revokeCandidate(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, keccak256("configDigest")); - } -} - -contract CCIPHome_promoteCandidateAndRevokeActive is CCIPHomeTest { - function test_promoteCandidateAndRevokeActive_multiplePlugins_success() public { - promoteCandidateAndRevokeActive(Internal.OCRPluginType.Commit); - promoteCandidateAndRevokeActive(Internal.OCRPluginType.Execution); - - // check that the two plugins have only active configs and no candidates. - (bytes32 activeDigest, bytes32 candidateDigest) = - s_ccipHome.getConfigDigests(DEFAULT_DON_ID, Internal.OCRPluginType.Commit); - assertTrue(activeDigest != ZERO_DIGEST); - assertEq(candidateDigest, ZERO_DIGEST); - - (activeDigest, candidateDigest) = s_ccipHome.getConfigDigests(DEFAULT_DON_ID, Internal.OCRPluginType.Execution); - assertTrue(activeDigest != ZERO_DIGEST); - assertEq(candidateDigest, ZERO_DIGEST); - } - - function promoteCandidateAndRevokeActive( - Internal.OCRPluginType pluginType - ) public { - CCIPHome.OCR3Config memory config = _getBaseConfig(pluginType); - bytes32 firstConfigToPromote = s_ccipHome.setCandidate(DEFAULT_DON_ID, pluginType, config, ZERO_DIGEST); - - vm.expectEmit(); - emit CCIPHome.ConfigPromoted(firstConfigToPromote); - - s_ccipHome.promoteCandidateAndRevokeActive(DEFAULT_DON_ID, pluginType, firstConfigToPromote, ZERO_DIGEST); - - // Assert the active digest is updated and the candidate digest is set to zero - (bytes32 activeDigest, bytes32 candidateDigest) = s_ccipHome.getConfigDigests(DEFAULT_DON_ID, pluginType); - assertEq(activeDigest, firstConfigToPromote); - assertEq(candidateDigest, ZERO_DIGEST); - - // Set a new candidate to promote over a non-zero active config. - config.offchainConfig = abi.encode("new_offchainConfig_config"); - bytes32 secondConfigToPromote = s_ccipHome.setCandidate(DEFAULT_DON_ID, pluginType, config, ZERO_DIGEST); - - vm.expectEmit(); - emit CCIPHome.ActiveConfigRevoked(firstConfigToPromote); - - vm.expectEmit(); - emit CCIPHome.ConfigPromoted(secondConfigToPromote); - - s_ccipHome.promoteCandidateAndRevokeActive(DEFAULT_DON_ID, pluginType, secondConfigToPromote, firstConfigToPromote); - - (CCIPHome.VersionedConfig memory activeConfig, CCIPHome.VersionedConfig memory candidateConfig) = - s_ccipHome.getAllConfigs(DEFAULT_DON_ID, pluginType); - assertEq(activeConfig.configDigest, secondConfigToPromote); - assertEq(candidateConfig.configDigest, ZERO_DIGEST); - assertEq(keccak256(abi.encode(activeConfig.config)), keccak256(abi.encode(config))); - } - - function test_promoteCandidateAndRevokeActive_NoOpStateTransitionNotAllowed_reverts() public { - vm.expectRevert(CCIPHome.NoOpStateTransitionNotAllowed.selector); - s_ccipHome.promoteCandidateAndRevokeActive(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, ZERO_DIGEST, ZERO_DIGEST); - } - - function test_promoteCandidateAndRevokeActive_ConfigDigestMismatch_reverts() public { - (bytes32 priorActiveDigest, bytes32 priorCandidateDigest) = - s_ccipHome.getConfigDigests(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE); - bytes32 wrongActiveDigest = keccak256("wrongActiveDigest"); - bytes32 wrongCandidateDigest = keccak256("wrongCandidateDigest"); - - vm.expectRevert( - abi.encodeWithSelector(CCIPHome.ConfigDigestMismatch.selector, priorActiveDigest, wrongCandidateDigest) - ); - s_ccipHome.promoteCandidateAndRevokeActive( - DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, wrongCandidateDigest, wrongActiveDigest - ); - - vm.expectRevert( - abi.encodeWithSelector(CCIPHome.ConfigDigestMismatch.selector, priorActiveDigest, wrongActiveDigest) - ); - - s_ccipHome.promoteCandidateAndRevokeActive( - DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, priorCandidateDigest, wrongActiveDigest - ); - } - - function test_promoteCandidateAndRevokeActive_CanOnlySelfCall_reverts() public { - vm.stopPrank(); - - vm.expectRevert(CCIPHome.CanOnlySelfCall.selector); - s_ccipHome.promoteCandidateAndRevokeActive( - DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, keccak256("toPromote"), keccak256("ToRevoke") - ); - } -} - -contract CCIPHome__validateConfig is CCIPHomeTest { - function setUp() public virtual override { - s_ccipHome = new CCIPHomeHelper(CAPABILITIES_REGISTRY); - } - - function _addChainConfig( - uint256 numNodes - ) internal returns (CCIPHome.OCR3Node[] memory nodes) { - return _addChainConfig(numNodes, 1); - } - - function _makeBytes32Array(uint256 length, uint256 seed) internal pure returns (bytes32[] memory arr) { - arr = new bytes32[](length); - for (uint256 i = 0; i < length; i++) { - arr[i] = keccak256(abi.encode(i, 1, seed)); - } - return arr; - } - - function _makeBytesArray(uint256 length, uint256 seed) internal pure returns (bytes[] memory arr) { - arr = new bytes[](length); - for (uint256 i = 0; i < length; i++) { - arr[i] = abi.encode(keccak256(abi.encode(i, 1, seed))); - } - return arr; - } - - function _addChainConfig(uint256 numNodes, uint8 fChain) internal returns (CCIPHome.OCR3Node[] memory nodes) { - bytes32[] memory p2pIds = _makeBytes32Array(numNodes, 0); - bytes[] memory signers = _makeBytesArray(numNodes, 10); - bytes[] memory transmitters = _makeBytesArray(numNodes, 20); - - nodes = new CCIPHome.OCR3Node[](numNodes); - INodeInfoProvider.NodeInfo[] memory nodeInfos = new INodeInfoProvider.NodeInfo[](numNodes); - for (uint256 i = 0; i < numNodes; i++) { - nodes[i] = CCIPHome.OCR3Node({p2pId: p2pIds[i], signerKey: signers[i], transmitterKey: transmitters[i]}); - nodeInfos[i] = INodeInfoProvider.NodeInfo({ - nodeOperatorId: 1, - signer: bytes32(signers[i]), - p2pId: p2pIds[i], - encryptionPublicKey: keccak256("encryptionPublicKey"), - hashedCapabilityIds: new bytes32[](0), - configCount: uint32(1), - workflowDONId: uint32(1), - capabilitiesDONIds: new uint256[](0) - }); - } - vm.mockCall( - CAPABILITIES_REGISTRY, - abi.encodeWithSelector(INodeInfoProvider.getNodesByP2PIds.selector, p2pIds), - abi.encode(nodeInfos) - ); - // Add chain selector for chain 1. - CCIPHome.ChainConfigArgs[] memory adds = new CCIPHome.ChainConfigArgs[](1); - adds[0] = CCIPHome.ChainConfigArgs({ - chainSelector: 1, - chainConfig: CCIPHome.ChainConfig({readers: p2pIds, fChain: fChain, config: bytes("config1")}) - }); - - vm.expectEmit(); - emit CCIPHome.ChainConfigSet(1, adds[0].chainConfig); - s_ccipHome.applyChainConfigUpdates(new uint64[](0), adds); - - return nodes; - } - - function _getCorrectOCR3Config(uint8 numNodes, uint8 FRoleDON) internal returns (CCIPHome.OCR3Config memory) { - CCIPHome.OCR3Node[] memory nodes = _addChainConfig(numNodes); - - return CCIPHome.OCR3Config({ - pluginType: Internal.OCRPluginType.Commit, - offrampAddress: abi.encode(keccak256(abi.encode("offramp"))), - rmnHomeAddress: abi.encode(keccak256(abi.encode("rmnHome"))), - chainSelector: 1, - nodes: nodes, - FRoleDON: FRoleDON, - offchainConfigVersion: 30, - offchainConfig: bytes("offchainConfig") - }); - } - - function _getCorrectOCR3Config() internal returns (CCIPHome.OCR3Config memory) { - return _getCorrectOCR3Config(4, 1); - } - - // Successes. - - function test__validateConfig_Success() public { - s_ccipHome.validateConfig(_getCorrectOCR3Config()); - } - - function test__validateConfigLessTransmittersThanSigners_Success() public { - // fChain is 1, so there should be at least 4 transmitters. - CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(5, 1); - config.nodes[1].transmitterKey = bytes(""); - - s_ccipHome.validateConfig(config); - } - - function test__validateConfigSmallerFChain_Success() public { - CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(11, 3); - - // Set fChain to 2 - _addChainConfig(4, 2); - - s_ccipHome.validateConfig(config); - } - - // Reverts - - function test__validateConfig_ChainSelectorNotSet_Reverts() public { - CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(); - config.chainSelector = 0; // invalid - - vm.expectRevert(CCIPHome.ChainSelectorNotSet.selector); - s_ccipHome.validateConfig(config); - } - - function test__validateConfig_OfframpAddressCannotBeZero_Reverts() public { - CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(); - config.offrampAddress = ""; // invalid - - vm.expectRevert(CCIPHome.OfframpAddressCannotBeZero.selector); - s_ccipHome.validateConfig(config); - } - - function test__validateConfig_ABIEncodedAddress_OfframpAddressCannotBeZero_Reverts() public { - CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(); - config.offrampAddress = abi.encode(address(0)); // invalid - - vm.expectRevert(CCIPHome.OfframpAddressCannotBeZero.selector); - s_ccipHome.validateConfig(config); - } - - function test__validateConfig_RMNHomeAddressCannotBeZero_Reverts() public { - CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(); - config.rmnHomeAddress = ""; // invalid - - vm.expectRevert(CCIPHome.RMNHomeAddressCannotBeZero.selector); - s_ccipHome.validateConfig(config); - } - - function test__validateConfig_ABIEncodedAddress_RMNHomeAddressCannotBeZero_Reverts() public { - CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(); - config.rmnHomeAddress = abi.encode(address(0)); // invalid - - vm.expectRevert(CCIPHome.RMNHomeAddressCannotBeZero.selector); - s_ccipHome.validateConfig(config); - } - - function test__validateConfig_ChainSelectorNotFound_Reverts() public { - CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(); - config.chainSelector = 2; // not set - - vm.expectRevert(abi.encodeWithSelector(CCIPHome.ChainSelectorNotFound.selector, 2)); - s_ccipHome.validateConfig(config); - } - - function test__validateConfig_NotEnoughTransmitters_Reverts() public { - CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(); - uint256 numberOfTransmitters = 3; - - // 32 > 31 (max num oracles) - CCIPHome.OCR3Node[] memory nodes = _addChainConfig(31); - - // truncate transmitters to < 3 * fChain + 1 - // since fChain is 1 in this case, we need to truncate to 3 transmitters. - for (uint256 i = numberOfTransmitters; i < nodes.length; ++i) { - nodes[i].transmitterKey = bytes(""); - } - - config.nodes = nodes; - vm.expectRevert(abi.encodeWithSelector(CCIPHome.NotEnoughTransmitters.selector, numberOfTransmitters, 4)); - s_ccipHome.validateConfig(config); - } - - function test__validateConfig_NotEnoughTransmittersEmptyAddresses_Reverts() public { - CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(); - config.nodes[0].transmitterKey = bytes(""); - - vm.expectRevert(abi.encodeWithSelector(CCIPHome.NotEnoughTransmitters.selector, 3, 4)); - s_ccipHome.validateConfig(config); - - // Zero out remaining transmitters to verify error changes - for (uint256 i = 1; i < config.nodes.length; ++i) { - config.nodes[i].transmitterKey = bytes(""); - } - - vm.expectRevert(abi.encodeWithSelector(CCIPHome.NotEnoughTransmitters.selector, 0, 4)); - s_ccipHome.validateConfig(config); - } - - function test__validateConfig_TooManySigners_Reverts() public { - CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(); - config.nodes = new CCIPHome.OCR3Node[](257); - - vm.expectRevert(CCIPHome.TooManySigners.selector); - s_ccipHome.validateConfig(config); - } - - function test__validateConfig_FChainTooHigh_Reverts() public { - CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(); - config.FRoleDON = 2; // too low - - // Set fChain to 3 - _addChainConfig(4, 3); - - vm.expectRevert(abi.encodeWithSelector(CCIPHome.FChainTooHigh.selector, 3, 2)); - s_ccipHome.validateConfig(config); - } - - function test__validateConfig_FMustBePositive_Reverts() public { - CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(); - config.FRoleDON = 0; // not positive - - vm.expectRevert(abi.encodeWithSelector(CCIPHome.FChainTooHigh.selector, 1, 0)); - s_ccipHome.validateConfig(config); - } - - function test__validateConfig_FTooHigh_Reverts() public { - CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(); - config.FRoleDON = 2; // too high - - vm.expectRevert(CCIPHome.FTooHigh.selector); - s_ccipHome.validateConfig(config); - } - - function test__validateConfig_ZeroP2PId_Reverts() public { - CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(); - config.nodes[1].p2pId = bytes32(0); - - vm.expectRevert(abi.encodeWithSelector(CCIPHome.InvalidNode.selector, config.nodes[1])); - s_ccipHome.validateConfig(config); - } - - function test__validateConfig_ZeroSignerKey_Reverts() public { - CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(); - config.nodes[2].signerKey = bytes(""); - - vm.expectRevert(abi.encodeWithSelector(CCIPHome.InvalidNode.selector, config.nodes[2])); - s_ccipHome.validateConfig(config); - } -} - -contract CCIPHome_applyChainConfigUpdates is CCIPHomeTest { - function setUp() public virtual override { - s_ccipHome = new CCIPHomeHelper(CAPABILITIES_REGISTRY); - } - - function test_applyChainConfigUpdates_addChainConfigs_Success() public { - bytes32[] memory chainReaders = new bytes32[](1); - chainReaders[0] = keccak256(abi.encode(1)); - CCIPHome.ChainConfigArgs[] memory adds = new CCIPHome.ChainConfigArgs[](2); - adds[0] = CCIPHome.ChainConfigArgs({ - chainSelector: 1, - chainConfig: CCIPHome.ChainConfig({readers: chainReaders, fChain: 1, config: bytes("config1")}) - }); - adds[1] = CCIPHome.ChainConfigArgs({ - chainSelector: 2, - chainConfig: CCIPHome.ChainConfig({readers: chainReaders, fChain: 1, config: bytes("config2")}) - }); - INodeInfoProvider.NodeInfo[] memory nodeInfos = new INodeInfoProvider.NodeInfo[](1); - nodeInfos[0] = INodeInfoProvider.NodeInfo({ - nodeOperatorId: 1, - signer: bytes32(uint256(1)), - p2pId: chainReaders[0], - encryptionPublicKey: keccak256("encryptionPublicKey"), - hashedCapabilityIds: new bytes32[](0), - configCount: uint32(1), - workflowDONId: uint32(1), - capabilitiesDONIds: new uint256[](0) - }); - vm.mockCall( - CAPABILITIES_REGISTRY, - abi.encodeWithSelector(INodeInfoProvider.getNodesByP2PIds.selector, chainReaders), - abi.encode(nodeInfos) - ); - vm.expectEmit(); - emit CCIPHome.ChainConfigSet(1, adds[0].chainConfig); - vm.expectEmit(); - emit CCIPHome.ChainConfigSet(2, adds[1].chainConfig); - s_ccipHome.applyChainConfigUpdates(new uint64[](0), adds); - - CCIPHome.ChainConfigArgs[] memory configs = s_ccipHome.getAllChainConfigs(0, 2); - assertEq(configs.length, 2, "chain configs length must be 2"); - assertEq(configs[0].chainSelector, 1, "chain selector must match"); - assertEq(configs[1].chainSelector, 2, "chain selector must match"); - assertEq(s_ccipHome.getNumChainConfigurations(), 2, "total chain configs must be 2"); - } - - function test_getPaginatedCCIPHomes_Success() public { - bytes32[] memory chainReaders = new bytes32[](1); - chainReaders[0] = keccak256(abi.encode(1)); - CCIPHome.ChainConfigArgs[] memory adds = new CCIPHome.ChainConfigArgs[](2); - adds[0] = CCIPHome.ChainConfigArgs({ - chainSelector: 1, - chainConfig: CCIPHome.ChainConfig({readers: chainReaders, fChain: 1, config: bytes("config1")}) - }); - adds[1] = CCIPHome.ChainConfigArgs({ - chainSelector: 2, - chainConfig: CCIPHome.ChainConfig({readers: chainReaders, fChain: 1, config: bytes("config2")}) - }); - INodeInfoProvider.NodeInfo[] memory nodeInfos = new INodeInfoProvider.NodeInfo[](1); - nodeInfos[0] = INodeInfoProvider.NodeInfo({ - nodeOperatorId: 1, - signer: bytes32(uint256(1)), - p2pId: chainReaders[0], - encryptionPublicKey: keccak256("encryptionPublicKey"), - hashedCapabilityIds: new bytes32[](0), - configCount: uint32(1), - workflowDONId: uint32(1), - capabilitiesDONIds: new uint256[](0) - }); - vm.mockCall( - CAPABILITIES_REGISTRY, - abi.encodeWithSelector(INodeInfoProvider.getNodesByP2PIds.selector, chainReaders), - abi.encode(nodeInfos) - ); - - s_ccipHome.applyChainConfigUpdates(new uint64[](0), adds); - - CCIPHome.ChainConfigArgs[] memory configs = s_ccipHome.getAllChainConfigs(0, 2); - assertEq(configs.length, 2, "chain configs length must be 2"); - assertEq(configs[0].chainSelector, 1, "chain selector must match"); - assertEq(configs[1].chainSelector, 2, "chain selector must match"); - - configs = s_ccipHome.getAllChainConfigs(0, 1); - assertEq(configs.length, 1, "chain configs length must be 1"); - assertEq(configs[0].chainSelector, 1, "chain selector must match"); - - configs = s_ccipHome.getAllChainConfigs(0, 10); - assertEq(configs.length, 2, "chain configs length must be 2"); - assertEq(configs[0].chainSelector, 1, "chain selector must match"); - assertEq(configs[1].chainSelector, 2, "chain selector must match"); - - configs = s_ccipHome.getAllChainConfigs(1, 1); - assertEq(configs.length, 1, "chain configs length must be 1"); - - configs = s_ccipHome.getAllChainConfigs(1, 2); - assertEq(configs.length, 0, "chain configs length must be 0"); - } - - function test_applyChainConfigUpdates_removeChainConfigs_Success() public { - bytes32[] memory chainReaders = new bytes32[](1); - chainReaders[0] = keccak256(abi.encode(1)); - CCIPHome.ChainConfigArgs[] memory adds = new CCIPHome.ChainConfigArgs[](2); - adds[0] = CCIPHome.ChainConfigArgs({ - chainSelector: 1, - chainConfig: CCIPHome.ChainConfig({readers: chainReaders, fChain: 1, config: bytes("config1")}) - }); - adds[1] = CCIPHome.ChainConfigArgs({ - chainSelector: 2, - chainConfig: CCIPHome.ChainConfig({readers: chainReaders, fChain: 1, config: bytes("config2")}) - }); - - INodeInfoProvider.NodeInfo[] memory nodeInfos = new INodeInfoProvider.NodeInfo[](1); - nodeInfos[0] = INodeInfoProvider.NodeInfo({ - nodeOperatorId: 1, - signer: bytes32(uint256(1)), - p2pId: chainReaders[0], - encryptionPublicKey: keccak256("encryptionPublicKey"), - hashedCapabilityIds: new bytes32[](0), - configCount: uint32(1), - workflowDONId: uint32(1), - capabilitiesDONIds: new uint256[](0) - }); - vm.mockCall( - CAPABILITIES_REGISTRY, - abi.encodeWithSelector(INodeInfoProvider.getNodesByP2PIds.selector, chainReaders), - abi.encode(nodeInfos) - ); - - vm.expectEmit(); - emit CCIPHome.ChainConfigSet(1, adds[0].chainConfig); - vm.expectEmit(); - emit CCIPHome.ChainConfigSet(2, adds[1].chainConfig); - s_ccipHome.applyChainConfigUpdates(new uint64[](0), adds); - - assertEq(s_ccipHome.getNumChainConfigurations(), 2, "total chain configs must be 2"); - - uint64[] memory removes = new uint64[](1); - removes[0] = uint64(1); - - vm.expectEmit(); - emit CCIPHome.ChainConfigRemoved(1); - s_ccipHome.applyChainConfigUpdates(removes, new CCIPHome.ChainConfigArgs[](0)); - - assertEq(s_ccipHome.getNumChainConfigurations(), 1, "total chain configs must be 1"); - } - - // Reverts. - - function test_applyChainConfigUpdates_selectorNotFound_Reverts() public { - uint64[] memory removes = new uint64[](1); - removes[0] = uint64(1); - - vm.expectRevert(abi.encodeWithSelector(CCIPHome.ChainSelectorNotFound.selector, 1)); - s_ccipHome.applyChainConfigUpdates(removes, new CCIPHome.ChainConfigArgs[](0)); - } - - function test_applyChainConfigUpdates_nodeNotInRegistry_Reverts() public { - bytes32[] memory chainReaders = new bytes32[](1); - chainReaders[0] = keccak256(abi.encode(1)); - CCIPHome.ChainConfigArgs[] memory adds = new CCIPHome.ChainConfigArgs[](1); - adds[0] = CCIPHome.ChainConfigArgs({ - chainSelector: 1, - chainConfig: CCIPHome.ChainConfig({readers: chainReaders, fChain: 1, config: abi.encode(1, 2, 3)}) - }); - - vm.mockCallRevert( - CAPABILITIES_REGISTRY, - abi.encodeWithSelector(INodeInfoProvider.getNodesByP2PIds.selector, chainReaders), - abi.encodeWithSelector(INodeInfoProvider.NodeDoesNotExist.selector, chainReaders[0]) - ); - - vm.expectRevert(abi.encodeWithSelector(INodeInfoProvider.NodeDoesNotExist.selector, chainReaders[0])); - s_ccipHome.applyChainConfigUpdates(new uint64[](0), adds); - } - - function test__applyChainConfigUpdates_FChainNotPositive_Reverts() public { - bytes32[] memory chainReaders = new bytes32[](1); - chainReaders[0] = keccak256(abi.encode(1)); - CCIPHome.ChainConfigArgs[] memory adds = new CCIPHome.ChainConfigArgs[](2); - adds[0] = CCIPHome.ChainConfigArgs({ - chainSelector: 1, - chainConfig: CCIPHome.ChainConfig({readers: chainReaders, fChain: 1, config: bytes("config1")}) - }); - adds[1] = CCIPHome.ChainConfigArgs({ - chainSelector: 2, - chainConfig: CCIPHome.ChainConfig({readers: chainReaders, fChain: 0, config: bytes("config2")}) // bad fChain - }); - INodeInfoProvider.NodeInfo[] memory nodeInfos = new INodeInfoProvider.NodeInfo[](1); - nodeInfos[0] = INodeInfoProvider.NodeInfo({ - nodeOperatorId: 1, - signer: bytes32(uint256(1)), - p2pId: chainReaders[0], - encryptionPublicKey: keccak256("encryptionPublicKey"), - hashedCapabilityIds: new bytes32[](0), - configCount: uint32(1), - workflowDONId: uint32(1), - capabilitiesDONIds: new uint256[](0) - }); - vm.mockCall( - CAPABILITIES_REGISTRY, - abi.encodeWithSelector(INodeInfoProvider.getNodesByP2PIds.selector, chainReaders), - abi.encode(nodeInfos) - ); - - vm.expectRevert(CCIPHome.FChainMustBePositive.selector); - s_ccipHome.applyChainConfigUpdates(new uint64[](0), adds); - } -} diff --git a/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.applyChainConfigUpdates.t.sol b/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.applyChainConfigUpdates.t.sol new file mode 100644 index 00000000000..1d2c3a70895 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.applyChainConfigUpdates.t.sol @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {INodeInfoProvider} from "../../../../keystone/interfaces/INodeInfoProvider.sol"; + +import {CCIPHome} from "../../../capability/CCIPHome.sol"; +import {CCIPHomeHelper} from "../../helpers/CCIPHomeHelper.sol"; + +import {CCIPHomeTestSetup} from "./CCIPHomeTestSetup.t.sol"; + +contract CCIPHome_applyChainConfigUpdates is CCIPHomeTestSetup { + function setUp() public virtual override { + s_ccipHome = new CCIPHomeHelper(CAPABILITIES_REGISTRY); + } + + function test_applyChainConfigUpdates_addChainConfigs_Success() public { + bytes32[] memory chainReaders = new bytes32[](1); + chainReaders[0] = keccak256(abi.encode(1)); + CCIPHome.ChainConfigArgs[] memory adds = new CCIPHome.ChainConfigArgs[](2); + adds[0] = CCIPHome.ChainConfigArgs({ + chainSelector: 1, + chainConfig: CCIPHome.ChainConfig({readers: chainReaders, fChain: 1, config: bytes("config1")}) + }); + adds[1] = CCIPHome.ChainConfigArgs({ + chainSelector: 2, + chainConfig: CCIPHome.ChainConfig({readers: chainReaders, fChain: 1, config: bytes("config2")}) + }); + INodeInfoProvider.NodeInfo[] memory nodeInfos = new INodeInfoProvider.NodeInfo[](1); + nodeInfos[0] = INodeInfoProvider.NodeInfo({ + nodeOperatorId: 1, + signer: bytes32(uint256(1)), + p2pId: chainReaders[0], + encryptionPublicKey: keccak256("encryptionPublicKey"), + hashedCapabilityIds: new bytes32[](0), + configCount: uint32(1), + workflowDONId: uint32(1), + capabilitiesDONIds: new uint256[](0) + }); + vm.mockCall( + CAPABILITIES_REGISTRY, + abi.encodeWithSelector(INodeInfoProvider.getNodesByP2PIds.selector, chainReaders), + abi.encode(nodeInfos) + ); + vm.expectEmit(); + emit CCIPHome.ChainConfigSet(1, adds[0].chainConfig); + vm.expectEmit(); + emit CCIPHome.ChainConfigSet(2, adds[1].chainConfig); + s_ccipHome.applyChainConfigUpdates(new uint64[](0), adds); + + CCIPHome.ChainConfigArgs[] memory configs = s_ccipHome.getAllChainConfigs(0, 2); + assertEq(configs.length, 2, "chain configs length must be 2"); + assertEq(configs[0].chainSelector, 1, "chain selector must match"); + assertEq(configs[1].chainSelector, 2, "chain selector must match"); + assertEq(s_ccipHome.getNumChainConfigurations(), 2, "total chain configs must be 2"); + } + + function test_getPaginatedCCIPHomes_Success() public { + bytes32[] memory chainReaders = new bytes32[](1); + chainReaders[0] = keccak256(abi.encode(1)); + CCIPHome.ChainConfigArgs[] memory adds = new CCIPHome.ChainConfigArgs[](2); + adds[0] = CCIPHome.ChainConfigArgs({ + chainSelector: 1, + chainConfig: CCIPHome.ChainConfig({readers: chainReaders, fChain: 1, config: bytes("config1")}) + }); + adds[1] = CCIPHome.ChainConfigArgs({ + chainSelector: 2, + chainConfig: CCIPHome.ChainConfig({readers: chainReaders, fChain: 1, config: bytes("config2")}) + }); + INodeInfoProvider.NodeInfo[] memory nodeInfos = new INodeInfoProvider.NodeInfo[](1); + nodeInfos[0] = INodeInfoProvider.NodeInfo({ + nodeOperatorId: 1, + signer: bytes32(uint256(1)), + p2pId: chainReaders[0], + encryptionPublicKey: keccak256("encryptionPublicKey"), + hashedCapabilityIds: new bytes32[](0), + configCount: uint32(1), + workflowDONId: uint32(1), + capabilitiesDONIds: new uint256[](0) + }); + vm.mockCall( + CAPABILITIES_REGISTRY, + abi.encodeWithSelector(INodeInfoProvider.getNodesByP2PIds.selector, chainReaders), + abi.encode(nodeInfos) + ); + + s_ccipHome.applyChainConfigUpdates(new uint64[](0), adds); + + CCIPHome.ChainConfigArgs[] memory configs = s_ccipHome.getAllChainConfigs(0, 2); + assertEq(configs.length, 2, "chain configs length must be 2"); + assertEq(configs[0].chainSelector, 1, "chain selector must match"); + assertEq(configs[1].chainSelector, 2, "chain selector must match"); + + configs = s_ccipHome.getAllChainConfigs(0, 1); + assertEq(configs.length, 1, "chain configs length must be 1"); + assertEq(configs[0].chainSelector, 1, "chain selector must match"); + + configs = s_ccipHome.getAllChainConfigs(0, 10); + assertEq(configs.length, 2, "chain configs length must be 2"); + assertEq(configs[0].chainSelector, 1, "chain selector must match"); + assertEq(configs[1].chainSelector, 2, "chain selector must match"); + + configs = s_ccipHome.getAllChainConfigs(1, 1); + assertEq(configs.length, 1, "chain configs length must be 1"); + + configs = s_ccipHome.getAllChainConfigs(1, 2); + assertEq(configs.length, 0, "chain configs length must be 0"); + } + + function test_applyChainConfigUpdates_removeChainConfigs_Success() public { + bytes32[] memory chainReaders = new bytes32[](1); + chainReaders[0] = keccak256(abi.encode(1)); + CCIPHome.ChainConfigArgs[] memory adds = new CCIPHome.ChainConfigArgs[](2); + adds[0] = CCIPHome.ChainConfigArgs({ + chainSelector: 1, + chainConfig: CCIPHome.ChainConfig({readers: chainReaders, fChain: 1, config: bytes("config1")}) + }); + adds[1] = CCIPHome.ChainConfigArgs({ + chainSelector: 2, + chainConfig: CCIPHome.ChainConfig({readers: chainReaders, fChain: 1, config: bytes("config2")}) + }); + + INodeInfoProvider.NodeInfo[] memory nodeInfos = new INodeInfoProvider.NodeInfo[](1); + nodeInfos[0] = INodeInfoProvider.NodeInfo({ + nodeOperatorId: 1, + signer: bytes32(uint256(1)), + p2pId: chainReaders[0], + encryptionPublicKey: keccak256("encryptionPublicKey"), + hashedCapabilityIds: new bytes32[](0), + configCount: uint32(1), + workflowDONId: uint32(1), + capabilitiesDONIds: new uint256[](0) + }); + vm.mockCall( + CAPABILITIES_REGISTRY, + abi.encodeWithSelector(INodeInfoProvider.getNodesByP2PIds.selector, chainReaders), + abi.encode(nodeInfos) + ); + + vm.expectEmit(); + emit CCIPHome.ChainConfigSet(1, adds[0].chainConfig); + vm.expectEmit(); + emit CCIPHome.ChainConfigSet(2, adds[1].chainConfig); + s_ccipHome.applyChainConfigUpdates(new uint64[](0), adds); + + assertEq(s_ccipHome.getNumChainConfigurations(), 2, "total chain configs must be 2"); + + uint64[] memory removes = new uint64[](1); + removes[0] = uint64(1); + + vm.expectEmit(); + emit CCIPHome.ChainConfigRemoved(1); + s_ccipHome.applyChainConfigUpdates(removes, new CCIPHome.ChainConfigArgs[](0)); + + assertEq(s_ccipHome.getNumChainConfigurations(), 1, "total chain configs must be 1"); + } + + // Reverts. + + function test_applyChainConfigUpdates_selectorNotFound_Reverts() public { + uint64[] memory removes = new uint64[](1); + removes[0] = uint64(1); + + vm.expectRevert(abi.encodeWithSelector(CCIPHome.ChainSelectorNotFound.selector, 1)); + s_ccipHome.applyChainConfigUpdates(removes, new CCIPHome.ChainConfigArgs[](0)); + } + + function test_applyChainConfigUpdates_nodeNotInRegistry_Reverts() public { + bytes32[] memory chainReaders = new bytes32[](1); + chainReaders[0] = keccak256(abi.encode(1)); + CCIPHome.ChainConfigArgs[] memory adds = new CCIPHome.ChainConfigArgs[](1); + adds[0] = CCIPHome.ChainConfigArgs({ + chainSelector: 1, + chainConfig: CCIPHome.ChainConfig({readers: chainReaders, fChain: 1, config: abi.encode(1, 2, 3)}) + }); + + vm.mockCallRevert( + CAPABILITIES_REGISTRY, + abi.encodeWithSelector(INodeInfoProvider.getNodesByP2PIds.selector, chainReaders), + abi.encodeWithSelector(INodeInfoProvider.NodeDoesNotExist.selector, chainReaders[0]) + ); + + vm.expectRevert(abi.encodeWithSelector(INodeInfoProvider.NodeDoesNotExist.selector, chainReaders[0])); + s_ccipHome.applyChainConfigUpdates(new uint64[](0), adds); + } + + function test__applyChainConfigUpdates_FChainNotPositive_Reverts() public { + bytes32[] memory chainReaders = new bytes32[](1); + chainReaders[0] = keccak256(abi.encode(1)); + CCIPHome.ChainConfigArgs[] memory adds = new CCIPHome.ChainConfigArgs[](2); + adds[0] = CCIPHome.ChainConfigArgs({ + chainSelector: 1, + chainConfig: CCIPHome.ChainConfig({readers: chainReaders, fChain: 1, config: bytes("config1")}) + }); + adds[1] = CCIPHome.ChainConfigArgs({ + chainSelector: 2, + chainConfig: CCIPHome.ChainConfig({readers: chainReaders, fChain: 0, config: bytes("config2")}) // bad fChain + }); + INodeInfoProvider.NodeInfo[] memory nodeInfos = new INodeInfoProvider.NodeInfo[](1); + nodeInfos[0] = INodeInfoProvider.NodeInfo({ + nodeOperatorId: 1, + signer: bytes32(uint256(1)), + p2pId: chainReaders[0], + encryptionPublicKey: keccak256("encryptionPublicKey"), + hashedCapabilityIds: new bytes32[](0), + configCount: uint32(1), + workflowDONId: uint32(1), + capabilitiesDONIds: new uint256[](0) + }); + vm.mockCall( + CAPABILITIES_REGISTRY, + abi.encodeWithSelector(INodeInfoProvider.getNodesByP2PIds.selector, chainReaders), + abi.encode(nodeInfos) + ); + + vm.expectRevert(CCIPHome.FChainMustBePositive.selector); + s_ccipHome.applyChainConfigUpdates(new uint64[](0), adds); + } +} diff --git a/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.beforeCapabilityConfigSet.t.sol b/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.beforeCapabilityConfigSet.t.sol new file mode 100644 index 00000000000..090c8336c48 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.beforeCapabilityConfigSet.t.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {CCIPHome} from "../../../capability/CCIPHome.sol"; +import {Internal} from "../../../libraries/Internal.sol"; + +import {CCIPHomeTestSetup} from "./CCIPHomeTestSetup.t.sol"; + +contract CCIPHome_beforeCapabilityConfigSet is CCIPHomeTestSetup { + function setUp() public virtual override { + super.setUp(); + vm.stopPrank(); + vm.startPrank(address(CAPABILITIES_REGISTRY)); + } + + function test_beforeCapabilityConfigSet_success() public { + // first set a config + bytes memory callData = abi.encodeCall( + CCIPHome.setCandidate, + (DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, _getBaseConfig(Internal.OCRPluginType.Commit), ZERO_DIGEST) + ); + + vm.expectCall(address(s_ccipHome), callData); + + s_ccipHome.beforeCapabilityConfigSet(new bytes32[](0), callData, 0, DEFAULT_DON_ID); + + // Then revoke the config + bytes32 candidateDigest = s_ccipHome.getCandidateDigest(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE); + assertNotEq(candidateDigest, ZERO_DIGEST); + + callData = abi.encodeCall(CCIPHome.revokeCandidate, (DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, candidateDigest)); + + vm.expectCall(address(s_ccipHome), callData); + + s_ccipHome.beforeCapabilityConfigSet(new bytes32[](0), callData, 0, DEFAULT_DON_ID); + + // Then set a new config + callData = abi.encodeCall( + CCIPHome.setCandidate, + (DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, _getBaseConfig(Internal.OCRPluginType.Commit), ZERO_DIGEST) + ); + + vm.expectCall(address(s_ccipHome), callData); + + s_ccipHome.beforeCapabilityConfigSet(new bytes32[](0), callData, 0, DEFAULT_DON_ID); + + // Then promote the new config + + bytes32 newCandidateDigest = s_ccipHome.getCandidateDigest(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE); + assertNotEq(newCandidateDigest, ZERO_DIGEST); + + callData = abi.encodeCall( + CCIPHome.promoteCandidateAndRevokeActive, (DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, newCandidateDigest, ZERO_DIGEST) + ); + + vm.expectCall(address(s_ccipHome), callData); + + s_ccipHome.beforeCapabilityConfigSet(new bytes32[](0), callData, 0, DEFAULT_DON_ID); + + bytes32 activeDigest = s_ccipHome.getActiveDigest(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE); + assertEq(activeDigest, newCandidateDigest); + } + + function test_beforeCapabilityConfigSet_OnlyCapabilitiesRegistryCanCall_reverts() public { + bytes memory callData = abi.encodeCall( + CCIPHome.setCandidate, + (DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, _getBaseConfig(Internal.OCRPluginType.Commit), ZERO_DIGEST) + ); + + vm.stopPrank(); + + vm.expectRevert(CCIPHome.OnlyCapabilitiesRegistryCanCall.selector); + + s_ccipHome.beforeCapabilityConfigSet(new bytes32[](0), callData, 0, DEFAULT_DON_ID); + } + + function test_beforeCapabilityConfigSet_InvalidSelector_reverts() public { + bytes memory callData = abi.encodeCall(CCIPHome.getConfigDigests, (DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE)); + + vm.expectRevert(abi.encodeWithSelector(CCIPHome.InvalidSelector.selector, CCIPHome.getConfigDigests.selector)); + s_ccipHome.beforeCapabilityConfigSet(new bytes32[](0), callData, 0, DEFAULT_DON_ID); + } + + function test_beforeCapabilityConfigSet_DONIdMismatch_reverts() public { + uint32 wrongDonId = DEFAULT_DON_ID + 1; + + bytes memory callData = abi.encodeCall( + CCIPHome.setCandidate, + (DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, _getBaseConfig(Internal.OCRPluginType.Commit), ZERO_DIGEST) + ); + + vm.expectRevert(abi.encodeWithSelector(CCIPHome.DONIdMismatch.selector, DEFAULT_DON_ID, wrongDonId)); + s_ccipHome.beforeCapabilityConfigSet(new bytes32[](0), callData, 0, wrongDonId); + } + + function test_beforeCapabilityConfigSet_InnerCallReverts_reverts() public { + bytes memory callData = abi.encodeCall(CCIPHome.revokeCandidate, (DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, ZERO_DIGEST)); + + vm.expectRevert(CCIPHome.RevokingZeroDigestNotAllowed.selector); + s_ccipHome.beforeCapabilityConfigSet(new bytes32[](0), callData, 0, DEFAULT_DON_ID); + } +} diff --git a/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.constructor.t.sol b/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.constructor.t.sol new file mode 100644 index 00000000000..f4c1a777f3d --- /dev/null +++ b/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.constructor.t.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {CCIPHome} from "../../../capability/CCIPHome.sol"; +import {CCIPHomeTestSetup} from "./CCIPHomeTestSetup.t.sol"; + +contract CCIPHome_constructor is CCIPHomeTestSetup { + function test_constructor_success() public { + CCIPHome ccipHome = new CCIPHome(CAPABILITIES_REGISTRY); + + assertEq(address(ccipHome.getCapabilityRegistry()), CAPABILITIES_REGISTRY); + } + + function test_constructor_CapabilitiesRegistryAddressZero_reverts() public { + vm.expectRevert(CCIPHome.ZeroAddressNotAllowed.selector); + new CCIPHome(address(0)); + } +} diff --git a/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.getAllConfigs.t.sol b/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.getAllConfigs.t.sol new file mode 100644 index 00000000000..277819e1179 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.getAllConfigs.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {CCIPHome} from "../../../capability/CCIPHome.sol"; +import {Internal} from "../../../libraries/Internal.sol"; +import {CCIPHomeTestSetup} from "./CCIPHomeTestSetup.t.sol"; + +contract CCIPHome_getAllConfigs is CCIPHomeTestSetup { + function test_getAllConfigs_success() public { + CCIPHome.OCR3Config memory config = _getBaseConfig(Internal.OCRPluginType.Commit); + bytes32 firstDigest = s_ccipHome.setCandidate(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, config, ZERO_DIGEST); + + (CCIPHome.VersionedConfig memory activeConfig, CCIPHome.VersionedConfig memory candidateConfig) = + s_ccipHome.getAllConfigs(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE); + assertEq(activeConfig.configDigest, ZERO_DIGEST); + assertEq(candidateConfig.configDigest, firstDigest); + + s_ccipHome.promoteCandidateAndRevokeActive(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, firstDigest, ZERO_DIGEST); + + (activeConfig, candidateConfig) = s_ccipHome.getAllConfigs(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE); + assertEq(activeConfig.configDigest, firstDigest); + assertEq(candidateConfig.configDigest, ZERO_DIGEST); + + bytes32 secondDigest = s_ccipHome.setCandidate(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, config, ZERO_DIGEST); + + (activeConfig, candidateConfig) = s_ccipHome.getAllConfigs(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE); + assertEq(activeConfig.configDigest, firstDigest); + assertEq(candidateConfig.configDigest, secondDigest); + + (activeConfig, candidateConfig) = s_ccipHome.getAllConfigs(DEFAULT_DON_ID + 1, DEFAULT_PLUGIN_TYPE); + assertEq(activeConfig.configDigest, ZERO_DIGEST); + assertEq(candidateConfig.configDigest, ZERO_DIGEST); + + (activeConfig, candidateConfig) = s_ccipHome.getAllConfigs(DEFAULT_DON_ID, Internal.OCRPluginType.Execution); + assertEq(activeConfig.configDigest, ZERO_DIGEST); + assertEq(candidateConfig.configDigest, ZERO_DIGEST); + } +} diff --git a/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.getCapabilityConfiguration.t.sol b/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.getCapabilityConfiguration.t.sol new file mode 100644 index 00000000000..ea65e111f0d --- /dev/null +++ b/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.getCapabilityConfiguration.t.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {CCIPHomeTestSetup} from "./CCIPHomeTestSetup.t.sol"; + +contract CCIPHome_getCapabilityConfiguration is CCIPHomeTestSetup { + function test_getCapabilityConfiguration_success() public view { + bytes memory config = s_ccipHome.getCapabilityConfiguration(DEFAULT_DON_ID); + assertEq(config.length, 0); + } +} diff --git a/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.getConfigDigests.t.sol b/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.getConfigDigests.t.sol new file mode 100644 index 00000000000..8cca6b12589 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.getConfigDigests.t.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {CCIPHome} from "../../../capability/CCIPHome.sol"; +import {Internal} from "../../../libraries/Internal.sol"; + +import {CCIPHomeTestSetup} from "./CCIPHomeTestSetup.t.sol"; + +contract CCIPHome_getConfigDigests is CCIPHomeTestSetup { + function test_getConfigDigests_success() public { + (bytes32 activeDigest, bytes32 candidateDigest) = s_ccipHome.getConfigDigests(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE); + assertEq(activeDigest, ZERO_DIGEST); + assertEq(candidateDigest, ZERO_DIGEST); + + CCIPHome.OCR3Config memory config = _getBaseConfig(Internal.OCRPluginType.Commit); + bytes32 firstDigest = s_ccipHome.setCandidate(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, config, ZERO_DIGEST); + + (activeDigest, candidateDigest) = s_ccipHome.getConfigDigests(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE); + assertEq(activeDigest, ZERO_DIGEST); + assertEq(candidateDigest, firstDigest); + + s_ccipHome.promoteCandidateAndRevokeActive(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, firstDigest, ZERO_DIGEST); + + (activeDigest, candidateDigest) = s_ccipHome.getConfigDigests(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE); + assertEq(activeDigest, firstDigest); + assertEq(candidateDigest, ZERO_DIGEST); + + bytes32 secondDigest = s_ccipHome.setCandidate(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, config, ZERO_DIGEST); + + (activeDigest, candidateDigest) = s_ccipHome.getConfigDigests(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE); + assertEq(activeDigest, firstDigest); + assertEq(candidateDigest, secondDigest); + + assertEq(activeDigest, s_ccipHome.getActiveDigest(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE)); + assertEq(candidateDigest, s_ccipHome.getCandidateDigest(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE)); + } +} diff --git a/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.promoteCandidateAndRevokeActive.t.sol b/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.promoteCandidateAndRevokeActive.t.sol new file mode 100644 index 00000000000..09f25750ac3 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.promoteCandidateAndRevokeActive.t.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {CCIPHome} from "../../../capability/CCIPHome.sol"; +import {Internal} from "../../../libraries/Internal.sol"; + +import {CCIPHomeTestSetup} from "./CCIPHomeTestSetup.t.sol"; + +contract CCIPHome_promoteCandidateAndRevokeActive is CCIPHomeTestSetup { + function test_promoteCandidateAndRevokeActive_multiplePlugins_success() public { + promoteCandidateAndRevokeActive(Internal.OCRPluginType.Commit); + promoteCandidateAndRevokeActive(Internal.OCRPluginType.Execution); + + // check that the two plugins have only active configs and no candidates. + (bytes32 activeDigest, bytes32 candidateDigest) = + s_ccipHome.getConfigDigests(DEFAULT_DON_ID, Internal.OCRPluginType.Commit); + assertTrue(activeDigest != ZERO_DIGEST); + assertEq(candidateDigest, ZERO_DIGEST); + + (activeDigest, candidateDigest) = s_ccipHome.getConfigDigests(DEFAULT_DON_ID, Internal.OCRPluginType.Execution); + assertTrue(activeDigest != ZERO_DIGEST); + assertEq(candidateDigest, ZERO_DIGEST); + } + + function promoteCandidateAndRevokeActive( + Internal.OCRPluginType pluginType + ) public { + CCIPHome.OCR3Config memory config = _getBaseConfig(pluginType); + bytes32 firstConfigToPromote = s_ccipHome.setCandidate(DEFAULT_DON_ID, pluginType, config, ZERO_DIGEST); + + vm.expectEmit(); + emit CCIPHome.ConfigPromoted(firstConfigToPromote); + + s_ccipHome.promoteCandidateAndRevokeActive(DEFAULT_DON_ID, pluginType, firstConfigToPromote, ZERO_DIGEST); + + // Assert the active digest is updated and the candidate digest is set to zero + (bytes32 activeDigest, bytes32 candidateDigest) = s_ccipHome.getConfigDigests(DEFAULT_DON_ID, pluginType); + assertEq(activeDigest, firstConfigToPromote); + assertEq(candidateDigest, ZERO_DIGEST); + + // Set a new candidate to promote over a non-zero active config. + config.offchainConfig = abi.encode("new_offchainConfig_config"); + bytes32 secondConfigToPromote = s_ccipHome.setCandidate(DEFAULT_DON_ID, pluginType, config, ZERO_DIGEST); + + vm.expectEmit(); + emit CCIPHome.ActiveConfigRevoked(firstConfigToPromote); + + vm.expectEmit(); + emit CCIPHome.ConfigPromoted(secondConfigToPromote); + + s_ccipHome.promoteCandidateAndRevokeActive(DEFAULT_DON_ID, pluginType, secondConfigToPromote, firstConfigToPromote); + + (CCIPHome.VersionedConfig memory activeConfig, CCIPHome.VersionedConfig memory candidateConfig) = + s_ccipHome.getAllConfigs(DEFAULT_DON_ID, pluginType); + assertEq(activeConfig.configDigest, secondConfigToPromote); + assertEq(candidateConfig.configDigest, ZERO_DIGEST); + assertEq(keccak256(abi.encode(activeConfig.config)), keccak256(abi.encode(config))); + } + + function test_promoteCandidateAndRevokeActive_NoOpStateTransitionNotAllowed_reverts() public { + vm.expectRevert(CCIPHome.NoOpStateTransitionNotAllowed.selector); + s_ccipHome.promoteCandidateAndRevokeActive(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, ZERO_DIGEST, ZERO_DIGEST); + } + + function test_promoteCandidateAndRevokeActive_ConfigDigestMismatch_reverts() public { + (bytes32 priorActiveDigest, bytes32 priorCandidateDigest) = + s_ccipHome.getConfigDigests(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE); + bytes32 wrongActiveDigest = keccak256("wrongActiveDigest"); + bytes32 wrongCandidateDigest = keccak256("wrongCandidateDigest"); + + vm.expectRevert( + abi.encodeWithSelector(CCIPHome.ConfigDigestMismatch.selector, priorActiveDigest, wrongCandidateDigest) + ); + s_ccipHome.promoteCandidateAndRevokeActive( + DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, wrongCandidateDigest, wrongActiveDigest + ); + + vm.expectRevert( + abi.encodeWithSelector(CCIPHome.ConfigDigestMismatch.selector, priorActiveDigest, wrongActiveDigest) + ); + + s_ccipHome.promoteCandidateAndRevokeActive( + DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, priorCandidateDigest, wrongActiveDigest + ); + } + + function test_promoteCandidateAndRevokeActive_CanOnlySelfCall_reverts() public { + vm.stopPrank(); + + vm.expectRevert(CCIPHome.CanOnlySelfCall.selector); + s_ccipHome.promoteCandidateAndRevokeActive( + DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, keccak256("toPromote"), keccak256("ToRevoke") + ); + } +} diff --git a/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.revokeCandidate.t.sol b/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.revokeCandidate.t.sol new file mode 100644 index 00000000000..b2793727d59 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.revokeCandidate.t.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {CCIPHome} from "../../../capability/CCIPHome.sol"; +import {Internal} from "../../../libraries/Internal.sol"; + +import {CCIPHomeTestSetup} from "./CCIPHomeTestSetup.t.sol"; + +contract CCIPHome_revokeCandidate is CCIPHomeTestSetup { + // Sets two configs + function setUp() public virtual override { + super.setUp(); + CCIPHome.OCR3Config memory config = _getBaseConfig(Internal.OCRPluginType.Commit); + bytes32 digest = s_ccipHome.setCandidate(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, config, ZERO_DIGEST); + s_ccipHome.promoteCandidateAndRevokeActive(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, digest, ZERO_DIGEST); + + config.offrampAddress = abi.encode("new_offrampAddress"); + s_ccipHome.setCandidate(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, config, ZERO_DIGEST); + } + + function test_revokeCandidate_success() public { + (bytes32 priorActiveDigest, bytes32 priorCandidateDigest) = + s_ccipHome.getConfigDigests(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE); + + vm.expectEmit(); + emit CCIPHome.CandidateConfigRevoked(priorCandidateDigest); + + s_ccipHome.revokeCandidate(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, priorCandidateDigest); + + (CCIPHome.VersionedConfig memory storedVersionedConfig, bool ok) = + s_ccipHome.getConfig(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, priorCandidateDigest); + assertFalse(ok); + // Ensure no old data is returned, even though it's still in storage + assertEq(storedVersionedConfig.version, 0); + assertEq(storedVersionedConfig.config.chainSelector, 0); + assertEq(storedVersionedConfig.config.FRoleDON, 0); + + // Asser the active digest is unaffected but the candidate digest is set to zero + (bytes32 activeDigest, bytes32 candidateDigest) = s_ccipHome.getConfigDigests(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE); + assertEq(activeDigest, priorActiveDigest); + assertEq(candidateDigest, ZERO_DIGEST); + assertTrue(candidateDigest != priorCandidateDigest); + } + + function test_revokeCandidate_ConfigDigestMismatch_reverts() public { + (, bytes32 priorCandidateDigest) = s_ccipHome.getConfigDigests(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE); + + bytes32 wrongDigest = keccak256("wrong_digest"); + vm.expectRevert(abi.encodeWithSelector(CCIPHome.ConfigDigestMismatch.selector, priorCandidateDigest, wrongDigest)); + s_ccipHome.revokeCandidate(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, wrongDigest); + } + + function test_revokeCandidate_RevokingZeroDigestNotAllowed_reverts() public { + vm.expectRevert(CCIPHome.RevokingZeroDigestNotAllowed.selector); + s_ccipHome.revokeCandidate(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, ZERO_DIGEST); + } + + function test_revokeCandidate_CanOnlySelfCall_reverts() public { + vm.startPrank(address(0)); + + vm.expectRevert(CCIPHome.CanOnlySelfCall.selector); + s_ccipHome.revokeCandidate(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, keccak256("configDigest")); + } +} diff --git a/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.setCandidate.t.sol b/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.setCandidate.t.sol new file mode 100644 index 00000000000..49f365b22cd --- /dev/null +++ b/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.setCandidate.t.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {CCIPHome} from "../../../capability/CCIPHome.sol"; +import {Internal} from "../../../libraries/Internal.sol"; + +import {CCIPHomeTestSetup} from "./CCIPHomeTestSetup.t.sol"; + +contract CCIPHome_setCandidate is CCIPHomeTestSetup { + function test_setCandidate_success() public { + CCIPHome.OCR3Config memory config = _getBaseConfig(Internal.OCRPluginType.Commit); + CCIPHome.VersionedConfig memory versionedConfig = + CCIPHome.VersionedConfig({version: 1, config: config, configDigest: ZERO_DIGEST}); + + versionedConfig.configDigest = + _getConfigDigest(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, abi.encode(versionedConfig.config), versionedConfig.version); + + vm.expectEmit(); + emit CCIPHome.ConfigSet(versionedConfig.configDigest, versionedConfig.version, versionedConfig.config); + + s_ccipHome.setCandidate(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, versionedConfig.config, ZERO_DIGEST); + + (CCIPHome.VersionedConfig memory storedVersionedConfig, bool ok) = + s_ccipHome.getConfig(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, versionedConfig.configDigest); + assertTrue(ok); + assertEq(storedVersionedConfig.version, versionedConfig.version); + assertEq(storedVersionedConfig.configDigest, versionedConfig.configDigest); + assertEq(keccak256(abi.encode(storedVersionedConfig.config)), keccak256(abi.encode(versionedConfig.config))); + } + + function test_setCandidate_ConfigDigestMismatch_reverts() public { + CCIPHome.OCR3Config memory config = _getBaseConfig(Internal.OCRPluginType.Commit); + + bytes32 digest = s_ccipHome.setCandidate(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, config, ZERO_DIGEST); + + vm.expectRevert(abi.encodeWithSelector(CCIPHome.ConfigDigestMismatch.selector, digest, ZERO_DIGEST)); + s_ccipHome.setCandidate(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, config, ZERO_DIGEST); + + vm.expectEmit(); + emit CCIPHome.CandidateConfigRevoked(digest); + + s_ccipHome.setCandidate(DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, config, digest); + } + + function test_setCandidate_CanOnlySelfCall_reverts() public { + vm.stopPrank(); + + vm.expectRevert(CCIPHome.CanOnlySelfCall.selector); + s_ccipHome.setCandidate( + DEFAULT_DON_ID, DEFAULT_PLUGIN_TYPE, _getBaseConfig(Internal.OCRPluginType.Commit), ZERO_DIGEST + ); + } +} diff --git a/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.supportsInterface.t.sol b/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.supportsInterface.t.sol new file mode 100644 index 00000000000..c67f007f735 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.supportsInterface.t.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {ICapabilityConfiguration} from "../../../../keystone/interfaces/ICapabilityConfiguration.sol"; + +import {IERC165} from "../../../../vendor/openzeppelin-solidity/v5.0.2/contracts/interfaces/IERC165.sol"; +import {CCIPHomeTestSetup} from "./CCIPHomeTestSetup.t.sol"; + +contract CCIPHome_supportsInterface is CCIPHomeTestSetup { + function test_supportsInterface_success() public view { + assertTrue(s_ccipHome.supportsInterface(type(IERC165).interfaceId)); + assertTrue(s_ccipHome.supportsInterface(type(ICapabilityConfiguration).interfaceId)); + } +} diff --git a/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.validateConfig.t.sol b/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.validateConfig.t.sol new file mode 100644 index 00000000000..13c1a0610d7 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHome.validateConfig.t.sol @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {INodeInfoProvider} from "../../../../keystone/interfaces/INodeInfoProvider.sol"; + +import {CCIPHome} from "../../../capability/CCIPHome.sol"; +import {Internal} from "../../../libraries/Internal.sol"; +import {CCIPHomeHelper} from "../../helpers/CCIPHomeHelper.sol"; + +import {CCIPHomeTestSetup} from "./CCIPHomeTestSetup.t.sol"; + +contract CCIPHome__validateConfig is CCIPHomeTestSetup { + function setUp() public virtual override { + s_ccipHome = new CCIPHomeHelper(CAPABILITIES_REGISTRY); + } + + function _addChainConfig( + uint256 numNodes + ) internal returns (CCIPHome.OCR3Node[] memory nodes) { + return _addChainConfig(numNodes, 1); + } + + function _makeBytes32Array(uint256 length, uint256 seed) internal pure returns (bytes32[] memory arr) { + arr = new bytes32[](length); + for (uint256 i = 0; i < length; i++) { + arr[i] = keccak256(abi.encode(i, 1, seed)); + } + return arr; + } + + function _makeBytesArray(uint256 length, uint256 seed) internal pure returns (bytes[] memory arr) { + arr = new bytes[](length); + for (uint256 i = 0; i < length; i++) { + arr[i] = abi.encode(keccak256(abi.encode(i, 1, seed))); + } + return arr; + } + + function _addChainConfig(uint256 numNodes, uint8 fChain) internal returns (CCIPHome.OCR3Node[] memory nodes) { + bytes32[] memory p2pIds = _makeBytes32Array(numNodes, 0); + bytes[] memory signers = _makeBytesArray(numNodes, 10); + bytes[] memory transmitters = _makeBytesArray(numNodes, 20); + + nodes = new CCIPHome.OCR3Node[](numNodes); + INodeInfoProvider.NodeInfo[] memory nodeInfos = new INodeInfoProvider.NodeInfo[](numNodes); + for (uint256 i = 0; i < numNodes; i++) { + nodes[i] = CCIPHome.OCR3Node({p2pId: p2pIds[i], signerKey: signers[i], transmitterKey: transmitters[i]}); + nodeInfos[i] = INodeInfoProvider.NodeInfo({ + nodeOperatorId: 1, + signer: bytes32(signers[i]), + p2pId: p2pIds[i], + encryptionPublicKey: keccak256("encryptionPublicKey"), + hashedCapabilityIds: new bytes32[](0), + configCount: uint32(1), + workflowDONId: uint32(1), + capabilitiesDONIds: new uint256[](0) + }); + } + vm.mockCall( + CAPABILITIES_REGISTRY, + abi.encodeWithSelector(INodeInfoProvider.getNodesByP2PIds.selector, p2pIds), + abi.encode(nodeInfos) + ); + // Add chain selector for chain 1. + CCIPHome.ChainConfigArgs[] memory adds = new CCIPHome.ChainConfigArgs[](1); + adds[0] = CCIPHome.ChainConfigArgs({ + chainSelector: 1, + chainConfig: CCIPHome.ChainConfig({readers: p2pIds, fChain: fChain, config: bytes("config1")}) + }); + + vm.expectEmit(); + emit CCIPHome.ChainConfigSet(1, adds[0].chainConfig); + s_ccipHome.applyChainConfigUpdates(new uint64[](0), adds); + + return nodes; + } + + function _getCorrectOCR3Config(uint8 numNodes, uint8 FRoleDON) internal returns (CCIPHome.OCR3Config memory) { + CCIPHome.OCR3Node[] memory nodes = _addChainConfig(numNodes); + + return CCIPHome.OCR3Config({ + pluginType: Internal.OCRPluginType.Commit, + offrampAddress: abi.encode(keccak256(abi.encode("offramp"))), + rmnHomeAddress: abi.encode(keccak256(abi.encode("rmnHome"))), + chainSelector: 1, + nodes: nodes, + FRoleDON: FRoleDON, + offchainConfigVersion: 30, + offchainConfig: bytes("offchainConfig") + }); + } + + function _getCorrectOCR3Config() internal returns (CCIPHome.OCR3Config memory) { + return _getCorrectOCR3Config(4, 1); + } + + // Successes. + + function test__validateConfig_Success() public { + s_ccipHome.validateConfig(_getCorrectOCR3Config()); + } + + function test__validateConfigLessTransmittersThanSigners_Success() public { + // fChain is 1, so there should be at least 4 transmitters. + CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(5, 1); + config.nodes[1].transmitterKey = bytes(""); + + s_ccipHome.validateConfig(config); + } + + function test__validateConfigSmallerFChain_Success() public { + CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(11, 3); + + // Set fChain to 2 + _addChainConfig(4, 2); + + s_ccipHome.validateConfig(config); + } + + // Reverts + + function test__validateConfig_ChainSelectorNotSet_Reverts() public { + CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(); + config.chainSelector = 0; // invalid + + vm.expectRevert(CCIPHome.ChainSelectorNotSet.selector); + s_ccipHome.validateConfig(config); + } + + function test__validateConfig_OfframpAddressCannotBeZero_Reverts() public { + CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(); + config.offrampAddress = ""; // invalid + + vm.expectRevert(CCIPHome.OfframpAddressCannotBeZero.selector); + s_ccipHome.validateConfig(config); + } + + function test__validateConfig_ABIEncodedAddress_OfframpAddressCannotBeZero_Reverts() public { + CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(); + config.offrampAddress = abi.encode(address(0)); // invalid + + vm.expectRevert(CCIPHome.OfframpAddressCannotBeZero.selector); + s_ccipHome.validateConfig(config); + } + + function test__validateConfig_RMNHomeAddressCannotBeZero_Reverts() public { + CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(); + config.rmnHomeAddress = ""; // invalid + + vm.expectRevert(CCIPHome.RMNHomeAddressCannotBeZero.selector); + s_ccipHome.validateConfig(config); + } + + function test__validateConfig_ABIEncodedAddress_RMNHomeAddressCannotBeZero_Reverts() public { + CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(); + config.rmnHomeAddress = abi.encode(address(0)); // invalid + + vm.expectRevert(CCIPHome.RMNHomeAddressCannotBeZero.selector); + s_ccipHome.validateConfig(config); + } + + function test__validateConfig_ChainSelectorNotFound_Reverts() public { + CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(); + config.chainSelector = 2; // not set + + vm.expectRevert(abi.encodeWithSelector(CCIPHome.ChainSelectorNotFound.selector, 2)); + s_ccipHome.validateConfig(config); + } + + function test__validateConfig_NotEnoughTransmitters_Reverts() public { + CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(); + uint256 numberOfTransmitters = 3; + + // 32 > 31 (max num oracles) + CCIPHome.OCR3Node[] memory nodes = _addChainConfig(31); + + // truncate transmitters to < 3 * fChain + 1 + // since fChain is 1 in this case, we need to truncate to 3 transmitters. + for (uint256 i = numberOfTransmitters; i < nodes.length; ++i) { + nodes[i].transmitterKey = bytes(""); + } + + config.nodes = nodes; + vm.expectRevert(abi.encodeWithSelector(CCIPHome.NotEnoughTransmitters.selector, numberOfTransmitters, 4)); + s_ccipHome.validateConfig(config); + } + + function test__validateConfig_NotEnoughTransmittersEmptyAddresses_Reverts() public { + CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(); + config.nodes[0].transmitterKey = bytes(""); + + vm.expectRevert(abi.encodeWithSelector(CCIPHome.NotEnoughTransmitters.selector, 3, 4)); + s_ccipHome.validateConfig(config); + + // Zero out remaining transmitters to verify error changes + for (uint256 i = 1; i < config.nodes.length; ++i) { + config.nodes[i].transmitterKey = bytes(""); + } + + vm.expectRevert(abi.encodeWithSelector(CCIPHome.NotEnoughTransmitters.selector, 0, 4)); + s_ccipHome.validateConfig(config); + } + + function test__validateConfig_TooManySigners_Reverts() public { + CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(); + config.nodes = new CCIPHome.OCR3Node[](257); + + vm.expectRevert(CCIPHome.TooManySigners.selector); + s_ccipHome.validateConfig(config); + } + + function test__validateConfig_FChainTooHigh_Reverts() public { + CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(); + config.FRoleDON = 2; // too low + + // Set fChain to 3 + _addChainConfig(4, 3); + + vm.expectRevert(abi.encodeWithSelector(CCIPHome.FChainTooHigh.selector, 3, 2)); + s_ccipHome.validateConfig(config); + } + + function test__validateConfig_FMustBePositive_Reverts() public { + CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(); + config.FRoleDON = 0; // not positive + + vm.expectRevert(abi.encodeWithSelector(CCIPHome.FChainTooHigh.selector, 1, 0)); + s_ccipHome.validateConfig(config); + } + + function test__validateConfig_FTooHigh_Reverts() public { + CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(); + config.FRoleDON = 2; // too high + + vm.expectRevert(CCIPHome.FTooHigh.selector); + s_ccipHome.validateConfig(config); + } + + function test__validateConfig_ZeroP2PId_Reverts() public { + CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(); + config.nodes[1].p2pId = bytes32(0); + + vm.expectRevert(abi.encodeWithSelector(CCIPHome.InvalidNode.selector, config.nodes[1])); + s_ccipHome.validateConfig(config); + } + + function test__validateConfig_ZeroSignerKey_Reverts() public { + CCIPHome.OCR3Config memory config = _getCorrectOCR3Config(); + config.nodes[2].signerKey = bytes(""); + + vm.expectRevert(abi.encodeWithSelector(CCIPHome.InvalidNode.selector, config.nodes[2])); + s_ccipHome.validateConfig(config); + } +} diff --git a/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHomeTestSetup.t.sol b/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHomeTestSetup.t.sol new file mode 100644 index 00000000000..a06f50a01cf --- /dev/null +++ b/contracts/src/v0.8/ccip/test/capability/CCIPHome/CCIPHomeTestSetup.t.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {INodeInfoProvider} from "../../../../keystone/interfaces/INodeInfoProvider.sol"; + +import {CCIPHome} from "../../../capability/CCIPHome.sol"; +import {Internal} from "../../../libraries/Internal.sol"; +import {CCIPHomeHelper} from "../../helpers/CCIPHomeHelper.sol"; +import {Test} from "forge-std/Test.sol"; + +contract CCIPHomeTestSetup is Test { + // address internal constant OWNER = address(0x0000000123123123123); + bytes32 internal constant ZERO_DIGEST = bytes32(uint256(0)); + address internal constant CAPABILITIES_REGISTRY = address(0x0000000123123123123); + Internal.OCRPluginType internal constant DEFAULT_PLUGIN_TYPE = Internal.OCRPluginType.Commit; + uint32 internal constant DEFAULT_DON_ID = 78978987; + + CCIPHomeHelper public s_ccipHome; + + uint256 private constant PREFIX_MASK = type(uint256).max << (256 - 16); // 0xFFFF00..00 + uint256 private constant PREFIX = 0x000a << (256 - 16); // 0x000b00..00 + + uint64 private constant DEFAULT_CHAIN_SELECTOR = 9381579735; + + function setUp() public virtual { + s_ccipHome = new CCIPHomeHelper(CAPABILITIES_REGISTRY); + s_ccipHome.applyChainConfigUpdates(new uint64[](0), _getBaseChainConfigs()); + vm.startPrank(address(s_ccipHome)); + } + + function _getBaseChainConfigs() internal pure returns (CCIPHome.ChainConfigArgs[] memory) { + CCIPHome.ChainConfigArgs[] memory configs = new CCIPHome.ChainConfigArgs[](1); + CCIPHome.ChainConfig memory chainConfig = + CCIPHome.ChainConfig({readers: new bytes32[](0), fChain: 1, config: abi.encode("chainConfig")}); + configs[0] = CCIPHome.ChainConfigArgs({chainSelector: DEFAULT_CHAIN_SELECTOR, chainConfig: chainConfig}); + + return configs; + } + + function _getConfigDigest( + uint32 donId, + Internal.OCRPluginType pluginType, + bytes memory config, + uint32 version + ) internal view returns (bytes32) { + return bytes32( + (PREFIX & PREFIX_MASK) + | ( + uint256( + keccak256( + bytes.concat( + abi.encode(bytes32("EVM"), block.chainid, address(s_ccipHome), donId, pluginType, version), config + ) + ) + ) & ~PREFIX_MASK + ) + ); + } + + function _getBaseConfig( + Internal.OCRPluginType pluginType + ) internal returns (CCIPHome.OCR3Config memory) { + CCIPHome.OCR3Node[] memory nodes = new CCIPHome.OCR3Node[](4); + bytes32[] memory p2pIds = new bytes32[](4); + for (uint256 i = 0; i < nodes.length; i++) { + p2pIds[i] = keccak256(abi.encode("p2pId", i)); + nodes[i] = CCIPHome.OCR3Node({ + p2pId: keccak256(abi.encode("p2pId", i)), + signerKey: abi.encode("signerKey"), + transmitterKey: abi.encode("transmitterKey") + }); + } + + // This is a work-around for not calling mockCall / expectCall with each scenario using _getBaseConfig + INodeInfoProvider.NodeInfo[] memory nodeInfos = new INodeInfoProvider.NodeInfo[](4); + vm.mockCall( + CAPABILITIES_REGISTRY, + abi.encodeWithSelector(INodeInfoProvider.getNodesByP2PIds.selector, p2pIds), + abi.encode(nodeInfos) + ); + + return CCIPHome.OCR3Config({ + pluginType: pluginType, + chainSelector: DEFAULT_CHAIN_SELECTOR, + FRoleDON: 1, + offchainConfigVersion: 98765, + offrampAddress: abi.encode("offrampAddress"), + rmnHomeAddress: abi.encode("rmnHomeAddress"), + nodes: nodes, + offchainConfig: abi.encode("offchainConfig") + }); + } +} diff --git a/contracts/src/v0.8/ccip/test/e2e/End2End.t.sol b/contracts/src/v0.8/ccip/test/e2e/End2End.t.sol index 610bf311cd8..77dd57a2d08 100644 --- a/contracts/src/v0.8/ccip/test/e2e/End2End.t.sol +++ b/contracts/src/v0.8/ccip/test/e2e/End2End.t.sol @@ -15,8 +15,8 @@ import {LockReleaseTokenPool} from "../../pools/LockReleaseTokenPool.sol"; import {TokenAdminRegistry} from "../../tokenAdminRegistry/TokenAdminRegistry.sol"; import {MerkleHelper} from "../helpers/MerkleHelper.sol"; import {OnRampHelper} from "../helpers/OnRampHelper.sol"; -import {OffRampSetup} from "../offRamp/offRamp/OffRampSetup.t.sol"; -import {OnRampSetup} from "../onRamp/onRamp/OnRampSetup.t.sol"; +import {OffRampSetup} from "../offRamp/OffRamp/OffRampSetup.t.sol"; +import {OnRampSetup} from "../onRamp/OnRamp/OnRampSetup.t.sol"; import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; diff --git a/contracts/src/v0.8/ccip/test/ocr/MultiOCR3Base.t.sol b/contracts/src/v0.8/ccip/test/ocr/MultiOCR3Base/MultiOCR3Base.setOCR3Configs.t.sol similarity index 67% rename from contracts/src/v0.8/ccip/test/ocr/MultiOCR3Base.t.sol rename to contracts/src/v0.8/ccip/test/ocr/MultiOCR3Base/MultiOCR3Base.setOCR3Configs.t.sol index 2783608e68e..458767a4512 100644 --- a/contracts/src/v0.8/ccip/test/ocr/MultiOCR3Base.t.sol +++ b/contracts/src/v0.8/ccip/test/ocr/MultiOCR3Base/MultiOCR3Base.setOCR3Configs.t.sol @@ -1,326 +1,12 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.24; -import {MultiOCR3Base} from "../../ocr/MultiOCR3Base.sol"; -import {MultiOCR3Helper} from "../helpers/MultiOCR3Helper.sol"; +import {MultiOCR3Base} from "../../../ocr/MultiOCR3Base.sol"; +import {MultiOCR3Helper} from "../../helpers/MultiOCR3Helper.sol"; import {MultiOCR3BaseSetup} from "./MultiOCR3BaseSetup.t.sol"; import {Vm} from "forge-std/Vm.sol"; -contract MultiOCR3Base_transmit is MultiOCR3BaseSetup { - bytes32 internal s_configDigest1; - bytes32 internal s_configDigest2; - bytes32 internal s_configDigest3; - - function setUp() public virtual override { - super.setUp(); - - s_configDigest1 = _getBasicConfigDigest(1, s_validSigners, s_validTransmitters); - s_configDigest2 = _getBasicConfigDigest(1, s_validSigners, s_validTransmitters); - s_configDigest3 = _getBasicConfigDigest(2, s_emptySigners, s_validTransmitters); - - MultiOCR3Base.OCRConfigArgs[] memory ocrConfigs = new MultiOCR3Base.OCRConfigArgs[](3); - ocrConfigs[0] = MultiOCR3Base.OCRConfigArgs({ - ocrPluginType: 0, - configDigest: s_configDigest1, - F: 1, - isSignatureVerificationEnabled: true, - signers: s_validSigners, - transmitters: s_validTransmitters - }); - ocrConfigs[1] = MultiOCR3Base.OCRConfigArgs({ - ocrPluginType: 1, - configDigest: s_configDigest2, - F: 2, - isSignatureVerificationEnabled: true, - signers: s_validSigners, - transmitters: s_validTransmitters - }); - ocrConfigs[2] = MultiOCR3Base.OCRConfigArgs({ - ocrPluginType: 2, - configDigest: s_configDigest3, - F: 1, - isSignatureVerificationEnabled: false, - signers: s_emptySigners, - transmitters: s_validTransmitters - }); - - s_multiOCR3.setOCR3Configs(ocrConfigs); - } - - function test_TransmitSigners_gas_Success() public { - vm.pauseGasMetering(); - bytes32[3] memory reportContext = [s_configDigest1, s_configDigest1, s_configDigest1]; - - // F = 2, need 2 signatures - (bytes32[] memory rs, bytes32[] memory ss,, bytes32 rawVs) = - _getSignaturesForDigest(s_validSignerKeys, REPORT, reportContext, 2); - - s_multiOCR3.setTransmitOcrPluginType(0); - - vm.expectEmit(); - emit MultiOCR3Base.Transmitted(0, s_configDigest1, uint64(uint256(s_configDigest1))); - - vm.startPrank(s_validTransmitters[1]); - vm.resumeGasMetering(); - s_multiOCR3.transmitWithSignatures(reportContext, REPORT, rs, ss, rawVs); - } - - function test_TransmitWithoutSignatureVerification_gas_Success() public { - vm.pauseGasMetering(); - bytes32[3] memory reportContext = [s_configDigest3, s_configDigest3, s_configDigest3]; - - s_multiOCR3.setTransmitOcrPluginType(2); - - vm.expectEmit(); - emit MultiOCR3Base.Transmitted(2, s_configDigest3, uint64(uint256(s_configDigest3))); - - vm.startPrank(s_validTransmitters[0]); - vm.resumeGasMetering(); - s_multiOCR3.transmitWithoutSignatures(reportContext, REPORT); - } - - function test_Fuzz_TransmitSignersWithSignatures_Success(uint8 F, uint64 randomAddressOffset) public { - vm.pauseGasMetering(); - - F = uint8(bound(F, 1, 3)); - - // condition: signers.length > 3F - uint8 signersLength = 3 * F + 1; - address[] memory signers = new address[](signersLength); - address[] memory transmitters = new address[](signersLength); - uint256[] memory signerKeys = new uint256[](signersLength); - - // Force addresses to be unique (with a random offset for broader testing) - for (uint160 i = 0; i < signersLength; ++i) { - transmitters[i] = vm.addr(PRIVATE0 + randomAddressOffset + i); - // condition: non-zero oracle address - vm.assume(transmitters[i] != address(0)); - - // condition: non-repeating addresses (no clashes with transmitters) - signerKeys[i] = PRIVATE0 + randomAddressOffset + i + signersLength; - signers[i] = vm.addr(signerKeys[i]); - vm.assume(signers[i] != address(0)); - } - - MultiOCR3Base.OCRConfigArgs[] memory ocrConfigs = new MultiOCR3Base.OCRConfigArgs[](1); - ocrConfigs[0] = MultiOCR3Base.OCRConfigArgs({ - ocrPluginType: 3, - configDigest: s_configDigest1, - F: F, - isSignatureVerificationEnabled: true, - signers: signers, - transmitters: transmitters - }); - s_multiOCR3.setOCR3Configs(ocrConfigs); - s_multiOCR3.setTransmitOcrPluginType(3); - - // Randomise picked transmitter with random offset - vm.startPrank(transmitters[randomAddressOffset % signersLength]); - - bytes32[3] memory reportContext = [s_configDigest1, s_configDigest1, s_configDigest1]; - - // condition: matches signature expectation for transmit - uint8 numSignatures = F + 1; - uint256[] memory pickedSignerKeys = new uint256[](numSignatures); - - // Randomise picked signers with random offset - for (uint256 i; i < numSignatures; ++i) { - pickedSignerKeys[i] = signerKeys[(i + randomAddressOffset) % numSignatures]; - } - - (bytes32[] memory rs, bytes32[] memory ss,, bytes32 rawVs) = - _getSignaturesForDigest(pickedSignerKeys, REPORT, reportContext, numSignatures); - - vm.expectEmit(); - emit MultiOCR3Base.Transmitted(3, s_configDigest1, uint64(uint256(s_configDigest1))); - - vm.resumeGasMetering(); - s_multiOCR3.transmitWithSignatures(reportContext, REPORT, rs, ss, rawVs); - } - - // Reverts - function test_ForkedChain_Revert() public { - bytes32[3] memory reportContext = [s_configDigest1, s_configDigest1, s_configDigest1]; - - (bytes32[] memory rs, bytes32[] memory ss,, bytes32 rawVs) = - _getSignaturesForDigest(s_validSignerKeys, REPORT, reportContext, 2); - - s_multiOCR3.setTransmitOcrPluginType(0); - - uint256 chain1 = block.chainid; - uint256 chain2 = chain1 + 1; - vm.chainId(chain2); - vm.expectRevert(abi.encodeWithSelector(MultiOCR3Base.ForkedChain.selector, chain1, chain2)); - - vm.startPrank(s_validTransmitters[0]); - s_multiOCR3.transmitWithSignatures(reportContext, REPORT, rs, ss, rawVs); - } - - function test_ZeroSignatures_Revert() public { - bytes32[3] memory reportContext = [s_configDigest1, s_configDigest1, s_configDigest1]; - - s_multiOCR3.setTransmitOcrPluginType(0); - - vm.startPrank(s_validTransmitters[0]); - vm.expectRevert(MultiOCR3Base.WrongNumberOfSignatures.selector); - s_multiOCR3.transmitWithSignatures(reportContext, REPORT, new bytes32[](0), new bytes32[](0), bytes32("")); - } - - function test_TooManySignatures_Revert() public { - bytes32[3] memory reportContext = [s_configDigest1, s_configDigest1, s_configDigest1]; - - // 1 signature too many - (bytes32[] memory rs, bytes32[] memory ss,, bytes32 rawVs) = - _getSignaturesForDigest(s_validSignerKeys, REPORT, reportContext, 6); - - s_multiOCR3.setTransmitOcrPluginType(1); - - vm.startPrank(s_validTransmitters[0]); - vm.expectRevert(MultiOCR3Base.WrongNumberOfSignatures.selector); - s_multiOCR3.transmitWithSignatures(reportContext, REPORT, rs, ss, rawVs); - } - - function test_InsufficientSignatures_Revert() public { - bytes32[3] memory reportContext = [s_configDigest1, s_configDigest1, s_configDigest1]; - - // Missing 1 signature for unique report - (bytes32[] memory rs, bytes32[] memory ss,, bytes32 rawVs) = - _getSignaturesForDigest(s_validSignerKeys, REPORT, reportContext, 4); - - s_multiOCR3.setTransmitOcrPluginType(1); - - vm.startPrank(s_validTransmitters[0]); - vm.expectRevert(MultiOCR3Base.WrongNumberOfSignatures.selector); - s_multiOCR3.transmitWithSignatures(reportContext, REPORT, rs, ss, rawVs); - } - - function test_ConfigDigestMismatch_Revert() public { - bytes32 configDigest; - bytes32[3] memory reportContext = [configDigest, configDigest, configDigest]; - - (,,, bytes32 rawVs) = _getSignaturesForDigest(s_validSignerKeys, REPORT, reportContext, 2); - - s_multiOCR3.setTransmitOcrPluginType(0); - - vm.expectRevert(abi.encodeWithSelector(MultiOCR3Base.ConfigDigestMismatch.selector, s_configDigest1, configDigest)); - s_multiOCR3.transmitWithSignatures(reportContext, REPORT, new bytes32[](0), new bytes32[](0), rawVs); - } - - function test_SignatureOutOfRegistration_Revert() public { - bytes32[3] memory reportContext = [s_configDigest1, s_configDigest1, s_configDigest1]; - - bytes32[] memory rs = new bytes32[](2); - bytes32[] memory ss = new bytes32[](1); - - s_multiOCR3.setTransmitOcrPluginType(0); - - vm.startPrank(s_validTransmitters[0]); - vm.expectRevert(MultiOCR3Base.SignaturesOutOfRegistration.selector); - s_multiOCR3.transmitWithSignatures(reportContext, REPORT, rs, ss, bytes32("")); - } - - function test_UnAuthorizedTransmitter_Revert() public { - bytes32[3] memory reportContext = [s_configDigest1, s_configDigest1, s_configDigest1]; - bytes32[] memory rs = new bytes32[](2); - bytes32[] memory ss = new bytes32[](2); - - s_multiOCR3.setTransmitOcrPluginType(0); - - vm.expectRevert(MultiOCR3Base.UnauthorizedTransmitter.selector); - s_multiOCR3.transmitWithSignatures(reportContext, REPORT, rs, ss, bytes32("")); - } - - function test_NonUniqueSignature_Revert() public { - bytes32[3] memory reportContext = [s_configDigest1, s_configDigest1, s_configDigest1]; - - (bytes32[] memory rs, bytes32[] memory ss, uint8[] memory vs, bytes32 rawVs) = - _getSignaturesForDigest(s_validSignerKeys, REPORT, reportContext, 2); - - rs[1] = rs[0]; - ss[1] = ss[0]; - // Need to reset the rawVs to be valid - rawVs = bytes32(bytes1(vs[0] - 27)) | (bytes32(bytes1(vs[0] - 27)) >> 8); - - s_multiOCR3.setTransmitOcrPluginType(0); - - vm.startPrank(s_validTransmitters[0]); - vm.expectRevert(MultiOCR3Base.NonUniqueSignatures.selector); - s_multiOCR3.transmitWithSignatures(reportContext, REPORT, rs, ss, rawVs); - } - - function test_UnauthorizedSigner_Revert() public { - bytes32[3] memory reportContext = [s_configDigest1, s_configDigest1, s_configDigest1]; - - (bytes32[] memory rs, bytes32[] memory ss,, bytes32 rawVs) = - _getSignaturesForDigest(s_validSignerKeys, REPORT, reportContext, 2); - - rs[0] = s_configDigest1; - ss = rs; - - s_multiOCR3.setTransmitOcrPluginType(0); - - vm.startPrank(s_validTransmitters[0]); - vm.expectRevert(MultiOCR3Base.UnauthorizedSigner.selector); - s_multiOCR3.transmitWithSignatures(reportContext, REPORT, rs, ss, rawVs); - } - - function test_UnconfiguredPlugin_Revert() public { - bytes32 configDigest; - bytes32[3] memory reportContext = [configDigest, configDigest, configDigest]; - - s_multiOCR3.setTransmitOcrPluginType(42); - - vm.expectRevert(MultiOCR3Base.UnauthorizedTransmitter.selector); - s_multiOCR3.transmitWithoutSignatures(reportContext, REPORT); - } - - function test_TransmitWithLessCalldataArgs_Revert() public { - bytes32[3] memory reportContext = [s_configDigest1, s_configDigest1, s_configDigest1]; - - s_multiOCR3.setTransmitOcrPluginType(0); - - // The transmit should fail, since we are trying to transmit without signatures when signatures are enabled - vm.startPrank(s_validTransmitters[1]); - - // report length + function selector + report length + abiencoded location of report value + report context words - uint256 receivedLength = REPORT.length + 4 + 5 * 32; - vm.expectRevert( - abi.encodeWithSelector( - MultiOCR3Base.WrongMessageLength.selector, - // Expecting inclusion of signature constant length components - receivedLength + 5 * 32, - receivedLength - ) - ); - s_multiOCR3.transmitWithoutSignatures(reportContext, REPORT); - } - - function test_TransmitWithExtraCalldataArgs_Revert() public { - bytes32[3] memory reportContext = [s_configDigest1, s_configDigest1, s_configDigest1]; - bytes32[] memory rs = new bytes32[](2); - bytes32[] memory ss = new bytes32[](2); - - s_multiOCR3.setTransmitOcrPluginType(2); - - // The transmit should fail, since we are trying to transmit with signatures when signatures are disabled - vm.startPrank(s_validTransmitters[1]); - - // dynamic length + function selector + report length + abiencoded location of report value + report context words - // rawVs value, lengths of rs, ss, and start locations of rs & ss -> 5 words - uint256 receivedLength = REPORT.length + 4 + (5 * 32) + (5 * 32) + (2 * 32) + (2 * 32); - vm.expectRevert( - abi.encodeWithSelector( - MultiOCR3Base.WrongMessageLength.selector, - // Expecting exclusion of signature constant length components and rs, ss words - receivedLength - (5 * 32) - (4 * 32), - receivedLength - ) - ); - s_multiOCR3.transmitWithSignatures(reportContext, REPORT, rs, ss, bytes32("")); - } -} - contract MultiOCR3Base_setOCR3Configs is MultiOCR3BaseSetup { function test_SetConfigsZeroInput_Success() public { vm.recordLogs(); diff --git a/contracts/src/v0.8/ccip/test/ocr/MultiOCR3Base/MultiOCR3Base.transmit.t.sol b/contracts/src/v0.8/ccip/test/ocr/MultiOCR3Base/MultiOCR3Base.transmit.t.sol new file mode 100644 index 00000000000..3d619dfa116 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/ocr/MultiOCR3Base/MultiOCR3Base.transmit.t.sol @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {MultiOCR3Base} from "../../../ocr/MultiOCR3Base.sol"; +import {MultiOCR3BaseSetup} from "./MultiOCR3BaseSetup.t.sol"; + +contract MultiOCR3Base_transmit is MultiOCR3BaseSetup { + bytes32 internal s_configDigest1; + bytes32 internal s_configDigest2; + bytes32 internal s_configDigest3; + + function setUp() public virtual override { + super.setUp(); + + s_configDigest1 = _getBasicConfigDigest(1, s_validSigners, s_validTransmitters); + s_configDigest2 = _getBasicConfigDigest(1, s_validSigners, s_validTransmitters); + s_configDigest3 = _getBasicConfigDigest(2, s_emptySigners, s_validTransmitters); + + MultiOCR3Base.OCRConfigArgs[] memory ocrConfigs = new MultiOCR3Base.OCRConfigArgs[](3); + ocrConfigs[0] = MultiOCR3Base.OCRConfigArgs({ + ocrPluginType: 0, + configDigest: s_configDigest1, + F: 1, + isSignatureVerificationEnabled: true, + signers: s_validSigners, + transmitters: s_validTransmitters + }); + ocrConfigs[1] = MultiOCR3Base.OCRConfigArgs({ + ocrPluginType: 1, + configDigest: s_configDigest2, + F: 2, + isSignatureVerificationEnabled: true, + signers: s_validSigners, + transmitters: s_validTransmitters + }); + ocrConfigs[2] = MultiOCR3Base.OCRConfigArgs({ + ocrPluginType: 2, + configDigest: s_configDigest3, + F: 1, + isSignatureVerificationEnabled: false, + signers: s_emptySigners, + transmitters: s_validTransmitters + }); + + s_multiOCR3.setOCR3Configs(ocrConfigs); + } + + function test_TransmitSigners_gas_Success() public { + vm.pauseGasMetering(); + bytes32[3] memory reportContext = [s_configDigest1, s_configDigest1, s_configDigest1]; + + // F = 2, need 2 signatures + (bytes32[] memory rs, bytes32[] memory ss,, bytes32 rawVs) = + _getSignaturesForDigest(s_validSignerKeys, REPORT, reportContext, 2); + + s_multiOCR3.setTransmitOcrPluginType(0); + + vm.expectEmit(); + emit MultiOCR3Base.Transmitted(0, s_configDigest1, uint64(uint256(s_configDigest1))); + + vm.startPrank(s_validTransmitters[1]); + vm.resumeGasMetering(); + s_multiOCR3.transmitWithSignatures(reportContext, REPORT, rs, ss, rawVs); + } + + function test_TransmitWithoutSignatureVerification_gas_Success() public { + vm.pauseGasMetering(); + bytes32[3] memory reportContext = [s_configDigest3, s_configDigest3, s_configDigest3]; + + s_multiOCR3.setTransmitOcrPluginType(2); + + vm.expectEmit(); + emit MultiOCR3Base.Transmitted(2, s_configDigest3, uint64(uint256(s_configDigest3))); + + vm.startPrank(s_validTransmitters[0]); + vm.resumeGasMetering(); + s_multiOCR3.transmitWithoutSignatures(reportContext, REPORT); + } + + function test_Fuzz_TransmitSignersWithSignatures_Success(uint8 F, uint64 randomAddressOffset) public { + vm.pauseGasMetering(); + + F = uint8(bound(F, 1, 3)); + + // condition: signers.length > 3F + uint8 signersLength = 3 * F + 1; + address[] memory signers = new address[](signersLength); + address[] memory transmitters = new address[](signersLength); + uint256[] memory signerKeys = new uint256[](signersLength); + + // Force addresses to be unique (with a random offset for broader testing) + for (uint160 i = 0; i < signersLength; ++i) { + transmitters[i] = vm.addr(PRIVATE0 + randomAddressOffset + i); + // condition: non-zero oracle address + vm.assume(transmitters[i] != address(0)); + + // condition: non-repeating addresses (no clashes with transmitters) + signerKeys[i] = PRIVATE0 + randomAddressOffset + i + signersLength; + signers[i] = vm.addr(signerKeys[i]); + vm.assume(signers[i] != address(0)); + } + + MultiOCR3Base.OCRConfigArgs[] memory ocrConfigs = new MultiOCR3Base.OCRConfigArgs[](1); + ocrConfigs[0] = MultiOCR3Base.OCRConfigArgs({ + ocrPluginType: 3, + configDigest: s_configDigest1, + F: F, + isSignatureVerificationEnabled: true, + signers: signers, + transmitters: transmitters + }); + s_multiOCR3.setOCR3Configs(ocrConfigs); + s_multiOCR3.setTransmitOcrPluginType(3); + + // Randomise picked transmitter with random offset + vm.startPrank(transmitters[randomAddressOffset % signersLength]); + + bytes32[3] memory reportContext = [s_configDigest1, s_configDigest1, s_configDigest1]; + + // condition: matches signature expectation for transmit + uint8 numSignatures = F + 1; + uint256[] memory pickedSignerKeys = new uint256[](numSignatures); + + // Randomise picked signers with random offset + for (uint256 i; i < numSignatures; ++i) { + pickedSignerKeys[i] = signerKeys[(i + randomAddressOffset) % numSignatures]; + } + + (bytes32[] memory rs, bytes32[] memory ss,, bytes32 rawVs) = + _getSignaturesForDigest(pickedSignerKeys, REPORT, reportContext, numSignatures); + + vm.expectEmit(); + emit MultiOCR3Base.Transmitted(3, s_configDigest1, uint64(uint256(s_configDigest1))); + + vm.resumeGasMetering(); + s_multiOCR3.transmitWithSignatures(reportContext, REPORT, rs, ss, rawVs); + } + + // Reverts + function test_ForkedChain_Revert() public { + bytes32[3] memory reportContext = [s_configDigest1, s_configDigest1, s_configDigest1]; + + (bytes32[] memory rs, bytes32[] memory ss,, bytes32 rawVs) = + _getSignaturesForDigest(s_validSignerKeys, REPORT, reportContext, 2); + + s_multiOCR3.setTransmitOcrPluginType(0); + + uint256 chain1 = block.chainid; + uint256 chain2 = chain1 + 1; + vm.chainId(chain2); + vm.expectRevert(abi.encodeWithSelector(MultiOCR3Base.ForkedChain.selector, chain1, chain2)); + + vm.startPrank(s_validTransmitters[0]); + s_multiOCR3.transmitWithSignatures(reportContext, REPORT, rs, ss, rawVs); + } + + function test_ZeroSignatures_Revert() public { + bytes32[3] memory reportContext = [s_configDigest1, s_configDigest1, s_configDigest1]; + + s_multiOCR3.setTransmitOcrPluginType(0); + + vm.startPrank(s_validTransmitters[0]); + vm.expectRevert(MultiOCR3Base.WrongNumberOfSignatures.selector); + s_multiOCR3.transmitWithSignatures(reportContext, REPORT, new bytes32[](0), new bytes32[](0), bytes32("")); + } + + function test_TooManySignatures_Revert() public { + bytes32[3] memory reportContext = [s_configDigest1, s_configDigest1, s_configDigest1]; + + // 1 signature too many + (bytes32[] memory rs, bytes32[] memory ss,, bytes32 rawVs) = + _getSignaturesForDigest(s_validSignerKeys, REPORT, reportContext, 6); + + s_multiOCR3.setTransmitOcrPluginType(1); + + vm.startPrank(s_validTransmitters[0]); + vm.expectRevert(MultiOCR3Base.WrongNumberOfSignatures.selector); + s_multiOCR3.transmitWithSignatures(reportContext, REPORT, rs, ss, rawVs); + } + + function test_InsufficientSignatures_Revert() public { + bytes32[3] memory reportContext = [s_configDigest1, s_configDigest1, s_configDigest1]; + + // Missing 1 signature for unique report + (bytes32[] memory rs, bytes32[] memory ss,, bytes32 rawVs) = + _getSignaturesForDigest(s_validSignerKeys, REPORT, reportContext, 4); + + s_multiOCR3.setTransmitOcrPluginType(1); + + vm.startPrank(s_validTransmitters[0]); + vm.expectRevert(MultiOCR3Base.WrongNumberOfSignatures.selector); + s_multiOCR3.transmitWithSignatures(reportContext, REPORT, rs, ss, rawVs); + } + + function test_ConfigDigestMismatch_Revert() public { + bytes32 configDigest; + bytes32[3] memory reportContext = [configDigest, configDigest, configDigest]; + + (,,, bytes32 rawVs) = _getSignaturesForDigest(s_validSignerKeys, REPORT, reportContext, 2); + + s_multiOCR3.setTransmitOcrPluginType(0); + + vm.expectRevert(abi.encodeWithSelector(MultiOCR3Base.ConfigDigestMismatch.selector, s_configDigest1, configDigest)); + s_multiOCR3.transmitWithSignatures(reportContext, REPORT, new bytes32[](0), new bytes32[](0), rawVs); + } + + function test_SignatureOutOfRegistration_Revert() public { + bytes32[3] memory reportContext = [s_configDigest1, s_configDigest1, s_configDigest1]; + + bytes32[] memory rs = new bytes32[](2); + bytes32[] memory ss = new bytes32[](1); + + s_multiOCR3.setTransmitOcrPluginType(0); + + vm.startPrank(s_validTransmitters[0]); + vm.expectRevert(MultiOCR3Base.SignaturesOutOfRegistration.selector); + s_multiOCR3.transmitWithSignatures(reportContext, REPORT, rs, ss, bytes32("")); + } + + function test_UnAuthorizedTransmitter_Revert() public { + bytes32[3] memory reportContext = [s_configDigest1, s_configDigest1, s_configDigest1]; + bytes32[] memory rs = new bytes32[](2); + bytes32[] memory ss = new bytes32[](2); + + s_multiOCR3.setTransmitOcrPluginType(0); + + vm.expectRevert(MultiOCR3Base.UnauthorizedTransmitter.selector); + s_multiOCR3.transmitWithSignatures(reportContext, REPORT, rs, ss, bytes32("")); + } + + function test_NonUniqueSignature_Revert() public { + bytes32[3] memory reportContext = [s_configDigest1, s_configDigest1, s_configDigest1]; + + (bytes32[] memory rs, bytes32[] memory ss, uint8[] memory vs, bytes32 rawVs) = + _getSignaturesForDigest(s_validSignerKeys, REPORT, reportContext, 2); + + rs[1] = rs[0]; + ss[1] = ss[0]; + // Need to reset the rawVs to be valid + rawVs = bytes32(bytes1(vs[0] - 27)) | (bytes32(bytes1(vs[0] - 27)) >> 8); + + s_multiOCR3.setTransmitOcrPluginType(0); + + vm.startPrank(s_validTransmitters[0]); + vm.expectRevert(MultiOCR3Base.NonUniqueSignatures.selector); + s_multiOCR3.transmitWithSignatures(reportContext, REPORT, rs, ss, rawVs); + } + + function test_UnauthorizedSigner_Revert() public { + bytes32[3] memory reportContext = [s_configDigest1, s_configDigest1, s_configDigest1]; + + (bytes32[] memory rs, bytes32[] memory ss,, bytes32 rawVs) = + _getSignaturesForDigest(s_validSignerKeys, REPORT, reportContext, 2); + + rs[0] = s_configDigest1; + ss = rs; + + s_multiOCR3.setTransmitOcrPluginType(0); + + vm.startPrank(s_validTransmitters[0]); + vm.expectRevert(MultiOCR3Base.UnauthorizedSigner.selector); + s_multiOCR3.transmitWithSignatures(reportContext, REPORT, rs, ss, rawVs); + } + + function test_UnconfiguredPlugin_Revert() public { + bytes32 configDigest; + bytes32[3] memory reportContext = [configDigest, configDigest, configDigest]; + + s_multiOCR3.setTransmitOcrPluginType(42); + + vm.expectRevert(MultiOCR3Base.UnauthorizedTransmitter.selector); + s_multiOCR3.transmitWithoutSignatures(reportContext, REPORT); + } + + function test_TransmitWithLessCalldataArgs_Revert() public { + bytes32[3] memory reportContext = [s_configDigest1, s_configDigest1, s_configDigest1]; + + s_multiOCR3.setTransmitOcrPluginType(0); + + // The transmit should fail, since we are trying to transmit without signatures when signatures are enabled + vm.startPrank(s_validTransmitters[1]); + + // report length + function selector + report length + abiencoded location of report value + report context words + uint256 receivedLength = REPORT.length + 4 + 5 * 32; + vm.expectRevert( + abi.encodeWithSelector( + MultiOCR3Base.WrongMessageLength.selector, + // Expecting inclusion of signature constant length components + receivedLength + 5 * 32, + receivedLength + ) + ); + s_multiOCR3.transmitWithoutSignatures(reportContext, REPORT); + } + + function test_TransmitWithExtraCalldataArgs_Revert() public { + bytes32[3] memory reportContext = [s_configDigest1, s_configDigest1, s_configDigest1]; + bytes32[] memory rs = new bytes32[](2); + bytes32[] memory ss = new bytes32[](2); + + s_multiOCR3.setTransmitOcrPluginType(2); + + // The transmit should fail, since we are trying to transmit with signatures when signatures are disabled + vm.startPrank(s_validTransmitters[1]); + + // dynamic length + function selector + report length + abiencoded location of report value + report context words + // rawVs value, lengths of rs, ss, and start locations of rs & ss -> 5 words + uint256 receivedLength = REPORT.length + 4 + (5 * 32) + (5 * 32) + (2 * 32) + (2 * 32); + vm.expectRevert( + abi.encodeWithSelector( + MultiOCR3Base.WrongMessageLength.selector, + // Expecting exclusion of signature constant length components and rs, ss words + receivedLength - (5 * 32) - (4 * 32), + receivedLength + ) + ); + s_multiOCR3.transmitWithSignatures(reportContext, REPORT, rs, ss, bytes32("")); + } +} diff --git a/contracts/src/v0.8/ccip/test/ocr/MultiOCR3BaseSetup.t.sol b/contracts/src/v0.8/ccip/test/ocr/MultiOCR3Base/MultiOCR3BaseSetup.t.sol similarity index 95% rename from contracts/src/v0.8/ccip/test/ocr/MultiOCR3BaseSetup.t.sol rename to contracts/src/v0.8/ccip/test/ocr/MultiOCR3Base/MultiOCR3BaseSetup.t.sol index 9cfddf0dd5c..f949017d588 100644 --- a/contracts/src/v0.8/ccip/test/ocr/MultiOCR3BaseSetup.t.sol +++ b/contracts/src/v0.8/ccip/test/ocr/MultiOCR3Base/MultiOCR3BaseSetup.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.24; -import {MultiOCR3Base} from "../../ocr/MultiOCR3Base.sol"; -import {BaseTest} from "../BaseTest.t.sol"; -import {MultiOCR3Helper} from "../helpers/MultiOCR3Helper.sol"; +import {MultiOCR3Base} from "../../../ocr/MultiOCR3Base.sol"; +import {BaseTest} from "../../BaseTest.t.sol"; +import {MultiOCR3Helper} from "../../helpers/MultiOCR3Helper.sol"; contract MultiOCR3BaseSetup is BaseTest { // Signer private keys used for these test diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.afterOC3ConfigSet.t.sol b/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.afterOC3ConfigSet.t.sol similarity index 100% rename from contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.afterOC3ConfigSet.t.sol rename to contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.afterOC3ConfigSet.t.sol diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.applySourceChainConfigUpdates.t.sol b/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.applySourceChainConfigUpdates.t.sol similarity index 100% rename from contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.applySourceChainConfigUpdates.t.sol rename to contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.applySourceChainConfigUpdates.t.sol diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.batchExecute.t.sol b/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.batchExecute.t.sol similarity index 100% rename from contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.batchExecute.t.sol rename to contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.batchExecute.t.sol diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.ccipReceive.t.sol b/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.ccipReceive.t.sol similarity index 100% rename from contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.ccipReceive.t.sol rename to contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.ccipReceive.t.sol diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.commit.t.sol b/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.commit.t.sol similarity index 100% rename from contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.commit.t.sol rename to contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.commit.t.sol diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.constructor.t.sol b/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.constructor.t.sol similarity index 100% rename from contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.constructor.t.sol rename to contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.constructor.t.sol diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.execute.t.sol b/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.execute.t.sol similarity index 100% rename from contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.execute.t.sol rename to contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.execute.t.sol diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.executeSingleMessage.t.sol b/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.executeSingleMessage.t.sol similarity index 100% rename from contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.executeSingleMessage.t.sol rename to contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.executeSingleMessage.t.sol diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.executeSingleReport.t.sol b/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.executeSingleReport.t.sol similarity index 100% rename from contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.executeSingleReport.t.sol rename to contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.executeSingleReport.t.sol diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.getExecutionState.t.sol b/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.getExecutionState.t.sol similarity index 100% rename from contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.getExecutionState.t.sol rename to contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.getExecutionState.t.sol diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.manuallyExecute.t.sol b/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.manuallyExecute.t.sol similarity index 100% rename from contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.manuallyExecute.t.sol rename to contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.manuallyExecute.t.sol diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.releaseOrMintSingleToken.t.sol b/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.releaseOrMintSingleToken.t.sol similarity index 100% rename from contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.releaseOrMintSingleToken.t.sol rename to contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.releaseOrMintSingleToken.t.sol diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.releaseOrMintTokens.t.sol b/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.releaseOrMintTokens.t.sol similarity index 100% rename from contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.releaseOrMintTokens.t.sol rename to contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.releaseOrMintTokens.t.sol diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.setDynamicConfig.t.sol b/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.setDynamicConfig.t.sol similarity index 100% rename from contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.setDynamicConfig.t.sol rename to contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.setDynamicConfig.t.sol diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.trialExecute.t.sol b/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.trialExecute.t.sol similarity index 100% rename from contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.trialExecute.t.sol rename to contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRamp.trialExecute.t.sol diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRampSetup.t.sol b/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRampSetup.t.sol similarity index 99% rename from contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRampSetup.t.sol rename to contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRampSetup.t.sol index 68b32390c0a..8e33f05c61d 100644 --- a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRampSetup.t.sol +++ b/contracts/src/v0.8/ccip/test/offRamp/OffRamp/OffRampSetup.t.sol @@ -16,7 +16,7 @@ import {MaybeRevertingBurnMintTokenPool} from "../../helpers/MaybeRevertingBurnM import {MessageInterceptorHelper} from "../../helpers/MessageInterceptorHelper.sol"; import {OffRampHelper} from "../../helpers/OffRampHelper.sol"; import {MaybeRevertMessageReceiver} from "../../helpers/receivers/MaybeRevertMessageReceiver.sol"; -import {MultiOCR3BaseSetup} from "../../ocr/MultiOCR3BaseSetup.t.sol"; +import {MultiOCR3BaseSetup} from "../../ocr/MultiOCR3Base/MultiOCR3BaseSetup.t.sol"; import {Vm} from "forge-std/Test.sol"; contract OffRampSetup is FeeQuoterSetup, MultiOCR3BaseSetup { diff --git a/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.applyDestChainConfigUpdates.t.sol b/contracts/src/v0.8/ccip/test/onRamp/OnRamp/OnRamp.applyDestChainConfigUpdates.t.sol similarity index 100% rename from contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.applyDestChainConfigUpdates.t.sol rename to contracts/src/v0.8/ccip/test/onRamp/OnRamp/OnRamp.applyDestChainConfigUpdates.t.sol diff --git a/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.constructor.t.sol b/contracts/src/v0.8/ccip/test/onRamp/OnRamp/OnRamp.constructor.t.sol similarity index 100% rename from contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.constructor.t.sol rename to contracts/src/v0.8/ccip/test/onRamp/OnRamp/OnRamp.constructor.t.sol diff --git a/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.forwardFromRouter.t.sol b/contracts/src/v0.8/ccip/test/onRamp/OnRamp/OnRamp.forwardFromRouter.t.sol similarity index 100% rename from contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.forwardFromRouter.t.sol rename to contracts/src/v0.8/ccip/test/onRamp/OnRamp/OnRamp.forwardFromRouter.t.sol diff --git a/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.getFee.t.sol b/contracts/src/v0.8/ccip/test/onRamp/OnRamp/OnRamp.getFee.t.sol similarity index 100% rename from contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.getFee.t.sol rename to contracts/src/v0.8/ccip/test/onRamp/OnRamp/OnRamp.getFee.t.sol diff --git a/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.getSupportedTokens.t.sol b/contracts/src/v0.8/ccip/test/onRamp/OnRamp/OnRamp.getSupportedTokens.t.sol similarity index 100% rename from contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.getSupportedTokens.t.sol rename to contracts/src/v0.8/ccip/test/onRamp/OnRamp/OnRamp.getSupportedTokens.t.sol diff --git a/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.getTokenPool.t.sol b/contracts/src/v0.8/ccip/test/onRamp/OnRamp/OnRamp.getTokenPool.t.sol similarity index 100% rename from contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.getTokenPool.t.sol rename to contracts/src/v0.8/ccip/test/onRamp/OnRamp/OnRamp.getTokenPool.t.sol diff --git a/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.setDynamicConfig.t.sol b/contracts/src/v0.8/ccip/test/onRamp/OnRamp/OnRamp.setDynamicConfig.t.sol similarity index 100% rename from contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.setDynamicConfig.t.sol rename to contracts/src/v0.8/ccip/test/onRamp/OnRamp/OnRamp.setDynamicConfig.t.sol diff --git a/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.withdrawFeeTokens.t.sol b/contracts/src/v0.8/ccip/test/onRamp/OnRamp/OnRamp.withdrawFeeTokens.t.sol similarity index 100% rename from contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.withdrawFeeTokens.t.sol rename to contracts/src/v0.8/ccip/test/onRamp/OnRamp/OnRamp.withdrawFeeTokens.t.sol diff --git a/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRampSetup.t.sol b/contracts/src/v0.8/ccip/test/onRamp/OnRamp/OnRampSetup.t.sol similarity index 100% rename from contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRampSetup.t.sol rename to contracts/src/v0.8/ccip/test/onRamp/OnRamp/OnRampSetup.t.sol diff --git a/contracts/src/v0.8/ccip/test/pools/BurnMintTokenPool/BurnMintSetup.t.sol b/contracts/src/v0.8/ccip/test/pools/BurnMintTokenPool/BurnMintSetup.t.sol index 7b3d875de4c..767ebfc9bfc 100644 --- a/contracts/src/v0.8/ccip/test/pools/BurnMintTokenPool/BurnMintSetup.t.sol +++ b/contracts/src/v0.8/ccip/test/pools/BurnMintTokenPool/BurnMintSetup.t.sol @@ -5,7 +5,7 @@ import {BurnMintERC677} from "../../../../shared/token/ERC677/BurnMintERC677.sol import {Router} from "../../../Router.sol"; import {BurnMintTokenPool} from "../../../pools/BurnMintTokenPool.sol"; import {TokenPool} from "../../../pools/TokenPool.sol"; -import {RouterSetup} from "../../router/RouterSetup.t.sol"; +import {RouterSetup} from "../../router/Router/RouterSetup.t.sol"; contract BurnMintSetup is RouterSetup { BurnMintERC677 internal s_burnMintERC677; diff --git a/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPoolSetup.t.sol b/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPoolSetup.t.sol index ce1104246dd..fa62df99828 100644 --- a/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPoolSetup.t.sol +++ b/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPoolSetup.t.sol @@ -7,7 +7,7 @@ import {LockReleaseTokenPool} from "../../../pools/LockReleaseTokenPool.sol"; import {TokenPool} from "../../../pools/TokenPool.sol"; import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; -import {RouterSetup} from "../../router/RouterSetup.t.sol"; +import {RouterSetup} from "../../router/Router/RouterSetup.t.sol"; contract LockReleaseTokenPoolSetup is RouterSetup { IERC20 internal s_token; diff --git a/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPoolSetup.t.sol b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPoolSetup.t.sol index e2285c67094..3d97f0c17c3 100644 --- a/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPoolSetup.t.sol +++ b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPoolSetup.t.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.24; import {BurnMintERC677} from "../../../../shared/token/ERC677/BurnMintERC677.sol"; import {TokenPoolHelper} from "../../helpers/TokenPoolHelper.sol"; -import {RouterSetup} from "../../router/RouterSetup.t.sol"; +import {RouterSetup} from "../../router/Router/RouterSetup.t.sol"; import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; diff --git a/contracts/src/v0.8/ccip/test/rateLimiter/MultiAggregateRateLimiter.t.sol b/contracts/src/v0.8/ccip/test/rateLimiter/MultiAggregateRateLimiter.t.sol deleted file mode 100644 index 893656c9c8b..00000000000 --- a/contracts/src/v0.8/ccip/test/rateLimiter/MultiAggregateRateLimiter.t.sol +++ /dev/null @@ -1,1271 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.24; - -import {AuthorizedCallers} from "../../../shared/access/AuthorizedCallers.sol"; -import {Ownable2Step} from "../../../shared/access/Ownable2Step.sol"; -import {MultiAggregateRateLimiter} from "../../MultiAggregateRateLimiter.sol"; -import {Client} from "../../libraries/Client.sol"; -import {Internal} from "../../libraries/Internal.sol"; -import {RateLimiter} from "../../libraries/RateLimiter.sol"; -import {BaseTest} from "../BaseTest.t.sol"; - -import {FeeQuoterSetup} from "../feeQuoter/FeeQuoterSetup.t.sol"; -import {MultiAggregateRateLimiterHelper} from "../helpers/MultiAggregateRateLimiterHelper.sol"; -import {stdError} from "forge-std/Test.sol"; -import {Vm} from "forge-std/Vm.sol"; - -contract MultiAggregateRateLimiterSetup is BaseTest, FeeQuoterSetup { - MultiAggregateRateLimiterHelper internal s_rateLimiter; - - address internal constant TOKEN = 0x21118E64E1fB0c487F25Dd6d3601FF6af8D32E4e; - uint224 internal constant TOKEN_PRICE = 4e18; - - uint64 internal constant CHAIN_SELECTOR_1 = 5009297550715157269; - uint64 internal constant CHAIN_SELECTOR_2 = 4949039107694359620; - - RateLimiter.Config internal s_rateLimiterConfig1 = RateLimiter.Config({isEnabled: true, rate: 5, capacity: 100}); - RateLimiter.Config internal s_rateLimiterConfig2 = RateLimiter.Config({isEnabled: true, rate: 10, capacity: 200}); - - address internal constant MOCK_OFFRAMP = address(1111); - address internal constant MOCK_ONRAMP = address(1112); - - address[] internal s_authorizedCallers; - - function setUp() public virtual override(BaseTest, FeeQuoterSetup) { - BaseTest.setUp(); - FeeQuoterSetup.setUp(); - - Internal.PriceUpdates memory priceUpdates = _getSingleTokenPriceUpdateStruct(TOKEN, TOKEN_PRICE); - s_feeQuoter.updatePrices(priceUpdates); - - MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = - new MultiAggregateRateLimiter.RateLimiterConfigArgs[](4); - configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ - remoteChainSelector: CHAIN_SELECTOR_1, - isOutboundLane: false, - rateLimiterConfig: s_rateLimiterConfig1 - }); - configUpdates[1] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ - remoteChainSelector: CHAIN_SELECTOR_2, - isOutboundLane: false, - rateLimiterConfig: s_rateLimiterConfig2 - }); - configUpdates[2] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ - remoteChainSelector: CHAIN_SELECTOR_1, - isOutboundLane: true, - rateLimiterConfig: s_rateLimiterConfig1 - }); - configUpdates[3] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ - remoteChainSelector: CHAIN_SELECTOR_2, - isOutboundLane: true, - rateLimiterConfig: s_rateLimiterConfig2 - }); - - s_authorizedCallers = new address[](2); - s_authorizedCallers[0] = MOCK_OFFRAMP; - s_authorizedCallers[1] = MOCK_ONRAMP; - - s_rateLimiter = new MultiAggregateRateLimiterHelper(address(s_feeQuoter), s_authorizedCallers); - s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); - } - - function _assertConfigWithTokenBucketEquality( - RateLimiter.Config memory config, - RateLimiter.TokenBucket memory tokenBucket - ) internal pure { - assertEq(config.rate, tokenBucket.rate); - assertEq(config.capacity, tokenBucket.capacity); - assertEq(config.capacity, tokenBucket.tokens); - assertEq(config.isEnabled, tokenBucket.isEnabled); - } - - function _assertTokenBucketEquality( - RateLimiter.TokenBucket memory tokenBucketA, - RateLimiter.TokenBucket memory tokenBucketB - ) internal pure { - assertEq(tokenBucketA.rate, tokenBucketB.rate); - assertEq(tokenBucketA.capacity, tokenBucketB.capacity); - assertEq(tokenBucketA.tokens, tokenBucketB.tokens); - assertEq(tokenBucketA.isEnabled, tokenBucketB.isEnabled); - } - - function _generateAny2EVMMessage( - uint64 sourceChainSelector, - Client.EVMTokenAmount[] memory tokenAmounts - ) internal pure returns (Client.Any2EVMMessage memory) { - return Client.Any2EVMMessage({ - messageId: keccak256(bytes("messageId")), - sourceChainSelector: sourceChainSelector, - sender: abi.encode(OWNER), - data: abi.encode(0), - destTokenAmounts: tokenAmounts - }); - } - - function _generateAny2EVMMessageNoTokens( - uint64 sourceChainSelector - ) internal pure returns (Client.Any2EVMMessage memory) { - return _generateAny2EVMMessage(sourceChainSelector, new Client.EVMTokenAmount[](0)); - } -} - -contract MultiAggregateRateLimiter_constructor is MultiAggregateRateLimiterSetup { - function test_ConstructorNoAuthorizedCallers_Success() public { - address[] memory authorizedCallers = new address[](0); - - vm.recordLogs(); - s_rateLimiter = new MultiAggregateRateLimiterHelper(address(s_feeQuoter), authorizedCallers); - - // FeeQuoterSet - Vm.Log[] memory logEntries = vm.getRecordedLogs(); - assertEq(logEntries.length, 1); - - assertEq(OWNER, s_rateLimiter.owner()); - assertEq(address(s_feeQuoter), s_rateLimiter.getFeeQuoter()); - } - - function test_Constructor_Success() public { - address[] memory authorizedCallers = new address[](2); - authorizedCallers[0] = MOCK_OFFRAMP; - authorizedCallers[1] = MOCK_ONRAMP; - - vm.expectEmit(); - emit MultiAggregateRateLimiter.FeeQuoterSet(address(s_feeQuoter)); - - s_rateLimiter = new MultiAggregateRateLimiterHelper(address(s_feeQuoter), authorizedCallers); - - assertEq(OWNER, s_rateLimiter.owner()); - assertEq(address(s_feeQuoter), s_rateLimiter.getFeeQuoter()); - assertEq(s_rateLimiter.typeAndVersion(), "MultiAggregateRateLimiter 1.6.0-dev"); - } -} - -contract MultiAggregateRateLimiter_setFeeQuoter is MultiAggregateRateLimiterSetup { - function test_Owner_Success() public { - address newAddress = address(42); - - vm.expectEmit(); - emit MultiAggregateRateLimiter.FeeQuoterSet(newAddress); - - s_rateLimiter.setFeeQuoter(newAddress); - assertEq(newAddress, s_rateLimiter.getFeeQuoter()); - } - - // Reverts - - function test_OnlyOwner_Revert() public { - vm.startPrank(STRANGER); - vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); - - s_rateLimiter.setFeeQuoter(STRANGER); - } - - function test_ZeroAddress_Revert() public { - vm.expectRevert(AuthorizedCallers.ZeroAddressNotAllowed.selector); - s_rateLimiter.setFeeQuoter(address(0)); - } -} - -contract MultiAggregateRateLimiter_getTokenBucket is MultiAggregateRateLimiterSetup { - function test_GetTokenBucket_Success() public view { - RateLimiter.TokenBucket memory bucketInbound = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, false); - _assertConfigWithTokenBucketEquality(s_rateLimiterConfig1, bucketInbound); - assertEq(BLOCK_TIME, bucketInbound.lastUpdated); - - RateLimiter.TokenBucket memory bucketOutbound = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, true); - _assertConfigWithTokenBucketEquality(s_rateLimiterConfig1, bucketOutbound); - assertEq(BLOCK_TIME, bucketOutbound.lastUpdated); - } - - function test_Refill_Success() public { - s_rateLimiterConfig1.capacity = s_rateLimiterConfig1.capacity * 2; - - MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = - new MultiAggregateRateLimiter.RateLimiterConfigArgs[](1); - configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ - remoteChainSelector: CHAIN_SELECTOR_1, - isOutboundLane: false, - rateLimiterConfig: s_rateLimiterConfig1 - }); - - s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); - - RateLimiter.TokenBucket memory bucket = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, false); - - assertEq(s_rateLimiterConfig1.rate, bucket.rate); - assertEq(s_rateLimiterConfig1.capacity, bucket.capacity); - assertEq(s_rateLimiterConfig1.capacity / 2, bucket.tokens); - assertEq(BLOCK_TIME, bucket.lastUpdated); - - uint256 warpTime = 4; - vm.warp(BLOCK_TIME + warpTime); - - bucket = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, false); - - assertEq(s_rateLimiterConfig1.rate, bucket.rate); - assertEq(s_rateLimiterConfig1.capacity, bucket.capacity); - assertEq(s_rateLimiterConfig1.capacity / 2 + warpTime * s_rateLimiterConfig1.rate, bucket.tokens); - assertEq(BLOCK_TIME + warpTime, bucket.lastUpdated); - - vm.warp(BLOCK_TIME + warpTime * 100); - - // Bucket overflow - bucket = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, false); - assertEq(s_rateLimiterConfig1.capacity, bucket.tokens); - } - - // Reverts - - function test_TimeUnderflow_Revert() public { - vm.warp(BLOCK_TIME - 1); - - vm.expectRevert(stdError.arithmeticError); - s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, false); - } -} - -contract MultiAggregateRateLimiter_applyRateLimiterConfigUpdates is MultiAggregateRateLimiterSetup { - function test_ZeroConfigs_Success() public { - MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = - new MultiAggregateRateLimiter.RateLimiterConfigArgs[](0); - - vm.recordLogs(); - s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); - - Vm.Log[] memory logEntries = vm.getRecordedLogs(); - assertEq(logEntries.length, 0); - } - - function test_SingleConfig_Success() public { - MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = - new MultiAggregateRateLimiter.RateLimiterConfigArgs[](1); - configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ - remoteChainSelector: CHAIN_SELECTOR_1 + 1, - isOutboundLane: false, - rateLimiterConfig: s_rateLimiterConfig1 - }); - - vm.expectEmit(); - emit MultiAggregateRateLimiter.RateLimiterConfigUpdated( - configUpdates[0].remoteChainSelector, false, configUpdates[0].rateLimiterConfig - ); - - vm.recordLogs(); - s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); - - Vm.Log[] memory logEntries = vm.getRecordedLogs(); - assertEq(logEntries.length, 1); - - RateLimiter.TokenBucket memory bucket1 = - s_rateLimiter.currentRateLimiterState(configUpdates[0].remoteChainSelector, false); - _assertConfigWithTokenBucketEquality(configUpdates[0].rateLimiterConfig, bucket1); - assertEq(BLOCK_TIME, bucket1.lastUpdated); - } - - function test_SingleConfigOutbound_Success() public { - MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = - new MultiAggregateRateLimiter.RateLimiterConfigArgs[](1); - configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ - remoteChainSelector: CHAIN_SELECTOR_1 + 1, - isOutboundLane: true, - rateLimiterConfig: s_rateLimiterConfig2 - }); - - vm.expectEmit(); - emit MultiAggregateRateLimiter.RateLimiterConfigUpdated( - configUpdates[0].remoteChainSelector, true, configUpdates[0].rateLimiterConfig - ); - - vm.recordLogs(); - s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); - - Vm.Log[] memory logEntries = vm.getRecordedLogs(); - assertEq(logEntries.length, 1); - - RateLimiter.TokenBucket memory bucket1 = - s_rateLimiter.currentRateLimiterState(configUpdates[0].remoteChainSelector, true); - _assertConfigWithTokenBucketEquality(configUpdates[0].rateLimiterConfig, bucket1); - assertEq(BLOCK_TIME, bucket1.lastUpdated); - } - - function test_MultipleConfigs_Success() public { - MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = - new MultiAggregateRateLimiter.RateLimiterConfigArgs[](5); - - for (uint64 i; i < configUpdates.length; ++i) { - configUpdates[i] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ - remoteChainSelector: CHAIN_SELECTOR_1 + i + 1, - isOutboundLane: i % 2 == 0 ? false : true, - rateLimiterConfig: RateLimiter.Config({isEnabled: true, rate: 5 + i, capacity: 100 + i}) - }); - - vm.expectEmit(); - emit MultiAggregateRateLimiter.RateLimiterConfigUpdated( - configUpdates[i].remoteChainSelector, configUpdates[i].isOutboundLane, configUpdates[i].rateLimiterConfig - ); - } - - vm.recordLogs(); - s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); - - Vm.Log[] memory logEntries = vm.getRecordedLogs(); - assertEq(logEntries.length, configUpdates.length); - - for (uint256 i; i < configUpdates.length; ++i) { - RateLimiter.TokenBucket memory bucket = - s_rateLimiter.currentRateLimiterState(configUpdates[i].remoteChainSelector, configUpdates[i].isOutboundLane); - _assertConfigWithTokenBucketEquality(configUpdates[i].rateLimiterConfig, bucket); - assertEq(BLOCK_TIME, bucket.lastUpdated); - } - } - - function test_MultipleConfigsBothLanes_Success() public { - MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = - new MultiAggregateRateLimiter.RateLimiterConfigArgs[](2); - - for (uint64 i; i < configUpdates.length; ++i) { - configUpdates[i] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ - remoteChainSelector: CHAIN_SELECTOR_1 + 1, - isOutboundLane: i % 2 == 0 ? false : true, - rateLimiterConfig: RateLimiter.Config({isEnabled: true, rate: 5 + i, capacity: 100 + i}) - }); - - vm.expectEmit(); - emit MultiAggregateRateLimiter.RateLimiterConfigUpdated( - configUpdates[i].remoteChainSelector, configUpdates[i].isOutboundLane, configUpdates[i].rateLimiterConfig - ); - } - - vm.recordLogs(); - s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); - - Vm.Log[] memory logEntries = vm.getRecordedLogs(); - assertEq(logEntries.length, configUpdates.length); - - for (uint256 i; i < configUpdates.length; ++i) { - RateLimiter.TokenBucket memory bucket = - s_rateLimiter.currentRateLimiterState(configUpdates[i].remoteChainSelector, configUpdates[i].isOutboundLane); - _assertConfigWithTokenBucketEquality(configUpdates[i].rateLimiterConfig, bucket); - assertEq(BLOCK_TIME, bucket.lastUpdated); - } - } - - function test_UpdateExistingConfig_Success() public { - MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = - new MultiAggregateRateLimiter.RateLimiterConfigArgs[](1); - configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ - remoteChainSelector: CHAIN_SELECTOR_1, - isOutboundLane: false, - rateLimiterConfig: s_rateLimiterConfig2 - }); - - RateLimiter.TokenBucket memory bucket1 = - s_rateLimiter.currentRateLimiterState(configUpdates[0].remoteChainSelector, false); - - // Capacity equals tokens - assertEq(bucket1.capacity, bucket1.tokens); - - vm.expectEmit(); - emit MultiAggregateRateLimiter.RateLimiterConfigUpdated( - configUpdates[0].remoteChainSelector, false, configUpdates[0].rateLimiterConfig - ); - - vm.recordLogs(); - s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); - - vm.warp(BLOCK_TIME + 1); - bucket1 = s_rateLimiter.currentRateLimiterState(configUpdates[0].remoteChainSelector, false); - assertEq(BLOCK_TIME + 1, bucket1.lastUpdated); - - // Tokens < capacity since capacity doubled - assertTrue(bucket1.capacity != bucket1.tokens); - - // Outbound lane config remains unchanged - _assertConfigWithTokenBucketEquality( - s_rateLimiterConfig1, s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, true) - ); - } - - function test_UpdateExistingConfigWithNoDifference_Success() public { - MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = - new MultiAggregateRateLimiter.RateLimiterConfigArgs[](1); - configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ - remoteChainSelector: CHAIN_SELECTOR_1, - isOutboundLane: false, - rateLimiterConfig: s_rateLimiterConfig1 - }); - - RateLimiter.TokenBucket memory bucketPreUpdate = - s_rateLimiter.currentRateLimiterState(configUpdates[0].remoteChainSelector, false); - - vm.expectEmit(); - emit MultiAggregateRateLimiter.RateLimiterConfigUpdated( - configUpdates[0].remoteChainSelector, false, configUpdates[0].rateLimiterConfig - ); - - vm.recordLogs(); - s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); - - vm.warp(BLOCK_TIME + 1); - RateLimiter.TokenBucket memory bucketPostUpdate = - s_rateLimiter.currentRateLimiterState(configUpdates[0].remoteChainSelector, false); - _assertTokenBucketEquality(bucketPreUpdate, bucketPostUpdate); - assertEq(BLOCK_TIME + 1, bucketPostUpdate.lastUpdated); - } - - // Reverts - function test_ZeroChainSelector_Revert() public { - MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = - new MultiAggregateRateLimiter.RateLimiterConfigArgs[](1); - configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ - remoteChainSelector: 0, - isOutboundLane: false, - rateLimiterConfig: s_rateLimiterConfig1 - }); - - vm.expectRevert(MultiAggregateRateLimiter.ZeroChainSelectorNotAllowed.selector); - s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); - } - - function test_OnlyCallableByOwner_Revert() public { - MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = - new MultiAggregateRateLimiter.RateLimiterConfigArgs[](1); - configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ - remoteChainSelector: CHAIN_SELECTOR_1 + 1, - isOutboundLane: false, - rateLimiterConfig: s_rateLimiterConfig1 - }); - vm.startPrank(STRANGER); - - vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); - s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); - } - - function test_ConfigRateMoreThanCapacity_Revert() public { - MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = - new MultiAggregateRateLimiter.RateLimiterConfigArgs[](1); - configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ - remoteChainSelector: CHAIN_SELECTOR_1 + 1, - isOutboundLane: false, - rateLimiterConfig: RateLimiter.Config({isEnabled: true, rate: 100, capacity: 100}) - }); - - vm.expectRevert( - abi.encodeWithSelector(RateLimiter.InvalidRateLimitRate.selector, configUpdates[0].rateLimiterConfig) - ); - s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); - } - - function test_ConfigRateZero_Revert() public { - MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = - new MultiAggregateRateLimiter.RateLimiterConfigArgs[](1); - configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ - remoteChainSelector: CHAIN_SELECTOR_1 + 1, - isOutboundLane: false, - rateLimiterConfig: RateLimiter.Config({isEnabled: true, rate: 0, capacity: 100}) - }); - - vm.expectRevert( - abi.encodeWithSelector(RateLimiter.InvalidRateLimitRate.selector, configUpdates[0].rateLimiterConfig) - ); - s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); - } - - function test_DisableConfigRateNonZero_Revert() public { - MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = - new MultiAggregateRateLimiter.RateLimiterConfigArgs[](1); - configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ - remoteChainSelector: CHAIN_SELECTOR_1 + 1, - isOutboundLane: false, - rateLimiterConfig: RateLimiter.Config({isEnabled: false, rate: 5, capacity: 100}) - }); - - vm.expectRevert( - abi.encodeWithSelector(RateLimiter.DisabledNonZeroRateLimit.selector, configUpdates[0].rateLimiterConfig) - ); - s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); - } - - function test_DiableConfigCapacityNonZero_Revert() public { - MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = - new MultiAggregateRateLimiter.RateLimiterConfigArgs[](1); - configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ - remoteChainSelector: CHAIN_SELECTOR_1 + 1, - isOutboundLane: false, - rateLimiterConfig: RateLimiter.Config({isEnabled: false, rate: 0, capacity: 100}) - }); - - vm.expectRevert( - abi.encodeWithSelector(RateLimiter.DisabledNonZeroRateLimit.selector, configUpdates[0].rateLimiterConfig) - ); - s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); - } -} - -contract MultiAggregateRateLimiter_getTokenValue is MultiAggregateRateLimiterSetup { - function test_GetTokenValue_Success() public view { - uint256 numberOfTokens = 10; - Client.EVMTokenAmount memory tokenAmount = Client.EVMTokenAmount({token: TOKEN, amount: 10}); - uint256 value = s_rateLimiter.getTokenValue(tokenAmount); - assertEq(value, (numberOfTokens * TOKEN_PRICE) / 1e18); - } - - // Reverts - function test_NoTokenPrice_Reverts() public { - address tokenWithNoPrice = makeAddr("Token with no price"); - Client.EVMTokenAmount memory tokenAmount = Client.EVMTokenAmount({token: tokenWithNoPrice, amount: 10}); - - vm.expectRevert(abi.encodeWithSelector(MultiAggregateRateLimiter.PriceNotFoundForToken.selector, tokenWithNoPrice)); - s_rateLimiter.getTokenValue(tokenAmount); - } -} - -contract MultiAggregateRateLimiter_updateRateLimitTokens is MultiAggregateRateLimiterSetup { - function setUp() public virtual override { - super.setUp(); - - // Clear rate limit tokens state - MultiAggregateRateLimiter.LocalRateLimitToken[] memory removes = - new MultiAggregateRateLimiter.LocalRateLimitToken[](s_sourceTokens.length); - for (uint256 i = 0; i < s_sourceTokens.length; ++i) { - removes[i] = MultiAggregateRateLimiter.LocalRateLimitToken({ - remoteChainSelector: CHAIN_SELECTOR_1, - localToken: s_destTokens[i] - }); - } - s_rateLimiter.updateRateLimitTokens(removes, new MultiAggregateRateLimiter.RateLimitTokenArgs[](0)); - } - - function test_UpdateRateLimitTokensSingleChain_Success() public { - MultiAggregateRateLimiter.RateLimitTokenArgs[] memory adds = new MultiAggregateRateLimiter.RateLimitTokenArgs[](2); - adds[0] = MultiAggregateRateLimiter.RateLimitTokenArgs({ - localTokenArgs: MultiAggregateRateLimiter.LocalRateLimitToken({ - remoteChainSelector: CHAIN_SELECTOR_1, - localToken: s_destTokens[0] - }), - remoteToken: abi.encode(s_sourceTokens[0]) - }); - adds[1] = MultiAggregateRateLimiter.RateLimitTokenArgs({ - localTokenArgs: MultiAggregateRateLimiter.LocalRateLimitToken({ - remoteChainSelector: CHAIN_SELECTOR_1, - localToken: s_destTokens[1] - }), - remoteToken: abi.encode(s_sourceTokens[1]) - }); - - for (uint256 i = 0; i < adds.length; ++i) { - vm.expectEmit(); - emit MultiAggregateRateLimiter.TokenAggregateRateLimitAdded( - CHAIN_SELECTOR_1, adds[i].remoteToken, adds[i].localTokenArgs.localToken - ); - } - - s_rateLimiter.updateRateLimitTokens(new MultiAggregateRateLimiter.LocalRateLimitToken[](0), adds); - - (address[] memory localTokens, bytes[] memory remoteTokens) = s_rateLimiter.getAllRateLimitTokens(CHAIN_SELECTOR_1); - - assertEq(localTokens.length, adds.length); - assertEq(localTokens.length, remoteTokens.length); - - for (uint256 i = 0; i < adds.length; ++i) { - assertEq(adds[i].remoteToken, remoteTokens[i]); - assertEq(adds[i].localTokenArgs.localToken, localTokens[i]); - } - } - - function test_UpdateRateLimitTokensMultipleChains_Success() public { - MultiAggregateRateLimiter.RateLimitTokenArgs[] memory adds = new MultiAggregateRateLimiter.RateLimitTokenArgs[](2); - adds[0] = MultiAggregateRateLimiter.RateLimitTokenArgs({ - localTokenArgs: MultiAggregateRateLimiter.LocalRateLimitToken({ - remoteChainSelector: CHAIN_SELECTOR_1, - localToken: s_destTokens[0] - }), - remoteToken: abi.encode(s_sourceTokens[0]) - }); - adds[1] = MultiAggregateRateLimiter.RateLimitTokenArgs({ - localTokenArgs: MultiAggregateRateLimiter.LocalRateLimitToken({ - remoteChainSelector: CHAIN_SELECTOR_2, - localToken: s_destTokens[1] - }), - remoteToken: abi.encode(s_sourceTokens[1]) - }); - - for (uint256 i = 0; i < adds.length; ++i) { - vm.expectEmit(); - emit MultiAggregateRateLimiter.TokenAggregateRateLimitAdded( - adds[i].localTokenArgs.remoteChainSelector, adds[i].remoteToken, adds[i].localTokenArgs.localToken - ); - } - - s_rateLimiter.updateRateLimitTokens(new MultiAggregateRateLimiter.LocalRateLimitToken[](0), adds); - - (address[] memory localTokensChain1, bytes[] memory remoteTokensChain1) = - s_rateLimiter.getAllRateLimitTokens(CHAIN_SELECTOR_1); - - assertEq(localTokensChain1.length, 1); - assertEq(localTokensChain1.length, remoteTokensChain1.length); - assertEq(localTokensChain1[0], adds[0].localTokenArgs.localToken); - assertEq(remoteTokensChain1[0], adds[0].remoteToken); - - (address[] memory localTokensChain2, bytes[] memory remoteTokensChain2) = - s_rateLimiter.getAllRateLimitTokens(CHAIN_SELECTOR_2); - - assertEq(localTokensChain2.length, 1); - assertEq(localTokensChain2.length, remoteTokensChain2.length); - assertEq(localTokensChain2[0], adds[1].localTokenArgs.localToken); - assertEq(remoteTokensChain2[0], adds[1].remoteToken); - } - - function test_UpdateRateLimitTokens_AddsAndRemoves_Success() public { - MultiAggregateRateLimiter.RateLimitTokenArgs[] memory adds = new MultiAggregateRateLimiter.RateLimitTokenArgs[](2); - adds[0] = MultiAggregateRateLimiter.RateLimitTokenArgs({ - localTokenArgs: MultiAggregateRateLimiter.LocalRateLimitToken({ - remoteChainSelector: CHAIN_SELECTOR_1, - localToken: s_destTokens[0] - }), - remoteToken: abi.encode(s_sourceTokens[0]) - }); - adds[1] = MultiAggregateRateLimiter.RateLimitTokenArgs({ - localTokenArgs: MultiAggregateRateLimiter.LocalRateLimitToken({ - remoteChainSelector: CHAIN_SELECTOR_1, - localToken: s_destTokens[1] - }), - remoteToken: abi.encode(s_sourceTokens[1]) - }); - - MultiAggregateRateLimiter.LocalRateLimitToken[] memory removes = - new MultiAggregateRateLimiter.LocalRateLimitToken[](1); - removes[0] = adds[0].localTokenArgs; - - for (uint256 i = 0; i < adds.length; ++i) { - vm.expectEmit(); - emit MultiAggregateRateLimiter.TokenAggregateRateLimitAdded( - CHAIN_SELECTOR_1, adds[i].remoteToken, adds[i].localTokenArgs.localToken - ); - } - - s_rateLimiter.updateRateLimitTokens(removes, adds); - - for (uint256 i = 0; i < removes.length; ++i) { - vm.expectEmit(); - emit MultiAggregateRateLimiter.TokenAggregateRateLimitRemoved(CHAIN_SELECTOR_1, removes[i].localToken); - } - - s_rateLimiter.updateRateLimitTokens(removes, new MultiAggregateRateLimiter.RateLimitTokenArgs[](0)); - - (address[] memory localTokens, bytes[] memory remoteTokens) = s_rateLimiter.getAllRateLimitTokens(CHAIN_SELECTOR_1); - - assertEq(1, remoteTokens.length); - assertEq(adds[1].remoteToken, remoteTokens[0]); - - assertEq(1, localTokens.length); - assertEq(adds[1].localTokenArgs.localToken, localTokens[0]); - } - - function test_UpdateRateLimitTokens_RemoveNonExistentToken_Success() public { - MultiAggregateRateLimiter.RateLimitTokenArgs[] memory adds = new MultiAggregateRateLimiter.RateLimitTokenArgs[](0); - - MultiAggregateRateLimiter.LocalRateLimitToken[] memory removes = - new MultiAggregateRateLimiter.LocalRateLimitToken[](1); - removes[0] = MultiAggregateRateLimiter.LocalRateLimitToken({ - remoteChainSelector: CHAIN_SELECTOR_1, - localToken: s_destTokens[0] - }); - - vm.recordLogs(); - s_rateLimiter.updateRateLimitTokens(removes, adds); - - // No event since no remove occurred - Vm.Log[] memory logEntries = vm.getRecordedLogs(); - assertEq(logEntries.length, 0); - - (address[] memory localTokens, bytes[] memory remoteTokens) = s_rateLimiter.getAllRateLimitTokens(CHAIN_SELECTOR_1); - - assertEq(localTokens.length, 0); - assertEq(localTokens.length, remoteTokens.length); - } - - // Reverts - - function test_ZeroSourceToken_Revert() public { - MultiAggregateRateLimiter.RateLimitTokenArgs[] memory adds = new MultiAggregateRateLimiter.RateLimitTokenArgs[](1); - adds[0] = MultiAggregateRateLimiter.RateLimitTokenArgs({ - localTokenArgs: MultiAggregateRateLimiter.LocalRateLimitToken({ - remoteChainSelector: CHAIN_SELECTOR_1, - localToken: s_destTokens[0] - }), - remoteToken: new bytes(0) - }); - - vm.expectRevert(AuthorizedCallers.ZeroAddressNotAllowed.selector); - s_rateLimiter.updateRateLimitTokens(new MultiAggregateRateLimiter.LocalRateLimitToken[](0), adds); - } - - function test_ZeroDestToken_Revert() public { - MultiAggregateRateLimiter.RateLimitTokenArgs[] memory adds = new MultiAggregateRateLimiter.RateLimitTokenArgs[](1); - adds[0] = MultiAggregateRateLimiter.RateLimitTokenArgs({ - localTokenArgs: MultiAggregateRateLimiter.LocalRateLimitToken({ - remoteChainSelector: CHAIN_SELECTOR_1, - localToken: address(0) - }), - remoteToken: abi.encode(s_destTokens[0]) - }); - - vm.expectRevert(AuthorizedCallers.ZeroAddressNotAllowed.selector); - s_rateLimiter.updateRateLimitTokens(new MultiAggregateRateLimiter.LocalRateLimitToken[](0), adds); - } - - function test_ZeroDestToken_AbiEncoded_Revert() public { - MultiAggregateRateLimiter.RateLimitTokenArgs[] memory adds = new MultiAggregateRateLimiter.RateLimitTokenArgs[](1); - adds[0] = MultiAggregateRateLimiter.RateLimitTokenArgs({ - localTokenArgs: MultiAggregateRateLimiter.LocalRateLimitToken({ - remoteChainSelector: CHAIN_SELECTOR_1, - localToken: address(0) - }), - remoteToken: abi.encode(address(0)) - }); - - vm.expectRevert(AuthorizedCallers.ZeroAddressNotAllowed.selector); - s_rateLimiter.updateRateLimitTokens(new MultiAggregateRateLimiter.LocalRateLimitToken[](0), adds); - } - - function test_NonOwner_Revert() public { - MultiAggregateRateLimiter.RateLimitTokenArgs[] memory adds = new MultiAggregateRateLimiter.RateLimitTokenArgs[](4); - - vm.startPrank(STRANGER); - - vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); - s_rateLimiter.updateRateLimitTokens(new MultiAggregateRateLimiter.LocalRateLimitToken[](0), adds); - } -} - -contract MultiAggregateRateLimiter_onInboundMessage is MultiAggregateRateLimiterSetup { - address internal constant MOCK_RECEIVER = address(1113); - - function setUp() public virtual override { - super.setUp(); - - MultiAggregateRateLimiter.RateLimitTokenArgs[] memory tokensToAdd = - new MultiAggregateRateLimiter.RateLimitTokenArgs[](s_sourceTokens.length); - for (uint224 i = 0; i < s_sourceTokens.length; ++i) { - tokensToAdd[i] = MultiAggregateRateLimiter.RateLimitTokenArgs({ - localTokenArgs: MultiAggregateRateLimiter.LocalRateLimitToken({ - remoteChainSelector: CHAIN_SELECTOR_1, - localToken: s_destTokens[i] - }), - remoteToken: abi.encode(s_sourceTokens[i]) - }); - - Internal.PriceUpdates memory priceUpdates = - _getSingleTokenPriceUpdateStruct(s_destTokens[i], TOKEN_PRICE * (i + 1)); - s_feeQuoter.updatePrices(priceUpdates); - } - s_rateLimiter.updateRateLimitTokens(new MultiAggregateRateLimiter.LocalRateLimitToken[](0), tokensToAdd); - } - - function test_ValidateMessageWithNoTokens_Success() public { - vm.startPrank(MOCK_OFFRAMP); - - vm.recordLogs(); - s_rateLimiter.onInboundMessage(_generateAny2EVMMessageNoTokens(CHAIN_SELECTOR_1)); - - // No consumed rate limit events - Vm.Log[] memory logEntries = vm.getRecordedLogs(); - assertEq(logEntries.length, 0); - } - - function test_ValidateMessageWithTokens_Success() public { - vm.startPrank(MOCK_OFFRAMP); - - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); - tokenAmounts[0] = Client.EVMTokenAmount({token: s_destTokens[0], amount: 3}); - tokenAmounts[1] = Client.EVMTokenAmount({token: s_destTokens[1], amount: 1}); - - // 3 tokens * TOKEN_PRICE + 1 token * (2 * TOKEN_PRICE) - vm.expectEmit(); - emit RateLimiter.TokensConsumed((5 * TOKEN_PRICE) / 1e18); - - s_rateLimiter.onInboundMessage(_generateAny2EVMMessage(CHAIN_SELECTOR_1, tokenAmounts)); - } - - function test_ValidateMessageWithDisabledRateLimitToken_Success() public { - MultiAggregateRateLimiter.LocalRateLimitToken[] memory removes = - new MultiAggregateRateLimiter.LocalRateLimitToken[](1); - removes[0] = MultiAggregateRateLimiter.LocalRateLimitToken({ - remoteChainSelector: CHAIN_SELECTOR_1, - localToken: s_destTokens[1] - }); - s_rateLimiter.updateRateLimitTokens(removes, new MultiAggregateRateLimiter.RateLimitTokenArgs[](0)); - - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); - tokenAmounts[0] = Client.EVMTokenAmount({token: s_destTokens[0], amount: 5}); - tokenAmounts[1] = Client.EVMTokenAmount({token: s_destTokens[1], amount: 1}); - - vm.startPrank(MOCK_OFFRAMP); - - vm.expectEmit(); - emit RateLimiter.TokensConsumed((5 * TOKEN_PRICE) / 1e18); - - s_rateLimiter.onInboundMessage(_generateAny2EVMMessage(CHAIN_SELECTOR_1, tokenAmounts)); - } - - function test_ValidateMessageWithRateLimitDisabled_Success() public { - MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = - new MultiAggregateRateLimiter.RateLimiterConfigArgs[](1); - configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ - remoteChainSelector: CHAIN_SELECTOR_1, - isOutboundLane: false, - rateLimiterConfig: s_rateLimiterConfig1 - }); - configUpdates[0].rateLimiterConfig.isEnabled = false; - - s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); - - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); - tokenAmounts[0] = Client.EVMTokenAmount({token: s_destTokens[0], amount: 1000}); - tokenAmounts[1] = Client.EVMTokenAmount({token: s_destTokens[1], amount: 50}); - - vm.startPrank(MOCK_OFFRAMP); - s_rateLimiter.onInboundMessage(_generateAny2EVMMessage(CHAIN_SELECTOR_1, tokenAmounts)); - - // No consumed rate limit events - Vm.Log[] memory logEntries = vm.getRecordedLogs(); - assertEq(logEntries.length, 0); - } - - function test_ValidateMessageWithTokensOnDifferentChains_Success() public { - MultiAggregateRateLimiter.RateLimitTokenArgs[] memory tokensToAdd = - new MultiAggregateRateLimiter.RateLimitTokenArgs[](s_sourceTokens.length); - for (uint224 i = 0; i < s_sourceTokens.length; ++i) { - tokensToAdd[i] = MultiAggregateRateLimiter.RateLimitTokenArgs({ - localTokenArgs: MultiAggregateRateLimiter.LocalRateLimitToken({ - remoteChainSelector: CHAIN_SELECTOR_2, - localToken: s_destTokens[i] - }), - // Create a remote token address that is different from CHAIN_SELECTOR_1 - remoteToken: abi.encode(uint256(uint160(s_sourceTokens[i])) + type(uint160).max + 1) - }); - } - s_rateLimiter.updateRateLimitTokens(new MultiAggregateRateLimiter.LocalRateLimitToken[](0), tokensToAdd); - - vm.startPrank(MOCK_OFFRAMP); - - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); - tokenAmounts[0] = Client.EVMTokenAmount({token: s_destTokens[0], amount: 2}); - tokenAmounts[1] = Client.EVMTokenAmount({token: s_destTokens[1], amount: 1}); - - // 2 tokens * (TOKEN_PRICE) + 1 token * (2 * TOKEN_PRICE) - uint256 totalValue = (4 * TOKEN_PRICE) / 1e18; - - s_rateLimiter.onInboundMessage(_generateAny2EVMMessage(CHAIN_SELECTOR_1, tokenAmounts)); - - // Chain 1 changed - RateLimiter.TokenBucket memory bucketChain1 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, false); - assertEq(bucketChain1.capacity - totalValue, bucketChain1.tokens); - - // Chain 2 unchanged - RateLimiter.TokenBucket memory bucketChain2 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_2, false); - assertEq(bucketChain2.capacity, bucketChain2.tokens); - - vm.expectEmit(); - emit RateLimiter.TokensConsumed(totalValue); - - s_rateLimiter.onInboundMessage(_generateAny2EVMMessage(CHAIN_SELECTOR_2, tokenAmounts)); - - // Chain 1 unchanged - bucketChain1 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, false); - assertEq(bucketChain1.capacity - totalValue, bucketChain1.tokens); - - // Chain 2 changed - bucketChain2 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_2, false); - assertEq(bucketChain2.capacity - totalValue, bucketChain2.tokens); - } - - function test_ValidateMessageWithDifferentTokensOnDifferentChains_Success() public { - MultiAggregateRateLimiter.RateLimitTokenArgs[] memory tokensToAdd = - new MultiAggregateRateLimiter.RateLimitTokenArgs[](1); - - // Only 1 rate limited token on different chain - tokensToAdd[0] = MultiAggregateRateLimiter.RateLimitTokenArgs({ - localTokenArgs: MultiAggregateRateLimiter.LocalRateLimitToken({ - remoteChainSelector: CHAIN_SELECTOR_2, - localToken: s_destTokens[0] - }), - // Create a remote token address that is different from CHAIN_SELECTOR_1 - remoteToken: abi.encode(uint256(uint160(s_sourceTokens[0])) + type(uint160).max + 1) - }); - s_rateLimiter.updateRateLimitTokens(new MultiAggregateRateLimiter.LocalRateLimitToken[](0), tokensToAdd); - - vm.startPrank(MOCK_OFFRAMP); - - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); - tokenAmounts[0] = Client.EVMTokenAmount({token: s_destTokens[0], amount: 3}); - tokenAmounts[1] = Client.EVMTokenAmount({token: s_destTokens[1], amount: 1}); - - // 3 tokens * (TOKEN_PRICE) + 1 token * (2 * TOKEN_PRICE) - uint256 totalValue = (5 * TOKEN_PRICE) / 1e18; - - s_rateLimiter.onInboundMessage(_generateAny2EVMMessage(CHAIN_SELECTOR_1, tokenAmounts)); - - // Chain 1 changed - RateLimiter.TokenBucket memory bucketChain1 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, false); - assertEq(bucketChain1.capacity - totalValue, bucketChain1.tokens); - - // Chain 2 unchanged - RateLimiter.TokenBucket memory bucketChain2 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_2, false); - assertEq(bucketChain2.capacity, bucketChain2.tokens); - - // 3 tokens * (TOKEN_PRICE) - uint256 totalValue2 = (3 * TOKEN_PRICE) / 1e18; - - vm.expectEmit(); - emit RateLimiter.TokensConsumed(totalValue2); - - s_rateLimiter.onInboundMessage(_generateAny2EVMMessage(CHAIN_SELECTOR_2, tokenAmounts)); - - // Chain 1 unchanged - bucketChain1 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, false); - assertEq(bucketChain1.capacity - totalValue, bucketChain1.tokens); - - // Chain 2 changed - bucketChain2 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_2, false); - assertEq(bucketChain2.capacity - totalValue2, bucketChain2.tokens); - } - - function test_ValidateMessageWithRateLimitReset_Success() public { - vm.startPrank(MOCK_OFFRAMP); - - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); - tokenAmounts[0] = Client.EVMTokenAmount({token: s_destTokens[0], amount: 20}); - - // Remaining capacity: 100 -> 20 - s_rateLimiter.onInboundMessage(_generateAny2EVMMessage(CHAIN_SELECTOR_1, tokenAmounts)); - - // Cannot fit 80 rate limit value (need to wait at least 12 blocks, current capacity is 20) - vm.expectRevert(abi.encodeWithSelector(RateLimiter.AggregateValueRateLimitReached.selector, 12, 20)); - s_rateLimiter.onInboundMessage(_generateAny2EVMMessage(CHAIN_SELECTOR_1, tokenAmounts)); - - // Remaining capacity: 20 -> 35 (need to wait 9 more blocks) - vm.warp(BLOCK_TIME + 3); - vm.expectRevert(abi.encodeWithSelector(RateLimiter.AggregateValueRateLimitReached.selector, 9, 35)); - s_rateLimiter.onInboundMessage(_generateAny2EVMMessage(CHAIN_SELECTOR_1, tokenAmounts)); - - // Remaining capacity: 35 -> 80 (can fit exactly 80) - vm.warp(BLOCK_TIME + 12); - s_rateLimiter.onInboundMessage(_generateAny2EVMMessage(CHAIN_SELECTOR_1, tokenAmounts)); - } - - // Reverts - - function test_ValidateMessageWithRateLimitExceeded_Revert() public { - vm.startPrank(MOCK_OFFRAMP); - - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); - tokenAmounts[0] = Client.EVMTokenAmount({token: s_destTokens[0], amount: 80}); - tokenAmounts[1] = Client.EVMTokenAmount({token: s_destTokens[1], amount: 30}); - - uint256 totalValue = (80 * TOKEN_PRICE + 2 * (30 * TOKEN_PRICE)) / 1e18; - vm.expectRevert(abi.encodeWithSelector(RateLimiter.AggregateValueMaxCapacityExceeded.selector, 100, totalValue)); - s_rateLimiter.onInboundMessage(_generateAny2EVMMessage(CHAIN_SELECTOR_1, tokenAmounts)); - } - - function test_ValidateMessageFromUnauthorizedCaller_Revert() public { - vm.startPrank(STRANGER); - - vm.expectRevert(abi.encodeWithSelector(AuthorizedCallers.UnauthorizedCaller.selector, STRANGER)); - s_rateLimiter.onInboundMessage(_generateAny2EVMMessageNoTokens(CHAIN_SELECTOR_1)); - } -} - -contract MultiAggregateRateLimiter_onOutboundMessage is MultiAggregateRateLimiterSetup { - function setUp() public virtual override { - super.setUp(); - - MultiAggregateRateLimiter.RateLimitTokenArgs[] memory tokensToAdd = - new MultiAggregateRateLimiter.RateLimitTokenArgs[](s_sourceTokens.length); - for (uint224 i = 0; i < s_sourceTokens.length; ++i) { - tokensToAdd[i] = MultiAggregateRateLimiter.RateLimitTokenArgs({ - localTokenArgs: MultiAggregateRateLimiter.LocalRateLimitToken({ - remoteChainSelector: CHAIN_SELECTOR_1, - localToken: s_sourceTokens[i] - }), - remoteToken: abi.encode(bytes20(s_destTokenBySourceToken[s_sourceTokens[i]])) - }); - - Internal.PriceUpdates memory priceUpdates = - _getSingleTokenPriceUpdateStruct(s_sourceTokens[i], TOKEN_PRICE * (i + 1)); - s_feeQuoter.updatePrices(priceUpdates); - } - s_rateLimiter.updateRateLimitTokens(new MultiAggregateRateLimiter.LocalRateLimitToken[](0), tokensToAdd); - } - - function test_ValidateMessageWithNoTokens_Success() public { - vm.startPrank(MOCK_ONRAMP); - - vm.recordLogs(); - s_rateLimiter.onOutboundMessage(CHAIN_SELECTOR_1, _generateEVM2AnyMessageNoTokens()); - - // No consumed rate limit events - assertEq(vm.getRecordedLogs().length, 0); - } - - function test_onOutboundMessage_ValidateMessageWithTokens_Success() public { - vm.startPrank(MOCK_ONRAMP); - - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); - tokenAmounts[0] = Client.EVMTokenAmount({token: s_sourceTokens[0], amount: 3}); - tokenAmounts[1] = Client.EVMTokenAmount({token: s_sourceTokens[1], amount: 1}); - - // 3 tokens * TOKEN_PRICE + 1 token * (2 * TOKEN_PRICE) - vm.expectEmit(); - emit RateLimiter.TokensConsumed((5 * TOKEN_PRICE) / 1e18); - - s_rateLimiter.onOutboundMessage(CHAIN_SELECTOR_1, _generateEVM2AnyMessage(tokenAmounts)); - } - - function test_onOutboundMessage_ValidateMessageWithDisabledRateLimitToken_Success() public { - MultiAggregateRateLimiter.LocalRateLimitToken[] memory removes = - new MultiAggregateRateLimiter.LocalRateLimitToken[](1); - removes[0] = MultiAggregateRateLimiter.LocalRateLimitToken({ - remoteChainSelector: CHAIN_SELECTOR_1, - localToken: s_sourceTokens[1] - }); - s_rateLimiter.updateRateLimitTokens(removes, new MultiAggregateRateLimiter.RateLimitTokenArgs[](0)); - - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); - tokenAmounts[0] = Client.EVMTokenAmount({token: s_sourceTokens[0], amount: 5}); - tokenAmounts[1] = Client.EVMTokenAmount({token: s_sourceTokens[1], amount: 1}); - - vm.startPrank(MOCK_ONRAMP); - - vm.expectEmit(); - emit RateLimiter.TokensConsumed((5 * TOKEN_PRICE) / 1e18); - - s_rateLimiter.onOutboundMessage(CHAIN_SELECTOR_1, _generateEVM2AnyMessage(tokenAmounts)); - } - - function test_onOutboundMessage_ValidateMessageWithRateLimitDisabled_Success() public { - MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = - new MultiAggregateRateLimiter.RateLimiterConfigArgs[](1); - configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ - remoteChainSelector: CHAIN_SELECTOR_1, - isOutboundLane: true, - rateLimiterConfig: s_rateLimiterConfig1 - }); - configUpdates[0].rateLimiterConfig.isEnabled = false; - - s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); - - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); - tokenAmounts[0] = Client.EVMTokenAmount({token: s_sourceTokens[0], amount: 1000}); - tokenAmounts[1] = Client.EVMTokenAmount({token: s_sourceTokens[1], amount: 50}); - - vm.startPrank(MOCK_ONRAMP); - s_rateLimiter.onOutboundMessage(CHAIN_SELECTOR_1, _generateEVM2AnyMessage(tokenAmounts)); - - // No consumed rate limit events - assertEq(vm.getRecordedLogs().length, 0); - } - - function test_onOutboundMessage_ValidateMessageWithTokensOnDifferentChains_Success() public { - MultiAggregateRateLimiter.RateLimitTokenArgs[] memory tokensToAdd = - new MultiAggregateRateLimiter.RateLimitTokenArgs[](s_sourceTokens.length); - for (uint224 i = 0; i < s_sourceTokens.length; ++i) { - tokensToAdd[i] = MultiAggregateRateLimiter.RateLimitTokenArgs({ - localTokenArgs: MultiAggregateRateLimiter.LocalRateLimitToken({ - remoteChainSelector: CHAIN_SELECTOR_2, - localToken: s_sourceTokens[i] - }), - // Create a remote token address that is different from CHAIN_SELECTOR_1 - remoteToken: abi.encode(uint256(uint160(s_destTokenBySourceToken[s_sourceTokens[i]])) + type(uint160).max + 1) - }); - } - s_rateLimiter.updateRateLimitTokens(new MultiAggregateRateLimiter.LocalRateLimitToken[](0), tokensToAdd); - - vm.startPrank(MOCK_ONRAMP); - - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); - tokenAmounts[0] = Client.EVMTokenAmount({token: s_sourceTokens[0], amount: 2}); - tokenAmounts[1] = Client.EVMTokenAmount({token: s_sourceTokens[1], amount: 1}); - - // 2 tokens * (TOKEN_PRICE) + 1 token * (2 * TOKEN_PRICE) - uint256 totalValue = (4 * TOKEN_PRICE) / 1e18; - - s_rateLimiter.onOutboundMessage(CHAIN_SELECTOR_1, _generateEVM2AnyMessage(tokenAmounts)); - - // Chain 1 changed - RateLimiter.TokenBucket memory bucketChain1 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, true); - assertEq(bucketChain1.capacity - totalValue, bucketChain1.tokens); - - // Chain 2 unchanged - RateLimiter.TokenBucket memory bucketChain2 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_2, true); - assertEq(bucketChain2.capacity, bucketChain2.tokens); - - vm.expectEmit(); - emit RateLimiter.TokensConsumed(totalValue); - - s_rateLimiter.onOutboundMessage(CHAIN_SELECTOR_2, _generateEVM2AnyMessage(tokenAmounts)); - - // Chain 1 unchanged - bucketChain1 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, true); - assertEq(bucketChain1.capacity - totalValue, bucketChain1.tokens); - - // Chain 2 changed - bucketChain2 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_2, true); - assertEq(bucketChain2.capacity - totalValue, bucketChain2.tokens); - } - - function test_onOutboundMessage_ValidateMessageWithDifferentTokensOnDifferentChains_Success() public { - MultiAggregateRateLimiter.RateLimitTokenArgs[] memory tokensToAdd = - new MultiAggregateRateLimiter.RateLimitTokenArgs[](1); - - // Only 1 rate limited token on different chain - tokensToAdd[0] = MultiAggregateRateLimiter.RateLimitTokenArgs({ - localTokenArgs: MultiAggregateRateLimiter.LocalRateLimitToken({ - remoteChainSelector: CHAIN_SELECTOR_2, - localToken: s_sourceTokens[0] - }), - // Create a remote token address that is different from CHAIN_SELECTOR_1 - remoteToken: abi.encode(uint256(uint160(s_destTokenBySourceToken[s_sourceTokens[0]])) + type(uint160).max + 1) - }); - s_rateLimiter.updateRateLimitTokens(new MultiAggregateRateLimiter.LocalRateLimitToken[](0), tokensToAdd); - - vm.startPrank(MOCK_ONRAMP); - - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); - tokenAmounts[0] = Client.EVMTokenAmount({token: s_sourceTokens[0], amount: 3}); - tokenAmounts[1] = Client.EVMTokenAmount({token: s_sourceTokens[1], amount: 1}); - - // 3 tokens * (TOKEN_PRICE) + 1 token * (2 * TOKEN_PRICE) - uint256 totalValue = (5 * TOKEN_PRICE) / 1e18; - - s_rateLimiter.onOutboundMessage(CHAIN_SELECTOR_1, _generateEVM2AnyMessage(tokenAmounts)); - - // Chain 1 changed - RateLimiter.TokenBucket memory bucketChain1 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, true); - assertEq(bucketChain1.capacity - totalValue, bucketChain1.tokens); - - // Chain 2 unchanged - RateLimiter.TokenBucket memory bucketChain2 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_2, true); - assertEq(bucketChain2.capacity, bucketChain2.tokens); - - // 3 tokens * (TOKEN_PRICE) - uint256 totalValue2 = (3 * TOKEN_PRICE) / 1e18; - - vm.expectEmit(); - emit RateLimiter.TokensConsumed(totalValue2); - - s_rateLimiter.onOutboundMessage(CHAIN_SELECTOR_2, _generateEVM2AnyMessage(tokenAmounts)); - - // Chain 1 unchanged - bucketChain1 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, true); - assertEq(bucketChain1.capacity - totalValue, bucketChain1.tokens); - - // Chain 2 changed - bucketChain2 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_2, true); - assertEq(bucketChain2.capacity - totalValue2, bucketChain2.tokens); - } - - function test_onOutboundMessage_ValidateMessageWithRateLimitReset_Success() public { - vm.startPrank(MOCK_ONRAMP); - - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); - tokenAmounts[0] = Client.EVMTokenAmount({token: s_sourceTokens[0], amount: 20}); - - // Remaining capacity: 100 -> 20 - s_rateLimiter.onOutboundMessage(CHAIN_SELECTOR_1, _generateEVM2AnyMessage(tokenAmounts)); - - // Cannot fit 80 rate limit value (need to wait at least 12 blocks, current capacity is 20) - vm.expectRevert(abi.encodeWithSelector(RateLimiter.AggregateValueRateLimitReached.selector, 12, 20)); - s_rateLimiter.onOutboundMessage(CHAIN_SELECTOR_1, _generateEVM2AnyMessage(tokenAmounts)); - - // Remaining capacity: 20 -> 35 (need to wait 9 more blocks) - vm.warp(BLOCK_TIME + 3); - vm.expectRevert(abi.encodeWithSelector(RateLimiter.AggregateValueRateLimitReached.selector, 9, 35)); - s_rateLimiter.onOutboundMessage(CHAIN_SELECTOR_1, _generateEVM2AnyMessage(tokenAmounts)); - - // Remaining capacity: 35 -> 80 (can fit exactly 80) - vm.warp(BLOCK_TIME + 12); - s_rateLimiter.onOutboundMessage(CHAIN_SELECTOR_1, _generateEVM2AnyMessage(tokenAmounts)); - } - - function test_RateLimitValueDifferentLanes_Success() public { - vm.pauseGasMetering(); - // start from blocktime that does not equal rate limiter init timestamp - vm.warp(BLOCK_TIME + 1); - - // 10 (tokens) * 4 (price) * 2 (number of times) = 80 < 100 (capacity) - uint256 numberOfTokens = 10; - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); - tokenAmounts[0] = Client.EVMTokenAmount({token: s_sourceTokens[0], amount: numberOfTokens}); - uint256 value = (numberOfTokens * TOKEN_PRICE) / 1e18; - - vm.expectEmit(); - emit RateLimiter.TokensConsumed(value); - - vm.resumeGasMetering(); - vm.startPrank(MOCK_ONRAMP); - s_rateLimiter.onOutboundMessage(CHAIN_SELECTOR_1, _generateEVM2AnyMessage(tokenAmounts)); - vm.pauseGasMetering(); - - // Get the updated bucket status - RateLimiter.TokenBucket memory bucket1 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, true); - RateLimiter.TokenBucket memory bucket2 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, false); - - // Assert the proper value has been taken out of the bucket - assertEq(bucket1.capacity - value, bucket1.tokens); - // Inbound lane should remain unchanged - assertEq(bucket2.capacity, bucket2.tokens); - - vm.expectEmit(); - emit RateLimiter.TokensConsumed(value); - - vm.resumeGasMetering(); - s_rateLimiter.onInboundMessage(_generateAny2EVMMessage(CHAIN_SELECTOR_1, tokenAmounts)); - vm.pauseGasMetering(); - - bucket1 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, true); - bucket2 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, false); - - // Inbound lane should remain unchanged - assertEq(bucket1.capacity - value, bucket1.tokens); - assertEq(bucket2.capacity - value, bucket2.tokens); - } - - // Reverts - - function test_onOutboundMessage_ValidateMessageWithRateLimitExceeded_Revert() public { - vm.startPrank(MOCK_OFFRAMP); - - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); - tokenAmounts[0] = Client.EVMTokenAmount({token: s_sourceTokens[0], amount: 80}); - tokenAmounts[1] = Client.EVMTokenAmount({token: s_sourceTokens[1], amount: 30}); - - uint256 totalValue = (80 * TOKEN_PRICE + 2 * (30 * TOKEN_PRICE)) / 1e18; - vm.expectRevert(abi.encodeWithSelector(RateLimiter.AggregateValueMaxCapacityExceeded.selector, 100, totalValue)); - s_rateLimiter.onOutboundMessage(CHAIN_SELECTOR_1, _generateEVM2AnyMessage(tokenAmounts)); - } - - function test_onOutboundMessage_ValidateMessageFromUnauthorizedCaller_Revert() public { - vm.startPrank(STRANGER); - - vm.expectRevert(abi.encodeWithSelector(AuthorizedCallers.UnauthorizedCaller.selector, STRANGER)); - s_rateLimiter.onOutboundMessage(CHAIN_SELECTOR_1, _generateEVM2AnyMessageNoTokens()); - } - - function _generateEVM2AnyMessage( - Client.EVMTokenAmount[] memory tokenAmounts - ) public view returns (Client.EVM2AnyMessage memory) { - return Client.EVM2AnyMessage({ - receiver: abi.encode(OWNER), - data: "", - tokenAmounts: tokenAmounts, - feeToken: s_sourceFeeToken, - extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT})) - }); - } - - function _generateEVM2AnyMessageNoTokens() internal view returns (Client.EVM2AnyMessage memory) { - return _generateEVM2AnyMessage(new Client.EVMTokenAmount[](0)); - } -} diff --git a/contracts/src/v0.8/ccip/test/rateLimiter/MutiAggregateRateLimiter/MultiAggregateRateLimiterSetup.t.sol b/contracts/src/v0.8/ccip/test/rateLimiter/MutiAggregateRateLimiter/MultiAggregateRateLimiterSetup.t.sol new file mode 100644 index 00000000000..d3e87f5faa4 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/rateLimiter/MutiAggregateRateLimiter/MultiAggregateRateLimiterSetup.t.sol @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {MultiAggregateRateLimiter} from "../../../MultiAggregateRateLimiter.sol"; +import {Client} from "../../../libraries/Client.sol"; +import {Internal} from "../../../libraries/Internal.sol"; +import {RateLimiter} from "../../../libraries/RateLimiter.sol"; +import {BaseTest} from "../../BaseTest.t.sol"; + +import {FeeQuoterSetup} from "../../feeQuoter/FeeQuoterSetup.t.sol"; +import {MultiAggregateRateLimiterHelper} from "../../helpers/MultiAggregateRateLimiterHelper.sol"; + +contract MultiAggregateRateLimiterSetup is BaseTest, FeeQuoterSetup { + MultiAggregateRateLimiterHelper internal s_rateLimiter; + + address internal constant TOKEN = 0x21118E64E1fB0c487F25Dd6d3601FF6af8D32E4e; + uint224 internal constant TOKEN_PRICE = 4e18; + + uint64 internal constant CHAIN_SELECTOR_1 = 5009297550715157269; + uint64 internal constant CHAIN_SELECTOR_2 = 4949039107694359620; + + RateLimiter.Config internal s_rateLimiterConfig1 = RateLimiter.Config({isEnabled: true, rate: 5, capacity: 100}); + RateLimiter.Config internal s_rateLimiterConfig2 = RateLimiter.Config({isEnabled: true, rate: 10, capacity: 200}); + + address internal constant MOCK_OFFRAMP = address(1111); + address internal constant MOCK_ONRAMP = address(1112); + + address[] internal s_authorizedCallers; + + function setUp() public virtual override(BaseTest, FeeQuoterSetup) { + BaseTest.setUp(); + FeeQuoterSetup.setUp(); + + Internal.PriceUpdates memory priceUpdates = _getSingleTokenPriceUpdateStruct(TOKEN, TOKEN_PRICE); + s_feeQuoter.updatePrices(priceUpdates); + + MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = + new MultiAggregateRateLimiter.RateLimiterConfigArgs[](4); + configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ + remoteChainSelector: CHAIN_SELECTOR_1, + isOutboundLane: false, + rateLimiterConfig: s_rateLimiterConfig1 + }); + configUpdates[1] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ + remoteChainSelector: CHAIN_SELECTOR_2, + isOutboundLane: false, + rateLimiterConfig: s_rateLimiterConfig2 + }); + configUpdates[2] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ + remoteChainSelector: CHAIN_SELECTOR_1, + isOutboundLane: true, + rateLimiterConfig: s_rateLimiterConfig1 + }); + configUpdates[3] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ + remoteChainSelector: CHAIN_SELECTOR_2, + isOutboundLane: true, + rateLimiterConfig: s_rateLimiterConfig2 + }); + + s_authorizedCallers = new address[](2); + s_authorizedCallers[0] = MOCK_OFFRAMP; + s_authorizedCallers[1] = MOCK_ONRAMP; + + s_rateLimiter = new MultiAggregateRateLimiterHelper(address(s_feeQuoter), s_authorizedCallers); + s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); + } + + function _assertConfigWithTokenBucketEquality( + RateLimiter.Config memory config, + RateLimiter.TokenBucket memory tokenBucket + ) internal pure { + assertEq(config.rate, tokenBucket.rate); + assertEq(config.capacity, tokenBucket.capacity); + assertEq(config.capacity, tokenBucket.tokens); + assertEq(config.isEnabled, tokenBucket.isEnabled); + } + + function _assertTokenBucketEquality( + RateLimiter.TokenBucket memory tokenBucketA, + RateLimiter.TokenBucket memory tokenBucketB + ) internal pure { + assertEq(tokenBucketA.rate, tokenBucketB.rate); + assertEq(tokenBucketA.capacity, tokenBucketB.capacity); + assertEq(tokenBucketA.tokens, tokenBucketB.tokens); + assertEq(tokenBucketA.isEnabled, tokenBucketB.isEnabled); + } + + function _generateAny2EVMMessage( + uint64 sourceChainSelector, + Client.EVMTokenAmount[] memory tokenAmounts + ) internal pure returns (Client.Any2EVMMessage memory) { + return Client.Any2EVMMessage({ + messageId: keccak256(bytes("messageId")), + sourceChainSelector: sourceChainSelector, + sender: abi.encode(OWNER), + data: abi.encode(0), + destTokenAmounts: tokenAmounts + }); + } + + function _generateAny2EVMMessageNoTokens( + uint64 sourceChainSelector + ) internal pure returns (Client.Any2EVMMessage memory) { + return _generateAny2EVMMessage(sourceChainSelector, new Client.EVMTokenAmount[](0)); + } +} diff --git a/contracts/src/v0.8/ccip/test/rateLimiter/MutiAggregateRateLimiter/MultiAggregateRateLimiter_applyRateLimiterConfigUpdates.t.sol b/contracts/src/v0.8/ccip/test/rateLimiter/MutiAggregateRateLimiter/MultiAggregateRateLimiter_applyRateLimiterConfigUpdates.t.sol new file mode 100644 index 00000000000..306be6b5956 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/rateLimiter/MutiAggregateRateLimiter/MultiAggregateRateLimiter_applyRateLimiterConfigUpdates.t.sol @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Ownable2Step} from "../../../../shared/access/Ownable2Step.sol"; +import {MultiAggregateRateLimiter} from "../../../MultiAggregateRateLimiter.sol"; +import {RateLimiter} from "../../../libraries/RateLimiter.sol"; + +import {MultiAggregateRateLimiterSetup} from "./MultiAggregateRateLimiterSetup.t.sol"; +import {Vm} from "forge-std/Vm.sol"; + +contract MultiAggregateRateLimiter_applyRateLimiterConfigUpdates is MultiAggregateRateLimiterSetup { + function test_ZeroConfigs_Success() public { + MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = + new MultiAggregateRateLimiter.RateLimiterConfigArgs[](0); + + vm.recordLogs(); + s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); + + Vm.Log[] memory logEntries = vm.getRecordedLogs(); + assertEq(logEntries.length, 0); + } + + function test_SingleConfig_Success() public { + MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = + new MultiAggregateRateLimiter.RateLimiterConfigArgs[](1); + configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ + remoteChainSelector: CHAIN_SELECTOR_1 + 1, + isOutboundLane: false, + rateLimiterConfig: s_rateLimiterConfig1 + }); + + vm.expectEmit(); + emit MultiAggregateRateLimiter.RateLimiterConfigUpdated( + configUpdates[0].remoteChainSelector, false, configUpdates[0].rateLimiterConfig + ); + + vm.recordLogs(); + s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); + + Vm.Log[] memory logEntries = vm.getRecordedLogs(); + assertEq(logEntries.length, 1); + + RateLimiter.TokenBucket memory bucket1 = + s_rateLimiter.currentRateLimiterState(configUpdates[0].remoteChainSelector, false); + _assertConfigWithTokenBucketEquality(configUpdates[0].rateLimiterConfig, bucket1); + assertEq(BLOCK_TIME, bucket1.lastUpdated); + } + + function test_SingleConfigOutbound_Success() public { + MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = + new MultiAggregateRateLimiter.RateLimiterConfigArgs[](1); + configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ + remoteChainSelector: CHAIN_SELECTOR_1 + 1, + isOutboundLane: true, + rateLimiterConfig: s_rateLimiterConfig2 + }); + + vm.expectEmit(); + emit MultiAggregateRateLimiter.RateLimiterConfigUpdated( + configUpdates[0].remoteChainSelector, true, configUpdates[0].rateLimiterConfig + ); + + vm.recordLogs(); + s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); + + Vm.Log[] memory logEntries = vm.getRecordedLogs(); + assertEq(logEntries.length, 1); + + RateLimiter.TokenBucket memory bucket1 = + s_rateLimiter.currentRateLimiterState(configUpdates[0].remoteChainSelector, true); + _assertConfigWithTokenBucketEquality(configUpdates[0].rateLimiterConfig, bucket1); + assertEq(BLOCK_TIME, bucket1.lastUpdated); + } + + function test_MultipleConfigs_Success() public { + MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = + new MultiAggregateRateLimiter.RateLimiterConfigArgs[](5); + + for (uint64 i; i < configUpdates.length; ++i) { + configUpdates[i] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ + remoteChainSelector: CHAIN_SELECTOR_1 + i + 1, + isOutboundLane: i % 2 == 0 ? false : true, + rateLimiterConfig: RateLimiter.Config({isEnabled: true, rate: 5 + i, capacity: 100 + i}) + }); + + vm.expectEmit(); + emit MultiAggregateRateLimiter.RateLimiterConfigUpdated( + configUpdates[i].remoteChainSelector, configUpdates[i].isOutboundLane, configUpdates[i].rateLimiterConfig + ); + } + + vm.recordLogs(); + s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); + + Vm.Log[] memory logEntries = vm.getRecordedLogs(); + assertEq(logEntries.length, configUpdates.length); + + for (uint256 i; i < configUpdates.length; ++i) { + RateLimiter.TokenBucket memory bucket = + s_rateLimiter.currentRateLimiterState(configUpdates[i].remoteChainSelector, configUpdates[i].isOutboundLane); + _assertConfigWithTokenBucketEquality(configUpdates[i].rateLimiterConfig, bucket); + assertEq(BLOCK_TIME, bucket.lastUpdated); + } + } + + function test_MultipleConfigsBothLanes_Success() public { + MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = + new MultiAggregateRateLimiter.RateLimiterConfigArgs[](2); + + for (uint64 i; i < configUpdates.length; ++i) { + configUpdates[i] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ + remoteChainSelector: CHAIN_SELECTOR_1 + 1, + isOutboundLane: i % 2 == 0 ? false : true, + rateLimiterConfig: RateLimiter.Config({isEnabled: true, rate: 5 + i, capacity: 100 + i}) + }); + + vm.expectEmit(); + emit MultiAggregateRateLimiter.RateLimiterConfigUpdated( + configUpdates[i].remoteChainSelector, configUpdates[i].isOutboundLane, configUpdates[i].rateLimiterConfig + ); + } + + vm.recordLogs(); + s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); + + Vm.Log[] memory logEntries = vm.getRecordedLogs(); + assertEq(logEntries.length, configUpdates.length); + + for (uint256 i; i < configUpdates.length; ++i) { + RateLimiter.TokenBucket memory bucket = + s_rateLimiter.currentRateLimiterState(configUpdates[i].remoteChainSelector, configUpdates[i].isOutboundLane); + _assertConfigWithTokenBucketEquality(configUpdates[i].rateLimiterConfig, bucket); + assertEq(BLOCK_TIME, bucket.lastUpdated); + } + } + + function test_UpdateExistingConfig_Success() public { + MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = + new MultiAggregateRateLimiter.RateLimiterConfigArgs[](1); + configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ + remoteChainSelector: CHAIN_SELECTOR_1, + isOutboundLane: false, + rateLimiterConfig: s_rateLimiterConfig2 + }); + + RateLimiter.TokenBucket memory bucket1 = + s_rateLimiter.currentRateLimiterState(configUpdates[0].remoteChainSelector, false); + + // Capacity equals tokens + assertEq(bucket1.capacity, bucket1.tokens); + + vm.expectEmit(); + emit MultiAggregateRateLimiter.RateLimiterConfigUpdated( + configUpdates[0].remoteChainSelector, false, configUpdates[0].rateLimiterConfig + ); + + vm.recordLogs(); + s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); + + vm.warp(BLOCK_TIME + 1); + bucket1 = s_rateLimiter.currentRateLimiterState(configUpdates[0].remoteChainSelector, false); + assertEq(BLOCK_TIME + 1, bucket1.lastUpdated); + + // Tokens < capacity since capacity doubled + assertTrue(bucket1.capacity != bucket1.tokens); + + // Outbound lane config remains unchanged + _assertConfigWithTokenBucketEquality( + s_rateLimiterConfig1, s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, true) + ); + } + + function test_UpdateExistingConfigWithNoDifference_Success() public { + MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = + new MultiAggregateRateLimiter.RateLimiterConfigArgs[](1); + configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ + remoteChainSelector: CHAIN_SELECTOR_1, + isOutboundLane: false, + rateLimiterConfig: s_rateLimiterConfig1 + }); + + RateLimiter.TokenBucket memory bucketPreUpdate = + s_rateLimiter.currentRateLimiterState(configUpdates[0].remoteChainSelector, false); + + vm.expectEmit(); + emit MultiAggregateRateLimiter.RateLimiterConfigUpdated( + configUpdates[0].remoteChainSelector, false, configUpdates[0].rateLimiterConfig + ); + + vm.recordLogs(); + s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); + + vm.warp(BLOCK_TIME + 1); + RateLimiter.TokenBucket memory bucketPostUpdate = + s_rateLimiter.currentRateLimiterState(configUpdates[0].remoteChainSelector, false); + _assertTokenBucketEquality(bucketPreUpdate, bucketPostUpdate); + assertEq(BLOCK_TIME + 1, bucketPostUpdate.lastUpdated); + } + + // Reverts + function test_ZeroChainSelector_Revert() public { + MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = + new MultiAggregateRateLimiter.RateLimiterConfigArgs[](1); + configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ + remoteChainSelector: 0, + isOutboundLane: false, + rateLimiterConfig: s_rateLimiterConfig1 + }); + + vm.expectRevert(MultiAggregateRateLimiter.ZeroChainSelectorNotAllowed.selector); + s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); + } + + function test_OnlyCallableByOwner_Revert() public { + MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = + new MultiAggregateRateLimiter.RateLimiterConfigArgs[](1); + configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ + remoteChainSelector: CHAIN_SELECTOR_1 + 1, + isOutboundLane: false, + rateLimiterConfig: s_rateLimiterConfig1 + }); + vm.startPrank(STRANGER); + + vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); + s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); + } + + function test_ConfigRateMoreThanCapacity_Revert() public { + MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = + new MultiAggregateRateLimiter.RateLimiterConfigArgs[](1); + configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ + remoteChainSelector: CHAIN_SELECTOR_1 + 1, + isOutboundLane: false, + rateLimiterConfig: RateLimiter.Config({isEnabled: true, rate: 100, capacity: 100}) + }); + + vm.expectRevert( + abi.encodeWithSelector(RateLimiter.InvalidRateLimitRate.selector, configUpdates[0].rateLimiterConfig) + ); + s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); + } + + function test_ConfigRateZero_Revert() public { + MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = + new MultiAggregateRateLimiter.RateLimiterConfigArgs[](1); + configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ + remoteChainSelector: CHAIN_SELECTOR_1 + 1, + isOutboundLane: false, + rateLimiterConfig: RateLimiter.Config({isEnabled: true, rate: 0, capacity: 100}) + }); + + vm.expectRevert( + abi.encodeWithSelector(RateLimiter.InvalidRateLimitRate.selector, configUpdates[0].rateLimiterConfig) + ); + s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); + } + + function test_DisableConfigRateNonZero_Revert() public { + MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = + new MultiAggregateRateLimiter.RateLimiterConfigArgs[](1); + configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ + remoteChainSelector: CHAIN_SELECTOR_1 + 1, + isOutboundLane: false, + rateLimiterConfig: RateLimiter.Config({isEnabled: false, rate: 5, capacity: 100}) + }); + + vm.expectRevert( + abi.encodeWithSelector(RateLimiter.DisabledNonZeroRateLimit.selector, configUpdates[0].rateLimiterConfig) + ); + s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); + } + + function test_DiableConfigCapacityNonZero_Revert() public { + MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = + new MultiAggregateRateLimiter.RateLimiterConfigArgs[](1); + configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ + remoteChainSelector: CHAIN_SELECTOR_1 + 1, + isOutboundLane: false, + rateLimiterConfig: RateLimiter.Config({isEnabled: false, rate: 0, capacity: 100}) + }); + + vm.expectRevert( + abi.encodeWithSelector(RateLimiter.DisabledNonZeroRateLimit.selector, configUpdates[0].rateLimiterConfig) + ); + s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); + } +} diff --git a/contracts/src/v0.8/ccip/test/rateLimiter/MutiAggregateRateLimiter/MultiAggregateRateLimiter_constructor.t.sol b/contracts/src/v0.8/ccip/test/rateLimiter/MutiAggregateRateLimiter/MultiAggregateRateLimiter_constructor.t.sol new file mode 100644 index 00000000000..0f858a79a56 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/rateLimiter/MutiAggregateRateLimiter/MultiAggregateRateLimiter_constructor.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {MultiAggregateRateLimiter} from "../../../MultiAggregateRateLimiter.sol"; +import {MultiAggregateRateLimiterHelper} from "../../helpers/MultiAggregateRateLimiterHelper.sol"; +import {MultiAggregateRateLimiterSetup} from "./MultiAggregateRateLimiterSetup.t.sol"; +import {Vm} from "forge-std/Vm.sol"; + +contract MultiAggregateRateLimiter_constructor is MultiAggregateRateLimiterSetup { + function test_ConstructorNoAuthorizedCallers_Success() public { + address[] memory authorizedCallers = new address[](0); + + vm.recordLogs(); + s_rateLimiter = new MultiAggregateRateLimiterHelper(address(s_feeQuoter), authorizedCallers); + + // FeeQuoterSet + Vm.Log[] memory logEntries = vm.getRecordedLogs(); + assertEq(logEntries.length, 1); + + assertEq(OWNER, s_rateLimiter.owner()); + assertEq(address(s_feeQuoter), s_rateLimiter.getFeeQuoter()); + } + + function test_Constructor_Success() public { + address[] memory authorizedCallers = new address[](2); + authorizedCallers[0] = MOCK_OFFRAMP; + authorizedCallers[1] = MOCK_ONRAMP; + + vm.expectEmit(); + emit MultiAggregateRateLimiter.FeeQuoterSet(address(s_feeQuoter)); + + s_rateLimiter = new MultiAggregateRateLimiterHelper(address(s_feeQuoter), authorizedCallers); + + assertEq(OWNER, s_rateLimiter.owner()); + assertEq(address(s_feeQuoter), s_rateLimiter.getFeeQuoter()); + assertEq(s_rateLimiter.typeAndVersion(), "MultiAggregateRateLimiter 1.6.0-dev"); + } +} diff --git a/contracts/src/v0.8/ccip/test/rateLimiter/MutiAggregateRateLimiter/MultiAggregateRateLimiter_getTokenBucket.t.sol b/contracts/src/v0.8/ccip/test/rateLimiter/MutiAggregateRateLimiter/MultiAggregateRateLimiter_getTokenBucket.t.sol new file mode 100644 index 00000000000..bfb5da07da3 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/rateLimiter/MutiAggregateRateLimiter/MultiAggregateRateLimiter_getTokenBucket.t.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {MultiAggregateRateLimiter} from "../../../MultiAggregateRateLimiter.sol"; +import {RateLimiter} from "../../../libraries/RateLimiter.sol"; + +import {MultiAggregateRateLimiterSetup} from "./MultiAggregateRateLimiterSetup.t.sol"; +import {stdError} from "forge-std/Test.sol"; + +contract MultiAggregateRateLimiter_getTokenBucket is MultiAggregateRateLimiterSetup { + function test_GetTokenBucket_Success() public view { + RateLimiter.TokenBucket memory bucketInbound = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, false); + _assertConfigWithTokenBucketEquality(s_rateLimiterConfig1, bucketInbound); + assertEq(BLOCK_TIME, bucketInbound.lastUpdated); + + RateLimiter.TokenBucket memory bucketOutbound = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, true); + _assertConfigWithTokenBucketEquality(s_rateLimiterConfig1, bucketOutbound); + assertEq(BLOCK_TIME, bucketOutbound.lastUpdated); + } + + function test_Refill_Success() public { + s_rateLimiterConfig1.capacity = s_rateLimiterConfig1.capacity * 2; + + MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = + new MultiAggregateRateLimiter.RateLimiterConfigArgs[](1); + configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ + remoteChainSelector: CHAIN_SELECTOR_1, + isOutboundLane: false, + rateLimiterConfig: s_rateLimiterConfig1 + }); + + s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); + + RateLimiter.TokenBucket memory bucket = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, false); + + assertEq(s_rateLimiterConfig1.rate, bucket.rate); + assertEq(s_rateLimiterConfig1.capacity, bucket.capacity); + assertEq(s_rateLimiterConfig1.capacity / 2, bucket.tokens); + assertEq(BLOCK_TIME, bucket.lastUpdated); + + uint256 warpTime = 4; + vm.warp(BLOCK_TIME + warpTime); + + bucket = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, false); + + assertEq(s_rateLimiterConfig1.rate, bucket.rate); + assertEq(s_rateLimiterConfig1.capacity, bucket.capacity); + assertEq(s_rateLimiterConfig1.capacity / 2 + warpTime * s_rateLimiterConfig1.rate, bucket.tokens); + assertEq(BLOCK_TIME + warpTime, bucket.lastUpdated); + + vm.warp(BLOCK_TIME + warpTime * 100); + + // Bucket overflow + bucket = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, false); + assertEq(s_rateLimiterConfig1.capacity, bucket.tokens); + } + + // Reverts + + function test_TimeUnderflow_Revert() public { + vm.warp(BLOCK_TIME - 1); + + vm.expectRevert(stdError.arithmeticError); + s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, false); + } +} diff --git a/contracts/src/v0.8/ccip/test/rateLimiter/MutiAggregateRateLimiter/MultiAggregateRateLimiter_getTokenValue.t.sol b/contracts/src/v0.8/ccip/test/rateLimiter/MutiAggregateRateLimiter/MultiAggregateRateLimiter_getTokenValue.t.sol new file mode 100644 index 00000000000..9b4448339e8 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/rateLimiter/MutiAggregateRateLimiter/MultiAggregateRateLimiter_getTokenValue.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {MultiAggregateRateLimiter} from "../../../MultiAggregateRateLimiter.sol"; +import {Client} from "../../../libraries/Client.sol"; +import {MultiAggregateRateLimiterSetup} from "./MultiAggregateRateLimiterSetup.t.sol"; + +contract MultiAggregateRateLimiter_getTokenValue is MultiAggregateRateLimiterSetup { + function test_GetTokenValue_Success() public view { + uint256 numberOfTokens = 10; + Client.EVMTokenAmount memory tokenAmount = Client.EVMTokenAmount({token: TOKEN, amount: 10}); + uint256 value = s_rateLimiter.getTokenValue(tokenAmount); + assertEq(value, (numberOfTokens * TOKEN_PRICE) / 1e18); + } + + // Reverts + function test_NoTokenPrice_Reverts() public { + address tokenWithNoPrice = makeAddr("Token with no price"); + Client.EVMTokenAmount memory tokenAmount = Client.EVMTokenAmount({token: tokenWithNoPrice, amount: 10}); + + vm.expectRevert(abi.encodeWithSelector(MultiAggregateRateLimiter.PriceNotFoundForToken.selector, tokenWithNoPrice)); + s_rateLimiter.getTokenValue(tokenAmount); + } +} diff --git a/contracts/src/v0.8/ccip/test/rateLimiter/MutiAggregateRateLimiter/MultiAggregateRateLimiter_onInboundMessage.t.sol b/contracts/src/v0.8/ccip/test/rateLimiter/MutiAggregateRateLimiter/MultiAggregateRateLimiter_onInboundMessage.t.sol new file mode 100644 index 00000000000..8697dae871e --- /dev/null +++ b/contracts/src/v0.8/ccip/test/rateLimiter/MutiAggregateRateLimiter/MultiAggregateRateLimiter_onInboundMessage.t.sol @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {AuthorizedCallers} from "../../../../shared/access/AuthorizedCallers.sol"; +import {MultiAggregateRateLimiter} from "../../../MultiAggregateRateLimiter.sol"; +import {Client} from "../../../libraries/Client.sol"; +import {Internal} from "../../../libraries/Internal.sol"; +import {RateLimiter} from "../../../libraries/RateLimiter.sol"; + +import {MultiAggregateRateLimiterSetup} from "./MultiAggregateRateLimiterSetup.t.sol"; +import {Vm} from "forge-std/Vm.sol"; + +contract MultiAggregateRateLimiter_onInboundMessage is MultiAggregateRateLimiterSetup { + address internal constant MOCK_RECEIVER = address(1113); + + function setUp() public virtual override { + super.setUp(); + + MultiAggregateRateLimiter.RateLimitTokenArgs[] memory tokensToAdd = + new MultiAggregateRateLimiter.RateLimitTokenArgs[](s_sourceTokens.length); + for (uint224 i = 0; i < s_sourceTokens.length; ++i) { + tokensToAdd[i] = MultiAggregateRateLimiter.RateLimitTokenArgs({ + localTokenArgs: MultiAggregateRateLimiter.LocalRateLimitToken({ + remoteChainSelector: CHAIN_SELECTOR_1, + localToken: s_destTokens[i] + }), + remoteToken: abi.encode(s_sourceTokens[i]) + }); + + Internal.PriceUpdates memory priceUpdates = + _getSingleTokenPriceUpdateStruct(s_destTokens[i], TOKEN_PRICE * (i + 1)); + s_feeQuoter.updatePrices(priceUpdates); + } + s_rateLimiter.updateRateLimitTokens(new MultiAggregateRateLimiter.LocalRateLimitToken[](0), tokensToAdd); + } + + function test_ValidateMessageWithNoTokens_Success() public { + vm.startPrank(MOCK_OFFRAMP); + + vm.recordLogs(); + s_rateLimiter.onInboundMessage(_generateAny2EVMMessageNoTokens(CHAIN_SELECTOR_1)); + + // No consumed rate limit events + Vm.Log[] memory logEntries = vm.getRecordedLogs(); + assertEq(logEntries.length, 0); + } + + function test_ValidateMessageWithTokens_Success() public { + vm.startPrank(MOCK_OFFRAMP); + + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); + tokenAmounts[0] = Client.EVMTokenAmount({token: s_destTokens[0], amount: 3}); + tokenAmounts[1] = Client.EVMTokenAmount({token: s_destTokens[1], amount: 1}); + + // 3 tokens * TOKEN_PRICE + 1 token * (2 * TOKEN_PRICE) + vm.expectEmit(); + emit RateLimiter.TokensConsumed((5 * TOKEN_PRICE) / 1e18); + + s_rateLimiter.onInboundMessage(_generateAny2EVMMessage(CHAIN_SELECTOR_1, tokenAmounts)); + } + + function test_ValidateMessageWithDisabledRateLimitToken_Success() public { + MultiAggregateRateLimiter.LocalRateLimitToken[] memory removes = + new MultiAggregateRateLimiter.LocalRateLimitToken[](1); + removes[0] = MultiAggregateRateLimiter.LocalRateLimitToken({ + remoteChainSelector: CHAIN_SELECTOR_1, + localToken: s_destTokens[1] + }); + s_rateLimiter.updateRateLimitTokens(removes, new MultiAggregateRateLimiter.RateLimitTokenArgs[](0)); + + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); + tokenAmounts[0] = Client.EVMTokenAmount({token: s_destTokens[0], amount: 5}); + tokenAmounts[1] = Client.EVMTokenAmount({token: s_destTokens[1], amount: 1}); + + vm.startPrank(MOCK_OFFRAMP); + + vm.expectEmit(); + emit RateLimiter.TokensConsumed((5 * TOKEN_PRICE) / 1e18); + + s_rateLimiter.onInboundMessage(_generateAny2EVMMessage(CHAIN_SELECTOR_1, tokenAmounts)); + } + + function test_ValidateMessageWithRateLimitDisabled_Success() public { + MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = + new MultiAggregateRateLimiter.RateLimiterConfigArgs[](1); + configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ + remoteChainSelector: CHAIN_SELECTOR_1, + isOutboundLane: false, + rateLimiterConfig: s_rateLimiterConfig1 + }); + configUpdates[0].rateLimiterConfig.isEnabled = false; + + s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); + + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); + tokenAmounts[0] = Client.EVMTokenAmount({token: s_destTokens[0], amount: 1000}); + tokenAmounts[1] = Client.EVMTokenAmount({token: s_destTokens[1], amount: 50}); + + vm.startPrank(MOCK_OFFRAMP); + s_rateLimiter.onInboundMessage(_generateAny2EVMMessage(CHAIN_SELECTOR_1, tokenAmounts)); + + // No consumed rate limit events + Vm.Log[] memory logEntries = vm.getRecordedLogs(); + assertEq(logEntries.length, 0); + } + + function test_ValidateMessageWithTokensOnDifferentChains_Success() public { + MultiAggregateRateLimiter.RateLimitTokenArgs[] memory tokensToAdd = + new MultiAggregateRateLimiter.RateLimitTokenArgs[](s_sourceTokens.length); + for (uint224 i = 0; i < s_sourceTokens.length; ++i) { + tokensToAdd[i] = MultiAggregateRateLimiter.RateLimitTokenArgs({ + localTokenArgs: MultiAggregateRateLimiter.LocalRateLimitToken({ + remoteChainSelector: CHAIN_SELECTOR_2, + localToken: s_destTokens[i] + }), + // Create a remote token address that is different from CHAIN_SELECTOR_1 + remoteToken: abi.encode(uint256(uint160(s_sourceTokens[i])) + type(uint160).max + 1) + }); + } + s_rateLimiter.updateRateLimitTokens(new MultiAggregateRateLimiter.LocalRateLimitToken[](0), tokensToAdd); + + vm.startPrank(MOCK_OFFRAMP); + + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); + tokenAmounts[0] = Client.EVMTokenAmount({token: s_destTokens[0], amount: 2}); + tokenAmounts[1] = Client.EVMTokenAmount({token: s_destTokens[1], amount: 1}); + + // 2 tokens * (TOKEN_PRICE) + 1 token * (2 * TOKEN_PRICE) + uint256 totalValue = (4 * TOKEN_PRICE) / 1e18; + + s_rateLimiter.onInboundMessage(_generateAny2EVMMessage(CHAIN_SELECTOR_1, tokenAmounts)); + + // Chain 1 changed + RateLimiter.TokenBucket memory bucketChain1 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, false); + assertEq(bucketChain1.capacity - totalValue, bucketChain1.tokens); + + // Chain 2 unchanged + RateLimiter.TokenBucket memory bucketChain2 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_2, false); + assertEq(bucketChain2.capacity, bucketChain2.tokens); + + vm.expectEmit(); + emit RateLimiter.TokensConsumed(totalValue); + + s_rateLimiter.onInboundMessage(_generateAny2EVMMessage(CHAIN_SELECTOR_2, tokenAmounts)); + + // Chain 1 unchanged + bucketChain1 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, false); + assertEq(bucketChain1.capacity - totalValue, bucketChain1.tokens); + + // Chain 2 changed + bucketChain2 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_2, false); + assertEq(bucketChain2.capacity - totalValue, bucketChain2.tokens); + } + + function test_ValidateMessageWithDifferentTokensOnDifferentChains_Success() public { + MultiAggregateRateLimiter.RateLimitTokenArgs[] memory tokensToAdd = + new MultiAggregateRateLimiter.RateLimitTokenArgs[](1); + + // Only 1 rate limited token on different chain + tokensToAdd[0] = MultiAggregateRateLimiter.RateLimitTokenArgs({ + localTokenArgs: MultiAggregateRateLimiter.LocalRateLimitToken({ + remoteChainSelector: CHAIN_SELECTOR_2, + localToken: s_destTokens[0] + }), + // Create a remote token address that is different from CHAIN_SELECTOR_1 + remoteToken: abi.encode(uint256(uint160(s_sourceTokens[0])) + type(uint160).max + 1) + }); + s_rateLimiter.updateRateLimitTokens(new MultiAggregateRateLimiter.LocalRateLimitToken[](0), tokensToAdd); + + vm.startPrank(MOCK_OFFRAMP); + + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); + tokenAmounts[0] = Client.EVMTokenAmount({token: s_destTokens[0], amount: 3}); + tokenAmounts[1] = Client.EVMTokenAmount({token: s_destTokens[1], amount: 1}); + + // 3 tokens * (TOKEN_PRICE) + 1 token * (2 * TOKEN_PRICE) + uint256 totalValue = (5 * TOKEN_PRICE) / 1e18; + + s_rateLimiter.onInboundMessage(_generateAny2EVMMessage(CHAIN_SELECTOR_1, tokenAmounts)); + + // Chain 1 changed + RateLimiter.TokenBucket memory bucketChain1 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, false); + assertEq(bucketChain1.capacity - totalValue, bucketChain1.tokens); + + // Chain 2 unchanged + RateLimiter.TokenBucket memory bucketChain2 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_2, false); + assertEq(bucketChain2.capacity, bucketChain2.tokens); + + // 3 tokens * (TOKEN_PRICE) + uint256 totalValue2 = (3 * TOKEN_PRICE) / 1e18; + + vm.expectEmit(); + emit RateLimiter.TokensConsumed(totalValue2); + + s_rateLimiter.onInboundMessage(_generateAny2EVMMessage(CHAIN_SELECTOR_2, tokenAmounts)); + + // Chain 1 unchanged + bucketChain1 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, false); + assertEq(bucketChain1.capacity - totalValue, bucketChain1.tokens); + + // Chain 2 changed + bucketChain2 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_2, false); + assertEq(bucketChain2.capacity - totalValue2, bucketChain2.tokens); + } + + function test_ValidateMessageWithRateLimitReset_Success() public { + vm.startPrank(MOCK_OFFRAMP); + + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); + tokenAmounts[0] = Client.EVMTokenAmount({token: s_destTokens[0], amount: 20}); + + // Remaining capacity: 100 -> 20 + s_rateLimiter.onInboundMessage(_generateAny2EVMMessage(CHAIN_SELECTOR_1, tokenAmounts)); + + // Cannot fit 80 rate limit value (need to wait at least 12 blocks, current capacity is 20) + vm.expectRevert(abi.encodeWithSelector(RateLimiter.AggregateValueRateLimitReached.selector, 12, 20)); + s_rateLimiter.onInboundMessage(_generateAny2EVMMessage(CHAIN_SELECTOR_1, tokenAmounts)); + + // Remaining capacity: 20 -> 35 (need to wait 9 more blocks) + vm.warp(BLOCK_TIME + 3); + vm.expectRevert(abi.encodeWithSelector(RateLimiter.AggregateValueRateLimitReached.selector, 9, 35)); + s_rateLimiter.onInboundMessage(_generateAny2EVMMessage(CHAIN_SELECTOR_1, tokenAmounts)); + + // Remaining capacity: 35 -> 80 (can fit exactly 80) + vm.warp(BLOCK_TIME + 12); + s_rateLimiter.onInboundMessage(_generateAny2EVMMessage(CHAIN_SELECTOR_1, tokenAmounts)); + } + + // Reverts + + function test_ValidateMessageWithRateLimitExceeded_Revert() public { + vm.startPrank(MOCK_OFFRAMP); + + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); + tokenAmounts[0] = Client.EVMTokenAmount({token: s_destTokens[0], amount: 80}); + tokenAmounts[1] = Client.EVMTokenAmount({token: s_destTokens[1], amount: 30}); + + uint256 totalValue = (80 * TOKEN_PRICE + 2 * (30 * TOKEN_PRICE)) / 1e18; + vm.expectRevert(abi.encodeWithSelector(RateLimiter.AggregateValueMaxCapacityExceeded.selector, 100, totalValue)); + s_rateLimiter.onInboundMessage(_generateAny2EVMMessage(CHAIN_SELECTOR_1, tokenAmounts)); + } + + function test_ValidateMessageFromUnauthorizedCaller_Revert() public { + vm.startPrank(STRANGER); + + vm.expectRevert(abi.encodeWithSelector(AuthorizedCallers.UnauthorizedCaller.selector, STRANGER)); + s_rateLimiter.onInboundMessage(_generateAny2EVMMessageNoTokens(CHAIN_SELECTOR_1)); + } +} diff --git a/contracts/src/v0.8/ccip/test/rateLimiter/MutiAggregateRateLimiter/MultiAggregateRateLimiter_onOutboundMessage.t.sol b/contracts/src/v0.8/ccip/test/rateLimiter/MutiAggregateRateLimiter/MultiAggregateRateLimiter_onOutboundMessage.t.sol new file mode 100644 index 00000000000..9d20e203619 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/rateLimiter/MutiAggregateRateLimiter/MultiAggregateRateLimiter_onOutboundMessage.t.sol @@ -0,0 +1,303 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {AuthorizedCallers} from "../../../../shared/access/AuthorizedCallers.sol"; +import {MultiAggregateRateLimiter} from "../../../MultiAggregateRateLimiter.sol"; +import {Client} from "../../../libraries/Client.sol"; +import {Internal} from "../../../libraries/Internal.sol"; +import {RateLimiter} from "../../../libraries/RateLimiter.sol"; + +import {MultiAggregateRateLimiterSetup} from "./MultiAggregateRateLimiterSetup.t.sol"; + +contract MultiAggregateRateLimiter_onOutboundMessage is MultiAggregateRateLimiterSetup { + function setUp() public virtual override { + super.setUp(); + + MultiAggregateRateLimiter.RateLimitTokenArgs[] memory tokensToAdd = + new MultiAggregateRateLimiter.RateLimitTokenArgs[](s_sourceTokens.length); + for (uint224 i = 0; i < s_sourceTokens.length; ++i) { + tokensToAdd[i] = MultiAggregateRateLimiter.RateLimitTokenArgs({ + localTokenArgs: MultiAggregateRateLimiter.LocalRateLimitToken({ + remoteChainSelector: CHAIN_SELECTOR_1, + localToken: s_sourceTokens[i] + }), + remoteToken: abi.encode(bytes20(s_destTokenBySourceToken[s_sourceTokens[i]])) + }); + + Internal.PriceUpdates memory priceUpdates = + _getSingleTokenPriceUpdateStruct(s_sourceTokens[i], TOKEN_PRICE * (i + 1)); + s_feeQuoter.updatePrices(priceUpdates); + } + s_rateLimiter.updateRateLimitTokens(new MultiAggregateRateLimiter.LocalRateLimitToken[](0), tokensToAdd); + } + + function test_ValidateMessageWithNoTokens_Success() public { + vm.startPrank(MOCK_ONRAMP); + + vm.recordLogs(); + s_rateLimiter.onOutboundMessage(CHAIN_SELECTOR_1, _generateEVM2AnyMessageNoTokens()); + + // No consumed rate limit events + assertEq(vm.getRecordedLogs().length, 0); + } + + function test_onOutboundMessage_ValidateMessageWithTokens_Success() public { + vm.startPrank(MOCK_ONRAMP); + + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); + tokenAmounts[0] = Client.EVMTokenAmount({token: s_sourceTokens[0], amount: 3}); + tokenAmounts[1] = Client.EVMTokenAmount({token: s_sourceTokens[1], amount: 1}); + + // 3 tokens * TOKEN_PRICE + 1 token * (2 * TOKEN_PRICE) + vm.expectEmit(); + emit RateLimiter.TokensConsumed((5 * TOKEN_PRICE) / 1e18); + + s_rateLimiter.onOutboundMessage(CHAIN_SELECTOR_1, _generateEVM2AnyMessage(tokenAmounts)); + } + + function test_onOutboundMessage_ValidateMessageWithDisabledRateLimitToken_Success() public { + MultiAggregateRateLimiter.LocalRateLimitToken[] memory removes = + new MultiAggregateRateLimiter.LocalRateLimitToken[](1); + removes[0] = MultiAggregateRateLimiter.LocalRateLimitToken({ + remoteChainSelector: CHAIN_SELECTOR_1, + localToken: s_sourceTokens[1] + }); + s_rateLimiter.updateRateLimitTokens(removes, new MultiAggregateRateLimiter.RateLimitTokenArgs[](0)); + + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); + tokenAmounts[0] = Client.EVMTokenAmount({token: s_sourceTokens[0], amount: 5}); + tokenAmounts[1] = Client.EVMTokenAmount({token: s_sourceTokens[1], amount: 1}); + + vm.startPrank(MOCK_ONRAMP); + + vm.expectEmit(); + emit RateLimiter.TokensConsumed((5 * TOKEN_PRICE) / 1e18); + + s_rateLimiter.onOutboundMessage(CHAIN_SELECTOR_1, _generateEVM2AnyMessage(tokenAmounts)); + } + + function test_onOutboundMessage_ValidateMessageWithRateLimitDisabled_Success() public { + MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = + new MultiAggregateRateLimiter.RateLimiterConfigArgs[](1); + configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ + remoteChainSelector: CHAIN_SELECTOR_1, + isOutboundLane: true, + rateLimiterConfig: s_rateLimiterConfig1 + }); + configUpdates[0].rateLimiterConfig.isEnabled = false; + + s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); + + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); + tokenAmounts[0] = Client.EVMTokenAmount({token: s_sourceTokens[0], amount: 1000}); + tokenAmounts[1] = Client.EVMTokenAmount({token: s_sourceTokens[1], amount: 50}); + + vm.startPrank(MOCK_ONRAMP); + s_rateLimiter.onOutboundMessage(CHAIN_SELECTOR_1, _generateEVM2AnyMessage(tokenAmounts)); + + // No consumed rate limit events + assertEq(vm.getRecordedLogs().length, 0); + } + + function test_onOutboundMessage_ValidateMessageWithTokensOnDifferentChains_Success() public { + MultiAggregateRateLimiter.RateLimitTokenArgs[] memory tokensToAdd = + new MultiAggregateRateLimiter.RateLimitTokenArgs[](s_sourceTokens.length); + for (uint224 i = 0; i < s_sourceTokens.length; ++i) { + tokensToAdd[i] = MultiAggregateRateLimiter.RateLimitTokenArgs({ + localTokenArgs: MultiAggregateRateLimiter.LocalRateLimitToken({ + remoteChainSelector: CHAIN_SELECTOR_2, + localToken: s_sourceTokens[i] + }), + // Create a remote token address that is different from CHAIN_SELECTOR_1 + remoteToken: abi.encode(uint256(uint160(s_destTokenBySourceToken[s_sourceTokens[i]])) + type(uint160).max + 1) + }); + } + s_rateLimiter.updateRateLimitTokens(new MultiAggregateRateLimiter.LocalRateLimitToken[](0), tokensToAdd); + + vm.startPrank(MOCK_ONRAMP); + + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); + tokenAmounts[0] = Client.EVMTokenAmount({token: s_sourceTokens[0], amount: 2}); + tokenAmounts[1] = Client.EVMTokenAmount({token: s_sourceTokens[1], amount: 1}); + + // 2 tokens * (TOKEN_PRICE) + 1 token * (2 * TOKEN_PRICE) + uint256 totalValue = (4 * TOKEN_PRICE) / 1e18; + + s_rateLimiter.onOutboundMessage(CHAIN_SELECTOR_1, _generateEVM2AnyMessage(tokenAmounts)); + + // Chain 1 changed + RateLimiter.TokenBucket memory bucketChain1 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, true); + assertEq(bucketChain1.capacity - totalValue, bucketChain1.tokens); + + // Chain 2 unchanged + RateLimiter.TokenBucket memory bucketChain2 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_2, true); + assertEq(bucketChain2.capacity, bucketChain2.tokens); + + vm.expectEmit(); + emit RateLimiter.TokensConsumed(totalValue); + + s_rateLimiter.onOutboundMessage(CHAIN_SELECTOR_2, _generateEVM2AnyMessage(tokenAmounts)); + + // Chain 1 unchanged + bucketChain1 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, true); + assertEq(bucketChain1.capacity - totalValue, bucketChain1.tokens); + + // Chain 2 changed + bucketChain2 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_2, true); + assertEq(bucketChain2.capacity - totalValue, bucketChain2.tokens); + } + + function test_onOutboundMessage_ValidateMessageWithDifferentTokensOnDifferentChains_Success() public { + MultiAggregateRateLimiter.RateLimitTokenArgs[] memory tokensToAdd = + new MultiAggregateRateLimiter.RateLimitTokenArgs[](1); + + // Only 1 rate limited token on different chain + tokensToAdd[0] = MultiAggregateRateLimiter.RateLimitTokenArgs({ + localTokenArgs: MultiAggregateRateLimiter.LocalRateLimitToken({ + remoteChainSelector: CHAIN_SELECTOR_2, + localToken: s_sourceTokens[0] + }), + // Create a remote token address that is different from CHAIN_SELECTOR_1 + remoteToken: abi.encode(uint256(uint160(s_destTokenBySourceToken[s_sourceTokens[0]])) + type(uint160).max + 1) + }); + s_rateLimiter.updateRateLimitTokens(new MultiAggregateRateLimiter.LocalRateLimitToken[](0), tokensToAdd); + + vm.startPrank(MOCK_ONRAMP); + + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); + tokenAmounts[0] = Client.EVMTokenAmount({token: s_sourceTokens[0], amount: 3}); + tokenAmounts[1] = Client.EVMTokenAmount({token: s_sourceTokens[1], amount: 1}); + + // 3 tokens * (TOKEN_PRICE) + 1 token * (2 * TOKEN_PRICE) + uint256 totalValue = (5 * TOKEN_PRICE) / 1e18; + + s_rateLimiter.onOutboundMessage(CHAIN_SELECTOR_1, _generateEVM2AnyMessage(tokenAmounts)); + + // Chain 1 changed + RateLimiter.TokenBucket memory bucketChain1 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, true); + assertEq(bucketChain1.capacity - totalValue, bucketChain1.tokens); + + // Chain 2 unchanged + RateLimiter.TokenBucket memory bucketChain2 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_2, true); + assertEq(bucketChain2.capacity, bucketChain2.tokens); + + // 3 tokens * (TOKEN_PRICE) + uint256 totalValue2 = (3 * TOKEN_PRICE) / 1e18; + + vm.expectEmit(); + emit RateLimiter.TokensConsumed(totalValue2); + + s_rateLimiter.onOutboundMessage(CHAIN_SELECTOR_2, _generateEVM2AnyMessage(tokenAmounts)); + + // Chain 1 unchanged + bucketChain1 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, true); + assertEq(bucketChain1.capacity - totalValue, bucketChain1.tokens); + + // Chain 2 changed + bucketChain2 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_2, true); + assertEq(bucketChain2.capacity - totalValue2, bucketChain2.tokens); + } + + function test_onOutboundMessage_ValidateMessageWithRateLimitReset_Success() public { + vm.startPrank(MOCK_ONRAMP); + + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); + tokenAmounts[0] = Client.EVMTokenAmount({token: s_sourceTokens[0], amount: 20}); + + // Remaining capacity: 100 -> 20 + s_rateLimiter.onOutboundMessage(CHAIN_SELECTOR_1, _generateEVM2AnyMessage(tokenAmounts)); + + // Cannot fit 80 rate limit value (need to wait at least 12 blocks, current capacity is 20) + vm.expectRevert(abi.encodeWithSelector(RateLimiter.AggregateValueRateLimitReached.selector, 12, 20)); + s_rateLimiter.onOutboundMessage(CHAIN_SELECTOR_1, _generateEVM2AnyMessage(tokenAmounts)); + + // Remaining capacity: 20 -> 35 (need to wait 9 more blocks) + vm.warp(BLOCK_TIME + 3); + vm.expectRevert(abi.encodeWithSelector(RateLimiter.AggregateValueRateLimitReached.selector, 9, 35)); + s_rateLimiter.onOutboundMessage(CHAIN_SELECTOR_1, _generateEVM2AnyMessage(tokenAmounts)); + + // Remaining capacity: 35 -> 80 (can fit exactly 80) + vm.warp(BLOCK_TIME + 12); + s_rateLimiter.onOutboundMessage(CHAIN_SELECTOR_1, _generateEVM2AnyMessage(tokenAmounts)); + } + + function test_RateLimitValueDifferentLanes_Success() public { + vm.pauseGasMetering(); + // start from blocktime that does not equal rate limiter init timestamp + vm.warp(BLOCK_TIME + 1); + + // 10 (tokens) * 4 (price) * 2 (number of times) = 80 < 100 (capacity) + uint256 numberOfTokens = 10; + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); + tokenAmounts[0] = Client.EVMTokenAmount({token: s_sourceTokens[0], amount: numberOfTokens}); + uint256 value = (numberOfTokens * TOKEN_PRICE) / 1e18; + + vm.expectEmit(); + emit RateLimiter.TokensConsumed(value); + + vm.resumeGasMetering(); + vm.startPrank(MOCK_ONRAMP); + s_rateLimiter.onOutboundMessage(CHAIN_SELECTOR_1, _generateEVM2AnyMessage(tokenAmounts)); + vm.pauseGasMetering(); + + // Get the updated bucket status + RateLimiter.TokenBucket memory bucket1 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, true); + RateLimiter.TokenBucket memory bucket2 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, false); + + // Assert the proper value has been taken out of the bucket + assertEq(bucket1.capacity - value, bucket1.tokens); + // Inbound lane should remain unchanged + assertEq(bucket2.capacity, bucket2.tokens); + + vm.expectEmit(); + emit RateLimiter.TokensConsumed(value); + + vm.resumeGasMetering(); + s_rateLimiter.onInboundMessage(_generateAny2EVMMessage(CHAIN_SELECTOR_1, tokenAmounts)); + vm.pauseGasMetering(); + + bucket1 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, true); + bucket2 = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, false); + + // Inbound lane should remain unchanged + assertEq(bucket1.capacity - value, bucket1.tokens); + assertEq(bucket2.capacity - value, bucket2.tokens); + } + + // Reverts + + function test_onOutboundMessage_ValidateMessageWithRateLimitExceeded_Revert() public { + vm.startPrank(MOCK_OFFRAMP); + + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); + tokenAmounts[0] = Client.EVMTokenAmount({token: s_sourceTokens[0], amount: 80}); + tokenAmounts[1] = Client.EVMTokenAmount({token: s_sourceTokens[1], amount: 30}); + + uint256 totalValue = (80 * TOKEN_PRICE + 2 * (30 * TOKEN_PRICE)) / 1e18; + vm.expectRevert(abi.encodeWithSelector(RateLimiter.AggregateValueMaxCapacityExceeded.selector, 100, totalValue)); + s_rateLimiter.onOutboundMessage(CHAIN_SELECTOR_1, _generateEVM2AnyMessage(tokenAmounts)); + } + + function test_onOutboundMessage_ValidateMessageFromUnauthorizedCaller_Revert() public { + vm.startPrank(STRANGER); + + vm.expectRevert(abi.encodeWithSelector(AuthorizedCallers.UnauthorizedCaller.selector, STRANGER)); + s_rateLimiter.onOutboundMessage(CHAIN_SELECTOR_1, _generateEVM2AnyMessageNoTokens()); + } + + function _generateEVM2AnyMessage( + Client.EVMTokenAmount[] memory tokenAmounts + ) public view returns (Client.EVM2AnyMessage memory) { + return Client.EVM2AnyMessage({ + receiver: abi.encode(OWNER), + data: "", + tokenAmounts: tokenAmounts, + feeToken: s_sourceFeeToken, + extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT})) + }); + } + + function _generateEVM2AnyMessageNoTokens() internal view returns (Client.EVM2AnyMessage memory) { + return _generateEVM2AnyMessage(new Client.EVMTokenAmount[](0)); + } +} diff --git a/contracts/src/v0.8/ccip/test/rateLimiter/MutiAggregateRateLimiter/MultiAggregateRateLimiter_setFeeQuoter.t.sol b/contracts/src/v0.8/ccip/test/rateLimiter/MutiAggregateRateLimiter/MultiAggregateRateLimiter_setFeeQuoter.t.sol new file mode 100644 index 00000000000..39412a65045 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/rateLimiter/MutiAggregateRateLimiter/MultiAggregateRateLimiter_setFeeQuoter.t.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {AuthorizedCallers} from "../../../../shared/access/AuthorizedCallers.sol"; +import {Ownable2Step} from "../../../../shared/access/Ownable2Step.sol"; +import {MultiAggregateRateLimiter} from "../../../MultiAggregateRateLimiter.sol"; +import {MultiAggregateRateLimiterSetup} from "./MultiAggregateRateLimiterSetup.t.sol"; + +contract MultiAggregateRateLimiter_setFeeQuoter is MultiAggregateRateLimiterSetup { + function test_Owner_Success() public { + address newAddress = address(42); + + vm.expectEmit(); + emit MultiAggregateRateLimiter.FeeQuoterSet(newAddress); + + s_rateLimiter.setFeeQuoter(newAddress); + assertEq(newAddress, s_rateLimiter.getFeeQuoter()); + } + + // Reverts + + function test_OnlyOwner_Revert() public { + vm.startPrank(STRANGER); + vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); + + s_rateLimiter.setFeeQuoter(STRANGER); + } + + function test_ZeroAddress_Revert() public { + vm.expectRevert(AuthorizedCallers.ZeroAddressNotAllowed.selector); + s_rateLimiter.setFeeQuoter(address(0)); + } +} diff --git a/contracts/src/v0.8/ccip/test/rateLimiter/MutiAggregateRateLimiter/MultiAggregateRateLimiter_updateRateLimitTokens.t.sol b/contracts/src/v0.8/ccip/test/rateLimiter/MutiAggregateRateLimiter/MultiAggregateRateLimiter_updateRateLimitTokens.t.sol new file mode 100644 index 00000000000..2125983ed70 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/rateLimiter/MutiAggregateRateLimiter/MultiAggregateRateLimiter_updateRateLimitTokens.t.sol @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {AuthorizedCallers} from "../../../../shared/access/AuthorizedCallers.sol"; +import {Ownable2Step} from "../../../../shared/access/Ownable2Step.sol"; +import {MultiAggregateRateLimiter} from "../../../MultiAggregateRateLimiter.sol"; + +import {MultiAggregateRateLimiterSetup} from "./MultiAggregateRateLimiterSetup.t.sol"; +import {Vm} from "forge-std/Vm.sol"; + +contract MultiAggregateRateLimiter_updateRateLimitTokens is MultiAggregateRateLimiterSetup { + function setUp() public virtual override { + super.setUp(); + + // Clear rate limit tokens state + MultiAggregateRateLimiter.LocalRateLimitToken[] memory removes = + new MultiAggregateRateLimiter.LocalRateLimitToken[](s_sourceTokens.length); + for (uint256 i = 0; i < s_sourceTokens.length; ++i) { + removes[i] = MultiAggregateRateLimiter.LocalRateLimitToken({ + remoteChainSelector: CHAIN_SELECTOR_1, + localToken: s_destTokens[i] + }); + } + s_rateLimiter.updateRateLimitTokens(removes, new MultiAggregateRateLimiter.RateLimitTokenArgs[](0)); + } + + function test_UpdateRateLimitTokensSingleChain_Success() public { + MultiAggregateRateLimiter.RateLimitTokenArgs[] memory adds = new MultiAggregateRateLimiter.RateLimitTokenArgs[](2); + adds[0] = MultiAggregateRateLimiter.RateLimitTokenArgs({ + localTokenArgs: MultiAggregateRateLimiter.LocalRateLimitToken({ + remoteChainSelector: CHAIN_SELECTOR_1, + localToken: s_destTokens[0] + }), + remoteToken: abi.encode(s_sourceTokens[0]) + }); + adds[1] = MultiAggregateRateLimiter.RateLimitTokenArgs({ + localTokenArgs: MultiAggregateRateLimiter.LocalRateLimitToken({ + remoteChainSelector: CHAIN_SELECTOR_1, + localToken: s_destTokens[1] + }), + remoteToken: abi.encode(s_sourceTokens[1]) + }); + + for (uint256 i = 0; i < adds.length; ++i) { + vm.expectEmit(); + emit MultiAggregateRateLimiter.TokenAggregateRateLimitAdded( + CHAIN_SELECTOR_1, adds[i].remoteToken, adds[i].localTokenArgs.localToken + ); + } + + s_rateLimiter.updateRateLimitTokens(new MultiAggregateRateLimiter.LocalRateLimitToken[](0), adds); + + (address[] memory localTokens, bytes[] memory remoteTokens) = s_rateLimiter.getAllRateLimitTokens(CHAIN_SELECTOR_1); + + assertEq(localTokens.length, adds.length); + assertEq(localTokens.length, remoteTokens.length); + + for (uint256 i = 0; i < adds.length; ++i) { + assertEq(adds[i].remoteToken, remoteTokens[i]); + assertEq(adds[i].localTokenArgs.localToken, localTokens[i]); + } + } + + function test_UpdateRateLimitTokensMultipleChains_Success() public { + MultiAggregateRateLimiter.RateLimitTokenArgs[] memory adds = new MultiAggregateRateLimiter.RateLimitTokenArgs[](2); + adds[0] = MultiAggregateRateLimiter.RateLimitTokenArgs({ + localTokenArgs: MultiAggregateRateLimiter.LocalRateLimitToken({ + remoteChainSelector: CHAIN_SELECTOR_1, + localToken: s_destTokens[0] + }), + remoteToken: abi.encode(s_sourceTokens[0]) + }); + adds[1] = MultiAggregateRateLimiter.RateLimitTokenArgs({ + localTokenArgs: MultiAggregateRateLimiter.LocalRateLimitToken({ + remoteChainSelector: CHAIN_SELECTOR_2, + localToken: s_destTokens[1] + }), + remoteToken: abi.encode(s_sourceTokens[1]) + }); + + for (uint256 i = 0; i < adds.length; ++i) { + vm.expectEmit(); + emit MultiAggregateRateLimiter.TokenAggregateRateLimitAdded( + adds[i].localTokenArgs.remoteChainSelector, adds[i].remoteToken, adds[i].localTokenArgs.localToken + ); + } + + s_rateLimiter.updateRateLimitTokens(new MultiAggregateRateLimiter.LocalRateLimitToken[](0), adds); + + (address[] memory localTokensChain1, bytes[] memory remoteTokensChain1) = + s_rateLimiter.getAllRateLimitTokens(CHAIN_SELECTOR_1); + + assertEq(localTokensChain1.length, 1); + assertEq(localTokensChain1.length, remoteTokensChain1.length); + assertEq(localTokensChain1[0], adds[0].localTokenArgs.localToken); + assertEq(remoteTokensChain1[0], adds[0].remoteToken); + + (address[] memory localTokensChain2, bytes[] memory remoteTokensChain2) = + s_rateLimiter.getAllRateLimitTokens(CHAIN_SELECTOR_2); + + assertEq(localTokensChain2.length, 1); + assertEq(localTokensChain2.length, remoteTokensChain2.length); + assertEq(localTokensChain2[0], adds[1].localTokenArgs.localToken); + assertEq(remoteTokensChain2[0], adds[1].remoteToken); + } + + function test_UpdateRateLimitTokens_AddsAndRemoves_Success() public { + MultiAggregateRateLimiter.RateLimitTokenArgs[] memory adds = new MultiAggregateRateLimiter.RateLimitTokenArgs[](2); + adds[0] = MultiAggregateRateLimiter.RateLimitTokenArgs({ + localTokenArgs: MultiAggregateRateLimiter.LocalRateLimitToken({ + remoteChainSelector: CHAIN_SELECTOR_1, + localToken: s_destTokens[0] + }), + remoteToken: abi.encode(s_sourceTokens[0]) + }); + adds[1] = MultiAggregateRateLimiter.RateLimitTokenArgs({ + localTokenArgs: MultiAggregateRateLimiter.LocalRateLimitToken({ + remoteChainSelector: CHAIN_SELECTOR_1, + localToken: s_destTokens[1] + }), + remoteToken: abi.encode(s_sourceTokens[1]) + }); + + MultiAggregateRateLimiter.LocalRateLimitToken[] memory removes = + new MultiAggregateRateLimiter.LocalRateLimitToken[](1); + removes[0] = adds[0].localTokenArgs; + + for (uint256 i = 0; i < adds.length; ++i) { + vm.expectEmit(); + emit MultiAggregateRateLimiter.TokenAggregateRateLimitAdded( + CHAIN_SELECTOR_1, adds[i].remoteToken, adds[i].localTokenArgs.localToken + ); + } + + s_rateLimiter.updateRateLimitTokens(removes, adds); + + for (uint256 i = 0; i < removes.length; ++i) { + vm.expectEmit(); + emit MultiAggregateRateLimiter.TokenAggregateRateLimitRemoved(CHAIN_SELECTOR_1, removes[i].localToken); + } + + s_rateLimiter.updateRateLimitTokens(removes, new MultiAggregateRateLimiter.RateLimitTokenArgs[](0)); + + (address[] memory localTokens, bytes[] memory remoteTokens) = s_rateLimiter.getAllRateLimitTokens(CHAIN_SELECTOR_1); + + assertEq(1, remoteTokens.length); + assertEq(adds[1].remoteToken, remoteTokens[0]); + + assertEq(1, localTokens.length); + assertEq(adds[1].localTokenArgs.localToken, localTokens[0]); + } + + function test_UpdateRateLimitTokens_RemoveNonExistentToken_Success() public { + MultiAggregateRateLimiter.RateLimitTokenArgs[] memory adds = new MultiAggregateRateLimiter.RateLimitTokenArgs[](0); + + MultiAggregateRateLimiter.LocalRateLimitToken[] memory removes = + new MultiAggregateRateLimiter.LocalRateLimitToken[](1); + removes[0] = MultiAggregateRateLimiter.LocalRateLimitToken({ + remoteChainSelector: CHAIN_SELECTOR_1, + localToken: s_destTokens[0] + }); + + vm.recordLogs(); + s_rateLimiter.updateRateLimitTokens(removes, adds); + + // No event since no remove occurred + Vm.Log[] memory logEntries = vm.getRecordedLogs(); + assertEq(logEntries.length, 0); + + (address[] memory localTokens, bytes[] memory remoteTokens) = s_rateLimiter.getAllRateLimitTokens(CHAIN_SELECTOR_1); + + assertEq(localTokens.length, 0); + assertEq(localTokens.length, remoteTokens.length); + } + + // Reverts + + function test_ZeroSourceToken_Revert() public { + MultiAggregateRateLimiter.RateLimitTokenArgs[] memory adds = new MultiAggregateRateLimiter.RateLimitTokenArgs[](1); + adds[0] = MultiAggregateRateLimiter.RateLimitTokenArgs({ + localTokenArgs: MultiAggregateRateLimiter.LocalRateLimitToken({ + remoteChainSelector: CHAIN_SELECTOR_1, + localToken: s_destTokens[0] + }), + remoteToken: new bytes(0) + }); + + vm.expectRevert(AuthorizedCallers.ZeroAddressNotAllowed.selector); + s_rateLimiter.updateRateLimitTokens(new MultiAggregateRateLimiter.LocalRateLimitToken[](0), adds); + } + + function test_ZeroDestToken_Revert() public { + MultiAggregateRateLimiter.RateLimitTokenArgs[] memory adds = new MultiAggregateRateLimiter.RateLimitTokenArgs[](1); + adds[0] = MultiAggregateRateLimiter.RateLimitTokenArgs({ + localTokenArgs: MultiAggregateRateLimiter.LocalRateLimitToken({ + remoteChainSelector: CHAIN_SELECTOR_1, + localToken: address(0) + }), + remoteToken: abi.encode(s_destTokens[0]) + }); + + vm.expectRevert(AuthorizedCallers.ZeroAddressNotAllowed.selector); + s_rateLimiter.updateRateLimitTokens(new MultiAggregateRateLimiter.LocalRateLimitToken[](0), adds); + } + + function test_ZeroDestToken_AbiEncoded_Revert() public { + MultiAggregateRateLimiter.RateLimitTokenArgs[] memory adds = new MultiAggregateRateLimiter.RateLimitTokenArgs[](1); + adds[0] = MultiAggregateRateLimiter.RateLimitTokenArgs({ + localTokenArgs: MultiAggregateRateLimiter.LocalRateLimitToken({ + remoteChainSelector: CHAIN_SELECTOR_1, + localToken: address(0) + }), + remoteToken: abi.encode(address(0)) + }); + + vm.expectRevert(AuthorizedCallers.ZeroAddressNotAllowed.selector); + s_rateLimiter.updateRateLimitTokens(new MultiAggregateRateLimiter.LocalRateLimitToken[](0), adds); + } + + function test_NonOwner_Revert() public { + MultiAggregateRateLimiter.RateLimitTokenArgs[] memory adds = new MultiAggregateRateLimiter.RateLimitTokenArgs[](4); + + vm.startPrank(STRANGER); + + vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); + s_rateLimiter.updateRateLimitTokens(new MultiAggregateRateLimiter.LocalRateLimitToken[](0), adds); + } +} diff --git a/contracts/src/v0.8/ccip/test/rmn/ARMProxy.t.sol b/contracts/src/v0.8/ccip/test/rmn/ARMProxy.t.sol deleted file mode 100644 index efcdfd82277..00000000000 --- a/contracts/src/v0.8/ccip/test/rmn/ARMProxy.t.sol +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.24; - -import {IRMN} from "../../interfaces/IRMN.sol"; - -import {ARMProxy} from "../../rmn/ARMProxy.sol"; -import {MockRMN} from "../mocks/MockRMN.sol"; -import {Test} from "forge-std/Test.sol"; - -contract ARMProxyTest is Test { - MockRMN internal s_mockRMN; - ARMProxy internal s_armProxy; - - function setUp() public virtual { - s_mockRMN = new MockRMN(); - s_armProxy = new ARMProxy(address(s_mockRMN)); - } - - function test_ARMIsCursed_Success() public { - s_armProxy.setARM(address(s_mockRMN)); - assertFalse(IRMN(address(s_armProxy)).isCursed()); - s_mockRMN.setGlobalCursed(true); - assertTrue(IRMN(address(s_armProxy)).isCursed()); - } - - function test_ARMCallRevertReasonForwarded() public { - bytes memory err = bytes("revert"); - s_mockRMN.setIsCursedRevert(err); - s_armProxy.setARM(address(s_mockRMN)); - vm.expectRevert(abi.encodeWithSelector(MockRMN.CustomError.selector, err)); - IRMN(address(s_armProxy)).isCursed(); - } -} - -contract ARMProxyStandaloneTest is Test { - address internal constant EMPTY_ADDRESS = address(0x1); - address internal constant OWNER_ADDRESS = 0xC0ffeeEeC0fFeeeEc0ffeEeEc0ffEEEEC0FfEEee; - address internal constant MOCK_RMN_ADDRESS = 0x1337133713371337133713371337133713371337; - - ARMProxy internal s_armProxy; - - function setUp() public virtual { - // needed so that the extcodesize check in ARMProxy.fallback doesn't revert - vm.etch(MOCK_RMN_ADDRESS, bytes("fake bytecode")); - - vm.prank(OWNER_ADDRESS); - s_armProxy = new ARMProxy(MOCK_RMN_ADDRESS); - } - - function test_Constructor() public { - vm.expectEmit(); - emit ARMProxy.ARMSet(MOCK_RMN_ADDRESS); - ARMProxy proxy = new ARMProxy(MOCK_RMN_ADDRESS); - assertEq(proxy.getARM(), MOCK_RMN_ADDRESS); - } - - function test_SetARM() public { - vm.expectEmit(); - emit ARMProxy.ARMSet(MOCK_RMN_ADDRESS); - vm.prank(OWNER_ADDRESS); - s_armProxy.setARM(MOCK_RMN_ADDRESS); - assertEq(s_armProxy.getARM(), MOCK_RMN_ADDRESS); - } - - function test_SetARMzero() public { - vm.expectRevert(abi.encodeWithSelector(ARMProxy.ZeroAddressNotAllowed.selector)); - vm.prank(OWNER_ADDRESS); - s_armProxy.setARM(address(0x0)); - } - - function test_ARMCallEmptyContractRevert() public { - vm.prank(OWNER_ADDRESS); - s_armProxy.setARM(EMPTY_ADDRESS); // No code at address 1, should revert. - vm.expectRevert(); - bytes memory b = new bytes(0); - (bool success,) = address(s_armProxy).call(b); - success; - } -} diff --git a/contracts/src/v0.8/ccip/test/rmn/ArmProxy/ARMProxyTestSetup.t.sol b/contracts/src/v0.8/ccip/test/rmn/ArmProxy/ARMProxyTestSetup.t.sol new file mode 100644 index 00000000000..6a98d726d63 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/rmn/ArmProxy/ARMProxyTestSetup.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {ARMProxy} from "../../../rmn/ARMProxy.sol"; +import {Test} from "forge-std/Test.sol"; + +contract ARMProxyTestSetup is Test { + address internal constant EMPTY_ADDRESS = address(0x1); + address internal constant OWNER_ADDRESS = 0xC0ffeeEeC0fFeeeEc0ffeEeEc0ffEEEEC0FfEEee; + address internal constant MOCK_RMN_ADDRESS = 0x1337133713371337133713371337133713371337; + ARMProxy internal s_armProxy; + + function setUp() public virtual { + // needed so that the extcodesize check in ARMProxy.fallback doesn't revert + vm.etch(MOCK_RMN_ADDRESS, bytes("fake bytecode")); + + vm.prank(OWNER_ADDRESS); + s_armProxy = new ARMProxy(MOCK_RMN_ADDRESS); + } +} diff --git a/contracts/src/v0.8/ccip/test/rmn/ArmProxy/ArmPorxy.setARM.t.sol b/contracts/src/v0.8/ccip/test/rmn/ArmProxy/ArmPorxy.setARM.t.sol new file mode 100644 index 00000000000..88e613c06da --- /dev/null +++ b/contracts/src/v0.8/ccip/test/rmn/ArmProxy/ArmPorxy.setARM.t.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {ARMProxy} from "../../../rmn/ARMProxy.sol"; + +import {ARMProxyTestSetup} from "./ARMProxyTestSetup.t.sol"; + +contract ARMProxy_setARM is ARMProxyTestSetup { + function test_SetARM() public { + vm.expectEmit(); + emit ARMProxy.ARMSet(MOCK_RMN_ADDRESS); + vm.prank(OWNER_ADDRESS); + s_armProxy.setARM(MOCK_RMN_ADDRESS); + assertEq(s_armProxy.getARM(), MOCK_RMN_ADDRESS); + } + + function test_SetARMzero() public { + vm.expectRevert(abi.encodeWithSelector(ARMProxy.ZeroAddressNotAllowed.selector)); + vm.prank(OWNER_ADDRESS); + s_armProxy.setARM(address(0x0)); + } +} diff --git a/contracts/src/v0.8/ccip/test/rmn/ArmProxy/ArmProxy.constructor.t.sol b/contracts/src/v0.8/ccip/test/rmn/ArmProxy/ArmProxy.constructor.t.sol new file mode 100644 index 00000000000..778a9d4086c --- /dev/null +++ b/contracts/src/v0.8/ccip/test/rmn/ArmProxy/ArmProxy.constructor.t.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {ARMProxy} from "../../../rmn/ARMProxy.sol"; +import {ARMProxyTestSetup} from "./ARMProxyTestSetup.t.sol"; + +contract ARMProxy_constructor is ARMProxyTestSetup { + function test_Constructor() public { + vm.expectEmit(); + emit ARMProxy.ARMSet(MOCK_RMN_ADDRESS); + ARMProxy proxy = new ARMProxy(MOCK_RMN_ADDRESS); + assertEq(proxy.getARM(), MOCK_RMN_ADDRESS); + } +} diff --git a/contracts/src/v0.8/ccip/test/rmn/ArmProxy/ArmProxy.isCursed.t.sol b/contracts/src/v0.8/ccip/test/rmn/ArmProxy/ArmProxy.isCursed.t.sol new file mode 100644 index 00000000000..fdc6fce0cf4 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/rmn/ArmProxy/ArmProxy.isCursed.t.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IRMN} from "../../../interfaces/IRMN.sol"; + +import {ARMProxy} from "../../../rmn/ARMProxy.sol"; +import {MockRMN} from "../../mocks/MockRMN.sol"; + +import {ARMProxyTestSetup} from "./ARMProxyTestSetup.t.sol"; + +contract ARMProxy_isCursed is ARMProxyTestSetup { + MockRMN internal s_mockRMN; + + function setUp() public virtual override { + super.setUp(); + s_mockRMN = new MockRMN(); + s_armProxy = new ARMProxy(address(s_mockRMN)); + } + + function test_IsCursed_Success() public { + s_armProxy.setARM(address(s_mockRMN)); + assertFalse(IRMN(address(s_armProxy)).isCursed()); + s_mockRMN.setGlobalCursed(true); + assertTrue(IRMN(address(s_armProxy)).isCursed()); + } + + function test_isCursed_RevertReasonForwarded_Revert() public { + bytes memory err = bytes("revert"); + s_mockRMN.setIsCursedRevert(err); + s_armProxy.setARM(address(s_mockRMN)); + vm.expectRevert(abi.encodeWithSelector(MockRMN.CustomError.selector, err)); + IRMN(address(s_armProxy)).isCursed(); + } + + function test_call_ARMCallEmptyContract_Revert() public { + s_armProxy.setARM(EMPTY_ADDRESS); // No code at address 1, should revert. + vm.expectRevert(); + bytes memory b = new bytes(0); + (bool success,) = address(s_armProxy).call(b); + success; + } +} diff --git a/contracts/src/v0.8/ccip/test/rmn/RMNHome.t.sol b/contracts/src/v0.8/ccip/test/rmn/RMNHome.t.sol deleted file mode 100644 index 449725317a1..00000000000 --- a/contracts/src/v0.8/ccip/test/rmn/RMNHome.t.sol +++ /dev/null @@ -1,373 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.24; - -import {Ownable2Step} from "../../../shared/access/Ownable2Step.sol"; -import {RMNHome} from "../../rmn/RMNHome.sol"; -import {Test} from "forge-std/Test.sol"; - -contract RMNHomeTest is Test { - struct Config { - RMNHome.StaticConfig staticConfig; - RMNHome.DynamicConfig dynamicConfig; - } - - bytes32 internal constant ZERO_DIGEST = bytes32(uint256(0)); - RMNHome public s_rmnHome = new RMNHome(); - - function _getBaseConfig() internal pure returns (Config memory) { - RMNHome.Node[] memory nodes = new RMNHome.Node[](3); - nodes[0] = RMNHome.Node({peerId: keccak256("peerId_0"), offchainPublicKey: keccak256("offchainPublicKey_0")}); - nodes[1] = RMNHome.Node({peerId: keccak256("peerId_1"), offchainPublicKey: keccak256("offchainPublicKey_1")}); - nodes[2] = RMNHome.Node({peerId: keccak256("peerId_2"), offchainPublicKey: keccak256("offchainPublicKey_2")}); - - RMNHome.SourceChain[] memory sourceChains = new RMNHome.SourceChain[](2); - // Observer 0 for source chain 9000 - sourceChains[0] = RMNHome.SourceChain({chainSelector: 9000, f: 1, observerNodesBitmap: 1 << 0 | 1 << 1 | 1 << 2}); - // Observers 0, 1 and 2 for source chain 9001 - sourceChains[1] = RMNHome.SourceChain({chainSelector: 9001, f: 1, observerNodesBitmap: 1 << 0 | 1 << 1 | 1 << 2}); - - return Config({ - staticConfig: RMNHome.StaticConfig({nodes: nodes, offchainConfig: abi.encode("static_config")}), - dynamicConfig: RMNHome.DynamicConfig({sourceChains: sourceChains, offchainConfig: abi.encode("dynamic_config")}) - }); - } - - uint256 private constant PREFIX_MASK = type(uint256).max << (256 - 16); // 0xFFFF00..00 - uint256 private constant PREFIX = 0x000b << (256 - 16); // 0x000b00..00 - - function _getConfigDigest(bytes memory staticConfig, uint32 version) internal view returns (bytes32) { - return bytes32( - (PREFIX & PREFIX_MASK) - | ( - uint256( - keccak256(bytes.concat(abi.encode(bytes32("EVM"), block.chainid, address(s_rmnHome), version), staticConfig)) - ) & ~PREFIX_MASK - ) - ); - } -} - -contract RMNHome_getConfigDigests is RMNHomeTest { - function test_getConfigDigests_success() public { - (bytes32 activeDigest, bytes32 candidateDigest) = s_rmnHome.getConfigDigests(); - assertEq(activeDigest, ZERO_DIGEST); - assertEq(candidateDigest, ZERO_DIGEST); - - Config memory config = _getBaseConfig(); - bytes32 firstDigest = s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); - - (activeDigest, candidateDigest) = s_rmnHome.getConfigDigests(); - assertEq(activeDigest, ZERO_DIGEST); - assertEq(candidateDigest, firstDigest); - - s_rmnHome.promoteCandidateAndRevokeActive(firstDigest, ZERO_DIGEST); - - (activeDigest, candidateDigest) = s_rmnHome.getConfigDigests(); - assertEq(activeDigest, firstDigest); - assertEq(candidateDigest, ZERO_DIGEST); - - bytes32 secondDigest = s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); - - (activeDigest, candidateDigest) = s_rmnHome.getConfigDigests(); - assertEq(activeDigest, firstDigest); - assertEq(candidateDigest, secondDigest); - - assertEq(activeDigest, s_rmnHome.getActiveDigest()); - assertEq(candidateDigest, s_rmnHome.getCandidateDigest()); - } -} - -contract RMNHome_setCandidate is RMNHomeTest { - function test_setCandidate_success() public { - Config memory config = _getBaseConfig(); - RMNHome.VersionedConfig memory versionedConfig = RMNHome.VersionedConfig({ - version: 1, - staticConfig: config.staticConfig, - dynamicConfig: config.dynamicConfig, - configDigest: ZERO_DIGEST - }); - - versionedConfig.configDigest = _getConfigDigest(abi.encode(versionedConfig.staticConfig), versionedConfig.version); - - vm.expectEmit(); - emit RMNHome.ConfigSet( - versionedConfig.configDigest, versionedConfig.version, versionedConfig.staticConfig, versionedConfig.dynamicConfig - ); - - s_rmnHome.setCandidate(versionedConfig.staticConfig, versionedConfig.dynamicConfig, ZERO_DIGEST); - - (RMNHome.VersionedConfig memory storedVersionedConfig, bool ok) = s_rmnHome.getConfig(versionedConfig.configDigest); - assertTrue(ok); - assertEq(storedVersionedConfig.version, versionedConfig.version); - RMNHome.StaticConfig memory storedStaticConfig = storedVersionedConfig.staticConfig; - RMNHome.DynamicConfig memory storedDynamicConfig = storedVersionedConfig.dynamicConfig; - - assertEq(storedStaticConfig.nodes.length, versionedConfig.staticConfig.nodes.length); - for (uint256 i = 0; i < storedStaticConfig.nodes.length; i++) { - RMNHome.Node memory storedNode = storedStaticConfig.nodes[i]; - assertEq(storedNode.peerId, versionedConfig.staticConfig.nodes[i].peerId); - assertEq(storedNode.offchainPublicKey, versionedConfig.staticConfig.nodes[i].offchainPublicKey); - } - - assertEq(storedDynamicConfig.sourceChains.length, versionedConfig.dynamicConfig.sourceChains.length); - for (uint256 i = 0; i < storedDynamicConfig.sourceChains.length; i++) { - RMNHome.SourceChain memory storedSourceChain = storedDynamicConfig.sourceChains[i]; - assertEq(storedSourceChain.chainSelector, versionedConfig.dynamicConfig.sourceChains[i].chainSelector); - assertEq(storedSourceChain.f, versionedConfig.dynamicConfig.sourceChains[i].f); - assertEq(storedSourceChain.observerNodesBitmap, versionedConfig.dynamicConfig.sourceChains[i].observerNodesBitmap); - } - assertEq(storedDynamicConfig.offchainConfig, versionedConfig.dynamicConfig.offchainConfig); - assertEq(storedStaticConfig.offchainConfig, versionedConfig.staticConfig.offchainConfig); - } - - function test_setCandidate_ConfigDigestMismatch_reverts() public { - Config memory config = _getBaseConfig(); - - bytes32 digest = s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); - - vm.expectRevert(abi.encodeWithSelector(RMNHome.ConfigDigestMismatch.selector, digest, ZERO_DIGEST)); - s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); - - vm.expectEmit(); - emit RMNHome.CandidateConfigRevoked(digest); - - s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, digest); - } - - function test_setCandidate_OnlyOwner_reverts() public { - Config memory config = _getBaseConfig(); - - vm.startPrank(address(0)); - - vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); - s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); - } -} - -contract RMNHome_revokeCandidate is RMNHomeTest { - // Sets two configs - function setUp() public { - Config memory config = _getBaseConfig(); - bytes32 digest = s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); - s_rmnHome.promoteCandidateAndRevokeActive(digest, ZERO_DIGEST); - - config.dynamicConfig.sourceChains[1].f--; - s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); - } - - function test_revokeCandidate_success() public { - (bytes32 priorActiveDigest, bytes32 priorCandidateDigest) = s_rmnHome.getConfigDigests(); - - vm.expectEmit(); - emit RMNHome.CandidateConfigRevoked(priorCandidateDigest); - - s_rmnHome.revokeCandidate(priorCandidateDigest); - - (RMNHome.VersionedConfig memory storedVersionedConfig, bool ok) = s_rmnHome.getConfig(priorCandidateDigest); - assertFalse(ok); - // Ensure no old data is returned, even though it's still in storage - assertEq(storedVersionedConfig.version, 0); - assertEq(storedVersionedConfig.staticConfig.nodes.length, 0); - assertEq(storedVersionedConfig.dynamicConfig.sourceChains.length, 0); - - // Asser the active digest is unaffected but the candidate digest is set to zero - (bytes32 activeDigest, bytes32 candidateDigest) = s_rmnHome.getConfigDigests(); - assertEq(activeDigest, priorActiveDigest); - assertEq(candidateDigest, ZERO_DIGEST); - assertTrue(candidateDigest != priorCandidateDigest); - } - - function test_revokeCandidate_ConfigDigestMismatch_reverts() public { - (, bytes32 priorCandidateDigest) = s_rmnHome.getConfigDigests(); - - bytes32 wrongDigest = keccak256("wrong_digest"); - vm.expectRevert(abi.encodeWithSelector(RMNHome.ConfigDigestMismatch.selector, priorCandidateDigest, wrongDigest)); - s_rmnHome.revokeCandidate(wrongDigest); - } - - function test_revokeCandidate_RevokingZeroDigestNotAllowed_reverts() public { - vm.expectRevert(RMNHome.RevokingZeroDigestNotAllowed.selector); - s_rmnHome.revokeCandidate(ZERO_DIGEST); - } - - function test_revokeCandidate_OnlyOwner_reverts() public { - vm.startPrank(address(0)); - - vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); - s_rmnHome.revokeCandidate(keccak256("configDigest")); - } -} - -contract RMNHome_promoteCandidateAndRevokeActive is RMNHomeTest { - function test_promoteCandidateAndRevokeActive_success() public { - Config memory config = _getBaseConfig(); - bytes32 firstConfigToPromote = s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); - - vm.expectEmit(); - emit RMNHome.ConfigPromoted(firstConfigToPromote); - - s_rmnHome.promoteCandidateAndRevokeActive(firstConfigToPromote, ZERO_DIGEST); - - // Assert the active digest is updated and the candidate digest is set to zero - (bytes32 activeDigest, bytes32 candidateDigest) = s_rmnHome.getConfigDigests(); - assertEq(activeDigest, firstConfigToPromote); - assertEq(candidateDigest, ZERO_DIGEST); - - // Set a new candidate to promote over a non-zero active config. - config.staticConfig.offchainConfig = abi.encode("new_static_config"); - config.dynamicConfig.offchainConfig = abi.encode("new_dynamic_config"); - bytes32 secondConfigToPromote = s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); - - vm.expectEmit(); - emit RMNHome.ActiveConfigRevoked(firstConfigToPromote); - - vm.expectEmit(); - emit RMNHome.ConfigPromoted(secondConfigToPromote); - - s_rmnHome.promoteCandidateAndRevokeActive(secondConfigToPromote, firstConfigToPromote); - - (RMNHome.VersionedConfig memory activeConfig, RMNHome.VersionedConfig memory candidateConfig) = - s_rmnHome.getAllConfigs(); - assertEq(activeConfig.configDigest, secondConfigToPromote); - assertEq(activeConfig.staticConfig.offchainConfig, config.staticConfig.offchainConfig); - assertEq(activeConfig.dynamicConfig.offchainConfig, config.dynamicConfig.offchainConfig); - - assertEq(candidateConfig.configDigest, ZERO_DIGEST); - } - - function test_promoteCandidateAndRevokeActive_NoOpStateTransitionNotAllowed_reverts() public { - vm.expectRevert(RMNHome.NoOpStateTransitionNotAllowed.selector); - s_rmnHome.promoteCandidateAndRevokeActive(ZERO_DIGEST, ZERO_DIGEST); - } - - function test_promoteCandidateAndRevokeActive_ConfigDigestMismatch_reverts() public { - (bytes32 priorActiveDigest, bytes32 priorCandidateDigest) = s_rmnHome.getConfigDigests(); - bytes32 wrongActiveDigest = keccak256("wrongActiveDigest"); - bytes32 wrongCandidateDigest = keccak256("wrongCandidateDigest"); - - vm.expectRevert( - abi.encodeWithSelector(RMNHome.ConfigDigestMismatch.selector, priorActiveDigest, wrongCandidateDigest) - ); - s_rmnHome.promoteCandidateAndRevokeActive(wrongCandidateDigest, wrongActiveDigest); - - vm.expectRevert(abi.encodeWithSelector(RMNHome.ConfigDigestMismatch.selector, priorActiveDigest, wrongActiveDigest)); - - s_rmnHome.promoteCandidateAndRevokeActive(priorCandidateDigest, wrongActiveDigest); - } - - function test_promoteCandidateAndRevokeActive_OnlyOwner_reverts() public { - vm.startPrank(address(0)); - - vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); - s_rmnHome.promoteCandidateAndRevokeActive(keccak256("toPromote"), keccak256("ToRevoke")); - } -} - -contract RMNHome__validateStaticAndDynamicConfig is RMNHomeTest { - function test_validateStaticAndDynamicConfig_OutOfBoundsNodesLength_reverts() public { - Config memory config = _getBaseConfig(); - config.staticConfig.nodes = new RMNHome.Node[](257); - - vm.expectRevert(RMNHome.OutOfBoundsNodesLength.selector); - s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); - } - - function test_validateStaticAndDynamicConfig_DuplicatePeerId_reverts() public { - Config memory config = _getBaseConfig(); - config.staticConfig.nodes[1].peerId = config.staticConfig.nodes[0].peerId; - - vm.expectRevert(RMNHome.DuplicatePeerId.selector); - s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); - } - - function test_validateStaticAndDynamicConfig_DuplicateOffchainPublicKey_reverts() public { - Config memory config = _getBaseConfig(); - config.staticConfig.nodes[1].offchainPublicKey = config.staticConfig.nodes[0].offchainPublicKey; - - vm.expectRevert(RMNHome.DuplicateOffchainPublicKey.selector); - s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); - } - - function test_validateStaticAndDynamicConfig_DuplicateSourceChain_reverts() public { - Config memory config = _getBaseConfig(); - config.dynamicConfig.sourceChains[1].chainSelector = config.dynamicConfig.sourceChains[0].chainSelector; - - vm.expectRevert(RMNHome.DuplicateSourceChain.selector); - s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); - } - - function test_validateStaticAndDynamicConfig_OutOfBoundsObserverNodeIndex_reverts() public { - Config memory config = _getBaseConfig(); - config.dynamicConfig.sourceChains[0].observerNodesBitmap = 1 << config.staticConfig.nodes.length; - - vm.expectRevert(RMNHome.OutOfBoundsObserverNodeIndex.selector); - s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); - } - - function test_validateStaticAndDynamicConfig_NotEnoughObservers_reverts() public { - Config memory config = _getBaseConfig(); - config.dynamicConfig.sourceChains[0].f++; - - vm.expectRevert(RMNHome.NotEnoughObservers.selector); - s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); - } -} - -contract RMNHome_setDynamicConfig is RMNHomeTest { - function setUp() public { - Config memory config = _getBaseConfig(); - s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); - } - - function test_setDynamicConfig_success() public { - (bytes32 priorActiveDigest,) = s_rmnHome.getConfigDigests(); - - Config memory config = _getBaseConfig(); - config.dynamicConfig.sourceChains[1].f--; - - (, bytes32 candidateConfigDigest) = s_rmnHome.getConfigDigests(); - - vm.expectEmit(); - emit RMNHome.DynamicConfigSet(candidateConfigDigest, config.dynamicConfig); - - s_rmnHome.setDynamicConfig(config.dynamicConfig, candidateConfigDigest); - - (RMNHome.VersionedConfig memory storedVersionedConfig, bool ok) = s_rmnHome.getConfig(candidateConfigDigest); - assertTrue(ok); - assertEq(storedVersionedConfig.dynamicConfig.sourceChains[0].f, config.dynamicConfig.sourceChains[0].f); - - // Asser the digests don't change when updating the dynamic config - (bytes32 activeDigest, bytes32 candidateDigest) = s_rmnHome.getConfigDigests(); - assertEq(activeDigest, priorActiveDigest); - assertEq(candidateDigest, candidateConfigDigest); - } - - // Asserts the validation function is being called - function test_setDynamicConfig_MinObserversTooHigh_reverts() public { - Config memory config = _getBaseConfig(); - config.dynamicConfig.sourceChains[0].f++; - - vm.expectRevert(abi.encodeWithSelector(RMNHome.DigestNotFound.selector, ZERO_DIGEST)); - s_rmnHome.setDynamicConfig(config.dynamicConfig, ZERO_DIGEST); - } - - function test_setDynamicConfig_DigestNotFound_reverts() public { - // Zero always reverts - vm.expectRevert(abi.encodeWithSelector(RMNHome.DigestNotFound.selector, ZERO_DIGEST)); - s_rmnHome.setDynamicConfig(_getBaseConfig().dynamicConfig, ZERO_DIGEST); - - // Non-existent digest reverts - bytes32 nonExistentDigest = keccak256("nonExistentDigest"); - vm.expectRevert(abi.encodeWithSelector(RMNHome.DigestNotFound.selector, nonExistentDigest)); - s_rmnHome.setDynamicConfig(_getBaseConfig().dynamicConfig, nonExistentDigest); - } - - function test_setDynamicConfig_OnlyOwner_reverts() public { - Config memory config = _getBaseConfig(); - - vm.startPrank(address(0)); - - vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); - s_rmnHome.setDynamicConfig(config.dynamicConfig, keccak256("configDigest")); - } -} diff --git a/contracts/src/v0.8/ccip/test/rmn/RMNHome/RMNHome.getConfigDigests.t.sol b/contracts/src/v0.8/ccip/test/rmn/RMNHome/RMNHome.getConfigDigests.t.sol new file mode 100644 index 00000000000..b339bb183e7 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/rmn/RMNHome/RMNHome.getConfigDigests.t.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {RMNHomeTestSetup} from "./RMNHomeTestSetup.t.sol"; + +contract RMNHome_getConfigDigests is RMNHomeTestSetup { + function test_getConfigDigests_success() public { + (bytes32 activeDigest, bytes32 candidateDigest) = s_rmnHome.getConfigDigests(); + assertEq(activeDigest, ZERO_DIGEST); + assertEq(candidateDigest, ZERO_DIGEST); + + Config memory config = _getBaseConfig(); + bytes32 firstDigest = s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); + + (activeDigest, candidateDigest) = s_rmnHome.getConfigDigests(); + assertEq(activeDigest, ZERO_DIGEST); + assertEq(candidateDigest, firstDigest); + + s_rmnHome.promoteCandidateAndRevokeActive(firstDigest, ZERO_DIGEST); + + (activeDigest, candidateDigest) = s_rmnHome.getConfigDigests(); + assertEq(activeDigest, firstDigest); + assertEq(candidateDigest, ZERO_DIGEST); + + bytes32 secondDigest = s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); + + (activeDigest, candidateDigest) = s_rmnHome.getConfigDigests(); + assertEq(activeDigest, firstDigest); + assertEq(candidateDigest, secondDigest); + + assertEq(activeDigest, s_rmnHome.getActiveDigest()); + assertEq(candidateDigest, s_rmnHome.getCandidateDigest()); + } +} diff --git a/contracts/src/v0.8/ccip/test/rmn/RMNHome/RMNHome.promoteCandidateAndRevokeActive.t.sol b/contracts/src/v0.8/ccip/test/rmn/RMNHome/RMNHome.promoteCandidateAndRevokeActive.t.sol new file mode 100644 index 00000000000..6d99ed1cfaa --- /dev/null +++ b/contracts/src/v0.8/ccip/test/rmn/RMNHome/RMNHome.promoteCandidateAndRevokeActive.t.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Ownable2Step} from "../../../../shared/access/Ownable2Step.sol"; +import {RMNHome} from "../../../rmn/RMNHome.sol"; + +import {RMNHomeTestSetup} from "./RMNHomeTestSetup.t.sol"; + +contract RMNHome_promoteCandidateAndRevokeActive is RMNHomeTestSetup { + function test_promoteCandidateAndRevokeActive_success() public { + Config memory config = _getBaseConfig(); + bytes32 firstConfigToPromote = s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); + + vm.expectEmit(); + emit RMNHome.ConfigPromoted(firstConfigToPromote); + + s_rmnHome.promoteCandidateAndRevokeActive(firstConfigToPromote, ZERO_DIGEST); + + // Assert the active digest is updated and the candidate digest is set to zero + (bytes32 activeDigest, bytes32 candidateDigest) = s_rmnHome.getConfigDigests(); + assertEq(activeDigest, firstConfigToPromote); + assertEq(candidateDigest, ZERO_DIGEST); + + // Set a new candidate to promote over a non-zero active config. + config.staticConfig.offchainConfig = abi.encode("new_static_config"); + config.dynamicConfig.offchainConfig = abi.encode("new_dynamic_config"); + bytes32 secondConfigToPromote = s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); + + vm.expectEmit(); + emit RMNHome.ActiveConfigRevoked(firstConfigToPromote); + + vm.expectEmit(); + emit RMNHome.ConfigPromoted(secondConfigToPromote); + + s_rmnHome.promoteCandidateAndRevokeActive(secondConfigToPromote, firstConfigToPromote); + + (RMNHome.VersionedConfig memory activeConfig, RMNHome.VersionedConfig memory candidateConfig) = + s_rmnHome.getAllConfigs(); + assertEq(activeConfig.configDigest, secondConfigToPromote); + assertEq(activeConfig.staticConfig.offchainConfig, config.staticConfig.offchainConfig); + assertEq(activeConfig.dynamicConfig.offchainConfig, config.dynamicConfig.offchainConfig); + + assertEq(candidateConfig.configDigest, ZERO_DIGEST); + } + + function test_promoteCandidateAndRevokeActive_NoOpStateTransitionNotAllowed_reverts() public { + vm.expectRevert(RMNHome.NoOpStateTransitionNotAllowed.selector); + s_rmnHome.promoteCandidateAndRevokeActive(ZERO_DIGEST, ZERO_DIGEST); + } + + function test_promoteCandidateAndRevokeActive_ConfigDigestMismatch_reverts() public { + (bytes32 priorActiveDigest, bytes32 priorCandidateDigest) = s_rmnHome.getConfigDigests(); + bytes32 wrongActiveDigest = keccak256("wrongActiveDigest"); + bytes32 wrongCandidateDigest = keccak256("wrongCandidateDigest"); + + vm.expectRevert( + abi.encodeWithSelector(RMNHome.ConfigDigestMismatch.selector, priorActiveDigest, wrongCandidateDigest) + ); + s_rmnHome.promoteCandidateAndRevokeActive(wrongCandidateDigest, wrongActiveDigest); + + vm.expectRevert(abi.encodeWithSelector(RMNHome.ConfigDigestMismatch.selector, priorActiveDigest, wrongActiveDigest)); + + s_rmnHome.promoteCandidateAndRevokeActive(priorCandidateDigest, wrongActiveDigest); + } + + function test_promoteCandidateAndRevokeActive_OnlyOwner_reverts() public { + vm.startPrank(address(0)); + + vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); + s_rmnHome.promoteCandidateAndRevokeActive(keccak256("toPromote"), keccak256("ToRevoke")); + } +} diff --git a/contracts/src/v0.8/ccip/test/rmn/RMNHome/RMNHome.revokeCandidate.t.sol b/contracts/src/v0.8/ccip/test/rmn/RMNHome/RMNHome.revokeCandidate.t.sol new file mode 100644 index 00000000000..a486bd193ff --- /dev/null +++ b/contracts/src/v0.8/ccip/test/rmn/RMNHome/RMNHome.revokeCandidate.t.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Ownable2Step} from "../../../../shared/access/Ownable2Step.sol"; +import {RMNHome} from "../../../rmn/RMNHome.sol"; + +import {RMNHomeTestSetup} from "./RMNHomeTestSetup.t.sol"; + +contract RMNHome_revokeCandidate is RMNHomeTestSetup { + // Sets two configs + function setUp() public { + Config memory config = _getBaseConfig(); + bytes32 digest = s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); + s_rmnHome.promoteCandidateAndRevokeActive(digest, ZERO_DIGEST); + + config.dynamicConfig.sourceChains[1].f--; + s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); + } + + function test_revokeCandidate_success() public { + (bytes32 priorActiveDigest, bytes32 priorCandidateDigest) = s_rmnHome.getConfigDigests(); + + vm.expectEmit(); + emit RMNHome.CandidateConfigRevoked(priorCandidateDigest); + + s_rmnHome.revokeCandidate(priorCandidateDigest); + + (RMNHome.VersionedConfig memory storedVersionedConfig, bool ok) = s_rmnHome.getConfig(priorCandidateDigest); + assertFalse(ok); + // Ensure no old data is returned, even though it's still in storage + assertEq(storedVersionedConfig.version, 0); + assertEq(storedVersionedConfig.staticConfig.nodes.length, 0); + assertEq(storedVersionedConfig.dynamicConfig.sourceChains.length, 0); + + // Asser the active digest is unaffected but the candidate digest is set to zero + (bytes32 activeDigest, bytes32 candidateDigest) = s_rmnHome.getConfigDigests(); + assertEq(activeDigest, priorActiveDigest); + assertEq(candidateDigest, ZERO_DIGEST); + assertTrue(candidateDigest != priorCandidateDigest); + } + + function test_revokeCandidate_ConfigDigestMismatch_reverts() public { + (, bytes32 priorCandidateDigest) = s_rmnHome.getConfigDigests(); + + bytes32 wrongDigest = keccak256("wrong_digest"); + vm.expectRevert(abi.encodeWithSelector(RMNHome.ConfigDigestMismatch.selector, priorCandidateDigest, wrongDigest)); + s_rmnHome.revokeCandidate(wrongDigest); + } + + function test_revokeCandidate_RevokingZeroDigestNotAllowed_reverts() public { + vm.expectRevert(RMNHome.RevokingZeroDigestNotAllowed.selector); + s_rmnHome.revokeCandidate(ZERO_DIGEST); + } + + function test_revokeCandidate_OnlyOwner_reverts() public { + vm.startPrank(address(0)); + + vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); + s_rmnHome.revokeCandidate(keccak256("configDigest")); + } +} diff --git a/contracts/src/v0.8/ccip/test/rmn/RMNHome/RMNHome.setCandidate.t.sol b/contracts/src/v0.8/ccip/test/rmn/RMNHome/RMNHome.setCandidate.t.sol new file mode 100644 index 00000000000..6fae7a90552 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/rmn/RMNHome/RMNHome.setCandidate.t.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Ownable2Step} from "../../../../shared/access/Ownable2Step.sol"; +import {RMNHome} from "../../../rmn/RMNHome.sol"; + +import {RMNHomeTestSetup} from "./RMNHomeTestSetup.t.sol"; + +contract RMNHome_setCandidate is RMNHomeTestSetup { + function test_setCandidate_success() public { + Config memory config = _getBaseConfig(); + RMNHome.VersionedConfig memory versionedConfig = RMNHome.VersionedConfig({ + version: 1, + staticConfig: config.staticConfig, + dynamicConfig: config.dynamicConfig, + configDigest: ZERO_DIGEST + }); + + versionedConfig.configDigest = _getConfigDigest(abi.encode(versionedConfig.staticConfig), versionedConfig.version); + + vm.expectEmit(); + emit RMNHome.ConfigSet( + versionedConfig.configDigest, versionedConfig.version, versionedConfig.staticConfig, versionedConfig.dynamicConfig + ); + + s_rmnHome.setCandidate(versionedConfig.staticConfig, versionedConfig.dynamicConfig, ZERO_DIGEST); + + (RMNHome.VersionedConfig memory storedVersionedConfig, bool ok) = s_rmnHome.getConfig(versionedConfig.configDigest); + assertTrue(ok); + assertEq(storedVersionedConfig.version, versionedConfig.version); + RMNHome.StaticConfig memory storedStaticConfig = storedVersionedConfig.staticConfig; + RMNHome.DynamicConfig memory storedDynamicConfig = storedVersionedConfig.dynamicConfig; + + assertEq(storedStaticConfig.nodes.length, versionedConfig.staticConfig.nodes.length); + for (uint256 i = 0; i < storedStaticConfig.nodes.length; i++) { + RMNHome.Node memory storedNode = storedStaticConfig.nodes[i]; + assertEq(storedNode.peerId, versionedConfig.staticConfig.nodes[i].peerId); + assertEq(storedNode.offchainPublicKey, versionedConfig.staticConfig.nodes[i].offchainPublicKey); + } + + assertEq(storedDynamicConfig.sourceChains.length, versionedConfig.dynamicConfig.sourceChains.length); + for (uint256 i = 0; i < storedDynamicConfig.sourceChains.length; i++) { + RMNHome.SourceChain memory storedSourceChain = storedDynamicConfig.sourceChains[i]; + assertEq(storedSourceChain.chainSelector, versionedConfig.dynamicConfig.sourceChains[i].chainSelector); + assertEq(storedSourceChain.f, versionedConfig.dynamicConfig.sourceChains[i].f); + assertEq(storedSourceChain.observerNodesBitmap, versionedConfig.dynamicConfig.sourceChains[i].observerNodesBitmap); + } + assertEq(storedDynamicConfig.offchainConfig, versionedConfig.dynamicConfig.offchainConfig); + assertEq(storedStaticConfig.offchainConfig, versionedConfig.staticConfig.offchainConfig); + } + + function test_setCandidate_ConfigDigestMismatch_reverts() public { + Config memory config = _getBaseConfig(); + + bytes32 digest = s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); + + vm.expectRevert(abi.encodeWithSelector(RMNHome.ConfigDigestMismatch.selector, digest, ZERO_DIGEST)); + s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); + + vm.expectEmit(); + emit RMNHome.CandidateConfigRevoked(digest); + + s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, digest); + } + + function test_setCandidate_OnlyOwner_reverts() public { + Config memory config = _getBaseConfig(); + + vm.startPrank(address(0)); + + vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); + s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); + } +} diff --git a/contracts/src/v0.8/ccip/test/rmn/RMNHome/RMNHome.setDynamicConfig.t.sol b/contracts/src/v0.8/ccip/test/rmn/RMNHome/RMNHome.setDynamicConfig.t.sol new file mode 100644 index 00000000000..048a6ef226e --- /dev/null +++ b/contracts/src/v0.8/ccip/test/rmn/RMNHome/RMNHome.setDynamicConfig.t.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Ownable2Step} from "../../../../shared/access/Ownable2Step.sol"; +import {RMNHome} from "../../../rmn/RMNHome.sol"; + +import {RMNHomeTestSetup} from "./RMNHomeTestSetup.t.sol"; + +contract RMNHome_setDynamicConfig is RMNHomeTestSetup { + function setUp() public { + Config memory config = _getBaseConfig(); + s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); + } + + function test_setDynamicConfig_success() public { + (bytes32 priorActiveDigest,) = s_rmnHome.getConfigDigests(); + + Config memory config = _getBaseConfig(); + config.dynamicConfig.sourceChains[1].f--; + + (, bytes32 candidateConfigDigest) = s_rmnHome.getConfigDigests(); + + vm.expectEmit(); + emit RMNHome.DynamicConfigSet(candidateConfigDigest, config.dynamicConfig); + + s_rmnHome.setDynamicConfig(config.dynamicConfig, candidateConfigDigest); + + (RMNHome.VersionedConfig memory storedVersionedConfig, bool ok) = s_rmnHome.getConfig(candidateConfigDigest); + assertTrue(ok); + assertEq(storedVersionedConfig.dynamicConfig.sourceChains[0].f, config.dynamicConfig.sourceChains[0].f); + + // Asser the digests don't change when updating the dynamic config + (bytes32 activeDigest, bytes32 candidateDigest) = s_rmnHome.getConfigDigests(); + assertEq(activeDigest, priorActiveDigest); + assertEq(candidateDigest, candidateConfigDigest); + } + + // Asserts the validation function is being called + function test_setDynamicConfig_MinObserversTooHigh_reverts() public { + Config memory config = _getBaseConfig(); + config.dynamicConfig.sourceChains[0].f++; + + vm.expectRevert(abi.encodeWithSelector(RMNHome.DigestNotFound.selector, ZERO_DIGEST)); + s_rmnHome.setDynamicConfig(config.dynamicConfig, ZERO_DIGEST); + } + + function test_setDynamicConfig_DigestNotFound_reverts() public { + // Zero always reverts + vm.expectRevert(abi.encodeWithSelector(RMNHome.DigestNotFound.selector, ZERO_DIGEST)); + s_rmnHome.setDynamicConfig(_getBaseConfig().dynamicConfig, ZERO_DIGEST); + + // Non-existent digest reverts + bytes32 nonExistentDigest = keccak256("nonExistentDigest"); + vm.expectRevert(abi.encodeWithSelector(RMNHome.DigestNotFound.selector, nonExistentDigest)); + s_rmnHome.setDynamicConfig(_getBaseConfig().dynamicConfig, nonExistentDigest); + } + + function test_setDynamicConfig_OnlyOwner_reverts() public { + Config memory config = _getBaseConfig(); + + vm.startPrank(address(0)); + + vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); + s_rmnHome.setDynamicConfig(config.dynamicConfig, keccak256("configDigest")); + } +} diff --git a/contracts/src/v0.8/ccip/test/rmn/RMNHome/RMNHome.validateStaticAndDynamicConfig.t.sol b/contracts/src/v0.8/ccip/test/rmn/RMNHome/RMNHome.validateStaticAndDynamicConfig.t.sol new file mode 100644 index 00000000000..2aa7b1a5100 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/rmn/RMNHome/RMNHome.validateStaticAndDynamicConfig.t.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {RMNHome} from "../../../rmn/RMNHome.sol"; + +import {RMNHomeTestSetup} from "./RMNHomeTestSetup.t.sol"; + +contract RMNHome_validateStaticAndDynamicConfig is RMNHomeTestSetup { + function test_validateStaticAndDynamicConfig_OutOfBoundsNodesLength_reverts() public { + Config memory config = _getBaseConfig(); + config.staticConfig.nodes = new RMNHome.Node[](257); + + vm.expectRevert(RMNHome.OutOfBoundsNodesLength.selector); + s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); + } + + function test_validateStaticAndDynamicConfig_DuplicatePeerId_reverts() public { + Config memory config = _getBaseConfig(); + config.staticConfig.nodes[1].peerId = config.staticConfig.nodes[0].peerId; + + vm.expectRevert(RMNHome.DuplicatePeerId.selector); + s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); + } + + function test_validateStaticAndDynamicConfig_DuplicateOffchainPublicKey_reverts() public { + Config memory config = _getBaseConfig(); + config.staticConfig.nodes[1].offchainPublicKey = config.staticConfig.nodes[0].offchainPublicKey; + + vm.expectRevert(RMNHome.DuplicateOffchainPublicKey.selector); + s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); + } + + function test_validateStaticAndDynamicConfig_DuplicateSourceChain_reverts() public { + Config memory config = _getBaseConfig(); + config.dynamicConfig.sourceChains[1].chainSelector = config.dynamicConfig.sourceChains[0].chainSelector; + + vm.expectRevert(RMNHome.DuplicateSourceChain.selector); + s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); + } + + function test_validateStaticAndDynamicConfig_OutOfBoundsObserverNodeIndex_reverts() public { + Config memory config = _getBaseConfig(); + config.dynamicConfig.sourceChains[0].observerNodesBitmap = 1 << config.staticConfig.nodes.length; + + vm.expectRevert(RMNHome.OutOfBoundsObserverNodeIndex.selector); + s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); + } + + function test_validateStaticAndDynamicConfig_NotEnoughObservers_reverts() public { + Config memory config = _getBaseConfig(); + config.dynamicConfig.sourceChains[0].f++; + + vm.expectRevert(RMNHome.NotEnoughObservers.selector); + s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST); + } +} diff --git a/contracts/src/v0.8/ccip/test/rmn/RMNHome/RMNHomeTestSetup.t.sol b/contracts/src/v0.8/ccip/test/rmn/RMNHome/RMNHomeTestSetup.t.sol new file mode 100644 index 00000000000..6bb76c29ba7 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/rmn/RMNHome/RMNHomeTestSetup.t.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {RMNHome} from "../../../rmn/RMNHome.sol"; +import {Test} from "forge-std/Test.sol"; + +contract RMNHomeTestSetup is Test { + struct Config { + RMNHome.StaticConfig staticConfig; + RMNHome.DynamicConfig dynamicConfig; + } + + bytes32 internal constant ZERO_DIGEST = bytes32(uint256(0)); + RMNHome public s_rmnHome = new RMNHome(); + + function _getBaseConfig() internal pure returns (Config memory) { + RMNHome.Node[] memory nodes = new RMNHome.Node[](3); + nodes[0] = RMNHome.Node({peerId: keccak256("peerId_0"), offchainPublicKey: keccak256("offchainPublicKey_0")}); + nodes[1] = RMNHome.Node({peerId: keccak256("peerId_1"), offchainPublicKey: keccak256("offchainPublicKey_1")}); + nodes[2] = RMNHome.Node({peerId: keccak256("peerId_2"), offchainPublicKey: keccak256("offchainPublicKey_2")}); + + RMNHome.SourceChain[] memory sourceChains = new RMNHome.SourceChain[](2); + // Observer 0 for source chain 9000 + sourceChains[0] = RMNHome.SourceChain({chainSelector: 9000, f: 1, observerNodesBitmap: 1 << 0 | 1 << 1 | 1 << 2}); + // Observers 0, 1 and 2 for source chain 9001 + sourceChains[1] = RMNHome.SourceChain({chainSelector: 9001, f: 1, observerNodesBitmap: 1 << 0 | 1 << 1 | 1 << 2}); + + return Config({ + staticConfig: RMNHome.StaticConfig({nodes: nodes, offchainConfig: abi.encode("static_config")}), + dynamicConfig: RMNHome.DynamicConfig({sourceChains: sourceChains, offchainConfig: abi.encode("dynamic_config")}) + }); + } + + uint256 private constant PREFIX_MASK = type(uint256).max << (256 - 16); // 0xFFFF00..00 + uint256 private constant PREFIX = 0x000b << (256 - 16); // 0x000b00..00 + + function _getConfigDigest(bytes memory staticConfig, uint32 version) internal view returns (bytes32) { + return bytes32( + (PREFIX & PREFIX_MASK) + | ( + uint256( + keccak256(bytes.concat(abi.encode(bytes32("EVM"), block.chainid, address(s_rmnHome), version), staticConfig)) + ) & ~PREFIX_MASK + ) + ); + } +} diff --git a/contracts/src/v0.8/ccip/test/rmn/RMNRemote.t.sol b/contracts/src/v0.8/ccip/test/rmn/RMNRemote.t.sol deleted file mode 100644 index b9411d2e3a9..00000000000 --- a/contracts/src/v0.8/ccip/test/rmn/RMNRemote.t.sol +++ /dev/null @@ -1,261 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.24; - -import {IRMNRemote} from "../../interfaces/IRMNRemote.sol"; - -import {Ownable2Step} from "../../../shared/access/Ownable2Step.sol"; -import {Internal} from "../../libraries/Internal.sol"; -import {GLOBAL_CURSE_SUBJECT, LEGACY_CURSE_SUBJECT, RMNRemote} from "../../rmn/RMNRemote.sol"; -import {RMNRemoteSetup} from "./RMNRemoteSetup.t.sol"; - -contract RMNRemote_constructor is RMNRemoteSetup { - function test_constructor_success() public view { - assertEq(s_rmnRemote.getLocalChainSelector(), 1); - } - - function test_constructor_zeroChainSelector_reverts() public { - vm.expectRevert(RMNRemote.ZeroValueNotAllowed.selector); - new RMNRemote(0); - } -} - -contract RMNRemote_setConfig is RMNRemoteSetup { - function test_setConfig_ZeroValueNotAllowed_revert() public { - RMNRemote.Config memory config = - RMNRemote.Config({rmnHomeContractConfigDigest: bytes32(0), signers: s_signers, f: 1}); - - vm.expectRevert(RMNRemote.ZeroValueNotAllowed.selector); - - s_rmnRemote.setConfig(config); - } - - function test_setConfig_addSigner_removeSigner_success() public { - uint32 currentConfigVersion = 0; - uint256 numSigners = s_signers.length; - RMNRemote.Config memory config = - RMNRemote.Config({rmnHomeContractConfigDigest: _randomBytes32(), signers: s_signers, f: 1}); - - vm.expectEmit(); - emit RMNRemote.ConfigSet(++currentConfigVersion, config); - - s_rmnRemote.setConfig(config); - - // add a signer - address newSigner = makeAddr("new signer"); - s_signers.push(RMNRemote.Signer({onchainPublicKey: newSigner, nodeIndex: uint64(numSigners)})); - config = RMNRemote.Config({rmnHomeContractConfigDigest: _randomBytes32(), signers: s_signers, f: 1}); - - vm.expectEmit(); - emit RMNRemote.ConfigSet(++currentConfigVersion, config); - - s_rmnRemote.setConfig(config); - - (uint32 version, RMNRemote.Config memory gotConfig) = s_rmnRemote.getVersionedConfig(); - assertEq(gotConfig.signers.length, s_signers.length); - assertEq(gotConfig.signers[numSigners].onchainPublicKey, newSigner); - assertEq(gotConfig.signers[numSigners].nodeIndex, uint64(numSigners)); - assertEq(version, currentConfigVersion); - - // remove two signers - s_signers.pop(); - s_signers.pop(); - config = RMNRemote.Config({rmnHomeContractConfigDigest: _randomBytes32(), signers: s_signers, f: 1}); - - vm.expectEmit(); - emit RMNRemote.ConfigSet(++currentConfigVersion, config); - - s_rmnRemote.setConfig(config); - - (version, gotConfig) = s_rmnRemote.getVersionedConfig(); - assertEq(gotConfig.signers.length, s_signers.length); - assertEq(version, currentConfigVersion); - } - - function test_setConfig_invalidSignerOrder_reverts() public { - s_signers.push(RMNRemote.Signer({onchainPublicKey: address(4), nodeIndex: 0})); - RMNRemote.Config memory config = - RMNRemote.Config({rmnHomeContractConfigDigest: _randomBytes32(), signers: s_signers, f: 1}); - - vm.expectRevert(RMNRemote.InvalidSignerOrder.selector); - s_rmnRemote.setConfig(config); - } - - function test_setConfig_notEnoughSigners_reverts() public { - RMNRemote.Config memory config = RMNRemote.Config({ - rmnHomeContractConfigDigest: _randomBytes32(), - signers: s_signers, - f: uint64(s_signers.length / 2) // at least 2f+1 is required - }); - - vm.expectRevert(RMNRemote.NotEnoughSigners.selector); - s_rmnRemote.setConfig(config); - } - - function test_setConfig_duplicateOnChainPublicKey_reverts() public { - s_signers.push(RMNRemote.Signer({onchainPublicKey: s_signerWallets[0].addr, nodeIndex: uint64(s_signers.length)})); - RMNRemote.Config memory config = - RMNRemote.Config({rmnHomeContractConfigDigest: _randomBytes32(), signers: s_signers, f: 1}); - - vm.expectRevert(RMNRemote.DuplicateOnchainPublicKey.selector); - s_rmnRemote.setConfig(config); - } -} - -contract RMNRemote_verify_withConfigNotSet is RMNRemoteSetup { - function test_verify_reverts() public { - Internal.MerkleRoot[] memory merkleRoots = new Internal.MerkleRoot[](0); - IRMNRemote.Signature[] memory signatures = new IRMNRemote.Signature[](0); - - vm.expectRevert(RMNRemote.ConfigNotSet.selector); - s_rmnRemote.verify(OFF_RAMP_ADDRESS, merkleRoots, signatures); - } -} - -contract RMNRemote_verify_withConfigSet is RMNRemoteSetup { - function setUp() public override { - super.setUp(); - RMNRemote.Config memory config = - RMNRemote.Config({rmnHomeContractConfigDigest: _randomBytes32(), signers: s_signers, f: 3}); - s_rmnRemote.setConfig(config); - _generatePayloadAndSigs(2, 4); - } - - function test_verify_success() public view { - s_rmnRemote.verify(OFF_RAMP_ADDRESS, s_merkleRoots, s_signatures); - } - - function test_verify_InvalidSignature_reverts() public { - IRMNRemote.Signature memory sig = s_signatures[s_signatures.length - 1]; - sig.r = _randomBytes32(); - s_signatures.pop(); - s_signatures.push(sig); - - vm.expectRevert(RMNRemote.InvalidSignature.selector); - s_rmnRemote.verify(OFF_RAMP_ADDRESS, s_merkleRoots, s_signatures); - } - - function test_verify_OutOfOrderSignatures_not_sorted_reverts() public { - IRMNRemote.Signature memory sig1 = s_signatures[s_signatures.length - 1]; - s_signatures.pop(); - IRMNRemote.Signature memory sig2 = s_signatures[s_signatures.length - 1]; - s_signatures.pop(); - s_signatures.push(sig1); - s_signatures.push(sig2); - - vm.expectRevert(RMNRemote.OutOfOrderSignatures.selector); - s_rmnRemote.verify(OFF_RAMP_ADDRESS, s_merkleRoots, s_signatures); - } - - function test_verify_OutOfOrderSignatures_duplicateSignature_reverts() public { - IRMNRemote.Signature memory sig = s_signatures[s_signatures.length - 2]; - s_signatures.pop(); - s_signatures.push(sig); - - vm.expectRevert(RMNRemote.OutOfOrderSignatures.selector); - s_rmnRemote.verify(OFF_RAMP_ADDRESS, s_merkleRoots, s_signatures); - } - - function test_verify_UnexpectedSigner_reverts() public { - _setupSigners(4); // create new signers that aren't configured on RMNRemote - _generatePayloadAndSigs(2, 4); - - vm.expectRevert(RMNRemote.UnexpectedSigner.selector); - s_rmnRemote.verify(OFF_RAMP_ADDRESS, s_merkleRoots, s_signatures); - } - - function test_verify_ThresholdNotMet_reverts() public { - RMNRemote.Config memory config = - RMNRemote.Config({rmnHomeContractConfigDigest: _randomBytes32(), signers: s_signers, f: 2}); // 3 = f+1 sigs required - s_rmnRemote.setConfig(config); - - _generatePayloadAndSigs(2, 2); // 2 sigs generated, but 3 required - - vm.expectRevert(RMNRemote.ThresholdNotMet.selector); - s_rmnRemote.verify(OFF_RAMP_ADDRESS, s_merkleRoots, s_signatures); - } -} - -contract RMNRemote_curse is RMNRemoteSetup { - function test_curse_success() public { - vm.expectEmit(); - emit RMNRemote.Cursed(s_curseSubjects); - - s_rmnRemote.curse(s_curseSubjects); - - assertEq(abi.encode(s_rmnRemote.getCursedSubjects()), abi.encode(s_curseSubjects)); - assertTrue(s_rmnRemote.isCursed(CURSE_SUBJ_1)); - assertTrue(s_rmnRemote.isCursed(CURSE_SUBJ_2)); - // Should not have cursed a random subject - assertFalse(s_rmnRemote.isCursed(bytes16(keccak256("subject 3")))); - } - - function test_curse_AlreadyCursed_duplicateSubject_reverts() public { - s_curseSubjects.push(CURSE_SUBJ_1); - - vm.expectRevert(abi.encodeWithSelector(RMNRemote.AlreadyCursed.selector, CURSE_SUBJ_1)); - s_rmnRemote.curse(s_curseSubjects); - } - - function test_curse_calledByNonOwner_reverts() public { - vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); - vm.stopPrank(); - vm.prank(STRANGER); - s_rmnRemote.curse(s_curseSubjects); - } -} - -contract RMNRemote_uncurse is RMNRemoteSetup { - function setUp() public override { - super.setUp(); - s_rmnRemote.curse(s_curseSubjects); - } - - function test_uncurse_success() public { - vm.expectEmit(); - emit RMNRemote.Uncursed(s_curseSubjects); - - s_rmnRemote.uncurse(s_curseSubjects); - - assertEq(s_rmnRemote.getCursedSubjects().length, 0); - assertFalse(s_rmnRemote.isCursed(CURSE_SUBJ_1)); - assertFalse(s_rmnRemote.isCursed(CURSE_SUBJ_2)); - } - - function test_uncurse_NotCursed_duplicatedUncurseSubject_reverts() public { - s_curseSubjects.push(CURSE_SUBJ_1); - - vm.expectRevert(abi.encodeWithSelector(RMNRemote.NotCursed.selector, CURSE_SUBJ_1)); - s_rmnRemote.uncurse(s_curseSubjects); - } - - function test_uncurse_calledByNonOwner_reverts() public { - vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); - vm.stopPrank(); - vm.prank(STRANGER); - s_rmnRemote.uncurse(s_curseSubjects); - } -} - -contract RMNRemote_global_and_legacy_curses is RMNRemoteSetup { - function test_global_and_legacy_curses_success() public { - bytes16 randSubject = bytes16(keccak256("random subject")); - assertFalse(s_rmnRemote.isCursed()); - assertFalse(s_rmnRemote.isCursed(randSubject)); - - s_rmnRemote.curse(GLOBAL_CURSE_SUBJECT); - assertTrue(s_rmnRemote.isCursed()); - assertTrue(s_rmnRemote.isCursed(randSubject)); - - s_rmnRemote.uncurse(GLOBAL_CURSE_SUBJECT); - assertFalse(s_rmnRemote.isCursed()); - assertFalse(s_rmnRemote.isCursed(randSubject)); - - s_rmnRemote.curse(LEGACY_CURSE_SUBJECT); - assertTrue(s_rmnRemote.isCursed()); - assertFalse(s_rmnRemote.isCursed(randSubject)); // legacy curse doesn't affect specific subjects - - s_rmnRemote.uncurse(LEGACY_CURSE_SUBJECT); - assertFalse(s_rmnRemote.isCursed()); - assertFalse(s_rmnRemote.isCursed(randSubject)); - } -} diff --git a/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemote.constructor.t.sol b/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemote.constructor.t.sol new file mode 100644 index 00000000000..1cc9d9addb7 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemote.constructor.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {RMNRemote} from "../../../rmn/RMNRemote.sol"; +import {RMNRemoteSetup} from "./RMNRemoteSetup.t.sol"; + +contract RMNRemote_constructor is RMNRemoteSetup { + function test_constructor_success() public view { + assertEq(s_rmnRemote.getLocalChainSelector(), 1); + } + + function test_constructor_zeroChainSelector_reverts() public { + vm.expectRevert(RMNRemote.ZeroValueNotAllowed.selector); + new RMNRemote(0); + } +} diff --git a/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemote.curse.t.sol b/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemote.curse.t.sol new file mode 100644 index 00000000000..e1af2ab4e6b --- /dev/null +++ b/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemote.curse.t.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Ownable2Step} from "../../../../shared/access/Ownable2Step.sol"; +import {RMNRemote} from "../../../rmn/RMNRemote.sol"; +import {RMNRemoteSetup} from "./RMNRemoteSetup.t.sol"; + +contract RMNRemote_curse is RMNRemoteSetup { + function test_curse_success() public { + vm.expectEmit(); + emit RMNRemote.Cursed(s_curseSubjects); + + s_rmnRemote.curse(s_curseSubjects); + + assertEq(abi.encode(s_rmnRemote.getCursedSubjects()), abi.encode(s_curseSubjects)); + assertTrue(s_rmnRemote.isCursed(CURSE_SUBJ_1)); + assertTrue(s_rmnRemote.isCursed(CURSE_SUBJ_2)); + // Should not have cursed a random subject + assertFalse(s_rmnRemote.isCursed(bytes16(keccak256("subject 3")))); + } + + function test_curse_AlreadyCursed_duplicateSubject_reverts() public { + s_curseSubjects.push(CURSE_SUBJ_1); + + vm.expectRevert(abi.encodeWithSelector(RMNRemote.AlreadyCursed.selector, CURSE_SUBJ_1)); + s_rmnRemote.curse(s_curseSubjects); + } + + function test_curse_calledByNonOwner_reverts() public { + vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); + vm.stopPrank(); + vm.prank(STRANGER); + s_rmnRemote.curse(s_curseSubjects); + } +} diff --git a/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemote.globalAndLegacyCurses.t.sol b/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemote.globalAndLegacyCurses.t.sol new file mode 100644 index 00000000000..da6677678fe --- /dev/null +++ b/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemote.globalAndLegacyCurses.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {GLOBAL_CURSE_SUBJECT, LEGACY_CURSE_SUBJECT} from "../../../rmn/RMNRemote.sol"; +import {RMNRemoteSetup} from "./RMNRemoteSetup.t.sol"; + +contract RMNRemote_global_and_legacy_curses is RMNRemoteSetup { + function test_global_and_legacy_curses_success() public { + bytes16 randSubject = bytes16(keccak256("random subject")); + assertFalse(s_rmnRemote.isCursed()); + assertFalse(s_rmnRemote.isCursed(randSubject)); + + s_rmnRemote.curse(GLOBAL_CURSE_SUBJECT); + assertTrue(s_rmnRemote.isCursed()); + assertTrue(s_rmnRemote.isCursed(randSubject)); + + s_rmnRemote.uncurse(GLOBAL_CURSE_SUBJECT); + assertFalse(s_rmnRemote.isCursed()); + assertFalse(s_rmnRemote.isCursed(randSubject)); + + s_rmnRemote.curse(LEGACY_CURSE_SUBJECT); + assertTrue(s_rmnRemote.isCursed()); + assertFalse(s_rmnRemote.isCursed(randSubject)); // legacy curse doesn't affect specific subjects + + s_rmnRemote.uncurse(LEGACY_CURSE_SUBJECT); + assertFalse(s_rmnRemote.isCursed()); + assertFalse(s_rmnRemote.isCursed(randSubject)); + } +} diff --git a/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemote.setConfig.t.sol b/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemote.setConfig.t.sol new file mode 100644 index 00000000000..0805871955d --- /dev/null +++ b/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemote.setConfig.t.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {RMNRemote} from "../../../rmn/RMNRemote.sol"; +import {RMNRemoteSetup} from "./RMNRemoteSetup.t.sol"; + +contract RMNRemote_setConfig is RMNRemoteSetup { + function test_setConfig_ZeroValueNotAllowed_revert() public { + RMNRemote.Config memory config = + RMNRemote.Config({rmnHomeContractConfigDigest: bytes32(0), signers: s_signers, f: 1}); + + vm.expectRevert(RMNRemote.ZeroValueNotAllowed.selector); + + s_rmnRemote.setConfig(config); + } + + function test_setConfig_addSigner_removeSigner_success() public { + uint32 currentConfigVersion = 0; + uint256 numSigners = s_signers.length; + RMNRemote.Config memory config = + RMNRemote.Config({rmnHomeContractConfigDigest: _randomBytes32(), signers: s_signers, f: 1}); + + vm.expectEmit(); + emit RMNRemote.ConfigSet(++currentConfigVersion, config); + + s_rmnRemote.setConfig(config); + + // add a signer + address newSigner = makeAddr("new signer"); + s_signers.push(RMNRemote.Signer({onchainPublicKey: newSigner, nodeIndex: uint64(numSigners)})); + config = RMNRemote.Config({rmnHomeContractConfigDigest: _randomBytes32(), signers: s_signers, f: 1}); + + vm.expectEmit(); + emit RMNRemote.ConfigSet(++currentConfigVersion, config); + + s_rmnRemote.setConfig(config); + + (uint32 version, RMNRemote.Config memory gotConfig) = s_rmnRemote.getVersionedConfig(); + assertEq(gotConfig.signers.length, s_signers.length); + assertEq(gotConfig.signers[numSigners].onchainPublicKey, newSigner); + assertEq(gotConfig.signers[numSigners].nodeIndex, uint64(numSigners)); + assertEq(version, currentConfigVersion); + + // remove two signers + s_signers.pop(); + s_signers.pop(); + config = RMNRemote.Config({rmnHomeContractConfigDigest: _randomBytes32(), signers: s_signers, f: 1}); + + vm.expectEmit(); + emit RMNRemote.ConfigSet(++currentConfigVersion, config); + + s_rmnRemote.setConfig(config); + + (version, gotConfig) = s_rmnRemote.getVersionedConfig(); + assertEq(gotConfig.signers.length, s_signers.length); + assertEq(version, currentConfigVersion); + } + + function test_setConfig_invalidSignerOrder_reverts() public { + s_signers.push(RMNRemote.Signer({onchainPublicKey: address(4), nodeIndex: 0})); + RMNRemote.Config memory config = + RMNRemote.Config({rmnHomeContractConfigDigest: _randomBytes32(), signers: s_signers, f: 1}); + + vm.expectRevert(RMNRemote.InvalidSignerOrder.selector); + s_rmnRemote.setConfig(config); + } + + function test_setConfig_notEnoughSigners_reverts() public { + RMNRemote.Config memory config = RMNRemote.Config({ + rmnHomeContractConfigDigest: _randomBytes32(), + signers: s_signers, + f: uint64(s_signers.length / 2) // at least 2f+1 is required + }); + + vm.expectRevert(RMNRemote.NotEnoughSigners.selector); + s_rmnRemote.setConfig(config); + } + + function test_setConfig_duplicateOnChainPublicKey_reverts() public { + s_signers.push(RMNRemote.Signer({onchainPublicKey: s_signerWallets[0].addr, nodeIndex: uint64(s_signers.length)})); + RMNRemote.Config memory config = + RMNRemote.Config({rmnHomeContractConfigDigest: _randomBytes32(), signers: s_signers, f: 1}); + + vm.expectRevert(RMNRemote.DuplicateOnchainPublicKey.selector); + s_rmnRemote.setConfig(config); + } +} diff --git a/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemote.uncurse.t.sol b/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemote.uncurse.t.sol new file mode 100644 index 00000000000..ad784a8cb30 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemote.uncurse.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Ownable2Step} from "../../../../shared/access/Ownable2Step.sol"; +import {RMNRemote} from "../../../rmn/RMNRemote.sol"; +import {RMNRemoteSetup} from "./RMNRemoteSetup.t.sol"; + +contract RMNRemote_uncurse is RMNRemoteSetup { + function setUp() public override { + super.setUp(); + s_rmnRemote.curse(s_curseSubjects); + } + + function test_uncurse_success() public { + vm.expectEmit(); + emit RMNRemote.Uncursed(s_curseSubjects); + + s_rmnRemote.uncurse(s_curseSubjects); + + assertEq(s_rmnRemote.getCursedSubjects().length, 0); + assertFalse(s_rmnRemote.isCursed(CURSE_SUBJ_1)); + assertFalse(s_rmnRemote.isCursed(CURSE_SUBJ_2)); + } + + function test_uncurse_NotCursed_duplicatedUncurseSubject_reverts() public { + s_curseSubjects.push(CURSE_SUBJ_1); + + vm.expectRevert(abi.encodeWithSelector(RMNRemote.NotCursed.selector, CURSE_SUBJ_1)); + s_rmnRemote.uncurse(s_curseSubjects); + } + + function test_uncurse_calledByNonOwner_reverts() public { + vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); + vm.stopPrank(); + vm.prank(STRANGER); + s_rmnRemote.uncurse(s_curseSubjects); + } +} diff --git a/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemote.verifywithConfigNotSet.t.sol b/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemote.verifywithConfigNotSet.t.sol new file mode 100644 index 00000000000..bba4e8e6a0d --- /dev/null +++ b/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemote.verifywithConfigNotSet.t.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IRMNRemote} from "../../../interfaces/IRMNRemote.sol"; + +import {Internal} from "../../../libraries/Internal.sol"; +import {RMNRemote} from "../../../rmn/RMNRemote.sol"; +import {RMNRemoteSetup} from "./RMNRemoteSetup.t.sol"; + +contract RMNRemote_verify_withConfigNotSet is RMNRemoteSetup { + function test_verify_reverts() public { + Internal.MerkleRoot[] memory merkleRoots = new Internal.MerkleRoot[](0); + IRMNRemote.Signature[] memory signatures = new IRMNRemote.Signature[](0); + + vm.expectRevert(RMNRemote.ConfigNotSet.selector); + s_rmnRemote.verify(OFF_RAMP_ADDRESS, merkleRoots, signatures); + } +} diff --git a/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemote.verifywithConfigSet.t.sol b/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemote.verifywithConfigSet.t.sol new file mode 100644 index 00000000000..1ba9de9d039 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemote.verifywithConfigSet.t.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IRMNRemote} from "../../../interfaces/IRMNRemote.sol"; +import {RMNRemote} from "../../../rmn/RMNRemote.sol"; +import {RMNRemoteSetup} from "./RMNRemoteSetup.t.sol"; + +contract RMNRemote_verify_withConfigSet is RMNRemoteSetup { + function setUp() public override { + super.setUp(); + RMNRemote.Config memory config = + RMNRemote.Config({rmnHomeContractConfigDigest: _randomBytes32(), signers: s_signers, f: 3}); + s_rmnRemote.setConfig(config); + _generatePayloadAndSigs(2, 4); + } + + function test_verify_success() public view { + s_rmnRemote.verify(OFF_RAMP_ADDRESS, s_merkleRoots, s_signatures); + } + + function test_verify_InvalidSignature_reverts() public { + IRMNRemote.Signature memory sig = s_signatures[s_signatures.length - 1]; + sig.r = _randomBytes32(); + s_signatures.pop(); + s_signatures.push(sig); + + vm.expectRevert(RMNRemote.InvalidSignature.selector); + s_rmnRemote.verify(OFF_RAMP_ADDRESS, s_merkleRoots, s_signatures); + } + + function test_verify_OutOfOrderSignatures_not_sorted_reverts() public { + IRMNRemote.Signature memory sig1 = s_signatures[s_signatures.length - 1]; + s_signatures.pop(); + IRMNRemote.Signature memory sig2 = s_signatures[s_signatures.length - 1]; + s_signatures.pop(); + s_signatures.push(sig1); + s_signatures.push(sig2); + + vm.expectRevert(RMNRemote.OutOfOrderSignatures.selector); + s_rmnRemote.verify(OFF_RAMP_ADDRESS, s_merkleRoots, s_signatures); + } + + function test_verify_OutOfOrderSignatures_duplicateSignature_reverts() public { + IRMNRemote.Signature memory sig = s_signatures[s_signatures.length - 2]; + s_signatures.pop(); + s_signatures.push(sig); + + vm.expectRevert(RMNRemote.OutOfOrderSignatures.selector); + s_rmnRemote.verify(OFF_RAMP_ADDRESS, s_merkleRoots, s_signatures); + } + + function test_verify_UnexpectedSigner_reverts() public { + _setupSigners(4); // create new signers that aren't configured on RMNRemote + _generatePayloadAndSigs(2, 4); + + vm.expectRevert(RMNRemote.UnexpectedSigner.selector); + s_rmnRemote.verify(OFF_RAMP_ADDRESS, s_merkleRoots, s_signatures); + } + + function test_verify_ThresholdNotMet_reverts() public { + RMNRemote.Config memory config = + RMNRemote.Config({rmnHomeContractConfigDigest: _randomBytes32(), signers: s_signers, f: 2}); // 3 = f+1 sigs required + s_rmnRemote.setConfig(config); + + _generatePayloadAndSigs(2, 2); // 2 sigs generated, but 3 required + + vm.expectRevert(RMNRemote.ThresholdNotMet.selector); + s_rmnRemote.verify(OFF_RAMP_ADDRESS, s_merkleRoots, s_signatures); + } +} diff --git a/contracts/src/v0.8/ccip/test/rmn/RMNRemoteSetup.t.sol b/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemoteSetup.t.sol similarity index 95% rename from contracts/src/v0.8/ccip/test/rmn/RMNRemoteSetup.t.sol rename to contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemoteSetup.t.sol index 88af28e992c..b32dcd98a1a 100644 --- a/contracts/src/v0.8/ccip/test/rmn/RMNRemoteSetup.t.sol +++ b/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemoteSetup.t.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.24; -import {IRMNRemote} from "../../interfaces/IRMNRemote.sol"; -import {Internal} from "../../libraries/Internal.sol"; -import {RMNRemote} from "../../rmn/RMNRemote.sol"; -import {BaseTest} from "../BaseTest.t.sol"; +import {IRMNRemote} from "../../../interfaces/IRMNRemote.sol"; +import {Internal} from "../../../libraries/Internal.sol"; +import {RMNRemote} from "../../../rmn/RMNRemote.sol"; +import {BaseTest} from "../../BaseTest.t.sol"; import {Vm} from "forge-std/Vm.sol"; contract RMNRemoteSetup is BaseTest { diff --git a/contracts/src/v0.8/ccip/test/router/Router.t.sol b/contracts/src/v0.8/ccip/test/router/Router.t.sol deleted file mode 100644 index 9b2bb92b4e4..00000000000 --- a/contracts/src/v0.8/ccip/test/router/Router.t.sol +++ /dev/null @@ -1,819 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.24; - -import {IAny2EVMMessageReceiver} from "../../interfaces/IAny2EVMMessageReceiver.sol"; -import {IRouter} from "../../interfaces/IRouter.sol"; -import {IRouterClient} from "../../interfaces/IRouterClient.sol"; -import {IWrappedNative} from "../../interfaces/IWrappedNative.sol"; - -import {Router} from "../../Router.sol"; -import {Client} from "../../libraries/Client.sol"; -import {Internal} from "../../libraries/Internal.sol"; -import {OnRamp} from "../../onRamp/OnRamp.sol"; -import {MaybeRevertMessageReceiver} from "../helpers/receivers/MaybeRevertMessageReceiver.sol"; -import {OffRampSetup} from "../offRamp/offRamp/OffRampSetup.t.sol"; -import {OnRampSetup} from "../onRamp/onRamp/OnRampSetup.t.sol"; -import {RouterSetup} from "../router/RouterSetup.t.sol"; - -import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; - -contract Router_constructor is OnRampSetup { - function test_Constructor_Success() public view { - assertEq("Router 1.2.0", s_sourceRouter.typeAndVersion()); - assertEq(OWNER, s_sourceRouter.owner()); - } -} - -contract Router_recoverTokens is OnRampSetup { - function test_RecoverTokens_Success() public { - // Assert we can recover sourceToken - IERC20 token = IERC20(s_sourceTokens[0]); - uint256 balanceBefore = token.balanceOf(OWNER); - token.transfer(address(s_sourceRouter), 1); - assertEq(token.balanceOf(address(s_sourceRouter)), 1); - s_sourceRouter.recoverTokens(address(token), OWNER, 1); - assertEq(token.balanceOf(address(s_sourceRouter)), 0); - assertEq(token.balanceOf(OWNER), balanceBefore); - - // Assert we can recover native - balanceBefore = OWNER.balance; - deal(address(s_sourceRouter), 10); - assertEq(address(s_sourceRouter).balance, 10); - s_sourceRouter.recoverTokens(address(0), OWNER, 10); - assertEq(OWNER.balance, balanceBefore + 10); - assertEq(address(s_sourceRouter).balance, 0); - } - - function test_RecoverTokensNonOwner_Revert() public { - // Reverts if not owner - vm.startPrank(STRANGER); - vm.expectRevert("Only callable by owner"); - s_sourceRouter.recoverTokens(address(0), STRANGER, 1); - } - - function test_RecoverTokensInvalidRecipient_Revert() public { - vm.expectRevert(abi.encodeWithSelector(Router.InvalidRecipientAddress.selector, address(0))); - s_sourceRouter.recoverTokens(address(0), address(0), 1); - } - - function test_RecoverTokensNoFunds_Revert() public { - // Reverts if no funds present - vm.expectRevert(); - s_sourceRouter.recoverTokens(address(0), OWNER, 10); - } - - function test_RecoverTokensValueReceiver_Revert() public { - MaybeRevertMessageReceiver revertingValueReceiver = new MaybeRevertMessageReceiver(true); - deal(address(s_sourceRouter), 10); - - // Value receiver reverts - vm.expectRevert(Router.FailedToSendValue.selector); - s_sourceRouter.recoverTokens(address(0), address(revertingValueReceiver), 10); - } -} - -contract Router_ccipSend is OnRampSetup { - event Burned(address indexed sender, uint256 amount); - - function test_CCIPSendLinkFeeOneTokenSuccess_gas() public { - vm.pauseGasMetering(); - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - - IERC20 sourceToken1 = IERC20(s_sourceTokens[1]); - sourceToken1.approve(address(s_sourceRouter), 2 ** 64); - - message.tokenAmounts = new Client.EVMTokenAmount[](1); - message.tokenAmounts[0].amount = 2 ** 64; - message.tokenAmounts[0].token = s_sourceTokens[1]; - - uint256 expectedFee = s_sourceRouter.getFee(DEST_CHAIN_SELECTOR, message); - assertGt(expectedFee, 0); - - uint256 balanceBefore = sourceToken1.balanceOf(OWNER); - - // Assert that the tokens are burned - vm.expectEmit(); - emit Burned(address(s_onRamp), message.tokenAmounts[0].amount); - - Internal.EVM2AnyRampMessage memory msgEvent = _messageToEvent(message, 1, 1, expectedFee, OWNER); - - vm.expectEmit(); - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, msgEvent.header.sequenceNumber, msgEvent); - - vm.resumeGasMetering(); - bytes32 messageId = s_sourceRouter.ccipSend(DEST_CHAIN_SELECTOR, message); - vm.pauseGasMetering(); - - assertEq(msgEvent.header.messageId, messageId); - // Assert the user balance is lowered by the tokenAmounts sent and the fee amount - uint256 expectedBalance = balanceBefore - (message.tokenAmounts[0].amount); - assertEq(expectedBalance, sourceToken1.balanceOf(OWNER)); - vm.resumeGasMetering(); - } - - function test_CCIPSendLinkFeeNoTokenSuccess_gas() public { - vm.pauseGasMetering(); - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - - uint256 expectedFee = s_sourceRouter.getFee(DEST_CHAIN_SELECTOR, message); - assertGt(expectedFee, 0); - - Internal.EVM2AnyRampMessage memory msgEvent = _messageToEvent(message, 1, 1, expectedFee, OWNER); - - vm.expectEmit(); - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, msgEvent.header.sequenceNumber, msgEvent); - - vm.resumeGasMetering(); - bytes32 messageId = s_sourceRouter.ccipSend(DEST_CHAIN_SELECTOR, message); - vm.pauseGasMetering(); - - assertEq(msgEvent.header.messageId, messageId); - vm.resumeGasMetering(); - } - - function test_ccipSend_nativeFeeOneTokenSuccess_gas() public { - vm.pauseGasMetering(); - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - - IERC20 sourceToken1 = IERC20(s_sourceTokens[1]); - sourceToken1.approve(address(s_sourceRouter), 2 ** 64); - - uint256 balanceBefore = sourceToken1.balanceOf(OWNER); - - message.tokenAmounts = new Client.EVMTokenAmount[](1); - message.tokenAmounts[0].amount = 2 ** 64; - message.tokenAmounts[0].token = s_sourceTokens[1]; - // Native fees will be wrapped so we need to calculate the event with - // the wrapped native feeCoin address. - message.feeToken = s_sourceRouter.getWrappedNative(); - uint256 expectedFee = s_sourceRouter.getFee(DEST_CHAIN_SELECTOR, message); - assertGt(expectedFee, 0); - - Internal.EVM2AnyRampMessage memory msgEvent = _messageToEvent(message, 1, 1, expectedFee, OWNER); - msgEvent.feeValueJuels = expectedFee * s_sourceTokenPrices[1] / s_sourceTokenPrices[0]; - - message.feeToken = address(0); - // Assert that the tokens are burned - vm.expectEmit(); - emit Burned(address(s_onRamp), message.tokenAmounts[0].amount); - - vm.expectEmit(); - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, msgEvent.header.sequenceNumber, msgEvent); - - vm.resumeGasMetering(); - bytes32 messageId = s_sourceRouter.ccipSend{value: expectedFee}(DEST_CHAIN_SELECTOR, message); - vm.pauseGasMetering(); - - assertEq(msgEvent.header.messageId, messageId); - // Assert the user balance is lowered by the tokenAmounts sent and the fee amount - uint256 expectedBalance = balanceBefore - (message.tokenAmounts[0].amount); - assertEq(expectedBalance, sourceToken1.balanceOf(OWNER)); - vm.resumeGasMetering(); - } - - function test_ccipSend_nativeFeeNoTokenSuccess_gas() public { - vm.pauseGasMetering(); - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - - // Native fees will be wrapped so we need to calculate the event with - // the wrapped native feeCoin address. - message.feeToken = s_sourceRouter.getWrappedNative(); - uint256 expectedFee = s_sourceRouter.getFee(DEST_CHAIN_SELECTOR, message); - assertGt(expectedFee, 0); - - Internal.EVM2AnyRampMessage memory msgEvent = _messageToEvent(message, 1, 1, expectedFee, OWNER); - msgEvent.feeValueJuels = expectedFee * s_sourceTokenPrices[1] / s_sourceTokenPrices[0]; - // Set it to address(0) to indicate native - message.feeToken = address(0); - - vm.expectEmit(); - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, msgEvent.header.sequenceNumber, msgEvent); - - vm.resumeGasMetering(); - bytes32 messageId = s_sourceRouter.ccipSend{value: expectedFee}(DEST_CHAIN_SELECTOR, message); - vm.pauseGasMetering(); - - assertEq(msgEvent.header.messageId, messageId); - // Assert the user balance is lowered by the tokenAmounts sent and the fee amount - vm.resumeGasMetering(); - } - - function test_NonLinkFeeToken_Success() public { - address[] memory feeTokens = new address[](1); - feeTokens[0] = s_sourceTokens[1]; - s_feeQuoter.applyFeeTokensUpdates(new address[](0), feeTokens); - - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.feeToken = s_sourceTokens[1]; - IERC20(s_sourceTokens[1]).approve(address(s_sourceRouter), 2 ** 64); - s_sourceRouter.ccipSend(DEST_CHAIN_SELECTOR, message); - } - - function test_NativeFeeToken_Success() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.feeToken = address(0); // Raw native - uint256 nativeQuote = s_sourceRouter.getFee(DEST_CHAIN_SELECTOR, message); - vm.stopPrank(); - hoax(address(1), 100 ether); - s_sourceRouter.ccipSend{value: nativeQuote}(DEST_CHAIN_SELECTOR, message); - } - - function test_NativeFeeTokenOverpay_Success() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.feeToken = address(0); // Raw native - uint256 nativeQuote = s_sourceRouter.getFee(DEST_CHAIN_SELECTOR, message); - vm.stopPrank(); - hoax(address(1), 100 ether); - s_sourceRouter.ccipSend{value: nativeQuote + 1}(DEST_CHAIN_SELECTOR, message); - // We expect the overpayment to be taken in full. - assertEq(address(1).balance, 100 ether - (nativeQuote + 1)); - assertEq(address(s_sourceRouter).balance, 0); - } - - function test_WrappedNativeFeeToken_Success() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.feeToken = s_sourceRouter.getWrappedNative(); - uint256 nativeQuote = s_sourceRouter.getFee(DEST_CHAIN_SELECTOR, message); - vm.stopPrank(); - hoax(address(1), 100 ether); - // Now address(1) has nativeQuote wrapped. - IWrappedNative(s_sourceRouter.getWrappedNative()).deposit{value: nativeQuote}(); - IWrappedNative(s_sourceRouter.getWrappedNative()).approve(address(s_sourceRouter), nativeQuote); - s_sourceRouter.ccipSend(DEST_CHAIN_SELECTOR, message); - } - - // Reverts - - function test_WhenNotHealthy_Revert() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - s_mockRMN.setGlobalCursed(true); - vm.expectRevert(Router.BadARMSignal.selector); - s_sourceRouter.ccipSend(DEST_CHAIN_SELECTOR, message); - } - - function test_UnsupportedDestinationChain_Revert() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - uint64 wrongChain = DEST_CHAIN_SELECTOR + 1; - - vm.expectRevert(abi.encodeWithSelector(IRouterClient.UnsupportedDestinationChain.selector, wrongChain)); - - s_sourceRouter.ccipSend(wrongChain, message); - } - - function test_FeeTokenAmountTooLow_Revert() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - IERC20(s_sourceTokens[0]).approve(address(s_sourceRouter), 0); - - vm.expectRevert("ERC20: insufficient allowance"); - - s_sourceRouter.ccipSend(DEST_CHAIN_SELECTOR, message); - } - - function test_InvalidMsgValue() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - // Non-empty feeToken but with msg.value should revert - vm.stopPrank(); - hoax(address(1), 1); - vm.expectRevert(IRouterClient.InvalidMsgValue.selector); - s_sourceRouter.ccipSend{value: 1}(DEST_CHAIN_SELECTOR, message); - } - - function test_NativeFeeTokenZeroValue() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.feeToken = address(0); // Raw native - // Include no value, should revert - vm.expectRevert(); - s_sourceRouter.ccipSend(DEST_CHAIN_SELECTOR, message); - } - - function test_NativeFeeTokenInsufficientValue() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.feeToken = address(0); // Raw native - // Include insufficient, should also revert - vm.stopPrank(); - - hoax(address(1), 1); - vm.expectRevert(IRouterClient.InsufficientFeeTokenAmount.selector); - s_sourceRouter.ccipSend{value: 1}(DEST_CHAIN_SELECTOR, message); - } -} - -contract Router_getArmProxy is RouterSetup { - function test_getArmProxy() public view { - assertEq(s_sourceRouter.getArmProxy(), address(s_mockRMN)); - } -} - -contract Router_applyRampUpdates is RouterSetup { - MaybeRevertMessageReceiver internal s_receiver; - - function setUp() public virtual override(RouterSetup) { - super.setUp(); - s_receiver = new MaybeRevertMessageReceiver(false); - } - - function _assertOffRampRouteSucceeds( - Router.OffRamp memory offRamp - ) internal { - vm.startPrank(offRamp.offRamp); - - Client.Any2EVMMessage memory message = _generateReceiverMessage(offRamp.sourceChainSelector); - vm.expectCall(address(s_receiver), abi.encodeWithSelector(IAny2EVMMessageReceiver.ccipReceive.selector, message)); - s_sourceRouter.routeMessage(message, GAS_FOR_CALL_EXACT_CHECK, 100_000, address(s_receiver)); - } - - function _assertOffRampRouteReverts( - Router.OffRamp memory offRamp - ) internal { - vm.startPrank(offRamp.offRamp); - - vm.expectRevert(IRouter.OnlyOffRamp.selector); - s_sourceRouter.routeMessage( - _generateReceiverMessage(offRamp.sourceChainSelector), GAS_FOR_CALL_EXACT_CHECK, 100_000, address(s_receiver) - ); - } - - function test_Fuzz_OffRampUpdates( - address[20] memory offRampsInput - ) public { - Router.OffRamp[] memory offRamps = new Router.OffRamp[](20); - - for (uint256 i = 0; i < offRampsInput.length; ++i) { - offRamps[i] = Router.OffRamp({sourceChainSelector: uint64(i), offRamp: offRampsInput[i]}); - } - - // Test adding offRamps - s_sourceRouter.applyRampUpdates(new Router.OnRamp[](0), new Router.OffRamp[](0), offRamps); - - // There is no uniqueness guarantee on fuzz input, offRamps will not emit in case of a duplicate, - // hence cannot assert on number of offRamps event emissions, we need to use isOffRa - for (uint256 i = 0; i < offRamps.length; ++i) { - assertTrue(s_sourceRouter.isOffRamp(offRamps[i].sourceChainSelector, offRamps[i].offRamp)); - } - - // Test removing offRamps - s_sourceRouter.applyRampUpdates(new Router.OnRamp[](0), s_sourceRouter.getOffRamps(), new Router.OffRamp[](0)); - - assertEq(0, s_sourceRouter.getOffRamps().length); - for (uint256 i = 0; i < offRamps.length; ++i) { - assertFalse(s_sourceRouter.isOffRamp(offRamps[i].sourceChainSelector, offRamps[i].offRamp)); - } - - // Testing removing and adding in same call - s_sourceRouter.applyRampUpdates(new Router.OnRamp[](0), new Router.OffRamp[](0), offRamps); - s_sourceRouter.applyRampUpdates(new Router.OnRamp[](0), offRamps, offRamps); - for (uint256 i = 0; i < offRamps.length; ++i) { - assertTrue(s_sourceRouter.isOffRamp(offRamps[i].sourceChainSelector, offRamps[i].offRamp)); - } - } - - function test_OffRampUpdatesWithRouting() public { - // Explicitly construct chain selectors and ramp addresses so we have ramp uniqueness for the various test scenarios. - uint256 numberOfSelectors = 10; - uint64[] memory sourceChainSelectors = new uint64[](numberOfSelectors); - for (uint256 i = 0; i < numberOfSelectors; ++i) { - sourceChainSelectors[i] = uint64(i); - } - - uint256 numberOfOffRamps = 5; - address[] memory offRamps = new address[](numberOfOffRamps); - for (uint256 i = 0; i < numberOfOffRamps; ++i) { - offRamps[i] = address(uint160(i * 10)); - } - - // 1st test scenario: add offramps. - // Check all the offramps are added correctly, and can route messages. - Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](0); - Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](numberOfSelectors * numberOfOffRamps); - - // Ensure there are multi-offramp source and multi-source offramps - for (uint256 i = 0; i < numberOfSelectors; ++i) { - for (uint256 j = 0; j < numberOfOffRamps; ++j) { - offRampUpdates[(i * numberOfOffRamps) + j] = Router.OffRamp(sourceChainSelectors[i], offRamps[j]); - } - } - - for (uint256 i = 0; i < offRampUpdates.length; ++i) { - vm.expectEmit(); - emit Router.OffRampAdded(offRampUpdates[i].sourceChainSelector, offRampUpdates[i].offRamp); - } - s_sourceRouter.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), offRampUpdates); - - Router.OffRamp[] memory gotOffRamps = s_sourceRouter.getOffRamps(); - assertEq(offRampUpdates.length, gotOffRamps.length); - - for (uint256 i = 0; i < offRampUpdates.length; ++i) { - assertEq(offRampUpdates[i].offRamp, gotOffRamps[i].offRamp); - assertTrue(s_sourceRouter.isOffRamp(offRampUpdates[i].sourceChainSelector, offRampUpdates[i].offRamp)); - _assertOffRampRouteSucceeds(offRampUpdates[i]); - } - - vm.startPrank(OWNER); - - // 2nd test scenario: partially remove existing offramps, add new offramps. - // Check offramps are removed correctly. Removed offramps cannot route messages. - // Check new offramps are added correctly. New offramps can route messages. - // Check unmodified offramps remain correct, and can still route messages. - uint256 numberOfPartialUpdates = offRampUpdates.length / 2; - Router.OffRamp[] memory partialOffRampRemoves = new Router.OffRamp[](numberOfPartialUpdates); - Router.OffRamp[] memory partialOffRampAdds = new Router.OffRamp[](numberOfPartialUpdates); - for (uint256 i = 0; i < numberOfPartialUpdates; ++i) { - partialOffRampRemoves[i] = offRampUpdates[i]; - partialOffRampAdds[i] = Router.OffRamp({ - sourceChainSelector: offRampUpdates[i].sourceChainSelector, - offRamp: address(uint160(offRampUpdates[i].offRamp) + 1e18) // Ensure unique new offRamps addresses - }); - } - - for (uint256 i = 0; i < numberOfPartialUpdates; ++i) { - vm.expectEmit(); - emit Router.OffRampRemoved(partialOffRampRemoves[i].sourceChainSelector, partialOffRampRemoves[i].offRamp); - } - for (uint256 i = 0; i < numberOfPartialUpdates; ++i) { - vm.expectEmit(); - emit Router.OffRampAdded(partialOffRampAdds[i].sourceChainSelector, partialOffRampAdds[i].offRamp); - } - s_sourceRouter.applyRampUpdates(onRampUpdates, partialOffRampRemoves, partialOffRampAdds); - - gotOffRamps = s_sourceRouter.getOffRamps(); - assertEq(offRampUpdates.length, gotOffRamps.length); - - for (uint256 i = 0; i < numberOfPartialUpdates; ++i) { - assertFalse( - s_sourceRouter.isOffRamp(partialOffRampRemoves[i].sourceChainSelector, partialOffRampRemoves[i].offRamp) - ); - _assertOffRampRouteReverts(partialOffRampRemoves[i]); - - assertTrue(s_sourceRouter.isOffRamp(partialOffRampAdds[i].sourceChainSelector, partialOffRampAdds[i].offRamp)); - _assertOffRampRouteSucceeds(partialOffRampAdds[i]); - } - for (uint256 i = numberOfPartialUpdates; i < offRampUpdates.length; ++i) { - assertTrue(s_sourceRouter.isOffRamp(offRampUpdates[i].sourceChainSelector, offRampUpdates[i].offRamp)); - _assertOffRampRouteSucceeds(offRampUpdates[i]); - } - - vm.startPrank(OWNER); - - // 3rd test scenario: remove all offRamps. - // Check all offramps have been removed, no offramp is able to route messages. - for (uint256 i = 0; i < numberOfPartialUpdates; ++i) { - vm.expectEmit(); - emit Router.OffRampRemoved(partialOffRampAdds[i].sourceChainSelector, partialOffRampAdds[i].offRamp); - } - s_sourceRouter.applyRampUpdates(onRampUpdates, partialOffRampAdds, new Router.OffRamp[](0)); - - uint256 numberOfRemainingOfframps = offRampUpdates.length - numberOfPartialUpdates; - Router.OffRamp[] memory remainingOffRampRemoves = new Router.OffRamp[](numberOfRemainingOfframps); - for (uint256 i = 0; i < numberOfRemainingOfframps; ++i) { - remainingOffRampRemoves[i] = offRampUpdates[i + numberOfPartialUpdates]; - } - - for (uint256 i = 0; i < numberOfRemainingOfframps; ++i) { - vm.expectEmit(); - emit Router.OffRampRemoved(remainingOffRampRemoves[i].sourceChainSelector, remainingOffRampRemoves[i].offRamp); - } - s_sourceRouter.applyRampUpdates(onRampUpdates, remainingOffRampRemoves, new Router.OffRamp[](0)); - - // Check there are no offRamps. - assertEq(0, s_sourceRouter.getOffRamps().length); - - for (uint256 i = 0; i < numberOfPartialUpdates; ++i) { - assertFalse(s_sourceRouter.isOffRamp(partialOffRampAdds[i].sourceChainSelector, partialOffRampAdds[i].offRamp)); - _assertOffRampRouteReverts(partialOffRampAdds[i]); - } - for (uint256 i = 0; i < offRampUpdates.length; ++i) { - assertFalse(s_sourceRouter.isOffRamp(offRampUpdates[i].sourceChainSelector, offRampUpdates[i].offRamp)); - _assertOffRampRouteReverts(offRampUpdates[i]); - } - - vm.startPrank(OWNER); - - // 4th test scenario: add initial onRamps back. - // Check the offramps are added correctly, and can route messages. - // Check offramps that were not added back remain unset, and cannot route messages. - for (uint256 i = 0; i < offRampUpdates.length; ++i) { - vm.expectEmit(); - emit Router.OffRampAdded(offRampUpdates[i].sourceChainSelector, offRampUpdates[i].offRamp); - } - s_sourceRouter.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), offRampUpdates); - - // Check initial offRamps are added back and can route to receiver. - gotOffRamps = s_sourceRouter.getOffRamps(); - assertEq(offRampUpdates.length, gotOffRamps.length); - - for (uint256 i = 0; i < offRampUpdates.length; ++i) { - assertEq(offRampUpdates[i].offRamp, gotOffRamps[i].offRamp); - assertTrue(s_sourceRouter.isOffRamp(offRampUpdates[i].sourceChainSelector, offRampUpdates[i].offRamp)); - _assertOffRampRouteSucceeds(offRampUpdates[i]); - } - - // Check offramps that were not added back remain unset. - for (uint256 i = 0; i < numberOfPartialUpdates; ++i) { - assertFalse(s_sourceRouter.isOffRamp(partialOffRampAdds[i].sourceChainSelector, partialOffRampAdds[i].offRamp)); - _assertOffRampRouteReverts(partialOffRampAdds[i]); - } - } - - function test_Fuzz_OnRampUpdates( - Router.OnRamp[] memory onRamps - ) public { - // Test adding onRamps - for (uint256 i = 0; i < onRamps.length; ++i) { - vm.expectEmit(); - emit Router.OnRampSet(onRamps[i].destChainSelector, onRamps[i].onRamp); - } - - s_sourceRouter.applyRampUpdates(onRamps, new Router.OffRamp[](0), new Router.OffRamp[](0)); - - // Test setting onRamps to unsupported - for (uint256 i = 0; i < onRamps.length; ++i) { - onRamps[i].onRamp = address(0); - - vm.expectEmit(); - emit Router.OnRampSet(onRamps[i].destChainSelector, onRamps[i].onRamp); - } - s_sourceRouter.applyRampUpdates(onRamps, new Router.OffRamp[](0), new Router.OffRamp[](0)); - for (uint256 i = 0; i < onRamps.length; ++i) { - assertEq(address(0), s_sourceRouter.getOnRamp(onRamps[i].destChainSelector)); - assertFalse(s_sourceRouter.isChainSupported(onRamps[i].destChainSelector)); - } - } - - function test_OnRampDisable() public { - // Add onRamp - Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](1); - Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](0); - address onRamp = address(uint160(2)); - onRampUpdates[0] = Router.OnRamp({destChainSelector: DEST_CHAIN_SELECTOR, onRamp: onRamp}); - s_sourceRouter.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), offRampUpdates); - assertEq(onRamp, s_sourceRouter.getOnRamp(DEST_CHAIN_SELECTOR)); - assertTrue(s_sourceRouter.isChainSupported(DEST_CHAIN_SELECTOR)); - - // Disable onRamp - onRampUpdates[0] = Router.OnRamp({destChainSelector: DEST_CHAIN_SELECTOR, onRamp: address(0)}); - s_sourceRouter.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), new Router.OffRamp[](0)); - assertEq(address(0), s_sourceRouter.getOnRamp(DEST_CHAIN_SELECTOR)); - assertFalse(s_sourceRouter.isChainSupported(DEST_CHAIN_SELECTOR)); - - // Re-enable onRamp - onRampUpdates[0] = Router.OnRamp({destChainSelector: DEST_CHAIN_SELECTOR, onRamp: onRamp}); - s_sourceRouter.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), new Router.OffRamp[](0)); - assertEq(onRamp, s_sourceRouter.getOnRamp(DEST_CHAIN_SELECTOR)); - assertTrue(s_sourceRouter.isChainSupported(DEST_CHAIN_SELECTOR)); - } - - function test_OnlyOwner_Revert() public { - vm.stopPrank(); - vm.expectRevert("Only callable by owner"); - Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](0); - Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](0); - s_sourceRouter.applyRampUpdates(onRampUpdates, offRampUpdates, offRampUpdates); - } - - function test_OffRampMismatch_Revert() public { - address offRamp = address(uint160(2)); - - Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](0); - Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](1); - offRampUpdates[0] = Router.OffRamp(DEST_CHAIN_SELECTOR, offRamp); - - vm.expectEmit(); - emit Router.OffRampAdded(DEST_CHAIN_SELECTOR, offRamp); - s_sourceRouter.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), offRampUpdates); - - offRampUpdates[0] = Router.OffRamp(SOURCE_CHAIN_SELECTOR, offRamp); - - vm.expectRevert(abi.encodeWithSelector(Router.OffRampMismatch.selector, SOURCE_CHAIN_SELECTOR, offRamp)); - s_sourceRouter.applyRampUpdates(onRampUpdates, offRampUpdates, offRampUpdates); - } -} - -contract Router_setWrappedNative is OnRampSetup { - function test_Fuzz_SetWrappedNative_Success( - address wrappedNative - ) public { - s_sourceRouter.setWrappedNative(wrappedNative); - assertEq(wrappedNative, s_sourceRouter.getWrappedNative()); - } - - // Reverts - function test_OnlyOwner_Revert() public { - vm.stopPrank(); - vm.expectRevert("Only callable by owner"); - s_sourceRouter.setWrappedNative(address(1)); - } -} - -contract Router_getSupportedTokens is OnRampSetup { - function test_GetSupportedTokens_Revert() public { - vm.expectRevert(OnRamp.GetSupportedTokensFunctionalityRemovedCheckAdminRegistry.selector); - s_onRamp.getSupportedTokens(DEST_CHAIN_SELECTOR); - } -} - -contract Router_routeMessage is OffRampSetup { - function setUp() public virtual override { - super.setUp(); - vm.startPrank(address(s_offRamp)); - } - - function _generateManualGasLimit( - uint256 callDataLength - ) internal view returns (uint256) { - return ((gasleft() - 2 * (16 * callDataLength + GAS_FOR_CALL_EXACT_CHECK)) * 62) / 64; - } - - function test_routeMessage_ManualExec_Success() public { - Client.Any2EVMMessage memory message = _generateReceiverMessage(SOURCE_CHAIN_SELECTOR); - // Manuel execution cannot run out of gas - - (bool success, bytes memory retData, uint256 gasUsed) = s_destRouter.routeMessage( - _generateReceiverMessage(SOURCE_CHAIN_SELECTOR), - GAS_FOR_CALL_EXACT_CHECK, - _generateManualGasLimit(message.data.length), - address(s_receiver) - ); - assertTrue(success); - assertEq("", retData); - assertGt(gasUsed, 3_000); - } - - function test_routeMessage_ExecutionEvent_Success() public { - Client.Any2EVMMessage memory message = _generateReceiverMessage(SOURCE_CHAIN_SELECTOR); - // Should revert with reason - bytes memory realError1 = new bytes(2); - realError1[0] = 0xbe; - realError1[1] = 0xef; - s_reverting_receiver.setErr(realError1); - - vm.expectEmit(); - emit Router.MessageExecuted( - message.messageId, - message.sourceChainSelector, - address(s_offRamp), - keccak256(abi.encodeWithSelector(IAny2EVMMessageReceiver.ccipReceive.selector, message)) - ); - - (bool success, bytes memory retData, uint256 gasUsed) = s_destRouter.routeMessage( - _generateReceiverMessage(SOURCE_CHAIN_SELECTOR), - GAS_FOR_CALL_EXACT_CHECK, - _generateManualGasLimit(message.data.length), - address(s_reverting_receiver) - ); - - assertFalse(success); - assertEq(abi.encodeWithSelector(MaybeRevertMessageReceiver.CustomError.selector, realError1), retData); - assertGt(gasUsed, 3_000); - - // Reason is truncated - // Over the MAX_RET_BYTES limit (including offset and length word since we have a dynamic values), should be ignored - bytes memory realError2 = new bytes(32 * 2 + 1); - realError2[32 * 2 - 1] = 0xAA; - realError2[32 * 2] = 0xFF; - s_reverting_receiver.setErr(realError2); - - vm.expectEmit(); - emit Router.MessageExecuted( - message.messageId, - message.sourceChainSelector, - address(s_offRamp), - keccak256(abi.encodeWithSelector(IAny2EVMMessageReceiver.ccipReceive.selector, message)) - ); - - (success, retData, gasUsed) = s_destRouter.routeMessage( - _generateReceiverMessage(SOURCE_CHAIN_SELECTOR), - GAS_FOR_CALL_EXACT_CHECK, - _generateManualGasLimit(message.data.length), - address(s_reverting_receiver) - ); - - assertFalse(success); - assertEq( - abi.encodeWithSelector( - MaybeRevertMessageReceiver.CustomError.selector, - uint256(32), - uint256(realError2.length), - uint256(0), - uint256(0xAA) - ), - retData - ); - assertGt(gasUsed, 3_000); - - // Should emit success - vm.expectEmit(); - emit Router.MessageExecuted( - message.messageId, - message.sourceChainSelector, - address(s_offRamp), - keccak256(abi.encodeWithSelector(IAny2EVMMessageReceiver.ccipReceive.selector, message)) - ); - - (success, retData, gasUsed) = s_destRouter.routeMessage( - _generateReceiverMessage(SOURCE_CHAIN_SELECTOR), - GAS_FOR_CALL_EXACT_CHECK, - _generateManualGasLimit(message.data.length), - address(s_receiver) - ); - - assertTrue(success); - assertEq("", retData); - assertGt(gasUsed, 3_000); - } - - function testFuzz_routeMessage_ExecutionEvent_Success( - bytes calldata error - ) public { - Client.Any2EVMMessage memory message = _generateReceiverMessage(SOURCE_CHAIN_SELECTOR); - s_reverting_receiver.setErr(error); - - bytes memory expectedRetData; - - if (error.length >= 33) { - uint256 cutOff = error.length > 64 ? 64 : error.length; - vm.expectEmit(); - emit Router.MessageExecuted( - message.messageId, - message.sourceChainSelector, - address(s_offRamp), - keccak256(abi.encodeWithSelector(IAny2EVMMessageReceiver.ccipReceive.selector, message)) - ); - expectedRetData = abi.encodeWithSelector( - MaybeRevertMessageReceiver.CustomError.selector, - uint256(32), - uint256(error.length), - bytes32(error[:32]), - bytes32(error[32:cutOff]) - ); - } else { - vm.expectEmit(); - emit Router.MessageExecuted( - message.messageId, - message.sourceChainSelector, - address(s_offRamp), - keccak256(abi.encodeWithSelector(IAny2EVMMessageReceiver.ccipReceive.selector, message)) - ); - expectedRetData = abi.encodeWithSelector(MaybeRevertMessageReceiver.CustomError.selector, error); - } - - (bool success, bytes memory retData,) = s_destRouter.routeMessage( - _generateReceiverMessage(SOURCE_CHAIN_SELECTOR), - GAS_FOR_CALL_EXACT_CHECK, - _generateManualGasLimit(message.data.length), - address(s_reverting_receiver) - ); - - assertFalse(success); - assertEq(expectedRetData, retData); - } - - function test_routeMessage_AutoExec_Success() public { - (bool success,,) = s_destRouter.routeMessage( - _generateReceiverMessage(SOURCE_CHAIN_SELECTOR), GAS_FOR_CALL_EXACT_CHECK, 100_000, address(s_receiver) - ); - - assertTrue(success); - - (success,,) = s_destRouter.routeMessage( - _generateReceiverMessage(SOURCE_CHAIN_SELECTOR), GAS_FOR_CALL_EXACT_CHECK, 1, address(s_receiver) - ); - - // Can run out of gas, should return false - assertFalse(success); - } - - // Reverts - function test_routeMessage_OnlyOffRamp_Revert() public { - vm.stopPrank(); - vm.startPrank(STRANGER); - - vm.expectRevert(IRouter.OnlyOffRamp.selector); - s_destRouter.routeMessage( - _generateReceiverMessage(SOURCE_CHAIN_SELECTOR), GAS_FOR_CALL_EXACT_CHECK, 100_000, address(s_receiver) - ); - } - - function test_routeMessage_WhenNotHealthy_Revert() public { - s_mockRMN.setGlobalCursed(true); - vm.expectRevert(Router.BadARMSignal.selector); - s_destRouter.routeMessage( - _generateReceiverMessage(SOURCE_CHAIN_SELECTOR), GAS_FOR_CALL_EXACT_CHECK, 100_000, address(s_receiver) - ); - } -} - -contract Router_getFee is OnRampSetup { - function test_GetFeeSupportedChain_Success() public view { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - uint256 expectedFee = s_sourceRouter.getFee(DEST_CHAIN_SELECTOR, message); - assertGt(expectedFee, 10e9); - } - - // Reverts - function test_UnsupportedDestinationChain_Revert() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - - vm.expectRevert(abi.encodeWithSelector(IRouterClient.UnsupportedDestinationChain.selector, 999)); - s_sourceRouter.getFee(999, message); - } -} diff --git a/contracts/src/v0.8/ccip/test/router/Router/Router.applyRampUpdates.t.sol b/contracts/src/v0.8/ccip/test/router/Router/Router.applyRampUpdates.t.sol new file mode 100644 index 00000000000..f877a4c6532 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/router/Router/Router.applyRampUpdates.t.sol @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Router} from "../../../Router.sol"; +import {IAny2EVMMessageReceiver} from "../../../interfaces/IAny2EVMMessageReceiver.sol"; +import {IRouter} from "../../../interfaces/IRouter.sol"; +import {Client} from "../../../libraries/Client.sol"; + +import {MaybeRevertMessageReceiver} from "../../helpers/receivers/MaybeRevertMessageReceiver.sol"; +import {RouterSetup} from "./RouterSetup.t.sol"; + +contract Router_applyRampUpdates is RouterSetup { + MaybeRevertMessageReceiver internal s_receiver; + + function setUp() public virtual override(RouterSetup) { + super.setUp(); + s_receiver = new MaybeRevertMessageReceiver(false); + } + + function _assertOffRampRouteSucceeds( + Router.OffRamp memory offRamp + ) internal { + vm.startPrank(offRamp.offRamp); + + Client.Any2EVMMessage memory message = _generateReceiverMessage(offRamp.sourceChainSelector); + vm.expectCall(address(s_receiver), abi.encodeWithSelector(IAny2EVMMessageReceiver.ccipReceive.selector, message)); + s_sourceRouter.routeMessage(message, GAS_FOR_CALL_EXACT_CHECK, 100_000, address(s_receiver)); + } + + function _assertOffRampRouteReverts( + Router.OffRamp memory offRamp + ) internal { + vm.startPrank(offRamp.offRamp); + + vm.expectRevert(IRouter.OnlyOffRamp.selector); + s_sourceRouter.routeMessage( + _generateReceiverMessage(offRamp.sourceChainSelector), GAS_FOR_CALL_EXACT_CHECK, 100_000, address(s_receiver) + ); + } + + function test_Fuzz_OffRampUpdates( + address[20] memory offRampsInput + ) public { + Router.OffRamp[] memory offRamps = new Router.OffRamp[](20); + + for (uint256 i = 0; i < offRampsInput.length; ++i) { + offRamps[i] = Router.OffRamp({sourceChainSelector: uint64(i), offRamp: offRampsInput[i]}); + } + + // Test adding offRamps + s_sourceRouter.applyRampUpdates(new Router.OnRamp[](0), new Router.OffRamp[](0), offRamps); + + // There is no uniqueness guarantee on fuzz input, offRamps will not emit in case of a duplicate, + // hence cannot assert on number of offRamps event emissions, we need to use isOffRa + for (uint256 i = 0; i < offRamps.length; ++i) { + assertTrue(s_sourceRouter.isOffRamp(offRamps[i].sourceChainSelector, offRamps[i].offRamp)); + } + + // Test removing offRamps + s_sourceRouter.applyRampUpdates(new Router.OnRamp[](0), s_sourceRouter.getOffRamps(), new Router.OffRamp[](0)); + + assertEq(0, s_sourceRouter.getOffRamps().length); + for (uint256 i = 0; i < offRamps.length; ++i) { + assertFalse(s_sourceRouter.isOffRamp(offRamps[i].sourceChainSelector, offRamps[i].offRamp)); + } + + // Testing removing and adding in same call + s_sourceRouter.applyRampUpdates(new Router.OnRamp[](0), new Router.OffRamp[](0), offRamps); + s_sourceRouter.applyRampUpdates(new Router.OnRamp[](0), offRamps, offRamps); + for (uint256 i = 0; i < offRamps.length; ++i) { + assertTrue(s_sourceRouter.isOffRamp(offRamps[i].sourceChainSelector, offRamps[i].offRamp)); + } + } + + function test_OffRampUpdatesWithRouting() public { + // Explicitly construct chain selectors and ramp addresses so we have ramp uniqueness for the various test scenarios. + uint256 numberOfSelectors = 10; + uint64[] memory sourceChainSelectors = new uint64[](numberOfSelectors); + for (uint256 i = 0; i < numberOfSelectors; ++i) { + sourceChainSelectors[i] = uint64(i); + } + + uint256 numberOfOffRamps = 5; + address[] memory offRamps = new address[](numberOfOffRamps); + for (uint256 i = 0; i < numberOfOffRamps; ++i) { + offRamps[i] = address(uint160(i * 10)); + } + + // 1st test scenario: add offramps. + // Check all the offramps are added correctly, and can route messages. + Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](0); + Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](numberOfSelectors * numberOfOffRamps); + + // Ensure there are multi-offramp source and multi-source offramps + for (uint256 i = 0; i < numberOfSelectors; ++i) { + for (uint256 j = 0; j < numberOfOffRamps; ++j) { + offRampUpdates[(i * numberOfOffRamps) + j] = Router.OffRamp(sourceChainSelectors[i], offRamps[j]); + } + } + + for (uint256 i = 0; i < offRampUpdates.length; ++i) { + vm.expectEmit(); + emit Router.OffRampAdded(offRampUpdates[i].sourceChainSelector, offRampUpdates[i].offRamp); + } + s_sourceRouter.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), offRampUpdates); + + Router.OffRamp[] memory gotOffRamps = s_sourceRouter.getOffRamps(); + assertEq(offRampUpdates.length, gotOffRamps.length); + + for (uint256 i = 0; i < offRampUpdates.length; ++i) { + assertEq(offRampUpdates[i].offRamp, gotOffRamps[i].offRamp); + assertTrue(s_sourceRouter.isOffRamp(offRampUpdates[i].sourceChainSelector, offRampUpdates[i].offRamp)); + _assertOffRampRouteSucceeds(offRampUpdates[i]); + } + + vm.startPrank(OWNER); + + // 2nd test scenario: partially remove existing offramps, add new offramps. + // Check offramps are removed correctly. Removed offramps cannot route messages. + // Check new offramps are added correctly. New offramps can route messages. + // Check unmodified offramps remain correct, and can still route messages. + uint256 numberOfPartialUpdates = offRampUpdates.length / 2; + Router.OffRamp[] memory partialOffRampRemoves = new Router.OffRamp[](numberOfPartialUpdates); + Router.OffRamp[] memory partialOffRampAdds = new Router.OffRamp[](numberOfPartialUpdates); + for (uint256 i = 0; i < numberOfPartialUpdates; ++i) { + partialOffRampRemoves[i] = offRampUpdates[i]; + partialOffRampAdds[i] = Router.OffRamp({ + sourceChainSelector: offRampUpdates[i].sourceChainSelector, + offRamp: address(uint160(offRampUpdates[i].offRamp) + 1e18) // Ensure unique new offRamps addresses + }); + } + + for (uint256 i = 0; i < numberOfPartialUpdates; ++i) { + vm.expectEmit(); + emit Router.OffRampRemoved(partialOffRampRemoves[i].sourceChainSelector, partialOffRampRemoves[i].offRamp); + } + for (uint256 i = 0; i < numberOfPartialUpdates; ++i) { + vm.expectEmit(); + emit Router.OffRampAdded(partialOffRampAdds[i].sourceChainSelector, partialOffRampAdds[i].offRamp); + } + s_sourceRouter.applyRampUpdates(onRampUpdates, partialOffRampRemoves, partialOffRampAdds); + + gotOffRamps = s_sourceRouter.getOffRamps(); + assertEq(offRampUpdates.length, gotOffRamps.length); + + for (uint256 i = 0; i < numberOfPartialUpdates; ++i) { + assertFalse( + s_sourceRouter.isOffRamp(partialOffRampRemoves[i].sourceChainSelector, partialOffRampRemoves[i].offRamp) + ); + _assertOffRampRouteReverts(partialOffRampRemoves[i]); + + assertTrue(s_sourceRouter.isOffRamp(partialOffRampAdds[i].sourceChainSelector, partialOffRampAdds[i].offRamp)); + _assertOffRampRouteSucceeds(partialOffRampAdds[i]); + } + for (uint256 i = numberOfPartialUpdates; i < offRampUpdates.length; ++i) { + assertTrue(s_sourceRouter.isOffRamp(offRampUpdates[i].sourceChainSelector, offRampUpdates[i].offRamp)); + _assertOffRampRouteSucceeds(offRampUpdates[i]); + } + + vm.startPrank(OWNER); + + // 3rd test scenario: remove all offRamps. + // Check all offramps have been removed, no offramp is able to route messages. + for (uint256 i = 0; i < numberOfPartialUpdates; ++i) { + vm.expectEmit(); + emit Router.OffRampRemoved(partialOffRampAdds[i].sourceChainSelector, partialOffRampAdds[i].offRamp); + } + s_sourceRouter.applyRampUpdates(onRampUpdates, partialOffRampAdds, new Router.OffRamp[](0)); + + uint256 numberOfRemainingOfframps = offRampUpdates.length - numberOfPartialUpdates; + Router.OffRamp[] memory remainingOffRampRemoves = new Router.OffRamp[](numberOfRemainingOfframps); + for (uint256 i = 0; i < numberOfRemainingOfframps; ++i) { + remainingOffRampRemoves[i] = offRampUpdates[i + numberOfPartialUpdates]; + } + + for (uint256 i = 0; i < numberOfRemainingOfframps; ++i) { + vm.expectEmit(); + emit Router.OffRampRemoved(remainingOffRampRemoves[i].sourceChainSelector, remainingOffRampRemoves[i].offRamp); + } + s_sourceRouter.applyRampUpdates(onRampUpdates, remainingOffRampRemoves, new Router.OffRamp[](0)); + + // Check there are no offRamps. + assertEq(0, s_sourceRouter.getOffRamps().length); + + for (uint256 i = 0; i < numberOfPartialUpdates; ++i) { + assertFalse(s_sourceRouter.isOffRamp(partialOffRampAdds[i].sourceChainSelector, partialOffRampAdds[i].offRamp)); + _assertOffRampRouteReverts(partialOffRampAdds[i]); + } + for (uint256 i = 0; i < offRampUpdates.length; ++i) { + assertFalse(s_sourceRouter.isOffRamp(offRampUpdates[i].sourceChainSelector, offRampUpdates[i].offRamp)); + _assertOffRampRouteReverts(offRampUpdates[i]); + } + + vm.startPrank(OWNER); + + // 4th test scenario: add initial onRamps back. + // Check the offramps are added correctly, and can route messages. + // Check offramps that were not added back remain unset, and cannot route messages. + for (uint256 i = 0; i < offRampUpdates.length; ++i) { + vm.expectEmit(); + emit Router.OffRampAdded(offRampUpdates[i].sourceChainSelector, offRampUpdates[i].offRamp); + } + s_sourceRouter.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), offRampUpdates); + + // Check initial offRamps are added back and can route to receiver. + gotOffRamps = s_sourceRouter.getOffRamps(); + assertEq(offRampUpdates.length, gotOffRamps.length); + + for (uint256 i = 0; i < offRampUpdates.length; ++i) { + assertEq(offRampUpdates[i].offRamp, gotOffRamps[i].offRamp); + assertTrue(s_sourceRouter.isOffRamp(offRampUpdates[i].sourceChainSelector, offRampUpdates[i].offRamp)); + _assertOffRampRouteSucceeds(offRampUpdates[i]); + } + + // Check offramps that were not added back remain unset. + for (uint256 i = 0; i < numberOfPartialUpdates; ++i) { + assertFalse(s_sourceRouter.isOffRamp(partialOffRampAdds[i].sourceChainSelector, partialOffRampAdds[i].offRamp)); + _assertOffRampRouteReverts(partialOffRampAdds[i]); + } + } + + function test_Fuzz_OnRampUpdates( + Router.OnRamp[] memory onRamps + ) public { + // Test adding onRamps + for (uint256 i = 0; i < onRamps.length; ++i) { + vm.expectEmit(); + emit Router.OnRampSet(onRamps[i].destChainSelector, onRamps[i].onRamp); + } + + s_sourceRouter.applyRampUpdates(onRamps, new Router.OffRamp[](0), new Router.OffRamp[](0)); + + // Test setting onRamps to unsupported + for (uint256 i = 0; i < onRamps.length; ++i) { + onRamps[i].onRamp = address(0); + + vm.expectEmit(); + emit Router.OnRampSet(onRamps[i].destChainSelector, onRamps[i].onRamp); + } + s_sourceRouter.applyRampUpdates(onRamps, new Router.OffRamp[](0), new Router.OffRamp[](0)); + for (uint256 i = 0; i < onRamps.length; ++i) { + assertEq(address(0), s_sourceRouter.getOnRamp(onRamps[i].destChainSelector)); + assertFalse(s_sourceRouter.isChainSupported(onRamps[i].destChainSelector)); + } + } + + function test_OnRampDisable() public { + // Add onRamp + Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](1); + Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](0); + address onRamp = address(uint160(2)); + onRampUpdates[0] = Router.OnRamp({destChainSelector: DEST_CHAIN_SELECTOR, onRamp: onRamp}); + s_sourceRouter.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), offRampUpdates); + assertEq(onRamp, s_sourceRouter.getOnRamp(DEST_CHAIN_SELECTOR)); + assertTrue(s_sourceRouter.isChainSupported(DEST_CHAIN_SELECTOR)); + + // Disable onRamp + onRampUpdates[0] = Router.OnRamp({destChainSelector: DEST_CHAIN_SELECTOR, onRamp: address(0)}); + s_sourceRouter.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), new Router.OffRamp[](0)); + assertEq(address(0), s_sourceRouter.getOnRamp(DEST_CHAIN_SELECTOR)); + assertFalse(s_sourceRouter.isChainSupported(DEST_CHAIN_SELECTOR)); + + // Re-enable onRamp + onRampUpdates[0] = Router.OnRamp({destChainSelector: DEST_CHAIN_SELECTOR, onRamp: onRamp}); + s_sourceRouter.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), new Router.OffRamp[](0)); + assertEq(onRamp, s_sourceRouter.getOnRamp(DEST_CHAIN_SELECTOR)); + assertTrue(s_sourceRouter.isChainSupported(DEST_CHAIN_SELECTOR)); + } + + function test_OnlyOwner_Revert() public { + vm.stopPrank(); + vm.expectRevert("Only callable by owner"); + Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](0); + Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](0); + s_sourceRouter.applyRampUpdates(onRampUpdates, offRampUpdates, offRampUpdates); + } + + function test_OffRampMismatch_Revert() public { + address offRamp = address(uint160(2)); + + Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](0); + Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](1); + offRampUpdates[0] = Router.OffRamp(DEST_CHAIN_SELECTOR, offRamp); + + vm.expectEmit(); + emit Router.OffRampAdded(DEST_CHAIN_SELECTOR, offRamp); + s_sourceRouter.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), offRampUpdates); + + offRampUpdates[0] = Router.OffRamp(SOURCE_CHAIN_SELECTOR, offRamp); + + vm.expectRevert(abi.encodeWithSelector(Router.OffRampMismatch.selector, SOURCE_CHAIN_SELECTOR, offRamp)); + s_sourceRouter.applyRampUpdates(onRampUpdates, offRampUpdates, offRampUpdates); + } +} diff --git a/contracts/src/v0.8/ccip/test/router/Router/Router.ccipSend.t.sol b/contracts/src/v0.8/ccip/test/router/Router/Router.ccipSend.t.sol new file mode 100644 index 00000000000..c44a94d8d3d --- /dev/null +++ b/contracts/src/v0.8/ccip/test/router/Router/Router.ccipSend.t.sol @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; + +import {Router} from "../../../Router.sol"; +import {IRouterClient} from "../../../interfaces/IRouterClient.sol"; +import {IWrappedNative} from "../../../interfaces/IWrappedNative.sol"; +import {Client} from "../../../libraries/Client.sol"; +import {Internal} from "../../../libraries/Internal.sol"; + +import {OnRamp} from "../../../onRamp/OnRamp.sol"; +import {OnRampSetup} from "../../onRamp/OnRamp/OnRampSetup.t.sol"; + +contract Router_ccipSend is OnRampSetup { + event Burned(address indexed sender, uint256 amount); + + function test_CCIPSendLinkFeeOneTokenSuccess_gas() public { + vm.pauseGasMetering(); + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + + IERC20 sourceToken1 = IERC20(s_sourceTokens[1]); + sourceToken1.approve(address(s_sourceRouter), 2 ** 64); + + message.tokenAmounts = new Client.EVMTokenAmount[](1); + message.tokenAmounts[0].amount = 2 ** 64; + message.tokenAmounts[0].token = s_sourceTokens[1]; + + uint256 expectedFee = s_sourceRouter.getFee(DEST_CHAIN_SELECTOR, message); + assertGt(expectedFee, 0); + + uint256 balanceBefore = sourceToken1.balanceOf(OWNER); + + // Assert that the tokens are burned + vm.expectEmit(); + emit Burned(address(s_onRamp), message.tokenAmounts[0].amount); + + Internal.EVM2AnyRampMessage memory msgEvent = _messageToEvent(message, 1, 1, expectedFee, OWNER); + + vm.expectEmit(); + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, msgEvent.header.sequenceNumber, msgEvent); + + vm.resumeGasMetering(); + bytes32 messageId = s_sourceRouter.ccipSend(DEST_CHAIN_SELECTOR, message); + vm.pauseGasMetering(); + + assertEq(msgEvent.header.messageId, messageId); + // Assert the user balance is lowered by the tokenAmounts sent and the fee amount + uint256 expectedBalance = balanceBefore - (message.tokenAmounts[0].amount); + assertEq(expectedBalance, sourceToken1.balanceOf(OWNER)); + vm.resumeGasMetering(); + } + + function test_CCIPSendLinkFeeNoTokenSuccess_gas() public { + vm.pauseGasMetering(); + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + + uint256 expectedFee = s_sourceRouter.getFee(DEST_CHAIN_SELECTOR, message); + assertGt(expectedFee, 0); + + Internal.EVM2AnyRampMessage memory msgEvent = _messageToEvent(message, 1, 1, expectedFee, OWNER); + + vm.expectEmit(); + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, msgEvent.header.sequenceNumber, msgEvent); + + vm.resumeGasMetering(); + bytes32 messageId = s_sourceRouter.ccipSend(DEST_CHAIN_SELECTOR, message); + vm.pauseGasMetering(); + + assertEq(msgEvent.header.messageId, messageId); + vm.resumeGasMetering(); + } + + function test_ccipSend_nativeFeeOneTokenSuccess_gas() public { + vm.pauseGasMetering(); + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + + IERC20 sourceToken1 = IERC20(s_sourceTokens[1]); + sourceToken1.approve(address(s_sourceRouter), 2 ** 64); + + uint256 balanceBefore = sourceToken1.balanceOf(OWNER); + + message.tokenAmounts = new Client.EVMTokenAmount[](1); + message.tokenAmounts[0].amount = 2 ** 64; + message.tokenAmounts[0].token = s_sourceTokens[1]; + // Native fees will be wrapped so we need to calculate the event with + // the wrapped native feeCoin address. + message.feeToken = s_sourceRouter.getWrappedNative(); + uint256 expectedFee = s_sourceRouter.getFee(DEST_CHAIN_SELECTOR, message); + assertGt(expectedFee, 0); + + Internal.EVM2AnyRampMessage memory msgEvent = _messageToEvent(message, 1, 1, expectedFee, OWNER); + msgEvent.feeValueJuels = expectedFee * s_sourceTokenPrices[1] / s_sourceTokenPrices[0]; + + message.feeToken = address(0); + // Assert that the tokens are burned + vm.expectEmit(); + emit Burned(address(s_onRamp), message.tokenAmounts[0].amount); + + vm.expectEmit(); + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, msgEvent.header.sequenceNumber, msgEvent); + + vm.resumeGasMetering(); + bytes32 messageId = s_sourceRouter.ccipSend{value: expectedFee}(DEST_CHAIN_SELECTOR, message); + vm.pauseGasMetering(); + + assertEq(msgEvent.header.messageId, messageId); + // Assert the user balance is lowered by the tokenAmounts sent and the fee amount + uint256 expectedBalance = balanceBefore - (message.tokenAmounts[0].amount); + assertEq(expectedBalance, sourceToken1.balanceOf(OWNER)); + vm.resumeGasMetering(); + } + + function test_ccipSend_nativeFeeNoTokenSuccess_gas() public { + vm.pauseGasMetering(); + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + + // Native fees will be wrapped so we need to calculate the event with + // the wrapped native feeCoin address. + message.feeToken = s_sourceRouter.getWrappedNative(); + uint256 expectedFee = s_sourceRouter.getFee(DEST_CHAIN_SELECTOR, message); + assertGt(expectedFee, 0); + + Internal.EVM2AnyRampMessage memory msgEvent = _messageToEvent(message, 1, 1, expectedFee, OWNER); + msgEvent.feeValueJuels = expectedFee * s_sourceTokenPrices[1] / s_sourceTokenPrices[0]; + // Set it to address(0) to indicate native + message.feeToken = address(0); + + vm.expectEmit(); + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, msgEvent.header.sequenceNumber, msgEvent); + + vm.resumeGasMetering(); + bytes32 messageId = s_sourceRouter.ccipSend{value: expectedFee}(DEST_CHAIN_SELECTOR, message); + vm.pauseGasMetering(); + + assertEq(msgEvent.header.messageId, messageId); + // Assert the user balance is lowered by the tokenAmounts sent and the fee amount + vm.resumeGasMetering(); + } + + function test_NonLinkFeeToken_Success() public { + address[] memory feeTokens = new address[](1); + feeTokens[0] = s_sourceTokens[1]; + s_feeQuoter.applyFeeTokensUpdates(new address[](0), feeTokens); + + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.feeToken = s_sourceTokens[1]; + IERC20(s_sourceTokens[1]).approve(address(s_sourceRouter), 2 ** 64); + s_sourceRouter.ccipSend(DEST_CHAIN_SELECTOR, message); + } + + function test_NativeFeeToken_Success() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.feeToken = address(0); // Raw native + uint256 nativeQuote = s_sourceRouter.getFee(DEST_CHAIN_SELECTOR, message); + vm.stopPrank(); + hoax(address(1), 100 ether); + s_sourceRouter.ccipSend{value: nativeQuote}(DEST_CHAIN_SELECTOR, message); + } + + function test_NativeFeeTokenOverpay_Success() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.feeToken = address(0); // Raw native + uint256 nativeQuote = s_sourceRouter.getFee(DEST_CHAIN_SELECTOR, message); + vm.stopPrank(); + hoax(address(1), 100 ether); + s_sourceRouter.ccipSend{value: nativeQuote + 1}(DEST_CHAIN_SELECTOR, message); + // We expect the overpayment to be taken in full. + assertEq(address(1).balance, 100 ether - (nativeQuote + 1)); + assertEq(address(s_sourceRouter).balance, 0); + } + + function test_WrappedNativeFeeToken_Success() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.feeToken = s_sourceRouter.getWrappedNative(); + uint256 nativeQuote = s_sourceRouter.getFee(DEST_CHAIN_SELECTOR, message); + vm.stopPrank(); + hoax(address(1), 100 ether); + // Now address(1) has nativeQuote wrapped. + IWrappedNative(s_sourceRouter.getWrappedNative()).deposit{value: nativeQuote}(); + IWrappedNative(s_sourceRouter.getWrappedNative()).approve(address(s_sourceRouter), nativeQuote); + s_sourceRouter.ccipSend(DEST_CHAIN_SELECTOR, message); + } + + // Reverts + + function test_WhenNotHealthy_Revert() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + s_mockRMN.setGlobalCursed(true); + vm.expectRevert(Router.BadARMSignal.selector); + s_sourceRouter.ccipSend(DEST_CHAIN_SELECTOR, message); + } + + function test_UnsupportedDestinationChain_Revert() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + uint64 wrongChain = DEST_CHAIN_SELECTOR + 1; + + vm.expectRevert(abi.encodeWithSelector(IRouterClient.UnsupportedDestinationChain.selector, wrongChain)); + + s_sourceRouter.ccipSend(wrongChain, message); + } + + function test_FeeTokenAmountTooLow_Revert() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + IERC20(s_sourceTokens[0]).approve(address(s_sourceRouter), 0); + + vm.expectRevert("ERC20: insufficient allowance"); + + s_sourceRouter.ccipSend(DEST_CHAIN_SELECTOR, message); + } + + function test_InvalidMsgValue() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + // Non-empty feeToken but with msg.value should revert + vm.stopPrank(); + hoax(address(1), 1); + vm.expectRevert(IRouterClient.InvalidMsgValue.selector); + s_sourceRouter.ccipSend{value: 1}(DEST_CHAIN_SELECTOR, message); + } + + function test_NativeFeeTokenZeroValue() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.feeToken = address(0); // Raw native + // Include no value, should revert + vm.expectRevert(); + s_sourceRouter.ccipSend(DEST_CHAIN_SELECTOR, message); + } + + function test_NativeFeeTokenInsufficientValue() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.feeToken = address(0); // Raw native + // Include insufficient, should also revert + vm.stopPrank(); + + hoax(address(1), 1); + vm.expectRevert(IRouterClient.InsufficientFeeTokenAmount.selector); + s_sourceRouter.ccipSend{value: 1}(DEST_CHAIN_SELECTOR, message); + } +} diff --git a/contracts/src/v0.8/ccip/test/router/Router/Router.constructor.t.sol b/contracts/src/v0.8/ccip/test/router/Router/Router.constructor.t.sol new file mode 100644 index 00000000000..a7dd90fc5e5 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/router/Router/Router.constructor.t.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {OnRampSetup} from "../../onRamp/OnRamp/OnRampSetup.t.sol"; + +contract Router_constructor is OnRampSetup { + function test_Constructor_Success() public view { + assertEq("Router 1.2.0", s_sourceRouter.typeAndVersion()); + assertEq(OWNER, s_sourceRouter.owner()); + } +} diff --git a/contracts/src/v0.8/ccip/test/router/Router/Router.getArmProxy.t.sol b/contracts/src/v0.8/ccip/test/router/Router/Router.getArmProxy.t.sol new file mode 100644 index 00000000000..a88e4c6b543 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/router/Router/Router.getArmProxy.t.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {RouterSetup} from "./RouterSetup.t.sol"; + +contract Router_getArmProxy is RouterSetup { + function test_getArmProxy() public view { + assertEq(s_sourceRouter.getArmProxy(), address(s_mockRMN)); + } +} diff --git a/contracts/src/v0.8/ccip/test/router/Router/Router.getFee.t.sol b/contracts/src/v0.8/ccip/test/router/Router/Router.getFee.t.sol new file mode 100644 index 00000000000..9cb2249bf6b --- /dev/null +++ b/contracts/src/v0.8/ccip/test/router/Router/Router.getFee.t.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IRouterClient} from "../../../interfaces/IRouterClient.sol"; +import {Client} from "../../../libraries/Client.sol"; +import {OnRampSetup} from "../../onRamp/OnRamp/OnRampSetup.t.sol"; + +contract Router_getFee is OnRampSetup { + function test_GetFeeSupportedChain_Success() public view { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + uint256 expectedFee = s_sourceRouter.getFee(DEST_CHAIN_SELECTOR, message); + assertGt(expectedFee, 10e9); + } + + // Reverts + function test_UnsupportedDestinationChain_Revert() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + + vm.expectRevert(abi.encodeWithSelector(IRouterClient.UnsupportedDestinationChain.selector, 999)); + s_sourceRouter.getFee(999, message); + } +} diff --git a/contracts/src/v0.8/ccip/test/router/Router/Router.getSupportedTokens.t.sol b/contracts/src/v0.8/ccip/test/router/Router/Router.getSupportedTokens.t.sol new file mode 100644 index 00000000000..734f1f35ca5 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/router/Router/Router.getSupportedTokens.t.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {OnRamp} from "../../../onRamp/OnRamp.sol"; +import {OnRampSetup} from "../../onRamp/OnRamp/OnRampSetup.t.sol"; + +contract Router_getSupportedTokens is OnRampSetup { + function test_GetSupportedTokens_Revert() public { + vm.expectRevert(OnRamp.GetSupportedTokensFunctionalityRemovedCheckAdminRegistry.selector); + s_onRamp.getSupportedTokens(DEST_CHAIN_SELECTOR); + } +} diff --git a/contracts/src/v0.8/ccip/test/router/Router/Router.recoverTokens.t.sol b/contracts/src/v0.8/ccip/test/router/Router/Router.recoverTokens.t.sol new file mode 100644 index 00000000000..3eb76b52d1b --- /dev/null +++ b/contracts/src/v0.8/ccip/test/router/Router/Router.recoverTokens.t.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; + +import {Router} from "../../../Router.sol"; + +import {MaybeRevertMessageReceiver} from "../../helpers/receivers/MaybeRevertMessageReceiver.sol"; +import {OnRampSetup} from "../../onRamp/OnRamp/OnRampSetup.t.sol"; + +contract Router_recoverTokens is OnRampSetup { + function test_RecoverTokens_Success() public { + // Assert we can recover sourceToken + IERC20 token = IERC20(s_sourceTokens[0]); + uint256 balanceBefore = token.balanceOf(OWNER); + token.transfer(address(s_sourceRouter), 1); + assertEq(token.balanceOf(address(s_sourceRouter)), 1); + s_sourceRouter.recoverTokens(address(token), OWNER, 1); + assertEq(token.balanceOf(address(s_sourceRouter)), 0); + assertEq(token.balanceOf(OWNER), balanceBefore); + + // Assert we can recover native + balanceBefore = OWNER.balance; + deal(address(s_sourceRouter), 10); + assertEq(address(s_sourceRouter).balance, 10); + s_sourceRouter.recoverTokens(address(0), OWNER, 10); + assertEq(OWNER.balance, balanceBefore + 10); + assertEq(address(s_sourceRouter).balance, 0); + } + + function test_RecoverTokensNonOwner_Revert() public { + // Reverts if not owner + vm.startPrank(STRANGER); + vm.expectRevert("Only callable by owner"); + s_sourceRouter.recoverTokens(address(0), STRANGER, 1); + } + + function test_RecoverTokensInvalidRecipient_Revert() public { + vm.expectRevert(abi.encodeWithSelector(Router.InvalidRecipientAddress.selector, address(0))); + s_sourceRouter.recoverTokens(address(0), address(0), 1); + } + + function test_RecoverTokensNoFunds_Revert() public { + // Reverts if no funds present + vm.expectRevert(); + s_sourceRouter.recoverTokens(address(0), OWNER, 10); + } + + function test_RecoverTokensValueReceiver_Revert() public { + MaybeRevertMessageReceiver revertingValueReceiver = new MaybeRevertMessageReceiver(true); + deal(address(s_sourceRouter), 10); + + // Value receiver reverts + vm.expectRevert(Router.FailedToSendValue.selector); + s_sourceRouter.recoverTokens(address(0), address(revertingValueReceiver), 10); + } +} diff --git a/contracts/src/v0.8/ccip/test/router/Router/Router.routeMessage.t.sol b/contracts/src/v0.8/ccip/test/router/Router/Router.routeMessage.t.sol new file mode 100644 index 00000000000..8fca851fa4a --- /dev/null +++ b/contracts/src/v0.8/ccip/test/router/Router/Router.routeMessage.t.sol @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Router} from "../../../Router.sol"; +import {IAny2EVMMessageReceiver} from "../../../interfaces/IAny2EVMMessageReceiver.sol"; +import {IRouter} from "../../../interfaces/IRouter.sol"; +import {Client} from "../../../libraries/Client.sol"; + +import {MaybeRevertMessageReceiver} from "../../helpers/receivers/MaybeRevertMessageReceiver.sol"; +import {OffRampSetup} from "../../offRamp/OffRamp/OffRampSetup.t.sol"; + +contract Router_routeMessage is OffRampSetup { + function setUp() public virtual override { + super.setUp(); + vm.startPrank(address(s_offRamp)); + } + + function _generateManualGasLimit( + uint256 callDataLength + ) internal view returns (uint256) { + return ((gasleft() - 2 * (16 * callDataLength + GAS_FOR_CALL_EXACT_CHECK)) * 62) / 64; + } + + function test_routeMessage_ManualExec_Success() public { + Client.Any2EVMMessage memory message = _generateReceiverMessage(SOURCE_CHAIN_SELECTOR); + // Manuel execution cannot run out of gas + + (bool success, bytes memory retData, uint256 gasUsed) = s_destRouter.routeMessage( + _generateReceiverMessage(SOURCE_CHAIN_SELECTOR), + GAS_FOR_CALL_EXACT_CHECK, + _generateManualGasLimit(message.data.length), + address(s_receiver) + ); + assertTrue(success); + assertEq("", retData); + assertGt(gasUsed, 3_000); + } + + function test_routeMessage_ExecutionEvent_Success() public { + Client.Any2EVMMessage memory message = _generateReceiverMessage(SOURCE_CHAIN_SELECTOR); + // Should revert with reason + bytes memory realError1 = new bytes(2); + realError1[0] = 0xbe; + realError1[1] = 0xef; + s_reverting_receiver.setErr(realError1); + + vm.expectEmit(); + emit Router.MessageExecuted( + message.messageId, + message.sourceChainSelector, + address(s_offRamp), + keccak256(abi.encodeWithSelector(IAny2EVMMessageReceiver.ccipReceive.selector, message)) + ); + + (bool success, bytes memory retData, uint256 gasUsed) = s_destRouter.routeMessage( + _generateReceiverMessage(SOURCE_CHAIN_SELECTOR), + GAS_FOR_CALL_EXACT_CHECK, + _generateManualGasLimit(message.data.length), + address(s_reverting_receiver) + ); + + assertFalse(success); + assertEq(abi.encodeWithSelector(MaybeRevertMessageReceiver.CustomError.selector, realError1), retData); + assertGt(gasUsed, 3_000); + + // Reason is truncated + // Over the MAX_RET_BYTES limit (including offset and length word since we have a dynamic values), should be ignored + bytes memory realError2 = new bytes(32 * 2 + 1); + realError2[32 * 2 - 1] = 0xAA; + realError2[32 * 2] = 0xFF; + s_reverting_receiver.setErr(realError2); + + vm.expectEmit(); + emit Router.MessageExecuted( + message.messageId, + message.sourceChainSelector, + address(s_offRamp), + keccak256(abi.encodeWithSelector(IAny2EVMMessageReceiver.ccipReceive.selector, message)) + ); + + (success, retData, gasUsed) = s_destRouter.routeMessage( + _generateReceiverMessage(SOURCE_CHAIN_SELECTOR), + GAS_FOR_CALL_EXACT_CHECK, + _generateManualGasLimit(message.data.length), + address(s_reverting_receiver) + ); + + assertFalse(success); + assertEq( + abi.encodeWithSelector( + MaybeRevertMessageReceiver.CustomError.selector, + uint256(32), + uint256(realError2.length), + uint256(0), + uint256(0xAA) + ), + retData + ); + assertGt(gasUsed, 3_000); + + // Should emit success + vm.expectEmit(); + emit Router.MessageExecuted( + message.messageId, + message.sourceChainSelector, + address(s_offRamp), + keccak256(abi.encodeWithSelector(IAny2EVMMessageReceiver.ccipReceive.selector, message)) + ); + + (success, retData, gasUsed) = s_destRouter.routeMessage( + _generateReceiverMessage(SOURCE_CHAIN_SELECTOR), + GAS_FOR_CALL_EXACT_CHECK, + _generateManualGasLimit(message.data.length), + address(s_receiver) + ); + + assertTrue(success); + assertEq("", retData); + assertGt(gasUsed, 3_000); + } + + function testFuzz_routeMessage_ExecutionEvent_Success( + bytes calldata error + ) public { + Client.Any2EVMMessage memory message = _generateReceiverMessage(SOURCE_CHAIN_SELECTOR); + s_reverting_receiver.setErr(error); + + bytes memory expectedRetData; + + if (error.length >= 33) { + uint256 cutOff = error.length > 64 ? 64 : error.length; + vm.expectEmit(); + emit Router.MessageExecuted( + message.messageId, + message.sourceChainSelector, + address(s_offRamp), + keccak256(abi.encodeWithSelector(IAny2EVMMessageReceiver.ccipReceive.selector, message)) + ); + expectedRetData = abi.encodeWithSelector( + MaybeRevertMessageReceiver.CustomError.selector, + uint256(32), + uint256(error.length), + bytes32(error[:32]), + bytes32(error[32:cutOff]) + ); + } else { + vm.expectEmit(); + emit Router.MessageExecuted( + message.messageId, + message.sourceChainSelector, + address(s_offRamp), + keccak256(abi.encodeWithSelector(IAny2EVMMessageReceiver.ccipReceive.selector, message)) + ); + expectedRetData = abi.encodeWithSelector(MaybeRevertMessageReceiver.CustomError.selector, error); + } + + (bool success, bytes memory retData,) = s_destRouter.routeMessage( + _generateReceiverMessage(SOURCE_CHAIN_SELECTOR), + GAS_FOR_CALL_EXACT_CHECK, + _generateManualGasLimit(message.data.length), + address(s_reverting_receiver) + ); + + assertFalse(success); + assertEq(expectedRetData, retData); + } + + function test_routeMessage_AutoExec_Success() public { + (bool success,,) = s_destRouter.routeMessage( + _generateReceiverMessage(SOURCE_CHAIN_SELECTOR), GAS_FOR_CALL_EXACT_CHECK, 100_000, address(s_receiver) + ); + + assertTrue(success); + + (success,,) = s_destRouter.routeMessage( + _generateReceiverMessage(SOURCE_CHAIN_SELECTOR), GAS_FOR_CALL_EXACT_CHECK, 1, address(s_receiver) + ); + + // Can run out of gas, should return false + assertFalse(success); + } + + // Reverts + function test_routeMessage_OnlyOffRamp_Revert() public { + vm.stopPrank(); + vm.startPrank(STRANGER); + + vm.expectRevert(IRouter.OnlyOffRamp.selector); + s_destRouter.routeMessage( + _generateReceiverMessage(SOURCE_CHAIN_SELECTOR), GAS_FOR_CALL_EXACT_CHECK, 100_000, address(s_receiver) + ); + } + + function test_routeMessage_WhenNotHealthy_Revert() public { + s_mockRMN.setGlobalCursed(true); + vm.expectRevert(Router.BadARMSignal.selector); + s_destRouter.routeMessage( + _generateReceiverMessage(SOURCE_CHAIN_SELECTOR), GAS_FOR_CALL_EXACT_CHECK, 100_000, address(s_receiver) + ); + } +} diff --git a/contracts/src/v0.8/ccip/test/router/Router/Router.setWrappedNative.t.sol b/contracts/src/v0.8/ccip/test/router/Router/Router.setWrappedNative.t.sol new file mode 100644 index 00000000000..823aa8d144e --- /dev/null +++ b/contracts/src/v0.8/ccip/test/router/Router/Router.setWrappedNative.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {OnRampSetup} from "../../onRamp/OnRamp/OnRampSetup.t.sol"; + +contract Router_setWrappedNative is OnRampSetup { + function test_Fuzz_SetWrappedNative_Success( + address wrappedNative + ) public { + s_sourceRouter.setWrappedNative(wrappedNative); + assertEq(wrappedNative, s_sourceRouter.getWrappedNative()); + } + + // Reverts + function test_OnlyOwner_Revert() public { + vm.stopPrank(); + vm.expectRevert("Only callable by owner"); + s_sourceRouter.setWrappedNative(address(1)); + } +} diff --git a/contracts/src/v0.8/ccip/test/router/RouterSetup.t.sol b/contracts/src/v0.8/ccip/test/router/Router/RouterSetup.t.sol similarity index 85% rename from contracts/src/v0.8/ccip/test/router/RouterSetup.t.sol rename to contracts/src/v0.8/ccip/test/router/Router/RouterSetup.t.sol index f4c1114bf2a..4a977db6c23 100644 --- a/contracts/src/v0.8/ccip/test/router/RouterSetup.t.sol +++ b/contracts/src/v0.8/ccip/test/router/Router/RouterSetup.t.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.24; -import {Router} from "../../Router.sol"; -import {Client} from "../../libraries/Client.sol"; -import {Internal} from "../../libraries/Internal.sol"; -import {BaseTest} from "../BaseTest.t.sol"; -import {WETH9} from "../WETH9.sol"; +import {Router} from "../../../Router.sol"; +import {Client} from "../../../libraries/Client.sol"; +import {Internal} from "../../../libraries/Internal.sol"; +import {BaseTest} from "../../BaseTest.t.sol"; +import {WETH9} from "../../WETH9.sol"; contract RouterSetup is BaseTest { Router internal s_sourceRouter; diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20.t.sol deleted file mode 100644 index b5f6e942685..00000000000 --- a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20.t.sol +++ /dev/null @@ -1,372 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.24; - -import {IBurnMintERC20} from "../../../shared/token/ERC20/IBurnMintERC20.sol"; - -import {FactoryBurnMintERC20} from "../../tokenAdminRegistry/TokenPoolFactory/FactoryBurnMintERC20.sol"; -import {BaseTest} from "../BaseTest.t.sol"; - -import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; -import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/introspection/IERC165.sol"; - -contract BurnMintERC20Setup is BaseTest { - FactoryBurnMintERC20 internal s_burnMintERC20; - - address internal s_mockPool = makeAddr("s_mockPool"); - uint256 internal s_amount = 1e18; - - address internal s_alice; - - function setUp() public virtual override { - BaseTest.setUp(); - - s_alice = makeAddr("alice"); - - s_burnMintERC20 = new FactoryBurnMintERC20("Chainlink Token", "LINK", 18, 1e27, 0, s_alice); - - // Set s_mockPool to be a burner and minter - s_burnMintERC20.grantMintAndBurnRoles(s_mockPool); - deal(address(s_burnMintERC20), OWNER, s_amount); - } -} - -contract FactoryBurnMintERC20constructor is BurnMintERC20Setup { - function test_Constructor_Success() public { - string memory name = "Chainlink token v2"; - string memory symbol = "LINK2"; - uint8 decimals = 19; - uint256 maxSupply = 1e33; - - s_burnMintERC20 = new FactoryBurnMintERC20(name, symbol, decimals, maxSupply, 1e18, s_alice); - - assertEq(name, s_burnMintERC20.name()); - assertEq(symbol, s_burnMintERC20.symbol()); - assertEq(decimals, s_burnMintERC20.decimals()); - assertEq(maxSupply, s_burnMintERC20.maxSupply()); - - assertTrue(s_burnMintERC20.isMinter(s_alice)); - assertTrue(s_burnMintERC20.isBurner(s_alice)); - assertEq(s_burnMintERC20.balanceOf(s_alice), 1e18); - assertEq(s_burnMintERC20.totalSupply(), 1e18); - } -} - -contract FactoryBurnMintERC20approve is BurnMintERC20Setup { - function test_Approve_Success() public { - uint256 balancePre = s_burnMintERC20.balanceOf(STRANGER); - uint256 sendingAmount = s_amount / 2; - - s_burnMintERC20.approve(STRANGER, sendingAmount); - - changePrank(STRANGER); - - s_burnMintERC20.transferFrom(OWNER, STRANGER, sendingAmount); - - assertEq(sendingAmount + balancePre, s_burnMintERC20.balanceOf(STRANGER)); - } - - // Reverts - - function test_InvalidAddress_Reverts() public { - vm.expectRevert(); - - s_burnMintERC20.approve(address(s_burnMintERC20), s_amount); - } -} - -contract FactoryBurnMintERC20transfer is BurnMintERC20Setup { - function test_Transfer_Success() public { - uint256 balancePre = s_burnMintERC20.balanceOf(STRANGER); - uint256 sendingAmount = s_amount / 2; - - s_burnMintERC20.transfer(STRANGER, sendingAmount); - - assertEq(sendingAmount + balancePre, s_burnMintERC20.balanceOf(STRANGER)); - } - - // Reverts - - function test_InvalidAddress_Reverts() public { - vm.expectRevert(); - - s_burnMintERC20.transfer(address(s_burnMintERC20), s_amount); - } -} - -contract FactoryBurnMintERC20mint is BurnMintERC20Setup { - function test_BasicMint_Success() public { - uint256 balancePre = s_burnMintERC20.balanceOf(OWNER); - - s_burnMintERC20.grantMintAndBurnRoles(OWNER); - - vm.expectEmit(); - emit IERC20.Transfer(address(0), OWNER, s_amount); - - s_burnMintERC20.mint(OWNER, s_amount); - - assertEq(balancePre + s_amount, s_burnMintERC20.balanceOf(OWNER)); - } - - // Revert - - function test_SenderNotMinter_Reverts() public { - vm.expectRevert(abi.encodeWithSelector(FactoryBurnMintERC20.SenderNotMinter.selector, OWNER)); - s_burnMintERC20.mint(STRANGER, 1e18); - } - - function test_MaxSupplyExceeded_Reverts() public { - changePrank(s_mockPool); - - // Mint max supply - s_burnMintERC20.mint(OWNER, s_burnMintERC20.maxSupply()); - - vm.expectRevert( - abi.encodeWithSelector(FactoryBurnMintERC20.MaxSupplyExceeded.selector, s_burnMintERC20.maxSupply() + 1) - ); - - // Attempt to mint 1 more than max supply - s_burnMintERC20.mint(OWNER, 1); - } -} - -contract FactoryBurnMintERC20burn is BurnMintERC20Setup { - function test_BasicBurn_Success() public { - s_burnMintERC20.grantBurnRole(OWNER); - deal(address(s_burnMintERC20), OWNER, s_amount); - - vm.expectEmit(); - emit IERC20.Transfer(OWNER, address(0), s_amount); - - s_burnMintERC20.burn(s_amount); - - assertEq(0, s_burnMintERC20.balanceOf(OWNER)); - } - - // Revert - - function test_SenderNotBurner_Reverts() public { - vm.expectRevert(abi.encodeWithSelector(FactoryBurnMintERC20.SenderNotBurner.selector, OWNER)); - - s_burnMintERC20.burnFrom(STRANGER, s_amount); - } - - function test_ExceedsBalance_Reverts() public { - changePrank(s_mockPool); - - vm.expectRevert("ERC20: burn amount exceeds balance"); - - s_burnMintERC20.burn(s_amount * 2); - } - - function test_BurnFromZeroAddress_Reverts() public { - s_burnMintERC20.grantBurnRole(address(0)); - changePrank(address(0)); - - vm.expectRevert("ERC20: burn from the zero address"); - - s_burnMintERC20.burn(0); - } -} - -contract FactoryBurnMintERC20burnFromAlias is BurnMintERC20Setup { - function setUp() public virtual override { - BurnMintERC20Setup.setUp(); - } - - function test_BurnFrom_Success() public { - s_burnMintERC20.approve(s_mockPool, s_amount); - - changePrank(s_mockPool); - - s_burnMintERC20.burn(OWNER, s_amount); - - assertEq(0, s_burnMintERC20.balanceOf(OWNER)); - } - - // Reverts - - function test_SenderNotBurner_Reverts() public { - vm.expectRevert(abi.encodeWithSelector(FactoryBurnMintERC20.SenderNotBurner.selector, OWNER)); - - s_burnMintERC20.burn(OWNER, s_amount); - } - - function test_InsufficientAllowance_Reverts() public { - changePrank(s_mockPool); - - vm.expectRevert("ERC20: insufficient allowance"); - - s_burnMintERC20.burn(OWNER, s_amount); - } - - function test_ExceedsBalance_Reverts() public { - s_burnMintERC20.approve(s_mockPool, s_amount * 2); - - changePrank(s_mockPool); - - vm.expectRevert("ERC20: burn amount exceeds balance"); - - s_burnMintERC20.burn(OWNER, s_amount * 2); - } -} - -contract FactoryBurnMintERC20burnFrom is BurnMintERC20Setup { - function setUp() public virtual override { - BurnMintERC20Setup.setUp(); - } - - function test_BurnFrom_Success() public { - s_burnMintERC20.approve(s_mockPool, s_amount); - - changePrank(s_mockPool); - - s_burnMintERC20.burnFrom(OWNER, s_amount); - - assertEq(0, s_burnMintERC20.balanceOf(OWNER)); - } - - // Reverts - - function test_SenderNotBurner_Reverts() public { - vm.expectRevert(abi.encodeWithSelector(FactoryBurnMintERC20.SenderNotBurner.selector, OWNER)); - - s_burnMintERC20.burnFrom(OWNER, s_amount); - } - - function test_InsufficientAllowance_Reverts() public { - changePrank(s_mockPool); - - vm.expectRevert("ERC20: insufficient allowance"); - - s_burnMintERC20.burnFrom(OWNER, s_amount); - } - - function test_ExceedsBalance_Reverts() public { - s_burnMintERC20.approve(s_mockPool, s_amount * 2); - - changePrank(s_mockPool); - - vm.expectRevert("ERC20: burn amount exceeds balance"); - - s_burnMintERC20.burnFrom(OWNER, s_amount * 2); - } -} - -contract FactoryBurnMintERC20grantRole is BurnMintERC20Setup { - function test_GrantMintAccess_Success() public { - assertFalse(s_burnMintERC20.isMinter(STRANGER)); - - vm.expectEmit(); - emit FactoryBurnMintERC20.MintAccessGranted(STRANGER); - - s_burnMintERC20.grantMintAndBurnRoles(STRANGER); - - assertTrue(s_burnMintERC20.isMinter(STRANGER)); - - vm.expectEmit(); - emit FactoryBurnMintERC20.MintAccessRevoked(STRANGER); - - s_burnMintERC20.revokeMintRole(STRANGER); - - assertFalse(s_burnMintERC20.isMinter(STRANGER)); - } - - function test_GrantBurnAccess_Success() public { - assertFalse(s_burnMintERC20.isBurner(STRANGER)); - - vm.expectEmit(); - emit FactoryBurnMintERC20.BurnAccessGranted(STRANGER); - - s_burnMintERC20.grantBurnRole(STRANGER); - - assertTrue(s_burnMintERC20.isBurner(STRANGER)); - - vm.expectEmit(); - emit FactoryBurnMintERC20.BurnAccessRevoked(STRANGER); - - s_burnMintERC20.revokeBurnRole(STRANGER); - - assertFalse(s_burnMintERC20.isBurner(STRANGER)); - } - - function test_GrantMany_Success() public { - // Since alice was already granted mint and burn roles in the setup, we will revoke them - // and then grant them again for the purposes of the test - s_burnMintERC20.revokeMintRole(s_alice); - s_burnMintERC20.revokeBurnRole(s_alice); - - uint256 numberOfPools = 10; - address[] memory permissionedAddresses = new address[](numberOfPools + 1); - permissionedAddresses[0] = s_mockPool; - - for (uint160 i = 0; i < numberOfPools; ++i) { - permissionedAddresses[i + 1] = address(i); - s_burnMintERC20.grantMintAndBurnRoles(address(i)); - } - - assertEq(permissionedAddresses, s_burnMintERC20.getBurners()); - assertEq(permissionedAddresses, s_burnMintERC20.getMinters()); - } -} - -contract FactoryBurnMintERC20grantMintAndBurnRoles is BurnMintERC20Setup { - function test_GrantMintAndBurnRoles_Success() public { - assertFalse(s_burnMintERC20.isMinter(STRANGER)); - assertFalse(s_burnMintERC20.isBurner(STRANGER)); - - vm.expectEmit(); - emit FactoryBurnMintERC20.MintAccessGranted(STRANGER); - vm.expectEmit(); - emit FactoryBurnMintERC20.BurnAccessGranted(STRANGER); - - s_burnMintERC20.grantMintAndBurnRoles(STRANGER); - - assertTrue(s_burnMintERC20.isMinter(STRANGER)); - assertTrue(s_burnMintERC20.isBurner(STRANGER)); - } -} - -contract FactoryBurnMintERC20decreaseApproval is BurnMintERC20Setup { - function test_DecreaseApproval_Success() public { - s_burnMintERC20.approve(s_mockPool, s_amount); - uint256 allowance = s_burnMintERC20.allowance(OWNER, s_mockPool); - assertEq(allowance, s_amount); - s_burnMintERC20.decreaseApproval(s_mockPool, s_amount); - assertEq(s_burnMintERC20.allowance(OWNER, s_mockPool), allowance - s_amount); - } -} - -contract FactoryBurnMintERC20increaseApproval is BurnMintERC20Setup { - function test_IncreaseApproval_Success() public { - s_burnMintERC20.approve(s_mockPool, s_amount); - uint256 allowance = s_burnMintERC20.allowance(OWNER, s_mockPool); - assertEq(allowance, s_amount); - s_burnMintERC20.increaseApproval(s_mockPool, s_amount); - assertEq(s_burnMintERC20.allowance(OWNER, s_mockPool), allowance + s_amount); - } -} - -contract FactoryBurnMintERC20supportsInterface is BurnMintERC20Setup { - function test_SupportsInterface_Success() public view { - assertTrue(s_burnMintERC20.supportsInterface(type(IERC20).interfaceId)); - assertTrue(s_burnMintERC20.supportsInterface(type(IBurnMintERC20).interfaceId)); - assertTrue(s_burnMintERC20.supportsInterface(type(IERC165).interfaceId)); - } -} - -contract FactoryBurnMintERC20getCCIPAdmin is BurnMintERC20Setup { - function test_getCCIPAdmin_Success() public view { - assertEq(s_alice, s_burnMintERC20.getCCIPAdmin()); - } - - function test_setCCIPAdmin_Success() public { - address newAdmin = makeAddr("newAdmin"); - - vm.expectEmit(); - emit FactoryBurnMintERC20.CCIPAdminTransferred(s_alice, newAdmin); - - s_burnMintERC20.setCCIPAdmin(newAdmin); - - assertEq(newAdmin, s_burnMintERC20.getCCIPAdmin()); - } -} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/BurnMintERC20Setup.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/BurnMintERC20Setup.t.sol new file mode 100644 index 00000000000..098d5e4601e --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/BurnMintERC20Setup.t.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {FactoryBurnMintERC20} from "../../../tokenAdminRegistry/TokenPoolFactory/FactoryBurnMintERC20.sol"; +import {BaseTest} from "../../BaseTest.t.sol"; + +contract BurnMintERC20Setup is BaseTest { + FactoryBurnMintERC20 internal s_burnMintERC20; + + address internal s_mockPool = makeAddr("s_mockPool"); + uint256 internal s_amount = 1e18; + + address internal s_alice; + + function setUp() public virtual override { + BaseTest.setUp(); + + s_alice = makeAddr("alice"); + + s_burnMintERC20 = new FactoryBurnMintERC20("Chainlink Token", "LINK", 18, 1e27, 0, s_alice); + + // Set s_mockPool to be a burner and minter + s_burnMintERC20.grantMintAndBurnRoles(s_mockPool); + deal(address(s_burnMintERC20), OWNER, s_amount); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.approve.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.approve.t.sol new file mode 100644 index 00000000000..9ba6da0186d --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.approve.t.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {BurnMintERC20Setup} from "./BurnMintERC20Setup.t.sol"; + +contract FactoryBurnMintERC20_approve is BurnMintERC20Setup { + function test_Approve_Success() public { + uint256 balancePre = s_burnMintERC20.balanceOf(STRANGER); + uint256 sendingAmount = s_amount / 2; + + s_burnMintERC20.approve(STRANGER, sendingAmount); + + changePrank(STRANGER); + + s_burnMintERC20.transferFrom(OWNER, STRANGER, sendingAmount); + + assertEq(sendingAmount + balancePre, s_burnMintERC20.balanceOf(STRANGER)); + } + + // Reverts + + function test_InvalidAddress_Reverts() public { + vm.expectRevert(); + + s_burnMintERC20.approve(address(s_burnMintERC20), s_amount); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.burn.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.burn.t.sol new file mode 100644 index 00000000000..5f6e7ee4d04 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.burn.t.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; +import {FactoryBurnMintERC20} from "../../../tokenAdminRegistry/TokenPoolFactory/FactoryBurnMintERC20.sol"; +import {BurnMintERC20Setup} from "./BurnMintERC20Setup.t.sol"; + +contract FactoryBurnMintERC20_burn is BurnMintERC20Setup { + function test_BasicBurn_Success() public { + s_burnMintERC20.grantBurnRole(OWNER); + deal(address(s_burnMintERC20), OWNER, s_amount); + + vm.expectEmit(); + emit IERC20.Transfer(OWNER, address(0), s_amount); + + s_burnMintERC20.burn(s_amount); + + assertEq(0, s_burnMintERC20.balanceOf(OWNER)); + } + + // Revert + + function test_SenderNotBurner_Reverts() public { + vm.expectRevert(abi.encodeWithSelector(FactoryBurnMintERC20.SenderNotBurner.selector, OWNER)); + + s_burnMintERC20.burnFrom(STRANGER, s_amount); + } + + function test_ExceedsBalance_Reverts() public { + changePrank(s_mockPool); + + vm.expectRevert("ERC20: burn amount exceeds balance"); + + s_burnMintERC20.burn(s_amount * 2); + } + + function test_BurnFromZeroAddress_Reverts() public { + s_burnMintERC20.grantBurnRole(address(0)); + changePrank(address(0)); + + vm.expectRevert("ERC20: burn from the zero address"); + + s_burnMintERC20.burn(0); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.burnFrom.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.burnFrom.t.sol new file mode 100644 index 00000000000..e2dcaf28563 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.burnFrom.t.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {FactoryBurnMintERC20} from "../../../tokenAdminRegistry/TokenPoolFactory/FactoryBurnMintERC20.sol"; +import {BurnMintERC20Setup} from "./BurnMintERC20Setup.t.sol"; + +contract FactoryBurnMintERC20_burnFrom is BurnMintERC20Setup { + function setUp() public virtual override { + BurnMintERC20Setup.setUp(); + } + + function test_BurnFrom_Success() public { + s_burnMintERC20.approve(s_mockPool, s_amount); + + changePrank(s_mockPool); + + s_burnMintERC20.burnFrom(OWNER, s_amount); + + assertEq(0, s_burnMintERC20.balanceOf(OWNER)); + } + + // Reverts + + function test_SenderNotBurner_Reverts() public { + vm.expectRevert(abi.encodeWithSelector(FactoryBurnMintERC20.SenderNotBurner.selector, OWNER)); + + s_burnMintERC20.burnFrom(OWNER, s_amount); + } + + function test_InsufficientAllowance_Reverts() public { + changePrank(s_mockPool); + + vm.expectRevert("ERC20: insufficient allowance"); + + s_burnMintERC20.burnFrom(OWNER, s_amount); + } + + function test_ExceedsBalance_Reverts() public { + s_burnMintERC20.approve(s_mockPool, s_amount * 2); + + changePrank(s_mockPool); + + vm.expectRevert("ERC20: burn amount exceeds balance"); + + s_burnMintERC20.burnFrom(OWNER, s_amount * 2); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.burnFromAlias.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.burnFromAlias.t.sol new file mode 100644 index 00000000000..0d46f1d54a5 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.burnFromAlias.t.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {FactoryBurnMintERC20} from "../../../tokenAdminRegistry/TokenPoolFactory/FactoryBurnMintERC20.sol"; +import {BurnMintERC20Setup} from "./BurnMintERC20Setup.t.sol"; + +contract FactoryBurnMintERC20_burnFromAlias is BurnMintERC20Setup { + function setUp() public virtual override { + BurnMintERC20Setup.setUp(); + } + + function test_BurnFrom_Success() public { + s_burnMintERC20.approve(s_mockPool, s_amount); + + changePrank(s_mockPool); + + s_burnMintERC20.burn(OWNER, s_amount); + + assertEq(0, s_burnMintERC20.balanceOf(OWNER)); + } + + // Reverts + + function test_SenderNotBurner_Reverts() public { + vm.expectRevert(abi.encodeWithSelector(FactoryBurnMintERC20.SenderNotBurner.selector, OWNER)); + + s_burnMintERC20.burn(OWNER, s_amount); + } + + function test_InsufficientAllowance_Reverts() public { + changePrank(s_mockPool); + + vm.expectRevert("ERC20: insufficient allowance"); + + s_burnMintERC20.burn(OWNER, s_amount); + } + + function test_ExceedsBalance_Reverts() public { + s_burnMintERC20.approve(s_mockPool, s_amount * 2); + + changePrank(s_mockPool); + + vm.expectRevert("ERC20: burn amount exceeds balance"); + + s_burnMintERC20.burn(OWNER, s_amount * 2); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.constructor.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.constructor.t.sol new file mode 100644 index 00000000000..f1ee0866abe --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.constructor.t.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {FactoryBurnMintERC20} from "../../../tokenAdminRegistry/TokenPoolFactory/FactoryBurnMintERC20.sol"; +import {BurnMintERC20Setup} from "./BurnMintERC20Setup.t.sol"; + +contract FactoryBurnMintERC20_constructor is BurnMintERC20Setup { + function test_Constructor_Success() public { + string memory name = "Chainlink token v2"; + string memory symbol = "LINK2"; + uint8 decimals = 19; + uint256 maxSupply = 1e33; + + s_burnMintERC20 = new FactoryBurnMintERC20(name, symbol, decimals, maxSupply, 1e18, s_alice); + + assertEq(name, s_burnMintERC20.name()); + assertEq(symbol, s_burnMintERC20.symbol()); + assertEq(decimals, s_burnMintERC20.decimals()); + assertEq(maxSupply, s_burnMintERC20.maxSupply()); + + assertTrue(s_burnMintERC20.isMinter(s_alice)); + assertTrue(s_burnMintERC20.isBurner(s_alice)); + assertEq(s_burnMintERC20.balanceOf(s_alice), 1e18); + assertEq(s_burnMintERC20.totalSupply(), 1e18); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.decreaseApproval.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.decreaseApproval.t.sol new file mode 100644 index 00000000000..aa621a998ed --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.decreaseApproval.t.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {BurnMintERC20Setup} from "./BurnMintERC20Setup.t.sol"; + +contract FactoryBurnMintERC20_decreaseApproval is BurnMintERC20Setup { + function test_DecreaseApproval_Success() public { + s_burnMintERC20.approve(s_mockPool, s_amount); + uint256 allowance = s_burnMintERC20.allowance(OWNER, s_mockPool); + assertEq(allowance, s_amount); + s_burnMintERC20.decreaseApproval(s_mockPool, s_amount); + assertEq(s_burnMintERC20.allowance(OWNER, s_mockPool), allowance - s_amount); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.getCCIPAdmin.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.getCCIPAdmin.t.sol new file mode 100644 index 00000000000..fc6a81a712b --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.getCCIPAdmin.t.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {FactoryBurnMintERC20} from "../../../tokenAdminRegistry/TokenPoolFactory/FactoryBurnMintERC20.sol"; +import {BurnMintERC20Setup} from "./BurnMintERC20Setup.t.sol"; + +contract FactoryBurnMintERC20_getCCIPAdmin is BurnMintERC20Setup { + function test_getCCIPAdmin_Success() public view { + assertEq(s_alice, s_burnMintERC20.getCCIPAdmin()); + } + + function test_setCCIPAdmin_Success() public { + address newAdmin = makeAddr("newAdmin"); + + vm.expectEmit(); + emit FactoryBurnMintERC20.CCIPAdminTransferred(s_alice, newAdmin); + + s_burnMintERC20.setCCIPAdmin(newAdmin); + + assertEq(newAdmin, s_burnMintERC20.getCCIPAdmin()); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.grantMintAndBurnRoles.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.grantMintAndBurnRoles.t.sol new file mode 100644 index 00000000000..aaa967edc15 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.grantMintAndBurnRoles.t.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {FactoryBurnMintERC20} from "../../../tokenAdminRegistry/TokenPoolFactory/FactoryBurnMintERC20.sol"; +import {BurnMintERC20Setup} from "./BurnMintERC20Setup.t.sol"; + +contract FactoryBurnMintERC20_grantMintAndBurnRoles is BurnMintERC20Setup { + function test_GrantMintAndBurnRoles_Success() public { + assertFalse(s_burnMintERC20.isMinter(STRANGER)); + assertFalse(s_burnMintERC20.isBurner(STRANGER)); + + vm.expectEmit(); + emit FactoryBurnMintERC20.MintAccessGranted(STRANGER); + vm.expectEmit(); + emit FactoryBurnMintERC20.BurnAccessGranted(STRANGER); + + s_burnMintERC20.grantMintAndBurnRoles(STRANGER); + + assertTrue(s_burnMintERC20.isMinter(STRANGER)); + assertTrue(s_burnMintERC20.isBurner(STRANGER)); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.grantRole.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.grantRole.t.sol new file mode 100644 index 00000000000..a06b52ac338 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.grantRole.t.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {FactoryBurnMintERC20} from "../../../tokenAdminRegistry/TokenPoolFactory/FactoryBurnMintERC20.sol"; +import {BurnMintERC20Setup} from "./BurnMintERC20Setup.t.sol"; + +contract FactoryBurnMintERC20_grantRole is BurnMintERC20Setup { + function test_GrantMintAccess_Success() public { + assertFalse(s_burnMintERC20.isMinter(STRANGER)); + + vm.expectEmit(); + emit FactoryBurnMintERC20.MintAccessGranted(STRANGER); + + s_burnMintERC20.grantMintAndBurnRoles(STRANGER); + + assertTrue(s_burnMintERC20.isMinter(STRANGER)); + + vm.expectEmit(); + emit FactoryBurnMintERC20.MintAccessRevoked(STRANGER); + + s_burnMintERC20.revokeMintRole(STRANGER); + + assertFalse(s_burnMintERC20.isMinter(STRANGER)); + } + + function test_GrantBurnAccess_Success() public { + assertFalse(s_burnMintERC20.isBurner(STRANGER)); + + vm.expectEmit(); + emit FactoryBurnMintERC20.BurnAccessGranted(STRANGER); + + s_burnMintERC20.grantBurnRole(STRANGER); + + assertTrue(s_burnMintERC20.isBurner(STRANGER)); + + vm.expectEmit(); + emit FactoryBurnMintERC20.BurnAccessRevoked(STRANGER); + + s_burnMintERC20.revokeBurnRole(STRANGER); + + assertFalse(s_burnMintERC20.isBurner(STRANGER)); + } + + function test_GrantMany_Success() public { + // Since alice was already granted mint and burn roles in the setup, we will revoke them + // and then grant them again for the purposes of the test + s_burnMintERC20.revokeMintRole(s_alice); + s_burnMintERC20.revokeBurnRole(s_alice); + + uint256 numberOfPools = 10; + address[] memory permissionedAddresses = new address[](numberOfPools + 1); + permissionedAddresses[0] = s_mockPool; + + for (uint160 i = 0; i < numberOfPools; ++i) { + permissionedAddresses[i + 1] = address(i); + s_burnMintERC20.grantMintAndBurnRoles(address(i)); + } + + assertEq(permissionedAddresses, s_burnMintERC20.getBurners()); + assertEq(permissionedAddresses, s_burnMintERC20.getMinters()); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.increaseApproval.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.increaseApproval.t.sol new file mode 100644 index 00000000000..e93cc2a71e6 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.increaseApproval.t.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {BurnMintERC20Setup} from "./BurnMintERC20Setup.t.sol"; + +contract FactoryBurnMintERC20_increaseApproval is BurnMintERC20Setup { + function test_IncreaseApproval_Success() public { + s_burnMintERC20.approve(s_mockPool, s_amount); + uint256 allowance = s_burnMintERC20.allowance(OWNER, s_mockPool); + assertEq(allowance, s_amount); + s_burnMintERC20.increaseApproval(s_mockPool, s_amount); + assertEq(s_burnMintERC20.allowance(OWNER, s_mockPool), allowance + s_amount); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.mint.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.mint.t.sol new file mode 100644 index 00000000000..b22783a3c75 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.mint.t.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {FactoryBurnMintERC20} from "../../../tokenAdminRegistry/TokenPoolFactory/FactoryBurnMintERC20.sol"; + +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; +import {BurnMintERC20Setup} from "./BurnMintERC20Setup.t.sol"; + +contract FactoryBurnMintERC20_mint is BurnMintERC20Setup { + function test_BasicMint_Success() public { + uint256 balancePre = s_burnMintERC20.balanceOf(OWNER); + + s_burnMintERC20.grantMintAndBurnRoles(OWNER); + + vm.expectEmit(); + emit IERC20.Transfer(address(0), OWNER, s_amount); + + s_burnMintERC20.mint(OWNER, s_amount); + + assertEq(balancePre + s_amount, s_burnMintERC20.balanceOf(OWNER)); + } + + // Revert + + function test_SenderNotMinter_Reverts() public { + vm.expectRevert(abi.encodeWithSelector(FactoryBurnMintERC20.SenderNotMinter.selector, OWNER)); + s_burnMintERC20.mint(STRANGER, 1e18); + } + + function test_MaxSupplyExceeded_Reverts() public { + changePrank(s_mockPool); + + // Mint max supply + s_burnMintERC20.mint(OWNER, s_burnMintERC20.maxSupply()); + + vm.expectRevert( + abi.encodeWithSelector(FactoryBurnMintERC20.MaxSupplyExceeded.selector, s_burnMintERC20.maxSupply() + 1) + ); + + // Attempt to mint 1 more than max supply + s_burnMintERC20.mint(OWNER, 1); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.supportsInterface.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.supportsInterface.t.sol new file mode 100644 index 00000000000..bdf3c3e7ae3 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.supportsInterface.t.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {IBurnMintERC20} from "../../../../shared/token/ERC20/IBurnMintERC20.sol"; +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; +import {IERC165} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/introspection/IERC165.sol"; +import {BurnMintERC20Setup} from "./BurnMintERC20Setup.t.sol"; + +contract FactoryBurnMintERC20_supportsInterface is BurnMintERC20Setup { + function test_SupportsInterface_Success() public view { + assertTrue(s_burnMintERC20.supportsInterface(type(IERC20).interfaceId)); + assertTrue(s_burnMintERC20.supportsInterface(type(IBurnMintERC20).interfaceId)); + assertTrue(s_burnMintERC20.supportsInterface(type(IERC165).interfaceId)); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.transfer.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.transfer.t.sol new file mode 100644 index 00000000000..333d50d333e --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/FactoryBurnMintERC20/FactoryBurnMintERC20.transfer.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {BurnMintERC20Setup} from "./BurnMintERC20Setup.t.sol"; + +contract FactoryBurnMintERC20_transfer is BurnMintERC20Setup { + function test_Transfer_Success() public { + uint256 balancePre = s_burnMintERC20.balanceOf(STRANGER); + uint256 sendingAmount = s_amount / 2; + + s_burnMintERC20.transfer(STRANGER, sendingAmount); + + assertEq(sendingAmount + balancePre, s_burnMintERC20.balanceOf(STRANGER)); + } + + // Reverts + + function test_InvalidAddress_Reverts() public { + vm.expectRevert(); + + s_burnMintERC20.transfer(address(s_burnMintERC20), s_amount); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/RegistryModuleOwnerCustom.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/RegistryModuleOwnerCustom.t.sol deleted file mode 100644 index cf40fb62d22..00000000000 --- a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/RegistryModuleOwnerCustom.t.sol +++ /dev/null @@ -1,156 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import {IGetCCIPAdmin} from "../../interfaces/IGetCCIPAdmin.sol"; -import {IOwner} from "../../interfaces/IOwner.sol"; - -import {RegistryModuleOwnerCustom} from "../../tokenAdminRegistry/RegistryModuleOwnerCustom.sol"; -import {TokenAdminRegistry} from "../../tokenAdminRegistry/TokenAdminRegistry.sol"; -import {BurnMintERC677Helper} from "../helpers/BurnMintERC677Helper.sol"; - -import {AccessControl} from "../../../vendor/openzeppelin-solidity/v5.0.2/contracts/access/AccessControl.sol"; -import {Test} from "forge-std/Test.sol"; - -contract RegistryModuleOwnerCustomSetup is Test { - address internal constant OWNER = 0x00007e64E1fB0C487F25dd6D3601ff6aF8d32e4e; - - RegistryModuleOwnerCustom internal s_registryModuleOwnerCustom; - TokenAdminRegistry internal s_tokenAdminRegistry; - address internal s_token; - - function setUp() public virtual { - vm.startPrank(OWNER); - - s_tokenAdminRegistry = new TokenAdminRegistry(); - s_token = address(new BurnMintERC677Helper("Test", "TST")); - s_registryModuleOwnerCustom = new RegistryModuleOwnerCustom(address(s_tokenAdminRegistry)); - s_tokenAdminRegistry.addRegistryModule(address(s_registryModuleOwnerCustom)); - } -} - -contract RegistryModuleOwnerCustom_constructor is RegistryModuleOwnerCustomSetup { - function test_constructor_Revert() public { - vm.expectRevert(abi.encodeWithSelector(RegistryModuleOwnerCustom.AddressZero.selector)); - - new RegistryModuleOwnerCustom(address(0)); - } -} - -contract RegistryModuleOwnerCustom_registerAdminViaGetCCIPAdmin is RegistryModuleOwnerCustomSetup { - function test_registerAdminViaGetCCIPAdmin_Success() public { - assertEq(s_tokenAdminRegistry.getTokenConfig(s_token).administrator, address(0)); - - address expectedOwner = IGetCCIPAdmin(s_token).getCCIPAdmin(); - - vm.expectCall(s_token, abi.encodeWithSelector(IGetCCIPAdmin.getCCIPAdmin.selector), 1); - vm.expectCall( - address(s_tokenAdminRegistry), - abi.encodeWithSelector(TokenAdminRegistry.proposeAdministrator.selector, s_token, expectedOwner), - 1 - ); - - vm.expectEmit(); - emit RegistryModuleOwnerCustom.AdministratorRegistered(s_token, expectedOwner); - - s_registryModuleOwnerCustom.registerAdminViaGetCCIPAdmin(s_token); - - assertEq(s_tokenAdminRegistry.getTokenConfig(s_token).pendingAdministrator, OWNER); - } - - function test_registerAdminViaGetCCIPAdmin_Revert() public { - address expectedOwner = IGetCCIPAdmin(s_token).getCCIPAdmin(); - - vm.startPrank(makeAddr("Not_expected_owner")); - - vm.expectRevert( - abi.encodeWithSelector(RegistryModuleOwnerCustom.CanOnlySelfRegister.selector, expectedOwner, s_token) - ); - - s_registryModuleOwnerCustom.registerAdminViaGetCCIPAdmin(s_token); - } -} - -contract RegistryModuleOwnerCustom_registerAdminViaOwner is RegistryModuleOwnerCustomSetup { - function test_registerAdminViaOwner_Success() public { - assertEq(s_tokenAdminRegistry.getTokenConfig(s_token).administrator, address(0)); - - address expectedOwner = IOwner(s_token).owner(); - - vm.expectCall(s_token, abi.encodeWithSelector(IOwner.owner.selector), 1); - vm.expectCall( - address(s_tokenAdminRegistry), - abi.encodeWithSelector(TokenAdminRegistry.proposeAdministrator.selector, s_token, expectedOwner), - 1 - ); - - vm.expectEmit(); - emit RegistryModuleOwnerCustom.AdministratorRegistered(s_token, expectedOwner); - - s_registryModuleOwnerCustom.registerAdminViaOwner(s_token); - - assertEq(s_tokenAdminRegistry.getTokenConfig(s_token).pendingAdministrator, OWNER); - } - - function test_registerAdminViaOwner_Revert() public { - address expectedOwner = IOwner(s_token).owner(); - - vm.startPrank(makeAddr("Not_expected_owner")); - - vm.expectRevert( - abi.encodeWithSelector(RegistryModuleOwnerCustom.CanOnlySelfRegister.selector, expectedOwner, s_token) - ); - - s_registryModuleOwnerCustom.registerAdminViaOwner(s_token); - } -} - -contract AccessController is AccessControl { - constructor( - address admin - ) { - _grantRole(DEFAULT_ADMIN_ROLE, admin); - } -} - -contract RegistryModuleOwnerCustom_registerAccessControlDefaultAdmin is RegistryModuleOwnerCustomSetup { - function setUp() public override { - super.setUp(); - - s_token = address(new AccessController(OWNER)); - } - - function test_registerAccessControlDefaultAdmin_Success() public { - assertEq(s_tokenAdminRegistry.getTokenConfig(s_token).administrator, address(0)); - - bytes32 defaultAdminRole = AccessController(s_token).DEFAULT_ADMIN_ROLE(); - - vm.expectCall(address(s_token), abi.encodeWithSelector(AccessControl.hasRole.selector, defaultAdminRole, OWNER), 1); - vm.expectCall( - address(s_tokenAdminRegistry), - abi.encodeWithSelector(TokenAdminRegistry.proposeAdministrator.selector, s_token, OWNER), - 1 - ); - - vm.expectEmit(); - emit RegistryModuleOwnerCustom.AdministratorRegistered(s_token, OWNER); - - s_registryModuleOwnerCustom.registerAccessControlDefaultAdmin(s_token); - - assertEq(s_tokenAdminRegistry.getTokenConfig(s_token).pendingAdministrator, OWNER); - } - - function test_registerAccessControlDefaultAdmin_Revert() public { - bytes32 defaultAdminRole = AccessController(s_token).DEFAULT_ADMIN_ROLE(); - - address wrongSender = makeAddr("Not_expected_owner"); - vm.startPrank(wrongSender); - - vm.expectRevert( - abi.encodeWithSelector( - RegistryModuleOwnerCustom.RequiredRoleNotFound.selector, wrongSender, defaultAdminRole, s_token - ) - ); - - s_registryModuleOwnerCustom.registerAccessControlDefaultAdmin(s_token); - } -} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/RegistryModuleOwnerCustom/RegistryModuleOwnerCustom.constructor.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/RegistryModuleOwnerCustom/RegistryModuleOwnerCustom.constructor.t.sol new file mode 100644 index 00000000000..22bf54e633b --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/RegistryModuleOwnerCustom/RegistryModuleOwnerCustom.constructor.t.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {RegistryModuleOwnerCustom} from "../../../tokenAdminRegistry/RegistryModuleOwnerCustom.sol"; +import {RegistryModuleOwnerCustomSetup} from "./RegistryModuleOwnerCustomSetup.t.sol"; + +contract RegistryModuleOwnerCustom_constructor is RegistryModuleOwnerCustomSetup { + function test_constructor_Revert() public { + vm.expectRevert(abi.encodeWithSelector(RegistryModuleOwnerCustom.AddressZero.selector)); + + new RegistryModuleOwnerCustom(address(0)); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/RegistryModuleOwnerCustom/RegistryModuleOwnerCustom.registerAccessControlDefaultAdmin.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/RegistryModuleOwnerCustom/RegistryModuleOwnerCustom.registerAccessControlDefaultAdmin.t.sol new file mode 100644 index 00000000000..5bf1c0aee34 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/RegistryModuleOwnerCustom/RegistryModuleOwnerCustom.registerAccessControlDefaultAdmin.t.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {RegistryModuleOwnerCustom} from "../../../tokenAdminRegistry/RegistryModuleOwnerCustom.sol"; +import {TokenAdminRegistry} from "../../../tokenAdminRegistry/TokenAdminRegistry.sol"; + +import {AccessControl} from "../../../../vendor/openzeppelin-solidity/v5.0.2/contracts/access/AccessControl.sol"; + +import {RegistryModuleOwnerCustomSetup} from "./RegistryModuleOwnerCustomSetup.t.sol"; + +contract AccessController is AccessControl { + constructor( + address admin + ) { + _grantRole(DEFAULT_ADMIN_ROLE, admin); + } +} + +contract RegistryModuleOwnerCustom_registerAccessControlDefaultAdmin is RegistryModuleOwnerCustomSetup { + function setUp() public override { + super.setUp(); + + s_token = address(new AccessController(OWNER)); + } + + function test_registerAccessControlDefaultAdmin_Success() public { + assertEq(s_tokenAdminRegistry.getTokenConfig(s_token).administrator, address(0)); + + bytes32 defaultAdminRole = AccessController(s_token).DEFAULT_ADMIN_ROLE(); + + vm.expectCall(address(s_token), abi.encodeWithSelector(AccessControl.hasRole.selector, defaultAdminRole, OWNER), 1); + vm.expectCall( + address(s_tokenAdminRegistry), + abi.encodeWithSelector(TokenAdminRegistry.proposeAdministrator.selector, s_token, OWNER), + 1 + ); + + vm.expectEmit(); + emit RegistryModuleOwnerCustom.AdministratorRegistered(s_token, OWNER); + + s_registryModuleOwnerCustom.registerAccessControlDefaultAdmin(s_token); + + assertEq(s_tokenAdminRegistry.getTokenConfig(s_token).pendingAdministrator, OWNER); + } + + function test_registerAccessControlDefaultAdmin_Revert() public { + bytes32 defaultAdminRole = AccessController(s_token).DEFAULT_ADMIN_ROLE(); + + address wrongSender = makeAddr("Not_expected_owner"); + vm.startPrank(wrongSender); + + vm.expectRevert( + abi.encodeWithSelector( + RegistryModuleOwnerCustom.RequiredRoleNotFound.selector, wrongSender, defaultAdminRole, s_token + ) + ); + + s_registryModuleOwnerCustom.registerAccessControlDefaultAdmin(s_token); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/RegistryModuleOwnerCustom/RegistryModuleOwnerCustom.registerAdminViaGetCCIPAdmin.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/RegistryModuleOwnerCustom/RegistryModuleOwnerCustom.registerAdminViaGetCCIPAdmin.t.sol new file mode 100644 index 00000000000..5e3c6545417 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/RegistryModuleOwnerCustom/RegistryModuleOwnerCustom.registerAdminViaGetCCIPAdmin.t.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {IGetCCIPAdmin} from "../../../interfaces/IGetCCIPAdmin.sol"; + +import {RegistryModuleOwnerCustom} from "../../../tokenAdminRegistry/RegistryModuleOwnerCustom.sol"; +import {TokenAdminRegistry} from "../../../tokenAdminRegistry/TokenAdminRegistry.sol"; + +import {RegistryModuleOwnerCustomSetup} from "./RegistryModuleOwnerCustomSetup.t.sol"; + +contract RegistryModuleOwnerCustom_registerAdminViaGetCCIPAdmin is RegistryModuleOwnerCustomSetup { + function test_registerAdminViaGetCCIPAdmin_Success() public { + assertEq(s_tokenAdminRegistry.getTokenConfig(s_token).administrator, address(0)); + + address expectedOwner = IGetCCIPAdmin(s_token).getCCIPAdmin(); + + vm.expectCall(s_token, abi.encodeWithSelector(IGetCCIPAdmin.getCCIPAdmin.selector), 1); + vm.expectCall( + address(s_tokenAdminRegistry), + abi.encodeWithSelector(TokenAdminRegistry.proposeAdministrator.selector, s_token, expectedOwner), + 1 + ); + + vm.expectEmit(); + emit RegistryModuleOwnerCustom.AdministratorRegistered(s_token, expectedOwner); + + s_registryModuleOwnerCustom.registerAdminViaGetCCIPAdmin(s_token); + + assertEq(s_tokenAdminRegistry.getTokenConfig(s_token).pendingAdministrator, OWNER); + } + + function test_registerAdminViaGetCCIPAdmin_Revert() public { + address expectedOwner = IGetCCIPAdmin(s_token).getCCIPAdmin(); + + vm.startPrank(makeAddr("Not_expected_owner")); + + vm.expectRevert( + abi.encodeWithSelector(RegistryModuleOwnerCustom.CanOnlySelfRegister.selector, expectedOwner, s_token) + ); + + s_registryModuleOwnerCustom.registerAdminViaGetCCIPAdmin(s_token); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/RegistryModuleOwnerCustom/RegistryModuleOwnerCustom.registerAdminViaOwner.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/RegistryModuleOwnerCustom/RegistryModuleOwnerCustom.registerAdminViaOwner.t.sol new file mode 100644 index 00000000000..b4e1a5e4577 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/RegistryModuleOwnerCustom/RegistryModuleOwnerCustom.registerAdminViaOwner.t.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {IOwner} from "../../../interfaces/IOwner.sol"; + +import {RegistryModuleOwnerCustom} from "../../../tokenAdminRegistry/RegistryModuleOwnerCustom.sol"; +import {TokenAdminRegistry} from "../../../tokenAdminRegistry/TokenAdminRegistry.sol"; + +import {RegistryModuleOwnerCustomSetup} from "./RegistryModuleOwnerCustomSetup.t.sol"; + +contract RegistryModuleOwnerCustom_registerAdminViaOwner is RegistryModuleOwnerCustomSetup { + function test_registerAdminViaOwner_Success() public { + assertEq(s_tokenAdminRegistry.getTokenConfig(s_token).administrator, address(0)); + + address expectedOwner = IOwner(s_token).owner(); + + vm.expectCall(s_token, abi.encodeWithSelector(IOwner.owner.selector), 1); + vm.expectCall( + address(s_tokenAdminRegistry), + abi.encodeWithSelector(TokenAdminRegistry.proposeAdministrator.selector, s_token, expectedOwner), + 1 + ); + + vm.expectEmit(); + emit RegistryModuleOwnerCustom.AdministratorRegistered(s_token, expectedOwner); + + s_registryModuleOwnerCustom.registerAdminViaOwner(s_token); + + assertEq(s_tokenAdminRegistry.getTokenConfig(s_token).pendingAdministrator, OWNER); + } + + function test_registerAdminViaOwner_Revert() public { + address expectedOwner = IOwner(s_token).owner(); + + vm.startPrank(makeAddr("Not_expected_owner")); + + vm.expectRevert( + abi.encodeWithSelector(RegistryModuleOwnerCustom.CanOnlySelfRegister.selector, expectedOwner, s_token) + ); + + s_registryModuleOwnerCustom.registerAdminViaOwner(s_token); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/RegistryModuleOwnerCustom/RegistryModuleOwnerCustomSetup.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/RegistryModuleOwnerCustom/RegistryModuleOwnerCustomSetup.t.sol new file mode 100644 index 00000000000..e12f01d4122 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/RegistryModuleOwnerCustom/RegistryModuleOwnerCustomSetup.t.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {RegistryModuleOwnerCustom} from "../../../tokenAdminRegistry/RegistryModuleOwnerCustom.sol"; +import {TokenAdminRegistry} from "../../../tokenAdminRegistry/TokenAdminRegistry.sol"; +import {BurnMintERC677Helper} from "../../helpers/BurnMintERC677Helper.sol"; + +import {Test} from "forge-std/Test.sol"; + +contract RegistryModuleOwnerCustomSetup is Test { + address internal constant OWNER = 0x00007e64E1fB0C487F25dd6D3601ff6aF8d32e4e; + + RegistryModuleOwnerCustom internal s_registryModuleOwnerCustom; + TokenAdminRegistry internal s_tokenAdminRegistry; + address internal s_token; + + function setUp() public virtual { + vm.startPrank(OWNER); + + s_tokenAdminRegistry = new TokenAdminRegistry(); + s_token = address(new BurnMintERC677Helper("Test", "TST")); + s_registryModuleOwnerCustom = new RegistryModuleOwnerCustom(address(s_tokenAdminRegistry)); + s_tokenAdminRegistry.addRegistryModule(address(s_registryModuleOwnerCustom)); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry.t.sol deleted file mode 100644 index 05825498a41..00000000000 --- a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry.t.sol +++ /dev/null @@ -1,396 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import {IPoolV1} from "../../interfaces/IPool.sol"; - -import {Ownable2Step} from "../../../shared/access/Ownable2Step.sol"; -import {TokenAdminRegistry} from "../../tokenAdminRegistry/TokenAdminRegistry.sol"; -import {TokenSetup} from "../TokenSetup.t.sol"; - -contract TokenAdminRegistrySetup is TokenSetup { - address internal s_registryModule = makeAddr("registryModule"); - - function setUp() public virtual override { - TokenSetup.setUp(); - - s_tokenAdminRegistry.addRegistryModule(s_registryModule); - } -} - -contract TokenAdminRegistry_getPools is TokenAdminRegistrySetup { - function test_getPools_Success() public { - address[] memory tokens = new address[](1); - tokens[0] = s_sourceTokens[0]; - - address[] memory got = s_tokenAdminRegistry.getPools(tokens); - assertEq(got.length, 1); - assertEq(got[0], s_sourcePoolByToken[tokens[0]]); - - got = s_tokenAdminRegistry.getPools(s_sourceTokens); - assertEq(got.length, s_sourceTokens.length); - for (uint256 i = 0; i < s_sourceTokens.length; i++) { - assertEq(got[i], s_sourcePoolByToken[s_sourceTokens[i]]); - } - - address doesNotExist = makeAddr("doesNotExist"); - tokens[0] = doesNotExist; - got = s_tokenAdminRegistry.getPools(tokens); - assertEq(got.length, 1); - assertEq(got[0], address(0)); - } -} - -contract TokenAdminRegistry_getPool is TokenAdminRegistrySetup { - function test_getPool_Success() public view { - address got = s_tokenAdminRegistry.getPool(s_sourceTokens[0]); - assertEq(got, s_sourcePoolByToken[s_sourceTokens[0]]); - } -} - -contract TokenAdminRegistry_setPool is TokenAdminRegistrySetup { - function test_setPool_Success() public { - address pool = makeAddr("pool"); - vm.mockCall(pool, abi.encodeWithSelector(IPoolV1.isSupportedToken.selector), abi.encode(true)); - - vm.expectEmit(); - emit TokenAdminRegistry.PoolSet(s_sourceTokens[0], s_sourcePoolByToken[s_sourceTokens[0]], pool); - - s_tokenAdminRegistry.setPool(s_sourceTokens[0], pool); - - assertEq(s_tokenAdminRegistry.getPool(s_sourceTokens[0]), pool); - - // Assert the event is not emitted if the pool is the same as the current pool. - vm.recordLogs(); - s_tokenAdminRegistry.setPool(s_sourceTokens[0], pool); - - vm.assertEq(vm.getRecordedLogs().length, 0); - } - - function test_setPool_ZeroAddressRemovesPool_Success() public { - address pool = makeAddr("pool"); - vm.mockCall(pool, abi.encodeWithSelector(IPoolV1.isSupportedToken.selector), abi.encode(true)); - s_tokenAdminRegistry.setPool(s_sourceTokens[0], pool); - - assertEq(s_tokenAdminRegistry.getPool(s_sourceTokens[0]), pool); - - vm.expectEmit(); - emit TokenAdminRegistry.PoolSet(s_sourceTokens[0], pool, address(0)); - - s_tokenAdminRegistry.setPool(s_sourceTokens[0], address(0)); - - assertEq(s_tokenAdminRegistry.getPool(s_sourceTokens[0]), address(0)); - } - - function test_setPool_InvalidTokenPoolToken_Revert() public { - address pool = makeAddr("pool"); - vm.mockCall(pool, abi.encodeWithSelector(IPoolV1.isSupportedToken.selector), abi.encode(false)); - - vm.expectRevert(abi.encodeWithSelector(TokenAdminRegistry.InvalidTokenPoolToken.selector, s_sourceTokens[0])); - s_tokenAdminRegistry.setPool(s_sourceTokens[0], pool); - } - - function test_setPool_OnlyAdministrator_Revert() public { - vm.stopPrank(); - - vm.expectRevert( - abi.encodeWithSelector(TokenAdminRegistry.OnlyAdministrator.selector, address(this), s_sourceTokens[0]) - ); - s_tokenAdminRegistry.setPool(s_sourceTokens[0], makeAddr("pool")); - } -} - -contract TokenAdminRegistry_getAllConfiguredTokens is TokenAdminRegistrySetup { - function test_Fuzz_getAllConfiguredTokens_Success( - uint8 numberOfTokens - ) public { - TokenAdminRegistry cleanTokenAdminRegistry = new TokenAdminRegistry(); - for (uint160 i = 0; i < numberOfTokens; ++i) { - cleanTokenAdminRegistry.proposeAdministrator(address(i), address(i + 1000)); - } - - uint160 count = 0; - for (uint160 start = 0; start < numberOfTokens; start += count++) { - address[] memory got = cleanTokenAdminRegistry.getAllConfiguredTokens(uint64(start), uint64(count)); - if (start + count > numberOfTokens) { - assertEq(got.length, numberOfTokens - start); - } else { - assertEq(got.length, count); - } - - for (uint160 j = 0; j < got.length; ++j) { - assertEq(got[j], address(j + start)); - } - } - } - - function test_getAllConfiguredTokens_outOfBounds_Success() public view { - address[] memory tokens = s_tokenAdminRegistry.getAllConfiguredTokens(type(uint64).max, 10); - assertEq(tokens.length, 0); - } -} - -contract TokenAdminRegistry_transferAdminRole is TokenAdminRegistrySetup { - function test_transferAdminRole_Success() public { - address token = s_sourceTokens[0]; - - address currentAdmin = s_tokenAdminRegistry.getTokenConfig(token).administrator; - address newAdmin = makeAddr("newAdmin"); - - vm.expectEmit(); - emit TokenAdminRegistry.AdministratorTransferRequested(token, currentAdmin, newAdmin); - - s_tokenAdminRegistry.transferAdminRole(token, newAdmin); - - TokenAdminRegistry.TokenConfig memory config = s_tokenAdminRegistry.getTokenConfig(token); - - // Assert only the pending admin updates, without affecting the pending admin. - assertEq(config.pendingAdministrator, newAdmin); - assertEq(config.administrator, currentAdmin); - } - - function test_transferAdminRole_OnlyAdministrator_Revert() public { - vm.stopPrank(); - - vm.expectRevert( - abi.encodeWithSelector(TokenAdminRegistry.OnlyAdministrator.selector, address(this), s_sourceTokens[0]) - ); - s_tokenAdminRegistry.transferAdminRole(s_sourceTokens[0], makeAddr("newAdmin")); - } -} - -contract TokenAdminRegistry_acceptAdminRole is TokenAdminRegistrySetup { - function test_acceptAdminRole_Success() public { - address token = s_sourceTokens[0]; - - address currentAdmin = s_tokenAdminRegistry.getTokenConfig(token).administrator; - address newAdmin = makeAddr("newAdmin"); - - vm.expectEmit(); - emit TokenAdminRegistry.AdministratorTransferRequested(token, currentAdmin, newAdmin); - - s_tokenAdminRegistry.transferAdminRole(token, newAdmin); - - TokenAdminRegistry.TokenConfig memory config = s_tokenAdminRegistry.getTokenConfig(token); - - // Assert only the pending admin updates, without affecting the pending admin. - assertEq(config.pendingAdministrator, newAdmin); - assertEq(config.administrator, currentAdmin); - - vm.startPrank(newAdmin); - - vm.expectEmit(); - emit TokenAdminRegistry.AdministratorTransferred(token, newAdmin); - - s_tokenAdminRegistry.acceptAdminRole(token); - - config = s_tokenAdminRegistry.getTokenConfig(token); - - // Assert only the pending admin updates, without affecting the pending admin. - assertEq(config.pendingAdministrator, address(0)); - assertEq(config.administrator, newAdmin); - } - - function test_acceptAdminRole_OnlyPendingAdministrator_Revert() public { - address token = s_sourceTokens[0]; - address currentAdmin = s_tokenAdminRegistry.getTokenConfig(token).administrator; - address newAdmin = makeAddr("newAdmin"); - - s_tokenAdminRegistry.transferAdminRole(token, newAdmin); - - TokenAdminRegistry.TokenConfig memory config = s_tokenAdminRegistry.getTokenConfig(token); - - // Assert only the pending admin updates, without affecting the pending admin. - assertEq(config.pendingAdministrator, newAdmin); - assertEq(config.administrator, currentAdmin); - - address notNewAdmin = makeAddr("notNewAdmin"); - vm.startPrank(notNewAdmin); - - vm.expectRevert(abi.encodeWithSelector(TokenAdminRegistry.OnlyPendingAdministrator.selector, notNewAdmin, token)); - s_tokenAdminRegistry.acceptAdminRole(token); - } -} - -contract TokenAdminRegistry_isAdministrator is TokenAdminRegistrySetup { - function test_isAdministrator_Success() public { - address newAdmin = makeAddr("newAdmin"); - address newToken = makeAddr("newToken"); - assertFalse(s_tokenAdminRegistry.isAdministrator(newToken, newAdmin)); - assertFalse(s_tokenAdminRegistry.isAdministrator(newToken, OWNER)); - - s_tokenAdminRegistry.proposeAdministrator(newToken, newAdmin); - changePrank(newAdmin); - s_tokenAdminRegistry.acceptAdminRole(newToken); - - assertTrue(s_tokenAdminRegistry.isAdministrator(newToken, newAdmin)); - assertFalse(s_tokenAdminRegistry.isAdministrator(newToken, OWNER)); - } -} - -contract TokenAdminRegistry_proposeAdministrator is TokenAdminRegistrySetup { - function test_proposeAdministrator_module_Success() public { - vm.startPrank(s_registryModule); - address newAdmin = makeAddr("newAdmin"); - address newToken = makeAddr("newToken"); - - vm.expectEmit(); - emit TokenAdminRegistry.AdministratorTransferRequested(newToken, address(0), newAdmin); - - s_tokenAdminRegistry.proposeAdministrator(newToken, newAdmin); - - assertEq(s_tokenAdminRegistry.getTokenConfig(newToken).pendingAdministrator, newAdmin); - assertEq(s_tokenAdminRegistry.getTokenConfig(newToken).administrator, address(0)); - assertEq(s_tokenAdminRegistry.getTokenConfig(newToken).tokenPool, address(0)); - - changePrank(newAdmin); - s_tokenAdminRegistry.acceptAdminRole(newToken); - - assertTrue(s_tokenAdminRegistry.isAdministrator(newToken, newAdmin)); - } - - function test_proposeAdministrator_owner_Success() public { - address newAdmin = makeAddr("newAdmin"); - address newToken = makeAddr("newToken"); - - vm.expectEmit(); - emit TokenAdminRegistry.AdministratorTransferRequested(newToken, address(0), newAdmin); - - s_tokenAdminRegistry.proposeAdministrator(newToken, newAdmin); - - assertEq(s_tokenAdminRegistry.getTokenConfig(newToken).pendingAdministrator, newAdmin); - - changePrank(newAdmin); - s_tokenAdminRegistry.acceptAdminRole(newToken); - - assertTrue(s_tokenAdminRegistry.isAdministrator(newToken, newAdmin)); - } - - function test_proposeAdministrator_reRegisterWhileUnclaimed_Success() public { - address newAdmin = makeAddr("wrongAddress"); - address newToken = makeAddr("newToken"); - - vm.expectEmit(); - emit TokenAdminRegistry.AdministratorTransferRequested(newToken, address(0), newAdmin); - - s_tokenAdminRegistry.proposeAdministrator(newToken, newAdmin); - - assertEq(s_tokenAdminRegistry.getTokenConfig(newToken).pendingAdministrator, newAdmin); - - newAdmin = makeAddr("correctAddress"); - - vm.expectEmit(); - emit TokenAdminRegistry.AdministratorTransferRequested(newToken, address(0), newAdmin); - - // Ensure we can still register the correct admin while the previous admin is unclaimed. - s_tokenAdminRegistry.proposeAdministrator(newToken, newAdmin); - - changePrank(newAdmin); - s_tokenAdminRegistry.acceptAdminRole(newToken); - - assertTrue(s_tokenAdminRegistry.isAdministrator(newToken, newAdmin)); - } - - mapping(address token => address admin) internal s_AdminByToken; - - function test_Fuzz_proposeAdministrator_Success(address[50] memory tokens, address[50] memory admins) public { - TokenAdminRegistry cleanTokenAdminRegistry = new TokenAdminRegistry(); - for (uint256 i = 0; i < tokens.length; i++) { - if (admins[i] == address(0)) { - continue; - } - if (cleanTokenAdminRegistry.getTokenConfig(tokens[i]).administrator != address(0)) { - continue; - } - cleanTokenAdminRegistry.proposeAdministrator(tokens[i], admins[i]); - s_AdminByToken[tokens[i]] = admins[i]; - } - - for (uint256 i = 0; i < tokens.length; i++) { - assertEq(cleanTokenAdminRegistry.getTokenConfig(tokens[i]).pendingAdministrator, s_AdminByToken[tokens[i]]); - } - } - - function test_proposeAdministrator_OnlyRegistryModule_Revert() public { - address newToken = makeAddr("newToken"); - vm.stopPrank(); - - vm.expectRevert(abi.encodeWithSelector(TokenAdminRegistry.OnlyRegistryModuleOrOwner.selector, address(this))); - s_tokenAdminRegistry.proposeAdministrator(newToken, OWNER); - } - - function test_proposeAdministrator_ZeroAddress_Revert() public { - address newToken = makeAddr("newToken"); - - vm.expectRevert(abi.encodeWithSelector(TokenAdminRegistry.ZeroAddress.selector)); - s_tokenAdminRegistry.proposeAdministrator(newToken, address(0)); - } - - function test_proposeAdministrator_AlreadyRegistered_Revert() public { - address newAdmin = makeAddr("newAdmin"); - address newToken = makeAddr("newToken"); - - s_tokenAdminRegistry.proposeAdministrator(newToken, newAdmin); - changePrank(newAdmin); - s_tokenAdminRegistry.acceptAdminRole(newToken); - - changePrank(OWNER); - - vm.expectRevert(abi.encodeWithSelector(TokenAdminRegistry.AlreadyRegistered.selector, newToken)); - s_tokenAdminRegistry.proposeAdministrator(newToken, newAdmin); - } -} - -contract TokenAdminRegistry_addRegistryModule is TokenAdminRegistrySetup { - function test_addRegistryModule_Success() public { - address newModule = makeAddr("newModule"); - - s_tokenAdminRegistry.addRegistryModule(newModule); - - assertTrue(s_tokenAdminRegistry.isRegistryModule(newModule)); - - // Assert the event is not emitted if the module is already added. - vm.recordLogs(); - s_tokenAdminRegistry.addRegistryModule(newModule); - - vm.assertEq(vm.getRecordedLogs().length, 0); - } - - function test_addRegistryModule_OnlyOwner_Revert() public { - address newModule = makeAddr("newModule"); - vm.stopPrank(); - - vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); - s_tokenAdminRegistry.addRegistryModule(newModule); - } -} - -contract TokenAdminRegistry_removeRegistryModule is TokenAdminRegistrySetup { - function test_removeRegistryModule_Success() public { - address newModule = makeAddr("newModule"); - - s_tokenAdminRegistry.addRegistryModule(newModule); - - assertTrue(s_tokenAdminRegistry.isRegistryModule(newModule)); - - vm.expectEmit(); - emit TokenAdminRegistry.RegistryModuleRemoved(newModule); - - s_tokenAdminRegistry.removeRegistryModule(newModule); - - assertFalse(s_tokenAdminRegistry.isRegistryModule(newModule)); - - // Assert the event is not emitted if the module is already removed. - vm.recordLogs(); - s_tokenAdminRegistry.removeRegistryModule(newModule); - - vm.assertEq(vm.getRecordedLogs().length, 0); - } - - function test_removeRegistryModule_OnlyOwner_Revert() public { - address newModule = makeAddr("newModule"); - vm.stopPrank(); - - vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); - s_tokenAdminRegistry.removeRegistryModule(newModule); - } -} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistry.acceptAdminRole.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistry.acceptAdminRole.t.sol new file mode 100644 index 00000000000..069159b8938 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistry.acceptAdminRole.t.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {TokenAdminRegistry} from "../../../tokenAdminRegistry/TokenAdminRegistry.sol"; +import {TokenAdminRegistrySetup} from "./TokenAdminRegistrySetup.t.sol"; + +contract TokenAdminRegistry_acceptAdminRole is TokenAdminRegistrySetup { + function test_acceptAdminRole_Success() public { + address token = s_sourceTokens[0]; + + address currentAdmin = s_tokenAdminRegistry.getTokenConfig(token).administrator; + address newAdmin = makeAddr("newAdmin"); + + vm.expectEmit(); + emit TokenAdminRegistry.AdministratorTransferRequested(token, currentAdmin, newAdmin); + + s_tokenAdminRegistry.transferAdminRole(token, newAdmin); + + TokenAdminRegistry.TokenConfig memory config = s_tokenAdminRegistry.getTokenConfig(token); + + // Assert only the pending admin updates, without affecting the pending admin. + assertEq(config.pendingAdministrator, newAdmin); + assertEq(config.administrator, currentAdmin); + + vm.startPrank(newAdmin); + + vm.expectEmit(); + emit TokenAdminRegistry.AdministratorTransferred(token, newAdmin); + + s_tokenAdminRegistry.acceptAdminRole(token); + + config = s_tokenAdminRegistry.getTokenConfig(token); + + // Assert only the pending admin updates, without affecting the pending admin. + assertEq(config.pendingAdministrator, address(0)); + assertEq(config.administrator, newAdmin); + } + + function test_acceptAdminRole_OnlyPendingAdministrator_Revert() public { + address token = s_sourceTokens[0]; + address currentAdmin = s_tokenAdminRegistry.getTokenConfig(token).administrator; + address newAdmin = makeAddr("newAdmin"); + + s_tokenAdminRegistry.transferAdminRole(token, newAdmin); + + TokenAdminRegistry.TokenConfig memory config = s_tokenAdminRegistry.getTokenConfig(token); + + // Assert only the pending admin updates, without affecting the pending admin. + assertEq(config.pendingAdministrator, newAdmin); + assertEq(config.administrator, currentAdmin); + + address notNewAdmin = makeAddr("notNewAdmin"); + vm.startPrank(notNewAdmin); + + vm.expectRevert(abi.encodeWithSelector(TokenAdminRegistry.OnlyPendingAdministrator.selector, notNewAdmin, token)); + s_tokenAdminRegistry.acceptAdminRole(token); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistry.addRegistryModule.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistry.addRegistryModule.t.sol new file mode 100644 index 00000000000..9874ceb72bc --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistry.addRegistryModule.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {Ownable2Step} from "../../../../shared/access/Ownable2Step.sol"; +import {TokenAdminRegistrySetup} from "./TokenAdminRegistrySetup.t.sol"; + +contract TokenAdminRegistry_addRegistryModule is TokenAdminRegistrySetup { + function test_addRegistryModule_Success() public { + address newModule = makeAddr("newModule"); + + s_tokenAdminRegistry.addRegistryModule(newModule); + + assertTrue(s_tokenAdminRegistry.isRegistryModule(newModule)); + + // Assert the event is not emitted if the module is already added. + vm.recordLogs(); + s_tokenAdminRegistry.addRegistryModule(newModule); + + vm.assertEq(vm.getRecordedLogs().length, 0); + } + + function test_addRegistryModule_OnlyOwner_Revert() public { + address newModule = makeAddr("newModule"); + vm.stopPrank(); + + vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); + s_tokenAdminRegistry.addRegistryModule(newModule); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistry.getAllConfiguredTokens.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistry.getAllConfiguredTokens.t.sol new file mode 100644 index 00000000000..ab34fd3ab0a --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistry.getAllConfiguredTokens.t.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {TokenAdminRegistry} from "../../../tokenAdminRegistry/TokenAdminRegistry.sol"; +import {TokenAdminRegistrySetup} from "./TokenAdminRegistrySetup.t.sol"; + +contract TokenAdminRegistry_getAllConfiguredTokens is TokenAdminRegistrySetup { + function test_Fuzz_getAllConfiguredTokens_Success( + uint8 numberOfTokens + ) public { + TokenAdminRegistry cleanTokenAdminRegistry = new TokenAdminRegistry(); + for (uint160 i = 0; i < numberOfTokens; ++i) { + cleanTokenAdminRegistry.proposeAdministrator(address(i), address(i + 1000)); + } + + uint160 count = 0; + for (uint160 start = 0; start < numberOfTokens; start += count++) { + address[] memory got = cleanTokenAdminRegistry.getAllConfiguredTokens(uint64(start), uint64(count)); + if (start + count > numberOfTokens) { + assertEq(got.length, numberOfTokens - start); + } else { + assertEq(got.length, count); + } + + for (uint160 j = 0; j < got.length; ++j) { + assertEq(got[j], address(j + start)); + } + } + } + + function test_getAllConfiguredTokens_outOfBounds_Success() public view { + address[] memory tokens = s_tokenAdminRegistry.getAllConfiguredTokens(type(uint64).max, 10); + assertEq(tokens.length, 0); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistry.getPool.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistry.getPool.t.sol new file mode 100644 index 00000000000..297e3c3143a --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistry.getPool.t.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {TokenAdminRegistrySetup} from "./TokenAdminRegistrySetup.t.sol"; + +contract TokenAdminRegistry_getPool is TokenAdminRegistrySetup { + function test_getPool_Success() public view { + address got = s_tokenAdminRegistry.getPool(s_sourceTokens[0]); + assertEq(got, s_sourcePoolByToken[s_sourceTokens[0]]); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistry.getPools.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistry.getPools.t.sol new file mode 100644 index 00000000000..7c673ee5be6 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistry.getPools.t.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {TokenAdminRegistrySetup} from "./TokenAdminRegistrySetup.t.sol"; + +contract TokenAdminRegistry_getPools is TokenAdminRegistrySetup { + function test_getPools_Success() public { + address[] memory tokens = new address[](1); + tokens[0] = s_sourceTokens[0]; + + address[] memory got = s_tokenAdminRegistry.getPools(tokens); + assertEq(got.length, 1); + assertEq(got[0], s_sourcePoolByToken[tokens[0]]); + + got = s_tokenAdminRegistry.getPools(s_sourceTokens); + assertEq(got.length, s_sourceTokens.length); + for (uint256 i = 0; i < s_sourceTokens.length; i++) { + assertEq(got[i], s_sourcePoolByToken[s_sourceTokens[i]]); + } + + address doesNotExist = makeAddr("doesNotExist"); + tokens[0] = doesNotExist; + got = s_tokenAdminRegistry.getPools(tokens); + assertEq(got.length, 1); + assertEq(got[0], address(0)); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistry.isAdministrator.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistry.isAdministrator.t.sol new file mode 100644 index 00000000000..00555ba3ff2 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistry.isAdministrator.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {TokenAdminRegistrySetup} from "./TokenAdminRegistrySetup.t.sol"; + +contract TokenAdminRegistry_isAdministrator is TokenAdminRegistrySetup { + function test_isAdministrator_Success() public { + address newAdmin = makeAddr("newAdmin"); + address newToken = makeAddr("newToken"); + assertFalse(s_tokenAdminRegistry.isAdministrator(newToken, newAdmin)); + assertFalse(s_tokenAdminRegistry.isAdministrator(newToken, OWNER)); + + s_tokenAdminRegistry.proposeAdministrator(newToken, newAdmin); + changePrank(newAdmin); + s_tokenAdminRegistry.acceptAdminRole(newToken); + + assertTrue(s_tokenAdminRegistry.isAdministrator(newToken, newAdmin)); + assertFalse(s_tokenAdminRegistry.isAdministrator(newToken, OWNER)); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistry.proposeAdministrator.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistry.proposeAdministrator.t.sol new file mode 100644 index 00000000000..c1be3a27b13 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistry.proposeAdministrator.t.sol @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {TokenAdminRegistry} from "../../../tokenAdminRegistry/TokenAdminRegistry.sol"; +import {TokenAdminRegistrySetup} from "./TokenAdminRegistrySetup.t.sol"; + +contract TokenAdminRegistry_proposeAdministrator is TokenAdminRegistrySetup { + function test_proposeAdministrator_module_Success() public { + vm.startPrank(s_registryModule); + address newAdmin = makeAddr("newAdmin"); + address newToken = makeAddr("newToken"); + + vm.expectEmit(); + emit TokenAdminRegistry.AdministratorTransferRequested(newToken, address(0), newAdmin); + + s_tokenAdminRegistry.proposeAdministrator(newToken, newAdmin); + + assertEq(s_tokenAdminRegistry.getTokenConfig(newToken).pendingAdministrator, newAdmin); + assertEq(s_tokenAdminRegistry.getTokenConfig(newToken).administrator, address(0)); + assertEq(s_tokenAdminRegistry.getTokenConfig(newToken).tokenPool, address(0)); + + changePrank(newAdmin); + s_tokenAdminRegistry.acceptAdminRole(newToken); + + assertTrue(s_tokenAdminRegistry.isAdministrator(newToken, newAdmin)); + } + + function test_proposeAdministrator_owner_Success() public { + address newAdmin = makeAddr("newAdmin"); + address newToken = makeAddr("newToken"); + + vm.expectEmit(); + emit TokenAdminRegistry.AdministratorTransferRequested(newToken, address(0), newAdmin); + + s_tokenAdminRegistry.proposeAdministrator(newToken, newAdmin); + + assertEq(s_tokenAdminRegistry.getTokenConfig(newToken).pendingAdministrator, newAdmin); + + changePrank(newAdmin); + s_tokenAdminRegistry.acceptAdminRole(newToken); + + assertTrue(s_tokenAdminRegistry.isAdministrator(newToken, newAdmin)); + } + + function test_proposeAdministrator_reRegisterWhileUnclaimed_Success() public { + address newAdmin = makeAddr("wrongAddress"); + address newToken = makeAddr("newToken"); + + vm.expectEmit(); + emit TokenAdminRegistry.AdministratorTransferRequested(newToken, address(0), newAdmin); + + s_tokenAdminRegistry.proposeAdministrator(newToken, newAdmin); + + assertEq(s_tokenAdminRegistry.getTokenConfig(newToken).pendingAdministrator, newAdmin); + + newAdmin = makeAddr("correctAddress"); + + vm.expectEmit(); + emit TokenAdminRegistry.AdministratorTransferRequested(newToken, address(0), newAdmin); + + // Ensure we can still register the correct admin while the previous admin is unclaimed. + s_tokenAdminRegistry.proposeAdministrator(newToken, newAdmin); + + changePrank(newAdmin); + s_tokenAdminRegistry.acceptAdminRole(newToken); + + assertTrue(s_tokenAdminRegistry.isAdministrator(newToken, newAdmin)); + } + + mapping(address token => address admin) internal s_AdminByToken; + + function test_Fuzz_proposeAdministrator_Success(address[50] memory tokens, address[50] memory admins) public { + TokenAdminRegistry cleanTokenAdminRegistry = new TokenAdminRegistry(); + for (uint256 i = 0; i < tokens.length; i++) { + if (admins[i] == address(0)) { + continue; + } + if (cleanTokenAdminRegistry.getTokenConfig(tokens[i]).administrator != address(0)) { + continue; + } + cleanTokenAdminRegistry.proposeAdministrator(tokens[i], admins[i]); + s_AdminByToken[tokens[i]] = admins[i]; + } + + for (uint256 i = 0; i < tokens.length; i++) { + assertEq(cleanTokenAdminRegistry.getTokenConfig(tokens[i]).pendingAdministrator, s_AdminByToken[tokens[i]]); + } + } + + function test_proposeAdministrator_OnlyRegistryModule_Revert() public { + address newToken = makeAddr("newToken"); + vm.stopPrank(); + + vm.expectRevert(abi.encodeWithSelector(TokenAdminRegistry.OnlyRegistryModuleOrOwner.selector, address(this))); + s_tokenAdminRegistry.proposeAdministrator(newToken, OWNER); + } + + function test_proposeAdministrator_ZeroAddress_Revert() public { + address newToken = makeAddr("newToken"); + + vm.expectRevert(abi.encodeWithSelector(TokenAdminRegistry.ZeroAddress.selector)); + s_tokenAdminRegistry.proposeAdministrator(newToken, address(0)); + } + + function test_proposeAdministrator_AlreadyRegistered_Revert() public { + address newAdmin = makeAddr("newAdmin"); + address newToken = makeAddr("newToken"); + + s_tokenAdminRegistry.proposeAdministrator(newToken, newAdmin); + changePrank(newAdmin); + s_tokenAdminRegistry.acceptAdminRole(newToken); + + changePrank(OWNER); + + vm.expectRevert(abi.encodeWithSelector(TokenAdminRegistry.AlreadyRegistered.selector, newToken)); + s_tokenAdminRegistry.proposeAdministrator(newToken, newAdmin); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistry.removeRegistryModule.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistry.removeRegistryModule.t.sol new file mode 100644 index 00000000000..d5fde7ad5d5 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistry.removeRegistryModule.t.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {Ownable2Step} from "../../../../shared/access/Ownable2Step.sol"; +import {TokenAdminRegistry} from "../../../tokenAdminRegistry/TokenAdminRegistry.sol"; +import {TokenAdminRegistrySetup} from "./TokenAdminRegistrySetup.t.sol"; + +contract TokenAdminRegistry_removeRegistryModule is TokenAdminRegistrySetup { + function test_removeRegistryModule_Success() public { + address newModule = makeAddr("newModule"); + + s_tokenAdminRegistry.addRegistryModule(newModule); + + assertTrue(s_tokenAdminRegistry.isRegistryModule(newModule)); + + vm.expectEmit(); + emit TokenAdminRegistry.RegistryModuleRemoved(newModule); + + s_tokenAdminRegistry.removeRegistryModule(newModule); + + assertFalse(s_tokenAdminRegistry.isRegistryModule(newModule)); + + // Assert the event is not emitted if the module is already removed. + vm.recordLogs(); + s_tokenAdminRegistry.removeRegistryModule(newModule); + + vm.assertEq(vm.getRecordedLogs().length, 0); + } + + function test_removeRegistryModule_OnlyOwner_Revert() public { + address newModule = makeAddr("newModule"); + vm.stopPrank(); + + vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); + s_tokenAdminRegistry.removeRegistryModule(newModule); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistry.setPool.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistry.setPool.t.sol new file mode 100644 index 00000000000..51119ce30bb --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistry.setPool.t.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {IPoolV1} from "../../../interfaces/IPool.sol"; +import {TokenAdminRegistry} from "../../../tokenAdminRegistry/TokenAdminRegistry.sol"; +import {TokenAdminRegistrySetup} from "./TokenAdminRegistrySetup.t.sol"; + +contract TokenAdminRegistry_setPool is TokenAdminRegistrySetup { + function test_setPool_Success() public { + address pool = makeAddr("pool"); + vm.mockCall(pool, abi.encodeWithSelector(IPoolV1.isSupportedToken.selector), abi.encode(true)); + + vm.expectEmit(); + emit TokenAdminRegistry.PoolSet(s_sourceTokens[0], s_sourcePoolByToken[s_sourceTokens[0]], pool); + + s_tokenAdminRegistry.setPool(s_sourceTokens[0], pool); + + assertEq(s_tokenAdminRegistry.getPool(s_sourceTokens[0]), pool); + + // Assert the event is not emitted if the pool is the same as the current pool. + vm.recordLogs(); + s_tokenAdminRegistry.setPool(s_sourceTokens[0], pool); + + vm.assertEq(vm.getRecordedLogs().length, 0); + } + + function test_setPool_ZeroAddressRemovesPool_Success() public { + address pool = makeAddr("pool"); + vm.mockCall(pool, abi.encodeWithSelector(IPoolV1.isSupportedToken.selector), abi.encode(true)); + s_tokenAdminRegistry.setPool(s_sourceTokens[0], pool); + + assertEq(s_tokenAdminRegistry.getPool(s_sourceTokens[0]), pool); + + vm.expectEmit(); + emit TokenAdminRegistry.PoolSet(s_sourceTokens[0], pool, address(0)); + + s_tokenAdminRegistry.setPool(s_sourceTokens[0], address(0)); + + assertEq(s_tokenAdminRegistry.getPool(s_sourceTokens[0]), address(0)); + } + + function test_setPool_InvalidTokenPoolToken_Revert() public { + address pool = makeAddr("pool"); + vm.mockCall(pool, abi.encodeWithSelector(IPoolV1.isSupportedToken.selector), abi.encode(false)); + + vm.expectRevert(abi.encodeWithSelector(TokenAdminRegistry.InvalidTokenPoolToken.selector, s_sourceTokens[0])); + s_tokenAdminRegistry.setPool(s_sourceTokens[0], pool); + } + + function test_setPool_OnlyAdministrator_Revert() public { + vm.stopPrank(); + + vm.expectRevert( + abi.encodeWithSelector(TokenAdminRegistry.OnlyAdministrator.selector, address(this), s_sourceTokens[0]) + ); + s_tokenAdminRegistry.setPool(s_sourceTokens[0], makeAddr("pool")); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistry.transferAdminRole.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistry.transferAdminRole.t.sol new file mode 100644 index 00000000000..07a10b083af --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistry.transferAdminRole.t.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {TokenAdminRegistry} from "../../../tokenAdminRegistry/TokenAdminRegistry.sol"; +import {TokenAdminRegistrySetup} from "./TokenAdminRegistrySetup.t.sol"; + +contract TokenAdminRegistry_transferAdminRole is TokenAdminRegistrySetup { + function test_transferAdminRole_Success() public { + address token = s_sourceTokens[0]; + + address currentAdmin = s_tokenAdminRegistry.getTokenConfig(token).administrator; + address newAdmin = makeAddr("newAdmin"); + + vm.expectEmit(); + emit TokenAdminRegistry.AdministratorTransferRequested(token, currentAdmin, newAdmin); + + s_tokenAdminRegistry.transferAdminRole(token, newAdmin); + + TokenAdminRegistry.TokenConfig memory config = s_tokenAdminRegistry.getTokenConfig(token); + + // Assert only the pending admin updates, without affecting the pending admin. + assertEq(config.pendingAdministrator, newAdmin); + assertEq(config.administrator, currentAdmin); + } + + function test_transferAdminRole_OnlyAdministrator_Revert() public { + vm.stopPrank(); + + vm.expectRevert( + abi.encodeWithSelector(TokenAdminRegistry.OnlyAdministrator.selector, address(this), s_sourceTokens[0]) + ); + s_tokenAdminRegistry.transferAdminRole(s_sourceTokens[0], makeAddr("newAdmin")); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistrySetup.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistrySetup.t.sol new file mode 100644 index 00000000000..b3ca4a50535 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenAdminRegistry/TokenAdminRegistrySetup.t.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {TokenSetup} from "../../TokenSetup.t.sol"; + +contract TokenAdminRegistrySetup is TokenSetup { + address internal s_registryModule = makeAddr("registryModule"); + + function setUp() public virtual override { + TokenSetup.setUp(); + + s_tokenAdminRegistry.addRegistryModule(s_registryModule); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenPoolFactory/TokenPoolFactory.constructor.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenPoolFactory/TokenPoolFactory.constructor.t.sol new file mode 100644 index 00000000000..fbba0ccbb06 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenPoolFactory/TokenPoolFactory.constructor.t.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {ITokenAdminRegistry} from "../../../interfaces/ITokenAdminRegistry.sol"; + +import {RegistryModuleOwnerCustom} from "../../../tokenAdminRegistry/RegistryModuleOwnerCustom.sol"; +import {TokenPoolFactory} from "../../../tokenAdminRegistry/TokenPoolFactory/TokenPoolFactory.sol"; + +import {Create2} from "../../../../vendor/openzeppelin-solidity/v5.0.2/contracts/utils/Create2.sol"; +import {TokenPoolFactorySetup} from "./TokenPoolFactorySetup.t.sol"; + +contract TokenPoolFactory_constructor is TokenPoolFactorySetup { + using Create2 for bytes32; + + function test_constructor_Revert() public { + // Revert cause the tokenAdminRegistry is address(0) + vm.expectRevert(TokenPoolFactory.InvalidZeroAddress.selector); + new TokenPoolFactory(ITokenAdminRegistry(address(0)), RegistryModuleOwnerCustom(address(0)), address(0), address(0)); + + new TokenPoolFactory( + ITokenAdminRegistry(address(0xdeadbeef)), + RegistryModuleOwnerCustom(address(0xdeadbeef)), + address(0xdeadbeef), + address(0xdeadbeef) + ); + } +} diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenPoolFactory.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenPoolFactory/TokenPoolFactory.createTokenPool.t.sol similarity index 86% rename from contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenPoolFactory.t.sol rename to contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenPoolFactory/TokenPoolFactory.createTokenPool.t.sol index 711f5fa7022..63106f20044 100644 --- a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenPoolFactory.t.sol +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenPoolFactory/TokenPoolFactory.createTokenPool.t.sol @@ -1,84 +1,25 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.24; -import {IBurnMintERC20} from "../../../shared/token/ERC20/IBurnMintERC20.sol"; -import {IOwner} from "../../interfaces/IOwner.sol"; -import {ITokenAdminRegistry} from "../../interfaces/ITokenAdminRegistry.sol"; - -import {Ownable2Step} from "../../../shared/access/Ownable2Step.sol"; - -import {RateLimiter} from "../../libraries/RateLimiter.sol"; - -import {BurnFromMintTokenPool} from "../../pools/BurnFromMintTokenPool.sol"; -import {BurnMintTokenPool} from "../../pools/BurnMintTokenPool.sol"; -import {LockReleaseTokenPool} from "../../pools/LockReleaseTokenPool.sol"; -import {TokenPool} from "../../pools/TokenPool.sol"; - -import {RegistryModuleOwnerCustom} from "../../tokenAdminRegistry/RegistryModuleOwnerCustom.sol"; -import {TokenAdminRegistry} from "../../tokenAdminRegistry/TokenAdminRegistry.sol"; -import {FactoryBurnMintERC20} from "../../tokenAdminRegistry/TokenPoolFactory/FactoryBurnMintERC20.sol"; -import {TokenPoolFactory} from "../../tokenAdminRegistry/TokenPoolFactory/TokenPoolFactory.sol"; -import {TokenAdminRegistrySetup} from "./TokenAdminRegistry.t.sol"; - -import {Create2} from "../../../vendor/openzeppelin-solidity/v5.0.2/contracts/utils/Create2.sol"; - -contract TokenPoolFactorySetup is TokenAdminRegistrySetup { +import {Ownable2Step} from "../../../../shared/access/Ownable2Step.sol"; +import {IBurnMintERC20} from "../../../../shared/token/ERC20/IBurnMintERC20.sol"; + +import {Create2} from "../../../../vendor/openzeppelin-solidity/v5.0.2/contracts/utils/Create2.sol"; +import {IOwner} from "../../../interfaces/IOwner.sol"; +import {RateLimiter} from "../../../libraries/RateLimiter.sol"; +import {BurnFromMintTokenPool} from "../../../pools/BurnFromMintTokenPool.sol"; +import {BurnMintTokenPool} from "../../../pools/BurnMintTokenPool.sol"; +import {LockReleaseTokenPool} from "../../../pools/LockReleaseTokenPool.sol"; +import {TokenPool} from "../../../pools/TokenPool.sol"; +import {RegistryModuleOwnerCustom} from "../../../tokenAdminRegistry/RegistryModuleOwnerCustom.sol"; +import {TokenAdminRegistry} from "../../../tokenAdminRegistry/TokenAdminRegistry.sol"; +import {FactoryBurnMintERC20} from "../../../tokenAdminRegistry/TokenPoolFactory/FactoryBurnMintERC20.sol"; +import {TokenPoolFactory} from "../../../tokenAdminRegistry/TokenPoolFactory/TokenPoolFactory.sol"; +import {TokenPoolFactorySetup} from "./TokenPoolFactorySetup.t.sol"; + +contract TokenPoolFactory_createTokenPool is TokenPoolFactorySetup { using Create2 for bytes32; - TokenPoolFactory internal s_tokenPoolFactory; - RegistryModuleOwnerCustom internal s_registryModuleOwnerCustom; - - bytes internal s_poolInitCode; - bytes internal s_poolInitArgs; - - bytes32 internal constant FAKE_SALT = keccak256(abi.encode("FAKE_SALT")); - - address internal s_rmnProxy = address(0x1234); - - bytes internal s_tokenCreationParams; - bytes internal s_tokenInitCode; - - uint256 public constant PREMINT_AMOUNT = 100 ether; - - function setUp() public virtual override { - TokenAdminRegistrySetup.setUp(); - - s_registryModuleOwnerCustom = new RegistryModuleOwnerCustom(address(s_tokenAdminRegistry)); - s_tokenAdminRegistry.addRegistryModule(address(s_registryModuleOwnerCustom)); - - s_tokenPoolFactory = - new TokenPoolFactory(s_tokenAdminRegistry, s_registryModuleOwnerCustom, s_rmnProxy, address(s_sourceRouter)); - - // Create Init Code for BurnMintERC20 TestToken with 18 decimals and supply cap of max uint256 value - s_tokenCreationParams = abi.encode("TestToken", "TT", 18, type(uint256).max, PREMINT_AMOUNT, OWNER); - - s_tokenInitCode = abi.encodePacked(type(FactoryBurnMintERC20).creationCode, s_tokenCreationParams); - - s_poolInitCode = type(BurnMintTokenPool).creationCode; - - // Create Init Args for BurnMintTokenPool with no allowlist minus the token address - address[] memory allowlist = new address[](1); - allowlist[0] = OWNER; - s_poolInitArgs = abi.encode(allowlist, address(0x1234), s_sourceRouter); - } -} - -contract TokenPoolFactoryTests is TokenPoolFactorySetup { - using Create2 for bytes32; - - function test_TokenPoolFactory_Constructor_Revert() public { - // Revert cause the tokenAdminRegistry is address(0) - vm.expectRevert(TokenPoolFactory.InvalidZeroAddress.selector); - new TokenPoolFactory(ITokenAdminRegistry(address(0)), RegistryModuleOwnerCustom(address(0)), address(0), address(0)); - - new TokenPoolFactory( - ITokenAdminRegistry(address(0xdeadbeef)), - RegistryModuleOwnerCustom(address(0xdeadbeef)), - address(0xdeadbeef), - address(0xdeadbeef) - ); - } - function test_createTokenPool_WithNoExistingTokenOnRemoteChain_Success() public { vm.startPrank(OWNER); diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenPoolFactory/TokenPoolFactorySetup.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenPoolFactory/TokenPoolFactorySetup.t.sol new file mode 100644 index 00000000000..9f78ceb9439 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenPoolFactory/TokenPoolFactorySetup.t.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Create2} from "../../../../vendor/openzeppelin-solidity/v5.0.2/contracts/utils/Create2.sol"; +import {BurnMintTokenPool} from "../../../pools/BurnMintTokenPool.sol"; +import {RegistryModuleOwnerCustom} from "../../../tokenAdminRegistry/RegistryModuleOwnerCustom.sol"; +import {FactoryBurnMintERC20} from "../../../tokenAdminRegistry/TokenPoolFactory/FactoryBurnMintERC20.sol"; +import {TokenPoolFactory} from "../../../tokenAdminRegistry/TokenPoolFactory/TokenPoolFactory.sol"; +import {TokenAdminRegistrySetup} from "../TokenAdminRegistry/TokenAdminRegistrySetup.t.sol"; + +contract TokenPoolFactorySetup is TokenAdminRegistrySetup { + using Create2 for bytes32; + + TokenPoolFactory internal s_tokenPoolFactory; + RegistryModuleOwnerCustom internal s_registryModuleOwnerCustom; + + bytes internal s_poolInitCode; + bytes internal s_poolInitArgs; + + bytes32 internal constant FAKE_SALT = keccak256(abi.encode("FAKE_SALT")); + + address internal s_rmnProxy = address(0x1234); + + bytes internal s_tokenCreationParams; + bytes internal s_tokenInitCode; + + uint256 public constant PREMINT_AMOUNT = 100 ether; + + function setUp() public virtual override { + TokenAdminRegistrySetup.setUp(); + + s_registryModuleOwnerCustom = new RegistryModuleOwnerCustom(address(s_tokenAdminRegistry)); + s_tokenAdminRegistry.addRegistryModule(address(s_registryModuleOwnerCustom)); + + s_tokenPoolFactory = + new TokenPoolFactory(s_tokenAdminRegistry, s_registryModuleOwnerCustom, s_rmnProxy, address(s_sourceRouter)); + + // Create Init Code for BurnMintERC20 TestToken with 18 decimals and supply cap of max uint256 value + s_tokenCreationParams = abi.encode("TestToken", "TT", 18, type(uint256).max, PREMINT_AMOUNT, OWNER); + + s_tokenInitCode = abi.encodePacked(type(FactoryBurnMintERC20).creationCode, s_tokenCreationParams); + + s_poolInitCode = type(BurnMintTokenPool).creationCode; + + // Create Init Args for BurnMintTokenPool with no allowlist minus the token address + address[] memory allowlist = new address[](1); + allowlist[0] = OWNER; + s_poolInitArgs = abi.encode(allowlist, address(0x1234), s_sourceRouter); + } +} diff --git a/core/capabilities/ccip/ccip_integration_tests/ccipreader/ccipreader_test.go b/core/capabilities/ccip/ccip_integration_tests/ccipreader/ccipreader_test.go index 39aa322e0a2..a992648a54f 100644 --- a/core/capabilities/ccip/ccip_integration_tests/ccipreader/ccipreader_test.go +++ b/core/capabilities/ccip/ccip_integration_tests/ccipreader/ccipreader_test.go @@ -439,7 +439,7 @@ func TestCCIPReader_Nonces(t *testing.T) { // Add some nonces. for chain, addrs := range nonces { for addr, nonce := range addrs { - _, err := s.contract.SetInboundNonce(s.auth, uint64(chain), nonce, addr.Bytes()) + _, err := s.contract.SetInboundNonce(s.auth, uint64(chain), nonce, common.LeftPadBytes(addr.Bytes(), 32)) assert.NoError(t, err) } } diff --git a/core/capabilities/ccip/configs/evm/chain_writer.go b/core/capabilities/ccip/configs/evm/chain_writer.go index f88bfce937b..ada135aecd8 100644 --- a/core/capabilities/ccip/configs/evm/chain_writer.go +++ b/core/capabilities/ccip/configs/evm/chain_writer.go @@ -8,7 +8,6 @@ import ( "github.com/smartcontractkit/chainlink-ccip/pkg/consts" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/offramp" evmrelaytypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" @@ -21,19 +20,12 @@ var ( // ChainWriterConfigRaw returns a ChainWriterConfig that can be used to transmit commit and execute reports. func ChainWriterConfigRaw( fromAddress common.Address, - maxGasPrice *assets.Wei, commitGasLimit, execBatchGasLimit uint64, ) (evmrelaytypes.ChainWriterConfig, error) { if fromAddress == common.HexToAddress("0x0") { return evmrelaytypes.ChainWriterConfig{}, fmt.Errorf("fromAddress cannot be zero") } - if maxGasPrice == nil { - return evmrelaytypes.ChainWriterConfig{}, fmt.Errorf("maxGasPrice cannot be nil") - } - if maxGasPrice.Cmp(assets.NewWeiI(0)) <= 0 { - return evmrelaytypes.ChainWriterConfig{}, fmt.Errorf("maxGasPrice must be greater than zero") - } if commitGasLimit == 0 { return evmrelaytypes.ChainWriterConfig{}, fmt.Errorf("commitGasLimit must be greater than zero") } @@ -59,7 +51,6 @@ func ChainWriterConfigRaw( }, }, }, - MaxGasPrice: maxGasPrice, }, nil } diff --git a/core/capabilities/ccip/configs/evm/chain_writer_test.go b/core/capabilities/ccip/configs/evm/chain_writer_test.go index e24863866cc..a8851ab6e48 100644 --- a/core/capabilities/ccip/configs/evm/chain_writer_test.go +++ b/core/capabilities/ccip/configs/evm/chain_writer_test.go @@ -8,14 +8,12 @@ import ( "github.com/smartcontractkit/chainlink-ccip/pkg/consts" "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/configs/evm" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" ) func TestChainWriterConfigRaw(t *testing.T) { tests := []struct { name string fromAddress common.Address - maxGasPrice *assets.Wei commitGasLimit uint64 execBatchGasLimit uint64 expectedError string @@ -23,7 +21,6 @@ func TestChainWriterConfigRaw(t *testing.T) { { name: "valid input", fromAddress: common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"), - maxGasPrice: assets.NewWeiI(1000000000), commitGasLimit: 21000, execBatchGasLimit: 42000, expectedError: "", @@ -31,39 +28,13 @@ func TestChainWriterConfigRaw(t *testing.T) { { name: "zero fromAddress", fromAddress: common.HexToAddress("0x0"), - maxGasPrice: assets.NewWeiI(1000000000), commitGasLimit: 21000, execBatchGasLimit: 42000, expectedError: "fromAddress cannot be zero", }, - { - name: "nil maxGasPrice", - fromAddress: common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"), - maxGasPrice: nil, - commitGasLimit: 21000, - execBatchGasLimit: 42000, - expectedError: "maxGasPrice cannot be nil", - }, - { - name: "zero maxGasPrice", - fromAddress: common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"), - maxGasPrice: assets.NewWeiI(0), - commitGasLimit: 21000, - execBatchGasLimit: 42000, - expectedError: "maxGasPrice must be greater than zero", - }, - { - name: "negative maxGasPrice", - fromAddress: common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"), - maxGasPrice: assets.NewWeiI(-1), - commitGasLimit: 21000, - execBatchGasLimit: 42000, - expectedError: "maxGasPrice must be greater than zero", - }, { name: "zero commitGasLimit", fromAddress: common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"), - maxGasPrice: assets.NewWeiI(1000000000), commitGasLimit: 0, execBatchGasLimit: 42000, expectedError: "commitGasLimit must be greater than zero", @@ -71,7 +42,6 @@ func TestChainWriterConfigRaw(t *testing.T) { { name: "zero execBatchGasLimit", fromAddress: common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"), - maxGasPrice: assets.NewWeiI(1000000000), commitGasLimit: 21000, execBatchGasLimit: 0, expectedError: "execBatchGasLimit must be greater than zero", @@ -80,7 +50,7 @@ func TestChainWriterConfigRaw(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - config, err := evm.ChainWriterConfigRaw(tt.fromAddress, tt.maxGasPrice, tt.commitGasLimit, tt.execBatchGasLimit) + config, err := evm.ChainWriterConfigRaw(tt.fromAddress, tt.commitGasLimit, tt.execBatchGasLimit) if tt.expectedError != "" { assert.EqualError(t, err, tt.expectedError) } else { @@ -94,9 +64,6 @@ func TestChainWriterConfigRaw(t *testing.T) { assert.Equal(t, tt.execBatchGasLimit, config.Contracts[consts.ContractNameOffRamp].Configs[consts.MethodExecute].GasLimit) - assert.Equal(t, - tt.maxGasPrice, - config.MaxGasPrice) } }) } diff --git a/core/capabilities/ccip/delegate.go b/core/capabilities/ccip/delegate.go index 7e5401ad5e6..09e3769627e 100644 --- a/core/capabilities/ccip/delegate.go +++ b/core/capabilities/ccip/delegate.go @@ -205,7 +205,6 @@ func (d *Delegate) ServicesForSpec(ctx context.Context, spec job.Job) (services bootstrapperLocators, hcr, cciptypes.ChainSelector(homeChainChainSelector), - d.evmConfigs, ) } else { oracleCreator = oraclecreator.NewBootstrapOracleCreator( diff --git a/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go b/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go index 7c8d61def80..86ad8bef809 100644 --- a/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go +++ b/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go @@ -415,7 +415,7 @@ func makeTestEvmTxm( keyStore keystore.Eth) (txmgr.TxManager, gas.EvmFeeEstimator) { config, dbConfig, evmConfig := MakeTestConfigs(t) - estimator, err := gas.NewEstimator(logger.TestLogger(t), ethClient, config.ChainType(), evmConfig.GasEstimator(), nil) + estimator, err := gas.NewEstimator(logger.TestLogger(t), ethClient, config.ChainType(), ethClient.ConfiguredChainID(), evmConfig.GasEstimator(), nil) require.NoError(t, err, "failed to create gas estimator") lggr := logger.TestLogger(t) diff --git a/core/capabilities/ccip/oraclecreator/plugin.go b/core/capabilities/ccip/oraclecreator/plugin.go index 5df0b1135d7..573d1dd0cac 100644 --- a/core/capabilities/ccip/oraclecreator/plugin.go +++ b/core/capabilities/ccip/oraclecreator/plugin.go @@ -73,7 +73,6 @@ type pluginOracleCreator struct { homeChainReader ccipreaderpkg.HomeChain homeChainSelector cciptypes.ChainSelector relayers map[types.RelayID]loop.Relayer - evmConfigs toml.EVMConfigs } func NewPluginOracleCreator( @@ -91,7 +90,6 @@ func NewPluginOracleCreator( bootstrapperLocators []commontypes.BootstrapperLocator, homeChainReader ccipreaderpkg.HomeChain, homeChainSelector cciptypes.ChainSelector, - evmConfigs toml.EVMConfigs, ) cctypes.OracleCreator { return &pluginOracleCreator{ ocrKeyBundles: ocrKeyBundles, @@ -108,7 +106,6 @@ func NewPluginOracleCreator( bootstrapperLocators: bootstrapperLocators, homeChainReader: homeChainReader, homeChainSelector: homeChainSelector, - evmConfigs: evmConfigs, } } @@ -367,7 +364,6 @@ func (i *pluginOracleCreator) createReadersAndWriters( cw, err1 := createChainWriter( ctx, chainID, - i.evmConfigs, relayer, i.transmitters, execBatchGasLimit) @@ -480,7 +476,6 @@ func isUSDCEnabled(ofc offChainConfig) bool { func createChainWriter( ctx context.Context, chainID *big.Int, - evmConfigs toml.EVMConfigs, relayer loop.Relayer, transmitters map[types.RelayID][]string, execBatchGasLimit uint64, @@ -492,14 +487,8 @@ func createChainWriter( fromAddress = common.HexToAddress(transmitter[0]) } - maxGasPrice := getKeySpecificMaxGasPrice(evmConfigs, chainID, fromAddress) - if maxGasPrice == nil { - return nil, fmt.Errorf("failed to find max gas price for chain %s", chainID.String()) - } - chainWriterRawConfig, err := evmconfig.ChainWriterConfigRaw( fromAddress, - maxGasPrice, defaultCommitGasLimit, execBatchGasLimit, ) diff --git a/core/capabilities/integration_tests/keystone/workflow.go b/core/capabilities/integration_tests/keystone/workflow.go index 1686fd2d6dd..25a26bf3f9f 100644 --- a/core/capabilities/integration_tests/keystone/workflow.go +++ b/core/capabilities/integration_tests/keystone/workflow.go @@ -26,7 +26,8 @@ consensus: observations: - "$(trigger.outputs)" config: - report_id: "0001" + report_id: "0001" + key_id: "evm" aggregation_method: "data_feeds" aggregation_config: feeds: diff --git a/core/capabilities/launcher.go b/core/capabilities/launcher.go index ef0dd19bd40..56144774baa 100644 --- a/core/capabilities/launcher.go +++ b/core/capabilities/launcher.go @@ -191,8 +191,8 @@ func (w *launcher) Launch(ctx context.Context, state *registrysyncer.LocalRegist } } - // - remote capability DONs (with IsPublic = true) the current node is a part of. - // These need server-side shims. + // Capability DONs (with IsPublic = true) the current node is a part of. + // These need server-side shims to expose my own capabilities externally. myCapabilityDONs := []registrysyncer.DON{} remoteCapabilityDONs := []registrysyncer.DON{} for _, d := range publicDONs { @@ -223,11 +223,11 @@ func (w *launcher) Launch(ctx context.Context, state *registrysyncer.LocalRegist } } - // Finally, if I'm a capability DON, let's enable external access + // Finally, if I'm in a capability DON, let's enable external access // to the capability. if len(myCapabilityDONs) > 0 { - for _, mcd := range myCapabilityDONs { - err := w.exposeCapabilities(ctx, myID, mcd, state, remoteWorkflowDONs) + for _, myDON := range myCapabilityDONs { + err := w.exposeCapabilities(ctx, myID, myDON, state, remoteWorkflowDONs) if err != nil { return err } @@ -265,6 +265,7 @@ func (w *launcher) addRemoteCapabilities(ctx context.Context, myDON registrysync codec, signers, int(remoteDON.F+1), + info.ID, w.lggr, ) } else { @@ -394,10 +395,10 @@ func (w *launcher) exposeCapabilities(ctx context.Context, myPeerID p2ptypes.Pee switch capability.CapabilityType { case capabilities.CapabilityTypeTrigger: - newTriggerPublisher := func(capability capabilities.BaseCapability, info capabilities.CapabilityInfo) (remotetypes.ReceiverService, error) { + newTriggerPublisher := func(cap capabilities.BaseCapability, info capabilities.CapabilityInfo) (remotetypes.ReceiverService, error) { publisher := remote.NewTriggerPublisher( capabilityConfig.RemoteTriggerConfig, - capability.(capabilities.TriggerCapability), + cap.(capabilities.TriggerCapability), info, don.DON, idsToDONs, @@ -409,18 +410,19 @@ func (w *launcher) exposeCapabilities(ctx context.Context, myPeerID p2ptypes.Pee err := w.addReceiver(ctx, capability, don, newTriggerPublisher) if err != nil { - return fmt.Errorf("failed to add server-side receiver: %w", err) + w.lggr.Errorw("failed to add server-side receiver for a trigger capability - it won't be exposed remotely", "id", cid, "error", err) + // continue attempting other capabilities } case capabilities.CapabilityTypeAction: w.lggr.Warn("no remote client configured for capability type action, skipping configuration") 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) (remotetypes.ReceiverService, error) { + newTargetServer := func(cap capabilities.BaseCapability, info capabilities.CapabilityInfo) (remotetypes.ReceiverService, error) { return target.NewServer( capabilityConfig.RemoteTargetConfig, myPeerID, - capability.(capabilities.TargetCapability), + cap.(capabilities.TargetCapability), info, don.DON, idsToDONs, @@ -432,7 +434,8 @@ func (w *launcher) exposeCapabilities(ctx context.Context, myPeerID p2ptypes.Pee err := w.addReceiver(ctx, capability, don, newTargetServer) if err != nil { - return fmt.Errorf("failed to add server-side receiver: %w", err) + w.lggr.Errorw("failed to add server-side receiver for a target capability - it won't be exposed remotely", "id", cid, "error", err) + // continue attempting other capabilities } default: w.lggr.Warnf("unknown capability type, skipping configuration: %+v", capability) diff --git a/core/capabilities/launcher_test.go b/core/capabilities/launcher_test.go index 11482b0dd30..43f283fc532 100644 --- a/core/capabilities/launcher_test.go +++ b/core/capabilities/launcher_test.go @@ -113,6 +113,9 @@ func TestLauncher_WiresUpExternalCapabilities(t *testing.T) { triggerCapID := randomWord() targetCapID := randomWord() + // one capability from onchain registry is not set up locally + fullMissingTargetID := "super-duper-target@6.6.6" + missingTargetCapID := randomWord() dID := uint32(1) // The below state describes a Workflow DON (AcceptsWorkflows = true), // which exposes the streams-trigger and write_chain capabilities. @@ -130,8 +133,9 @@ func TestLauncher_WiresUpExternalCapabilities(t *testing.T) { Members: nodes, }, CapabilityConfigurations: map[string]registrysyncer.CapabilityConfiguration{ - fullTriggerCapID: {}, - fullTargetID: {}, + fullTriggerCapID: {}, + fullTargetID: {}, + fullMissingTargetID: {}, }, }, }, @@ -144,6 +148,10 @@ func TestLauncher_WiresUpExternalCapabilities(t *testing.T) { ID: "write-chain_evm_1@1.0.0", CapabilityType: capabilities.CapabilityTypeTarget, }, + fullMissingTargetID: { + ID: fullMissingTargetID, + CapabilityType: capabilities.CapabilityTypeTarget, + }, }, IDsToNodes: map[p2ptypes.PeerID]kcr.INodeInfoProviderNodeInfo{ nodes[0]: { @@ -151,28 +159,28 @@ func TestLauncher_WiresUpExternalCapabilities(t *testing.T) { Signer: randomWord(), P2pId: nodes[0], EncryptionPublicKey: randomWord(), - HashedCapabilityIds: [][32]byte{triggerCapID, targetCapID}, + HashedCapabilityIds: [][32]byte{triggerCapID, targetCapID, missingTargetCapID}, }, nodes[1]: { NodeOperatorId: 1, Signer: randomWord(), P2pId: nodes[1], EncryptionPublicKey: randomWord(), - HashedCapabilityIds: [][32]byte{triggerCapID, targetCapID}, + HashedCapabilityIds: [][32]byte{triggerCapID, targetCapID, missingTargetCapID}, }, nodes[2]: { NodeOperatorId: 1, Signer: randomWord(), P2pId: nodes[2], EncryptionPublicKey: randomWord(), - HashedCapabilityIds: [][32]byte{triggerCapID, targetCapID}, + HashedCapabilityIds: [][32]byte{triggerCapID, targetCapID, missingTargetCapID}, }, nodes[3]: { NodeOperatorId: 1, Signer: randomWord(), P2pId: nodes[3], EncryptionPublicKey: randomWord(), - HashedCapabilityIds: [][32]byte{triggerCapID, targetCapID}, + HashedCapabilityIds: [][32]byte{triggerCapID, targetCapID, missingTargetCapID}, }, }, } diff --git a/core/capabilities/streams/trigger_test.go b/core/capabilities/streams/trigger_test.go index 3db6f2445ea..d5ab367252e 100644 --- a/core/capabilities/streams/trigger_test.go +++ b/core/capabilities/streams/trigger_test.go @@ -74,7 +74,7 @@ func TestStreamsTrigger(t *testing.T) { lggr := logger.TestLogger(t) ctx := testutils.Context(t) codec := streams.NewCodec(lggr) - agg := triggers.NewMercuryRemoteAggregator(codec, allowedSigners, F, lggr) + agg := triggers.NewMercuryRemoteAggregator(codec, allowedSigners, F, "streams-trigger@1.2.3", lggr) capInfo := capabilities.CapabilityInfo{ ID: triggerID, diff --git a/core/chains/evm/client/chain_client.go b/core/chains/evm/client/chain_client.go index ee45226ab16..79c2eef9769 100644 --- a/core/chains/evm/client/chain_client.go +++ b/core/chains/evm/client/chain_client.go @@ -81,7 +81,7 @@ type Client interface { SuggestGasPrice(ctx context.Context) (*big.Int, error) SuggestGasTipCap(ctx context.Context) (*big.Int, error) LatestBlockHeight(ctx context.Context) (*big.Int, error) - FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) + FeeHistory(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) HeaderByNumber(ctx context.Context, n *big.Int) (*types.Header, error) HeaderByHash(ctx context.Context, h common.Hash) (*types.Header, error) @@ -473,12 +473,12 @@ func (c *chainClient) LatestFinalizedBlock(ctx context.Context) (*evmtypes.Head, return r.LatestFinalizedBlock(ctx) } -func (c *chainClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) { +func (c *chainClient) FeeHistory(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) { r, err := c.multiNode.SelectRPC() if err != nil { return feeHistory, err } - return r.FeeHistory(ctx, blockCount, rewardPercentiles) + return r.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles) } func (c *chainClient) CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) *SendError { diff --git a/core/chains/evm/client/mocks/client.go b/core/chains/evm/client/mocks/client.go index c3937a8e171..25ed3698fe9 100644 --- a/core/chains/evm/client/mocks/client.go +++ b/core/chains/evm/client/mocks/client.go @@ -723,9 +723,9 @@ func (_c *Client_EstimateGas_Call) RunAndReturn(run func(context.Context, ethere return _c } -// FeeHistory provides a mock function with given fields: ctx, blockCount, rewardPercentiles -func (_m *Client) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (*ethereum.FeeHistory, error) { - ret := _m.Called(ctx, blockCount, rewardPercentiles) +// FeeHistory provides a mock function with given fields: ctx, blockCount, lastBlock, rewardPercentiles +func (_m *Client) FeeHistory(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64) (*ethereum.FeeHistory, error) { + ret := _m.Called(ctx, blockCount, lastBlock, rewardPercentiles) if len(ret) == 0 { panic("no return value specified for FeeHistory") @@ -733,19 +733,19 @@ func (_m *Client) FeeHistory(ctx context.Context, blockCount uint64, rewardPerce var r0 *ethereum.FeeHistory var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)); ok { - return rf(ctx, blockCount, rewardPercentiles) + if rf, ok := ret.Get(0).(func(context.Context, uint64, *big.Int, []float64) (*ethereum.FeeHistory, error)); ok { + return rf(ctx, blockCount, lastBlock, rewardPercentiles) } - if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) *ethereum.FeeHistory); ok { - r0 = rf(ctx, blockCount, rewardPercentiles) + if rf, ok := ret.Get(0).(func(context.Context, uint64, *big.Int, []float64) *ethereum.FeeHistory); ok { + r0 = rf(ctx, blockCount, lastBlock, rewardPercentiles) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*ethereum.FeeHistory) } } - if rf, ok := ret.Get(1).(func(context.Context, uint64, []float64) error); ok { - r1 = rf(ctx, blockCount, rewardPercentiles) + if rf, ok := ret.Get(1).(func(context.Context, uint64, *big.Int, []float64) error); ok { + r1 = rf(ctx, blockCount, lastBlock, rewardPercentiles) } else { r1 = ret.Error(1) } @@ -761,14 +761,15 @@ type Client_FeeHistory_Call struct { // FeeHistory is a helper method to define mock.On call // - ctx context.Context // - blockCount uint64 +// - lastBlock *big.Int // - rewardPercentiles []float64 -func (_e *Client_Expecter) FeeHistory(ctx interface{}, blockCount interface{}, rewardPercentiles interface{}) *Client_FeeHistory_Call { - return &Client_FeeHistory_Call{Call: _e.mock.On("FeeHistory", ctx, blockCount, rewardPercentiles)} +func (_e *Client_Expecter) FeeHistory(ctx interface{}, blockCount interface{}, lastBlock interface{}, rewardPercentiles interface{}) *Client_FeeHistory_Call { + return &Client_FeeHistory_Call{Call: _e.mock.On("FeeHistory", ctx, blockCount, lastBlock, rewardPercentiles)} } -func (_c *Client_FeeHistory_Call) Run(run func(ctx context.Context, blockCount uint64, rewardPercentiles []float64)) *Client_FeeHistory_Call { +func (_c *Client_FeeHistory_Call) Run(run func(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64)) *Client_FeeHistory_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint64), args[2].([]float64)) + run(args[0].(context.Context), args[1].(uint64), args[2].(*big.Int), args[3].([]float64)) }) return _c } @@ -778,7 +779,7 @@ func (_c *Client_FeeHistory_Call) Return(feeHistory *ethereum.FeeHistory, err er return _c } -func (_c *Client_FeeHistory_Call) RunAndReturn(run func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)) *Client_FeeHistory_Call { +func (_c *Client_FeeHistory_Call) RunAndReturn(run func(context.Context, uint64, *big.Int, []float64) (*ethereum.FeeHistory, error)) *Client_FeeHistory_Call { _c.Call.Return(run) return _c } diff --git a/core/chains/evm/client/null_client.go b/core/chains/evm/client/null_client.go index 13fbf66c6cc..b1dedd3f74a 100644 --- a/core/chains/evm/client/null_client.go +++ b/core/chains/evm/client/null_client.go @@ -236,6 +236,6 @@ func (nc *NullClient) CheckTxValidity(_ context.Context, _ common.Address, _ com return nil } -func (nc *NullClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) { +func (nc *NullClient) FeeHistory(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) { return nil, nil } diff --git a/core/chains/evm/client/rpc_client.go b/core/chains/evm/client/rpc_client.go index 260c6b4b5e7..8d6e25d5540 100644 --- a/core/chains/evm/client/rpc_client.go +++ b/core/chains/evm/client/rpc_client.go @@ -1093,7 +1093,7 @@ func (r *RPCClient) BalanceAt(ctx context.Context, account common.Address, block return } -func (r *RPCClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) { +func (r *RPCClient) FeeHistory(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) { ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx, r.rpcTimeout) defer cancel() lggr := r.newRqLggr().With("blockCount", blockCount, "rewardPercentiles", rewardPercentiles) @@ -1101,10 +1101,10 @@ func (r *RPCClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPer lggr.Debug("RPC call: evmclient.Client#FeeHistory") start := time.Now() if http != nil { - feeHistory, err = http.geth.FeeHistory(ctx, blockCount, nil, rewardPercentiles) + feeHistory, err = http.geth.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles) err = r.wrapHTTP(err) } else { - feeHistory, err = ws.geth.FeeHistory(ctx, blockCount, nil, rewardPercentiles) + feeHistory, err = ws.geth.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles) err = r.wrapWS(err) } duration := time.Since(start) diff --git a/core/chains/evm/client/simulated_backend_client.go b/core/chains/evm/client/simulated_backend_client.go index 7935434edce..c745641935f 100644 --- a/core/chains/evm/client/simulated_backend_client.go +++ b/core/chains/evm/client/simulated_backend_client.go @@ -156,7 +156,7 @@ func (c *SimulatedBackendClient) LINKBalance(ctx context.Context, address common panic("not implemented") } -func (c *SimulatedBackendClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) { +func (c *SimulatedBackendClient) FeeHistory(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) { panic("not implemented") } diff --git a/core/chains/evm/config/toml/defaults/Celo_Testnet.toml b/core/chains/evm/config/toml/defaults/Celo_Testnet.toml index 8bee5323f4d..0e4594150dd 100644 --- a/core/chains/evm/config/toml/defaults/Celo_Testnet.toml +++ b/core/chains/evm/config/toml/defaults/Celo_Testnet.toml @@ -1,21 +1,30 @@ ChainID = '44787' ChainType = 'celo' -FinalityDepth = 10 -LogPollInterval = '5s' -MinIncomingConfirmations = 1 +FinalityTagEnabled = true +FinalityDepth = 2750 # mean finality time of ~37 minutes + 500 block buffer +LogPollInterval = '1s' # 1 sec block rate NoNewHeadsThreshold = '1m' -OCR.ContractConfirmations = 1 -FinalizedBlockOffset = 2 -NoNewFinalizedHeadsThreshold = '1m' +MinIncomingConfirmations = 1 +NoNewFinalizedHeadsThreshold = '45m' # Set slightly higher than mean finality time [GasEstimator] -PriceDefault = '5 gwei' -PriceMax = '500 gwei' -PriceMin = '5 gwei' -BumpMin = '2 gwei' +EIP1559DynamicFees = true +PriceMin = '5 gwei' # Mean gas price around 5 gwei and celo txns are extremely cheap at ~0.00088 CELO per txn ($0.000058) +PriceMax = '1000 gwei' # DS&A recommendation [GasEstimator.BlockHistory] -BlockHistorySize = 24 +# Default is 8, which leads to bumpy gas prices. In CCIP +# we want to smooth out the gas prices, so we increase the sample size. +BlockHistorySize = 200 + +[Transactions] +ResendAfterThreshold = '30s' [HeadTracker] -HistoryDepth = 50 +HistoryDepth = 300 + +[NodePool] +SyncThreshold = 10 # recommended for OP stack chains + +[OCR] +ContractConfirmations = 1 # recommended for OP stack chains \ No newline at end of file diff --git a/core/chains/evm/config/toml/defaults/Linea_Mainnet.toml b/core/chains/evm/config/toml/defaults/Linea_Mainnet.toml index 94d8bedc44b..5a89873acae 100644 --- a/core/chains/evm/config/toml/defaults/Linea_Mainnet.toml +++ b/core/chains/evm/config/toml/defaults/Linea_Mainnet.toml @@ -15,3 +15,8 @@ ResendAfterThreshold = '3m' # set greater than finality depth [HeadTracker] HistoryDepth = 350 + +[Transactions.AutoPurge] +Enabled = true +Threshold = 50 # 50 blocks at 3s block time ~2.5 minutes +MinAttempts = 3 diff --git a/core/chains/evm/config/toml/defaults/Linea_Sepolia.toml b/core/chains/evm/config/toml/defaults/Linea_Sepolia.toml index ac5e18a09b6..8f168ee93a6 100644 --- a/core/chains/evm/config/toml/defaults/Linea_Sepolia.toml +++ b/core/chains/evm/config/toml/defaults/Linea_Sepolia.toml @@ -10,4 +10,9 @@ PriceMin = '1 wei' ResendAfterThreshold = '3m' [HeadTracker] -HistoryDepth = 1000 \ No newline at end of file +HistoryDepth = 1000 + +[Transactions.AutoPurge] +Enabled = true +Threshold = 50 # 50 blocks at 3s block time ~2.5 minutes +MinAttempts = 3 diff --git a/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Cardona.toml b/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Cardona.toml index 46ce80e29fc..5e5bc573706 100644 --- a/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Cardona.toml +++ b/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Cardona.toml @@ -24,3 +24,7 @@ CacheTimeout = '4s' [HeadTracker] HistoryDepth = 2000 + +[Transactions.AutoPurge] +Enabled = true +MinAttempts = 3 diff --git a/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Mainnet.toml b/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Mainnet.toml index 2fef7874d17..1952c40d590 100644 --- a/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Mainnet.toml +++ b/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Mainnet.toml @@ -26,3 +26,7 @@ CacheTimeout = '4s' [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 + +[Transactions.AutoPurge] +Enabled = true +MinAttempts = 3 diff --git a/core/chains/evm/config/toml/defaults/Scroll_Mainnet.toml b/core/chains/evm/config/toml/defaults/Scroll_Mainnet.toml index 5a5e0459512..6a7441201ae 100644 --- a/core/chains/evm/config/toml/defaults/Scroll_Mainnet.toml +++ b/core/chains/evm/config/toml/defaults/Scroll_Mainnet.toml @@ -24,3 +24,7 @@ HistoryDepth = 50 [OCR] ContractConfirmations = 1 + +[Transactions.AutoPurge] +Enabled = true +DetectionApiUrl = 'https://venus.scroll.io' diff --git a/core/chains/evm/config/toml/defaults/Scroll_Sepolia.toml b/core/chains/evm/config/toml/defaults/Scroll_Sepolia.toml index a9c6a979e64..1351bf73ded 100644 --- a/core/chains/evm/config/toml/defaults/Scroll_Sepolia.toml +++ b/core/chains/evm/config/toml/defaults/Scroll_Sepolia.toml @@ -24,3 +24,7 @@ HistoryDepth = 50 [OCR] ContractConfirmations = 1 + +[Transactions.AutoPurge] +Enabled = true +DetectionApiUrl = 'https://sepolia-venus.scroll.io' diff --git a/core/chains/evm/config/toml/defaults/XLayer_Mainnet.toml b/core/chains/evm/config/toml/defaults/XLayer_Mainnet.toml index 37dcae91baf..1d96e95e10d 100644 --- a/core/chains/evm/config/toml/defaults/XLayer_Mainnet.toml +++ b/core/chains/evm/config/toml/defaults/XLayer_Mainnet.toml @@ -24,3 +24,7 @@ BlockHistorySize = 12 [HeadTracker] HistoryDepth = 2000 + +[Transactions.AutoPurge] +Enabled = true +MinAttempts = 3 diff --git a/core/chains/evm/config/toml/defaults/XLayer_Sepolia.toml b/core/chains/evm/config/toml/defaults/XLayer_Sepolia.toml index f39c9dab704..3f4a5debde5 100644 --- a/core/chains/evm/config/toml/defaults/XLayer_Sepolia.toml +++ b/core/chains/evm/config/toml/defaults/XLayer_Sepolia.toml @@ -24,3 +24,7 @@ BlockHistorySize = 12 [HeadTracker] HistoryDepth = 2000 + +[Transactions.AutoPurge] +Enabled = true +MinAttempts = 3 diff --git a/core/chains/evm/gas/fee_history_estimator.go b/core/chains/evm/gas/fee_history_estimator.go index 5f8dc32c2eb..211528a1a7d 100644 --- a/core/chains/evm/gas/fee_history_estimator.go +++ b/core/chains/evm/gas/fee_history_estimator.go @@ -69,7 +69,7 @@ type FeeHistoryEstimatorConfig struct { type feeHistoryEstimatorClient interface { SuggestGasPrice(ctx context.Context) (*big.Int, error) - FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) + FeeHistory(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) } type FeeHistoryEstimator struct { @@ -235,7 +235,7 @@ func (f *FeeHistoryEstimator) RefreshDynamicPrice() error { defer cancel() // RewardPercentile will be used for maxPriorityFeePerGas estimations and connectivityPercentile to set the highest threshold for bumping. - feeHistory, err := f.client.FeeHistory(ctx, max(f.config.BlockHistorySize, 1), []float64{f.config.RewardPercentile, ConnectivityPercentile}) + feeHistory, err := f.client.FeeHistory(ctx, max(f.config.BlockHistorySize, 1), nil, []float64{f.config.RewardPercentile, ConnectivityPercentile}) if err != nil { return err } diff --git a/core/chains/evm/gas/fee_history_estimator_test.go b/core/chains/evm/gas/fee_history_estimator_test.go index a96046f1f26..7d66b9375d7 100644 --- a/core/chains/evm/gas/fee_history_estimator_test.go +++ b/core/chains/evm/gas/fee_history_estimator_test.go @@ -230,7 +230,7 @@ func TestFeeHistoryEstimatorGetDynamicFee(t *testing.T) { BaseFee: []*big.Int{baseFee, baseFee}, GasUsedRatio: nil, } - client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() + client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() blockHistoryLength := 2 cfg := gas.FeeHistoryEstimatorConfig{BlockHistorySize: uint64(blockHistoryLength)} @@ -269,7 +269,7 @@ func TestFeeHistoryEstimatorGetDynamicFee(t *testing.T) { BaseFee: []*big.Int{baseFee}, GasUsedRatio: nil, } - client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() + client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() cfg := gas.FeeHistoryEstimatorConfig{BlockHistorySize: 1} @@ -303,7 +303,7 @@ func TestFeeHistoryEstimatorBumpDynamicFee(t *testing.T) { BaseFee: []*big.Int{big.NewInt(5)}, GasUsedRatio: nil, } - client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() + client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() cfg := gas.FeeHistoryEstimatorConfig{ BlockHistorySize: 2, @@ -366,7 +366,7 @@ func TestFeeHistoryEstimatorBumpDynamicFee(t *testing.T) { BaseFee: []*big.Int{baseFee}, GasUsedRatio: nil, } - client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() + client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() maxFee := (*assets.Wei)(baseFee).AddPercentage(gas.BaseFeeBufferPercentage).Add((*assets.Wei)(maxPriorityFeePerGas)) @@ -400,7 +400,7 @@ func TestFeeHistoryEstimatorBumpDynamicFee(t *testing.T) { BaseFee: []*big.Int{baseFee}, GasUsedRatio: nil, } - client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() + client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() cfg := gas.FeeHistoryEstimatorConfig{ BlockHistorySize: 1, @@ -432,7 +432,7 @@ func TestFeeHistoryEstimatorBumpDynamicFee(t *testing.T) { BaseFee: []*big.Int{baseFee}, GasUsedRatio: nil, } - client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() + client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() cfg := gas.FeeHistoryEstimatorConfig{ BlockHistorySize: 1, @@ -465,7 +465,7 @@ func TestFeeHistoryEstimatorBumpDynamicFee(t *testing.T) { BaseFee: []*big.Int{baseFee}, GasUsedRatio: nil, } - client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() + client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() cfg := gas.FeeHistoryEstimatorConfig{ BlockHistorySize: 1, @@ -495,7 +495,7 @@ func TestFeeHistoryEstimatorBumpDynamicFee(t *testing.T) { BaseFee: []*big.Int{baseFee}, GasUsedRatio: nil, } - client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil) + client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil) cfg := gas.FeeHistoryEstimatorConfig{ BlockHistorySize: 0, diff --git a/core/chains/evm/gas/mocks/fee_estimator_client.go b/core/chains/evm/gas/mocks/fee_estimator_client.go index f74aac07699..782c897923b 100644 --- a/core/chains/evm/gas/mocks/fee_estimator_client.go +++ b/core/chains/evm/gas/mocks/fee_estimator_client.go @@ -194,53 +194,6 @@ func (_c *FeeEstimatorClient_CallContract_Call) RunAndReturn(run func(context.Co return _c } -// ConfiguredChainID provides a mock function with given fields: -func (_m *FeeEstimatorClient) ConfiguredChainID() *big.Int { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for ConfiguredChainID") - } - - var r0 *big.Int - if rf, ok := ret.Get(0).(func() *big.Int); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*big.Int) - } - } - - return r0 -} - -// FeeEstimatorClient_ConfiguredChainID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ConfiguredChainID' -type FeeEstimatorClient_ConfiguredChainID_Call struct { - *mock.Call -} - -// ConfiguredChainID is a helper method to define mock.On call -func (_e *FeeEstimatorClient_Expecter) ConfiguredChainID() *FeeEstimatorClient_ConfiguredChainID_Call { - return &FeeEstimatorClient_ConfiguredChainID_Call{Call: _e.mock.On("ConfiguredChainID")} -} - -func (_c *FeeEstimatorClient_ConfiguredChainID_Call) Run(run func()) *FeeEstimatorClient_ConfiguredChainID_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *FeeEstimatorClient_ConfiguredChainID_Call) Return(_a0 *big.Int) *FeeEstimatorClient_ConfiguredChainID_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *FeeEstimatorClient_ConfiguredChainID_Call) RunAndReturn(run func() *big.Int) *FeeEstimatorClient_ConfiguredChainID_Call { - _c.Call.Return(run) - return _c -} - // EstimateGas provides a mock function with given fields: ctx, call func (_m *FeeEstimatorClient) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) { ret := _m.Called(ctx, call) @@ -298,9 +251,9 @@ func (_c *FeeEstimatorClient_EstimateGas_Call) RunAndReturn(run func(context.Con return _c } -// FeeHistory provides a mock function with given fields: ctx, blockCount, rewardPercentiles -func (_m *FeeEstimatorClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (*ethereum.FeeHistory, error) { - ret := _m.Called(ctx, blockCount, rewardPercentiles) +// FeeHistory provides a mock function with given fields: ctx, blockCount, lastBlock, rewardPercentiles +func (_m *FeeEstimatorClient) FeeHistory(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64) (*ethereum.FeeHistory, error) { + ret := _m.Called(ctx, blockCount, lastBlock, rewardPercentiles) if len(ret) == 0 { panic("no return value specified for FeeHistory") @@ -308,19 +261,19 @@ func (_m *FeeEstimatorClient) FeeHistory(ctx context.Context, blockCount uint64, var r0 *ethereum.FeeHistory var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)); ok { - return rf(ctx, blockCount, rewardPercentiles) + if rf, ok := ret.Get(0).(func(context.Context, uint64, *big.Int, []float64) (*ethereum.FeeHistory, error)); ok { + return rf(ctx, blockCount, lastBlock, rewardPercentiles) } - if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) *ethereum.FeeHistory); ok { - r0 = rf(ctx, blockCount, rewardPercentiles) + if rf, ok := ret.Get(0).(func(context.Context, uint64, *big.Int, []float64) *ethereum.FeeHistory); ok { + r0 = rf(ctx, blockCount, lastBlock, rewardPercentiles) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*ethereum.FeeHistory) } } - if rf, ok := ret.Get(1).(func(context.Context, uint64, []float64) error); ok { - r1 = rf(ctx, blockCount, rewardPercentiles) + if rf, ok := ret.Get(1).(func(context.Context, uint64, *big.Int, []float64) error); ok { + r1 = rf(ctx, blockCount, lastBlock, rewardPercentiles) } else { r1 = ret.Error(1) } @@ -336,14 +289,15 @@ type FeeEstimatorClient_FeeHistory_Call struct { // FeeHistory is a helper method to define mock.On call // - ctx context.Context // - blockCount uint64 +// - lastBlock *big.Int // - rewardPercentiles []float64 -func (_e *FeeEstimatorClient_Expecter) FeeHistory(ctx interface{}, blockCount interface{}, rewardPercentiles interface{}) *FeeEstimatorClient_FeeHistory_Call { - return &FeeEstimatorClient_FeeHistory_Call{Call: _e.mock.On("FeeHistory", ctx, blockCount, rewardPercentiles)} +func (_e *FeeEstimatorClient_Expecter) FeeHistory(ctx interface{}, blockCount interface{}, lastBlock interface{}, rewardPercentiles interface{}) *FeeEstimatorClient_FeeHistory_Call { + return &FeeEstimatorClient_FeeHistory_Call{Call: _e.mock.On("FeeHistory", ctx, blockCount, lastBlock, rewardPercentiles)} } -func (_c *FeeEstimatorClient_FeeHistory_Call) Run(run func(ctx context.Context, blockCount uint64, rewardPercentiles []float64)) *FeeEstimatorClient_FeeHistory_Call { +func (_c *FeeEstimatorClient_FeeHistory_Call) Run(run func(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64)) *FeeEstimatorClient_FeeHistory_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint64), args[2].([]float64)) + run(args[0].(context.Context), args[1].(uint64), args[2].(*big.Int), args[3].([]float64)) }) return _c } @@ -353,7 +307,7 @@ func (_c *FeeEstimatorClient_FeeHistory_Call) Return(feeHistory *ethereum.FeeHis return _c } -func (_c *FeeEstimatorClient_FeeHistory_Call) RunAndReturn(run func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)) *FeeEstimatorClient_FeeHistory_Call { +func (_c *FeeEstimatorClient_FeeHistory_Call) RunAndReturn(run func(context.Context, uint64, *big.Int, []float64) (*ethereum.FeeHistory, error)) *FeeEstimatorClient_FeeHistory_Call { _c.Call.Return(run) return _c } diff --git a/core/chains/evm/gas/mocks/fee_history_estimator_client.go b/core/chains/evm/gas/mocks/fee_history_estimator_client.go index 5f42614af79..24ad7e23ad9 100644 --- a/core/chains/evm/gas/mocks/fee_history_estimator_client.go +++ b/core/chains/evm/gas/mocks/fee_history_estimator_client.go @@ -24,9 +24,9 @@ func (_m *FeeHistoryEstimatorClient) EXPECT() *FeeHistoryEstimatorClient_Expecte return &FeeHistoryEstimatorClient_Expecter{mock: &_m.Mock} } -// FeeHistory provides a mock function with given fields: ctx, blockCount, rewardPercentiles -func (_m *FeeHistoryEstimatorClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (*ethereum.FeeHistory, error) { - ret := _m.Called(ctx, blockCount, rewardPercentiles) +// FeeHistory provides a mock function with given fields: ctx, blockCount, lastBlock, rewardPercentiles +func (_m *FeeHistoryEstimatorClient) FeeHistory(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64) (*ethereum.FeeHistory, error) { + ret := _m.Called(ctx, blockCount, lastBlock, rewardPercentiles) if len(ret) == 0 { panic("no return value specified for FeeHistory") @@ -34,19 +34,19 @@ func (_m *FeeHistoryEstimatorClient) FeeHistory(ctx context.Context, blockCount var r0 *ethereum.FeeHistory var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)); ok { - return rf(ctx, blockCount, rewardPercentiles) + if rf, ok := ret.Get(0).(func(context.Context, uint64, *big.Int, []float64) (*ethereum.FeeHistory, error)); ok { + return rf(ctx, blockCount, lastBlock, rewardPercentiles) } - if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) *ethereum.FeeHistory); ok { - r0 = rf(ctx, blockCount, rewardPercentiles) + if rf, ok := ret.Get(0).(func(context.Context, uint64, *big.Int, []float64) *ethereum.FeeHistory); ok { + r0 = rf(ctx, blockCount, lastBlock, rewardPercentiles) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*ethereum.FeeHistory) } } - if rf, ok := ret.Get(1).(func(context.Context, uint64, []float64) error); ok { - r1 = rf(ctx, blockCount, rewardPercentiles) + if rf, ok := ret.Get(1).(func(context.Context, uint64, *big.Int, []float64) error); ok { + r1 = rf(ctx, blockCount, lastBlock, rewardPercentiles) } else { r1 = ret.Error(1) } @@ -62,14 +62,15 @@ type FeeHistoryEstimatorClient_FeeHistory_Call struct { // FeeHistory is a helper method to define mock.On call // - ctx context.Context // - blockCount uint64 +// - lastBlock *big.Int // - rewardPercentiles []float64 -func (_e *FeeHistoryEstimatorClient_Expecter) FeeHistory(ctx interface{}, blockCount interface{}, rewardPercentiles interface{}) *FeeHistoryEstimatorClient_FeeHistory_Call { - return &FeeHistoryEstimatorClient_FeeHistory_Call{Call: _e.mock.On("FeeHistory", ctx, blockCount, rewardPercentiles)} +func (_e *FeeHistoryEstimatorClient_Expecter) FeeHistory(ctx interface{}, blockCount interface{}, lastBlock interface{}, rewardPercentiles interface{}) *FeeHistoryEstimatorClient_FeeHistory_Call { + return &FeeHistoryEstimatorClient_FeeHistory_Call{Call: _e.mock.On("FeeHistory", ctx, blockCount, lastBlock, rewardPercentiles)} } -func (_c *FeeHistoryEstimatorClient_FeeHistory_Call) Run(run func(ctx context.Context, blockCount uint64, rewardPercentiles []float64)) *FeeHistoryEstimatorClient_FeeHistory_Call { +func (_c *FeeHistoryEstimatorClient_FeeHistory_Call) Run(run func(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64)) *FeeHistoryEstimatorClient_FeeHistory_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint64), args[2].([]float64)) + run(args[0].(context.Context), args[1].(uint64), args[2].(*big.Int), args[3].([]float64)) }) return _c } @@ -79,7 +80,7 @@ func (_c *FeeHistoryEstimatorClient_FeeHistory_Call) Return(feeHistory *ethereum return _c } -func (_c *FeeHistoryEstimatorClient_FeeHistory_Call) RunAndReturn(run func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)) *FeeHistoryEstimatorClient_FeeHistory_Call { +func (_c *FeeHistoryEstimatorClient_FeeHistory_Call) RunAndReturn(run func(context.Context, uint64, *big.Int, []float64) (*ethereum.FeeHistory, error)) *FeeHistoryEstimatorClient_FeeHistory_Call { _c.Call.Return(run) return _c } diff --git a/core/chains/evm/gas/models.go b/core/chains/evm/gas/models.go index 6a7b7c1a1ef..6cb89818c8f 100644 --- a/core/chains/evm/gas/models.go +++ b/core/chains/evm/gas/models.go @@ -45,15 +45,14 @@ type feeEstimatorClient interface { CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error - ConfiguredChainID() *big.Int HeadByNumber(ctx context.Context, n *big.Int) (*evmtypes.Head, error) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) SuggestGasPrice(ctx context.Context) (*big.Int, error) - FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) + FeeHistory(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) } // NewEstimator returns the estimator for a given config -func NewEstimator(lggr logger.Logger, ethClient feeEstimatorClient, chaintype chaintype.ChainType, geCfg evmconfig.GasEstimator, clientsByChainID map[string]rollups.DAClient) (EvmFeeEstimator, error) { +func NewEstimator(lggr logger.Logger, ethClient feeEstimatorClient, chaintype chaintype.ChainType, chainID *big.Int, geCfg evmconfig.GasEstimator, clientsByChainID map[string]rollups.DAClient) (EvmFeeEstimator, error) { bh := geCfg.BlockHistory() s := geCfg.Mode() lggr.Infow(fmt.Sprintf("Initializing EVM gas estimator in mode: %s", s), @@ -98,7 +97,7 @@ func NewEstimator(lggr logger.Logger, ethClient feeEstimatorClient, chaintype ch } case "BlockHistory": newEstimator = func(l logger.Logger) EvmEstimator { - return NewBlockHistoryEstimator(lggr, ethClient, chaintype, geCfg, bh, ethClient.ConfiguredChainID(), l1Oracle) + return NewBlockHistoryEstimator(lggr, ethClient, chaintype, geCfg, bh, chainID, l1Oracle) } case "FixedPrice": newEstimator = func(l logger.Logger) EvmEstimator { @@ -117,7 +116,7 @@ func NewEstimator(lggr logger.Logger, ethClient feeEstimatorClient, chaintype ch BlockHistorySize: uint64(geCfg.BlockHistory().BlockHistorySize()), RewardPercentile: float64(geCfg.BlockHistory().TransactionPercentile()), } - return NewFeeHistoryEstimator(lggr, ethClient, ccfg, ethClient.ConfiguredChainID(), l1Oracle) + return NewFeeHistoryEstimator(lggr, ethClient, ccfg, chainID, l1Oracle) } default: diff --git a/core/chains/evm/gas/rollups/l1_oracle.go b/core/chains/evm/gas/rollups/l1_oracle.go index 18c326ab8f6..ceecb80c608 100644 --- a/core/chains/evm/gas/rollups/l1_oracle.go +++ b/core/chains/evm/gas/rollups/l1_oracle.go @@ -36,7 +36,7 @@ type l1OracleClient interface { // DAClient is interface of client connections for additional chains layers type DAClient interface { SuggestGasPrice(ctx context.Context) (*big.Int, error) - FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) + FeeHistory(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) } type priceEntry struct { diff --git a/core/chains/evm/txmgr/txmgr_test.go b/core/chains/evm/txmgr/txmgr_test.go index b70e2fed671..7052a694719 100644 --- a/core/chains/evm/txmgr/txmgr_test.go +++ b/core/chains/evm/txmgr/txmgr_test.go @@ -101,7 +101,7 @@ func TestTxm_SendNativeToken_DoesNotSendToZero(t *testing.T) { keyStore := cltest.NewKeyStore(t, db).Eth() ethClient := testutils.NewEthClientMockWithDefaultChain(t) - estimator, err := gas.NewEstimator(logger.Test(t), ethClient, config.ChainType(), evmConfig.GasEstimator(), nil) + estimator, err := gas.NewEstimator(logger.Test(t), ethClient, config.ChainType(), ethClient.ConfiguredChainID(), evmConfig.GasEstimator(), nil) require.NoError(t, err) txm, err := makeTestEvmTxm(t, db, ethClient, estimator, evmConfig, evmConfig.GasEstimator(), evmConfig.Transactions(), dbConfig, dbConfig.Listener(), keyStore) require.NoError(t, err) @@ -127,7 +127,7 @@ func TestTxm_CreateTransaction(t *testing.T) { ethClient := testutils.NewEthClientMockWithDefaultChain(t) - estimator, err := gas.NewEstimator(logger.Test(t), ethClient, config.ChainType(), evmConfig.GasEstimator(), nil) + estimator, err := gas.NewEstimator(logger.Test(t), ethClient, config.ChainType(), ethClient.ConfiguredChainID(), evmConfig.GasEstimator(), nil) require.NoError(t, err) txm, err := makeTestEvmTxm(t, db, ethClient, estimator, evmConfig, evmConfig.GasEstimator(), evmConfig.Transactions(), dbConfig, dbConfig.Listener(), kst.Eth()) require.NoError(t, err) @@ -409,7 +409,7 @@ func TestTxm_CreateTransaction_OutOfEth(t *testing.T) { config, dbConfig, evmConfig := txmgr.MakeTestConfigs(t) ethClient := testutils.NewEthClientMockWithDefaultChain(t) - estimator, err := gas.NewEstimator(logger.Test(t), ethClient, config.ChainType(), evmConfig.GasEstimator(), nil) + estimator, err := gas.NewEstimator(logger.Test(t), ethClient, config.ChainType(), ethClient.ConfiguredChainID(), evmConfig.GasEstimator(), nil) require.NoError(t, err) txm, err := makeTestEvmTxm(t, db, ethClient, estimator, evmConfig, evmConfig.GasEstimator(), evmConfig.Transactions(), dbConfig, dbConfig.Listener(), etKeyStore) require.NoError(t, err) @@ -507,7 +507,7 @@ func TestTxm_Lifecycle(t *testing.T) { keyChangeCh := make(chan struct{}) unsub := cltest.NewAwaiter() kst.On("SubscribeToKeyChanges", mock.Anything).Return(keyChangeCh, unsub.ItHappened) - estimator, err := gas.NewEstimator(logger.Test(t), ethClient, config.ChainType(), evmConfig.GasEstimator(), nil) + estimator, err := gas.NewEstimator(logger.Test(t), ethClient, config.ChainType(), ethClient.ConfiguredChainID(), evmConfig.GasEstimator(), nil) require.NoError(t, err) txm, err := makeTestEvmTxm(t, db, ethClient, estimator, evmConfig, evmConfig.GasEstimator(), evmConfig.Transactions(), dbConfig, dbConfig.Listener(), kst) require.NoError(t, err) @@ -562,7 +562,7 @@ func TestTxm_Reset(t *testing.T) { ethClient.On("PendingNonceAt", mock.Anything, addr).Return(uint64(128), nil).Maybe() ethClient.On("PendingNonceAt", mock.Anything, addr2).Return(uint64(44), nil).Maybe() - estimator, err := gas.NewEstimator(logger.Test(t), ethClient, cfg.EVM().ChainType(), cfg.EVM().GasEstimator(), nil) + estimator, err := gas.NewEstimator(logger.Test(t), ethClient, cfg.EVM().ChainType(), ethClient.ConfiguredChainID(), cfg.EVM().GasEstimator(), nil) require.NoError(t, err) txm, err := makeTestEvmTxm(t, db, ethClient, estimator, cfg.EVM(), cfg.EVM().GasEstimator(), cfg.EVM().Transactions(), gcfg.Database(), gcfg.Database().Listener(), kst.Eth()) require.NoError(t, err) diff --git a/core/chains/legacyevm/evm_txm.go b/core/chains/legacyevm/evm_txm.go index 5af3799ec7b..3a96a9da937 100644 --- a/core/chains/legacyevm/evm_txm.go +++ b/core/chains/legacyevm/evm_txm.go @@ -47,7 +47,7 @@ func newEvmTxm( // build estimator from factory if opts.GenGasEstimator == nil { - if estimator, err = gas.NewEstimator(lggr, client, cfg.ChainType(), cfg.GasEstimator(), clientsByChainID); err != nil { + if estimator, err = gas.NewEstimator(lggr, client, cfg.ChainType(), chainID, cfg.GasEstimator(), clientsByChainID); err != nil { return nil, nil, fmt.Errorf("failed to initialize estimator: %w", err) } } else { diff --git a/core/cmd/key_store_authenticator.go b/core/cmd/key_store_authenticator.go index 6ad4b0ef2ba..7833566fcdc 100644 --- a/core/cmd/key_store_authenticator.go +++ b/core/cmd/key_store_authenticator.go @@ -17,11 +17,11 @@ type TerminalKeyStoreAuthenticator struct { Prompter Prompter } -type keystorePassword interface { +type KeystorePassword interface { Keystore() string } -func (auth TerminalKeyStoreAuthenticator) authenticate(ctx context.Context, keyStore keystore.Master, password keystorePassword) error { +func (auth TerminalKeyStoreAuthenticator) Authenticate(ctx context.Context, keyStore keystore.Master, password KeystorePassword) error { isEmpty, err := keyStore.IsEmpty(ctx) if err != nil { return errors.Wrap(err, "error determining if keystore is empty") diff --git a/core/cmd/shell.go b/core/cmd/shell.go index 56a7eb001ed..966fa1a0ff8 100644 --- a/core/cmd/shell.go +++ b/core/cmd/shell.go @@ -66,7 +66,7 @@ var ( grpcOpts loop.GRPCOpts ) -func initGlobals(cfgProm config.Prometheus, cfgTracing config.Tracing, cfgTelemetry config.Telemetry, lggr logger.Logger) error { +func initGlobals(cfgProm config.Prometheus, cfgTracing config.Tracing, cfgTelemetry config.Telemetry, lggr logger.Logger, csaPubKeyHex string, beholderAuthHeaders map[string]string) error { // Avoid double initializations, but does not prevent relay methods from being called multiple times. var err error initGlobalsOnce.Do(func() { @@ -97,12 +97,17 @@ func initGlobals(cfgProm config.Prometheus, cfgTracing config.Tracing, cfgTeleme 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(), + EmitterBatchProcessor: cfgTelemetry.EmitterBatchProcessor(), + EmitterExportTimeout: cfgTelemetry.EmitterExportTimeout(), + AuthPublicKeyHex: csaPubKeyHex, + AuthHeaders: beholderAuthHeaders, } if tracingCfg.Enabled { clientCfg.TraceSpanExporter, err = tracingCfg.NewSpanExporter() @@ -173,19 +178,14 @@ func (s *Shell) configExitErr(validateFn func() error) cli.ExitCoder { // AppFactory implements the NewApplication method. type AppFactory interface { - NewApplication(ctx context.Context, cfg chainlink.GeneralConfig, appLggr logger.Logger, db *sqlx.DB) (chainlink.Application, error) + NewApplication(ctx context.Context, cfg chainlink.GeneralConfig, appLggr logger.Logger, db *sqlx.DB, keyStoreAuthenticator TerminalKeyStoreAuthenticator) (chainlink.Application, error) } // ChainlinkAppFactory is used to create a new Application. 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(), cfg.Telemetry(), appLggr) - if err != nil { - appLggr.Errorf("Failed to initialize globals: %v", err) - } - +func (n ChainlinkAppFactory) NewApplication(ctx context.Context, cfg chainlink.GeneralConfig, appLggr logger.Logger, db *sqlx.DB, keyStoreAuthenticator TerminalKeyStoreAuthenticator) (app chainlink.Application, err error) { err = migrate.SetMigrationENVVars(cfg) if err != nil { return nil, err @@ -197,11 +197,31 @@ func (n ChainlinkAppFactory) NewApplication(ctx context.Context, cfg chainlink.G } ds := sqlutil.WrapDataSource(db, appLggr, sqlutil.TimeoutHook(cfg.Database().DefaultQueryTimeout), sqlutil.MonitorHook(cfg.Database().LogSQL)) - keyStore := keystore.New(ds, utils.GetScryptParams(cfg), appLggr) + + err = keyStoreAuthenticator.Authenticate(ctx, keyStore, cfg.Password()) + if err != nil { + return nil, errors.Wrap(err, "error authenticating keystore") + } + + err = keyStore.CSA().EnsureKey(ctx) + if err != nil { + return nil, errors.Wrap(err, "failed to ensure CSA key") + } + + beholderAuthHeaders, csaPubKeyHex, err := keystore.BuildBeholderAuth(keyStore) + if err != nil { + return nil, errors.Wrap(err, "failed to build Beholder auth") + } + + err = initGlobals(cfg.Prometheus(), cfg.Tracing(), cfg.Telemetry(), appLggr, csaPubKeyHex, beholderAuthHeaders) + if err != nil { + appLggr.Errorf("Failed to initialize globals: %v", err) + } + mailMon := mailbox.NewMonitor(cfg.AppID().String(), appLggr.Named("Mailbox")) - loopRegistry := plugins.NewLoopRegistry(appLggr, cfg.Tracing(), cfg.Telemetry()) + loopRegistry := plugins.NewLoopRegistry(appLggr, cfg.Tracing(), cfg.Telemetry(), beholderAuthHeaders, csaPubKeyHex) mercuryPool := wsrpc.NewPool(appLggr, cache.Config{ LatestReportTTL: cfg.Mercury().Cache().LatestReportTTL(), diff --git a/core/cmd/shell_local.go b/core/cmd/shell_local.go index 689e7d27d26..50411e10d42 100644 --- a/core/cmd/shell_local.go +++ b/core/cmd/shell_local.go @@ -382,18 +382,13 @@ func (s *Shell) runNode(c *cli.Context) error { // From now on, DB locks and DB connection will be released on every return. // Keep watching on logger.Fatal* calls and os.Exit(), because defer will not be executed. - app, err := s.AppFactory.NewApplication(rootCtx, s.Config, s.Logger, ldb.DB()) + app, err := s.AppFactory.NewApplication(rootCtx, s.Config, s.Logger, ldb.DB(), s.KeyStoreAuthenticator) if err != nil { return s.errorOut(errors.Wrap(err, "fatal error instantiating application")) } // Local shell initialization always uses local auth users table for admin auth authProviderORM := app.BasicAdminUsersORM() - keyStore := app.GetKeyStore() - err = s.KeyStoreAuthenticator.authenticate(rootCtx, keyStore, s.Config.Password()) - if err != nil { - return errors.Wrap(err, "error authenticating keystore") - } legacyEVMChains := app.GetRelayers().LegacyEVMChains() @@ -634,7 +629,7 @@ func (s *Shell) RebroadcastTransactions(c *cli.Context) (err error) { } defer lggr.ErrorIfFn(db.Close, "Error closing db") - app, err := s.AppFactory.NewApplication(ctx, s.Config, lggr, db) + app, err := s.AppFactory.NewApplication(ctx, s.Config, lggr, db, s.KeyStoreAuthenticator) if err != nil { return s.errorOut(errors.Wrap(err, "fatal error instantiating application")) } @@ -1281,7 +1276,7 @@ func (s *Shell) RemoveBlocks(c *cli.Context) error { // From now on, DB locks and DB connection will be released on every return. // Keep watching on logger.Fatal* calls and os.Exit(), because defer will not be executed. - app, err := s.AppFactory.NewApplication(ctx, s.Config, s.Logger, ldb.DB()) + app, err := s.AppFactory.NewApplication(ctx, s.Config, s.Logger, ldb.DB(), s.KeyStoreAuthenticator) if err != nil { return s.errorOut(errors.Wrap(err, "fatal error instantiating application")) } diff --git a/core/cmd/shell_local_test.go b/core/cmd/shell_local_test.go index 79d2b9f07a6..78254c0279e 100644 --- a/core/cmd/shell_local_test.go +++ b/core/cmd/shell_local_test.go @@ -46,7 +46,7 @@ import ( func genTestEVMRelayers(t *testing.T, opts legacyevm.ChainRelayOpts, ks evmrelayer.CSAETHKeystore) *chainlink.CoreRelayerChainInteroperators { f := chainlink.RelayerFactory{ Logger: opts.Logger, - LoopRegistry: plugins.NewLoopRegistry(opts.Logger, opts.AppConfig.Tracing(), opts.AppConfig.Telemetry()), + LoopRegistry: plugins.NewLoopRegistry(opts.Logger, opts.AppConfig.Tracing(), opts.AppConfig.Telemetry(), nil, ""), CapabilitiesRegistry: capabilities.NewRegistry(opts.Logger), } @@ -122,7 +122,7 @@ func TestShell_RunNodeWithPasswords(t *testing.T) { Config: cfg, FallbackAPIInitializer: apiPrompt, Runner: cltest.EmptyRunner{}, - AppFactory: cltest.InstanceAppFactory{App: app}, + AppFactory: cltest.InstanceAppFactoryWithKeystoreMock{App: app}, Logger: lggr, } diff --git a/core/cmd/shell_test.go b/core/cmd/shell_test.go index a93be2fb9ea..13b914ba1c7 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, nil) + reg := plugins.NewLoopRegistry(lggr, nil, 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, nil) + reg := plugins.NewLoopRegistry(lggr, nil, 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/docs/core.toml b/core/config/docs/core.toml index dde898ed3b1..083633ab57f 100644 --- a/core/config/docs/core.toml +++ b/core/config/docs/core.toml @@ -706,6 +706,10 @@ CACertFile = 'cert-file' # Example InsecureConnection = false # Default # TraceSampleRatio is the rate at which to sample traces. Must be between 0 and 1. TraceSampleRatio = 0.01 # Default +# EmitterBatchProcessor enables batching for telemetry events +EmitterBatchProcessor = true # Default +# EmitterExportTimeout sets timeout for exporting telemetry events +EmitterExportTimeout = '1s' # Default # ResourceAttributes are global metadata to include with all telemetry. [Telemetry.ResourceAttributes] diff --git a/core/config/telemetry_config.go b/core/config/telemetry_config.go index 5440e70b43b..e182e95eb6c 100644 --- a/core/config/telemetry_config.go +++ b/core/config/telemetry_config.go @@ -1,5 +1,7 @@ package config +import "time" + type Telemetry interface { Enabled() bool InsecureConnection() bool @@ -7,4 +9,6 @@ type Telemetry interface { OtelExporterGRPCEndpoint() string ResourceAttributes() map[string]string TraceSampleRatio() float64 + EmitterBatchProcessor() bool + EmitterExportTimeout() time.Duration } diff --git a/core/config/toml/types.go b/core/config/toml/types.go index 5246f0861f5..d9302b81fb0 100644 --- a/core/config/toml/types.go +++ b/core/config/toml/types.go @@ -1657,12 +1657,14 @@ func (t *Tracing) ValidateConfig() (err error) { } type Telemetry struct { - Enabled *bool - CACertFile *string - Endpoint *string - InsecureConnection *bool - ResourceAttributes map[string]string `toml:",omitempty"` - TraceSampleRatio *float64 + Enabled *bool + CACertFile *string + Endpoint *string + InsecureConnection *bool + ResourceAttributes map[string]string `toml:",omitempty"` + TraceSampleRatio *float64 + EmitterBatchProcessor *bool + EmitterExportTimeout *commonconfig.Duration } func (b *Telemetry) setFrom(f *Telemetry) { @@ -1684,6 +1686,12 @@ func (b *Telemetry) setFrom(f *Telemetry) { if v := f.TraceSampleRatio; v != nil { b.TraceSampleRatio = v } + if v := f.EmitterBatchProcessor; v != nil { + b.EmitterBatchProcessor = v + } + if v := f.EmitterExportTimeout; v != nil { + b.EmitterExportTimeout = v + } } func (b *Telemetry) ValidateConfig() (err error) { diff --git a/core/internal/cltest/cltest.go b/core/internal/cltest/cltest.go index a858fc1d508..5ff48549490 100644 --- a/core/internal/cltest/cltest.go +++ b/core/internal/cltest/cltest.go @@ -394,7 +394,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, nil) + loopRegistry := plugins.NewLoopRegistry(lggr, nil, nil, nil, "") mercuryPool := wsrpc.NewPool(lggr, cache.Config{ LatestReportTTL: cfg.Mercury().Cache().LatestReportTTL(), @@ -487,7 +487,7 @@ func NewApplicationWithConfig(t testing.TB, cfg chainlink.GeneralConfig, flagsAn RestrictedHTTPClient: c, UnrestrictedHTTPClient: c, SecretGenerator: MockSecretGenerator{}, - LoopRegistry: plugins.NewLoopRegistry(lggr, nil, nil), + LoopRegistry: plugins.NewLoopRegistry(lggr, nil, nil, nil, ""), MercuryPool: mercuryPool, CapabilitiesRegistry: capabilitiesRegistry, CapabilitiesDispatcher: dispatcher, diff --git a/core/internal/cltest/mocks.go b/core/internal/cltest/mocks.go index fd01f72c131..b8bb4657056 100644 --- a/core/internal/cltest/mocks.go +++ b/core/internal/cltest/mocks.go @@ -10,11 +10,11 @@ import ( "testing" "time" + "github.com/jmoiron/sqlx" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/jmoiron/sqlx" - 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/chains/legacyevm" @@ -82,13 +82,27 @@ func (rm *RendererMock) Render(v interface{}, headers ...string) error { return nil } +type InstanceAppFactoryWithKeystoreMock struct { + App chainlink.Application +} + +// NewApplication creates a new application with specified config and calls the authenticate function of the keystore +func (f InstanceAppFactoryWithKeystoreMock) NewApplication(ctx context.Context, cfg chainlink.GeneralConfig, lggr logger.Logger, db *sqlx.DB, ks cmd.TerminalKeyStoreAuthenticator) (chainlink.Application, error) { + keyStore := f.App.GetKeyStore() + err := ks.Authenticate(ctx, keyStore, cfg.Password()) + if err != nil { + return nil, fmt.Errorf("error authenticating keystore: %w", err) + } + return f.App, nil +} + // InstanceAppFactory is an InstanceAppFactory type InstanceAppFactory struct { App chainlink.Application } // NewApplication creates a new application with specified config -func (f InstanceAppFactory) NewApplication(context.Context, chainlink.GeneralConfig, logger.Logger, *sqlx.DB) (chainlink.Application, error) { +func (f InstanceAppFactory) NewApplication(context.Context, chainlink.GeneralConfig, logger.Logger, *sqlx.DB, cmd.TerminalKeyStoreAuthenticator) (chainlink.Application, error) { return f.App, nil } @@ -96,7 +110,7 @@ type seededAppFactory struct { Application chainlink.Application } -func (s seededAppFactory) NewApplication(context.Context, chainlink.GeneralConfig, logger.Logger, *sqlx.DB) (chainlink.Application, error) { +func (s seededAppFactory) NewApplication(context.Context, chainlink.GeneralConfig, logger.Logger, *sqlx.DB, cmd.TerminalKeyStoreAuthenticator) (chainlink.Application, error) { return noopStopApplication{s.Application}, nil } diff --git a/core/scripts/go.mod b/core/scripts/go.mod index 5e71ab7619d..c715a9d360a 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -24,7 +24,7 @@ require ( github.com/prometheus/client_golang v1.20.5 github.com/shopspring/decimal v1.4.0 github.com/smartcontractkit/chainlink-automation v0.8.1 - github.com/smartcontractkit/chainlink-common v0.3.1-0.20241106142051-c7bded1c08ae + github.com/smartcontractkit/chainlink-common v0.3.1-0.20241109002240-af894848b3b4 github.com/smartcontractkit/chainlink/deployment v0.0.0-00010101000000-000000000000 github.com/smartcontractkit/chainlink/v2 v2.0.0-00010101000000-000000000000 github.com/smartcontractkit/libocr v0.0.0-20241007185508-adbe57025f12 @@ -198,7 +198,7 @@ require ( github.com/hashicorp/go-envparse v0.1.0 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect - github.com/hashicorp/go-plugin v1.6.2-0.20240829161738-06afb6d7ae99 // indirect + github.com/hashicorp/go-plugin v1.6.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/golang-lru v0.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect @@ -288,7 +288,7 @@ require ( github.com/shirou/gopsutil/v3 v3.24.3 // indirect github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240926212305-a6deabdfce86 // indirect github.com/smartcontractkit/chain-selectors v1.0.27 // indirect - github.com/smartcontractkit/chainlink-ccip v0.0.0-20241106140121-4c9ee21ab422 // indirect + github.com/smartcontractkit/chainlink-ccip v0.0.0-20241111114733-aa3b2f8e9f94 // indirect github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f // indirect github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241018134907-a00ba3729b5e // indirect github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect diff --git a/core/scripts/go.sum b/core/scripts/go.sum index 3160b2c6c6c..11e1a5d6b88 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -648,8 +648,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-plugin v1.6.2 h1:zdGAEd0V1lCaU0u+MxWQhtSDQmahpkwOun8U8EiRVog= +github.com/hashicorp/go-plugin v1.6.2/go.mod h1:CkgLQ5CZqNmdL9U9JzM532t8ZiYQ35+pj3b1FD37R0Q= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= @@ -1090,10 +1090,10 @@ github.com/smartcontractkit/chain-selectors v1.0.27 h1:VE/ftX9Aae4gnw67yR1raKi+3 github.com/smartcontractkit/chain-selectors v1.0.27/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgBc2xpDKBco/Q4h4ydl6+UUU= github.com/smartcontractkit/chainlink-automation v0.8.1/go.mod h1:Iij36PvWZ6blrdC5A/nrQUBuf3MH3JvsBB9sSyc9W08= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241106140121-4c9ee21ab422 h1:VfH/AW5NtTmroY9zz6OYCPFbFTqpMyJ2ubgT9ahYf3U= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241106140121-4c9ee21ab422/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241106142051-c7bded1c08ae h1:uqce0bjNVYzFrrVLafXgyn8SVNdfOtZekLfAwQihHiA= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241106142051-c7bded1c08ae/go.mod h1:TQ9/KKXZ9vr8QAlUquqGpSvDCpR+DtABKPXZY4CiRns= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241111114733-aa3b2f8e9f94 h1:BeLnOf2KKQpJj9nzfnE7QEg9ZqJ2jy/sbpNYVixVM2Y= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241111114733-aa3b2f8e9f94/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241109002240-af894848b3b4 h1:e+uFsxQ21tMQKRu4oBXKycNzoR30vO/7STBtqtDvQJQ= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241109002240-af894848b3b4/go.mod h1:ny87uTW6hLjCTLiBqBRNFEhETSXhHWevYlPclT5lSco= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f h1:BwrIaQIx5Iy6eT+DfLhFfK2XqjxRm74mVdlX8gbu4dw= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f/go.mod h1:wHtwSR3F1CQSJJZDQKuqaqFYnvkT+kMyget7dl8Clvo= github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241018134907-a00ba3729b5e h1:JiETqdNM0bktAUGMc62COwXIaw3rR3M77Me6bBLG0Fg= diff --git a/core/services/chainlink/application.go b/core/services/chainlink/application.go index 2c918b3a8d8..0b2352f67d4 100644 --- a/core/services/chainlink/application.go +++ b/core/services/chainlink/application.go @@ -294,7 +294,11 @@ 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(), opts.Config.Telemetry()) + beholderAuthHeaders, csaPubKeyHex, err := keystore.BuildBeholderAuth(keyStore) + if err != nil { + return nil, fmt.Errorf("could not build Beholder auth: %w", err) + } + loopRegistry = plugins.NewLoopRegistry(globalLogger, opts.Config.Tracing(), opts.Config.Telemetry(), beholderAuthHeaders, csaPubKeyHex) } // If the audit logger is enabled diff --git a/core/services/chainlink/config.go b/core/services/chainlink/config.go index 476e758ccbb..9f083ef89af 100644 --- a/core/services/chainlink/config.go +++ b/core/services/chainlink/config.go @@ -3,7 +3,9 @@ package chainlink import ( "errors" "fmt" + "slices" + "github.com/imdario/mergo" "go.uber.org/multierr" gotoml "github.com/pelletier/go-toml/v2" @@ -49,40 +51,201 @@ type Config struct { // RawConfigs is a list of RawConfig. type RawConfigs []RawConfig +func (rs *RawConfigs) SetFrom(configs RawConfigs) error { + if err := configs.validateKeys(); err != nil { + return err + } + + for _, config := range configs { + chainID := config.ChainID() + i := slices.IndexFunc(*rs, func(r RawConfig) bool { + otherChainID := r.ChainID() + return otherChainID != "" && chainID == otherChainID + }) + if i != -1 { + if err := (*rs)[i].SetFrom(config); err != nil { + return err + } + } else { + *rs = append(*rs, config) + } + } + + return nil +} + +func (rs RawConfigs) validateKeys() (err error) { + chainIDs := commonconfig.UniqueStrings{} + for i, config := range rs { + chainID := config.ChainID() + if chainIDs.IsDupe(&chainID) { + err = errors.Join(err, commonconfig.NewErrDuplicate(fmt.Sprintf("%d.ChainID", i), chainID)) + } + } + + nodeNames := commonconfig.UniqueStrings{} + for i, config := range rs { + configNodeNames := config.NodeNames() + for j, nodeName := range configNodeNames { + if nodeNames.IsDupe(&nodeName) { + err = errors.Join(err, commonconfig.NewErrDuplicate(fmt.Sprintf("%d.Nodes.%d.Name", i, j), nodeName)) + } + } + } + return +} + +func (rs RawConfigs) ValidateConfig() (err error) { + return rs.validateKeys() +} + // RawConfig is the config used for chains that are not embedded. 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 { +type parsedRawConfig struct { + chainID string + nodesExist bool + nodes []map[string]any + nodeNames []string +} + +func (c RawConfig) parse() (*parsedRawConfig, error) { + var 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 v, ok := (*c)["ChainID"]; ok { - if _, ok := v.(string); !ok { - err = multierr.Append(err, commonconfig.ErrInvalid{Name: "ChainID", Value: v, Msg: "expected string"}) + + parsedRawConfig := &parsedRawConfig{} + chainID, exists := c["ChainID"] + if !exists { + err = multierr.Append(err, commonconfig.ErrMissing{Name: "ChainID", Msg: "required for all chains"}) + } else { + chainIDStr, ok := chainID.(string) + switch { + case !ok: + err = multierr.Append(err, commonconfig.ErrInvalid{Name: "ChainID", Value: chainID, Msg: "expected string"}) + case chainIDStr == "": + err = multierr.Append(err, commonconfig.ErrEmpty{Name: "ChainID", Msg: "required for all chains"}) + default: + parsedRawConfig.chainID = chainIDStr } } - return err + nodes, nodesExist := c["Nodes"] + parsedRawConfig.nodesExist = nodesExist + if nodesExist { + nodeMaps, ok := nodes.([]any) + switch { + case !ok: + err = multierr.Append(err, commonconfig.ErrInvalid{Name: "Nodes", Value: nodes, Msg: "expected array of node configs"}) + default: + for i, node := range nodeMaps { + nodeConfig, ok := node.(map[string]any) + if !ok { + err = multierr.Append(err, commonconfig.ErrInvalid{Name: fmt.Sprintf("Nodes.%d", i), Value: nodeConfig, Msg: "expected node config map"}) + } else { + parsedRawConfig.nodes = append(parsedRawConfig.nodes, nodeConfig) + nodeName, exists := nodeConfig["Name"] + if !exists { + err = multierr.Append(err, commonconfig.ErrMissing{Name: fmt.Sprintf("Nodes.%d.Name", i), Msg: "required for all nodes"}) + } else { + nodeNameStr, ok := nodeName.(string) + switch { + case !ok: + err = multierr.Append(err, commonconfig.ErrInvalid{Name: fmt.Sprintf("Nodes.%d.Name", i), Value: nodeName, Msg: "expected string"}) + case nodeNameStr == "": + err = multierr.Append(err, commonconfig.ErrEmpty{Name: fmt.Sprintf("Nodes.%d.Name", i), Msg: "required for all nodes"}) + default: + parsedRawConfig.nodeNames = append(parsedRawConfig.nodeNames, nodeNameStr) + } + } + } + } + } + } + + return parsedRawConfig, err } -func (c *RawConfig) IsEnabled() bool { - if c == nil { - return false +// ValidateConfig returns an error if the Config is not valid for use, as-is. +func (c RawConfig) ValidateConfig() error { + parsedRawConfig, err := c.parse() + if !parsedRawConfig.nodesExist { + err = multierr.Append(err, commonconfig.ErrMissing{Name: "Nodes", Msg: "expected at least one node"}) + } else if len(parsedRawConfig.nodes) == 0 { + err = multierr.Append(err, commonconfig.ErrEmpty{Name: "Nodes", Msg: "expected at least one node"}) } + return err +} - enabled, ok := (*c)["Enabled"].(bool) +func (c RawConfig) IsEnabled() bool { + enabled, ok := c["Enabled"].(bool) return ok && enabled } -func (c *RawConfig) ChainID() string { - if c == nil { - return "" +func (c RawConfig) ChainID() string { + chainID, _ := c["ChainID"].(string) + return chainID +} + +func (c *RawConfig) SetFrom(config RawConfig) error { + parsedRawConfig, err := c.parse() + if err != nil { + return err } - chainID, _ := (*c)["ChainID"].(string) - return chainID + incomingParsedRawConfig, err := config.parse() + if err != nil { + return err + } + + // Create a copy of config without nodes to merge other fields + configWithoutNodes := make(RawConfig) + for k, v := range config { + if k != "Nodes" { + configWithoutNodes[k] = v + } + } + + // Merge all non-node fields + if err := mergo.Merge(c, configWithoutNodes, mergo.WithOverride); err != nil { + return err + } + + // Handle node merging + for i, nodeConfig := range incomingParsedRawConfig.nodes { + nodeName := incomingParsedRawConfig.nodeNames[i] + i := slices.Index(parsedRawConfig.nodeNames, nodeName) + if i != -1 { + if err := mergo.Merge(&parsedRawConfig.nodes[i], nodeConfig, mergo.WithOverride); err != nil { + return err + } + } else { + parsedRawConfig.nodes = append(parsedRawConfig.nodes, nodeConfig) + } + } + + // Subsequence SetFrom invocations will call parse(), and expect to be able to cast c["Nodes"] to []any, + // so we can't directly assign parsedRawConfig.nodes back to c["Nodes"]. + anyConfigs := []any{} + for _, nodeConfig := range parsedRawConfig.nodes { + anyConfigs = append(anyConfigs, nodeConfig) + } + + (*c)["Nodes"] = anyConfigs + return nil +} + +func (c RawConfig) NodeNames() []string { + nodes, _ := c["Nodes"].([]any) + nodeNames := []string{} + for _, node := range nodes { + config, _ := node.(map[string]any) + nodeName, _ := config["Name"].(string) + nodeNames = append(nodeNames, nodeName) + } + return nodeNames } // TOMLString returns a TOML encoded string. @@ -185,8 +348,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 + if err5 := c.Aptos.SetFrom(f.Aptos); err5 != nil { + err = multierr.Append(err, commonconfig.NamedMultiErrorList(err5, "Aptos")) + } _, err = commonconfig.MultiErrorList(err) diff --git a/core/services/chainlink/config_telemetry.go b/core/services/chainlink/config_telemetry.go index 790f2a19953..125eeed64e5 100644 --- a/core/services/chainlink/config_telemetry.go +++ b/core/services/chainlink/config_telemetry.go @@ -1,7 +1,10 @@ package chainlink import ( + "time" + "github.com/smartcontractkit/chainlink/v2/core/config/toml" + "github.com/smartcontractkit/chainlink/v2/core/static" ) type telemetryConfig struct { @@ -31,8 +34,24 @@ func (b *telemetryConfig) OtelExporterGRPCEndpoint() string { return *b.s.Endpoint } +// ResourceAttributes returns the resource attributes set in the TOML config +// by the user, but first sets OTEL required attributes: +// +// service.name +// service.version +// +// These can be overridden by the TOML if the user so chooses func (b *telemetryConfig) ResourceAttributes() map[string]string { - return b.s.ResourceAttributes + defaults := map[string]string{ + "service.name": "chainlink", + "service.version": static.Version, + } + + for k, v := range b.s.ResourceAttributes { + defaults[k] = v + } + + return defaults } func (b *telemetryConfig) TraceSampleRatio() float64 { @@ -41,3 +60,17 @@ func (b *telemetryConfig) TraceSampleRatio() float64 { } return *b.s.TraceSampleRatio } + +func (b *telemetryConfig) EmitterBatchProcessor() bool { + if b.s.EmitterBatchProcessor == nil { + return false + } + return *b.s.EmitterBatchProcessor +} + +func (b *telemetryConfig) EmitterExportTimeout() time.Duration { + if b.s.EmitterExportTimeout == nil { + return 0 + } + return b.s.EmitterExportTimeout.Duration() +} diff --git a/core/services/chainlink/config_telemetry_test.go b/core/services/chainlink/config_telemetry_test.go new file mode 100644 index 00000000000..d0963129994 --- /dev/null +++ b/core/services/chainlink/config_telemetry_test.go @@ -0,0 +1,142 @@ +package chainlink + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/smartcontractkit/chainlink/v2/core/config/toml" + "github.com/smartcontractkit/chainlink/v2/core/static" +) + +func TestTelemetryConfig_Enabled(t *testing.T) { + trueVal := true + falseVal := false + + tests := []struct { + name string + telemetry toml.Telemetry + expected bool + }{ + {"EnabledTrue", toml.Telemetry{Enabled: &trueVal}, true}, + {"EnabledFalse", toml.Telemetry{Enabled: &falseVal}, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tc := telemetryConfig{s: tt.telemetry} + assert.Equal(t, tt.expected, tc.Enabled()) + }) + } +} + +func TestTelemetryConfig_InsecureConnection(t *testing.T) { + trueVal := true + falseVal := false + + tests := []struct { + name string + telemetry toml.Telemetry + expected bool + }{ + {"InsecureConnectionTrue", toml.Telemetry{InsecureConnection: &trueVal}, true}, + {"InsecureConnectionFalse", toml.Telemetry{InsecureConnection: &falseVal}, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tc := telemetryConfig{s: tt.telemetry} + assert.Equal(t, tt.expected, tc.InsecureConnection()) + }) + } +} + +func TestTelemetryConfig_CACertFile(t *testing.T) { + tests := []struct { + name string + telemetry toml.Telemetry + expected string + }{ + {"CACertFileSet", toml.Telemetry{CACertFile: ptr("test.pem")}, "test.pem"}, + {"CACertFileNil", toml.Telemetry{CACertFile: nil}, ""}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tc := telemetryConfig{s: tt.telemetry} + assert.Equal(t, tt.expected, tc.CACertFile()) + }) + } +} + +func TestTelemetryConfig_OtelExporterGRPCEndpoint(t *testing.T) { + tests := []struct { + name string + telemetry toml.Telemetry + expected string + }{ + {"EndpointSet", toml.Telemetry{Endpoint: ptr("localhost:4317")}, "localhost:4317"}, + {"EndpointNil", toml.Telemetry{Endpoint: nil}, ""}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tc := telemetryConfig{s: tt.telemetry} + assert.Equal(t, tt.expected, tc.OtelExporterGRPCEndpoint()) + }) + } +} + +func TestTelemetryConfig_ResourceAttributes(t *testing.T) { + tests := []struct { + name string + telemetry toml.Telemetry + expected map[string]string + }{ + { + "DefaultAttributes", + toml.Telemetry{ResourceAttributes: nil}, + map[string]string{ + "service.name": "chainlink", + "service.version": static.Version, + }, + }, + { + "CustomAttributes", + toml.Telemetry{ResourceAttributes: map[string]string{"custom.key": "custom.value"}}, + map[string]string{ + "service.name": "chainlink", + "service.version": static.Version, + "custom.key": "custom.value", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tc := telemetryConfig{s: tt.telemetry} + assert.Equal(t, tt.expected, tc.ResourceAttributes()) + }) + } +} + +func TestTelemetryConfig_TraceSampleRatio(t *testing.T) { + tests := []struct { + name string + telemetry toml.Telemetry + expected float64 + }{ + {"TraceSampleRatioSet", toml.Telemetry{TraceSampleRatio: ptrFloat(0.5)}, 0.5}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tc := telemetryConfig{s: tt.telemetry} + assert.InEpsilon(t, tt.expected, tc.TraceSampleRatio(), 0.0001) + }) + } +} + +func ptrFloat(f float64) *float64 { + return &f +} diff --git a/core/services/chainlink/config_test.go b/core/services/chainlink/config_test.go index c6dd58369d0..10a861960ac 100644 --- a/core/services/chainlink/config_test.go +++ b/core/services/chainlink/config_test.go @@ -556,12 +556,14 @@ func TestConfig_Marshal(t *testing.T) { 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), + 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), + EmitterBatchProcessor: ptr(true), + EmitterExportTimeout: commoncfg.MustNewDuration(1 * time.Second), } full.EVM = []*evmcfg.EVMConfig{ { @@ -1520,7 +1522,11 @@ 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: 2 errors: + - 0.Nodes.1.Name: invalid value (primary): duplicate - must be unique + - 0: 2 errors: + - Enabled: invalid value (1): expected bool + - ChainID: missing: required for all chains`}, } { t.Run(tt.name, func(t *testing.T) { var c Config diff --git a/core/services/chainlink/relayer_chain_interoperators_test.go b/core/services/chainlink/relayer_chain_interoperators_test.go index e83c2881c93..a4bd8c168ba 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, nil), + LoopRegistry: plugins.NewLoopRegistry(lggr, nil, nil, nil, ""), GRPCOpts: loop.GRPCOpts{}, CapabilitiesRegistry: capabilities.NewRegistry(lggr), } diff --git a/core/services/chainlink/testdata/config-empty-effective.toml b/core/services/chainlink/testdata/config-empty-effective.toml index 4cfe5e2086c..cd51afac5f8 100644 --- a/core/services/chainlink/testdata/config-empty-effective.toml +++ b/core/services/chainlink/testdata/config-empty-effective.toml @@ -286,3 +286,5 @@ CACertFile = '' Endpoint = '' InsecureConnection = false TraceSampleRatio = 0.01 +EmitterBatchProcessor = true +EmitterExportTimeout = '1s' diff --git a/core/services/chainlink/testdata/config-full.toml b/core/services/chainlink/testdata/config-full.toml index f6cb497f7c8..c5d79dbe5bc 100644 --- a/core/services/chainlink/testdata/config-full.toml +++ b/core/services/chainlink/testdata/config-full.toml @@ -296,6 +296,8 @@ CACertFile = 'cert-file' Endpoint = 'example.com/collector' InsecureConnection = true TraceSampleRatio = 0.01 +EmitterBatchProcessor = true +EmitterExportTimeout = '1s' [Telemetry.ResourceAttributes] Baz = 'test' diff --git a/core/services/chainlink/testdata/config-invalid.toml b/core/services/chainlink/testdata/config-invalid.toml index 411741b1b5b..967ef76de8e 100644 --- a/core/services/chainlink/testdata/config-invalid.toml +++ b/core/services/chainlink/testdata/config-invalid.toml @@ -181,9 +181,15 @@ APIKey = 'key' [[Aptos]] Enabled = 1 +[[Aptos.Nodes]] +Name = 'primary' + +[[Aptos.Nodes]] +Name = 'primary' + [OCR2] Enabled = true [P2P] [P2P.V2] -Enabled = false \ No newline at end of file +Enabled = false diff --git a/core/services/chainlink/testdata/config-multi-chain-effective.toml b/core/services/chainlink/testdata/config-multi-chain-effective.toml index 4d25d23c333..d71ebc4a2d5 100644 --- a/core/services/chainlink/testdata/config-multi-chain-effective.toml +++ b/core/services/chainlink/testdata/config-multi-chain-effective.toml @@ -286,6 +286,8 @@ CACertFile = '' Endpoint = '' InsecureConnection = false TraceSampleRatio = 0.01 +EmitterBatchProcessor = true +EmitterExportTimeout = '1s' [[EVM]] ChainID = '1' diff --git a/core/services/headreporter/prometheus_reporter_test.go b/core/services/headreporter/prometheus_reporter_test.go index ab85bac1ac0..9fd42baa15e 100644 --- a/core/services/headreporter/prometheus_reporter_test.go +++ b/core/services/headreporter/prometheus_reporter_test.go @@ -109,7 +109,7 @@ func newLegacyChainContainer(t *testing.T, db *sqlx.DB) legacyevm.LegacyChainCon config, dbConfig, evmConfig := txmgr.MakeTestConfigs(t) keyStore := cltest.NewKeyStore(t, db).Eth() ethClient := evmtest.NewEthClientMockWithDefaultChain(t) - estimator, err := gas.NewEstimator(logger.TestLogger(t), ethClient, config.ChainType(), evmConfig.GasEstimator(), nil) + estimator, err := gas.NewEstimator(logger.TestLogger(t), ethClient, config.ChainType(), ethClient.ConfiguredChainID(), evmConfig.GasEstimator(), nil) require.NoError(t, err) lggr := logger.TestLogger(t) lpOpts := logpoller.Opts{ diff --git a/core/services/keystore/beholder.go b/core/services/keystore/beholder.go new file mode 100644 index 00000000000..40655cf0e82 --- /dev/null +++ b/core/services/keystore/beholder.go @@ -0,0 +1,19 @@ +package keystore + +import ( + "encoding/hex" + + "github.com/smartcontractkit/chainlink-common/pkg/beholder" +) + +func BuildBeholderAuth(keyStore Master) (authHeaders map[string]string, pubKeyHex string, err error) { + csaKeys, err := keyStore.CSA().GetAll() + if err != nil { + return nil, "", err + } + csaKey := csaKeys[0] + csaPrivKey := csaKey.Raw().Bytes() + authHeaders = beholder.BuildAuthHeaders(csaPrivKey) + pubKeyHex = hex.EncodeToString(csaKey.PublicKey) + return +} diff --git a/core/services/llo/evm/report_codec_premium_legacy.go b/core/services/llo/evm/report_codec_premium_legacy.go index 572340e53f9..9bca9587a0e 100644 --- a/core/services/llo/evm/report_codec_premium_legacy.go +++ b/core/services/llo/evm/report_codec_premium_legacy.go @@ -85,8 +85,8 @@ func (r ReportCodecPremiumLegacy) Encode(ctx context.Context, report llo.Report, rf := v3.ReportFields{ ValidFromTimestamp: report.ValidAfterSeconds + 1, Timestamp: report.ObservationTimestampSeconds, - NativeFee: CalculateFee(nativePrice.Decimal(), opts.BaseUSDFee), - LinkFee: CalculateFee(linkPrice.Decimal(), opts.BaseUSDFee), + NativeFee: CalculateFee(nativePrice, opts.BaseUSDFee), + LinkFee: CalculateFee(linkPrice, opts.BaseUSDFee), ExpiresAt: report.ObservationTimestampSeconds + opts.ExpirationWindow, BenchmarkPrice: quote.Benchmark.Mul(multiplier).BigInt(), Bid: quote.Bid.Mul(multiplier).BigInt(), @@ -124,34 +124,58 @@ func (r ReportCodecPremiumLegacy) Pack(digest types.ConfigDigest, seqNr uint64, return payload, nil } -// TODO: Test this -// MERC-3524 -func ExtractReportValues(report llo.Report) (nativePrice, linkPrice *llo.Decimal, quote *llo.Quote, err error) { +// ExtractReportValues extracts the native price, link price and quote from the report +// Can handle either *Decimal or *Quote types for native/link prices +func ExtractReportValues(report llo.Report) (nativePrice, linkPrice decimal.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) + err = fmt.Errorf("ReportCodecPremiumLegacy requires exactly 3 values (NativePrice, LinkPrice, Quote{Bid, Mid, Ask}); got report.Values: %v", report.Values) + return } - 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]) + nativePrice, err = extractPrice(report.Values[0]) + if err != nil { + err = fmt.Errorf("ReportCodecPremiumLegacy failed to extract native price: %w", err) + return } - 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]) + linkPrice, err = extractPrice(report.Values[1]) + if err != nil { + err = fmt.Errorf("ReportCodecPremiumLegacy failed to extract link price: %w", err) + return } + var is bool 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]) + err = fmt.Errorf("ReportCodecPremiumLegacy expects third stream value to be of type *Quote; got: %T", report.Values[2]) + return + } + if quote == nil { + err = errors.New("ReportCodecPremiumLegacy expects third stream value to be non-nil") + return } return nativePrice, linkPrice, quote, nil } +func extractPrice(price llo.StreamValue) (decimal.Decimal, error) { + switch p := price.(type) { + case *llo.Decimal: + if p == nil { + // Missing price will cause a zero fee + return decimal.Zero, nil + } + return p.Decimal(), nil + case *llo.Quote: + // in case of quote feed, use the benchmark price + if p == nil { + return decimal.Zero, nil + } + return p.Benchmark, nil + + case nil: + return decimal.Zero, nil + default: + return decimal.Zero, fmt.Errorf("expected *Decimal or *Quote; got: %T", price) + } +} + // TODO: Consider embedding the DON ID here? // MERC-3524 var LLOExtraHash = common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001") diff --git a/core/services/llo/evm/report_codec_premium_legacy_test.go b/core/services/llo/evm/report_codec_premium_legacy_test.go index d88a1fc90ce..804555d06be 100644 --- a/core/services/llo/evm/report_codec_premium_legacy_test.go +++ b/core/services/llo/evm/report_codec_premium_legacy_test.go @@ -42,7 +42,7 @@ func Test_ReportCodecPremiumLegacy(t *testing.T) { _, err := rc.Encode(ctx, 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)") + 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: []") }) t.Run("does not encode specimen reports", func(t *testing.T) { @@ -52,7 +52,7 @@ func Test_ReportCodecPremiumLegacy(t *testing.T) { _, err := rc.Encode(ctx, report, cd) require.Error(t, err) - assert.EqualError(t, err, "ReportCodecPremiumLegacy does not support encoding specimen reports") + require.EqualError(t, err, "ReportCodecPremiumLegacy does not support encoding specimen reports") }) t.Run("Encode constructs a report from observations", func(t *testing.T) { @@ -123,13 +123,104 @@ func Test_ReportCodecPremiumLegacy(t *testing.T) { 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") + require.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") + require.EqualError(t, err, "failed to decode report: abi: improperly encoded uint32 value") + }) +} + +type UnhandledStreamValue struct{} + +var _ llo.StreamValue = &UnhandledStreamValue{} + +func (sv *UnhandledStreamValue) MarshalBinary() (data []byte, err error) { return } +func (sv *UnhandledStreamValue) UnmarshalBinary(data []byte) error { return nil } +func (sv *UnhandledStreamValue) MarshalText() (text []byte, err error) { return } +func (sv *UnhandledStreamValue) UnmarshalText(text []byte) error { return nil } +func (sv *UnhandledStreamValue) Type() llo.LLOStreamValue_Type { return 0 } + +func Test_ExtractReportValues(t *testing.T) { + t.Run("with wrong number of stream values", func(t *testing.T) { + report := llo.Report{Values: []llo.StreamValue{llo.ToDecimal(decimal.NewFromInt(35)), llo.ToDecimal(decimal.NewFromInt(36))}} + _, _, _, err := ExtractReportValues(report) + require.EqualError(t, err, "ReportCodecPremiumLegacy requires exactly 3 values (NativePrice, LinkPrice, Quote{Bid, Mid, Ask}); got report.Values: [35 36]") + }) + t.Run("with (nil, nil, nil) values", func(t *testing.T) { + report := llo.Report{Values: []llo.StreamValue{nil, nil, nil}} + _, _, _, err := ExtractReportValues(report) + + require.EqualError(t, err, "ReportCodecPremiumLegacy expects third stream value to be of type *Quote; got: ") + }) + t.Run("with ((*llo.Quote)(nil), nil, (*llo.Quote)(nil)) values", func(t *testing.T) { + report := llo.Report{Values: []llo.StreamValue{(*llo.Quote)(nil), nil, (*llo.Quote)(nil)}} + nativePrice, linkPrice, quote, err := ExtractReportValues(report) + + require.EqualError(t, err, "ReportCodecPremiumLegacy expects third stream value to be non-nil") + assert.Equal(t, decimal.Zero, nativePrice) + assert.Equal(t, decimal.Zero, linkPrice) + assert.Nil(t, quote) + }) + t.Run("with (*llo.Decimal, *llo.Decimal, *llo.Decimal) values", func(t *testing.T) { + report := llo.Report{Values: []llo.StreamValue{llo.ToDecimal(decimal.NewFromInt(35)), llo.ToDecimal(decimal.NewFromInt(36)), llo.ToDecimal(decimal.NewFromInt(37))}} + _, _, _, err := ExtractReportValues(report) + + require.EqualError(t, err, "ReportCodecPremiumLegacy expects third stream value to be of type *Quote; got: *llo.Decimal") + }) + t.Run("with ((*llo.Quote)(nil), nil, *llo.Quote) values", func(t *testing.T) { + report := llo.Report{Values: []llo.StreamValue{(*llo.Quote)(nil), nil, &llo.Quote{Bid: decimal.NewFromInt(37), Benchmark: decimal.NewFromInt(38), Ask: decimal.NewFromInt(39)}}} + nativePrice, linkPrice, quote, err := ExtractReportValues(report) + + require.NoError(t, err) + assert.Equal(t, decimal.Zero, nativePrice) + assert.Equal(t, decimal.Zero, linkPrice) + assert.Equal(t, &llo.Quote{Bid: decimal.NewFromInt(37), Benchmark: decimal.NewFromInt(38), Ask: decimal.NewFromInt(39)}, quote) + }) + t.Run("with unrecognized types", func(t *testing.T) { + report := llo.Report{Values: []llo.StreamValue{&UnhandledStreamValue{}, &UnhandledStreamValue{}, &UnhandledStreamValue{}}} + _, _, _, err := ExtractReportValues(report) + + require.EqualError(t, err, "ReportCodecPremiumLegacy failed to extract native price: expected *Decimal or *Quote; got: *evm.UnhandledStreamValue") + + report = llo.Report{Values: []llo.StreamValue{llo.ToDecimal(decimal.NewFromInt(35)), &UnhandledStreamValue{}, &UnhandledStreamValue{}}} + _, _, _, err = ExtractReportValues(report) + + require.EqualError(t, err, "ReportCodecPremiumLegacy failed to extract link price: expected *Decimal or *Quote; got: *evm.UnhandledStreamValue") + + report = llo.Report{Values: []llo.StreamValue{llo.ToDecimal(decimal.NewFromInt(35)), llo.ToDecimal(decimal.NewFromInt(36)), &UnhandledStreamValue{}}} + _, _, _, err = ExtractReportValues(report) + + require.EqualError(t, err, "ReportCodecPremiumLegacy expects third stream value to be of type *Quote; got: *evm.UnhandledStreamValue") + }) + t.Run("with (*llo.Decimal, *llo.Decimal, *llo.Quote) values", func(t *testing.T) { + report := llo.Report{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)}}} + nativePrice, linkPrice, quote, err := ExtractReportValues(report) + + require.NoError(t, err) + assert.Equal(t, decimal.NewFromInt(35), nativePrice) + assert.Equal(t, decimal.NewFromInt(36), linkPrice) + assert.Equal(t, &llo.Quote{Bid: decimal.NewFromInt(37), Benchmark: decimal.NewFromInt(38), Ask: decimal.NewFromInt(39)}, quote) + }) + t.Run("with (*llo.Quote, *llo.Quote, *llo.Quote) values", func(t *testing.T) { + report := llo.Report{Values: []llo.StreamValue{&llo.Quote{Bid: decimal.NewFromInt(35), Benchmark: decimal.NewFromInt(36), Ask: decimal.NewFromInt(37)}, &llo.Quote{Bid: decimal.NewFromInt(38), Benchmark: decimal.NewFromInt(39), Ask: decimal.NewFromInt(40)}, &llo.Quote{Bid: decimal.NewFromInt(41), Benchmark: decimal.NewFromInt(42), Ask: decimal.NewFromInt(43)}}} + nativePrice, linkPrice, quote, err := ExtractReportValues(report) + + require.NoError(t, err) + assert.Equal(t, decimal.NewFromInt(36), nativePrice) + assert.Equal(t, decimal.NewFromInt(39), linkPrice) + assert.Equal(t, &llo.Quote{Bid: decimal.NewFromInt(41), Benchmark: decimal.NewFromInt(42), Ask: decimal.NewFromInt(43)}, quote) + }) + t.Run("with (nil, nil, *llo.Quote) values", 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)}}} + nativePrice, linkPrice, quote, err := ExtractReportValues(report) + + require.NoError(t, err) + assert.Equal(t, decimal.Zero, nativePrice) + assert.Equal(t, decimal.Zero, linkPrice) + assert.Equal(t, &llo.Quote{Bid: decimal.NewFromInt(37), Benchmark: decimal.NewFromInt(38), Ask: decimal.NewFromInt(39)}, quote) }) } diff --git a/core/services/ocr2/plugins/ccip/internal/rpclib/evm_test.go b/core/services/ocr2/plugins/ccip/internal/rpclib/evm_test.go index 1a3d7baf0fc..46b41723af3 100644 --- a/core/services/ocr2/plugins/ccip/internal/rpclib/evm_test.go +++ b/core/services/ocr2/plugins/ccip/internal/rpclib/evm_test.go @@ -116,7 +116,7 @@ func TestDefaultEvmBatchCaller_batchCallLimit(t *testing.T) { {numCalls: 10, batchSize: 100, parallelRpcCallsLimit: 10}, {numCalls: 1, batchSize: 100, parallelRpcCallsLimit: 10}, {numCalls: 1000, batchSize: 10, parallelRpcCallsLimit: 2}, - {numCalls: rand.Uint() % 1000, batchSize: rand.Uint() % 500, parallelRpcCallsLimit: rand.Uint() % 500}, + {numCalls: 1 + rand.Uint()%1000, batchSize: 1 + rand.Uint()%500, parallelRpcCallsLimit: 1 + rand.Uint()%500}, } for _, tc := range testCases { diff --git a/core/services/ocr2/plugins/ccip/testhelpers/integration/chainlink.go b/core/services/ocr2/plugins/ccip/testhelpers/integration/chainlink.go index 0b7f0de4d25..b34aab8decd 100644 --- a/core/services/ocr2/plugins/ccip/testhelpers/integration/chainlink.go +++ b/core/services/ocr2/plugins/ccip/testhelpers/integration/chainlink.go @@ -460,7 +460,10 @@ func setupNodeCCIP( }, CSAETHKeystore: simEthKeyStore, } - loopRegistry := plugins.NewLoopRegistry(lggr.Named("LoopRegistry"), config.Tracing(), config.Telemetry()) + beholderAuthHeaders, csaPubKeyHex, err := keystore.BuildBeholderAuth(keyStore) + require.NoError(t, err) + + loopRegistry := plugins.NewLoopRegistry(lggr.Named("LoopRegistry"), config.Tracing(), config.Telemetry(), beholderAuthHeaders, csaPubKeyHex) relayerFactory := chainlink.RelayerFactory{ Logger: lggr, LoopRegistry: loopRegistry, @@ -490,7 +493,7 @@ func setupNodeCCIP( RestrictedHTTPClient: &http.Client{}, AuditLogger: audit.NoopLogger, MailMon: mailMon, - LoopRegistry: plugins.NewLoopRegistry(lggr, config.Tracing(), config.Telemetry()), + LoopRegistry: plugins.NewLoopRegistry(lggr, config.Tracing(), config.Telemetry(), beholderAuthHeaders, csaPubKeyHex), }) 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 b897d565bae..4118f158210 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 @@ -455,7 +455,11 @@ func setupNodeCCIP( }, CSAETHKeystore: simEthKeyStore, } - loopRegistry := plugins.NewLoopRegistry(lggr.Named("LoopRegistry"), config.Tracing(), config.Telemetry()) + + beholderAuthHeaders, csaPubKeyHex, err := keystore.BuildBeholderAuth(keyStore) + require.NoError(t, err) + + loopRegistry := plugins.NewLoopRegistry(lggr.Named("LoopRegistry"), config.Tracing(), config.Telemetry(), beholderAuthHeaders, csaPubKeyHex) relayerFactory := chainlink.RelayerFactory{ Logger: lggr, LoopRegistry: loopRegistry, @@ -485,7 +489,7 @@ func setupNodeCCIP( RestrictedHTTPClient: &http.Client{}, AuditLogger: audit.NoopLogger, MailMon: mailMon, - LoopRegistry: plugins.NewLoopRegistry(lggr, config.Tracing(), config.Telemetry()), + LoopRegistry: plugins.NewLoopRegistry(lggr, config.Tracing(), config.Telemetry(), beholderAuthHeaders, csaPubKeyHex), }) ctx := testutils.Context(t) require.NoError(t, err) diff --git a/core/services/registrysyncer/syncer.go b/core/services/registrysyncer/syncer.go index c0d2a60b47e..5fc241ad249 100644 --- a/core/services/registrysyncer/syncer.go +++ b/core/services/registrysyncer/syncer.go @@ -202,7 +202,7 @@ func (s *registrySyncer) updateStateLoop() { } } -func (s *registrySyncer) localRegistry(ctx context.Context) (*LocalRegistry, error) { +func (s *registrySyncer) importOnchainRegistry(ctx context.Context) (*LocalRegistry, error) { caps := []kcr.CapabilitiesRegistryCapabilityInfo{} err := s.reader.GetLatestValue(ctx, s.capabilitiesContract.ReadIdentifier("getCapabilities"), primitives.Unconfirmed, nil, &caps) @@ -288,33 +288,33 @@ func (s *registrySyncer) Sync(ctx context.Context, isInitialSync bool) error { s.reader = reader } - var lr *LocalRegistry + var latestRegistry *LocalRegistry var err error if isInitialSync { s.lggr.Debug("syncing with local registry") - lr, err = s.orm.LatestLocalRegistry(ctx) + latestRegistry, 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 + latestRegistry.lggr = s.lggr + latestRegistry.getPeerID = s.getPeerID } } - if lr == nil { + if latestRegistry == nil { s.lggr.Debug("syncing with remote registry") - localRegistry, err := s.localRegistry(ctx) + importedRegistry, err := s.importOnchainRegistry(ctx) if err != nil { return fmt.Errorf("failed to sync with remote registry: %w", err) } - lr = localRegistry + latestRegistry = importedRegistry // 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: + case s.updateChan <- latestRegistry: // Successfully sent state s.lggr.Debug("remote registry update triggered successfully") default: @@ -324,7 +324,7 @@ func (s *registrySyncer) Sync(ctx context.Context, isInitialSync bool) error { } for _, h := range s.launchers { - lrCopy := deepCopyLocalRegistry(lr) + lrCopy := deepCopyLocalRegistry(latestRegistry) if err := h.Launch(ctx, &lrCopy); err != nil { s.lggr.Errorf("error calling launcher: %s", err) s.metrics.incrementLauncherFailureCounter(ctx) diff --git a/core/services/relay/evm/evm.go b/core/services/relay/evm/evm.go index 7c380211ea0..7c070cc282d 100644 --- a/core/services/relay/evm/evm.go +++ b/core/services/relay/evm/evm.go @@ -431,12 +431,16 @@ func (r *Relayer) NewMercuryProvider(ctx context.Context, rargs commontypes.Rela if r.capabilitiesRegistry == nil { lggr.Errorw("trigger capability is enabled but capabilities registry is not set") } else { - r.triggerCapability = triggers.NewMercuryTriggerService(0, lggr) - if err := r.triggerCapability.Start(ctx); err != nil { - return nil, err + var err2 error + r.triggerCapability, err2 = triggers.NewMercuryTriggerService(0, relayConfig.TriggerCapabilityName, relayConfig.TriggerCapabilityVersion, lggr) + if err2 != nil { + return nil, fmt.Errorf("failed to start required trigger service: %w", err2) + } + if err2 = r.triggerCapability.Start(ctx); err2 != nil { + return nil, err2 } - if err := r.capabilitiesRegistry.Add(ctx, r.triggerCapability); err != nil { - return nil, err + if err2 = r.capabilitiesRegistry.Add(ctx, r.triggerCapability); err2 != nil { + return nil, err2 } lggr.Infow("successfully added trigger service to the Registry") } @@ -838,6 +842,7 @@ func (r *Relayer) NewChainWriter(_ context.Context, config []byte) (commontypes. return nil, fmt.Errorf("failed to unmarshall chain writer config err: %s", err) } + cfg.MaxGasPrice = r.chain.Config().EVM().GasEstimator().PriceMax() return NewChainWriterService(r.lggr, r.chain.Client(), r.chain.TxManager(), r.chain.GasEstimator(), cfg) } diff --git a/core/services/relay/evm/mercury/transmitter_test.go b/core/services/relay/evm/mercury/transmitter_test.go index a28d4cd4ded..70ee8fca74f 100644 --- a/core/services/relay/evm/mercury/transmitter_test.go +++ b/core/services/relay/evm/mercury/transmitter_test.go @@ -96,11 +96,12 @@ func Test_MercuryTransmitter_Transmit(t *testing.T) { report := sampleV3Report c := &mocks.MockWSRPCClient{} clients[sURL] = c - triggerService := triggers.NewMercuryTriggerService(0, lggr) + triggerService, err := triggers.NewMercuryTriggerService(0, "", "", lggr) + require.NoError(t, err) mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, benchmarkPriceDecoder, triggerService) // init the queue since we skipped starting transmitter mt.servers[sURL].q.Init([]*Transmission{}) - err := mt.Transmit(testutils.Context(t), sampleReportContext, report, sampleSigs) + err = mt.Transmit(testutils.Context(t), sampleReportContext, report, sampleSigs) require.NoError(t, err) // queue is empty require.Equal(t, mt.servers[sURL].q.(*transmitQueue).pq.Len(), 0) diff --git a/core/services/relay/evm/types/types.go b/core/services/relay/evm/types/types.go index a96dd9f9508..e1a61098be5 100644 --- a/core/services/relay/evm/types/types.go +++ b/core/services/relay/evm/types/types.go @@ -207,8 +207,10 @@ type RelayConfig struct { SendingKeys pq.StringArray `json:"sendingKeys"` // Mercury-specific - FeedID *common.Hash `json:"feedID"` - EnableTriggerCapability bool `json:"enableTriggerCapability"` + FeedID *common.Hash `json:"feedID"` + EnableTriggerCapability bool `json:"enableTriggerCapability"` + TriggerCapabilityName string `json:"triggerCapabilityName"` + TriggerCapabilityVersion string `json:"triggerCapabilityVersion"` // LLO-specific LLODONID uint32 `json:"lloDonID" toml:"lloDonID"` diff --git a/core/services/standardcapabilities/standard_capabilities.go b/core/services/standardcapabilities/standard_capabilities.go index fe3dad7bb2f..76ca5fa1952 100644 --- a/core/services/standardcapabilities/standard_capabilities.go +++ b/core/services/standardcapabilities/standard_capabilities.go @@ -3,6 +3,8 @@ package standardcapabilities import ( "context" "fmt" + "sync" + "time" "github.com/smartcontractkit/chainlink-common/pkg/loop" "github.com/smartcontractkit/chainlink-common/pkg/services" @@ -12,6 +14,8 @@ import ( "github.com/smartcontractkit/chainlink/v2/plugins" ) +const defaultStartTimeout = 3 * time.Minute + type standardCapabilities struct { services.StateMachine log logger.Logger @@ -26,6 +30,10 @@ type standardCapabilities struct { oracleFactory core.OracleFactory capabilitiesLoop *loop.StandardCapabilitiesService + + wg sync.WaitGroup + stopChan services.StopChan + startTimeout time.Duration } func newStandardCapabilities( @@ -51,6 +59,7 @@ func newStandardCapabilities( pipelineRunner: pipelineRunner, relayerSet: relayerSet, oracleFactory: oracleFactory, + stopChan: make(chan struct{}), } } @@ -63,38 +72,53 @@ func (s *standardCapabilities) Start(ctx context.Context) error { Cmd: cmdName, Env: nil, }) - if err != nil { return fmt.Errorf("error registering loop: %v", err) } s.capabilitiesLoop = loop.NewStandardCapabilitiesService(s.log, opts, cmdFn) - if err = s.capabilitiesLoop.Start(ctx); err != nil { return fmt.Errorf("error starting standard capabilities service: %v", err) } - if err = s.capabilitiesLoop.WaitCtx(ctx); err != nil { - return fmt.Errorf("error waiting for standard capabilities service to start: %v", err) - } + s.wg.Add(1) + go func() { + defer s.wg.Done() - if err = s.capabilitiesLoop.Service.Initialise(ctx, s.spec.Config, s.telemetryService, s.store, s.CapabilitiesRegistry, s.errorLog, - s.pipelineRunner, s.relayerSet, s.oracleFactory); err != nil { - return fmt.Errorf("error initialising standard capabilities service: %v", err) - } + if s.startTimeout == 0 { + s.startTimeout = defaultStartTimeout + } - capabilityInfos, err := s.capabilitiesLoop.Service.Infos(ctx) - if err != nil { - return fmt.Errorf("error getting standard capabilities service info: %v", err) - } + cctx, cancel := s.stopChan.CtxWithTimeout(s.startTimeout) + defer cancel() + + if err = s.capabilitiesLoop.WaitCtx(cctx); err != nil { + s.log.Errorf("error waiting for standard capabilities service to start: %v", err) + return + } + + if err = s.capabilitiesLoop.Service.Initialise(cctx, s.spec.Config, s.telemetryService, s.store, s.CapabilitiesRegistry, s.errorLog, + s.pipelineRunner, s.relayerSet, s.oracleFactory); err != nil { + s.log.Errorf("error initialising standard capabilities service: %v", err) + return + } + + capabilityInfos, err := s.capabilitiesLoop.Service.Infos(cctx) + if err != nil { + s.log.Errorf("error getting standard capabilities service info: %v", err) + return + } - s.log.Info("Started standard capabilities for job spec", "spec", s.spec, "capabilities", capabilityInfos) + s.log.Info("Started standard capabilities for job spec", "spec", s.spec, "capabilities", capabilityInfos) + }() return nil }) } func (s *standardCapabilities) Close() error { + close(s.stopChan) + s.wg.Wait() return s.StopOnce("StandardCapabilities", func() error { if s.capabilitiesLoop != nil { return s.capabilitiesLoop.Close() diff --git a/core/services/standardcapabilities/standard_capabilities_test.go b/core/services/standardcapabilities/standard_capabilities_test.go new file mode 100644 index 00000000000..538e08c65ad --- /dev/null +++ b/core/services/standardcapabilities/standard_capabilities_test.go @@ -0,0 +1,99 @@ +package standardcapabilities + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/loop" + "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types/core" + "github.com/smartcontractkit/chainlink-common/pkg/types/core/mocks" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/job" + "github.com/smartcontractkit/chainlink/v2/plugins" +) + +func TestStandardCapabilityStart(t *testing.T) { + t.Run("NOK-not_found_binary_does_not_block", func(t *testing.T) { + ctx := tests.Context(t) + lggr := logger.TestLogger(t) + + pluginRegistrar := plugins.NewRegistrarConfig(loop.GRPCOpts{}, func(name string) (*plugins.RegisteredLoop, error) { return &plugins.RegisteredLoop{}, nil }, func(loopId string) {}) + registry := mocks.NewCapabilitiesRegistry(t) + + spec := &job.StandardCapabilitiesSpec{ + Command: "not/found/path/to/binary", + OracleFactory: job.OracleFactoryConfig{ + Enabled: true, + BootstrapPeers: []string{ + "12D3KooWEBVwbfdhKnicois7FTYVsBFGFcoMhMCKXQC57BQyZMhz@localhost:6690", + }, + OCRContractAddress: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6", + ChainID: "31337", + Network: "evm", + }} + + standardCapability := newStandardCapabilities(lggr, spec, pluginRegistrar, &telemetryServiceMock{}, &kvstoreMock{}, registry, &errorLogMock{}, &pipelineRunnerServiceMock{}, &relayerSetMock{}, &oracleFactoryMock{}) + standardCapability.startTimeout = 1 * time.Second + err := standardCapability.Start(ctx) + require.NoError(t, err) + + standardCapability.wg.Wait() + }) +} + +type telemetryServiceMock struct{} + +func (t *telemetryServiceMock) Send(ctx context.Context, network string, chainID string, contractID string, telemetryType string, payload []byte) error { + return nil +} + +type kvstoreMock struct{} + +func (k *kvstoreMock) Store(ctx context.Context, key string, val []byte) error { + return nil +} +func (k *kvstoreMock) Get(ctx context.Context, key string) ([]byte, error) { + return nil, nil +} + +type errorLogMock struct{} + +func (e *errorLogMock) SaveError(ctx context.Context, msg string) error { + return nil +} + +type relayerSetMock struct{} + +func (r *relayerSetMock) Get(ctx context.Context, relayID types.RelayID) (core.Relayer, error) { + return nil, nil +} +func (r *relayerSetMock) List(ctx context.Context, relayIDs ...types.RelayID) (map[types.RelayID]core.Relayer, error) { + return nil, nil +} + +type pipelineRunnerServiceMock struct{} + +func (p *pipelineRunnerServiceMock) ExecuteRun(ctx context.Context, spec string, vars core.Vars, options core.Options) (core.TaskResults, error) { + return nil, nil +} + +type oracleFactoryMock struct{} + +func (o *oracleFactoryMock) NewOracle(ctx context.Context, args core.OracleArgs) (core.Oracle, error) { + return &oracleMock{}, nil +} + +type oracleMock struct{} + +func (o *oracleMock) Start(ctx context.Context) error { + return nil +} +func (o *oracleMock) Close(ctx context.Context) error { + return nil +} diff --git a/core/services/workflows/engine.go b/core/services/workflows/engine.go index 0d5bfefcbd3..8e2cb8e34cb 100644 --- a/core/services/workflows/engine.go +++ b/core/services/workflows/engine.go @@ -358,7 +358,7 @@ var ( ) func (e *Engine) resumeInProgressExecutions(ctx context.Context) error { - wipExecutions, err := e.executionStates.GetUnfinished(ctx, defaultOffset, defaultLimit) + wipExecutions, err := e.executionStates.GetUnfinished(ctx, e.workflow.id, defaultOffset, defaultLimit) if err != nil { return err } diff --git a/core/services/workflows/engine_test.go b/core/services/workflows/engine_test.go index e283a2b700c..5e87d4f7603 100644 --- a/core/services/workflows/engine_test.go +++ b/core/services/workflows/engine_test.go @@ -37,7 +37,6 @@ import ( 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" - "github.com/smartcontractkit/chainlink/v2/core/services/workflows/syncer" ) const testWorkflowId = "" @@ -151,6 +150,12 @@ func newTestEngineWithYAMLSpec(t *testing.T, reg *coreCap.Registry, spec string, return newTestEngine(t, reg, sdkSpec, opts...) } +type mockSecretsFetcher struct{} + +func (s mockSecretsFetcher) SecretsFor(workflowOwner, workflowName string) (map[string]string, error) { + return map[string]string{}, nil +} + // newTestEngine creates a new engine with some test defaults. func newTestEngine(t *testing.T, reg *coreCap.Registry, sdkSpec sdk.WorkflowSpec, opts ...func(c *Config)) (*Engine, *testHooks) { initFailed := make(chan struct{}) @@ -176,7 +181,7 @@ func newTestEngine(t *testing.T, reg *coreCap.Registry, sdkSpec sdk.WorkflowSpec onExecutionFinished: func(weid string) { executionFinished <- weid }, - SecretsFetcher: syncer.NewWorkflowRegistry(), + SecretsFetcher: mockSecretsFetcher{}, clock: clock, } for _, o := range opts { diff --git a/core/services/workflows/store/store.go b/core/services/workflows/store/store.go index 9f77cf3380e..19d8a881192 100644 --- a/core/services/workflows/store/store.go +++ b/core/services/workflows/store/store.go @@ -9,7 +9,7 @@ type Store interface { UpsertStep(ctx context.Context, step *WorkflowExecutionStep) (WorkflowExecution, error) UpdateStatus(ctx context.Context, executionID string, status string) error Get(ctx context.Context, executionID string) (WorkflowExecution, error) - GetUnfinished(ctx context.Context, offset, limit int) ([]WorkflowExecution, error) + GetUnfinished(ctx context.Context, workflowID string, offset, limit int) ([]WorkflowExecution, error) } var _ Store = (*DBStore)(nil) diff --git a/core/services/workflows/store/store_db.go b/core/services/workflows/store/store_db.go index a070062aee6..926dd06d09b 100644 --- a/core/services/workflows/store/store_db.go +++ b/core/services/workflows/store/store_db.go @@ -29,12 +29,12 @@ type DBStore struct { // `workflowExecutionRow` describes a row // of the `workflow_executions` table type workflowExecutionRow struct { - ID string - WorkflowID *string - Status string - CreatedAt *time.Time - UpdatedAt *time.Time - FinishedAt *time.Time + ID string `db:"id"` + WorkflowID *string `db:"workflow_id"` + Status string `db:"status"` + CreatedAt *time.Time `db:"created_at"` + UpdatedAt *time.Time `db:"updated_at"` + FinishedAt *time.Time `db:"finished_at"` } // `workflowStepRow` describes a row @@ -362,7 +362,7 @@ func (d *DBStore) transact(ctx context.Context, fn func(*DBStore) error) error { ) } -func (d *DBStore) GetUnfinished(ctx context.Context, offset, limit int) ([]WorkflowExecution, error) { +func (d *DBStore) GetUnfinished(ctx context.Context, workflowID string, offset, limit int) ([]WorkflowExecution, error) { sql := ` SELECT workflow_steps.workflow_execution_id AS ws_workflow_execution_id, @@ -382,12 +382,13 @@ func (d *DBStore) GetUnfinished(ctx context.Context, offset, limit int) ([]Workf JOIN workflow_steps ON workflow_steps.workflow_execution_id = workflow_executions.id WHERE workflow_executions.status = $1 + AND workflow_executions.workflow_id = $2 ORDER BY workflow_executions.created_at DESC - LIMIT $2 - OFFSET $3 + LIMIT $3 + OFFSET $4 ` var joinRecords []workflowExecutionWithStep - err := d.db.SelectContext(ctx, &joinRecords, sql, StatusStarted, limit, offset) + err := d.db.SelectContext(ctx, &joinRecords, sql, StatusStarted, workflowID, limit, offset) if err != nil { return []WorkflowExecution{}, err } diff --git a/core/services/workflows/store/store_db_test.go b/core/services/workflows/store/store_db_test.go index 1b58f816e59..03e2e1d55fa 100644 --- a/core/services/workflows/store/store_db_test.go +++ b/core/services/workflows/store/store_db_test.go @@ -12,13 +12,18 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" "github.com/smartcontractkit/chainlink-common/pkg/values" + "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" ) func randomID() string { - b := make([]byte, 32) + return random(32) +} + +func random(length int) string { + b := make([]byte, length) _, err := rand.Read(b) if err != nil { panic(err) @@ -234,10 +239,24 @@ func Test_StoreDB_WorkflowStepStatus(t *testing.T) { } } +func createWorkflow(t *testing.T, store *DBStore, id string) { + 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())` + var wfSpec job.WorkflowSpec + wfSpec.Workflow = "" + wfSpec.WorkflowID = id + wfSpec.WorkflowOwner = random(20) + wfSpec.WorkflowName = random(20) + _, err := store.db.NamedExecContext(tests.Context(t), sql, wfSpec) + require.NoError(t, err) +} + func Test_StoreDB_GetUnfinishedSteps(t *testing.T) { store := newTestDBStore(t) id := randomID() + wid := randomID() + createWorkflow(t, store, wid) stepOne := &WorkflowExecutionStep{ ExecutionID: id, Ref: "step1", @@ -254,12 +273,29 @@ func Test_StoreDB_GetUnfinishedSteps(t *testing.T) { "step2": stepTwo, }, ExecutionID: id, + WorkflowID: wid, Status: StatusStarted, } _, err := store.Add(tests.Context(t), &es) require.NoError(t, err) + id2 := randomID() + wid2 := randomID() + createWorkflow(t, store, wid2) + es2 := WorkflowExecution{ + Steps: map[string]*WorkflowExecutionStep{ + "step1": stepOne, + "step2": stepTwo, + }, + ExecutionID: id2, + WorkflowID: wid2, + Status: StatusStarted, + } + + _, err = store.Add(tests.Context(t), &es2) + require.NoError(t, err) + id = randomID() esTwo := WorkflowExecution{ ExecutionID: id, @@ -269,7 +305,7 @@ func Test_StoreDB_GetUnfinishedSteps(t *testing.T) { _, err = store.Add(tests.Context(t), &esTwo) require.NoError(t, err) - states, err := store.GetUnfinished(tests.Context(t), 0, 100) + states, err := store.GetUnfinished(tests.Context(t), wid, 0, 100) require.NoError(t, err) assert.Len(t, states, 1) diff --git a/core/web/assets/index.html b/core/web/assets/index.html index 8c19349705c..b4327b8f308 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 275f95a017f..4d4296b8676 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.73737bcc031c687ddea1.js b/core/web/assets/main.1d1632f9bf7627b5ea5e.js similarity index 90% rename from core/web/assets/main.73737bcc031c687ddea1.js rename to core/web/assets/main.1d1632f9bf7627b5ea5e.js index db7222e7664..6707f6d377a 100644 --- a/core/web/assets/main.73737bcc031c687ddea1.js +++ b/core/web/assets/main.1d1632f9bf7627b5ea5e.js @@ -171,4 +171,4 @@ object-assign */ 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;nAu});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)))};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 disabledAt\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"},activeIcon:{color:xh.default[500]},inactiveIcon:{color:xp.default[500]},text:{marginLeft:4}})},xv=(0,b.withStyles)(xg)(function(e){var t=e.isActive,n=e.activeText,r=e.inactiveText,i=e.classes;return l.createElement("div",{className:i.root},t?l.createElement(xm.Z,{fontSize:"small",className:i.activeIcon}):l.createElement(xb.Z,{fontSize:"small",className:i.inactiveIcon}),l.createElement(x.default,{variant:"body1",inline:!0,className:i.text},t?n:r))}),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,{isActive:t.isConnectionActive,activeText:"Connected",inactiveText:"Disconnected"})),l.createElement(r7.default,null,l.createElement(xv,{isActive:!t.disabledAt,activeText:"Enabled",inactiveText:"Disabled"})),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,"Connection Status"),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=function(){return(0,b.createStyles)({root:{fontSize:24}})},xQ=(0,b.withStyles)(xJ)(function(e){var t=e.children,n=e.classes;return l.createElement(x.default,{variant:"h2",className:n.root},t)}),x1=n(9290),x0=n(74923);function x2(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);n=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}function TS(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 Tk(e,t){return Tv(e)||Tw(e,t)||Tx(e,t)||T_()}function Tx(e,t){if(e){if("string"==typeof e)return Tg(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 Tg(e,t)}}var TT=function(e){return"SN_MAIN"===e||"SN_SEPOLIA"===e},TM=bv().shape({chainID:p0().required("Required"),chainType:p0().required("Required"),accountAddr:p0().required("Required"),accountAddrPubKey:p0().nullable(),adminAddr:p0(),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()}),TO=function(e){return(0,b.createStyles)({supportedJobOptionsPaper:{padding:2*e.spacing.unit}})},TA=function(e){var t=e.addresses,n=TE(e,["addresses"]),r=h_(),i=r.values,a=i.chainID,o=i.accountAddr,s=r.setFieldValue,u=Tk(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,!TT(a)&&l.createElement(hP,Ty({},n,{select:!0,value:c?"custom":o,onChange:h}),t.map(function(e){return l.createElement(tE.default,{key:e,value:e},e)})),TT(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}),TT(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"}})))},TL=(0,b.withStyles)(TO)(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.chains,u=void 0===s?[]:s,c=e.accountsEVM,f=void 0===c?[]:c,h=e.accountsAptos,p=void 0===h?[]:h,b=e.p2pKeys,m=void 0===b?[]:b,g=e.ocrKeys,v=void 0===g?[]:g,y=e.ocr2Keys,w=void 0===y?[]:y,_=e.showSubmit,E=void 0!==_&&_;return l.createElement(hT,{innerRef:i,initialValues:a,validationSchema:TM,onSubmit:o},function(e){var n=e.values,i=[];return n.chainType===Tm.EVM&&(i=f.filter(function(e){return e.chain.id==n.chainID&&!e.isDisabled}).map(function(e){return e.address})),n.chainType===Tm.APTOS&&(i=p.map(function(e){return e.account})),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:r},l.createElement(tE.default,{key:Tm.EVM,value:Tm.EVM},"EVM"),l.createElement(tE.default,{key:Tm.APTOS,value:Tm.APTOS},"APTOS"))),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.filter(function(e){return e.network.toUpperCase()===n.chainType}).map(function(e){return l.createElement(tE.default,{key:e.id,value:e.id},e.id)}))),l.createElement(d.Z,{item:!0,xs:12,md:6},l.createElement(TA,{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",addresses: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",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"}},m.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"}},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(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"}},m.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"}},w.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"}}))))))),E&&l.createElement(d.Z,{item:!0,xs:12,md:7},l.createElement(ok.default,{variant:"contained",color:"primary",type:"submit",size:"large"},"Submit"))))})});function TC(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function TI(){var e=TC(["\n fragment AptosKeysPayload_ResultsFields on AptosKey {\n account\n id\n }\n"]);return TI=function(){return e},e}function TD(){var e=TC(["\n ","\n query FetchAptosKeys {\n aptosKeys {\n results {\n ...AptosKeysPayload_ResultsFields\n }\n }\n }\n"]);return TD=function(){return e},e}var TN=n0(TI()),TP=n0(TD(),TN),TR=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return rv(TP,e)},Tj=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=TR({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,d={chainID:"",chainType:Tm.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:""},h=a?a.chains.results:[],p=o?o.ethKeys.results:[],b=s?s.aptosKeys.results:[],m=u?u.p2pKeys.results:[],g=c?c.ocrKeyBundles.results:[],v=f?f.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(TL,{innerRef:i,initialValues:d,onSubmit:r,chains:h,accountsEVM:p,accountsAptos:b,p2pKeys:m,ocrKeys:g,ocr2Keys:v})),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")))},TF=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=TR({fetchPolicy:"cache-and-network"}).data,c=SV({fetchPolicy:"cache-and-network"}).data,f=EO({fetchPolicy:"cache-and-network"}).data,d=E2({fetchPolicy:"cache-and-network"}).data;if(!t)return null;var h={chainID:t.chainID,chainType:Tm.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},p=o?o.chains.results:[],b=s?s.ethKeys.results:[],m=u?u.aptosKeys.results:[],g=c?c.p2pKeys.results:[],v=f?f.ocrKeyBundles.results:[],y=d?d.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(TL,{innerRef:a,initialValues:h,onSubmit:i,chains:p,accountsEVM:b,accountsAptos:m,p2pKeys:g,ocrKeys:v,ocr2Keys:y,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 TY(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&&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 MV(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?MQ[c.action].title:"",body:c?MQ[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(MF,{open:h,onClose:function(){return p(!1)},initialValues:{definition:m.definition,id:m.id},onSubmit:a}))});function M0(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function M2(){var e=M0(["\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 M2=function(){return e},e}var M3=n0(M2(),MX),M4=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(MI,{proposal:a}),l.createElement(d.Z,{container:!0,spacing:32},l.createElement(d.Z,{item:!0,xs:9},l.createElement(xQ,null,"Specs"))),l.createElement(d.Z,{container:!0,spacing:32},l.createElement(d.Z,{item:!0,xs:12},l.createElement(M1,{proposal:a,specs:a.specs,onReject:r,onApprove:t,onCancel:n,onUpdateSpec:i}))))};function M6(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"],DisableFeedsManagerPayload:["DisableFeedsManagerSuccess","NotFoundError"],DismissJobErrorPayload:["DismissJobErrorSuccess","NotFoundError"],EnableFeedsManagerPayload:["EnableFeedsManagerSuccess","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 +`+(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 disabledAt\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"},activeIcon:{color:xh.default[500]},inactiveIcon:{color:xp.default[500]},text:{marginLeft:4}})},xv=(0,b.withStyles)(xg)(function(e){var t=e.isActive,n=e.activeText,r=e.inactiveText,i=e.classes;return l.createElement("div",{className:i.root},t?l.createElement(xm.Z,{fontSize:"small",className:i.activeIcon}):l.createElement(xb.Z,{fontSize:"small",className:i.inactiveIcon}),l.createElement(x.default,{variant:"body1",inline:!0,className:i.text},t?n:r))}),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,{isActive:t.isConnectionActive,activeText:"Connected",inactiveText:"Disconnected"})),l.createElement(r7.default,null,l.createElement(xv,{isActive:!t.disabledAt,activeText:"Enabled",inactiveText:"Disabled"})),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,"Connection Status"),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=function(){return(0,b.createStyles)({root:{fontSize:24}})},xQ=(0,b.withStyles)(xJ)(function(e){var t=e.children,n=e.classes;return l.createElement(x.default,{variant:"h2",className:n.root},t)}),x1=n(9290),x0=n(74923);function x2(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);n=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}function TS(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 Tk(e,t){return Tv(e)||Tw(e,t)||Tx(e,t)||T_()}function Tx(e,t){if(e){if("string"==typeof e)return Tg(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 Tg(e,t)}}var TT=function(e){return"SN_MAIN"===e||"SN_SEPOLIA"===e},TM=bv().shape({chainID:p0().required("Required"),chainType:p0().required("Required"),accountAddr:p0().required("Required"),accountAddrPubKey:p0().nullable(),adminAddr:p0(),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()}),TO=function(e){return(0,b.createStyles)({supportedJobOptionsPaper:{padding:2*e.spacing.unit}})},TA=function(e){var t=e.addresses,n=TE(e,["addresses"]),r=h_(),i=r.values,a=i.chainID,o=i.accountAddr,s=r.setFieldValue,u=Tk(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,!TT(a)&&l.createElement(hP,Ty({},n,{select:!0,value:c?"custom":o,onChange:h}),t.map(function(e){return l.createElement(tE.default,{key:e,value:e},e)})),TT(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}),TT(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"}})))},TL=(0,b.withStyles)(TO)(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.chains,u=void 0===s?[]:s,c=e.accountsEVM,f=void 0===c?[]:c,h=e.accountsAptos,p=void 0===h?[]:h,b=e.p2pKeys,m=void 0===b?[]:b,g=e.ocrKeys,v=void 0===g?[]:g,y=e.ocr2Keys,w=void 0===y?[]:y,_=e.showSubmit,E=void 0!==_&&_;return l.createElement(hT,{innerRef:i,initialValues:a,validationSchema:TM,onSubmit:o},function(e){var n=e.values,i=[];return n.chainType===Tm.EVM&&(i=f.filter(function(e){return e.chain.id==n.chainID&&!e.isDisabled}).map(function(e){return e.address})),n.chainType===Tm.APTOS&&(i=p.map(function(e){return e.account})),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:r},l.createElement(tE.default,{key:Tm.EVM,value:Tm.EVM},"EVM"),l.createElement(tE.default,{key:Tm.APTOS,value:Tm.APTOS},"APTOS"))),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.filter(function(e){return e.network.toUpperCase()===n.chainType}).map(function(e){return l.createElement(tE.default,{key:e.id,value:e.id},e.id)}))),l.createElement(d.Z,{item:!0,xs:12,md:6},l.createElement(TA,{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",addresses: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",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"}},m.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"}},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(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"}},m.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"}},w.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"}}))))))),E&&l.createElement(d.Z,{item:!0,xs:12,md:7},l.createElement(ok.default,{variant:"contained",color:"primary",type:"submit",size:"large"},"Submit"))))})});function TC(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function TI(){var e=TC(["\n fragment AptosKeysPayload_ResultsFields on AptosKey {\n account\n id\n }\n"]);return TI=function(){return e},e}function TD(){var e=TC(["\n ","\n query FetchAptosKeys {\n aptosKeys {\n results {\n ...AptosKeysPayload_ResultsFields\n }\n }\n }\n"]);return TD=function(){return e},e}var TN=n0(TI()),TP=n0(TD(),TN),TR=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return rv(TP,e)},Tj=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=TR({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,d={chainID:"",chainType:Tm.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:""},h=a?a.chains.results:[],p=o?o.ethKeys.results:[],b=s?s.aptosKeys.results:[],m=u?u.p2pKeys.results:[],g=c?c.ocrKeyBundles.results:[],v=f?f.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(TL,{innerRef:i,initialValues:d,onSubmit:r,chains:h,accountsEVM:p,accountsAptos:b,p2pKeys:m,ocrKeys:g,ocr2Keys:v})),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")))},TF=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=TR({fetchPolicy:"cache-and-network"}).data,c=SV({fetchPolicy:"cache-and-network"}).data,f=EO({fetchPolicy:"cache-and-network"}).data,d=E2({fetchPolicy:"cache-and-network"}).data;if(!t)return null;var h={chainID:t.chainID,chainType:Tm.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},p=o?o.chains.results:[],b=s?s.ethKeys.results:[],m=u?u.aptosKeys.results:[],g=c?c.p2pKeys.results:[],v=f?f.ocrKeyBundles.results:[],y=d?d.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(TL,{innerRef:a,initialValues:h,onSubmit:i,chains:p,accountsEVM:b,accountsAptos:m,p2pKeys:g,ocrKeys:v,ocr2Keys:y,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 TY(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&&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 MV(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?MQ[c.action].title:"",body:c?MQ[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(MF,{open:h,onClose:function(){return p(!1)},initialValues:{definition:m.definition,id:m.id},onSubmit:a}))});function M0(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function M2(){var e=M0(["\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 M2=function(){return e},e}var M3=n0(M2(),MX),M4=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(MI,{proposal:a}),l.createElement(d.Z,{container:!0,spacing:32},l.createElement(d.Z,{item:!0,xs:9},l.createElement(xQ,null,"Specs"))),l.createElement(d.Z,{container:!0,spacing:32},l.createElement(d.Z,{item:!0,xs:12},l.createElement(M1,{proposal:a,specs:a.specs,onReject:r,onApprove:t,onCancel:n,onUpdateSpec:i}))))};function M6(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.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"],DisableFeedsManagerPayload:["DisableFeedsManagerSuccess","NotFoundError"],DismissJobErrorPayload:["DismissJobErrorSuccess","NotFoundError"],EnableFeedsManagerPayload:["EnableFeedsManagerSuccess","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","StreamSpec","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.73737bcc031c687ddea1.js.gz b/core/web/assets/main.1d1632f9bf7627b5ea5e.js.gz similarity index 92% rename from core/web/assets/main.73737bcc031c687ddea1.js.gz rename to core/web/assets/main.1d1632f9bf7627b5ea5e.js.gz index 393cf470265..d99776f49e5 100644 Binary files a/core/web/assets/main.73737bcc031c687ddea1.js.gz and b/core/web/assets/main.1d1632f9bf7627b5ea5e.js.gz differ diff --git a/core/web/loop_registry_internal_test.go b/core/web/loop_registry_internal_test.go index a02fa20802a..d1235cd09b4 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, nil), + registry: plugins.NewLoopRegistry(l, nil, 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, nil), + registry: plugins.NewLoopRegistry(l, nil, nil, nil, ""), logger: l.(logger.SugaredLogger), jsonMarshalFn: func(any) ([]byte, error) { return []byte(""), errors.New("can't unmarshal") diff --git a/core/web/resolver/testdata/config-empty-effective.toml b/core/web/resolver/testdata/config-empty-effective.toml index 4cfe5e2086c..cd51afac5f8 100644 --- a/core/web/resolver/testdata/config-empty-effective.toml +++ b/core/web/resolver/testdata/config-empty-effective.toml @@ -286,3 +286,5 @@ CACertFile = '' Endpoint = '' InsecureConnection = false TraceSampleRatio = 0.01 +EmitterBatchProcessor = true +EmitterExportTimeout = '1s' diff --git a/core/web/resolver/testdata/config-full.toml b/core/web/resolver/testdata/config-full.toml index 8fbe07f97f9..3ae24d81f40 100644 --- a/core/web/resolver/testdata/config-full.toml +++ b/core/web/resolver/testdata/config-full.toml @@ -296,6 +296,8 @@ CACertFile = 'cert-file' Endpoint = 'example.com/collector' InsecureConnection = true TraceSampleRatio = 0.01 +EmitterBatchProcessor = true +EmitterExportTimeout = '1s' [Telemetry.ResourceAttributes] Baz = 'test' diff --git a/core/web/resolver/testdata/config-multi-chain-effective.toml b/core/web/resolver/testdata/config-multi-chain-effective.toml index adc1394e654..ea8022fa6ae 100644 --- a/core/web/resolver/testdata/config-multi-chain-effective.toml +++ b/core/web/resolver/testdata/config-multi-chain-effective.toml @@ -286,6 +286,8 @@ CACertFile = '' Endpoint = '' InsecureConnection = false TraceSampleRatio = 0.01 +EmitterBatchProcessor = true +EmitterExportTimeout = '1s' [[EVM]] ChainID = '1' diff --git a/deployment/ccip/add_lane_test.go b/deployment/ccip/add_lane_test.go index d8443ad288b..223d978b814 100644 --- a/deployment/ccip/add_lane_test.go +++ b/deployment/ccip/add_lane_test.go @@ -11,6 +11,7 @@ import ( "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/offramp" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" "github.com/smartcontractkit/chainlink/v2/core/logger" ) @@ -94,7 +95,13 @@ func TestAddLane(t *testing.T) { startBlock := latesthdr.Number.Uint64() // Send traffic on the first lane and it should not be processed by the plugin as onRamp is disabled // we will check this by confirming that the message is not executed by the end of the test - seqNum1 := TestSendRequest(t, e.Env, state, chain1, chain2, false, nil) + seqNum1 := TestSendRequest(t, e.Env, state, chain1, chain2, false, router.ClientEVM2AnyMessage{ + Receiver: common.LeftPadBytes(state.Chains[chain2].Receiver.Address().Bytes(), 32), + Data: []byte("hello world"), + TokenAmounts: nil, + FeeToken: common.HexToAddress("0x0"), + ExtraArgs: nil, + }) require.Equal(t, uint64(1), seqNum1) // Add another lane @@ -104,7 +111,13 @@ func TestAddLane(t *testing.T) { latesthdr, err = e.Env.Chains[chain1].Client.HeaderByNumber(testcontext.Get(t), nil) require.NoError(t, err) startBlock2 := latesthdr.Number.Uint64() - seqNum2 := TestSendRequest(t, e.Env, state, chain2, chain1, false, nil) + seqNum2 := TestSendRequest(t, e.Env, state, chain2, chain1, false, router.ClientEVM2AnyMessage{ + Receiver: common.LeftPadBytes(state.Chains[chain2].Receiver.Address().Bytes(), 32), + Data: []byte("hello world"), + TokenAmounts: nil, + FeeToken: common.HexToAddress("0x0"), + ExtraArgs: nil, + }) require.Equal(t, uint64(1), seqNum2) require.NoError(t, ConfirmExecWithSeqNr(t, e.Env.Chains[chain2], e.Env.Chains[chain1], state.Chains[chain1].OffRamp, &startBlock2, seqNum2)) diff --git a/deployment/ccip/changeset/active_candidate_test.go b/deployment/ccip/changeset/active_candidate_test.go index ab27d4c96db..9daf383c971 100644 --- a/deployment/ccip/changeset/active_candidate_test.go +++ b/deployment/ccip/changeset/active_candidate_test.go @@ -3,12 +3,14 @@ package changeset import ( "testing" + "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" "github.com/smartcontractkit/chainlink/deployment" @@ -82,7 +84,13 @@ func TestActiveCandidate(t *testing.T) { require.NoError(t, err) block := latesthdr.Number.Uint64() startBlocks[dest] = &block - seqNum := ccdeploy.TestSendRequest(t, e, state, src, dest, false, nil) + seqNum := ccdeploy.TestSendRequest(t, e, state, src, dest, false, router.ClientEVM2AnyMessage{ + Receiver: common.LeftPadBytes(state.Chains[dest].Receiver.Address().Bytes(), 32), + Data: []byte("hello world"), + TokenAmounts: nil, + FeeToken: common.HexToAddress("0x0"), + ExtraArgs: nil, + }) expectedSeqNum[dest] = seqNum } } diff --git a/deployment/ccip/changeset/add_chain_test.go b/deployment/ccip/changeset/add_chain_test.go index c0d76875b6c..ff02430fd51 100644 --- a/deployment/ccip/changeset/add_chain_test.go +++ b/deployment/ccip/changeset/add_chain_test.go @@ -211,7 +211,13 @@ func TestAddChainInbound(t *testing.T) { latesthdr, err := e.Env.Chains[newChain].Client.HeaderByNumber(testcontext.Get(t), nil) require.NoError(t, err) startBlock := latesthdr.Number.Uint64() - seqNr := ccipdeployment.TestSendRequest(t, e.Env, state, initialDeploy[0], newChain, true, nil) + seqNr := ccipdeployment.TestSendRequest(t, e.Env, state, initialDeploy[0], newChain, true, router.ClientEVM2AnyMessage{ + Receiver: common.LeftPadBytes(state.Chains[newChain].Receiver.Address().Bytes(), 32), + Data: []byte("hello world"), + TokenAmounts: nil, + FeeToken: common.HexToAddress("0x0"), + ExtraArgs: nil, + }) require.NoError(t, ccipdeployment.ConfirmCommitWithExpectedSeqNumRange(t, e.Env.Chains[initialDeploy[0]], e.Env.Chains[newChain], state.Chains[newChain].OffRamp, &startBlock, cciptypes.SeqNumRange{ cciptypes.SeqNum(1), diff --git a/deployment/ccip/changeset/cap_reg.go b/deployment/ccip/changeset/cap_reg.go deleted file mode 100644 index 1eded730a7c..00000000000 --- a/deployment/ccip/changeset/cap_reg.go +++ /dev/null @@ -1,30 +0,0 @@ -package changeset - -import ( - "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" - - "github.com/smartcontractkit/chainlink/deployment" - ccipdeployment "github.com/smartcontractkit/chainlink/deployment/ccip" -) - -var _ deployment.ChangeSet = DeployCapReg - -// DeployCapReg is a separate changeset because cap reg is an env var for CL nodes. -func DeployCapReg(env deployment.Environment, config interface{}) (deployment.ChangesetOutput, error) { - homeChainSel, ok := config.(uint64) - if !ok { - return deployment.ChangesetOutput{}, deployment.ErrInvalidConfig - } - // Note we also deploy the cap reg. - ab := deployment.NewMemoryAddressBook() - _, err := ccipdeployment.DeployCapReg(env.Logger, ab, env.Chains[homeChainSel]) - if err != nil { - env.Logger.Errorw("Failed to deploy cap reg", "err", err, "addresses", ab) - return deployment.ChangesetOutput{}, err - } - return deployment.ChangesetOutput{ - Proposals: []timelock.MCMSWithTimelockProposal{}, - AddressBook: ab, - JobSpecs: nil, - }, nil -} diff --git a/deployment/ccip/changeset/home_chain.go b/deployment/ccip/changeset/home_chain.go new file mode 100644 index 00000000000..5fa5cab5b21 --- /dev/null +++ b/deployment/ccip/changeset/home_chain.go @@ -0,0 +1,77 @@ +package changeset + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" + + "github.com/smartcontractkit/chainlink/deployment" + ccipdeployment "github.com/smartcontractkit/chainlink/deployment/ccip" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_home" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" +) + +var _ deployment.ChangeSet = DeployHomeChain + +// DeployHomeChain is a separate changeset because it is a standalone deployment performed once in home chain for the entire CCIP deployment. +func DeployHomeChain(env deployment.Environment, config interface{}) (deployment.ChangesetOutput, error) { + cfg, ok := config.(DeployHomeChainConfig) + if !ok { + return deployment.ChangesetOutput{}, deployment.ErrInvalidConfig + } + err := cfg.Validate() + if err != nil { + return deployment.ChangesetOutput{}, errors.Wrapf(deployment.ErrInvalidConfig, "%v", err) + } + ab := deployment.NewMemoryAddressBook() + // Note we also deploy the cap reg. + _, err = ccipdeployment.DeployHomeChain(env.Logger, env, ab, env.Chains[cfg.HomeChainSel], cfg.RMNStaticConfig, cfg.RMNDynamicConfig, cfg.NodeOperators, cfg.NodeP2PIDsPerNodeOpAdmin) + if err != nil { + env.Logger.Errorw("Failed to deploy cap reg", "err", err, "addresses", env.ExistingAddresses) + return deployment.ChangesetOutput{}, err + } + + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{}, + AddressBook: ab, + JobSpecs: nil, + }, nil +} + +type DeployHomeChainConfig struct { + HomeChainSel uint64 + RMNStaticConfig rmn_home.RMNHomeStaticConfig + RMNDynamicConfig rmn_home.RMNHomeDynamicConfig + NodeOperators []capabilities_registry.CapabilitiesRegistryNodeOperator + NodeP2PIDsPerNodeOpAdmin map[string][][32]byte +} + +func (c DeployHomeChainConfig) Validate() error { + if c.HomeChainSel == 0 { + return fmt.Errorf("home chain selector must be set") + } + if c.RMNDynamicConfig.OffchainConfig == nil { + return fmt.Errorf("offchain config for RMNHomeDynamicConfig must be set") + } + if c.RMNStaticConfig.OffchainConfig == nil { + return fmt.Errorf("offchain config for RMNHomeStaticConfig must be set") + } + if len(c.NodeOperators) == 0 { + return fmt.Errorf("node operators must be set") + } + for _, nop := range c.NodeOperators { + if nop.Admin == (common.Address{}) { + return fmt.Errorf("node operator admin address must be set") + } + if nop.Name == "" { + return fmt.Errorf("node operator name must be set") + } + if len(c.NodeP2PIDsPerNodeOpAdmin[nop.Name]) == 0 { + return fmt.Errorf("node operator %s must have node p2p ids provided", nop.Name) + } + } + + return nil +} diff --git a/deployment/ccip/changeset/home_chain_test.go b/deployment/ccip/changeset/home_chain_test.go new file mode 100644 index 00000000000..f0abdc64437 --- /dev/null +++ b/deployment/ccip/changeset/home_chain_test.go @@ -0,0 +1,63 @@ +package changeset + +import ( + "testing" + + chainsel "github.com/smartcontractkit/chain-selectors" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + + "github.com/smartcontractkit/chainlink/deployment" + ccdeploy "github.com/smartcontractkit/chainlink/deployment/ccip" + "github.com/smartcontractkit/chainlink/deployment/common/view/v1_0" + "github.com/smartcontractkit/chainlink/deployment/environment/memory" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +func TestDeployHomeChain(t *testing.T) { + lggr := logger.TestLogger(t) + e := memory.NewMemoryEnvironment(t, lggr, zapcore.InfoLevel, memory.MemoryEnvironmentConfig{ + Bootstraps: 1, + Chains: 2, + Nodes: 4, + }) + homeChainSel := e.AllChainSelectors()[0] + nodes, err := deployment.NodeInfo(e.NodeIDs, e.Offchain) + require.NoError(t, err) + p2pIds := nodes.NonBootstraps().PeerIDs() + homeChainCfg := DeployHomeChainConfig{ + HomeChainSel: homeChainSel, + RMNStaticConfig: ccdeploy.NewTestRMNStaticConfig(), + RMNDynamicConfig: ccdeploy.NewTestRMNDynamicConfig(), + NodeOperators: ccdeploy.NewTestNodeOperator(e.Chains[homeChainSel].DeployerKey.From), + NodeP2PIDsPerNodeOpAdmin: map[string][][32]byte{ + "NodeOperator": p2pIds, + }, + } + output, err := DeployHomeChain(e, homeChainCfg) + require.NoError(t, err) + require.NoError(t, e.ExistingAddresses.Merge(output.AddressBook)) + state, err := ccdeploy.LoadOnchainState(e) + require.NoError(t, err) + require.NotNil(t, state.Chains[homeChainSel].CapabilityRegistry) + require.NotNil(t, state.Chains[homeChainSel].CCIPHome) + require.NotNil(t, state.Chains[homeChainSel].RMNHome) + snap, err := state.View([]uint64{homeChainSel}) + require.NoError(t, err) + chainid, err := chainsel.ChainIdFromSelector(homeChainSel) + require.NoError(t, err) + chainName, err := chainsel.NameFromChainId(chainid) + require.NoError(t, err) + _, ok := snap[chainName] + require.True(t, ok) + capRegSnap, ok := snap[chainName].CapabilityRegistry[state.Chains[homeChainSel].CapabilityRegistry.Address().String()] + require.True(t, ok) + require.NotNil(t, capRegSnap) + require.Equal(t, capRegSnap.Nops, []v1_0.NopView{ + { + Admin: e.Chains[homeChainSel].DeployerKey.From, + Name: "NodeOperator", + }, + }) + require.Len(t, capRegSnap.Nodes, len(p2pIds)) +} diff --git a/deployment/ccip/changeset/initial_deploy_test.go b/deployment/ccip/changeset/initial_deploy_test.go index c172f9f84c8..e1adbf2050f 100644 --- a/deployment/ccip/changeset/initial_deploy_test.go +++ b/deployment/ccip/changeset/initial_deploy_test.go @@ -3,6 +3,7 @@ package changeset import ( "testing" + "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/chainlink/deployment" ccdeploy "github.com/smartcontractkit/chainlink/deployment/ccip" @@ -11,6 +12,7 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/stretchr/testify/require" @@ -72,7 +74,13 @@ func TestInitialDeploy(t *testing.T) { require.NoError(t, err) block := latesthdr.Number.Uint64() startBlocks[dest] = &block - seqNum := ccdeploy.TestSendRequest(t, e, state, src, dest, false, nil) + seqNum := ccdeploy.TestSendRequest(t, e, state, src, dest, false, router.ClientEVM2AnyMessage{ + Receiver: common.LeftPadBytes(state.Chains[dest].Receiver.Address().Bytes(), 32), + Data: []byte("hello"), + TokenAmounts: nil, + FeeToken: common.HexToAddress("0x0"), + ExtraArgs: nil, + }) expectedSeqNum[dest] = seqNum } } diff --git a/deployment/ccip/changeset/messaging_test.go b/deployment/ccip/changeset/messaging_test.go new file mode 100644 index 00000000000..a5fde58742b --- /dev/null +++ b/deployment/ccip/changeset/messaging_test.go @@ -0,0 +1,209 @@ +package changeset + +import ( + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink/deployment" + ccipdeployment "github.com/smartcontractkit/chainlink/deployment/ccip" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/test-go/testify/require" + "golang.org/x/exp/maps" +) + +type testCaseSetup struct { + t *testing.T + sender []byte + deployedEnv ccipdeployment.DeployedEnv + onchainState ccipdeployment.CCIPOnChainState + sourceChain, destChain uint64 +} + +type messagingTestCase struct { + testCaseSetup + replayed bool + nonce uint64 +} + +type messagingTestCaseOutput struct { + replayed bool + nonce uint64 +} + +func Test_Messaging(t *testing.T) { + t.Parallel() + + // Setup 2 chains and a single lane. + e := ccipdeployment.NewMemoryEnvironmentWithJobs(t, logger.TestLogger(t), 2, 4) + state, err := ccipdeployment.LoadOnchainState(e.Env) + require.NoError(t, err) + + allChainSelectors := maps.Keys(e.Env.Chains) + require.Len(t, allChainSelectors, 2) + sourceChain := allChainSelectors[0] + destChain := allChainSelectors[1] + t.Log("All chain selectors:", allChainSelectors, + ", home chain selector:", e.HomeChainSel, + ", feed chain selector:", e.FeedChainSel, + ", source chain selector:", sourceChain, + ", dest chain selector:", destChain, + ) + + tokenConfig := ccipdeployment.NewTestTokenConfig(state.Chains[e.FeedChainSel].USDFeeds) + newAddresses := deployment.NewMemoryAddressBook() + err = ccipdeployment.DeployCCIPContracts(e.Env, newAddresses, ccipdeployment.DeployCCIPContractConfig{ + HomeChainSel: e.HomeChainSel, + FeedChainSel: e.FeedChainSel, + ChainsToDeploy: allChainSelectors, + TokenConfig: tokenConfig, + MCMSConfig: ccipdeployment.NewTestMCMSConfig(t, e.Env), + OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), + }) + require.NoError(t, err) + require.NoError(t, e.Env.ExistingAddresses.Merge(newAddresses)) + state, err = ccipdeployment.LoadOnchainState(e.Env) + require.NoError(t, err) + + // connect a single lane, source to dest + require.NoError(t, ccipdeployment.AddLane(e.Env, state, sourceChain, destChain)) + + var ( + replayed bool + nonce uint64 + sender = common.LeftPadBytes(e.Env.Chains[sourceChain].DeployerKey.From.Bytes(), 32) + out messagingTestCaseOutput + setup = testCaseSetup{ + t: t, + sender: sender, + deployedEnv: e, + onchainState: state, + sourceChain: sourceChain, + destChain: destChain, + } + ) + + t.Run("data message to eoa", func(t *testing.T) { + out = runMessagingTestCase(messagingTestCase{ + testCaseSetup: setup, + replayed: replayed, + nonce: nonce, + }, + common.HexToAddress("0xdead"), + []byte("hello eoa"), + ) + }) + + t.Run("message to contract not implementing CCIPReceiver", func(t *testing.T) { + out = runMessagingTestCase( + messagingTestCase{ + testCaseSetup: setup, + replayed: out.replayed, + nonce: out.nonce, + }, + state.Chains[destChain].FeeQuoter.Address(), + []byte("hello FeeQuoter"), + ) + }) + + t.Run("message to contract implementing CCIPReceiver", func(t *testing.T) { + out = runMessagingTestCase( + messagingTestCase{ + testCaseSetup: setup, + replayed: out.replayed, + nonce: out.nonce, + }, + state.Chains[destChain].Receiver.Address(), + []byte("hello CCIPReceiver"), + func(t *testing.T) { + iter, err := state.Chains[destChain].Receiver.FilterMessageReceived(nil) + require.NoError(t, err) + require.True(t, iter.Next()) + // MessageReceived doesn't emit the data unfortunately, so can't check that. + }, + ) + }) + + t.Run("message to contract implementing CCIPReceiver with low exec gas", func(t *testing.T) { + out = runMessagingTestCase( + messagingTestCase{ + testCaseSetup: setup, + replayed: out.replayed, + nonce: out.nonce, + }, + state.Chains[destChain].Receiver.Address(), + []byte("hello CCIPReceiver with low exec gas"), + func(t *testing.T) { + // Message should not be emitted, not enough gas to emit log. + // TODO: this is still returning a log, probably the older one since FAILURE is the execution state. + // Not enough ctx in the message received log to confirm that it's from another test. + // Maybe check the log block number and assert that its < the header before block number from above? + // iter, err := ccipReceiver.FilterMessageReceived(&bind.FilterOpts{ + // Start: headerBefore.Number.Uint64(), + // }) + // require.NoError(t, err) + // require.False(t, iter.Next(), "MessageReceived should not be emitted in this test case since gas is too low") + }, + ) + }) +} + +func sleepAndReplay(t *testing.T, e ccipdeployment.DeployedEnv, sourceChain, destChain uint64) { + time.Sleep(30 * time.Second) + replayBlocks := make(map[uint64]uint64) + replayBlocks[sourceChain] = 1 + replayBlocks[destChain] = 1 + ccipdeployment.ReplayLogs(t, e.Env.Offchain, replayBlocks) +} + +func runMessagingTestCase( + tc messagingTestCase, + receiver common.Address, + msgData []byte, + extraAssertions ...func(t *testing.T), +) (out messagingTestCaseOutput) { + // check latest nonce + latestNonce, err := tc.onchainState.Chains[tc.destChain].NonceManager.GetInboundNonce(&bind.CallOpts{ + Context: tests.Context(tc.t), + }, tc.sourceChain, tc.sender) + require.NoError(tc.t, err) + require.Equal(tc.t, tc.nonce, latestNonce) + + startBlocks := make(map[uint64]*uint64) + seqNum := ccipdeployment.TestSendRequest(tc.t, tc.deployedEnv.Env, tc.onchainState, tc.sourceChain, tc.destChain, false, router.ClientEVM2AnyMessage{ + Receiver: common.LeftPadBytes(receiver.Bytes(), 32), + Data: msgData, + TokenAmounts: nil, + FeeToken: common.HexToAddress("0x0"), + ExtraArgs: nil, + }) + expectedSeqNum := make(map[uint64]uint64) + expectedSeqNum[tc.destChain] = seqNum + + // hack + if !tc.replayed { + sleepAndReplay(tc.t, tc.deployedEnv, tc.sourceChain, tc.destChain) + out.replayed = true + } + + ccipdeployment.ConfirmCommitForAllWithExpectedSeqNums(tc.t, tc.deployedEnv.Env, tc.onchainState, expectedSeqNum, startBlocks) + ccipdeployment.ConfirmExecWithSeqNrForAll(tc.t, tc.deployedEnv.Env, tc.onchainState, expectedSeqNum, startBlocks) + + // check the sender latestNonce on the dest, should be incremented + latestNonce, err = tc.onchainState.Chains[tc.destChain].NonceManager.GetInboundNonce(&bind.CallOpts{ + Context: tests.Context(tc.t), + }, tc.sourceChain, tc.sender) + require.NoError(tc.t, err) + require.Equal(tc.t, tc.nonce+1, latestNonce) + out.nonce = latestNonce + tc.t.Logf("confirmed nonce bump for sender %x, latestNonce %d", tc.sender, latestNonce) + + for _, assertion := range extraAssertions { + assertion(tc.t) + } + + return +} diff --git a/deployment/ccip/deploy.go b/deployment/ccip/deploy.go index 4d90422c843..77df3aab60f 100644 --- a/deployment/ccip/deploy.go +++ b/deployment/ccip/deploy.go @@ -12,6 +12,7 @@ import ( owner_helpers "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/fee_quoter" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/registry_module_owner_custom" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_home" @@ -187,15 +188,6 @@ func DeployCCIPContracts(e deployment.Environment, ab deployment.AddressBook, c return fmt.Errorf("ccip home address mismatch") } - // Signal to CR that our nodes support CCIP capability. - if err := AddNodes( - e.Logger, - capReg, - e.Chains[c.HomeChainSel], - nodes.NonBootstraps().PeerIDs(), - ); err != nil { - return err - } rmnHome := existingState.Chains[c.HomeChainSel].RMNHome if rmnHome == nil { e.Logger.Errorw("Failed to get rmn home", "err", err) @@ -581,8 +573,15 @@ func DeployChainContracts( tx, err = tokenAdminRegistry.Contract.AddRegistryModule(chain.DeployerKey, customRegistryModule.Address) if err != nil { e.Logger.Errorw("Failed to assign registry module on token admin registry", "err", err) - return err + return fmt.Errorf("failed to assign registry module on token admin registry: %w", err) } + + _, err = chain.Confirm(tx) + if err != nil { + e.Logger.Errorw("Failed to confirm assign registry module on token admin registry", "err", err) + return fmt.Errorf("failed to confirm assign registry module on token admin registry: %w", err) + } + e.Logger.Infow("assigned registry module on token admin registry") nonceManager, err := deployContract(e.Logger, chain, ab, diff --git a/deployment/ccip/deploy_home_chain.go b/deployment/ccip/deploy_home_chain.go index 3f614b8510f..341f53a0438 100644 --- a/deployment/ccip/deploy_home_chain.go +++ b/deployment/ccip/deploy_home_chain.go @@ -15,6 +15,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" + "golang.org/x/exp/maps" "github.com/smartcontractkit/chainlink-ccip/chainconfig" "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" @@ -85,7 +86,24 @@ func MustABIEncode(abiString string, args ...interface{}) []byte { return encoded } -func DeployCapReg(lggr logger.Logger, ab deployment.AddressBook, chain deployment.Chain) (*ContractDeploy[*capabilities_registry.CapabilitiesRegistry], error) { +// DeployCapReg deploys the CapabilitiesRegistry contract if it is not already deployed +// and returns a ContractDeploy struct with the address and contract instance. +func DeployCapReg( + lggr logger.Logger, + state CCIPOnChainState, + ab deployment.AddressBook, + chain deployment.Chain, +) (*ContractDeploy[*capabilities_registry.CapabilitiesRegistry], error) { + homeChainState, exists := state.Chains[chain.Selector] + if exists { + cr := homeChainState.CapabilityRegistry + if cr != nil { + lggr.Infow("Found CapabilitiesRegistry in chain state", "address", cr.Address().String()) + return &ContractDeploy[*capabilities_registry.CapabilitiesRegistry]{ + Address: cr.Address(), Contract: cr, Tv: deployment.NewTypeAndVersion(CapabilitiesRegistry, deployment.Version1_0_0), + }, nil + } + } capReg, err := deployContract(lggr, chain, ab, func(chain deployment.Chain) ContractDeploy[*capabilities_registry.CapabilitiesRegistry] { crAddr, tx, cr, err2 := capabilities_registry.DeployCapabilitiesRegistry( @@ -100,8 +118,31 @@ func DeployCapReg(lggr logger.Logger, ab deployment.AddressBook, chain deploymen lggr.Errorw("Failed to deploy capreg", "err", err) return nil, err } + return capReg, nil +} - lggr.Infow("deployed capreg", "addr", capReg.Address) +func DeployHomeChain( + lggr logger.Logger, + e deployment.Environment, + ab deployment.AddressBook, + chain deployment.Chain, + rmnHomeStatic rmn_home.RMNHomeStaticConfig, + rmnHomeDynamic rmn_home.RMNHomeDynamicConfig, + nodeOps []capabilities_registry.CapabilitiesRegistryNodeOperator, + nodeP2PIDsPerNodeOpAdmin map[string][][32]byte, +) (*ContractDeploy[*capabilities_registry.CapabilitiesRegistry], error) { + // load existing state + state, err := LoadOnchainState(e) + if err != nil { + return nil, fmt.Errorf("failed to load onchain state: %w", err) + } + // Deploy CapabilitiesRegistry, CCIPHome, RMNHome + capReg, err := DeployCapReg(lggr, state, ab, chain) + if err != nil { + return nil, err + } + + lggr.Infow("deployed/connected to capreg", "addr", capReg.Address) ccipHome, err := deployContract( lggr, chain, ab, func(chain deployment.Chain) ContractDeploy[*ccip_home.CCIPHome] { @@ -138,14 +179,8 @@ func DeployCapReg(lggr logger.Logger, ab deployment.AddressBook, chain deploymen } lggr.Infow("deployed RMNHome", "addr", rmnHome.Address) - // TODO: properly configure RMNHome - tx, err := rmnHome.Contract.SetCandidate(chain.DeployerKey, rmn_home.RMNHomeStaticConfig{ - Nodes: []rmn_home.RMNHomeNode{}, - OffchainConfig: []byte("static config"), - }, rmn_home.RMNHomeDynamicConfig{ - SourceChains: []rmn_home.RMNHomeSourceChain{}, - OffchainConfig: []byte("dynamic config"), - }, [32]byte{}) + // considering the RMNHome is recently deployed, there is no digest to overwrite + tx, err := rmnHome.Contract.SetCandidate(chain.DeployerKey, rmnHomeStatic, rmnHomeDynamic, [32]byte{}) if _, err := deployment.ConfirmIfNoError(chain, tx, err); err != nil { lggr.Errorw("Failed to set candidate on RMNHome", "err", err) return nil, err @@ -189,20 +224,63 @@ func DeployCapReg(lggr logger.Logger, ab deployment.AddressBook, chain deploymen lggr.Errorw("Failed to add capabilities", "err", err) return nil, err } - // TODO: Just one for testing. - tx, err = capReg.Contract.AddNodeOperators(chain.DeployerKey, []capabilities_registry.CapabilitiesRegistryNodeOperator{ - { - Admin: chain.DeployerKey.From, - Name: "NodeOperator", - }, - }) - if _, err := deployment.ConfirmIfNoError(chain, tx, err); err != nil { + + tx, err = capReg.Contract.AddNodeOperators(chain.DeployerKey, nodeOps) + txBlockNum, err := deployment.ConfirmIfNoError(chain, tx, err) + if err != nil { lggr.Errorw("Failed to add node operators", "err", err) return nil, err } + addedEvent, err := capReg.Contract.FilterNodeOperatorAdded(&bind.FilterOpts{ + Start: txBlockNum, + Context: context.Background(), + }, nil, nil) + if err != nil { + lggr.Errorw("Failed to filter NodeOperatorAdded event", "err", err) + return capReg, err + } + // Need to fetch nodeoperators ids to be able to add nodes for corresponding node operators + p2pIDsByNodeOpId := make(map[uint32][][32]byte) + for addedEvent.Next() { + for nopName, p2pId := range nodeP2PIDsPerNodeOpAdmin { + if addedEvent.Event.Name == nopName { + lggr.Infow("Added node operator", "admin", addedEvent.Event.Admin, "name", addedEvent.Event.Name) + p2pIDsByNodeOpId[addedEvent.Event.NodeOperatorId] = p2pId + } + } + } + if len(p2pIDsByNodeOpId) != len(nodeP2PIDsPerNodeOpAdmin) { + lggr.Errorw("Failed to add all node operators", "added", maps.Keys(p2pIDsByNodeOpId), "expected", maps.Keys(nodeP2PIDsPerNodeOpAdmin)) + return capReg, errors.New("failed to add all node operators") + } + // Adds initial set of nodes to CR, who all have the CCIP capability + if err := AddNodes(lggr, capReg.Contract, chain, p2pIDsByNodeOpId); err != nil { + return capReg, err + } return capReg, nil } +// getNodeOperatorIDMap returns a map of node operator names to their IDs +// If maxNops is greater than the number of node operators, it will return all node operators +func getNodeOperatorIDMap(capReg *capabilities_registry.CapabilitiesRegistry, maxNops uint32) (map[string]uint32, error) { + nopIdByName := make(map[string]uint32) + operators, err := capReg.GetNodeOperators(nil) + if err != nil { + return nil, err + } + if len(operators) < int(maxNops) { + maxNops = uint32(len(operators)) + } + for i := uint32(1); i <= maxNops; i++ { + operator, err := capReg.GetNodeOperator(nil, i) + if err != nil { + return nil, err + } + nopIdByName[operator.Name] = i + } + return nopIdByName, nil +} + func isEqualCapabilitiesRegistryNodeParams(a, b capabilities_registry.CapabilitiesRegistryNodeParams) (bool, error) { aBytes, err := json.Marshal(a) if err != nil { @@ -219,7 +297,7 @@ func AddNodes( lggr logger.Logger, capReg *capabilities_registry.CapabilitiesRegistry, chain deployment.Chain, - p2pIDs [][32]byte, + p2pIDsByNodeOpId map[uint32][][32]byte, ) error { var nodeParams []capabilities_registry.CapabilitiesRegistryNodeParams nodes, err := capReg.GetNodes(nil) @@ -235,26 +313,28 @@ func AddNodes( HashedCapabilityIds: node.HashedCapabilityIds, } } - for _, p2pID := range p2pIDs { - // if any p2pIDs are empty throw error - if bytes.Equal(p2pID[:], make([]byte, 32)) { - return errors.Wrapf(errors.New("empty p2pID"), "p2pID: %x selector: %d", p2pID, chain.Selector) - } - nodeParam := capabilities_registry.CapabilitiesRegistryNodeParams{ - NodeOperatorId: NodeOperatorID, - Signer: p2pID, // Not used in tests - P2pId: p2pID, - EncryptionPublicKey: p2pID, // Not used in tests - HashedCapabilityIds: [][32]byte{CCIPCapabilityID}, - } - if existing, ok := existingNodeParams[p2pID]; ok { - if isEqual, err := isEqualCapabilitiesRegistryNodeParams(existing, nodeParam); err != nil && isEqual { - lggr.Infow("Node already exists", "p2pID", p2pID) - continue + for nopID, p2pIDs := range p2pIDsByNodeOpId { + for _, p2pID := range p2pIDs { + // if any p2pIDs are empty throw error + if bytes.Equal(p2pID[:], make([]byte, 32)) { + return errors.Wrapf(errors.New("empty p2pID"), "p2pID: %x selector: %d", p2pID, chain.Selector) + } + nodeParam := capabilities_registry.CapabilitiesRegistryNodeParams{ + NodeOperatorId: nopID, + Signer: p2pID, // Not used in tests + P2pId: p2pID, + EncryptionPublicKey: p2pID, // Not used in tests + HashedCapabilityIds: [][32]byte{CCIPCapabilityID}, + } + if existing, ok := existingNodeParams[p2pID]; ok { + if isEqual, err := isEqualCapabilitiesRegistryNodeParams(existing, nodeParam); err != nil && isEqual { + lggr.Infow("Node already exists", "p2pID", p2pID) + continue + } } - } - nodeParams = append(nodeParams, nodeParam) + nodeParams = append(nodeParams, nodeParam) + } } if len(nodeParams) == 0 { lggr.Infow("No new nodes to add") diff --git a/deployment/ccip/deploy_test.go b/deployment/ccip/deploy_test.go index ecb17017193..63aeacb4bdf 100644 --- a/deployment/ccip/deploy_test.go +++ b/deployment/ccip/deploy_test.go @@ -25,6 +25,18 @@ func TestDeployCCIPContracts(t *testing.T) { homeChainSel, feedChainSel := allocateCCIPChainSelectors(e.Chains) _ = DeployTestContracts(t, lggr, e.ExistingAddresses, homeChainSel, feedChainSel, e.Chains) + nodes, err := deployment.NodeInfo(e.NodeIDs, e.Offchain) + require.NoError(t, err) + + _, err = DeployHomeChain(lggr, e, e.ExistingAddresses, e.Chains[homeChainSel], + NewTestRMNStaticConfig(), + NewTestRMNDynamicConfig(), + NewTestNodeOperator(e.Chains[homeChainSel].DeployerKey.From), + map[string][][32]byte{ + "NodeOperator": nodes.NonBootstraps().PeerIDs(), + }, + ) + require.NoError(t, err) // Load the state after deploying the cap reg and feeds. s, err := LoadOnchainState(e) require.NoError(t, err) diff --git a/deployment/ccip/test_assertions.go b/deployment/ccip/test_assertions.go index d1389fc5ce3..26c0d93c8a0 100644 --- a/deployment/ccip/test_assertions.go +++ b/deployment/ccip/test_assertions.go @@ -134,7 +134,7 @@ func ConfirmTokenPriceUpdated( } if len(tokenToInitialPrice) > 0 { - return fmt.Errorf("Not all tokens updated on chain %d", chain.Selector) + return fmt.Errorf("not all tokens updated on chain %d", chain.Selector) } return nil @@ -272,7 +272,7 @@ func ConfirmCommitWithExpectedSeqNumRange( } // ConfirmExecWithSeqNrForAll waits for all chains in the environment to execute the given expectedSeqNums. -// expectedSeqNums is a map of destinationchain selector to expected sequence number +// expectedSeqNums is a map of destination chain selector to expected sequence number // startBlocks is a map of destination chain selector to start block number to start watching from. // If startBlocks is nil, it will start watching from the latest block. func ConfirmExecWithSeqNrForAll( @@ -343,17 +343,17 @@ func ConfirmExecWithSeqNr( scc, executionState := GetExecutionState(t, source, dest, offRamp, expectedSeqNr) t.Logf("Waiting for ExecutionStateChanged on chain %d (offramp %s) from chain %d with expected sequence number %d, current onchain minSeqNr: %d, execution state: %s", dest.Selector, offRamp.Address().String(), source.Selector, expectedSeqNr, scc.MinSeqNr, executionStateToString(executionState)) - if executionState == EXECUTION_STATE_SUCCESS { - t.Logf("Observed SUCCESS execution state on chain %d (offramp %s) from chain %d with expected sequence number %d", - dest.Selector, offRamp.Address().String(), source.Selector, expectedSeqNr) + if executionState == EXECUTION_STATE_SUCCESS || executionState == EXECUTION_STATE_FAILURE { + t.Logf("Observed %s execution state on chain %d (offramp %s) from chain %d with expected sequence number %d", + executionStateToString(executionState), dest.Selector, offRamp.Address().String(), source.Selector, expectedSeqNr) return nil } case execEvent := <-sink: - t.Logf("Received ExecutionStateChanged for seqNum %d on chain %d (offramp %s) from chain %d", - execEvent.SequenceNumber, dest.Selector, offRamp.Address().String(), source.Selector) + t.Logf("Received ExecutionStateChanged (state %s) for seqNum %d on chain %d (offramp %s) from chain %d", + executionStateToString(execEvent.State), execEvent.SequenceNumber, dest.Selector, offRamp.Address().String(), source.Selector) if execEvent.SequenceNumber == expectedSeqNr && execEvent.SourceChainSelector == source.Selector { - t.Logf("Received ExecutionStateChanged on chain %d (offramp %s) from chain %d with expected sequence number %d", - dest.Selector, offRamp.Address().String(), source.Selector, expectedSeqNr) + t.Logf("Received ExecutionStateChanged (state %s) on chain %d (offramp %s) from chain %d with expected sequence number %d", + executionStateToString(execEvent.State), dest.Selector, offRamp.Address().String(), source.Selector, expectedSeqNr) return nil } case <-timer.C: diff --git a/deployment/ccip/test_helpers.go b/deployment/ccip/test_helpers.go index de1ebd7e675..e6ad3987a0b 100644 --- a/deployment/ccip/test_helpers.go +++ b/deployment/ccip/test_helpers.go @@ -3,13 +3,15 @@ package ccipdeployment import ( "context" "fmt" - mapset "github.com/deckarep/golang-set/v2" "math/big" "sort" "testing" "time" + mapset "github.com/deckarep/golang-set/v2" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/pkg/errors" @@ -48,6 +50,11 @@ const ( FeedChainIndex = 1 ) +var ( + // bytes4 public constant EVM_EXTRA_ARGS_V2_TAG = 0x181dcf10; + evmExtraArgsV2Tag = hexutil.MustDecode("0x181dcf10") +) + // Context returns a context with the test's deadline, if available. func Context(tb testing.TB) context.Context { ctx := context.Background() @@ -111,7 +118,11 @@ func DeployTestContracts(t *testing.T, feedChainSel uint64, chains map[uint64]deployment.Chain, ) deployment.CapabilityRegistryConfig { - capReg, err := DeployCapReg(lggr, ab, chains[homeChainSel]) + capReg, err := DeployCapReg(lggr, + // deploying cap reg for the first time on a blank chain state + CCIPOnChainState{ + Chains: make(map[uint64]CCIPChainState), + }, ab, chains[homeChainSel]) require.NoError(t, err) _, err = DeployFeeds(lggr, ab, chains[feedChainSel]) require.NoError(t, err) @@ -172,9 +183,20 @@ func NewMemoryEnvironment(t *testing.T, lggr logger.Logger, numChains int, numNo require.NoError(t, node.App.Stop()) }) } - e := memory.NewMemoryEnvironmentFromChainsNodes(t, lggr, chains, nodes) + envNodes, err := deployment.NodeInfo(e.NodeIDs, e.Offchain) + require.NoError(t, err) e.ExistingAddresses = ab + _, err = DeployHomeChain(lggr, e, e.ExistingAddresses, chains[homeChainSel], + NewTestRMNStaticConfig(), + NewTestRMNDynamicConfig(), + NewTestNodeOperator(chains[homeChainSel].DeployerKey.From), + map[string][][32]byte{ + "NodeOperator": envNodes.NonBootstraps().PeerIDs(), + }, + ) + require.NoError(t, err) + return DeployedEnv{ Env: e, HomeChainSel: homeChainSel, @@ -183,6 +205,8 @@ func NewMemoryEnvironment(t *testing.T, lggr logger.Logger, numChains int, numNo } } +// NewMemoryEnvironmentWithJobs creates a new CCIP environment +// with capreg, fee tokens, feeds, nodes and jobs set up. func NewMemoryEnvironmentWithJobs(t *testing.T, lggr logger.Logger, numChains int, numNodes int) DeployedEnv { e := NewMemoryEnvironment(t, lggr, numChains, numNodes) e.SetupJobs(t) @@ -193,18 +217,15 @@ func CCIPSendRequest( e deployment.Environment, state CCIPOnChainState, src, dest uint64, - data []byte, - tokensAndAmounts []router.ClientEVMTokenAmount, - feeToken common.Address, testRouter bool, - extraArgs []byte, + evm2AnyMessage router.ClientEVM2AnyMessage, ) (*types.Transaction, uint64, error) { msg := router.ClientEVM2AnyMessage{ - Receiver: common.LeftPadBytes(state.Chains[dest].Receiver.Address().Bytes(), 32), - Data: data, - TokenAmounts: tokensAndAmounts, - FeeToken: feeToken, - ExtraArgs: extraArgs, + Receiver: evm2AnyMessage.Receiver, + Data: evm2AnyMessage.Data, + TokenAmounts: evm2AnyMessage.TokenAmounts, + FeeToken: evm2AnyMessage.FeeToken, + ExtraArgs: evm2AnyMessage.ExtraArgs, } r := state.Chains[src].Router if testRouter { @@ -233,10 +254,23 @@ func CCIPSendRequest( return tx, blockNum, nil } -func TestSendRequest(t *testing.T, e deployment.Environment, state CCIPOnChainState, src, dest uint64, testRouter bool, tokensAndAmounts []router.ClientEVMTokenAmount) uint64 { +func TestSendRequest( + t *testing.T, + e deployment.Environment, + state CCIPOnChainState, + src, dest uint64, + testRouter bool, + evm2AnyMessage router.ClientEVM2AnyMessage, +) (seqNum uint64) { t.Logf("Sending CCIP request from chain selector %d to chain selector %d", src, dest) - tx, blockNum, err := CCIPSendRequest(e, state, src, dest, []byte("hello"), tokensAndAmounts, common.HexToAddress("0x0"), testRouter, nil) + tx, blockNum, err := CCIPSendRequest( + e, + state, + src, dest, + testRouter, + evm2AnyMessage, + ) require.NoError(t, err) it, err := state.Chains[src].OnRamp.FilterCCIPMessageSent(&bind.FilterOpts{ Start: blockNum, @@ -245,11 +279,37 @@ func TestSendRequest(t *testing.T, e deployment.Environment, state CCIPOnChainSt }, []uint64{dest}, []uint64{}) require.NoError(t, err) require.True(t, it.Next()) - seqNum := it.Event.Message.Header.SequenceNumber - t.Logf("CCIP message sent from chain selector %d to chain selector %d tx %s seqNum %d", src, dest, tx.Hash().String(), seqNum) + seqNum = it.Event.Message.Header.SequenceNumber + nonce := it.Event.Message.Header.Nonce + sender := it.Event.Message.Sender + t.Logf("CCIP message sent from chain selector %d to chain selector %d tx %s seqNum %d nonce %d sender %s", + src, dest, tx.Hash().String(), seqNum, nonce, sender.String()) return seqNum } +func MakeExtraArgsV2(gasLimit uint64, allowOOO bool) []byte { + // extra args is the tag followed by the gas limit and allowOOO abi-encoded. + var extraArgs []byte + extraArgs = append(extraArgs, evmExtraArgsV2Tag...) + gasLimitBytes := new(big.Int).SetUint64(gasLimit).Bytes() + // pad from the left to 32 bytes + gasLimitBytes = common.LeftPadBytes(gasLimitBytes, 32) + + // abi-encode allowOOO + var allowOOOBytes []byte + if allowOOO { + allowOOOBytes = append(allowOOOBytes, 1) + } else { + allowOOOBytes = append(allowOOOBytes, 0) + } + // pad from the left to 32 bytes + allowOOOBytes = common.LeftPadBytes(allowOOOBytes, 32) + + extraArgs = append(extraArgs, gasLimitBytes...) + extraArgs = append(extraArgs, allowOOOBytes...) + return extraArgs +} + // AddLanesForAll adds densely connected lanes for all chains in the environment so that each chain // is connected to every other chain except itself. func AddLanesForAll(e deployment.Environment, state CCIPOnChainState) error { @@ -377,7 +437,13 @@ func ConfirmRequestOnSourceAndDest(t *testing.T, env deployment.Environment, sta require.NoError(t, err) startBlock := latesthdr.Number.Uint64() fmt.Printf("startblock %d", startBlock) - seqNum := TestSendRequest(t, env, state, sourceCS, destCS, false, nil) + seqNum := TestSendRequest(t, env, state, sourceCS, destCS, false, router.ClientEVM2AnyMessage{ + Receiver: common.LeftPadBytes(state.Chains[destCS].Receiver.Address().Bytes(), 32), + Data: []byte("hello world"), + TokenAmounts: nil, + FeeToken: common.HexToAddress("0x0"), + ExtraArgs: nil, + }) require.Equal(t, expectedSeqNr, seqNum) fmt.Printf("Request sent for seqnr %d", seqNum) @@ -517,7 +583,7 @@ func setTokenPoolCounterPart( }, ) if err != nil { - return err + return fmt.Errorf("failed to apply chain updates on token pool %s: %w", tokenPool.Address(), err) } _, err = chain.Confirm(tx) @@ -530,6 +596,11 @@ func setTokenPoolCounterPart( destChainSelector, destTokenPoolAddress.Bytes(), ) + if err != nil { + return fmt.Errorf("failed to set remote pool on token pool %s: %w", tokenPool.Address(), err) + } + + _, err = chain.Confirm(tx) return err } diff --git a/deployment/ccip/test_params.go b/deployment/ccip/test_params.go new file mode 100644 index 00000000000..531c48532f1 --- /dev/null +++ b/deployment/ccip/test_params.go @@ -0,0 +1,31 @@ +package ccipdeployment + +import ( + "github.com/ethereum/go-ethereum/common" + + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_home" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" +) + +func NewTestRMNStaticConfig() rmn_home.RMNHomeStaticConfig { + return rmn_home.RMNHomeStaticConfig{ + Nodes: []rmn_home.RMNHomeNode{}, + OffchainConfig: []byte("static config"), + } +} + +func NewTestRMNDynamicConfig() rmn_home.RMNHomeDynamicConfig { + return rmn_home.RMNHomeDynamicConfig{ + SourceChains: []rmn_home.RMNHomeSourceChain{}, + OffchainConfig: []byte("dynamic config"), + } +} + +func NewTestNodeOperator(admin common.Address) []capabilities_registry.CapabilitiesRegistryNodeOperator { + return []capabilities_registry.CapabilitiesRegistryNodeOperator{ + { + Admin: admin, + Name: "NodeOperator", + }, + } +} diff --git a/deployment/changeset.go b/deployment/changeset.go index 687d772bf73..e6c0988e67e 100644 --- a/deployment/changeset.go +++ b/deployment/changeset.go @@ -8,7 +8,7 @@ import ( ) var ( - ErrInvalidConfig = errors.New("invalid config") + ErrInvalidConfig = errors.New("invalid changeset config") ) // ChangeSet represents a set of changes to be made to an environment. diff --git a/deployment/common/view/v1_0/capreg.go b/deployment/common/view/v1_0/capreg.go index 92d44af5983..2ddd5a13463 100644 --- a/deployment/common/view/v1_0/capreg.go +++ b/deployment/common/view/v1_0/capreg.go @@ -9,6 +9,7 @@ import ( "slices" "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/chainlink/deployment/common/view/types" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" @@ -250,9 +251,9 @@ func (cc CapabilitiesConfiguration) Validate() error { // NodeView is a serialization-friendly view of a node in the capabilities registry. type NodeView struct { NodeUniversalMetadata - NodeOperatorID uint32 `json:"node_operator_id"` - CapabilityIDs []string `json:"capability_ids,omitempty"` // hex 32 bytes - DONIDs []*big.Int `json:",don_ids, omitempty"` + NodeOperatorID uint32 `json:"node_operator_id"` + CapabilityIDs []string `json:"capability_ids,omitempty"` // hex 32 bytes + CapabilityDONIDs []*big.Int `json:"capability_don_ids,omitempty"` } // NodeUniversalMetadata is a serialization-friendly view of the universal metadata of a node in the capabilities registry. @@ -274,9 +275,9 @@ func NewNodeView(n capabilities_registry.INodeInfoProviderNodeInfo) NodeView { P2pId: p2pkey.PeerID(n.P2pId), EncryptionPublicKey: hex.EncodeToString(n.EncryptionPublicKey[:]), }, - NodeOperatorID: n.NodeOperatorId, - CapabilityIDs: hexIds(n.HashedCapabilityIds), - DONIDs: n.CapabilitiesDONIds, + NodeOperatorID: n.NodeOperatorId, + CapabilityIDs: hexIds(n.HashedCapabilityIds), + CapabilityDONIDs: n.CapabilitiesDONIds, } } @@ -367,7 +368,7 @@ func hexIds(ids [][32]byte) []string { func (v DonView) hasNode(node NodeView) bool { donId := big.NewInt(int64(v.ID)) - return slices.ContainsFunc(node.DONIDs, func(elem *big.Int) bool { return elem.Cmp(donId) == 0 }) + return slices.ContainsFunc(node.CapabilityDONIDs, func(elem *big.Int) bool { return elem.Cmp(donId) == 0 }) || node.WorkflowDONID == v.ID } func (v DonView) hasCapability(candidate CapabilityView) bool { diff --git a/deployment/environment/clo/utils.go b/deployment/environment/clo/utils.go new file mode 100644 index 00000000000..79502ef6706 --- /dev/null +++ b/deployment/environment/clo/utils.go @@ -0,0 +1,32 @@ +package clo + +import ( + jd "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/node" + "github.com/smartcontractkit/chainlink/deployment/environment/clo/models" +) + +// NewChainConfig creates a new JobDistributor ChainConfig from a clo model NodeChainConfig +func NewChainConfig(chain *models.NodeChainConfig) *jd.ChainConfig { + return &jd.ChainConfig{ + Chain: &jd.Chain{ + Id: chain.Network.ChainID, + Type: jd.ChainType_CHAIN_TYPE_EVM, // TODO: support other chain types + }, + + AccountAddress: chain.AccountAddress, + AdminAddress: chain.AdminAddress, + Ocr2Config: &jd.OCR2Config{ + Enabled: chain.Ocr2Config.Enabled, + P2PKeyBundle: &jd.OCR2Config_P2PKeyBundle{ + PeerId: chain.Ocr2Config.P2pKeyBundle.PeerID, + PublicKey: chain.Ocr2Config.P2pKeyBundle.PublicKey, + }, + OcrKeyBundle: &jd.OCR2Config_OCRKeyBundle{ + BundleId: chain.Ocr2Config.OcrKeyBundle.BundleID, + OnchainSigningAddress: chain.Ocr2Config.OcrKeyBundle.OnchainSigningAddress, + OffchainPublicKey: chain.Ocr2Config.OcrKeyBundle.OffchainPublicKey, + ConfigPublicKey: chain.Ocr2Config.OcrKeyBundle.ConfigPublicKey, + }, + }, + } +} diff --git a/deployment/environment/memory/node.go b/deployment/environment/memory/node.go index a2a690cbae5..90ad264faa9 100644 --- a/deployment/environment/memory/node.go +++ b/deployment/environment/memory/node.go @@ -145,10 +145,17 @@ func NewNode( CSAETHKeystore: kStore, } + // Build Beholder auth + ctx := tests.Context(t) + require.NoError(t, master.Unlock(ctx, "password")) + require.NoError(t, master.CSA().EnsureKey(ctx)) + beholderAuthHeaders, csaPubKeyHex, err := keystore.BuildBeholderAuth(master) + require.NoError(t, err) + // Build relayer factory with EVM. relayerFactory := chainlink.RelayerFactory{ Logger: lggr, - LoopRegistry: plugins.NewLoopRegistry(lggr.Named("LoopRegistry"), cfg.Tracing(), cfg.Telemetry()), + LoopRegistry: plugins.NewLoopRegistry(lggr.Named("LoopRegistry"), cfg.Tracing(), cfg.Telemetry(), beholderAuthHeaders, csaPubKeyHex), GRPCOpts: loop.GRPCOpts{}, CapabilitiesRegistry: capabilities.NewRegistry(lggr), } @@ -168,7 +175,7 @@ func NewNode( RestrictedHTTPClient: &http.Client{}, AuditLogger: audit.NoopLogger, MailMon: mailMon, - LoopRegistry: plugins.NewLoopRegistry(lggr, cfg.Tracing(), cfg.Telemetry()), + LoopRegistry: plugins.NewLoopRegistry(lggr, cfg.Tracing(), cfg.Telemetry(), beholderAuthHeaders, csaPubKeyHex), }) require.NoError(t, err) t.Cleanup(func() { @@ -193,7 +200,6 @@ type Keys struct { func CreateKeys(t *testing.T, app chainlink.Application, chains map[uint64]EVMChain) Keys { ctx := tests.Context(t) - require.NoError(t, app.GetKeyStore().Unlock(ctx, "password")) _, err := app.GetKeyStore().P2P().Create(ctx) require.NoError(t, err) diff --git a/deployment/go.mod b/deployment/go.mod index e88dd743849..ce15ace764d 100644 --- a/deployment/go.mod +++ b/deployment/go.mod @@ -23,8 +23,8 @@ require ( github.com/sethvargo/go-retry v0.2.4 github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240926212305-a6deabdfce86 github.com/smartcontractkit/chain-selectors v1.0.27 - github.com/smartcontractkit/chainlink-ccip v0.0.0-20241106140121-4c9ee21ab422 - github.com/smartcontractkit/chainlink-common v0.3.1-0.20241106142051-c7bded1c08ae + github.com/smartcontractkit/chainlink-ccip v0.0.0-20241111114733-aa3b2f8e9f94 + github.com/smartcontractkit/chainlink-common v0.3.1-0.20241109002240-af894848b3b4 github.com/smartcontractkit/chainlink-protos/job-distributor v0.4.0 github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.13 github.com/smartcontractkit/chainlink/v2 v2.0.0-00010101000000-000000000000 @@ -34,6 +34,7 @@ require ( github.com/testcontainers/testcontainers-go v0.34.0 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 + golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c golang.org/x/sync v0.8.0 google.golang.org/grpc v1.67.1 google.golang.org/protobuf v1.35.1 @@ -266,7 +267,7 @@ require ( github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-msgpack v0.5.5 // indirect - github.com/hashicorp/go-plugin v1.6.2-0.20240829161738-06afb6d7ae99 // indirect + github.com/hashicorp/go-plugin v1.6.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-sockaddr v1.0.6 // indirect @@ -478,7 +479,6 @@ require ( go4.org/netipx v0.0.0-20230125063823-8449b0a6169f // indirect golang.org/x/arch v0.11.0 // indirect golang.org/x/crypto v0.28.0 // indirect - golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect golang.org/x/mod v0.21.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect diff --git a/deployment/go.sum b/deployment/go.sum index 07d83f9a968..e3a008b34e3 100644 --- a/deployment/go.sum +++ b/deployment/go.sum @@ -850,8 +850,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-plugin v1.6.2 h1:zdGAEd0V1lCaU0u+MxWQhtSDQmahpkwOun8U8EiRVog= +github.com/hashicorp/go-plugin v1.6.2/go.mod h1:CkgLQ5CZqNmdL9U9JzM532t8ZiYQ35+pj3b1FD37R0Q= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= @@ -1382,10 +1382,10 @@ github.com/smartcontractkit/chain-selectors v1.0.27 h1:VE/ftX9Aae4gnw67yR1raKi+3 github.com/smartcontractkit/chain-selectors v1.0.27/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgBc2xpDKBco/Q4h4ydl6+UUU= github.com/smartcontractkit/chainlink-automation v0.8.1/go.mod h1:Iij36PvWZ6blrdC5A/nrQUBuf3MH3JvsBB9sSyc9W08= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241106140121-4c9ee21ab422 h1:VfH/AW5NtTmroY9zz6OYCPFbFTqpMyJ2ubgT9ahYf3U= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241106140121-4c9ee21ab422/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241106142051-c7bded1c08ae h1:uqce0bjNVYzFrrVLafXgyn8SVNdfOtZekLfAwQihHiA= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241106142051-c7bded1c08ae/go.mod h1:TQ9/KKXZ9vr8QAlUquqGpSvDCpR+DtABKPXZY4CiRns= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241111114733-aa3b2f8e9f94 h1:BeLnOf2KKQpJj9nzfnE7QEg9ZqJ2jy/sbpNYVixVM2Y= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241111114733-aa3b2f8e9f94/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241109002240-af894848b3b4 h1:e+uFsxQ21tMQKRu4oBXKycNzoR30vO/7STBtqtDvQJQ= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241109002240-af894848b3b4/go.mod h1:ny87uTW6hLjCTLiBqBRNFEhETSXhHWevYlPclT5lSco= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f h1:BwrIaQIx5Iy6eT+DfLhFfK2XqjxRm74mVdlX8gbu4dw= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f/go.mod h1:wHtwSR3F1CQSJJZDQKuqaqFYnvkT+kMyget7dl8Clvo= github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241018134907-a00ba3729b5e h1:JiETqdNM0bktAUGMc62COwXIaw3rR3M77Me6bBLG0Fg= diff --git a/deployment/keystone/changeset/append_node_capbilities.go b/deployment/keystone/changeset/append_node_capbilities.go index 0cee9b442c8..ae654b7017d 100644 --- a/deployment/keystone/changeset/append_node_capbilities.go +++ b/deployment/keystone/changeset/append_node_capbilities.go @@ -56,6 +56,5 @@ func (req *AppendNodeCapabilitiesRequest) convert(e deployment.Environment) (*in Chain: registryChain, Registry: registry, P2pToCapabilities: req.P2pToCapabilities, - NopToNodes: req.NopToNodes, }, nil } diff --git a/deployment/keystone/changeset/internal/append_node_capabilities.go b/deployment/keystone/changeset/internal/append_node_capabilities.go index f917039e8e0..e74d3178ef2 100644 --- a/deployment/keystone/changeset/internal/append_node_capabilities.go +++ b/deployment/keystone/changeset/internal/append_node_capabilities.go @@ -15,16 +15,12 @@ type AppendNodeCapabilitiesRequest struct { Registry *kcr.CapabilitiesRegistry P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability - NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*P2PSignerEnc } func (req *AppendNodeCapabilitiesRequest) Validate() error { if len(req.P2pToCapabilities) == 0 { return fmt.Errorf("p2pToCapabilities is empty") } - if len(req.NopToNodes) == 0 { - return fmt.Errorf("nopToNodes is empty") - } if req.Registry == nil { return fmt.Errorf("registry is nil") } @@ -59,7 +55,6 @@ func AppendNodeCapabilitiesImpl(lggr logger.Logger, req *AppendNodeCapabilitiesR Chain: req.Chain, Registry: req.Registry, P2pToCapabilities: capsByPeer, - NopToNodes: req.NopToNodes, } resp, err := UpdateNodes(lggr, updateNodesReq) if err != nil { diff --git a/deployment/keystone/changeset/internal/append_node_capabilities_test.go b/deployment/keystone/changeset/internal/append_node_capabilities_test.go index 48a9af3e38d..d28dcd73230 100644 --- a/deployment/keystone/changeset/internal/append_node_capabilities_test.go +++ b/deployment/keystone/changeset/internal/append_node_capabilities_test.go @@ -84,7 +84,6 @@ func TestAppendNodeCapabilities(t *testing.T) { }, }, }, - NopToNodes: nopToNodes, }, }, want: deployment.ChangesetOutput{}, @@ -93,7 +92,6 @@ func TestAppendNodeCapabilities(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - // chagen the name and args to be mor egeneral setupResp := kstest.SetupTestRegistry(t, lggr, tt.args.initialState) tt.args.req.Registry = setupResp.Registry diff --git a/deployment/keystone/changeset/internal/test/utils.go b/deployment/keystone/changeset/internal/test/utils.go index 64b08a70150..cea20fd327d 100644 --- a/deployment/keystone/changeset/internal/test/utils.go +++ b/deployment/keystone/changeset/internal/test/utils.go @@ -8,21 +8,30 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + capabilitiespb "github.com/smartcontractkit/chainlink-common/pkg/capabilities/pb" "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/values" "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/environment/memory" kslib "github.com/smartcontractkit/chainlink/deployment/keystone" internal "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" ) +type Don struct { + Name string + P2PIDs []p2pkey.PeerID + CapabilityConfigs []internal.CapabilityConfig +} type SetupTestRegistryRequest struct { P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc - DonToNodes map[string][]*internal.P2PSignerEnc + Dons []Don } type SetupTestRegistryResponse struct { @@ -59,21 +68,32 @@ func SetupTestRegistry(t *testing.T, lggr logger.Logger, req *SetupTestRegistryR } require.Len(t, registeredCapabilities, len(expectedDeduped)) - // add the nodes with the phony capabilities. cannot register a node without a capability and capability must exist - // to do this make an initial phony request and extract the node params - initialp2pToCapabilities := make(map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability) + // make the nodes and register node + var nodeParams []kcr.CapabilitiesRegistryNodeParams + initialp2pToCapabilities := make(map[p2pkey.PeerID][][32]byte) for p2pID := range req.P2pToCapabilities { - initialp2pToCapabilities[p2pID] = vanillaCapabilities(registeredCapabilities) + initialp2pToCapabilities[p2pID] = mustCapabilityIds(t, registry, registeredCapabilities) } - phonyRequest := &internal.UpdateNodesRequest{ - Chain: chain, - Registry: registry, - P2pToCapabilities: req.P2pToCapabilities, - NopToNodes: req.NopToNodes, + // create node with initial capabilities assigned to nop + for i, nop := range nops { + if _, exists := req.NopToNodes[nop]; !exists { + require.Fail(t, "missing nopToNodes for %s", nop.Name) + } + for _, p2pSignerEnc := range req.NopToNodes[nop] { + nodeParams = append(nodeParams, kcr.CapabilitiesRegistryNodeParams{ + Signer: p2pSignerEnc.Signer, + P2pId: p2pSignerEnc.P2PKey, + EncryptionPublicKey: p2pSignerEnc.EncryptionPublicKey, + HashedCapabilityIds: initialp2pToCapabilities[p2pSignerEnc.P2PKey], + NodeOperatorId: uint32(i + 1), // nopid in contract is 1-indexed + }) + } } - nodeParams, err := phonyRequest.NodeParams() - require.NoError(t, err) addNodes(t, lggr, chain, registry, nodeParams) + + // add the Dons + addDons(t, lggr, chain, registry, capCache, req.Dons) + return &SetupTestRegistryResponse{ Registry: registry, Chain: chain, @@ -89,7 +109,7 @@ func deployCapReg(t *testing.T, lggr logger.Logger, chain deployment.Chain) *kcr } func addNops(t *testing.T, lggr logger.Logger, chain deployment.Chain, registry *kcr.CapabilitiesRegistry, nops []kcr.CapabilitiesRegistryNodeOperator) *kslib.RegisterNOPSResponse { - resp, err := kslib.RegisterNOPS(context.TODO(), kslib.RegisterNOPSRequest{ + resp, err := kslib.RegisterNOPS(context.TODO(), lggr, kslib.RegisterNOPSRequest{ Chain: chain, Registry: registry, Nops: nops, @@ -108,6 +128,50 @@ func addNodes(t *testing.T, lggr logger.Logger, chain deployment.Chain, registry require.NoError(t, err) } +func addDons(t *testing.T, lggr logger.Logger, chain deployment.Chain, registry *kcr.CapabilitiesRegistry, capCache *CapabilityCache, dons []Don) { + for _, don := range dons { + acceptsWorkflows := false + // lookup the capabilities + var capConfigs []kcr.CapabilitiesRegistryCapabilityConfiguration + for _, ccfg := range don.CapabilityConfigs { + var cc = kcr.CapabilitiesRegistryCapabilityConfiguration{ + CapabilityId: [32]byte{}, + Config: ccfg.Config, + } + if cc.Config == nil { + cc.Config = defaultCapConfig(t, ccfg.Capability) + } + var exists bool + //var cc kcr.CapabilitiesRegistryCapabilityConfiguration{} + cc.CapabilityId, exists = capCache.Get(ccfg.Capability) + require.True(t, exists, "capability not found in cache %v", ccfg.Capability) + capConfigs = append(capConfigs, cc) + if ccfg.Capability.CapabilityType == 2 { // ocr3 capabilities + acceptsWorkflows = true + } + } + // add the don + isPublic := true + f := len(don.P2PIDs)/3 + 1 + tx, err := registry.AddDON(chain.DeployerKey, internal.PeerIDsToBytes(don.P2PIDs), capConfigs, isPublic, acceptsWorkflows, uint8(f)) + if err != nil { + err2 := kslib.DecodeErr(kcr.CapabilitiesRegistryABI, err) + require.Fail(t, fmt.Sprintf("failed to call AddDON: %s: %s", err, err2)) + } + _, err = chain.Confirm(tx) + require.NoError(t, err) + } +} + +func defaultCapConfig(t *testing.T, cap kcr.CapabilitiesRegistryCapability) []byte { + empty := &capabilitiespb.CapabilityConfig{ + DefaultConfig: values.Proto(values.EmptyMap()).GetMapValue(), + } + emptyb, err := proto.Marshal(empty) + require.NoError(t, err) + return emptyb +} + // CapabilityCache tracks registered capabilities by name type CapabilityCache struct { t *testing.T @@ -120,6 +184,10 @@ func NewCapabiltyCache(t *testing.T) *CapabilityCache { nameToId: make(map[string][32]byte), } } +func (cc *CapabilityCache) Get(cap kcr.CapabilitiesRegistryCapability) ([32]byte, bool) { + id, exists := cc.nameToId[kslib.CapabilityID(cap)] + return id, exists +} // AddCapabilities adds the capabilities to the registry and returns the registered capabilities // if the capability is already registered, it will not be re-registered @@ -182,10 +250,28 @@ func testChain(t *testing.T) deployment.Chain { return chain } -func vanillaCapabilities(rcs []kslib.RegisteredCapability) []kcr.CapabilitiesRegistryCapability { - out := make([]kcr.CapabilitiesRegistryCapability, len(rcs)) +func capabilityIds(registry *capabilities_registry.CapabilitiesRegistry, rcs []kslib.RegisteredCapability) ([][32]byte, error) { + out := make([][32]byte, len(rcs)) for i := range rcs { - out[i] = rcs[i].CapabilitiesRegistryCapability + id, err := registry.GetHashedCapabilityId(&bind.CallOpts{}, rcs[i].LabelledName, rcs[i].Version) + if err != nil { + return nil, fmt.Errorf("failed to get capability id: %w", err) + } + out[i] = id } + return out, nil +} + +func mustCapabilityIds(t *testing.T, registry *capabilities_registry.CapabilitiesRegistry, rcs []kslib.RegisteredCapability) [][32]byte { + t.Helper() + out, err := capabilityIds(registry, rcs) + require.NoError(t, err) return out } + +func MustCapabilityId(t *testing.T, registry *capabilities_registry.CapabilitiesRegistry, cap capabilities_registry.CapabilitiesRegistryCapability) [32]byte { + t.Helper() + id, err := registry.GetHashedCapabilityId(&bind.CallOpts{}, cap.LabelledName, cap.Version) + require.NoError(t, err) + return id +} diff --git a/deployment/keystone/changeset/internal/update_don.go b/deployment/keystone/changeset/internal/update_don.go new file mode 100644 index 00000000000..4883368dc4d --- /dev/null +++ b/deployment/keystone/changeset/internal/update_don.go @@ -0,0 +1,194 @@ +package internal + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "sort" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" + "google.golang.org/protobuf/proto" + + kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" + + kslib "github.com/smartcontractkit/chainlink/deployment/keystone" +) + +// CapabilityConfig is a struct that holds a capability and its configuration +type CapabilityConfig struct { + Capability kcr.CapabilitiesRegistryCapability + Config []byte // this is the marshalled proto config. if nil, a default config is used +} + +type UpdateDonRequest struct { + Registry *kcr.CapabilitiesRegistry + Chain deployment.Chain + + P2PIDs []p2pkey.PeerID // this is the unique identifier for the don + CapabilityConfigs []CapabilityConfig // if Config subfield is nil, a default config is used +} + +func (r *UpdateDonRequest) appendNodeCapabilitiesRequest() *AppendNodeCapabilitiesRequest { + out := &AppendNodeCapabilitiesRequest{ + Chain: r.Chain, + Registry: r.Registry, + P2pToCapabilities: make(map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability), + } + for _, p2pid := range r.P2PIDs { + if _, exists := out.P2pToCapabilities[p2pid]; !exists { + out.P2pToCapabilities[p2pid] = make([]kcr.CapabilitiesRegistryCapability, 0) + } + for _, cc := range r.CapabilityConfigs { + out.P2pToCapabilities[p2pid] = append(out.P2pToCapabilities[p2pid], cc.Capability) + } + } + return out +} + +func (r *UpdateDonRequest) Validate() error { + if r.Registry == nil { + return fmt.Errorf("registry is required") + } + if len(r.P2PIDs) == 0 { + return fmt.Errorf("p2pIDs is required") + } + return nil +} + +type UpdateDonResponse struct { + DonInfo kcr.CapabilitiesRegistryDONInfo +} + +func UpdateDon(lggr logger.Logger, req *UpdateDonRequest) (*UpdateDonResponse, error) { + if err := req.Validate(); err != nil { + return nil, fmt.Errorf("failed to validate request: %w", err) + } + + getDonsResp, err := req.Registry.GetDONs(&bind.CallOpts{}) + if err != nil { + return nil, fmt.Errorf("failed to get Dons: %w", err) + } + + don, err := lookupDonByPeerIDs(getDonsResp, req.P2PIDs) + if err != nil { + return nil, fmt.Errorf("failed to lookup don by p2pIDs: %w", err) + } + cfgs, err := computeConfigs(req.Registry, req.CapabilityConfigs, don) + if err != nil { + return nil, fmt.Errorf("failed to compute configs: %w", err) + } + + _, err = AppendNodeCapabilitiesImpl(lggr, req.appendNodeCapabilitiesRequest()) + if err != nil { + return nil, fmt.Errorf("failed to append node capabilities: %w", err) + } + + tx, err := req.Registry.UpdateDON(req.Chain.DeployerKey, don.Id, don.NodeP2PIds, cfgs, don.IsPublic, don.F) + if err != nil { + err = kslib.DecodeErr(kcr.CapabilitiesRegistryABI, err) + return nil, fmt.Errorf("failed to call UpdateDON: %w", err) + } + + _, err = req.Chain.Confirm(tx) + if err != nil { + return nil, fmt.Errorf("failed to confirm UpdateDON transaction %s: %w", tx.Hash().String(), err) + } + out := don + out.CapabilityConfigurations = cfgs + return &UpdateDonResponse{DonInfo: out}, nil +} + +func PeerIDsToBytes(p2pIDs []p2pkey.PeerID) [][32]byte { + out := make([][32]byte, len(p2pIDs)) + for i, p2pID := range p2pIDs { + out[i] = p2pID + } + return out +} + +func BytesToPeerIDs(p2pIDs [][32]byte) []p2pkey.PeerID { + out := make([]p2pkey.PeerID, len(p2pIDs)) + for i, p2pID := range p2pIDs { + out[i] = p2pID + } + return out +} + +func computeConfigs(registry *kcr.CapabilitiesRegistry, caps []CapabilityConfig, donInfo kcr.CapabilitiesRegistryDONInfo) ([]kcr.CapabilitiesRegistryCapabilityConfiguration, error) { + out := make([]kcr.CapabilitiesRegistryCapabilityConfiguration, len(caps)) + for i, cap := range caps { + out[i] = kcr.CapabilitiesRegistryCapabilityConfiguration{} + id, err := registry.GetHashedCapabilityId(&bind.CallOpts{}, cap.Capability.LabelledName, cap.Capability.Version) + if err != nil { + return nil, fmt.Errorf("failed to get capability id: %w", err) + } + out[i].CapabilityId = id + if out[i].Config == nil { + c := kslib.DefaultCapConfig(cap.Capability.CapabilityType, int(donInfo.F)) + cb, err := proto.Marshal(c) + if err != nil { + return nil, fmt.Errorf("failed to marshal capability config for %v: %w", c, err) + } + out[i].Config = cb + } + } + return out, nil +} + +func SortedHash(p2pids [][32]byte) string { + sha256Hash := sha256.New() + sort.Slice(p2pids, func(i, j int) bool { + return bytes.Compare(p2pids[i][:], p2pids[j][:]) < 0 + }) + for _, id := range p2pids { + sha256Hash.Write(id[:]) + } + return hex.EncodeToString(sha256Hash.Sum(nil)) +} + +func lookupDonByPeerIDs(donResp []kcr.CapabilitiesRegistryDONInfo, wanted []p2pkey.PeerID) (kcr.CapabilitiesRegistryDONInfo, error) { + var don kcr.CapabilitiesRegistryDONInfo + wantedDonID := SortedHash(PeerIDsToBytes(wanted)) + found := false + for i, di := range donResp { + gotID := SortedHash(di.NodeP2PIds) + if gotID == wantedDonID { + don = donResp[i] + found = true + break + } + } + if !found { + return don, verboseDonNotFound(donResp, wanted) + } + return don, nil +} + +func verboseDonNotFound(donResp []kcr.CapabilitiesRegistryDONInfo, wanted []p2pkey.PeerID) error { + type debugDonInfo struct { + OnchainID uint32 + P2PIDsHash string + Want []p2pkey.PeerID + Got []p2pkey.PeerID + } + debugIds := make([]debugDonInfo, len(donResp)) + for i, di := range donResp { + debugIds[i] = debugDonInfo{ + OnchainID: di.Id, + P2PIDsHash: SortedHash(di.NodeP2PIds), + Want: wanted, + Got: BytesToPeerIDs(di.NodeP2PIds), + } + } + wantedID := SortedHash(PeerIDsToBytes(wanted)) + b, err2 := json.Marshal(debugIds) + if err2 == nil { + return fmt.Errorf("don not found by p2pIDs %s in %s", wantedID, b) + } + return fmt.Errorf("don not found by p2pIDs %s in %v", wantedID, debugIds) +} diff --git a/deployment/keystone/changeset/internal/update_don_test.go b/deployment/keystone/changeset/internal/update_don_test.go new file mode 100644 index 00000000000..baedda5e93d --- /dev/null +++ b/deployment/keystone/changeset/internal/update_don_test.go @@ -0,0 +1,321 @@ +package internal_test + +import ( + "bytes" + "math/big" + "sort" + "strconv" + "testing" + + "github.com/ethereum/go-ethereum/common" + chainsel "github.com/smartcontractkit/chain-selectors" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/environment/clo/models" + kslib "github.com/smartcontractkit/chainlink/deployment/keystone" + kscs "github.com/smartcontractkit/chainlink/deployment/keystone/changeset" + "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal" + kstest "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal/test" + kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestUpdateDon(t *testing.T) { + var ( + registryChain = chainsel.TEST_90000001 + // nodes + p2p_1 = p2pkey.MustNewV2XXXTestingOnly(big.NewInt(100)) + pubKey_1 = "11114981a6119ca3f932cdb8c402d71a72d672adae7849f581ecff8b8e1098e7" // valid csa key + admin_1 = common.HexToAddress("0x1111567890123456789012345678901234567890") // valid eth address + signing_1 = "11117293a4cc2621b61193135a95928735e4795f" // valid eth address + node_1 = newNode(t, minimalNodeCfg{ + id: "test node 1", + pubKey: pubKey_1, + registryChain: registryChain, + p2p: p2p_1, + signingAddr: signing_1, + admin: admin_1, + }) + + p2p_2 = p2pkey.MustNewV2XXXTestingOnly(big.NewInt(200)) + pubKey_2 = "22224981a6119ca3f932cdb8c402d71a72d672adae7849f581ecff8b8e109000" // valid csa key + admin_2 = common.HexToAddress("0x2222567890123456789012345678901234567891") // valid eth address + signing_2 = "22227293a4cc2621b61193135a95928735e4ffff" // valid eth address + node_2 = newNode(t, minimalNodeCfg{ + id: "test node 2", + pubKey: pubKey_2, + registryChain: registryChain, + p2p: p2p_2, + signingAddr: signing_2, + admin: admin_2, + }) + + p2p_3 = p2pkey.MustNewV2XXXTestingOnly(big.NewInt(300)) + pubKey_3 = "33334981a6119ca3f932cdb8c402d71a72d672adae7849f581ecff8b8e109111" // valid csa key + admin_3 = common.HexToAddress("0x3333567890123456789012345678901234567892") // valid eth address + signing_3 = "33337293a4cc2621b61193135a959287aaaaffff" // valid eth address + node_3 = newNode(t, minimalNodeCfg{ + id: "test node 3", + pubKey: pubKey_3, + registryChain: registryChain, + p2p: p2p_3, + signingAddr: signing_3, + admin: admin_3, + }) + + p2p_4 = p2pkey.MustNewV2XXXTestingOnly(big.NewInt(400)) + pubKey_4 = "44444981a6119ca3f932cdb8c402d71a72d672adae7849f581ecff8b8e109222" // valid csa key + admin_4 = common.HexToAddress("0x4444567890123456789012345678901234567893") // valid eth address + signing_4 = "44447293a4cc2621b61193135a959287aaaaffff" // valid eth address + node_4 = newNode(t, minimalNodeCfg{ + id: "test node 4", + pubKey: pubKey_4, + registryChain: registryChain, + p2p: p2p_4, + signingAddr: signing_4, + admin: admin_4, + }) + // capabilities + cap_A = kcr.CapabilitiesRegistryCapability{ + LabelledName: "test", + Version: "1.0.0", + CapabilityType: 0, + } + + cap_B = kcr.CapabilitiesRegistryCapability{ + LabelledName: "cap b", + Version: "1.0.0", + CapabilityType: 1, + } + ) + + lggr := logger.Test(t) + + t.Run("empty", func(t *testing.T) { + cfg := setupUpdateDonTestConfig{ + dons: []kslib.DonCapabilities{ + { + Name: "don 1", + Nops: []*models.NodeOperator{ + { + Name: "nop 1", + Nodes: []*models.Node{node_1, node_2, node_3, node_4}, + }, + }, + Capabilities: []kcr.CapabilitiesRegistryCapability{cap_A}, + }, + }, + } + + testCfg := setupUpdateDonTest(t, lggr, cfg) + + req := &internal.UpdateDonRequest{ + Registry: testCfg.Registry, + Chain: testCfg.Chain, + P2PIDs: []p2pkey.PeerID{p2p_1.PeerID(), p2p_2.PeerID(), p2p_3.PeerID(), p2p_4.PeerID()}, + CapabilityConfigs: []internal.CapabilityConfig{ + {Capability: cap_A}, {Capability: cap_B}, + }, + } + want := &internal.UpdateDonResponse{ + DonInfo: kcr.CapabilitiesRegistryDONInfo{ + Id: 1, + ConfigCount: 1, + NodeP2PIds: internal.PeerIDsToBytes([]p2pkey.PeerID{p2p_1.PeerID(), p2p_2.PeerID(), p2p_3.PeerID(), p2p_4.PeerID()}), + CapabilityConfigurations: []kcr.CapabilitiesRegistryCapabilityConfiguration{ + {CapabilityId: kstest.MustCapabilityId(t, testCfg.Registry, cap_A)}, + {CapabilityId: kstest.MustCapabilityId(t, testCfg.Registry, cap_B)}, + }, + }, + } + + got, err := internal.UpdateDon(lggr, req) + require.NoError(t, err) + assert.Equal(t, want.DonInfo.Id, got.DonInfo.Id) + assert.Equal(t, want.DonInfo.ConfigCount, got.DonInfo.ConfigCount) + assert.Equal(t, sortedP2Pids(want.DonInfo.NodeP2PIds), sortedP2Pids(got.DonInfo.NodeP2PIds)) + assert.Equal(t, capIds(want.DonInfo.CapabilityConfigurations), capIds(got.DonInfo.CapabilityConfigurations)) + + }) +} + +func sortedP2Pids(p2pids [][32]byte) [][32]byte { + // sha256Hash := sha256.New() + sort.Slice(p2pids, func(i, j int) bool { + return bytes.Compare(p2pids[i][:], p2pids[j][:]) < 0 + }) + return p2pids +} + +func capIds(ccs []kcr.CapabilitiesRegistryCapabilityConfiguration) [][32]byte { + out := make([][32]byte, len(ccs)) + for i, cc := range ccs { + out[i] = cc.CapabilityId + } + sort.Slice(out, func(i, j int) bool { + return bytes.Compare(out[i][:], out[j][:]) < 0 + }) + return out +} + +type minimalNodeCfg struct { + id string + pubKey string + registryChain chainsel.Chain + p2p p2pkey.KeyV2 + signingAddr string + admin common.Address +} + +func newNode(t *testing.T, cfg minimalNodeCfg) *models.Node { + t.Helper() + + return &models.Node{ + ID: cfg.id, + PublicKey: &cfg.pubKey, + ChainConfigs: []*models.NodeChainConfig{ + { + ID: "test chain", + Network: &models.Network{ + ID: "test network 1", + ChainID: strconv.FormatUint(cfg.registryChain.EvmChainID, 10), + ChainType: models.ChainTypeEvm, + }, + AdminAddress: cfg.admin.String(), + Ocr2Config: &models.NodeOCR2Config{ + P2pKeyBundle: &models.NodeOCR2ConfigP2PKeyBundle{ + PeerID: cfg.p2p.PeerID().String(), + }, + OcrKeyBundle: &models.NodeOCR2ConfigOCRKeyBundle{ + OnchainSigningAddress: cfg.signingAddr, + }, + }, + }, + }, + } +} + +type setupUpdateDonTestConfig struct { + dons []kslib.DonCapabilities +} + +type setupUpdateDonTestResult struct { + registry *kcr.CapabilitiesRegistry + chain deployment.Chain +} + +func setupUpdateDonTest(t *testing.T, lggr logger.Logger, cfg setupUpdateDonTestConfig) *kstest.SetupTestRegistryResponse { + t.Helper() + req := newSetupTestRegistryRequest(t, cfg.dons) + return kstest.SetupTestRegistry(t, lggr, req) +} + +func newSetupTestRegistryRequest(t *testing.T, dons []kslib.DonCapabilities) *kstest.SetupTestRegistryRequest { + t.Helper() + allNops := make(map[string]*models.NodeOperator) + for _, don := range dons { + for _, nop := range don.Nops { + nop := nop + n, exists := allNops[nop.ID] + if exists { + nop.Nodes = append(n.Nodes, nop.Nodes...) + } + allNops[nop.ID] = nop + } + } + var nops []*models.NodeOperator + for _, nop := range allNops { + nops = append(nops, nop) + } + nopsToNodes := makeNopToNodes(t, nops) + testDons := makeTestDon(t, dons) + p2pToCapabilities := makeP2PToCapabilities(t, dons) + req := &kstest.SetupTestRegistryRequest{ + NopToNodes: nopsToNodes, + Dons: testDons, + P2pToCapabilities: p2pToCapabilities, + } + return req +} + +func makeNopToNodes(t *testing.T, cloNops []*models.NodeOperator) map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc { + nopToNodes := make(map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc) + + for _, nop := range cloNops { + // all chain configs are the same wrt admin address & node keys + // so we can just use the first one + crnop := kcr.CapabilitiesRegistryNodeOperator{ + Name: nop.Name, + Admin: common.HexToAddress(nop.Nodes[0].ChainConfigs[0].AdminAddress), + } + var nodes []*internal.P2PSignerEnc + for _, node := range nop.Nodes { + require.NotNil(t, node.PublicKey, "public key is nil %s", node.ID) + // all chain configs are the same wrt admin address & node keys + p, err := kscs.NewP2PSignerEncFromCLO(node.ChainConfigs[0], *node.PublicKey) + require.NoError(t, err, "failed to make p2p signer enc from clo nod %s", node.ID) + nodes = append(nodes, p) + } + nopToNodes[crnop] = nodes + } + return nopToNodes +} + +func makeP2PToCapabilities(t *testing.T, dons []kslib.DonCapabilities) map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability { + p2pToCapabilities := make(map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability) + for _, don := range dons { + for _, nop := range don.Nops { + for _, node := range nop.Nodes { + for _, cap := range don.Capabilities { + p, err := kscs.NewP2PSignerEncFromCLO(node.ChainConfigs[0], *node.PublicKey) + require.NoError(t, err, "failed to make p2p signer enc from clo nod %s", node.ID) + p2pToCapabilities[p.P2PKey] = append(p2pToCapabilities[p.P2PKey], cap) + } + } + } + } + return p2pToCapabilities +} + +func makeTestDon(t *testing.T, dons []kslib.DonCapabilities) []kstest.Don { + out := make([]kstest.Don, len(dons)) + for i, don := range dons { + out[i] = testDon(t, don) + } + return out +} + +func testDon(t *testing.T, don kslib.DonCapabilities) kstest.Don { + var p2pids []p2pkey.PeerID + for _, nop := range don.Nops { + for _, node := range nop.Nodes { + // all chain configs are the same wrt admin address & node keys + // so we can just use the first one + p, err := kscs.NewP2PSignerEncFromCLO(node.ChainConfigs[0], *node.PublicKey) + require.NoError(t, err, "failed to make p2p signer enc from clo nod %s", node.ID) + p2pids = append(p2pids, p.P2PKey) + } + } + + var capabilityConfigs []internal.CapabilityConfig + for _, cap := range don.Capabilities { + capabilityConfigs = append(capabilityConfigs, internal.CapabilityConfig{ + Capability: cap, + }) + } + return kstest.Don{ + Name: don.Name, + P2PIDs: p2pids, + CapabilityConfigs: capabilityConfigs, + } +} + +func newP2PSignerEnc(signer [32]byte, p2pkey p2pkey.PeerID, encryptionPublicKey [32]byte) *internal.P2PSignerEnc { + return &internal.P2PSignerEnc{ + Signer: signer, + P2PKey: p2pkey, + EncryptionPublicKey: encryptionPublicKey, + } +} diff --git a/deployment/keystone/changeset/internal/update_node_capabilities.go b/deployment/keystone/changeset/internal/update_node_capabilities.go index 3d3e1e80607..c7e2e902437 100644 --- a/deployment/keystone/changeset/internal/update_node_capabilities.go +++ b/deployment/keystone/changeset/internal/update_node_capabilities.go @@ -15,16 +15,12 @@ type UpdateNodeCapabilitiesImplRequest struct { Registry *kcr.CapabilitiesRegistry P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability - NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*P2PSignerEnc } func (req *UpdateNodeCapabilitiesImplRequest) Validate() error { if len(req.P2pToCapabilities) == 0 { return fmt.Errorf("p2pToCapabilities is empty") } - if len(req.NopToNodes) == 0 { - return fmt.Errorf("nopToNodes is empty") - } if req.Registry == nil { return fmt.Errorf("registry is nil") } @@ -50,7 +46,6 @@ func UpdateNodeCapabilitiesImpl(lggr logger.Logger, req *UpdateNodeCapabilitiesI Chain: req.Chain, Registry: req.Registry, P2pToCapabilities: req.P2pToCapabilities, - NopToNodes: req.NopToNodes, } resp, err := UpdateNodes(lggr, updateNodesReq) if err != nil { @@ -58,59 +53,3 @@ func UpdateNodeCapabilitiesImpl(lggr logger.Logger, req *UpdateNodeCapabilitiesI } return resp, nil } - -/* -// AddCapabilities adds the capabilities to the registry -// it tries to add all capabilities in one go, if that fails, it falls back to adding them one by one -func AddCapabilities(lggr logger.Logger, registry *kcr.CapabilitiesRegistry, chain deployment.Chain, capabilities []kcr.CapabilitiesRegistryCapability) error { - if len(capabilities) == 0 { - return nil - } - // dedup capabilities - var deduped []kcr.CapabilitiesRegistryCapability - seen := make(map[string]struct{}) - for _, cap := range capabilities { - if _, ok := seen[CapabilityID(cap)]; !ok { - seen[CapabilityID(cap)] = struct{}{} - deduped = append(deduped, cap) - } - } - - tx, err := registry.AddCapabilities(chain.DeployerKey, deduped) - if err != nil { - err = DecodeErr(kcr.CapabilitiesRegistryABI, err) - // no typed errors in the abi, so we have to do string matching - // try to add all capabilities in one go, if that fails, fall back to 1-by-1 - if !strings.Contains(err.Error(), "CapabilityAlreadyExists") { - return fmt.Errorf("failed to call AddCapabilities: %w", err) - } - lggr.Warnw("capabilities already exist, falling back to 1-by-1", "capabilities", deduped) - for _, cap := range deduped { - tx, err = registry.AddCapabilities(chain.DeployerKey, []kcr.CapabilitiesRegistryCapability{cap}) - if err != nil { - err = DecodeErr(kcr.CapabilitiesRegistryABI, err) - if strings.Contains(err.Error(), "CapabilityAlreadyExists") { - lggr.Warnw("capability already exists, skipping", "capability", cap) - continue - } - return fmt.Errorf("failed to call AddCapabilities for capability %v: %w", cap, err) - } - // 1-by-1 tx is pending and we need to wait for it to be mined - _, err = chain.Confirm(tx) - if err != nil { - return fmt.Errorf("failed to confirm AddCapabilities confirm transaction %s: %w", tx.Hash().String(), err) - } - lggr.Debugw("registered capability", "capability", cap) - - } - } else { - // the bulk add tx is pending and we need to wait for it to be mined - _, err = chain.Confirm(tx) - if err != nil { - return fmt.Errorf("failed to confirm AddCapabilities confirm transaction %s: %w", tx.Hash().String(), err) - } - lggr.Info("registered capabilities", "capabilities", deduped) - } - return nil -} -*/ diff --git a/deployment/keystone/changeset/internal/update_node_capabilities_test.go b/deployment/keystone/changeset/internal/update_node_capabilities_test.go index d90840f5d13..0346ff20dd6 100644 --- a/deployment/keystone/changeset/internal/update_node_capabilities_test.go +++ b/deployment/keystone/changeset/internal/update_node_capabilities_test.go @@ -8,7 +8,6 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/deployment" - //"github.com/smartcontractkit/chainlink/deployment/keystone/changeset" kslib "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal" kstest "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal/test" kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" @@ -84,7 +83,6 @@ func TestUpdateNodeCapabilities(t *testing.T) { }, }, }, - NopToNodes: nopToNodes, }, }, want: deployment.ChangesetOutput{}, diff --git a/deployment/keystone/changeset/internal/update_nodes.go b/deployment/keystone/changeset/internal/update_nodes.go index 48face7086b..d263623cdc6 100644 --- a/deployment/keystone/changeset/internal/update_nodes.go +++ b/deployment/keystone/changeset/internal/update_nodes.go @@ -1,8 +1,10 @@ package internal import ( + "bytes" "errors" "fmt" + "sort" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -19,11 +21,10 @@ type UpdateNodesRequest struct { Registry *kcr.CapabilitiesRegistry P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability - NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*P2PSignerEnc } func (req *UpdateNodesRequest) NodeParams() ([]kcr.CapabilitiesRegistryNodeParams, error) { - return makeNodeParams(req.Registry, req.NopToNodes, req.P2pToCapabilities) + return makeNodeParams(req.Registry, req.P2pToCapabilities) } // P2PSignerEnc represent the key fields in kcr.CapabilitiesRegistryNodeParams @@ -38,9 +39,18 @@ func (req *UpdateNodesRequest) Validate() error { if len(req.P2pToCapabilities) == 0 { return errors.New("p2pToCapabilities is empty") } - if len(req.NopToNodes) == 0 { - return errors.New("nopToNodes is empty") + // no duplicate capabilities + for peer, caps := range req.P2pToCapabilities { + seen := make(map[string]struct{}) + for _, cap := range caps { + id := kslib.CapabilityID(cap) + if _, exists := seen[id]; exists { + return fmt.Errorf("duplicate capability %s for %s", id, peer) + } + seen[id] = struct{}{} + } } + if req.Registry == nil { return errors.New("registry is nil") } @@ -61,6 +71,7 @@ func UpdateNodes(lggr logger.Logger, req *UpdateNodesRequest) (*UpdateNodesRespo params, err := req.NodeParams() if err != nil { + err = kslib.DecodeErr(kcr.CapabilitiesRegistryABI, err) return nil, fmt.Errorf("failed to make node params: %w", err) } tx, err := req.Registry.UpdateNodes(req.Chain.DeployerKey, params) @@ -125,58 +136,45 @@ func AppendCapabilities(lggr logger.Logger, registry *kcr.CapabilitiesRegistry, } func makeNodeParams(registry *kcr.CapabilitiesRegistry, - nopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*P2PSignerEnc, p2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability) ([]kcr.CapabilitiesRegistryNodeParams, error) { - out := make([]kcr.CapabilitiesRegistryNodeParams, 0) - // get all the node operators from chain - registeredNops, err := registry.GetNodeOperators(&bind.CallOpts{}) - if err != nil { - return nil, fmt.Errorf("failed to get node operators: %w", err) + var out []kcr.CapabilitiesRegistryNodeParams + var p2pIds []p2pkey.PeerID + for p2pID := range p2pToCapabilities { + p2pIds = append(p2pIds, p2pID) } - // make a cache of capability from chain - var allCaps []kcr.CapabilitiesRegistryCapability - for _, caps := range p2pToCapabilities { - allCaps = append(allCaps, caps...) - } - capMap, err := fetchCapabilityIDs(registry, allCaps) + nodes, err := registry.GetNodesByP2PIds(&bind.CallOpts{}, PeerIDsToBytes(p2pIds)) if err != nil { - return nil, fmt.Errorf("failed to fetch capability ids: %w", err) + err = kslib.DecodeErr(kcr.CapabilitiesRegistryABI, err) + return nil, fmt.Errorf("failed to get nodes by p2p ids: %w", err) } - - // flatten the onchain state to list of node params filtered by the input nops and nodes - for idx, rnop := range registeredNops { - // nop id is 1-indexed. no way to get value from chain. must infer from index - nopID := uint32(idx + 1) - nodes, ok := nopToNodes[rnop] + for _, node := range nodes { + caps, ok := p2pToCapabilities[node.P2pId] if !ok { - continue + return nil, fmt.Errorf("capabilities not found for node %s", node.P2pId) } - for _, node := range nodes { - caps, ok := p2pToCapabilities[node.P2PKey] - if !ok { - return nil, fmt.Errorf("capabilities not found for node %s", node.P2PKey) - } - hashedCaps := make([][32]byte, len(caps)) - for i, cap := range caps { - hashedCap, exists := capMap[kslib.CapabilityID(cap)] - if !exists { - return nil, fmt.Errorf("capability id not found for %s", kslib.CapabilityID(cap)) - } - hashedCaps[i] = hashedCap - } - out = append(out, kcr.CapabilitiesRegistryNodeParams{ - NodeOperatorId: nopID, - P2pId: node.P2PKey, - HashedCapabilityIds: hashedCaps, - EncryptionPublicKey: node.EncryptionPublicKey, - Signer: node.Signer, - }) + ids, err := capabilityIds(registry, caps) + if err != nil { + return nil, fmt.Errorf("failed to get capability ids: %w", err) } + out = append(out, kcr.CapabilitiesRegistryNodeParams{ + NodeOperatorId: node.NodeOperatorId, + P2pId: node.P2pId, + HashedCapabilityIds: ids, + EncryptionPublicKey: node.EncryptionPublicKey, + Signer: node.Signer, + }) } + sort.Slice(out, func(i, j int) bool { + if out[i].NodeOperatorId == out[j].NodeOperatorId { + return bytes.Compare(out[i].P2pId[:], out[j].P2pId[:]) < 0 + } + return out[i].NodeOperatorId < out[j].NodeOperatorId + }) return out, nil + } // fetchkslib.CapabilityIDs fetches the capability ids for the given capabilities @@ -195,3 +193,15 @@ func fetchCapabilityIDs(registry *kcr.CapabilitiesRegistry, caps []kcr.Capabilit } return out, nil } + +func capabilityIds(registry *kcr.CapabilitiesRegistry, caps []kcr.CapabilitiesRegistryCapability) ([][32]byte, error) { + out := make([][32]byte, len(caps)) + for i, cap := range caps { + id, err := registry.GetHashedCapabilityId(&bind.CallOpts{}, cap.LabelledName, cap.Version) + if err != nil { + return nil, fmt.Errorf("failed to get capability id: %w", err) + } + out[i] = id + } + return out, nil +} diff --git a/deployment/keystone/changeset/internal/update_nodes_test.go b/deployment/keystone/changeset/internal/update_nodes_test.go index 3515ef13cbe..5488e5c761d 100644 --- a/deployment/keystone/changeset/internal/update_nodes_test.go +++ b/deployment/keystone/changeset/internal/update_nodes_test.go @@ -25,10 +25,9 @@ import ( func Test_UpdateNodesRequest_validate(t *testing.T) { type fields struct { p2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability - //nopToNodes map[uint32][]*internal.P2PSigner - nopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc - chain deployment.Chain - registry *kcr.CapabilitiesRegistry + nopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc + chain deployment.Chain + registry *kcr.CapabilitiesRegistry } tests := []struct { name string @@ -50,7 +49,6 @@ func Test_UpdateNodesRequest_validate(t *testing.T) { t.Run(tt.name, func(t *testing.T) { req := &internal.UpdateNodesRequest{ P2pToCapabilities: tt.fields.p2pToCapabilities, - NopToNodes: tt.fields.nopToNodes, Chain: tt.fields.chain, Registry: tt.fields.registry, } @@ -67,8 +65,9 @@ func TestUpdateNodes(t *testing.T) { lggr := logger.Test(t) type args struct { - lggr logger.Logger - req *internal.UpdateNodesRequest + lggr logger.Logger + req *internal.UpdateNodesRequest + nopsToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc } tests := []struct { name string @@ -90,18 +89,18 @@ func TestUpdateNodes(t *testing.T) { }, }, }, - NopToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ - testNop(t, "nop1"): []*internal.P2PSignerEnc{ - { - P2PKey: testPeerID(t, "peerID_1"), - Signer: [32]byte{0: 1, 1: 2}, - EncryptionPublicKey: [32]byte{0: 7, 1: 7}, - }, - }, - }, Chain: chain, Registry: nil, // set in test to ensure no conflicts }, + nopsToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ + testNop(t, "nop1"): []*internal.P2PSignerEnc{ + { + P2PKey: testPeerID(t, "peerID_1"), + Signer: [32]byte{0: 1, 1: 2}, + EncryptionPublicKey: [32]byte{0: 7, 1: 7}, + }, + }, + }, }, want: &internal.UpdateNodesResponse{ NodeParams: []kcr.CapabilitiesRegistryNodeParams{ @@ -116,6 +115,7 @@ func TestUpdateNodes(t *testing.T) { }, wantErr: false, }, + { name: "one node, two capabilities", args: args{ @@ -135,18 +135,18 @@ func TestUpdateNodes(t *testing.T) { }, }, }, - NopToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ - testNop(t, "nop1"): []*internal.P2PSignerEnc{ - { - P2PKey: testPeerID(t, "peerID_1"), - Signer: [32]byte{0: 1, 1: 2}, - EncryptionPublicKey: [32]byte{0: 7, 1: 7}, - }, - }, - }, Chain: chain, Registry: nil, // set in test to ensure no conflicts }, + nopsToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ + testNop(t, "nop1"): []*internal.P2PSignerEnc{ + { + P2PKey: testPeerID(t, "peerID_1"), + Signer: [32]byte{0: 1, 1: 2}, + EncryptionPublicKey: [32]byte{0: 7, 1: 7}, + }, + }, + }, }, want: &internal.UpdateNodesResponse{ NodeParams: []kcr.CapabilitiesRegistryNodeParams{ @@ -189,24 +189,24 @@ func TestUpdateNodes(t *testing.T) { }, }, }, - NopToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ - testNop(t, "nopA"): []*internal.P2PSignerEnc{ - { - P2PKey: testPeerID(t, "peerID_1"), - Signer: [32]byte{0: 1, 31: 1}, - EncryptionPublicKey: [32]byte{0: 7, 1: 7}, - }, + Chain: chain, + Registry: nil, // set in test to ensure no conflicts + }, + nopsToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ + testNop(t, "nopA"): []*internal.P2PSignerEnc{ + { + P2PKey: testPeerID(t, "peerID_1"), + Signer: [32]byte{0: 1, 31: 1}, + EncryptionPublicKey: [32]byte{0: 7, 1: 7}, }, - testNop(t, "nopB"): []*internal.P2PSignerEnc{ - { - P2PKey: testPeerID(t, "peerID_2"), - Signer: [32]byte{0: 2, 31: 2}, - EncryptionPublicKey: [32]byte{0: 7, 1: 7}, - }, + }, + testNop(t, "nopB"): []*internal.P2PSignerEnc{ + { + P2PKey: testPeerID(t, "peerID_2"), + Signer: [32]byte{0: 2, 31: 2}, + EncryptionPublicKey: [32]byte{0: 7, 1: 7}, }, }, - Chain: chain, - Registry: nil, // set in test to ensure no conflicts }, }, want: &internal.UpdateNodesResponse{ @@ -250,24 +250,24 @@ func TestUpdateNodes(t *testing.T) { }, }, }, - NopToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ - testNop(t, "nopA"): []*internal.P2PSignerEnc{ - { - P2PKey: testPeerID(t, "peerID_1"), - Signer: [32]byte{0: 1, 31: 1}, - EncryptionPublicKey: [32]byte{0: 7, 1: 7}, - }, + Chain: chain, + Registry: nil, // set in test to ensure no conflicts + }, + nopsToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ + testNop(t, "nopA"): []*internal.P2PSignerEnc{ + { + P2PKey: testPeerID(t, "peerID_1"), + Signer: [32]byte{0: 1, 31: 1}, + EncryptionPublicKey: [32]byte{0: 7, 1: 7}, }, - testNop(t, "nopB"): []*internal.P2PSignerEnc{ - { - P2PKey: testPeerID(t, "peerID_2"), - Signer: [32]byte{0: 2, 31: 2}, - EncryptionPublicKey: [32]byte{0: 7, 1: 7}, - }, + }, + testNop(t, "nopB"): []*internal.P2PSignerEnc{ + { + P2PKey: testPeerID(t, "peerID_2"), + Signer: [32]byte{0: 2, 31: 2}, + EncryptionPublicKey: [32]byte{0: 7, 1: 7}, }, }, - Chain: chain, - Registry: nil, // set in test to ensure no conflicts }, }, want: &internal.UpdateNodesResponse{ @@ -305,14 +305,12 @@ func TestUpdateNodes(t *testing.T) { } setupResp := kstest.SetupTestRegistry(t, tt.args.lggr, &kstest.SetupTestRegistryRequest{ P2pToCapabilities: initMap, - NopToNodes: tt.args.req.NopToNodes, + NopToNodes: tt.args.nopsToNodes, }) registry := setupResp.Registry tt.args.req.Registry = setupResp.Registry tt.args.req.Chain = setupResp.Chain - //registry := kstest.SetupUpdateNodes(t, tt.args.lggr, tt.args.req) - //tt.args.req.Registry = registry // register the capabilities that the Update will use expectedUpdatedCaps := make(map[p2pkey.PeerID][]kslib.RegisteredCapability) capCache := kstest.NewCapabiltyCache(t) @@ -414,7 +412,6 @@ func TestUpdateNodes(t *testing.T) { var req = &internal.UpdateNodesRequest{ P2pToCapabilities: p2pToCapabilitiesUpdated, - NopToNodes: nopToNodes, Chain: chain, Registry: registry, } diff --git a/deployment/keystone/changeset/types.go b/deployment/keystone/changeset/types.go new file mode 100644 index 00000000000..e8a86fa4272 --- /dev/null +++ b/deployment/keystone/changeset/types.go @@ -0,0 +1,50 @@ +package changeset + +import ( + "encoding/hex" + "errors" + "fmt" + + v1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/node" + "github.com/smartcontractkit/chainlink/deployment/environment/clo" + "github.com/smartcontractkit/chainlink/deployment/environment/clo/models" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" +) + +func NewP2PSignerEncFromCLO(cc *models.NodeChainConfig, pubkey string) (*P2PSignerEnc, error) { + ccfg := clo.NewChainConfig(cc) + var pubkeyB [32]byte + if _, err := hex.Decode(pubkeyB[:], []byte(pubkey)); err != nil { + return nil, fmt.Errorf("failed to decode pubkey %s: %w", pubkey, err) + } + return newP2PSignerEncFromJD(ccfg, pubkeyB) +} + +func newP2PSignerEncFromJD(ccfg *v1.ChainConfig, pubkey [32]byte) (*P2PSignerEnc, error) { + if ccfg == nil { + return nil, errors.New("nil ocr2config") + } + ocfg := ccfg.Ocr2Config + p2p := p2pkey.PeerID{} + if err := p2p.UnmarshalString(ocfg.P2PKeyBundle.PeerId); err != nil { + return nil, fmt.Errorf("failed to unmarshal peer id %s: %w", ocfg.P2PKeyBundle.PeerId, err) + } + + signer := ocfg.OcrKeyBundle.OnchainSigningAddress + if len(signer) != 40 { + return nil, fmt.Errorf("invalid onchain signing address %s", ocfg.OcrKeyBundle.OnchainSigningAddress) + } + signerB, err := hex.DecodeString(signer) + if err != nil { + return nil, fmt.Errorf("failed to convert signer %s: %w", signer, err) + } + + var sigb [32]byte + copy(sigb[:], signerB) + + return &P2PSignerEnc{ + Signer: sigb, + P2PKey: p2p, + EncryptionPublicKey: pubkey, // TODO. no current way to get this from the node itself (and therefore not in clo or jd) + }, nil +} diff --git a/deployment/keystone/changeset/update_don.go b/deployment/keystone/changeset/update_don.go new file mode 100644 index 00000000000..1a535c5aa11 --- /dev/null +++ b/deployment/keystone/changeset/update_don.go @@ -0,0 +1,32 @@ +package changeset + +import ( + "fmt" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal" + kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" +) + +var _ deployment.ChangeSet = UpdateDon + +// CapabilityConfig is a struct that holds a capability and its configuration +type CapabilityConfig = internal.CapabilityConfig + +type UpdateDonRequest = internal.UpdateDonRequest + +type UpdateDonResponse struct { + DonInfo kcr.CapabilitiesRegistryDONInfo +} + +// UpdateDon updates the capabilities of a Don +// This a complex action in practice that involves registering missing capabilities, adding the nodes, and updating +// the capabilities of the DON +func UpdateDon(env deployment.Environment, cfg any) (deployment.ChangesetOutput, error) { + req := cfg.(*UpdateDonRequest) + _, err := internal.UpdateDon(env.Logger, req) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to update don: %w", err) + } + return deployment.ChangesetOutput{}, nil +} diff --git a/deployment/keystone/changeset/update_node_capabilities.go b/deployment/keystone/changeset/update_node_capabilities.go index 422411e9061..09cf351cc85 100644 --- a/deployment/keystone/changeset/update_node_capabilities.go +++ b/deployment/keystone/changeset/update_node_capabilities.go @@ -39,7 +39,6 @@ type MutateNodeCapabilitiesRequest struct { RegistryChainSel uint64 P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability - NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*P2PSignerEnc } func (req *MutateNodeCapabilitiesRequest) Validate() error { @@ -49,9 +48,6 @@ func (req *MutateNodeCapabilitiesRequest) Validate() error { if len(req.P2pToCapabilities) == 0 { return fmt.Errorf("p2pToCapabilities is empty") } - if len(req.NopToNodes) == 0 { - return fmt.Errorf("nopToNodes is empty") - } _, exists := chainsel.ChainBySelector(req.RegistryChainSel) if !exists { return fmt.Errorf("registry chain selector %d does not exist", req.RegistryChainSel) @@ -84,7 +80,6 @@ func (req *MutateNodeCapabilitiesRequest) updateNodeCapabilitiesImplRequest(e de Chain: registryChain, Registry: registry, P2pToCapabilities: req.P2pToCapabilities, - NopToNodes: req.NopToNodes, }, nil } diff --git a/deployment/keystone/deploy.go b/deployment/keystone/deploy.go index eec648979f4..f0231338ac3 100644 --- a/deployment/keystone/deploy.go +++ b/deployment/keystone/deploy.go @@ -192,14 +192,18 @@ func ConfigureRegistry(ctx context.Context, lggr logger.Logger, req ConfigureCon lggr.Infow("registered capabilities", "capabilities", capabilitiesResp.donToCapabilities) // register node operators - var nops []kcr.CapabilitiesRegistryNodeOperator + dedupedNops := make(map[kcr.CapabilitiesRegistryNodeOperator]struct{}) + var nopsList []kcr.CapabilitiesRegistryNodeOperator for _, nop := range nodeIdToNop { - nops = append(nops, nop) + dedupedNops[nop] = struct{}{} } - nopsResp, err := RegisterNOPS(ctx, RegisterNOPSRequest{ + for nop := range dedupedNops { + nopsList = append(nopsList, nop) + } + nopsResp, err := RegisterNOPS(ctx, lggr, RegisterNOPSRequest{ Chain: registryChain, Registry: registry, - Nops: nops, + Nops: nopsList, }) if err != nil { return nil, fmt.Errorf("failed to register node operators: %w", err) @@ -231,7 +235,7 @@ func ConfigureRegistry(ctx context.Context, lggr logger.Logger, req ConfigureCon if err != nil { return nil, fmt.Errorf("failed to register DONS: %w", err) } - lggr.Infow("registered DONS", "dons", len(donsResp.donInfos)) + lggr.Infow("registered DONs", "dons", len(donsResp.donInfos)) return &ConfigureContractsResponse{ Changeset: &deployment.ChangesetOutput{ @@ -371,6 +375,7 @@ func registerCapabilities(lggr logger.Logger, req registerCapabilitiesRequest) ( if len(req.donToCapabilities) == 0 { return nil, fmt.Errorf("no capabilities to register") } + lggr.Infow("registering capabilities...", "len", len(req.donToCapabilities)) resp := ®isterCapabilitiesResponse{ donToCapabilities: make(map[string][]RegisteredCapability), } @@ -421,8 +426,37 @@ type RegisterNOPSResponse struct { Nops []*kcr.CapabilitiesRegistryNodeOperatorAdded } -func RegisterNOPS(ctx context.Context, req RegisterNOPSRequest) (*RegisterNOPSResponse, error) { - nops := req.Nops +func RegisterNOPS(ctx context.Context, lggr logger.Logger, req RegisterNOPSRequest) (*RegisterNOPSResponse, error) { + lggr.Infow("registering node operators...", "len", len(req.Nops)) + existingNops, err := req.Registry.GetNodeOperators(&bind.CallOpts{}) + if err != nil { + return nil, err + } + existingNopsAddrToID := make(map[capabilities_registry.CapabilitiesRegistryNodeOperator]uint32) + for id, nop := range existingNops { + existingNopsAddrToID[nop] = uint32(id) + } + lggr.Infow("fetched existing node operators", "len", len(existingNopsAddrToID)) + resp := &RegisterNOPSResponse{ + Nops: []*kcr.CapabilitiesRegistryNodeOperatorAdded{}, + } + nops := []kcr.CapabilitiesRegistryNodeOperator{} + for _, nop := range req.Nops { + if id, ok := existingNopsAddrToID[nop]; !ok { + nops = append(nops, nop) + } else { + lggr.Debugw("node operator already exists", "name", nop.Name, "admin", nop.Admin.String(), "id", id) + resp.Nops = append(resp.Nops, &kcr.CapabilitiesRegistryNodeOperatorAdded{ + NodeOperatorId: id, + Name: nop.Name, + Admin: nop.Admin, + }) + } + } + if len(nops) == 0 { + lggr.Debug("no new node operators to register") + return resp, nil + } tx, err := req.Registry.AddNodeOperators(req.Chain.DeployerKey, nops) if err != nil { err = DecodeErr(kcr.CapabilitiesRegistryABI, err) @@ -442,21 +476,18 @@ func RegisterNOPS(ctx context.Context, req RegisterNOPSRequest) (*RegisterNOPSRe if len(receipt.Logs) != len(nops) { return nil, fmt.Errorf("expected %d log entries for AddNodeOperators, got %d", len(nops), len(receipt.Logs)) } - resp := &RegisterNOPSResponse{ - Nops: make([]*kcr.CapabilitiesRegistryNodeOperatorAdded, len(receipt.Logs)), - } for i, log := range receipt.Logs { o, err := req.Registry.ParseNodeOperatorAdded(*log) if err != nil { return nil, fmt.Errorf("failed to parse log %d for operator added: %w", i, err) } - resp.Nops[i] = o + resp.Nops = append(resp.Nops, o) } return resp, nil } -func defaultCapConfig(capType uint8, nNodes int) *capabilitiespb.CapabilityConfig { +func DefaultCapConfig(capType uint8, nNodes int) *capabilitiespb.CapabilityConfig { switch capType { // TODO: use the enum defined in ?? case uint8(0): // trigger @@ -531,6 +562,7 @@ type registerNodesResponse struct { // can sign the transactions update the contract state // TODO: 467 refactor to support MCMS. Specifically need to separate the call data generation from the actual contract call func registerNodes(lggr logger.Logger, req *registerNodesRequest) (*registerNodesResponse, error) { + lggr.Infow("registering nodes...", "len", len(req.nodeIdToNop)) nopToNodeIDs := make(map[kcr.CapabilitiesRegistryNodeOperator][]string) for nodeID, nop := range req.nodeIdToNop { if _, ok := nopToNodeIDs[nop]; !ok { @@ -623,7 +655,7 @@ func registerNodes(lggr logger.Logger, req *registerNodesRequest) (*registerNode if err != nil { err = DecodeErr(kcr.CapabilitiesRegistryABI, err) if strings.Contains(err.Error(), "NodeAlreadyExists") { - lggr.Warnw("node already exists, skipping", "p2pid", singleNodeParams.P2pId) + lggr.Warnw("node already exists, skipping", "p2pid", hex.EncodeToString(singleNodeParams.P2pId[:])) continue } return nil, fmt.Errorf("failed to call AddNode for node with p2pid %v: %w", singleNodeParams.P2pId, err) @@ -672,13 +704,22 @@ func sortedHash(p2pids [][32]byte) string { } func registerDons(lggr logger.Logger, req registerDonsRequest) (*registerDonsResponse, error) { - resp := registerDonsResponse{ - donInfos: make(map[string]kcr.CapabilitiesRegistryDONInfo), - } + lggr.Infow("registering DONs...", "len", len(req.donToOcr2Nodes)) // track hash of sorted p2pids to don name because the registry return value does not include the don name // and we need to map it back to the don name to access the other mapping data such as the don's capabilities & nodes p2pIdsToDon := make(map[string]string) - var registeredDons = 0 + var addedDons = 0 + + donInfos, err := req.registry.GetDONs(&bind.CallOpts{}) + if err != nil { + err = DecodeErr(kcr.CapabilitiesRegistryABI, err) + return nil, fmt.Errorf("failed to call GetDONs: %w", err) + } + existingDONs := make(map[string]struct{}) + for _, donInfo := range donInfos { + existingDONs[sortedHash(donInfo.NodeP2PIds)] = struct{}{} + } + lggr.Infow("fetched existing DONs...", "len", len(donInfos), "lenByNodesHash", len(existingDONs)) for don, ocr2nodes := range req.donToOcr2Nodes { var p2pIds [][32]byte @@ -695,6 +736,12 @@ func registerDons(lggr logger.Logger, req registerDonsRequest) (*registerDonsRes p2pSortedHash := sortedHash(p2pIds) p2pIdsToDon[p2pSortedHash] = don + + if _, ok := existingDONs[p2pSortedHash]; ok { + lggr.Debugw("don already exists, ignoring", "don", don, "p2p sorted hash", p2pSortedHash) + continue + } + caps, ok := req.donToCapabilities[don] if !ok { return nil, fmt.Errorf("capabilities not found for node operator %s", don) @@ -706,7 +753,7 @@ func registerDons(lggr logger.Logger, req registerDonsRequest) (*registerDonsRes wfSupported = true } // TODO: accept configuration from external source for each (don,capability) - capCfg := defaultCapConfig(cap.CapabilityType, len(p2pIds)) + capCfg := DefaultCapConfig(cap.CapabilityType, len(p2pIds)) cfgb, err := proto.Marshal(capCfg) if err != nil { return nil, fmt.Errorf("failed to marshal capability config for %v: %w", cap, err) @@ -728,21 +775,21 @@ func registerDons(lggr logger.Logger, req registerDonsRequest) (*registerDonsRes return nil, fmt.Errorf("failed to confirm AddDON transaction %s for don %s: %w", tx.Hash().String(), don, err) } lggr.Debugw("registered DON", "don", don, "p2p sorted hash", p2pSortedHash, "cgs", cfgs, "wfSupported", wfSupported, "f", f) - registeredDons++ + addedDons++ } - lggr.Debugf("Registered all DONS %d, waiting for registry to update", registeredDons) + lggr.Debugf("Registered all DONs (new=%d), waiting for registry to update", addedDons) // occasionally the registry does not return the expected number of DONS immediately after the txns above // so we retry a few times. while crude, it is effective - var donInfos []capabilities_registry.CapabilitiesRegistryDONInfo - var err error + foundAll := false for i := 0; i < 10; i++ { - lggr.Debug("attempting to get DONS from registry", i) + lggr.Debugw("attempting to get DONs from registry", "attempt#", i) donInfos, err = req.registry.GetDONs(&bind.CallOpts{}) - if len(donInfos) != registeredDons { - lggr.Debugw("expected dons not registered", "expected", registeredDons, "got", len(donInfos)) + if !containsAllDONs(donInfos, p2pIdsToDon) { + lggr.Debugw("some expected dons not registered yet, re-checking after a delay ...") time.Sleep(2 * time.Second) } else { + foundAll = true break } } @@ -750,22 +797,37 @@ func registerDons(lggr logger.Logger, req registerDonsRequest) (*registerDonsRes err = DecodeErr(kcr.CapabilitiesRegistryABI, err) return nil, fmt.Errorf("failed to call GetDONs: %w", err) } + if !foundAll { + return nil, fmt.Errorf("did not find all desired DONS") + } + resp := registerDonsResponse{ + donInfos: make(map[string]kcr.CapabilitiesRegistryDONInfo), + } for i, donInfo := range donInfos { donName, ok := p2pIdsToDon[sortedHash(donInfo.NodeP2PIds)] if !ok { - return nil, fmt.Errorf("don not found for p2pids %s in %v", sortedHash(donInfo.NodeP2PIds), p2pIdsToDon) + lggr.Debugw("irrelevant DON found in the registry, ignoring", "p2p sorted hash", sortedHash(donInfo.NodeP2PIds)) + continue } - lggr.Debugw("adding don info", "don", donName, "cnt", i) + lggr.Debugw("adding don info to the reponse (keyed by DON name)", "don", donName) resp.donInfos[donName] = donInfos[i] } - lggr.Debugw("found registered DONs", "count", len(resp.donInfos)) - if len(resp.donInfos) != registeredDons { - return nil, fmt.Errorf("expected %d dons, got %d", registeredDons, len(resp.donInfos)) - } return &resp, nil } +// are all DONs from p2pIdsToDon in donInfos +func containsAllDONs(donInfos []kcr.CapabilitiesRegistryDONInfo, p2pIdsToDon map[string]string) bool { + found := make(map[string]struct{}) + for _, donInfo := range donInfos { + hash := sortedHash(donInfo.NodeP2PIds) + if _, ok := p2pIdsToDon[hash]; ok { + found[hash] = struct{}{} + } + } + return len(found) == len(p2pIdsToDon) +} + // configureForwarder sets the config for the forwarder contract on the chain for all Dons that accept workflows // dons that don't accept workflows are not registered with the forwarder func configureForwarder(lggr logger.Logger, chain deployment.Chain, fwdr *kf.KeystoneForwarder, dons []RegisteredDon) error { diff --git a/deployment/keystone/deploy_test.go b/deployment/keystone/deploy_test.go index 211e273c38e..96350a91d6c 100644 --- a/deployment/keystone/deploy_test.go +++ b/deployment/keystone/deploy_test.go @@ -120,14 +120,22 @@ func TestDeploy(t *testing.T) { require.True(t, ok) gotRegistry := regChainContracts.CapabilitiesRegistry require.NotNil(t, gotRegistry) - // contract reads + // check DONs gotDons, err := gotRegistry.GetDONs(&bind.CallOpts{}) if err != nil { err = keystone.DecodeErr(kcr.CapabilitiesRegistryABI, err) - require.Fail(t, fmt.Sprintf("failed to get Dons from registry at %s: %s", gotRegistry.Address().String(), err)) + require.Fail(t, fmt.Sprintf("failed to get DONs from registry at %s: %s", gotRegistry.Address().String(), err)) } require.NoError(t, err) assert.Len(t, gotDons, len(deployReq.Dons)) + // check NOPs + nops, err := gotRegistry.GetNodeOperators(&bind.CallOpts{}) + if err != nil { + err = keystone.DecodeErr(kcr.CapabilitiesRegistryABI, err) + require.Fail(t, fmt.Sprintf("failed to get NOPs from registry at %s: %s", gotRegistry.Address().String(), err)) + } + require.NoError(t, err) + assert.Len(t, nops, 26) // 10 NOPs owning workflow & writer DONs + 16 NOPs owning Asset DON for n, info := range deployResp.DonInfos { found := false diff --git a/deployment/keystone/types.go b/deployment/keystone/types.go index 18967ccf445..e01ec6d0d55 100644 --- a/deployment/keystone/types.go +++ b/deployment/keystone/types.go @@ -210,15 +210,15 @@ type DonCapabilities struct { } // map the node id to the NOP -func (dc DonCapabilities) nodeIdToNop(cs uint64) (map[string]capabilities_registry.CapabilitiesRegistryNodeOperator, error) { +func (dc DonCapabilities) nopsByNodeID(chainSelector uint64) (map[string]capabilities_registry.CapabilitiesRegistryNodeOperator, error) { out := make(map[string]capabilities_registry.CapabilitiesRegistryNodeOperator) for _, nop := range dc.Nops { for _, node := range nop.Nodes { - a, err := AdminAddress(node, cs) + a, err := AdminAddress(node, chainSelector) if err != nil { return nil, fmt.Errorf("failed to get admin address for node %s: %w", node.ID, err) } - out[node.ID] = NodeOperator(dc.Name, a) + out[node.ID] = NodeOperator(nop.Name, a) } } @@ -251,7 +251,7 @@ func AdminAddress(n *models.Node, chainSel uint64) (string, error) { func nodesToNops(dons []DonCapabilities, chainSel uint64) (map[string]capabilities_registry.CapabilitiesRegistryNodeOperator, error) { out := make(map[string]capabilities_registry.CapabilitiesRegistryNodeOperator) for _, don := range dons { - nops, err := don.nodeIdToNop(chainSel) + nops, err := don.nopsByNodeID(chainSel) if err != nil { return nil, fmt.Errorf("failed to get registry NOPs for don %s: %w", don.Name, err) } diff --git a/docs/CONFIG.md b/docs/CONFIG.md index 8e872a458e6..2592e48b642 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -1905,6 +1905,8 @@ Endpoint = 'example.com/collector' # Example CACertFile = 'cert-file' # Example InsecureConnection = false # Default TraceSampleRatio = 0.01 # Default +EmitterBatchProcessor = true # Default +EmitterExportTimeout = '1s' # Default ``` Telemetry holds OTEL settings. This data includes open telemetry metrics, traces, & logs. @@ -1942,6 +1944,18 @@ TraceSampleRatio = 0.01 # Default ``` TraceSampleRatio is the rate at which to sample traces. Must be between 0 and 1. +### EmitterBatchProcessor +```toml +EmitterBatchProcessor = true # Default +``` +EmitterBatchProcessor enables batching for telemetry events + +### EmitterExportTimeout +```toml +EmitterExportTimeout = '1s' # Default +``` +EmitterExportTimeout sets timeout for exporting telemetry events + ## Telemetry.ResourceAttributes ```toml [Telemetry.ResourceAttributes] @@ -3557,7 +3571,8 @@ ReaperThreshold = '168h0m0s' ResendAfterThreshold = '3m0s' [Transactions.AutoPurge] -Enabled = false +Enabled = true +MinAttempts = 3 [BalanceMonitor] Enabled = true @@ -3661,7 +3676,8 @@ ReaperThreshold = '168h0m0s' ResendAfterThreshold = '3m0s' [Transactions.AutoPurge] -Enabled = false +Enabled = true +MinAttempts = 3 [BalanceMonitor] Enabled = true @@ -4926,7 +4942,8 @@ ReaperThreshold = '168h0m0s' ResendAfterThreshold = '3m0s' [Transactions.AutoPurge] -Enabled = false +Enabled = true +MinAttempts = 3 [BalanceMonitor] Enabled = true @@ -5558,7 +5575,8 @@ ReaperThreshold = '168h0m0s' ResendAfterThreshold = '3m0s' [Transactions.AutoPurge] -Enabled = false +Enabled = true +MinAttempts = 3 [BalanceMonitor] Enabled = true @@ -6691,10 +6709,10 @@ AutoCreateKey = true BlockBackfillDepth = 10 BlockBackfillSkip = false ChainType = 'celo' -FinalityDepth = 10 -FinalityTagEnabled = false +FinalityDepth = 2750 +FinalityTagEnabled = true LogBackfillBatchSize = 1000 -LogPollInterval = '5s' +LogPollInterval = '1s' LogKeepBlocksDepth = 100000 LogPrunePageSize = 0 BackupLogPollerBlockDelay = 100 @@ -6705,8 +6723,8 @@ NoNewHeadsThreshold = '1m0s' LogBroadcasterEnabled = true RPCDefaultBatchSize = 250 RPCBlockQueryDelay = 1 -FinalizedBlockOffset = 2 -NoNewFinalizedHeadsThreshold = '1m0s' +FinalizedBlockOffset = 0 +NoNewFinalizedHeadsThreshold = '45m0s' [Transactions] ForwardersEnabled = false @@ -6714,7 +6732,7 @@ MaxInFlight = 16 MaxQueued = 250 ReaperInterval = '1h0m0s' ReaperThreshold = '168h0m0s' -ResendAfterThreshold = '1m0s' +ResendAfterThreshold = '30s' [Transactions.AutoPurge] Enabled = false @@ -6724,25 +6742,25 @@ Enabled = true [GasEstimator] Mode = 'BlockHistory' -PriceDefault = '5 gwei' -PriceMax = '500 gwei' +PriceDefault = '20 gwei' +PriceMax = '1 micro' PriceMin = '5 gwei' LimitDefault = 500000 LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateLimit = false -BumpMin = '2 gwei' +BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 -EIP1559DynamicFees = false +EIP1559DynamicFees = true FeeCapDefault = '100 gwei' TipCapDefault = '1 wei' TipCapMin = '1 wei' [GasEstimator.BlockHistory] BatchSize = 25 -BlockHistorySize = 24 +BlockHistorySize = 200 CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 @@ -6751,7 +6769,7 @@ TransactionPercentile = 60 CacheTimeout = '10s' [HeadTracker] -HistoryDepth = 50 +HistoryDepth = 300 MaxBufferSize = 3 SamplingInterval = '1s' MaxAllowedFinalityDepth = 10000 @@ -6762,7 +6780,7 @@ PersistenceEnabled = true PollFailureThreshold = 5 PollInterval = '10s' SelectionMode = 'HighestHead' -SyncThreshold = 5 +SyncThreshold = 10 LeaseDuration = '0s' NodeIsSyncingEnabled = false FinalizedBlockPollInterval = '5s' @@ -7145,7 +7163,9 @@ ReaperThreshold = '168h0m0s' ResendAfterThreshold = '3m0s' [Transactions.AutoPurge] -Enabled = false +Enabled = true +Threshold = 50 +MinAttempts = 3 [BalanceMonitor] Enabled = true @@ -7248,7 +7268,9 @@ ReaperThreshold = '168h0m0s' ResendAfterThreshold = '3m0s' [Transactions.AutoPurge] -Enabled = false +Enabled = true +Threshold = 50 +MinAttempts = 3 [BalanceMonitor] Enabled = true @@ -8202,7 +8224,8 @@ ReaperThreshold = '168h0m0s' ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] -Enabled = false +Enabled = true +DetectionApiUrl = 'https://sepolia-venus.scroll.io' [BalanceMonitor] Enabled = true @@ -8310,7 +8333,8 @@ ReaperThreshold = '168h0m0s' ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] -Enabled = false +Enabled = true +DetectionApiUrl = 'https://venus.scroll.io' [BalanceMonitor] Enabled = true diff --git a/fuzz/fuzz_all_native.py b/fuzz/fuzz_all_native.py index aa191fc5e8d..2d1cc4ccb29 100755 --- a/fuzz/fuzz_all_native.py +++ b/fuzz/fuzz_all_native.py @@ -6,6 +6,7 @@ import re import subprocess import sys +import time def main(): parser = argparse.ArgumentParser( @@ -22,35 +23,51 @@ def main(): # use float for remaining_seconds so we can represent infinity if args.seconds: - remaining_seconds = float(args.seconds) + total_time = float(args.seconds) else: - remaining_seconds = float("inf") + total_time = float("inf") + + start_time = time.time() + remaining_seconds = total_time fuzzers = discover_fuzzers(args.go_module_root) - print(f"🐝 Discovered fuzzers:", file=sys.stderr) + num_fuzzers = len(fuzzers) + print(f"🐝 Discovered {num_fuzzers} fuzzers:", file=sys.stderr) for fuzzfn, path in fuzzers.items(): print(f"{fuzzfn} in {path}", file=sys.stderr) + if num_fuzzers == 0: + print(f"No fuzzers found, this is likely an error. Exiting.") + exit(1) + + # run forever or until --seconds, with increasingly longer durations per fuzz run + durations_seconds = itertools.chain([5, 10, 30, 90, 270], itertools.repeat(600)) if args.ci: - # only run each fuzzer once for 60 seconds in CI - durations_seconds = [60] - else: - # run forever or until --seconds, with increasingly longer durations per fuzz run - durations_seconds = itertools.chain([5, 10, 30, 90, 270], itertools.repeat(600)) + # In CI - default to 60s fuzzes for scheduled runs, and 45 seconds for everything else + durations_seconds = [60] if os.getenv('GITHUB_EVENT_NAME') == 'scheduled' else [45] + if args.seconds: + # However, if seconds was specified, evenly divide total time among all fuzzers + # leaving a 10 second buffer for processing/building time between fuzz runs + actual_fuzz_time = total_time - (num_fuzzers * 10) + if actual_fuzz_time <= 5 * num_fuzzers: + print(f"Seconds (--seconds {arg.seconds}) is too low to properly run fuzzers for 5sec each. Exiting.") + exit(1) + durations_seconds = [ actual_fuzz_time / num_fuzzers ] for duration_seconds in durations_seconds: print(f"🐝 Running each fuzzer for {duration_seconds}s before switching to next fuzzer", file=sys.stderr) for fuzzfn, path in fuzzers.items(): + elapsed_time = time.time() - start_time + remaining_seconds = total_time - elapsed_time + if remaining_seconds <= 0: print(f"🐝 Time budget of {args.seconds}s is exhausted. Exiting.", file=sys.stderr) return next_duration_seconds = min(remaining_seconds, duration_seconds) - remaining_seconds -= next_duration_seconds - - print(f"🐝 Running {fuzzfn} in {path} for {next_duration_seconds}s before switching to next fuzzer", file=sys.stderr) + print(f"🐝 Running {fuzzfn} in {path} for {next_duration_seconds}s (Elapsed: {elapsed_time:.2f}s, Remaining: {remaining_seconds:.2f}s)", file=sys.stderr) run_fuzzer(fuzzfn, path, next_duration_seconds, args.go_module_root) - print(f"🐝 Completed running {fuzzfn} in {path} for {next_duration_seconds}s. Total remaining time is {remaining_seconds}s", file=sys.stderr) + print(f"🐝 Completed running {fuzzfn} in {path} for {next_duration_seconds}s.", file=sys.stderr) def discover_fuzzers(go_module_root): fuzzers = {} diff --git a/go.md b/go.md index e2c5ae57bdd..9f51ecd4c81 100644 --- a/go.md +++ b/go.md @@ -18,86 +18,65 @@ flowchart LR chainlink-vrf end - subgraph tdh2 - tdh2/go/tdh2 - tdh2/go/ocr2/decryptionplugin - end + classDef group stroke-dasharray:6,fill:none; + class chains,products group - subgraph chainlink-protos - chainlink-protos/orchestrator - chainlink-protos/job-distributor - end + chain-selectors + click chain-selectors href "https://github.com/smartcontractkit/chain-selectors" + chainlink-automation --> chainlink-common + click chainlink-automation href "https://github.com/smartcontractkit/chainlink-automation" + chainlink-ccip --> chain-selectors + chainlink-ccip --> chainlink-common + click chainlink-ccip href "https://github.com/smartcontractkit/chainlink-ccip" + chainlink-common --> grpc-proxy + chainlink-common --> libocr + click chainlink-common href "https://github.com/smartcontractkit/chainlink-common" + chainlink-cosmos --> chainlink-common + click chainlink-cosmos href "https://github.com/smartcontractkit/chainlink-cosmos" + chainlink-data-streams --> chainlink-common + click chainlink-data-streams href "https://github.com/smartcontractkit/chainlink-data-streams" + chainlink-feeds --> chainlink-common + click chainlink-feeds href "https://github.com/smartcontractkit/chainlink-feeds" + chainlink-protos/orchestrator --> wsrpc + click chainlink-protos/orchestrator href "https://github.com/smartcontractkit/chainlink-protos" + chainlink-solana --> chainlink-common + click chainlink-solana href "https://github.com/smartcontractkit/chainlink-solana" + chainlink-starknet/relayer --> chainlink-common + click chainlink-starknet/relayer href "https://github.com/smartcontractkit/chainlink-starknet" + chainlink/v2 --> chainlink-automation + chainlink/v2 --> chainlink-ccip + chainlink/v2 --> chainlink-cosmos + chainlink/v2 --> chainlink-data-streams + chainlink/v2 --> chainlink-feeds + chainlink/v2 --> chainlink-protos/orchestrator + chainlink/v2 --> chainlink-solana + chainlink/v2 --> chainlink-starknet/relayer + chainlink/v2 --> tdh2/go/ocr2/decryptionplugin + click chainlink/v2 href "https://github.com/smartcontractkit/chainlink" + grpc-proxy + click grpc-proxy href "https://github.com/smartcontractkit/grpc-proxy" + libocr + click libocr href "https://github.com/smartcontractkit/libocr" + tdh2/go/ocr2/decryptionplugin --> libocr + tdh2/go/ocr2/decryptionplugin --> tdh2/go/tdh2 + click tdh2/go/ocr2/decryptionplugin href "https://github.com/smartcontractkit/tdh2" + tdh2/go/tdh2 + click tdh2/go/tdh2 href "https://github.com/smartcontractkit/tdh2" + wsrpc + click wsrpc href "https://github.com/smartcontractkit/wsrpc" - classDef outline stroke-dasharray:6,fill:none; - class chains,products,tdh2,chainlink-protos outline + subgraph tdh2-repo[tdh2] + tdh2/go/ocr2/decryptionplugin + tdh2/go/tdh2 + end + click tdh2-repo href "https://github.com/smartcontractkit/tdh2" - chainlink/v2 --> chain-selectors - click chain-selectors href "https://github.com/smartcontractkit/chain-selectors" - chainlink/v2 --> chainlink-automation - click chainlink-automation href "https://github.com/smartcontractkit/chainlink-automation" - chainlink/v2 --> chainlink-ccip - click chainlink-ccip href "https://github.com/smartcontractkit/chainlink-ccip" - chainlink/v2 --> chainlink-common - click chainlink-common href "https://github.com/smartcontractkit/chainlink-common" - chainlink/v2 --> chainlink-cosmos - click chainlink-cosmos href "https://github.com/smartcontractkit/chainlink-cosmos" - chainlink/v2 --> chainlink-data-streams - click chainlink-data-streams href "https://github.com/smartcontractkit/chainlink-data-streams" - chainlink/v2 --> chainlink-feeds - click chainlink-feeds href "https://github.com/smartcontractkit/chainlink-feeds" - chainlink/v2 --> chainlink-protos/orchestrator - click chainlink-protos/orchestrator href "https://github.com/smartcontractkit/chainlink-protos" - chainlink/v2 --> chainlink-solana - 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 - click tdh2/go/ocr2/decryptionplugin href "https://github.com/smartcontractkit/tdh2" - chainlink/v2 --> tdh2/go/tdh2 - click tdh2/go/tdh2 href "https://github.com/smartcontractkit/tdh2" - chainlink/v2 --> wsrpc - click wsrpc href "https://github.com/smartcontractkit/wsrpc" - chainlink-automation --> chainlink-common - chainlink-automation --> libocr - chainlink-ccip --> chain-selectors - chainlink-ccip --> chainlink-common - chainlink-ccip --> 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-protos/orchestrator --> wsrpc - 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 + classDef outline stroke-dasharray:6,fill:none; + class tdh2-repo outline ``` ## All modules ```mermaid flowchart LR - subgraph chainlink - chainlink/v2 - chainlink/deployment - chainlink/integration-tests - chainlink/load-tests - chainlink/core/scripts - end - subgraph chains chainlink-cosmos chainlink-solana @@ -114,186 +93,112 @@ flowchart LR chainlink-vrf end - subgraph tdh2 - tdh2/go/tdh2 - tdh2/go/ocr2/decryptionplugin - end + classDef group stroke-dasharray:6,fill:none; + class chains,products group - subgraph chainlink-testing-framework - chainlink-testing-framework/havoc - chainlink-testing-framework/lib - chainlink-testing-framework/lib/grafana - chainlink-testing-framework/seth - chainlink-testing-framework/wasp - end + ccip-owner-contracts --> chain-selectors + click ccip-owner-contracts href "https://github.com/smartcontractkit/ccip-owner-contracts" + chain-selectors + click chain-selectors href "https://github.com/smartcontractkit/chain-selectors" + chainlink-automation --> chainlink-common + click chainlink-automation href "https://github.com/smartcontractkit/chainlink-automation" + chainlink-ccip --> chain-selectors + chainlink-ccip --> chainlink-common + click chainlink-ccip href "https://github.com/smartcontractkit/chainlink-ccip" + chainlink-common --> grpc-proxy + chainlink-common --> libocr + click chainlink-common href "https://github.com/smartcontractkit/chainlink-common" + chainlink-cosmos --> chainlink-common + click chainlink-cosmos href "https://github.com/smartcontractkit/chainlink-cosmos" + chainlink-data-streams --> chainlink-common + click chainlink-data-streams href "https://github.com/smartcontractkit/chainlink-data-streams" + chainlink-feeds --> chainlink-common + click chainlink-feeds href "https://github.com/smartcontractkit/chainlink-feeds" + chainlink-protos/job-distributor + click chainlink-protos/job-distributor href "https://github.com/smartcontractkit/chainlink-protos" + chainlink-protos/orchestrator --> wsrpc + click chainlink-protos/orchestrator href "https://github.com/smartcontractkit/chainlink-protos" + chainlink-solana --> chainlink-common + click chainlink-solana href "https://github.com/smartcontractkit/chainlink-solana" + chainlink-starknet/relayer --> chainlink-common + click chainlink-starknet/relayer href "https://github.com/smartcontractkit/chainlink-starknet" + chainlink-testing-framework/havoc --> chainlink-testing-framework/lib/grafana + click chainlink-testing-framework/havoc href "https://github.com/smartcontractkit/chainlink-testing-framework" + chainlink-testing-framework/lib --> chainlink-testing-framework/seth + chainlink-testing-framework/lib --> chainlink-testing-framework/wasp + click chainlink-testing-framework/lib href "https://github.com/smartcontractkit/chainlink-testing-framework" + chainlink-testing-framework/lib/grafana + click chainlink-testing-framework/lib/grafana href "https://github.com/smartcontractkit/chainlink-testing-framework" + chainlink-testing-framework/seth --> seth + click chainlink-testing-framework/seth href "https://github.com/smartcontractkit/chainlink-testing-framework" + chainlink-testing-framework/wasp --> chainlink-testing-framework/lib/grafana + click chainlink-testing-framework/wasp href "https://github.com/smartcontractkit/chainlink-testing-framework" + chainlink/core/scripts --> chainlink/deployment + click chainlink/core/scripts href "https://github.com/smartcontractkit/chainlink" + chainlink/deployment --> ccip-owner-contracts + chainlink/deployment --> chainlink-protos/job-distributor + chainlink/deployment --> chainlink-testing-framework/lib + chainlink/deployment --> chainlink/v2 + click chainlink/deployment href "https://github.com/smartcontractkit/chainlink" + chainlink/integration-tests --> chainlink-testing-framework/havoc + chainlink/integration-tests --> chainlink/deployment + click chainlink/integration-tests href "https://github.com/smartcontractkit/chainlink" + chainlink/load-tests --> chainlink/integration-tests + click chainlink/load-tests href "https://github.com/smartcontractkit/chainlink" + chainlink/v2 --> chainlink-automation + chainlink/v2 --> chainlink-ccip + chainlink/v2 --> chainlink-cosmos + chainlink/v2 --> chainlink-data-streams + chainlink/v2 --> chainlink-feeds + chainlink/v2 --> chainlink-protos/orchestrator + chainlink/v2 --> chainlink-solana + chainlink/v2 --> chainlink-starknet/relayer + chainlink/v2 --> tdh2/go/ocr2/decryptionplugin + click chainlink/v2 href "https://github.com/smartcontractkit/chainlink" + grpc-proxy + click grpc-proxy href "https://github.com/smartcontractkit/grpc-proxy" + libocr + click libocr href "https://github.com/smartcontractkit/libocr" + seth + click seth href "https://github.com/smartcontractkit/seth" + tdh2/go/ocr2/decryptionplugin --> libocr + tdh2/go/ocr2/decryptionplugin --> tdh2/go/tdh2 + click tdh2/go/ocr2/decryptionplugin href "https://github.com/smartcontractkit/tdh2" + tdh2/go/tdh2 + click tdh2/go/tdh2 href "https://github.com/smartcontractkit/tdh2" + wsrpc + click wsrpc href "https://github.com/smartcontractkit/wsrpc" - subgraph chainlink-protos - chainlink-protos/orchestrator - chainlink-protos/job-distributor - end + subgraph chainlink-repo[chainlink] + chainlink/core/scripts + chainlink/deployment + chainlink/integration-tests + chainlink/load-tests + chainlink/v2 + end + click chainlink-repo href "https://github.com/smartcontractkit/chainlink" + + subgraph chainlink-protos-repo[chainlink-protos] + chainlink-protos/job-distributor + chainlink-protos/orchestrator + end + click chainlink-protos-repo href "https://github.com/smartcontractkit/chainlink-protos" + + subgraph chainlink-testing-framework-repo[chainlink-testing-framework] + chainlink-testing-framework/havoc + chainlink-testing-framework/lib + chainlink-testing-framework/lib/grafana + chainlink-testing-framework/seth + chainlink-testing-framework/wasp + end + click chainlink-testing-framework-repo href "https://github.com/smartcontractkit/chainlink-testing-framework" - classDef outline stroke-dasharray:6,fill:none; - class chainlink,chains,products,tdh2,chainlink-protos,chainlink-testing-framework outline + subgraph tdh2-repo[tdh2] + tdh2/go/ocr2/decryptionplugin + tdh2/go/tdh2 + end + click tdh2-repo href "https://github.com/smartcontractkit/tdh2" - chainlink/v2 --> chain-selectors - click chain-selectors href "https://github.com/smartcontractkit/chain-selectors" - chainlink/v2 --> chainlink-automation - click chainlink-automation href "https://github.com/smartcontractkit/chainlink-automation" - chainlink/v2 --> chainlink-ccip - click chainlink-ccip href "https://github.com/smartcontractkit/chainlink-ccip" - chainlink/v2 --> chainlink-common - click chainlink-common href "https://github.com/smartcontractkit/chainlink-common" - chainlink/v2 --> chainlink-cosmos - click chainlink-cosmos href "https://github.com/smartcontractkit/chainlink-cosmos" - chainlink/v2 --> chainlink-data-streams - click chainlink-data-streams href "https://github.com/smartcontractkit/chainlink-data-streams" - chainlink/v2 --> chainlink-feeds - click chainlink-feeds href "https://github.com/smartcontractkit/chainlink-feeds" - chainlink/v2 --> chainlink-protos/orchestrator - click chainlink-protos/orchestrator href "https://github.com/smartcontractkit/chainlink-protos" - chainlink/v2 --> chainlink-solana - 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 - click tdh2/go/ocr2/decryptionplugin href "https://github.com/smartcontractkit/tdh2" - chainlink/v2 --> tdh2/go/tdh2 - click tdh2/go/tdh2 href "https://github.com/smartcontractkit/tdh2" - chainlink/v2 --> wsrpc - click wsrpc href "https://github.com/smartcontractkit/wsrpc" - chainlink-automation --> chainlink-common - chainlink-automation --> libocr - chainlink-ccip --> chain-selectors - chainlink-ccip --> chainlink-common - chainlink-ccip --> 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-protos/orchestrator --> wsrpc - 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 - chainlink/core/scripts --> ccip-owner-contracts - click ccip-owner-contracts href "https://github.com/smartcontractkit/ccip-owner-contracts" - chainlink/core/scripts --> chain-selectors - chainlink/core/scripts --> chainlink-automation - chainlink/core/scripts --> chainlink-ccip - chainlink/core/scripts --> chainlink-common - chainlink/core/scripts --> chainlink-cosmos - chainlink/core/scripts --> chainlink-data-streams - chainlink/core/scripts --> chainlink-feeds - chainlink/core/scripts --> chainlink-protos/job-distributor - click chainlink-protos/job-distributor href "https://github.com/smartcontractkit/chainlink-protos" - chainlink/core/scripts --> chainlink-protos/orchestrator - chainlink/core/scripts --> chainlink-solana - chainlink/core/scripts --> chainlink-starknet/relayer - chainlink/core/scripts --> chainlink/deployment - click chainlink/deployment href "https://github.com/smartcontractkit/chainlink" - chainlink/core/scripts --> chainlink/v2 - click chainlink/v2 href "https://github.com/smartcontractkit/chainlink" - chainlink/core/scripts --> grpc-proxy - chainlink/core/scripts --> libocr - chainlink/core/scripts --> tdh2/go/ocr2/decryptionplugin - chainlink/core/scripts --> tdh2/go/tdh2 - chainlink/core/scripts --> wsrpc - ccip-owner-contracts --> chain-selectors - chainlink/deployment --> ccip-owner-contracts - chainlink/deployment --> chain-selectors - chainlink/deployment --> chainlink-ccip - chainlink/deployment --> chainlink-common - chainlink/deployment --> chainlink-protos/job-distributor - chainlink/deployment --> chainlink-testing-framework/lib - click chainlink-testing-framework/lib href "https://github.com/smartcontractkit/chainlink-testing-framework" - chainlink/deployment --> chainlink/v2 - chainlink/deployment --> libocr - chainlink/deployment --> chainlink-automation - chainlink/deployment --> chainlink-cosmos - chainlink/deployment --> chainlink-data-streams - chainlink/deployment --> chainlink-feeds - chainlink/deployment --> chainlink-protos/orchestrator - chainlink/deployment --> chainlink-solana - chainlink/deployment --> chainlink-starknet/relayer - chainlink/deployment --> chainlink-testing-framework/lib/grafana - click chainlink-testing-framework/lib/grafana href "https://github.com/smartcontractkit/chainlink-testing-framework" - chainlink/deployment --> chainlink-testing-framework/seth - click chainlink-testing-framework/seth href "https://github.com/smartcontractkit/chainlink-testing-framework" - chainlink/deployment --> chainlink-testing-framework/wasp - click chainlink-testing-framework/wasp href "https://github.com/smartcontractkit/chainlink-testing-framework" - chainlink/deployment --> grpc-proxy - chainlink/deployment --> tdh2/go/ocr2/decryptionplugin - chainlink/deployment --> tdh2/go/tdh2 - chainlink/deployment --> wsrpc - chainlink-testing-framework/lib --> chainlink-testing-framework/seth - chainlink-testing-framework/lib --> chainlink-testing-framework/wasp - chainlink-testing-framework/lib --> chainlink-testing-framework/lib/grafana - chainlink-testing-framework/seth --> seth - click seth href "https://github.com/smartcontractkit/seth" - chainlink-testing-framework/wasp --> chainlink-testing-framework/lib/grafana - chainlink/integration-tests --> ccip-owner-contracts - chainlink/integration-tests --> chain-selectors - chainlink/integration-tests --> chainlink-automation - chainlink/integration-tests --> chainlink-ccip - chainlink/integration-tests --> chainlink-common - chainlink/integration-tests --> chainlink-cosmos - chainlink/integration-tests --> chainlink-data-streams - chainlink/integration-tests --> chainlink-feeds - chainlink/integration-tests --> chainlink-protos/job-distributor - chainlink/integration-tests --> chainlink-protos/orchestrator - chainlink/integration-tests --> chainlink-solana - chainlink/integration-tests --> chainlink-starknet/relayer - chainlink/integration-tests --> chainlink-testing-framework/havoc - click chainlink-testing-framework/havoc href "https://github.com/smartcontractkit/chainlink-testing-framework" - chainlink/integration-tests --> chainlink-testing-framework/lib - chainlink/integration-tests --> chainlink-testing-framework/lib/grafana - chainlink/integration-tests --> chainlink-testing-framework/seth - chainlink/integration-tests --> chainlink-testing-framework/wasp - chainlink/integration-tests --> chainlink/deployment - chainlink/integration-tests --> chainlink/v2 - chainlink/integration-tests --> grpc-proxy - chainlink/integration-tests --> libocr - chainlink/integration-tests --> tdh2/go/ocr2/decryptionplugin - chainlink/integration-tests --> tdh2/go/tdh2 - chainlink/integration-tests --> wsrpc - chainlink-testing-framework/havoc --> chainlink-testing-framework/lib/grafana - chainlink/load-tests --> chain-selectors - chainlink/load-tests --> chainlink-automation - chainlink/load-tests --> chainlink-ccip - chainlink/load-tests --> chainlink-common - chainlink/load-tests --> chainlink-cosmos - chainlink/load-tests --> chainlink-data-streams - chainlink/load-tests --> chainlink-feeds - chainlink/load-tests --> chainlink-protos/orchestrator - chainlink/load-tests --> chainlink-solana - chainlink/load-tests --> chainlink-starknet/relayer - chainlink/load-tests --> chainlink-testing-framework/havoc - chainlink/load-tests --> chainlink-testing-framework/lib - chainlink/load-tests --> chainlink-testing-framework/lib/grafana - chainlink/load-tests --> chainlink-testing-framework/seth - chainlink/load-tests --> chainlink-testing-framework/wasp - chainlink/load-tests --> chainlink/deployment - chainlink/load-tests --> chainlink/integration-tests - click chainlink/integration-tests href "https://github.com/smartcontractkit/chainlink" - chainlink/load-tests --> chainlink/v2 - chainlink/load-tests --> grpc-proxy - chainlink/load-tests --> libocr - chainlink/load-tests --> tdh2/go/ocr2/decryptionplugin - chainlink/load-tests --> tdh2/go/tdh2 - chainlink/load-tests --> wsrpc + classDef outline stroke-dasharray:6,fill:none; + class chainlink-repo,chainlink-protos-repo,chainlink-testing-framework-repo,tdh2-repo outline ``` diff --git a/go.mod b/go.mod index c18a53ecdd4..a2242962894 100644 --- a/go.mod +++ b/go.mod @@ -41,9 +41,10 @@ require ( 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.2-0.20240829161738-06afb6d7ae99 + github.com/hashicorp/go-plugin v1.6.2 github.com/hashicorp/go-retryablehttp v0.7.7 github.com/hdevalence/ed25519consensus v0.1.0 + github.com/imdario/mergo v0.3.16 github.com/jackc/pgconn v1.14.3 github.com/jackc/pgtype v1.14.0 github.com/jackc/pgx/v4 v4.18.3 @@ -75,8 +76,8 @@ require ( github.com/shopspring/decimal v1.4.0 github.com/smartcontractkit/chain-selectors v1.0.27 github.com/smartcontractkit/chainlink-automation v0.8.1 - github.com/smartcontractkit/chainlink-ccip v0.0.0-20241106140121-4c9ee21ab422 - github.com/smartcontractkit/chainlink-common v0.3.1-0.20241106142051-c7bded1c08ae + github.com/smartcontractkit/chainlink-ccip v0.0.0-20241111114733-aa3b2f8e9f94 + github.com/smartcontractkit/chainlink-common v0.3.1-0.20241109002240-af894848b3b4 github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241018134907-a00ba3729b5e github.com/smartcontractkit/chainlink-feeds v0.1.1 @@ -252,7 +253,6 @@ require ( github.com/huandu/xstrings v1.4.0 // indirect github.com/huin/goupnp v1.3.0 // indirect github.com/iancoleman/strcase v0.3.0 // indirect - github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/invopop/jsonschema v0.12.0 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect diff --git a/go.sum b/go.sum index e0adc852492..e20c421ceaa 100644 --- a/go.sum +++ b/go.sum @@ -636,8 +636,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-plugin v1.6.2 h1:zdGAEd0V1lCaU0u+MxWQhtSDQmahpkwOun8U8EiRVog= +github.com/hashicorp/go-plugin v1.6.2/go.mod h1:CkgLQ5CZqNmdL9U9JzM532t8ZiYQ35+pj3b1FD37R0Q= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= @@ -1075,10 +1075,10 @@ github.com/smartcontractkit/chain-selectors v1.0.27 h1:VE/ftX9Aae4gnw67yR1raKi+3 github.com/smartcontractkit/chain-selectors v1.0.27/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgBc2xpDKBco/Q4h4ydl6+UUU= github.com/smartcontractkit/chainlink-automation v0.8.1/go.mod h1:Iij36PvWZ6blrdC5A/nrQUBuf3MH3JvsBB9sSyc9W08= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241106140121-4c9ee21ab422 h1:VfH/AW5NtTmroY9zz6OYCPFbFTqpMyJ2ubgT9ahYf3U= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241106140121-4c9ee21ab422/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241106142051-c7bded1c08ae h1:uqce0bjNVYzFrrVLafXgyn8SVNdfOtZekLfAwQihHiA= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241106142051-c7bded1c08ae/go.mod h1:TQ9/KKXZ9vr8QAlUquqGpSvDCpR+DtABKPXZY4CiRns= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241111114733-aa3b2f8e9f94 h1:BeLnOf2KKQpJj9nzfnE7QEg9ZqJ2jy/sbpNYVixVM2Y= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241111114733-aa3b2f8e9f94/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241109002240-af894848b3b4 h1:e+uFsxQ21tMQKRu4oBXKycNzoR30vO/7STBtqtDvQJQ= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241109002240-af894848b3b4/go.mod h1:ny87uTW6hLjCTLiBqBRNFEhETSXhHWevYlPclT5lSco= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f h1:BwrIaQIx5Iy6eT+DfLhFfK2XqjxRm74mVdlX8gbu4dw= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f/go.mod h1:wHtwSR3F1CQSJJZDQKuqaqFYnvkT+kMyget7dl8Clvo= github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241018134907-a00ba3729b5e h1:JiETqdNM0bktAUGMc62COwXIaw3rR3M77Me6bBLG0Fg= diff --git a/integration-tests/ccip-tests/testsetups/test_helpers.go b/integration-tests/ccip-tests/testsetups/test_helpers.go index 4acefca0975..4de5d0988a2 100644 --- a/integration-tests/ccip-tests/testsetups/test_helpers.go +++ b/integration-tests/ccip-tests/testsetups/test_helpers.go @@ -95,11 +95,22 @@ func NewLocalDevEnvironment(t *testing.T, lggr logger.Logger) (ccipdeployment.De crConfig, testEnv, cfg) require.NoError(t, err) - e, don, err := devenv.NewEnvironment(ctx, lggr, *envConfig) require.NoError(t, err) require.NotNil(t, e) e.ExistingAddresses = ab + + envNodes, err := deployment.NodeInfo(e.NodeIDs, e.Offchain) + require.NoError(t, err) + _, err = ccipdeployment.DeployHomeChain(lggr, *e, e.ExistingAddresses, chains[homeChainSel], + ccipdeployment.NewTestRMNStaticConfig(), + ccipdeployment.NewTestRMNDynamicConfig(), + ccipdeployment.NewTestNodeOperator(chains[homeChainSel].DeployerKey.From), + map[string][][32]byte{ + "NodeOperator": envNodes.NonBootstraps().PeerIDs(), + }, + ) + require.NoError(t, err) zeroLogLggr := logging.GetTestLogger(t) // fund the nodes FundNodes(t, zeroLogLggr, testEnv, cfg, don.PluginNodes()) @@ -132,6 +143,8 @@ func NewLocalDevEnvironmentWithRMN( OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), }) require.NoError(t, err) + require.NoError(t, tenv.Env.ExistingAddresses.Merge(newAddresses)) + l := logging.GetTestLogger(t) config := GenerateTestRMNConfig(t, numRmnNodes, tenv, MustNetworksToRPCMap(dockerenv.EVMNetworks)) rmnCluster, err := devenv.NewRMNCluster( diff --git a/integration-tests/go.mod b/integration-tests/go.mod index e021543206c..18fba9e3031 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -15,6 +15,7 @@ require ( github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df github.com/chaos-mesh/chaos-mesh/api v0.0.0-20240821051457-da69c6d9617a github.com/cli/go-gh/v2 v2.0.0 + github.com/deckarep/golang-set/v2 v2.6.0 github.com/ethereum/go-ethereum v1.13.8 github.com/fxamacker/cbor/v2 v2.7.0 github.com/go-resty/resty/v2 v2.15.3 @@ -35,8 +36,8 @@ require ( github.com/slack-go/slack v0.15.0 github.com/smartcontractkit/chain-selectors v1.0.27 github.com/smartcontractkit/chainlink-automation v0.8.1 - github.com/smartcontractkit/chainlink-ccip v0.0.0-20241106140121-4c9ee21ab422 - github.com/smartcontractkit/chainlink-common v0.3.1-0.20241106142051-c7bded1c08ae + github.com/smartcontractkit/chainlink-ccip v0.0.0-20241111114733-aa3b2f8e9f94 + github.com/smartcontractkit/chainlink-common v0.3.1-0.20241109002240-af894848b3b4 github.com/smartcontractkit/chainlink-protos/job-distributor v0.4.0 github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.2 github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.13 @@ -171,7 +172,6 @@ require ( github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect github.com/danieljoos/wincred v1.1.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/deckarep/golang-set/v2 v2.6.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/dennwc/varint v1.0.0 // indirect github.com/dfuse-io/logging v0.0.0-20210109005628-b97a57253f70 // indirect @@ -285,7 +285,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.2-0.20240829161738-06afb6d7ae99 // indirect + github.com/hashicorp/go-plugin v1.6.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-sockaddr v1.0.6 // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index 144d29156e5..0a98d182126 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -855,8 +855,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-plugin v1.6.2 h1:zdGAEd0V1lCaU0u+MxWQhtSDQmahpkwOun8U8EiRVog= +github.com/hashicorp/go-plugin v1.6.2/go.mod h1:CkgLQ5CZqNmdL9U9JzM532t8ZiYQ35+pj3b1FD37R0Q= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= @@ -1403,10 +1403,10 @@ github.com/smartcontractkit/chain-selectors v1.0.27 h1:VE/ftX9Aae4gnw67yR1raKi+3 github.com/smartcontractkit/chain-selectors v1.0.27/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgBc2xpDKBco/Q4h4ydl6+UUU= github.com/smartcontractkit/chainlink-automation v0.8.1/go.mod h1:Iij36PvWZ6blrdC5A/nrQUBuf3MH3JvsBB9sSyc9W08= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241106140121-4c9ee21ab422 h1:VfH/AW5NtTmroY9zz6OYCPFbFTqpMyJ2ubgT9ahYf3U= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241106140121-4c9ee21ab422/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241106142051-c7bded1c08ae h1:uqce0bjNVYzFrrVLafXgyn8SVNdfOtZekLfAwQihHiA= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241106142051-c7bded1c08ae/go.mod h1:TQ9/KKXZ9vr8QAlUquqGpSvDCpR+DtABKPXZY4CiRns= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241111114733-aa3b2f8e9f94 h1:BeLnOf2KKQpJj9nzfnE7QEg9ZqJ2jy/sbpNYVixVM2Y= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241111114733-aa3b2f8e9f94/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241109002240-af894848b3b4 h1:e+uFsxQ21tMQKRu4oBXKycNzoR30vO/7STBtqtDvQJQ= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241109002240-af894848b3b4/go.mod h1:ny87uTW6hLjCTLiBqBRNFEhETSXhHWevYlPclT5lSco= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f h1:BwrIaQIx5Iy6eT+DfLhFfK2XqjxRm74mVdlX8gbu4dw= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f/go.mod h1:wHtwSR3F1CQSJJZDQKuqaqFYnvkT+kMyget7dl8Clvo= github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241018134907-a00ba3729b5e h1:JiETqdNM0bktAUGMc62COwXIaw3rR3M77Me6bBLG0Fg= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index 24cb2cb2a4f..2f25d98536d 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -17,7 +17,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.33.0 github.com/slack-go/slack v0.15.0 - github.com/smartcontractkit/chainlink-common v0.3.1-0.20241106142051-c7bded1c08ae + github.com/smartcontractkit/chainlink-common v0.3.1-0.20241109002240-af894848b3b4 github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.13 github.com/smartcontractkit/chainlink-testing-framework/seth v1.50.5 github.com/smartcontractkit/chainlink-testing-framework/wasp v1.50.2 @@ -64,7 +64,7 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect - github.com/smartcontractkit/chainlink-ccip v0.0.0-20241106140121-4c9ee21ab422 // indirect + github.com/smartcontractkit/chainlink-ccip v0.0.0-20241111114733-aa3b2f8e9f94 // indirect github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f // indirect github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.2 // indirect github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // indirect @@ -289,7 +289,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.2-0.20240829161738-06afb6d7ae99 // indirect + github.com/hashicorp/go-plugin v1.6.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-sockaddr v1.0.6 // indirect diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index 9ad23c4de4c..15cf08c7970 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -853,8 +853,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-plugin v1.6.2 h1:zdGAEd0V1lCaU0u+MxWQhtSDQmahpkwOun8U8EiRVog= +github.com/hashicorp/go-plugin v1.6.2/go.mod h1:CkgLQ5CZqNmdL9U9JzM532t8ZiYQ35+pj3b1FD37R0Q= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= @@ -1392,10 +1392,10 @@ github.com/smartcontractkit/chain-selectors v1.0.27 h1:VE/ftX9Aae4gnw67yR1raKi+3 github.com/smartcontractkit/chain-selectors v1.0.27/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgBc2xpDKBco/Q4h4ydl6+UUU= github.com/smartcontractkit/chainlink-automation v0.8.1/go.mod h1:Iij36PvWZ6blrdC5A/nrQUBuf3MH3JvsBB9sSyc9W08= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241106140121-4c9ee21ab422 h1:VfH/AW5NtTmroY9zz6OYCPFbFTqpMyJ2ubgT9ahYf3U= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241106140121-4c9ee21ab422/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241106142051-c7bded1c08ae h1:uqce0bjNVYzFrrVLafXgyn8SVNdfOtZekLfAwQihHiA= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241106142051-c7bded1c08ae/go.mod h1:TQ9/KKXZ9vr8QAlUquqGpSvDCpR+DtABKPXZY4CiRns= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241111114733-aa3b2f8e9f94 h1:BeLnOf2KKQpJj9nzfnE7QEg9ZqJ2jy/sbpNYVixVM2Y= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241111114733-aa3b2f8e9f94/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241109002240-af894848b3b4 h1:e+uFsxQ21tMQKRu4oBXKycNzoR30vO/7STBtqtDvQJQ= +github.com/smartcontractkit/chainlink-common v0.3.1-0.20241109002240-af894848b3b4/go.mod h1:ny87uTW6hLjCTLiBqBRNFEhETSXhHWevYlPclT5lSco= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f h1:BwrIaQIx5Iy6eT+DfLhFfK2XqjxRm74mVdlX8gbu4dw= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f/go.mod h1:wHtwSR3F1CQSJJZDQKuqaqFYnvkT+kMyget7dl8Clvo= github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241018134907-a00ba3729b5e h1:JiETqdNM0bktAUGMc62COwXIaw3rR3M77Me6bBLG0Fg= diff --git a/integration-tests/smoke/ccip_rmn_test.go b/integration-tests/smoke/ccip_rmn_test.go index 76f69afd99e..d058b3c0e72 100644 --- a/integration-tests/smoke/ccip_rmn_test.go +++ b/integration-tests/smoke/ccip_rmn_test.go @@ -3,61 +3,231 @@ package smoke import ( "math/big" "os" + "strconv" "testing" + "time" + mapset "github.com/deckarep/golang-set/v2" "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/rs/zerolog" "github.com/stretchr/testify/require" jobv1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/job" + "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/osutil" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" - "github.com/smartcontractkit/chainlink/deployment" ccipdeployment "github.com/smartcontractkit/chainlink/deployment/ccip" - "github.com/smartcontractkit/chainlink/integration-tests/ccip-tests/testsetups" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_home" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_remote" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" + + "github.com/smartcontractkit/chainlink/integration-tests/ccip-tests/testsetups" "github.com/smartcontractkit/chainlink/v2/core/logger" ) -func TestRMN(t *testing.T) { - t.Skip("Local only") +// Set false to run the RMN tests +const skipRmnTest = true + +func TestRMN_TwoMessagesOnTwoLanes(t *testing.T) { + runRmnTestCase(t, rmnTestCase{ + name: "messages on two lanes", + waitForExec: true, + homeChainConfig: homeChainConfig{ + f: map[int]int{chain0: 1, chain1: 1}, + }, + remoteChainsConfig: []remoteChainConfig{ + {chainIdx: chain0, f: 1}, + {chainIdx: chain1, f: 1}, + }, + rmnNodes: []rmnNode{ + {id: 0, isSigner: true, observedChainIdxs: []int{chain0, chain1}}, + {id: 1, isSigner: true, observedChainIdxs: []int{chain0, chain1}}, + {id: 2, isSigner: true, observedChainIdxs: []int{chain0, chain1}}, + }, + messagesToSend: []messageToSend{ + {fromChainIdx: chain0, toChainIdx: chain1, count: 1}, + {fromChainIdx: chain1, toChainIdx: chain0, count: 1}, + }, + }) +} - require.NoError(t, os.Setenv("ENABLE_RMN", "true")) +func TestRMN_MultipleMessagesOnOneLaneNoWaitForExec(t *testing.T) { + runRmnTestCase(t, rmnTestCase{ + name: "multiple messages for rmn batching inspection and one rmn node down", + waitForExec: false, // do not wait for execution reports + homeChainConfig: homeChainConfig{ + f: map[int]int{chain0: 1, chain1: 1}, + }, + remoteChainsConfig: []remoteChainConfig{ + {chainIdx: chain0, f: 1}, + {chainIdx: chain1, f: 1}, + }, + rmnNodes: []rmnNode{ + {id: 0, isSigner: true, observedChainIdxs: []int{chain0, chain1}}, + {id: 1, isSigner: true, observedChainIdxs: []int{chain0, chain1}}, + {id: 2, isSigner: true, observedChainIdxs: []int{chain0, chain1}, forceExit: true}, // one rmn node is down + }, + messagesToSend: []messageToSend{ + {fromChainIdx: chain1, toChainIdx: chain0, count: 10}, + }, + }) +} + +func TestRMN_NotEnoughObservers(t *testing.T) { + runRmnTestCase(t, rmnTestCase{ + name: "one message but not enough observers, should not get a commit report", + passIfNoCommitAfter: time.Minute, // wait for a minute and assert that commit report was not delivered + homeChainConfig: homeChainConfig{ + f: map[int]int{chain0: 1, chain1: 1}, + }, + remoteChainsConfig: []remoteChainConfig{ + {chainIdx: chain0, f: 1}, + {chainIdx: chain1, f: 1}, + }, + rmnNodes: []rmnNode{ + {id: 0, isSigner: true, observedChainIdxs: []int{chain0, chain1}}, + {id: 1, isSigner: true, observedChainIdxs: []int{chain0, chain1}, forceExit: true}, + {id: 2, isSigner: true, observedChainIdxs: []int{chain0, chain1}, forceExit: true}, + }, + messagesToSend: []messageToSend{ + {fromChainIdx: chain0, toChainIdx: chain1, count: 1}, + }, + }) +} + +func TestRMN_DifferentSigners(t *testing.T) { + runRmnTestCase(t, rmnTestCase{ + name: "different signers and different observers", + homeChainConfig: homeChainConfig{ + f: map[int]int{chain0: 1, chain1: 1}, + }, + remoteChainsConfig: []remoteChainConfig{ + {chainIdx: chain0, f: 1}, + {chainIdx: chain1, f: 1}, + }, + rmnNodes: []rmnNode{ + {id: 0, isSigner: false, observedChainIdxs: []int{chain0, chain1}}, + {id: 1, isSigner: false, observedChainIdxs: []int{chain0, chain1}}, + {id: 2, isSigner: false, observedChainIdxs: []int{chain0, chain1}}, + {id: 3, isSigner: true, observedChainIdxs: []int{}}, + {id: 4, isSigner: true, observedChainIdxs: []int{}}, + {id: 5, isSigner: true, observedChainIdxs: []int{}}, + }, + messagesToSend: []messageToSend{ + {fromChainIdx: chain0, toChainIdx: chain1, count: 1}, + }, + }) +} + +func TestRMN_NotEnoughSigners(t *testing.T) { + runRmnTestCase(t, rmnTestCase{ + name: "different signers and different observers", + passIfNoCommitAfter: time.Minute, // wait for a minute and assert that commit report was not delivered + homeChainConfig: homeChainConfig{ + f: map[int]int{chain0: 1, chain1: 1}, + }, + remoteChainsConfig: []remoteChainConfig{ + {chainIdx: chain0, f: 1}, + {chainIdx: chain1, f: 1}, + }, + rmnNodes: []rmnNode{ + {id: 0, isSigner: false, observedChainIdxs: []int{chain0, chain1}}, + {id: 1, isSigner: false, observedChainIdxs: []int{chain0, chain1}}, + {id: 2, isSigner: false, observedChainIdxs: []int{chain0, chain1}}, + {id: 3, isSigner: true, observedChainIdxs: []int{}}, + {id: 4, isSigner: true, observedChainIdxs: []int{}, forceExit: true}, // signer is down + {id: 5, isSigner: true, observedChainIdxs: []int{}, forceExit: true}, // signer is down + }, + messagesToSend: []messageToSend{ + {fromChainIdx: chain0, toChainIdx: chain1, count: 1}, + }, + }) +} + +func TestRMN_DifferentRmnNodesForDifferentChains(t *testing.T) { + runRmnTestCase(t, rmnTestCase{ + name: "different rmn nodes support different chains", + waitForExec: false, + homeChainConfig: homeChainConfig{ + f: map[int]int{chain0: 1, chain1: 1}, + }, + remoteChainsConfig: []remoteChainConfig{ + {chainIdx: chain0, f: 1}, + {chainIdx: chain1, f: 1}, + }, + rmnNodes: []rmnNode{ + {id: 0, isSigner: true, observedChainIdxs: []int{chain0}}, + {id: 1, isSigner: true, observedChainIdxs: []int{chain0}}, + {id: 2, isSigner: true, observedChainIdxs: []int{chain0}}, + {id: 3, isSigner: true, observedChainIdxs: []int{chain1}}, + {id: 4, isSigner: true, observedChainIdxs: []int{chain1}}, + {id: 5, isSigner: true, observedChainIdxs: []int{chain1}}, + }, + messagesToSend: []messageToSend{ + {fromChainIdx: chain0, toChainIdx: chain1, count: 1}, + {fromChainIdx: chain1, toChainIdx: chain0, count: 1}, + }, + }) +} + +const ( + chain0 = 0 + chain1 = 1 +) - // In this test setup every RMN node is both observer and signer. - const homeF = 2 - const remoteF = 2 - const numRmnNodes = 2*homeF + 1 +func runRmnTestCase(t *testing.T, tc rmnTestCase) { + if skipRmnTest { + t.Skip("Local only") + } + require.NoError(t, os.Setenv("ENABLE_RMN", "true")) - envWithRMN, rmnCluster := testsetups.NewLocalDevEnvironmentWithRMN(t, logger.TestLogger(t), numRmnNodes) + envWithRMN, rmnCluster := testsetups.NewLocalDevEnvironmentWithRMN(t, logger.TestLogger(t), len(tc.rmnNodes)) t.Logf("envWithRmn: %#v", envWithRMN) + var chainSelectors []uint64 + for _, chain := range envWithRMN.Env.Chains { + chainSelectors = append(chainSelectors, chain.Selector) + } + require.Greater(t, len(chainSelectors), 1, "There should be at least two chains") + + remoteChainSelectors := make([]uint64, 0, len(envWithRMN.Env.Chains)-1) + for _, chain := range envWithRMN.Env.Chains { + remoteChainSelectors = append(remoteChainSelectors, chain.Selector) + } + require.Greater(t, len(remoteChainSelectors), 0, "There should be at least one remote chain") + var ( rmnHomeNodes []rmn_home.RMNHomeNode rmnRemoteSigners []rmn_remote.RMNRemoteSigner - nodeIndex uint64 ) - for rmnNode, rmn := range rmnCluster.Nodes { - t.Log(rmnNode, rmn.Proxy.PeerID, rmn.RMN.OffchainPublicKey, rmn.RMN.EVMOnchainPublicKey) + + for _, rmnNodeInfo := range tc.rmnNodes { + rmn := rmnCluster.Nodes["rmn_"+strconv.Itoa(rmnNodeInfo.id)] + + t.Log(rmnNodeInfo.id, rmn.Proxy.PeerID, rmn.RMN.OffchainPublicKey, rmn.RMN.EVMOnchainPublicKey) + var offchainPublicKey [32]byte copy(offchainPublicKey[:], rmn.RMN.OffchainPublicKey) + rmnHomeNodes = append(rmnHomeNodes, rmn_home.RMNHomeNode{ PeerId: rmn.Proxy.PeerID, OffchainPublicKey: offchainPublicKey, }) + rmnRemoteSigners = append(rmnRemoteSigners, rmn_remote.RMNRemoteSigner{ OnchainPublicKey: rmn.RMN.EVMOnchainPublicKey, - NodeIndex: nodeIndex, + NodeIndex: uint64(rmnNodeInfo.id), }) - nodeIndex++ } var rmnHomeSourceChains []rmn_home.RMNHomeSourceChain - for _, chain := range envWithRMN.Env.Chains { + for remoteChainIdx, remoteF := range tc.homeChainConfig.f { + // configure remote chain details on the home contract rmnHomeSourceChains = append(rmnHomeSourceChains, rmn_home.RMNHomeSourceChain{ - ChainSelector: chain.Selector, - F: homeF, - ObserverNodesBitmap: createObserverNodesBitmap(len(rmnHomeNodes)), + ChainSelector: chainSelectors[remoteChainIdx], + F: uint64(remoteF), + ObserverNodesBitmap: createObserverNodesBitmap(chainSelectors[remoteChainIdx], tc.rmnNodes, chainSelectors), }) } @@ -120,14 +290,18 @@ func TestRMN(t *testing.T) { candidateDigest[:], activeDigest[:]) // Set RMN remote config appropriately - for _, chain := range envWithRMN.Env.Chains { - chState, ok := onChainState.Chains[chain.Selector] + for _, remoteCfg := range tc.remoteChainsConfig { + remoteSel := chainSelectors[remoteCfg.chainIdx] + chState, ok := onChainState.Chains[remoteSel] require.True(t, ok) rmnRemoteConfig := rmn_remote.RMNRemoteConfig{ RmnHomeContractConfigDigest: activeDigest, Signers: rmnRemoteSigners, - F: remoteF, + F: uint64(remoteCfg.f), } + + chain := envWithRMN.Env.Chains[chainSelectors[remoteCfg.chainIdx]] + t.Logf("Setting RMNRemote config with RMNHome active digest: %x, cfg: %+v", activeDigest[:], rmnRemoteConfig) tx2, err2 := chState.RMNRemote.SetConfig(chain.DeployerKey, rmnRemoteConfig) require.NoError(t, err2) @@ -148,6 +322,16 @@ func TestRMN(t *testing.T) { t.Logf("RMNRemote config digest after setting: %x", config.Config.RmnHomeContractConfigDigest[:]) } + // Kill the RMN nodes that are marked for force exit + for _, n := range tc.rmnNodes { + if n.forceExit { + t.Logf("Pausing RMN node %d", n.id) + rmnN := rmnCluster.Nodes["rmn_"+strconv.Itoa(n.id)] + require.NoError(t, osutil.ExecCmd(zerolog.Nop(), "docker kill "+rmnN.Proxy.ContainerName)) + t.Logf("Paused RMN node %d", n.id) + } + } + jobSpecs, err := ccipdeployment.NewCCIPJobSpecs(envWithRMN.Env.NodeIDs, envWithRMN.Env.Offchain) require.NoError(t, err) @@ -171,40 +355,105 @@ func TestRMN(t *testing.T) { // Need to keep track of the block number for each chain so that event subscription can be done from that block. startBlocks := make(map[uint64]*uint64) - - // Send one message from one chain to another. expectedSeqNum := make(map[uint64]uint64) - e := envWithRMN.Env - for src := range e.Chains { - for dest, destChain := range e.Chains { - if src == dest { - continue - } - latesthdr, err := destChain.Client.HeaderByNumber(testcontext.Get(t), nil) - require.NoError(t, err) - block := latesthdr.Number.Uint64() - startBlocks[dest] = &block - seqNum := ccipdeployment.TestSendRequest(t, e, onChainState, src, dest, false, nil) - expectedSeqNum[dest] = seqNum + for _, msg := range tc.messagesToSend { + fromChain := chainSelectors[msg.fromChainIdx] + toChain := chainSelectors[msg.toChainIdx] + + for i := 0; i < msg.count; i++ { + seqNum := ccipdeployment.TestSendRequest(t, envWithRMN.Env, onChainState, fromChain, toChain, false, router.ClientEVM2AnyMessage{ + Receiver: common.LeftPadBytes(onChainState.Chains[toChain].Receiver.Address().Bytes(), 32), + Data: []byte("hello world"), + TokenAmounts: nil, + FeeToken: common.HexToAddress("0x0"), + ExtraArgs: nil, + }) + expectedSeqNum[toChain] = seqNum + t.Logf("Sent message from chain %d to chain %d with seqNum %d", fromChain, toChain, seqNum) + } + + zero := uint64(0) + startBlocks[toChain] = &zero + } + t.Logf("Sent all messages, expectedSeqNum: %v", expectedSeqNum) + + commitReportReceived := make(chan struct{}) + go func() { + ccipdeployment.ConfirmCommitForAllWithExpectedSeqNums(t, envWithRMN.Env, onChainState, expectedSeqNum, startBlocks) + commitReportReceived <- struct{}{} + }() + + if tc.passIfNoCommitAfter > 0 { // wait for a duration and assert that commit reports were not delivered + tim := time.NewTimer(tc.passIfNoCommitAfter) + t.Logf("waiting for %s before asserting that commit report was not received", tc.passIfNoCommitAfter) + select { + case <-commitReportReceived: + t.Errorf("Commit report was received while it was not expected") + return + case <-tim.C: + return } } t.Logf("⌛ Waiting for commit reports...") - ccipdeployment.ConfirmCommitForAllWithExpectedSeqNums(t, envWithRMN.Env, onChainState, expectedSeqNum, startBlocks) + <-commitReportReceived // wait for commit reports t.Logf("✅ Commit report") - t.Logf("⌛ Waiting for exec reports...") - ccipdeployment.ConfirmExecWithSeqNrForAll(t, envWithRMN.Env, onChainState, expectedSeqNum, startBlocks) - t.Logf("✅ Exec report") + if tc.waitForExec { + t.Logf("⌛ Waiting for exec reports...") + ccipdeployment.ConfirmExecWithSeqNrForAll(t, envWithRMN.Env, onChainState, expectedSeqNum, startBlocks) + t.Logf("✅ Exec report") + } } -func createObserverNodesBitmap(numNodes int) *big.Int { - // for now, all nodes support all chains, so the bitmap is all 1s. - // first, initialize a big.Int with all bits set to 0. - // then, set the first numNodes bits to 1. +func createObserverNodesBitmap(chainSel uint64, rmnNodes []rmnNode, chainSelectors []uint64) *big.Int { bitmap := new(big.Int) - for i := 0; i < numNodes; i++ { - bitmap.SetBit(bitmap, i, 1) + for _, n := range rmnNodes { + observedChainSelectors := mapset.NewSet[uint64]() + for _, chainIdx := range n.observedChainIdxs { + observedChainSelectors.Add(chainSelectors[chainIdx]) + } + + if !observedChainSelectors.Contains(chainSel) { + continue + } + + bitmap.SetBit(bitmap, n.id, 1) } + return bitmap } + +type homeChainConfig struct { + f map[int]int +} + +type remoteChainConfig struct { + chainIdx int + f int +} + +type rmnNode struct { + id int + isSigner bool + observedChainIdxs []int + forceExit bool // force exit will simply force exit the rmn node to simulate failure scenarios +} + +type messageToSend struct { + fromChainIdx int + toChainIdx int + count int +} + +type rmnTestCase struct { + name string + // If set to 0, the test will wait for commit reports. + // If set to a positive value, the test will wait for that duration and will assert that commit report was not delivered. + passIfNoCommitAfter time.Duration + waitForExec bool + homeChainConfig homeChainConfig + remoteChainsConfig []remoteChainConfig + rmnNodes []rmnNode + messagesToSend []messageToSend +} diff --git a/integration-tests/smoke/ccip_test.go b/integration-tests/smoke/ccip_test.go index 686f2c10299..5b0ba285527 100644 --- a/integration-tests/smoke/ccip_test.go +++ b/integration-tests/smoke/ccip_test.go @@ -4,6 +4,7 @@ import ( "math/big" "testing" + "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" @@ -83,7 +84,13 @@ func TestInitialDeployOnLocal(t *testing.T) { require.NoError(t, err) block := latesthdr.Number.Uint64() startBlocks[dest] = &block - seqNum := ccdeploy.TestSendRequest(t, e, state, src, dest, false, nil) + seqNum := ccdeploy.TestSendRequest(t, e, state, src, dest, false, router.ClientEVM2AnyMessage{ + Receiver: common.LeftPadBytes(state.Chains[dest].Receiver.Address().Bytes(), 32), + Data: []byte("hello world"), + TokenAmounts: nil, + FeeToken: common.HexToAddress("0x0"), + ExtraArgs: nil, + }) expectedSeqNum[dest] = seqNum } } @@ -223,11 +230,28 @@ func TestTokenTransfer(t *testing.T) { block := latesthdr.Number.Uint64() startBlocks[dest] = &block + var ( + receiver = common.LeftPadBytes(state.Chains[dest].Receiver.Address().Bytes(), 32) + data = []byte("hello world") + feeToken = common.HexToAddress("0x0") + ) if src == tenv.HomeChainSel && dest == tenv.FeedChainSel { - seqNum := ccdeploy.TestSendRequest(t, e, state, src, dest, false, tokens[src]) + seqNum := ccdeploy.TestSendRequest(t, e, state, src, dest, false, router.ClientEVM2AnyMessage{ + Receiver: receiver, + Data: data, + TokenAmounts: tokens[src], + FeeToken: feeToken, + ExtraArgs: nil, + }) expectedSeqNum[dest] = seqNum } else { - seqNum := ccdeploy.TestSendRequest(t, e, state, src, dest, false, nil) + seqNum := ccdeploy.TestSendRequest(t, e, state, src, dest, false, router.ClientEVM2AnyMessage{ + Receiver: receiver, + Data: data, + TokenAmounts: nil, + FeeToken: feeToken, + ExtraArgs: nil, + }) expectedSeqNum[dest] = seqNum } } diff --git a/integration-tests/smoke/vrfv2plus_test.go b/integration-tests/smoke/vrfv2plus_test.go index afff910130f..df0917fd4fb 100644 --- a/integration-tests/smoke/vrfv2plus_test.go +++ b/integration-tests/smoke/vrfv2plus_test.go @@ -1887,6 +1887,7 @@ func TestVRFv2PlusPendingBlockSimulationAndZeroConfirmationDelays(t *testing.T) } func TestVRFv2PlusNodeReorg(t *testing.T) { + t.Skip("Flakey", "https://smartcontract-it.atlassian.net/browse/DEVSVCS-829") t.Parallel() var ( env *test_env.CLClusterTestEnv diff --git a/operator_ui/TAG b/operator_ui/TAG index 259a021299a..26d6a040a47 100644 --- a/operator_ui/TAG +++ b/operator_ui/TAG @@ -1 +1 @@ -v0.8.0-cb39b89 +v0.8.0-7c02aca diff --git a/plugins/loop_registry.go b/plugins/loop_registry.go index 51c6310ffa7..82ef219566a 100644 --- a/plugins/loop_registry.go +++ b/plugins/loop_registry.go @@ -27,17 +27,21 @@ type LoopRegistry struct { mu sync.Mutex registry map[string]*RegisteredLoop - lggr logger.Logger - cfgTracing config.Tracing - cfgTelemetry config.Telemetry + lggr logger.Logger + cfgTracing config.Tracing + cfgTelemetry config.Telemetry + telemetryAuthHeaders map[string]string + telemetryAuthPubKeyHex string } -func NewLoopRegistry(lggr logger.Logger, tracing config.Tracing, telemetry config.Telemetry) *LoopRegistry { +func NewLoopRegistry(lggr logger.Logger, tracing config.Tracing, telemetry config.Telemetry, telemetryAuthHeaders map[string]string, telemetryAuthPubKeyHex string) *LoopRegistry { return &LoopRegistry{ - registry: map[string]*RegisteredLoop{}, - lggr: logger.Named(lggr, "LoopRegistry"), - cfgTracing: tracing, - cfgTelemetry: telemetry, + registry: map[string]*RegisteredLoop{}, + lggr: logger.Named(lggr, "LoopRegistry"), + cfgTracing: tracing, + cfgTelemetry: telemetry, + telemetryAuthHeaders: telemetryAuthHeaders, + telemetryAuthPubKeyHex: telemetryAuthPubKeyHex, } } @@ -74,10 +78,18 @@ func (m *LoopRegistry) Register(id string) (*RegisteredLoop, error) { envCfg.TelemetryCACertFile = m.cfgTelemetry.CACertFile() envCfg.TelemetryAttributes = m.cfgTelemetry.ResourceAttributes() envCfg.TelemetryTraceSampleRatio = m.cfgTelemetry.TraceSampleRatio() + envCfg.TelemetryEmitterBatchProcessor = m.cfgTelemetry.EmitterBatchProcessor() + envCfg.TelemetryEmitterExportTimeout = m.cfgTelemetry.EmitterExportTimeout() + envCfg.TelemetryAuthPubKeyHex = m.telemetryAuthPubKeyHex + } + m.lggr.Debugf("Registered loopp %q with config %v, port %d", id, envCfg, envCfg.PrometheusPort) + + // Add auth header after logging config + if m.cfgTelemetry != nil { + envCfg.TelemetryAuthHeaders = m.telemetryAuthHeaders } 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 84b6b0cefc9..c7484b7aca9 100644 --- a/plugins/loop_registry_test.go +++ b/plugins/loop_registry_test.go @@ -2,6 +2,7 @@ package plugins import ( "testing" + "time" "github.com/stretchr/testify/require" @@ -11,7 +12,7 @@ import ( func TestPluginPortManager(t *testing.T) { // register one - m := NewLoopRegistry(logger.TestLogger(t), nil, nil) + m := NewLoopRegistry(logger.TestLogger(t), nil, nil, nil, "") pFoo, err := m.Register("foo") require.NoError(t, err) require.Equal(t, "foo", pFoo.Name) @@ -55,6 +56,10 @@ func (m mockCfgTelemetry) ResourceAttributes() map[string]string { func (m mockCfgTelemetry) TraceSampleRatio() float64 { return 0.42 } +func (m mockCfgTelemetry) EmitterBatchProcessor() bool { return true } + +func (m mockCfgTelemetry) EmitterExportTimeout() time.Duration { return 1 * time.Second } + func TestLoopRegistry_Register(t *testing.T) { mockCfgTracing := &mockCfgTracing{} mockCfgTelemetry := &mockCfgTelemetry{} @@ -86,4 +91,6 @@ func TestLoopRegistry_Register(t *testing.T) { require.Equal(t, "http://localhost:9001", envCfg.TelemetryEndpoint) require.Equal(t, loop.OtelAttributes{"foo": "bar"}, envCfg.TelemetryAttributes) require.Equal(t, 0.42, envCfg.TelemetryTraceSampleRatio) + require.True(t, envCfg.TelemetryEmitterBatchProcessor) + require.Equal(t, 1*time.Second, envCfg.TelemetryEmitterExportTimeout) } diff --git a/testdata/scripts/config/merge_raw_configs.txtar b/testdata/scripts/config/merge_raw_configs.txtar new file mode 100644 index 00000000000..b3d50f22b36 --- /dev/null +++ b/testdata/scripts/config/merge_raw_configs.txtar @@ -0,0 +1,470 @@ +exec chainlink node -c config1.toml -c config2.toml -s secrets.toml validate +cmp stdout out.txt +! exists $WORK/logs + +-- config1.toml -- +[Log] +Level = 'debug' + +[Feature] +FeedsManager = true +LogPoller = true +UICSAKeys = true + +[OCR2] +Enabled = true + +[P2P] +[P2P.V2] +Enabled = true +DeltaDial = '5s' +DeltaReconcile = '5s' +ListenAddresses = ['0.0.0.0:6691'] + +[WebServer] +HTTPPort = 6688 +TLS.HTTPSPort = 0 +AllowOrigins = '*' + +[[Aptos]] +ChainID = '1' +Enabled = true + +[Aptos.Workflow] +ForwarderAddress = "address" +PublicKey = "" + +[[Aptos.Nodes]] +Name = 'a' +URL = 'https://example.com/a' + +[[Aptos.Nodes]] +Name = 'b' +URL = 'https://example.com/b' + +-- config2.toml -- +Log.Level = 'debug' + +[[Aptos]] +ChainID = '1' + +[Aptos.Workflow] +ForwarderAddress = "replacement-address" +PublicKey = "" + +[[Aptos.Nodes]] +Name = 'b' +URL = 'https://example.com/b/updated' + +[[Aptos.Nodes]] +Name = 'c' +URL = 'https://example.com/c' + +[[Aptos]] +ChainID = '2' + +[Aptos.Workflow] +ForwarderAddress = "chain-two" +Publickey = "abcdef" + +[[Aptos.Nodes]] +Name = 'd' +URL = 'https://example.com/d' + +-- secrets.toml -- +[Database] +URL = 'postgresql://user:pass1234567890abcd@localhost:5432/dbname?sslmode=disable' + +[Password] +Keystore = 'keystore_pass' + +-- out.txt -- +# Secrets: +[Database] +URL = 'xxxxx' +AllowSimplePasswords = false + +[Password] +Keystore = 'xxxxx' + +# Input Configuration: +[Feature] +FeedsManager = true +LogPoller = true +UICSAKeys = true + +[Log] +Level = 'debug' + +[WebServer] +AllowOrigins = '*' +HTTPPort = 6688 + +[WebServer.TLS] +HTTPSPort = 0 + +[OCR2] +Enabled = true + +[P2P] +[P2P.V2] +Enabled = true +DeltaDial = '5s' +DeltaReconcile = '5s' +ListenAddresses = ['0.0.0.0:6691'] + +[[Aptos]] +ChainID = '1' +Enabled = true + +[[Aptos.Nodes]] +Name = 'a' +URL = 'https://example.com/a' + +[[Aptos.Nodes]] +Name = 'b' +URL = 'https://example.com/b/updated' + +[[Aptos.Nodes]] +Name = 'c' +URL = 'https://example.com/c' + +[Aptos.Workflow] +ForwarderAddress = 'replacement-address' +PublicKey = '' + +[[Aptos]] +ChainID = '2' + +[[Aptos.Nodes]] +Name = 'd' +URL = 'https://example.com/d' + +[Aptos.Workflow] +ForwarderAddress = 'chain-two' +Publickey = 'abcdef' + +# Effective Configuration, with defaults applied: +InsecureFastScrypt = false +RootDir = '~/.chainlink' +ShutdownGracePeriod = '5s' + +[Feature] +FeedsManager = true +LogPoller = true +UICSAKeys = true +CCIP = true +MultiFeedsManagers = false + +[Database] +DefaultIdleInTxSessionTimeout = '1h0m0s' +DefaultLockTimeout = '15s' +DefaultQueryTimeout = '10s' +LogQueries = false +MaxIdleConns = 10 +MaxOpenConns = 100 +MigrateOnStartup = true + +[Database.Backup] +Dir = '' +Frequency = '1h0m0s' +Mode = 'none' +OnVersionUpgrade = true + +[Database.Listener] +MaxReconnectDuration = '10m0s' +MinReconnectInterval = '1m0s' +FallbackPollInterval = '30s' + +[Database.Lock] +Enabled = true +LeaseDuration = '10s' +LeaseRefreshInterval = '1s' + +[TelemetryIngress] +UniConn = false +Logging = false +BufferSize = 100 +MaxBatchSize = 50 +SendInterval = '500ms' +SendTimeout = '10s' +UseBatchSend = true + +[AuditLogger] +Enabled = false +ForwardToUrl = '' +JsonWrapperKey = '' +Headers = [] + +[Log] +Level = 'debug' +JSONConsole = false +UnixTS = false + +[Log.File] +Dir = '' +MaxSize = '5.12gb' +MaxAgeDays = 0 +MaxBackups = 1 + +[WebServer] +AuthenticationMethod = 'local' +AllowOrigins = '*' +BridgeResponseURL = '' +BridgeCacheTTL = '0s' +HTTPWriteTimeout = '10s' +HTTPPort = 6688 +SecureCookies = true +SessionTimeout = '15m0s' +SessionReaperExpiration = '240h0m0s' +HTTPMaxSize = '32.77kb' +StartTimeout = '15s' +ListenIP = '0.0.0.0' + +[WebServer.LDAP] +ServerTLS = true +SessionTimeout = '15m0s' +QueryTimeout = '2m0s' +BaseUserAttr = 'uid' +BaseDN = '' +UsersDN = 'ou=users' +GroupsDN = 'ou=groups' +ActiveAttribute = '' +ActiveAttributeAllowedValue = '' +AdminUserGroupCN = 'NodeAdmins' +EditUserGroupCN = 'NodeEditors' +RunUserGroupCN = 'NodeRunners' +ReadUserGroupCN = 'NodeReadOnly' +UserApiTokenEnabled = false +UserAPITokenDuration = '240h0m0s' +UpstreamSyncInterval = '0s' +UpstreamSyncRateLimit = '2m0s' + +[WebServer.MFA] +RPID = '' +RPOrigin = '' + +[WebServer.RateLimit] +Authenticated = 1000 +AuthenticatedPeriod = '1m0s' +Unauthenticated = 5 +UnauthenticatedPeriod = '20s' + +[WebServer.TLS] +CertPath = '' +ForceRedirect = false +Host = '' +HTTPSPort = 0 +KeyPath = '' +ListenIP = '0.0.0.0' + +[JobPipeline] +ExternalInitiatorsEnabled = false +MaxRunDuration = '10m0s' +MaxSuccessfulRuns = 10000 +ReaperInterval = '1h0m0s' +ReaperThreshold = '24h0m0s' +ResultWriteQueueDepth = 100 +VerboseLogging = true + +[JobPipeline.HTTPRequest] +DefaultTimeout = '15s' +MaxSize = '32.77kb' + +[FluxMonitor] +DefaultTransactionQueueDepth = 1 +SimulateTransactions = false + +[OCR2] +Enabled = true +ContractConfirmations = 3 +BlockchainTimeout = '20s' +ContractPollInterval = '1m0s' +ContractSubscribeInterval = '2m0s' +ContractTransmitterTransmitTimeout = '10s' +DatabaseTimeout = '10s' +KeyBundleID = '0000000000000000000000000000000000000000000000000000000000000000' +CaptureEATelemetry = false +CaptureAutomationCustomTelemetry = true +DefaultTransactionQueueDepth = 1 +SimulateTransactions = false +TraceLogging = false + +[OCR] +Enabled = false +ObservationTimeout = '5s' +BlockchainTimeout = '20s' +ContractPollInterval = '1m0s' +ContractSubscribeInterval = '2m0s' +DefaultTransactionQueueDepth = 1 +KeyBundleID = '0000000000000000000000000000000000000000000000000000000000000000' +SimulateTransactions = false +TransmitterAddress = '' +CaptureEATelemetry = false +TraceLogging = false + +[P2P] +IncomingMessageBufferSize = 10 +OutgoingMessageBufferSize = 10 +PeerID = '' +TraceLogging = false + +[P2P.V2] +Enabled = true +AnnounceAddresses = [] +DefaultBootstrappers = [] +DeltaDial = '5s' +DeltaReconcile = '5s' +ListenAddresses = ['0.0.0.0:6691'] + +[Keeper] +DefaultTransactionQueueDepth = 1 +GasPriceBufferPercent = 20 +GasTipCapBufferPercent = 20 +BaseFeeBufferPercent = 20 +MaxGracePeriod = 100 +TurnLookBack = 1000 + +[Keeper.Registry] +CheckGasOverhead = 200000 +PerformGasOverhead = 300000 +MaxPerformDataSize = 5000 +SyncInterval = '30m0s' +SyncUpkeepQueueSize = 10 + +[AutoPprof] +Enabled = false +ProfileRoot = '' +PollInterval = '10s' +GatherDuration = '10s' +GatherTraceDuration = '5s' +MaxProfileSize = '100.00mb' +CPUProfileRate = 1 +MemProfileRate = 1 +BlockProfileRate = 1 +MutexProfileFraction = 1 +MemThreshold = '4.00gb' +GoroutineThreshold = 5000 + +[Pyroscope] +ServerAddress = '' +Environment = 'mainnet' + +[Sentry] +Debug = false +DSN = '' +Environment = '' +Release = '' + +[Insecure] +DevWebServer = false +OCRDevelopmentMode = false +InfiniteDepthQueries = false +DisableRateLimiting = false + +[Tracing] +Enabled = false +CollectorTarget = '' +NodeID = '' +SamplingRatio = 0.0 +Mode = 'tls' +TLSCertPath = '' + +[Mercury] +VerboseLogging = false + +[Mercury.Cache] +LatestReportTTL = '1s' +MaxStaleAge = '1h0m0s' +LatestReportDeadline = '5s' + +[Mercury.TLS] +CertFile = '' + +[Mercury.Transmitter] +TransmitQueueMaxSize = 10000 +TransmitTimeout = '5s' + +[Capabilities] +[Capabilities.Peering] +IncomingMessageBufferSize = 10 +OutgoingMessageBufferSize = 10 +PeerID = '' +TraceLogging = false + +[Capabilities.Peering.V2] +Enabled = false +AnnounceAddresses = [] +DefaultBootstrappers = [] +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 +EmitterBatchProcessor = true +EmitterExportTimeout = '1s' + +[[Aptos]] +ChainID = '1' +Enabled = true + +[[Aptos.Nodes]] +Name = 'a' +URL = 'https://example.com/a' + +[[Aptos.Nodes]] +Name = 'b' +URL = 'https://example.com/b/updated' + +[[Aptos.Nodes]] +Name = 'c' +URL = 'https://example.com/c' + +[Aptos.Workflow] +ForwarderAddress = 'replacement-address' +PublicKey = '' + +[[Aptos]] +ChainID = '2' + +[[Aptos.Nodes]] +Name = 'd' +URL = 'https://example.com/d' + +[Aptos.Workflow] +ForwarderAddress = 'chain-two' +Publickey = 'abcdef' + +Valid configuration. diff --git a/testdata/scripts/node/validate/default.txtar b/testdata/scripts/node/validate/default.txtar index ad923919e08..5e8b847ceda 100644 --- a/testdata/scripts/node/validate/default.txtar +++ b/testdata/scripts/node/validate/default.txtar @@ -298,6 +298,8 @@ CACertFile = '' Endpoint = '' InsecureConnection = false TraceSampleRatio = 0.01 +EmitterBatchProcessor = true +EmitterExportTimeout = '1s' Invalid configuration: invalid secrets: 2 errors: - Database.URL: empty: must be provided and non-empty diff --git a/testdata/scripts/node/validate/defaults-override.txtar b/testdata/scripts/node/validate/defaults-override.txtar index 14c26bf85cd..bf8bece28bf 100644 --- a/testdata/scripts/node/validate/defaults-override.txtar +++ b/testdata/scripts/node/validate/defaults-override.txtar @@ -359,6 +359,8 @@ CACertFile = '' Endpoint = '' InsecureConnection = false TraceSampleRatio = 0.01 +EmitterBatchProcessor = true +EmitterExportTimeout = '1s' [[EVM]] ChainID = '1' diff --git a/testdata/scripts/node/validate/disk-based-logging-disabled.txtar b/testdata/scripts/node/validate/disk-based-logging-disabled.txtar index bff1c74c97b..2e72ed7e9bb 100644 --- a/testdata/scripts/node/validate/disk-based-logging-disabled.txtar +++ b/testdata/scripts/node/validate/disk-based-logging-disabled.txtar @@ -342,6 +342,8 @@ CACertFile = '' Endpoint = '' InsecureConnection = false TraceSampleRatio = 0.01 +EmitterBatchProcessor = true +EmitterExportTimeout = '1s' [[EVM]] ChainID = '1' 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 6b0b5db9eaf..7b27328f7a6 100644 --- a/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar +++ b/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar @@ -342,6 +342,8 @@ CACertFile = '' Endpoint = '' InsecureConnection = false TraceSampleRatio = 0.01 +EmitterBatchProcessor = true +EmitterExportTimeout = '1s' [[EVM]] ChainID = '1' diff --git a/testdata/scripts/node/validate/disk-based-logging.txtar b/testdata/scripts/node/validate/disk-based-logging.txtar index 59c8d7b1c5d..83d23546175 100644 --- a/testdata/scripts/node/validate/disk-based-logging.txtar +++ b/testdata/scripts/node/validate/disk-based-logging.txtar @@ -342,6 +342,8 @@ CACertFile = '' Endpoint = '' InsecureConnection = false TraceSampleRatio = 0.01 +EmitterBatchProcessor = true +EmitterExportTimeout = '1s' [[EVM]] ChainID = '1' diff --git a/testdata/scripts/node/validate/invalid-ocr-p2p.txtar b/testdata/scripts/node/validate/invalid-ocr-p2p.txtar index d3e56f97fbb..3fccffc4e69 100644 --- a/testdata/scripts/node/validate/invalid-ocr-p2p.txtar +++ b/testdata/scripts/node/validate/invalid-ocr-p2p.txtar @@ -327,6 +327,8 @@ CACertFile = '' Endpoint = '' InsecureConnection = false TraceSampleRatio = 0.01 +EmitterBatchProcessor = true +EmitterExportTimeout = '1s' Invalid configuration: invalid configuration: P2P.V2.Enabled: invalid value (false): P2P required for OCR or OCR2. Please enable P2P or disable OCR/OCR2. diff --git a/testdata/scripts/node/validate/invalid.txtar b/testdata/scripts/node/validate/invalid.txtar index 739f427317e..5ea0aa289a8 100644 --- a/testdata/scripts/node/validate/invalid.txtar +++ b/testdata/scripts/node/validate/invalid.txtar @@ -332,6 +332,8 @@ CACertFile = '' Endpoint = '' InsecureConnection = false TraceSampleRatio = 0.01 +EmitterBatchProcessor = true +EmitterExportTimeout = '1s' [[EVM]] ChainID = '1' diff --git a/testdata/scripts/node/validate/valid.txtar b/testdata/scripts/node/validate/valid.txtar index 6543a53c43d..26641c0ef76 100644 --- a/testdata/scripts/node/validate/valid.txtar +++ b/testdata/scripts/node/validate/valid.txtar @@ -339,6 +339,8 @@ CACertFile = '' Endpoint = '' InsecureConnection = false TraceSampleRatio = 0.01 +EmitterBatchProcessor = true +EmitterExportTimeout = '1s' [[EVM]] ChainID = '1' diff --git a/testdata/scripts/node/validate/warnings.txtar b/testdata/scripts/node/validate/warnings.txtar index ac8489f3246..51b3e897741 100644 --- a/testdata/scripts/node/validate/warnings.txtar +++ b/testdata/scripts/node/validate/warnings.txtar @@ -321,6 +321,8 @@ CACertFile = '' Endpoint = '' InsecureConnection = false TraceSampleRatio = 0.01 +EmitterBatchProcessor = true +EmitterExportTimeout = '1s' # Configuration warning: Tracing.TLSCertPath: invalid value (something): must be empty when Tracing.Mode is 'unencrypted' diff --git a/tools/bin/go_core_fuzz b/tools/bin/go_core_fuzz index eb0334fe7ca..49aaf33b65e 100755 --- a/tools/bin/go_core_fuzz +++ b/tools/bin/go_core_fuzz @@ -4,14 +4,22 @@ set +e SCRIPT_PATH=`dirname "$0"`; SCRIPT_PATH=`eval "cd \"$SCRIPT_PATH\" && pwd"` OUTPUT_FILE=${OUTPUT_FILE:-"./output.txt"} -FUZZ_TIMEOUT=${FUZZ_TIMEOUT:-10m} +FUZZ_TIMEOUT_MINUTES=${FUZZ_TIMEOUT_MINUTES:-"3"} +TOTAL_SECONDS=$((FUZZ_TIMEOUT_MINUTES * 60)) +if (( TOTAL_SECONDS >= 120 )); then + # Allow for a 30 second buffer between the timeout, and fuzz test runtime + FUZZ_SECONDS=$((TOTAL_SECONDS - 30)) +else + echo "Increase FUZZ_TIMEOUT_MINUTES to >=2, received $FUZZ_TIMEOUT_MINUTES" + exit 1 +fi + +echo "timeout minutes: $FUZZ_TIMEOUT_MINUTES" +echo "fuzz seconds: $FUZZ_SECONDS" echo "Failed fuzz tests and panics: ---------------------" echo "" -# the amount of --seconds here is subject to change based on how long the CI job takes in the future -# as we add more fuzz tests, we should take into consideration increasing this timelapse, so we can have enough coverage. -# We are timing out after ~10mins in case the tests hang. (Current CI duration is ~8m, modify if needed) -timeout "${FUZZ_TIMEOUT}" ./fuzz/fuzz_all_native.py --ci --seconds 420 --go_module_root ./ | tee $OUTPUT_FILE +timeout "${FUZZ_TIMEOUT_MINUTES}"m ./fuzz/fuzz_all_native.py --ci --seconds "$FUZZ_SECONDS" --go_module_root ./ | tee $OUTPUT_FILE EXITCODE=${PIPESTATUS[0]} # Assert no known sensitive strings present in test logger output diff --git a/tools/bin/go_core_race_tests b/tools/bin/go_core_race_tests index d09a8d903e4..2c4071bc20f 100755 --- a/tools/bin/go_core_race_tests +++ b/tools/bin/go_core_race_tests @@ -1,8 +1,8 @@ #!/usr/bin/env bash set -ex OUTPUT_FILE=${OUTPUT_FILE:-"./output.txt"} -TIMEOUT="${TIMEOUT:-30s}" -COUNT="${COUNT:-10}" +TIMEOUT="${TIMEOUT:-10s}" +COUNT="${COUNT:-5}" echo "Failed tests and panics: ---------------------" echo "" @@ -10,13 +10,13 @@ if [[ $GITHUB_EVENT_NAME == "schedule" ]]; then if [[ $DEBUG == "true" ]]; then GORACE="log_path=$PWD/race" go test -json -vet=off -race -shuffle on -timeout "$TIMEOUT" -count "$COUNT" $1 | tee $OUTPUT_FILE else - GORACE="log_path=$PWD/race" go test -json -vet=off -race -shuffle on -timeout "$TIMEOUT" -count "$COUNT" $1 | cat > $OUTPUT_FILE + GORACE="log_path=$PWD/race" go test -vet=off -race -shuffle on -timeout "$TIMEOUT" -count "$COUNT" $1 | cat > $OUTPUT_FILE fi else if [[ $DEBUG == "true" ]]; then GORACE="log_path=$PWD/race" go test -json -vet=off -race -shuffle on -timeout "$TIMEOUT" -count "$COUNT" $1 | tee $OUTPUT_FILE else - GORACE="log_path=$PWD/race" go test -json -vet=off -race -shuffle on -timeout "$TIMEOUT" -count "$COUNT" $1 | cat > $OUTPUT_FILE + GORACE="log_path=$PWD/race" go test -vet=off -race -shuffle on -timeout "$TIMEOUT" -count "$COUNT" $1 | cat > $OUTPUT_FILE fi fi EXITCODE=${PIPESTATUS[0]} diff --git a/tools/bin/modgraph b/tools/bin/modgraph index 14e6f78e81a..fdb9789b044 100755 --- a/tools/bin/modgraph +++ b/tools/bin/modgraph @@ -24,49 +24,15 @@ flowchart LR chainlink-vrf end - subgraph tdh2 - tdh2/go/tdh2 - tdh2/go/ocr2/decryptionplugin - end - - subgraph chainlink-protos - chainlink-protos/orchestrator - chainlink-protos/job-distributor - end - - classDef outline stroke-dasharray:6,fill:none; - class chains,products,tdh2,chainlink-protos outline + classDef group stroke-dasharray:6,fill:none; + class chains,products group " -go mod graph | \ - # org only - grep smartcontractkit.*smartcontractkit | \ - # drop prefix - sed s/"github\.com\/smartcontractkit\/"/""/g | \ - # insert edges - sed s/" "/" --> "/ | \ - # drop versions - sed s/"@[^ ]*"/""/g | \ - # insert links - sed s/"\([^ ]*\)$"/"\1\nclick \1 href \"https:\/\/github.com\/smartcontractkit\/\1\""/ | \ - # truncate links to repo - sed s/"\"https:\/\/github.com\/smartcontractkit\/\([^\"\/]*\)\/.*\""/"\"https:\/\/github.com\/smartcontractkit\/\1\""/ | \ - # dedupe lines - awk '!x[$0]++' | \ - # indent - sed 's/^/ /' +go mod graph | modgraph -prefix github.com/smartcontractkit/ echo "\`\`\`" echo "## All modules \`\`\`mermaid flowchart LR - subgraph chainlink - chainlink/v2 - chainlink/deployment - chainlink/integration-tests - chainlink/load-tests - chainlink/core/scripts - end - subgraph chains chainlink-cosmos chainlink-solana @@ -83,42 +49,8 @@ flowchart LR chainlink-vrf end - subgraph tdh2 - tdh2/go/tdh2 - tdh2/go/ocr2/decryptionplugin - end - - subgraph chainlink-testing-framework - chainlink-testing-framework/havoc - chainlink-testing-framework/lib - chainlink-testing-framework/lib/grafana - chainlink-testing-framework/seth - chainlink-testing-framework/wasp - end - - subgraph chainlink-protos - chainlink-protos/orchestrator - chainlink-protos/job-distributor - end - - classDef outline stroke-dasharray:6,fill:none; - class chainlink,chains,products,tdh2,chainlink-protos,chainlink-testing-framework outline + classDef group stroke-dasharray:6,fill:none; + class chains,products group " -gomods graph | \ - # org only - grep smartcontractkit.*smartcontractkit | \ - # drop prefix - sed s/"github\.com\/smartcontractkit\/"/""/g | \ - # insert edges - sed s/" "/" --> "/ | \ - # drop versions - sed s/"@[^ ]*"/""/g | \ - # insert links - sed s/"\([^ ]*\)$"/"\1\nclick \1 href \"https:\/\/github.com\/smartcontractkit\/\1\""/ | \ - # truncate links to repo - sed s/"\"https:\/\/github.com\/smartcontractkit\/\([^\"\/]*\)\/.*\""/"\"https:\/\/github.com\/smartcontractkit\/\1\""/ | \ - # dedupe lines - awk '!x[$0]++' | \ - # indent - sed 's/^/ /' +gomods graph | modgraph -prefix github.com/smartcontractkit/ echo "\`\`\`" \ No newline at end of file