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==