From 6c165f72bb75548fa4ba54e6c6309768d4aa8d1f Mon Sep 17 00:00:00 2001 From: Harman Singh Date: Fri, 22 Nov 2024 09:05:26 -0700 Subject: [PATCH] feat: add all contracts and scripts (#1) --- .env | 32 + .eslintignore | 3 + .eslintrc.json | 75 + .github/pull_request_template.md | 11 + .github/workflows/ci.yaml | 114 + .gitignore | 7 + .gitmodules | 9 + .licenseignore | 3 + .nvmrc | 1 + .prettierignore | 3 + .prettierrc | 5 + Makefile | 82 + README.md | 81 +- SECURITY.md | 4 + aptos-core | 1 + cspell.json | 32 + docker-delete-containers.sh | 22 + docker-export-logs.sh | 23 + docker-start-containers.sh | 26 + e2e/test/e2e.test.ts | 502 ++ eslint.config.mjs | 42 + evm-cctp-contracts | 1 + jest.config.ts | 26 + package.json | 52 + packages/message_transmitter/Makefile | 24 + packages/message_transmitter/Move.toml | 24 + packages/message_transmitter/Prover.toml | 21 + .../message_transmitter/sources/attester.move | 671 +++ .../sources/deserialize.move | 87 + .../message_transmitter/sources/message.move | 224 + .../sources/message_transmitter.move | 1074 ++++ .../sources/serialize.move | 85 + .../message_transmitter/sources/state.move | 349 ++ packages/token_messenger_minter/Makefile | 23 + packages/token_messenger_minter/Move.toml | 34 + .../scripts/deposit_for_burn.move | 21 + .../scripts/deposit_for_burn_with_caller.move | 24 + .../scripts/handle_receive_message.move | 15 + .../token_messenger_minter/scripts/mint.move | 10 + .../scripts/replace_deposit_for_burn.move | 18 + .../token_messenger_minter/sources/state.move | 427 ++ .../sources/token_messenger/burn_message.move | 171 + .../token_messenger/token_messenger.move | 1127 ++++ .../sources/token_messenger_minter.move | 174 + .../token_minter/token_controller.move | 372 ++ .../sources/token_minter/token_minter.move | 303 + renovate.json | 7 + scripts/aptos/aptos-local-network.sh | 39 + scripts/evm/cctp_deploy.s.sol | 230 + scripts/evm/usdc_deploy.s.sol | 80 + setup-evm-contracts.sh | 90 + stablecoin-aptos | 1 + tsconfig.json | 21 + .../aptos/client/aptosContractClient.ts | 166 + .../aptos/client/aptosExtensionsClient.ts | 148 + .../aptos/client/messageTransmitterClient.ts | 179 + typescript/aptos/client/stablecoinClient.ts | 80 + .../client/tokenMessengerMinterClient.ts | 225 + .../deploy/calculateDeploymentAddresses.ts | 55 + typescript/aptos/deploy/deployAllPackages.ts | 72 + .../deploy/generateDefaultPackageMetadata.ts | 108 + typescript/aptos/deploy/index.ts | 36 + typescript/aptos/deploy/upgradePackage.ts | 80 + typescript/aptos/deploy/verifyPackage.ts | 103 + typescript/aptos/utils/cctpFunction.ts | 54 + typescript/aptos/utils/cctpFunctionScript.ts | 29 + typescript/aptos/utils/cctpModule.ts | 26 + typescript/aptos/utils/cctpPackage.ts | 19 + typescript/aptos/utils/helper.ts | 201 + typescript/aptos/utils/moveFunction.ts | 69 + typescript/aptos/utils/moveModule.ts | 27 + typescript/aptos/utils/package.ts | 19 + typescript/evm/client/evmContractClient.ts | 129 + versions.sh | 1 + yarn.lock | 4885 +++++++++++++++++ 75 files changed, 13612 insertions(+), 2 deletions(-) create mode 100644 .env create mode 100644 .eslintignore create mode 100644 .eslintrc.json create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/ci.yaml create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 .licenseignore create mode 100644 .nvmrc create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 Makefile create mode 100644 SECURITY.md create mode 160000 aptos-core create mode 100644 cspell.json create mode 100755 docker-delete-containers.sh create mode 100755 docker-export-logs.sh create mode 100755 docker-start-containers.sh create mode 100644 e2e/test/e2e.test.ts create mode 100644 eslint.config.mjs create mode 160000 evm-cctp-contracts create mode 100644 jest.config.ts create mode 100644 package.json create mode 100644 packages/message_transmitter/Makefile create mode 100644 packages/message_transmitter/Move.toml create mode 100644 packages/message_transmitter/Prover.toml create mode 100644 packages/message_transmitter/sources/attester.move create mode 100644 packages/message_transmitter/sources/deserialize.move create mode 100644 packages/message_transmitter/sources/message.move create mode 100644 packages/message_transmitter/sources/message_transmitter.move create mode 100644 packages/message_transmitter/sources/serialize.move create mode 100644 packages/message_transmitter/sources/state.move create mode 100644 packages/token_messenger_minter/Makefile create mode 100644 packages/token_messenger_minter/Move.toml create mode 100644 packages/token_messenger_minter/scripts/deposit_for_burn.move create mode 100644 packages/token_messenger_minter/scripts/deposit_for_burn_with_caller.move create mode 100644 packages/token_messenger_minter/scripts/handle_receive_message.move create mode 100644 packages/token_messenger_minter/scripts/mint.move create mode 100644 packages/token_messenger_minter/scripts/replace_deposit_for_burn.move create mode 100644 packages/token_messenger_minter/sources/state.move create mode 100644 packages/token_messenger_minter/sources/token_messenger/burn_message.move create mode 100644 packages/token_messenger_minter/sources/token_messenger/token_messenger.move create mode 100644 packages/token_messenger_minter/sources/token_messenger_minter.move create mode 100644 packages/token_messenger_minter/sources/token_minter/token_controller.move create mode 100644 packages/token_messenger_minter/sources/token_minter/token_minter.move create mode 100644 renovate.json create mode 100755 scripts/aptos/aptos-local-network.sh create mode 100644 scripts/evm/cctp_deploy.s.sol create mode 100644 scripts/evm/usdc_deploy.s.sol create mode 100755 setup-evm-contracts.sh create mode 160000 stablecoin-aptos create mode 100644 tsconfig.json create mode 100644 typescript/aptos/client/aptosContractClient.ts create mode 100644 typescript/aptos/client/aptosExtensionsClient.ts create mode 100644 typescript/aptos/client/messageTransmitterClient.ts create mode 100644 typescript/aptos/client/stablecoinClient.ts create mode 100644 typescript/aptos/client/tokenMessengerMinterClient.ts create mode 100644 typescript/aptos/deploy/calculateDeploymentAddresses.ts create mode 100644 typescript/aptos/deploy/deployAllPackages.ts create mode 100644 typescript/aptos/deploy/generateDefaultPackageMetadata.ts create mode 100644 typescript/aptos/deploy/index.ts create mode 100644 typescript/aptos/deploy/upgradePackage.ts create mode 100644 typescript/aptos/deploy/verifyPackage.ts create mode 100644 typescript/aptos/utils/cctpFunction.ts create mode 100644 typescript/aptos/utils/cctpFunctionScript.ts create mode 100644 typescript/aptos/utils/cctpModule.ts create mode 100644 typescript/aptos/utils/cctpPackage.ts create mode 100644 typescript/aptos/utils/helper.ts create mode 100644 typescript/aptos/utils/moveFunction.ts create mode 100644 typescript/aptos/utils/moveModule.ts create mode 100644 typescript/aptos/utils/package.ts create mode 100644 typescript/evm/client/evmContractClient.ts create mode 100644 versions.sh create mode 100644 yarn.lock diff --git a/.env b/.env new file mode 100644 index 0000000..7273306 --- /dev/null +++ b/.env @@ -0,0 +1,32 @@ +# Copyright 2024 Circle Internet Financial, LTD. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +################################## +# EVM Contract Configurations # +################################## + +EVM_RPC_URL=http://localhost:8500 + +# These values will always be consistent after an Anvil node is started +# and the setup-evm-contracts script is run. +EVM_USDC_ADDRESS=0x700b6a60ce7eaaea56f065753d8dcb9653dbad35 +EVM_MESSAGE_TRANSMITTER_ADDRESS=0x2E983A1Ba5e8b38AAAeC4B440B9dDcFBf72E15d1 +EVM_TOKEN_MINTER_ADDRESS=0xbdEd0D2bf404bdcBa897a74E6657f1f12e5C6fb6 +EVM_TOKEN_MESSENGER_ADDRESS=0x057ef64E23666F000b34aE31332854aCBd1c8544 +EVM_TOKEN_MINTER_DEPLOYER_KEY=0x701b615bbdfb9de65240bc28bd21bbc0d996645a3dd57e7b12bc2bdf6f192c82 +EVM_TOKEN_CONTROLLER_DEPLOYER_KEY=0x701b615bbdfb9de65240bc28bd21bbc0d996645a3dd57e7b12bc2bdf6f192c82 +EVM_TOKEN_MESSENGER_DEPLOYER_KEY=0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6 +EVM_ATTESTER_ADDRESS=0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..cf762fe --- /dev/null +++ b/.eslintignore @@ -0,0 +1,3 @@ +node_modules +dist +yarn.lock diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..41ba910 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,75 @@ +{ + "env": { + "es2021": true, + "node": true + }, + "extends": [ + "airbnb-base", + "airbnb-typescript/base", + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:prettier/recommended", + "prettier" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module", + "project": "./tsconfig.json" + }, + "plugins": ["@typescript-eslint", "simple-import-sort", "prettier", "prefer-arrow"], + "rules": { + "no-plusplus": "off", + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_" + } + ], + "@typescript-eslint/explicit-module-boundary-types": "error", + "@typescript-eslint/ban-types": "warn", + "@typescript-eslint/no-var-requires": "error", + "@typescript-eslint/no-explicit-any": "error", + "@typescript-eslint/no-empty-function": "error", + "@typescript-eslint/ban-ts-comment": "warn", + "@typescript-eslint/adjacent-overload-signatures": "error", + "class-methods-use-this": "off", + "no-undef": "error", + "no-empty": "error", + "simple-import-sort/imports": "error", + "simple-import-sort/exports": "error", + "import/prefer-default-export": "off", + "import/no-relative-packages": "off", + "prettier/prettier": "warn", + "no-console": "error", + "import/no-cycle": "off", // Disabling for now, this is taking multiple seconds to run -- https://github.com/import-js/eslint-plugin-import/issues/2348 + "comma-dangle": [ + "error", + { + "arrays": "always-multiline", + "exports": "always-multiline", + "functions": "never", + "imports": "always-multiline", + "objects": "always-multiline" + } + ], + "prefer-arrow/prefer-arrow-functions": [ + "error", + { + "disallowPrototype": true, + "singleReturnOnly": false, + "classPropertiesAllowed": false + } + ], + "prefer-arrow-callback": ["error", { "allowNamedFunctions": true }], + "func-style": ["error", "expression"] + }, + "overrides": [ + { + "files": ["**/*.ts"] + } + ] +} diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..e734a1f --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,11 @@ +## Summary + +## Detail + +## Testing + +## Documentation + +--- + +**Requested Reviewers:** @mention diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..e0bc52b --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,114 @@ +# Copyright (c) 2024, Circle Internet Group, Inc. +# All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: Continuous Integration +on: + pull_request: + push: + branches: [master] +jobs: + run-message-transmitter-tests: + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v3 + with: + submodules: "true" + + - name: Install Node + uses: actions/setup-node@v2 + with: + node-version: "20.14.0" + + - name: Compile and Run Tests + run: | + make setup + make message-transmitter-coverage + make verify-message-transmitter-coverage + + run-token-messenger-tests: + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v3 + with: + submodules: "true" + + - name: Install Node + uses: actions/setup-node@v2 + with: + node-version: "20.14.0" + + - name: Compile and Run Tests + run: | + make setup + make token-messenger-minter-coverage + make verify-token-messenger-minter-coverage + + run-e2e-tests: + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v3 + with: + submodules: "true" + + - name: Install Node + uses: actions/setup-node@v2 + with: + node-version: "20.14.0" + + - name: Compile and Run Tests + run: | + ./docker-start-containers.sh + sleep 30 + make compile-scripts + yarn test:e2e + + spell-checker: + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v3 + + - name: Install Node + uses: actions/setup-node@v2 + with: + node-version: "20.14.0" + + - name: Compile and Run Tests + run: | + npm install -g cspell@latest + cspell ** + + scan: + needs: + - run-message-transmitter-tests + - run-token-messenger-tests + - run-e2e-tests + - spell-checker + if: github.event_name == 'pull_request' + uses: circlefin/circle-public-github-workflows/.github/workflows/pr-scan.yaml@v1 + + release-sbom: + needs: + - run-message-transmitter-tests + - run-token-messenger-tests + - run-e2e-tests + - spell-checker + if: github.event_name == 'push' + uses: circlefin/circle-public-github-workflows/.github/workflows/attach-release-assets.yaml@v1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c9ba74d --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +build/ +.idea + +dist +node_modules + +logs diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..a7982c2 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "evm-cctp-contracts"] + path = evm-cctp-contracts + url = git@github.com:circlefin/evm-cctp-contracts.git +[submodule "aptos-core"] + path = aptos-core + url = git@github.com:aptos-labs/aptos-core.git +[submodule "stablecoin-aptos"] + path = stablecoin-aptos + url = git@github.com:circlefin/stablecoin-aptos.git diff --git a/.licenseignore b/.licenseignore new file mode 100644 index 0000000..13f355c --- /dev/null +++ b/.licenseignore @@ -0,0 +1,3 @@ +pkg:npm/v8-coverage +pkg:npm/sha.js +pkg:npm/sprintf-js diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..42e31a0 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v20.14.0 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..cf762fe --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +node_modules +dist +yarn.lock diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..054d599 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "tabWidth": 2, + "useTabs": false, + "printWidth": 120 +} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..08410a6 --- /dev/null +++ b/Makefile @@ -0,0 +1,82 @@ +.PHONY: setup install-aptos test compile-scripts message-transmitter-test message-transmitter-coverage token-messenger-minter-test \ +token-messenger-minter-coverage verify-message-transmitter-coverage verify-token-messenger-minter-coverage compile-message-transmitter-scripts compile-token-messenger-minter-script + +setup: install-aptos + git submodule update --init --recursive + +install-aptos: + @arch=$$(uname -m); \ + if [ "$$arch" = "arm64" ]; then \ + if ! command -v brew >/dev/null 2>&1; then \ + echo "Please install brew."; \ + exit 1; \ + fi; \ + brew install aptos; \ + brew install jq; \ + else \ + if [ ! -f versions.sh ]; then \ + echo "Please ensure versions.sh exists in top-level of repository."; \ + exit 1; \ + fi; \ + . ./versions.sh; \ + if [ -z "$$APTOS_CLI_VERSION" ]; then \ + echo "Please ensure that version is set for APTOS_CLI_VERSION in versions.sh."; \ + exit 1; \ + fi; \ + curl -sSfL -o /tmp/aptos.zip "https://github.com/aptos-labs/aptos-core/releases/download/aptos-cli-v$$APTOS_CLI_VERSION/aptos-cli-$$APTOS_CLI_VERSION-Ubuntu-22.04-x86_64.zip"; \ + sudo unzip /tmp/aptos.zip -d /usr/local/bin; \ + sudo chmod +x /usr/local/bin/*; \ + fi + +test: message-transmitter-test token-messenger-minter-test + +compile-scripts: compile-message-transmitter-scripts compile-token-messenger-minter-scripts + +message-transmitter-test: + aptos move test --package-dir packages/message_transmitter --dev + +message-transmitter-coverage: + aptos move test --package-dir packages/message_transmitter --coverage --dev + +token-messenger-minter-test: + aptos move test --package-dir packages/token_messenger_minter --dev + +token-messenger-minter-coverage: + aptos move test --package-dir packages/token_messenger_minter --coverage --dev + +verify-message-transmitter-coverage: + @coverage=$$(aptos move test --package-dir packages/message_transmitter --coverage --dev | grep "Move Coverage:" | grep -Eo "[0-9]+" | head -1); \ + if [ $$coverage -eq 100 ]; then \ + echo "Test Coverage is $$coverage%"; \ + else \ + echo "Test Coverage is only $$coverage%. Should be at least 99%"; \ + exit 1; \ + fi + +verify-token-messenger-minter-coverage: + @coverage=$$(aptos move test --package-dir packages/token_messenger_minter --coverage --dev | grep "Move Coverage:" | grep -Eo "[0-9]+" | head -1); \ + if [ $$coverage -eq 100 ]; then \ + echo "Test Coverage is $$coverage%"; \ + else \ + echo "Test Coverage is only $$coverage%. Should be at least 99%"; \ + exit 1; \ + fi + +compile-message-transmitter-scripts: + aptos move compile --package-dir packages/message_transmitter --dev + +compile-token-messenger-minter-scripts: + aptos move compile --package-dir packages/token_messenger_minter --dev + +verify-metadata: + @if [ -z "$(package)" ] || [ -z "$(package_id)" ] || [ -z "$(url)" ] || [ -z "$(included_artifacts)" ]; then \ + echo "Usage: make verify-package package=\"\" package_id=\"\" included_artifacts=\"\" url=\"\" [named_addresses=\"\"]"; \ + exit 1; \ + fi; \ + \ + aptos move verify-package \ + --package-dir "packages/$(package)" \ + --account "$(package_id)" \ + --named-addresses "$(named_addresses)" \ + --included-artifacts "$(included_artifacts)" \ + --url "${url}"; diff --git a/README.md b/README.md index bbec958..63d1415 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,79 @@ -# aptos-cctp -Official repository for Aptos smart contracts used by the Cross-Chain Transfer Protocol +# aptos-cctp-private + +Official repository for Aptos smart contracts used by the Cross-Chain Transfer Protocol. + +--- + +## Getting Started + +### Prerequisites + +Before you can start working with the contracts in this repository, make sure you have the following prerequisites installed: + +1. Run `make setup` to install required dependencies (Aptos CLI, Import Git submodules). +2. [IntelliJ](https://www.jetbrains.com/idea/download/?section=mac) is recommended for developing Move contracts. Install the [Move Intellij IDE Plugin](https://pontem.network/move-intellij-ide-plugin). + +--- + +## Testing + +### Run Aptos and EVM Local Network + +```sh +./docker-start-containers.sh +``` + +### Run Unit Tests for All Package Modules + +```sh +make test +``` + +### Running E2E Tests + +```sh +# Install Dependencies +yarn install +``` + +```sh +# Run e2e tests +yarn test:e2e +``` + +## Deployment + +1. Create a deployer keypair and fund it with APT +2. Deploy MessageTransmitter and TokenMessengerMinter package + +```sh +yarn deploy --privateKey= --rpc= --aptosExtensionsPackageId= --stablecoinPackageId= +``` + +3. Verify source bytecode + +```sh +# Message Transmitter +yarn verify-pkg --packageName=message_transmitter --rpc= --packageId= --namedDeps aptos_extensions=,deployer= + +# TokenMessengerMinter +yarn verify-pkg --packageName=token_messenger_minter --rpc= --packageId= --namedDeps aptos_extensions=,deployer=,message_transmitter=,stablecoin= +``` + +## Upgrading + +1. Build payload for publishing + +```sh +# Message Transmitter +aptos move build-publish-payload --package-dir packages/message_transmitter --named-addresses deployer=,aptos_extensions=,message_transmitter= --json-output-file upgrade.json + +# Token Messenger Minter +aptos move build-publish-payload --package-dir packages/token_messenger_minter --named-addresses deployer=,aptos_extensions=,message_transmitter=,stablecoin=,token_messenger_minter= --json-output-file upgrade.json +``` + +2. Execute Tx for upgrading + +```sh +yarn upgrade-pkg --privateKey= --rpc= --payloadFilePath=upgrade.json --aptosExtensionsPackageId= --packageId= +``` diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..816a7f1 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,4 @@ +# Security Policy + +## Reporting a Vulnerability +Please do not file public issues on Github for security vulnerabilities. All security vulnerabilities should be reported to Circle privately, through Circle's [Vulnerability Disclosure Program](https://hackerone.com/circle). Please read through the program policy before submitting a report. \ No newline at end of file diff --git a/aptos-core b/aptos-core new file mode 160000 index 0000000..893ee58 --- /dev/null +++ b/aptos-core @@ -0,0 +1 @@ +Subproject commit 893ee58fcfde06a5019546b84ddec11d5737b0b0 diff --git a/cspell.json b/cspell.json new file mode 100644 index 0000000..e084de5 --- /dev/null +++ b/cspell.json @@ -0,0 +1,32 @@ +{ + "words": [ + "struct", + "structs", + "aptos", + "CCTP", + "keccak", + "secp", + "mvcov", + "upsert", + "foundryup", + "keypair", + "pausable", + "DOCROOT", + "localnet", + "pkill", + "dispatchable", + "fullnode", + "circlefin" + ], + "useGitignore": true, + "patterns": [ + { + "name": "error_codes", + "pattern": "\\bE([A-Z_]+)\\b" + } + ], + "ignoreRegExpList": [ + "error_codes" + ], + "ignorePaths": ["aptos-core", "stablecoin-aptos", "evm-cctp-contracts"] +} diff --git a/docker-delete-containers.sh b/docker-delete-containers.sh new file mode 100755 index 0000000..d769bf6 --- /dev/null +++ b/docker-delete-containers.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# Copyright (c) 2024, Circle Internet Group, Inc. +# All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +docker stop local-testnet-postgres && docker rm -f -v local-testnet-postgres || true +docker stop local-testnet-indexer-api && docker rm -f -v local-testnet-indexer-api || true +docker stop anvil-eth && docker rm -f -v anvil-eth || true +docker volume rm local-testnet-postgres-data diff --git a/docker-export-logs.sh b/docker-export-logs.sh new file mode 100755 index 0000000..12c877d --- /dev/null +++ b/docker-export-logs.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2024, Circle Internet Group, Inc. +# All rights reserved. +# +# Circle Internet Group, Inc. CONFIDENTIAL +# +# This file includes unpublished proprietary source code of Circle Internet +# Group, Inc. The copyright notice above does not evidence any actual or +# intended publication of such source code. Disclosure of this source code +# or any related proprietary information is strictly prohibited without +# the express written permission of Circle Internet Group, Inc. +# + +OUTPUT_DIR="container-logs" +mkdir -p ${OUTPUT_DIR} +CONTAINERS=$(docker ps --format '{{.Names}}') +echo "found containers: ${CONTAINERS}" +for CONTAINER in ${CONTAINERS}; do + docker logs ${CONTAINER} >& ${OUTPUT_DIR}/${CONTAINER}.log +done + +echo "Successfully exported logs in ${OUTPUT_DIR} directory." diff --git a/docker-start-containers.sh b/docker-start-containers.sh new file mode 100755 index 0000000..70f34af --- /dev/null +++ b/docker-start-containers.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +# Copyright (c) 2024, Circle Internet Group, Inc. +# All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +DOCROOT="$( cd "$( dirname "$0" )" && pwd )" + +make setup + +source "${DOCROOT}/scripts/aptos/aptos-local-network.sh" +start_aptos_localnet +source setup-evm-contracts.sh diff --git a/e2e/test/e2e.test.ts b/e2e/test/e2e.test.ts new file mode 100644 index 0000000..736a3dd --- /dev/null +++ b/e2e/test/e2e.test.ts @@ -0,0 +1,502 @@ +/* + * Copyright (c) 2024, Circle Internet Group, Inc. + * All rights reserved. + * + * Circle Internet Group, Inc. CONFIDENTIAL + * + * This file includes unpublished proprietary source code of Circle Internet + * Group, Inc. The copyright notice above does not evidence any actual or + * intended publication of such source code. Disclosure of this source code + * or any related proprietary information is strictly prohibited without + * the express written permission of Circle Internet Group, Inc. + */ + +import { MessageTransmitterClient } from "../../typescript/aptos/client/messageTransmitterClient"; +import { + Account, + AccountAddress, + Aptos, + Ed25519Account, + MoveVector, + U32, + U64, + U8, + UserTransactionResponse, +} from "@aptos-labs/ts-sdk"; +import { AptosExtensionsClient } from "../../typescript/aptos/client/aptosExtensionsClient"; +import { + attestToMessage, + EvmContractDefinition, + generateEvmBurn, + receiveEvm, +} from "../../typescript/evm/client/evmContractClient"; +import { Web3 } from "web3"; +import fs from "fs"; +import { afterAll, beforeAll, describe, expect, jest, test } from "@jest/globals"; +import dotenv from "dotenv"; +import { TokenMessengerMinterClient } from "../../typescript/aptos/client/tokenMessengerMinterClient"; +import { MoveModule } from "../../typescript/aptos/utils/moveModule"; +import { + generateFundedAccount, + generateFundedAccountFromPrivateKey, + getAptosClient, + getEventByType, + normalizeAddress, +} from "../../typescript/aptos/utils/helper"; +import { StablecoinClient } from "../../typescript/aptos/client/stablecoinClient"; +import waitForExpect from "wait-for-expect"; + +jest.setTimeout(200_000); +dotenv.config(); + +describe("End to End Tests", () => { + const cctpPackagesFilePath = "packages"; + const stablecoinFilePath = "stablecoin-aptos/packages"; + + let deployer: Ed25519Account; + let messageTransmitterClient: MessageTransmitterClient; + let tokenMessengerMinterClient: TokenMessengerMinterClient; + let aptosExtensionsClient: AptosExtensionsClient; + let stablecoinClient: StablecoinClient; + let web3: Web3; + let contractDefinition: EvmContractDefinition; + let aptos: Aptos; + let secondaryMinterController: Ed25519Account; + let secondaryMinter: Ed25519Account; + + const evmUserAddress = "0xfabb0ac9d68b0b445fb7357272ff202c5651694a"; + const usdcContractAddress = `${process.env.EVM_USDC_ADDRESS}`; + + const setupStablecoinAndAptosExtensions = async (): Promise<{ + aptosExtensionsPackageId: string; + stablecoinPackageId: string; + }> => { + const aptosExtensionsPackageId = await aptosExtensionsClient.publishPackage(stablecoinFilePath); + const stablecoinPackageId = await stablecoinClient.publishPackage(stablecoinFilePath, aptosExtensionsPackageId); + await stablecoinClient.initializeState( + "USDC", + "USDC", + new U8(6), + "https://www.circle.com/hubfs/Brand/USDC/USDC_icon_32x32.png", + "https://circle.com/usdc" + ); + return { stablecoinPackageId, aptosExtensionsPackageId }; + }; + + const setupAptos = async () => { + aptos = getAptosClient(); + deployer = await generateFundedAccount(aptos); + messageTransmitterClient = new MessageTransmitterClient(aptos, deployer); + tokenMessengerMinterClient = new TokenMessengerMinterClient(aptos, deployer); + aptosExtensionsClient = new AptosExtensionsClient(aptos, deployer); + stablecoinClient = new StablecoinClient(aptos, deployer); + const { aptosExtensionsPackageId, stablecoinPackageId } = await setupStablecoinAndAptosExtensions(); + + const messageTransmitterPackageId = await messageTransmitterClient.publishPackage( + "packages", + aptosExtensionsPackageId, + "sparse" + ); + console.log(`MessageTransmitter package ID: ${messageTransmitterPackageId}\n`); + + const tokenMessengerMinterPackageId = await tokenMessengerMinterClient.publishPackage( + "packages", + aptosExtensionsPackageId, + messageTransmitterPackageId, + stablecoinPackageId, + "sparse" + ); + console.log(`TokenMessengerMinter package ID: ${tokenMessengerMinterPackageId}\n`); + + // Initialize Message Transmitter + const localDomain = new U32(9); + const attester = AccountAddress.from(`${process.env.EVM_ATTESTER_ADDRESS}`); + const maxMessageBodySize = new U64(8192); + const version = new U32(0); + await messageTransmitterClient.initializeState(localDomain, attester, maxMessageBodySize, version); + console.log(`Message Transmitter initialized.\n`); + + // Initialize Token Messenger Minter + const messageBodyVersion = new U32(0); + await tokenMessengerMinterClient.initializeState(messageBodyVersion, deployer.accountAddress); + console.log(`Token Messenger Minter initialized.\n`); + + messageTransmitterClient.packageId = messageTransmitterPackageId; + tokenMessengerMinterClient.packageId = tokenMessengerMinterPackageId; + + // Configure minter + await stablecoinClient.configureController(deployer.accountAddress, tokenMessengerMinterClient.signerAddress()); + await stablecoinClient.configureMinter(deployer, new U64(100_000_000)); + + secondaryMinterController = await generateFundedAccount(aptos); + secondaryMinter = await generateFundedAccount(aptos); + await stablecoinClient.configureController( + secondaryMinterController.accountAddress, + secondaryMinter.accountAddress + ); + await stablecoinClient.configureMinter(secondaryMinterController, new U64(100_000_000)); + + // Add ETH remote token messenger in Aptos + await tokenMessengerMinterClient.addRemoteTokenMessenger( + new U32(0), + AccountAddress.from(`${process.env.EVM_TOKEN_MESSENGER_ADDRESS}`) + ); + + // Add remote token messenger in evm + const tokenMessengerContractOwner = web3.eth.accounts.privateKeyToAccount( + `${process.env.EVM_TOKEN_MESSENGER_DEPLOYER_KEY}` + ); + await contractDefinition.tokenMessengerContract.methods + .addRemoteTokenMessenger(9, normalizeAddress(tokenMessengerMinterClient.getObjectAddress().toString())) + .send({ from: tokenMessengerContractOwner.address }); + + // Link Token Pair in EVM + const tokenControllerAccount = web3.eth.accounts.privateKeyToAccount( + `${process.env.EVM_TOKEN_CONTROLLER_DEPLOYER_KEY}` + ); + + await contractDefinition.tokenMinterContract.methods + .linkTokenPair(usdcContractAddress, 9, normalizeAddress(stablecoinClient.getObjectAddress().toString())) + .send({ from: tokenControllerAccount.address }); + + // Link Token Pair in APtos + await tokenMessengerMinterClient.linkTokenPair( + stablecoinClient.getObjectAddress(), + new U32(0), + AccountAddress.from(usdcContractAddress) + ); + + // Set max burn amount per message + await tokenMessengerMinterClient.setMaxBurnAmountPerMessage(stablecoinClient.getObjectAddress(), new U64(1)); + }; + + const setupEvm = async () => { + web3 = new Web3(new Web3.providers.HttpProvider(`${process.env.EVM_RPC_URL}`)); + const evmUSDCAddress = `${process.env.EVM_USDC_ADDRESS}`; + const evmMessageTransmitterAddress = `${process.env.EVM_MESSAGE_TRANSMITTER_ADDRESS}`; + const evmTokenMessengerAddress = `${process.env.EVM_TOKEN_MESSENGER_ADDRESS}`; + const evmTokenMinterContractAddress = `${process.env.EVM_TOKEN_MINTER_ADDRESS}`; + + const messageTransmitterInterface = JSON.parse( + fs.readFileSync("evm-cctp-contracts/cctp-interfaces/MessageTransmitter.sol/MessageTransmitter.json").toString() + ); + const usdcInterface = JSON.parse( + fs.readFileSync("evm-cctp-contracts/usdc-interfaces/FiatTokenV2_1.sol/FiatTokenV2_1.json").toString() + ); + const tokenMessengerInterface = JSON.parse( + fs.readFileSync("evm-cctp-contracts/cctp-interfaces/TokenMessenger.sol/TokenMessenger.json").toString() + ); + const tokenMinterContractInterface = JSON.parse( + fs.readFileSync("evm-cctp-contracts/cctp-interfaces/TokenMinter.sol/TokenMinter.json").toString() + ); + + const messageTransmitterContract = new web3.eth.Contract( + messageTransmitterInterface.abi, + evmMessageTransmitterAddress + ); + const tokenMessengerContract = new web3.eth.Contract(tokenMessengerInterface.abi, evmTokenMessengerAddress); + const usdcContract = new web3.eth.Contract(usdcInterface.abi, evmUSDCAddress); + const tokenMinterContract = new web3.eth.Contract(tokenMinterContractInterface.abi, evmTokenMinterContractAddress); + + contractDefinition = { + web3, + messageTransmitterContract, + messageTransmitterContractAddress: evmMessageTransmitterAddress, + tokenMessengerContract, + tokenMessengerContractAddress: evmTokenMessengerAddress, + tokenMinterContract, + tokenMinterContractAddress: evmTokenMinterContractAddress, + usdcContract, + usdcContractAddress: evmUSDCAddress, + } as EvmContractDefinition; + }; + + beforeAll(async () => { + await setupEvm(); + await setupAptos(); + }); + + afterAll(async () => { + // Remove remote token messenger in evm + try { + const tokenMessengerContractInterface = JSON.parse( + fs.readFileSync("evm-cctp-contracts/cctp-interfaces/TokenMessenger.sol/TokenMessenger.json").toString() + ); + const evmTokenMessengerContractAddress = `${process.env.EVM_TOKEN_MESSENGER_ADDRESS}`; + const tokenMessengerContract = new web3.eth.Contract( + tokenMessengerContractInterface.abi, + evmTokenMessengerContractAddress + ); + const tokenMessengerContractOwner = web3.eth.accounts.privateKeyToAccount( + `${process.env.EVM_TOKEN_MESSENGER_DEPLOYER_KEY}` + ); + tokenMessengerContract.methods.removeRemoteTokenMessenger(9).send({ from: tokenMessengerContractOwner.address }); + + // Unlink Token Pair + const tokenMinterContractInterface = JSON.parse( + fs.readFileSync("evm-cctp-contracts/cctp-interfaces/TokenMinter.sol/TokenMinter.json").toString() + ); + const evmTokenMinterContractAddress = `${process.env.EVM_TOKEN_MINTER_ADDRESS}`; + const tokenMinterContract = new web3.eth.Contract( + tokenMinterContractInterface.abi, + evmTokenMinterContractAddress + ); + const tokenControllerAccount = web3.eth.accounts.privateKeyToAccount( + `${process.env.EVM_TOKEN_CONTROLLER_DEPLOYER_KEY}` + ); + + const usdcContractAddress = `${process.env.EVM_USDC_ADDRESS}`; + tokenMinterContract.methods + .unlinkTokenPair(usdcContractAddress, 9, normalizeAddress(stablecoinClient.getObjectAddress().toString())) + .send({ from: tokenControllerAccount.address }); + } catch (e) { + console.error(e); + } + }); + + describe("Admin Functions", () => { + describe("MessageTransmitter Client", () => { + test("Enable & Disable Attester", async () => { + // Enable attester + const newAttester = Account.generate().accountAddress; + await messageTransmitterClient.enableAttester(newAttester); + expect(await messageTransmitterClient.isEnabledAttester(newAttester)).toBeTruthy(); + + // Verify random address is not enabled + expect( + await messageTransmitterClient.isEnabledAttester(messageTransmitterClient.getObjectAddress()) + ).toBeFalsy(); + + // Disable attester + await messageTransmitterClient.disableAttester(newAttester); + expect(await messageTransmitterClient.isEnabledAttester(newAttester)).toBeFalsy(); + }); + test("Get & Update Attester Manager", async () => { + // Get original attester manager + const originalAttesterManager = await messageTransmitterClient.getAttesterManager(); + expect(originalAttesterManager.toString()).toBe(deployer.accountAddress.toString()); + + // Update attester manager + const newAttesterManager = Account.generate().accountAddress; + await messageTransmitterClient.updateAttesterManager(newAttesterManager); + expect((await messageTransmitterClient.getAttesterManager()).toString()).toBe(newAttesterManager.toString()); + + // Revert to original attester manager + await messageTransmitterClient.updateAttesterManager(originalAttesterManager); + expect((await messageTransmitterClient.getAttesterManager()).toString()).toBe( + originalAttesterManager.toString() + ); + }); + test("Fetch States", async () => { + // Get & Verify local_domain + expect(await messageTransmitterClient.getLocalDomain()).toEqual(9); + + // Get & Verify version + expect(await messageTransmitterClient.getVersion()).toEqual(0); + + // Get & Verify max_message_body_size + expect(await messageTransmitterClient.getMaxMessageBodySize()).toEqual("8192"); + + // Get & Verify signature_threshold + expect(await messageTransmitterClient.getSignatureThreshold()).toEqual("1"); + + // Get & Verify enabled_attesters + expect(await messageTransmitterClient.getNumEnabledAttesters()).toEqual("1"); + }); + }); + describe("TokenMessengerMinter Client", () => { + test("Add & Remove Remote Token Messenger", async () => { + // Add remote token messenger + const remoteDomain = new U32(1); + const remoteTokenMessenger = Account.generate().accountAddress; + await tokenMessengerMinterClient.addRemoteTokenMessenger(remoteDomain, remoteTokenMessenger); + + // Verify remote token messenger was added + const remoteTokenMessengerAddress = await tokenMessengerMinterClient.getRemoteTokenMessenger(remoteDomain); + expect(remoteTokenMessengerAddress).toStrictEqual(remoteTokenMessenger); + + // Remove remote token messenger + await tokenMessengerMinterClient.removeRemoteTokenMessenger(remoteDomain); + await expect(() => tokenMessengerMinterClient.getRemoteTokenMessenger(remoteDomain)).rejects.toThrow(); + }); + test("Fetch states", async () => { + // Get & Verify message_body_version + expect(await tokenMessengerMinterClient.getMessageBodyVersion()).toEqual(0); + + // Get & Verify num_remote_token_messengers + expect(await tokenMessengerMinterClient.getNumRemoteTokenMessenger()).toEqual("1"); + + // Get & Verify get_linked_token + expect( + await tokenMessengerMinterClient.getLinkedToken(new U32(0), AccountAddress.from(usdcContractAddress)) + ).toEqual(stablecoinClient.getObjectAddress()); + }); + }); + describe("AptosExtensions Client", () => { + test("Change & Fetch pauser, isPaused", async () => { + const newPauser = Account.generate().accountAddress; + await aptosExtensionsClient.updatePauser(deployer, messageTransmitterClient.getObjectAddress(), newPauser); + + const pauser = await aptosExtensionsClient.pauser(messageTransmitterClient.getObjectAddress()); + expect(pauser.equals(newPauser)).toBe(true); + + const isPaused = await aptosExtensionsClient.isPaused(messageTransmitterClient.getObjectAddress()); + expect(isPaused).toBeFalsy(); + }); + + test("Transfer, Accept & Fetch ownership", async () => { + // Initiate owner transfer + const newOwner = await generateFundedAccount(aptos); + await aptosExtensionsClient.transferOwnership( + deployer, + messageTransmitterClient.getObjectAddress(), + newOwner.accountAddress + ); + + // Fetch Pending owner + const pendingOwner = (await aptosExtensionsClient.pendingOwner( + messageTransmitterClient.getObjectAddress() + )) as AccountAddress; + expect(pendingOwner.equals(newOwner.accountAddress)).toBe(true); + + // Accept Ownership + await aptosExtensionsClient.acceptOwnership(newOwner, messageTransmitterClient.getObjectAddress()); + + // Fetch current owner + const owner = await aptosExtensionsClient.owner(messageTransmitterClient.getObjectAddress()); + expect(owner.equals(newOwner.accountAddress)).toBe(true); + }); + + test("Change, Accept & Fetch Admin", async () => { + // Initiate admin change + const newAdmin = await generateFundedAccount(aptos); + await aptosExtensionsClient.changeAdmin(deployer, messageTransmitterClient.packageId, newAdmin.accountAddress); + + // Fetch Pending admin + const pendingAdmin = (await aptosExtensionsClient.pendingAdmin( + messageTransmitterClient.packageId + )) as AccountAddress; + expect(pendingAdmin.equals(newAdmin.accountAddress)).toBe(true); + + // Accept Ownership + await aptosExtensionsClient.acceptAdmin(newAdmin, messageTransmitterClient.packageId); + + // Fetch current owner + const admin = await aptosExtensionsClient.admin(messageTransmitterClient.packageId); + expect(admin.equals(newAdmin.accountAddress)).toBe(true); + }); + }); + }); + + describe("E2E Tests", () => { + test("EVM <-> APTOS", async () => { + const aptosUser = await generateFundedAccount(aptos); + + // Send 1 USDC from EVM to APTOS + const depositForBurnTx = await generateEvmBurn( + contractDefinition, + evmUserAddress, + aptosUser.accountAddress.toString(), + 9 + ); + const receiveTx = await tokenMessengerMinterClient.handleReceiveMessage( + cctpPackagesFilePath, + MoveVector.U8(depositForBurnTx.messageBytes), + MoveVector.U8(depositForBurnTx.attestation) + ); + + waitForExpect(async () => { + expect( + getEventByType( + receiveTx as UserTransactionResponse, + `${messageTransmitterClient.packageId}::${MoveModule.MessageTransmitter}::MessageReceived` + ) + ).not.toBeNull(); + expect( + getEventByType( + receiveTx as UserTransactionResponse, + `${tokenMessengerMinterClient.packageId}::${MoveModule.TokenMessenger}::MintAndWithdraw` + ) + ).not.toBeNull(); + }); + + console.log(`Received 1 USDC from EVM to APTOS: ${receiveTx.hash}`); + + // Send 1 USDC from APTOS to EVM + const sendTx = await tokenMessengerMinterClient.depositForBurn( + cctpPackagesFilePath, + aptosUser, + new U64(1), + new U32(0), + AccountAddress.from(evmUserAddress), + stablecoinClient.getObjectAddress() + ); + + console.log(`Sent 1 USDC from APTOS to EVM: ${sendTx.hash}`); + + waitForExpect(async () => { + expect( + getEventByType( + receiveTx as UserTransactionResponse, + `${messageTransmitterClient.packageId}::${MoveModule.MessageTransmitter}::MessageSent` + ) + ).not.toBeNull(); + }); + + const messageSentEvent = getEventByType( + sendTx as UserTransactionResponse, + `${messageTransmitterClient.packageId}::${MoveModule.MessageTransmitter}::MessageSent` + ); + + const messageBytes = messageSentEvent.data.message; + const attestation = attestToMessage(web3, messageBytes); + + await receiveEvm(evmUserAddress, contractDefinition, messageBytes, attestation); + }); + + test("Deposit For Burn with caller", async () => { + const aptosUser = await generateFundedAccount(aptos); + + // Fund with USDC + const mintTx = await tokenMessengerMinterClient.mint( + cctpPackagesFilePath, + secondaryMinter, + new U64(1), + aptosUser.accountAddress + ); + expect(mintTx.success).toBe(true); + + // Send 1 USDC from APTOS to EVM + const sendTx = await tokenMessengerMinterClient.depositForBurn( + cctpPackagesFilePath, + aptosUser, + new U64(1), + new U32(0), + AccountAddress.from(evmUserAddress), + stablecoinClient.getObjectAddress(), + AccountAddress.from(evmUserAddress) + ); + + console.log(`Sent 1 USDC from APTOS to EVM: ${sendTx.hash}`); + + waitForExpect(async () => { + expect( + getEventByType( + sendTx as UserTransactionResponse, + `${messageTransmitterClient.packageId}::${MoveModule.MessageTransmitter}::MessageSent` + ) + ).not.toBeNull(); + }); + + const messageSentEvent = getEventByType( + sendTx as UserTransactionResponse, + `${messageTransmitterClient.packageId}::${MoveModule.MessageTransmitter}::MessageSent` + ); + + const messageBytes = messageSentEvent.data.message; + const attestation = attestToMessage(web3, messageBytes); + + await receiveEvm(evmUserAddress, contractDefinition, messageBytes, attestation); + }); + }); +}); diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..91b4f54 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,42 @@ +import typescriptEslint from "@typescript-eslint/eslint-plugin"; +import globals from "globals"; +import tsParser from "@typescript-eslint/parser"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import js from "@eslint/js"; +import { FlatCompat } from "@eslint/eslintrc"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all, +}); + +export default [ + { + ignores: ["build/*", "node_modules/*", "**/yarn.lock", ".yarn/*"], + }, + ...compat.extends("eslint:recommended", "plugin:@typescript-eslint/recommended"), + { + plugins: { + "@typescript-eslint": typescriptEslint, + }, + + languageOptions: { + globals: { + ...globals.node, + }, + + parser: tsParser, + ecmaVersion: "latest", + sourceType: "module", + }, + + rules: { + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-non-null-assertion": "off", + }, + }, +]; diff --git a/evm-cctp-contracts b/evm-cctp-contracts new file mode 160000 index 0000000..4b2eb0c --- /dev/null +++ b/evm-cctp-contracts @@ -0,0 +1 @@ +Subproject commit 4b2eb0cb6eabd2a8f19e5d8e27d496e03b0a3db1 diff --git a/jest.config.ts b/jest.config.ts new file mode 100644 index 0000000..a94e41b --- /dev/null +++ b/jest.config.ts @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024, Circle Internet Group, Inc. + * All rights reserved. + * + * Circle Internet Group, Inc. CONFIDENTIAL + * + * This file includes unpublished proprietary source code of Circle Internet + * Group, Inc. The copyright notice above does not evidence any actual or + * intended publication of such source code. Disclosure of this source code + * or any related proprietary information is strictly prohibited without + * the express written permission of Circle Internet Group, Inc. + */ + +/** @type {import('ts-jest/dist/types').JestConfigWithTsJest} */ + +module.exports = { + preset: "ts-jest", + testEnvironment: "node", + testMatch: ["/e2e/test/*.test.[jt]s?(x)"], + testPathIgnorePatterns: ["/node_modules/"], + maxWorkers: 1, + verbose: true, + transform: { + "^.+\\.ts?(x)$": ["ts-jest", { tsconfig: "tsconfig.json" }], + }, +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..db70117 --- /dev/null +++ b/package.json @@ -0,0 +1,52 @@ +{ + "name": "aptos-cctp", + "version": "1.0.0", + "description": "Circle's Aptos CCTP Smart Contracts", + "private": true, + "repository": { + "type": "git", + "url": "git+ssh://github.com/circlefin/aptos-cctp.git" + }, + "license": "Apache-2.0", + "scripts": { + "build": "yarn", + "lint": "eslint .", + "lint:fix": "eslint --fix .", + "test:e2e": "yarn build && yarn jest", + "calculate-deployment-addresses": "yarn ts-node typescript/aptos/deploy/index.ts calculate-deployment-addresses", + "deploy": "yarn ts-node typescript/aptos/deploy/index.ts deploy", + "upgrade-pkg": "yarn ts-node typescript/aptos/deploy/index.ts upgrade", + "verify-pkg": "yarn ts-node typescript/aptos/deploy/index.ts verify-pkg" + }, + "dependencies": { + "@aptos-labs/ts-sdk": "^1.28.0", + "@jest/globals": "^29.3.1", + "commander": "^12.1.0", + "dotenv": "^16.4.5", + "@ethereumjs/util": "^9.1.0", + "ts-jest": "^29.2.5", + "ts-node": "^10.9.2", + "wait-for-expect": "^3.0.2", + "web3": "^4.13.0" + }, + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^5.13.0", + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^8.0.1", + "eslint-config-airbnb-typescript": "^17.0.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-prefer-arrow": "^1.2.3", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-simple-import-sort": "^7.0.0", + "jest": "^28.1.3", + "jest-junit": "^14.0.0", + "prettier": "^2.7.1", + "prettier-eslint": "^16.3.0", + "typescript": "^4.7.4" + }, + "engines": { + "node": "20.14.0", + "yarn": "1.22.22" + } +} \ No newline at end of file diff --git a/packages/message_transmitter/Makefile b/packages/message_transmitter/Makefile new file mode 100644 index 0000000..23a544b --- /dev/null +++ b/packages/message_transmitter/Makefile @@ -0,0 +1,24 @@ +.PHONY: print-coverage test coverage compile publish publish-extension + +print-coverage: + @if [ ! -f .coverage_map.mvcov ]; then $(MAKE) coverage; fi; + aptos move coverage source --module $(word 2,$(MAKECMDGOALS)) --dev + +test: + aptos move test --dev + +coverage: + aptos move test --coverage --dev + +compile: + aptos move compile --named-addresses message_transmitter=message_transmitter,aptos_extensions=aptos_extensions + +publish: + aptos move publish --profile $(profile) --named-addresses message_transmitter=message_transmitter,aptos_extensions=aptos_extensions --assume-yes + +publish-extensions: + aptos move publish --profile $(profile) --named-addresses message_transmitter=message_transmitter,aptos_extensions=aptos_extensions --assume-yes --package-dir ../../stablecoin-aptos/packages/aptos_extensions + +# Prevents make from thinking the arguments are actual targets +%: + @: diff --git a/packages/message_transmitter/Move.toml b/packages/message_transmitter/Move.toml new file mode 100644 index 0000000..adda3b5 --- /dev/null +++ b/packages/message_transmitter/Move.toml @@ -0,0 +1,24 @@ +[package] +name = "MessageTransmitter" +version = "1.0.0" +upgrade_policy = "compatible" + +[addresses] +message_transmitter = "_" +aptos_extensions = "_" +deployer = "_" + +[dev-addresses] +message_transmitter = "0xebe6e5b8db973b4c8f5c3fbe7f5802e7a823b7ae4b70e5b6b1ce978418e5dad5" +aptos_extensions = "5ba1674a3ffa843ed88aa4a0a051b9a52f76459a8853e5cd62b22bcc488d2765" +deployer = "c06c5aa31d28c27be8345770a83b48314b829039ec5a33b79265216c13c66071" + +[dependencies.AptosFramework] +git = "https://github.com/aptos-labs/aptos-core.git" +rev = "mainnet" +subdir = "aptos-move/framework/aptos-framework" + +[dependencies.AptosExtensions] +local = "../../stablecoin-aptos/packages/aptos_extensions" + +[dev-dependencies] diff --git a/packages/message_transmitter/Prover.toml b/packages/message_transmitter/Prover.toml new file mode 100644 index 0000000..3f91e3a --- /dev/null +++ b/packages/message_transmitter/Prover.toml @@ -0,0 +1,21 @@ +# Verbosity level +# Possible values: "ERROR", "WARN", "INFO", "DEBUG". Each level subsumes the output of the previous one. +verbosity_level = "INFO" + +[prover] +# Set auto-tracing level, which enhances the diagnosis the Move Prover produces on verification errors. +# Possible values: "Off", "VerifiedFunction", "AllFunctions" +auto_trace_level = "AllFunctions" + +# Minimal severity level for diagnosis to be reported. +# Possible values: "Error", "Warning", "Note" +report_severity = "Warning" + +[backend] +# Timeout in seconds for the solver backend. Note that this is a soft timeout and may not always +# be respected. +vc_timeout = 40 + +# Random seed for the solver backend. Different seeds can result in different verification run times, +# as the solver uses heuristics. +random_seed = 1 diff --git a/packages/message_transmitter/sources/attester.move b/packages/message_transmitter/sources/attester.move new file mode 100644 index 0000000..e670d9a --- /dev/null +++ b/packages/message_transmitter/sources/attester.move @@ -0,0 +1,671 @@ +/// Copyright (c) 2024, Circle Internet Group, Inc. +/// All rights reserved. +/// +/// SPDX-License-Identifier: Apache-2.0 +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. + +module message_transmitter::attester { + // Built-in Modules + use std::error; + use std::signer; + use std::event; + use std::option; + use std::vector; + use aptos_std::aptos_hash::keccak256; + use aptos_std::comparator; + use aptos_std::from_bcs; + use aptos_std::secp256k1; + + // Package Modules + use message_transmitter::state; + + // Friend Modules + friend message_transmitter::message_transmitter; + + // Constants + const SIGNATURE_LENGTH: u64 = 65; + const HALF_CURVE_ORDER: vector = x"7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0"; + + // Errors + const ENOT_OWNER: u64 = 1; + const EATTESTER_ALREADY_ENABLED: u64 = 2; + const ETOO_FEW_ENABLED_ATTESTERS: u64 = 3; + const ENUM_ATTESTERS_SHOULD_BE_GREATER_THAN_SIGNATURE_THRESHOLD: u64 = 4; + const ENOT_ATTESTER_MANAGER: u64 = 5; + const EINVALID_ATTESTER_MANAGER: u64 = 6; + const EINVALID_SIGNATURE_THRESHOLD: u64 = 7; + const ESIGNATURE_THRESHOLD_ALREADY_SET: u64 = 8; + const ESIGNATURE_THRESHOLD_TOO_HIGH: u64 = 9; + const EINVALID_ATTESTATION_LENGTH: u64 = 10; + const EINVALID_SIGNATURE: u64 = 11; + const ESIGNATURE_IS_NOT_ATTESTER: u64 = 12; + const EINVALID_MESSAGE_OR_SIGNATURE: u64 = 13; + const EINVALID_SIGNATURE_CURVE_ORDER: u64 = 14; + const EINVALID_ATTESTER_ADDRESS: u64 = 15; + const EATTESTER_NOT_IN_INCREASING_ORDER: u64 = 16; + + // ----------------------------- + // ---------- Events ----------- + // ----------------------------- + + #[event] + struct AttesterEnabled has drop, store { + attester: address + } + + #[event] + struct AttesterDisabled has drop, store { + attester: address + } + + #[event] + struct SignatureThresholdUpdated has drop, store { + old_signature_threshold: u64, + new_signature_threshold: u64 + } + + #[event] + struct AttesterManagerUpdated has drop, store { + previous_attester_manager: address, + new_attester_manager: address + } + + // ----------------------------- + // --- Public View Functions --- + // ----------------------------- + + #[view] + /// Returns true if the passed address is one of the enabled attester. + public fun is_enabled_attester(attester: address): bool { + let enabled_attesters = state::get_enabled_attesters(); + vector::contains(&enabled_attesters, &attester) + } + + #[view] + /// Returns the attester manager address. + public fun attester_manager(): address { + state::get_attester_manager() + } + + #[view] + /// Returns attester address at the given index. + public fun get_enabled_attester(index: u64): address { + let enabled_attesters = state::get_enabled_attesters(); + *vector::borrow(&enabled_attesters, index) + } + + #[view] + /// Returns the number of enabled attesters. + public fun get_num_enabled_attesters(): u64 { + state::get_num_enabled_attesters() + } + + #[view] + /// Returns signature threshold. + public fun get_signature_threshold(): u64 { + state::get_signature_threshold() + } + + // ----------------------------- + // ----- Public Functions ------ + // ----------------------------- + + /// Enables an attester. Emits `AttesterEnabled` event + /// Aborts if: + /// - the caller is not the attester manager + /// - attester is zero address + /// - the attester is already enabled + entry fun enable_attester(caller: &signer, new_attester: address) { + assert_is_attester_manager(caller); + assert!(new_attester != @0x0, error::invalid_argument(EINVALID_ATTESTER_ADDRESS)); + assert!(!is_enabled_attester(new_attester), error::already_exists(EATTESTER_ALREADY_ENABLED)); + state::add_attester(new_attester); + event::emit(AttesterEnabled { attester: new_attester }); + } + + /// Disables an attester. Emits `AttesterDisabled` event + /// Aborts if: + /// - the caller is not the attester manager + /// - there is only 1 enabled attester + /// - the number of remaining enabled attesters will fall below signature threshold + /// - the attester is not in the list of enabled attesters + entry fun disable_attester(caller: &signer, attester: address) { + assert_is_attester_manager(caller); + let enabled_attesters = state::get_enabled_attesters(); + assert!(vector::length
(&enabled_attesters) > 1, error::invalid_state(ETOO_FEW_ENABLED_ATTESTERS)); + assert!( + vector::length
(&enabled_attesters) > state::get_signature_threshold(), + error::invalid_state(ENUM_ATTESTERS_SHOULD_BE_GREATER_THAN_SIGNATURE_THRESHOLD) + ); + state::remove_attester(attester); + event::emit(AttesterDisabled { attester }); + } + + /// Updates attester manager. Emits `AttesterManagerUpdated` event + /// Aborts if: + /// - the caller is not the owner + /// - the new attester manager is the same the old one + entry fun update_attester_manager(caller: &signer, new_attester_manager: address) { + assert!(state::get_owner() == signer::address_of(caller), error::permission_denied(ENOT_OWNER)); + let previous_attester_manager = state::get_attester_manager(); + assert!(previous_attester_manager != new_attester_manager, error::already_exists(EINVALID_ATTESTER_MANAGER)); + state::set_attester_manager(new_attester_manager); + event::emit(AttesterManagerUpdated { previous_attester_manager, new_attester_manager }); + } + + /// Sets the signature threshold. Emits `SignatureThresholdUpdated` event + /// Aborts if: + /// - the caller is not the attester manager + /// - the signature threshold is not valid (e.g 0) + /// - the signature threshold is the same as the existing one + /// - the signature threshold exceeds the number of enabled attesters + entry fun set_signature_threshold(caller: &signer, new_signature_threshold: u64) { + assert_is_attester_manager(caller); + assert!(new_signature_threshold != 0, error::invalid_argument(EINVALID_SIGNATURE_THRESHOLD)); + let old_signature_threshold = state::get_signature_threshold(); + assert!( + new_signature_threshold != old_signature_threshold, + error::already_exists(ESIGNATURE_THRESHOLD_ALREADY_SET) + ); + assert!( + new_signature_threshold <= state::get_num_enabled_attesters(), + error::invalid_argument(ESIGNATURE_THRESHOLD_TOO_HIGH) + ); + state::set_signature_threshold(new_signature_threshold); + event::emit(SignatureThresholdUpdated { old_signature_threshold, new_signature_threshold }); + } + + /// Validates the attestation for the given message + /// Aborts if: + /// - length of attestation != ATTESTATION_SIGNATURE_LENGTH * signature_threshold + /// - there are duplicate signers + /// - signer is not one of the enabled attesters + /// - addresses recovered are not in increasing order + public fun verify_attestation_signature(message: &vector, attestation: &vector) { + // Validate Attestation Size + let signature_threshold = state::get_signature_threshold(); + assert!( + vector::length(attestation) == SIGNATURE_LENGTH * signature_threshold, + error::invalid_argument(EINVALID_ATTESTATION_LENGTH) + ); + + // Create message hash + let message_digest = keccak256(*message); + let current_attester_address = @0x0; + for (i in 0..signature_threshold) { + // Get the nth signature + let signature_with_recovery_id = vector::slice( + attestation, + i * SIGNATURE_LENGTH, + (i + 1) * SIGNATURE_LENGTH + ); + + // Enforce low s value signature check to prevent malleability + assert!(verify_low_s_value(&signature_with_recovery_id), error::invalid_argument( + EINVALID_SIGNATURE_CURVE_ORDER + )); + + // Recover address from signature + let recovered_attester_address = recover_attester_address(&signature_with_recovery_id, &message_digest); + + // Compare the recovered attester address with existing one to make sure they are in increasing order + let result = comparator::compare(&recovered_attester_address, ¤t_attester_address); + assert!(comparator::is_greater_than(&result), error::invalid_argument(EATTESTER_NOT_IN_INCREASING_ORDER)); + + // Validate the recovered attester is one of the enabled attesters + assert!( + is_enabled_attester(recovered_attester_address), + error::invalid_argument(ESIGNATURE_IS_NOT_ATTESTER) + ); + current_attester_address = recovered_attester_address; + } + } + + // ----------------------------- + // ----- Friend Functions ------ + // ----------------------------- + + public(friend) fun init_attester(caller: &signer, attester: address) { + enable_attester(caller, attester); + } + + // ----------------------------- + // ----- Private Functions ----- + // ----------------------------- + + fun assert_is_attester_manager(caller: &signer) { + let attester_manager = state::get_attester_manager(); + assert!(attester_manager == signer::address_of(caller), error::permission_denied(ENOT_ATTESTER_MANAGER)); + } + + fun recover_attester_address(signature: &vector, message_digest: &vector): address { + // Retrieve and validate signature id + let recovery_id = *vector::borrow(signature, SIGNATURE_LENGTH - 1) - 27; + assert!( + vector::contains(&vector[0, 1], &recovery_id), + error::invalid_argument(EINVALID_SIGNATURE) + ); + + // Recover public key + let ecdsa_signature = secp256k1::ecdsa_signature_from_bytes( + vector::slice(signature, 0, SIGNATURE_LENGTH - 1) + ); + let recovered_public_key = secp256k1::ecdsa_recover(*message_digest, recovery_id, &ecdsa_signature); + assert!(option::is_some(&recovered_public_key), error::invalid_argument(EINVALID_MESSAGE_OR_SIGNATURE)); + + // Convert public key to address + let recovered_attester_address = get_address_from_public_key( + &secp256k1::ecdsa_raw_public_key_to_bytes(option::borrow(&recovered_public_key)) + ); + recovered_attester_address + } + + /// Returns true if `s` value of signature is in the lower half of curve order + /// Using Secp256k1Ecdsa Half Curve order from OpenZeppelin ecdsa recover + /// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol#L137 + fun verify_low_s_value(signature: &vector): bool { + // Signature is made of r(32 bytes) + s(32 Bytes) + v(1 Byte) + let signature_s_value = vector::slice(signature, 32, SIGNATURE_LENGTH-1); + let result = comparator::compare(&signature_s_value, &HALF_CURVE_ORDER); + comparator::is_smaller_than(&result) || comparator::is_equal(&result) + } + + fun get_address_from_public_key(public_key: &vector): address { + // Hash the public key + let address_bytes = keccak256(*public_key); + + // EVM address is the made of last 20 bytes of the hash + let address_without_prefix = vector::slice( + &address_bytes, + vector::length(&address_bytes) - 20, + vector::length(&address_bytes) + ); + + // Add 0x0 prefix to make the address 32 bytes + let address_with_prefix = x"000000000000000000000000"; + vector::append(&mut address_with_prefix, address_without_prefix); + from_bcs::to_address(address_with_prefix) + } + + // ----------------------------- + // -------- Unit Tests --------- + // ----------------------------- + + #[test_only] + use aptos_framework::account::create_signer_for_test; + + #[test_only] + public fun init_for_test(caller: &signer, attester: address) { + state::init_test_state(caller); + init_attester(caller, attester); + } + + #[test(owner = @message_transmitter)] + fun test_init_attester(owner: &signer) { + state::init_test_state(owner); + let attester = @0xfab; + + init_attester(owner, attester); + assert!(state::get_num_enabled_attesters() == 1, 0); + assert!(*vector::borrow(&state::get_enabled_attesters(), 0) == attester, 0); + + let attester_enabled_event = AttesterEnabled { attester }; + assert!(event::was_event_emitted(&attester_enabled_event), 0); + } + + #[test(owner = @message_transmitter)] + #[expected_failure(abort_code = 0x50005, location = Self)] + fun test_init_attester_not_attester_manager(owner: &signer) { + state::init_test_state(owner); + let not_owner = create_signer_for_test(@0x10); + init_attester(¬_owner, @0xfab); + } + + // Admin Tests + + #[test(owner = @message_transmitter)] + fun test_is_attester_manager_valid(owner: &signer) { + state::init_test_state(owner); + assert_is_attester_manager(owner); + } + + #[test(owner = @message_transmitter, caller = @0xfab)] + #[expected_failure(abort_code = 0x50005, location = Self)] + fun test_is_attester_manager_invalid(owner: &signer, caller: &signer) { + state::init_test_state(owner); + assert_is_attester_manager(caller); + } + + // Enable Attester Tests + + #[test(owner = @message_transmitter)] + fun test_enable_attester_success(owner: &signer) { + init_for_test(owner, @0xfac); + + let new_attester = @0xfab; + enable_attester(owner, new_attester); + assert!(vector::contains(&state::get_enabled_attesters(), &new_attester), 0); + + let attester_enabled_event = AttesterEnabled { attester: new_attester }; + assert!(event::was_event_emitted(&attester_enabled_event), 0); + } + + #[test(owner = @message_transmitter, not_attester_manager = @0x99)] + #[expected_failure(abort_code = 0x50005, location = Self)] + fun test_enable_attester_not_attester_manager(owner: &signer, not_attester_manager: &signer) { + init_for_test(owner, @0xfac); + let new_attester = @0xfab; + enable_attester(not_attester_manager, new_attester); + } + + #[test(owner = @message_transmitter)] + #[expected_failure(abort_code = 0x80002, location = Self)] + fun test_enable_attester_not_already_exist(owner: &signer) { + init_for_test(owner, @0xfac); + enable_attester(owner, @0xfac); + } + + #[test(owner = @message_transmitter)] + #[expected_failure(abort_code = 0x1000f, location = Self)] + fun test_enable_attester_zero_address(owner: &signer) { + init_for_test(owner, @0xfac); + enable_attester(owner, @0x0); + } + + // Disable Attester Tests + + #[test(owner = @message_transmitter)] + fun test_disable_attester_success(owner: &signer) { + init_for_test(owner, @0xfac); + + let new_attester = @0xfab; + enable_attester(owner, new_attester); + assert!(vector::contains(&state::get_enabled_attesters(), &new_attester), 0); + + disable_attester(owner, new_attester); + assert!(!vector::contains(&state::get_enabled_attesters(), &new_attester), 0); + + let attester_enabled_event = AttesterDisabled { attester: new_attester }; + assert!(event::was_event_emitted(&attester_enabled_event), 0); + } + + #[test(owner = @message_transmitter, not_attester_manager = @0x99)] + #[expected_failure(abort_code = 0x50005, location = Self)] + fun test_disable_attester_not_attester_manager(owner: &signer, not_attester_manager: &signer) { + init_for_test(owner, @0xfac); + let new_attester = @0xfab; + disable_attester(not_attester_manager, new_attester); + } + + #[test(owner = @message_transmitter)] + #[expected_failure(abort_code = 0x30003, location = Self)] + fun test_disable_attester_too_few_attesters(owner: &signer) { + init_for_test(owner, @0xfac); + disable_attester(owner, @0xfac); + } + + #[test(owner = @message_transmitter)] + #[expected_failure(abort_code = 0x30004, location = Self)] + fun test_disable_attester_low_signature_threshold(owner: &signer) { + init_for_test(owner, @0xfac); + state::add_attester(@0xfab); + state::set_signature_threshold(2); + disable_attester(owner, @0xfab); + } + + #[test(owner = @message_transmitter)] + #[expected_failure(abort_code = 0x60001, location = state)] + fun test_disable_attester_attester_not_found(owner: &signer) { + init_for_test(owner, @0xfac); + state::add_attester(@0xfab); + disable_attester(owner, @0xfaa); + } + + // Update Attester Manager Tests + + #[test(owner = @message_transmitter)] + fun test_update_attester_manager_success(owner: &signer) { + init_for_test(owner, @0xfac); + + let new_attester_manager = @0xfab; + update_attester_manager(owner, new_attester_manager); + + let attester_manager_updated_event = AttesterManagerUpdated { + previous_attester_manager: signer::address_of(owner), + new_attester_manager + }; + assert!(event::was_event_emitted(&attester_manager_updated_event), 0); + } + + #[test(owner = @message_transmitter, not_owner = @0x99)] + #[expected_failure(abort_code = 0x50001, location = Self)] + fun test_update_attester_manager_not_owner(owner: &signer, not_owner: &signer) { + init_for_test(owner, @0xfac); + let new_attester_manager = @0xfab; + update_attester_manager(not_owner, new_attester_manager); + } + + #[test(owner = @message_transmitter)] + #[expected_failure(abort_code = 0x80006, location = Self)] + fun test_update_attester_manager_already_exists(owner: &signer) { + init_for_test(owner, @0xfac); + update_attester_manager(owner, @message_transmitter); + } + + // Set Signature Threshold Tests + + #[test(owner = @message_transmitter)] + fun test_set_signature_threshold(owner: &signer) { + init_for_test(owner, @0xfac); + state::add_attester(@0xfab); + + let old_signature_threshold = state::get_signature_threshold(); + let new_signature_threshold = 2; + set_signature_threshold(owner, new_signature_threshold); + + let attester_enabled_event = SignatureThresholdUpdated { old_signature_threshold, new_signature_threshold }; + assert!(event::was_event_emitted(&attester_enabled_event), 0); + } + + #[test(owner = @message_transmitter, not_attester_manager = @0x99)] + #[expected_failure(abort_code = 0x50005, location = Self)] + fun test_set_signature_threshold_not_attester_manager(owner: &signer, not_attester_manager: &signer) { + init_for_test(owner, @0xfac); + set_signature_threshold(not_attester_manager, 2); + } + + #[test(owner = @message_transmitter)] + #[expected_failure(abort_code = 0x10009, location = Self)] + fun test_set_signature_threshold_too_high(owner: &signer) { + init_for_test(owner, @0xfac); + set_signature_threshold(owner, 2); + } + + #[test(owner = @message_transmitter)] + #[expected_failure(abort_code = 0x10007, location = Self)] + fun test_set_signature_threshold_zero_threshold(owner: &signer) { + init_for_test(owner, @0xfac); + set_signature_threshold(owner, 0); + } + + #[test(owner = @message_transmitter)] + #[expected_failure(abort_code = 0x80008, location = Self)] + fun test_set_signature_threshold_already_exists(owner: &signer) { + init_for_test(owner, @0xfac); + set_signature_threshold(owner, state::get_signature_threshold()); + } + + // Verify Attestation Signature Tests + // Based on valid Message and Attestation From DepositForBurn Tx + // https://subnets.avax.network/c-chain/tx/0x1f0eb507b4650881092fbb238065d10ddedaac3bfe7fa456957176aa80e2e15f + + #[test(owner = @message_transmitter)] + fun test_verify_attestation_signature_single_signature(owner: &signer) { + let attester = @0xb0Ea8E1bE37F346C7EA7ec708834D0db18A17361; + init_for_test(owner, attester); + let message = x"000000000000000100000005000000000001c50c0000000000000000000000006b25532e1060ce10cc3b0a99e5683b91bfde6982a65fc943419a5ad590042fd67c9791fd015acf53a54cc823edb8ff81b9ed722e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b97ef9ef8734c71904d8002f8b6bc66dd9c48a6e2b35a76035073ce97cd401aa4781fe579433c2fed59e7bdcc458748a7632277900000000000000000000000000000000000000000000000000000000483b92f90000000000000000000000001cd223dbc9ff35ff6b29dab2339acc842bf58ccb"; + let attestation = x"9ca6e57cdbaff834d0faaffa5315a2da29d751ad26616a8a394e4b365f09f2d01e3697d85c80a04c5c5299ff8419c255688787c1b6dff50c5cf0621c4b29ffbf1c"; + verify_attestation_signature(&message, &attestation); + } + + #[test(owner = @message_transmitter)] + fun test_verify_attestation_signature_multiple_signatures(owner: &signer) { + let attester_one = @0xb0Ea8E1bE37F346C7EA7ec708834D0db18A17361; + let attester_two = @0xE2fEfe09E74b921CbbFF229E7cD40009231501CA; + + init_for_test(owner, attester_one); + enable_attester(owner, attester_two); + set_signature_threshold(owner, 2); + + let message = x"000000000000000100000005000000000001c50c0000000000000000000000006b25532e1060ce10cc3b0a99e5683b91bfde6982a65fc943419a5ad590042fd67c9791fd015acf53a54cc823edb8ff81b9ed722e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b97ef9ef8734c71904d8002f8b6bc66dd9c48a6e2b35a76035073ce97cd401aa4781fe579433c2fed59e7bdcc458748a7632277900000000000000000000000000000000000000000000000000000000483b92f90000000000000000000000001cd223dbc9ff35ff6b29dab2339acc842bf58ccb"; + let attestation = x"9ca6e57cdbaff834d0faaffa5315a2da29d751ad26616a8a394e4b365f09f2d01e3697d85c80a04c5c5299ff8419c255688787c1b6dff50c5cf0621c4b29ffbf1c742186b73f110593d67ffd1272979dfbccf467d005701392bc41714045f17ecc0c88242eba6a2c230202ebd0c2bb7c1a11358375bc6a035ba377a0cfe1b5a4e21c"; + verify_attestation_signature(&message, &attestation); + } + + #[test(owner = @message_transmitter)] + #[expected_failure(abort_code = 0x1000e, location = Self)] + fun test_verify_attestation_signature_malleable_signature(owner: &signer) { + let attester = @0xb0Ea8E1bE37F346C7EA7ec708834D0db18A17361; + init_for_test(owner, attester); + let message = x"000000000000000100000005000000000001c50c0000000000000000000000006b25532e1060ce10cc3b0a99e5683b91bfde6982a65fc943419a5ad590042fd67c9791fd015acf53a54cc823edb8ff81b9ed722e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b97ef9ef8734c71904d8002f8b6bc66dd9c48a6e2b35a76035073ce97cd401aa4781fe579433c2fed59e7bdcc458748a7632277900000000000000000000000000000000000000000000000000000000483b92f90000000000000000000000001cd223dbc9ff35ff6b29dab2339acc842bf58ccb"; + let attestation = x"9ca6e57cdbaff834d0faaffa5315a2da29d751ad26616a8a394e4b365f09f2d0e1c96827a37f5fb3a3ad66007be63da952275524f868ab2f62e1fc70850c41821b"; + verify_attestation_signature(&message, &attestation); + } + + #[test(owner = @message_transmitter)] + #[expected_failure(abort_code = 0x1000a, location = Self)] + fun test_verify_attestation_signature_invalid_attestation_length(owner: &signer) { + let attester = @0xb0Ea8E1bE37F346C7EA7ec708834D0db18A17361; + init_for_test(owner, attester); + + let message = b"message"; + let attestation = x"9ca6e57c"; + verify_attestation_signature(&message, &attestation); + } + + #[test(owner = @message_transmitter)] + #[expected_failure(abort_code = 0x1000d, location = Self)] + fun test_verify_attestation_signature_failed_recovery(owner: &signer) { + let attester = @0xb0Ea8E1bE37F346C7EA7ec708834D0db18A17361; + init_for_test(owner, attester); + let message = b"hello"; + let attestation = x"67315456c4b8e5b453174517326d8e1eefbb2d461d343e303dd25106afadfe35586c7375afd81251b0e72c0de112d2b9d9bdb136744c26d0d96d7bd2f84284021c"; + verify_attestation_signature(&message, &attestation); + } + + #[test(owner = @message_transmitter)] + #[expected_failure(abort_code = 0x1000b, location = Self)] + fun test_verify_attestation_signature_invalid_signature_recovery_id(owner: &signer) { + let attester = @0xb0Ea8E1bE37F346C7EA7ec708834D0db18A17361; + init_for_test(owner, attester); + + let message = b"message"; + let attestation = x"9ca6e57cdbaff834d0faaffa5315a2da29d751ad26616a8a394e4b365f09f2d01e3697d85c80a04c5c5299ff8419c255688787c1b6dff50c5cf0621c4b29ffbfcc"; + verify_attestation_signature(&message, &attestation); + } + + #[test(owner = @message_transmitter)] + #[expected_failure(abort_code = 0x10010, location = Self)] + fun test_verify_attestation_signature_duplicate_attester(owner: &signer) { + let attester_one = @0xb0Ea8E1bE37F346C7EA7ec708834D0db18A17361; + let attester_two = @0xE2fEfe09E74b921CbbFF229E7cD40009231501CA; + + init_for_test(owner, attester_one); + enable_attester(owner, attester_two); + set_signature_threshold(owner, 2); + + let message = x"000000000000000100000005000000000001c50c0000000000000000000000006b25532e1060ce10cc3b0a99e5683b91bfde6982a65fc943419a5ad590042fd67c9791fd015acf53a54cc823edb8ff81b9ed722e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b97ef9ef8734c71904d8002f8b6bc66dd9c48a6e2b35a76035073ce97cd401aa4781fe579433c2fed59e7bdcc458748a7632277900000000000000000000000000000000000000000000000000000000483b92f90000000000000000000000001cd223dbc9ff35ff6b29dab2339acc842bf58ccb"; + let attestation = x"9ca6e57cdbaff834d0faaffa5315a2da29d751ad26616a8a394e4b365f09f2d01e3697d85c80a04c5c5299ff8419c255688787c1b6dff50c5cf0621c4b29ffbf1c9ca6e57cdbaff834d0faaffa5315a2da29d751ad26616a8a394e4b365f09f2d01e3697d85c80a04c5c5299ff8419c255688787c1b6dff50c5cf0621c4b29ffbf1c"; + verify_attestation_signature(&message, &attestation); + } + + #[test(owner = @message_transmitter)] + #[expected_failure(abort_code = 0x10010, location = Self)] + fun test_verify_attestation_signature_wrong_signature_order(owner: &signer) { + let attester_one = @0xb0Ea8E1bE37F346C7EA7ec708834D0db18A17361; + let attester_two = @0xE2fEfe09E74b921CbbFF229E7cD40009231501CA; + + init_for_test(owner, attester_one); + enable_attester(owner, attester_two); + set_signature_threshold(owner, 2); + + let message = x"000000000000000100000005000000000001c50c0000000000000000000000006b25532e1060ce10cc3b0a99e5683b91bfde6982a65fc943419a5ad590042fd67c9791fd015acf53a54cc823edb8ff81b9ed722e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b97ef9ef8734c71904d8002f8b6bc66dd9c48a6e2b35a76035073ce97cd401aa4781fe579433c2fed59e7bdcc458748a7632277900000000000000000000000000000000000000000000000000000000483b92f90000000000000000000000001cd223dbc9ff35ff6b29dab2339acc842bf58ccb"; + let attestation = x"742186b73f110593d67ffd1272979dfbccf467d005701392bc41714045f17ecc0c88242eba6a2c230202ebd0c2bb7c1a11358375bc6a035ba377a0cfe1b5a4e21c9ca6e57cdbaff834d0faaffa5315a2da29d751ad26616a8a394e4b365f09f2d01e3697d85c80a04c5c5299ff8419c255688787c1b6dff50c5cf0621c4b29ffbf1c"; + verify_attestation_signature(&message, &attestation); + } + + #[test(owner = @message_transmitter)] + #[expected_failure(abort_code = 0x1000c, location = Self)] + fun test_verify_attestation_signature_incorrect_attester(owner: &signer) { + let attester_one = @0xb0Ea8E1bE37F346C7EA7ec708834D0db18A17361; + let attester_two = @0x0a992d191DEeC32aFe36203Ad87D7d289a738F81; + + init_for_test(owner, attester_one); + enable_attester(owner, attester_two); + set_signature_threshold(owner, 2); + + let message = x"000000000000000100000005000000000001c50c0000000000000000000000006b25532e1060ce10cc3b0a99e5683b91bfde6982a65fc943419a5ad590042fd67c9791fd015acf53a54cc823edb8ff81b9ed722e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b97ef9ef8734c71904d8002f8b6bc66dd9c48a6e2b35a76035073ce97cd401aa4781fe579433c2fed59e7bdcc458748a7632277900000000000000000000000000000000000000000000000000000000483b92f90000000000000000000000001cd223dbc9ff35ff6b29dab2339acc842bf58ccb"; + let attestation = x"9ca6e57cdbaff834d0faaffa5315a2da29d751ad26616a8a394e4b365f09f2d01e3697d85c80a04c5c5299ff8419c255688787c1b6dff50c5cf0621c4b29ffbf1c742186b73f110593d67ffd1272979dfbccf467d005701392bc41714045f17ecc0c88242eba6a2c230202ebd0c2bb7c1a11358375bc6a035ba377a0cfe1b5a4e21c"; + verify_attestation_signature(&message, &attestation); + } + + // Test Malleability + + #[test] + fun test_verify_low_s_value_normal_signature() { + let signature_less_than_half = x"9ca6e57cdbaff834d0faaffa5315a2da29d751ad26616a8a394e4b365f09f2d01e3697d85c80a04c5c5299ff8419c255688787c1b6dff50c5cf0621c4b29ffbf"; + assert!(verify_low_s_value(&signature_less_than_half), 0); + } + + #[test] + fun test_verify_low_s_value_malleable_greater_signature() { + let signature_greater_than_half = x"9ca6e57cdbaff834d0faaffa5315a2da29d751ad26616a8a394e4b365f09f2d0e1c96827a37f5fb3a3ad66007be63da952275524f868ab2f62e1fc70850c4182"; + assert!(!verify_low_s_value(&signature_greater_than_half), 0); + } + + #[test] + fun test_verify_low_s_value_malleable_equal_signature() { + let signature_equals_half = x"9ca6e57cdbaff834d0faaffa5315a2da29d751ad26616a8a394e4b365f09f2d07FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0"; + assert!(verify_low_s_value(&signature_equals_half), 0); + } + + // View Function Tests + + #[test(owner = @message_transmitter)] + fun test_is_enabled_attester(owner: &signer) { + init_for_test(owner, @0xfac); + assert!(is_enabled_attester(@0xfac) == true, 0); + assert!(is_enabled_attester(@0xfab) == false, 0); + } + + #[test(owner = @message_transmitter)] + fun test_get_num_enabled_attester(owner: &signer) { + init_for_test(owner, @0xfac); + assert!(get_num_enabled_attesters() == 1, 0); + enable_attester(owner, @0xfab); + assert!(get_num_enabled_attesters() == 2, 0); + } + + #[test(owner = @message_transmitter)] + fun test_view_attester_manager(owner: &signer) { + init_for_test(owner, @0xfac); + assert!(attester_manager() == state::get_attester_manager(), 0); + } + + #[test(owner = @message_transmitter)] + fun test_get_signature_threshold(owner: &signer) { + init_for_test(owner, @0xfac); + assert!(get_signature_threshold() == 1, 0); + } +} diff --git a/packages/message_transmitter/sources/deserialize.move b/packages/message_transmitter/sources/deserialize.move new file mode 100644 index 0000000..b37ee24 --- /dev/null +++ b/packages/message_transmitter/sources/deserialize.move @@ -0,0 +1,87 @@ +/// Copyright (c) 2024, Circle Internet Group, Inc. +/// All rights reserved. +/// +/// SPDX-License-Identifier: Apache-2.0 +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. + +module message_transmitter::deserialize { + // Built-in Modules + use std::vector; + use aptos_std::from_bcs; + + #[test_only] + use std::bcs; + + public fun deserialize_u32(data: &vector, index: u64, size: u64): u32 { + let result = vector::slice(data, index, index+size); + vector::reverse(&mut result); + from_bcs::to_u32(result) + } + + public fun deserialize_u64(data: &vector, index: u64, size: u64): u64 { + let result = vector::slice(data, index, index+size); + vector::reverse(&mut result); + from_bcs::to_u64(result) + } + + public fun deserialize_u256(data: &vector, index: u64, size: u64): u256 { + let result = vector::slice(data, index, index+size); + vector::reverse(&mut result); + from_bcs::to_u256(result) + } + + public fun deserialize_address(data: &vector, index: u64, size: u64): address { + let result = vector::slice(data, index, index+size); + from_bcs::to_address(result) + } + + #[test] + public fun test_deserialize_u32() { + let num: u32 = 1234; + let serialized = bcs::to_bytes(&num); + vector::reverse(&mut serialized); + + let deserialized = deserialize_u32(&serialized, 0, vector::length(&serialized)); + assert!(deserialized == num, 0); + } + + #[test] + public fun test_deserialize_u64() { + let num: u64 = 123456789; + let serialized = bcs::to_bytes(&num); + vector::reverse(&mut serialized); + + let deserialized = deserialize_u64(&serialized, 0, vector::length(&serialized)); + assert!(deserialized == num, 0); + } + + #[test] + public fun test_deserialize_u256() { + let num: u256 = 123456789123456789123456789; + let serialized = bcs::to_bytes(&num); + vector::reverse(&mut serialized); + + let deserialized = deserialize_u256(&serialized, 0, vector::length(&serialized)); + assert!(deserialized == num, 0); + } + + #[test] + public fun test_deserialize_address() { + let address: address = @0xa9fb1b3009dcb79e2fe346c16a604b8fa8ae0a79; + let serialized = bcs::to_bytes(&address); + + let deserialized = deserialize_address(&serialized, 0, vector::length(&serialized)); + assert!(deserialized == address, 0); + } +} diff --git a/packages/message_transmitter/sources/message.move b/packages/message_transmitter/sources/message.move new file mode 100644 index 0000000..d3b9743 --- /dev/null +++ b/packages/message_transmitter/sources/message.move @@ -0,0 +1,224 @@ +/// Copyright (c) 2024, Circle Internet Group, Inc. +/// All rights reserved. +/// +/// SPDX-License-Identifier: Apache-2.0 +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. + +/// Module for serializing outgoing and deserializing incoming messages. Message is dynamically sized. +/// +/// Message is structured in the following order: +/// -------------------------------------------------- +/// Field Bytes Type Index +/// version 4 uint32 0 +/// sourceDomain 4 uint32 4 +/// destinationDomain 4 uint32 8 +/// nonce 8 uint64 12 +/// sender 32 bytes32 20 +/// recipient 32 bytes32 52 +/// destinationCaller 32 bytes32 84 +/// messageBody dynamic bytes 116 +/// -------------------------------------------------- +module message_transmitter::message { + // Built-in Modules + use std::error; + use std::vector; + + // Package Modules + use message_transmitter::serialize; + use message_transmitter::deserialize; + + // Constants + const VERSION_INDEX: u64 = 0; + const VERSION_LEN: u64 = 4; + const SOURCE_DOMAIN_INDEX: u64 = 4; + const SOURCE_DOMAIN_LEN: u64 = 4; + const DESTINATION_DOMAIN_INDEX: u64 = 8; + const DESTINATION_DOMAIN_LEN: u64 = 4; + const NONCE_INDEX: u64 = 12; + const NONCE_LEN: u64 = 8; + const SENDER_INDEX: u64 = 20; + const SENDER_LEN: u64 = 32; + const RECIPIENT_INDEX: u64 = 52; + const RECIPIENT_LEN: u64 = 32; + const DESTINATION_CALLER_INDEX: u64 = 84; + const DESTINATION_CALLER_LEN: u64 = 32; + const MESSAGE_BODY_INDEX: u64 = 116; + + // Errors + const EINVALID_FORMAT: u64 = 1; + + public fun get_message_version(message: &vector): u32 { + deserialize::deserialize_u32(message, VERSION_INDEX, VERSION_LEN) + } + + public fun get_src_domain_id(message: &vector): u32 { + deserialize::deserialize_u32(message, SOURCE_DOMAIN_INDEX, SOURCE_DOMAIN_LEN) + } + + public fun get_destination_domain_id(message: &vector): u32 { + deserialize::deserialize_u32(message, DESTINATION_DOMAIN_INDEX, DESTINATION_DOMAIN_LEN) + } + + public fun get_nonce(message: &vector): u64 { + deserialize::deserialize_u64(message, NONCE_INDEX, NONCE_LEN) + } + + public fun get_sender_address(message: &vector): address { + deserialize::deserialize_address(message, SENDER_INDEX, SENDER_LEN) + } + + public fun get_recipient_address(message: &vector): address { + deserialize::deserialize_address(message, RECIPIENT_INDEX, RECIPIENT_LEN) + } + + public fun get_destination_caller(message: &vector): address { + deserialize::deserialize_address(message, DESTINATION_CALLER_INDEX, DESTINATION_CALLER_LEN) + } + + public fun get_message_body(message: &vector): vector { + vector::slice(message, MESSAGE_BODY_INDEX, vector::length(message)) + } + + public fun serialize( + version: u32, + source_domain: u32, + destination_domain: u32, + nonce: u64, + sender: address, + recipient: address, + destination_caller: address, + raw_body: &vector + ): vector { + let result = vector::empty(); + vector::append(&mut result, serialize::serialize_u32(version)); + vector::append(&mut result, serialize::serialize_u32(source_domain)); + vector::append(&mut result, serialize::serialize_u32(destination_domain)); + vector::append(&mut result, serialize::serialize_u64(nonce)); + vector::append(&mut result, serialize::serialize_address(sender)); + vector::append(&mut result, serialize::serialize_address(recipient)); + vector::append(&mut result, serialize::serialize_address(destination_caller)); + vector::append(&mut result, *raw_body); + result + } + + // Bytes message should contain every all the data required for message transmitter. Message body is optional + public fun validate_message(message: &vector) { + assert!(vector::length(message) >= MESSAGE_BODY_INDEX, error::invalid_argument(EINVALID_FORMAT)); + } + + // Following test are based on -> + // ETH (Source): https://sepolia.etherscan.io/tx/0x151c196be83e2fcbd84204a521ee0a758a5e7335ac7d2c0958ef840fd485dc61 + // AVAX (Destination): https://testnet.snowtrace.io/tx/0xa98d5c33b7571609875f56ae148563411377392c87b9e8cebd483683a0e36413 + // + // Sender: 0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5 (ETH TokenMessenger) + // Recipient: 0xeb08f243e5d3fcff26a9e38ae5520a669f4019d0 (AVAX TokenMessenger) + // + // Custom destination caller: 0x1f26414439C8D03FC4b9CA912CeFd5Cb508C9605 (https://testnet.snowtrace.io/tx/0xa98d5c33b7571609875f56ae148563411377392c87b9e8cebd483683a0e36413) + + #[test] + fun test_message_serialization() { + let original_message = get_test_message(); + let serialized_message= serialize(0, 0, 1, 258836, @0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5, @0xeb08f243e5d3fcff26a9e38ae5520a669f4019d0, @0x1f26414439C8D03FC4b9CA912CeFd5Cb508C9605, &x"000000000000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c72380000000000000000000000001f26414439c8d03fc4b9ca912cefd5cb508c960500000000000000000000000000000000000000000000000000000000000004be0000000000000000000000003b61abee91852714e4e99b09a1af3e9c13893ef1"); + assert!(original_message == serialized_message, 0); + } + + #[test] + fun test_get_message_version() { + let original_message = get_test_message(); + let expected_message_version = 0; + assert!(get_message_version(&original_message) == expected_message_version, 0); + } + + #[test] + fun test_get_src_domain_id() { + let original_message = get_test_message(); + let expected_src_domain_id = 0; + assert!(get_src_domain_id(&original_message) == expected_src_domain_id, 0); + } + + #[test] + fun test_get_destination_domain_id() { + let original_message = get_test_message(); + let expected_destination_domain_id = 1; + assert!(get_destination_domain_id(&original_message) == expected_destination_domain_id, 0); + } + + #[test] + fun test_get_nonce() { + let original_message = get_test_message(); + let expected_nonce = 258836; + assert!(get_nonce(&original_message) == expected_nonce, 0); + } + + #[test] + fun test_get_sender_address() { + let original_message = get_test_message(); + let expected_sender_address = @0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5; + assert!(get_sender_address(&original_message) == expected_sender_address, 0); + } + + #[test] + fun test_get_recipient_address() { + let original_message = get_test_message(); + let expected_recipient_address = @0xeb08f243e5d3fcff26a9e38ae5520a669f4019d0; + assert!(get_recipient_address(&original_message) == expected_recipient_address, 0); + } + + #[test] + fun test_get_destination_caller() { + let original_message = get_test_message(); + let expected_destination_caller = @0x1f26414439C8D03FC4b9CA912CeFd5Cb508C9605; + assert!(get_destination_caller(&original_message) == expected_destination_caller, 0); + } + + #[test] + fun test_get_message_body() { + let original_message = get_test_message(); + let expected_message_body = x"000000000000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c72380000000000000000000000001f26414439c8d03fc4b9ca912cefd5cb508c960500000000000000000000000000000000000000000000000000000000000004be0000000000000000000000003b61abee91852714e4e99b09a1af3e9c13893ef1"; + assert!(get_message_body(&original_message) == expected_message_body, 0); + } + + #[test] + fun test_message_serialization_deserialization_combined() { + let original_message= get_test_message(); + let serialized_message= serialize(0, 0, 1, 258836, @0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5, @0xeb08f243e5d3fcff26a9e38ae5520a669f4019d0, @0x1f26414439C8D03FC4b9CA912CeFd5Cb508C9605, &vector[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,125,75,25,108,176,199,176,29,116,63,188,97,22,169,2,55,156,114,56,0,0,0,0,0,0,0,0,0,0,0,0,31,38,65,68,57,200,208,63,196,185,202,145,44,239,213,203,80,140,150,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,190,0,0,0,0,0,0,0,0,0,0,0,0,59,97,171,238,145,133,39,20,228,233,155,9,161,175,62,156,19,137,62,241]); + + assert!(get_message_version(&original_message) == get_message_version(&serialized_message), 1); + assert!(get_src_domain_id(&original_message) == get_src_domain_id(&serialized_message), 1); + assert!(get_destination_domain_id(&original_message) == get_destination_domain_id(&serialized_message), 1); + assert!(get_nonce(&original_message) == get_nonce(&serialized_message), 1); + assert!(get_sender_address(&original_message) == get_sender_address(&serialized_message), 1); + assert!(get_recipient_address(&original_message) == get_recipient_address(&serialized_message), 1); + assert!(get_destination_caller(&original_message) == get_destination_caller(&serialized_message), 1); + assert!(get_message_body(&original_message) == get_message_body(&serialized_message), 1); + } + + #[test] + fun test_message_is_valid_sufficient_length() { + let original_message = get_test_message(); + validate_message(&original_message) + } + + #[test] + #[expected_failure(abort_code = 0x10001, location = Self)] + fun test_message_is_valid_not_sufficient_length() { + let invalid_message = vector[5, 2, 32, 2, 21, 23]; + validate_message(&invalid_message) + } + + #[test_only] + fun get_test_message(): vector { + x"000000000000000000000001000000000003f3140000000000000000000000009f3b8679c73c2fef8b59b4f3444d4e156fb70aa5000000000000000000000000eb08f243e5d3fcff26a9e38ae5520a669f4019d00000000000000000000000001f26414439c8d03fc4b9ca912cefd5cb508c9605000000000000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c72380000000000000000000000001f26414439c8d03fc4b9ca912cefd5cb508c960500000000000000000000000000000000000000000000000000000000000004be0000000000000000000000003b61abee91852714e4e99b09a1af3e9c13893ef1" + } +} diff --git a/packages/message_transmitter/sources/message_transmitter.move b/packages/message_transmitter/sources/message_transmitter.move new file mode 100644 index 0000000..b2d6379 --- /dev/null +++ b/packages/message_transmitter/sources/message_transmitter.move @@ -0,0 +1,1074 @@ +/// Copyright (c) 2024, Circle Internet Group, Inc. +/// All rights reserved. +/// +/// SPDX-License-Identifier: Apache-2.0 +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. + +module message_transmitter::message_transmitter { + // Built-in Modules + use std::bcs; + use std::error; + use std::option; + use std::signer; + use std::vector; + use aptos_std::aptos_hash; + use aptos_std::from_bcs; + use aptos_framework::event; + use aptos_framework::object; + use aptos_framework::resource_account; + use aptos_extensions::upgradable; + use aptos_extensions::manageable; + use aptos_extensions::pausable; + use aptos_extensions::ownable; + + // Package Modules + use message_transmitter::state; + use message_transmitter::attester; + use message_transmitter::message; + + // Constants + const SEED_NAME: vector = b"MessageTransmitter"; + + // Errors + const EMESSAGE_BODY_EXCEEDS_MAX_SIZE: u64 = 1; + const EINVALID_RECIPIENT_ADDRESS: u64 = 2; + const EINVALID_DESTINATION_CALLER_ADDRESS: u64 = 3; + const ENOT_ORIGINAL_SENDER: u64 = 4; + const EINCORRECT_SOURCE_DOMAIN: u64 = 5; + const EALREADY_INITIALIZED: u64 = 6; + const EINCORRECT_DESTINATION_DOMAIN: u64 = 7; + const EINCORRECT_CALLER_FOR_THE_MESSAGE: u64 = 8; + const EINVALID_MESSAGE_VERSION: u64 = 9; + const ENONCE_ALREADY_USED: u64 = 10; + const EUNAUTHORIZED_RECEIVING_ADDRESS: u64 = 11; + + #[resource_group_member(group = aptos_framework::object::ObjectGroup)] + /// Store the extend ref to generate signer + struct ObjectController has key { + extend_ref: object::ExtendRef, + } + + struct Receipt { + caller: address, + recipient: address, + source_domain: u32, + sender: address, + nonce: u64, + message_body: vector + } + + // ----------------------------- + // ---------- Events ----------- + // ----------------------------- + + #[event] + struct MessageSent has drop, store { + message: vector + } + + #[event] + struct MessageReceived has drop, store { + caller: address, + source_domain: u32, + nonce: u64, + sender: address, + message_body: vector + } + + #[event] + struct MaxMessageBodySizeUpdated has drop, store { + max_message_body_size: u64, + } + + // ----------------------------- + // --- Public View Functions --- + // ----------------------------- + + #[view] + public fun local_domain(): u32 { + state::get_local_domain() + } + + #[view] + public fun version(): u32 { + state::get_version() + } + + #[view] + public fun is_nonce_used(hash: address): bool { + state::is_nonce_used(hash) + } + + #[view] + public fun next_available_nonce(): u64 { + state::get_next_available_nonce() + } + + #[view] + public fun max_message_body_size(): u64 { + state::get_max_message_body_size() + } + + #[view] + public fun object_address(): address { + state::get_object_address() + } + + // ----------------------------- + // ----- Public Functions ------ + // ----------------------------- + + fun init_module(resource_acct_signer: &signer) { + let constructor_ref = object::create_named_object(resource_acct_signer, SEED_NAME); + let message_transmitter_signer = &object::generate_signer(&constructor_ref); + let extend_ref = object::generate_extend_ref(&constructor_ref); + move_to(message_transmitter_signer, ObjectController { extend_ref }); + + ownable::new(message_transmitter_signer, @deployer); + pausable::new(message_transmitter_signer, @deployer); + + let signer_cap = resource_account::retrieve_resource_account_cap(resource_acct_signer, @deployer); + manageable::new(resource_acct_signer, @deployer); + upgradable::new(resource_acct_signer, signer_cap); + + } + /// Create and initialize Message Transmitter object + /// Aborts if: + /// - caller is not the deployer + /// - it has already been initialized + entry fun initialize_message_transmitter( + caller: &signer, + local_domain: u32, + attester: address, + max_message_body_size: u64, + version: u32 + ) acquires ObjectController { + manageable::assert_is_admin(caller, @message_transmitter); + assert!(!state::is_initialized(), error::already_exists(EALREADY_INITIALIZED)); + state::init_state(caller, &get_signer(), local_domain, version, max_message_body_size); + attester::init_attester(caller, attester); + } + + /// Send the message to the destination domain and recipient. Increments the nonce, serializes the message and + /// emits `MessageSent` event. + /// Aborts if: + /// - the contract is paused + /// - message body size exceeds the max size allowed + /// - recipient is zero address + public fun send_message( + caller: &signer, + destination_domain: u32, + recipient: address, + message_body: &vector + ): u64 { + pausable::assert_not_paused(state::get_object_address()); + let empty_destination_caller = @0x0; + let nonce = reserve_and_increment_nonce(); + let sender_address = signer::address_of(caller); + serialize_message_and_emit_event( + destination_domain, + recipient, + sender_address, + empty_destination_caller, + nonce, + message_body + ); + nonce + } + + /// Send the message to the destination domain and recipient for a specified `destinationCaller`. + /// Only the `destinationCaller` can receive this message. Increments the nonce, serializes the message and + /// emits `MessageSent` event. + /// Aborts if: + /// - the contract is paused + /// - message body size exceeds than the max size allowed + /// - recipient is zero address + /// - destination caller is zero address + public fun send_message_with_caller( + caller: &signer, + destination_domain: u32, + recipient: address, + destination_caller: address, + message_body: &vector + ): u64 { + pausable::assert_not_paused(state::get_object_address()); + assert!(destination_caller != @0x0, error::invalid_argument(EINVALID_DESTINATION_CALLER_ADDRESS)); + let nonce = reserve_and_increment_nonce(); + let sender_address = signer::address_of(caller); + serialize_message_and_emit_event( + destination_domain, + recipient, + sender_address, + destination_caller, + nonce, + message_body + ); + nonce + } + + /// Replaces the given message with new message body and/or destination caller. The replaced message reuses the same + /// nonce making both the existing and new messages valid. Serializes the message and emits `MessageSent` event. + /// Aborts if: + /// - the contract is paused + /// - message is invalid + /// - attestation is invalid + /// - message body size exceeds than the max size allowed + /// - caller is not the original sender + /// - domain id from the message doesn't matches the local domain id + public fun replace_message( + caller: &signer, + original_message: &vector, + original_attestation: &vector, + new_message_body: &option::Option>, + new_destination_caller: &option::Option
+ ) { + pausable::assert_not_paused(state::get_object_address()); + message::validate_message(original_message); + attester::verify_attestation_signature(original_message, original_attestation); + + let sender_address = message::get_sender_address(original_message); + assert!(sender_address == signer::address_of(caller), error::permission_denied(ENOT_ORIGINAL_SENDER)); + + let source_domain = message::get_src_domain_id(original_message); + assert!(source_domain == local_domain(), error::invalid_argument(EINCORRECT_SOURCE_DOMAIN)); + + let destination_domain = message::get_destination_domain_id(original_message); + let recipient = message::get_recipient_address(original_message); + let nonce = message::get_nonce(original_message); + let original_destination_caller = message::get_destination_caller(original_message); + let original_message_body = message::get_message_body(original_message); + serialize_message_and_emit_event( + destination_domain, + recipient, + sender_address, + option::get_with_default(new_destination_caller, original_destination_caller), + nonce, + option::borrow_with_default(new_message_body, &original_message_body), + ) + } + + /// Receives a message. Messages with a given nonce can only be received once for a + /// (sourceDomain, destinationDomain). Message format is defined in `message_transmitter::message` module. + /// A valid attestation is the concatenated 65-byte signature(s) of exactly `thresholdSignature` signatures, in + /// increasing order of attester address. + /// + /// This functions returns `Receipt` struct ([Hot Potato](https://medium.com/@borispovod/move-hot-potato-pattern-bbc48a48d93c)) + /// after validating attestation and marking nonce used. The receiving contract calls `complete_receive()` with the + /// receipt after running through its own logic to emit the `MessageReceived` event + /// and destroy `Receipt`. e.g + /// ``` + /// let receipt = message_transmitter::receive_message(caller, message, attestation); + /// receiving_module::handle_receive_message(caller, receipt) + /// + /// // The `complete_receive_message` will be called from the receiving contract. Signer should be generated + /// // from the receiving contract's account or object. + /// let success = message_transmitter::complete_receive_message(caller, receipt); + /// assert!(success, 0); + /// ``` + /// Aborts if: + /// - the contract is paused + /// - message is invalid + /// - attestation is not valid + /// - caller is not authorized to receive the message (not the destination caller) + /// - message version is invalid + /// - destination domain id from the message doesn't match the local domain id + /// - the nonce is already used + public fun receive_message(caller: &signer, message_bytes: &vector, attestation: &vector): Receipt { + pausable::assert_not_paused(state::get_object_address()); + message::validate_message(message_bytes); + attester::verify_attestation_signature(message_bytes, attestation); + + // Validate destination domain + let destination_domain = message::get_destination_domain_id(message_bytes); + assert!(destination_domain == local_domain(), error::invalid_argument(EINCORRECT_DESTINATION_DOMAIN)); + + // Validate destination caller + let destination_caller = message::get_destination_caller(message_bytes); + assert!( + destination_caller == @0x0 || destination_caller == signer::address_of(caller), + error::permission_denied(EINCORRECT_CALLER_FOR_THE_MESSAGE) + ); + + // Validate message version + assert!( + message::get_message_version(message_bytes) == version(), + error::invalid_argument(EINVALID_MESSAGE_VERSION) + ); + + // Validate nonce is available and mark it used + let source_domain = message::get_src_domain_id(message_bytes); + let nonce = message::get_nonce(message_bytes); + let source_and_nonce_hash = hash_source_and_nonce(source_domain, nonce); + assert!(!is_nonce_used(source_and_nonce_hash), error::already_exists(ENONCE_ALREADY_USED)); + state::set_nonce_used(source_and_nonce_hash); + + // Return unstamped receipt + Receipt { + caller: signer::address_of(caller), + recipient: message::get_recipient_address(message_bytes), + source_domain, + nonce, + sender: message::get_sender_address(message_bytes), + message_body: message::get_message_body(message_bytes) + } + } + + /// This function takes in a receipt, verifies it, emits `MessageReceived` event and destroys the receipt. + /// Aborts if: + /// - caller is not the receipt recipient + public fun complete_receive_message(caller: &signer, receipt: Receipt): bool { + assert!( + receipt.recipient == signer::address_of(caller), + error::permission_denied(EUNAUTHORIZED_RECEIVING_ADDRESS) + ); + event::emit(MessageReceived { + caller: receipt.caller, + source_domain: receipt.source_domain, + nonce: receipt.nonce, + sender: receipt.sender, + message_body: receipt.message_body + }); + destroy_receipt(receipt); + true + } + + /// Sets the max message body size (in bytes). Emits `MaxMessageBodySizeUpdated` event. + /// Aborts if: + /// - the caller is not the owner + entry fun set_max_message_body_size(caller: &signer, new_max_message_body_size: u64) { + ownable::assert_is_owner(caller, state::get_object_address()); + state::set_max_message_body_size(new_max_message_body_size); + event::emit(MaxMessageBodySizeUpdated { max_message_body_size: new_max_message_body_size }) + } + + /// Public helper functions to retrieve struct fields since structs are only accessible within the same module + public fun get_receipt_details(receipt: &Receipt): (address, address, u32, vector) { + (receipt.sender, receipt.recipient, receipt.source_domain, receipt.message_body) + } + + // ----------------------------- + // ----- Private Functions ----- + // ----------------------------- + + /// Generate signer from the `ExtendRef` + fun get_signer(): signer acquires ObjectController { + let object_address = state::get_object_address(); + let object_controller = borrow_global(object_address); + let object_signer = object::generate_signer_for_extending( + &object_controller.extend_ref + ); + object_signer + } + + fun serialize_message_and_emit_event( + destination_domain: u32, + recipient: address, + sender_address: address, + destination_caller: address, + nonce: u64, + message_body: &vector + ) { + assert!( + vector::length(message_body) <= state::get_max_message_body_size(), + error::invalid_argument(EMESSAGE_BODY_EXCEEDS_MAX_SIZE) + ); + assert!(recipient != @0x0, error::invalid_argument(EINVALID_RECIPIENT_ADDRESS)); + let message = message::serialize( + version(), + local_domain(), + destination_domain, + nonce, + sender_address, + recipient, + destination_caller, + message_body + ); + event::emit(MessageSent { message }); + } + + fun reserve_and_increment_nonce(): u64 { + let nonce = state::get_next_available_nonce(); + state::set_next_available_nonce(nonce+1); + nonce + } + + /// Create hash based on "{source}-{nonce}" + fun hash_source_and_nonce(source: u32, nonce: u64): address { + let key = bcs::to_bytes(&source); + vector::append(&mut key, b"-"); + vector::append(&mut key, bcs::to_bytes(&nonce)); + let hash = aptos_hash::keccak256(key); + from_bcs::to_address(hash) + } + + fun destroy_receipt(receipt: Receipt) { + let Receipt { + caller: _, + recipient: _, + source_domain: _, + nonce: _, + sender: _, + message_body: _ + } = receipt; + } + + // ----------------------------- + // -------- Unit Tests --------- + // ----------------------------- + + #[test_only] + use aptos_std::aptos_hash::keccak256; + #[test_only] + use aptos_framework::account::{Self, create_signer_for_test}; + #[test_only] + use aptos_extensions::ownable::OwnerRole; + #[test_only] + use aptos_extensions::pausable::PauseState; + + // Test Helper Functions + + #[test_only] + const RECEIVING_CONTRACT: address = @0x7b62ddceded1acb449413404df81dd8d240f340605f626db1e15183cf04fa43e; + #[test_only] + const TEST_SEED: vector = b"test_seed_mt"; + + #[test_only] + public fun init_test_message_transmitter_module(deployer: address) { + account::create_account_for_test(deployer); + resource_account::create_resource_account( + &create_signer_for_test(deployer), + TEST_SEED, + x"", + ); + let resource_account_address = account::create_resource_address(&deployer, TEST_SEED); + assert!(@message_transmitter == resource_account_address, 0); + let resource_account_signer = create_signer_for_test(resource_account_address); + init_module(&resource_account_signer); + } + + #[test_only] + public fun initialize_test_message_transmitter(deployer: &signer) acquires ObjectController { + init_test_message_transmitter_module(signer::address_of(deployer)); + initialize_message_transmitter(deployer, 9, @0xC0664d3a3b411653A3DD791492c01f4819AC84B4, 256, 0); + } + + #[test_only] + fun get_valid_send_message_and_attestation(): (vector, vector) { + let original_message = message::serialize( + state::get_version(), + state::get_local_domain(), + 1, + 7384, + @deployer, + @0x1CD223dBC9ff35fF6B29dAB2339ACC842BF58cCb, + @0x1CD223dBC9ff35fF6B29dAB2339ACC842BF58cCb, + &b"Hello", + ); + let original_attestation = x"027a76974e2c7c5264544eaf079a62a42d48d7c5015844dd996ab35c4285380a5e7dc8d22813cccd1650aac26965abc1224145ec37cc4f80b027ca2d7877aa451b"; + (original_message, original_attestation) + } + + #[test_only] + fun get_valid_receive_message_and_attestation(): (vector, vector){ + let message = message::serialize( + state::get_version(), + 0, + local_domain(), + 7384, + @0x1CD223dBC9ff35fF6B29dAB2339ACC842BF58cCb, + RECEIVING_CONTRACT, + @deployer, + &b"Hello", + ); + let attestation = x"78eede2dcaa5dbf6d1a9ce0831f699a6f8a6db9150cb8e26354432b2667f92406092b130cfc71f21ddb814a5ee1f1cee4f2d5cb43b712a7bb0b59fbbae018f2c1c"; + (message, attestation) + } + + #[test_only] + public fun get_message_from_event(message_sent_event: &MessageSent): vector { + message_sent_event.message + } + + // Message Transmitter Initialization Tests + + #[test(owner = @deployer)] + fun test_init_message_transmitter(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + assert!(state::is_initialized(), 0); + assert!(exists(state::get_object_address()), 0); + assert!(attester::is_enabled_attester(attester::get_enabled_attester(0)), 0); + assert!(manageable::admin(@message_transmitter) == @deployer, 0); + assert!(ownable::owner(object::address_to_object(state::get_object_address())) == @deployer, 0); + assert!(pausable::pauser(object::address_to_object(state::get_object_address())) == @deployer, 0); + assert!(!pausable::is_paused(object::address_to_object(state::get_object_address())), 0); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x80006, location = Self)] + fun test_init_message_transmitter_already_initialized(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + initialize_message_transmitter(owner, 9, @0xfac, 256, 0); + } + + #[test(not_owner = @0xfaa)] + #[expected_failure(abort_code = manageable::ENOT_ADMIN, location = manageable)] + fun test_init_message_transmitter_not_owner(not_owner: &signer) acquires ObjectController { + init_test_message_transmitter_module(@deployer); + initialize_message_transmitter(not_owner, 9, @0xfac, 256, 0); + } + + // Send Message Tests + + #[test(owner = @deployer)] + fun test_send_message_success(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + + let expected_nonce = state::get_next_available_nonce(); + let recipient = @0xfac; + let message_body = b"message"; + let destination_domain = 1; + let nonce = send_message(owner, destination_domain, recipient, &message_body); + let expected_message = message::serialize( + state::get_version(), + state::get_local_domain(), + destination_domain, + expected_nonce, + @deployer, + recipient, + @0x0, + &message_body + ); + + assert!(nonce == expected_nonce, 0); + assert!(event::was_event_emitted(&MessageSent { message: expected_message }), 0); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = pausable::EPAUSED, location = pausable)] + fun test_send_message_contract_paused(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + state::set_paused(owner); + send_message(owner, 1, @0xfac, &b"message"); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x10001, location = Self)] + fun test_send_message_excess_message_body_size(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + state::set_max_message_body_size(2); + send_message(owner, 1, @0xfac, &b"message"); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x10002, location = Self)] + fun test_send_message_zero_recipient_address(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + send_message(owner, 1, @0x0, &b"message"); + } + + // Send Message With Caller Tests + + #[test(owner = @deployer)] + fun test_send_message_with_caller_success(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + + let expected_nonce = state::get_next_available_nonce(); + let recipient = @0xfac; + let message_body = b"message"; + let destination_domain = 1; + let destination_caller = @0xfaa; + let nonce = send_message_with_caller(owner, destination_domain, recipient, destination_caller, &message_body); + let expected_message = message::serialize( + state::get_version(), + state::get_local_domain(), + destination_domain, + expected_nonce, + @deployer, + recipient, + destination_caller, + &message_body + ); + + assert!(nonce == expected_nonce, 0); + assert!(event::was_event_emitted(&MessageSent { message: expected_message }), 0); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = pausable::EPAUSED, location = pausable)] + fun test_send_message_with_caller_contract_paused(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + state::set_paused(owner); + send_message_with_caller(owner, 1, @0xfac, @0xfaa, &b"message"); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x10001, location = Self)] + fun test_send_message_with_caller_excess_message_body_size(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + state::set_max_message_body_size(2); + send_message_with_caller(owner, 1, @0xfac, @0xfaa, &b"message"); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x10002, location = Self)] + fun test_send_message_with_caller_zero_recipient_address(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + send_message_with_caller(owner, 1, @0x0, @0xfaa, &b"message"); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x10003, location = Self)] + fun test_send_message_with_caller_zero_destination_caller_address(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + send_message_with_caller(owner, 1, @0xfac, @0x0, &b"message"); + } + + // Replace Message Tests + + #[test(owner = @deployer)] + fun test_replace_message_new_destination_caller(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + let (original_message, original_attestation) = get_valid_send_message_and_attestation(); + let new_destination_caller = @0xfab; + replace_message( + owner, + &original_message, + &original_attestation, + &option::none(), + &option::some(new_destination_caller), + ); + + let expected_message = message::serialize( + state::get_version(), + state::get_local_domain(), + 1, + 7384, + @deployer, + @0x1CD223dBC9ff35fF6B29dAB2339ACC842BF58cCb, + new_destination_caller, + &b"Hello", + ); + assert!(event::was_event_emitted(&MessageSent { message: expected_message }), 0); + } + + #[test(owner = @deployer)] + fun test_replace_message_new_message_body(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + let (original_message, original_attestation) = get_valid_send_message_and_attestation(); + let new_message_bdy = b"New Message"; + replace_message( + owner, + &original_message, + &original_attestation, + &option::some(new_message_bdy), + &option::none(), + ); + + let expected_message = message::serialize( + state::get_version(), + state::get_local_domain(), + 1, + 7384, + @deployer, + @0x1CD223dBC9ff35fF6B29dAB2339ACC842BF58cCb, + @0x1CD223dBC9ff35fF6B29dAB2339ACC842BF58cCb, + &new_message_bdy, + ); + assert!(event::was_event_emitted(&MessageSent { message: expected_message }), 0); + } + + #[test(owner = @deployer)] + fun test_replace_message_new_message_body_and_destination_caller(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + let (original_message, original_attestation) = get_valid_send_message_and_attestation(); + let new_message_bdy = b"New Message"; + let new_destination_caller = @0xfab; + replace_message( + owner, + &original_message, + &original_attestation, + &option::some(new_message_bdy), + &option::some(new_destination_caller), + ); + + let expected_message = message::serialize( + state::get_version(), + state::get_local_domain(), + 1, + 7384, + @deployer, + @0x1CD223dBC9ff35fF6B29dAB2339ACC842BF58cCb, + new_destination_caller, + &new_message_bdy, + ); + assert!(event::was_event_emitted(&MessageSent { message: expected_message }), 0); + } + + #[test(owner = @deployer)] + fun test_replace_message_no_change(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + let (original_message, original_attestation) = get_valid_send_message_and_attestation(); + replace_message( + owner, + &original_message, + &original_attestation, + &option::none(), + &option::none(), + ); + assert!(event::was_event_emitted(&MessageSent { message: original_message }), 0); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = pausable::EPAUSED, location = pausable)] + fun test_replace_message_contract_paused(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + state::set_paused(owner); + let (original_message, original_attestation) = get_valid_send_message_and_attestation(); + replace_message( + owner, + &original_message, + &original_attestation, + &option::none(), + &option::none(), + ); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x10001, location = Self)] + fun test_replace_message_excess_message_body_size(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + state::set_max_message_body_size(2); + let (original_message, original_attestation) = get_valid_send_message_and_attestation(); + replace_message( + owner, + &original_message, + &original_attestation, + &option::some(b"Hello"), + &option::none(), + ); + } + + #[test(owner = @deployer, incorrect_sender = @0xfaa)] + #[expected_failure(abort_code = 0x50004, location = Self)] + fun test_replace_message_not_original_sender(owner: &signer, incorrect_sender: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + let (original_message, original_attestation) = get_valid_send_message_and_attestation(); + replace_message( + incorrect_sender, + &original_message, + &original_attestation, + &option::some(b"New Message"), + &option::none(), + ); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x10005, location = Self)] + fun test_replace_message_incorrect_domain_id(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + let original_message = message::serialize( + state::get_version(), + 5, + 1, + 7384, + @deployer, + @0x1CD223dBC9ff35fF6B29dAB2339ACC842BF58cCb, + @0x1CD223dBC9ff35fF6B29dAB2339ACC842BF58cCb, + &b"Hello", + ); + let original_attestation = x"d83ece55985280777daff7e74c80e3480aa8c98aec0da457817ce95c4e7be7d34e4e66ef12a6e32cd7c95571873bd3a186cb510a093644abb5c7c07525dfe8221b"; + replace_message( + owner, + &original_message, + &original_attestation, + &option::some(b"New Message"), + &option::none(), + ); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x10001, location = message)] + fun test_replace_message_invalid_message(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + let (original_message, original_attestation) = get_valid_send_message_and_attestation(); + let invalid_message = vector::slice(&original_message, 0, 10); + replace_message( + owner, + &invalid_message, + &original_attestation, + &option::none(), + &option::none(), + ); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x1000c, location = attester)] + fun test_replace_message_invalid_attestation(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + let (original_message, _) = get_valid_send_message_and_attestation(); + let (_, attestation) = get_valid_receive_message_and_attestation(); + replace_message( + owner, + &original_message, + &attestation, + &option::none(), + &option::none(), + ); + } + + // Receive Message Tests + + #[test( + owner = @deployer, + receiving_contract = @0x7b62ddceded1acb449413404df81dd8d240f340605f626db1e15183cf04fa43e + )] + fun test_receive_message_success(owner: &signer, receiving_contract: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + + let source_domain = 0; + let sender = @0x1CD223dBC9ff35fF6B29dAB2339ACC842BF58cCb; + let message_body = b"Hello"; + let nonce = 7384; + let (message, attestation) = get_valid_receive_message_and_attestation(); + let receipt = receive_message(owner, &message, &attestation); + + assert!(receipt.nonce == nonce, 0); + assert!(receipt.recipient == RECEIVING_CONTRACT, 0); + assert!(receipt.sender == sender, 0); + assert!(receipt.source_domain == source_domain, 0); + assert!(receipt.message_body == message_body, 0); + assert!(complete_receive_message(receiving_contract, receipt), 0); + assert!(event::was_event_emitted(&MessageReceived { + caller: signer::address_of(owner), + source_domain, + nonce, + sender, + message_body + }), 0); + assert!(state::is_nonce_used(hash_source_and_nonce(source_domain, nonce)), 0); + } + + #[test( + owner = @deployer, + receiving_contract = @0x7b62ddceded1acb449413404df81dd8d240f340605f626db1e15183cf04fa43e + )] + fun test_receive_message_empty_destination_caller(owner: &signer, receiving_contract: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + + let source_domain = 0; + let sender = @0x1CD223dBC9ff35fF6B29dAB2339ACC842BF58cCb; + let message_body = b"Hello"; + let recipient = RECEIVING_CONTRACT; + let nonce = 7384; + let message = message::serialize( + state::get_version(), + source_domain, + local_domain(), + nonce, + sender, + recipient, + @0x0, + &b"Hello" + ); + let attestation = x"2a5f3a941fa31140b74e05b4fb218976691777a00199ec5de6fb24146060f6c96c8da25b08a3cb3be22d11c9cf3ac0705062167104db50cb6111e3193efafccd1b"; + let receipt = receive_message(owner, &message, &attestation); + assert!(receipt.nonce == nonce, 0); + assert!(receipt.recipient == recipient, 0); + assert!(receipt.sender == sender, 0); + assert!(receipt.source_domain == source_domain, 0); + assert!(receipt.message_body == message_body, 0); + assert!(complete_receive_message(receiving_contract, receipt), 0); + assert!(event::was_event_emitted(&MessageReceived { + caller: signer::address_of(owner), + source_domain, + nonce, + sender, + message_body + }), 0); + assert!(state::is_nonce_used(hash_source_and_nonce(source_domain, nonce)), 0); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = pausable::EPAUSED, location = pausable)] + fun test_receive_message_contract_paused(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + state::set_paused(owner); + let (message, attestation) = get_valid_receive_message_and_attestation(); + let receipt = receive_message(owner, &message, &attestation); + destroy_receipt(receipt) + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x10001, location = message)] + fun test_receive_message_invalid_message(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + let (message, attestation) = get_valid_receive_message_and_attestation(); + let invalid_message = vector::slice(&message, 0, 10); + let receipt = receive_message(owner, &invalid_message, &attestation); + destroy_receipt(receipt) + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x1000c, location = attester)] + fun test_receive_message_invalid_attestation(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + let (message, _) = get_valid_receive_message_and_attestation(); + let (_, attestation) = get_valid_send_message_and_attestation(); + let receipt = receive_message(owner, &message, &attestation); + destroy_receipt(receipt) + } + + #[test(owner = @deployer, unauthorized_caller = @0xfaa)] + #[expected_failure(abort_code = 0x50008, location = Self)] + fun test_receive_message_not_authorized(owner: &signer, unauthorized_caller: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + let (message, attestation) = get_valid_receive_message_and_attestation(); + let receipt = receive_message(unauthorized_caller, &message, &attestation); + destroy_receipt(receipt) + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x10007, location = Self)] + fun test_receive_message_incorrect_domain_id(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + let (message, attestation) = get_valid_send_message_and_attestation(); + let receipt = receive_message(owner, &message, &attestation); + destroy_receipt(receipt) + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x10009, location = Self)] + fun test_receive_message_invalid_message_version(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + let (message, attestation) = get_valid_receive_message_and_attestation(); + state::set_version(1); + let receipt = receive_message(owner, &message, &attestation); + destroy_receipt(receipt) + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x8000a, location = Self)] + fun test_receive_message_nonce_already_used(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + let (message, attestation) = get_valid_receive_message_and_attestation(); + let nonce = message::get_nonce(&message); + let source_domain = message::get_src_domain_id(&message); + state::set_nonce_used(hash_source_and_nonce(source_domain, nonce)); + let receipt = receive_message(owner, &message, &attestation); + destroy_receipt(receipt) + } + + // Complete Receive Message Tests + + #[test( + owner = @deployer, + receiving_contract = @0x7b62ddceded1acb449413404df81dd8d240f340605f626db1e15183cf04fa43e + )] + fun test_complete_receive_message_success(owner: &signer, receiving_contract: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + let receipt = Receipt { + caller: signer::address_of(owner), + recipient: RECEIVING_CONTRACT, + source_domain: 6, + nonce: 523344, + sender: @0xfaa, + message_body: b"Message", + }; + assert!(complete_receive_message(receiving_contract, receipt), 0); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x5000b, location = Self)] + fun test_complete_receive_message_unauthorized_caller(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + let stamped_receipt = Receipt { + caller: signer::address_of(owner), + recipient: RECEIVING_CONTRACT, + source_domain: 6, + nonce: 523344, + sender: @0xfaa, + message_body: b"Message", + }; + complete_receive_message(owner, stamped_receipt); + } + + // Set Max Message Body Size Tests + + #[test(owner = @deployer)] + fun test_set_max_message_body_size_success(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + set_max_message_body_size(owner, 512); + assert!(state::get_max_message_body_size() == 512, 0); + assert!(event::was_event_emitted(&MaxMessageBodySizeUpdated { + max_message_body_size: 512 + }), 0) + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = ownable::ENOT_OWNER, location = ownable)] + fun test_set_max_message_body_size_not_owner(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + state::set_owner(@0xfaa); + set_max_message_body_size(owner, 512); + } + + // Get Receipt Details test + + #[test] + fun test_get_receipt_details() { + let receipt = Receipt { + caller: @0xfac, + recipient: @0xfaa, + source_domain: 1, + sender: @0xfab, + nonce: 5723, + message_body: b"message_body" + }; + + let (sender, recipient, source_domain, message_body) = get_receipt_details(&receipt); + assert!(recipient == @0xfaa, 0); + assert!(source_domain == 1, 0); + assert!(sender == @0xfab, 0); + assert!(message_body == b"message_body", 0); + destroy_receipt(receipt); + } + + // View Function Tests + + #[test(owner = @deployer)] + fun test_is_nonce_used(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + let key = from_bcs::to_address(keccak256(b"Hello!")); + state::set_nonce_used(key); + assert!(is_nonce_used(key), 0); + } + + #[test(owner = @deployer)] + fun test_next_available_nonce(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + assert!(next_available_nonce() == state::get_next_available_nonce(), 0); + } + + #[test(owner = @deployer)] + fun test_max_message_body_size(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + assert!(max_message_body_size() == state::get_max_message_body_size(), 0); + } + + #[test(owner = @deployer)] + fun test_object_address(owner: &signer) acquires ObjectController { + initialize_test_message_transmitter(owner); + assert!(object_address() == state::get_object_address(), 0); + } +} diff --git a/packages/message_transmitter/sources/serialize.move b/packages/message_transmitter/sources/serialize.move new file mode 100644 index 0000000..72fabdc --- /dev/null +++ b/packages/message_transmitter/sources/serialize.move @@ -0,0 +1,85 @@ +/// Copyright (c) 2024, Circle Internet Group, Inc. +/// All rights reserved. +/// +/// SPDX-License-Identifier: Apache-2.0 +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. + +/// Module for implementing serialization based on big-endian encoding (to be compatible with other CCTP contracts). +/// std::bcs use little-endian encoding +module message_transmitter::serialize { + // Built-in Modules + use std::bcs; + use std::vector; + + public fun serialize_u32(num: u32): vector { + let serialized = bcs::to_bytes(&num); + vector::reverse(&mut serialized); + serialized + } + + public fun serialize_u64(num: u64): vector { + let serialized = bcs::to_bytes(&num); + vector::reverse(&mut serialized); + serialized + } + + public fun serialize_u256(num: u256): vector { + let serialized = bcs::to_bytes(&num); + vector::reverse(&mut serialized); + serialized + } + + public fun serialize_address(address: address): vector { + bcs::to_bytes(&address) + } + + #[test] + fun test_serialize_u32() { + let num: u32 = 1234; + let expected = bcs::to_bytes(&num); + vector::reverse(&mut expected); + + let result = serialize_u32(num); + assert!(expected == result, 0); + } + + #[test] + fun test_serialize_u64() { + let num: u64 = 123456789; + let expected = bcs::to_bytes(&num); + vector::reverse(&mut expected); + + let result = serialize_u64(num); + assert!(expected == result, 0); + } + + #[test] + fun test_serialize_u256() { + let num: u256 = 123456789123456789123456789; + let expected = bcs::to_bytes(&num); + vector::reverse(&mut expected); + + let result = serialize_u256(num); + assert!(expected == result, 0); + } + + #[test] + fun test_serialize_address() { + let address: address = @0xa9fb1b3009dcb79e2fe346c16a604b8fa8ae0a79; + let expected = bcs::to_bytes(&address); + + let result = serialize_address(address); + assert!(expected == result, 0); + } +} diff --git a/packages/message_transmitter/sources/state.move b/packages/message_transmitter/sources/state.move new file mode 100644 index 0000000..34a9ed5 --- /dev/null +++ b/packages/message_transmitter/sources/state.move @@ -0,0 +1,349 @@ +/// Copyright (c) 2024, Circle Internet Group, Inc. +/// All rights reserved. +/// +/// SPDX-License-Identifier: Apache-2.0 +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. + +module message_transmitter::state { + // Built-in Modules + use std::error; + use std::signer; + use std::vector; + use aptos_std::table_with_length::{Self, TableWithLength}; + use aptos_framework::object; + use aptos_extensions::ownable::OwnerRole; + + // Package Modules + use aptos_extensions::ownable; + + // Friend Modules + friend message_transmitter::attester; + friend message_transmitter::message_transmitter; + + // Constants + const SEED_NAME: vector = b"MessageTransmitter"; + + // Error Codes + const EATTESTER_NOT_FOUND: u64 = 1; + + #[resource_group_member(group = aptos_framework::object::ObjectGroup)] + struct State has key { + local_domain: u32, + version: u32, + max_message_body_size: u64, + next_available_nonce: u64, + // Key for used_nonces is 32 bytes hash which can be represented using address + used_nonces: TableWithLength, + signature_threshold: u64, + + // Admin Roles + enabled_attesters: vector
, // Authorized witnesses to bridge transactions + attester_manager: address, // Manages attester state and configuration + } + + public(friend) fun init_state( + owner: &signer, + object_signer: &signer, + local_domain: u32, + version: u32, + max_message_body_size: u64 + ) { + move_to( + object_signer, + State { + local_domain, + version, + max_message_body_size, + next_available_nonce: 0, + used_nonces: table_with_length::new(), + signature_threshold: 1, + + // Admin Roles + attester_manager: signer::address_of(owner), + enabled_attesters: vector::empty(), + } + ); + } + + // ----------------------------- + // ---------- Getters ---------- + // ----------------------------- + + public(friend) fun is_initialized(): bool { + exists(get_object_address()) + } + + public(friend) fun get_local_domain(): u32 acquires State { + borrow_global(get_object_address()).local_domain + } + + public(friend) fun get_version(): u32 acquires State { + borrow_global(get_object_address()).version + } + + public(friend) fun get_max_message_body_size(): u64 acquires State { + borrow_global(get_object_address()).max_message_body_size + } + + public(friend) fun get_next_available_nonce(): u64 acquires State { + borrow_global(get_object_address()).next_available_nonce + } + + public(friend) fun is_nonce_used(source_and_nonce_hash: address): bool acquires State { + table_with_length::contains(&borrow_global(get_object_address()).used_nonces, source_and_nonce_hash) + } + + public(friend) fun get_signature_threshold(): u64 acquires State { + borrow_global(get_object_address()).signature_threshold + } + + public(friend) fun get_enabled_attesters(): vector
acquires State { + borrow_global(get_object_address()).enabled_attesters + } + + public(friend) fun get_num_enabled_attesters(): u64 acquires State { + vector::length(&borrow_global(get_object_address()).enabled_attesters) + } + + public(friend) fun get_attester_manager(): address acquires State { + borrow_global(get_object_address()).attester_manager + } + + public(friend) fun get_owner(): address { + ownable::owner(object::address_to_object(get_object_address())) + } + + public(friend) fun get_object_address(): address { + object::create_object_address(&@message_transmitter, SEED_NAME) + } + + // ----------------------------- + // ---------- Setters ---------- + // ----------------------------- + + public(friend) fun set_max_message_body_size(max_message_body_size: u64) acquires State { + borrow_global_mut(get_object_address()).max_message_body_size = max_message_body_size + } + + public(friend) fun set_next_available_nonce(next_available_nonce: u64) acquires State { + borrow_global_mut(get_object_address()).next_available_nonce = next_available_nonce + } + + public(friend) fun set_nonce_used(source_and_nonce_hash: address) acquires State { + table_with_length::add( + &mut borrow_global_mut(get_object_address()).used_nonces, source_and_nonce_hash, + true + ); + } + + public(friend) fun set_signature_threshold(signature_threshold: u64) acquires State { + borrow_global_mut(get_object_address()).signature_threshold = signature_threshold + } + + public(friend) fun add_attester(attester: address) acquires State { + vector::push_back(&mut borrow_global_mut(get_object_address()).enabled_attesters, attester); + } + + public(friend) fun remove_attester(attester: address) acquires State { + let state = borrow_global_mut(get_object_address()); + let (found, index) = vector::index_of(&mut state.enabled_attesters, &attester); + assert!(found, error::not_found(EATTESTER_NOT_FOUND)); + vector::remove(&mut state.enabled_attesters, index); + } + + public(friend) fun set_attester_manager(attester_manager: address) acquires State { + borrow_global_mut(get_object_address()).attester_manager = attester_manager + } + + // ----------------------------- + // -------- Unit Tests --------- + // ----------------------------- + + #[test_only] + use aptos_std::aptos_hash::keccak256; + #[test_only] + use aptos_std::from_bcs; + #[test_only] + use aptos_framework::account::{Self, create_signer_for_test}; + #[test_only] + use aptos_extensions::pausable::{Self, PauseState}; + + #[test_only] + public fun init_test_state(caller: &signer) { + let resource_account_address = account::create_resource_address(&@deployer, b"test_seed_mt"); + let resource_account_signer = create_signer_for_test(resource_account_address); + let constructor_ref = object::create_named_object(&resource_account_signer, SEED_NAME); + let signer = object::generate_signer(&constructor_ref); + init_state(caller, &signer, 9, 0, 256); + ownable::new(&signer, signer::address_of(caller)); + pausable::new(&signer, signer::address_of(caller)); + } + + #[test_only] + public fun set_paused(pauser: &signer) { + pausable::test_pause(pauser, object::address_to_object(get_object_address())); + } + + #[test_only] + public fun set_owner(owner_address: address) { + ownable::set_owner_for_testing(get_object_address(), owner_address); + } + + #[test_only] + public fun set_version(version: u32) acquires State { + borrow_global_mut(get_object_address()).version = version + } + + // ----------------------------- + // ---------- Getters ---------- + // ----------------------------- + + #[test(owner = @message_transmitter)] + fun test_is_initialized(owner: &signer) { + init_test_state(owner); + assert!(is_initialized() == true, 0); + } + + #[test(owner = @message_transmitter)] + fun test_get_local_domain(owner: &signer) acquires State { + init_test_state(owner); + assert!(get_local_domain() == 9, 0); + } + + #[test(owner = @message_transmitter)] + fun test_get_version(owner: &signer) acquires State { + init_test_state(owner); + assert!(get_version() == 0, 0); + } + + #[test(owner = @message_transmitter)] + fun test_get_max_message_body_size(owner: &signer) acquires State { + init_test_state(owner); + assert!(get_max_message_body_size() == 256, 0); + } + + #[test(owner = @message_transmitter)] + fun test_get_next_available_nonce(owner: &signer) acquires State { + init_test_state(owner); + assert!(get_next_available_nonce() == 0, 0); + } + + #[test(owner = @message_transmitter)] + fun test_is_nonce_used(owner: &signer) acquires State { + init_test_state(owner); + let key = from_bcs::to_address(keccak256(b"Hello!")); + assert!(is_nonce_used(key) == false, 0); + } + + #[test(owner = @message_transmitter)] + fun test_get_signature_threshold(owner: &signer) acquires State { + init_test_state(owner); + assert!(get_signature_threshold() == 1, 0); + } + + #[test(owner = @message_transmitter)] + fun test_get_attester_manager(owner: &signer) acquires State { + init_test_state(owner); + assert!(get_attester_manager() == signer::address_of(owner), 0); + } + + #[test(owner = @message_transmitter)] + fun test_get_enabled_attesters(owner: &signer) acquires State { + init_test_state(owner); + assert!(get_enabled_attesters() == vector::empty(), 0); + } + + #[test(owner = @message_transmitter, attester = @0x1234)] + fun test_get_num_enabled_attesters(owner: &signer, attester: &signer) acquires State { + init_test_state(owner); + let address = signer::address_of(attester); + add_attester(address); + assert!(get_num_enabled_attesters() == 1, 0); + } + + #[test(owner = @message_transmitter)] + fun test_get_owner(owner: &signer) { + init_test_state(owner); + assert!(get_owner() == signer::address_of(owner), 0); + } + + // ----------------------------- + // ---------- Setters ---------- + // ----------------------------- + + #[test(owner = @message_transmitter)] + fun test_set_max_message_body_size(owner: &signer) acquires State { + init_test_state(owner); + let max_message_body_size = 512; + set_max_message_body_size(max_message_body_size); + assert!(get_max_message_body_size() == max_message_body_size, 0); + } + + #[test(owner = @message_transmitter)] + fun test_set_next_available_nonce(owner: &signer) acquires State { + init_test_state(owner); + let next_available_nonce = 10; + set_next_available_nonce(next_available_nonce); + assert!(get_next_available_nonce() == next_available_nonce, 0); + } + + #[test(owner = @message_transmitter)] + fun test_set_nonce_used(owner: &signer) acquires State { + init_test_state(owner); + let key = from_bcs::to_address(keccak256(b"Hello!")); + set_nonce_used(key); + assert!(is_nonce_used(key) == true, 0); + } + + #[test(owner = @message_transmitter)] + fun test_set_signature_threshold(owner: &signer) acquires State { + init_test_state(owner); + let signature_threshold = 10; + set_signature_threshold(signature_threshold); + assert!(get_signature_threshold() == signature_threshold, 0); + } + + #[test(owner = @message_transmitter, manager = @0x1234)] + fun test_set_attester_manager(owner: &signer, manager: &signer) acquires State { + init_test_state(owner); + let address = signer::address_of(manager); + set_attester_manager(address); + assert!(get_attester_manager() == address, 0); + } + + #[test(owner = @message_transmitter, attester = @0x1234)] + fun test_add_attester(owner: &signer, attester: &signer) acquires State { + init_test_state(owner); + let address = signer::address_of(attester); + add_attester(address); + assert!(vector::contains(&get_enabled_attesters(), &address), 0); + } + + #[test(owner = @message_transmitter, attester = @0x1234)] + fun test_remove_attester(owner: &signer, attester: &signer) acquires State { + init_test_state(owner); + let address = signer::address_of(attester); + add_attester(address); + remove_attester(address); + assert!(!vector::contains(&get_enabled_attesters(), &address), 0); + } + + #[test(owner = @message_transmitter, unknown_attester = @0x1234)] + #[expected_failure(abort_code = 0x60001, location = Self)] + fun test_remove_attester_attester_does_not_exist(owner: &signer, unknown_attester: &signer) acquires State { + init_test_state(owner); + let address = signer::address_of(unknown_attester); + remove_attester(address); + } +} diff --git a/packages/token_messenger_minter/Makefile b/packages/token_messenger_minter/Makefile new file mode 100644 index 0000000..ee001ac --- /dev/null +++ b/packages/token_messenger_minter/Makefile @@ -0,0 +1,23 @@ +.PHONY: print-coverage test coverage compile publish publish-extensions + +print-coverage: + @if [ ! -f .coverage_map.mvcov ]; then $(MAKE) coverage; fi; + aptos move coverage source --module $(word 2,$(MAKECMDGOALS)) --dev + +test: + aptos move test --dev + +coverage: + aptos move test --coverage --dev + +compile: + aptos move compile --named-addresses token_messenger_minter=token_messenger_minter,message_transmitter=message_transmitter,aptos_extensions=aptos_extensions + +publish: + aptos move publish --profile $(profile) --named-addresses token_messenger_minter=token_messenger_minter,message_transmitter=message_transmitter,aptos_extensions=aptos_extensions --assume-yes + +publish-extensions: + aptos move publish --profile $(profile) --named-addresses token_messenger_minter=token_messenger_minter,message_transmitter=message_transmitter,aptos_extensions=aptos_extensions --assume-yes --package-dir ../../stablecoin-aptos/packages/aptos_extensions +# Prevents make from thinking the arguments are actual targets +%: + @: diff --git a/packages/token_messenger_minter/Move.toml b/packages/token_messenger_minter/Move.toml new file mode 100644 index 0000000..3adb590 --- /dev/null +++ b/packages/token_messenger_minter/Move.toml @@ -0,0 +1,34 @@ +[package] +name = "TokenMessengerMinter" +version = "1.0.0" +upgrade_policy = "compatible" + +[addresses] +message_transmitter = "_" +token_messenger_minter = "_" +aptos_extensions = "_" +stablecoin = "_" +deployer = "_" + +[dev-addresses] +message_transmitter = "0xebe6e5b8db973b4c8f5c3fbe7f5802e7a823b7ae4b70e5b6b1ce978418e5dad5" +token_messenger_minter = "0x4e0d4a160a8490565c7f61cd21531a320ccda0f55b9c7d20131b3717650db043" +aptos_extensions = "5ba1674a3ffa843ed88aa4a0a051b9a52f76459a8853e5cd62b22bcc488d2765" +stablecoin = "0xd59e6aa0af32155459919e5ed2792eecd8fedcac70f2bf59840431446ed36973" +deployer = "c06c5aa31d28c27be8345770a83b48314b829039ec5a33b79265216c13c66071" + +[dependencies.AptosFramework] +git = "https://github.com/aptos-labs/aptos-core.git" +rev = "mainnet" +subdir = "aptos-move/framework/aptos-framework" + +[dependencies.AptosExtensions] +local = "../../stablecoin-aptos/packages/aptos_extensions" + +[dependencies.MessageTransmitter] +local = "../message_transmitter" + +[dependencies.Stablecoin] +local = "../../stablecoin-aptos/packages/stablecoin" + +[dev-dependencies] diff --git a/packages/token_messenger_minter/scripts/deposit_for_burn.move b/packages/token_messenger_minter/scripts/deposit_for_burn.move new file mode 100644 index 0000000..c145e1f --- /dev/null +++ b/packages/token_messenger_minter/scripts/deposit_for_burn.move @@ -0,0 +1,21 @@ +script { + use aptos_framework::fungible_asset::{Metadata}; + use aptos_framework::object::{Self, Object}; + use aptos_framework::primary_fungible_store; + use token_messenger_minter::token_messenger; + + fun deposit_for_burn(caller: &signer, + amount: u64, + destination_domain: u32, + mint_recipient: address, + burn_token: address) { + let token_obj: Object = object::address_to_object(burn_token); + let asset = primary_fungible_store::withdraw(caller, token_obj, amount); + token_messenger::deposit_for_burn( + caller, + asset, + destination_domain, + mint_recipient, + ); + } +} diff --git a/packages/token_messenger_minter/scripts/deposit_for_burn_with_caller.move b/packages/token_messenger_minter/scripts/deposit_for_burn_with_caller.move new file mode 100644 index 0000000..d673211 --- /dev/null +++ b/packages/token_messenger_minter/scripts/deposit_for_burn_with_caller.move @@ -0,0 +1,24 @@ +script { + use aptos_framework::fungible_asset::{Metadata}; + use aptos_framework::object::{Self, Object}; + use aptos_framework::primary_fungible_store; + use token_messenger_minter::token_messenger; + + + fun deposit_for_burn_with_caller(caller: &signer, + amount: u64, + destination_domain: u32, + mint_recipient: address, + burn_token: address, + destination_caller: address) { + let token_obj: Object = object::address_to_object(burn_token); + let asset = primary_fungible_store::withdraw(caller, token_obj, amount); + token_messenger::deposit_for_burn_with_caller( + caller, + asset, + destination_domain, + mint_recipient, + destination_caller + ); + } +} diff --git a/packages/token_messenger_minter/scripts/handle_receive_message.move b/packages/token_messenger_minter/scripts/handle_receive_message.move new file mode 100644 index 0000000..a17e529 --- /dev/null +++ b/packages/token_messenger_minter/scripts/handle_receive_message.move @@ -0,0 +1,15 @@ +script { + use message_transmitter::message_transmitter; + use token_messenger_minter::token_messenger; + + fun handle_receive_message(caller: &signer, + message: vector, + attestation: vector) { + let receipt = message_transmitter::receive_message( + caller, + &message, + &attestation + ); + token_messenger::handle_receive_message(receipt); + } +} diff --git a/packages/token_messenger_minter/scripts/mint.move b/packages/token_messenger_minter/scripts/mint.move new file mode 100644 index 0000000..1217547 --- /dev/null +++ b/packages/token_messenger_minter/scripts/mint.move @@ -0,0 +1,10 @@ +script { + use aptos_framework::primary_fungible_store; + use stablecoin::treasury; + + + fun mint(caller: &signer, amount: u64, mint_recipient: address) { + let fa = treasury::mint(caller, amount); + primary_fungible_store::deposit(mint_recipient, fa); + } +} diff --git a/packages/token_messenger_minter/scripts/replace_deposit_for_burn.move b/packages/token_messenger_minter/scripts/replace_deposit_for_burn.move new file mode 100644 index 0000000..b859006 --- /dev/null +++ b/packages/token_messenger_minter/scripts/replace_deposit_for_burn.move @@ -0,0 +1,18 @@ +script { + use std::option; + use token_messenger_minter::token_messenger; + + fun replace_deposit_for_burn(caller: &signer, + original_message: vector, + original_attestation: vector, + new_destination_caller: option::Option
, + new_mint_recipient: option::Option
) { + token_messenger::replace_deposit_for_burn( + caller, + &original_message, + &original_attestation, + &new_destination_caller, + &new_mint_recipient + ); + } +} diff --git a/packages/token_messenger_minter/sources/state.move b/packages/token_messenger_minter/sources/state.move new file mode 100644 index 0000000..4013633 --- /dev/null +++ b/packages/token_messenger_minter/sources/state.move @@ -0,0 +1,427 @@ +/// Copyright (c) 2024, Circle Internet Group, Inc. +/// All rights reserved. +/// +/// SPDX-License-Identifier: Apache-2.0 +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. + +module token_messenger_minter::state { + // Built-in Modules + use std::bcs; + use std::vector; + use aptos_std::aptos_hash; + use aptos_std::from_bcs; + use aptos_std::smart_table; + use aptos_framework::object; + use aptos_extensions::pausable::PauseState; + + // Package Modules + use aptos_extensions::pausable; + #[test_only] + use std::signer; + #[test_only] + use aptos_framework::account; + #[test_only] + use aptos_framework::account::create_signer_for_test; + #[test_only] + use aptos_extensions::ownable; + + // Friend Modules + friend token_messenger_minter::token_messenger_minter; + friend token_messenger_minter::token_messenger; + friend token_messenger_minter::token_controller; + friend token_messenger_minter::token_minter; + + // Constants + const SEED_NAME: vector = b"TokenMessengerMinter"; + + #[resource_group_member(group = aptos_framework::object::ObjectGroup)] + struct State has key { + message_body_version: u32, + remote_token_messengers: smart_table::SmartTable, + burn_limits_per_message: smart_table::SmartTable, + remote_tokens_to_local_tokens: smart_table::SmartTable, + + // Admin Roles + token_controller: address // Controls remote resources and burn limits + } + + public(friend) fun init_state( + admin: &signer, + message_body_version: u32, + ) { + move_to( + admin, + State { + message_body_version, + remote_token_messengers: smart_table::new(), + remote_tokens_to_local_tokens: smart_table::new(), + burn_limits_per_message: smart_table::new(), + + // Admin Roles + token_controller: @0x0, // Token Controller gets initialized in its own module + } + ); + } + + // ----------------------------- + // ---------- Getters ---------- + // ----------------------------- + + public(friend) fun is_initialized(): bool { + exists(get_object_address()) + } + + public(friend) fun get_message_body_version(): u32 acquires State { + borrow_global(get_object_address()).message_body_version + } + + public(friend) fun get_remote_token_messenger(domain: u32): address acquires State { + *smart_table::borrow(&borrow_global(get_object_address()).remote_token_messengers, domain) + } + + public(friend) fun is_remote_token_messenger_set_for_domain( + domain: u32 + ): bool acquires State { + smart_table::contains( + &borrow_global(get_object_address()).remote_token_messengers, + domain + ) + } + + public(friend) fun get_max_burn_limit_per_message_for_token(token: address): (bool, u64) acquires State { + let exists = smart_table::contains(&borrow_global(get_object_address()).burn_limits_per_message, token); + if (exists) { + (true, *smart_table::borrow(&borrow_global(get_object_address()).burn_limits_per_message, token)) + } else { + (false, 0) + } + } + + public(friend) fun local_token_exists(remote_domain: u32, remote_token: address): bool acquires State { + let key = hash_remote_domain_and_token(remote_domain, remote_token); + smart_table::contains(&borrow_global(get_object_address()).remote_tokens_to_local_tokens, key) + } + + public(friend) fun get_local_token(remote_domain: u32, remote_token: address): address acquires State { + let key = hash_remote_domain_and_token(remote_domain, remote_token); + *smart_table::borrow(&borrow_global(get_object_address()).remote_tokens_to_local_tokens, key) + } + + public(friend) fun is_paused(): bool { + pausable::is_paused(object::address_to_object(get_object_address())) + } + + public(friend) fun get_token_controller(): address acquires State { + borrow_global(get_object_address()).token_controller + } + + public(friend) fun get_num_remote_token_messengers(): u64 acquires State { + smart_table::length(&borrow_global(get_object_address()).remote_token_messengers) + } + + public(friend) fun get_num_linked_tokens(): u64 acquires State { + smart_table::length(&borrow_global(get_object_address()).remote_tokens_to_local_tokens) + } + + public(friend) fun get_object_address(): address { + object::create_object_address(&@token_messenger_minter, SEED_NAME) + } + + // ----------------------------- + // ---------- Setters ---------- + // ----------------------------- + + public(friend) fun add_remote_token_messenger(domain: u32, token_messenger: address) acquires State { + smart_table::add( + &mut borrow_global_mut(get_object_address()).remote_token_messengers, + domain, + token_messenger + ); + } + + public(friend) fun remove_remote_token_messenger(domain: u32): address acquires State { + smart_table::remove( + &mut borrow_global_mut(get_object_address()).remote_token_messengers, + domain + ) + } + + public(friend) fun add_local_token_for_remote_token( + remote_domain: u32, + remote_token: address, + local_token: address + ) acquires State { + let key = hash_remote_domain_and_token(remote_domain, remote_token); + smart_table::add( + &mut borrow_global_mut(get_object_address()).remote_tokens_to_local_tokens, + key, + local_token + ); + } + + public(friend) fun remove_local_token_for_remote_token( + remote_domain: u32, + remote_token: address + ): address acquires State { + let key = hash_remote_domain_and_token(remote_domain, remote_token); + smart_table::remove( + &mut borrow_global_mut(get_object_address()).remote_tokens_to_local_tokens, + key + ) + } + + public(friend) fun set_max_burn_limit_per_message_for_token(token: address, limit: u64) acquires State { + smart_table::upsert( + &mut borrow_global_mut(get_object_address()).burn_limits_per_message, + token, + limit + ); + } + + public(friend) fun set_token_controller(token_controller: address) acquires State { + borrow_global_mut(get_object_address()).token_controller = token_controller; + } + + // ----------------------------- + // ----- Private Functions ----- + // ----------------------------- + + /// Create hash based on "{remote_domain}{token}" + fun hash_remote_domain_and_token(remote_domain: u32, token: address): address { + let key = bcs::to_bytes(&remote_domain); + vector::append(&mut key, bcs::to_bytes(&token)); + let hash = aptos_hash::keccak256(key); + from_bcs::to_address(hash) + } + + // ----------------------------- + // -------- Unit Tests --------- + // ----------------------------- + + #[test_only] + public fun init_test_state(caller: &signer) { + let resource_account_address = account::create_resource_address(&@deployer, b"test_seed_tmm"); + let resource_account_signer = create_signer_for_test(resource_account_address); + let constructor_ref = object::create_named_object(&resource_account_signer, SEED_NAME); + let signer = object::generate_signer(&constructor_ref); + init_state(&signer, 1); + ownable::new(&signer, signer::address_of(caller)); + pausable::new(&signer, signer::address_of(caller)); + } + + #[test_only] + public fun set_paused(pauser: &signer) { + pausable::test_pause(pauser, object::address_to_object(get_object_address())); + } + + #[test_only] + public fun set_message_body_version(message_body_version: u32) acquires State { + borrow_global_mut(get_object_address()).message_body_version = message_body_version; + } + + // ----------------------------- + // ---------- Getters ---------- + // ----------------------------- + + #[test(owner = @token_messenger_minter)] + fun test_is_initialized(owner: &signer) { + init_test_state(owner); + assert!(is_initialized() == true, 0); + } + + #[test(owner = @token_messenger_minter)] + fun test_get_message_body_version(owner: &signer) acquires State { + init_test_state(owner); + assert!(get_message_body_version() == 1, 0); + } + + #[test(owner = @token_messenger_minter)] + fun test_get_remote_token_messenger(owner: &signer) acquires State { + init_test_state(owner); + let domain = 4; + let token_messenger = @0xfab; + smart_table::add( + &mut borrow_global_mut(get_object_address()).remote_token_messengers, + domain, + token_messenger + ); + assert!(get_remote_token_messenger(domain) == token_messenger, 0); + } + + #[test(owner = @token_messenger_minter)] + fun test_is_remote_token_messenger_set_for_domain(owner: &signer) acquires State { + init_test_state(owner); + let domain = 4; + let token_messenger = @0xfab; + smart_table::add( + &mut borrow_global_mut(get_object_address()).remote_token_messengers, + domain, + token_messenger + ); + assert!(is_remote_token_messenger_set_for_domain(domain), 0); + } + + #[test(owner = @token_messenger_minter)] + fun test_get_burn_limit_per_message_for_token(owner: &signer) acquires State { + init_test_state(owner); + let token = @0xfab; + let expected_limit = 300; + smart_table::add( + &mut borrow_global_mut(get_object_address()).burn_limits_per_message, + token, + expected_limit + ); + let (exists, limit) = get_max_burn_limit_per_message_for_token(token); + assert!(exists, 0); + assert!(limit == expected_limit, 0); + } + + #[test(owner = @token_messenger_minter)] + fun test_is_remote_token(owner: &signer) acquires State { + init_test_state(owner); + let remote_token = @0xfab; + let remote_domain = 4; + let local_token = @0xfac; + smart_table::add( + &mut borrow_global_mut(get_object_address()).remote_tokens_to_local_tokens, + hash_remote_domain_and_token(remote_domain, remote_token), + local_token + ); + assert!(local_token_exists(remote_domain, remote_token), 0); + } + + #[test(owner = @token_messenger_minter)] + fun test_get_remote_token(owner: &signer) acquires State { + init_test_state(owner); + let remote_token = @0xfab; + let remote_domain = 4; + let local_token = @0xfac; + smart_table::add( + &mut borrow_global_mut(get_object_address()).remote_tokens_to_local_tokens, + hash_remote_domain_and_token(remote_domain, remote_token), + local_token + ); + assert!(get_local_token(remote_domain, remote_token) == local_token, 0); + } + + #[test(owner = @token_messenger_minter)] + fun test_is_paused(owner: &signer) { + init_test_state(owner); + assert!(is_paused() == false, 0); + } + + #[test(owner = @token_messenger_minter)] + fun test_get_token_controller(owner: &signer) acquires State { + init_test_state(owner); + assert!(get_token_controller() == @0x0, 0); + } + + #[test(owner = @token_messenger_minter)] + fun test_get_num_remote_token_messengers(owner: &signer) acquires State { + init_test_state(owner); + let domain = 4; + let token_messenger = @0xfab; + smart_table::add( + &mut borrow_global_mut(get_object_address()).remote_token_messengers, + domain, + token_messenger + ); + assert!(get_num_remote_token_messengers() == 1, 0); + } + + #[test(owner = @token_messenger_minter)] + fun test_hash_remote_domain_and_token(owner: &signer) { + init_test_state(owner); + let hash = hash_remote_domain_and_token(4, @0xfac); + assert!(hash == @0x8ae28a8d79ea231bafe63fb643c45dce060b33a1c2e122d161c4d8b75997436, 0); + } + + #[test(owner = @token_messenger_minter)] + fun test_get_num_linked_tokens(owner: &signer) acquires State { + init_test_state(owner); + smart_table::add( + &mut borrow_global_mut(get_object_address()).remote_tokens_to_local_tokens, @0x100, @0x101 + ); + assert!(get_num_linked_tokens() == 1, 0) + } + + + // ----------------------------- + // ---------- Setters ---------- + // ----------------------------- + + #[test(owner = @token_messenger_minter)] + fun test_add_remote_token_messenger(owner: &signer) acquires State { + init_test_state(owner); + let domain = 5; + let token_messenger = @0xcaf; + add_remote_token_messenger(domain, token_messenger); + assert!(get_remote_token_messenger(domain) == token_messenger, 0); + } + + #[test(owner = @token_messenger_minter)] + fun test_remove_remote_token_messenger(owner: &signer) acquires State { + init_test_state(owner); + let domain = 5; + let token_messenger = @0xcaf; + add_remote_token_messenger(domain, token_messenger); + assert!(get_remote_token_messenger(domain) == token_messenger, 0); + + remove_remote_token_messenger(domain); + assert!(!is_remote_token_messenger_set_for_domain(domain), 0) + } + + #[test(owner = @token_messenger_minter)] + fun test_add_remote_token(owner: &signer) acquires State { + init_test_state(owner); + let remote_token = @0xfab; + let remote_domain = 4; + let local_token = @0xfac; + add_local_token_for_remote_token(remote_domain, remote_token, local_token); + assert!(get_local_token(remote_domain, remote_token) == local_token, 0); + } + + #[test(owner = @token_messenger_minter)] + fun test_remove_remote_token(owner: &signer) acquires State { + init_test_state(owner); + let remote_token = @0xfab; + let remote_domain = 4; + let local_token = @0xfac; + add_local_token_for_remote_token(remote_domain, remote_token, local_token); + assert!(get_local_token(remote_domain, remote_token) == local_token, 0); + + remove_local_token_for_remote_token(remote_domain, remote_token); + assert!(!local_token_exists(remote_domain, remote_token), 0); + } + + #[test(owner = @token_messenger_minter)] + fun test_set_max_burn_limit_for_token(owner: &signer) acquires State { + init_test_state(owner); + let token = @0xcaf; + let limit = 300; + set_max_burn_limit_per_message_for_token(token, limit); + assert!( + *smart_table::borrow(&borrow_global(get_object_address()).burn_limits_per_message, token) == limit, + 0 + ) + } + + #[test(owner = @token_messenger_minter)] + fun test_set_token_controller(owner: &signer) acquires State { + init_test_state(owner); + let new_token_controller = @0xfad; + set_token_controller(new_token_controller); + assert!(get_token_controller() == new_token_controller, 0); + } +} diff --git a/packages/token_messenger_minter/sources/token_messenger/burn_message.move b/packages/token_messenger_minter/sources/token_messenger/burn_message.move new file mode 100644 index 0000000..7c523ad --- /dev/null +++ b/packages/token_messenger_minter/sources/token_messenger/burn_message.move @@ -0,0 +1,171 @@ +/// Copyright (c) 2024, Circle Internet Group, Inc. +/// All rights reserved. +/// +/// SPDX-License-Identifier: Apache-2.0 +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. + +/// Module for serializing outgoing and deserializing deposit for burn messages +/// +/// Message is structured in the following order: +/// -------------------------------------------------- +/// Field Bytes Type Index +/// version 4 uint32 0 +/// burnToken 32 bytes32 4 +/// mintRecipient 32 bytes32 36 +/// amount 32 uint256 68 +/// messageSender 32 bytes32 100 +/// -------------------------------------------------- +module token_messenger_minter::burn_message { + // Built-in Modules + use std::error; + use std::vector; + + // Package Modules + use message_transmitter::serialize; + use message_transmitter::deserialize; + + // Friend Modules + friend token_messenger_minter::token_messenger; + + // Constants + const VERSION_INDEX: u64 = 0; + const VERSION_LEN: u64 = 4; + const BURN_TOKEN_INDEX: u64 = 4; + const BURN_TOKEN_LEN: u64 = 32; + const MINT_RECIPIENT_INDEX: u64 = 36; + const MINT_RECIPIENT_LEN: u64 = 32; + const AMOUNT_INDEX: u64 = 68; + const AMOUNT_LEN: u64 = 32; + const MSG_SENDER_INDEX: u64 = 100; + const MSG_SENDER_LEN: u64 = 32; + + // 4 byte version + 32 bytes burn_token + 32 bytes mint_recipient + 32 bytes amount + 32 bytes message_sender + const BURN_MESSAGE_LEN: u64 = 132; + + // Errors + const EINVALID_MESSAGE_LENGTH: u64 = 1; + + public(friend) fun get_version(message: &vector): u32 { + deserialize::deserialize_u32(message, VERSION_INDEX, VERSION_LEN) + } + + public(friend) fun get_burn_token(message: &vector): address { + deserialize::deserialize_address(message, BURN_TOKEN_INDEX, BURN_TOKEN_LEN) + } + + public(friend) fun get_mint_recipient(message: &vector): address { + deserialize::deserialize_address(message, MINT_RECIPIENT_INDEX, MINT_RECIPIENT_LEN) + } + + public(friend) fun get_amount(message: &vector): u256 { + deserialize::deserialize_u256(message, AMOUNT_INDEX, AMOUNT_LEN) + } + + public(friend) fun get_message_sender(message: &vector): address { + deserialize::deserialize_address(message, MSG_SENDER_INDEX, MSG_SENDER_LEN) + } + + public(friend) fun serialize( + version: u32, + burn_token: address, + mint_recipient: address, + amount: u256, + message_sender: address + ): vector { + let result = vector::empty(); + vector::append(&mut result, serialize::serialize_u32(version)); + vector::append(&mut result, serialize::serialize_address(burn_token)); + vector::append(&mut result, serialize::serialize_address(mint_recipient)); + vector::append(&mut result, serialize::serialize_u256(amount)); + vector::append(&mut result, serialize::serialize_address(message_sender)); + result + } + + public(friend) fun validate_message(message: &vector) { + assert!(vector::length(message) == BURN_MESSAGE_LEN, error::invalid_argument(EINVALID_MESSAGE_LENGTH)); + } + + // ----------------------------- + // -------- Unit Tests --------- + // ----------------------------- + + // Following test message is based on -> + // ETH (Source): https://sepolia.etherscan.io/tx/0x151c196be83e2fcbd84204a521ee0a758a5e7335ac7d2c0958ef840fd485dc61 + // AVAX (Destination): https://testnet.snowtrace.io/tx/0xa98d5c33b7571609875f56ae148563411377392c87b9e8cebd483683a0e36413 + // Burn Token: 0x0000000000000000000000001c7D4B196Cb0C7B01d743Fbc6116a902379C7238 + // Mint Recipient: 0x0000000000000000000000001F26414439C8D03FC4B9CA912CEFD5CB508C9605 + // Amount: 1214 + // Sender: 0x0000000000000000000000003b61AbEe91852714E4e99b09a1AF3e9C13893eF1 + + #[test_only] + fun get_raw_test_message(): vector { + x"000000000000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c72380000000000000000000000001f26414439c8d03fc4b9ca912cefd5cb508c960500000000000000000000000000000000000000000000000000000000000004be0000000000000000000000003b61abee91852714e4e99b09a1af3e9c13893ef1" + } + + #[test_only] const VERSION: u32 = 0; + #[test_only] const BURN_TOKEN: address = @0x0000000000000000000000001c7D4B196Cb0C7B01d743Fbc6116a902379C7238; + #[test_only] const MINT_RECIPIENT: address = @0x0000000000000000000000001F26414439C8D03FC4B9CA912CEFD5CB508C9605; + #[test_only] const AMOUNT: u256 = 1214; + #[test_only] const MESSAGE_SENDER: address = @0x0000000000000000000000003b61AbEe91852714E4e99b09a1AF3e9C13893eF1; + + #[test] + fun test_burn_message_serialization() { + let original_message = get_raw_test_message(); + let serialized_message = serialize(VERSION, BURN_TOKEN, MINT_RECIPIENT, AMOUNT, MESSAGE_SENDER); + assert!(original_message == serialized_message, 0); + } + + #[test] + fun test_get_version() { + let original_message = get_raw_test_message(); + assert!(get_version(&original_message) == VERSION, 0); + } + + #[test] + fun test_get_burn_token() { + let original_message = get_raw_test_message(); + assert!(get_burn_token(&original_message) == BURN_TOKEN, 0); + } + + #[test] + fun test_get_mint_recipient() { + let original_message = get_raw_test_message(); + assert!(get_mint_recipient(&original_message) == MINT_RECIPIENT, 0); + } + + #[test] + fun test_get_amount() { + let original_message = get_raw_test_message(); + assert!(get_amount(&original_message) == AMOUNT, 0); + } + + #[test] + fun test_get_message_sender() { + let original_message = get_raw_test_message(); + assert!(get_message_sender(&original_message) == MESSAGE_SENDER, 0); + } + + #[test] + fun test_validate_message_success() { + let original_message = get_raw_test_message(); + validate_message(&original_message) + } + + #[test] + #[expected_failure(abort_code = 0x10001, location = Self)] + fun test_validate_message_not_sufficient_length() { + let invalid_message = vector[5, 2, 32, 2, 21, 23]; + validate_message(&invalid_message) + } +} diff --git a/packages/token_messenger_minter/sources/token_messenger/token_messenger.move b/packages/token_messenger_minter/sources/token_messenger/token_messenger.move new file mode 100644 index 0000000..7e9b8b6 --- /dev/null +++ b/packages/token_messenger_minter/sources/token_messenger/token_messenger.move @@ -0,0 +1,1127 @@ +/// Copyright (c) 2024, Circle Internet Group, Inc. +/// All rights reserved. +/// +/// SPDX-License-Identifier: Apache-2.0 +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. + +module token_messenger_minter::token_messenger { + // Built-in Modules + use std::error; + use std::option; + use std::signer; + use aptos_framework::event; + use aptos_framework::fungible_asset::{Self, FungibleAsset}; + use aptos_framework::object::object_address; + use aptos_extensions::ownable; + use message_transmitter::message; + use token_messenger_minter::token_messenger_minter; + + // Package Modules + use token_messenger_minter::state; + use token_messenger_minter::token_minter; + use token_messenger_minter::burn_message; + use message_transmitter::message_transmitter::{Self, Receipt}; + + // Errors + const EINVALID_AMOUNT: u64 = 1; + const EINVALID_MINT_RECIPIENT_ADDRESS: u64 = 2; + const EINVALID_DESTINATION_CALLER_ADDRESS: u64 = 3; + const ENOT_ORIGINAL_SENDER: u64 = 4; + const ETOKEN_MESSENGER_ALREADY_SET: u64 = 5; + const ENO_TOKEN_MESSENGER_SET_FOR_DOMAIN: u64 = 6; + const ENOT_TOKEN_MESSENGER: u64 = 7; + const EINVALID_MESSAGE_BODY_VERSION: u64 = 8; + const ERECIPIENT_NOT_TOKEN_MESSENGER: u64 = 9; + const EUNSUPPORTED_DESTINATION_DOAMIN: u64 = 10; + const EINVALID_TOKEN_MESSENGER_ADDRESS: u64 = 11; + + // Constants + const MAX_U64: u256 = 18_446_744_073_709_551_615; + + // ----------------------------- + // ---------- Events ----------- + // ----------------------------- + + #[event] + struct DepositForBurn has drop, store { + nonce: u64, + burn_token: address, + amount: u64, + depositor: address, + mint_recipient: address, + destination_domain: u32, + destination_token_messenger: address, + destination_caller: address + } + + #[event] + struct MintAndWithdraw has drop, store { + mint_recipient: address, + amount: u64, + mint_token: address + } + + #[event] + struct RemoteTokenMessengerAdded has drop, store { + domain: u32, + token_messenger: address + } + + #[event] + struct RemoteTokenMessengerRemoved has drop, store { + domain: u32, + token_messenger: address + } + + // ----------------------------- + // --- Public View Functions --- + // ----------------------------- + + #[view] + public fun message_body_version(): u32 { + state::get_message_body_version() + } + + #[view] + public fun remote_token_messenger(domain: u32): address { + state::get_remote_token_messenger(domain) + } + + #[view] + public fun num_remote_token_messengers(): u64 { + state::get_num_remote_token_messengers() + } + + #[view] + public fun max_burn_amount_per_message(token: address): u64 { + let (_, max_burn_amount) = state::get_max_burn_limit_per_message_for_token(token); + max_burn_amount + } + + // ----------------------------- + // ----- Public Functions ------ + // ----------------------------- + + /// Burns token from sender address, to be minted on destination domain. Emits `DepositForBurn` event. + /// Aborts if: + /// - amount is zero + /// - destination domain has no TokenMessenger registered + /// - mint recipient is zero address + /// - TokenMinter aborts (e.g burn token is not supported) + /// - MessageTransmitter aborts + public fun deposit_for_burn( + caller: &signer, + asset: FungibleAsset, + destination_domain: u32, + mint_recipient: address, + ): u64 { + return deposit_for_burn_helper( + caller, + asset, + destination_domain, + mint_recipient, + @0x0 + ) + } + + /// Burns token from sender address, to be minted on destination domain. The mint on the destination domain must + /// be called by `destinationCaller`. Emits `DepositForBurn` event. + /// Aborts if: + /// - amount is zero + /// - destination caller is zero address + /// - destination domain has no TokenMessenger registered + /// - mint recipient is zero address + /// - TokenMinter aborts (e.g burn token is not supported) + /// - MessageTransmitter aborts + public fun deposit_for_burn_with_caller( + caller: &signer, + asset: FungibleAsset, + destination_domain: u32, + mint_recipient: address, + destination_caller: address + ): u64 { + assert!(destination_caller != @0x0, error::invalid_argument(EINVALID_DESTINATION_CALLER_ADDRESS)); + return deposit_for_burn_helper( + caller, + asset, + destination_domain, + mint_recipient, + destination_caller + ) + } + + /// Replaces the given burn message with new new_mint_recipient and/or destination caller. + /// The replaced message reuses the same nonce making both the existing and new messages valid. + /// Serializes the new burn message and emits `DepositForBurn` event. + /// Aborts if: + /// - caller is not the original sender + /// - new mint recipient is zero address + /// - MessageTransmitter aborts + public fun replace_deposit_for_burn( + caller: &signer, + original_message: &vector, + original_attestation: &vector, + new_destination_caller: &option::Option
, + new_mint_recipient: &option::Option
+ ) { + // Validate message + message::validate_message(original_message); + + // Validate message body (burn message) + let burn_msg = message::get_message_body(original_message); + burn_message::validate_message(&burn_msg); + + // Only original sender can replace the message + let original_message_sender = burn_message::get_message_sender(&burn_msg); + assert!(signer::address_of(caller) == original_message_sender, error::permission_denied(ENOT_ORIGINAL_SENDER)); + + let burn_token = burn_message::get_burn_token(&burn_msg); + let amount = burn_message::get_amount(&burn_msg); + let old_mint_recipient = burn_message::get_mint_recipient(&burn_msg); + let message_body = option::none>(); + + // Create new message body based on updated mint recipient + let mint_recipient = option::get_with_default(new_mint_recipient, old_mint_recipient); + assert!(mint_recipient != @0x0, error::invalid_argument(EINVALID_MINT_RECIPIENT_ADDRESS)); + let new_burn_message = burn_message::serialize( + message_body_version(), + burn_token, + mint_recipient, + amount, + original_message_sender + ); + option::fill(&mut message_body, new_burn_message); + + // Use Token Messenger Minter's signer for calling replace_message + let token_messenger_minter_signer = token_messenger_minter::get_signer(); + message_transmitter::replace_message( + &token_messenger_minter_signer, + original_message, + original_attestation, + &message_body, + new_destination_caller + ); + + let destination_caller = option::get_with_default( + new_destination_caller, + message::get_destination_caller(original_message) + ); + event::emit(DepositForBurn { + nonce: message::get_nonce(original_message), + burn_token, + amount: (amount as u64), + depositor: original_message_sender, + mint_recipient, + destination_domain: message::get_destination_domain_id(original_message), + destination_token_messenger: message::get_recipient_address(original_message), + destination_caller + }); + } + + /// Handles incoming message based on the receipt generated by local message transmitter. For a burn message, mints + /// the associated token to the requested recipient on the local domain. Call local message transmitter's + /// `complete_receive` function to destroy receipt. Emits `MintAndWithdraw` event. + /// Aborts if: + /// - sender in receipt is not a remote token messenger + /// - recipient in receipt is not `TokenMessengerMinter` object + /// - message body is not a valid burn message + /// - message body version is invalid + /// - mint function in `TokenMinter` aborts + public fun handle_receive_message(receipt: Receipt): bool { + let (sender, recipient, remote_domain, message_body) = message_transmitter::get_receipt_details(&receipt); + + // Validate `recipient` in receipt is the `TokenMessengerMinter` contract + assert!(recipient == state::get_object_address(), error::invalid_argument(ERECIPIENT_NOT_TOKEN_MESSENGER)); + + // Validate `sender` in receipt is a remote token messenger for `remote_domain` + validate_remote_token_messenger(remote_domain, sender); + + // Verify message body is a valid burn message + burn_message::validate_message(&message_body); + + // Verify message body version matches the one included in the burn message + let message_version = burn_message::get_version(&message_body); + assert!(message_version == message_body_version(), error::invalid_argument(EINVALID_MESSAGE_BODY_VERSION)); + + // Mint the given amount of tokens at recipient's address + let mint_recipient = burn_message::get_mint_recipient(&message_body); + let burn_token = burn_message::get_burn_token(&message_body); + let amount = burn_message::get_amount(&message_body); + assert!(amount <= MAX_U64, error::invalid_argument(EINVALID_AMOUNT)); + let mint_token = token_minter::mint( + remote_domain, + burn_token, + mint_recipient, + (amount as u64) + ); + event::emit(MintAndWithdraw { + mint_recipient, + amount: (amount as u64), + mint_token + }); + + // Call message transmitter to emit `MessageReceived` event and destroy receipt + let token_messenger_minter_signer = token_messenger_minter::get_signer(); + message_transmitter::complete_receive_message(&token_messenger_minter_signer, receipt) + } + + /// Add TokenMessenger for a remote domain. Emits `RemoteTokenMessengerAdded` event + /// Aborts if: + /// - caller is not the owner + /// - TokenMessenger is zero address + /// - there is already a TokenMessenger set for domain + entry fun add_remote_token_messenger(caller: &signer, domain: u32, token_messenger: address) { + ownable::assert_is_owner(caller, state::get_object_address()); + assert!(token_messenger != @0x0, error::invalid_argument(EINVALID_TOKEN_MESSENGER_ADDRESS)); + assert!( + !state::is_remote_token_messenger_set_for_domain(domain), + error::already_exists(ETOKEN_MESSENGER_ALREADY_SET) + ); + state::add_remote_token_messenger(domain, token_messenger); + event::emit(RemoteTokenMessengerAdded { domain, token_messenger } ); + } + + /// Remove TokenMessenger for a remote domain. Emits `RemoteTokenMessengerRemoved` event + /// Aborts if: + /// - caller is not the owner + /// - there is no TokenMessenger set for domain + entry fun remove_remote_token_messenger(caller: &signer, domain: u32) { + ownable::assert_is_owner(caller, state::get_object_address()); + assert!( + state::is_remote_token_messenger_set_for_domain(domain), + error::invalid_argument(ENO_TOKEN_MESSENGER_SET_FOR_DOMAIN) + ); + let token_messenger = state::remove_remote_token_messenger(domain); + event::emit(RemoteTokenMessengerRemoved { domain, token_messenger } ); + } + + // ----------------------------- + // ----- Private Functions ----- + // ----------------------------- + + fun deposit_for_burn_helper( + caller: &signer, + asset: FungibleAsset, + destination_domain: u32, + mint_recipient: address, + destination_caller: address + ): u64 { + let amount = fungible_asset::amount(&asset); + assert!(amount > 0, error::invalid_argument(EINVALID_AMOUNT)); + assert!(mint_recipient != @0x0, error::invalid_argument(EINVALID_MINT_RECIPIENT_ADDRESS)); + + // Get burn token address from asset metadata + let metadata = fungible_asset::metadata_from_asset(&asset); + let burn_token = object_address(&metadata); + + // Verify the destination domain is supported + assert!( + state::is_remote_token_messenger_set_for_domain(destination_domain), + error::invalid_argument(EUNSUPPORTED_DESTINATION_DOAMIN) + ); + + let destination_token_messenger = state::get_remote_token_messenger(destination_domain); + token_minter::burn(burn_token, asset); + + let serialized_burn_message = burn_message::serialize( + message_body_version(), + burn_token, + mint_recipient, + (amount as u256), + signer::address_of(caller) + ); + let nonce = send_deposit_for_burn( + destination_domain, + destination_token_messenger, + destination_caller, + &serialized_burn_message + ); + event::emit(DepositForBurn { + nonce, + burn_token, + amount, + depositor: signer::address_of(caller), + mint_recipient, + destination_domain, + destination_token_messenger, + destination_caller + }); + nonce + } + + /// Execute `send_message` or `send_message_with_caller` on local MessageTransmitter using + /// TokenMessengerMinter'signer + fun send_deposit_for_burn( + destination_domain: u32, + destination_token_messenger: address, + destination_caller: address, + burn_message: &vector, + ): u64 { + let token_messenger_minter_signer = token_messenger_minter::get_signer(); + if (destination_caller == @0x0) { + message_transmitter::send_message( + &token_messenger_minter_signer, + destination_domain, + destination_token_messenger, + burn_message + ) + } else { + message_transmitter::send_message_with_caller( + &token_messenger_minter_signer, + destination_domain, + destination_token_messenger, + destination_caller, + burn_message + ) + } + } + + fun validate_remote_token_messenger(domain: u32, token_messenger: address) { + assert!( + state::is_remote_token_messenger_set_for_domain(domain), + error::invalid_argument(ENO_TOKEN_MESSENGER_SET_FOR_DOMAIN) + ); + assert!( + state::get_remote_token_messenger(domain) == token_messenger, + error::permission_denied(ENOT_TOKEN_MESSENGER) + ); + } + + // ----------------------------- + // -------- Unit Tests --------- + // ----------------------------- + + #[test_only] + use std::hash; + #[test_only] + use std::vector; + #[test_only] + use aptos_std::from_bcs; + #[test_only] + use aptos_framework::account::create_signer_for_test; + #[test_only] + use aptos_extensions::pausable; + #[test_only] + use message_transmitter::message_transmitter::MessageSent; + #[test_only] + use message_transmitter::state as mt_state; + #[test_only] + use stablecoin::stablecoin::stablecoin_address; + + // Test Helpers + + #[test_only] + const REMOTE_DOMAIN: u32 = 4; + #[test_only] + const REMOTE_TOKEN_MESSENGER: address = @0xe786e705b98581cbf28488ce4ae116db0918e1f7eb1877d07bf0995cf67724ef; + #[test_only] + const REMOTE_STABLECOIN_ADDRESS: address = @0xcafe; + + #[test_only] + fun init_test_token_messenger(owner: &signer) { + // Initialize Message Transmitter + let mt_deployer = create_signer_for_test(@deployer); + message_transmitter::initialize_test_message_transmitter(&mt_deployer); + + // Initialize Token Messenger and Minter + token_minter::init_test_token_minter(owner); + state::add_remote_token_messenger(REMOTE_DOMAIN, REMOTE_TOKEN_MESSENGER); + token_minter::mint(REMOTE_DOMAIN, REMOTE_STABLECOIN_ADDRESS, signer::address_of(owner), 1_000_000); + } + + #[test_only] + fun get_signer_balance(account: &signer): u64 { + let account_address = signer::address_of(account); + token_minter::get_account_balance(account_address) + } + + #[test_only] + fun get_valid_deposit_for_burn_message_and_attestation(): (vector, vector) { + let burn_message = burn_message::serialize( + message_body_version(), + from_bcs::to_address(hash::sha3_256(b"burn_token")), + from_bcs::to_address(hash::sha3_256(b"mint_recipient")), + 85720194, + @deployer + ); + let original_message = message::serialize( + 0, + 9, + REMOTE_DOMAIN, + 7384, + signer::address_of(&token_messenger_minter::get_signer()), + REMOTE_TOKEN_MESSENGER, + @0x1CD223dBC9ff35fF6B29dAB2339ACC842BF58cCb, + &burn_message, + ); + let original_attestation = x"278a9949e5e022256553a072ef00f9e5928235354c79f277539ec56174becea7133ac670f9fc6c38bca534e14aca05e8271112f5d8a9da3375c56d399790d5ea1c"; + (original_message, original_attestation) + } + + #[test_only] + fun get_valid_receive_message_and_attestation(): (vector, vector) { + let burn_message = burn_message::serialize( + message_body_version(), + REMOTE_STABLECOIN_ADDRESS, + from_bcs::to_address(hash::sha3_256(b"mint_recipient")), + 8572, + from_bcs::to_address(hash::sha3_256(b"sender_address")) + ); + let message_bytes = message::serialize( + 0, + REMOTE_DOMAIN, + 9, + 7384, + REMOTE_TOKEN_MESSENGER, + signer::address_of(&token_messenger_minter::get_signer()), + from_bcs::to_address(hash::sha3_256(b"destination_caller")), + &burn_message, + ); + let attestation = x"2718d6be14108dfef8f0a24a5dc7a4623dd8139420c0b695baa8178cd2f48d8a70484a52ec5d71c9575685b0319927e669355139c099601d29e3f9363c2d20ba1c"; + (message_bytes, attestation) + } + + // Deposit For Burn Tests + + #[test(owner = @deployer)] + fun test_deposit_for_burn_success(owner: &signer) { + init_test_token_messenger(owner); + let amount = 3482; + let mint_recipient = from_bcs::to_address(hash::sha3_256(b"mint_recipient")); + let burn_token = stablecoin_address(); + let account_balance = get_signer_balance(owner); + let asset = token_minter::withdraw_from_primary_store(owner, amount, burn_token); + let nonce = deposit_for_burn(owner, asset, REMOTE_DOMAIN, mint_recipient); + assert!(event::was_event_emitted(&DepositForBurn { + nonce, + burn_token, + amount, + depositor: signer::address_of(owner), + mint_recipient, + destination_domain: REMOTE_DOMAIN, + destination_token_messenger: from_bcs::to_address(hash::sha3_256(b"token_messenger")), + destination_caller: @0x0 + }), 0); + let expected_account_balance = account_balance - amount; + assert!(get_signer_balance(owner) == expected_account_balance, 0); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x10001, location = Self)] + fun test_deposit_for_burn_invalid_amount(owner: &signer) { + init_test_token_messenger(owner); + let amount = 0; + let mint_recipient = from_bcs::to_address(hash::sha3_256(b"mint_recipient")); + let burn_token = stablecoin_address(); + let asset = token_minter::withdraw_from_primary_store(owner, amount, burn_token); + deposit_for_burn(owner, asset, REMOTE_DOMAIN, mint_recipient); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x1000a, location = Self)] + fun test_deposit_for_burn_invalid_destination_domain(owner: &signer) { + init_test_token_messenger(owner); + let amount = 51; + let destination_domain = 3; + let mint_recipient = from_bcs::to_address(hash::sha3_256(b"mint_recipient")); + let burn_token = stablecoin_address(); + let asset = token_minter::withdraw_from_primary_store(owner, amount, burn_token); + deposit_for_burn(owner, asset, destination_domain, mint_recipient,); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x10002, location = Self)] + fun test_deposit_for_burn_invalid_mint_recipient(owner: &signer) { + init_test_token_messenger(owner); + let amount = 51; + let mint_recipient = @0x0; + let burn_token = stablecoin_address(); + let asset = token_minter::withdraw_from_primary_store(owner, amount, burn_token); + deposit_for_burn(owner, asset, REMOTE_DOMAIN, mint_recipient); + } + + #[test(owner = @deployer, mt_signer = @message_transmitter)] + #[expected_failure(abort_code = pausable::ENOT_PAUSER, location = pausable)] + fun test_deposit_for_burn_message_transmitter_aborts(owner: &signer, mt_signer: &signer) { + init_test_token_messenger(owner); + mt_state::set_paused(mt_signer); + let amount = 51; + let mint_recipient = from_bcs::to_address(hash::sha3_256(b"mint_recipient")); + let burn_token = stablecoin_address(); + let asset = token_minter::withdraw_from_primary_store(owner, amount, burn_token); + deposit_for_burn(owner, asset, REMOTE_DOMAIN, mint_recipient); + } + + // Deposit For Burn With Caller Tests + + #[test(owner = @deployer)] + fun test_deposit_for_burn_with_caller_success(owner: &signer) { + init_test_token_messenger(owner); + let account_balance = get_signer_balance(owner); + let amount = 3482; + let mint_recipient = from_bcs::to_address(hash::sha3_256(b"mint_recipient")); + let burn_token = stablecoin_address(); + let destination_caller = from_bcs::to_address(hash::sha3_256(b"destination_caller")); + let asset = token_minter::withdraw_from_primary_store(owner, amount, burn_token); + let nonce = deposit_for_burn_with_caller( + owner, + asset, + REMOTE_DOMAIN, + mint_recipient, + destination_caller + ); + assert!(event::was_event_emitted(&DepositForBurn { + nonce, + burn_token, + amount, + depositor: signer::address_of(owner), + mint_recipient, + destination_domain: REMOTE_DOMAIN, + destination_token_messenger: from_bcs::to_address(hash::sha3_256(b"token_messenger")), + destination_caller + }), 0); + let expected_account_balance = account_balance - amount; + assert!(get_signer_balance(owner) == expected_account_balance, 0); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x10001, location = Self)] + fun test_deposit_for_burn_with_caller_invalid_amount(owner: &signer) { + init_test_token_messenger(owner); + let amount = 0; + let mint_recipient = from_bcs::to_address(hash::sha3_256(b"mint_recipient")); + let burn_token = stablecoin_address(); + let destination_caller = from_bcs::to_address(hash::sha3_256(b"destination_caller")); + let asset = token_minter::withdraw_from_primary_store(owner, amount, burn_token); + deposit_for_burn_with_caller( + owner, + asset, + REMOTE_DOMAIN, + mint_recipient, + destination_caller + ); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x1000a, location = Self)] + fun test_deposit_for_burn_with_caller_invalid_destination_domain(owner: &signer) { + init_test_token_messenger(owner); + let amount = 12; + let destination_domain = 3; + let mint_recipient = from_bcs::to_address(hash::sha3_256(b"mint_recipient")); + let burn_token = stablecoin_address(); + let destination_caller = from_bcs::to_address(hash::sha3_256(b"destination_caller")); + let asset = token_minter::withdraw_from_primary_store(owner, amount, burn_token); + deposit_for_burn_with_caller( + owner, + asset, + destination_domain, + mint_recipient, + destination_caller + ); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x10002, location = Self)] + fun test_deposit_for_burn_with_caller_invalid_mint_recipient(owner: &signer) { + init_test_token_messenger(owner); + let amount = 12; + let mint_recipient = @0x0; + let burn_token = stablecoin_address(); + let destination_caller = from_bcs::to_address(hash::sha3_256(b"destination_caller")); + let asset = token_minter::withdraw_from_primary_store(owner, amount, burn_token); + deposit_for_burn_with_caller( + owner, + asset, + REMOTE_DOMAIN, + mint_recipient, + destination_caller + ); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x10003, location = Self)] + fun test_deposit_for_burn_with_caller_invalid_destination_caller(owner: &signer) { + init_test_token_messenger(owner); + let amount = 12; + let mint_recipient = from_bcs::to_address(hash::sha3_256(b"mint_recipient")); + let burn_token = stablecoin_address(); + let destination_caller = @0x0; + let asset = token_minter::withdraw_from_primary_store(owner, amount, burn_token); + deposit_for_burn_with_caller( + owner, + asset, + REMOTE_DOMAIN, + mint_recipient, + destination_caller + ); + } + + #[test(owner = @deployer, mt_signer = @message_transmitter)] + #[expected_failure(abort_code = pausable::ENOT_PAUSER, location = pausable)] + fun test_deposit_for_burn_with_caller_message_transmitter_aborts(owner: &signer, mt_signer: &signer) { + init_test_token_messenger(owner); + mt_state::set_paused(mt_signer); + let amount = 12; + let mint_recipient = from_bcs::to_address(hash::sha3_256(b"mint_recipient")); + let burn_token = stablecoin_address(); + let destination_caller = from_bcs::to_address(hash::sha3_256(b"destination_caller")); + let asset = token_minter::withdraw_from_primary_store(owner, amount, burn_token); + deposit_for_burn_with_caller( + owner, + asset, + REMOTE_DOMAIN, + mint_recipient, + destination_caller + ); + } + + // Replace Deposit For Burn Tests + + #[test(owner = @deployer)] + fun test_replace_deposit_for_burn_new_destination_caller(owner: &signer) { + init_test_token_messenger(owner); + let original_account_balance = get_signer_balance(owner); + let new_destination_caller = @0xfab; + let (original_message, original_attestation) = get_valid_deposit_for_burn_message_and_attestation(); + replace_deposit_for_burn( + owner, + &original_message, + &original_attestation, + &option::some(new_destination_caller), + &option::none() + ); + + let burn_msg = message::get_message_body(&original_message); + assert!(event::was_event_emitted(&DepositForBurn { + nonce: message::get_nonce(&original_message), + burn_token: burn_message::get_burn_token(&burn_msg), + amount: (burn_message::get_amount(&burn_msg) as u64), + depositor: signer::address_of(owner), + mint_recipient: burn_message::get_mint_recipient(&burn_msg), + destination_domain: REMOTE_DOMAIN, + destination_token_messenger: REMOTE_TOKEN_MESSENGER, + destination_caller: new_destination_caller + }), 0); + let message_sent_event = vector::borrow(&event::emitted_events(), 0); + let message = message_transmitter::get_message_from_event(message_sent_event); + let new_burn_message = message::get_message_body(&message); + assert!(burn_message::get_version(&new_burn_message) == message_body_version(), 0); + assert!(get_signer_balance(owner) == original_account_balance, 0); + } + + #[test(owner = @deployer)] + fun test_replace_deposit_for_burn_new_mint_recipient(owner: &signer) { + init_test_token_messenger(owner); + let new_mint_recipient = @0xfab; + let original_account_balance = get_signer_balance(owner); + let (original_message, original_attestation) = get_valid_deposit_for_burn_message_and_attestation(); + replace_deposit_for_burn( + owner, + &original_message, + &original_attestation, + &option::none(), + &option::some(new_mint_recipient), + ); + + let burn_msg = message::get_message_body(&original_message); + assert!(event::was_event_emitted(&DepositForBurn { + nonce: message::get_nonce(&original_message), + burn_token: burn_message::get_burn_token(&burn_msg), + amount: (burn_message::get_amount(&burn_msg) as u64), + depositor: signer::address_of(owner), + mint_recipient: new_mint_recipient, + destination_domain: REMOTE_DOMAIN, + destination_token_messenger: REMOTE_TOKEN_MESSENGER, + destination_caller: message::get_destination_caller(&original_message) + }), 0); + let message_sent_event = vector::borrow(&event::emitted_events(), 0); + let message = message_transmitter::get_message_from_event(message_sent_event); + let new_burn_message = message::get_message_body(&message); + assert!(burn_message::get_version(&new_burn_message) == message_body_version(), 0); + assert!(get_signer_balance(owner) == original_account_balance, 0); + } + + #[test(owner = @deployer)] + fun test_replace_deposit_for_burn_new_destination_caller_version_bump(owner: &signer) { + init_test_token_messenger(owner); + let original_account_balance = get_signer_balance(owner); + let new_destination_caller = @0xfab; + let (original_message, original_attestation) = get_valid_deposit_for_burn_message_and_attestation(); + state::set_message_body_version(2); + replace_deposit_for_burn( + owner, + &original_message, + &original_attestation, + &option::some(new_destination_caller), + &option::none() + ); + + let burn_msg = message::get_message_body(&original_message); + assert!(event::was_event_emitted(&DepositForBurn { + nonce: message::get_nonce(&original_message), + burn_token: burn_message::get_burn_token(&burn_msg), + amount: (burn_message::get_amount(&burn_msg) as u64), + depositor: signer::address_of(owner), + mint_recipient: burn_message::get_mint_recipient(&burn_msg), + destination_domain: REMOTE_DOMAIN, + destination_token_messenger: REMOTE_TOKEN_MESSENGER, + destination_caller: new_destination_caller + }), 0); + + let message_sent_event = vector::borrow(&event::emitted_events(), 0); + let message = message_transmitter::get_message_from_event(message_sent_event); + let new_burn_message = message::get_message_body(&message); + assert!(burn_message::get_version(&new_burn_message) == message_body_version(), 0); + assert!(get_signer_balance(owner) == original_account_balance, 0); + } + + #[test(owner = @deployer)] + fun test_replace_deposit_for_burn_new_mint_recipient_version_bump(owner: &signer) { + init_test_token_messenger(owner); + let new_mint_recipient = @0xfab; + let original_account_balance = get_signer_balance(owner); + let (original_message, original_attestation) = get_valid_deposit_for_burn_message_and_attestation(); + state::set_message_body_version(2); + replace_deposit_for_burn( + owner, + &original_message, + &original_attestation, + &option::none(), + &option::some(new_mint_recipient), + ); + + let burn_msg = message::get_message_body(&original_message); + assert!(event::was_event_emitted(&DepositForBurn { + nonce: message::get_nonce(&original_message), + burn_token: burn_message::get_burn_token(&burn_msg), + amount: (burn_message::get_amount(&burn_msg) as u64), + depositor: signer::address_of(owner), + mint_recipient: new_mint_recipient, + destination_domain: REMOTE_DOMAIN, + destination_token_messenger: REMOTE_TOKEN_MESSENGER, + destination_caller: message::get_destination_caller(&original_message) + }), 0); + let message_sent_event = vector::borrow(&event::emitted_events(), 0); + let message = message_transmitter::get_message_from_event(message_sent_event); + let new_burn_message = message::get_message_body(&message); + assert!(burn_message::get_version(&new_burn_message) == message_body_version(), 0); + assert!(get_signer_balance(owner) == original_account_balance, 0); + } + + #[test(owner = @deployer)] + fun test_replace_deposit_for_burn_new_mint_recipient_and_destination_caller(owner: &signer) { + init_test_token_messenger(owner); + let original_account_balance = get_signer_balance(owner); + let new_destination_caller = @0xfab; + let new_mint_recipient = @0xfac; + let (original_message, original_attestation) = get_valid_deposit_for_burn_message_and_attestation(); + replace_deposit_for_burn( + owner, + &original_message, + &original_attestation, + &option::some(new_destination_caller), + &option::some(new_mint_recipient), + ); + + let burn_msg = message::get_message_body(&original_message); + assert!(event::was_event_emitted(&DepositForBurn { + nonce: message::get_nonce(&original_message), + burn_token: burn_message::get_burn_token(&burn_msg), + amount: (burn_message::get_amount(&burn_msg) as u64), + depositor: signer::address_of(owner), + mint_recipient: new_mint_recipient, + destination_domain: REMOTE_DOMAIN, + destination_token_messenger: REMOTE_TOKEN_MESSENGER, + destination_caller: new_destination_caller + }), 0); + assert!(get_signer_balance(owner) == original_account_balance, 0); + } + + #[test(owner = @deployer)] + fun test_replace_deposit_for_burn_no_change(owner: &signer) { + init_test_token_messenger(owner); + let original_account_balance = get_signer_balance(owner); + let (original_message, original_attestation) = get_valid_deposit_for_burn_message_and_attestation(); + replace_deposit_for_burn( + owner, + &original_message, + &original_attestation, + &option::none(), + &option::none(), + ); + + let burn_msg = message::get_message_body(&original_message); + assert!(event::was_event_emitted(&DepositForBurn { + nonce: message::get_nonce(&original_message), + burn_token: burn_message::get_burn_token(&burn_msg), + amount: (burn_message::get_amount(&burn_msg) as u64), + depositor: signer::address_of(owner), + mint_recipient: burn_message::get_mint_recipient(&burn_msg), + destination_domain: REMOTE_DOMAIN, + destination_token_messenger: REMOTE_TOKEN_MESSENGER, + destination_caller: message::get_destination_caller(&original_message) + }), 0); + assert!(get_signer_balance(owner) == original_account_balance, 0); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x10002, location = Self)] + fun test_replace_deposit_for_burn_invalid_mint_recipient(owner: &signer) { + init_test_token_messenger(owner); + let (original_message, original_attestation) = get_valid_deposit_for_burn_message_and_attestation(); + replace_deposit_for_burn( + owner, + &original_message, + &original_attestation, + &option::none(), + &option::some(@0x0), + ); + } + + #[test(owner = @deployer, not_owner = @0xfaa)] + #[expected_failure(abort_code = 0x50004, location = Self)] + fun test_replace_deposit_for_burn_not_the_original_sender(owner: &signer, not_owner: &signer) { + init_test_token_messenger(owner); + let (original_message, original_attestation) = get_valid_deposit_for_burn_message_and_attestation(); + replace_deposit_for_burn( + not_owner, + &original_message, + &original_attestation, + &option::none(), + &option::none(), + ); + } + + // Add TokenMessenger Tests + + #[test(owner = @deployer)] + fun test_add_remote_token_messenger(owner: &signer) { + init_test_token_messenger(owner); + let domain = 7; + let token_messenger = from_bcs::to_address(hash::sha3_256(b"token_messenger")); + add_remote_token_messenger(owner, domain, token_messenger); + assert!(event::was_event_emitted(&RemoteTokenMessengerAdded { domain, token_messenger }), 0); + } + + #[test(owner = @deployer, not_owner = @0xfaa)] + #[expected_failure(abort_code = ownable::ENOT_OWNER, location = ownable)] + fun test_add_remote_token_messenger_not_owner(owner: &signer, not_owner: &signer) { + init_test_token_messenger(owner); + let domain = 7; + let token_messenger = from_bcs::to_address(hash::sha3_256(b"token_messenger")); + add_remote_token_messenger(not_owner, domain, token_messenger); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x80005, location = Self)] + fun test_add_remote_token_messenger_already_set(owner: &signer) { + init_test_token_messenger(owner); + assert!(state::is_remote_token_messenger_set_for_domain(REMOTE_DOMAIN), 0); + add_remote_token_messenger(owner, REMOTE_DOMAIN, REMOTE_TOKEN_MESSENGER); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x1000b, location = Self)] + fun test_add_remote_token_messenger_zero_address(owner: &signer) { + init_test_token_messenger(owner); + add_remote_token_messenger(owner, 7, @0x0); + } + + // Remove TokenMessenger Tests + + #[test(owner = @deployer)] + fun test_remove_remote_token_messenger(owner: &signer) { + init_test_token_messenger(owner); + let domain = 7; + let token_messenger = from_bcs::to_address(hash::sha3_256(b"token_messenger")); + state::add_remote_token_messenger(domain, token_messenger); + remove_remote_token_messenger(owner, domain); + assert!(event::was_event_emitted(&RemoteTokenMessengerRemoved{ domain, token_messenger }), 0); + } + + #[test(owner = @deployer, not_owner = @0xfaa)] + #[expected_failure(abort_code = ownable::ENOT_OWNER, location = ownable)] + fun test_remove_remote_token_messenger_not_owner(owner: &signer, not_owner: &signer) { + init_test_token_messenger(owner); + let domain = 7; + let token_messenger = from_bcs::to_address(hash::sha3_256(b"token_messenger")); + state::add_remote_token_messenger(domain, token_messenger); + remove_remote_token_messenger(not_owner, domain); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x10006, location = Self)] + fun test_remove_remote_token_messenger_none_set(owner: &signer) { + init_test_token_messenger(owner); + let remote_domain = 15; + assert!(!state::is_remote_token_messenger_set_for_domain(remote_domain), 0); + remove_remote_token_messenger(owner, remote_domain); + } + + // Handle Receive Message Tests + + #[test( + owner = @deployer, + destination_caller = @0x8a4edb71919e22728ffe9a925df33202328aca8d6e13be2c5f4e02b4370c8f71 + )] + fun test_handle_receive_message_success(owner: &signer, destination_caller: &signer) { + init_test_token_messenger(owner); + let mint_recipient = from_bcs::to_address(hash::sha3_256(b"mint_recipient")); + let account_balance = token_minter::get_account_balance(mint_recipient); + let amount = 8572; + let (message, attestation) = get_valid_receive_message_and_attestation(); + let receipt = message_transmitter::receive_message(destination_caller, &message, &attestation); + assert!(handle_receive_message(receipt), 0); + assert!(event::was_event_emitted(&MintAndWithdraw { + mint_token: stablecoin_address(), + mint_recipient, + amount + }), 0); + let expected_account_balance = account_balance + amount; + assert!(token_minter::get_account_balance(mint_recipient) == expected_account_balance, 0); + } + + #[test( + owner = @deployer, + destination_caller = @0x8a4edb71919e22728ffe9a925df33202328aca8d6e13be2c5f4e02b4370c8f71 + )] + #[expected_failure(abort_code = 0x10008, location = Self)] + fun test_handle_receive_message_invalid_version(owner: &signer, destination_caller: &signer) { + init_test_token_messenger(owner); + let (message, attestation) = get_valid_receive_message_and_attestation(); + state::set_message_body_version(2); + let receipt = message_transmitter::receive_message(destination_caller, &message, &attestation); + handle_receive_message(receipt); + } + + #[test( + owner = @deployer, + destination_caller = @0x8a4edb71919e22728ffe9a925df33202328aca8d6e13be2c5f4e02b4370c8f71 + )] + #[expected_failure(abort_code = 0x10001, location = burn_message)] + fun test_handle_receive_message_invalid_burn_message(owner: &signer, destination_caller: &signer + ) { + init_test_token_messenger(owner); + let message = message::serialize( + 0, + REMOTE_DOMAIN, + 9, + 7384, + REMOTE_TOKEN_MESSENGER, + signer::address_of(&token_messenger_minter::get_signer()), + from_bcs::to_address(hash::sha3_256(b"destination_caller")), + &b"Invalid Message", + ); + let attestation = x"a17e6548171cc8d014ec1d3953e3b90ab17c47ae029de4fdf32bbfc68d7bdc5003a8e3fface57934d54e6f907a3b5fd5825da1ffec1016972d5a508f0738beda1c"; + let receipt = message_transmitter::receive_message(destination_caller, &message, &attestation); + handle_receive_message(receipt); + } + + #[test( + owner = @deployer, + destination_caller = @0x8a4edb71919e22728ffe9a925df33202328aca8d6e13be2c5f4e02b4370c8f71 + )] + #[expected_failure(abort_code = 0x10009, location = Self)] + fun test_handle_receive_message_invalid_message_recipient(owner: &signer, destination_caller: &signer) { + init_test_token_messenger(owner); + let burn_message = burn_message::serialize( + message_body_version(), + from_bcs::to_address(hash::sha3_256(b"burn_token")), + from_bcs::to_address(hash::sha3_256(b"mint_recipient")), + 85720194, + from_bcs::to_address(hash::sha3_256(b"sender_address")) + ); + let message = message::serialize( + 0, + REMOTE_DOMAIN, + 9, + 7384, + REMOTE_TOKEN_MESSENGER, + from_bcs::to_address(hash::sha3_256(b"invalid_message_recipient")), + from_bcs::to_address(hash::sha3_256(b"destination_caller")), + &burn_message, + ); + let attestation = x"94e9bc648d05934214c52f5e8a43f44ab5bf74e4e19666ff00c7acb8d21b4bcc0f346dc91e953cf0bd09f89f77380480c01e4ce591b77ee0661cb3b1fa7f17361b"; + let receipt = message_transmitter::receive_message(destination_caller, &message, &attestation); + handle_receive_message(receipt); + } + + #[test( + owner = @deployer, + destination_caller = @0x8a4edb71919e22728ffe9a925df33202328aca8d6e13be2c5f4e02b4370c8f71 + )] + #[expected_failure(abort_code = 0x10006, location = Self)] + fun test_handle_receive_message_no_remote_token_messenger(owner: &signer, destination_caller: &signer) { + init_test_token_messenger(owner); + let (message, attestation) = get_valid_receive_message_and_attestation(); + state::remove_remote_token_messenger(REMOTE_DOMAIN); + let receipt = message_transmitter::receive_message(destination_caller, &message, &attestation); + handle_receive_message(receipt); + } + + #[test( + owner = @deployer, + destination_caller = @0x8a4edb71919e22728ffe9a925df33202328aca8d6e13be2c5f4e02b4370c8f71 + )] + #[expected_failure(abort_code = 0x50007, location = Self)] + fun test_handle_receive_message_invalid_remote_token_messenger(owner: &signer, destination_caller: &signer) { + init_test_token_messenger(owner); + let (message, attestation) = get_valid_receive_message_and_attestation(); + state::remove_remote_token_messenger(REMOTE_DOMAIN); + state::add_remote_token_messenger(REMOTE_DOMAIN, @0xfaa); + let receipt = message_transmitter::receive_message(destination_caller, &message, &attestation); + handle_receive_message(receipt); + } + + #[test( + owner = @deployer, + destination_caller = @0x8a4edb71919e22728ffe9a925df33202328aca8d6e13be2c5f4e02b4370c8f71 + )] + #[expected_failure(abort_code = 0x10001, location = Self)] + fun test_handle_receive_invalid_amount(owner: &signer, destination_caller: &signer) { + init_test_token_messenger(owner); + let message = x"0000000000000004000000090000000000001cd8e786e705b98581cbf28488ce4ae116db0918e1f7eb1877d07bf0995cf67724ef0b27dce48d6e8682b00e1a51d6e19728194770ff0a5b5549aa5c417a6f0aed0f8a4edb71919e22728ffe9a925df33202328aca8d6e13be2c5f4e02b4370c8f7100000001000000000000000000000000000000000000000000000000000000000000cafee6344c4f54e1e11cdfa1ee14a721177c3d9289d9989aa439b8e0141c486e161b00000000000000000000000000000000000000000000000100000000000000003ec52aadabf6254eaa382b3dec7256b360e804114611c12c551d99daec50d1c1"; + let attestation = x"543ac51d16dd8ccc8ba2cd849be3bc0ee63dfe490d01ad35a33017d904fbb3257a43b5f8699e817efc946fd99b4445a4b0864b0f763c152c57ac8600dcb388481b"; + let receipt = message_transmitter::receive_message(destination_caller, &message, &attestation); + handle_receive_message(receipt); + } + + // View Function Test + + #[test(owner = @deployer)] + fun test_view_message_body_version(owner: &signer) { + init_test_token_messenger(owner); + assert!(message_body_version() == state::get_message_body_version(), 0); + } + + #[test(owner = @deployer)] + fun test_view_remote_token_messenger(owner: &signer) { + init_test_token_messenger(owner); + assert!(state::get_remote_token_messenger(REMOTE_DOMAIN) == remote_token_messenger(REMOTE_DOMAIN), 0); + } + + #[test(owner = @deployer)] + fun test_view_num_remote_token_messengers(owner: &signer) { + init_test_token_messenger(owner); + assert!(state::get_num_remote_token_messengers() == num_remote_token_messengers(), 0); + } + + #[test(owner = @deployer)] + fun test_view_max_burn_amount_per_message(owner: &signer) { + init_test_token_messenger(owner); + let (_, max_burn_amount) = state::get_max_burn_limit_per_message_for_token(@stablecoin); + assert!(max_burn_amount_per_message(@stablecoin) == max_burn_amount, 0); + } +} diff --git a/packages/token_messenger_minter/sources/token_messenger_minter.move b/packages/token_messenger_minter/sources/token_messenger_minter.move new file mode 100644 index 0000000..a417eb4 --- /dev/null +++ b/packages/token_messenger_minter/sources/token_messenger_minter.move @@ -0,0 +1,174 @@ +/// Copyright (c) 2024, Circle Internet Group, Inc. +/// All rights reserved. +/// +/// SPDX-License-Identifier: Apache-2.0 +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. + +module token_messenger_minter::token_messenger_minter { + // Built-in Modules + use std::error; + use aptos_framework::object; + use aptos_framework::resource_account; + use aptos_extensions::upgradable; + use aptos_extensions::manageable; + use aptos_extensions::pausable; + use aptos_extensions::ownable; + + // Package Modules + use token_messenger_minter::state; + use token_messenger_minter::token_controller; + + // Friend Modules + friend token_messenger_minter::token_messenger; + friend token_messenger_minter::token_minter; + + // Constants + const SEED_NAME: vector = b"TokenMessengerMinter"; + + // Errors + const ENOT_OWNER: u64 = 1; + const EALREADY_INITIALIZED: u64 = 2; + + #[resource_group_member(group = aptos_framework::object::ObjectGroup)] + struct ObjectController has key { + extend_ref: object::ExtendRef, + } + + #[view] + public fun object_address(): address { + state::get_object_address() + } + + // ----------------------------- + // ----- Public Functions ------ + // ----------------------------- + + fun init_module(resource_acct_signer: &signer) { + let constructor_ref = object::create_named_object(resource_acct_signer, SEED_NAME); + let token_messenger_minter_signer = &object::generate_signer(&constructor_ref); + let extend_ref = object::generate_extend_ref(&constructor_ref); + move_to(token_messenger_minter_signer, ObjectController { extend_ref }); + + ownable::new(token_messenger_minter_signer, @deployer); + pausable::new(token_messenger_minter_signer, @deployer); + + let signer_cap = resource_account::retrieve_resource_account_cap(resource_acct_signer, @deployer); + manageable::new(resource_acct_signer, @deployer); + upgradable::new(resource_acct_signer, signer_cap); + + } + + /// Create and initialize Token Messenger Minter object + /// Aborts if: + /// - caller is not the deployer + /// - it has already been initialized + entry fun initialize_token_messenger_minter( + caller: &signer, + message_body_version: u32, + token_controller: address, + ) acquires ObjectController { + manageable::assert_is_admin(caller, @token_messenger_minter); + assert!(!state::is_initialized(), error::already_exists(EALREADY_INITIALIZED)); + state::init_state(&get_signer(), message_body_version); + token_controller::set_token_controller(caller, token_controller); + } + + // ----------------------------- + // ----- Friend Functions ------ + // ----------------------------- + + /// Generate signer from the `ExtendRef` to call `TokenMinter` and `MessageTransmitter` + public(friend) fun get_signer(): signer acquires ObjectController { + let object_address = state::get_object_address(); + let object_controller = borrow_global(object_address); + let object_signer = object::generate_signer_for_extending( + &object_controller.extend_ref + ); + object_signer + } + + // ----------------------------- + // -------- Unit Tests --------- + // ----------------------------- + + #[test_only] + use aptos_framework::account; + #[test_only] + use aptos_framework::account::create_signer_for_test; + #[test_only] + use std::vector; + #[test_only] + use aptos_extensions::ownable::OwnerRole; + #[test_only] + use aptos_extensions::pausable::PauseState; + + #[test_only] + const TEST_SEED: vector = b"test_seed_tmm"; + + #[test_only] + fun init_test_token_messenger_minter_module() { + account::create_account_for_test(@deployer); + resource_account::create_resource_account_and_publish_package( + &create_signer_for_test(@deployer), + TEST_SEED, + x"04746573740100000000000000000000000000", // empty BCS serialized PackageMetadata + vector::empty() + ); + let resource_account_address = account::create_resource_address(&@deployer, TEST_SEED); + assert!(@token_messenger_minter == resource_account_address, 0); + let resource_account_signer = create_signer_for_test(resource_account_address); + init_module(&resource_account_signer); + } + + #[test_only] + public fun initialize_test_token_messenger_minter(message_body_version: u32, + token_controller: address,) acquires ObjectController { + init_test_token_messenger_minter_module(); + initialize_token_messenger_minter(&create_signer_for_test(@deployer), message_body_version, token_controller); + } + + // Token Messenger Minter Initialization Tests + + #[test] + fun test_init_token_messenger_minter() acquires ObjectController { + initialize_test_token_messenger_minter(1, @0xfac); + assert!(state::is_initialized(), 0); + assert!(state::get_token_controller() == @0xfac, 0); + assert!(exists(state::get_object_address()), 0); + assert!(manageable::admin(@token_messenger_minter) == @deployer, 0); + assert!(ownable::owner(object::address_to_object(state::get_object_address())) == @deployer, 0); + assert!(pausable::pauser(object::address_to_object(state::get_object_address())) == @deployer, 0); + assert!(!pausable::is_paused(object::address_to_object(state::get_object_address())), 0); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x80002, location = Self)] + fun test_init_token_messenger_minter_already_initialized(owner: &signer) acquires ObjectController { + initialize_test_token_messenger_minter(1, @0xfac); + initialize_token_messenger_minter(owner, 2, @0xfab); + } + + #[test(not_owner = @0xfaa)] + #[expected_failure(abort_code = manageable::ENOT_ADMIN, location = manageable)] + fun test_init_token_messenger_minter_not_owner(not_owner: &signer) acquires ObjectController { + init_test_token_messenger_minter_module(); + initialize_token_messenger_minter(not_owner, 1, @0xfac); + } + + #[test] + fun test_object_address() { + init_test_token_messenger_minter_module(); + assert!(object_address() == state::get_object_address(), 0); + } +} diff --git a/packages/token_messenger_minter/sources/token_minter/token_controller.move b/packages/token_messenger_minter/sources/token_minter/token_controller.move new file mode 100644 index 0000000..e61af54 --- /dev/null +++ b/packages/token_messenger_minter/sources/token_minter/token_controller.move @@ -0,0 +1,372 @@ +/// Copyright (c) 2024, Circle Internet Group, Inc. +/// All rights reserved. +/// +/// SPDX-License-Identifier: Apache-2.0 +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. + + +module token_messenger_minter::token_controller { + // Built-in Modules + use std::error; + use std::signer; + use aptos_framework::event; + use aptos_extensions::ownable; + + // Package Modules + use token_messenger_minter::state; + + // Friend Modules + friend token_messenger_minter::token_minter; + friend token_messenger_minter::token_messenger_minter; + friend token_messenger_minter::token_messenger; + + // Errors + const ENOT_TOKEN_CONTROLLER: u64 = 1; + const EBURN_TOKEN_NOT_SUPPORTED: u64 = 2; + const EAMOUNT_EXCEEDS_BURN_LIMIT: u64 = 3; + const EREMOTE_DOMAIN_AND_TOKEN_ALREADY_LINKED: u64 = 4; + const ENO_LINK_EXIST_FOR_REMOTE_DOMAIN_AND_TOKEN: u64 = 5; + + // ----------------------------- + // ---------- Events ----------- + // ----------------------------- + + #[event] + struct TokenPairLinked has drop, store { + local_token: address, + remote_domain: u32, + remote_token: address + } + + #[event] + struct TokenPairUnlinked has drop, store { + local_token: address, + remote_domain: u32, + remote_token: address + } + + #[event] + struct SetBurnLimitPerMessage has drop, store { + token: address, + burn_limit_per_message: u64, + } + + #[event] + struct SetTokenController has drop, store { + token_controller: address, + } + + // ----------------------------- + // --- Public View Functions --- + // ----------------------------- + + #[view] + public fun token_controller(): address { + state::get_token_controller() + } + + #[view] + public fun get_linked_token(remote_domain: u32, remote_token: address): address { + get_local_token(remote_domain, remote_token) + } + + #[view] + public fun get_num_linked_tokens(): u64 { + state::get_num_linked_tokens() + } + + // ----------------------------- + // ----- Public Functions ------ + // ----------------------------- + + /// Sets the token controller address. Emits `SetTokenController` event + /// Aborts if: + /// - the caller is not the owner + public(friend) entry fun set_token_controller(caller: &signer, new_token_controller: address) { + ownable::assert_is_owner(caller, state::get_object_address()); + state::set_token_controller(new_token_controller); + event::emit(SetTokenController { token_controller: new_token_controller }); + } + + /// Sets the maximum amount allowed to be burned per message/tx. Emits `SetBurnLimitPerMessage` event + /// Aborts if: + /// - the caller is not the token controller + entry fun set_max_burn_amount_per_message(caller: &signer, token: address, burn_limit_per_message: u64) { + assert_is_token_controller(caller); + state::set_max_burn_limit_per_message_for_token(token, burn_limit_per_message); + event::emit(SetBurnLimitPerMessage { token, burn_limit_per_message }) + } + + /// Links remote token to local token that can be minted/burned. Remote token can only be linked to one local token + /// at a time. Emits `TokenPairLinked` event + /// Aborts if: + /// - the caller is not the token controller + /// - remote token is already linked to a local token + entry fun link_token_pair(caller: &signer, local_token: address, remote_domain: u32, remote_token: address) { + assert_is_token_controller(caller); + assert!( + !state::local_token_exists(remote_domain, remote_token), + error::already_exists(EREMOTE_DOMAIN_AND_TOKEN_ALREADY_LINKED) + ); + state::add_local_token_for_remote_token(remote_domain, remote_token, local_token); + event::emit(TokenPairLinked { local_token, remote_domain, remote_token } ); + } + + /// Unlinks remote token and local token pair. Emits `TokenPairUnlinked` event + /// Aborts if: + /// - the caller is not the token controller + /// - no link exists for the given remote token and domain + entry fun unlink_token_pair(caller: &signer, remote_domain: u32, remote_token: address) { + assert_is_token_controller(caller); + assert!( + state::local_token_exists(remote_domain, remote_token), + error::not_found(ENO_LINK_EXIST_FOR_REMOTE_DOMAIN_AND_TOKEN) + ); + let local_token = state::remove_local_token_for_remote_token(remote_domain, remote_token); + event::emit(TokenPairUnlinked { local_token, remote_domain, remote_token } ); + } + + // ----------------------------- + // ----- Friend Functions ------ + // ----------------------------- + + public(friend) fun assert_amount_within_burn_limit(token: address, amount: u64) { + let (token_exists, limit) = state::get_max_burn_limit_per_message_for_token(token); + assert!(token_exists, error::invalid_argument(EBURN_TOKEN_NOT_SUPPORTED)); + assert!(amount <= limit, error::out_of_range(EAMOUNT_EXCEEDS_BURN_LIMIT)) + } + + public(friend) fun get_local_token(remote_domain: u32, remote_token: address): address { + assert!( + state::local_token_exists(remote_domain, remote_token), + error::not_found(ENO_LINK_EXIST_FOR_REMOTE_DOMAIN_AND_TOKEN) + ); + state::get_local_token(remote_domain, remote_token) + } + + // ----------------------------- + // ----- Private Functions ----- + // ----------------------------- + + fun assert_is_token_controller(caller: &signer) { + assert!(token_controller() == signer::address_of(caller), error::permission_denied(ENOT_TOKEN_CONTROLLER)); + } + + // ----------------------------- + // -------- Unit Tests --------- + // ----------------------------- + + #[test_only] + use aptos_framework::account::create_signer_for_test; + + #[test_only] const TOKEN_CONTROLLER: address = @0x8e72; + + #[test_only] + public fun init_test_token_controller(owner: &signer) { + state::init_test_state(owner); + set_token_controller(owner, TOKEN_CONTROLLER); + } + + #[test_only] + public fun test_link_token_pair(caller: &signer, local_token: address, remote_domain: u32, remote_token: address) { + link_token_pair(caller, local_token, remote_domain, remote_token); + } + + #[test_only] + public fun test_unlink_token_pair(caller: &signer, remote_domain: u32, remote_token: address) { + unlink_token_pair(caller, remote_domain, remote_token); + } + + #[test_only] + public fun test_set_max_burn_amount_per_message(caller: &signer, token: address, burn_limit_per_message: u64) { + set_max_burn_amount_per_message(caller, token, burn_limit_per_message) + } + + // Test Set Token Controller + + #[test(owner = @deployer)] + fun test_set_token_controller(owner: &signer) { + init_test_token_controller(owner); + let token_controller = @0xfac; + set_token_controller(owner, token_controller); + assert!(state::get_token_controller() == token_controller, 0); + assert!(event::was_event_emitted(&SetTokenController { token_controller }), 0); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = ownable::ENOT_OWNER, location = ownable)] + fun test_set_token_controller_not_owner(owner: &signer) { + init_test_token_controller(owner); + let not_owner = create_signer_for_test(@10); + set_token_controller(¬_owner, @0xfac); + } + + // Test Assert Amount Within Burn Limit + + #[test(owner = @deployer)] + fun test_assert_amount_within_burn_limit(owner: &signer) { + init_test_token_controller(owner); + let token = @0xfac; + state::set_max_burn_limit_per_message_for_token(token, 10); + assert_amount_within_burn_limit(token, 8); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x10002, location = Self)] + fun test_assert_amount_within_burn_limit_token_not_supported(owner: &signer) { + init_test_token_controller(owner); + assert_amount_within_burn_limit(@0xfac, 8); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x20003, location = Self)] + fun test_assert_amount_within_burn_limit_amount_exceeds_limit(owner: &signer) { + init_test_token_controller(owner); + let token = @0xfac; + state::set_max_burn_limit_per_message_for_token(token, 10); + assert_amount_within_burn_limit(token, 15); + } + + // Test Is Token Controller + + #[test(owner = @deployer, token_controller = @0xfac )] + fun test_is_token_controller_success(owner: &signer, token_controller: &signer) { + init_test_token_controller(owner); + set_token_controller(owner, signer::address_of(token_controller)); + assert_is_token_controller(token_controller); + } + + #[test(owner = @deployer )] + #[expected_failure(abort_code = 0x50001, location = Self)] + fun test_is_token_controller_permission_denied(owner: &signer) { + init_test_token_controller(owner); + assert_is_token_controller(owner); + } + + // Test Set Max Burn Amount Per Message + + #[test(owner = @deployer, token_controller = @0x8e72)] + fun test_set_max_burn_amount_per_message_success(owner: &signer, token_controller: &signer) { + init_test_token_controller(owner); + let token = @0xfac; + let limit = 87345; + set_max_burn_amount_per_message(token_controller, token, limit); + let (exists, burn_limit) = state::get_max_burn_limit_per_message_for_token(token); + assert!(exists, 0); + assert!(burn_limit == limit, 0); + assert!(event::was_event_emitted(&SetBurnLimitPerMessage { token, burn_limit_per_message: limit } ), 0); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x50001, location = Self)] + fun test_set_max_burn_amount_per_message_not_token_controller(owner: &signer) { + init_test_token_controller(owner); + set_max_burn_amount_per_message(owner, @0xfac, 87345); + } + + // Test Get Local Token + + #[test(owner = @deployer)] + fun test_get_local_token(owner: &signer) { + init_test_token_controller(owner); + let remote_token = @0xfac; + let remote_domain = 4; + let local_token = @0xfab; + state::add_local_token_for_remote_token(remote_domain, remote_token, local_token); + assert!(get_local_token(remote_domain, remote_token) == local_token, 0); + } + + // Test Link Token Pair + + #[test(owner = @deployer, token_controller = @0x8e72)] + fun test_link_token_pair_success(owner: &signer, token_controller: &signer) { + init_test_token_controller(owner); + let remote_token = @0xfab; + let remote_domain = 5; + let local_token = @0xfaa; + link_token_pair(token_controller, local_token, remote_domain, remote_token); + assert!(get_local_token(remote_domain, remote_token) == local_token, 0); + assert!(event::was_event_emitted(&TokenPairLinked { local_token, remote_token, remote_domain } ), 0); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x50001, location = Self)] + fun test_link_token_pair_not_token_controller(owner: &signer) { + init_test_token_controller(owner); + let remote_token = @0xfab; + let remote_domain = 5; + let local_token = @0xfaa; + link_token_pair(owner, local_token, remote_domain, remote_token); + } + + #[test(owner = @deployer, token_controller = @0x8e72)] + #[expected_failure(abort_code = 0x80004, location = Self)] + fun test_link_token_pair_already_linked(owner: &signer, token_controller: &signer) { + init_test_token_controller(owner); + let remote_token = @0xfab; + let remote_domain = 5; + let local_token = @0xfaa; + link_token_pair(token_controller, local_token, remote_domain, remote_token); + assert!(get_local_token(remote_domain, remote_token) == local_token, 0); + link_token_pair(token_controller, @0xfff, remote_domain, remote_token); + } + + // Test Unlink Token Pair + + #[test(owner = @deployer, token_controller = @0x8e72)] + fun test_unlink_token_pair_success(owner: &signer, token_controller: &signer) { + init_test_token_controller(owner); + let remote_token = @0xfab; + let remote_domain = 5; + let local_token = @0xfaa; + state::add_local_token_for_remote_token(remote_domain, remote_token, local_token); + unlink_token_pair(token_controller, remote_domain, remote_token); + assert!(!state::local_token_exists(remote_domain, remote_token), 0); + assert!(event::was_event_emitted(&TokenPairUnlinked { local_token, remote_token, remote_domain } ), 0); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x50001, location = Self)] + fun test_unlink_token_pair_not_token_controller(owner: &signer) { + init_test_token_controller(owner); + let remote_token = @0xfab; + let remote_domain = 5; + let local_token = @0xfaa; + state::add_local_token_for_remote_token(remote_domain, remote_token, local_token); + unlink_token_pair(owner, remote_domain, remote_token); + } + + #[test(owner = @deployer, token_controller = @0x8e72)] + #[expected_failure(abort_code = 0x60005, location = Self)] + fun test_unlink_token_pair_no_link_exist(owner: &signer, token_controller: &signer) { + init_test_token_controller(owner); + unlink_token_pair(token_controller, 5, @0xfab); + } + + // Test Vew Functions + + #[test(owner = @deployer)] + fun test_view_functions(owner: &signer) { + init_test_token_controller(owner); + assert!(token_controller() == state::get_token_controller(), 0); + assert!(get_num_linked_tokens() == 0, 0); + + let remote_token = @0xfab; + let remote_domain = 5; + let local_token = @0xfaa; + state::add_local_token_for_remote_token(remote_domain, remote_token, local_token); + assert!(get_linked_token(remote_domain, remote_token) == local_token, 1); + assert!(get_num_linked_tokens() == 1, 0); + } +} diff --git a/packages/token_messenger_minter/sources/token_minter/token_minter.move b/packages/token_messenger_minter/sources/token_minter/token_minter.move new file mode 100644 index 0000000..49077d2 --- /dev/null +++ b/packages/token_messenger_minter/sources/token_minter/token_minter.move @@ -0,0 +1,303 @@ +/// Copyright (c) 2024, Circle Internet Group, Inc. +/// All rights reserved. +/// +/// SPDX-License-Identifier: Apache-2.0 +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. + +module token_messenger_minter::token_minter { + // Built-in Modules + use std::error; + use aptos_framework::dispatchable_fungible_asset; + use aptos_framework::fungible_asset; + use aptos_framework::fungible_asset::{Metadata, FungibleAsset, FungibleStore}; + use aptos_framework::object; + use aptos_framework::object::Object; + use aptos_framework::primary_fungible_store; + use stablecoin::stablecoin::stablecoin_address; + use aptos_extensions::pausable; + use token_messenger_minter::token_messenger_minter; + use stablecoin::treasury; + + // Package Modules + use token_messenger_minter::state; + use token_messenger_minter::token_controller; + + // Friend Modules + friend token_messenger_minter::token_messenger; + + // Errors + const EMINT_TOKEN_NOT_SUPPORTED: u64 = 1; + + // ----------------------------- + // ----- Friend Functions ------ + // ----------------------------- + + /// Mints a specified amount of tokens to a recipient address. The address can be store address or account address. + /// In the later case, tokens will be minted into the primary store of the account address. If the store does not + /// exist, it will be created. + /// Aborts if: + /// - contract is paused + /// - the burn_token isn't supported + /// - the mint_token isn't supported + /// - stablecoin mint aborts + public(friend) fun mint(source_domain: u32, burn_token: address, mint_recipient: address, amount: u64): address { + pausable::assert_not_paused(state::get_object_address()); + let mint_token = token_controller::get_local_token(source_domain, burn_token); + assert!(mint_token == stablecoin_address(), error::invalid_argument(EMINT_TOKEN_NOT_SUPPORTED)); + let token_messenger_minter_signer = token_messenger_minter::get_signer(); + let asset = treasury::mint(&token_messenger_minter_signer, amount); + let token_obj: Object = object::address_to_object(mint_token); + let store = if (fungible_asset::store_exists(mint_recipient)) { + object::address_to_object(mint_recipient) + } else { + primary_fungible_store::ensure_primary_store_exists(mint_recipient, token_obj) + }; + dispatchable_fungible_asset::deposit(store, asset); + mint_token + } + + /// Burns the passed in Fungible Asset. + /// Aborts if: + /// - contract is paused + /// - the burn_token isn't supported + /// - amount exceeds the burn limit + /// - stablecoin burn aborts + public(friend) fun burn(burn_token: address, asset: FungibleAsset) { + pausable::assert_not_paused(state::get_object_address()); + let amount = fungible_asset::amount(&asset); + token_controller::assert_amount_within_burn_limit(burn_token, amount); + let token_messenger_minter_signer = token_messenger_minter::get_signer(); + treasury::burn(&token_messenger_minter_signer, asset); + } + + // ----------------------------- + // -------- Unit Tests --------- + // ----------------------------- + + #[test_only] + use std::signer; + #[test_only] + use std::string; + #[test_only] + use aptos_framework::account; + #[test_only] + use aptos_framework::account::create_signer_for_test; + #[test_only] + use aptos_framework::fungible_asset::{create_test_store}; + #[test_only] + use aptos_framework::resource_account; + #[test_only] + use stablecoin::stablecoin; + + // Test Helpers + + #[test_only] + const REMOTE_DOMAIN: u32 = 4; + + #[test_only] + const REMOTE_STABLECOIN_ADDRESS: address = @0xcafe; + + #[test_only] + const TEST_SEED: vector = b"test_seed_stablecoin"; + + #[test_only] + public fun get_account_balance(account_address: address): u64 { + let asset: Object = object::address_to_object(stablecoin::stablecoin_address()); + primary_fungible_store::ensure_primary_store_exists(account_address, asset); + primary_fungible_store::balance(account_address, asset) + } + + #[test_only] + fun deploy_stablecoin_package(): signer { + account::create_account_for_test(@deployer); + + // deploy an empty package to a new resource account + resource_account::create_resource_account( + &create_signer_for_test(@deployer), + TEST_SEED, + b"", + ); + + // compute the resource account address + let resource_account_address = account::create_resource_address(&@deployer, TEST_SEED); + + // verify the resource account address is the same as the configured test package address + assert!(@stablecoin == resource_account_address, 1); + + // return a resource account signer + let resource_account_signer = create_signer_for_test(resource_account_address); + resource_account_signer + } + + #[test_only] + fun init_test_stablecoin() { + let resource_acct_signer = deploy_stablecoin_package(); + stablecoin::test_init_module(&resource_acct_signer); + stablecoin::test_initialize_v1( + &create_signer_for_test(@deployer), + string::utf8(b"name"), + string::utf8(b"symbol"), + 6, + string::utf8( b"icon uri"), + string::utf8(b"project uri") + ); + } + + #[test_only] + public(friend) fun init_test_token_minter(owner: &signer) { + // Initialize Token Messenger minter + token_messenger_minter::initialize_test_token_messenger_minter(1, signer::address_of(owner)); + + // Initialize Stablecoin + init_test_stablecoin(); + + // Add Token Messenger Minter object signer as minter + let tmm_signer = token_messenger_minter::get_signer(); + treasury::test_configure_controller(owner, signer::address_of(&tmm_signer), signer::address_of(&tmm_signer)); + treasury::test_configure_minter(&tmm_signer, 10_000_000); + + // Link the newly created FA + token_controller::test_link_token_pair( + owner, + stablecoin::stablecoin_address(), + REMOTE_DOMAIN, + REMOTE_STABLECOIN_ADDRESS + ); + + // Set Burn Limit + token_controller::test_set_max_burn_amount_per_message( + owner, + stablecoin::stablecoin_address(), + 1_000_000 + ); + } + + #[test_only] + public(friend) fun withdraw_from_primary_store(owner: &signer, amount: u64, burn_token: address): FungibleAsset { + let token_obj: Object = object::address_to_object(burn_token); + let store = primary_fungible_store::ensure_primary_store_exists(signer::address_of(owner), token_obj); + dispatchable_fungible_asset::withdraw(owner, store, amount) + } + + // Mint Tests + + #[test(owner = @deployer, user = @0xfaa)] + fun test_mint_and_burn_success(owner: &signer, user: &signer) { + init_test_token_minter(owner); + let to_address = signer::address_of(user); + let balance = get_account_balance(to_address); + assert!(balance == 0, 0); + + // Mint 100 tokens to the user + let expected_mint_token = state::get_local_token(REMOTE_DOMAIN, REMOTE_STABLECOIN_ADDRESS); + let mint_token = mint(REMOTE_DOMAIN, REMOTE_STABLECOIN_ADDRESS, to_address, 100); + assert!(mint_token == expected_mint_token, 0); + assert!(get_account_balance(to_address) == 100, 0); + + // Burn 50 tokens from the user + let asset = withdraw_from_primary_store(user, 50, stablecoin::stablecoin_address()); + burn(stablecoin::stablecoin_address(), asset); + assert!(get_account_balance(signer::address_of(user)) == 50, 0); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = pausable::EPAUSED, location = pausable)] + fun test_mint_contract_paused(owner: &signer) { + init_test_token_minter(owner); + state::set_paused(owner); + mint(REMOTE_DOMAIN, REMOTE_STABLECOIN_ADDRESS, @0xfaa, 57834); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x60005, location = token_controller)] + fun test_mint_no_local_token(owner: &signer) { + init_test_token_minter(owner); + mint(REMOTE_DOMAIN + 1, REMOTE_STABLECOIN_ADDRESS, @0xfaa, 57834); + } + + #[test(owner = @deployer)] + #[expected_failure(abort_code = 0x10001, location = Self)] + fun test_mint_unsupported_mint_token(owner: &signer) { + init_test_token_minter(owner); + token_controller::test_unlink_token_pair(owner, REMOTE_DOMAIN, REMOTE_STABLECOIN_ADDRESS); + token_controller::test_link_token_pair( + owner, + @0xfac, + REMOTE_DOMAIN, + REMOTE_STABLECOIN_ADDRESS + ); + + mint(REMOTE_DOMAIN, REMOTE_STABLECOIN_ADDRESS, @0xfaa, 57834); + } + + #[test(owner = @deployer, user = @0xfaa)] + fun test_mint_and_burn_secondary_store_success(owner: &signer, user: &signer) { + init_test_token_minter(owner); + let to_address = signer::address_of(user); + let balance = get_account_balance(to_address); + assert!(balance == 0, 0); + + // Create secondary store + let metadata: Object = object::address_to_object(stablecoin::stablecoin_address()); + let secondary_store = create_test_store(user, metadata); + let store_address = object::object_address(&secondary_store); + assert!(fungible_asset::balance(secondary_store) == 0, 0); + + // Mint 100 tokens to the user's secondary store + let expected_mint_token = state::get_local_token(REMOTE_DOMAIN, REMOTE_STABLECOIN_ADDRESS); + let mint_token = mint(REMOTE_DOMAIN, REMOTE_STABLECOIN_ADDRESS, store_address, 100); + assert!(mint_token == expected_mint_token, 0); + assert!(fungible_asset::balance(secondary_store) == 100, 0); + + // Burn 50 tokens from the user's secondary store + let asset = dispatchable_fungible_asset::withdraw(user, secondary_store, 50); + burn(stablecoin::stablecoin_address(), asset); + assert!(fungible_asset::balance(secondary_store) == 50, 0); + } + + #[test(owner = @deployer, user = @0xfaa)] + #[expected_failure(abort_code = pausable::EPAUSED, location = pausable)] + fun test_burn_contract_paused(owner: &signer, user: &signer) { + init_test_token_minter(owner); + mint(REMOTE_DOMAIN, REMOTE_STABLECOIN_ADDRESS, signer::address_of(user), 100); + state::set_paused(owner); + let asset = withdraw_from_primary_store(user, 50, stablecoin::stablecoin_address()); + burn(stablecoin::stablecoin_address(), asset); + } + + #[test(owner = @deployer, user = @0xfaa)] + #[expected_failure(abort_code = 0x20003, location = token_controller)] + fun test_burn_invalid_burn_amount(owner: &signer, user: &signer) { + init_test_token_minter(owner); + mint(REMOTE_DOMAIN, REMOTE_STABLECOIN_ADDRESS, signer::address_of(user), 100); + + // Set limit of 10 tokens + state::set_max_burn_limit_per_message_for_token(stablecoin::stablecoin_address(), 10); + + // Try to Burn 50 tokens from the user + let asset = withdraw_from_primary_store(user, 50, stablecoin::stablecoin_address()); + burn(stablecoin::stablecoin_address(), asset); + } + + #[test(owner = @deployer, user = @0xfaa)] + #[expected_failure(abort_code = 0x10002, location = token_controller)] + fun test_burn_invalid_burn_token(owner: &signer, user: &signer) { + init_test_token_minter(owner); + mint(REMOTE_DOMAIN, REMOTE_STABLECOIN_ADDRESS, signer::address_of(user), 100); + + // Try to Burn 50 tokens from the user + let asset = withdraw_from_primary_store(user, 50, stablecoin::stablecoin_address()); + burn(REMOTE_STABLECOIN_ADDRESS, asset); + } +} diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..3e4387c --- /dev/null +++ b/renovate.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "github>circlefin/renovate-config", + "github>circlefin/renovate-config:versions.sh" + ] +} diff --git a/scripts/aptos/aptos-local-network.sh b/scripts/aptos/aptos-local-network.sh new file mode 100755 index 0000000..4bb6449 --- /dev/null +++ b/scripts/aptos/aptos-local-network.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# Copyright (c) 2024, Circle Internet Group, Inc. +# All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +start_aptos_localnet() { + # Create logs directory and aptos.log file if they don't exist + mkdir -p logs + touch logs/aptos.log + + echo "Starting aptos localnet" + aptos node run-localnet \ + --with-indexer-api \ + --force-restart \ + --assume-yes \ + --test-dir ~/.aptos/testnet \ + >logs/aptos.log 2>&1 & # the startup output holds the shell so we redirect to a file and send to the background. + + until curl -s localhost:8070 | jq '.not_ready | length == 0' + do + sleep 2 + done + + echo "Successfully started Aptos localnet!" +} diff --git a/scripts/evm/cctp_deploy.s.sol b/scripts/evm/cctp_deploy.s.sol new file mode 100644 index 0000000..5a9d87f --- /dev/null +++ b/scripts/evm/cctp_deploy.s.sol @@ -0,0 +1,230 @@ +pragma solidity ^0.7.6; + +import "forge-std/Script.sol"; +import "src/TokenMessenger.sol"; +import "src/TokenMinter.sol"; +import "src/MessageTransmitter.sol"; +import "src/messages/Message.sol"; + +contract DeployScript is Script { + address private attesterAddress; + address private usdcContractAddress; + address private usdcRemoteContractAddress; + address private remoteTokenMessengerAddress; + address private tokenControllerAddress; + address private messageTransmitterPauserAddress; + address private tokenMinterPauserAddress; + address private messageTransmitterRescuerAddress; + address private tokenMessengerRescuerAddress; + address private tokenMinterRescuerAddress; + + uint32 private messageBodyVersion = 0; + uint32 private version = 0; + uint32 private domain; + uint32 private remoteDomain; + uint32 private maxMessageBodySize = 8192; + uint256 private burnLimitPerMessage; + + uint256 private messageTransmitterDeployerPrivateKey; + uint256 private tokenMessengerDeployerPrivateKey; + uint256 private tokenMinterDeployerPrivateKey; + uint256 private tokenControllerPrivateKey; + + /** + * @notice deploys Message Transmitter + * @param privateKey Private Key for signing the transactions + * @return MessageTransmitter instance + */ + function deployMessageTransmitter(uint256 privateKey) + private + returns (MessageTransmitter) + { + // Start recording transactions + vm.startBroadcast(privateKey); + + // Deploy MessageTransmitter + MessageTransmitter messageTransmitter = new MessageTransmitter( + domain, + attesterAddress, + maxMessageBodySize, + version + ); + + // Add Pauser + messageTransmitter.updatePauser(messageTransmitterPauserAddress); + + // Add Rescuer + messageTransmitter.updateRescuer(messageTransmitterRescuerAddress); + + // Stop recording transactions + vm.stopBroadcast(); + return messageTransmitter; + } + + /** + * @notice deploys TokenMessenger + * @param privateKey Private Key for signing the transactions + * @param messageTransmitterAddress Message Transmitter Contract address + * @return TokenMessenger instance + */ + function deployTokenMessenger( + uint256 privateKey, + address messageTransmitterAddress + ) private returns (TokenMessenger) { + // Start recording transactions + vm.startBroadcast(privateKey); + + // Deploy TokenMessenger + TokenMessenger tokenMessenger = new TokenMessenger( + messageTransmitterAddress, + messageBodyVersion + ); + + // Add Rescuer + tokenMessenger.updateRescuer(tokenMessengerRescuerAddress); + + // Stop recording transactions + vm.stopBroadcast(); + + return tokenMessenger; + } + + /** + * @notice deploys TokenMinter + * @param privateKey Private Key for signing the transactions + * @param tokenMessengerAddress TokenMessenger Contract address + * @return TokenMinter instance + */ + function deployTokenMinter( + uint256 privateKey, + address tokenMessengerAddress + ) private returns (TokenMinter) { + // Start recording transactions + vm.startBroadcast(privateKey); + + // Deploy TokenMinter + TokenMinter tokenMinter = new TokenMinter(tokenControllerAddress); + + // Add Local TokenMessenger + tokenMinter.addLocalTokenMessenger(tokenMessengerAddress); + + // Add Pauser + tokenMinter.updatePauser(tokenMinterPauserAddress); + + // Add Rescuer + tokenMinter.updateRescuer(tokenMinterRescuerAddress); + + // Stop recording transactions + vm.stopBroadcast(); + + return tokenMinter; + } + + /** + * @notice add local minter to the TokenMessenger + */ + function addMinterAddressToTokenMessenger( + TokenMessenger tokenMessenger, + uint256 privateKey, + address minterAddress + ) private { + // Start recording transactions + vm.startBroadcast(privateKey); + + tokenMessenger.addLocalMinter(minterAddress); + + // Stop recording transactions + vm.stopBroadcast(); + } + + /** + * @notice add usdc per-message burn limit for the TokenMinter + */ + function setBurnLimitPerMessage( + TokenMinter tokenMinter, + uint256 privateKey + ) private { + // Start recording transactions + vm.startBroadcast(privateKey); + + // Configure burn limit + tokenMinter.setMaxBurnAmountPerMessage( + usdcContractAddress, + burnLimitPerMessage + ); + + // Stop recording transactions + vm.stopBroadcast(); + } + + /** + * @notice initialize variables from environment + */ + function setUp() public { + messageTransmitterDeployerPrivateKey = vm.envUint( + "MESSAGE_TRANSMITTER_DEPLOYER_KEY" + ); + tokenMessengerDeployerPrivateKey = vm.envUint( + "TOKEN_MESSENGER_DEPLOYER_KEY" + ); + tokenMinterDeployerPrivateKey = vm.envUint("TOKEN_MINTER_DEPLOYER_KEY"); + tokenControllerPrivateKey = vm.envUint("TOKEN_CONTROLLER_DEPLOYER_KEY"); + + attesterAddress = vm.envAddress("ATTESTER_ADDRESS"); + usdcContractAddress = vm.envAddress("USDC_CONTRACT_ADDRESS"); + tokenControllerAddress = vm.envAddress("TOKEN_CONTROLLER_ADDRESS"); + burnLimitPerMessage = vm.envUint("BURN_LIMIT_PER_MESSAGE"); + + domain = uint32(vm.envUint("DOMAIN")); + + messageTransmitterPauserAddress = vm.envAddress( + "MESSAGE_TRANSMITTER_PAUSER_ADDRESS" + ); + tokenMinterPauserAddress = vm.envAddress("TOKEN_MINTER_PAUSER_ADDRESS"); + + messageTransmitterRescuerAddress = vm.envAddress( + "MESSAGE_TRANSMITTER_RESCUER_ADDRESS" + ); + tokenMessengerRescuerAddress = vm.envAddress( + "TOKEN_MESSENGER_RESCUER_ADDRESS" + ); + tokenMinterRescuerAddress = vm.envAddress( + "TOKEN_MINTER_RESCUER_ADDRESS" + ); + } + + /** + * @notice main function that will be run by forge + */ + function run() public { + // Deploy MessageTransmitter + MessageTransmitter messageTransmitter = deployMessageTransmitter( + messageTransmitterDeployerPrivateKey + ); + + // Deploy TokenMessenger + TokenMessenger tokenMessenger = deployTokenMessenger( + tokenMessengerDeployerPrivateKey, + address(messageTransmitter) + ); + + // Deploy TokenMinter + TokenMinter tokenMinter = deployTokenMinter( + tokenMinterDeployerPrivateKey, + address(tokenMessenger) + ); + + // Add Local Minter + addMinterAddressToTokenMessenger( + tokenMessenger, + tokenMessengerDeployerPrivateKey, + address(tokenMinter) + ); + + // Set burn limit + setBurnLimitPerMessage( + tokenMinter, + tokenControllerPrivateKey + ); + } +} diff --git a/scripts/evm/usdc_deploy.s.sol b/scripts/evm/usdc_deploy.s.sol new file mode 100644 index 0000000..b73fad4 --- /dev/null +++ b/scripts/evm/usdc_deploy.s.sol @@ -0,0 +1,80 @@ +pragma solidity 0.6.12; + +import "forge-std/Script.sol"; +import "centre-tokens.git/contracts/v2/FiatTokenV2_1.sol"; + +contract USDCDeployScript is Script { + address private masterMinterAddress; + address private tokenMinterAddress; + address private dummyAddress; + + uint256 private masterMinterPrivateKey; + uint256 private usdcMinterAllowance = 10000; + + /** + * @notice deploys and initializes USDC + * @param privateKey Private Key for signing the transactions + * @return FiatTokenV2_1 USDC instance + */ + function deploy(uint256 privateKey) private returns (FiatTokenV2_1) { + vm.startBroadcast(privateKey); + + // Deploy USDC contract + FiatTokenV2_1 usdc = new FiatTokenV2_1(); + + // Initialize V1 + usdc.initialize( + "USDC", + "USDC", + "USDC", + 0, + masterMinterAddress, + dummyAddress, + dummyAddress, + masterMinterAddress + ); + + // Initialize V2 + usdc.initializeV2("USDC"); + + // Initialize V2_1 + usdc.initializeV2_1(dummyAddress); + vm.stopBroadcast(); + return usdc; + } + + /** + * @notice Configures master minter and tokenMinter with mint allowances and funds test address + * @param privateKey Private Key for signing the transactions + * @param usdc USDC contract instance + */ + function configureMintersAndBalances(uint256 privateKey, FiatTokenV2_1 usdc) public { + vm.startBroadcast(privateKey); + + usdc.configureMinter(masterMinterAddress, usdcMinterAllowance); + usdc.configureMinter(tokenMinterAddress, usdcMinterAllowance); + + usdc.mint(dummyAddress, 1000); + usdc.mint(tokenMinterAddress, 1000); + + vm.stopBroadcast(); + } + + /** + * @notice initialize variables from environment + */ + function setUp() public { + masterMinterAddress = vm.envAddress("MASTER_MINTER_ADDRESS"); + tokenMinterAddress = vm.envAddress("TOKEN_MINTER_ADDRESS"); + dummyAddress = vm.envAddress("DUMMY_ADDRESS"); + masterMinterPrivateKey = vm.envUint("MASTER_MINTER_KEY"); + } + + /** + * @notice main function that will be run by forge + */ + function run() public { + FiatTokenV2_1 usdc = deploy(masterMinterPrivateKey); + configureMintersAndBalances(masterMinterPrivateKey, usdc); + } +} diff --git a/setup-evm-contracts.sh b/setup-evm-contracts.sh new file mode 100755 index 0000000..665f114 --- /dev/null +++ b/setup-evm-contracts.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +# Copyright (c) 2024, Circle Internet Group, Inc. +# All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +echo "Deploying evm-cctp-contracts contracts" + +FOUNDRY_PATH=~/.foundry/bin + +# Check if foundry is installed +if ! "$FOUNDRY_PATH/forge" -V; then + curl -L https://foundry.paradigm.xyz | bash + # 07-14-2023 - The version following this version breaks our build, so setting to this version for now. + if ! "$FOUNDRY_PATH/foundryup" -V; then + FOUNDRY_PATH=~/.config/.foundry/bin + fi + "$FOUNDRY_PATH/foundryup" --version nightly-d369d2486f85576eec4ca41d277391dfdae21ba7 +fi + +cd evm-cctp-contracts + +# Update submodules +git submodule update --init --recursive + +# Install any needed dependency +yarn install + +# Build the anvil image +docker build --no-cache -f Dockerfile -t foundry . + +# Create the anvil container +docker stop anvil-eth && docker rm anvil-eth || true +docker run -d -p 8500:8545 --name anvil-eth --rm foundry "anvil --host 0.0.0.0 -a 13 --code-size-limit 250000" + +# Define the contract parameters +RPC_URL_ETH=http://localhost:8500 +SENDER='0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc' + +export MESSAGE_TRANSMITTER_DEPLOYER_KEY=0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a +export TOKEN_MESSENGER_DEPLOYER_KEY=0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6 +export TOKEN_MINTER_DEPLOYER_KEY=0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a +export TOKEN_CONTROLLER_DEPLOYER_KEY=0x701b615bbdfb9de65240bc28bd21bbc0d996645a3dd57e7b12bc2bdf6f192c82 +export ATTESTER_ADDRESS=0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f +export USDC_CONTRACT_ADDRESS=0x700b6a60ce7eaaea56f065753d8dcb9653dbad35 +export TOKEN_CONTROLLER_ADDRESS=0x71be63f3384f5fb98995898a86b02fb2426c5788 + +export BURN_LIMIT_PER_MESSAGE=100000 +export REMOTE_TOKEN_MESSENGER_ADDRESS=0x057ef64E23666F000b34aE31332854aCBd1c8544 +export REMOTE_USDC_CONTRACT_ADDRESS=0x700b6a60ce7eaaea56f065753d8dcb9653dbad35 + +# Arbitrary addresses +export MESSAGE_TRANSMITTER_PAUSER_ADDRESS=0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 +export TOKEN_MINTER_PAUSER_ADDRESS=0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 +export MESSAGE_TRANSMITTER_RESCUER_ADDRESS=0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 +export TOKEN_MESSENGER_RESCUER_ADDRESS=0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 +export TOKEN_MINTER_RESCUER_ADDRESS=0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 + +export MASTER_MINTER_ADDRESS=0xa0ee7a142d267c1f36714e4a8f75612f20a79720 +export TOKEN_MINTER_ADDRESS=0xbdEd0D2bf404bdcBa897a74E6657f1f12e5C6fb6 +export DUMMY_ADDRESS=0xfabb0ac9d68b0b445fb7357272ff202c5651694a +export MASTER_MINTER_KEY=0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6 + +export UPGRADEABLE_KEY=0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6 +export UPGRADEABLE_ADDRESS=0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 + +sleep 10; + +# Deploy the contracts +export DOMAIN=0 +"$FOUNDRY_PATH/forge" script ../scripts/evm/cctp_deploy.s.sol:DeployScript --rpc-url $RPC_URL_ETH --sender $SENDER --broadcast +mkdir -p cctp-interfaces +cp -R ./out/* ./cctp-interfaces +"$FOUNDRY_PATH/forge" script ../scripts/evm/usdc_deploy.s.sol:USDCDeployScript --rpc-url $RPC_URL_ETH --sender $SENDER --broadcast --force --use 0.6.12 +mkdir -p usdc-interfaces +cp -R ./out/* ./usdc-interfaces + +cd .. diff --git a/stablecoin-aptos b/stablecoin-aptos new file mode 160000 index 0000000..c5f24d0 --- /dev/null +++ b/stablecoin-aptos @@ -0,0 +1 @@ +Subproject commit c5f24d06ad0aebd9f61456da0c036be911da97c4 diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..fe22b40 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "module": "CommonJS", + "target": "es2021", + "moduleResolution": "node", + "composite": true, + "noEmit": false, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "noImplicitAny": true, + "sourceMap": true, + "incremental": true, + "declaration": true, + "declarationMap": true, + "skipLibCheck": true, + "experimentalDecorators": true, + "outDir": "dist", + }, + "include": ["**/*.ts", "*.ts"], + "exclude": ["node_modules"] +} diff --git a/typescript/aptos/client/aptosContractClient.ts b/typescript/aptos/client/aptosContractClient.ts new file mode 100644 index 0000000..fbeaa0d --- /dev/null +++ b/typescript/aptos/client/aptosContractClient.ts @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2024, Circle Internet Group, Inc. + * All rights reserved. + * + * Circle Internet Group, Inc. CONFIDENTIAL + * + * This file includes unpublished proprietary source code of Circle Internet + * Group, Inc. The copyright notice above does not evidence any actual or + * intended publication of such source code. Disclosure of this source code + * or any related proprietary information is strictly prohibited without + * the express written permission of Circle Internet Group, Inc. + */ + +import { + Account, + Aptos, + CommittedTransactionResponse, + Ed25519Account, + createResourceAddress, + MoveVector, + AccountAddress, + MoveValue, +} from "@aptos-labs/ts-sdk"; +import { readFileSync } from "fs"; +import { PackageName } from "../utils/package"; +import { MoveModule } from "../utils/moveModule"; +import { MoveFunction } from "../utils/moveFunction"; +import { CctpFunctionScript } from "../utils/cctpFunctionScript"; +import { buildPackage, executeTransaction, getPublishedPackageFromTxOutput, NamedAddress } from "../utils/helper"; + +export abstract class AptosContractClient { + protected readonly aptos: Aptos; + protected readonly packageName: PackageName; + protected readonly packageDeployer: Ed25519Account; + packageId: string; + + constructor(aptos: Aptos, packageId: PackageName, packageDeployer: Ed25519Account) { + this.aptos = aptos; + this.packageName = packageId; + this.packageDeployer = packageDeployer; + } + + abstract initializeState: (...args: any[]) => Promise; + + abstract publishPackage: (...args: any[]) => Promise; + + abstract getObjectAddress: () => AccountAddress; + + protected executeMoveScript = async ( + filePath: string, + moveScript: CctpFunctionScript, + functionArguments: Array, + signer?: Account + ): Promise => { + // Build a transaction with the script bytecode + const bytecode = this.loadScriptBytecode(filePath, moveScript); + const transaction = await this.aptos.transaction.build.simple({ + sender: signer?.accountAddress ?? this.packageDeployer.accountAddress, + data: { + bytecode, + functionArguments, + }, + }); + + // Submit and wait for the transaction to complete + const pendingTxn = await this.aptos.signAndSubmitTransaction({ + signer: signer ?? this.packageDeployer, + transaction, + }); + return this.aptos.waitForTransaction({ transactionHash: pendingTxn.hash }); + }; + + protected executeMoveFunction = async ( + module: MoveModule, + func: MoveFunction, + functionArguments: any[], + signer?: Account + ): Promise => { + const transaction = await this.aptos.transaction.build.simple({ + sender: signer?.accountAddress ?? this.packageDeployer.accountAddress, + data: { + function: `${this.packageId}::${module}::${func}`, + functionArguments, + }, + }); + + // Submit and wait for the transaction to complete + const pendingTxn = await this.aptos.signAndSubmitTransaction({ + signer: signer ?? this.packageDeployer, + transaction, + }); + return this.aptos.waitForTransaction({ transactionHash: pendingTxn.hash }); + }; + + protected executeMoveViewFunction = async ( + module: MoveModule, + func: MoveFunction, + functionArguments?: any[] + ): Promise => { + return await this.aptos.view({ + payload: { + function: `${this.packageId}::${module}::${func}`, + functionArguments: functionArguments || [], + }, + }); + }; + + protected loadScriptBytecode = (filePath: string, moveScript: CctpFunctionScript): Uint8Array => { + const scriptFile = this.getScriptFile(filePath, moveScript); + const buffer = readFileSync(scriptFile); + return Uint8Array.from(buffer); + }; + + protected getScriptFile = (filePath: string, moveScript: CctpFunctionScript): string => { + let builtPackage; + switch (this.packageName) { + case PackageName.MessageTransmitter: + builtPackage = "MessageTransmitter"; + break; + case PackageName.TokenMessengerMinter: + builtPackage = "TokenMessengerMinter"; + break; + default: + console.log("Unknown package has been defined. Please investigate.", this.packageName); + break; + } + return `${filePath}/${this.packageName}/build/${builtPackage}/bytecode_scripts/${moveScript}.mv`; + }; + + protected buildAndPublishPackage = async ( + packageDir: string, + deployer: Ed25519Account, + packageName: string, + namedDeps: NamedAddress[], + seed: Uint8Array, + includedArtifacts: string + ): Promise => { + const expectedCodeAddress = (await createResourceAddress(deployer.accountAddress, seed)).toString(); + const { metadataBytes, bytecode } = await buildPackage( + packageDir, + packageName, + [ + { + name: packageName, + address: expectedCodeAddress, + }, + ...namedDeps, + ], + includedArtifacts + ); + const functionArguments = [MoveVector.U8(metadataBytes), new MoveVector(bytecode.map(MoveVector.U8))]; + functionArguments.unshift(MoveVector.U8(seed!)); + const publishExtensionsTxOutput = await executeTransaction({ + aptos: this.aptos, + sender: deployer, + data: { + function: "0x1::resource_account::create_resource_account_and_publish_package", + functionArguments, + }, + }); + + this.packageId = getPublishedPackageFromTxOutput(publishExtensionsTxOutput); + console.log(`Deployed ${packageName} package at ${this.packageId}\n`); + return this.packageId; + }; +} diff --git a/typescript/aptos/client/aptosExtensionsClient.ts b/typescript/aptos/client/aptosExtensionsClient.ts new file mode 100644 index 0000000..855de47 --- /dev/null +++ b/typescript/aptos/client/aptosExtensionsClient.ts @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2024, Circle Internet Group, Inc. + * All rights reserved. + * + * Circle Internet Group, Inc. CONFIDENTIAL + * + * This file includes unpublished proprietary source code of Circle Internet + * Group, Inc. The copyright notice above does not evidence any actual or + * intended publication of such source code. Disclosure of this source code + * or any related proprietary information is strictly prohibited without + * the express written permission of Circle Internet Group, Inc. + */ + +import { AptosContractClient } from "./aptosContractClient"; +import { Account, AccountAddress, Aptos, CommittedTransactionResponse, Ed25519Account } from "@aptos-labs/ts-sdk"; +import { PackageName } from "../utils/package"; +import { MoveModule } from "../utils/moveModule"; +import { MoveFunction } from "../utils/moveFunction"; + +export class AptosExtensionsClient extends AptosContractClient { + constructor(aptos: Aptos, packageDeployer: Ed25519Account) { + super(aptos, PackageName.AptosExtensions, packageDeployer); + } + + initializeState = async (): Promise => { + return Promise.resolve(undefined); // do nothing + }; + + getObjectAddress = (): AccountAddress => { + throw new Error("Method not supported."); + }; + + pause = async (signer: Account, objectAddress: AccountAddress): Promise => { + return await this.executeMoveFunction(MoveModule.Pausable, MoveFunction.Pause, [objectAddress.toString()], signer); + }; + + unpause = async (signer: Account, objectAddress: AccountAddress): Promise => { + return await this.executeMoveFunction( + MoveModule.Pausable, + MoveFunction.Unpause, + [objectAddress.toString()], + signer + ); + }; + + updatePauser = async ( + signer: Account, + objectAddress: AccountAddress, + newPauser: AccountAddress + ): Promise => { + return await this.executeMoveFunction( + MoveModule.Pausable, + MoveFunction.UpdatePauser, + [objectAddress.toString(), newPauser.toString()], + signer + ); + }; + + pauser = async (objectAddress: AccountAddress): Promise => { + const pauser = await this.executeMoveViewFunction(MoveModule.Pausable, MoveFunction.Pauser, [ + objectAddress.toString(), + ]); + return AccountAddress.fromString(pauser[0] as string); + }; + + isPaused = async (objectAddress: AccountAddress): Promise => { + const isPaused = await this.executeMoveViewFunction(MoveModule.Pausable, MoveFunction.IsPaused, [ + objectAddress.toString(), + ]); + return isPaused[0] as boolean; + }; + + transferOwnership = async ( + signer: Account, + objectAddress: AccountAddress, + newOwner: AccountAddress + ): Promise => { + return await this.executeMoveFunction( + MoveModule.Ownable, + MoveFunction.TransferOwnership, + [objectAddress.toString(), newOwner.toString()], + signer + ); + }; + + acceptOwnership = async (signer: Account, objectAddress: AccountAddress): Promise => { + return await this.executeMoveFunction( + MoveModule.Ownable, + MoveFunction.AcceptOwnership, + [objectAddress.toString()], + signer + ); + }; + + owner = async (objectAddress: AccountAddress): Promise => { + const owner = await this.executeMoveViewFunction(MoveModule.Ownable, MoveFunction.Owner, [ + objectAddress.toString(), + ]); + return AccountAddress.fromString(owner[0] as string); + }; + + pendingOwner = async (objectAddress: AccountAddress): Promise => { + const pendingOwner = await this.executeMoveViewFunction(MoveModule.Ownable, MoveFunction.PendingOwner, [ + objectAddress.toString(), + ]); + return (pendingOwner[0] as any).vec.length == 0 ? "" : AccountAddress.fromString((pendingOwner[0] as any).vec[0]); + }; + + changeAdmin = async ( + signer: Account, + packageId: string, + newOwner: AccountAddress + ): Promise => { + return await this.executeMoveFunction( + MoveModule.Manageable, + MoveFunction.ChangeAdmin, + [packageId, newOwner.toString()], + signer + ); + }; + + acceptAdmin = async (signer: Account, packageId: string): Promise => { + return await this.executeMoveFunction(MoveModule.Manageable, MoveFunction.AcceptAdmin, [packageId], signer); + }; + + admin = async (packageId: string): Promise => { + const owner = await this.executeMoveViewFunction(MoveModule.Manageable, MoveFunction.Admin, [packageId]); + return AccountAddress.fromString(owner[0] as string); + }; + + pendingAdmin = async (packageId: string): Promise => { + const pendingAdmin = await this.executeMoveViewFunction(MoveModule.Manageable, MoveFunction.PendingAdmin, [ + packageId, + ]); + return (pendingAdmin[0] as any).vec.length == 0 ? "" : AccountAddress.fromString((pendingAdmin[0] as any).vec[0]); + }; + + publishPackage = async (filePath: string): Promise => { + return await this.buildAndPublishPackage( + `${filePath}/${this.packageName}/`, + this.packageDeployer, + "aptos_extensions", + [{ name: "deployer", address: this.packageDeployer.accountAddress.toString() }], + new Uint8Array(Buffer.from("aptos_extensions")), + "sparse" + ); + }; +} diff --git a/typescript/aptos/client/messageTransmitterClient.ts b/typescript/aptos/client/messageTransmitterClient.ts new file mode 100644 index 0000000..15c5883 --- /dev/null +++ b/typescript/aptos/client/messageTransmitterClient.ts @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2024, Circle Internet Group, Inc. + * All rights reserved. + * + * Circle Internet Group, Inc. CONFIDENTIAL + * + * This file includes unpublished proprietary source code of Circle Internet + * Group, Inc. The copyright notice above does not evidence any actual or + * intended publication of such source code. Disclosure of this source code + * or any related proprietary information is strictly prohibited without + * the express written permission of Circle Internet Group, Inc. + */ + +import { AptosContractClient } from "./aptosContractClient"; +import { + AccountAddress, + Aptos, + CommittedTransactionResponse, + createObjectAddress, + Ed25519Account, + MoveOption, + MoveValue, + MoveVector, + U32, + U64, + U8, +} from "@aptos-labs/ts-sdk"; +import { CctpFunctionScript } from "../utils/cctpFunctionScript"; +import { MoveModule } from "../utils/moveModule"; +import { MoveFunction } from "../utils/moveFunction"; +import { PackageName } from "../utils/package"; +import { MoveUint32Type, MoveUint64Type } from "@aptos-labs/ts-sdk/src/types"; + +export class MessageTransmitterClient extends AptosContractClient { + constructor(aptos: Aptos, packageDeployer: Ed25519Account) { + super(aptos, PackageName.MessageTransmitter, packageDeployer); + } + + initializeState = async ( + localDomain: U32, + attester: AccountAddress, + maxMessageBodySize: U64, + version: U32 + ): Promise => { + return await this.executeMoveFunction(MoveModule.MessageTransmitter, MoveFunction.InitializeMessageTransmitter, [ + localDomain, + attester, + maxMessageBodySize, + version, + ]); + }; + + publishPackage = async ( + filePath: string, + aptosExtensionsPackageId: string, + includedArtifacts: string + ): Promise => { + return await this.buildAndPublishPackage( + `${filePath}/${this.packageName}/`, + this.packageDeployer, + PackageName.MessageTransmitter, + [ + { name: "deployer", address: this.packageDeployer.accountAddress.toString() }, + { name: `${PackageName.AptosExtensions}`, address: aptosExtensionsPackageId }, + ], + new Uint8Array(Buffer.from(PackageName.MessageTransmitter)), + includedArtifacts + ); + }; + + replaceMessage = async ( + filePath: string, + originalMessage: MoveVector, + originalAttestation: MoveVector, + newMessageBody: MoveOption>, + newDestinationCaller: MoveOption + ): Promise => { + return await this.executeMoveScript(filePath, CctpFunctionScript.ReplaceMessage, [ + originalMessage, + originalAttestation, + newMessageBody, + newDestinationCaller, + ]); + }; + + receiveMessage = async ( + filePath: string, + messageBytes: MoveVector, + attestation: MoveVector + ): Promise => { + return await this.executeMoveScript(filePath, CctpFunctionScript.ReceiveMessage, [messageBytes, attestation]); + }; + + sendMessage = async ( + filePath: string, + destinationDomain: U32, + recipient: AccountAddress, + messageBody: MoveVector, + destinationCaller?: AccountAddress + ): Promise => { + let moveScript = CctpFunctionScript.SendMessage; + const functionArgs = [destinationDomain, recipient, messageBody]; + if (destinationCaller) { + moveScript = CctpFunctionScript.SendMessageWithCaller; + functionArgs.push(destinationCaller); + } + return await this.executeMoveScript(filePath, moveScript, functionArgs); + }; + + enableAttester = async (attester: AccountAddress): Promise => { + return await this.executeMoveFunction(MoveModule.Attester, MoveFunction.EnableAttester, [attester.toString()]); + }; + + disableAttester = async (attester: AccountAddress): Promise => { + return await this.executeMoveFunction(MoveModule.Attester, MoveFunction.DisableAttester, [attester.toString()]); + }; + + isEnabledAttester = async (attester: AccountAddress): Promise => { + const isEnabledAttester = await this.executeMoveViewFunction(MoveModule.Attester, MoveFunction.IsEnabledAttester, [ + attester.toString(), + ]); + return isEnabledAttester[0] == true; + }; + + getEnabledAttester = async (index: number): Promise => { + const enabledAttester = await this.executeMoveViewFunction(MoveModule.Attester, MoveFunction.GetEnabledAttester, [ + index, + ]); + return AccountAddress.fromString(enabledAttester[0] as string); + }; + + getNumEnabledAttesters = async (): Promise => { + const numEnabledAttesters: MoveValue[] = await this.executeMoveViewFunction(MoveModule.Attester, MoveFunction.GetNumEnabledAttesters); + return numEnabledAttesters[0] as MoveUint64Type; + }; + + getAttesterManager = async (): Promise => { + const attesterManager = await this.executeMoveViewFunction(MoveModule.Attester, MoveFunction.GetAttesterManager); + return AccountAddress.fromString(attesterManager[0] as string); + }; + + updateAttesterManager = async (manager: AccountAddress): Promise => { + return await this.executeMoveFunction(MoveModule.Attester, MoveFunction.UpdateAttesterManager, [ + manager.toString(), + ]); + }; + + setSignatureThreshold = async (threshold: number): Promise => { + return await this.executeMoveFunction(MoveModule.Attester, MoveFunction.SetSignatureThreshold, [threshold]); + }; + + getSignatureThreshold = async (): Promise => { + const signatureThreshold = await this.executeMoveViewFunction(MoveModule.Attester, MoveFunction.GetSignatureThreshold); + return signatureThreshold[0] as MoveUint64Type; + }; + + setMaxMessageBodySize = async (size: number): Promise => { + return await this.executeMoveFunction(MoveModule.MessageTransmitter, MoveFunction.SetMaxMessageBodySize, [size]); + }; + + getLocalDomain = async (): Promise => { + const localDomain = await this.executeMoveViewFunction(MoveModule.MessageTransmitter, MoveFunction.LocalDomain); + return localDomain[0] as MoveUint32Type; + }; + + getVersion = async (): Promise => { + const version = await this.executeMoveViewFunction(MoveModule.MessageTransmitter, MoveFunction.Version); + return version[0] as MoveUint32Type; + }; + + getMaxMessageBodySize = async (): Promise => { + const maxMessageBodySize = await this.executeMoveViewFunction(MoveModule.MessageTransmitter, MoveFunction.MaxMessageBodySize); + return maxMessageBodySize[0] as MoveUint64Type; + }; + + getObjectAddress = (): AccountAddress => { + return createObjectAddress(AccountAddress.from(this.packageId), "MessageTransmitter"); + }; +} diff --git a/typescript/aptos/client/stablecoinClient.ts b/typescript/aptos/client/stablecoinClient.ts new file mode 100644 index 0000000..de58181 --- /dev/null +++ b/typescript/aptos/client/stablecoinClient.ts @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2024, Circle Internet Group, Inc. + * All rights reserved. + * + * Circle Internet Group, Inc. CONFIDENTIAL + * + * This file includes unpublished proprietary source code of Circle Internet + * Group, Inc. The copyright notice above does not evidence any actual or + * intended publication of such source code. Disclosure of this source code + * or any related proprietary information is strictly prohibited without + * the express written permission of Circle Internet Group, Inc. + */ + +import { AptosContractClient } from "./aptosContractClient"; +import { + AccountAddress, + Aptos, + CommittedTransactionResponse, + createObjectAddress, + Ed25519Account, + U8, + U64, +} from "@aptos-labs/ts-sdk"; +import { MoveModule } from "../utils/moveModule"; +import { MoveFunction } from "../utils/moveFunction"; +import { PackageName } from "../utils/package"; + +export class StablecoinClient extends AptosContractClient { + constructor(aptos: Aptos, packageDeployer: Ed25519Account) { + super(aptos, PackageName.Stablecoin, packageDeployer); + } + + initializeState = async ( + name: string, + symbol: string, + decimals: U8, + iconUri: string, + projectUri: string + ): Promise => { + return await this.executeMoveFunction(MoveModule.Stablecoin, MoveFunction.InitializeV1, [ + name, + symbol, + decimals, + iconUri, + projectUri, + ]); + }; + + publishPackage = async (filePath: string, aptosExtensionsPackageId: string): Promise => { + return await this.buildAndPublishPackage( + `${filePath}/${this.packageName}/`, + this.packageDeployer, + PackageName.Stablecoin, + [ + { name: "deployer", address: this.packageDeployer.accountAddress.toString() }, + { name: `${PackageName.AptosExtensions}`, address: aptosExtensionsPackageId }, + ], + new Uint8Array(Buffer.from(PackageName.Stablecoin)), + "sparse" + ); + }; + + configureController = async ( + controller: AccountAddress, + minter: AccountAddress + ): Promise => { + return await this.executeMoveFunction(MoveModule.Treasury, MoveFunction.ConfigureController, [ + controller, + minter.toString(), + ]); + }; + + configureMinter = async (signer: Ed25519Account, allowance: U64): Promise => { + return await this.executeMoveFunction(MoveModule.Treasury, MoveFunction.ConfigureMinter, [allowance], signer); + }; + + getObjectAddress = (): AccountAddress => { + return createObjectAddress(AccountAddress.from(this.packageId), "stablecoin"); + }; +} diff --git a/typescript/aptos/client/tokenMessengerMinterClient.ts b/typescript/aptos/client/tokenMessengerMinterClient.ts new file mode 100644 index 0000000..6f96907 --- /dev/null +++ b/typescript/aptos/client/tokenMessengerMinterClient.ts @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2024, Circle Internet Group, Inc. + * All rights reserved. + * + * Circle Internet Group, Inc. CONFIDENTIAL + * + * This file includes unpublished proprietary source code of Circle Internet + * Group, Inc. The copyright notice above does not evidence any actual or + * intended publication of such source code. Disclosure of this source code + * or any related proprietary information is strictly prohibited without + * the express written permission of Circle Internet Group, Inc. + */ + +import { AptosContractClient } from "./aptosContractClient"; +import { + Account, + AccountAddress, + Aptos, + CommittedTransactionResponse, + createObjectAddress, + Ed25519Account, + MoveOption, + MoveVector, + U32, + U64, + U8, +} from "@aptos-labs/ts-sdk"; +import { PackageName } from "../utils/package"; +import { CctpFunctionScript } from "../utils/cctpFunctionScript"; +import { MoveModule } from "../utils/moveModule"; +import { MoveFunction } from "../utils/moveFunction"; +import { MoveUint32Type, MoveUint64Type } from "@aptos-labs/ts-sdk/src/types"; + +export class TokenMessengerMinterClient extends AptosContractClient { + constructor(aptos: Aptos, packageDeployer: Ed25519Account) { + super(aptos, PackageName.TokenMessengerMinter, packageDeployer); + } + + initializeState = async ( + messageBodyVersion: U32, + tokenController: AccountAddress + ): Promise => { + return await this.executeMoveFunction( + MoveModule.TokenMessengerMinter, + MoveFunction.InitializeTokenMessengerMinter, + [messageBodyVersion, tokenController] + ); + }; + + publishPackage = async ( + filePath: string, + aptosExtensionsPackageId: string, + messageTransmitterPackageId: string, + stablecoinPackageId: string, + includedArtifacts: string + ): Promise => { + return await this.buildAndPublishPackage( + `${filePath}/${this.packageName}/`, + this.packageDeployer, + PackageName.TokenMessengerMinter, + [ + { name: "deployer", address: this.packageDeployer.accountAddress.toString() }, + { name: `${PackageName.AptosExtensions}`, address: aptosExtensionsPackageId }, + { name: `${PackageName.MessageTransmitter}`, address: messageTransmitterPackageId }, + { name: `${PackageName.Stablecoin}`, address: stablecoinPackageId }, + ], + new Uint8Array(Buffer.from(PackageName.TokenMessengerMinter)), + includedArtifacts + ); + }; + + getObjectAddress = (): AccountAddress => { + return createObjectAddress(AccountAddress.from(this.packageId), "TokenMessengerMinter"); + }; + + depositForBurn = async ( + filePath: string, + signer: Account, + amount: U64, + destinationDomain: U32, + mintRecipient: AccountAddress, + burnToken: AccountAddress, + destinationCaller?: AccountAddress + ): Promise => { + let moveScript = CctpFunctionScript.DepositForBurn; + const functionArgs = [amount, destinationDomain, mintRecipient, burnToken]; + if (destinationCaller) { + moveScript = CctpFunctionScript.DepositForBurnWithCaller; + functionArgs.push(destinationCaller); + } + return await this.executeMoveScript(filePath, moveScript, functionArgs, signer); + }; + + replaceDepositForBurn = async ( + filePath: string, + originalMessage: MoveVector, + originalAttestation: MoveVector, + newDestinationCaller: MoveOption, + newMintRecipient: MoveOption + ): Promise => { + return await this.executeMoveScript(filePath, CctpFunctionScript.ReplaceDepositForBurn, [ + originalMessage, + originalAttestation, + newDestinationCaller, + newMintRecipient, + ]); + }; + + handleReceiveMessage = async ( + filePath: string, + messageBytes: MoveVector, + attestation: MoveVector + ): Promise => { + return await this.executeMoveScript(filePath, CctpFunctionScript.HandleReceiveMessage, [messageBytes, attestation]); + }; + + addRemoteTokenMessenger = async ( + remoteDomain: U32, + remoteTokenMessenger: AccountAddress + ): Promise => { + return await this.executeMoveFunction(MoveModule.TokenMessenger, MoveFunction.AddRemoteTokenMessenger, [ + remoteDomain, + remoteTokenMessenger.toString(), + ]); + }; + + removeRemoteTokenMessenger = async (remoteDomain: U32): Promise => { + return await this.executeMoveFunction(MoveModule.TokenMessenger, MoveFunction.RemoveRemoteTokenMessenger, [ + remoteDomain, + ]); + }; + + getRemoteTokenMessenger = async (remoteDomain: U32): Promise => { + const remoteTokenMessenger = await this.executeMoveViewFunction( + MoveModule.TokenMessenger, + MoveFunction.GetRemoteTokenMessenger, + [remoteDomain] + ); + return AccountAddress.fromString(remoteTokenMessenger[0] as string); + }; + + getMessageBodyVersion = async (): Promise => { + const messageBodyVersion = await this.executeMoveViewFunction( + MoveModule.TokenMessenger, + MoveFunction.MessageBodyVersion + ); + return messageBodyVersion[0] as MoveUint32Type; + }; + + getNumRemoteTokenMessenger = async (): Promise => { + const numRemoteTokenMessengers = await this.executeMoveViewFunction( + MoveModule.TokenMessenger, + MoveFunction.NumRemoteTokenMessengers + ); + return numRemoteTokenMessengers[0] as MoveUint64Type; + }; + + getTokenController = async (): Promise => { + const tokenController = await this.executeMoveViewFunction( + MoveModule.TokenController, + MoveFunction.GetTokenController + ); + return AccountAddress.fromString(tokenController[0] as string); + }; + + getLinkedToken = async (remoteDomain: U32, remoteToken: AccountAddress): Promise => { + const localToken = await this.executeMoveViewFunction( + MoveModule.TokenController, + MoveFunction.GetLinkedToken, + [remoteDomain, remoteToken] + ); + return AccountAddress.fromString(localToken[0] as string); + }; + + setTokenController = async (controllerAddress: AccountAddress): Promise => { + return await this.executeMoveFunction(MoveModule.TokenController, MoveFunction.SetTokenController, [ + controllerAddress.toString(), + ]); + }; + + setMaxBurnAmountPerMessage = async ( + tokenAddress: AccountAddress, + amount: U64 + ): Promise => { + return await this.executeMoveFunction(MoveModule.TokenController, MoveFunction.SetMaxBurnAmountPerMessage, [ + tokenAddress.toString(), + amount, + ]); + }; + + linkTokenPair = async ( + localToken: AccountAddress, + remoteDomain: U32, + remoteTokenAddress: AccountAddress + ): Promise => { + return await this.executeMoveFunction(MoveModule.TokenController, MoveFunction.LinkTokenPair, [ + localToken.toString(), + remoteDomain, + remoteTokenAddress.toString(), + ]); + }; + + unlinkTokenPair = async ( + remoteDomain: number, + remoteTokenAddress: AccountAddress + ): Promise => { + return await this.executeMoveFunction(MoveModule.TokenController, MoveFunction.UnlinkTokenPair, [ + remoteDomain, + remoteTokenAddress.toString(), + ]); + }; + + mint = async ( + filePath: string, + minter: Ed25519Account, + amount: U64, + mintRecipient: AccountAddress + ): Promise => { + return await this.executeMoveScript(filePath, CctpFunctionScript.Mint, [amount, mintRecipient], minter); + }; + + signerAddress = (): AccountAddress => { + return this.getObjectAddress(); + }; +} diff --git a/typescript/aptos/deploy/calculateDeploymentAddresses.ts b/typescript/aptos/deploy/calculateDeploymentAddresses.ts new file mode 100644 index 0000000..ffe8615 --- /dev/null +++ b/typescript/aptos/deploy/calculateDeploymentAddresses.ts @@ -0,0 +1,55 @@ +/** + * Copyright 2024 Circle Internet Group, Inc. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AccountAddress, createObjectAddress, createResourceAddress } from "@aptos-labs/ts-sdk"; +import { program } from "commander"; + +function calculateDeploymentAddresses({ deployer }: { deployer: string }) { + const messageTransmitterPackageAddress = createResourceAddress( + AccountAddress.fromStringStrict(deployer), + new Uint8Array(Buffer.from("message_transmitter")) + ); + const messageTransmitterObjectAddress = createObjectAddress( + messageTransmitterPackageAddress, + new Uint8Array(Buffer.from("MessageTransmitter")) + ); + + const tokenMessengerMinterPackageAddress = createResourceAddress( + AccountAddress.fromStringStrict(deployer), + new Uint8Array(Buffer.from("token_messenger_minter")) + ); + const tokenMessengerMinterObjectAddress = createObjectAddress( + tokenMessengerMinterPackageAddress, + new Uint8Array(Buffer.from("TokenMessengerMinter")) + ); + + console.log(`MessageTransmitter package address: ${messageTransmitterPackageAddress.toStringLong()}`); + console.log(`TokenMessengerMinter package address: ${tokenMessengerMinterPackageAddress.toStringLong()}`); + console.log(`MessageTransmitter object address: ${messageTransmitterObjectAddress.toStringLong()}`); + console.log(`TokenMessengerMinter object address: ${tokenMessengerMinterObjectAddress.toStringLong()}`); +} + +/* +Example - yarn calculate-deployment-addresses --deployer=0x5ba1674a3ffa843ed88aa4a0a051b9a52f76459a8853e5cd62b22bcc488d2765 +*/ + +export default program + .createCommand("calculate-deployment-addresses") + .description("Calculate the addresses that the packages will be deployed to.") + .requiredOption("--deployer ", "Deployer address") + .action(calculateDeploymentAddresses); diff --git a/typescript/aptos/deploy/deployAllPackages.ts b/typescript/aptos/deploy/deployAllPackages.ts new file mode 100644 index 0000000..6d32f4f --- /dev/null +++ b/typescript/aptos/deploy/deployAllPackages.ts @@ -0,0 +1,72 @@ +/** + * Copyright 2024 Circle Internet Group, Inc. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { program } from "commander"; +import { Ed25519Account, Ed25519PrivateKey } from "@aptos-labs/ts-sdk"; +import { MessageTransmitterClient } from "../client/messageTransmitterClient"; +import { getAptosClient } from "../utils/helper"; +import { TokenMessengerMinterClient } from "../client/tokenMessengerMinterClient"; + +export type DeployAllPackagesOptions = { + privateKey: string; + rpc: string; + aptosExtensionsPackageId: string; + stablecoinPackageId: string; + includedArtifacts: string; +}; + +async function deployAllPackages(options: DeployAllPackagesOptions) { + const aptos = getAptosClient(options.rpc); + const deployer = new Ed25519Account({ + privateKey: new Ed25519PrivateKey(options.privateKey), + }); + console.log(`Deployer account: ${deployer.accountAddress}\n`); + + const messageTransmitterClient = new MessageTransmitterClient(aptos, deployer); + const tokenMessengerMinterClient = new TokenMessengerMinterClient(aptos, deployer); + + const messageTransmitterPackageId = await messageTransmitterClient.publishPackage( + "packages", + options.aptosExtensionsPackageId, + options.includedArtifacts + ); + console.log(`MessageTransmitter package ID: ${messageTransmitterPackageId}\n`); + + const tokenMessengerMinterPackageId = await tokenMessengerMinterClient.publishPackage( + "packages", + options.aptosExtensionsPackageId, + messageTransmitterPackageId, + options.stablecoinPackageId, + options.includedArtifacts + ); + console.log(`TokenMessengerMinter package ID: ${tokenMessengerMinterPackageId}\n`); +} + +/* +Example - yarn deploy --privateKey= --rpc=http://localhost:8080 --aptosExtensionsPackageId=0x9b43208bdff59a953f929309b1238ad0126a471002796261f8ce936c59917efb --stablecoinPackageId=0xf8e95ec344bb3fa74d2539fe15a01094ab3746ed3782c740e8befdd37e8095c7 +*/ + +export default program + .createCommand("deploy") + .description("Deploy all packages") + .requiredOption("--privateKey ", "Deployer Private key") + .requiredOption("--rpc ", "RPC URL") + .requiredOption("--aptosExtensionsPackageId ", "AptosExtensions package address") + .requiredOption("--stablecoinPackageId ", "Stablecoin package address") + .option("--includedArtifacts ", "Included artifacts", "none") + .action(deployAllPackages); diff --git a/typescript/aptos/deploy/generateDefaultPackageMetadata.ts b/typescript/aptos/deploy/generateDefaultPackageMetadata.ts new file mode 100644 index 0000000..99cd058 --- /dev/null +++ b/typescript/aptos/deploy/generateDefaultPackageMetadata.ts @@ -0,0 +1,108 @@ +/** + * Copyright 2024 Circle Internet Group, Inc. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + MoveOption, + MoveString, + MoveVector, + Serializable, + Serializer, + U64, + U8 +} from "@aptos-labs/ts-sdk"; + +/** + * Translated from https://github.com/aptos-labs/aptos-core/blob/f996f5855e54388f3d4f77e46b159b2498c850d4/aptos-move/framework/aptos-framework/sources/code.move#L67. + */ +class UpgradePolicy extends Serializable { + private policy: U8; + + constructor(policy: U8) { + super(); + this.policy = policy; + } + + serialize(serializer: Serializer): void { + serializer.serialize(this.policy); + } +} + +/** + * Translated from https://github.com/aptos-labs/aptos-core/blob/f996f5855e54388f3d4f77e46b159b2498c850d4/aptos-move/framework/aptos-framework/sources/code.move#L27. + */ +class PackageMetadata extends Serializable { + private name: MoveString; + private upgrade_policy: UpgradePolicy; + private upgrade_number: U64; + private source_digest: MoveString; + private manifest: MoveVector; + private modules: MoveVector; + private deps: MoveVector; + private extension: MoveOption; + + constructor( + name: MoveString, + upgrade_policy: UpgradePolicy, + upgrade_number: U64, + source_digest: MoveString, + manifest: MoveVector, + modules: MoveVector, + deps: MoveVector, + extension: MoveOption + ) { + super(); + this.name = name; + this.upgrade_policy = upgrade_policy; + this.upgrade_number = upgrade_number; + this.source_digest = source_digest; + this.manifest = manifest; + this.modules = modules; + this.deps = deps; + this.extension = extension; + } + + serialize(serializer: Serializer): void { + serializer.serialize(this.name); + serializer.serialize(this.upgrade_policy); + serializer.serialize(this.upgrade_number); + serializer.serialize(this.source_digest); + serializer.serialize(this.manifest); + serializer.serialize(this.modules); + serializer.serialize(this.deps); + serializer.serialize(this.extension); + } +} + +async function main() { + // BCS-serialize a default PackageMetadata. + const packageMetadata = new PackageMetadata( + new MoveString("test"), + new UpgradePolicy(new U8(1)), + new U64(0), + new MoveString(""), + new MoveVector([]), + new MoveVector([]), + new MoveVector([]), + new MoveOption(null) + ); + const serializer = new Serializer(); + serializer.serialize(packageMetadata); + console.log("0x" + Buffer.from(serializer.toUint8Array()).toString("hex")); +} + +main().catch(console.error); diff --git a/typescript/aptos/deploy/index.ts b/typescript/aptos/deploy/index.ts new file mode 100644 index 0000000..e04a499 --- /dev/null +++ b/typescript/aptos/deploy/index.ts @@ -0,0 +1,36 @@ +/** + * Copyright 2024 Circle Internet Group, Inc. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { program } from "commander"; + +import calculateDeploymentAddresses from "./calculateDeploymentAddresses"; +import deployAllPackages from "./deployAllPackages"; +import upgradePackage from "./upgradePackage"; +import verifyPackage from "./verifyPackage"; + +program + .name("scripts") + .description("Scripts related to Aptos development") + .addCommand(calculateDeploymentAddresses) + .addCommand(deployAllPackages) + .addCommand(upgradePackage) + .addCommand(verifyPackage); + +if (process.env.NODE_ENV !== "TESTING") { + program.parse(); +} diff --git a/typescript/aptos/deploy/upgradePackage.ts b/typescript/aptos/deploy/upgradePackage.ts new file mode 100644 index 0000000..9473462 --- /dev/null +++ b/typescript/aptos/deploy/upgradePackage.ts @@ -0,0 +1,80 @@ +/** + * Copyright 2024 Circle Internet Group, Inc. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { program } from "commander"; +import { executeTransaction, getAptosClient } from "../utils/helper"; +import { Ed25519Account, Ed25519PrivateKey, InputGenerateTransactionPayloadData, MoveVector } from "@aptos-labs/ts-sdk"; +import fs from "fs"; + +async function upgradePackage({ + privateKey, + rpc, + payloadFilePath, + aptosExtensionsPackageId, + packageId, +}: { + privateKey: string; + rpc: string; + payloadFilePath: string; + aptosExtensionsPackageId: string; + packageId: string; +}): Promise { + const aptos = getAptosClient(rpc); + + const admin = new Ed25519Account({ + privateKey: new Ed25519PrivateKey(privateKey), + }); + + console.log(`Admin account: ${admin.accountAddress}\n`); + + if (!fs.existsSync(payloadFilePath)) { + throw new Error(`Failed to load payload file: ${payloadFilePath}`); + } + const jsonData = JSON.parse(fs.readFileSync(payloadFilePath, "utf-8")); + + if (!jsonData || !jsonData.args || jsonData.args.length < 2 || !jsonData.args[0].value || !jsonData.args[1].value) { + throw new Error(`Invalid payload file: ${payloadFilePath}`); + } + console.log(`Upgrading package using payload: ${payloadFilePath}\n`); + + const metadataBytes: string = jsonData.args[0].value; + const bytecode: string[] = jsonData.args[1].value; + + const data: InputGenerateTransactionPayloadData = { + function: `${aptosExtensionsPackageId}::upgradable::upgrade_package`, + functionArguments: [packageId, MoveVector.U8(metadataBytes), new MoveVector(bytecode.map(MoveVector.U8))], + }; + + await executeTransaction({ aptos, sender: admin, data }); + + console.log(`Package upgraded successfully!\n`); +} + +/* +Example - yarn upgrade-pkg --privateKey= --rpc=http://localhost:8080 --payloadFilePath=upgrade.json --aptosExtensionsPackageId=0x9b43208bdff59a953f929309b1238ad0126a471002796261f8ce936c59917efb --packageId=0x66535c727de1d398a82593bec9b90be36385f9ad28cc48852123404adbf9bccf +*/ + +export default program + .createCommand("upgrade") + .description("Upgrade the given package") + .requiredOption("-r, --rpc ", "Network RPC URL") + .requiredOption("--privateKey ", "Admin private key") + .requiredOption("--payloadFilePath ", "The publish package JSON payload file path") + .requiredOption("--aptosExtensionsPackageId ", "aptos_extensions package address") + .requiredOption("--packageId ", "address of package to be upgraded") + .action(upgradePackage); diff --git a/typescript/aptos/deploy/verifyPackage.ts b/typescript/aptos/deploy/verifyPackage.ts new file mode 100644 index 0000000..e8cd827 --- /dev/null +++ b/typescript/aptos/deploy/verifyPackage.ts @@ -0,0 +1,103 @@ +/** + * Copyright 2024 Circle Internet Group, Inc. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { strict as assert } from "assert"; +import { execSync } from "child_process"; +import { program } from "commander"; +import { + buildPackage, + formatNamedAddresses, + getAptosClient, + getPackageBytecode, + NamedAddress, + parseNamedAddresses, +} from "../utils/helper"; + +type BytecodeVerificationResult = { + packageName: string; + bytecodeVerified: boolean; + metadataVerified: boolean; +}; + +export async function verifyPackage({ + packageName, + packageId, + namedDeps, + rpc, + includedArtifacts, +}: { + packageName: string; + packageId: string; + namedDeps: NamedAddress[]; + rpc: string; + includedArtifacts: string; +}): Promise { + const aptos = getAptosClient(rpc); + const namedAddresses = [...namedDeps, { name: packageName, address: packageId }]; + + const localModuleBytecode = ( + await buildPackage(`packages/${packageName}/`, packageName, namedAddresses, includedArtifacts) + ).bytecode; + const remoteModuleBytecode = await getPackageBytecode(aptos, packageId); + + // Comparing remote bytecode against local compilation + // Local bytecode list is arranged according to the module dependency hierarchy + // For simplicity, we compare the sorted list of bytecode + localModuleBytecode.sort(); + remoteModuleBytecode.sort(); + + let bytecodeVerified = false; + try { + assert.deepStrictEqual(localModuleBytecode, remoteModuleBytecode); + bytecodeVerified = true; + } catch (e) { + console.error(e); + } + + // Begin verifying package metadata + // Setting to enable or disable source code verification + const verifyMetadataCommand = `make verify-metadata \ + package="${packageName}" \ + package_id="${packageId}" \ + url="${rpc}" \ + named_addresses="${formatNamedAddresses(namedAddresses)}" \ + included_artifacts="${includedArtifacts}"`; + + const result = execSync(verifyMetadataCommand, { encoding: "utf-8" }); + const metadataVerified = result.includes("Successfully verified source of package"); + + return { packageName, bytecodeVerified, metadataVerified }; +} + +/* +Example - yarn verify-pkg --packageName=message_transmitter --rpc=http://localhost:8080 --packageId=0x66535c727de1d398a82593bec9b90be36385f9ad28cc48852123404adbf9bccf --namedDeps aptos_extensions=0x9b43208bdff59a953f929309b1238ad0126a471002796261f8ce936c59917efb,deployer=0x5ba1674a3ffa843ed88aa4a0a051b9a52f76459a8853e5cd62b22bcc488d2765 +*/ + +export default program + .createCommand("verify-pkg") + .description("Verify bytecode and metadata of a deployed package match local source code.") + .requiredOption("--packageName ", "The name of the package to verify.") + .requiredOption("--packageId ", "The address where the package is located.") + .requiredOption("--namedDeps ", "Named dependency addresses of the deployed package.") + .requiredOption("-r, --rpc ", "Network RPC URL") + .option("--includedArtifacts", "Whether source code verification was enabled during package deployment.", "sparse") + .action(async (options) => { + const namedDeps = parseNamedAddresses(options.namedDeps); + const result = await verifyPackage(Object.assign(options, { namedDeps })); + console.log(result); + }); diff --git a/typescript/aptos/utils/cctpFunction.ts b/typescript/aptos/utils/cctpFunction.ts new file mode 100644 index 0000000..fbd73cc --- /dev/null +++ b/typescript/aptos/utils/cctpFunction.ts @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024, Circle Internet Group, Inc. + * All rights reserved. + * + * Circle Internet Group, Inc. CONFIDENTIAL + * + * This file includes unpublished proprietary source code of Circle Internet + * Group, Inc. The copyright notice above does not evidence any actual or + * intended publication of such source code. Disclosure of this source code + * or any related proprietary information is strictly prohibited without + * the express written permission of Circle Internet Group, Inc. + */ + +export enum MoveFunction { + // MessageTransmitter + InitializeMessageTransmitter = "initialize_message_transmitter", + DisableAttester = "disable_attester", + EnableAttester = "enable_attester", + GetAttesterManager = "attester_manager", + GetEnabledAttester = "get_enabled_attester", + IsEnabledAttester = "is_enabled_attester", + SetMaxMessageBodySize = "set_max_message_body_size", + SetSignatureThreshold = "set_signature_threshold", + UpdateAttesterManager = "update_attester_manager", + + // TokenMessengerMinter + InitializeTokenMessengerMinter = "initialize_token_messenger_minter", + AddRemoteTokenMessenger = "add_remote_token_messenger", + GetRemoteTokenMessenger = "remote_token_messenger", + GetTokenController = "get_token_controller", + LinkTokenPair = "link_token_pair", + SetMaxBurnAmountPerMessage = "set_max_burn_amount_per_message", + SetTokenController = "set_token_controller", + RemoveRemoteTokenMessenger = "remove_remote_token_messenger", + UnlinkTokenPair = "unlink_token_pair", + SignerAddress = "signer_address", + + // Ownable + TransferOwnership = "transfer_ownership", + AcceptOwnership = "accept_ownership", + Owner = "owner", + PendingOwner = "pending_owner", + + // Pausable + Pause = "pause", + Unpause = "unpause", + UpdatePauser = "update_pauser", + Pauser = "pauser", + + // Stablecoin + InitializeV1 = "initialize_v1", + ConfigureController = "configure_controller", + ConfigureMinter = "configure_minter", +} diff --git a/typescript/aptos/utils/cctpFunctionScript.ts b/typescript/aptos/utils/cctpFunctionScript.ts new file mode 100644 index 0000000..6968bea --- /dev/null +++ b/typescript/aptos/utils/cctpFunctionScript.ts @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024, Circle Internet Group, Inc. + * All rights reserved. + * + * Circle Internet Group, Inc. CONFIDENTIAL + * + * This file includes unpublished proprietary source code of Circle Internet + * Group, Inc. The copyright notice above does not evidence any actual or + * intended publication of such source code. Disclosure of this source code + * or any related proprietary information is strictly prohibited without + * the express written permission of Circle Internet Group, Inc. + */ + +export enum CctpFunctionScript { + // MessageTransmitter + InitMessageTransmitter = "init_message_transmitter", + ReceiveMessage = "receive_message", + ReplaceMessage = "replace_message", + SendMessage = "send_message", + SendMessageWithCaller = "send_message_with_caller", + + // TokenMessengerMinter + InitTokenMessengerMinter = "init_token_messenger_minter", + DepositForBurn = "deposit_for_burn", + DepositForBurnWithCaller = "deposit_for_burn_with_caller", + HandleReceiveMessage = "handle_receive_message", + ReplaceDepositForBurn = "replace_deposit_for_burn", + Mint = "mint", +} diff --git a/typescript/aptos/utils/cctpModule.ts b/typescript/aptos/utils/cctpModule.ts new file mode 100644 index 0000000..26f75cf --- /dev/null +++ b/typescript/aptos/utils/cctpModule.ts @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024, Circle Internet Group, Inc. + * All rights reserved. + * + * Circle Internet Group, Inc. CONFIDENTIAL + * + * This file includes unpublished proprietary source code of Circle Internet + * Group, Inc. The copyright notice above does not evidence any actual or + * intended publication of such source code. Disclosure of this source code + * or any related proprietary information is strictly prohibited without + * the express written permission of Circle Internet Group, Inc. + */ + +export enum MoveModule { + Attester = "attester", + MessageTransmitter = "message_transmitter", + TokenMessenger = "token_messenger", + TokenMessengerMinter = "token_messenger_minter", + TokenController = "token_controller", + Stablecoin = "stablecoin", + Treasury = "treasury", + + // Aptos Extensions + Pausable = "pausable", + Ownable = "ownable", +} diff --git a/typescript/aptos/utils/cctpPackage.ts b/typescript/aptos/utils/cctpPackage.ts new file mode 100644 index 0000000..c3a7993 --- /dev/null +++ b/typescript/aptos/utils/cctpPackage.ts @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2024, Circle Internet Group, Inc. + * All rights reserved. + * + * Circle Internet Group, Inc. CONFIDENTIAL + * + * This file includes unpublished proprietary source code of Circle Internet + * Group, Inc. The copyright notice above does not evidence any actual or + * intended publication of such source code. Disclosure of this source code + * or any related proprietary information is strictly prohibited without + * the express written permission of Circle Internet Group, Inc. + */ + +export enum PackageName { + MessageTransmitter = "message_transmitter", + TokenMessengerMinter = "token_messenger_minter", + AptosExtensions = "aptos_extensions", + Stablecoin = "stablecoin", +} diff --git a/typescript/aptos/utils/helper.ts b/typescript/aptos/utils/helper.ts new file mode 100644 index 0000000..78eb5ae --- /dev/null +++ b/typescript/aptos/utils/helper.ts @@ -0,0 +1,201 @@ +/** + * Copyright 2024 Circle Internet Group, Inc. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Aptos, + Account, + Ed25519Account, + Event, + UserTransactionResponse, + InputGenerateTransactionPayloadData, + AptosConfig, + Network, + Ed25519PrivateKey, + MoveModuleBytecode, +} from "@aptos-labs/ts-sdk"; +import { execSync } from "child_process"; + +import fs from "fs"; + +export type NamedAddress = { name: string; address: string }; + +export function getAptosClient(url?: string): Aptos { + return url === undefined + ? new Aptos(new AptosConfig({ network: Network.LOCAL })) + : new Aptos( + new AptosConfig({ + fullnode: `${url}/v1`, + }) + ); +} + +/** + * @returns a new account with 1_000_000_000 Aptos + */ +export async function generateFundedAccount(aptosClient: Aptos): Promise { + const acct = Account.generate(); + await aptosClient.fundAccount({ + accountAddress: acct.accountAddress, + amount: 1_000_000_000, + }); + return acct; +} + +/** + * @returns a new account with 1_000_000_000 Aptos + */ +export async function generateFundedAccountFromPrivateKey( + aptosClient: Aptos, + privateKey: string +): Promise { + const acct = Account.fromPrivateKey({ privateKey: new Ed25519PrivateKey(privateKey) }); + await aptosClient.fundAccount({ + accountAddress: acct.accountAddress, + amount: 1_000_000_000, + }); + return acct; +} + +/** + * Finds a specific event from the transaction output + */ +export function getEventByType(txOutput: UserTransactionResponse, eventType: string): Event { + const event = txOutput.events.find((e: any) => e.type === eventType); + return event; +} + +/** + * Reformat address to ensure it is 0x-prefixed and 66 characters long + */ +export function normalizeAddress(address: string): string { + if (address.length < 66) { + return `0x${address.replace("0x", "").padStart(64, "0")}`; + } + return address; +} + +/** + * Executes a transaction and waits for it to be included in a block + * @returns the transaction output + * @throws if the transaction fails + */ +export async function executeTransaction({ + aptos, + sender, + data, +}: { + aptos: Aptos; + sender: Ed25519Account; + data: InputGenerateTransactionPayloadData; +}): Promise { + const transaction = await aptos.transaction.build.simple({ + sender: sender.accountAddress, + data, + }); + const response = await aptos.signAndSubmitTransaction({ + signer: sender, + transaction, + }); + const txOutput = await aptos.waitForTransaction({ + transactionHash: response.hash, + }); + if (!txOutput.success) { + console.error(txOutput); + throw new Error("Unexpected transaction failure"); + } + return txOutput as UserTransactionResponse; +} + +/** + * Parses the package address from the transaction output + */ +export function getPublishedPackageFromTxOutput(txOutput: UserTransactionResponse): string { + const rawCodeAddress = getEventByType(txOutput, "0x1::code::PublishPackage").data.code_address; + return normalizeAddress(rawCodeAddress); +} + +export async function buildPackage( + packageDir: string, + packageName: string, + namedAddresses: NamedAddress[], + includedArtifacts: string +): Promise<{ + metadataBytes: string; + bytecode: string[]; +}> { + const payloadFilePath = await buildPackagePublishPayload(packageDir, packageName, namedAddresses, includedArtifacts); + const jsonData = JSON.parse(fs.readFileSync(payloadFilePath, "utf-8")); + fs.unlinkSync(payloadFilePath); // delete saved json at PAYLOAD_FILE_PATH + + return { + metadataBytes: jsonData.args[0].value, + bytecode: jsonData.args[1].value, + }; +} + +/** + * A convenience function to compile a package locally with the CLI + * @param packageDir + * @param packageName + * @param namedAddresses + */ +export async function buildPackagePublishPayload( + packageDir: string, + packageName: string, + namedAddresses: NamedAddress[], + includedArtifacts: string +) { + const addressArg = formatNamedAddresses(namedAddresses); + const payloadFilePath = `build-output-${packageName}-${Date.now()}.json`; + const buildCommand = `aptos move build-publish-payload --json-output-file ${payloadFilePath} --package-dir ${packageDir} --named-addresses ${addressArg} --included-artifacts ${includedArtifacts}`; + execSync(buildCommand, { encoding: "utf-8" }); + if (!fs.existsSync(payloadFilePath)) { + throw new Error(`Build failed with the following command: ${buildCommand}`); + } + return payloadFilePath; +} + +/** + * Fetches the bytecode of a package + * Works for packages that have up to 25 modules + * @returns string[] of bytecode + */ +export async function getPackageBytecode(aptos: Aptos, packageId: string): Promise { + const rawRemoteModuleBytecode: MoveModuleBytecode[] = await aptos.getAccountModules({ + accountAddress: packageId, + options: { limit: 25 }, + }); + return rawRemoteModuleBytecode.map((module) => module.bytecode); +} + +/** + * Formats a list of named addresses into a string of the format "name1=address1,name2=address2" + */ +export function formatNamedAddresses(namedAddresses: NamedAddress[]): string { + return namedAddresses.map(({ name, address }) => `${name}=${address}`).join(","); +} + +/** + * Parses a string of named addresses in the format "name1=address1,name2=address2" + */ +export function parseNamedAddresses(namedAddressArg: string): NamedAddress[] { + return namedAddressArg.split(",").map((arg) => { + const [name, address] = arg.split("="); + return { name, address }; + }); +} diff --git a/typescript/aptos/utils/moveFunction.ts b/typescript/aptos/utils/moveFunction.ts new file mode 100644 index 0000000..4d5fe92 --- /dev/null +++ b/typescript/aptos/utils/moveFunction.ts @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2024, Circle Internet Group, Inc. + * All rights reserved. + * + * Circle Internet Group, Inc. CONFIDENTIAL + * + * This file includes unpublished proprietary source code of Circle Internet + * Group, Inc. The copyright notice above does not evidence any actual or + * intended publication of such source code. Disclosure of this source code + * or any related proprietary information is strictly prohibited without + * the express written permission of Circle Internet Group, Inc. + */ + +export enum MoveFunction { + // MessageTransmitter + InitializeMessageTransmitter = "initialize_message_transmitter", + DisableAttester = "disable_attester", + EnableAttester = "enable_attester", + GetAttesterManager = "attester_manager", + GetEnabledAttester = "get_enabled_attester", + IsEnabledAttester = "is_enabled_attester", + SetMaxMessageBodySize = "set_max_message_body_size", + SetSignatureThreshold = "set_signature_threshold", + UpdateAttesterManager = "update_attester_manager", + LocalDomain = "local_domain", + Version = "version", + MaxMessageBodySize = "max_message_body_size", + GetSignatureThreshold = "get_signature_threshold", + GetNumEnabledAttesters = "get_num_enabled_attesters", + + // TokenMessengerMinter + InitializeTokenMessengerMinter = "initialize_token_messenger_minter", + AddRemoteTokenMessenger = "add_remote_token_messenger", + GetRemoteTokenMessenger = "remote_token_messenger", + GetTokenController = "token_controller", + LinkTokenPair = "link_token_pair", + SetMaxBurnAmountPerMessage = "set_max_burn_amount_per_message", + SetTokenController = "set_token_controller", + RemoveRemoteTokenMessenger = "remove_remote_token_messenger", + UnlinkTokenPair = "unlink_token_pair", + SignerAddress = "signer_address", + MessageBodyVersion = "message_body_version", + NumRemoteTokenMessengers = "num_remote_token_messengers", + GetLinkedToken = "get_linked_token", + + // Ownable + TransferOwnership = "transfer_ownership", + AcceptOwnership = "accept_ownership", + Owner = "owner", + PendingOwner = "pending_owner", + + // Pausable + Pause = "pause", + Unpause = "unpause", + UpdatePauser = "update_pauser", + Pauser = "pauser", + IsPaused = "is_paused", + + // Stablecoin + InitializeV1 = "initialize_v1", + ConfigureController = "configure_controller", + ConfigureMinter = "configure_minter", + + // Manageable + ChangeAdmin = "change_admin", + AcceptAdmin = "accept_admin", + Admin = "admin", + PendingAdmin = "pending_admin", +} diff --git a/typescript/aptos/utils/moveModule.ts b/typescript/aptos/utils/moveModule.ts new file mode 100644 index 0000000..7a89905 --- /dev/null +++ b/typescript/aptos/utils/moveModule.ts @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024, Circle Internet Group, Inc. + * All rights reserved. + * + * Circle Internet Group, Inc. CONFIDENTIAL + * + * This file includes unpublished proprietary source code of Circle Internet + * Group, Inc. The copyright notice above does not evidence any actual or + * intended publication of such source code. Disclosure of this source code + * or any related proprietary information is strictly prohibited without + * the express written permission of Circle Internet Group, Inc. + */ + +export enum MoveModule { + Attester = "attester", + MessageTransmitter = "message_transmitter", + TokenMessenger = "token_messenger", + TokenMessengerMinter = "token_messenger_minter", + TokenController = "token_controller", + Stablecoin = "stablecoin", + Treasury = "treasury", + + // Aptos Extensions + Pausable = "pausable", + Ownable = "ownable", + Manageable = "manageable", +} diff --git a/typescript/aptos/utils/package.ts b/typescript/aptos/utils/package.ts new file mode 100644 index 0000000..c3a7993 --- /dev/null +++ b/typescript/aptos/utils/package.ts @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2024, Circle Internet Group, Inc. + * All rights reserved. + * + * Circle Internet Group, Inc. CONFIDENTIAL + * + * This file includes unpublished proprietary source code of Circle Internet + * Group, Inc. The copyright notice above does not evidence any actual or + * intended publication of such source code. Disclosure of this source code + * or any related proprietary information is strictly prohibited without + * the express written permission of Circle Internet Group, Inc. + */ + +export enum PackageName { + MessageTransmitter = "message_transmitter", + TokenMessengerMinter = "token_messenger_minter", + AptosExtensions = "aptos_extensions", + Stablecoin = "stablecoin", +} diff --git a/typescript/evm/client/evmContractClient.ts b/typescript/evm/client/evmContractClient.ts new file mode 100644 index 0000000..75bdffc --- /dev/null +++ b/typescript/evm/client/evmContractClient.ts @@ -0,0 +1,129 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/** + * Copyright (c) 2024, Circle Internet Group, Inc. + * All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { expect } from "@jest/globals"; +import * as ethutil from "@ethereumjs/util"; +import waitForExpect from "wait-for-expect"; +import { Contract, EventLog, TransactionReceipt, Web3 } from "web3"; + +export interface EvmContractDefinition { + messageTransmitterContract: Contract; + messageTransmitterContractAddress: string; + tokenMessengerContract: Contract; + tokenMessengerContractAddress: string; + tokenMinterContract: Contract; + tokenMinterContractAddress: string; + usdcContract: Contract; + usdcContractAddress: string; + web3: Web3; +} + +export const attestedMessage = async ( + contractDefinition: EvmContractDefinition, + txReceipt: TransactionReceipt +): Promise<{ attestation: string; txHash: string; messageBytes: Buffer; blockHeight: number }> => { + // Create an attestation using the initialized Anvil keypair + let logs: any = []; + await waitForExpect(async () => { + logs = await contractDefinition.messageTransmitterContract.getPastEvents("MessageSent", { + fromBlock: txReceipt.blockNumber, + toBlock: txReceipt.blockNumber, + }); + expect(logs.length).toBeGreaterThan(0); + }, 90_000); + + const messageBytes = String((logs[0] as EventLog).returnValues.message); + + return { + attestation: attestToMessage(contractDefinition.web3, messageBytes), + messageBytes: Buffer.from(messageBytes.replace("0x", ""), "hex"), + txHash: String(txReceipt.transactionHash), + blockHeight: Number(txReceipt.blockNumber), + }; +}; + +export const attestToMessage = (web3: Web3, messageBytes: string): string => { + // Create an attestation using the initialized Anvil keypair + const attesterPrivateKey = "0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97"; + + const messageHash = web3.utils.keccak256(messageBytes); + const signedMessage = ethutil.ecsign( + Buffer.from(ethutil.toBytes(messageHash)), + Buffer.from(ethutil.toBytes(attesterPrivateKey)) + ); + const attestation = ethutil.toRpcSig(signedMessage.v, signedMessage.r, signedMessage.s); + return attestation; +}; + +// Generates a depositForBurn tx from the given evm chain +export const generateEvmBurn = async ( + contractDefinition: EvmContractDefinition, + userAddress: string, + destAddress: string, + destDomain: number +): Promise<{ attestation: string; txHash: string; messageBytes: Buffer; blockHeight: number }> => { + // Set allowance for the userAddress + const txReceipt1 = await contractDefinition.usdcContract.methods + .approve(contractDefinition.tokenMessengerContractAddress, 1000) + .send({ from: userAddress }); + expect(txReceipt1.status).toBe(BigInt(1)); + + const paddedDestAddress = contractDefinition.web3.utils.padLeft(destAddress, 64); + + // Initiate a depositForBurn from the userAddress + const txReceipt2: TransactionReceipt = await contractDefinition.tokenMessengerContract.methods + .depositForBurn(1, destDomain, paddedDestAddress, contractDefinition.usdcContractAddress) + .send({ from: userAddress }); + expect(txReceipt2.status).toBe(BigInt(1)); + + return attestedMessage(contractDefinition, txReceipt2); +}; + +// Generates a sendMessage tx from the given evm chain +export const generateEvmSendMessage = async ( + contractDefinition: EvmContractDefinition, + userAddress: string, + destAddress: string, + destDomain: number +): Promise<{ attestation: string; txHash: string; messageBytes: Buffer; blockHeight: number }> => { + const paddedDestAddress = contractDefinition.web3.utils.padLeft(destAddress, 64); + const txReceipt: TransactionReceipt = await contractDefinition.messageTransmitterContract.methods + .sendMessage(destDomain, paddedDestAddress, "0x6d657373616765") // "message" string hex encoded + .send({ from: userAddress }); + expect(txReceipt.status).toBe(BigInt(1)); + return attestedMessage(contractDefinition, txReceipt); +}; + +export const receiveEvm = async ( + evmTestAddress: string, + destination: EvmContractDefinition, + message: Buffer, + attestation: string +): Promise => { + const destinationFrom = await destination.web3.eth.getBlockNumber(); + const destinationTxReceipt: TransactionReceipt = await destination.messageTransmitterContract.methods + .receiveMessage(message, attestation) + .send({ from: evmTestAddress }); + + const destinationLogs = await destination.messageTransmitterContract.getPastEvents("MessageReceived", { + fromBlock: destinationFrom, + toBlock: BigInt(destinationTxReceipt.blockNumber) + BigInt(1), + }); + expect(destinationLogs.length).toBeGreaterThan(0); +}; diff --git a/versions.sh b/versions.sh new file mode 100644 index 0000000..216f41b --- /dev/null +++ b/versions.sh @@ -0,0 +1 @@ +export APTOS_CLI_VERSION=4.5.0 # github-releases:aptos-labs/aptos-core extractVersion=^aptos-cli-v(?.*)$ diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..e0f1c2b --- /dev/null +++ b/yarn.lock @@ -0,0 +1,4885 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@adraffy/ens-normalize@^1.8.8": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz#42cc67c5baa407ac25059fcd7d405cc5ecdb0c33" + integrity sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg== + +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@aptos-labs/aptos-cli@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@aptos-labs/aptos-cli/-/aptos-cli-0.2.0.tgz#ee14acd2fe1e97d8aadf0970b5323ead90a58e62" + integrity sha512-6kljJFRsTLXCvgkNhBoOLhVyo7rmih+8+XAtdeciIXkZYwzwVS3TFPLMqBUO2HcY6gYtQQRmTG52R5ihyi/bXA== + +"@aptos-labs/aptos-client@^0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@aptos-labs/aptos-client/-/aptos-client-0.1.1.tgz#cbcd2a73bad252e344318baec32ecc54d8136ee0" + integrity sha512-kJsoy4fAPTOhzVr7Vwq8s/AUg6BQiJDa7WOqRzev4zsuIS3+JCuIZ6vUd7UBsjnxtmguJJulMRs9qWCzVBt2XA== + dependencies: + axios "1.7.4" + got "^11.8.6" + +"@aptos-labs/ts-sdk@^1.28.0": + version "1.29.0" + resolved "https://registry.yarnpkg.com/@aptos-labs/ts-sdk/-/ts-sdk-1.29.0.tgz#5935ce569746a3179e79a7076bbf6995da7ce3ca" + integrity sha512-aKYPXOqrSj/y+mDBa7r0PMxryqmLs2oxo0ZCvyx3t8712cFXxAA22jdcWpq6Mym4mTg1UY6Nc6MgbeiYjhJYng== + dependencies: + "@aptos-labs/aptos-cli" "^0.2.0" + "@aptos-labs/aptos-client" "^0.1.1" + "@noble/curves" "^1.4.0" + "@noble/hashes" "^1.4.0" + "@scure/bip32" "^1.4.0" + "@scure/bip39" "^1.3.0" + eventemitter3 "^5.0.1" + form-data "^4.0.0" + js-base64 "^3.7.7" + jwt-decode "^4.0.0" + poseidon-lite "^0.2.0" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.25.7.tgz#438f2c524071531d643c6f0188e1e28f130cebc7" + integrity sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g== + dependencies: + "@babel/highlight" "^7.25.7" + picocolors "^1.0.0" + +"@babel/compat-data@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.7.tgz#b8479fe0018ef0ac87b6b7a5c6916fcd67ae2c9c" + integrity sha512-9ickoLz+hcXCeh7jrcin+/SLWm+GkxE2kTvoYyp38p4WkdFXfQJxDFGWp/YHjiKLPx06z2A7W8XKuqbReXDzsw== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.25.7.tgz#1b3d144157575daf132a3bc80b2b18e6e3ca6ece" + integrity sha512-yJ474Zv3cwiSOO9nXJuqzvwEeM+chDuQ8GJirw+pZ91sCGCyOZ3dJkVE09fTV0VEVzXyLWhh3G/AolYTPX7Mow== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.25.7" + "@babel/generator" "^7.25.7" + "@babel/helper-compilation-targets" "^7.25.7" + "@babel/helper-module-transforms" "^7.25.7" + "@babel/helpers" "^7.25.7" + "@babel/parser" "^7.25.7" + "@babel/template" "^7.25.7" + "@babel/traverse" "^7.25.7" + "@babel/types" "^7.25.7" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.25.7", "@babel/generator@^7.7.2": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.7.tgz#de86acbeb975a3e11ee92dd52223e6b03b479c56" + integrity sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA== + dependencies: + "@babel/types" "^7.25.7" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^3.0.2" + +"@babel/helper-compilation-targets@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.7.tgz#11260ac3322dda0ef53edfae6e97b961449f5fa4" + integrity sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A== + dependencies: + "@babel/compat-data" "^7.25.7" + "@babel/helper-validator-option" "^7.25.7" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-module-imports@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.7.tgz#dba00d9523539152906ba49263e36d7261040472" + integrity sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw== + dependencies: + "@babel/traverse" "^7.25.7" + "@babel/types" "^7.25.7" + +"@babel/helper-module-transforms@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.25.7.tgz#2ac9372c5e001b19bc62f1fe7d96a18cb0901d1a" + integrity sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ== + dependencies: + "@babel/helper-module-imports" "^7.25.7" + "@babel/helper-simple-access" "^7.25.7" + "@babel/helper-validator-identifier" "^7.25.7" + "@babel/traverse" "^7.25.7" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.25.7", "@babel/helper-plugin-utils@^7.8.0": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.7.tgz#8ec5b21812d992e1ef88a9b068260537b6f0e36c" + integrity sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw== + +"@babel/helper-simple-access@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.25.7.tgz#5eb9f6a60c5d6b2e0f76057004f8dacbddfae1c0" + integrity sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ== + dependencies: + "@babel/traverse" "^7.25.7" + "@babel/types" "^7.25.7" + +"@babel/helper-string-parser@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz#d50e8d37b1176207b4fe9acedec386c565a44a54" + integrity sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g== + +"@babel/helper-validator-identifier@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz#77b7f60c40b15c97df735b38a66ba1d7c3e93da5" + integrity sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg== + +"@babel/helper-validator-option@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.7.tgz#97d1d684448228b30b506d90cace495d6f492729" + integrity sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ== + +"@babel/helpers@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.7.tgz#091b52cb697a171fe0136ab62e54e407211f09c2" + integrity sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA== + dependencies: + "@babel/template" "^7.25.7" + "@babel/types" "^7.25.7" + +"@babel/highlight@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.25.7.tgz#20383b5f442aa606e7b5e3043b0b1aafe9f37de5" + integrity sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw== + dependencies: + "@babel/helper-validator-identifier" "^7.25.7" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.7.tgz#99b927720f4ddbfeb8cd195a363ed4532f87c590" + integrity sha512-aZn7ETtQsjjGG5HruveUK06cU3Hljuhd9Iojm4M8WWv3wLE6OkE5PWbDUkItmMgegmccaITudyuW5RPYrYlgWw== + dependencies: + "@babel/types" "^7.25.7" + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-import-attributes@^7.24.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.7.tgz#d78dd0499d30df19a598e63ab895e21b909bc43f" + integrity sha512-AqVo+dguCgmpi/3mYBdu9lkngOBlQ2w2vnNpa6gfiCxQZLzV4ZbhsXitJ2Yblkoe1VQwtHSaNmIaGll/26YWRw== + dependencies: + "@babel/helper-plugin-utils" "^7.25.7" + +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.7.2": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.7.tgz#5352d398d11ea5e7ef330c854dea1dae0bf18165" + integrity sha512-ruZOnKO+ajVL/MVx+PwNBPOkrnXTXoWMtte1MBpegfCArhqOe3Bj52avVj1huLLxNKYKXYaSxZ2F+woK1ekXfw== + dependencies: + "@babel/helper-plugin-utils" "^7.25.7" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.7.tgz#bfc05b0cc31ebd8af09964650cee723bb228108b" + integrity sha512-rR+5FDjpCHqqZN2bzZm18bVYGaejGq5ZkpVCJLXor/+zlSrSoc4KWcHI0URVWjl/68Dyr1uwZUz/1njycEAv9g== + dependencies: + "@babel/helper-plugin-utils" "^7.25.7" + +"@babel/template@^7.25.7", "@babel/template@^7.3.3": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.7.tgz#27f69ce382855d915b14ab0fe5fb4cbf88fa0769" + integrity sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA== + dependencies: + "@babel/code-frame" "^7.25.7" + "@babel/parser" "^7.25.7" + "@babel/types" "^7.25.7" + +"@babel/traverse@^7.25.7", "@babel/traverse@^7.7.2": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.7.tgz#83e367619be1cab8e4f2892ef30ba04c26a40fa8" + integrity sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg== + dependencies: + "@babel/code-frame" "^7.25.7" + "@babel/generator" "^7.25.7" + "@babel/parser" "^7.25.7" + "@babel/template" "^7.25.7" + "@babel/types" "^7.25.7" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.7", "@babel/types@^7.3.3": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.7.tgz#1b7725c1d3a59f328cb700ce704c46371e6eef9b" + integrity sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ== + dependencies: + "@babel/helper-string-parser" "^7.25.7" + "@babel/helper-validator-identifier" "^7.25.7" + to-fast-properties "^2.0.0" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@eslint-community/eslint-utils@^4.2.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.4.0", "@eslint-community/regexpp@^4.6.1": + version "4.11.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.1.tgz#a547badfc719eb3e5f4b556325e542fbe9d7a18f" + integrity sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.57.1": + version "8.57.1" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2" + integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== + +"@ethereumjs/rlp@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@ethereumjs/rlp/-/rlp-4.0.1.tgz#626fabfd9081baab3d0a3074b0c7ecaf674aaa41" + integrity sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw== + +"@ethereumjs/rlp@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@ethereumjs/rlp/-/rlp-5.0.2.tgz#c89bd82f2f3bec248ab2d517ae25f5bbc4aac842" + integrity sha512-DziebCdg4JpGlEqEdGgXmjqcFoJi+JGulUXwEjsZGAscAQ7MyD/7LE/GVCP29vEQxKc7AAwjT3A2ywHp2xfoCA== + +"@ethereumjs/util@^9.1.0": + version "9.1.0" + resolved "https://registry.yarnpkg.com/@ethereumjs/util/-/util-9.1.0.tgz#75e3898a3116d21c135fa9e29886565609129bce" + integrity sha512-XBEKsYqLGXLah9PNJbgdkigthkG7TAGvlD/sH12beMXEyHDyigfcbdvHhmLyDWgDyOJn4QwiQUaF7yeuhnjdog== + dependencies: + "@ethereumjs/rlp" "^5.0.2" + ethereum-cryptography "^2.2.1" + +"@humanwhocodes/config-array@^0.13.0": + version "0.13.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748" + integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw== + dependencies: + "@humanwhocodes/object-schema" "^2.0.3" + debug "^4.3.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-28.1.3.tgz#2030606ec03a18c31803b8a36382762e447655df" + integrity sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw== + dependencies: + "@jest/types" "^28.1.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^28.1.3" + jest-util "^28.1.3" + slash "^3.0.0" + +"@jest/core@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-28.1.3.tgz#0ebf2bd39840f1233cd5f2d1e6fc8b71bd5a1ac7" + integrity sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA== + dependencies: + "@jest/console" "^28.1.3" + "@jest/reporters" "^28.1.3" + "@jest/test-result" "^28.1.3" + "@jest/transform" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^28.1.3" + jest-config "^28.1.3" + jest-haste-map "^28.1.3" + jest-message-util "^28.1.3" + jest-regex-util "^28.0.2" + jest-resolve "^28.1.3" + jest-resolve-dependencies "^28.1.3" + jest-runner "^28.1.3" + jest-runtime "^28.1.3" + jest-snapshot "^28.1.3" + jest-util "^28.1.3" + jest-validate "^28.1.3" + jest-watcher "^28.1.3" + micromatch "^4.0.4" + pretty-format "^28.1.3" + rimraf "^3.0.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-28.1.3.tgz#abed43a6b040a4c24fdcb69eab1f97589b2d663e" + integrity sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA== + dependencies: + "@jest/fake-timers" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/node" "*" + jest-mock "^28.1.3" + +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== + dependencies: + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + +"@jest/expect-utils@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-28.1.3.tgz#58561ce5db7cd253a7edddbc051fb39dda50f525" + integrity sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA== + dependencies: + jest-get-type "^28.0.2" + +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== + dependencies: + jest-get-type "^29.6.3" + +"@jest/expect@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-28.1.3.tgz#9ac57e1d4491baca550f6bdbd232487177ad6a72" + integrity sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw== + dependencies: + expect "^28.1.3" + jest-snapshot "^28.1.3" + +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== + dependencies: + expect "^29.7.0" + jest-snapshot "^29.7.0" + +"@jest/fake-timers@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-28.1.3.tgz#230255b3ad0a3d4978f1d06f70685baea91c640e" + integrity sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw== + dependencies: + "@jest/types" "^28.1.3" + "@sinonjs/fake-timers" "^9.1.2" + "@types/node" "*" + jest-message-util "^28.1.3" + jest-mock "^28.1.3" + jest-util "^28.1.3" + +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== + dependencies: + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +"@jest/globals@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-28.1.3.tgz#a601d78ddc5fdef542728309894895b4a42dc333" + integrity sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA== + dependencies: + "@jest/environment" "^28.1.3" + "@jest/expect" "^28.1.3" + "@jest/types" "^28.1.3" + +"@jest/globals@^29.3.1": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" + +"@jest/reporters@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-28.1.3.tgz#9adf6d265edafc5fc4a434cfb31e2df5a67a369a" + integrity sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^28.1.3" + "@jest/test-result" "^28.1.3" + "@jest/transform" "^28.1.3" + "@jest/types" "^28.1.3" + "@jridgewell/trace-mapping" "^0.3.13" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^5.1.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^28.1.3" + jest-util "^28.1.3" + jest-worker "^28.1.3" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + terminal-link "^2.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-28.1.3.tgz#ad8b86a66f11f33619e3d7e1dcddd7f2d40ff905" + integrity sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg== + dependencies: + "@sinclair/typebox" "^0.24.1" + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/source-map@^28.1.2": + version "28.1.2" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-28.1.2.tgz#7fe832b172b497d6663cdff6c13b0a920e139e24" + integrity sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww== + dependencies: + "@jridgewell/trace-mapping" "^0.3.13" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-28.1.3.tgz#5eae945fd9f4b8fcfce74d239e6f725b6bf076c5" + integrity sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg== + dependencies: + "@jest/console" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz#9d0c283d906ac599c74bde464bc0d7e6a82886c3" + integrity sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw== + dependencies: + "@jest/test-result" "^28.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^28.1.3" + slash "^3.0.0" + +"@jest/transform@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-28.1.3.tgz#59d8098e50ab07950e0f2fc0fc7ec462371281b0" + integrity sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^28.1.3" + "@jridgewell/trace-mapping" "^0.3.13" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^28.1.3" + jest-regex-util "^28.0.2" + jest-util "^28.1.3" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.1" + +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + +"@jest/types@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-28.1.3.tgz#b05de80996ff12512bc5ceb1d208285a7d11748b" + integrity sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ== + dependencies: + "@jest/schemas" "^28.1.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.13", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@noble/curves@1.4.2", "@noble/curves@~1.4.0": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.4.2.tgz#40309198c76ed71bc6dbf7ba24e81ceb4d0d1fe9" + integrity sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw== + dependencies: + "@noble/hashes" "1.4.0" + +"@noble/curves@^1.4.0", "@noble/curves@~1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.6.0.tgz#be5296ebcd5a1730fccea4786d420f87abfeb40b" + integrity sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ== + dependencies: + "@noble/hashes" "1.5.0" + +"@noble/hashes@1.4.0", "@noble/hashes@~1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" + integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== + +"@noble/hashes@1.5.0", "@noble/hashes@^1.4.0", "@noble/hashes@~1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.5.0.tgz#abadc5ca20332db2b1b2aa3e496e9af1213570b0" + integrity sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@rtsao/scc@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" + integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== + +"@scure/base@~1.1.6", "@scure/base@~1.1.7", "@scure/base@~1.1.8": + version "1.1.9" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.9.tgz#e5e142fbbfe251091f9c5f1dd4c834ac04c3dbd1" + integrity sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg== + +"@scure/bip32@1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.4.0.tgz#4e1f1e196abedcef395b33b9674a042524e20d67" + integrity sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg== + dependencies: + "@noble/curves" "~1.4.0" + "@noble/hashes" "~1.4.0" + "@scure/base" "~1.1.6" + +"@scure/bip32@^1.4.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.5.0.tgz#dd4a2e1b8a9da60e012e776d954c4186db6328e6" + integrity sha512-8EnFYkqEQdnkuGBVpCzKxyIwDCBLDVj3oiX0EKUFre/tOjL/Hqba1D6n/8RcmaQy4f95qQFrO2A8Sr6ybh4NRw== + dependencies: + "@noble/curves" "~1.6.0" + "@noble/hashes" "~1.5.0" + "@scure/base" "~1.1.7" + +"@scure/bip39@1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.3.0.tgz#0f258c16823ddd00739461ac31398b4e7d6a18c3" + integrity sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ== + dependencies: + "@noble/hashes" "~1.4.0" + "@scure/base" "~1.1.6" + +"@scure/bip39@^1.3.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.4.0.tgz#664d4f851564e2e1d4bffa0339f9546ea55960a6" + integrity sha512-BEEm6p8IueV/ZTfQLp/0vhw4NPnT9oWf5+28nvmeUICjP99f4vr2d+qc7AVGDDtwRep6ifR43Yed9ERVmiITzw== + dependencies: + "@noble/hashes" "~1.5.0" + "@scure/base" "~1.1.8" + +"@sinclair/typebox@^0.24.1": + version "0.24.51" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.51.tgz#645f33fe4e02defe26f2f5c0410e1c094eac7f5f" + integrity sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA== + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@sindresorhus/is@^4.0.0": + version "4.6.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" + integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== + +"@sinonjs/commons@^1.7.0": + version "1.8.6" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.6.tgz#80c516a4dc264c2a69115e7578d62581ff455ed9" + integrity sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/commons@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@sinonjs/fake-timers@^9.1.2": + version "9.1.2" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz#4eaab737fab77332ab132d396a3c0d364bd0ea8c" + integrity sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw== + dependencies: + "@sinonjs/commons" "^1.7.0" + +"@szmarczak/http-timer@^4.0.5": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" + integrity sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w== + dependencies: + defer-to-connect "^2.0.0" + +"@tsconfig/node10@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" + integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + +"@types/babel__core@^7.1.14": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.8" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.8.tgz#f836c61f48b1346e7d2b0d93c6dacc5b9535d3ab" + integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.20.6" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.6.tgz#8dc9f0ae0f202c08d8d4dab648912c8d6038e3f7" + integrity sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg== + dependencies: + "@babel/types" "^7.20.7" + +"@types/cacheable-request@^6.0.1": + version "6.0.3" + resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.3.tgz#a430b3260466ca7b5ca5bfd735693b36e7a9d183" + integrity sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw== + dependencies: + "@types/http-cache-semantics" "*" + "@types/keyv" "^3.1.4" + "@types/node" "*" + "@types/responselike" "^1.0.0" + +"@types/graceful-fs@^4.1.3": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" + integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== + dependencies: + "@types/node" "*" + +"@types/http-cache-semantics@*": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4" + integrity sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA== + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + +"@types/istanbul-lib-report@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/json-schema@^7.0.9": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== + +"@types/keyv@^3.1.4": + version "3.1.4" + resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6" + integrity sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg== + dependencies: + "@types/node" "*" + +"@types/node@*": + version "22.7.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.4.tgz#e35d6f48dca3255ce44256ddc05dee1c23353fcc" + integrity sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg== + dependencies: + undici-types "~6.19.2" + +"@types/prettier@^2.1.5": + version "2.7.3" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.3.tgz#3e51a17e291d01d17d3fc61422015a933af7a08f" + integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA== + +"@types/responselike@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.3.tgz#cc29706f0a397cfe6df89debfe4bf5cea159db50" + integrity sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw== + dependencies: + "@types/node" "*" + +"@types/semver@^7.3.12": + version "7.5.8" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" + integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== + +"@types/stack-utils@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + +"@types/ws@8.5.3": + version "8.5.3" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" + integrity sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w== + dependencies: + "@types/node" "*" + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.8": + version "17.0.33" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@^5.13.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz#aeef0328d172b9e37d9bab6dbc13b87ed88977db" + integrity sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag== + dependencies: + "@eslint-community/regexpp" "^4.4.0" + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/type-utils" "5.62.0" + "@typescript-eslint/utils" "5.62.0" + debug "^4.3.4" + graphemer "^1.4.0" + ignore "^5.2.0" + natural-compare-lite "^1.4.0" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/parser@^5.0.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.62.0.tgz#1b63d082d849a2fcae8a569248fbe2ee1b8a56c7" + integrity sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA== + dependencies: + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/typescript-estree" "5.62.0" + debug "^4.3.4" + +"@typescript-eslint/parser@^6.7.5": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.21.0.tgz#af8fcf66feee2edc86bc5d1cf45e33b0630bf35b" + integrity sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ== + dependencies: + "@typescript-eslint/scope-manager" "6.21.0" + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/typescript-estree" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" + integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w== + dependencies: + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/visitor-keys" "5.62.0" + +"@typescript-eslint/scope-manager@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz#ea8a9bfc8f1504a6ac5d59a6df308d3a0630a2b1" + integrity sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg== + dependencies: + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" + +"@typescript-eslint/type-utils@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz#286f0389c41681376cdad96b309cedd17d70346a" + integrity sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew== + dependencies: + "@typescript-eslint/typescript-estree" "5.62.0" + "@typescript-eslint/utils" "5.62.0" + debug "^4.3.4" + tsutils "^3.21.0" + +"@typescript-eslint/types@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" + integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== + +"@typescript-eslint/types@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.21.0.tgz#205724c5123a8fef7ecd195075fa6e85bac3436d" + integrity sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg== + +"@typescript-eslint/typescript-estree@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" + integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== + dependencies: + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/visitor-keys" "5.62.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/typescript-estree@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz#c47ae7901db3b8bddc3ecd73daff2d0895688c46" + integrity sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ== + dependencies: + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + minimatch "9.0.3" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/utils@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" + integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@types/json-schema" "^7.0.9" + "@types/semver" "^7.3.12" + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/typescript-estree" "5.62.0" + eslint-scope "^5.1.1" + semver "^7.3.7" + +"@typescript-eslint/visitor-keys@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" + integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== + dependencies: + "@typescript-eslint/types" "5.62.0" + eslint-visitor-keys "^3.3.0" + +"@typescript-eslint/visitor-keys@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz#87a99d077aa507e20e238b11d56cc26ade45fe47" + integrity sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A== + dependencies: + "@typescript-eslint/types" "6.21.0" + eslint-visitor-keys "^3.4.1" + +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + +abitype@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.7.1.tgz#16db20abe67de80f6183cf75f3de1ff86453b745" + integrity sha512-VBkRHTDZf9Myaek/dO3yMmOzB/y2s3Zo6nVU7yaw1G+TvCHAjwaJzNGN9yo4K5D8bU/VZXKP1EJpRhFr862PlQ== + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-walk@^8.1.1: + version "8.3.4" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" + integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== + dependencies: + acorn "^8.11.0" + +acorn@^8.11.0, acorn@^8.4.1, acorn@^8.9.0: + version "8.12.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" + integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== + +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + integrity sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +anymatch@^3.0.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-buffer-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" + integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== + dependencies: + call-bind "^1.0.5" + is-array-buffer "^3.0.4" + +array-includes@^3.1.8: + version "3.1.8" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d" + integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.4" + is-string "^1.0.7" + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +array.prototype.findlastindex@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz#8c35a755c72908719453f87145ca011e39334d0d" + integrity sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-shim-unscopables "^1.0.2" + +array.prototype.flat@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18" + integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +array.prototype.flatmap@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" + integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +arraybuffer.prototype.slice@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz#097972f4255e41bc3425e37dc3f6421cf9aefde6" + integrity sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A== + dependencies: + array-buffer-byte-length "^1.0.1" + call-bind "^1.0.5" + define-properties "^1.2.1" + es-abstract "^1.22.3" + es-errors "^1.2.1" + get-intrinsic "^1.2.3" + is-array-buffer "^3.0.4" + is-shared-array-buffer "^1.0.2" + +async@^3.2.3: + version "3.2.6" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" + integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +available-typed-arrays@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== + dependencies: + possible-typed-array-names "^1.0.0" + +axios@1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.4.tgz#4c8ded1b43683c8dd362973c393f3ede24052aa2" + integrity sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + +babel-jest@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-28.1.3.tgz#c1187258197c099072156a0a121c11ee1e3917d5" + integrity sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q== + dependencies: + "@jest/transform" "^28.1.3" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^28.1.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz#1952c4d0ea50f2d6d794353762278d1d8cca3fbe" + integrity sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-preset-current-node-syntax@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz#9a929eafece419612ef4ae4f60b1862ebad8ef30" + integrity sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + +babel-preset-jest@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz#5dfc20b99abed5db994406c2b9ab94c73aaa419d" + integrity sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A== + dependencies: + babel-plugin-jest-hoist "^28.1.3" + babel-preset-current-node-syntax "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browserslist@^4.24.0: + version "4.24.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.0.tgz#a1325fe4bc80b64fda169629fc01b3d6cecd38d4" + integrity sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A== + dependencies: + caniuse-lite "^1.0.30001663" + electron-to-chromium "^1.5.28" + node-releases "^2.0.18" + update-browserslist-db "^1.1.0" + +bs-logger@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +cacheable-lookup@^5.0.3: + version "5.0.4" + resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" + integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== + +cacheable-request@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.4.tgz#7a33ebf08613178b403635be7b899d3e69bbe817" + integrity sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^4.0.0" + lowercase-keys "^2.0.0" + normalize-url "^6.0.1" + responselike "^2.0.0" + +call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001663: + version "1.0.30001667" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001667.tgz#99fc5ea0d9c6e96897a104a8352604378377f949" + integrity sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw== + +chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + integrity sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A== + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0, chalk@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +cjs-module-lexer@^1.0.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz#707413784dbb3a72aa11c2f2b042a0bef4004170" + integrity sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA== + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +clone-response@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.3.tgz#af2032aa47816399cf5f0a1d0db902f517abb8c3" + integrity sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA== + dependencies: + mimic-response "^1.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +collect-v8-coverage@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" + integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@^12.1.0: + version "12.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" + integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== + +common-tags@^1.4.0: + version "1.8.2" + resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.2.tgz#94ebb3c076d26032745fd54face7f688ef5ac9c6" + integrity sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +confusing-browser-globals@^1.0.10: + version "1.0.11" + resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz#ae40e9b57cdd3915408a2805ebd3a5585608dc81" + integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA== + +convert-source-map@^1.4.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +crc-32@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" + integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +cross-fetch@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-4.0.0.tgz#f037aef1580bb3a1a35164ea2a848ba81b445983" + integrity sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g== + dependencies: + node-fetch "^2.6.12" + +cross-spawn@^7.0.2, cross-spawn@^7.0.3: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +data-view-buffer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" + integrity sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +data-view-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz#90721ca95ff280677eb793749fce1011347669e2" + integrity sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +data-view-byte-offset@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz#5e0bbfb4828ed2d1b9b400cd8a7d119bca0ff18a" + integrity sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: + version "4.3.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== + dependencies: + ms "^2.1.3" + +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + +dedent@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" + integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +defer-to-connect@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" + integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== + +define-data-property@^1.0.1, define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +define-properties@^1.2.0, define-properties@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-28.1.1.tgz#9989dc731266dc2903457a70e996f3a041913ac6" + integrity sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw== + +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +dlv@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" + integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== + +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dotenv@^16.4.5: + version "16.4.5" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" + integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== + +ejs@^3.1.10: + version "3.1.10" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" + integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== + dependencies: + jake "^10.8.5" + +electron-to-chromium@^1.5.28: + version "1.5.32" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.32.tgz#4a05ee78e29e240aabaf73a67ce9fe73f52e1bc7" + integrity sha512-M+7ph0VGBQqqpTT2YrabjNKSQ2fEl9PVx6AK3N558gDH9NO8O6XN9SXXFWRo9u9PbEg/bWq+tjXQr+eXmxubCw== + +emittery@^0.10.2: + version "0.10.2" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.10.2.tgz#902eec8aedb8c41938c46e9385e9db7e03182933" + integrity sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.2: + version "1.23.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" + integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== + dependencies: + array-buffer-byte-length "^1.0.1" + arraybuffer.prototype.slice "^1.0.3" + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + data-view-buffer "^1.0.1" + data-view-byte-length "^1.0.1" + data-view-byte-offset "^1.0.0" + es-define-property "^1.0.0" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-set-tostringtag "^2.0.3" + es-to-primitive "^1.2.1" + function.prototype.name "^1.1.6" + get-intrinsic "^1.2.4" + get-symbol-description "^1.0.2" + globalthis "^1.0.3" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + has-proto "^1.0.3" + has-symbols "^1.0.3" + hasown "^2.0.2" + internal-slot "^1.0.7" + is-array-buffer "^3.0.4" + is-callable "^1.2.7" + is-data-view "^1.0.1" + is-negative-zero "^2.0.3" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.3" + is-string "^1.0.7" + is-typed-array "^1.1.13" + is-weakref "^1.0.2" + object-inspect "^1.13.1" + object-keys "^1.1.1" + object.assign "^4.1.5" + regexp.prototype.flags "^1.5.2" + safe-array-concat "^1.1.2" + safe-regex-test "^1.0.3" + string.prototype.trim "^1.2.9" + string.prototype.trimend "^1.0.8" + string.prototype.trimstart "^1.0.8" + typed-array-buffer "^1.0.2" + typed-array-byte-length "^1.0.1" + typed-array-byte-offset "^1.0.2" + typed-array-length "^1.0.6" + unbox-primitive "^1.0.2" + which-typed-array "^1.1.15" + +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.2.1, es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-object-atoms@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" + integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" + integrity sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ== + dependencies: + get-intrinsic "^1.2.4" + has-tostringtag "^1.0.2" + hasown "^2.0.1" + +es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763" + integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw== + dependencies: + hasown "^2.0.0" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +escalade@^3.1.1, escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-config-airbnb-base@^15.0.0: + version "15.0.0" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz#6b09add90ac79c2f8d723a2580e07f3925afd236" + integrity sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig== + dependencies: + confusing-browser-globals "^1.0.10" + object.assign "^4.1.2" + object.entries "^1.1.5" + semver "^6.3.0" + +eslint-config-airbnb-typescript@^17.0.0: + version "17.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.1.0.tgz#fda960eee4a510f092a9a1c139035ac588937ddc" + integrity sha512-GPxI5URre6dDpJ0CtcthSZVBAfI+Uw7un5OYNVxP2EYi3H81Jw701yFP7AU+/vCE7xBtFmjge7kfhhk4+RAiig== + dependencies: + eslint-config-airbnb-base "^15.0.0" + +eslint-config-prettier@^8.5.0: + version "8.10.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz#3a06a662130807e2502fc3ff8b4143d8a0658e11" + integrity sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg== + +eslint-import-resolver-node@^0.3.9: + version "0.3.9" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" + integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== + dependencies: + debug "^3.2.7" + is-core-module "^2.13.0" + resolve "^1.22.4" + +eslint-module-utils@^2.12.0: + version "2.12.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz#fe4cfb948d61f49203d7b08871982b65b9af0b0b" + integrity sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg== + dependencies: + debug "^3.2.7" + +eslint-plugin-import@^2.25.2: + version "2.31.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz#310ce7e720ca1d9c0bb3f69adfd1c6bdd7d9e0e7" + integrity sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A== + dependencies: + "@rtsao/scc" "^1.1.0" + array-includes "^3.1.8" + array.prototype.findlastindex "^1.2.5" + array.prototype.flat "^1.3.2" + array.prototype.flatmap "^1.3.2" + debug "^3.2.7" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.9" + eslint-module-utils "^2.12.0" + hasown "^2.0.2" + is-core-module "^2.15.1" + is-glob "^4.0.3" + minimatch "^3.1.2" + object.fromentries "^2.0.8" + object.groupby "^1.0.3" + object.values "^1.2.0" + semver "^6.3.1" + string.prototype.trimend "^1.0.8" + tsconfig-paths "^3.15.0" + +eslint-plugin-prefer-arrow@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-prefer-arrow/-/eslint-plugin-prefer-arrow-1.2.3.tgz#e7fbb3fa4cd84ff1015b9c51ad86550e55041041" + integrity sha512-J9I5PKCOJretVuiZRGvPQxCbllxGAV/viI20JO3LYblAodofBxyMnZAJ+WGeClHgANnSJberTNoFWWjrWKBuXQ== + +eslint-plugin-prettier@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b" + integrity sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ== + dependencies: + prettier-linter-helpers "^1.0.0" + +eslint-plugin-simple-import-sort@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-7.0.0.tgz#a1dad262f46d2184a90095a60c66fef74727f0f8" + integrity sha512-U3vEDB5zhYPNfxT5TYR7u01dboFZp+HNpnGhkDB2g/2E4wZ/g1Q9Ton8UwCLfRV9yAKyYqDh62oHOamvkFxsvw== + +eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-scope@^7.1.1, eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint@^8.0.1, eslint@^8.7.0: + version "8.57.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9" + integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.1" + "@humanwhocodes/config-array" "^0.13.0" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +espree@^9.3.1, espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.0, esquery@^1.4.2: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +ethereum-cryptography@^2.0.0, ethereum-cryptography@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz#58f2810f8e020aecb97de8c8c76147600b0b8ccf" + integrity sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg== + dependencies: + "@noble/curves" "1.4.2" + "@noble/hashes" "1.4.0" + "@scure/bip32" "1.4.0" + "@scure/bip39" "1.3.0" + +eventemitter3@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/expect/-/expect-28.1.3.tgz#90a7c1a124f1824133dd4533cce2d2bdcb6603ec" + integrity sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g== + dependencies: + "@jest/expect-utils" "^28.1.3" + jest-get-type "^28.0.2" + jest-matcher-utils "^28.1.3" + jest-message-util "^28.1.3" + jest-util "^28.1.3" + +expect@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== + dependencies: + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" + integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== + +fast-glob@^3.2.9: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.17.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" + integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== + dependencies: + reusify "^1.0.4" + +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +filelist@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== + dependencies: + minimatch "^5.0.1" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flatted@^3.2.9: + version "3.3.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" + integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== + +follow-redirects@^1.15.6: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + +for-each@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + dependencies: + is-callable "^1.1.3" + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@^2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +function.prototype.name@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" + integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + functions-have-names "^1.2.3" + +functions-have-names@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +get-symbol-description@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" + integrity sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg== + dependencies: + call-bind "^1.0.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@^7.1.3, glob@^7.1.4: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^13.19.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + +globalthis@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" + integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== + dependencies: + define-properties "^1.2.1" + gopd "^1.0.1" + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + +got@^11.8.6: + version "11.8.6" + resolved "https://registry.yarnpkg.com/got/-/got-11.8.6.tgz#276e827ead8772eddbcfc97170590b841823233a" + integrity sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g== + dependencies: + "@sindresorhus/is" "^4.0.0" + "@szmarczak/http-timer" "^4.0.5" + "@types/cacheable-request" "^6.0.1" + "@types/responselike" "^1.0.0" + cacheable-lookup "^5.0.3" + cacheable-request "^7.0.2" + decompress-response "^6.0.0" + http2-wrapper "^1.0.0-beta.5.2" + lowercase-keys "^2.0.0" + p-cancelable "^2.0.0" + responselike "^2.0.0" + +graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + integrity sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg== + dependencies: + ansi-regex "^2.0.0" + +has-bigints@^1.0.1, has-bigints@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.0.1, has-proto@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" + integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== + +has-symbols@^1.0.2, has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + +hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +http-cache-semantics@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" + integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== + +http2-wrapper@^1.0.0-beta.5.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" + integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.0.0" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +ignore@^5.2.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-local@^3.0.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +internal-slot@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" + integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== + dependencies: + es-errors "^1.3.0" + hasown "^2.0.0" + side-channel "^1.0.4" + +is-arguments@^1.0.4: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-array-buffer@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" + integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + +is-core-module@^2.13.0, is-core-module@^2.15.1: + version "2.15.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.1.tgz#a7363a25bee942fefab0de13bf6aa372c82dcc37" + integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ== + dependencies: + hasown "^2.0.2" + +is-data-view@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f" + integrity sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w== + dependencies: + is-typed-array "^1.1.13" + +is-date-object@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-generator-function@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" + integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== + dependencies: + has-tostringtag "^1.0.0" + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-negative-zero@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" + integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== + +is-number-object@^1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" + integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== + dependencies: + has-tostringtag "^1.0.0" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688" + integrity sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg== + dependencies: + call-bind "^1.0.7" + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-typed-array@^1.1.13, is-typed-array@^1.1.3: + version "1.1.13" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" + integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== + dependencies: + which-typed-array "^1.1.14" + +is-weakref@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" + +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +isomorphic-ws@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf" + integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.7" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" + integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jake@^10.8.5: + version "10.9.2" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" + integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA== + dependencies: + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.4" + minimatch "^3.1.2" + +jest-changed-files@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-28.1.3.tgz#d9aeee6792be3686c47cb988a8eaf82ff4238831" + integrity sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA== + dependencies: + execa "^5.0.0" + p-limit "^3.1.0" + +jest-circus@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-28.1.3.tgz#d14bd11cf8ee1a03d69902dc47b6bd4634ee00e4" + integrity sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow== + dependencies: + "@jest/environment" "^28.1.3" + "@jest/expect" "^28.1.3" + "@jest/test-result" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^0.7.0" + is-generator-fn "^2.0.0" + jest-each "^28.1.3" + jest-matcher-utils "^28.1.3" + jest-message-util "^28.1.3" + jest-runtime "^28.1.3" + jest-snapshot "^28.1.3" + jest-util "^28.1.3" + p-limit "^3.1.0" + pretty-format "^28.1.3" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-28.1.3.tgz#558b33c577d06de55087b8448d373b9f654e46b2" + integrity sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ== + dependencies: + "@jest/core" "^28.1.3" + "@jest/test-result" "^28.1.3" + "@jest/types" "^28.1.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + import-local "^3.0.2" + jest-config "^28.1.3" + jest-util "^28.1.3" + jest-validate "^28.1.3" + prompts "^2.0.1" + yargs "^17.3.1" + +jest-config@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-28.1.3.tgz#e315e1f73df3cac31447eed8b8740a477392ec60" + integrity sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^28.1.3" + "@jest/types" "^28.1.3" + babel-jest "^28.1.3" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^28.1.3" + jest-environment-node "^28.1.3" + jest-get-type "^28.0.2" + jest-regex-util "^28.0.2" + jest-resolve "^28.1.3" + jest-runner "^28.1.3" + jest-util "^28.1.3" + jest-validate "^28.1.3" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^28.1.3" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-28.1.3.tgz#948a192d86f4e7a64c5264ad4da4877133d8792f" + integrity sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw== + dependencies: + chalk "^4.0.0" + diff-sequences "^28.1.1" + jest-get-type "^28.0.2" + pretty-format "^28.1.3" + +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-docblock@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-28.1.1.tgz#6f515c3bf841516d82ecd57a62eed9204c2f42a8" + integrity sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA== + dependencies: + detect-newline "^3.0.0" + +jest-each@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-28.1.3.tgz#bdd1516edbe2b1f3569cfdad9acd543040028f81" + integrity sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g== + dependencies: + "@jest/types" "^28.1.3" + chalk "^4.0.0" + jest-get-type "^28.0.2" + jest-util "^28.1.3" + pretty-format "^28.1.3" + +jest-environment-node@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-28.1.3.tgz#7e74fe40eb645b9d56c0c4b70ca4357faa349be5" + integrity sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A== + dependencies: + "@jest/environment" "^28.1.3" + "@jest/fake-timers" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/node" "*" + jest-mock "^28.1.3" + jest-util "^28.1.3" + +jest-get-type@^28.0.2: + version "28.0.2" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-28.0.2.tgz#34622e628e4fdcd793d46db8a242227901fcf203" + integrity sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA== + +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + +jest-haste-map@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-28.1.3.tgz#abd5451129a38d9841049644f34b034308944e2b" + integrity sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA== + dependencies: + "@jest/types" "^28.1.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^28.0.2" + jest-util "^28.1.3" + jest-worker "^28.1.3" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== + dependencies: + "@jest/types" "^29.6.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-junit@^14.0.0: + version "14.0.1" + resolved "https://registry.yarnpkg.com/jest-junit/-/jest-junit-14.0.1.tgz#5b357d6f5d333459585d628a24cd48b5bbc92ba2" + integrity sha512-h7/wwzPbllgpQhhVcRzRC76/cc89GlazThoV1fDxcALkf26IIlRsu/AcTG64f4nR2WPE3Cbd+i/sVf+NCUHrWQ== + dependencies: + mkdirp "^1.0.4" + strip-ansi "^6.0.1" + uuid "^8.3.2" + xml "^1.0.1" + +jest-leak-detector@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz#a6685d9b074be99e3adee816ce84fd30795e654d" + integrity sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA== + dependencies: + jest-get-type "^28.0.2" + pretty-format "^28.1.3" + +jest-matcher-utils@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz#5a77f1c129dd5ba3b4d7fc20728806c78893146e" + integrity sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw== + dependencies: + chalk "^4.0.0" + jest-diff "^28.1.3" + jest-get-type "^28.0.2" + pretty-format "^28.1.3" + +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-message-util@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-28.1.3.tgz#232def7f2e333f1eecc90649b5b94b0055e7c43d" + integrity sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^28.1.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^28.1.3" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-28.1.3.tgz#d4e9b1fc838bea595c77ab73672ebf513ab249da" + integrity sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA== + dependencies: + "@jest/types" "^28.1.3" + "@types/node" "*" + +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-util "^29.7.0" + +jest-pnp-resolver@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +jest-regex-util@^28.0.2: + version "28.0.2" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-28.0.2.tgz#afdc377a3b25fb6e80825adcf76c854e5bf47ead" + integrity sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw== + +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== + +jest-resolve-dependencies@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.3.tgz#8c65d7583460df7275c6ea2791901fa975c1fe66" + integrity sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA== + dependencies: + jest-regex-util "^28.0.2" + jest-snapshot "^28.1.3" + +jest-resolve@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-28.1.3.tgz#cfb36100341ddbb061ec781426b3c31eb51aa0a8" + integrity sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^28.1.3" + jest-pnp-resolver "^1.2.2" + jest-util "^28.1.3" + jest-validate "^28.1.3" + resolve "^1.20.0" + resolve.exports "^1.1.0" + slash "^3.0.0" + +jest-runner@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-28.1.3.tgz#5eee25febd730b4713a2cdfd76bdd5557840f9a1" + integrity sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA== + dependencies: + "@jest/console" "^28.1.3" + "@jest/environment" "^28.1.3" + "@jest/test-result" "^28.1.3" + "@jest/transform" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.10.2" + graceful-fs "^4.2.9" + jest-docblock "^28.1.1" + jest-environment-node "^28.1.3" + jest-haste-map "^28.1.3" + jest-leak-detector "^28.1.3" + jest-message-util "^28.1.3" + jest-resolve "^28.1.3" + jest-runtime "^28.1.3" + jest-util "^28.1.3" + jest-watcher "^28.1.3" + jest-worker "^28.1.3" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-28.1.3.tgz#a57643458235aa53e8ec7821949e728960d0605f" + integrity sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw== + dependencies: + "@jest/environment" "^28.1.3" + "@jest/fake-timers" "^28.1.3" + "@jest/globals" "^28.1.3" + "@jest/source-map" "^28.1.2" + "@jest/test-result" "^28.1.3" + "@jest/transform" "^28.1.3" + "@jest/types" "^28.1.3" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + execa "^5.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^28.1.3" + jest-message-util "^28.1.3" + jest-mock "^28.1.3" + jest-regex-util "^28.0.2" + jest-resolve "^28.1.3" + jest-snapshot "^28.1.3" + jest-util "^28.1.3" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-28.1.3.tgz#17467b3ab8ddb81e2f605db05583d69388fc0668" + integrity sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/traverse" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^28.1.3" + "@jest/transform" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/babel__traverse" "^7.0.6" + "@types/prettier" "^2.1.5" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^28.1.3" + graceful-fs "^4.2.9" + jest-diff "^28.1.3" + jest-get-type "^28.0.2" + jest-haste-map "^28.1.3" + jest-matcher-utils "^28.1.3" + jest-message-util "^28.1.3" + jest-util "^28.1.3" + natural-compare "^1.4.0" + pretty-format "^28.1.3" + semver "^7.3.5" + +jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.7.0" + graceful-fs "^4.2.9" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + natural-compare "^1.4.0" + pretty-format "^29.7.0" + semver "^7.5.3" + +jest-util@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-28.1.3.tgz#f4f932aa0074f0679943220ff9cbba7e497028b0" + integrity sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ== + dependencies: + "@jest/types" "^28.1.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-util@^29.0.0, jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-28.1.3.tgz#e322267fd5e7c64cea4629612c357bbda96229df" + integrity sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA== + dependencies: + "@jest/types" "^28.1.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^28.0.2" + leven "^3.1.0" + pretty-format "^28.1.3" + +jest-watcher@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-28.1.3.tgz#c6023a59ba2255e3b4c57179fc94164b3e73abd4" + integrity sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g== + dependencies: + "@jest/test-result" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.10.2" + jest-util "^28.1.3" + string-length "^4.0.1" + +jest-worker@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-28.1.3.tgz#7e3c4ce3fa23d1bb6accb169e7f396f98ed4bb98" + integrity sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest/-/jest-28.1.3.tgz#e9c6a7eecdebe3548ca2b18894a50f45b36dfc6b" + integrity sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA== + dependencies: + "@jest/core" "^28.1.3" + "@jest/types" "^28.1.3" + import-local "^3.0.2" + jest-cli "^28.1.3" + +js-base64@^3.7.7: + version "3.7.7" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.7.7.tgz#e51b84bf78fbf5702b9541e2cb7bfcb893b43e79" + integrity sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw== + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsesc@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" + integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== + dependencies: + minimist "^1.2.0" + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jwt-decode@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-4.0.0.tgz#2270352425fd413785b2faf11f6e755c5151bd4b" + integrity sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA== + +keyv@^4.0.0, keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lodash.merge@^4.6.0, lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +loglevel-colored-level-prefix@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/loglevel-colored-level-prefix/-/loglevel-colored-level-prefix-1.0.0.tgz#6a40218fdc7ae15fc76c3d0f3e676c465388603e" + integrity sha512-u45Wcxxc+SdAlh4yeF/uKlC1SPUPCy0gullSNKXod5I4bmifzk+Q4lSLExNEVn19tGaJipbZ4V4jbFn79/6mVA== + dependencies: + chalk "^1.1.3" + loglevel "^1.4.1" + +loglevel@^1.4.1: + version "1.9.2" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.9.2.tgz#c2e028d6c757720107df4e64508530db6621ba08" + integrity sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg== + +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + +make-error@^1.1.1, make-error@^1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +mimic-response@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + +minimatch@9.0.3: + version "9.0.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" + integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.0, minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +ms@^2.1.1, ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +natural-compare-lite@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" + integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +node-fetch@^2.6.12: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.18: + version "2.0.18" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" + integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-url@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +object-inspect@^1.13.1: + version "1.13.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" + integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.2, object.assign@^4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" + integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== + dependencies: + call-bind "^1.0.5" + define-properties "^1.2.1" + has-symbols "^1.0.3" + object-keys "^1.1.1" + +object.entries@^1.1.5: + version "1.1.8" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.8.tgz#bffe6f282e01f4d17807204a24f8edd823599c41" + integrity sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +object.fromentries@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" + integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + +object.groupby@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.3.tgz#9b125c36238129f6f7b61954a1e7176148d5002e" + integrity sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + +object.values@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b" + integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + +p-cancelable@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" + integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2, p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picocolors@^1.0.0, picocolors@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59" + integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw== + +picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pirates@^4.0.4: + version "4.0.6" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" + integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +poseidon-lite@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/poseidon-lite/-/poseidon-lite-0.2.1.tgz#7ad98e3a3aa5b91a1fd3a61a87460e9e46fd76d6" + integrity sha512-xIr+G6HeYfOhCuswdqcFpSX47SPhm0EpisWJ6h7fHlWwaVIvH3dLnejpatrtw6Xc6HaLrpq05y7VRfvDmDGIog== + +possible-typed-array-names@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" + integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier-eslint@^16.3.0: + version "16.3.0" + resolved "https://registry.yarnpkg.com/prettier-eslint/-/prettier-eslint-16.3.0.tgz#8f7bbc863f35939948e386eafe72ffd653b2d80b" + integrity sha512-Lh102TIFCr11PJKUMQ2kwNmxGhTsv/KzUg9QYF2Gkw259g/kPgndZDWavk7/ycbRvj2oz4BPZ1gCU8bhfZH/Xg== + dependencies: + "@typescript-eslint/parser" "^6.7.5" + common-tags "^1.4.0" + dlv "^1.1.0" + eslint "^8.7.0" + indent-string "^4.0.0" + lodash.merge "^4.6.0" + loglevel-colored-level-prefix "^1.0.0" + prettier "^3.0.1" + pretty-format "^29.7.0" + require-relative "^0.8.7" + typescript "^5.2.2" + vue-eslint-parser "^9.1.0" + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@^2.7.1: + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== + +prettier@^3.0.1: + version "3.3.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105" + integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== + +pretty-format@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-28.1.3.tgz#c9fba8cedf99ce50963a11b27d982a9ae90970d5" + integrity sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q== + dependencies: + "@jest/schemas" "^28.1.3" + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +pump@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.2.tgz#836f3edd6bc2ee599256c924ffe0d88573ddcbf8" + integrity sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + +react-is@^18.0.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +regexp.prototype.flags@^1.5.2: + version "1.5.3" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz#b3ae40b1d2499b8350ab2c3fe6ef3845d3a96f42" + integrity sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-errors "^1.3.0" + set-function-name "^2.0.2" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +require-relative@^0.8.7: + version "0.8.7" + resolved "https://registry.yarnpkg.com/require-relative/-/require-relative-0.8.7.tgz#7999539fc9e047a37928fa196f8e1563dabd36de" + integrity sha512-AKGr4qvHiryxRb19m3PsLRGuKVAbJLUD7E6eOaHkfKhwc+vSgVOCY5xNvm9EkolBKTOf0GrQAZKLimOCz81Khg== + +resolve-alpn@^1.0.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" + integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve.exports@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.1.tgz#05cfd5b3edf641571fd46fa608b610dda9ead999" + integrity sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ== + +resolve@^1.20.0, resolve@^1.22.4: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +responselike@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.1.tgz#9a0bc8fdc252f3fb1cca68b016591059ba1422bc" + integrity sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw== + dependencies: + lowercase-keys "^2.0.0" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^3.0.0, rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +safe-array-concat@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" + integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q== + dependencies: + call-bind "^1.0.7" + get-intrinsic "^1.2.4" + has-symbols "^1.0.3" + isarray "^2.0.5" + +safe-regex-test@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" + integrity sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-regex "^1.1.4" + +semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.3.5, semver@^7.3.6, semver@^7.3.7, semver@^7.5.3, semver@^7.5.4, semver@^7.6.3: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +set-function-name@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" + integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.2" + +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + +signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stack-utils@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string.prototype.trim@^1.2.9: + version "1.2.9" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" + integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.0" + es-object-atoms "^1.0.0" + +string.prototype.trimend@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229" + integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +string.prototype.trimstart@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" + integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +strip-ansi@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg== + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + integrity sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.0.0, supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-hyperlinks@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624" + integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +terminal-link@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" + integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== + dependencies: + ansi-escapes "^4.2.1" + supports-hyperlinks "^2.0.0" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +ts-api-utils@^1.0.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" + integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== + +ts-jest@^29.2.5: + version "29.2.5" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.2.5.tgz#591a3c108e1f5ebd013d3152142cb5472b399d63" + integrity sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA== + dependencies: + bs-logger "^0.2.6" + ejs "^3.1.10" + fast-json-stable-stringify "^2.1.0" + jest-util "^29.0.0" + json5 "^2.2.3" + lodash.memoize "^4.1.2" + make-error "^1.3.6" + semver "^7.6.3" + yargs-parser "^21.1.1" + +ts-node@^10.9.2: + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +tsconfig-paths@^3.15.0: + version "3.15.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" + integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tslib@^1.8.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +typed-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" + integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + is-typed-array "^1.1.13" + +typed-array-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" + integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + +typed-array-byte-offset@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" + integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + +typed-array-length@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" + integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + possible-typed-array-names "^1.0.0" + +typescript@^4.7.4: + version "4.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" + integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== + +typescript@^5.2.2: + version "5.6.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.2.tgz#d1de67b6bef77c41823f822df8f0b3bcff60a5a0" + integrity sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw== + +unbox-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" + integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== + dependencies: + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" + which-boxed-primitive "^1.0.2" + +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + +update-browserslist-db@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5" + integrity sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.0" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +util@^0.12.5: + version "0.12.5" + resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" + integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== + dependencies: + inherits "^2.0.3" + is-arguments "^1.0.4" + is-generator-function "^1.0.7" + is-typed-array "^1.1.3" + which-typed-array "^1.1.2" + +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +v8-to-istanbul@^9.0.1: + version "9.3.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" + integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" + +vue-eslint-parser@^9.1.0: + version "9.4.3" + resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz#9b04b22c71401f1e8bca9be7c3e3416a4bde76a8" + integrity sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg== + dependencies: + debug "^4.3.4" + eslint-scope "^7.1.1" + eslint-visitor-keys "^3.3.0" + espree "^9.3.1" + esquery "^1.4.0" + lodash "^4.17.21" + semver "^7.3.6" + +wait-for-expect@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/wait-for-expect/-/wait-for-expect-3.0.2.tgz#d2f14b2f7b778c9b82144109c8fa89ceaadaa463" + integrity sha512-cfS1+DZxuav1aBYbaO/kE06EOS8yRw7qOFoD3XtjTkYvCvh3zUvNST8DXK/nPaeqIzIv3P3kL3lRJn8iwOiSag== + +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +web3-core@^4.4.0, web3-core@^4.5.0, web3-core@^4.5.1, web3-core@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-4.6.0.tgz#1b3e88ed35142b4d6fcbc7737e1d71943f99cf45" + integrity sha512-j8uQ/7zSwpmLClMMeZb736Ok3V4cWSd0dnd29jkd10d1pedi32r+hSAgycxSJLLWtPHOzMBIXUjj3TF/IAClVQ== + dependencies: + web3-errors "^1.3.0" + web3-eth-accounts "^4.2.1" + web3-eth-iban "^4.0.7" + web3-providers-http "^4.2.0" + web3-providers-ws "^4.0.8" + web3-types "^1.8.0" + web3-utils "^4.3.1" + web3-validator "^2.0.6" + optionalDependencies: + web3-providers-ipc "^4.0.7" + +web3-errors@^1.1.3, web3-errors@^1.2.0, web3-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/web3-errors/-/web3-errors-1.3.0.tgz#504e4d3218899df108856940087a8022d6688d74" + integrity sha512-j5JkAKCtuVMbY3F5PYXBqg1vWrtF4jcyyMY1rlw8a4PV67AkqlepjGgpzWJZd56Mt+TvHy6DA1F/3Id8LatDSQ== + dependencies: + web3-types "^1.7.0" + +web3-eth-abi@^4.2.3, web3-eth-abi@^4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-4.2.4.tgz#b66f4b067ba06c0aecc013e98a4d717547ab8174" + integrity sha512-FGoj/ENm/Iq3+6myJyiDCwbFkha9ZCx2fRdiIdw3mp7S4lgu+ay3EVzQPRxJjNBm09UEfxB9yoSAPKj9Z3Mbxg== + dependencies: + abitype "0.7.1" + web3-errors "^1.3.0" + web3-types "^1.8.0" + web3-utils "^4.3.1" + web3-validator "^2.0.6" + +web3-eth-accounts@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-4.2.1.tgz#db27399137e1a26f9d467b9500019a70771f5724" + integrity sha512-aOlEZFzqAgKprKs7+DGArU4r9b+ILBjThpeq42aY7LAQcP+mSpsWcQgbIRK3r/n3OwTYZ3aLPk0Ih70O/LwnYA== + dependencies: + "@ethereumjs/rlp" "^4.0.1" + crc-32 "^1.2.2" + ethereum-cryptography "^2.0.0" + web3-errors "^1.3.0" + web3-types "^1.7.0" + web3-utils "^4.3.1" + web3-validator "^2.0.6" + +web3-eth-contract@^4.5.0, web3-eth-contract@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-4.7.0.tgz#119a744e8a35f60fd7bc3e4f8637f0380a3d0e85" + integrity sha512-fdStoBOjFyMHwlyJmSUt/BTDL1ATwKGmG3zDXQ/zTKlkkW/F/074ut0Vry4GuwSBg9acMHc0ycOiZx9ZKjNHsw== + dependencies: + "@ethereumjs/rlp" "^5.0.2" + web3-core "^4.5.1" + web3-errors "^1.3.0" + web3-eth "^4.8.2" + web3-eth-abi "^4.2.3" + web3-types "^1.7.0" + web3-utils "^4.3.1" + web3-validator "^2.0.6" + +web3-eth-ens@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-4.4.0.tgz#bc0d11d755cb15ed4b82e38747c5104622d9a4b9" + integrity sha512-DeyVIS060hNV9g8dnTx92syqvgbvPricE3MerCxe/DquNZT3tD8aVgFfq65GATtpCgDDJffO2bVeHp3XBemnSQ== + dependencies: + "@adraffy/ens-normalize" "^1.8.8" + web3-core "^4.5.0" + web3-errors "^1.2.0" + web3-eth "^4.8.0" + web3-eth-contract "^4.5.0" + web3-net "^4.1.0" + web3-types "^1.7.0" + web3-utils "^4.3.0" + web3-validator "^2.0.6" + +web3-eth-iban@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-4.0.7.tgz#ee504f845d7b6315f0be78fcf070ccd5d38e4aaf" + integrity sha512-8weKLa9KuKRzibC87vNLdkinpUE30gn0IGY027F8doeJdcPUfsa4IlBgNC4k4HLBembBB2CTU0Kr/HAOqMeYVQ== + dependencies: + web3-errors "^1.1.3" + web3-types "^1.3.0" + web3-utils "^4.0.7" + web3-validator "^2.0.3" + +web3-eth-personal@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-4.1.0.tgz#f5b506a4570bf1241d1db2de12cb413ea0bb4486" + integrity sha512-RFN83uMuvA5cu1zIwwJh9A/bAj0OBxmGN3tgx19OD/9ygeUZbifOL06jgFzN0t+1ekHqm3DXYQM8UfHpXi7yDQ== + dependencies: + web3-core "^4.6.0" + web3-eth "^4.9.0" + web3-rpc-methods "^1.3.0" + web3-types "^1.8.0" + web3-utils "^4.3.1" + web3-validator "^2.0.6" + +web3-eth@^4.8.0, web3-eth@^4.8.2, web3-eth@^4.9.0: + version "4.9.0" + resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-4.9.0.tgz#324403d913cc29bcae6cc1ad50a6defeb762828a" + integrity sha512-lE+5rQUkQq1Mzf3uZ/tlay8nvMyC/CmaRFRFQ015OZuvSrRr/byZhhkzY5ZWkIetESTMqfWapu67yeHebcHxwA== + dependencies: + setimmediate "^1.0.5" + web3-core "^4.6.0" + web3-errors "^1.3.0" + web3-eth-abi "^4.2.4" + web3-eth-accounts "^4.2.1" + web3-net "^4.1.0" + web3-providers-ws "^4.0.8" + web3-rpc-methods "^1.3.0" + web3-types "^1.8.0" + web3-utils "^4.3.1" + web3-validator "^2.0.6" + +web3-net@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-4.1.0.tgz#db7bde675e58b153339e4f149f29ec0410d6bab2" + integrity sha512-WWmfvHVIXWEoBDWdgKNYKN8rAy6SgluZ0abyRyXOL3ESr7ym7pKWbfP4fjApIHlYTh8tNqkrdPfM4Dyi6CA0SA== + dependencies: + web3-core "^4.4.0" + web3-rpc-methods "^1.3.0" + web3-types "^1.6.0" + web3-utils "^4.3.0" + +web3-providers-http@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-4.2.0.tgz#0f4bf424681a068d49994aa7fabc69bed45ac50b" + integrity sha512-IPMnDtHB7dVwaB7/mMxAZzyq7d5ezfO1+Vw0bNfAeIi7gaDlJiggp85SdyAfOgov8AMUA/dyiY72kQ0KmjXKvQ== + dependencies: + cross-fetch "^4.0.0" + web3-errors "^1.3.0" + web3-types "^1.7.0" + web3-utils "^4.3.1" + +web3-providers-ipc@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-4.0.7.tgz#9ec4c8565053af005a5170ba80cddeb40ff3e3d3" + integrity sha512-YbNqY4zUvIaK2MHr1lQFE53/8t/ejHtJchrWn9zVbFMGXlTsOAbNoIoZWROrg1v+hCBvT2c9z8xt7e/+uz5p1g== + dependencies: + web3-errors "^1.1.3" + web3-types "^1.3.0" + web3-utils "^4.0.7" + +web3-providers-ws@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-4.0.8.tgz#6de7b262f7ec6df1a2dff466ba91d7ebdac2c45e" + integrity sha512-goJdgata7v4pyzHRsg9fSegUG4gVnHZSHODhNnn6J93ykHkBI1nz4fjlGpcQLUMi4jAMz6SHl9Ibzs2jj9xqPw== + dependencies: + "@types/ws" "8.5.3" + isomorphic-ws "^5.0.0" + web3-errors "^1.2.0" + web3-types "^1.7.0" + web3-utils "^4.3.1" + ws "^8.17.1" + +web3-rpc-methods@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/web3-rpc-methods/-/web3-rpc-methods-1.3.0.tgz#d5ee299a69389d63822d354ddee2c6a121a6f670" + integrity sha512-/CHmzGN+IYgdBOme7PdqzF+FNeMleefzqs0LVOduncSaqsppeOEoskLXb2anSpzmQAP3xZJPaTrkQPWSJMORig== + dependencies: + web3-core "^4.4.0" + web3-types "^1.6.0" + web3-validator "^2.0.6" + +web3-rpc-providers@^1.0.0-rc.2: + version "1.0.0-rc.2" + resolved "https://registry.yarnpkg.com/web3-rpc-providers/-/web3-rpc-providers-1.0.0-rc.2.tgz#2f3bf62415708ce09ffd5c77d197046e7d5fefb1" + integrity sha512-ocFIEXcBx/DYQ90HhVepTBUVnL9pGsZw8wyPb1ZINSenwYus9SvcFkjU1Hfvd/fXjuhAv2bUVch9vxvMx1mXAQ== + dependencies: + web3-errors "^1.3.0" + web3-providers-http "^4.2.0" + web3-providers-ws "^4.0.8" + web3-types "^1.7.0" + web3-utils "^4.3.1" + web3-validator "^2.0.6" + +web3-types@^1.3.0, web3-types@^1.6.0, web3-types@^1.7.0, web3-types@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/web3-types/-/web3-types-1.8.0.tgz#d2151fd9e87d711ef5a13079885665b458243e46" + integrity sha512-Z51wFLPGhZM/1uDxrxE8gzju3t2aEdRGn+YmLX463id5UjTuMEmP/9in1GFjqrsPB3m86czs8RnGBUt3ovueMw== + +web3-utils@^4.0.7, web3-utils@^4.3.0, web3-utils@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-4.3.1.tgz#3dcd75e3c683c26f0ba824bf27d7bc0a68b111de" + integrity sha512-kGwOk8FxOLJ9DQC68yqNQc7AzN+k9YDLaW+ZjlAXs3qORhf8zXk5SxWAAGLbLykMs3vTeB0FTb1Exut4JEYfFA== + dependencies: + ethereum-cryptography "^2.0.0" + eventemitter3 "^5.0.1" + web3-errors "^1.2.0" + web3-types "^1.7.0" + web3-validator "^2.0.6" + +web3-validator@^2.0.3, web3-validator@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/web3-validator/-/web3-validator-2.0.6.tgz#a0cdaa39e1d1708ece5fae155b034e29d6a19248" + integrity sha512-qn9id0/l1bWmvH4XfnG/JtGKKwut2Vokl6YXP5Kfg424npysmtRLe9DgiNBM9Op7QL/aSiaA0TVXibuIuWcizg== + dependencies: + ethereum-cryptography "^2.0.0" + util "^0.12.5" + web3-errors "^1.2.0" + web3-types "^1.6.0" + zod "^3.21.4" + +web3@^4.13.0: + version "4.13.0" + resolved "https://registry.yarnpkg.com/web3/-/web3-4.13.0.tgz#b1740006d61fec9388517d7b63e15f53363f3956" + integrity sha512-wRXTu/YjelvBJ7PSLzp/rW8/6pqj4RlXzdKSkjk01RaHDvnpLogLU/VL8OF5ygqhY7IzhY5MSrl9SnC8C9Z4uA== + dependencies: + web3-core "^4.6.0" + web3-errors "^1.3.0" + web3-eth "^4.9.0" + web3-eth-abi "^4.2.4" + web3-eth-accounts "^4.2.1" + web3-eth-contract "^4.7.0" + web3-eth-ens "^4.4.0" + web3-eth-iban "^4.0.7" + web3-eth-personal "^4.1.0" + web3-net "^4.1.0" + web3-providers-http "^4.2.0" + web3-providers-ws "^4.0.8" + web3-rpc-methods "^1.3.0" + web3-rpc-providers "^1.0.0-rc.2" + web3-types "^1.8.0" + web3-utils "^4.3.1" + web3-validator "^2.0.6" + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which-typed-array@^1.1.14, which-typed-array@^1.1.15, which-typed-array@^1.1.2: + version "1.1.15" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" + integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.2" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^4.0.1, write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +ws@^8.17.1: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== + +xml@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" + integrity sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.3.1: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +zod@^3.21.4: + version "3.23.8" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" + integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==