diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 79bf0d2e1..a623261ad 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -207,16 +207,12 @@ jobs: sui: name: Sui runs-on: ubuntu-20.04 - defaults: - run: - shell: bash - working-directory: ./sui steps: - name: Checkout repository uses: actions/checkout@v2 - name: Run tests via docker - run: make test-docker + run: cd sui && make test-docker terra: runs-on: ubuntu-20.04 diff --git a/.gitignore b/.gitignore index b8e28da48..e22024f31 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ bigtable-writer.json /solana/artifacts-mainnet/ /ethereum/out/ /ethereum/cache/ +sui.log.* +sui/examples/wrapped_coin diff --git a/Dockerfile.const b/Dockerfile.const index 1cfad2640..7f3d7e7cf 100644 --- a/Dockerfile.const +++ b/Dockerfile.const @@ -47,6 +47,7 @@ COPY --from=const-build /scripts/.env.hex terra/tools/.env COPY --from=const-build /scripts/.env.hex cosmwasm/deployment/terra2/tools/.env COPY --from=const-build /scripts/.env.hex algorand/.env COPY --from=const-build /scripts/.env.hex near/.env +COPY --from=const-build /scripts/.env.hex sui/.env COPY --from=const-build /scripts/.env.hex aptos/.env COPY --from=const-build /scripts/.env.hex wormchain/contracts/tools/.env diff --git a/sui/.gitignore b/sui/.gitignore index f2ca2e862..6ddb4b0ac 100644 --- a/sui/.gitignore +++ b/sui/.gitignore @@ -1,2 +1,2 @@ -env.sh +deploy.out sui.log.* diff --git a/sui/Docker.md b/sui/Docker.md old mode 100644 new mode 100755 index 414901556..f6ff7cd7c --- a/sui/Docker.md +++ b/sui/Docker.md @@ -1,8 +1,13 @@ # first build the image -(cd ..; DOCKER_BUILDKIT=1 docker build --progress plain -f sui/Dockerfile.base -t sui .) + +cd ..; DOCKER_BUILDKIT=1 docker build --no-cache --progress plain -f sui/Dockerfile.base -t sui . + # tag the image with the appropriate version -docker tag sui:latest ghcr.io/wormhole-foundation/sui:0.15.0 + +docker tag sui:latest ghcr.io/wormhole-foundation/sui:1.0.0-testnet + # push to ghcr -docker push ghcr.io/wormhole-foundation/sui:0.15.0 + +docker push ghcr.io/wormhole-foundation/sui:1.0.0-testnet echo remember to update both Dockerfile and Dockerfile.export diff --git a/sui/Dockerfile b/sui/Dockerfile index 2ab9d2560..4fad4cfa2 100644 --- a/sui/Dockerfile +++ b/sui/Dockerfile @@ -1,25 +1,33 @@ -FROM ghcr.io/wormhole-foundation/sui:0.21.1@sha256:59b91529e426b44c152b40ad0e7a6a7aafc8225722b5d7e331056a4d65845015 as sui +FROM ghcr.io/wormhole-foundation/sui:1.0.0-testnet@sha256:63a8094590ddb90320aa1c86414f17cc73c759ecbdfaf2fe78f135b7c08ec536 as sui -RUN dnf -y install make git +RUN dnf -y install make git npm -COPY README.md cert.pem* /certs/ +COPY sui/README.md sui/cert.pem* /certs/ RUN if [ -e /certs/cert.pem ]; then cp /certs/cert.pem /etc/ssl/certs/ca-certificates.crt; fi RUN if [ -e /certs/cert.pem ]; then git config --global http.sslCAInfo /certs/cert.pem; fi +RUN sui genesis -f + +COPY sui/devnet/ /root/.sui/sui_config/ + +# Build CLI, TODO(aki): move this to base image before merging into main +RUN npm install -g n typescript ts-node +RUN n stable +COPY clients/js /tmp/clients/js +WORKDIR /tmp/clients/js +RUN make install + WORKDIR /tmp -RUN sui genesis -f -COPY scripts/start_node.sh . -COPY scripts/funder.sh . - -COPY wormhole/ wormhole -COPY token_bridge/ token_bridge -# COPY examples/ examples -COPY Makefile Makefile +COPY sui/scripts/ scripts +COPY sui/wormhole/ wormhole +COPY sui/token_bridge/ token_bridge +COPY sui/examples/ examples +COPY sui/Makefile Makefile +COPY sui/.env* . FROM sui AS tests WORKDIR /tmp -RUN --mount=type=cache,target=/root/.move,id=move_cache \ - make test +RUN --mount=type=cache,target=/root/.move,id=move_cache make test diff --git a/sui/Dockerfile.base b/sui/Dockerfile.base index febda65a2..447b61c03 100644 --- a/sui/Dockerfile.base +++ b/sui/Dockerfile.base @@ -13,10 +13,11 @@ COPY sui/scripts/node_builder.sh /tmp RUN /tmp/node_builder.sh -WORKDIR /tmp - FROM docker.io/redhat/ubi8@sha256:56c374376a42da40f3aec753c4eab029b5ea162d70cb5f0cda24758780c31d81 as export-stage +RUN dnf -y update +RUN dnf -y install jq curl git + COPY --from=sui-node /root/.cargo/bin/sui /bin/sui COPY --from=sui-node /root/.cargo/bin/sui-faucet /bin/sui-faucet COPY --from=sui-node /root/.cargo/bin/sui-node /bin/sui-node diff --git a/sui/Makefile b/sui/Makefile index 07ce384d8..6c3f2318e 100644 --- a/sui/Makefile +++ b/sui/Makefile @@ -1,13 +1,13 @@ -CONTRACT_DIRS := wormhole token_bridge +TEST_CONTRACT_DIRS := wormhole token_bridge examples/coins examples/core_messages +CLEAN_CONTRACT_DIRS := wormhole token_bridge examples/coins examples/core_messages -TARGETS := build test +.PHONY: clean +clean: + $(foreach dir,$(TEST_CONTRACT_DIRS), make -C $(dir) $@ &&) true -.PHONY: $(TARGETS) -$(TARGETS): - $(foreach dir,$(CONTRACT_DIRS), make -C $(dir) $@ &&) true +.PHONY: test +test: + $(foreach dir,$(TEST_CONTRACT_DIRS), make -C $(dir) $@ &&) true test-docker: - DOCKER_BUILDKIT=1 docker build -f Dockerfile --target tests . - -sui_export: - DOCKER_BUILDKIT=1 docker build --progress plain -f Dockerfile.export -t near-export -o type=local,dest=$$HOME/.cargo/bin . + DOCKER_BUILDKIT=1 docker build -f Dockerfile .. diff --git a/sui/NOTES.md b/sui/NOTES.md index 671ce4351..5677cfb92 100644 --- a/sui/NOTES.md +++ b/sui/NOTES.md @@ -110,3 +110,5 @@ Digest: cL+uWFEVcQrkAiOxOJmaK7JmlOJdE3/8X5JFbJwBxCQ= kubectl exec -it guardian-0 -- /guardiand admin send-observation-request --socket /tmp/admin.sock 21 70bfae585115710ae40223b138999a2bb26694e25d137ffc5f92456c9c01c424 // curl -s -X POST -d '{"jsonrpc":"2.0", "id": 1, "method": "sui_getCommitteeInfo", "params": []}' -H 'Content-Type: application/json' http://127.0.0.1:9002 | jq + +// curl -s -X POST -d '{"jsonrpc":"2.0", "id": 1, "method": "sui_getLatestCheckpointSequenceNumber", "params": []}' -H 'Content-Type: application/json' http://127.0.0.1:9000 diff --git a/sui/README.md b/sui/README.md index ed9281bb0..0d7161fd5 100644 --- a/sui/README.md +++ b/sui/README.md @@ -1,14 +1,137 @@ +# Wormhole on Sui + +This folder contains the reference implementation of the Wormhole cross-chain +messaging protocol smart contracts on the [Sui](https://mystenlabs.com/) +blockchain, implemented in the [Move](https://move-book.com/) programming +language. + +# Project structure + +The project is laid out as follows: + +- [wormhole](./wormhole) the core messaging layer +- [token_bridge](./token_bridge) the asset transfer layer +- [coin](./coin) a template for creating Wormhole wrapped coins + # Installation -Make sure your Cargo version is at least 1.64.0 and then follow the steps below: + +Make sure your Cargo version is at least 1.65.0 and then follow the steps below: + - https://docs.sui.io/build/install +## Prerequisites -# Sui CLI -- do `sui start` to spin up a local network -- do `rpc-server` to start a server for handling rpc calls -- do `sui-faucet` to start a faucet for requesting funds from active-address +Install the `Sui` CLI. This tool is used to compile the contracts and run the tests. -# TODOs -- The move dependencies are currently pinned to a version that matches the - docker image for reproducibility. These should be regularly updated to track - any upstream changes before the mainnet release. +```sh +cargo install --locked --git https://github.com/MystenLabs/sui.git --rev 09b2081498366df936abae26eea4b2d5cafb2788 sui sui-faucet +``` + +Some useful Sui CLI commands are + +- `sui start` to spin up a local network +- `rpc-server` to start a server for handling rpc calls +- `sui-faucet` to start a faucet for requesting funds from active-address + +Next, install the [worm](../clients/js/README.md) CLI tool by running + +```sh +wormhole/clients/js $ make install +``` + +`worm` is the swiss army knife for interacting with wormhole contracts on all +supported chains, and generating signed messages (VAAs) for testing. + +As an optional, but recommended step, install the +[move-analyzer](https://github.com/move-language/move/tree/main/language/move-analyzer) +Language Server (LSP): + +```sh +cargo install --git https://github.com/move-language/move.git move-analyzer --branch main --features "address32" +``` + +This installs the LSP backend which is then supported by most popular editors such as [emacs](https://github.com/emacs-lsp/lsp-mode), [vim](https://github.com/neoclide/coc.nvim), and even [vscode](https://marketplace.visualstudio.com/items?itemName=move.move-analyzer). + +
+ For emacs, you may need to add the following to your config file: + +```lisp +;; Move +(define-derived-mode move-mode rust-mode "Move" + :group 'move-mode) + +(add-to-list 'auto-mode-alist '("\\.move\\'" . move-mode)) + +(with-eval-after-load 'lsp-mode + (add-to-list 'lsp-language-id-configuration + '(move-mode . "move")) + + (lsp-register-client + (make-lsp-client :new-connection (lsp-stdio-connection "move-analyzer") + :activation-fn (lsp-activate-on "move") + :server-id 'move-analyzer))) +``` + +
+ +## Building & running tests + +The project uses a simple `make`-based build system for building and running +tests. Running `make test` in this directory will run the tests for each +contract. If you only want to run the tests for, say, the token bridge contract, +then you can run `make test` in the `token_bridge` directory, or run `make -C +token_bridge test` from this directory. + +Additionally, `make test-docker` runs the tests in a docker container which is +set up with all the necessary dependencies. This is the command that runs in CI. + +## Running a local validator and deploying the contracts to it + +Simply run + +```sh +worm start-validator sui +``` + +which will start a local sui validator with an RPC endpoint at `0.0.0.0:9000` +and the faucet endpoint at `0.0.0.0:5003/gas`. Note that the faucet takes a few +(~10) seconds to come up, so only proceed when you see the following: + +```text +Faucet is running. Faucet endpoint: 0.0.0.0:5003/gas +``` + +Once the validator is running, the contracts are ready to deploy. In the +[scripts](./scripts) directory, run + +```sh +scripts $ ./deploy.sh devnet +``` + +This will deploy the core contract and the token bridge. + +When you make a change to the contract, you can simply restart the validator and +run the deploy script again. + + + +# Implementation notes / coding practices + +In this section, we describe some of the implementation design decisions and +coding practices we converged on along the way. Note that the coding guidelines +are prescriptive rather than descriptive, and the goal is for the contracts to +ultimately follow these, but they might not during earlier development phases. + +### TODO diff --git a/sui/coin/Move.toml b/sui/coin/Move.toml deleted file mode 100644 index 8baa4d506..000000000 --- a/sui/coin/Move.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "Coin" -version = "0.0.1" - -[dependencies] -Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework", rev = "2d709054a08d904b9229a2472af679f210af3827" } -TokenBridge = { local = "../token_bridge"} -Wormhole = { local = "../wormhole"} - -[addresses] -coin="0x0" diff --git a/sui/coin/sources/coin.move b/sui/coin/sources/coin.move deleted file mode 100644 index cb82eac2f..000000000 --- a/sui/coin/sources/coin.move +++ /dev/null @@ -1,24 +0,0 @@ -module coin::coin { - use sui::transfer; - use sui::tx_context::{Self, TxContext}; - - use token_bridge::wrapped; - - struct COIN has drop {} - - fun init(coin_witness: COIN, ctx: &mut TxContext) { - // Step 1. Paste token attestation VAA below. - let vaa_bytes = x"0100000000010080366065746148420220f25a6275097370e8db40984529a6676b7a5fc9feb11755ec49ca626b858ddfde88d15601f85ab7683c5f161413b0412143241c700aff010000000100000001000200000000000000000000000000000000000000000000000000000000deadbeef000000000150eb23000200000000000000000000000000000000000000000000000000000000beefface00020c424545460000000000000000000000000000000000000000000000000000000042656566206661636520546f6b656e0000000000000000000000000000000000"; - - let new_wrapped_coin = wrapped::create_wrapped_coin(vaa_bytes, coin_witness, ctx); - transfer::transfer( - new_wrapped_coin, - tx_context::sender(ctx) - ); - } - - #[test_only] - public fun test_init(ctx: &mut TxContext) { - init(COIN {}, ctx) - } -} diff --git a/sui/devnet/client.yaml b/sui/devnet/client.yaml new file mode 100644 index 000000000..28fb28695 --- /dev/null +++ b/sui/devnet/client.yaml @@ -0,0 +1,12 @@ +--- +keystore: + File: /root/.sui/sui_config/sui.keystore +envs: + - alias: localnet + rpc: "http://0.0.0.0:9000" + ws: ~ + - alias: devnet + rpc: "https://fullnode.devnet.sui.io:443" + ws: ~ +active_env: localnet +active_address: "0xed867315e3f7c83ae82e6d5858b6a6cc57c291fd84f7509646ebc8162169cf96" diff --git a/sui/devnet/fullnode.yaml b/sui/devnet/fullnode.yaml new file mode 100644 index 000000000..f29f7d442 --- /dev/null +++ b/sui/devnet/fullnode.yaml @@ -0,0 +1,45 @@ +--- +protocol-key-pair: + value: U/JD+ELChkDDYiw/jq+I1RVJAUE8Kcu8FiQBSDVM7Aw= +worker-key-pair: + value: AAw8S7FtL0j0VTRKWNrL/GJCB8BwJOA6I2N0OGW6QlG8 +account-key-pair: + value: AFyoDGvHw/O4uSwf+qXg96bbRan1T0v6Zmnv+P8brtGE +network-key-pair: + value: AH8ybx8kJxx1VDebF9aJv2pQQqWoDM9tnkHnPUo5EFwS +db-path: /root/.sui/sui_config/authorities_db/full_node_db +network-address: /ip4/127.0.0.1/tcp/32889/http +json-rpc-address: "0.0.0.0:9000" +metrics-address: "127.0.0.1:43249" +admin-interface-port: 36887 +enable-event-processing: true +grpc-load-shed: ~ +grpc-concurrency-limit: ~ +p2p-config: + listen-address: "127.0.0.1:34131" + external-address: /ip4/127.0.0.1/udp/34131 + seed-peers: + - peer-id: 68f5be330e0db85dccbdc36d4e1c59dd2828a27365db26a75198c8a54335b1de + address: /ip4/127.0.0.1/udp/33745 + - peer-id: 0b75d3508725b9d260995e7081e9ff3639d7c7d858f8d2379cc9c9f39d8dfc8a + address: /ip4/127.0.0.1/udp/40075 + - peer-id: 6447bdd9b1bf65a16036950d7ba7a495247ecb291c55b113ed3e76d0543196d9 + address: /ip4/127.0.0.1/udp/39953 + - peer-id: f454efea06dadd727a682d0b08d55f2df32b83e364a0b75270aaf9c247f0ed6f + address: /ip4/127.0.0.1/udp/35253 +genesis: + genesis-file-location: /root/.sui/sui_config/genesis.blob +authority-store-pruning-config: + num-latest-epoch-dbs-to-retain: 3 + epoch-db-pruning-period-secs: 3600 + num-epochs-to-retain: 1 + max-checkpoints-in-batch: 200 + max-transactions-in-batch: 1000 + use-range-deletion: true +end-of-epoch-broadcast-channel-capacity: 128 +checkpoint-executor-config: + checkpoint-execution-max-concurrency: 200 + local-execution-timeout-sec: 10 +db-checkpoint-config: + perform-db-checkpoints-at-epoch-end: false +indirect-objects-threshold: 18446744073709551615 diff --git a/sui/devnet/genesis.blob b/sui/devnet/genesis.blob new file mode 100644 index 000000000..7afaa8783 Binary files /dev/null and b/sui/devnet/genesis.blob differ diff --git a/sui/devnet/network.yaml b/sui/devnet/network.yaml new file mode 100644 index 000000000..32bddfeb7 --- /dev/null +++ b/sui/devnet/network.yaml @@ -0,0 +1,297 @@ +--- +validator_configs: + - protocol-key-pair: + value: VTDx4HjVmRBqdqBWg2zN+zcFE20io3CrBchGy/iV1lo= + worker-key-pair: + value: AOALyRbJi6ld82KFPzzSTqFcvnEJe7cxFmE4UmgTMkt1 + account-key-pair: + value: AFO6/i9VpgpR9Tb5fA7PBh2bM8HMbsxjPC/twrRFXZoh + network-key-pair: + value: ACTXLjvKVNOmez4eB/undu829MFIpNqRactays+pGNnf + db-path: /root/.sui/sui_config/authorities_db/99f25ef61f80 + network-address: /ip4/127.0.0.1/tcp/37395/http + json-rpc-address: "127.0.0.1:34677" + metrics-address: "127.0.0.1:34733" + admin-interface-port: 33561 + consensus-config: + address: /ip4/127.0.0.1/tcp/36643/http + db-path: /root/.sui/sui_config/consensus_db/99f25ef61f80 + internal-worker-address: ~ + max-pending-transactions: ~ + narwhal-config: + header_num_of_batches_threshold: 32 + max_header_num_of_batches: 1000 + max_header_delay: 2000ms + min_header_delay: 500ms + gc_depth: 50 + sync_retry_delay: 5000ms + sync_retry_nodes: 3 + batch_size: 500000 + max_batch_delay: 100ms + block_synchronizer: + range_synchronize_timeout: 30000ms + certificates_synchronize_timeout: 30000ms + payload_synchronize_timeout: 30000ms + payload_availability_timeout: 30000ms + handler_certificate_deliver_timeout: 30000ms + consensus_api_grpc: + socket_addr: /ip4/127.0.0.1/tcp/32825/http + get_collections_timeout: 5000ms + remove_collections_timeout: 5000ms + max_concurrent_requests: 500000 + prometheus_metrics: + socket_addr: /ip4/127.0.0.1/tcp/43711/http + network_admin_server: + primary_network_admin_server_port: 34257 + worker_network_admin_server_base_port: 45379 + anemo: + send_certificate_rate_limit: ~ + get_payload_availability_rate_limit: ~ + get_certificates_rate_limit: ~ + report_batch_rate_limit: ~ + request_batch_rate_limit: ~ + enable-event-processing: false + grpc-load-shed: ~ + grpc-concurrency-limit: 20000000000 + p2p-config: + listen-address: "127.0.0.1:33745" + external-address: /ip4/127.0.0.1/udp/33745 + genesis: + genesis-file-location: /root/.sui/sui_config/genesis.blob + authority-store-pruning-config: + num-latest-epoch-dbs-to-retain: 3 + epoch-db-pruning-period-secs: 3600 + num-epochs-to-retain: 1 + max-checkpoints-in-batch: 200 + max-transactions-in-batch: 1000 + use-range-deletion: true + end-of-epoch-broadcast-channel-capacity: 128 + checkpoint-executor-config: + checkpoint-execution-max-concurrency: 200 + local-execution-timeout-sec: 10 + db-checkpoint-config: + perform-db-checkpoints-at-epoch-end: false + indirect-objects-threshold: 18446744073709551615 + - protocol-key-pair: + value: avYcyVgYMXTyaUYh9IRwLK0gSzl7YF6ZQDAbrS1Bhvo= + worker-key-pair: + value: ALcYC9nZa2UFKkxycem4wHZUW6nPTmAPWIC0Me/X8/OQ + account-key-pair: + value: AFQ60/bLdbiryFJsWRrXW29RvC56WN2CAyS75jTRtQWj + network-key-pair: + value: AKgyYvFpPmPmmEPNdltJ4cfcb9D0t0bigFz3cak+iblf + db-path: /root/.sui/sui_config/authorities_db/8dcff6d15504 + network-address: /ip4/127.0.0.1/tcp/45631/http + json-rpc-address: "127.0.0.1:39065" + metrics-address: "127.0.0.1:36781" + admin-interface-port: 41085 + consensus-config: + address: /ip4/127.0.0.1/tcp/46119/http + db-path: /root/.sui/sui_config/consensus_db/8dcff6d15504 + internal-worker-address: ~ + max-pending-transactions: ~ + narwhal-config: + header_num_of_batches_threshold: 32 + max_header_num_of_batches: 1000 + max_header_delay: 2000ms + min_header_delay: 500ms + gc_depth: 50 + sync_retry_delay: 5000ms + sync_retry_nodes: 3 + batch_size: 500000 + max_batch_delay: 100ms + block_synchronizer: + range_synchronize_timeout: 30000ms + certificates_synchronize_timeout: 30000ms + payload_synchronize_timeout: 30000ms + payload_availability_timeout: 30000ms + handler_certificate_deliver_timeout: 30000ms + consensus_api_grpc: + socket_addr: /ip4/127.0.0.1/tcp/36797/http + get_collections_timeout: 5000ms + remove_collections_timeout: 5000ms + max_concurrent_requests: 500000 + prometheus_metrics: + socket_addr: /ip4/127.0.0.1/tcp/40191/http + network_admin_server: + primary_network_admin_server_port: 34921 + worker_network_admin_server_base_port: 43785 + anemo: + send_certificate_rate_limit: ~ + get_payload_availability_rate_limit: ~ + get_certificates_rate_limit: ~ + report_batch_rate_limit: ~ + request_batch_rate_limit: ~ + enable-event-processing: false + grpc-load-shed: ~ + grpc-concurrency-limit: 20000000000 + p2p-config: + listen-address: "127.0.0.1:40075" + external-address: /ip4/127.0.0.1/udp/40075 + genesis: + genesis-file-location: /root/.sui/sui_config/genesis.blob + authority-store-pruning-config: + num-latest-epoch-dbs-to-retain: 3 + epoch-db-pruning-period-secs: 3600 + num-epochs-to-retain: 1 + max-checkpoints-in-batch: 200 + max-transactions-in-batch: 1000 + use-range-deletion: true + end-of-epoch-broadcast-channel-capacity: 128 + checkpoint-executor-config: + checkpoint-execution-max-concurrency: 200 + local-execution-timeout-sec: 10 + db-checkpoint-config: + perform-db-checkpoints-at-epoch-end: false + indirect-objects-threshold: 18446744073709551615 + - protocol-key-pair: + value: OXnx3yM1C/ppgnDMx/o1d49fJs7E05kq11mXNae/O+I= + worker-key-pair: + value: AFNNzJwFj3Xdc6SoCzXZ+8otMB5HXVBX/57G9JRkOJcJ + account-key-pair: + value: AIRtltXdA0Y0eLXY9eLpXFpZOcvP3HW9bzBnpVQMZs6Q + network-key-pair: + value: AOSqdS3WsvNdxqIZtKCK31wTPM5AqSkpDLkd56ZL/u+G + db-path: /root/.sui/sui_config/authorities_db/addeef94d898 + network-address: /ip4/127.0.0.1/tcp/34255/http + json-rpc-address: "127.0.0.1:40709" + metrics-address: "127.0.0.1:43007" + admin-interface-port: 45245 + consensus-config: + address: /ip4/127.0.0.1/tcp/41199/http + db-path: /root/.sui/sui_config/consensus_db/addeef94d898 + internal-worker-address: ~ + max-pending-transactions: ~ + narwhal-config: + header_num_of_batches_threshold: 32 + max_header_num_of_batches: 1000 + max_header_delay: 2000ms + min_header_delay: 500ms + gc_depth: 50 + sync_retry_delay: 5000ms + sync_retry_nodes: 3 + batch_size: 500000 + max_batch_delay: 100ms + block_synchronizer: + range_synchronize_timeout: 30000ms + certificates_synchronize_timeout: 30000ms + payload_synchronize_timeout: 30000ms + payload_availability_timeout: 30000ms + handler_certificate_deliver_timeout: 30000ms + consensus_api_grpc: + socket_addr: /ip4/127.0.0.1/tcp/36241/http + get_collections_timeout: 5000ms + remove_collections_timeout: 5000ms + max_concurrent_requests: 500000 + prometheus_metrics: + socket_addr: /ip4/127.0.0.1/tcp/36547/http + network_admin_server: + primary_network_admin_server_port: 38113 + worker_network_admin_server_base_port: 45711 + anemo: + send_certificate_rate_limit: ~ + get_payload_availability_rate_limit: ~ + get_certificates_rate_limit: ~ + report_batch_rate_limit: ~ + request_batch_rate_limit: ~ + enable-event-processing: false + grpc-load-shed: ~ + grpc-concurrency-limit: 20000000000 + p2p-config: + listen-address: "127.0.0.1:39953" + external-address: /ip4/127.0.0.1/udp/39953 + genesis: + genesis-file-location: /root/.sui/sui_config/genesis.blob + authority-store-pruning-config: + num-latest-epoch-dbs-to-retain: 3 + epoch-db-pruning-period-secs: 3600 + num-epochs-to-retain: 1 + max-checkpoints-in-batch: 200 + max-transactions-in-batch: 1000 + use-range-deletion: true + end-of-epoch-broadcast-channel-capacity: 128 + checkpoint-executor-config: + checkpoint-execution-max-concurrency: 200 + local-execution-timeout-sec: 10 + db-checkpoint-config: + perform-db-checkpoints-at-epoch-end: false + indirect-objects-threshold: 18446744073709551615 + - protocol-key-pair: + value: CyNkjqNVr3HrHTH7f/NLs7u5lUHJzuPAw0PqMTD2y2s= + worker-key-pair: + value: ACKCL1GI/NLZHFVW1cUR6gu9fdh5ll/XpJWCRirrVDgj + account-key-pair: + value: AI5hzubOtOnMiBuXhZ2Cvnop5AQOxMHxhUdyPiy7/C37 + network-key-pair: + value: AG5LdZdKNOxwTtshbJjyRcq3dJIHu9NQnBz4WwbQPl3K + db-path: /root/.sui/sui_config/authorities_db/b3fd5efb5c87 + network-address: /ip4/127.0.0.1/tcp/43597/http + json-rpc-address: "127.0.0.1:40123" + metrics-address: "127.0.0.1:40221" + admin-interface-port: 44653 + consensus-config: + address: /ip4/127.0.0.1/tcp/39661/http + db-path: /root/.sui/sui_config/consensus_db/b3fd5efb5c87 + internal-worker-address: ~ + max-pending-transactions: ~ + narwhal-config: + header_num_of_batches_threshold: 32 + max_header_num_of_batches: 1000 + max_header_delay: 2000ms + min_header_delay: 500ms + gc_depth: 50 + sync_retry_delay: 5000ms + sync_retry_nodes: 3 + batch_size: 500000 + max_batch_delay: 100ms + block_synchronizer: + range_synchronize_timeout: 30000ms + certificates_synchronize_timeout: 30000ms + payload_synchronize_timeout: 30000ms + payload_availability_timeout: 30000ms + handler_certificate_deliver_timeout: 30000ms + consensus_api_grpc: + socket_addr: /ip4/127.0.0.1/tcp/39889/http + get_collections_timeout: 5000ms + remove_collections_timeout: 5000ms + max_concurrent_requests: 500000 + prometheus_metrics: + socket_addr: /ip4/127.0.0.1/tcp/40255/http + network_admin_server: + primary_network_admin_server_port: 38277 + worker_network_admin_server_base_port: 41135 + anemo: + send_certificate_rate_limit: ~ + get_payload_availability_rate_limit: ~ + get_certificates_rate_limit: ~ + report_batch_rate_limit: ~ + request_batch_rate_limit: ~ + enable-event-processing: false + grpc-load-shed: ~ + grpc-concurrency-limit: 20000000000 + p2p-config: + listen-address: "127.0.0.1:35253" + external-address: /ip4/127.0.0.1/udp/35253 + genesis: + genesis-file-location: /root/.sui/sui_config/genesis.blob + authority-store-pruning-config: + num-latest-epoch-dbs-to-retain: 3 + epoch-db-pruning-period-secs: 3600 + num-epochs-to-retain: 1 + max-checkpoints-in-batch: 200 + max-transactions-in-batch: 1000 + use-range-deletion: true + end-of-epoch-broadcast-channel-capacity: 128 + checkpoint-executor-config: + checkpoint-execution-max-concurrency: 200 + local-execution-timeout-sec: 10 + db-checkpoint-config: + perform-db-checkpoints-at-epoch-end: false + indirect-objects-threshold: 18446744073709551615 +account_keys: + - HnbaoooSxcxMVEPZIbcd63/TZL9kE66CZwfdHUIMbqU= + - YDbTC0ZzBtw0AbifBqltDnAi5fCRhARYVShJUDFy0ds= + - 4uFzSvJVacPkuaofeQIeu4GlVX7rwRaMQa7Z4L2Cd0o= + - oKjUXKFDTNSmehkuYYkn7wu21Ou3npbzseji8EX5H7w= + - 6Y+r0HXoXdbeKM7/iz2Ejq/wt2JgJrAoBbfU8qmDhCk= +genesis: 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BwgPAQgDAgYICwkAAQgFAQgEB0JhbGFuY2UEQ29pbgJJRARJdGVtCkl0ZW1MaXN0ZWQFS2lvc2sNS2lvc2tPd25lckNhcAdMaXN0aW5nBExvY2sGT3B0aW9uC1B1cmNoYXNlQ2FwA1NVSQ5UcmFuc2ZlclBvbGljeQ9UcmFuc2ZlclJlcXVlc3QJVHhDb250ZXh0A1VJRANhZGQQYWxsb3dfZXh0ZW5zaW9ucwdiYWxhbmNlEmNsb3NlX2FuZF93aXRoZHJhdwRjb2luBmRlbGV0ZQxkZXN0cm95X3NvbWUNZHluYW1pY19maWVsZBRkeW5hbWljX29iamVjdF9maWVsZARlbWl0BWV2ZW50B2V4aXN0c18DZm9yDGZyb21fYmFsYW5jZQpoYXNfYWNjZXNzCGhhc19pdGVtAmlkDGludG9fYmFsYW5jZQxpc19leGNsdXNpdmUJaXNfbGlzdGVkFWlzX2xpc3RlZF9leGNsdXNpdmVseQlpc19sb2NrZWQHaXNfc29tZQppdGVtX2NvdW50B2l0ZW1faWQEam9pbgVraW9zawhraW9za19pZARsaXN0Fmxpc3Rfd2l0aF9wdXJjaGFzZV9jYXAEbG9jawltaW5fcHJpY2UDbmV3C25ld19yZXF1ZXN0Bm9iamVjdAZvcHRpb24Fb3duZXIFcGxhY2UOcGxhY2VfYW5kX2xpc3QFcHJpY2UHcHJvZml0cw5wcm9maXRzX2Ftb3VudAtwcm9maXRzX211dAhwdXJjaGFzZRFwdXJjaGFzZV9jYXBfaXRlbRJwdXJjaGFzZV9jYXBfa2lvc2sWcHVyY2hhc2VfY2FwX21pbl9wcmljZRFwdXJjaGFzZV93aXRoX2NhcAZyZW1vdmUQcmVtb3ZlX2lmX2V4aXN0cxNyZXR1cm5fcHVyY2hhc2VfY2FwBnNlbmRlchRzZXRfYWxsb3dfZXh0ZW5zaW9ucwlzZXRfb3duZXIDc3VpBHRha2UPdHJhbnNmZXJfcG9saWN5CnR4X2NvbnRleHQHdWlkX211dBB1aWRfbXV0X2FzX293bmVyDHVpZF90b19pbm5lcgV2YWx1ZQh3aXRoZHJhdwR6ZXJvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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AAA2BggAADUGCQAALgoHAAAvCgkAACkLBwAAKgsHAAAtCgwAABECCQAACQIJAAATAgkAACINAgAAIw0CAAAeDgIAAAsPEAAAEBECAAArEgIAAhcCEwEAAhgYGQACGRgZAAMSFwIAAxoVBwEIAxsbBwADHB8bAAMgFhcABCYcAgEMBSwaGwAGHRUEAQIiFBgUABQgARwOAgkABwgIAQgAAAEGCAABAQEGCAQBBggBAQgGAQMBAgEGCAIBBggDAQYKAgEHCAEBCAEDBwgBAgoCAQgCAgcIAQgDAgcIAQIBCAUBCQABBgkAAQcICAEIBwEGCAUBCAQBBggIAQUCCQAFAgEIBQIIBggGAQYIBgJJRAlQdWJsaXNoZXIGU3RyaW5nCVR4Q29udGV4dAhUeXBlTmFtZQNVSUQKVXBncmFkZUNhcA5VcGdyYWRlUmVjZWlwdA1VcGdyYWRlVGlja2V0D2FkZGl0aXZlX3BvbGljeQVhc2NpaRFhdXRob3JpemVfdXBncmFkZQ5idXJuX3B1Ymxpc2hlcgNjYXAFY2xhaW0OY2xhaW1fYW5kX2tlZXAOY29tbWl0X3VwZ3JhZGURY29tcGF0aWJsZV9wb2xpY3kGZGVsZXRlD2RlcF9vbmx5X3BvbGljeQZkaWdlc3QLZnJvbV9tb2R1bGUMZnJvbV9wYWNrYWdlA2dldAtnZXRfYWRkcmVzcwpnZXRfbW9kdWxlAmlkD2lkX2Zyb21fYWRkcmVzcw1pZF90b19hZGRyZXNzE2lzX29uZV90aW1lX3dpdG5lc3MObWFrZV9pbW11dGFibGULbW9kdWxlX25hbWUDbmV3Bm9iamVjdBZvbmx5X2FkZGl0aXZlX3VwZ3JhZGVzEW9ubHlfZGVwX3VwZ3JhZGVzB3BhY2thZ2UGcG9saWN5D3B1YmxpY190cmFuc2ZlchBwdWJsaXNoZWRfbW9kdWxlEXB1Ymxpc2hlZF9wYWNrYWdlC3JlY2VpcHRfY2FwD3JlY2VpcHRfcGFja2FnZQhyZXN0cmljdAZzZW5kZXINdGlja2V0X2RpZ2VzdA50aWNrZXRfcGFja2FnZQ10aWNrZXRfcG9saWN5CHRyYW5zZmVyCnR4X2NvbnRleHQJdHlwZV9uYW1lBXR5cGVzD3VwZ3JhZGVfcGFja2FnZQ51cGdyYWRlX3BvbGljeQd2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BwoIDBkKCAMDAwoCAwMKAgoCCgIKAgoCCgIKAgoCCgIICgMLBQEICAsFAQgIBQgLCAwKCAwKAgoCAQYICQEDAQgIAgcLBQEJAAMBCwUBCQABCAwBCAAQBQoCCgIKAgoCCgIKAgoCCgIKAgoCCgIKAgMDBwgJAgYKCAwGCAwBAQgDAwMDAwMDBwgJAQgLBQsFAQgIAwMNBwgJAQgKCAgHCggMCwUBCAgDAwgLCAoHCAkFCwUBCAgDBQsEAQUFAQgDAQYKCQABBQEGCwQBCQABCwQBCQABCQACBwoIDAUBBwgMBAcIDAsFAQgIBQcICQILBQEJAAcICQELBgEJAAILBgEICAUCAwMCBwgMAwdCYWxhbmNlBENvaW4WR2VuZXNpc0NoYWluUGFyYW1ldGVycxhHZW5lc2lzVmFsaWRhdG9yTWV0YWRhdGEGT3B0aW9uA1NVSQxTdGFrZVN1YnNpZHkQU3lzdGVtUGFyYW1ldGVycw9Ub2tlbkFsbG9jYXRpb24ZVG9rZW5EaXN0cmlidXRpb25TY2hlZHVsZQlUeENvbnRleHQDVUlECVZhbGlkYXRvcghhY3RpdmF0ZRNhY3RpdmF0ZV92YWxpZGF0b3JzD2FsbG9jYXRlX3Rva2VucwthbGxvY2F0aW9ucwthbW91bnRfbWlzdAdiYWxhbmNlGGNoYWluX3N0YXJ0X3RpbWVzdGFtcF9tcwRjb2luD2NvbW1pc3Npb25fcmF0ZQZjcmVhdGUYY3JlYXRlX3N5c3RlbV9wYXJhbWV0ZXJzC2Rlc2NyaXB0aW9uDGRlc3Ryb3lfc29tZQxkZXN0cm95X3plcm8FZXBvY2gRZXBvY2hfZHVyYXRpb25fbXMMZnJvbV9iYWxhbmNlCWdhc19wcmljZQdnZW5lc2lzEWdldF92YWxpZGF0b3JfbXV0CWltYWdlX3VybBZpc19kdXBsaWNhdGVfdmFsaWRhdG9yCGlzX2VtcHR5B2lzX3NvbWUTbWF4X3ZhbGlkYXRvcl9jb3VudBttaW5fdmFsaWRhdG9yX2pvaW5pbmdfc3Rha2UEbmFtZQ9uZXR3b3JrX2FkZHJlc3MSbmV0d29ya19wdWJsaWNfa2V5A25ldwZvYmplY3QGb3B0aW9uC3AycF9hZGRyZXNzD3ByaW1hcnlfYWRkcmVzcwtwcm9qZWN0X3VybBNwcm9vZl9vZl9wb3NzZXNzaW9uE3Byb3RvY29sX3B1YmxpY19rZXkQcHJvdG9jb2xfdmVyc2lvbhFyZWNpcGllbnRfYWRkcmVzcxxyZXF1ZXN0X2FkZF9zdGFrZV9hdF9nZW5lc2lzBXNwbGl0DXN0YWtlX3N1YnNpZHkbc3Rha2Vfc3Vic2lkeV9kZWNyZWFzZV9yYXRlF3N0YWtlX3N1YnNpZHlfZnVuZF9taXN0KXN0YWtlX3N1YnNpZHlfaW5pdGlhbF9kaXN0cmlidXRpb25fYW1vdW50G3N0YWtlX3N1YnNpZHlfcGVyaW9kX2xlbmd0aBlzdGFrZV9zdWJzaWR5X3N0YXJ0X2Vwb2NoFXN0YWtlZF93aXRoX3ZhbGlkYXRvcgNzdWkLc3VpX2FkZHJlc3MKc3VpX3N5c3RlbRZzdWlfc3lzdGVtX3N0YXRlX2lubmVyCHRyYW5zZmVyCnR4X2NvbnRleHQJdmFsaWRhdG9yIHZhbGlkYXRvcl9sb3dfc3Rha2VfZ3JhY2VfcGVyaW9kHXZhbGlkYXRvcl9sb3dfc3Rha2VfdGhyZXNob2xkDXZhbGlkYXRvcl9zZXQidmFsaWRhdG9yX3ZlcnlfbG93X3N0YWtlX3RocmVzaG9sZAZ2ZWN0b3IOd29ya2VyX2FkZHJlc3MRd29ya2VyX3B1YmxpY19rZXkEemVybwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgACDycKAhgKAiEKAi8KAj4FHgMVAzEKAjAKAikKAkoKAigKAi0KAi4KAkkKAgECDDIDEwMcAzsDOQM6AzcNJQMmA0UDRwNEAwICAjgDEAoIAwMCAzMFEQM8CwQBBQAAAAAEkwEKBS4RCwYAAAAAAAAAACEEBwULCwUBBgAAAAAAAAAAJwsEEwIMBgwWDQELFjgADBg4AQwXQAoAAAAAAAAAAAwcDgNBCwwIBgAAAAAAAAAADAsKCwoIIwRZBSEOAwoLQgsUEwAMHQwRDBAMDgweDA8MEwwUDAcMCgwZDBIMDAwJDA0LGQsUCw8LHgsTCw0LCQsMCxILDgsQCxELHQsKCwcKBREQDBsOHA4bERMgBE0F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Y3RpdmF0ZV9zdGFraW5nX3Bvb2waYWRqdXN0X3N0YWtlX2FuZF9nYXNfcHJpY2UGYW1vdW50BWFzY2lpA2JhZwdiYWxhbmNlA2JjcwZib3Jyb3cPY29tbWlzc2lvbl9yYXRlCmRlYWN0aXZhdGUXZGVhY3RpdmF0ZV9zdGFraW5nX3Bvb2wPZGVwb3NpdF9yZXdhcmRzFWRlcG9zaXRfc3Rha2VfcmV3YXJkcwtkZXNjcmlwdGlvbhplZmZlY3R1YXRlX3N0YWdlZF9tZXRhZGF0YQRlbWl0BWVwb2NoBWV2ZW50DGV4dHJhX2ZpZWxkcwdleHRyYWN0CmZyb21fYXNjaWkJZ2FzX3ByaWNlB2dlbmVzaXMCaWQJaW1hZ2VfdXJsDGlzX2R1cGxpY2F0ZQ1pc19lcXVhbF9zb21lF2lzX2VxdWFsX3NvbWVfYW5kX3ZhbHVlB2lzX25vbmUMaXNfcHJlYWN0aXZlB2lzX3NvbWUIbWV0YWRhdGEEbmFtZQtuZXRfYWRkcmVzcw9uZXR3b3JrX2FkZHJlc3MUbmV0d29ya19wdWJrZXlfYnl0ZXMDbmV3EW5ld19mcm9tX21ldGFkYXRhDG5ld19tZXRhZGF0YRVuZXdfdW5zYWZlX2Zyb21fYnl0ZXMzbmV3X3VudmVyaWZpZWRfdmFsaWRhdG9yX29wZXJhdGlvbl9jYXBfYW5kX3RyYW5zZmVyGm5leHRfZXBvY2hfY29tbWlzc2lvbl9yYXRlFG5leHRfZXBvY2hfZ2FzX3ByaWNlFm5leHRfZXBvY2hfbmV0X2FkZHJlc3MabmV4dF9lcG9jaF9uZXR3b3JrX2FkZHJlc3MfbmV4dF9lcG9jaF9uZXR3b3JrX3B1YmtleV9ieXRlcxZuZXh0X2Vwb2NoX3AycF9hZGRyZXNzGm5leHRfZXBvY2hfcHJpbWFyeV9hZGRyZXNzHm5leHRfZXBvY2hfcHJvb2Zfb2ZfcG9zc2Vzc2lvbiBuZXh0X2Vwb2NoX3Byb3RvY29sX3B1YmtleV9ieXRlcxBuZXh0X2Vwb2NoX3N0YWtlGW5leHRfZXBvY2hfd29ya2VyX2FkZHJlc3MebmV4dF9lcG9jaF93b3JrZXJfcHVia2V5X2J5dGVzBG5vbmUGb2JqZWN0EG9wZXJhdGlvbl9jYXBfaWQGb3B0aW9uC3AycF9hZGRyZXNzFHBlbmRpbmdfc3Rha2VfYW1vdW50HXBlbmRpbmdfc3Rha2Vfd2l0aGRyYXdfYW1vdW50B3Bvb2xfaWQhcG9vbF90b2tlbl9leGNoYW5nZV9yYXRlX2F0X2Vwb2NoD3ByaW1hcnlfYWRkcmVzcxBwcmluY2lwYWxfYW1vdW50FXByb2Nlc3NfcGVuZGluZ19zdGFrZSRwcm9jZXNzX3BlbmRpbmdfc3Rha2VzX2FuZF93aXRoZHJhd3MLcHJvamVjdF91cmwTcHJvb2Zfb2ZfcG9zc2Vzc2lvbhVwcm90b2NvbF9wdWJrZXlfYnl0ZXMRcmVxdWVzdF9hZGRfc3Rha2UccmVxdWVzdF9hZGRfc3Rha2VfYXRfZ2VuZXNpcxtyZXF1ZXN0X3NldF9jb21taXNzaW9uX3JhdGUVcmVxdWVzdF9zZXRfZ2FzX3ByaWNlFnJlcXVlc3Rfd2l0aGRyYXdfc3Rha2UNcmV3YXJkX2Ftb3VudAZzZW5kZXIdc2V0X2NhbmRpZGF0ZV9jb21taXNzaW9uX3JhdGUXc2V0X2NhbmRpZGF0ZV9nYXNfcHJpY2UQc2V0X3ZvdGluZ19wb3dlcgRzb21lFnN0YWtlX2FjdGl2YXRpb25fZXBvY2gMc3Rha2VfYW1vdW50EXN0YWtlZF9zdWlfYW1vdW50DnN0YWtlcl9hZGRyZXNzDHN0YWtpbmdfcG9vbA9zdGFraW5nX3Bvb2xfaWQGc3RyaW5nA3N1aQtzdWlfYWRkcmVzcwtzdWlfYmFsYW5jZRZzdWlfc3lzdGVtX3N0YXRlX2lubmVyCHRvX2J5dGVzC3RvdGFsX3N0YWtlEnRvdGFsX3N0YWtlX2Ftb3VudAp0eF9jb250ZXh0D3Vuc3Rha2luZ19lcG9jaCB1cGRhdGVfY2FuZGlkYXRlX25ldHdvcmtfYWRkcmVzcx91cGRhdGVfY2FuZGlkYXRlX25ldHdvcmtfcHVia2V5HHVwZGF0ZV9jYW5kaWRhdGVfcDJwX2FkZHJlc3MgdXBkYXRlX2NhbmRpZGF0ZV9wcmltYXJ5X2FkZHJlc3MgdXBkYXRlX2NhbmRpZGF0ZV9wcm90b2NvbF9wdWJrZXkfdXBkYXRlX2NhbmRpZGF0ZV93b3JrZXJfYWRkcmVzcx51cGRhdGVfY2FuZGlkYXRlX3dvcmtlcl9wdWJrZXkSdXBkYXRlX2Rlc2NyaXB0aW9uEHVwZGF0ZV9pbWFnZV91cmwLdXBkYXRlX25hbWUhdXBkYXRlX25leHRfZXBvY2hfbmV0d29ya19hZGRyZXNzIHVwZGF0ZV9uZXh0X2Vwb2NoX25ldHdvcmtfcHVia2V5HXVwZGF0ZV9uZXh0X2Vwb2NoX3AycF9hZGRyZXNzIXVwZGF0ZV9uZXh0X2Vwb2NoX3ByaW1hcnlfYWRkcmVzcyF1cGRhdGVfbmV4dF9lcG9jaF9wcm90b2NvbF9wdWJrZXkgdXBkYXRlX25leHRfZXBvY2hfd29ya2VyX2FkZHJlc3MfdXBkYXRlX25leHRfZXBvY2hfd29ya2VyX3B1YmtleRJ1cGRhdGVfcHJvamVjdF91cmwDdXJsEXZhbGlkYXRlX21ldGFkYXRhFXZhbGlkYXRlX21ldGFkYXRhX2Jjcwl2YWxpZGF0b3IRdmFsaWRhdG9yX2FkZHJlc3MNdmFsaWRhdG9yX2NhcA12YWxpZGF0b3Jfc2V0EXZhbGlkYXRvcl93cmFwcGVyBXZhbHVlHnZlcmlmaWVkX29wZXJhdGlvbl9jYXBfYWRkcmVzcwx2b3RpbmdfcG93ZXIOd29ya2VyX2FkZHJlc3MTd29ya2VyX3B1YmtleV9ieXRlcwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgMIAAAAAAAAAAADCAEAAAAAAAAAAwgCAAAAAAAAAAMIAwAAAAAAAAADCAQAAAAAAAAAAwgFAAAAAAAAAAMIBgAAAAAAAAADCAcAAAAAAAAAAwgIAAAAAAAAAAMICQAAAAAAAAADCAoAAAAAAAAAAwhkAAAAAAAAAAMIZQAAAAAAAAADCBAnAAAAAAAAAwgAAQAAAAAAAAACFmkFVQoCNAoCjwEKAlQKAjEIBh4IBikIDFMIDDIIBkoIBk8IBo4BCAZCCwUBCgJBCwUBCgI+CwUBCgJFCwUBCgI8CwUBCAY/CwUBCAZACwUBCAZECwUBCAYjCAcBAgowCACNAQNICAkmA2UIDxkDQwM7AzoDIwgHAgIFTQgJhwEFZAUhAxMDAwIHTQgJ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BgEFCwcICQMDCwIBCAULAgEIBQMDAwMDBwgGCAcICQMDCwIBCAULAgEIBQMDBwgGAgcICQMCBwgECQABBwkBB0JhbGFuY2UEQ29pbgZPcHRpb24DU1VJDFN0YWtlU3Vic2lkeQlTdGFrZWRTdWkOU3VpU3lzdGVtU3RhdGUTU3VpU3lzdGVtU3RhdGVJbm5lchBTeXN0ZW1QYXJhbWV0ZXJzCVR4Q29udGV4dANVSUQfVW52ZXJpZmllZFZhbGlkYXRvck9wZXJhdGlvbkNhcAlWYWxpZGF0b3IDYWRkDWFkdmFuY2VfZXBvY2gXYWR2YW5jZV9lcG9jaF9zYWZlX21vZGUHYmFsYW5jZQpib3Jyb3dfbXV0BGNvaW4GY3JlYXRlDWR5bmFtaWNfZmllbGQHZ2VuZXNpcxxnZW5lc2lzX3N5c3RlbV9zdGF0ZV92ZXJzaW9uAmlkGGxvYWRfaW5uZXJfbWF5YmVfdXBncmFkZRFsb2FkX3N5c3RlbV9zdGF0ZRVsb2FkX3N5c3RlbV9zdGF0ZV9tdXQGb2JqZWN0Bm9wdGlvbhByZXBvcnRfdmFsaWRhdG9yEXJlcXVlc3RfYWRkX3N0YWtlGnJlcXVlc3RfYWRkX3N0YWtlX211bF9jb2luFXJlcXVlc3RfYWRkX3ZhbGlkYXRvch9yZXF1ZXN0X2FkZF92YWxpZGF0b3JfY2FuZGlkYXRlGHJlcXVlc3RfcmVtb3ZlX3ZhbGlkYXRvciJyZXF1ZXN0X3JlbW92ZV92YWxpZGF0b3JfY2FuZGlkYXRlG3JlcXVlc3Rfc2V0X2NvbW1pc3Npb25fcmF0ZRVyZXF1ZXN0X3NldF9nYXNfcHJpY2UWcmVxdWVzdF93aXRoZHJhd19zdGFrZRRyb3RhdGVfb3BlcmF0aW9uX2NhcAZzZW5kZXInc2V0X2NhbmRpZGF0ZV92YWxpZGF0b3JfY29tbWlzc2lvbl9yYXRlIXNldF9jYW5kaWRhdGVfdmFsaWRhdG9yX2dhc19wcmljZQxzaGFyZV9vYmplY3QNc3Rha2Vfc3Vic2lkeQxzdGFraW5nX3Bvb2wDc3VpCnN1aV9zeXN0ZW0Wc3VpX3N5c3RlbV9zdGF0ZV9pbm5lchRzeXN0ZW1fc3RhdGVfdmVyc2lvbgh0cmFuc2Zlcgp0eF9jb250ZXh0FXVuZG9fcmVwb3J0X3ZhbGlkYXRvcip1cGRhdGVfY2FuZGlkYXRlX3ZhbGlkYXRvcl9uZXR3b3JrX2FkZHJlc3MpdXBkYXRlX2NhbmRpZGF0ZV92YWxpZGF0b3JfbmV0d29ya19wdWJrZXkmdXBkYXRlX2NhbmRpZGF0ZV92YWxpZGF0b3JfcDJwX2FkZHJlc3MqdXBkYXRlX2NhbmRpZGF0ZV92YWxpZGF0b3JfcHJpbWFyeV9hZGRyZXNzKnVwZGF0ZV9jYW5kaWRhdGVfdmFsaWRhdG9yX3Byb3RvY29sX3B1YmtleSl1cGRhdGVfY2FuZGlkYXRlX3ZhbGlkYXRvcl93b3JrZXJfYWRkcmVzcyh1cGRhdGVfY2FuZGlkYXRlX3ZhbGlkYXRvcl93b3JrZXJfcHVia2V5HHVwZGF0ZV92YWxpZGF0b3JfZGVzY3JpcHRpb24adXBkYXRlX3ZhbGlkYXRvcl9pbWFnZV91cmwVdXBkYXRlX3ZhbGlkYXRvcl9uYW1lK3VwZGF0ZV92YWxpZGF0b3JfbmV4dF9lcG9jaF9uZXR3b3JrX2FkZHJlc3MqdXBkYXRlX3ZhbGlkYXRvcl9uZXh0X2Vwb2NoX25ldHdvcmtfcHVia2V5J3VwZGF0ZV92YWxpZGF0b3JfbmV4dF9lcG9jaF9wMnBfYWRkcmVzcyt1cGRhdGVfdmFsaWRhdG9yX25leHRfZXBvY2hfcHJpbWFyeV9hZGRyZXNzK3VwZGF0ZV92YWxpZGF0b3JfbmV4dF9lcG9jaF9wcm90b2NvbF9wdWJrZXkqdXBkYXRlX3ZhbGlkYXRvcl9uZXh0X2Vwb2NoX3dvcmtlcl9hZGRyZXNzKXVwZGF0ZV92YWxpZGF0b3JfbmV4dF9lcG9jaF93b3JrZXJfcHVia2V5HHVwZGF0ZV92YWxpZGF0b3JfcHJvamVjdF91cmwJdmFsaWRhdG9yDXZhbGlkYXRvcl9jYXAHdmVyc2lvbgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgIXCARJAwADAAASFwsBCwILAwsE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YmFnB2JhbGFuY2UGYm9ycm93GGNoZWNrX2JhbGFuY2VfaW52YXJpYW50cwRjb2luCGNvbnRhaW5zF2RlYWN0aXZhdGVfc3Rha2luZ19wb29sEmRlYWN0aXZhdGlvbl9lcG9jaAZkZWxldGUPZGVwb3NpdF9yZXdhcmRzBWVwb2NoDmV4Y2hhbmdlX3JhdGVzDGV4dHJhX2ZpZWxkcwRmaWxsDGZyb21fYmFsYW5jZQ5nZXRfc3VpX2Ftb3VudBBnZXRfdG9rZW5fYW1vdW50EGdldF93aXRoX2RlZmF1bHQCaWQVaW5pdGlhbF9leGNoYW5nZV9yYXRlGWlzX2VxdWFsX3N0YWtpbmdfbWV0YWRhdGELaXNfaW5hY3RpdmUHaXNfbm9uZQxpc19wcmVhY3RpdmUVaXNfcHJlYWN0aXZlX2F0X2Vwb2NoB2lzX3NvbWUEam9pbg9qb2luX3N0YWtlZF9zdWkEbWF0aANtaW4DbmV3BG5vbmUGb2JqZWN0Bm9wdGlvbhtwZW5kaW5nX3Bvb2xfdG9rZW5fd2l0aGRyYXcNcGVuZGluZ19zdGFrZRRwZW5kaW5nX3N0YWtlX2Ftb3VudB1wZW5kaW5nX3N0YWtlX3dpdGhkcmF3X2Ftb3VudBpwZW5kaW5nX3RvdGFsX3N1aV93aXRoZHJhdwdwb29sX2lkEXBvb2xfdG9rZW5fYW1vdW50EnBvb2xfdG9rZW5fYmFsYW5jZSFwb29sX3Rva2VuX2V4Y2hhbmdlX3JhdGVfYXRfZXBvY2gJcHJpbmNpcGFsFXByb2Nlc3NfcGVuZGluZ19zdGFrZR5wcm9jZXNzX3BlbmRpbmdfc3Rha2Vfd2l0aGRyYXckcHJvY2Vzc19wZW5kaW5nX3N0YWtlc19hbmRfd2l0aGRyYXdzD3B1YmxpY190cmFuc2ZlchFyZXF1ZXN0X2FkZF9zdGFrZRZyZXF1ZXN0X3dpdGhkcmF3X3N0YWtlIHJlcXVlc3Rfd2l0aGRyYXdfc3Rha2VfcHJlYWN0aXZlDHJld2FyZHNfcG9vbAZzZW5kZXIEc29tZQVzcGxpdBBzcGxpdF9zdGFrZWRfc3VpFnN0YWtlX2FjdGl2YXRpb25fZXBvY2gRc3Rha2VkX3N1aV9hbW91bnQMc3Rha2luZ19wb29sA3N1aQpzdWlfYW1vdW50C3N1aV9iYWxhbmNlBXRhYmxlCHRyYW5zZmVyCnR4X2NvbnRleHQRdW53cmFwX3N0YWtlZF9zdWkJdmFsaWRhdG9yDXZhbGlkYXRvcl9zZXQFdmFsdWUXd2l0aGRyYXdfZnJvbV9wcmluY2lwYWwQd2l0aGRyYXdfcmV3YXJkcwR6ZXJvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwgAAAAAAAAAAAMIAQAAAAAAAAADCAIAAAAAAAAAAwgDAAAAAAAAAAMIBAAAAAAAAAADCAUAAAAAAAAAAwgGAAAAAAAAAAMIBwAAAAAAAAADCAgAAAAAAAAAAwgJAAAAAAAAAAMICgAAAAAAAAADCAsAAAAAAAAAAwgMAAAAAAAAAAMIDQAAAAAAAAADCA4AAAAAAAAAAwgPAAAAAAAAAAMIEAAAAAAAAAADCBEAAAAAAAAAAAILIQgIDQsDAQMWCwMBA0wDQgsFAQgJOAMaCwoCAwgBMgM1AzEDGwgEAQICSwM3AwICBCEICDYIB0cDOgsFAQgJAAMAABkSCgA4AAwBCgARLzgBOAEGAAAAAAAAAAA4AgYAAAAAAAAAAAsBBgAAAAAAAAAABgAAAAAAAAAABgAAAAAAAAAACwARJhIAAgEDAAAhLw4BOAMMBgoALhESIAQJBQ8LAAELBAEHCicKBgYAAAAAAAAAACQEFAUaCwABCwQBBxEnCwQRLwoALjgECwMLARICDAUKABAAFAsGFgsADwAVCwULAjgFAgIDAAAlPQoACwERAwwEDAMKAi4RNwwHDgQ4AwwFCgAKBQoDCgIuETYRCQwGCwUOBjgDFgwICgAQARQKCBYKAA8BFQoAEAIUCwMWCgAPAhUKAC4REgQwCwARBwUyCwABDQQLBjgGAQsECwI4BwsHOAgLCAIDAwAALB8OARADFAoALjgEIQQJBQ0LAAEHAScLAA4BEAQUDAIuCwIRFwwDCwERBAwEDgMOBDgDERwLBAIEAAAACQgLABMCDAEBAREtCwECBQMAAAMPCgAQBRQOATgDFgoADwUVCwAPBgsBOAYBAgYDAAAtHAsBLhE2BgEAAAAAAAAAFgwDCgARBwoAEQgKAA8HCgMKABAFFAoAEAgUEgE4CQsACwMMAi4LAhEeAgcAAAADHQoAEAUUCgAQARQXCgAPBRUKABAIFAoAEAIUFwoADwgVBgAAAAAAAAAACgAPARUGAAAAAAAAAAALAA8CFQIIAwAAFx8KABAFFAoAEAgUEgEMAQoAEAUUCgAQABQWCgAPBRUOAQoAEAUUERwKAA8IFQYAAAAAAAAAAAsADwAVAgkAAAAvIQoACwMMBC4LBBEXDAYOBgsCERsMCAoICgEmBBQLCAsBFwwFBRYGAAAAAAAAAAAMBQsFCgAQBjgDESwMBwsADwYLBzgKAgoDAAADHQoADwcKAREdOAkKAC4REQQKBQ4LAAEHDicKAC4REiAEFAUYCwABBxAnCwAPCQsBOAsCCwMAADI7DgEQAxQKAC44BCEECQUPCwABCwIBBwEnCgAuEREEFAUaCwABCwIBBw8nCgIuETcMBAsBEQQMAw4DOAMMBQoAEAUUCgUXCgAPBRUKABAIFAoFFwsADwgVCwMLAjgHCwQ4CAsFAgwDAAADEAoALhESIAQGBQoLAAEHCycLATgMCwAPChUCDQEAAAMECwAQBRQCDgEAAAMECwAQAxQCDwEAAAMECwAQCzgDAhABAAADBAsAEAQUAhEBAAADBAsAEAk4DQISAQAAAwQLABAKOA4CEwEAAAMOCwIRLwoAEAMUCgAQBBQLAA8LCwE4ChICAhQBBAADCQsACwEKAhETCwIuETc4BQIVAQQANRgKAA4BDAIuCwIRFgQIBQwLAAEHDCcLARMCDAMBAREtCwAPCwsDOAYBAhYBAAASGQoAEAMUCgEQAxQhBBELABAEFAsBEAQUIQwCBRcLAAELAQEJDAILAgIXAQAALS0KAAoBERoECAsAAREdAgoAEAoKATgPCwERLAwDCgAQCTgQFAwCCgMKAiYEKQUZCgAQBwoDOBEEJAsAEAcLAzgSFAILAwYBAAAAAAAAABcMAwUUCwABER0CGAEAAAMECwAQABQCGQEAAAMECwAQARQCGgAAABIRCgAREQQICwABCAwCBQ8LABAJOBAUCwEkDAILAgIbAAAAEiMKABAMFAYAAAAAAAAAACEECQgMAgUPCgAQDRQGAAAAAAAAAAAhDAILAgQVCwABCwECCgAQDBQ1CwE1GAsAEA0UNRo0AhwAAAASIwoAEAwUBgAAAAAAAAAAIQQJCAwCBQ8KABANFAYAAAAAAAAAACEMAgsCBBULAAELAQIKABANFDULATUYCwAQDBQ1GjQCHQAAAAMEBgAAAAAAAAAABgAAAAAAAAAAEgECHgAAADkWCgALAREXDAMOAwoAEAUUERwMBAsAEAgUDAILBAsCIQQTBRUHCScCAAcACAAJAgECAgADAAQABgAFAAEAAgIDAQABAQBRAFIADHN0b3JhZ2VfZnVuZLUEoRzrCwYAAAALAQAGAgYOAxQsBEAIBUhJB5EBsgEIwwJACoMDDwySA2sN/QMED4EEAgAJAQQBCgACBAABAAQBAAECAQIAAAYAAQAAAwIAAAANAwQAAAwDBAABBQgEAQABCAkHAQABDgoEAQABDwUHAQAHBgQGBQYGBgELAQEIAgEIAAYHCAALAQEIAgsBAQgCCwEBCAIDAwEGCAABAwABCAIBCwEBCQACBwsBAQkACwEBCQACBwsBAQkAAwEGCwEBCQAHQmFsYW5jZQNTVUkLU3RvcmFnZUZ1bmQNYWR2YW5jZV9lcG9jaAdiYWxhbmNlBGpvaW4DbmV3Fm5vbl9yZWZ1bmRhYmxlX2JhbGFuY2UFc3BsaXQMc3RvcmFnZV9mdW5kA3N1aRZzdWlfc3lzdGVtX3N0YXRlX2lubmVyDXRvdGFsX2JhbGFuY2UcdG90YWxfb2JqZWN0X3N0b3JhZ2VfcmViYXRlcwV2YWx1ZQR6ZXJvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgACAg0LAQEIAgcLAQEIAgADAAAFBDgACwASAAIBAwAAAB4KAA8ACwI4AQEKAA8ACwM4AQEKAA8BCwE4AQEKAA8BCwU4AgwGCgAPAAsGOAEBCwAPAQsEOAICAgEAAAUECwAQATgDAgMBAAAFCAoAEAE4AwsAEAA4AxYCAAEAAAALAAx2b3RpbmdfcG93ZXLoDKEc6wsGAAAADAEACAIICAMQVwRnBAVrhQEH8AGYAgiIBGAG6ARGCq4FBwy1BfcGDawMAg+uDAIAFAETAggAEAABAgADAAQAAAwAAQAABQIDAAANBAUAAAYGAQAAAgcBAAAPCAEAAAMEAQAADgEFAAALAQUAAQYRAQEAAQcTFAEAAgQLBQACCQsFAAIKCwUAAwwVAQADDQ4FAAMUDgUACQ0KDQEHCggBAAIGCggBAwIKCAADAQYKCAEBAwIHCggACAADBwoIAAMDAgcKCAEKCAAEAwoIAAMDAQgBAgMDBwMIAAMKCAADAwMBCAABBggBAwMDAwQDAQMDAwcKCQAJAAMHAQMDAwMDBwgAAQYKCQABAQIHCAEDDAMDAwMDAwMDAwYIAQYIAQMJVmFsaWRhdG9yD1ZvdGluZ1Bvd2VySW5mbxNhZGp1c3Rfdm90aW5nX3Bvd2VyEGNoZWNrX2ludmFyaWFudHMTZGl2aWRlX2FuZF9yb3VuZF91cBZpbml0X3ZvdGluZ19wb3dlcl9pbmZvBmluc2VydAhpc19lbXB0eQRtYXRoA21heANtaW4QcXVvcnVtX3RocmVzaG9sZBBzZXRfdm90aW5nX3Bvd2VyC3RvdGFsX3N0YWtlEnRvdGFsX3ZvdGluZ19wb3dlchN1cGRhdGVfdm90aW5nX3Bvd2VyCXZhbGlkYXRvcg92YWxpZGF0b3JfaW5kZXgNdmFsaWRhdG9yX3NldAZ2ZWN0b3IMdm90aW5nX3Bvd2VyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwgQJwAAAAAAAAMICxoAAAAAAAADCOgDAAAAAAAAAwgBAAAAAAAAAAMIAgAAAAAAAAADCAMAAAAAAAAAAwgEAAAAAAAAAAACAhEDFAMAAwAACR0HAAcCBwAKAC5BChELEQwRDQwECgAKBAwBLgsBEQEMAwwCDQILBAsDEQQKAAsCEQULAC4RBgIBAAAADDcKABECDAcGAAAAAAAAAAAMAgoAQQoMBAYAAAAAAAAAAAwGQA0AAAAAAAAAAAwFCgIKBCMEMAURCgAKAkIKEQ81BwA1GAoHNRo0CgERDQwICgIKCBIADAMNBQsDEQMLBgsIFgwGCwIGAQAAAAAAAAAWDAIFDAsAAQsFBwALBhcCAgAAAA8cBgAAAAAAAAAADAEKAEEKDAIGAAAAAAAAAAAMAwoBCgIjBBgFDAsDCgAKAUIKEQ8WDAMLAQYBAAAAAAAAABYMAQUHCwABCwMCAwAAABAnBgAAAAAAAAAADAQKAC5BDQwFCgQKBSMEGQULCgAKBAwCLgsCQg0QABQOARAAFCQMAwUbCQwDCwMEIgsEBgEAAAAAAAAAFgwEBQYLAAsBCwQ4AAIEAAAAElQGAAAAAAAAAAAMBQoALkENDAYKBQoGIwQQBQsKAgYAAAAAAAAAACQMAwUSCQwDCwMESgoACgVDDQwJCgIKBgoFFxELDAcKAQoJEAAUCwcWEQ0MCAoCCwgKCRAAFBcRDQwECgkQABQKBBYKCQ8AFQsJEAAUCgElBD0FQQsAAQcFJwsCCwQXDAILBQYBAAAAAAAAABYMBQUGCwABCwIGAAAAAAAAAAAhBFEFUwcDJwIFAAAACxUOATgBIAQQBQUNAUUNEwAMAwwCCgALAkMKCwMRDgUACwABCwFGDQAAAAAAAAAAAgYAAAAWdwYAAAAAAAAAAAwDCgBBCgwEBgAAAAAAAAAADAkKAwoEIwQjBQwKAAoDQgoREAwMCgwGAAAAAAAAAAAkBBYFGgsAAQcGJwsJCwwWDAkLAwYBAAAAAAAAABYMAwUHCwkHACEEKAUsCwABBwMnBgAAAAAAAAAADAEKAQoEIwR0BTMKAQYBAAAAAAAAABYMAgoCCgQjBG8FPAoACgFCCgwKCgAKAkIKDAsKChEPDAcKCxEPDAgLChEQDAULCxEQDAYKBwoIJARdCgUKBiYEWQVdCwABBwQnCwcLCCMEagsFCwYlBGYFagsAAQcEJwsCBgEAAAAAAAAAFgwCBTcLAQYBAAAAAAAAABYMAQUuCwABAgcBAAABAgcAAggBAAABAgcBAgABABIADXN0YWtlX3N1YnNpZHmrBqEc6wsGAAAADAEADAIMFgMiJQRHBAVLSgeVAbACCMUDQAaFBBIKlwQUDKsEuwEN5gUKD/AFBAASAQYBBwEOARUBFwADBAABAAwAAgEEAQABBAICAAUEAgAACAABAAAFAgMAAAoEBQABEAcIAAIRDQ4BAAIYCwUBAAMPDAUABQoECgULAgEIAwMDDQcIBAEIAAEHCAABCwIBCAMBBggAAQMAAQcIBAEIAQMECwIBCAMDAQgDAQYLAgEJAAIDAwIHCwIBCQADAQsCAQkAA0JhZwdCYWxhbmNlA1NVSQxTdGFrZVN1YnNpZHkJVHhDb250ZXh0DWFkdmFuY2VfZXBvY2gDYmFnB2JhbGFuY2UGY3JlYXRlG2N1cnJlbnRfZGlzdHJpYnV0aW9uX2Ftb3VudBxjdXJyZW50X2Vwb2NoX3N1YnNpZHlfYW1vdW50FGRpc3RyaWJ1dGlvbl9jb3VudGVyDGV4dHJhX2ZpZWxkcwdnZW5lc2lzBG1hdGgDbWluA25ldwVzcGxpdA1zdGFrZV9zdWJzaWR5G3N0YWtlX3N1YnNpZHlfZGVjcmVhc2VfcmF0ZRtzdGFrZV9zdWJzaWR5X3BlcmlvZF9sZW5ndGgDc3VpFnN1aV9zeXN0ZW1fc3RhdGVfaW5uZXIKdHhfY29udGV4dAV2YWx1ZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIEEBAnAAAAAAAAAAAAAAAAAAAAAgYHCwIBCAMLAwkDFAMTDQwIAQADAAAGEwoDBwBLJQQGBQoLBAEGAAAAAAAAAAAnCwAGAAAAAAAAAAALAQsCCwMLBBEDEgACAQMAAAk5CgAQABQKABABOAARBgwDCgAPAQsDOAEMAgoAEAIUBgEAAAAAAAAAFgoADwIVCgAQAhQKABADFBkGAAAAAAAAAAAhBDUKABAAFDUKABAEFDUYBwAaDAEKABAAFAsBNBcLAA8AFQU3CwABCwICAgEAAAYICgAQABQLABABOAARBgIAAgAAAAEAAwAEAA0AFgANdmFsaWRhdG9yX2NhcJkFoRzrCwYAAAALAQAIAggUAxwlBEEEBUUuB3PbAgjOA0AKjgQNDJsEQw3eBAQP4gQGABEBCgENAQ4AAwwAAAQCAAEABwABAgQAAwECAAAPAAEAABMCAQAACQMEAAAIAAUAAQYLBAEIAQcICQACCwwGAQwECgYKAQYIAAEGBQEGCAECBQcIBAEIAgEIAQACCAAIAgEHCAQBCAMBCAABBgkAAgkABQJJRAlUeENvbnRleHQDVUlEH1VudmVyaWZpZWRWYWxpZGF0b3JPcGVyYXRpb25DYXAVVmFsaWRhdG9yT3BlcmF0aW9uQ2FwHGF1dGhvcml6ZXJfdmFsaWRhdG9yX2FkZHJlc3MCaWQDbmV3E25ld19mcm9tX3VudmVyaWZpZWQzbmV3X3VudmVyaWZpZWRfdmFsaWRhdG9yX29wZXJhdGlvbl9jYXBfYW5kX3RyYW5zZmVyBm9iamVjdA9wdWJsaWNfdHJhbnNmZXIWc3VpX3N5c3RlbV9zdGF0ZV9pbm5lcgh0cmFuc2Zlcgp0eF9jb250ZXh0IHVudmVyaWZpZWRfb3BlcmF0aW9uX2NhcF9hZGRyZXNzCXZhbGlkYXRvcg12YWxpZGF0b3JfY2FwDXZhbGlkYXRvcl9zZXQedmVyaWZpZWRfb3BlcmF0aW9uX2NhcF9hZGRyZXNzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgACAgYIAwUFAQIBBQUAAwAABgMLABAAAgEDAAAGAwsAEAECAgMAAAcNCwERBQoAEgAMAg4COAAMAwsCCwA4AQsDAgMDAAAGBQsAEAAUEgECAAEBAAAMABAAEgANdmFsaWRhdG9yX3NldPFPoRzrCwYAAAAMAQAzAjNsA58BhAYEoweFAQWoCNQJB/wRrRcIqSlgBokqkQEKmitsDIYsmCMNnk8QD65PBQCeAQFmAaYBAh0CHgI4AmQCcAKMAQKQAQKRAQKXAQKkAQKlAQCIAQCaAQCdAQCiAQCpAQASBAAADgMAAA8DAAAQAwABBAcBAAADAAwABAEEAQABBgMHAAcCBgECAAcGBgECAAgHAgAJCQwCBwEEAQoKBAEEAQsLAgAMFAcCAQAAAA0VBwEDAA4FBwAOCAgADw0EABAMDAAQEQIAERMEAABfAAEAAHsCAwAAfQQDAAB6BQMAABsGAwAAfAQDAAB5BwMAAH8IAwAAfgkDAAAaCgMAAJkBCwMAADMMAwAALg0OAACUAQ0OAAChAQ8OAACfAQ8OAACgAQ8QAACKAQ0RAABiDQ4AAFAPEgAAUwYSAABSExIAACoTDgAAVAYSAAApFA4AAEMVFgAAOxcYAAA8GRgAAEYaGwAARxwWAABAHRYAAEoeFgAASB8WAABJHxYAAEsXIAAAQSEgAABCDyAAAEUPIAAAqAEiIwAAcSQDAAB0JQMAACImAwAAcycDAACEASgDAAByKQMAACEqDgAAGSsDAAAlLC0AACYuLwAAJzAxAAAkMjEAADIzAwAANTQDAACPARoOAAAXDSoAAFkPEgAAVjUSAAEwb1EBAAE6UFEBAAFYTxIBAAFjA28BAAGDAVFvAQACKFISAQACVX0SAQACeGFRAQADXzhCAAQxlAEDAQAEW5UBDgEABIUBkwGUAQEABKMBWw4BAAU0UQMBAwZMbhABCAdfZ2gBAgdgZWYBAgdvaWUBAgkYPAMCBwQJH0RVAgcECSBJVwIHBAkoRBICBwQJXzg5AgcECXhJSgIHBAofbW4BBAogcnMBBAo2OD0BBApVahIBBApdag4BBApuhwFRAQQKdU0DAQQLN0gOAAuAAUg7AAwoXRICAQAMNgNBAgEADD9dVQIBAAxEXlcCAQAMTmADAgEADFWCARICAQAMXIIBgwECAQAMbY4BXwIBAAx4Xl8CAQAMggGCAQ4CAQANKIQBEgEDDU+PAYMBAQMNVYYBEgEDDXiFAQMBAw5pVBAADxZLAwAPGRYDAA8jIA4ADyxLAwAPLZYBAwAPMxYDAA89IA4AD1FsEgAPVyASAA9lIHsAD2yYAZkBAA9yigEDAA95UwMAD35LAwAPf1YDAA+HASAOAA+JASAQAA+NASA7AA+VASAOAA+pASAOABBheSMAEJgBeXUAEKcBdHUAEStFRgARL0Y6ABFeWBYAEnYDDgASgQErAwASlgEDDgBPN0s3UzpPPk8/W0BOP0s/UD9QN0s+Vzo7DjoOPg5ON0w3Tj5NPkVaWkBiQF1AXkBAOkkOSA5KDlU6UTpNPz0OPA45DlI6R3o/DkZ/WoEBYoEBYIEBXYEBZDtnO2Y7VDpWOkaIAVtiXmJfgQFhgQFlO2NiWmJcYkRaQ1pCWlyBAT47RpoBAgoIEgcIDQEIAAMHCAAIEgcIDQACBwgABwgNAwcIAAMHCA0CBggABggSBAcIAAULBgEICgcIDQMHCAAIEQcIDQMHCAADBggNCQcIAAcLBgEICgcLBgEICgcLDgIFCw8BBQMDAwMHCA0GBwgAAwMDBwsOAgULDwEFBwgNAQcIAAEGCAABAwIGCAAFAQgHAQYLCwIIBwUBAQIGCggSBggSAgYLDAEIEgYIEgIHCAAFAQcIEgIGCggSBQELBAEDAgYLDAEIEgUCBgoIEgYKBQEKAwIHCggSBQMHCAAFAQMHCAAGCBQBAgcIAAYIDQEGCBIDBwgABQIDBwgABggTAgEIFAMHCAAHCw4CBQsPAQUHCA0FBwgACBIHCw4CBQsPAQUBBwgNAgcLDgIFCw8BBQUCBwgAAwEHCgMCBwoIEgcIDQEGCggSAQcKCBIECgMDBgoDBgoDBAMLDgIDAwMLDgIDAwIGCAALDgIFCw8BBQIKBQMEBgoIEgMDAwIKAwoDCQYKCBIDAwoDCgMDCw4CAwMDCw4CAwMGBwoIEgYKAwYKAwcLBgEICgcLBgEICgcIDQYDBgoIEgYKAwYKAwYLDgIFCw8BBQYKBQIGCAAIBwYDAwsLAggHBQMGCBIIAAIIBwUBBwgNAQsLAgkACQEBCBIBBQMHCwsCCQAJAQkACQEBCwwBCQACCAcIFQIFCBUCBQMBCw4CCQAJAQEIBQQGCBIGCBIBBQIGCwsCCQAJAQkAAggSBwgNAQgVAwgHCBIFAQYIDQIHCwsCCQAJAQkAAQkBAgcIEgMFBggSBggSAQgSBQIHCwwBCQAJAAMFAwsEAQMBBgsEAQkAAQcLBAEJAAEJAAIGCgkABgkABAcIEgsGAQgKBQcIDQEGCBEBBgkBAwcIEggRBwgNAQcJAQEHCBUOCw4CBQsPAQUGCgUKAwoDCw4CAwMLDgIDAwMKBQMDAwMKAwoDAQgKAQYLBgEJAAgDAwcDAwgSCBIFBggSAgYLDgIJAAkBBgkAAgcLDgIJAAkBBgkAAgkACQEDBwsOAgkACQEJAAkBAgcKCQADAgMDCgoLCAEDAwMLCQEDAwMDAwYIEgYKCBIBCwgBAwIDCQABCwgBCQABCgsIAQkAAQsJAQkAAQcLCQEJAAEGCwwBCQADAwMDAgYIEgYIEgIGCwwBCQADAQYJAAELBAEJAAUFAwsEAQMDCgMEAwMLBAEDCwQBAwIHCwwBCQADAQcJAAEGCBQBBgUCAwsEAQMGAQEDAwsEAQMLBAEDBQUGCBIIBwUGCBIBBggTAQgTAQYIBwIDCBIBBgoJAAMDBQgHAQgDBwYFBgUDAwYFCgUHCw8BBQIFCw8BBQEGCw4CCQAJAQEKCQACBgsPAQkABgkAAgcLDwEJAAYJAAEGCw8BCQABBwsMAQkAAQgCBgMDAwMDAwIHCBIHCA0EAwMDBggSBwsOAgMDCw4CAwMEBAMDAwYKBQYKCBILDwEFCgUDBQEHCw4CCQAJAQELDwEJAAYDAwQKAwoDAxEDAwMKAwMKAwMEAwMDAwMEAwMDCAMDCwYBCAoDBwgSBQQLBgEICgIHCwYBCQADAQsGAQkAAgcLBgEJAAsGAQkAAgcIEgsGAQgKCAoFAwMDAwoFBggSBQIGCBIDAQgQAQgBA0JhZwdCYWxhbmNlBUVudHJ5AklEBk9wdGlvbhVQb29sVG9rZW5FeGNoYW5nZVJhdGUNUHJpb3JpdHlRdWV1ZQNTVUkJU3Rha2VkU3VpBVRhYmxlCFRhYmxlVmVjCVR4Q29udGV4dB9VbnZlcmlmaWVkVmFsaWRhdG9yT3BlcmF0aW9uQ2FwCVZhbGlkYXRvchdWYWxpZGF0b3JFcG9jaEluZm9FdmVudBJWYWxpZGF0b3JKb2luRXZlbnQTVmFsaWRhdG9yTGVhdmVFdmVudBVWYWxpZGF0b3JPcGVyYXRpb25DYXAMVmFsaWRhdG9yU2V0EFZhbGlkYXRvcldyYXBwZXIGVmVjTWFwBlZlY1NldAhhY3RpdmF0ZRFhY3RpdmVfdmFsaWRhdG9ycwNhZGQaYWRqdXN0X3N0YWtlX2FuZF9nYXNfcHJpY2UNYWR2YW5jZV9lcG9jaCZhc3NlcnRfbm9fcGVuZGluZ19vcl9hY3RpY2VfZHVwbGljYXRlcxJhdF9yaXNrX3ZhbGlkYXRvcnMDYmFnB2JhbGFuY2UGYm9ycm93CmJvcnJvd19tdXQWY2FsY3VsYXRlX3RvdGFsX3N0YWtlcyZjbGVhbl9yZXBvcnRfcmVjb3Jkc19sZWF2aW5nX3ZhbGlkYXRvcg9jb21taXNzaW9uX3JhdGUkY29tcHV0ZV9hZGp1c3RlZF9yZXdhcmRfZGlzdHJpYnV0aW9uGmNvbXB1dGVfcmV3YXJkX2FkanVzdG1lbnRzKmNvbXB1dGVfc2xhc2hlZF92YWxpZGF0b3JzX2FuZF90b3RhbF9zdGFrZSZjb21wdXRlX3VuYWRqdXN0ZWRfcmV3YXJkX2Rpc3RyaWJ1dGlvbghjb250YWlucxljb3VudF9kdXBsaWNhdGVzX3RhYmxldmVjFGNvdW50X2R1cGxpY2F0ZXNfdmVjCWNyZWF0ZV92MQpkZWFjdGl2YXRlFWRlcG9zaXRfc3Rha2VfcmV3YXJkcxpkZXJpdmVfcmVmZXJlbmNlX2dhc19wcmljZQdkZXN0cm95DGRlc3Ryb3lfc29tZQxkZXN0cm95X3plcm8RZGlzdHJpYnV0ZV9yZXdhcmQaZWZmZWN0dWF0ZV9zdGFnZWRfbWV0YWRhdGEEZW1pdBtlbWl0X3ZhbGlkYXRvcl9lcG9jaF9ldmVudHMFZW1wdHkFZXBvY2gFZXZlbnQMZXh0cmFfZmllbGRzB2V4dHJhY3QOZmluZF92YWxpZGF0b3IdZmluZF92YWxpZGF0b3JfZnJvbV90YWJsZV92ZWMJZ2FzX3ByaWNlB2dlbmVzaXMDZ2V0MGdldF9hY3RpdmVfb3JfcGVuZGluZ19vcl9jYW5kaWRhdGVfdmFsaWRhdG9yX211dDBnZXRfYWN0aXZlX29yX3BlbmRpbmdfb3JfY2FuZGlkYXRlX3ZhbGlkYXRvcl9yZWYYZ2V0X2FjdGl2ZV92YWxpZGF0b3JfcmVmJWdldF9jYW5kaWRhdGVfb3JfYWN0aXZlX3ZhbGlkYXRvcl9tdXQHZ2V0X211dBlnZXRfcGVuZGluZ192YWxpZGF0b3JfcmVmFWdldF92YWxpZGF0b3JfaW5kaWNlcxFnZXRfdmFsaWRhdG9yX211dBpnZXRfdmFsaWRhdG9yX211dF93aXRoX2N0eC9nZXRfdmFsaWRhdG9yX211dF93aXRoX2N0eF9pbmNsdWRpbmdfY2FuZGlkYXRlcyNnZXRfdmFsaWRhdG9yX211dF93aXRoX3ZlcmlmaWVkX2NhcBFnZXRfdmFsaWRhdG9yX3JlZgJpZBNpbmFjdGl2ZV92YWxpZGF0b3JzBmluc2VydAlpbnRvX2tleXMiaXNfYWN0aXZlX3ZhbGlkYXRvcl9ieV9zdWlfYWRkcmVzcwxpc19kdXBsaWNhdGUWaXNfZHVwbGljYXRlX3ZhbGlkYXRvciJpc19kdXBsaWNhdGVfd2l0aF9hY3RpdmVfdmFsaWRhdG9yI2lzX2R1cGxpY2F0ZV93aXRoX3BlbmRpbmdfdmFsaWRhdG9yCGlzX2VtcHR5FWlzX2luYWN0aXZlX3ZhbGlkYXRvcgxpc19wcmVhY3RpdmUHaXNfc29tZRZpc192YWxpZGF0b3JfY2FuZGlkYXRlDGlzX3ZvbHVudGFyeQRqb2luBGtleXMGbGVuZ3RoHGxvYWRfdmFsaWRhdG9yX21heWJlX3VwZ3JhZGUDbmV3CW5ld19lbnRyeRNuZXdfZnJvbV91bnZlcmlmaWVkGm5leHRfZXBvY2hfdmFsaWRhdG9yX2NvdW50BG5vbmUGb2JqZWN0EG9wZXJhdGlvbl9jYXBfaWQGb3B0aW9uGXBlbmRpbmdfYWN0aXZlX3ZhbGlkYXRvcnMQcGVuZGluZ19yZW1vdmFscwdwb29sX2lkE3Bvb2xfc3Rha2luZ19yZXdhcmQYcG9vbF90b2tlbl9leGNoYW5nZV9yYXRlIXBvb2xfdG9rZW5fZXhjaGFuZ2VfcmF0ZV9hdF9lcG9jaANwb3AIcG9wX2JhY2sHcG9wX21heA5wcmlvcml0eV9xdWV1ZRhwcm9jZXNzX3BlbmRpbmdfcmVtb3ZhbHMkcHJvY2Vzc19wZW5kaW5nX3N0YWtlc19hbmRfd2l0aGRyYXdzGnByb2Nlc3NfcGVuZGluZ192YWxpZGF0b3JzG3Byb2Nlc3NfdmFsaWRhdG9yX2RlcGFydHVyZQlwdXNoX2JhY2sQcXVvcnVtX3RocmVzaG9sZBpyZWZlcmVuY2VfZ2FzX3N1cnZleV9xdW90ZQZyZW1vdmURcmVxdWVzdF9hZGRfc3Rha2UVcmVxdWVzdF9hZGRfdmFsaWRhdG9yH3JlcXVlc3RfYWRkX3ZhbGlkYXRvcl9jYW5kaWRhdGUYcmVxdWVzdF9yZW1vdmVfdmFsaWRhdG9yInJlcXVlc3RfcmVtb3ZlX3ZhbGlkYXRvcl9jYW5kaWRhdGUbcmVxdWVzdF9zZXRfY29tbWlzc2lvbl9yYXRlFnJlcXVlc3Rfd2l0aGRyYXdfc3Rha2UGc2VuZGVyEHNldF92b3RpbmdfcG93ZXIEc2l6ZQRzb21lEXNvcnRfcmVtb3ZhbF9saXN0BXNwbGl0BXN0YWtlDHN0YWtlX2Ftb3VudAxzdGFraW5nX3Bvb2wPc3Rha2luZ19wb29sX2lkFXN0YWtpbmdfcG9vbF9tYXBwaW5ncxtzdG9yYWdlX2Z1bmRfc3Rha2luZ19yZXdhcmQDc3VpC3N1aV9hZGRyZXNzFnN1aV9zeXN0ZW1fc3RhdGVfaW5uZXIdc3VtX3ZvdGluZ19wb3dlcl9ieV9hZGRyZXNzZXMFdGFibGUJdGFibGVfdmVjGnRhbGx5aW5nX3J1bGVfZ2xvYmFsX3Njb3JlF3RhbGx5aW5nX3J1bGVfcmVwb3J0ZXJzC3RvdGFsX3N0YWtlEnRvdGFsX3N0YWtlX2Ftb3VudBJ0b3RhbF92b3RpbmdfcG93ZXIKdHhfY29udGV4dCB1bnZlcmlmaWVkX29wZXJhdGlvbl9jYXBfYWRkcmVzcyd1cGRhdGVfYW5kX3Byb2Nlc3NfbG93X3N0YWtlX2Rl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BgUGBQUHCwwBBQEGCBIBBgUCBgsLAgkACQEGCQABCQABCwwBCQADBwsLAgkACQEJAAkBAgcLCwIJAAkBBgkAAQcJAQIGCwwBCQAGCQACBwsMAQkACQACBwsMAQkABgkAAQYLDAEJAAIJAAkBAgcIEAcICgIHCBAKAgIHCBAGCBACBggTBggQAwcIEAoCCgIqAQMDAwMDAQELBQEICAMDAwMDAwMDAwQDAwMLBQEICAMDCwUBCAgLBQEICAsFAQgICwUBCAgDAwMLBQEICAQLBQEICAQDAwMDBAMBBwsFAQkAAgcLBQEJAAsFAQkAAQYIDwEGCwUBCQABBwgNAgcLBQEJAAMJBwgTBwsFAQgIBwsFAQgIBwsLAgULDAEFAwMDAwcICgYHCA8LBQEICAsFAQgICwUBCAgDAwEIAgEGCQEFCwUBCAgDCwUBCAgLBgEICAsFAQgIAQsGAQgIAgcLBgEJAAoLBgEJAAEGCwMBCQABCwMBCQACCwUBCQAHCAoCCQAFA0JhZwdCYWxhbmNlBENvaW4CSUQGT3B0aW9uA1NVSQxTdGFrZVN1YnNpZHkJU3Rha2VkU3VpC1N0b3JhZ2VGdW5kE1N1aVN5c3RlbVN0YXRlSW5uZXIUU3lzdGVtRXBvY2hJbmZvRXZlbnQQU3lzdGVtUGFyYW1ldGVycwVUYWJsZQlUeENvbnRleHQfVW52ZXJpZmllZFZhbGlkYXRvck9wZXJhdGlvbkNhcAlWYWxpZGF0b3IVVmFsaWRhdG9yT3BlcmF0aW9uQ2FwDFZhbGlkYXRvclNldAZWZWNNYXAGVmVjU2V0DWFkdmFuY2VfZXBvY2gXYWR2YW5jZV9lcG9jaF9zYWZlX21vZGUmYXNzZXJ0X25vX3BlbmRpbmdfb3JfYWN0aWNlX2R1cGxpY2F0ZXMDYmFnB2JhbGFuY2UEY29pbghjb250YWlucwZjcmVhdGUYY3JlYXRlX3N5c3RlbV9wYXJhbWV0ZXJzGmRlcml2ZV9yZWZlcmVuY2VfZ2FzX3ByaWNlDGRlc3Ryb3lfc29tZQxkZXN0cm95X3plcm8EZW1pdAVlbXB0eQVlcG9jaBFlcG9jaF9kdXJhdGlvbl9tcxhlcG9jaF9zdGFydF90aW1lc3RhbXBfbXMFZXZlbnQMZXh0cmFfZmllbGRzFGV4dHJhY3RfY29pbl9iYWxhbmNlDGZyb21fYmFsYW5jZQdnZW5lc2lzHGdlbmVzaXNfc3lzdGVtX3N0YXRlX3ZlcnNpb24DZ2V0B2dldF9tdXQQZ2V0X3JlcG9ydGVyc19vZh9nZXRfc3RvcmFnZV9mdW5kX29iamVjdF9yZWJhdGVzHmdldF9zdG9yYWdlX2Z1bmRfdG90YWxfYmFsYW5jZRpnZXRfdmFsaWRhdG9yX211dF93aXRoX2N0eC9nZXRfdmFsaWRhdG9yX211dF93aXRoX2N0eF9pbmNsdWRpbmdfY2FuZGlkYXRlcyNnZXRfdmFsaWRhdG9yX211dF93aXRoX3ZlcmlmaWVkX2NhcAZpbnNlcnQMaW50b19iYWxhbmNlImlzX2FjdGl2ZV92YWxpZGF0b3JfYnlfc3VpX2FkZHJlc3MIaXNfZW1wdHkHaXNfc29tZQRqb2luCGpvaW5fdmVjHGxlZnRvdmVyX3N0b3JhZ2VfZnVuZF9pbmZsb3cTbWF4X3ZhbGlkYXRvcl9jb3VudBttaW5fdmFsaWRhdG9yX2pvaW5pbmdfc3Rha2UDbmV3M25ld191bnZlcmlmaWVkX3ZhbGlkYXRvcl9vcGVyYXRpb25fY2FwX2FuZF90cmFuc2ZlchpuZXh0X2Vwb2NoX3ZhbGlkYXRvcl9jb3VudAZvYmplY3QGb3B0aW9uCnBhcmFtZXRlcnMDcGF5EHByb3RvY29sX3ZlcnNpb24PcHVibGljX3RyYW5zZmVyE3JlZmVyZW5jZV9nYXNfcHJpY2UGcmVtb3ZlEHJlcG9ydF92YWxpZGF0b3IVcmVwb3J0X3ZhbGlkYXRvcl9pbXBsEXJlcXVlc3RfYWRkX3N0YWtlGnJlcXVlc3RfYWRkX3N0YWtlX211bF9jb2luFXJlcXVlc3RfYWRkX3ZhbGlkYXRvch9yZXF1ZXN0X2FkZF92YWxpZGF0b3JfY2FuZGlkYXRlGHJlcXVlc3RfcmVtb3ZlX3ZhbGlkYXRvciJyZXF1ZXN0X3JlbW92ZV92YWxpZGF0b3JfY2FuZGlkYXRlG3JlcXVlc3Rfc2V0X2NvbW1pc3Npb25fcmF0ZRVyZXF1ZXN0X3NldF9nYXNfcHJpY2UWcmVxdWVzdF93aXRoZHJhd19zdGFrZRRyb3RhdGVfb3BlcmF0aW9uX2NhcAlzYWZlX21vZGUdc2FmZV9tb2RlX2NvbXB1dGF0aW9uX3Jld2FyZHMkc2FmZV9tb2RlX25vbl9yZWZ1bmRhYmxlX3N0b3JhZ2VfZmVlGXNhZmVfbW9kZV9zdG9yYWdlX3JlYmF0ZXMZc2FmZV9tb2RlX3N0b3JhZ2VfcmV3YXJkcwZzZW5kZXIdc2V0X2NhbmRpZGF0ZV9jb21taXNzaW9uX3JhdGUXc2V0X2NhbmRpZGF0ZV9nYXNfcHJpY2Unc2V0X2NhbmRpZGF0ZV92YWxpZGF0b3JfY29tbWlzc2lvbl9yYXRlIXNldF9jYW5kaWRhdGVfdmFsaWRhdG9yX2dhc19wcmljZQlzaW5nbGV0b24Fc3BsaXQWc3Rha2VfYWN0aXZhdGlvbl9lcG9jaA1zdGFrZV9zdWJzaWR5FHN0YWtlX3N1YnNpZHlfYW1vdW50GXN0YWtlX3N1YnNpZHlfc3RhcnRfZXBvY2gMc3Rha2luZ19wb29sFXN0YWtpbmdfcG9vbF9tYXBwaW5ncw5zdG9yYWdlX2NoYXJnZQxzdG9yYWdlX2Z1bmQUc3RvcmFnZV9mdW5kX2JhbGFuY2UZc3RvcmFnZV9mdW5kX3JlaW52ZXN0bWVudA5zdG9yYWdlX3JlYmF0ZQNzdWkKc3VpX3N5c3RlbRZzdWlfc3lzdGVtX3N0YXRlX2lubmVyFHN5c3RlbV9zdGF0ZV92ZXJzaW9uBXRhYmxlDXRvdGFsX2JhbGFuY2UOdG90YWxfZ2FzX2ZlZXMcdG90YWxfb2JqZWN0X3N0b3JhZ2VfcmViYXRlcwt0b3RhbF9zdGFrZR90b3RhbF9zdGFrZV9yZXdhcmRzX2Rpc3RyaWJ1dGVkCHRyYW5zZmVyCnR4X2NvbnRleHQVdW5kb19yZXBvcnRfdmFsaWRhdG9yGnVuZG9fcmVwb3J0X3ZhbGlkYXRvcl9pbXBsIHVwZGF0ZV9jYW5kaWRhdGVfbmV0d29ya19hZGRyZXNzH3VwZGF0ZV9jYW5kaWRhdGVfbmV0d29ya19wdWJrZXkcdXBkYXRlX2NhbmRpZGF0ZV9wMnBfYWRkcmVzcyB1cGRhdGVfY2FuZGlkYXRlX3ByaW1hcnlfYWRkcmVzcyB1cGRhdGVfY2FuZGlkYXRlX3Byb3RvY29sX3B1YmtleSp1cGRhdGVfY2FuZGlkYXRlX3ZhbGlkYXRvcl9uZXR3b3JrX2FkZHJlc3MpdXBkYXRlX2NhbmRpZGF0ZV92YWxpZGF0b3JfbmV0d29ya19wdWJrZXkmdXBkYXRlX2NhbmRpZGF0ZV92YWxpZGF0b3JfcDJwX2FkZHJlc3MqdXBkYXRlX2NhbmRpZGF0ZV92YWxpZGF0b3JfcHJpbWFyeV9hZGRyZXNzKnVwZGF0ZV9jYW5kaWRhdGVfdmFsaWRhdG9yX3Byb3RvY29sX3B1YmtleSl1cGRhdGVfY2FuZGlkYXRlX3ZhbGlkYXRvcl93b3JrZXJfYWRkcmVzcyh1cGRhdGVfY2FuZGlkYXRlX3ZhbGlkYXRvcl93b3JrZXJfcHVia2V5H3VwZGF0ZV9jYW5kaWRhdGVfd29ya2VyX2FkZHJlc3MedXBkYXRlX2NhbmRpZGF0ZV93b3JrZXJfcHVia2V5EnVwZGF0ZV9kZXNjcmlwdGlvbhB1cGRhdGVfaW1hZ2VfdXJsC3VwZGF0ZV9uYW1lIXVwZGF0ZV9uZXh0X2Vwb2NoX25ldHdvcmtfYWRkcmVzcyB1cGRhdGVfbmV4dF9lcG9jaF9uZXR3b3JrX3B1YmtleR11cGRhdGVfbmV4dF9lcG9jaF9wMnBfYWRkcmVzcyF1cGRhdGVfbmV4dF9lcG9jaF9wcmltYXJ5X2FkZHJlc3MhdXBkYXRlX25leHRfZXBvY2hfcHJvdG9jb2xfcHVia2V5IHVwZGF0ZV9uZXh0X2Vwb2NoX3dvcmtlcl9hZGRyZXNzH3VwZGF0ZV9uZXh0X2Vwb2NoX3dvcmtlcl9wdWJrZXkSdXBkYXRlX3Byb2plY3RfdXJsHHVwZGF0ZV92YWxpZGF0b3JfZGVzY3JpcHRpb24adXBkYXRlX3ZhbGlkYXRvcl9pbWFnZV91cmwVdXBkYXRlX3ZhbGlkYXRvcl9uYW1lK3VwZGF0ZV92YWxpZGF0b3JfbmV4dF9lcG9jaF9uZXR3b3JrX2FkZHJlc3MqdXBkYXRlX3ZhbGlkYXRvcl9uZXh0X2Vwb2NoX25ldHdvcmtfcHVia2V5J3VwZGF0ZV92YWxpZGF0b3JfbmV4dF9lcG9jaF9wMnBfYWRkcmVzcyt1cGRhdGVfdmFsaWRhdG9yX25leHRfZXBvY2hfcHJpbWFyeV9hZGRyZXNzK3VwZGF0ZV92YWxpZGF0b3JfbmV4dF9lcG9jaF9wcm90b2NvbF9wdWJrZXkqdXBkYXRlX3ZhbGlkYXRvcl9uZXh0X2Vwb2NoX3dvcmtlcl9hZGRyZXNzKXVwZGF0ZV92YWxpZGF0b3JfbmV4dF9lcG9jaF93b3JrZXJfcHVia2V5HHVwZGF0ZV92YWxpZGF0b3JfcHJvamVjdF91cmwJdmFsaWRhdG9yDXZhbGlkYXRvcl9jYXAgdmFsaWRhdG9yX2xvd19zdGFrZV9ncmFjZV9wZXJpb2QddmFsaWRhdG9yX2xvd19zdGFrZV90aHJlc2hvbGQYdmFsaWRhdG9yX3JlcG9ydF9yZWNvcmRzDXZhbGlkYXRvcl9zZXQWdmFsaWRhdG9yX3N0YWtlX2Ftb3VudBl2YWxpZGF0b3Jfc3Rha2luZ19wb29sX2lkH3ZhbGlkYXRvcl9zdGFraW5nX3Bvb2xfbWFwcGluZ3McdmFsaWRhdG9yX3RvdGFsX3N0YWtlX2Ftb3VudCJ2YWxpZGF0b3JfdmVyeV9sb3dfc3Rha2VfdGhyZXNob2xkCnZhbGlkYXRvcnMFdmFsdWUHdmVjX21hcAd2ZWNfc2V0HnZlcmlmaWVkX29wZXJhdGlvbl9jYXBfYWRkcmVzcwp2ZXJpZnlfY2FwDHdpdGhkcmF3X2FsbAR6ZXJvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgEBAgECAgEDAwgBAAAAAAAAAAMIAAAAAAAAAAADCAIAAAAAAAAAAwgDAAAAAAAAAAMIBAAAAAAAAAADCAUAAAAAAAAAAwgGAAAAAAAAAAMIBwAAAAAAAAAEEBAnAAAAAAAAAAAAAAAAAAAFIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIIIwNjAzsDPAOgAQOnAQOfAQMmCAQBAhAiA0QDbgOoAQgTZwgPQggARgOhAQsLAgULDAEFYQgNVAFYCwUBCAhVCwUBCAhXA1YDJAMmCAQCAgwiA0QDRgNzA2kDZgNqA2gDYgNxA3QDOgMAAwAAGhsLAAoGEXMMCA4IEW4MBwYAAAAAAAAAAAsCESkLCAsBEVELBAsHOAALBQk4ATgBBgAAAAAAAAAA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AggBCAABCAsBCAwBCwgBCQABBggPAQgAAQYJAAUICwgKCAsOCwgBCAwBBggLAgsIAQkABwgPAQsJAQkAAggDCQADBwgLCQAJAQIIBQECCAoICgIIBAMCBwgLCQABCwcBCQEBCQEBCwYBCQACCQADAQYLCQEJAAIHCwgBCQALCAEJAAMICgMICgELDgEJAAYICggLCAoICgMICwUICwgKCAoDAwMICwgKCAoDAwMDAQYLBwEJAAELBwEJAAEGCwgBCQADBwsIAQkAAwcIDwEIAwIGCAsJAAEIBQEIBAdCYWxhbmNlBENvaW4CSUQESXRlbQpJdGVtTGlzdGVkBUtpb3NrDUtpb3NrT3duZXJDYXAHTGlzdGluZwRMb2NrBk9wdGlvbgtQdXJjaGFzZUNhcANTVUkOVHJhbnNmZXJQb2xpY3kPVHJhbnNmZXJSZXF1ZXN0CVR4Q29udGV4dANVSUQDYWRkEGFsbG93X2V4dGVuc2lvbnMHYmFsYW5jZRJjbG9zZV9hbmRfd2l0aGRyYXcEY29pbgZkZWxldGUMZGVzdHJveV9zb21lDWR5bmFtaWNfZmllbGQUZHluYW1pY19vYmplY3RfZmllbGQEZW1pdAVldmVudAdleGlzdHNfA2Zvcgxmcm9tX2JhbGFuY2UKaGFzX2FjY2VzcwhoYXNfaXRlbQJpZAxpbnRvX2JhbGFuY2UMaXNfZXhjbHVzaXZlCWlzX2xpc3RlZBVpc19saXN0ZWRfZXhjbHVzaXZlbHkJaXNfbG9ja2VkB2lzX3NvbWUKaXRlbV9jb3VudAdpdGVtX2lkBGpvaW4Fa2lvc2sIa2lvc2tfaWQEbGlzdBZsaXN0X3dpdGhfcHVyY2hhc2VfY2FwBGxvY2sJbWluX3ByaWNlA25ldwtuZXdfcmVxdWVzdAZvYmplY3QGb3B0aW9uBW93bmVyBXBsYWNlDnBsYWNlX2FuZF9saXN0BXByaWNlB3Byb2ZpdHMOcHJvZml0c19hbW91bnQLcHJvZml0c19tdXQIcHVyY2hhc2URcHVyY2hhc2VfY2FwX2l0ZW0ScHVyY2hhc2VfY2FwX2tpb3NrFnB1cmNoYXNlX2NhcF9taW5fcHJpY2URcHVyY2hhc2Vfd2l0aF9jYXAGcmVtb3ZlEHJlbW92ZV9pZl9leGlzdHMTcmV0dXJuX3B1cmNo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SUQGT3B0aW9uCVB1Ymxpc2hlcgdSdWxlS2V5A1NVSQ5UcmFuc2ZlclBvbGljeRFUcmFuc2ZlclBvbGljeUNhcBVUcmFuc2ZlclBvbGljeUNyZWF0ZWQPVHJhbnNmZXJSZXF1ZXN0CVR4Q29udGV4dAhUeXBlTmFtZQNVSUQGVmVjU2V0A2FkZAthZGRfcmVjZWlwdAhhZGRfcnVsZQ5hZGRfdG9fYmFsYW5jZQdiYWxhbmNlBmJvcnJvdwRjb2luD2NvbmZpcm1fcmVxdWVzdAhjb250YWlucwZkZWxldGUUZGVzdHJveV9hbmRfd2l0aGRyYXcMZGVzdHJveV9zb21lC2R1bW15X2ZpZWxkDWR5bmFtaWNfZmllbGQEZW1pdAVlbXB0eQVldmVudAdleGlzdHNfBGZyb20MZnJvbV9iYWxhbmNlDGZyb21fcGFja2FnZQNnZXQIZ2V0X3J1bGUIaGFzX3J1bGUCaWQGaW5zZXJ0CWludG9fa2V5cwdpc19zb21lBGl0ZW0DbmV3C25ld19yZXF1ZXN0Bm9iamVjdAZvcHRpb24HcGFja2FnZQRwYWlkCXBvbGljeV9pZANwdXQIcmVjZWlwdHMGcmVtb3ZlC3JlbW92ZV9ydWxlBXJ1bGVzBHNpemUDc3VpBHRha2UPdHJhbnNmZXJfcG9saWN5CnR4X2NvbnRleHQJdHlwZV9uYW1lA3VpZBB1aWRfbXV0X2FzX293bmVyDHVpZF90b19pbm5lcgV2YWx1ZQd2ZWNfc2V0CHdpdGhkcmF3BHplcm8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAwgAAAAAAAAAAAMIAQAAAAAAAAADCAIAAAAAAAAAAwgDAAAAAAAAAAMIBAAAAAAAAAADCAUAAAAAAAAAAAIEKwgJMQMhCAk0Cw4BCAYBAgMnCAoTCwcBCAw3Cw4BCAYCAgInCAoyCAkDAgEnCAkEAgEbAQAYAxgBGAIYBC8AAQAACQYLAAsBCwI4ADkAAgEBAAAZIAsAOAEEBAUICwEBBgAAAAAAAAAAJwoBER8MBQ4FESAMBgoGOQE4AgsFDAI4AAwDOAMMBAsCCwQLAzkCCwERHwsGOQMCAgEAACAxCgAuOAQLATcAFCEECQUP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X2JhbGFuY2UJZ2FzX3ByaWNlB2dlbmVzaXMRZ2V0X3ZhbGlkYXRvcl9tdXQJaW1hZ2VfdXJsFmlzX2R1cGxpY2F0ZV92YWxpZGF0b3IIaXNfZW1wdHkHaXNfc29tZRNtYXhfdmFsaWRhdG9yX2NvdW50G21pbl92YWxpZGF0b3Jfam9pbmluZ19zdGFrZQRuYW1lD25ldHdvcmtfYWRkcmVzcxJuZXR3b3JrX3B1YmxpY19rZXkDbmV3Bm9iamVjdAZvcHRpb24LcDJwX2FkZHJlc3MPcHJpbWFyeV9hZGRyZXNzC3Byb2plY3RfdXJsE3Byb29mX29mX3Bvc3Nlc3Npb24TcHJvdG9jb2xfcHVibGljX2tleRBwcm90b2NvbF92ZXJzaW9uEXJlY2lwaWVudF9hZGRyZXNzHHJlcXVlc3RfYWRkX3N0YWtlX2F0X2dlbmVzaXMFc3BsaXQNc3Rha2Vfc3Vic2lkeRtzdGFrZV9zdWJzaWR5X2RlY3JlYXNlX3JhdGUXc3Rha2Vfc3Vic2lkeV9mdW5kX21pc3Qpc3Rha2Vfc3Vic2lkeV9pbml0aWFsX2Rpc3RyaWJ1dGlvbl9hbW91bnQbc3Rha2Vfc3Vic2lkeV9wZXJpb2RfbGVuZ3RoGXN0YWtlX3N1YnNpZHlfc3RhcnRfZXBvY2gVc3Rha2VkX3dpdGhfdmFsaWRhdG9yA3N1aQtzdWlfYWRkcmVzcwpzdWlfc3lzdGVtFnN1aV9zeXN0ZW1fc3RhdGVfaW5uZXIIdHJhbnNmZXIKdHhfY29udGV4dAl2YWxpZGF0b3IgdmFsaWRhdG9yX2xvd19zdGFrZV9ncmFjZV9wZXJpb2QddmFsaWRhdG9yX2xvd19zdGFrZV90aHJlc2hvbGQNdmFsaWRhdG9yX3NldCJ2YWxpZGF0b3JfdmVyeV9sb3dfc3Rha2VfdGhyZXNob2xkBnZlY3Rvcg53b3JrZXJfYWRkcmVzcxF3b3JrZXJfcHVibGljX2tleQR6ZXJvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAIPJwoCGAoCIQoCLwoCPgUeAxUDMQoCMAoCKQoCSgoC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AQEBAQEBAQEBAQEBAQEBAQEBAQEBBgsFAQkAAgEBAgUHCAsBBwsFAQkAAwgJCA8FA0JhZwdCYWxhbmNlAklEBk9wdGlvbhVQb29sVG9rZW5FeGNoYW5nZVJhdGUDU1VJCVN0YWtlZFN1aQtTdGFraW5nUG9vbBNTdGFraW5nUmVxdWVzdEV2ZW50BlN0cmluZwlUeENvbnRleHQVVW5zdGFraW5nUmVxdWVzdEV2ZW50A1VybAlWYWxpZGF0b3IRVmFsaWRhdG9yTWV0YWRhdGEVVmFsaWRhdG9yT3BlcmF0aW9uQ2FwCGFjdGl2YXRlFWFjdGl2YXRlX3N0YWtpbmdfcG9vbBphZGp1c3Rfc3Rha2VfYW5kX2dhc19wcmljZQZhbW91bnQFYXNjaWkDYmFnB2JhbGFuY2UDYmNzBmJvcnJvdw9jb21taXNzaW9uX3JhdGUKZGVhY3RpdmF0ZRdkZWFjdGl2YXRlX3N0YWtpbmdfcG9vbA9kZXBvc2l0X3Jld2FyZHMVZGVwb3NpdF9zdGFrZV9yZXdhcmRzC2Rlc2NyaXB0aW9uGmVmZmVjdHVhdGVfc3RhZ2VkX21ldGFkYXRhBGVtaXQFZXBvY2gFZXZlbnQMZXh0cmFfZmllbGRzB2V4dHJhY3QKZnJvbV9hc2NpaQlnYXNfcHJpY2UHZ2VuZXNpcwJpZAlpbWFnZV91cmwMaXNfZHVwbGljYXRlDWlzX2VxdWFsX3NvbWUXaXNfZXF1YWxfc29tZV9hbmRfdmFsdWUHaXNfbm9uZQxpc19wcmVhY3RpdmUHaXNfc29tZQhtZXRhZGF0YQRuYW1lC25ldF9hZGRyZXNzD25ldHdvcmtfYWRkcmVzcxRuZXR3b3JrX3B1YmtleV9ieXRlcwNuZXcRbmV3X2Zyb21fbWV0YWRhdGEMbmV3X21ldGFkYXRhFW5ld191bnNhZmVfZnJvbV9ieXRlczNuZXdfdW52ZXJpZmllZF92YWxpZGF0b3Jfb3BlcmF0aW9uX2NhcF9hbmRfdHJhbnNmZXIabmV4dF9lcG9jaF9jb21taXNzaW9uX3JhdGUUbmV4dF9lcG9jaF9nYXNfcHJpY2UWbmV4dF9lcG9jaF9uZXRfYWRkcmVzcxpuZXh0X2Vwb2NoX25ldHdvcmtfYWRkcmVzcx9uZXh0X2Vwb2NoX25ldHdvcmtfcHVia2V5X2J5dGVzFm5leHRfZXBvY2hfcDJwX2FkZHJlc3MabmV4dF9lcG9jaF9wcmltYXJ5X2FkZHJlc3MebmV4dF9lcG9jaF9wcm9vZl9vZl9wb3NzZXNzaW9uIG5leHRfZXBvY2hfcHJvdG9jb2xfcHVia2V5X2J5dGVzEG5leHRfZXBvY2hfc3Rha2UZbmV4dF9lcG9jaF93b3JrZXJfYWRkcmVzcx5uZXh0X2Vwb2NoX3dvcmtlcl9wdWJrZXlfYnl0ZXMEbm9uZQZvYmplY3QQb3BlcmF0aW9uX2NhcF9pZAZvcHRpb24LcDJwX2FkZHJlc3MUcGVuZGluZ19zdGFrZV9hbW91bnQdcGVuZGluZ19zdGFrZV93aXRoZHJhd19hbW91bnQHcG9vbF9pZCFwb29sX3Rva2VuX2V4Y2hhbmdlX3JhdGVfYXRfZXBvY2gPcHJpbWFyeV9hZGRyZXNzEHByaW5jaXBhbF9hbW91bnQVcHJvY2Vzc19wZW5kaW5nX3N0YWtlJHByb2Nlc3NfcGVuZGluZ19zdGFrZXNfYW5kX3dpdGhkcmF3cwtwcm9qZWN0X3VybBNwcm9vZl9vZl9wb3NzZXNzaW9uFXByb3RvY29sX3B1YmtleV9ieXRlcxFyZXF1ZXN0X2FkZF9zdGFrZRxyZXF1ZXN0X2FkZF9zdGFrZV9hdF9nZW5lc2lzG3JlcXVlc3Rfc2V0X2NvbW1pc3Npb25fcmF0ZRVyZXF1ZXN0X3NldF9nYXNfcHJpY2UWcmVxdWVzdF93aXRoZHJhd19zdGFrZQ1yZXdhcmRfYW1vdW50BnNlbmRlch1zZXRfY2FuZGlkYXRlX2NvbW1pc3Npb25fcmF0ZRdzZXRfY2FuZGlkYXRlX2dhc19wcmljZRBzZXRfdm90aW5nX3Bvd2VyBHNvbWUWc3Rha2VfYWN0aXZhdGlvbl9lcG9jaAxzdGFrZV9hbW91bnQRc3Rha2VkX3N1aV9hbW91bnQOc3Rha2VyX2FkZHJlc3MMc3Rha2luZ19wb29sD3N0YWtpbmdfcG9vbF9pZAZzdHJpbmcDc3VpC3N1aV9hZGRyZXNzC3N1aV9iYWxhbmNlFnN1aV9zeXN0ZW1fc3RhdGVfaW5uZXIIdG9fYnl0ZXMLdG90YWxfc3Rha2USdG90YWxfc3Rha2VfYW1vdW50CnR4X2NvbnRleHQPdW5zdGFraW5nX2Vwb2NoIHVwZGF0ZV9jYW5kaWRhdGVfbmV0d29ya19hZGRyZXNzH3VwZGF0ZV9jYW5kaWRhdGVfbmV0d29ya19wdWJrZXkcdXBkYXRlX2NhbmRpZGF0ZV9wMnBfYWRkcmVzcyB1cGRhdGVfY2FuZGlkYXRlX3ByaW1hcnlfYWRkcmVzcyB1cGRhdGVfY2FuZGlkYXRlX3Byb3RvY29sX3B1YmtleR91cGRhdGVfY2FuZGlkYXRlX3dvcmtlcl9hZGRyZXNzHnVwZGF0ZV9jYW5kaWRhdGVfd29ya2VyX3B1YmtleRJ1cGRhdGVfZGVzY3JpcHRpb24QdXBkYXRlX2ltYWdlX3VybAt1cGRhdGVfbmFtZSF1cGRhdGVfbmV4dF9lcG9jaF9uZXR3b3JrX2FkZHJlc3MgdXBkYXRlX25leHRfZXBvY2hfbmV0d29ya19wdWJrZXkddXBkYXRlX25leHRfZXBvY2hfcDJwX2FkZHJlc3MhdXBkYXRlX25leHRfZXBvY2hfcHJpbWFyeV9hZGRyZXNzIXVwZGF0ZV9uZXh0X2Vwb2NoX3Byb3RvY29sX3B1YmtleSB1cGRhdGVfbmV4dF9lcG9jaF93b3JrZXJfYWRkcmVzcx91cGRhdGVfbmV4dF9lcG9jaF93b3JrZXJfcHVia2V5EnVwZGF0ZV9wcm9qZWN0X3VybAN1cmwRdmFsaWRhdGVfbWV0YWRhdGEVdmFsaWRhdGVfbWV0YWRhdGFfYmNzCXZhbGlkYXRvchF2YWxpZGF0b3JfYWRkcmVzcw12YWxpZGF0b3JfY2FwDXZhbGlkYXRvcl9zZXQRdmFsaWRhdG9yX3dyYXBwZXIFdmFsdWUedmVyaWZpZWRfb3BlcmF0aW9uX2NhcF9hZGRyZXNzDHZvdGluZ19wb3dlcg53b3JrZXJfYWRkcmVzcxN3b3JrZXJfcHVia2V5X2J5dGVzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwgAAAAAAAAAAAMIAQAAAAAAAAADCAIAAAAAAAAAAwgDAAAAAAAAAAMIBAAAAAAAAAAD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CgIGCAYLCwIBCAULAgEIBQcIAAMDAwMDAwMHCAYBCwIBCAUICwIBCAULAgEIBQcIAAMDAwMHCAYBBwgAAQYICQEHCAkDCAAICQMHCggLCwIBCAUDAwgKCAcHCAYBCAkBAwIDCAkDBwgECQAJAQEIAAEJABAHCAkKAgoCCgIKAgoCCgIKAgoCCgIKAgoCCgIDAwcIBgIHCAkHCAYDBwgJBggMAwMHCAkDBwgGBAcICQsDAQgFBQcIBgUHCAkKCwMBCAULAQEDBQcIBgMHCAkICAcIBgMHCAkGCAwFAwcICQoCBggGBAcICQoCCgIGCAYBBggGAQULBwgJAwMLAgEIBQsCAQgFAwMDAwMHCAYIBwgJAwMLAgEIBQsCAQgFAwMHCAYCBwgJAwIHCAQJAAEHCQEHQmFsYW5jZQRDb2luBk9wdGlvbgNTVUkMU3Rha2VTdWJzaWR5CVN0YWtlZFN1aQ5TdWlTeXN0ZW1TdGF0ZRNTdWlTeXN0ZW1TdGF0ZUlubmVyEFN5c3RlbVBhcmFtZXRlcnMJVHhDb250ZXh0A1VJRB9VbnZlcmlmaWVkVmFsaWRhdG9yT3BlcmF0aW9uQ2FwCVZhbGlkYXRvcgNhZGQNYWR2YW5jZV9lcG9jaBdhZHZhbmNlX2Vwb2NoX3NhZmVfbW9kZQdiYWxhbmNlCmJvcnJvd19tdXQEY29pbgZjcmVhdGUNZHluYW1pY19maWVsZAdnZW5lc2lzHGdlbmVzaXNfc3lzdGVtX3N0YXRlX3ZlcnNpb24CaWQYbG9hZF9pbm5lcl9tYXliZV91cGdyYWRlEWxvYWRfc3lzdGVtX3N0YXRlFWxvYWRfc3lzdGVtX3N0YXRlX211dAZvYmplY3QGb3B0aW9uEHJlcG9ydF92YWxpZGF0b3IRcmVxdWVzdF9hZGRfc3Rha2UacmVxdWVzdF9hZGRfc3Rha2VfbXVsX2NvaW4VcmVxdWVzdF9hZGRfdmFsaWRhdG9yH3JlcXVlc3RfYWRkX3ZhbGlkYXRvcl9jYW5kaWRhdGUYcmVxdWVzdF9yZW1vdmVfdmFsaWRhdG9yInJlcXVlc3RfcmVtb3ZlX3ZhbGlkYXRvcl9jYW5kaWRhdGUbcmVxdWVzdF9zZXRfY29tbWlzc2lvbl9yYXRlFXJlcXVlc3Rfc2V0X2dhc19wcmljZRZyZXF1ZXN0X3dpdGhkcmF3X3N0YWtlFHJvdGF0ZV9vcGVyYXRpb25fY2FwBnNlbmRlcidzZXRfY2FuZGlkYXRlX3ZhbGlkYXRvcl9jb21taXNzaW9uX3JhdGUhc2V0X2NhbmRpZGF0ZV92YWxpZGF0b3JfZ2FzX3ByaWNlDHNoYXJlX29iamVjdA1zdGFrZV9zdWJzaWR5DHN0YWtpbmdfcG9vbANzdWkKc3VpX3N5c3RlbRZzdWlfc3lzdGVtX3N0YXRlX2lubmVyFHN5c3RlbV9zdGF0ZV92ZXJzaW9uCHRyYW5zZmVyCnR4X2NvbnRleHQVdW5kb19yZXBvcnRfdmFsaWRhdG9yKnVwZGF0ZV9jYW5kaWRhdGVfdmFsaWRhdG9yX25ldHdvcmtfYWRkcmVzcyl1cGRhdGVfY2FuZGlkYXRlX3ZhbGlkYXRvcl9uZXR3b3JrX3B1YmtleSZ1cGRhdGVfY2FuZGlkYXRlX3ZhbGlkYXRvcl9wMnBfYWRkcmVzcyp1cGRhdGVfY2FuZGlkYXRlX3ZhbGlkYXRvcl9wcmltYXJ5X2FkZHJlc3MqdXBkYXRlX2NhbmRpZGF0ZV92YWxpZGF0b3JfcHJvdG9jb2xfcHVia2V5KXVwZGF0ZV9jYW5kaWRhdGVfdmFsaWRhdG9yX3dvcmtlcl9hZGRyZXNzKHVwZGF0ZV9jYW5kaWRhdGVfdmFsaWRhdG9yX3dvcmtlcl9wdWJrZXkcdXBkYXRlX3ZhbGlkYXRvcl9kZXNjcmlwdGlvbhp1cGRhdGVfdmFsaWRhdG9yX2ltYWdlX3VybBV1cGRhdGVfdmFsaWRhdG9yX25hbWUrdXBkYXRlX3ZhbGlkYXRvcl9uZXh0X2Vwb2NoX25ldHdvcmtfYWRkcmVzcyp1cGRhdGVfdmFsaWRhdG9yX25leHRfZXBvY2hfbmV0d29ya19wdWJrZXkndXBkYXRlX3ZhbGlkYXRvcl9uZXh0X2Vwb2NoX3AycF9hZGRyZXNzK3VwZGF0ZV92YWxpZGF0b3JfbmV4dF9lcG9jaF9wcmltYXJ5X2FkZHJlc3MrdXBkYXRlX3ZhbGlkYXRvcl9uZXh0X2Vwb2NoX3Byb3RvY29sX3B1YmtleSp1cGRhdGVfdmFsaWRhdG9yX25leHRfZXBvY2hfd29ya2VyX2FkZHJlc3MpdXBkYXRlX3ZhbGlk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CAELBQEICQIDAwMHCwoCCQAJAQkACQEFAwMIAQMDAgcLBQEJAAMCBwsDAQkACQADCwUBCAkFAwEJAAEGCwMBCQACBggCCwUBCAkCBgsDAQkACQACBgsKAgkACQEJAAEGCQEDAwgBAwNCYWcHQmFsYW5jZQRDb2luAklEBk9wdGlvbhVQb29sVG9rZW5FeGNoYW5nZVJhdGUDU1VJCVN0YWtlZFN1aQtTdGFraW5nUG9vbAVUYWJsZQlUeENvbnRleHQDVUlEFWFjdGl2YXRlX3N0YWtpbmdfcG9vbBBhY3RpdmF0aW9uX2Vwb2NoA2FkZANiYWcHYmFsYW5jZQZib3Jyb3cYY2hlY2tfYmFsYW5jZV9pbnZhcmlhbnRzBGNvaW4IY29udGFpbnMXZGVhY3RpdmF0ZV9zdGFraW5nX3Bvb2wSZGVhY3RpdmF0aW9uX2Vwb2NoBmRlbGV0ZQ9kZXBvc2l0X3Jld2FyZHMFZXBvY2gOZXhjaGFuZ2VfcmF0ZXMMZXh0cmFfZmllbGRzBGZpbGwMZnJvbV9iYWxhbmNlDmdldF9zdWlfYW1vdW50EGdldF90b2tlbl9hbW91bnQQZ2V0X3dpdGhfZGVmYXVsdAJpZBVpbml0aWFsX2V4Y2hhbmdlX3JhdGUZaXNfZXF1YWxfc3Rha2luZ19tZXRhZGF0YQtpc19pbmFjdGl2ZQdpc19ub25lDGlzX3ByZWFjdGl2ZRVpc19wcmVhY3RpdmVfYXRfZXBvY2gHaXNfc29tZQRqb2luD2pvaW5fc3Rha2VkX3N1aQRtYXRoA21pbgNuZXcEbm9uZQZvYmplY3QGb3B0aW9uG3BlbmRpbmdfcG9vbF90b2tlbl93aXRoZHJhdw1wZW5kaW5nX3N0YWtlFHBlbmRpbmdfc3Rha2VfYW1vdW50HXBlbmRpbmdfc3Rha2Vfd2l0aGRyYXdfYW1vdW50GnBlbmRpbmdfdG90YWxfc3VpX3dpdGhkcmF3B3Bvb2xfaWQRcG9vbF90b2tlbl9hbW91bnQScG9vbF90b2tlbl9iYWxhbmNlIXBvb2xfdG9rZW5fZXhjaGFuZ2VfcmF0ZV9hdF9lcG9jaAlwcmluY2lwYWwVcHJvY2Vzc19wZW5kaW5nX3N0YWtlHnByb2Nlc3NfcGVuZGluZ19zdGFrZV93aXRoZHJhdyRwcm9j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AQUHCA0BBwgAAQYIAAEDAgYIAAUBCAcBBgsLAggHBQEBAgYKCBIGCBICBgsMAQgSBggSAgcIAAUBBwgSAgYKCBIFAQsEAQMCBgsMAQgSBQIGCggSBgoFAQoDAgcKCBIFAwcIAAUBAwcIAAYIFAECBwgABggNAQYIEgMHCAAFAgMHCAAGCBMCAQgUAwcIAAcLDgIFCw8BBQcIDQUHCAAIEgcLDgIFCw8BBQEHCA0CBwsOAgULDwEFBQIHCAADAQcKAwIHCggSBwgNAQYKCBIBBwoIEgQKAwMGCgMGCgMEAwsOAgMDAwsOAgMDAgYIAAsOAgULDwEFAgoFAwQGCggSAwMDAgoDCgMJBgoIEgMDCgMKAwMLDgIDAwMLDgIDAwYHCggSBgoDBgoDBwsGAQgKBwsGAQgKBwgNBgMGCggSBgoDBgoDBgsOAgULDwEFBgoFAgYIAAgHBgMDCwsCCAcFAwYIEggAAggHBQEHCA0BCwsCCQAJAQEIEgEFAwcLCwIJAAkBCQAJAQELDAEJAAIIBwgVAgUIFQIFAwELDgIJAAkBAQgFBAYIEgYIEgEFAgYLCwIJAAkBCQACCBIHCA0BCBUDCAcIEgUBBggNAgcLCwIJAAkBCQABCQECBwgSAwUGCBIGCBIBCBIFAgcLDAEJAAkAAwUDCwQBAwEGCwQBCQABBwsEAQkAAQkAAgYKCQAGCQAEBwgSCwYBCAoFBwgNAQYIEQEGCQEDBwgSCBEHCA0BBwkBAQcIFQ4LDgIFCw8BBQYKBQoDCgMLDgIDAwsOAgMDAwoFAwMDAwoDCgMBCAoBBgsGAQkACAMDBwMDCBIIEgUGCBICBgsOAgkACQEGCQACBwsOAgkACQEGCQACCQAJAQMHCw4CCQAJAQkACQECBwoJAAMCAwMKCgsIAQMDAwsJAQMDAwMDBggSBgoIEgELCAEDAgMJAAELCAEJAAEKCwgBCQABCwkBCQABBwsJAQkAAQYLDAEJAAMDAwMCBggSBggSAgYLDAEJAAMBBgkAAQsEAQkABQUDCwQBAwMKAwQDAwsEAQMLBAEDAgcLDAEJAAMBBwkAAQYIFAEGBQIDCwQBAwYBAQMDCwQBAwsEAQMFBQYIEggHBQYIEgEGCBMBCBMBBggHAgMIEgEGCgkAAwMFCAcBCAMHBgUGBQMDBgUKBQcLDwEFAgULDwEFAQYLDgIJAAkBAQoJAAIGCw8BCQAGCQACBwsPAQkABgkAAQYLDwEJAAEHCwwBCQABCAIGAwMDAwMDAgcIEgcIDQQDAwMGCBIHCw4CAwMLDgIDAwQEAwMDBgoFBgoIEgsPAQUKBQMFAQcLDgIJAAkBAQsPAQkABgMDBAoDCgMDEQMDAwoDAwoDAwQDAwMDAwQDAwMIAwMLBgEICgMHCBIFBAsGAQgKAgcLBgEJAAMBCwYBCQACBwsGAQkACwYBCQACBwgSCwYBCAoICgUDAwMDCgUGCBIFAgYIEgMBCBABCAEDQmFnB0JhbGFuY2UFRW50cnkCSUQGT3B0aW9uFVBvb2xUb2tlbkV4Y2hhbmdlUmF0ZQ1Qcmlvcml0eVF1ZXVlA1NVSQlTdGFrZWRTdWkFVGFibGUIVGFibGVWZWMJVHhDb250ZXh0H1VudmVyaWZpZWRWYWxpZGF0b3JPcGVyYXRpb25DYXAJVmFsaWRhdG9yF1ZhbGlkYXRvckVwb2NoSW5mb0V2ZW50ElZhbGlkYXRvckpvaW5FdmVudBNWYWxpZGF0b3JMZWF2ZUV2ZW50FVZhbGlkYXRvck9wZXJhdGlvbkNhcAxWYWxpZGF0b3JTZXQQVmFsaWRhdG9yV3JhcHBlcgZWZWNNYXAGVmVjU2V0CGFjdGl2YXRlEWFjdGl2ZV92YWxpZGF0b3JzA2FkZBphZGp1c3Rfc3Rha2VfYW5kX2dhc19wcmljZQ1hZHZhbmNlX2Vwb2NoJmFzc2VydF9ub19wZW5kaW5nX29yX2FjdGljZV9kdXBsaWNhdGVzEmF0X3Jpc2tfdmFsaWRhdG9ycwNiYWcHYmFsYW5jZQZib3Jyb3cKYm9ycm93X211dBZjYWxjdWxhdGVfdG90YWxfc3Rha2VzJmNsZWFuX3JlcG9ydF9yZWNvcmRzX2xlYXZpbmdfdmFsaWRhdG9yD2NvbW1pc3Npb25fcmF0ZSRjb21wdXRlX2FkanVzdGVkX3Jld2FyZF9kaXN0cmlidXRpb24aY29tcHV0ZV9yZXdhcmRfYWRqdXN0bWVudHMqY29tcHV0ZV9zbGFzaGVkX3ZhbGlkYXRvcnNfYW5kX3RvdGFsX3N0YWtlJmNvbXB1dGVfdW5hZGp1c3RlZF9yZXdhcmRfZGlzdHJpYnV0aW9uCGNvbnRhaW5zGWNvdW50X2R1cGxpY2F0ZXNfdGFibGV2ZWMUY291bnRfZHVwbGljYXRlc192ZWMJY3JlYXRlX3YxCmRlYWN0aXZhdGUVZGVwb3NpdF9zdGFrZV9yZXdhcmRzGmRlcml2ZV9yZWZlcmVuY2VfZ2FzX3ByaWNlB2Rlc3Ryb3kMZGVzdHJveV9zb21lDGRlc3Ryb3lfemVybxFkaXN0cmlidXRlX3Jld2FyZBplZmZlY3R1YXRlX3N0YWdlZF9tZXRhZGF0YQRlbWl0G2VtaXRfdmFsaWRhdG9yX2Vwb2NoX2V2ZW50cwVlbXB0eQVlcG9jaAVldmVudAxleHRyYV9maWVsZHMHZXh0cmFjdA5maW5kX3ZhbGlkYXRvch1maW5kX3ZhbGlkYXRvcl9mcm9tX3RhYmxlX3ZlYwlnYXNfcHJpY2UHZ2VuZXNpcwNnZXQwZ2V0X2FjdGl2ZV9vcl9wZW5kaW5nX29yX2NhbmRpZGF0ZV92YWxpZGF0b3JfbXV0MGdldF9hY3RpdmVfb3JfcGVuZGluZ19vcl9jYW5kaWRhdGVfdmFsaWRhdG9yX3JlZhhnZXRfYWN0aXZlX3ZhbGlkYXRvcl9yZWYlZ2V0X2NhbmRpZGF0ZV9vcl9hY3RpdmVfdmFsaWRhdG9yX211dAdnZXRfbXV0GWdldF9wZW5kaW5nX3ZhbGlkYXRvcl9yZWYVZ2V0X3ZhbGlkYXRvcl9pbmRpY2VzEWdldF92YWxpZGF0b3JfbXV0GmdldF92YWxpZGF0b3JfbXV0X3dpdGhfY3R4L2dldF92YWxpZGF0b3JfbXV0X3dpdGhfY3R4X2luY2x1ZGluZ19jYW5kaWRhdGVzI2dldF92YWxpZGF0b3JfbXV0X3dpdGhfdmVyaWZpZWRfY2FwEWdldF92YWxpZGF0b3JfcmVmAmlkE2luYWN0aXZlX3ZhbGlkYXRvcnMGaW5zZXJ0CWludG9fa2V5cyJpc19hY3RpdmVfdmFsaWRhdG9yX2J5X3N1aV9hZGRyZXNzDGlzX2R1cGxpY2F0ZRZpc19kdXBsaWNhdGVfdmFsaWRhdG9yImlzX2R1cGxpY2F0ZV93aXRoX2FjdGl2ZV92YWxpZGF0b3IjaXNfZHVwbGljYXRlX3dpdGhfcGVuZGluZ192YWxpZGF0b3IIaXNfZW1wdHkVaXNfaW5hY3RpdmVfdmFsaWRhdG9yDGlzX3ByZWFjdGl2ZQdpc19zb21lFmlzX3ZhbGlkYXRvcl9jYW5kaWRhdGUMaXNfdm9sdW50YXJ5BGpvaW4Ea2V5cwZsZW5ndGgcbG9hZF92YWxpZGF0b3JfbWF5YmVfdXBncmFkZQNuZXcJbmV3X2VudHJ5E25ld19mcm9tX3VudmVyaWZpZWQabmV4dF9lcG9jaF92YWxpZGF0b3JfY291bnQEbm9uZQZvYmplY3QQb3BlcmF0aW9uX2NhcF9pZAZvcHRpb24ZcGVuZGluZ19hY3RpdmVfdmFsaWRhdG9ycxBwZW5kaW5nX3JlbW92YWxzB3Bvb2xfaWQTcG9vbF9zdGFraW5nX3Jld2FyZBhwb29sX3Rva2VuX2V4Y2hhbmdlX3JhdGUhcG9vbF90b2tlbl9leGNoYW5nZV9yYXRlX2F0X2Vwb2NoA3BvcAhwb3BfYmFjawdwb3BfbWF4DnByaW9yaXR5X3F1ZXVlGHByb2Nlc3NfcGVuZGluZ19yZW1vdmFscyRwcm9jZXNzX3BlbmRpbmdfc3Rha2VzX2FuZF93aXRoZHJhd3MacHJvY2Vzc19wZW5kaW5nX3ZhbGlkYXRvcnMbcHJvY2Vzc192YWxpZGF0b3JfZGVwYXJ0dXJlCXB1c2hfYmFjaxBxdW9ydW1fdGhyZXNob2xkGnJlZmVyZW5jZV9nYXNfc3VydmV5X3F1b3RlBnJlbW92ZRFyZXF1ZXN0X2FkZF9zdGFrZRVyZXF1ZXN0X2FkZF92YWxpZGF0b3IfcmVxdWVzdF9hZGRfdmFsaWRhdG9yX2NhbmRpZGF0ZRhyZXF1ZXN0X3JlbW92ZV92YWxpZGF0b3IicmVxdWVzdF9yZW1vdmVfdmFsaWRhdG9yX2NhbmRpZGF0ZRtyZXF1ZXN0X3NldF9jb21taXNzaW9uX3JhdGUWcmVxdWVzdF93aXRoZHJhd19zdGFrZQZzZW5kZXIQc2V0X3ZvdGluZ19wb3dlcgRzaXplBHNvbWURc29ydF9yZW1vdmFsX2xpc3QFc3BsaXQFc3Rha2UMc3Rha2VfYW1vdW50DHN0YWtpbmdfcG9vbA9zdGFraW5nX3Bvb2xfaWQVc3Rha2luZ19wb29sX21hcHBpbmdzG3N0b3JhZ2VfZnVuZF9zdGFraW5nX3Jld2FyZANzdWkLc3VpX2FkZHJlc3MWc3VpX3N5c3RlbV9zdGF0ZV9pbm5lch1zdW1fdm90aW5nX3Bvd2VyX2J5X2FkZHJlc3NlcwV0YWJsZQl0YWJsZV92ZWMadGFsbHlpbmdfcnVsZV9nbG9iYWxfc2NvcmUXdGFsbHlpbmdfcnVsZV9yZXBvcnRlcnMLdG90YWxfc3Rha2USdG90YWxfc3Rha2VfYW1vdW50EnRvdGFsX3ZvdGluZ19wb3dlcgp0eF9jb250ZXh0IHVudmVyaWZpZWRfb3BlcmF0aW9uX2NhcF9hZGRyZXNzJ3VwZGF0ZV9hbmRfcHJvY2Vzc19sb3dfc3Rha2VfZGVwYXJ0dXJlcwl2YWxpZGF0b3IRdmFsaWRhdG9yX2FkZHJlc3MUdmFsaWRhdG9yX2NhbmRpZGF0ZXMNdmFsaWRhdG9yX2NhcA12YWxpZGF0b3Jfc2V0FnZhbGlkYXRvcl9zdGFrZV9hbW91bnQZdmFsaWRhdG9yX3N0YWtpbmdfcG9vbF9pZBx2YWxpZGF0b3JfdG90YWxfc3Rha2VfYW1vdW50EXZhbGlkYXRvcl93cmFwcGVyBXZhbHVlB3ZlY19tYXAHdmVjX3NldAZ2ZWN0b3IedmVyaWZpZWRfb3BlcmF0aW9uX2NhcF9hZGRyZXNzCnZlcmlmeV9jYXAMdm90aW5nX3Bvd2VyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgEBAgECAgEDBBAQJwAAAAAAAAAAAAAAAAAAAwgAAAAAAAAAAAMIAQAAAAAAAAADCAIAAAAAAAAAAwgDAAAAAAAAAAMIBAAAAAAAAAADCAUAAAAAAAAAAwgGAAAAAAAAAAMIBwAAAAAAAAADCAgAAAAAAAAAAwgJAAAAAAAAAAMIZQAAAAAAAAAKAwEACgUBAAACCZQBAxcKCBJnCwwBCBJoCgOKAQsLAggHBU0LCwIIBwgVnAELCwIFCBUcCw4CBQM5CAUBAgo3A5sBBXcDhgEDIwNqA4sBA2sIEJMBCgWSAQMCAgM3A5sBBYkBCAcDAgQ3A5sBBYkBCAdaAQADAAA2Mw4AES0MBQoBOAAMBA4AQToMAwYAAAAAAAAAAAwCCgIKAyMEHwUQ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AQMCBggBBQEIBwEGCwkCCAcFAQsMAQUDCgsGAQgICwMBAwcICgIDCBMCCggQBwgKAQgTAQYIEwEIDwIFCwwBBQELCwIJAAkBAQgIAQsFAQkAAQcICgEIBAEIEAEGCAoBBRAFCgIKAgoCCgIKAgoCCgIKAgoCCgIKAgoCAwMHCAoDBwgTCBAHCAoCBwgTBwgKAwcIEwMHCAoBCBIDBwgTBggRAgMHCBMGCBIBAQcIEAMHCBAIEgMDBwgTAwYICgIHCBMGCAoCBwgQAwELBgEJAAQHCBMFCwUBCAgHCAoBBggOAwcIEwgOBwgKAgYIEwUBAQQGBQYFBQcLDAEFAQYIEgEGBQIGCwsCCQAJAQYJAAEJAAELDAEJAAMHCwsCCQAJAQkACQECBwsLAgkACQEGCQABBwkBAgYLDAEJAAYJAAIHCwwBCQAJAAIHCwwBCQAGCQABBgsMAQkAAgkACQECBwgQBwgKAgcIEAoCAgcIEAYIEAIGCBMGCBADBwgQCgIKAioBAwMDAwMBAQsFAQgIAwMDAwMDAwMDBAMDAwsFAQgIAwMLBQEICAsFAQgICwUBCAgLBQEICAMDAwsFAQgIBAsFAQgIBAMDAwMEAwEHCwUBCQACBwsFAQkACwUBCQABBggPAQYLBQEJAAEHCA0CBwsFAQkAAwkHCBMHCwUBCAgHCwUBCAgHCwsCBQsMAQUDAwMDBwgKBgcIDwsFAQgICwUBCAgLBQEICAMDAQgCAQYJAQULBQEICAMLBQEICAsGAQgICwUBCAgBCwYBCAgCBwsGAQkACgsGAQkAAQYLAwEJAAELAwEJAAILBQEJAAcICgIJAAUDQmFnB0JhbGFuY2UEQ29pbgJJRAZPcHRpb24DU1VJDFN0YWtlU3Vic2lkeQlTdGFrZWRTdWkLU3RvcmFnZUZ1bmQTU3VpU3lzdGVtU3RhdGVJbm5lchRTeXN0ZW1FcG9jaEluZm9FdmVudBBTeXN0ZW1QYXJhbWV0ZXJzBVRhYmxlCVR4Q29udGV4dB9VbnZlcmlmaWVkVmFsaWRhdG9yT3BlcmF0aW9uQ2FwCVZhbGlkYXRvchVWYWxpZGF0b3JPcGVyYXRpb25DYXAMVmFsaWRhdG9yU2V0BlZlY01hcAZWZWNTZXQNYWR2YW5jZV9lcG9jaBdhZHZhbmNlX2Vwb2NoX3NhZmVfbW9kZSZhc3NlcnRfbm9fcGVuZGluZ19vcl9hY3RpY2VfZHVwbGljYXRlcwNiYWcHYmFsYW5jZQRjb2luCGNvbnRhaW5zBmNyZWF0ZRhjcmVhdGVfc3lzdGVtX3BhcmFtZXRlcnMaZGVyaXZlX3JlZmVyZW5jZV9nYXNfcHJpY2UMZGVzdHJveV9zb21lDGRlc3Ryb3lfemVybwRlbWl0BWVtcHR5BWVwb2NoEWVwb2NoX2R1cmF0aW9uX21zGGVwb2NoX3N0YXJ0X3RpbWVzdGFtcF9tcwVldmVudAxleHRyYV9maWVsZHMUZXh0cmFjdF9jb2luX2JhbGFuY2UMZnJvbV9iYWxhbmNlB2dlbmVzaXMcZ2VuZXNpc19zeXN0ZW1fc3RhdGVfdmVyc2lvbgNnZXQHZ2V0X211dBBnZXRfcmVwb3J0ZXJzX29mH2dldF9zdG9yYWdlX2Z1bmRfb2JqZWN0X3JlYmF0ZXMeZ2V0X3N0b3JhZ2VfZnVuZF90b3RhbF9iYWxhbmNlGmdldF92YWxpZGF0b3JfbXV0X3dpdGhfY3R4L2dldF92YWxpZGF0b3JfbXV0X3dpdGhfY3R4X2luY2x1ZGluZ19jYW5kaWRhdGVzI2dldF92YWxpZGF0b3JfbXV0X3dpdGhfdmVyaWZpZWRfY2FwBmluc2VydAxpbnRvX2JhbGFuY2UiaXNfYWN0aXZlX3ZhbGlkYXRvcl9ieV9zdWlfYWRkcmVzcwhpc19lbXB0eQdpc19zb21lBGpvaW4Iam9pbl92ZWMcbGVmdG92ZXJfc3RvcmFnZV9mdW5kX2luZmxvdxNtYXhfdmFsaWRhdG9yX2NvdW50G21pbl92YWxpZGF0b3Jfam9pbmluZ19zdGFrZQNuZXczbmV3X3VudmVyaWZpZWRfdmFsaWRhdG9yX29wZXJhdGlvbl9jYXBfYW5kX3RyYW5zZmVyGm5leHRfZXBvY2hfdmFsaWRhdG9yX2NvdW50Bm9iamVjdAZvcHRpb24KcGFyYW1ldGVycwNwYXkQcHJvdG9jb2xfdmVyc2lvbg9wdWJsaWNfdHJhbnNmZXITcmVmZXJlbmNlX2dhc19wcmljZQZyZW1vdmUQcmVwb3J0X3ZhbGlkYXRvchVyZXBvcnRfdmFsaWRhdG9yX2ltcGwRcmVxdWVzdF9hZGRfc3Rha2UacmVxdWVzdF9hZGRfc3Rha2VfbXVsX2NvaW4VcmVxdWVzdF9hZGRfdmFsaWRhdG9yH3JlcXVlc3RfYWRkX3ZhbGlkYXRvcl9jYW5kaWRhdGUYcmVxdWVzdF9yZW1vdmVfdmFsaWRhdG9yInJlcXVlc3RfcmVtb3ZlX3ZhbGlkYXRvcl9jYW5kaWRhdGUbcmVxdWVzdF9zZXRfY29tbWlzc2lvbl9yYXRlFXJlcXVlc3Rfc2V0X2dhc19wcmljZRZyZXF1ZXN0X3dpdGhkcmF3X3N0YWtlFHJvdGF0ZV9vcGVyYXRpb25fY2FwCXNhZmVfbW9kZR1zYWZlX21vZGVfY29tcHV0YXRpb25fcmV3YXJkcyRzYWZlX21vZGVfbm9uX3JlZnVuZGFibGVfc3RvcmFnZV9mZWUZc2FmZV9tb2RlX3N0b3JhZ2VfcmViYXRlcxlzYWZlX21vZGVfc3RvcmFnZV9yZXdhcmRzBnNlbmRlch1zZXRfY2FuZGlkYXRlX2NvbW1pc3Npb25fcmF0ZRdzZXRfY2FuZGlkYXRlX2dhc19wcmljZSdzZXRfY2FuZGlkYXRlX3ZhbGlkYXRvcl9jb21taXNzaW9uX3JhdGUhc2V0X2NhbmRpZGF0ZV92YWxpZGF0b3JfZ2FzX3ByaWNlCXNpbmdsZXRvbgVzcGxpdBZzdGFrZV9hY3RpdmF0aW9uX2Vwb2NoDXN0YWtlX3N1YnNpZHkUc3Rha2Vfc3Vic2lkeV9hbW91bnQZc3Rha2Vfc3Vic2lkeV9zdGFydF9lcG9jaAxzdGFraW5nX3Bvb2wVc3Rha2luZ19wb29sX21hcHBpbmdzDnN0b3JhZ2VfY2hhcmdlDHN0b3JhZ2VfZnVuZBRzdG9yYWdlX2Z1bmRfYmFsYW5jZRlzdG9yYWdlX2Z1bmRfcmVpbnZlc3RtZW50DnN0b3JhZ2VfcmViYXRlA3N1aQpzdWlfc3lzdGVtFnN1aV9zeXN0ZW1fc3RhdGVfaW5uZXIUc3lzdGVtX3N0YXRlX3ZlcnNpb24FdGFibGUNdG90YWxfYmFsYW5jZQ50b3RhbF9nYXNfZmVlcxx0b3RhbF9vYmplY3Rfc3RvcmFnZV9yZWJhdGVzC3RvdGFsX3N0YWtlH3RvdGFsX3N0YWtlX3Jld2FyZHNfZGlzdHJpYnV0ZWQIdHJhbnNmZXIKdHhfY29udGV4dBV1bmRvX3JlcG9ydF92YWxpZGF0b3IadW5kb19yZXBvcnRfdmFsaWRhdG9yX2ltcGwgdXBkYXRlX2NhbmRpZGF0ZV9uZXR3b3JrX2FkZHJlc3MfdXBkYXRlX2NhbmRpZGF0ZV9uZXR3b3JrX3B1YmtleRx1cGRhdGVfY2FuZGlkYXRlX3AycF9hZGRyZXNzIHVwZGF0ZV9jYW5kaWRhdGVfcHJpbWFyeV9hZGRyZXNzIHVwZGF0ZV9jYW5kaWRhdGVfcHJvdG9jb2xfcHVia2V5KnVwZGF0ZV9jYW5kaWRhdGVfdmFsaWRhdG9yX25ldHdvcmtfYWRkcmVzcyl1cGRhdGVfY2FuZGlkYXRlX3ZhbGlkYXRvcl9uZXR3b3JrX3B1YmtleSZ1cGRhdGVfY2FuZGlkYXRlX3ZhbGlkYXRvcl9wMnBfYWRkcmVzcyp1cGRhdGVfY2FuZGlkYXRlX3ZhbGlkYXRvcl9wcmltYXJ5X2FkZHJlc3MqdXBkYXRlX2NhbmRpZGF0ZV92YWxpZGF0b3JfcHJvdG9jb2xfcHVia2V5KXVwZGF0ZV9jYW5kaWRhdGVfdmFsaWRhdG9yX3dvcmtlcl9hZGRyZXNzKHVwZGF0ZV9jYW5kaWRhdGVfdmFsaWRhdG9yX3dvcmtlcl9wdWJrZXkfdXBkYXRlX2NhbmRpZGF0ZV93b3JrZXJfYWRkcmVzcx51cGRhdGVfY2FuZGlkYXRlX3dvcmtlcl9wdWJrZXkSdXBkYXRlX2Rlc2NyaXB0aW9uEHVwZGF0ZV9pbWFnZV91cmwLdXBkYXRlX25hbWUhdXBkYXRlX25leHRfZXBvY2hfbmV0d29ya19hZGRyZXNzIHVwZGF0ZV9uZXh0X2Vwb2NoX25ldHdvcmtfcHVia2V5HXVwZGF0ZV9uZXh0X2Vwb2NoX3AycF9hZGRyZXNzIXVwZGF0ZV9uZXh0X2Vwb2NoX3ByaW1hcnlfYWRkcmVzcyF1cGRhdGVfbmV4dF9lcG9jaF9wcm90b2NvbF9wdWJrZXkgdXBkYXRlX25leHRfZXBvY2hfd29ya2VyX2FkZHJlc3MfdXBkYXRlX25leHRfZXBvY2hfd29ya2VyX3B1YmtleRJ1cGRhdGVfcHJvamVjdF91cmwcdXBkYXRlX3ZhbGlkYXRvcl9kZXNjcmlwdGlvbhp1cGRhdGVfdmFsaWRhdG9yX2ltYWdlX3VybBV1cGRhdGVfdmFsaWRhdG9yX25hbWUrdXBkYXRlX3ZhbGlkYXRvcl9uZXh0X2Vwb2NoX25ldHdvcmtfYWRkcmVzcyp1cGRhdGVfdmFsaWRhdG9yX25leHRfZXBvY2hfbmV0d29ya19wdWJrZXkndXBkYXRlX3ZhbGlkYXRvcl9uZXh0X2Vwb2NoX3AycF9hZGRyZXNzK3VwZGF0ZV92YWxpZGF0b3JfbmV4dF9lcG9jaF9wcmltYXJ5X2FkZHJlc3MrdXBkYXRlX3ZhbGlkYXRvcl9uZXh0X2Vwb2NoX3Byb3RvY29sX3B1YmtleSp1cGRhdGVfdmFsaWRhdG9yX25leHRfZXBvY2hfd29ya2VyX2FkZHJlc3MpdXBkYXRlX3ZhbGlkYXRvcl9uZXh0X2Vwb2NoX3dvcmtlcl9wdWJrZXkcdXBkYXRlX3ZhbGlkYXRvcl9wcm9qZWN0X3VybAl2YWxpZGF0b3INdmFsaWRhdG9yX2NhcCB2YWxpZGF0b3JfbG93X3N0YWtlX2dyYWNlX3BlcmlvZB12YWxpZGF0b3JfbG93X3N0YWtlX3RocmVzaG9sZBh2YWxpZGF0b3JfcmVwb3J0X3JlY29yZHMNdmFsaWRhdG9yX3NldBZ2YWxpZGF0b3Jfc3Rha2VfYW1vdW50GXZhbGlkYXRvcl9zdGFraW5nX3Bvb2xfaWQfdmFsaWRhdG9yX3N0YWtpbmdfcG9vbF9tYXBwaW5ncxx2YWxpZGF0b3JfdG90YWxfc3Rha2VfYW1vdW50InZhbGlkYXRvcl92ZXJ5X2xvd19zdGFrZV90aHJlc2hvbGQKdmFsaWRhdG9ycwV2YWx1ZQd2ZWNfbWFwB3ZlY19zZXQedmVyaWZpZWRfb3BlcmF0aW9uX2NhcF9hZGRyZXNzCnZlcmlmeV9jYXAMd2l0aGRyYXdfYWxsBHplcm8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAQECAQICAQMDCAEAAAAAAAAAAwgAAAAAAAAAAAMIAgAAAAAAAAADCAMAAAAAAAAAAwgEAAAAAAAAAAMIBQAAAAAA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 diff --git a/sui/devnet/sui.keystore b/sui/devnet/sui.keystore new file mode 100644 index 000000000..47355b939 --- /dev/null +++ b/sui/devnet/sui.keystore @@ -0,0 +1,7 @@ +[ + "AB522qKKEsXMTFRD2SG3Het/02S/ZBOugmcH3R1CDG6l", + "AOmPq9B16F3W3ijO/4s9hI6v8LdiYCawKAW31PKpg4Qp", + "AOLhc0ryVWnD5LmqH3kCHruBpVV+68EWjEGu2eC9gndK", + "AKCo1FyhQ0zUpnoZLmGJJ+8LttTrt56W87Ho4vBF+R+8", + "AGA20wtGcwbcNAG4nwapbQ5wIuXwkYQEWFUoSVAxctHb" +] \ No newline at end of file diff --git a/sui/devnet/validator-config-0.yaml b/sui/devnet/validator-config-0.yaml new file mode 100644 index 000000000..65505c34b --- /dev/null +++ b/sui/devnet/validator-config-0.yaml @@ -0,0 +1,73 @@ +--- +protocol-key-pair: + value: VTDx4HjVmRBqdqBWg2zN+zcFE20io3CrBchGy/iV1lo= +worker-key-pair: + value: AOALyRbJi6ld82KFPzzSTqFcvnEJe7cxFmE4UmgTMkt1 +account-key-pair: + value: AFO6/i9VpgpR9Tb5fA7PBh2bM8HMbsxjPC/twrRFXZoh +network-key-pair: + value: ACTXLjvKVNOmez4eB/undu829MFIpNqRactays+pGNnf +db-path: /root/.sui/sui_config/authorities_db/99f25ef61f80 +network-address: /ip4/127.0.0.1/tcp/37395/http +json-rpc-address: "127.0.0.1:34677" +metrics-address: "127.0.0.1:34733" +admin-interface-port: 33561 +consensus-config: + address: /ip4/127.0.0.1/tcp/36643/http + db-path: /root/.sui/sui_config/consensus_db/99f25ef61f80 + internal-worker-address: ~ + max-pending-transactions: ~ + narwhal-config: + header_num_of_batches_threshold: 32 + max_header_num_of_batches: 1000 + max_header_delay: 2000ms + min_header_delay: 500ms + gc_depth: 50 + sync_retry_delay: 5000ms + sync_retry_nodes: 3 + batch_size: 500000 + max_batch_delay: 100ms + block_synchronizer: + range_synchronize_timeout: 30000ms + certificates_synchronize_timeout: 30000ms + payload_synchronize_timeout: 30000ms + payload_availability_timeout: 30000ms + handler_certificate_deliver_timeout: 30000ms + consensus_api_grpc: + socket_addr: /ip4/127.0.0.1/tcp/32825/http + get_collections_timeout: 5000ms + remove_collections_timeout: 5000ms + max_concurrent_requests: 500000 + prometheus_metrics: + socket_addr: /ip4/127.0.0.1/tcp/43711/http + network_admin_server: + primary_network_admin_server_port: 34257 + worker_network_admin_server_base_port: 45379 + anemo: + send_certificate_rate_limit: ~ + get_payload_availability_rate_limit: ~ + get_certificates_rate_limit: ~ + report_batch_rate_limit: ~ + request_batch_rate_limit: ~ +enable-event-processing: false +grpc-load-shed: ~ +grpc-concurrency-limit: 20000000000 +p2p-config: + listen-address: "127.0.0.1:33745" + external-address: /ip4/127.0.0.1/udp/33745 +genesis: + genesis-file-location: /root/.sui/sui_config/genesis.blob +authority-store-pruning-config: + num-latest-epoch-dbs-to-retain: 3 + epoch-db-pruning-period-secs: 3600 + num-epochs-to-retain: 1 + max-checkpoints-in-batch: 200 + max-transactions-in-batch: 1000 + use-range-deletion: true +end-of-epoch-broadcast-channel-capacity: 128 +checkpoint-executor-config: + checkpoint-execution-max-concurrency: 200 + local-execution-timeout-sec: 10 +db-checkpoint-config: + perform-db-checkpoints-at-epoch-end: false +indirect-objects-threshold: 18446744073709551615 diff --git a/sui/devnet/validator-config-1.yaml b/sui/devnet/validator-config-1.yaml new file mode 100644 index 000000000..5dbfa8776 --- /dev/null +++ b/sui/devnet/validator-config-1.yaml @@ -0,0 +1,73 @@ +--- +protocol-key-pair: + value: avYcyVgYMXTyaUYh9IRwLK0gSzl7YF6ZQDAbrS1Bhvo= +worker-key-pair: + value: ALcYC9nZa2UFKkxycem4wHZUW6nPTmAPWIC0Me/X8/OQ +account-key-pair: + value: AFQ60/bLdbiryFJsWRrXW29RvC56WN2CAyS75jTRtQWj +network-key-pair: + value: AKgyYvFpPmPmmEPNdltJ4cfcb9D0t0bigFz3cak+iblf +db-path: /root/.sui/sui_config/authorities_db/8dcff6d15504 +network-address: /ip4/127.0.0.1/tcp/45631/http +json-rpc-address: "127.0.0.1:39065" +metrics-address: "127.0.0.1:36781" +admin-interface-port: 41085 +consensus-config: + address: /ip4/127.0.0.1/tcp/46119/http + db-path: /root/.sui/sui_config/consensus_db/8dcff6d15504 + internal-worker-address: ~ + max-pending-transactions: ~ + narwhal-config: + header_num_of_batches_threshold: 32 + max_header_num_of_batches: 1000 + max_header_delay: 2000ms + min_header_delay: 500ms + gc_depth: 50 + sync_retry_delay: 5000ms + sync_retry_nodes: 3 + batch_size: 500000 + max_batch_delay: 100ms + block_synchronizer: + range_synchronize_timeout: 30000ms + certificates_synchronize_timeout: 30000ms + payload_synchronize_timeout: 30000ms + payload_availability_timeout: 30000ms + handler_certificate_deliver_timeout: 30000ms + consensus_api_grpc: + socket_addr: /ip4/127.0.0.1/tcp/36797/http + get_collections_timeout: 5000ms + remove_collections_timeout: 5000ms + max_concurrent_requests: 500000 + prometheus_metrics: + socket_addr: /ip4/127.0.0.1/tcp/40191/http + network_admin_server: + primary_network_admin_server_port: 34921 + worker_network_admin_server_base_port: 43785 + anemo: + send_certificate_rate_limit: ~ + get_payload_availability_rate_limit: ~ + get_certificates_rate_limit: ~ + report_batch_rate_limit: ~ + request_batch_rate_limit: ~ +enable-event-processing: false +grpc-load-shed: ~ +grpc-concurrency-limit: 20000000000 +p2p-config: + listen-address: "127.0.0.1:40075" + external-address: /ip4/127.0.0.1/udp/40075 +genesis: + genesis-file-location: /root/.sui/sui_config/genesis.blob +authority-store-pruning-config: + num-latest-epoch-dbs-to-retain: 3 + epoch-db-pruning-period-secs: 3600 + num-epochs-to-retain: 1 + max-checkpoints-in-batch: 200 + max-transactions-in-batch: 1000 + use-range-deletion: true +end-of-epoch-broadcast-channel-capacity: 128 +checkpoint-executor-config: + checkpoint-execution-max-concurrency: 200 + local-execution-timeout-sec: 10 +db-checkpoint-config: + perform-db-checkpoints-at-epoch-end: false +indirect-objects-threshold: 18446744073709551615 diff --git a/sui/devnet/validator-config-2.yaml b/sui/devnet/validator-config-2.yaml new file mode 100644 index 000000000..a94a08442 --- /dev/null +++ b/sui/devnet/validator-config-2.yaml @@ -0,0 +1,73 @@ +--- +protocol-key-pair: + value: OXnx3yM1C/ppgnDMx/o1d49fJs7E05kq11mXNae/O+I= +worker-key-pair: + value: AFNNzJwFj3Xdc6SoCzXZ+8otMB5HXVBX/57G9JRkOJcJ +account-key-pair: + value: AIRtltXdA0Y0eLXY9eLpXFpZOcvP3HW9bzBnpVQMZs6Q +network-key-pair: + value: AOSqdS3WsvNdxqIZtKCK31wTPM5AqSkpDLkd56ZL/u+G +db-path: /root/.sui/sui_config/authorities_db/addeef94d898 +network-address: /ip4/127.0.0.1/tcp/34255/http +json-rpc-address: "127.0.0.1:40709" +metrics-address: "127.0.0.1:43007" +admin-interface-port: 45245 +consensus-config: + address: /ip4/127.0.0.1/tcp/41199/http + db-path: /root/.sui/sui_config/consensus_db/addeef94d898 + internal-worker-address: ~ + max-pending-transactions: ~ + narwhal-config: + header_num_of_batches_threshold: 32 + max_header_num_of_batches: 1000 + max_header_delay: 2000ms + min_header_delay: 500ms + gc_depth: 50 + sync_retry_delay: 5000ms + sync_retry_nodes: 3 + batch_size: 500000 + max_batch_delay: 100ms + block_synchronizer: + range_synchronize_timeout: 30000ms + certificates_synchronize_timeout: 30000ms + payload_synchronize_timeout: 30000ms + payload_availability_timeout: 30000ms + handler_certificate_deliver_timeout: 30000ms + consensus_api_grpc: + socket_addr: /ip4/127.0.0.1/tcp/36241/http + get_collections_timeout: 5000ms + remove_collections_timeout: 5000ms + max_concurrent_requests: 500000 + prometheus_metrics: + socket_addr: /ip4/127.0.0.1/tcp/36547/http + network_admin_server: + primary_network_admin_server_port: 38113 + worker_network_admin_server_base_port: 45711 + anemo: + send_certificate_rate_limit: ~ + get_payload_availability_rate_limit: ~ + get_certificates_rate_limit: ~ + report_batch_rate_limit: ~ + request_batch_rate_limit: ~ +enable-event-processing: false +grpc-load-shed: ~ +grpc-concurrency-limit: 20000000000 +p2p-config: + listen-address: "127.0.0.1:39953" + external-address: /ip4/127.0.0.1/udp/39953 +genesis: + genesis-file-location: /root/.sui/sui_config/genesis.blob +authority-store-pruning-config: + num-latest-epoch-dbs-to-retain: 3 + epoch-db-pruning-period-secs: 3600 + num-epochs-to-retain: 1 + max-checkpoints-in-batch: 200 + max-transactions-in-batch: 1000 + use-range-deletion: true +end-of-epoch-broadcast-channel-capacity: 128 +checkpoint-executor-config: + checkpoint-execution-max-concurrency: 200 + local-execution-timeout-sec: 10 +db-checkpoint-config: + perform-db-checkpoints-at-epoch-end: false +indirect-objects-threshold: 18446744073709551615 diff --git a/sui/devnet/validator-config-3.yaml b/sui/devnet/validator-config-3.yaml new file mode 100644 index 000000000..34fc5b505 --- /dev/null +++ b/sui/devnet/validator-config-3.yaml @@ -0,0 +1,73 @@ +--- +protocol-key-pair: + value: CyNkjqNVr3HrHTH7f/NLs7u5lUHJzuPAw0PqMTD2y2s= +worker-key-pair: + value: ACKCL1GI/NLZHFVW1cUR6gu9fdh5ll/XpJWCRirrVDgj +account-key-pair: + value: AI5hzubOtOnMiBuXhZ2Cvnop5AQOxMHxhUdyPiy7/C37 +network-key-pair: + value: AG5LdZdKNOxwTtshbJjyRcq3dJIHu9NQnBz4WwbQPl3K +db-path: /root/.sui/sui_config/authorities_db/b3fd5efb5c87 +network-address: /ip4/127.0.0.1/tcp/43597/http +json-rpc-address: "127.0.0.1:40123" +metrics-address: "127.0.0.1:40221" +admin-interface-port: 44653 +consensus-config: + address: /ip4/127.0.0.1/tcp/39661/http + db-path: /root/.sui/sui_config/consensus_db/b3fd5efb5c87 + internal-worker-address: ~ + max-pending-transactions: ~ + narwhal-config: + header_num_of_batches_threshold: 32 + max_header_num_of_batches: 1000 + max_header_delay: 2000ms + min_header_delay: 500ms + gc_depth: 50 + sync_retry_delay: 5000ms + sync_retry_nodes: 3 + batch_size: 500000 + max_batch_delay: 100ms + block_synchronizer: + range_synchronize_timeout: 30000ms + certificates_synchronize_timeout: 30000ms + payload_synchronize_timeout: 30000ms + payload_availability_timeout: 30000ms + handler_certificate_deliver_timeout: 30000ms + consensus_api_grpc: + socket_addr: /ip4/127.0.0.1/tcp/39889/http + get_collections_timeout: 5000ms + remove_collections_timeout: 5000ms + max_concurrent_requests: 500000 + prometheus_metrics: + socket_addr: /ip4/127.0.0.1/tcp/40255/http + network_admin_server: + primary_network_admin_server_port: 38277 + worker_network_admin_server_base_port: 41135 + anemo: + send_certificate_rate_limit: ~ + get_payload_availability_rate_limit: ~ + get_certificates_rate_limit: ~ + report_batch_rate_limit: ~ + request_batch_rate_limit: ~ +enable-event-processing: false +grpc-load-shed: ~ +grpc-concurrency-limit: 20000000000 +p2p-config: + listen-address: "127.0.0.1:35253" + external-address: /ip4/127.0.0.1/udp/35253 +genesis: + genesis-file-location: /root/.sui/sui_config/genesis.blob +authority-store-pruning-config: + num-latest-epoch-dbs-to-retain: 3 + epoch-db-pruning-period-secs: 3600 + num-epochs-to-retain: 1 + max-checkpoints-in-batch: 200 + max-transactions-in-batch: 1000 + use-range-deletion: true +end-of-epoch-broadcast-channel-capacity: 128 +checkpoint-executor-config: + checkpoint-execution-max-concurrency: 200 + local-execution-timeout-sec: 10 +db-checkpoint-config: + perform-db-checkpoints-at-epoch-end: false +indirect-objects-threshold: 18446744073709551615 diff --git a/sui/examples/coins/.gitignore b/sui/examples/coins/.gitignore new file mode 100644 index 000000000..378eac25d --- /dev/null +++ b/sui/examples/coins/.gitignore @@ -0,0 +1 @@ +build diff --git a/sui/examples/coins/Makefile b/sui/examples/coins/Makefile new file mode 100644 index 000000000..9b862faf0 --- /dev/null +++ b/sui/examples/coins/Makefile @@ -0,0 +1,15 @@ +.PHONY: all clean test check + +all: check + +.PHONY: clean +clean: + rm -rf build + +.PHONY: check +check: + sui move build -d + +.PHONY: test +test: check + sui move test -d diff --git a/sui/examples/coins/Move.devnet.toml b/sui/examples/coins/Move.devnet.toml new file mode 100644 index 000000000..da45c8b6a --- /dev/null +++ b/sui/examples/coins/Move.devnet.toml @@ -0,0 +1,17 @@ +[package] +name = "Coins" +version = "0.1.0" + +[dependencies.Sui] +git = "https://github.com/MystenLabs/sui.git" +subdir = "crates/sui-framework/packages/sui-framework" +rev = "09b2081498366df936abae26eea4b2d5cafb2788" + +[dependencies.Wormhole] +local = "../../wormhole" + +[dependencies.TokenBridge] +local = "../../token_bridge" + +[addresses] +coins = "_" diff --git a/sui/examples/coins/Move.lock b/sui/examples/coins/Move.lock new file mode 100644 index 000000000..72f0e2c2c --- /dev/null +++ b/sui/examples/coins/Move.lock @@ -0,0 +1,45 @@ +# @generated by Move, please check-in and do not edit manually. + +[move] +version = 0 + +dependencies = [ + { name = "Sui" }, +] + +dev-dependencies = [ + { name = "TokenBridge" }, + { name = "Wormhole" }, +] + +[[move.package]] +name = "MoveStdlib" +source = { git = "https://github.com/MystenLabs/sui.git", rev = "09b2081498366df936abae26eea4b2d5cafb2788", subdir = "crates/sui-framework/packages/move-stdlib" } + +[[move.package]] +name = "Sui" +source = { git = "https://github.com/MystenLabs/sui.git", rev = "09b2081498366df936abae26eea4b2d5cafb2788", subdir = "crates/sui-framework/packages/sui-framework" } + +dependencies = [ + { name = "MoveStdlib" }, +] + +[[move.package]] +name = "TokenBridge" +source = { local = "../../token_bridge" } + +dependencies = [ + { name = "Sui" }, +] + +dev-dependencies = [ + { name = "Wormhole" }, +] + +[[move.package]] +name = "Wormhole" +source = { local = "../../wormhole" } + +dependencies = [ + { name = "Sui" }, +] diff --git a/sui/examples/coins/Move.toml b/sui/examples/coins/Move.toml new file mode 100644 index 000000000..d80265e34 --- /dev/null +++ b/sui/examples/coins/Move.toml @@ -0,0 +1,28 @@ +[package] +name = "Coins" +version = "0.1.0" + +[dependencies.Sui] +git = "https://github.com/MystenLabs/sui.git" +subdir = "crates/sui-framework/packages/sui-framework" +rev = "09b2081498366df936abae26eea4b2d5cafb2788" + +[dependencies.Wormhole] +local = "../../wormhole" + +[dependencies.TokenBridge] +local = "../../token_bridge" + +[addresses] +coins = "_" + +[dev-dependencies.Wormhole] +local = "../../wormhole" + +[dev-dependencies.TokenBridge] +local = "../../token_bridge" + +[dev-addresses] +wormhole = "0x100" +token_bridge = "0x200" +coins = "0x20c" diff --git a/sui/examples/coins/sources/coin.move b/sui/examples/coins/sources/coin.move new file mode 100644 index 000000000..108ebe52d --- /dev/null +++ b/sui/examples/coins/sources/coin.move @@ -0,0 +1,210 @@ +// Example wrapped coin for testing purposes + +#[test_only] +module coins::coin { + use sui::object::{Self}; + use sui::package::{Self}; + use sui::transfer::{Self}; + use sui::tx_context::{Self, TxContext}; + + use token_bridge::create_wrapped::{Self}; + + struct COIN has drop {} + + fun init(witness: COIN, ctx: &mut TxContext) { + use token_bridge::version_control::{V__0_2_0 as V__CURRENT}; + + transfer::public_transfer( + create_wrapped::prepare_registration( + witness, + // TODO: create a version of this for each decimal to be used + 8, + ctx + ), + tx_context::sender(ctx) + ); + } + + #[test_only] + /// NOTE: Even though this module is `#[test_only]`, this method is tagged + /// with the same macro as a trick to allow another method within this + /// module to call `init` using OTW. + public fun init_test_only(ctx: &mut TxContext) { + init(COIN {}, ctx); + + // This will be created and sent to the transaction sender + // automatically when the contract is published. + transfer::public_transfer( + package::test_publish(object::id_from_address(@coins), ctx), + tx_context::sender(ctx) + ); + } +} + +#[test_only] +module coins::coin_tests { + use sui::coin::{Self}; + use sui::package::{UpgradeCap}; + use sui::test_scenario::{Self}; + use token_bridge::create_wrapped::{Self, WrappedAssetSetup}; + use token_bridge::state::{Self}; + use token_bridge::token_bridge_scenario::{ + register_dummy_emitter, + return_state, + set_up_wormhole_and_token_bridge, + take_state, + two_people + }; + use token_bridge::token_registry::{Self}; + use token_bridge::vaa::{Self}; + use token_bridge::wrapped_asset::{Self}; + use wormhole::bytes32::{Self}; + use wormhole::external_address::{Self}; + use wormhole::wormhole_scenario::{parse_and_verify_vaa}; + + use token_bridge::version_control::{V__0_2_0 as V__CURRENT}; + + use coins::coin::{COIN}; + +// +------------------------------------------------------------------------------+ +// | Wormhole VAA v1 | nonce: 1 | time: 1 | +// | guardian set #0 | #22080291 | consistency: 0 | +// |------------------------------------------------------------------------------| +// | Signature: | +// | #0: 80366065746148420220f25a6275097370e8db40984529a6676b7a5fc9fe... | +// |------------------------------------------------------------------------------| +// | Emitter: 0x00000000000000000000000000000000deadbeef (Ethereum) | +// |==============================================================================| +// | Token attestation | +// | decimals: 12 | +// | Token: 0x00000000000000000000000000000000beefface (Ethereum) | +// | Symbol: BEEF | +// | Name: Beef face Token | +// +------------------------------------------------------------------------------+ + const VAA: vector = + x"0100000000010080366065746148420220f25a6275097370e8db40984529a6676b7a5fc9feb11755ec49ca626b858ddfde88d15601f85ab7683c5f161413b0412143241c700aff010000000100000001000200000000000000000000000000000000000000000000000000000000deadbeef000000000150eb23000200000000000000000000000000000000000000000000000000000000beefface00020c424545460000000000000000000000000000000000000000000000000000000042656566206661636520546f6b656e0000000000000000000000000000000000"; + +// +------------------------------------------------------------------------------+ +// | Wormhole VAA v1 | nonce: 69 | time: 0 | +// | guardian set #0 | #1 | consistency: 15 | +// |------------------------------------------------------------------------------| +// | Signature: | +// | #0: b0571650590e147fce4eb60105e0463522c1244a97bd5dcb365d3e7bc7f3... | +// |------------------------------------------------------------------------------| +// | Emitter: 0x00000000000000000000000000000000deadbeef (Ethereum) | +// |==============================================================================| +// | Token attestation | +// | decimals: 12 | +// | Token: 0x00000000000000000000000000000000beefface (Ethereum) | +// | Symbol: BEEF??? and profit | +// | Name: Beef face Token??? and profit | +// +------------------------------------------------------------------------------+ + const UPDATED_VAA: vector = + x"0100000000010062f4dcd21bbbc4af8b8baaa2da3a0b168efc4c975de5b828c7a3c710b67a0a0d476d10a74aba7a7867866daf97d1372d8e6ee62ccc5ae522e3e603c67fa23787000000000000000045000200000000000000000000000000000000000000000000000000000000deadbeef00000000000000010f0200000000000000000000000000000000000000000000000000000000beefface00020c424545463f3f3f20616e642070726f666974000000000000000000000000000042656566206661636520546f6b656e3f3f3f20616e642070726f666974000000"; + + + #[test] + public fun test_complete_and_update_attestation() { + let (caller, coin_deployer) = two_people(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register foreign emitter on chain ID == 2. + let expected_source_chain = 2; + register_dummy_emitter(scenario, expected_source_chain); + + // Ignore effects. Make sure `coin_deployer` receives + // `WrappedAssetSetup`. + test_scenario::next_tx(scenario, coin_deployer); + + // Publish coin. + coins::coin::init_test_only(test_scenario::ctx(scenario)); + + // Ignore effects. + test_scenario::next_tx(scenario, coin_deployer); + + let wrapped_asset_setup = + test_scenario::take_from_address>( + scenario, + coin_deployer + ); + + let token_bridge_state = take_state(scenario); + + let verified_vaa = parse_and_verify_vaa(scenario, VAA); + let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa); + + let coin_meta = test_scenario::take_shared(scenario); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + create_wrapped::complete_registration( + &mut token_bridge_state, + &mut coin_meta, + wrapped_asset_setup, + test_scenario::take_from_address( + scenario, + coin_deployer + ), + msg + ); + + // Check registry. + { + let verified = state::verified_asset(&token_bridge_state); + assert!(token_bridge::token_registry::is_wrapped(&verified), 0); + + let registry = state::borrow_token_registry(&token_bridge_state); + let asset = + token_registry::borrow_wrapped(registry); + assert!(wrapped_asset::total_supply(asset) == 0, 0); + + // Decimals are capped for this wrapped asset. + assert!(coin::get_decimals(&coin_meta) == 8, 0); + + // Check metadata against asset metadata. + let info = wrapped_asset::info(asset); + assert!(wrapped_asset::token_chain(info) == 2, 0); + assert!(wrapped_asset::token_address(info) == external_address::new(bytes32::from_bytes(x"00000000000000000000000000000000beefface")), 0); + assert!( + wrapped_asset::native_decimals(info) == 12, + 0 + ); + assert!(coin::get_symbol(&coin_meta) == std::ascii::string(b"BEEF"), 0); + assert!(coin::get_name(&coin_meta) == std::string::utf8(b"Beef face Token"), 0); + }; + + let verified_vaa = + parse_and_verify_vaa(scenario, UPDATED_VAA); + let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + // Now update metadata. + create_wrapped::update_attestation(&mut token_bridge_state, &mut coin_meta, msg); + + // Check updated name and symbol. + assert!( + coin::get_name(&coin_meta) == std::string::utf8(b"Beef face Token??? and profit"), + 0 + ); + assert!( + coin::get_symbol(&coin_meta) == std::ascii::string(b"BEEF??? and profit"), + 0 + ); + + // Clean up. + return_state(token_bridge_state); + test_scenario::return_shared(coin_meta); + + + // Done. + test_scenario::end(my_scenario); + } +} diff --git a/sui/examples/coins/sources/coin_10.move b/sui/examples/coins/sources/coin_10.move new file mode 100644 index 000000000..2c98b8793 --- /dev/null +++ b/sui/examples/coins/sources/coin_10.move @@ -0,0 +1,72 @@ +module coins::coin_10 { + use std::option; + use sui::coin::{Self, TreasuryCap, CoinMetadata}; + use sui::transfer; + use sui::tx_context::{Self, TxContext}; + + /// The type identifier of coin. The coin will have a type + /// tag of kind: `Coin` + /// Make sure that the name of the type matches the module's name. + struct COIN_10 has drop {} + + /// Module initializer is called once on module publish. A treasury + /// cap is sent to the publisher, who then controls minting and burning + fun init(witness: COIN_10, ctx: &mut TxContext) { + let (treasury, metadata) = create_coin(witness, ctx); + transfer::public_freeze_object(metadata); + transfer::public_transfer(treasury, tx_context::sender(ctx)); + } + + fun create_coin( + witness: COIN_10, + ctx: &mut TxContext + ): (TreasuryCap, CoinMetadata) { + coin::create_currency( + witness, + 10, // decimals + b"COIN_10", // symbol + b"10-Decimal Coin", // name + b"", // description + option::none(), // icon_url + ctx + ) + } + + #[test_only] + public fun create_coin_test_only( + ctx: &mut TxContext + ): (TreasuryCap, CoinMetadata) { + create_coin(COIN_10 {}, ctx) + } + + #[test_only] + public fun init_test_only(ctx: &mut TxContext) { + init(COIN_10 {}, ctx) + } +} + +#[test_only] +module coins::coin_10_tests { + use sui::test_scenario::{Self}; + + use coins::coin_10::{Self}; + + #[test] + public fun init_test() { + let my_scenario = test_scenario::begin(@0x0); + let scenario = &mut my_scenario; + let creator = @0xDEADBEEF; + + // Proceed. + test_scenario::next_tx(scenario, creator); + + // Init. + coin_10::init_test_only(test_scenario::ctx(scenario)); + + // Proceed. + test_scenario::next_tx(scenario, creator); + + // Done. + test_scenario::end(my_scenario); + } +} diff --git a/sui/examples/coins/sources/coin_8.move b/sui/examples/coins/sources/coin_8.move new file mode 100644 index 000000000..0edd76160 --- /dev/null +++ b/sui/examples/coins/sources/coin_8.move @@ -0,0 +1,72 @@ +module coins::coin_8 { + use std::option::{Self}; + use sui::coin::{Self, TreasuryCap, CoinMetadata}; + use sui::transfer::{Self}; + use sui::tx_context::{Self, TxContext}; + + /// The type identifier of coin. The coin will have a type + /// tag of kind: `Coin` + /// Make sure that the name of the type matches the module's name. + struct COIN_8 has drop {} + + /// Module initializer is called once on module publish. A treasury + /// cap is sent to the publisher, who then controls minting and burning + fun init(witness: COIN_8, ctx: &mut TxContext) { + let (treasury, metadata) = create_coin(witness, ctx); + transfer::public_freeze_object(metadata); + transfer::public_transfer(treasury, tx_context::sender(ctx)); + } + + fun create_coin( + witness: COIN_8, + ctx: &mut TxContext + ): (TreasuryCap, CoinMetadata) { + coin::create_currency( + witness, + 8, // decimals + b"COIN_8", // symbol + b"8-Decimal Coin", // name + b"", // description + option::none(), // icon_url + ctx + ) + } + + #[test_only] + public fun create_coin_test_only( + ctx: &mut TxContext + ): (TreasuryCap, CoinMetadata) { + create_coin(COIN_8 {}, ctx) + } + + #[test_only] + public fun init_test_only(ctx: &mut TxContext) { + init(COIN_8 {}, ctx) + } +} + +#[test_only] +module coins::coin_8_tests { + use sui::test_scenario::{Self}; + + use coins::coin_8::{Self}; + + #[test] + public fun init_test() { + let my_scenario = test_scenario::begin(@0x0); + let scenario = &mut my_scenario; + let creator = @0xDEADBEEF; + + // Proceed. + test_scenario::next_tx(scenario, creator); + + // Init. + coin_8::init_test_only(test_scenario::ctx(scenario)); + + // Proceed. + test_scenario::next_tx(scenario, creator); + + // Done. + test_scenario::end(my_scenario); + } +} diff --git a/sui/examples/core_messages/Makefile b/sui/examples/core_messages/Makefile new file mode 100644 index 000000000..210a28de7 --- /dev/null +++ b/sui/examples/core_messages/Makefile @@ -0,0 +1,20 @@ +-include ../../../Makefile.help + +.PHONY: artifacts +artifacts: clean + +.PHONY: clean +# Clean build artifacts +clean: + rm -rf build + +.PHONY: build +# Build contract +build: + sui move build + +.PHONY: test +# Run tests +test: + sui move build -d || exit $? + sui move test -t 1 diff --git a/sui/examples/core_messages/Move.devnet.toml b/sui/examples/core_messages/Move.devnet.toml new file mode 100644 index 000000000..b2eb46e86 --- /dev/null +++ b/sui/examples/core_messages/Move.devnet.toml @@ -0,0 +1,14 @@ +[package] +name = "CoreMessages" +version = "1.0.0" + +[dependencies.Sui] +git = "https://github.com/MystenLabs/sui.git" +subdir = "crates/sui-framework/packages/sui-framework" +rev = "09b2081498366df936abae26eea4b2d5cafb2788" + +[dependencies.Wormhole] +local = "../../wormhole" + +[addresses] +core_messages = "_" diff --git a/sui/examples/core_messages/Move.lock b/sui/examples/core_messages/Move.lock new file mode 100644 index 000000000..e1778e108 --- /dev/null +++ b/sui/examples/core_messages/Move.lock @@ -0,0 +1,32 @@ +# @generated by Move, please check-in and do not edit manually. + +[move] +version = 0 + +dependencies = [ + { name = "Sui" }, +] + +dev-dependencies = [ + { name = "Wormhole" }, +] + +[[move.package]] +name = "MoveStdlib" +source = { git = "https://github.com/MystenLabs/sui.git", rev = "09b2081498366df936abae26eea4b2d5cafb2788", subdir = "crates/sui-framework/packages/move-stdlib" } + +[[move.package]] +name = "Sui" +source = { git = "https://github.com/MystenLabs/sui.git", rev = "09b2081498366df936abae26eea4b2d5cafb2788", subdir = "crates/sui-framework/packages/sui-framework" } + +dependencies = [ + { name = "MoveStdlib" }, +] + +[[move.package]] +name = "Wormhole" +source = { local = "../../wormhole" } + +dependencies = [ + { name = "Sui" }, +] diff --git a/sui/examples/core_messages/Move.toml b/sui/examples/core_messages/Move.toml new file mode 100644 index 000000000..5c720855a --- /dev/null +++ b/sui/examples/core_messages/Move.toml @@ -0,0 +1,21 @@ +[package] +name = "CoreMessages" +version = "1.0.0" + +[dependencies.Sui] +git = "https://github.com/MystenLabs/sui.git" +subdir = "crates/sui-framework/packages/sui-framework" +rev = "09b2081498366df936abae26eea4b2d5cafb2788" + +[dependencies.Wormhole] +local = "../../wormhole" + +[addresses] +core_messages = "_" + +[dev-dependencies.Wormhole] +local = "../../wormhole" + +[dev-addresses] +wormhole = "0x100" +core_messages = "0x169" diff --git a/sui/examples/core_messages/sources/sender.move b/sui/examples/core_messages/sources/sender.move new file mode 100644 index 000000000..79883e64e --- /dev/null +++ b/sui/examples/core_messages/sources/sender.move @@ -0,0 +1,149 @@ +/// A simple contracts that demonstrates how to send messages with wormhole. +module core_messages::sender { + use sui::clock::{Clock}; + use sui::coin::{Self}; + use sui::object::{Self, UID}; + use sui::transfer::{Self}; + use sui::tx_context::{TxContext}; + use wormhole::emitter::{Self, EmitterCap}; + use wormhole::state::{State as WormholeState}; + + struct State has key, store { + id: UID, + emitter_cap: EmitterCap, + } + + /// Register ourselves as a wormhole emitter. This gives back an + /// `EmitterCap` which will be required to send messages through + /// wormhole. + public fun init_with_params( + wormhole_state: &WormholeState, + ctx: &mut TxContext + ) { + transfer::share_object( + State { + id: object::new(ctx), + emitter_cap: emitter::new(wormhole_state, ctx) + } + ); + } + + public fun send_message_entry( + state: &mut State, + wormhole_state: &mut WormholeState, + payload: vector, + the_clock: &Clock, + ctx: &mut TxContext + ) { + send_message( + state, + wormhole_state, + payload, + the_clock, + ctx + ); + } + + /// NOTE: This is NOT the proper way of using the `prepare_message` and + /// `publish_message` workflow. This example app is meant for testing for + /// observing Wormhole messages via the guardian. + /// + /// See `publish_message` module for more info. + public fun send_message( + state: &mut State, + wormhole_state: &mut WormholeState, + payload: vector, + the_clock: &Clock, + ctx: &mut TxContext + ): u64 { + use wormhole::publish_message::{prepare_message, publish_message}; + + // NOTE AGAIN: Integrators should NEVER call this within their contract. + publish_message( + wormhole_state, + coin::zero(ctx), + prepare_message( + &mut state.emitter_cap, + 0, // Set nonce to 0, intended for batch VAAs. + payload + ), + the_clock + ) + } +} + +#[test_only] +module core_messages::sender_test { + use sui::test_scenario::{Self}; + use wormhole::wormhole_scenario::{ + return_clock, + return_state, + set_up_wormhole, + take_clock, + take_state, + two_people, + }; + + use core_messages::sender::{ + State, + init_with_params, + send_message, + }; + + #[test] + public fun test_send_message() { + let (user, admin) = two_people(); + let my_scenario = test_scenario::begin(admin); + let scenario = &mut my_scenario; + + // Initialize Wormhole. + let wormhole_message_fee = 0; + set_up_wormhole(scenario, wormhole_message_fee); + + // Initialize sender module. + test_scenario::next_tx(scenario, admin); + { + let wormhole_state = take_state(scenario); + init_with_params(&mut wormhole_state, test_scenario::ctx(scenario)); + return_state(wormhole_state); + }; + + // Send message as an ordinary user. + test_scenario::next_tx(scenario, user); + { + let state = test_scenario::take_shared(scenario); + let wormhole_state = take_state(scenario); + let the_clock = take_clock(scenario); + + let first_message_sequence = send_message( + &mut state, + &mut wormhole_state, + b"Hello", + &the_clock, + test_scenario::ctx(scenario) + ); + assert!(first_message_sequence == 0, 0); + + let second_message_sequence = send_message( + &mut state, + &mut wormhole_state, + b"World", + &the_clock, + test_scenario::ctx(scenario) + ); + assert!(second_message_sequence == 1, 0); + + // Clean up. + test_scenario::return_shared(state); + return_state(wormhole_state); + return_clock(the_clock); + }; + + // Check effects. + let effects = test_scenario::next_tx(scenario, user); + assert!(test_scenario::num_user_events(&effects) == 2, 0); + + // End test. + test_scenario::end(my_scenario); + } +} diff --git a/sui/examples/templates/README.md b/sui/examples/templates/README.md new file mode 100644 index 000000000..41a8dbd90 --- /dev/null +++ b/sui/examples/templates/README.md @@ -0,0 +1,3 @@ +# Templates + +This directory contains templates for Sui contracts. These templates aren't fully functional contracts and require substitution of variables prior to deployment. For example, the `wrapped_coin` template requires the version control struct name as well as the decimals of the wrapped token. diff --git a/sui/examples/templates/wrapped_coin/Move.toml b/sui/examples/templates/wrapped_coin/Move.toml new file mode 100644 index 000000000..b28699c34 --- /dev/null +++ b/sui/examples/templates/wrapped_coin/Move.toml @@ -0,0 +1,19 @@ +[package] +name = "WrappedCoin" +version = "0.0.1" + +[dependencies.Sui] +git = "https://github.com/MystenLabs/sui.git" +subdir = "crates/sui-framework/packages/sui-framework" +rev = "09b2081498366df936abae26eea4b2d5cafb2788" + +[dependencies.Wormhole] +local = "../../wormhole" + +[dependencies.TokenBridge] +local = "../../token_bridge" + +[addresses] +wormhole = "_" +token_bridge = "_" +wrapped_coin = "0x0" diff --git a/sui/examples/templates/wrapped_coin/sources/coin.move b/sui/examples/templates/wrapped_coin/sources/coin.move new file mode 100644 index 000000000..313b9ba91 --- /dev/null +++ b/sui/examples/templates/wrapped_coin/sources/coin.move @@ -0,0 +1,21 @@ +module wrapped_coin::coin { + use sui::transfer::{Self}; + use sui::tx_context::{Self, TxContext}; + + use token_bridge::create_wrapped::{Self}; + + struct COIN has drop {} + + fun init(witness: COIN, ctx: &mut TxContext) { + use token_bridge::version_control::{{{VERSION}}}; + + transfer::public_transfer( + create_wrapped::prepare_registration( + witness, + {{DECIMALS}}, + ctx + ), + tx_context::sender(ctx) + ); + } +} diff --git a/sui/scripts/create_wrapped.sh b/sui/scripts/create_wrapped.sh deleted file mode 100755 index 72ed42875..000000000 --- a/sui/scripts/create_wrapped.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -f - -set -euo pipefail - -cd "$(dirname "$0")" - -. ../env.sh - -echo "Creating wrapped asset..." -echo "$COIN_PACKAGE::coin_witness::COIN_WITNESS" - -sui client call --function register_wrapped_coin \ - --module wrapped --package $TOKEN_PACKAGE --gas-budget 20000 \ - --args "$WORM_STATE" "$TOKEN_STATE" "$NEW_WRAPPED_COIN" \ - --type-args "$COIN_PACKAGE::coin_witness::COIN_WITNESS" diff --git a/sui/scripts/deploy.sh b/sui/scripts/deploy.sh index 567508bd9..d572f8cbf 100755 --- a/sui/scripts/deploy.sh +++ b/sui/scripts/deploy.sh @@ -1,54 +1,119 @@ -#!/bin/bash -f +#!/usr/bin/env bash set -euo pipefail -cd "$(dirname "$0")"/.. +# Help message +function usage() { +cat <&2 +Deploy and initialize Sui core bridge and token bridge contracts to the +specified network. Additionally deploys an example messaging contract in +devnet. -#Transaction Kind : Publish -#----- Transaction Effects ---- -#Status : Success -#Created Objects: -# - ID: 0x069b6d8ea50a0b0756518cb08ddbbad2babf8ae0 <= STATE , Owner: Account Address ( 0xe6a09658743da40b0f48c4da1f3fa0d34797d0d3 <= OWNER ) -# - ID: 0x73fc05ae6f172f90b12a98cf3ad0b669d6b70e5b <= PACKAGE , Owner: Immutable + Usage: $(basename "$0") [options] -cd wormhole -sed -i -e 's/wormhole = .*/wormhole = "0x0"/' Move.toml -make build -sui client publish --gas-budget 10000 | tee publish.log -grep ID: publish.log | head -2 > ids.log + Positional args: + Network to deploy to (devnet, testnet, mainnet) -WORM_PACKAGE=$(grep "Immutable" ids.log | sed -e 's/^.*: \(.*\) ,.*/\1/') -sed -i -e "s/wormhole = .*/wormhole = \"$WORM_PACKAGE\"/" Move.toml -WORM_DEPLOYER_CAPABILITY=$(grep -v "Immutable" ids.log | sed -e 's/^.*: \(.*\) ,.*/\1/') -WORM_OWNER=$(grep -v "Immutable" ids.log | sed -e 's/^.*( \(.*\) )/\1/') + Options: + -k, --private-key Use given key to sign transactions + -h, --help Show this help message +EOF +exit 1 +} -cd ../token_bridge -sed -i -e 's/token_bridge = .*/token_bridge = "0x0"/' Move.toml -make build -sui client publish --gas-budget 10000 | tee publish.log -grep ID: publish.log | head -2 > ids.log +# If positional args are missing, print help message and exit +if [ $# -lt 1 ]; then + usage +fi -TOKEN_PACKAGE=$(grep "Immutable" ids.log | sed -e 's/^.*: \(.*\) ,.*/\1/') -sed -i -e "s/token_bridge = .*/token_bridge = \"$TOKEN_PACKAGE\"/" Move.toml -TOKEN_DEPLOYER_CAPABILITY=$(grep -v "Immutable" ids.log | sed -e 's/^.*: \(.*\) ,.*/\1/') -TOKEN_OWNER=$(grep -v "Immutable" ids.log | sed -e 's/^.*( \(.*\) )/\1/') +# Default values +PRIVATE_KEY_ARG= -sui client call --function init_and_share_state --module state --package $WORM_PACKAGE --gas-budget 20000 --args \"$WORM_DEPLOYER_CAPABILITY\" 0 0 "[190,250,66,157,87,205,24,183,248,164,217,26,45,169,171,74,240,93,15,190]" "[[190,250,66,157,87,205,24,183,248,164,217,26,45,169,171,74,240,93,15,190]]" | tee wormhole.log -WORM_STATE=$(grep Shared wormhole.log | head -1 | sed -e 's/^.*: \(.*\) ,.*/\1/') +# Set network +NETWORK=$1 || usage +shift -sui client call --function get_new_emitter --module wormhole --package $WORM_PACKAGE --gas-budget 20000 --args \"$WORM_STATE\" | tee emitter.log -TOKEN_EMITTER_CAPABILITY=$(grep ID: emitter.log | head -1 | sed -e 's/^.*: \(.*\) ,.*/\1/') +# Set guardian address +if [ "$NETWORK" = mainnet ]; then + echo "Mainnet not supported yet" + exit 1 +elif [ "$NETWORK" = testnet ]; then + echo "Testnet not supported yet" + exit 1 +elif [ "$NETWORK" = devnet ]; then + GUARDIAN_ADDR=befa429d57cd18b7f8a4d91a2da9ab4af05d0fbe +else + usage +fi -sui client call --function init_and_share_state --module bridge_state --package $TOKEN_PACKAGE --gas-budget 20000 --args "$TOKEN_DEPLOYER_CAPABILITY" "$TOKEN_EMITTER_CAPABILITY" | tee token.log -TOKEN_STATE=$(grep Shared token.log | head -1 | sed -e 's/^.*: \(.*\) ,.*/\1/') +# Parse short/long flags +while [[ $# -gt 0 ]]; do + case "$1" in + -k|--private-key) + if [[ ! -z "$2" ]]; then + PRIVATE_KEY_ARG="-k $2" + fi + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" + usage + exit 1 + ;; + esac +done -{ echo "export WORM_PACKAGE=$WORM_PACKAGE"; - echo "export WORM_DEPLOYER_CAPABILITY=$WORM_DEPLOYER_CAPABILITY"; - echo "export WORM_OWNER=$WORM_OWNER"; - echo "export TOKEN_PACKAGE=$TOKEN_PACKAGE"; - echo "export TOKEN_DEPLOYER_CAPABILITY=$TOKEN_DEPLOYER_CAPABILITY"; - echo "export TOKEN_OWNER=$TOKEN_OWNER"; - echo "export WORM_STATE=$WORM_STATE"; - echo "export TOKEN_EMITTER_CAPABILITY=$TOKEN_EMITTER_CAPABILITY"; - echo "export TOKEN_STATE=$TOKEN_STATE"; -} > ../env.sh +# Assumes this script is in a sibling directory to contract dirs +DIRNAME=$(dirname "$0") +WORMHOLE_PATH=$(realpath "$DIRNAME"/../wormhole) +TOKEN_BRIDGE_PATH=$(realpath "$DIRNAME"/../token_bridge) +EXAMPLE_APP_PATH=$(realpath "$DIRNAME"/../examples/core_messages) +EXAMPLE_COIN_PATH=$(realpath "$DIRNAME"/../examples/coins) + +echo -e "[1/4] Publishing core bridge contracts..." +WORMHOLE_PUBLISH_OUTPUT=$($(echo worm sui deploy "$WORMHOLE_PATH" -n "$NETWORK" "$PRIVATE_KEY_ARG")) +echo "$WORMHOLE_PUBLISH_OUTPUT" + +echo -e "\n[2/4] Initializing core bridge..." +WORMHOLE_PACKAGE_ID=$(echo "$WORMHOLE_PUBLISH_OUTPUT" | grep -oP 'Published to +\K.*') +WORMHOLE_INIT_OUTPUT=$($(echo worm sui init-wormhole -n "$NETWORK" --initial-guardian "$GUARDIAN_ADDR" -p "$WORMHOLE_PACKAGE_ID" "$PRIVATE_KEY_ARG")) +WORMHOLE_STATE_OBJECT_ID=$(echo "$WORMHOLE_INIT_OUTPUT" | grep -oP 'Wormhole state object ID +\K.*') +echo "$WORMHOLE_INIT_OUTPUT" + +echo -e "\n[3/4] Publishing token bridge contracts..." +TOKEN_BRIDGE_PUBLISH_OUTPUT=$($(echo worm sui deploy "$TOKEN_BRIDGE_PATH" -n "$NETWORK" "$PRIVATE_KEY_ARG")) +echo "$TOKEN_BRIDGE_PUBLISH_OUTPUT" + +echo -e "\n[4/4] Initializing token bridge..." +TOKEN_BRIDGE_PACKAGE_ID=$(echo "$TOKEN_BRIDGE_PUBLISH_OUTPUT" | grep -oP 'Published to +\K.*') +TOKEN_BRIDGE_INIT_OUTPUT=$($(echo worm sui init-token-bridge -n "$NETWORK" -p "$TOKEN_BRIDGE_PACKAGE_ID" -w "$WORMHOLE_STATE_OBJECT_ID" "$PRIVATE_KEY_ARG")) +TOKEN_BRIDGE_STATE_OBJECT_ID=$(echo "$TOKEN_BRIDGE_INIT_OUTPUT" | grep -oP 'Token bridge state object ID +\K.*') +echo "$TOKEN_BRIDGE_INIT_OUTPUT" + +if [ "$NETWORK" = devnet ]; then + echo -e "\n[+1/2] Deploying and initializing example app..." + EXAMPLE_APP_PUBLISH_OUTPUT=$($(echo worm sui deploy "$EXAMPLE_APP_PATH" -n "$NETWORK" "$PRIVATE_KEY_ARG")) + EXAMPLE_APP_PACKAGE_ID=$(echo "$EXAMPLE_APP_PUBLISH_OUTPUT" | grep -oP 'Published to +\K.*') + echo "$EXAMPLE_APP_PUBLISH_OUTPUT" + + EXAMPLE_INIT_OUTPUT=$($(echo worm sui init-example-message-app -n "$NETWORK" -p "$EXAMPLE_APP_PACKAGE_ID" -w "$WORMHOLE_STATE_OBJECT_ID" "$PRIVATE_KEY_ARG")) + EXAMPLE_APP_STATE_OBJECT_ID=$(echo "$EXAMPLE_INIT_OUTPUT" | grep -oP 'Example app state object ID +\K.*') + echo "$EXAMPLE_INIT_OUTPUT" + + echo -e "\n[+2/2] Deploying example coins..." + EXAMPLE_COIN_PUBLISH_OUTPUT=$($(echo worm sui deploy "$EXAMPLE_COIN_PATH" -n "$NETWORK" "$PRIVATE_KEY_ARG")) + echo "$EXAMPLE_COIN_PUBLISH_OUTPUT" + + echo -e "\nWormhole package ID: $WORMHOLE_PACKAGE_ID" + echo "Token bridge package ID: $TOKEN_BRIDGE_PACKAGE_ID" + echo "Wormhole state object ID: $WORMHOLE_STATE_OBJECT_ID" + echo "Token bridge state object ID: $TOKEN_BRIDGE_STATE_OBJECT_ID" + + echo -e "\nPublish message command:" worm sui publish-example-message -n devnet -p "$EXAMPLE_APP_PACKAGE_ID" -s "$EXAMPLE_APP_STATE_OBJECT_ID" -w "$WORMHOLE_STATE_OBJECT_ID" -m "hello" "$PRIVATE_KEY_ARG" +fi + +echo -e "\nDeployments successful!" diff --git a/sui/scripts/deploy_coin.sh b/sui/scripts/deploy_coin.sh deleted file mode 100755 index e05acc9c1..000000000 --- a/sui/scripts/deploy_coin.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash -f - -set -euo pipefail - -cd "$(dirname "$0")"/.. - -. env.sh - -sui client publish --gas-budget 20000 --path coin | tee publish.log -grep ID: publish.log | head -2 > ids.log -NEW_WRAPPED_COIN=$(grep "Account Address" ids.log | sed -e 's/^.*: \(.*\) ,.*/\1/') -COIN_PACKAGE=$(grep "Immutable" ids.log | sed -e 's/^.*: \(.*\) ,.*/\1/') -echo "export NEW_WRAPPED_COIN=$NEW_WRAPPED_COIN" >> env.sh -echo "export COIN_PACKAGE=$COIN_PACKAGE" >> env.sh diff --git a/sui/scripts/faucet.sh b/sui/scripts/faucet.sh deleted file mode 100755 index d232ebf21..000000000 --- a/sui/scripts/faucet.sh +++ /dev/null @@ -1,2 +0,0 @@ -# -curl -X POST -d '{"FixedAmountRequest":{"recipient": "'"$1"'"}}' -H 'Content-Type: application/json' http://127.0.0.1:5003/gas \ No newline at end of file diff --git a/sui/scripts/funder.sh b/sui/scripts/funder.sh deleted file mode 100755 index 7c5dc6478..000000000 --- a/sui/scripts/funder.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -f - -sui client transfer-sui --to 0x2acab6bb0e4722e528291bc6ca4f097e18ce9331 --sui-coin-object-id `sui client objects | grep sui::SUI | tail -1 | sed -e 's/|.*//'` --gas-budget 10000 diff --git a/sui/scripts/generate_account.ts b/sui/scripts/generate_account.ts deleted file mode 100644 index ab7d8d052..000000000 --- a/sui/scripts/generate_account.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Ed25519Keypair, JsonRpcProvider, RawSigner } from '@mysten/sui.js'; -// Generate a new Secp256k1 Keypair -const keypair = new Ed25519Keypair(); - -const signer = new RawSigner( - keypair, - new JsonRpcProvider('https://gateway.devnet.sui.io:443') -); - -console.log(keypair) \ No newline at end of file diff --git a/sui/scripts/get_new_emitter.sh b/sui/scripts/get_new_emitter.sh deleted file mode 100644 index ab2bec07d..000000000 --- a/sui/scripts/get_new_emitter.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -f - -. env.sh - -sui client call --function get_new_emitter --module wormhole --package $WORM_PACKAGE --gas-budget 20000 --args \"$WORM_STATE\" diff --git a/sui/scripts/import_account_and_deploy.sh b/sui/scripts/import_account_and_deploy.sh deleted file mode 100755 index ded546a17..000000000 --- a/sui/scripts/import_account_and_deploy.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash - -# This dev script imports and funds an account, following the steps in -# `sui/NOTES.md`. It also deploys the core/token bridge contracts. - -# Remove directory for idempotency -rm -rf $HOME/.sui - -# Import key so we have a deterministic address and make it the default account -sui keytool import "daughter exclude wheat pudding police weapon giggle taste space whip satoshi occur" ed25519 -sui client << EOF -y -http://localhost:9000 -dev -0 -EOF -sed -i -e 's/active_address.*/active_address: "0x13b3cb89cf3226d3b860294fc75dc6c91f0c5ecf"/' ~/.sui/sui_config/client.yaml - -# Fund account -kubectl exec -it sui-0 -c sui-node -- /tmp/funder.sh - -# Deploy contracts -DIR_PATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" -$DIR_PATH/deploy.sh diff --git a/sui/scripts/init_tokenbridge.sh b/sui/scripts/init_tokenbridge.sh deleted file mode 100644 index e3be5a796..000000000 --- a/sui/scripts/init_tokenbridge.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -f - -. env.sh - -sui client call --function init_and_share_state --module bridge_state --package $TOKEN_PACKAGE --gas-budget 20000 --args \"$TOKEN_STATE\" \"$EMITTER_CAP\" diff --git a/sui/scripts/init_wormhole.sh b/sui/scripts/init_wormhole.sh deleted file mode 100755 index ec3bc74ce..000000000 --- a/sui/scripts/init_wormhole.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -f - -. env.sh - -sui client call --function init_and_share_state --module state --package $WORM_PACKAGE --gas-budget 20000 --args \"$WORM_STATE\" 0 0 [190,250,66,157,87,205,24,183,248,164,217,26,45,169,171,74,240,93,15,190] [190,250,66,157,87,205,24,183,248,164,217,26,45,169,171,74,240,93,15,190] diff --git a/sui/scripts/node_builder.sh b/sui/scripts/node_builder.sh index 029ba3e21..fc1f5fadb 100755 --- a/sui/scripts/node_builder.sh +++ b/sui/scripts/node_builder.sh @@ -4,6 +4,8 @@ source $HOME/.cargo/env git clone https://github.com/MystenLabs/sui.git --branch devnet cd sui +# Corresponds to https://github.com/MystenLabs/sui/releases/tag/testnet-1.0.0 +git reset --hard 09b2081498366df936abae26eea4b2d5cafb2788 cargo --locked install --path crates/sui cargo --locked install --path crates/sui-faucet diff --git a/sui/scripts/package-lock.json b/sui/scripts/package-lock.json deleted file mode 100644 index 00a395261..000000000 --- a/sui/scripts/package-lock.json +++ /dev/null @@ -1,804 +0,0 @@ -{ - "name": "sui-scripts", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "sui-scripts", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "@mysten/sui.js": "^0.10.0", - "axios": "^1.0.0", - "node-fetch": "^3.2.10" - } - }, - "node_modules/@mysten/sui.js": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@mysten/sui.js/-/sui.js-0.10.0.tgz", - "integrity": "sha512-z9K34+jQBzeUCTcroTExBzYPCNHohyuz1sR85HYkbymDqdRCTi1IcfBzZDinWibZlk0sZhJtjnozxHklsPvYLQ==", - "dependencies": { - "bn.js": "^5.2.0", - "buffer": "^6.0.3", - "cross-fetch": "^3.1.5", - "jayson": "^3.6.6", - "js-sha3": "^0.8.0", - "lossless-json": "^1.0.5", - "tweetnacl": "^1.0.3" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/node": { - "version": "12.20.55", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", - "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" - }, - "node_modules/@types/ws": { - "version": "7.4.7", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", - "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/axios": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.0.0.tgz", - "integrity": "sha512-SsHsGFN1qNPFT5QhSoSD37SHDfGyLSW5AESmyLk2JeCMHv5g0I9g0Hz/zQHx2KNe0jGXh2q2hAm7OdkXm360CA==", - "dependencies": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" - }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "node_modules/cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", - "dependencies": { - "node-fetch": "2.6.7" - } - }, - "node_modules/cross-fetch/node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", - "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==", - "engines": { - "node": ">= 12" - } - }, - "node_modules/delay": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz", - "integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" - }, - "node_modules/es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", - "dependencies": { - "es6-promise": "^4.0.3" - } - }, - "node_modules/eyes": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", - "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", - "engines": { - "node": "> 0.1.90" - } - }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/isomorphic-ws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", - "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", - "peerDependencies": { - "ws": "*" - } - }, - "node_modules/jayson": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/jayson/-/jayson-3.7.0.tgz", - "integrity": "sha512-tfy39KJMrrXJ+mFcMpxwBvFDetS8LAID93+rycFglIQM4kl3uNR3W4lBLE/FFhsoUCEox5Dt2adVpDm/XtebbQ==", - "dependencies": { - "@types/connect": "^3.4.33", - "@types/node": "^12.12.54", - "@types/ws": "^7.4.4", - "commander": "^2.20.3", - "delay": "^5.0.0", - "es6-promisify": "^5.0.0", - "eyes": "^0.1.8", - "isomorphic-ws": "^4.0.1", - "json-stringify-safe": "^5.0.1", - "JSONStream": "^1.3.5", - "lodash": "^4.17.20", - "uuid": "^8.3.2", - "ws": "^7.4.5" - }, - "bin": { - "jayson": "bin/jayson.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/js-sha3": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", - "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" - }, - "node_modules/jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", - "engines": [ - "node >= 0.2.0" - ] - }, - "node_modules/JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "dependencies": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - }, - "bin": { - "JSONStream": "bin.js" - }, - "engines": { - "node": "*" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lossless-json": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/lossless-json/-/lossless-json-1.0.5.tgz", - "integrity": "sha512-RicKUuLwZVNZ6ZdJHgIZnSeA05p8qWc5NW0uR96mpPIjN9WDLUg9+kj1esQU1GkPn9iLZVKatSQK5gyiaFHgJA==" - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "3.2.10", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.10.tgz", - "integrity": "sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/web-streams-polyfill": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", - "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - } - }, - "dependencies": { - "@mysten/sui.js": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@mysten/sui.js/-/sui.js-0.10.0.tgz", - "integrity": "sha512-z9K34+jQBzeUCTcroTExBzYPCNHohyuz1sR85HYkbymDqdRCTi1IcfBzZDinWibZlk0sZhJtjnozxHklsPvYLQ==", - "requires": { - "bn.js": "^5.2.0", - "buffer": "^6.0.3", - "cross-fetch": "^3.1.5", - "jayson": "^3.6.6", - "js-sha3": "^0.8.0", - "lossless-json": "^1.0.5", - "tweetnacl": "^1.0.3" - } - }, - "@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "requires": { - "@types/node": "*" - } - }, - "@types/node": { - "version": "12.20.55", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", - "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" - }, - "@types/ws": { - "version": "7.4.7", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", - "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", - "requires": { - "@types/node": "*" - } - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "axios": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.0.0.tgz", - "integrity": "sha512-SsHsGFN1qNPFT5QhSoSD37SHDfGyLSW5AESmyLk2JeCMHv5g0I9g0Hz/zQHx2KNe0jGXh2q2hAm7OdkXm360CA==", - "requires": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, - "bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" - }, - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", - "requires": { - "node-fetch": "2.6.7" - }, - "dependencies": { - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "requires": { - "whatwg-url": "^5.0.0" - } - } - } - }, - "data-uri-to-buffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", - "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==" - }, - "delay": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz", - "integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==" - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" - }, - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" - }, - "es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", - "requires": { - "es6-promise": "^4.0.3" - } - }, - "eyes": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", - "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==" - }, - "fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "requires": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - } - }, - "follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" - }, - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "requires": { - "fetch-blob": "^3.1.2" - } - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" - }, - "isomorphic-ws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", - "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", - "requires": {} - }, - "jayson": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/jayson/-/jayson-3.7.0.tgz", - "integrity": "sha512-tfy39KJMrrXJ+mFcMpxwBvFDetS8LAID93+rycFglIQM4kl3uNR3W4lBLE/FFhsoUCEox5Dt2adVpDm/XtebbQ==", - "requires": { - "@types/connect": "^3.4.33", - "@types/node": "^12.12.54", - "@types/ws": "^7.4.4", - "commander": "^2.20.3", - "delay": "^5.0.0", - "es6-promisify": "^5.0.0", - "eyes": "^0.1.8", - "isomorphic-ws": "^4.0.1", - "json-stringify-safe": "^5.0.1", - "JSONStream": "^1.3.5", - "lodash": "^4.17.20", - "uuid": "^8.3.2", - "ws": "^7.4.5" - } - }, - "js-sha3": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", - "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" - }, - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==" - }, - "JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "lossless-json": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/lossless-json/-/lossless-json-1.0.5.tgz", - "integrity": "sha512-RicKUuLwZVNZ6ZdJHgIZnSeA05p8qWc5NW0uR96mpPIjN9WDLUg9+kj1esQU1GkPn9iLZVKatSQK5gyiaFHgJA==" - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } - }, - "node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==" - }, - "node-fetch": { - "version": "3.2.10", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.10.tgz", - "integrity": "sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==", - "requires": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - } - }, - "proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" - }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" - }, - "web-streams-polyfill": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", - "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==" - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "requires": {} - } - } -} diff --git a/sui/scripts/package.json b/sui/scripts/package.json deleted file mode 100644 index 644e5b0f9..000000000 --- a/sui/scripts/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "@wormhole-foundation/sui-scripts", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "", - "license": "ISC", - "dependencies": { - "@mysten/sui.js": "^0.10.0", - "axios": "^1.0.0", - "node-fetch": "^3.2.10" - } -} diff --git a/sui/scripts/publish_message.sh b/sui/scripts/publish_message.sh deleted file mode 100755 index 7928de7f4..000000000 --- a/sui/scripts/publish_message.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -f - -. env.sh - -sui client call --function publish_message_free --module wormhole --package $WORM_PACKAGE --gas-budget 20000 --args \"$1\" \"$WORM_STATE\" 400 [2] diff --git a/sui/scripts/register_devnet.sh b/sui/scripts/register_devnet.sh new file mode 100755 index 000000000..79df93567 --- /dev/null +++ b/sui/scripts/register_devnet.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +set -e + +DOTENV=$(realpath "$(dirname "$0")"/../.env) +[ -f $DOTENV ] || (echo "$DOTENV does not exist." >&2; exit 1) + +# 1. load variables from .env file +. $DOTENV + +# 2. next we get all the token bridge registration VAAs from the environment +# if a new VAA is added, this will automatically pick it up +VAAS=$(set | grep "REGISTER_.*_TOKEN_BRIDGE_VAA" | grep -v SUI | cut -d '=' -f1) + +# 3. use 'worm' to submit each registration VAA +for VAA in $VAAS +do + VAA=${!VAA} + worm submit $VAA --chain sui --network devnet +done + +echo "Registrations successful." diff --git a/sui/scripts/start_node.sh b/sui/scripts/start_node.sh index b3bc51990..8db6bcf5c 100755 --- a/sui/scripts/start_node.sh +++ b/sui/scripts/start_node.sh @@ -2,14 +2,6 @@ set -x -sui start & -sleep 10 -#sleep infinity -sui client object --id 0x5 -#sui-faucet --host-ip 0.0.0.0& -#sleep 2 -#curl -X POST -d '{"FixedAmountRequest":{"recipient": "'"0x2acab6bb0e4722e528291bc6ca4f097e18ce9331"'"}}' -H 'Content-Type: application/json' http://127.0.0.1:5003/gas -sed -i -e 's/:9000/:9002/' ~/.sui/sui_config/fullnode.yaml -sui-node --config-path ~/.sui/sui_config/fullnode.yaml - -#sleep infinity +sui start >/dev/null 2>&1 & +sleep 5 +sui-faucet --write-ahead-log faucet.log diff --git a/sui/scripts/test.ts b/sui/scripts/test.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/sui/scripts/wait_for_devnet.sh b/sui/scripts/wait_for_devnet.sh new file mode 100755 index 000000000..c0a941081 --- /dev/null +++ b/sui/scripts/wait_for_devnet.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +set -e + +# Wait for sui to start +while [[ "$(curl -X POST -H "Content-Type: application/json" -d '{ "jsonrpc":"2.0", "method":"rpc.discover","id":1 }' -s -o /dev/null -w '%{http_code}' 0.0.0.0:9000/)" != "200" ]]; do sleep 5; done + +# Wait for sui-faucet to start +while [[ "$(curl -s -o /dev/null -w '%{http_code}' 0.0.0.0:5003/)" != "200" ]]; do sleep 5; done diff --git a/sui/testing/.gitignore b/sui/testing/.gitignore new file mode 100644 index 000000000..b552b7394 --- /dev/null +++ b/sui/testing/.gitignore @@ -0,0 +1,4 @@ +node_modules +sui.log.* +./token_bridge/ +./wormhole/ diff --git a/sui/testing/Makefile b/sui/testing/Makefile new file mode 100644 index 000000000..3aa8b2505 --- /dev/null +++ b/sui/testing/Makefile @@ -0,0 +1,13 @@ +-include ../Makefile.help + +.PHONY: clean +clean: + rm -rf node_modules + +node_modules: + npm ci + +.PHONY: test +## Run tests +test: node_modules + bash run_integration_test.sh diff --git a/sui/testing/js/00_environment.ts b/sui/testing/js/00_environment.ts new file mode 100644 index 000000000..3ca09f236 --- /dev/null +++ b/sui/testing/js/00_environment.ts @@ -0,0 +1,78 @@ +import { expect } from "chai"; +import * as mock from "@certusone/wormhole-sdk/lib/cjs/mock"; + +import { + CREATOR_PRIVATE_KEY, + GUARDIAN_PRIVATE_KEY, + RELAYER_PRIVATE_KEY, + WALLET_PRIVATE_KEY, +} from "./helpers/consts"; +import { + Ed25519Keypair, + JsonRpcProvider, + localnetConnection, + RawSigner, +} from "@mysten/sui.js"; + +describe(" 0. Environment", () => { + const provider = new JsonRpcProvider(localnetConnection); + + // User wallet. + const wallet = new RawSigner( + Ed25519Keypair.fromSecretKey(WALLET_PRIVATE_KEY), + provider + ); + + // Relayer wallet. + const relayer = new RawSigner( + Ed25519Keypair.fromSecretKey(RELAYER_PRIVATE_KEY), + provider + ); + + // Deployer wallet. + const creator = new RawSigner( + Ed25519Keypair.fromSecretKey(CREATOR_PRIVATE_KEY), + provider + ); + + describe("Verify Local Validator", () => { + it("Balance", async () => { + // Balance check wallet. + { + const coinData = await wallet + .getAddress() + .then((owner) => + provider + .getCoins({ owner, coinType: "0x2::sui::SUI" }) + .then((result) => result.data) + ); + expect(coinData).has.length(5); + } + + // Balance check relayer. + { + const coinData = await relayer + .getAddress() + .then((owner) => + provider + .getCoins({ owner, coinType: "0x2::sui::SUI" }) + .then((result) => result.data) + ); + expect(coinData).has.length(5); + } + + // Balance check creator. This should only have one gas object at this + // point. + { + const coinData = await creator + .getAddress() + .then((owner) => + provider + .getCoins({ owner, coinType: "0x2::sui::SUI" }) + .then((result) => result.data) + ); + expect(coinData).has.length(1); + } + }); + }); +}); diff --git a/sui/testing/js/01_wormhole.ts b/sui/testing/js/01_wormhole.ts new file mode 100644 index 000000000..ae9a26f08 --- /dev/null +++ b/sui/testing/js/01_wormhole.ts @@ -0,0 +1,109 @@ +import { expect } from "chai"; + +import { WALLET_PRIVATE_KEY, WORMHOLE_STATE_ID } from "./helpers/consts"; +import { + Ed25519Keypair, + JsonRpcProvider, + localnetConnection, + RawSigner, + SUI_CLOCK_OBJECT_ID, + TransactionBlock, +} from "@mysten/sui.js"; +import { getPackageId } from "./helpers/utils"; +import { addPrepareMessageAndPublishMessage } from "./helpers/wormhole/testPublishMessage"; + +describe(" 1. Wormhole", () => { + const provider = new JsonRpcProvider(localnetConnection); + + // User wallet. + const wallet = new RawSigner( + Ed25519Keypair.fromSecretKey(WALLET_PRIVATE_KEY), + provider + ); + + describe("Publish Message", () => { + it("Check `WormholeMessage` Event", async () => { + const wormholePackage = await getPackageId( + wallet.provider, + WORMHOLE_STATE_ID + ); + + const owner = await wallet.getAddress(); + + // Create emitter cap. + const emitterCapId = await (async () => { + const tx = new TransactionBlock(); + const [emitterCap] = tx.moveCall({ + target: `${wormholePackage}::emitter::new`, + arguments: [tx.object(WORMHOLE_STATE_ID)], + }); + tx.transferObjects([emitterCap], tx.pure(owner)); + + // Execute and fetch created Emitter cap. + return wallet + .signAndExecuteTransactionBlock({ + transactionBlock: tx, + options: { + showObjectChanges: true, + }, + }) + .then((result) => { + const found = result.objectChanges?.filter( + (item) => "created" === item.type! + ); + if (found?.length == 1 && "objectId" in found[0]) { + return found[0].objectId; + } + + throw new Error("no objects found"); + }); + })(); + + // Publish messages using emitter cap. + { + const nonce = 69; + const basePayload = "All your base are belong to us."; + + const numMessages = 32; + const payloads: string[] = []; + const tx = new TransactionBlock(); + + // Construct transaction block to send multiple messages. + for (let i = 0; i < numMessages; ++i) { + // Make a unique message. + const payload = basePayload + `... ${i}`; + payloads.push(payload); + + addPrepareMessageAndPublishMessage( + tx, + wormholePackage, + WORMHOLE_STATE_ID, + emitterCapId, + nonce, + payload + ); + } + + const events = await wallet + .signAndExecuteTransactionBlock({ + transactionBlock: tx, + options: { + showEvents: true, + }, + }) + .then((result) => result.events!); + expect(events).has.length(numMessages); + + for (let i = 0; i < numMessages; ++i) { + const eventData = events[i].parsedJson!; + expect(eventData.consistency_level).equals(0); + expect(eventData.nonce).equals(nonce); + expect(eventData.payload).deep.equals([...Buffer.from(payloads[i])]); + expect(eventData.sender).equals(emitterCapId); + expect(eventData.sequence).equals(i.toString()); + expect(BigInt(eventData.timestamp) > 0n).is.true; + } + } + }); + }); +}); diff --git a/sui/testing/js/helpers/build.ts b/sui/testing/js/helpers/build.ts new file mode 100644 index 000000000..374831ab8 --- /dev/null +++ b/sui/testing/js/helpers/build.ts @@ -0,0 +1,32 @@ +import { fromB64, normalizeSuiObjectId } from "@mysten/sui.js"; +import { execSync, ExecSyncOptionsWithStringEncoding } from "child_process"; +import { UTF8 } from "./consts"; + +export const EXEC_UTF8: ExecSyncOptionsWithStringEncoding = { encoding: UTF8 }; + +export function buildForBytecode(packagePath: string) { + const buildOutput: { + modules: string[]; + dependencies: string[]; + } = JSON.parse( + execSync( + `sui move build --dump-bytecode-as-base64 -p ${packagePath} 2> /dev/null`, + EXEC_UTF8 + ) + ); + return { + modules: buildOutput.modules.map((m: string) => Array.from(fromB64(m))), + dependencies: buildOutput.dependencies.map((d: string) => + normalizeSuiObjectId(d) + ), + }; +} + +export function buildForDigest(packagePath: string) { + const digest = execSync( + `sui move build --dump-package-digest -p ${packagePath} 2> /dev/null`, + EXEC_UTF8 + ).substring(0, 64); + + return Buffer.from(digest, "hex"); +} diff --git a/sui/testing/js/helpers/consts.ts b/sui/testing/js/helpers/consts.ts new file mode 100644 index 000000000..c4aa0787f --- /dev/null +++ b/sui/testing/js/helpers/consts.ts @@ -0,0 +1,40 @@ +// NOTE: modify these to reflect current versions of packages +export const VERSION_WORMHOLE = 1; +export const VERSION_TOKEN_BRIDGE = 1; + +// keystore +export const KEYSTORE = [ + "AB522qKKEsXMTFRD2SG3Het/02S/ZBOugmcH3R1CDG6l", + "AOmPq9B16F3W3ijO/4s9hI6v8LdiYCawKAW31PKpg4Qp", + "AGA20wtGcwbcNAG4nwapbQ5wIuXwkYQEWFUoSVAxctHb", +]; + +// wallets +export const WALLET_PRIVATE_KEY = Buffer.from(KEYSTORE[0], "base64").subarray( + 1 +); +export const RELAYER_PRIVATE_KEY = Buffer.from(KEYSTORE[1], "base64").subarray( + 1 +); +export const CREATOR_PRIVATE_KEY = Buffer.from(KEYSTORE[2], "base64").subarray( + 1 +); + +// guardian signer +export const GUARDIAN_PRIVATE_KEY = + "cfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0"; + +// wormhole +export const WORMHOLE_STATE_ID = + "0xc561a02a143575e53b87ba6c1476f053a307eac5179cb1c8121a3d3b220b81c1"; + +// token bridge +export const TOKEN_BRIDGE_STATE_ID = + "0x1c8de839f6331f2d745eb53b1b595bc466b4001c11617b0b66214b2e25ee72fc"; + +// governance +export const GOVERNANCE_EMITTER = + "0000000000000000000000000000000000000000000000000000000000000004"; + +// file encoding +export const UTF8: BufferEncoding = "utf-8"; diff --git a/sui/testing/js/helpers/error/moveAbort.ts b/sui/testing/js/helpers/error/moveAbort.ts new file mode 100644 index 000000000..04fdde752 --- /dev/null +++ b/sui/testing/js/helpers/error/moveAbort.ts @@ -0,0 +1,42 @@ +export function parseMoveAbort(errorMessage: string) { + const parsed = errorMessage.matchAll( + /MoveAbort\(MoveLocation { module: ModuleId { address: ([0-9a-f]{64}), name: Identifier\("([A-Za-z_]+)"\) }, function: ([0-9]+), instruction: ([0-9]+), function_name: Some\("([A-Za-z_]+)"\) }, ([0-9]+)\) in command ([0-9]+)/g + ); + + return parsed.next().value.slice(1, 8); +} + +export class MoveAbort { + packageId: string; + moduleName: string; + functionName: string; + errorCode: bigint; + command: number; + + constructor( + packageId: string, + moduleName: string, + functionName: string, + errorCode: string, + command: string + ) { + this.packageId = packageId; + this.moduleName = moduleName; + this.functionName = functionName; + this.errorCode = BigInt(errorCode); + this.command = Number(command); + } + + static parseError(errorMessage: string): MoveAbort { + const [packageId, moduleName, , , functionName, errorCode, command] = + parseMoveAbort(errorMessage); + + return new MoveAbort( + "0x" + packageId, + moduleName, + functionName, + errorCode, + command + ); + } +} diff --git a/sui/testing/js/helpers/error/wormhole.ts b/sui/testing/js/helpers/error/wormhole.ts new file mode 100644 index 000000000..f011d606d --- /dev/null +++ b/sui/testing/js/helpers/error/wormhole.ts @@ -0,0 +1,22 @@ +import { MoveAbort } from "./moveAbort"; + +export function parseWormholeError(errorMessage: string) { + const abort = MoveAbort.parseError(errorMessage); + const code = abort.errorCode; + + switch (abort.moduleName) { + case "required_version": { + switch (code) { + case 0n: { + return "E_OUTDATED_VERSION"; + } + default: { + throw new Error(`unrecognized error code: ${abort}`); + } + } + } + default: { + throw new Error(`unrecognized module: ${abort}`); + } + } +} diff --git a/sui/testing/js/helpers/setup.ts b/sui/testing/js/helpers/setup.ts new file mode 100644 index 000000000..0399be84e --- /dev/null +++ b/sui/testing/js/helpers/setup.ts @@ -0,0 +1,75 @@ +import * as fs from "fs"; +import * as mock from "@certusone/wormhole-sdk/lib/cjs/mock"; +import { GUARDIAN_PRIVATE_KEY, UTF8 } from "./consts"; + +export function generateVaaFromDigest( + digest: Buffer, + governance: mock.GovernanceEmitter +) { + const timestamp = 12345678; + const published = governance.publishWormholeUpgradeContract( + timestamp, + 2, + "0x" + digest.toString("hex") + ); + + // Sui is not supported yet by the SDK, so we need to adjust the payload. + published.writeUInt16BE(21, published.length - 34); + + // We will use the signed VAA when we execute the upgrade. + const guardians = new mock.MockGuardians(0, [GUARDIAN_PRIVATE_KEY]); + return guardians.addSignatures(published, [0]); +} + +export function modifyHardCodedVersionControl( + packagePath: string, + currentVersion: number, + newVersion: number +) { + const versionControlDotMove = `${packagePath}/sources/version_control.move`; + + const contents = fs.readFileSync(versionControlDotMove, UTF8); + const src = `const CURRENT_BUILD_VERSION: u64 = ${currentVersion}`; + if (contents.indexOf(src) < 0) { + throw new Error("current version not found"); + } + + const dst = `const CURRENT_BUILD_VERSION: u64 = ${newVersion}`; + fs.writeFileSync(versionControlDotMove, contents.replace(src, dst), UTF8); +} + +export function setUpWormholeDirectory( + srcWormholePath: string, + dstWormholePath: string +) { + fs.cpSync(srcWormholePath, dstWormholePath, { recursive: true }); + + // Remove irrelevant files. This part is not necessary, but is helpful + // for debugging a clean package directory. + const removeThese = [ + "Move.devnet.toml", + "Move.lock", + "Makefile", + "README.md", + "build", + ]; + for (const basename of removeThese) { + fs.rmSync(`${dstWormholePath}/${basename}`, { + recursive: true, + force: true, + }); + } + + // Fix Move.toml file. + const moveTomlPath = `${dstWormholePath}/Move.toml`; + const moveToml = fs.readFileSync(moveTomlPath, UTF8); + fs.writeFileSync( + moveTomlPath, + moveToml.replace(`wormhole = "_"`, `wormhole = "0x0"`), + UTF8 + ); +} + +export function cleanUpPackageDirectory(packagePath: string) { + fs.rmSync(packagePath, { recursive: true, force: true }); +} diff --git a/sui/testing/js/helpers/upgrade.ts b/sui/testing/js/helpers/upgrade.ts new file mode 100644 index 000000000..4698e9a85 --- /dev/null +++ b/sui/testing/js/helpers/upgrade.ts @@ -0,0 +1,73 @@ +import { + RawSigner, + SUI_CLOCK_OBJECT_ID, + TransactionBlock, +} from "@mysten/sui.js"; +import { buildForBytecode } from "./build"; +import { getPackageId } from "./utils"; + +export async function buildAndUpgradeWormhole( + signer: RawSigner, + signedVaa: Buffer, + wormholePath: string, + wormholeStateId: string +) { + const wormholePackage = await getPackageId(signer.provider, wormholeStateId); + + const tx = new TransactionBlock(); + + // Authorize upgrade. + const [upgradeTicket] = tx.moveCall({ + target: `${wormholePackage}::upgrade_contract::authorize_upgrade`, + arguments: [ + tx.object(wormholeStateId), + tx.pure(Array.from(signedVaa)), + tx.object(SUI_CLOCK_OBJECT_ID), + ], + }); + + // Build and generate modules and dependencies for upgrade. + const { modules, dependencies } = buildForBytecode(wormholePath); + const [upgradeReceipt] = tx.upgrade({ + modules, + dependencies, + packageId: wormholePackage, + ticket: upgradeTicket, + }); + + // Commit upgrade. + tx.moveCall({ + target: `${wormholePackage}::upgrade_contract::commit_upgrade`, + arguments: [tx.object(wormholeStateId), upgradeReceipt], + }); + + // Cannot auto compute gas budget, so we need to configure it manually. + // Gas ~215m. + tx.setGasBudget(215_000_000n); + + return signer.signAndExecuteTransactionBlock({ + transactionBlock: tx, + options: { + showEffects: true, + showEvents: true, + }, + }); +} + +export async function migrate(signer: RawSigner, stateId: string) { + const contractPackage = await getPackageId(signer.provider, stateId); + + const tx = new TransactionBlock(); + tx.moveCall({ + target: `${contractPackage}::migrate::migrate`, + arguments: [tx.object(stateId)], + }); + + return signer.signAndExecuteTransactionBlock({ + transactionBlock: tx, + options: { + showEffects: true, + showEvents: true, + }, + }); +} diff --git a/sui/testing/js/helpers/utils.ts b/sui/testing/js/helpers/utils.ts new file mode 100644 index 000000000..58f71444e --- /dev/null +++ b/sui/testing/js/helpers/utils.ts @@ -0,0 +1,27 @@ +import { JsonRpcProvider } from "@mysten/sui.js"; + +export async function getPackageId( + provider: JsonRpcProvider, + stateId: string +): Promise { + const state = await provider + .getObject({ + id: stateId, + options: { + showContent: true, + }, + }) + .then((result) => { + if (result.data?.content?.dataType == "moveObject") { + return result.data.content.fields; + } + + throw new Error("not move object"); + }); + + if ("upgrade_cap" in state) { + return state.upgrade_cap.fields.package; + } + + throw new Error("upgrade_cap not found"); +} diff --git a/sui/testing/js/helpers/wormhole/testPublishMessage.ts b/sui/testing/js/helpers/wormhole/testPublishMessage.ts new file mode 100644 index 000000000..35a3876a5 --- /dev/null +++ b/sui/testing/js/helpers/wormhole/testPublishMessage.ts @@ -0,0 +1,31 @@ +import { SUI_CLOCK_OBJECT_ID, TransactionBlock } from "@mysten/sui.js"; + +export function addPrepareMessageAndPublishMessage( + tx: TransactionBlock, + wormholePackage: string, + wormholeStateId: string, + emitterCapId: string, + nonce: number, + payload: number[] | string +): TransactionBlock { + const [feeAmount] = tx.moveCall({ + target: `${wormholePackage}::state::message_fee`, + arguments: [tx.object(wormholeStateId)], + }); + const [wormholeFee] = tx.splitCoins(tx.gas, [feeAmount]); + const [messageTicket] = tx.moveCall({ + target: `${wormholePackage}::publish_message::prepare_message`, + arguments: [tx.object(emitterCapId), tx.pure(nonce), tx.pure(payload)], + }); + tx.moveCall({ + target: `${wormholePackage}::publish_message::publish_message`, + arguments: [ + tx.object(wormholeStateId), + wormholeFee, + messageTicket, + tx.object(SUI_CLOCK_OBJECT_ID), + ], + }); + + return tx; +} diff --git a/sui/testing/package-lock.json b/sui/testing/package-lock.json new file mode 100644 index 000000000..3b6dd637e --- /dev/null +++ b/sui/testing/package-lock.json @@ -0,0 +1,5917 @@ +{ + "name": "wormhole-sui-integration-test", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "wormhole-sui-integration-test", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@certusone/wormhole-sdk": "^0.9.12", + "@mysten/sui.js": "^0.32.2", + "chai": "^4.3.7", + "mocha": "^10.2.0", + "prettier": "^2.8.7", + "ts-mocha": "^10.0.0", + "ts-node": "^10.9.1", + "typescript": "^5.0.4" + }, + "devDependencies": { + "@types/chai": "^4.3.4", + "@types/mocha": "^10.0.1", + "@types/node": "^18.15.11" + } + }, + "node_modules/@apollo/client": { + "version": "3.7.11", + "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.7.11.tgz", + "integrity": "sha512-uLg2KtxoAyj9ta7abLxXx8cGRM7HypCkXVmxtL7Ko//N5g37aoJ3ca7VYoFCMUFO1BXBulj+yKVl0U3+ILj5AQ==", + "license": "MIT", + "dependencies": { + "@graphql-typed-document-node/core": "^3.1.1", + "@wry/context": "^0.7.0", + "@wry/equality": "^0.5.0", + "@wry/trie": "^0.3.0", + "graphql-tag": "^2.12.6", + "hoist-non-react-statics": "^3.3.2", + "optimism": "^0.16.2", + "prop-types": "^15.7.2", + "response-iterator": "^0.2.6", + "symbol-observable": "^4.0.0", + "ts-invariant": "^0.10.3", + "tslib": "^2.3.0", + "zen-observable-ts": "^1.2.5" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0", + "graphql-ws": "^5.5.5", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", + "subscriptions-transport-ws": "^0.9.0 || ^0.11.0" + }, + "peerDependenciesMeta": { + "graphql-ws": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "subscriptions-transport-ws": { + "optional": true + } + } + }, + "node_modules/@babel/runtime": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", + "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@certusone/wormhole-sdk": { + "version": "0.9.12", + "resolved": "https://registry.npmjs.org/@certusone/wormhole-sdk/-/wormhole-sdk-0.9.12.tgz", + "integrity": "sha512-ywMNc/tHg6qb9dcZLND1BMUISp7eFN+ksymOgjhwQcZZ/KUA/N1uVvbMVs0uSx+i0y4VloO9MwGc/uFnYKNsMQ==", + "license": "Apache-2.0", + "dependencies": { + "@certusone/wormhole-sdk-proto-web": "0.0.6", + "@certusone/wormhole-sdk-wasm": "^0.0.1", + "@coral-xyz/borsh": "0.2.6", + "@injectivelabs/networks": "^1.0.73", + "@injectivelabs/sdk-ts": "^1.0.368", + "@injectivelabs/utils": "^1.0.63", + "@project-serum/anchor": "^0.25.0", + "@solana/spl-token": "^0.3.5", + "@solana/web3.js": "^1.66.2", + "@terra-money/terra.js": "^3.1.3", + "@xpla/xpla.js": "^0.2.1", + "algosdk": "^1.15.0", + "aptos": "1.5.0", + "axios": "^0.24.0", + "bech32": "^2.0.0", + "binary-parser": "^2.2.1", + "bs58": "^4.0.1", + "elliptic": "^6.5.4", + "js-base64": "^3.6.1", + "near-api-js": "^1.0.0" + } + }, + "node_modules/@certusone/wormhole-sdk-proto-web": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@certusone/wormhole-sdk-proto-web/-/wormhole-sdk-proto-web-0.0.6.tgz", + "integrity": "sha512-LTyjsrWryefx5WmkoBP6FQ2EjLxhMExAGxLkloHUhufVQZdrbGh0htBBUviP+HaDSJBCMPMtulNFwkBJV6muqQ==", + "license": "Apache-2.0", + "dependencies": { + "@improbable-eng/grpc-web": "^0.15.0", + "protobufjs": "^7.0.0", + "rxjs": "^7.5.6" + } + }, + "node_modules/@certusone/wormhole-sdk-proto-web/node_modules/@improbable-eng/grpc-web": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@improbable-eng/grpc-web/-/grpc-web-0.15.0.tgz", + "integrity": "sha512-ERft9/0/8CmYalqOVnJnpdDry28q+j+nAlFFARdjyxXDJ+Mhgv9+F600QC8BR9ygOfrXRlAk6CvST2j+JCpQPg==", + "license": "Apache-2.0", + "dependencies": { + "browser-headers": "^0.4.1" + }, + "peerDependencies": { + "google-protobuf": "^3.14.0" + } + }, + "node_modules/@certusone/wormhole-sdk-proto-web/node_modules/long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==", + "license": "Apache-2.0" + }, + "node_modules/@certusone/wormhole-sdk-proto-web/node_modules/protobufjs": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.3.tgz", + "integrity": "sha512-TtpvOqwB5Gdz/PQmOjgsrGH1nHjAQVCN7JG4A6r1sXRWESL5rNMAiRcBQlCAdKxZcAbstExQePYG8xof/JVRgg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@certusone/wormhole-sdk-wasm": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@certusone/wormhole-sdk-wasm/-/wormhole-sdk-wasm-0.0.1.tgz", + "integrity": "sha512-LdIwLhOyr4pPs2jqYubqC7d4UkqYBX0EG/ppspQlW3qlVE0LZRMrH6oVzzLMyHtV0Rw7O9sIKzORW/T3mrJv2w==", + "license": "Apache-2.0", + "dependencies": { + "@types/long": "^4.0.2", + "@types/node": "^18.0.3" + } + }, + "node_modules/@certusone/wormhole-sdk/node_modules/axios": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", + "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.14.4" + } + }, + "node_modules/@classic-terra/terra.proto": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@classic-terra/terra.proto/-/terra.proto-1.1.0.tgz", + "integrity": "sha512-bYhQG5LUaGF0KPRY9hYT/HEcd1QExZPQd6zLV/rQkCe/eDxfwFRLzZHpaaAdfWoAAZjsRWqJbUCqCg7gXBbJpw==", + "license": "Apache-2.0", + "dependencies": { + "@improbable-eng/grpc-web": "^0.14.1", + "google-protobuf": "^3.17.3", + "long": "^4.0.0", + "protobufjs": "~6.11.2" + } + }, + "node_modules/@confio/ics23": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@confio/ics23/-/ics23-0.6.8.tgz", + "integrity": "sha512-wB6uo+3A50m0sW/EWcU64xpV/8wShZ6bMTa7pF8eYsTrSkQA7oLUIJcs/wb8g4y2Oyq701BaGiO6n/ak5WXO1w==", + "license": "Apache-2.0", + "dependencies": { + "@noble/hashes": "^1.0.0", + "protobufjs": "^6.8.8" + } + }, + "node_modules/@coral-xyz/borsh": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@coral-xyz/borsh/-/borsh-0.2.6.tgz", + "integrity": "sha512-y6nmHw1bFcJib7sMHsQPpC8r47xhqDZVvhUdna7NUPzpSbOZG6f46N21+aXsQ2w/tG8Ggls488J/ZmwbgVmyjg==", + "license": "Apache-2.0", + "dependencies": { + "bn.js": "^5.1.2", + "buffer-layout": "^1.2.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@solana/web3.js": "^1.2.0" + } + }, + "node_modules/@cosmjs/amino": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@cosmjs/amino/-/amino-0.30.1.tgz", + "integrity": "sha512-yNHnzmvAlkETDYIpeCTdVqgvrdt1qgkOXwuRVi8s27UKI5hfqyE9fJ/fuunXE6ZZPnKkjIecDznmuUOMrMvw4w==", + "license": "Apache-2.0", + "dependencies": { + "@cosmjs/crypto": "^0.30.1", + "@cosmjs/encoding": "^0.30.1", + "@cosmjs/math": "^0.30.1", + "@cosmjs/utils": "^0.30.1" + } + }, + "node_modules/@cosmjs/crypto": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.30.1.tgz", + "integrity": "sha512-rAljUlake3MSXs9xAm87mu34GfBLN0h/1uPPV6jEwClWjNkAMotzjC0ab9MARy5FFAvYHL3lWb57bhkbt2GtzQ==", + "license": "Apache-2.0", + "dependencies": { + "@cosmjs/encoding": "^0.30.1", + "@cosmjs/math": "^0.30.1", + "@cosmjs/utils": "^0.30.1", + "@noble/hashes": "^1", + "bn.js": "^5.2.0", + "elliptic": "^6.5.4", + "libsodium-wrappers": "^0.7.6" + } + }, + "node_modules/@cosmjs/encoding": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@cosmjs/encoding/-/encoding-0.30.1.tgz", + "integrity": "sha512-rXmrTbgqwihORwJ3xYhIgQFfMSrwLu1s43RIK9I8EBudPx3KmnmyAKzMOVsRDo9edLFNuZ9GIvysUCwQfq3WlQ==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "bech32": "^1.1.4", + "readonly-date": "^1.0.0" + } + }, + "node_modules/@cosmjs/encoding/node_modules/bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", + "license": "MIT" + }, + "node_modules/@cosmjs/json-rpc": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@cosmjs/json-rpc/-/json-rpc-0.30.1.tgz", + "integrity": "sha512-pitfC/2YN9t+kXZCbNuyrZ6M8abnCC2n62m+JtU9vQUfaEtVsgy+1Fk4TRQ175+pIWSdBMFi2wT8FWVEE4RhxQ==", + "license": "Apache-2.0", + "dependencies": { + "@cosmjs/stream": "^0.30.1", + "xstream": "^11.14.0" + } + }, + "node_modules/@cosmjs/math": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@cosmjs/math/-/math-0.30.1.tgz", + "integrity": "sha512-yaoeI23pin9ZiPHIisa6qqLngfnBR/25tSaWpkTm8Cy10MX70UF5oN4+/t1heLaM6SSmRrhk3psRkV4+7mH51Q==", + "license": "Apache-2.0", + "dependencies": { + "bn.js": "^5.2.0" + } + }, + "node_modules/@cosmjs/proto-signing": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@cosmjs/proto-signing/-/proto-signing-0.30.1.tgz", + "integrity": "sha512-tXh8pPYXV4aiJVhTKHGyeZekjj+K9s2KKojMB93Gcob2DxUjfKapFYBMJSgfKPuWUPEmyr8Q9km2hplI38ILgQ==", + "license": "Apache-2.0", + "dependencies": { + "@cosmjs/amino": "^0.30.1", + "@cosmjs/crypto": "^0.30.1", + "@cosmjs/encoding": "^0.30.1", + "@cosmjs/math": "^0.30.1", + "@cosmjs/utils": "^0.30.1", + "cosmjs-types": "^0.7.1", + "long": "^4.0.0" + } + }, + "node_modules/@cosmjs/socket": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@cosmjs/socket/-/socket-0.30.1.tgz", + "integrity": "sha512-r6MpDL+9N+qOS/D5VaxnPaMJ3flwQ36G+vPvYJsXArj93BjgyFB7BwWwXCQDzZ+23cfChPUfhbINOenr8N2Kow==", + "license": "Apache-2.0", + "dependencies": { + "@cosmjs/stream": "^0.30.1", + "isomorphic-ws": "^4.0.1", + "ws": "^7", + "xstream": "^11.14.0" + } + }, + "node_modules/@cosmjs/stargate": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@cosmjs/stargate/-/stargate-0.30.1.tgz", + "integrity": "sha512-RdbYKZCGOH8gWebO7r6WvNnQMxHrNXInY/gPHPzMjbQF6UatA6fNM2G2tdgS5j5u7FTqlCI10stNXrknaNdzog==", + "license": "Apache-2.0", + "dependencies": { + "@confio/ics23": "^0.6.8", + "@cosmjs/amino": "^0.30.1", + "@cosmjs/encoding": "^0.30.1", + "@cosmjs/math": "^0.30.1", + "@cosmjs/proto-signing": "^0.30.1", + "@cosmjs/stream": "^0.30.1", + "@cosmjs/tendermint-rpc": "^0.30.1", + "@cosmjs/utils": "^0.30.1", + "cosmjs-types": "^0.7.1", + "long": "^4.0.0", + "protobufjs": "~6.11.3", + "xstream": "^11.14.0" + } + }, + "node_modules/@cosmjs/stream": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.30.1.tgz", + "integrity": "sha512-Fg0pWz1zXQdoxQZpdHRMGvUH5RqS6tPv+j9Eh7Q953UjMlrwZVo0YFLC8OTf/HKVf10E4i0u6aM8D69Q6cNkgQ==", + "license": "Apache-2.0", + "dependencies": { + "xstream": "^11.14.0" + } + }, + "node_modules/@cosmjs/tendermint-rpc": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.30.1.tgz", + "integrity": "sha512-Z3nCwhXSbPZJ++v85zHObeUggrEHVfm1u18ZRwXxFE9ZMl5mXTybnwYhczuYOl7KRskgwlB+rID0WYACxj4wdQ==", + "license": "Apache-2.0", + "dependencies": { + "@cosmjs/crypto": "^0.30.1", + "@cosmjs/encoding": "^0.30.1", + "@cosmjs/json-rpc": "^0.30.1", + "@cosmjs/math": "^0.30.1", + "@cosmjs/socket": "^0.30.1", + "@cosmjs/stream": "^0.30.1", + "@cosmjs/utils": "^0.30.1", + "axios": "^0.21.2", + "readonly-date": "^1.0.0", + "xstream": "^11.14.0" + } + }, + "node_modules/@cosmjs/utils": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.30.1.tgz", + "integrity": "sha512-KvvX58MGMWh7xA+N+deCfunkA/ZNDvFLw4YbOmX3f/XBIkqrVY7qlotfy2aNb1kgp6h4B6Yc8YawJPDTfvWX7g==", + "license": "Apache-2.0" + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@ethereumjs/common": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.6.5.tgz", + "integrity": "sha512-lRyVQOeCDaIVtgfbowla32pzeDv2Obr8oR8Put5RdUBNRGr1VGPGQNGP6elWIpgK3YdpzqTOh4GyUGOureVeeA==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "ethereumjs-util": "^7.1.5" + } + }, + "node_modules/@ethereumjs/tx": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.5.2.tgz", + "integrity": "sha512-gQDNJWKrSDGu2w7w0PzVXVBNMzb7wwdDOmOqczmhNjqFxFuIbhVJDwiGEnxFNC2/b8ifcZzY7MLcluizohRzNw==", + "license": "MPL-2.0", + "dependencies": { + "@ethereumjs/common": "^2.6.4", + "ethereumjs-util": "^7.1.5" + } + }, + "node_modules/@ethersproject/abi": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", + "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/abstract-provider": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz", + "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/networks": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/web": "^5.7.0" + } + }, + "node_modules/@ethersproject/abstract-signer": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz", + "integrity": "sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0" + } + }, + "node_modules/@ethersproject/address": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz", + "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/rlp": "^5.7.0" + } + }, + "node_modules/@ethersproject/base64": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz", + "integrity": "sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0" + } + }, + "node_modules/@ethersproject/basex": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.7.0.tgz", + "integrity": "sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/properties": "^5.7.0" + } + }, + "node_modules/@ethersproject/bignumber": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz", + "integrity": "sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "bn.js": "^5.2.1" + } + }, + "node_modules/@ethersproject/bytes": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz", + "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/constants": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz", + "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.7.0" + } + }, + "node_modules/@ethersproject/contracts": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.7.0.tgz", + "integrity": "sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abi": "^5.7.0", + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/transactions": "^5.7.0" + } + }, + "node_modules/@ethersproject/hash": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz", + "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/base64": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/hdnode": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.7.0.tgz", + "integrity": "sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/basex": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/pbkdf2": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/wordlists": "^5.7.0" + } + }, + "node_modules/@ethersproject/json-wallets": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz", + "integrity": "sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hdnode": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/pbkdf2": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "aes-js": "3.0.0", + "scrypt-js": "3.0.1" + } + }, + "node_modules/@ethersproject/keccak256": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz", + "integrity": "sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "js-sha3": "0.8.0" + } + }, + "node_modules/@ethersproject/logger": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz", + "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT" + }, + "node_modules/@ethersproject/networks": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz", + "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/pbkdf2": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz", + "integrity": "sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/sha2": "^5.7.0" + } + }, + "node_modules/@ethersproject/properties": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz", + "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/providers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.7.2.tgz", + "integrity": "sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/base64": "^5.7.0", + "@ethersproject/basex": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/networks": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/rlp": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/web": "^5.7.0", + "bech32": "1.1.4", + "ws": "7.4.6" + } + }, + "node_modules/@ethersproject/providers/node_modules/bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", + "license": "MIT" + }, + "node_modules/@ethersproject/providers/node_modules/ws": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@ethersproject/random": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.7.0.tgz", + "integrity": "sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/rlp": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz", + "integrity": "sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/sha2": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.7.0.tgz", + "integrity": "sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "hash.js": "1.1.7" + } + }, + "node_modules/@ethersproject/signing-key": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz", + "integrity": "sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "bn.js": "^5.2.1", + "elliptic": "6.5.4", + "hash.js": "1.1.7" + } + }, + "node_modules/@ethersproject/solidity": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.7.0.tgz", + "integrity": "sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/strings": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz", + "integrity": "sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/transactions": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz", + "integrity": "sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/rlp": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0" + } + }, + "node_modules/@ethersproject/units": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.7.0.tgz", + "integrity": "sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/wallet": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.7.0.tgz", + "integrity": "sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/hdnode": "^5.7.0", + "@ethersproject/json-wallets": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/wordlists": "^5.7.0" + } + }, + "node_modules/@ethersproject/web": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz", + "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/base64": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/wordlists": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.7.0.tgz", + "integrity": "sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@graphql-typed-document-node/core": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", + "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", + "license": "MIT", + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@improbable-eng/grpc-web": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@improbable-eng/grpc-web/-/grpc-web-0.14.1.tgz", + "integrity": "sha512-XaIYuunepPxoiGVLLHmlnVminUGzBTnXr8Wv7khzmLWbNw4TCwJKX09GSMJlKhu/TRk6gms0ySFxewaETSBqgw==", + "license": "Apache-2.0", + "dependencies": { + "browser-headers": "^0.4.1" + }, + "peerDependencies": { + "google-protobuf": "^3.14.0" + } + }, + "node_modules/@injectivelabs/core-proto-ts": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/@injectivelabs/core-proto-ts/-/core-proto-ts-0.0.11.tgz", + "integrity": "sha512-gYMzkoZ0olXLbEhSQVarUCMR6VAHytvENDv2Psjl9EjO5Pg93vTGLViS4E4vA5fezRfdF/x0Uic31w+ogp66jA==", + "license": "MIT", + "dependencies": { + "@injectivelabs/grpc-web": "^0.0.1", + "google-protobuf": "^3.14.0", + "protobufjs": "^7.0.0", + "rxjs": "^7.4.0" + } + }, + "node_modules/@injectivelabs/core-proto-ts/node_modules/long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==", + "license": "Apache-2.0" + }, + "node_modules/@injectivelabs/core-proto-ts/node_modules/protobufjs": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.3.tgz", + "integrity": "sha512-TtpvOqwB5Gdz/PQmOjgsrGH1nHjAQVCN7JG4A6r1sXRWESL5rNMAiRcBQlCAdKxZcAbstExQePYG8xof/JVRgg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@injectivelabs/exceptions": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/@injectivelabs/exceptions/-/exceptions-1.10.2.tgz", + "integrity": "sha512-JLHgU/MjxRYSpn/9G9mJvHuNiA5ze6w86sXz09kQh7tlSaTC4PGqBBbBSu0hrUBBX86O+vk2ULkn1Ks1n7FlOw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@injectivelabs/grpc-web": "^0.0.1", + "@injectivelabs/ts-types": "^1.10.1", + "http-status-codes": "^2.2.0", + "link-module-alias": "^1.2.0", + "shx": "^0.3.2" + } + }, + "node_modules/@injectivelabs/exceptions/dist": { + "extraneous": true + }, + "node_modules/@injectivelabs/grpc-web": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@injectivelabs/grpc-web/-/grpc-web-0.0.1.tgz", + "integrity": "sha512-Pu5YgaZp+OvR5UWfqbrPdHer3+gDf+b5fQoY+t2VZx1IAVHX8bzbN9EreYTvTYtFeDpYRWM8P7app2u4EX5wTw==", + "license": "Apache-2.0", + "dependencies": { + "browser-headers": "^0.4.1" + }, + "peerDependencies": { + "google-protobuf": "^3.14.0" + } + }, + "node_modules/@injectivelabs/grpc-web-node-http-transport": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@injectivelabs/grpc-web-node-http-transport/-/grpc-web-node-http-transport-0.0.2.tgz", + "integrity": "sha512-rpyhXLiGY/UMs6v6YmgWHJHiO9l0AgDyVNv+jcutNVt4tQrmNvnpvz2wCAGOFtq5LuX/E9ChtTVpk3gWGqXcGA==", + "license": "Apache-2.0", + "peerDependencies": { + "@injectivelabs/grpc-web": ">=0.0.1" + } + }, + "node_modules/@injectivelabs/grpc-web-react-native-transport": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@injectivelabs/grpc-web-react-native-transport/-/grpc-web-react-native-transport-0.0.2.tgz", + "integrity": "sha512-mk+aukQXnYNgPsPnu3KBi+FD0ZHQpazIlaBZ2jNZG7QAVmxTWtv3R66Zoq99Wx2dnE946NsZBYAoa0K5oSjnow==", + "license": "Apache-2.0", + "peerDependencies": { + "@injectivelabs/grpc-web": ">=0.0.1" + } + }, + "node_modules/@injectivelabs/indexer-proto-ts": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@injectivelabs/indexer-proto-ts/-/indexer-proto-ts-0.0.9.tgz", + "integrity": "sha512-ZFTUKlHAY2WYnB9RPPf11nq7SNm7wcKFTmFTavTiHV8UvNEni7dCR3Un6U5Mo1qD0xHEsfoCDMdqGcIguliPMA==", + "license": "MIT", + "dependencies": { + "@injectivelabs/grpc-web": "^0.0.1", + "google-protobuf": "^3.14.0", + "protobufjs": "^7.0.0", + "rxjs": "^7.4.0" + } + }, + "node_modules/@injectivelabs/indexer-proto-ts/node_modules/long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==", + "license": "Apache-2.0" + }, + "node_modules/@injectivelabs/indexer-proto-ts/node_modules/protobufjs": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.3.tgz", + "integrity": "sha512-TtpvOqwB5Gdz/PQmOjgsrGH1nHjAQVCN7JG4A6r1sXRWESL5rNMAiRcBQlCAdKxZcAbstExQePYG8xof/JVRgg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@injectivelabs/mito-proto-ts": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@injectivelabs/mito-proto-ts/-/mito-proto-ts-1.0.2.tgz", + "integrity": "sha512-A/5Nf/RJiBRiwYNqH2K0nNrOuuVcYCebqgEt3btpDfQXcyaHIssjDmZOtmMT1M7P/enEVgDu0auxE7tsmSFijg==", + "license": "MIT", + "dependencies": { + "@injectivelabs/grpc-web": "^0.0.1", + "google-protobuf": "^3.14.0", + "protobufjs": "^7.0.0", + "rxjs": "^7.4.0" + } + }, + "node_modules/@injectivelabs/mito-proto-ts/node_modules/long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==", + "license": "Apache-2.0" + }, + "node_modules/@injectivelabs/mito-proto-ts/node_modules/protobufjs": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.3.tgz", + "integrity": "sha512-TtpvOqwB5Gdz/PQmOjgsrGH1nHjAQVCN7JG4A6r1sXRWESL5rNMAiRcBQlCAdKxZcAbstExQePYG8xof/JVRgg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@injectivelabs/networks": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@injectivelabs/networks/-/networks-1.10.4.tgz", + "integrity": "sha512-EjWdTXpU+j8YFikxiMacVhPK8dzamMD4czkrst7NfcMRoBCMNMrOp5lItF5GFq0BSx3xu/zfkb2+3wWTIdWUxQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@injectivelabs/exceptions": "^1.10.2", + "@injectivelabs/ts-types": "^1.10.1", + "@injectivelabs/utils": "^1.10.2", + "link-module-alias": "^1.2.0", + "shx": "^0.3.2" + } + }, + "node_modules/@injectivelabs/networks/dist": { + "extraneous": true + }, + "node_modules/@injectivelabs/sdk-ts": { + "version": "1.10.37", + "resolved": "https://registry.npmjs.org/@injectivelabs/sdk-ts/-/sdk-ts-1.10.37.tgz", + "integrity": "sha512-+7LzC1iDiN3oT7PZ3yV2PchsrH1WQfS+tV8/geesi0EBKT4AW4v2Ur3OYhtDXvQia1zSxWJY9phS3iAmaBd9vQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@apollo/client": "^3.5.8", + "@cosmjs/amino": "^0.30.1", + "@cosmjs/proto-signing": "^0.30.1", + "@cosmjs/stargate": "^0.30.1", + "@ethersproject/bytes": "^5.7.0", + "@injectivelabs/core-proto-ts": "^0.0.11", + "@injectivelabs/exceptions": "^1.10.2", + "@injectivelabs/grpc-web": "^0.0.1", + "@injectivelabs/grpc-web-node-http-transport": "^0.0.2", + "@injectivelabs/grpc-web-react-native-transport": "^0.0.2", + "@injectivelabs/indexer-proto-ts": "^0.0.9", + "@injectivelabs/mito-proto-ts": "1.0.2", + "@injectivelabs/networks": "^1.10.4", + "@injectivelabs/test-utils": "^1.10.1", + "@injectivelabs/token-metadata": "^1.10.17", + "@injectivelabs/ts-types": "^1.10.1", + "@injectivelabs/utils": "^1.10.2", + "@metamask/eth-sig-util": "^4.0.0", + "axios": "^0.27.2", + "bech32": "^2.0.0", + "bip39": "^3.0.4", + "cosmjs-types": "^0.7.1", + "eth-crypto": "^2.6.0", + "ethereumjs-util": "^7.1.4", + "ethers": "^5.7.2", + "google-protobuf": "^3.21.0", + "graphql": "^16.3.0", + "http-status-codes": "^2.2.0", + "js-sha3": "^0.8.0", + "jscrypto": "^1.0.3", + "keccak256": "^1.0.6", + "link-module-alias": "^1.2.0", + "rxjs": "^7.8.0", + "secp256k1": "^4.0.3", + "shx": "^0.3.2", + "snakecase-keys": "^5.4.1" + } + }, + "node_modules/@injectivelabs/sdk-ts/dist": { + "extraneous": true + }, + "node_modules/@injectivelabs/sdk-ts/node_modules/axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + } + }, + "node_modules/@injectivelabs/test-utils": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@injectivelabs/test-utils/-/test-utils-1.10.1.tgz", + "integrity": "sha512-ULP3XJBZN8Muv0jVpo0rfUOD/CDlyg4rij6YuRpYhTg6P0wIlKq9dL36cZlylay+F+4HeLn9qB0D2Cr3+FrhPw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "axios": "^0.21.1", + "bignumber.js": "^9.0.1", + "link-module-alias": "^1.2.0", + "shx": "^0.3.2", + "snakecase-keys": "^5.1.2", + "store2": "^2.12.0" + } + }, + "node_modules/@injectivelabs/test-utils/dist": { + "extraneous": true + }, + "node_modules/@injectivelabs/token-metadata": { + "version": "1.10.17", + "resolved": "https://registry.npmjs.org/@injectivelabs/token-metadata/-/token-metadata-1.10.17.tgz", + "integrity": "sha512-1TFZMs38B21Y0uzqxRuIHifmj6VrJCZLEJnjGuhzIfhtLqSB/ZtCf3JNAarujwwgj6xWb7vzqzqNpo+SIYKvwg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@injectivelabs/exceptions": "^1.10.2", + "@injectivelabs/networks": "^1.10.4", + "@injectivelabs/ts-types": "^1.10.1", + "@injectivelabs/utils": "^1.10.2", + "@types/lodash.values": "^4.3.6", + "copyfiles": "^2.4.1", + "jsonschema": "^1.4.0", + "link-module-alias": "^1.2.0", + "lodash": "^4.17.21", + "lodash.values": "^4.3.0", + "shx": "^0.3.2" + } + }, + "node_modules/@injectivelabs/token-metadata/dist": { + "extraneous": true + }, + "node_modules/@injectivelabs/ts-types": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@injectivelabs/ts-types/-/ts-types-1.10.1.tgz", + "integrity": "sha512-gQQjcnRx2TjLmZDMV8IIkRvLtAzTPptJuWKwPCfSlCRKOIv7Eafzy2qFINUIkKDOeu/lZUtSykEsAIUBEmXqFg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "link-module-alias": "^1.2.0", + "shx": "^0.3.2" + } + }, + "node_modules/@injectivelabs/ts-types/dist": { + "extraneous": true + }, + "node_modules/@injectivelabs/utils": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/@injectivelabs/utils/-/utils-1.10.2.tgz", + "integrity": "sha512-XMO7RRbXs06cChr5Wezr0Dbl1Z9hq+ceB4Dn3qyulzupGepeivkoPTcyG4IdjOiwf7PnFeGQ/aVG3hr0rJI7dQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@injectivelabs/exceptions": "^1.10.2", + "@injectivelabs/ts-types": "^1.10.1", + "axios": "^0.21.1", + "bignumber.js": "^9.0.1", + "http-status-codes": "^2.2.0", + "link-module-alias": "^1.2.0", + "shx": "^0.3.2", + "snakecase-keys": "^5.1.2", + "store2": "^2.12.0" + } + }, + "node_modules/@injectivelabs/utils/dist": { + "extraneous": true + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@metamask/eth-sig-util": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz", + "integrity": "sha512-tghyZKLHZjcdlDqCA3gNZmLeR0XvOE9U1qoQO9ohyAZT6Pya+H9vkBPcsyXytmYLNgVoin7CKCmweo/R43V+tQ==", + "license": "ISC", + "dependencies": { + "ethereumjs-abi": "^0.6.8", + "ethereumjs-util": "^6.2.1", + "ethjs-util": "^0.1.6", + "tweetnacl": "^1.0.3", + "tweetnacl-util": "^0.15.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@metamask/eth-sig-util/node_modules/@types/bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@metamask/eth-sig-util/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "license": "MIT" + }, + "node_modules/@metamask/eth-sig-util/node_modules/ethereumjs-util": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", + "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", + "license": "MPL-2.0", + "dependencies": { + "@types/bn.js": "^4.11.3", + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "0.1.6", + "rlp": "^2.2.3" + } + }, + "node_modules/@mysten/bcs": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@mysten/bcs/-/bcs-0.7.1.tgz", + "integrity": "sha512-wFPb8bkhwrbiStfZMV5rFM7J+umpke59/dNjDp+UYJKykNlW23LCk2ePyEUvGdb62HGJM1jyOJ8g4egE3OmdKA==", + "license": "Apache-2.0", + "dependencies": { + "bs58": "^5.0.0" + } + }, + "node_modules/@mysten/bcs/node_modules/base-x": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", + "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==", + "license": "MIT" + }, + "node_modules/@mysten/bcs/node_modules/bs58": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", + "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", + "license": "MIT", + "dependencies": { + "base-x": "^4.0.0" + } + }, + "node_modules/@mysten/sui.js": { + "version": "0.32.2", + "resolved": "https://registry.npmjs.org/@mysten/sui.js/-/sui.js-0.32.2.tgz", + "integrity": "sha512-/Hm4xkGolJhqj8FvQr7QSHDTlxIvL52mtbOao9f75YjrBh7y1Uh9kbJSY7xiTF1NY9sv6p5hUVlYRJuM0Hvn9A==", + "license": "Apache-2.0", + "dependencies": { + "@mysten/bcs": "0.7.1", + "@noble/curves": "^1.0.0", + "@noble/hashes": "^1.3.0", + "@scure/bip32": "^1.3.0", + "@scure/bip39": "^1.2.0", + "@suchipi/femver": "^1.0.0", + "jayson": "^4.0.0", + "rpc-websockets": "^7.5.1", + "superstruct": "^1.0.3", + "tweetnacl": "^1.0.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@noble/curves": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.0.0.tgz", + "integrity": "sha512-2upgEu0iLiDVDZkNLeFV2+ht0BAVgQnEmCk6JsOch9Rp8xfkMCbvbAZlA2pBHQc73dbl+vFOXfqkf4uemdn0bw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.0" + } + }, + "node_modules/@noble/ed25519": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.3.tgz", + "integrity": "sha512-iR8GBkDt0Q3GyaVcIu7mSsVIqnFbkbRzGLWlvhwunacoLwt4J3swfKhfaM6rN6WY+TBGoYT1GtT1mIh2/jGbRQ==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, + "node_modules/@noble/hashes": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz", + "integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, + "node_modules/@noble/secp256k1": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz", + "integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, + "node_modules/@project-serum/anchor": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@project-serum/anchor/-/anchor-0.25.0.tgz", + "integrity": "sha512-E6A5Y/ijqpfMJ5psJvbw0kVTzLZFUcOFgs6eSM2M2iWE1lVRF18T6hWZVNl6zqZsoz98jgnNHtVGJMs+ds9A7A==", + "license": "(MIT OR Apache-2.0)", + "dependencies": { + "@project-serum/borsh": "^0.2.5", + "@solana/web3.js": "^1.36.0", + "base64-js": "^1.5.1", + "bn.js": "^5.1.2", + "bs58": "^4.0.1", + "buffer-layout": "^1.2.2", + "camelcase": "^5.3.1", + "cross-fetch": "^3.1.5", + "crypto-hash": "^1.3.0", + "eventemitter3": "^4.0.7", + "js-sha256": "^0.9.0", + "pako": "^2.0.3", + "snake-case": "^3.0.4", + "superstruct": "^0.15.4", + "toml": "^3.0.0" + }, + "engines": { + "node": ">=11" + } + }, + "node_modules/@project-serum/anchor/node_modules/superstruct": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.15.5.tgz", + "integrity": "sha512-4AOeU+P5UuE/4nOUkmcQdW5y7i9ndt1cQd/3iUe+LTz3RxESf/W/5lg4B74HbDMMv8PHnPnGCQFH45kBcrQYoQ==", + "license": "MIT" + }, + "node_modules/@project-serum/borsh": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@project-serum/borsh/-/borsh-0.2.5.tgz", + "integrity": "sha512-UmeUkUoKdQ7rhx6Leve1SssMR/Ghv8qrEiyywyxSWg7ooV7StdpPBhciiy5eB3T0qU1BXvdRNC8TdrkxK7WC5Q==", + "license": "Apache-2.0", + "dependencies": { + "bn.js": "^5.1.2", + "buffer-layout": "^1.2.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@solana/web3.js": "^1.2.0" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, + "node_modules/@scure/base": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", + "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, + "node_modules/@scure/bip32": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.0.tgz", + "integrity": "sha512-bcKpo1oj54hGholplGLpqPHRbIsnbixFtc06nwuNM5/dwSXOq/AAYoIBRsBmnZJSdfeNW5rnff7NTAz3ZCqR9Q==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.0.0", + "@noble/hashes": "~1.3.0", + "@scure/base": "~1.1.0" + } + }, + "node_modules/@scure/bip39": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.0.tgz", + "integrity": "sha512-SX/uKq52cuxm4YFXWFaVByaSHJh2w3BnokVSeUJVCv6K7WulT9u2BuNRBhuFl8vAuYnzx9bEu9WgpcNYTrYieg==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.3.0", + "@scure/base": "~1.1.0" + } + }, + "node_modules/@solana/buffer-layout": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz", + "integrity": "sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==", + "license": "MIT", + "dependencies": { + "buffer": "~6.0.3" + }, + "engines": { + "node": ">=5.10" + } + }, + "node_modules/@solana/buffer-layout-utils": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@solana/buffer-layout-utils/-/buffer-layout-utils-0.2.0.tgz", + "integrity": "sha512-szG4sxgJGktbuZYDg2FfNmkMi0DYQoVjN2h7ta1W1hPrwzarcFLBq9UpX1UjNXsNpT9dn+chgprtWGioUAr4/g==", + "license": "Apache-2.0", + "dependencies": { + "@solana/buffer-layout": "^4.0.0", + "@solana/web3.js": "^1.32.0", + "bigint-buffer": "^1.1.5", + "bignumber.js": "^9.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@solana/spl-token": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@solana/spl-token/-/spl-token-0.3.7.tgz", + "integrity": "sha512-bKGxWTtIw6VDdCBngjtsGlKGLSmiu/8ghSt/IOYJV24BsymRbgq7r12GToeetpxmPaZYLddKwAz7+EwprLfkfg==", + "license": "Apache-2.0", + "dependencies": { + "@solana/buffer-layout": "^4.0.0", + "@solana/buffer-layout-utils": "^0.2.0", + "buffer": "^6.0.3" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@solana/web3.js": "^1.47.4" + } + }, + "node_modules/@solana/web3.js": { + "version": "1.75.0", + "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.75.0.tgz", + "integrity": "sha512-rHQgdo1EWfb+nPUpHe4O7i8qJPELHKNR5PAZRK+a7XxiykqOfbaAlPt5boDWAGPnYbSv0ziWZv5mq9DlFaQCxg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@noble/ed25519": "^1.7.0", + "@noble/hashes": "^1.1.2", + "@noble/secp256k1": "^1.6.3", + "@solana/buffer-layout": "^4.0.0", + "agentkeepalive": "^4.2.1", + "bigint-buffer": "^1.1.5", + "bn.js": "^5.0.0", + "borsh": "^0.7.0", + "bs58": "^4.0.1", + "buffer": "6.0.3", + "fast-stable-stringify": "^1.0.0", + "jayson": "^3.4.4", + "node-fetch": "^2.6.7", + "rpc-websockets": "^7.5.1", + "superstruct": "^0.14.2" + } + }, + "node_modules/@solana/web3.js/node_modules/@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", + "license": "MIT" + }, + "node_modules/@solana/web3.js/node_modules/jayson": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/jayson/-/jayson-3.7.0.tgz", + "integrity": "sha512-tfy39KJMrrXJ+mFcMpxwBvFDetS8LAID93+rycFglIQM4kl3uNR3W4lBLE/FFhsoUCEox5Dt2adVpDm/XtebbQ==", + "license": "MIT", + "dependencies": { + "@types/connect": "^3.4.33", + "@types/node": "^12.12.54", + "@types/ws": "^7.4.4", + "commander": "^2.20.3", + "delay": "^5.0.0", + "es6-promisify": "^5.0.0", + "eyes": "^0.1.8", + "isomorphic-ws": "^4.0.1", + "json-stringify-safe": "^5.0.1", + "JSONStream": "^1.3.5", + "lodash": "^4.17.20", + "uuid": "^8.3.2", + "ws": "^7.4.5" + }, + "bin": { + "jayson": "bin/jayson.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@solana/web3.js/node_modules/superstruct": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.14.2.tgz", + "integrity": "sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ==", + "license": "MIT" + }, + "node_modules/@suchipi/femver": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@suchipi/femver/-/femver-1.0.0.tgz", + "integrity": "sha512-bprE8+K5V+DPX7q2e2K57ImqNBdfGHDIWaGI5xHxZoxbKOuQZn4wzPiUxOAHnsUr3w3xHrWXwN7gnG/iIuEMIg==", + "license": "MIT" + }, + "node_modules/@terra-money/legacy.proto": { + "name": "@terra-money/terra.proto", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@terra-money/terra.proto/-/terra.proto-0.1.7.tgz", + "integrity": "sha512-NXD7f6pQCulvo6+mv6MAPzhOkUzRjgYVuHZE/apih+lVnPG5hDBU0rRYnOGGofwvKT5/jQoOENnFn/gioWWnyQ==", + "license": "Apache-2.0", + "dependencies": { + "google-protobuf": "^3.17.3", + "long": "^4.0.0", + "protobufjs": "~6.11.2" + } + }, + "node_modules/@terra-money/terra.js": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@terra-money/terra.js/-/terra.js-3.1.8.tgz", + "integrity": "sha512-Cd/fh4MswT00fDGVckoZ0cm77EpIy4+CjSDO0RqZ3Qfp4CJBp7sWTLRNsyzUWjdYOT5iTx+1wOMCYbbyKo6LAw==", + "license": "MIT", + "dependencies": { + "@classic-terra/terra.proto": "^1.1.0", + "@terra-money/terra.proto": "^2.1.0", + "axios": "^0.27.2", + "bech32": "^2.0.0", + "bip32": "^2.0.6", + "bip39": "^3.0.3", + "bufferutil": "^4.0.3", + "decimal.js": "^10.2.1", + "jscrypto": "^1.0.1", + "readable-stream": "^3.6.0", + "secp256k1": "^4.0.2", + "tmp": "^0.2.1", + "utf-8-validate": "^5.0.5", + "ws": "^7.5.9" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@terra-money/terra.js/node_modules/axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + } + }, + "node_modules/@terra-money/terra.proto": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@terra-money/terra.proto/-/terra.proto-2.1.0.tgz", + "integrity": "sha512-rhaMslv3Rkr+QsTQEZs64FKA4QlfO0DfQHaR6yct/EovenMkibDEQ63dEL6yJA6LCaEQGYhyVB9JO9pTUA8ybw==", + "license": "Apache-2.0", + "dependencies": { + "@improbable-eng/grpc-web": "^0.14.1", + "google-protobuf": "^3.17.3", + "long": "^4.0.0", + "protobufjs": "~6.11.2" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==" + }, + "node_modules/@types/bn.js": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz", + "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/lodash": { + "version": "4.14.192", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.192.tgz", + "integrity": "sha512-km+Vyn3BYm5ytMO13k9KTp27O75rbQ0NFw+U//g+PX7VZyjCioXaRFisqSIJRECljcTv73G3i6BpglNGHgUQ5A==", + "license": "MIT" + }, + "node_modules/@types/lodash.values": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/@types/lodash.values/-/lodash.values-4.3.7.tgz", + "integrity": "sha512-Moex9/sWxtKEa+BKiH5zvmhfcieDlcz4wRxMhO/oJ2qOKUdujoU6dQjUTxWA8jwEREpHXmiY4HCwNRpycW8JQA==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "license": "MIT" + }, + "node_modules/@types/mocha": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", + "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "18.15.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz", + "integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==", + "license": "MIT" + }, + "node_modules/@types/pbkdf2": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.0.tgz", + "integrity": "sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/secp256k1": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.3.tgz", + "integrity": "sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ws": { + "version": "7.4.7", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", + "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@wry/context": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@wry/context/-/context-0.7.0.tgz", + "integrity": "sha512-LcDAiYWRtwAoSOArfk7cuYvFXytxfVrdX7yxoUmK7pPITLk5jYh2F8knCwS7LjgYL8u1eidPlKKV6Ikqq0ODqQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@wry/equality": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.5.3.tgz", + "integrity": "sha512-avR+UXdSrsF2v8vIqIgmeTY0UR91UT+IyablCyKe/uk22uOJ8fusKZnH9JH9e1/EtLeNJBtagNmL3eJdnOV53g==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@wry/trie": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@wry/trie/-/trie-0.3.2.tgz", + "integrity": "sha512-yRTyhWSls2OY/pYLfwff867r8ekooZ4UI+/gxot5Wj8EFwSf2rG+n+Mo/6LoLQm1TKA4GRj2+LCpbfS937dClQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@xpla/xpla.js": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@xpla/xpla.js/-/xpla.js-0.2.3.tgz", + "integrity": "sha512-Tfk7hCGWXtwr08reY3Pi6dmzIqFbzri9jcyzJdfNmdo4cN0PMwpRJuZZcPmtxiIUnNef3AN1E/6nJUD5MKniuA==", + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/keccak256": "^5.6.1", + "@ethersproject/signing-key": "^5.6.2", + "@terra-money/legacy.proto": "npm:@terra-money/terra.proto@^0.1.7", + "@terra-money/terra.proto": "^2.1.0", + "axios": "^0.26.1", + "bech32": "^2.0.0", + "bip32": "^2.0.6", + "bip39": "^3.0.3", + "bufferutil": "^4.0.3", + "crypto-addr-codec": "^0.1.7", + "decimal.js": "^10.2.1", + "elliptic": "^6.5.4", + "ethereumjs-util": "^7.1.5", + "jscrypto": "^1.0.1", + "readable-stream": "^3.6.0", + "secp256k1": "^4.0.2", + "tmp": "^0.2.1", + "utf-8-validate": "^5.0.5", + "ws": "^7.5.8" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@xpla/xpla.js/node_modules/axios": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.14.8" + } + }, + "node_modules/acorn": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", + "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/aes-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", + "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==", + "license": "MIT" + }, + "node_modules/agentkeepalive": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.3.0.tgz", + "integrity": "sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg==", + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "depd": "^2.0.0", + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/algo-msgpack-with-bigint": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/algo-msgpack-with-bigint/-/algo-msgpack-with-bigint-2.1.1.tgz", + "integrity": "sha512-F1tGh056XczEaEAqu7s+hlZUDWwOBT70Eq0lfMpBP2YguSQVyxRbprLq5rELXKQOyOaixTWYhMeMQMzP0U5FoQ==", + "license": "ISC", + "engines": { + "node": ">= 10" + } + }, + "node_modules/algosdk": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/algosdk/-/algosdk-1.24.1.tgz", + "integrity": "sha512-9moZxdqeJ6GdE4N6fA/GlUP4LrbLZMYcYkt141J4Ss68OfEgH9qW0wBuZ3ZOKEx/xjc5bg7mLP2Gjg7nwrkmww==", + "license": "MIT", + "dependencies": { + "algo-msgpack-with-bigint": "^2.1.1", + "buffer": "^6.0.2", + "cross-fetch": "^3.1.5", + "hi-base32": "^0.5.1", + "js-sha256": "^0.9.0", + "js-sha3": "^0.8.0", + "js-sha512": "^0.8.0", + "json-bigint": "^1.0.0", + "tweetnacl": "^1.0.3", + "vlq": "^2.0.4" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/aptos": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/aptos/-/aptos-1.5.0.tgz", + "integrity": "sha512-N7OuRtU7IYHkDkNx+4QS3g/QQGCp+36KzYn3oXPmT7Kttfuv+UKliQVdjy3cLmwd/DCQSh9ObTovwdxnHjUn0g==", + "license": "Apache-2.0", + "dependencies": { + "@noble/hashes": "1.1.3", + "@scure/bip39": "1.1.0", + "axios": "0.27.2", + "form-data": "4.0.0", + "tweetnacl": "1.0.3" + }, + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/aptos/node_modules/@noble/hashes": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.3.tgz", + "integrity": "sha512-CE0FCR57H2acVI5UOzIGSSIYxZ6v/HOhDR0Ro9VLyhnzLwx0o8W1mmgaqlEUx4049qJDlIBRztv5k+MM8vbO3A==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, + "node_modules/aptos/node_modules/@scure/bip39": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.0.tgz", + "integrity": "sha512-pwrPOS16VeTKg98dYXQyIjJEcWfz7/1YJIwxUEPFfQPtc86Ym/1sVgQ2RLoD43AazMk2l/unK4ITySSpW2+82w==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.1.1", + "@scure/base": "~1.1.0" + } + }, + "node_modules/aptos/node_modules/@scure/bip39/node_modules/@noble/hashes": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.5.tgz", + "integrity": "sha512-LTMZiiLc+V4v1Yi16TD6aX2gmtKszNye0pQgbaLqkvhIqP7nVsSaJsWloGQjJfJ8offaoP5GtX3yY5swbcJxxQ==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, + "node_modules/aptos/node_modules/axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base-x": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", + "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bech32": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", + "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==", + "license": "MIT" + }, + "node_modules/big-integer": { + "version": "1.6.36", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.36.tgz", + "integrity": "sha512-t70bfa7HYEA1D9idDbmuv7YbsbVkQ+Hp+8KFSul4aE5e/i1bjCNIRYJZlA8Q8p0r9T8cF/RVvwUgRA//FydEyg==", + "license": "Unlicense", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/bigint-buffer": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bigint-buffer/-/bigint-buffer-1.1.5.tgz", + "integrity": "sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "bindings": "^1.3.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/bignumber.js": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/binary-parser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/binary-parser/-/binary-parser-2.2.1.tgz", + "integrity": "sha512-5ATpz/uPDgq5GgEDxTB4ouXCde7q2lqAQlSdBRQVl/AJnxmQmhIfyxJx+0MGu//D5rHQifkfGbWWlaysG0o9NA==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bip32": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/bip32/-/bip32-2.0.6.tgz", + "integrity": "sha512-HpV5OMLLGTjSVblmrtYRfFFKuQB+GArM0+XP8HGWfJ5vxYBqo+DesvJwOdC2WJ3bCkZShGf0QIfoIpeomVzVdA==", + "license": "MIT", + "dependencies": { + "@types/node": "10.12.18", + "bs58check": "^2.1.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "tiny-secp256k1": "^1.1.3", + "typeforce": "^1.11.5", + "wif": "^2.0.6" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bip32/node_modules/@types/node": { + "version": "10.12.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz", + "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==", + "license": "MIT" + }, + "node_modules/bip39": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.1.0.tgz", + "integrity": "sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==", + "license": "ISC", + "dependencies": { + "@noble/hashes": "^1.2.0" + } + }, + "node_modules/bip66": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz", + "integrity": "sha512-nemMHz95EmS38a26XbbdxIYj5csHd3RMP3H5bwQknX0WYHF01qhpufP42mLOwVICuH2JmhIhXiWs89MfUGL7Xw==", + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/blakejs": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", + "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==", + "license": "MIT" + }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "license": "MIT" + }, + "node_modules/borsh": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz", + "integrity": "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==", + "license": "Apache-2.0", + "dependencies": { + "bn.js": "^5.2.0", + "bs58": "^4.0.0", + "text-encoding-utf-8": "^1.0.2" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "license": "MIT" + }, + "node_modules/browser-headers": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/browser-headers/-/browser-headers-0.4.1.tgz", + "integrity": "sha512-CA9hsySZVo9371qEHjHZtYxV2cFtVj5Wj/ZHi8ooEsrtm4vOnl9Y9HmyYWk9q+05d7K3rdoAE0j3MVEFVvtQtg==", + "license": "Apache-2.0" + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "license": "ISC" + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "license": "MIT", + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "license": "MIT", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "license": "MIT", + "dependencies": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/buffer-layout": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/buffer-layout/-/buffer-layout-1.2.2.tgz", + "integrity": "sha512-kWSuLN694+KTk8SrYvCqwP2WcgQjoRCiF5b4QDvkkz8EmgD+aWAIceGFKMIAdmF/pH+vpgNV3d3kAKorcdAmWA==", + "license": "MIT", + "engines": { + "node": ">=4.5" + } + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "license": "MIT" + }, + "node_modules/bufferutil": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz", + "integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/capability": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/capability/-/capability-0.2.5.tgz", + "integrity": "sha512-rsJZYVCgXd08sPqwmaIqjAd5SUTfonV0z/gDJ8D6cN8wQphky1kkAYEqQ+hmDxTw7UihvBfjUVUSY+DBEe44jg==", + "license": "MIT" + }, + "node_modules/chai": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", + "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "license": "MIT", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^4.1.2", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/copyfiles": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/copyfiles/-/copyfiles-2.4.1.tgz", + "integrity": "sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==", + "license": "MIT", + "dependencies": { + "glob": "^7.0.5", + "minimatch": "^3.0.3", + "mkdirp": "^1.0.4", + "noms": "0.0.0", + "through2": "^2.0.1", + "untildify": "^4.0.0", + "yargs": "^16.1.0" + }, + "bin": { + "copyfiles": "copyfiles", + "copyup": "copyfiles" + } + }, + "node_modules/copyfiles/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cosmjs-types": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.7.2.tgz", + "integrity": "sha512-vf2uLyktjr/XVAgEq0DjMxeAWh1yYREe7AMHDKd7EiHVqxBPCaBS+qEEQUkXbR9ndnckqr1sUG8BQhazh4X5lA==", + "license": "Apache-2.0", + "dependencies": { + "long": "^4.0.0", + "protobufjs": "~6.11.2" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + }, + "node_modules/cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "license": "MIT", + "dependencies": { + "node-fetch": "2.6.7" + } + }, + "node_modules/cross-fetch/node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/crypto-addr-codec": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/crypto-addr-codec/-/crypto-addr-codec-0.1.7.tgz", + "integrity": "sha512-X4hzfBzNhy4mAc3UpiXEC/L0jo5E8wAa9unsnA8nNXYzXjCcGk83hfC5avJWCSGT8V91xMnAS9AKMHmjw5+XCg==", + "license": "MIT", + "dependencies": { + "base-x": "^3.0.8", + "big-integer": "1.6.36", + "blakejs": "^1.1.0", + "bs58": "^4.0.1", + "ripemd160-min": "0.0.6", + "safe-buffer": "^5.2.0", + "sha3": "^2.1.1" + } + }, + "node_modules/crypto-hash": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/crypto-hash/-/crypto-hash-1.3.0.tgz", + "integrity": "sha512-lyAZ0EMyjDkVvz8WOeVnuCPvKVBXcMv1l5SVqO1yC7PzTwrD/pPje/BIRbWhMoPe436U+Y2nD7f5bFx0kt+Sbg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "license": "MIT" + }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "license": "MIT", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "license": "MIT", + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delay": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz", + "integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/drbg.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/drbg.js/-/drbg.js-1.0.1.tgz", + "integrity": "sha512-F4wZ06PvqxYLFEZKkFxTDcns9oFNk34hvmJSEwdzsxVQ8YI5YaxtACgQatkYgv2VI2CFkUd2Y+xosPQnHv809g==", + "license": "MIT", + "optional": true, + "dependencies": { + "browserify-aes": "^1.0.6", + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/eccrypto": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/eccrypto/-/eccrypto-1.1.6.tgz", + "integrity": "sha512-d78ivVEzu7Tn0ZphUUaL43+jVPKTMPFGtmgtz1D0LrFn7cY3K8CdrvibuLz2AAkHBLKZtR8DMbB2ukRYFk987A==", + "hasInstallScript": true, + "license": "CC0-1.0", + "dependencies": { + "acorn": "7.1.1", + "elliptic": "6.5.4", + "es6-promise": "4.2.8", + "nan": "2.14.0" + }, + "optionalDependencies": { + "secp256k1": "3.7.1" + } + }, + "node_modules/eccrypto/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "license": "MIT", + "optional": true + }, + "node_modules/eccrypto/node_modules/nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "license": "MIT" + }, + "node_modules/eccrypto/node_modules/secp256k1": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.7.1.tgz", + "integrity": "sha512-1cf8sbnRreXrQFdH6qsg2H71Xw91fCCS9Yp021GnUNJzWJS/py96fS4lHbnTnouLp08Xj6jBoBB6V78Tdbdu5g==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "bindings": "^1.5.0", + "bip66": "^1.1.5", + "bn.js": "^4.11.8", + "create-hash": "^1.2.0", + "drbg.js": "^1.0.1", + "elliptic": "^6.4.1", + "nan": "^2.14.0", + "safe-buffer": "^5.1.2" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/eccrypto/node_modules/secp256k1/node_modules/nan": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", + "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", + "license": "MIT", + "optional": true + }, + "node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/error-polyfill": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/error-polyfill/-/error-polyfill-0.1.3.tgz", + "integrity": "sha512-XHJk60ufE+TG/ydwp4lilOog549iiQF2OAPhkk9DdiYWMrltz5yhDz/xnKuenNwP7gy3dsibssO5QpVhkrSzzg==", + "license": "MIT", + "dependencies": { + "capability": "^0.2.5", + "o3": "^1.0.3", + "u3": "^0.1.1" + } + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "license": "MIT" + }, + "node_modules/es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", + "license": "MIT", + "dependencies": { + "es6-promise": "^4.0.3" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eth-crypto": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/eth-crypto/-/eth-crypto-2.6.0.tgz", + "integrity": "sha512-GCX4ffFYRUGgnuWR5qxcZIRQJ1KEqPFiyXU9yVy7s6dtXIMlUXZQ2h+5ID6rFaOHWbpJbjfkC6YdhwtwRYCnug==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "7.20.13", + "@ethereumjs/tx": "3.5.2", + "@types/bn.js": "5.1.1", + "eccrypto": "1.1.6", + "ethereumjs-util": "7.1.5", + "ethers": "5.7.2", + "secp256k1": "5.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/pubkey" + } + }, + "node_modules/eth-crypto/node_modules/@babel/runtime": { + "version": "7.20.13", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz", + "integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/eth-crypto/node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", + "license": "MIT" + }, + "node_modules/eth-crypto/node_modules/secp256k1": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-5.0.0.tgz", + "integrity": "sha512-TKWX8xvoGHrxVdqbYeZM9w+izTF4b9z3NhSaDkdn81btvuh+ivbIMGT/zQvDtTFWhRlThpoz6LEYTr7n8A5GcA==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "elliptic": "^6.5.4", + "node-addon-api": "^5.0.0", + "node-gyp-build": "^4.2.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "license": "MIT", + "dependencies": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + }, + "node_modules/ethereumjs-abi": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz", + "integrity": "sha512-Tx0r/iXI6r+lRsdvkFDlut0N08jWMnKRZ6Gkq+Nmw75lZe4e6o3EkSnkaBP5NF6+m5PTGAr9JP43N3LyeoglsA==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.8", + "ethereumjs-util": "^6.0.0" + } + }, + "node_modules/ethereumjs-abi/node_modules/@types/bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/ethereumjs-abi/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "license": "MIT" + }, + "node_modules/ethereumjs-abi/node_modules/ethereumjs-util": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", + "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", + "license": "MPL-2.0", + "dependencies": { + "@types/bn.js": "^4.11.3", + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "0.1.6", + "rlp": "^2.2.3" + } + }, + "node_modules/ethereumjs-util": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", + "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", + "license": "MPL-2.0", + "dependencies": { + "@types/bn.js": "^5.1.0", + "bn.js": "^5.1.2", + "create-hash": "^1.1.2", + "ethereum-cryptography": "^0.1.3", + "rlp": "^2.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/ethers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz", + "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abi": "5.7.0", + "@ethersproject/abstract-provider": "5.7.0", + "@ethersproject/abstract-signer": "5.7.0", + "@ethersproject/address": "5.7.0", + "@ethersproject/base64": "5.7.0", + "@ethersproject/basex": "5.7.0", + "@ethersproject/bignumber": "5.7.0", + "@ethersproject/bytes": "5.7.0", + "@ethersproject/constants": "5.7.0", + "@ethersproject/contracts": "5.7.0", + "@ethersproject/hash": "5.7.0", + "@ethersproject/hdnode": "5.7.0", + "@ethersproject/json-wallets": "5.7.0", + "@ethersproject/keccak256": "5.7.0", + "@ethersproject/logger": "5.7.0", + "@ethersproject/networks": "5.7.1", + "@ethersproject/pbkdf2": "5.7.0", + "@ethersproject/properties": "5.7.0", + "@ethersproject/providers": "5.7.2", + "@ethersproject/random": "5.7.0", + "@ethersproject/rlp": "5.7.0", + "@ethersproject/sha2": "5.7.0", + "@ethersproject/signing-key": "5.7.0", + "@ethersproject/solidity": "5.7.0", + "@ethersproject/strings": "5.7.0", + "@ethersproject/transactions": "5.7.0", + "@ethersproject/units": "5.7.0", + "@ethersproject/wallet": "5.7.0", + "@ethersproject/web": "5.7.1", + "@ethersproject/wordlists": "5.7.0" + } + }, + "node_modules/ethjs-util": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz", + "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==", + "license": "MIT", + "dependencies": { + "is-hex-prefixed": "1.0.0", + "strip-hex-prefix": "1.0.0" + }, + "engines": { + "node": ">=6.5.0", + "npm": ">=3" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "license": "MIT", + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", + "engines": { + "node": "> 0.1.90" + } + }, + "node_modules/fast-stable-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz", + "integrity": "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==", + "license": "MIT" + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "license": "MIT" + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "license": "ISC", + "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" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/google-protobuf": { + "version": "3.21.2", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.2.tgz", + "integrity": "sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA==", + "license": "(BSD-3-Clause AND Apache-2.0)" + }, + "node_modules/graphql": { + "version": "16.6.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.6.0.tgz", + "integrity": "sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==", + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/graphql-tag": { + "version": "2.12.6", + "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz", + "integrity": "sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/hi-base32": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/hi-base32/-/hi-base32-0.5.1.tgz", + "integrity": "sha512-EmBBpvdYh/4XxsnUybsPag6VikPYnN30td+vQk+GI3qpahVEG9+gTkG0aXVxTjBqQ5T6ijbWIu77O+C5WFWsnA==", + "license": "MIT" + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "license": "MIT", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-errors/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-status-codes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.2.0.tgz", + "integrity": "sha512-feERVo9iWxvnejp3SEfm/+oNG517npqL2/PIA8ORjyOZjGC7TwCRQsZylciLS64i6pJ0wRYz3rkXLRwbtFa8Ng==", + "license": "MIT" + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "license": "MIT", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hex-prefixed": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz", + "integrity": "sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA==", + "license": "MIT", + "engines": { + "node": ">=6.5.0", + "npm": ">=3" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "license": "MIT" + }, + "node_modules/isomorphic-ws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", + "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/jayson": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.0.0.tgz", + "integrity": "sha512-v2RNpDCMu45fnLzSk47vx7I+QUaOsox6f5X0CUlabAFwxoP+8MfAY0NQRFwOEYXIxm8Ih5y6OaEa5KYiQMkyAA==", + "license": "MIT", + "dependencies": { + "@types/connect": "^3.4.33", + "@types/node": "^12.12.54", + "@types/ws": "^7.4.4", + "commander": "^2.20.3", + "delay": "^5.0.0", + "es6-promisify": "^5.0.0", + "eyes": "^0.1.8", + "isomorphic-ws": "^4.0.1", + "json-stringify-safe": "^5.0.1", + "JSONStream": "^1.3.5", + "uuid": "^8.3.2", + "ws": "^7.4.5" + }, + "bin": { + "jayson": "bin/jayson.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jayson/node_modules/@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", + "license": "MIT" + }, + "node_modules/js-base64": { + "version": "3.7.5", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.5.tgz", + "integrity": "sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA==", + "license": "BSD-3-Clause" + }, + "node_modules/js-sha256": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", + "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==", + "license": "MIT" + }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", + "license": "MIT" + }, + "node_modules/js-sha512": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha512/-/js-sha512-0.8.0.tgz", + "integrity": "sha512-PWsmefG6Jkodqt+ePTvBZCSMFgN7Clckjd0O7su3I0+BW2QWUTJNzjktHsztGLhncP2h8mcF9V9Y2Ha59pAViQ==", + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jscrypto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/jscrypto/-/jscrypto-1.0.3.tgz", + "integrity": "sha512-lryZl0flhodv4SZHOqyb1bx5sKcJxj0VBo0Kzb4QMAg3L021IC9uGpl0RCZa+9KJwlRGSK2C80ITcwbe19OKLQ==", + "license": "MIT", + "bin": { + "jscrypto": "bin/cli.js" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "license": "MIT", + "optional": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "engines": [ + "node >= 0.2.0" + ], + "license": "MIT" + }, + "node_modules/jsonschema": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz", + "integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "license": "(MIT OR Apache-2.0)", + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/keccak": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.3.tgz", + "integrity": "sha512-JZrLIAJWuZxKbCilMpNz5Vj7Vtb4scDG3dMXLOsbzBmQGyjwE61BbW7bJkfKKCShXiQZt3T6sBgALRtmd+nZaQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/keccak256": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/keccak256/-/keccak256-1.0.6.tgz", + "integrity": "sha512-8GLiM01PkdJVGUhR1e6M/AvWnSqYS0HaERI+K/QtStGDGlSTx2B1zTqZk4Zlqu5TxHJNTxWAdP9Y+WI50OApUw==", + "license": "MIT", + "dependencies": { + "bn.js": "^5.2.0", + "buffer": "^6.0.3", + "keccak": "^3.0.2" + } + }, + "node_modules/libsodium": { + "version": "0.7.11", + "resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.11.tgz", + "integrity": "sha512-WPfJ7sS53I2s4iM58QxY3Inb83/6mjlYgcmZs7DJsvDlnmVUwNinBCi5vBT43P6bHRy01O4zsMU2CoVR6xJ40A==", + "license": "ISC" + }, + "node_modules/libsodium-wrappers": { + "version": "0.7.11", + "resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.11.tgz", + "integrity": "sha512-SrcLtXj7BM19vUKtQuyQKiQCRJPgbpauzl3s0rSwD+60wtHqSUuqcoawlMDheCJga85nKOQwxNYQxf/CKAvs6Q==", + "license": "ISC", + "dependencies": { + "libsodium": "^0.7.11" + } + }, + "node_modules/link-module-alias": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/link-module-alias/-/link-module-alias-1.2.0.tgz", + "integrity": "sha512-ahPjXepbSVKbahTB6LxR//VHm8HPfI+QQygCH+E82spBY4HR5VPJTvlhKBc9F7muVxnS6C1rRfoPOXAbWO/fyw==", + "license": "MIT", + "dependencies": { + "chalk": "^2.4.1" + }, + "bin": { + "link-module-alias": "index.js" + }, + "engines": { + "node": "> 8.0.0" + } + }, + "node_modules/link-module-alias/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/link-module-alias/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/link-module-alias/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/link-module-alias/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/link-module-alias/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/link-module-alias/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/link-module-alias/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.values": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-4.3.0.tgz", + "integrity": "sha512-r0RwvdCv8id9TUblb/O7rYPwVy6lerCbcawrfdo9iC/1t1wsNMJknO79WNBgwkH0hIeJ08jmvvESbFpNb4jH0Q==", + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "license": "Apache-2.0" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loupe": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.0" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "license": "ISC" + }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "license": "MIT", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "license": "MIT", + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/mocha/node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "license": "MIT", + "bin": { + "mustache": "bin/mustache" + } + }, + "node_modules/nan": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", + "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/near-api-js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/near-api-js/-/near-api-js-1.1.0.tgz", + "integrity": "sha512-qYKv1mYsaDZc2uYndhS+ttDhR9+60qFc+ZjD6lWsAxr3ZskMjRwPffDGQZYhC7BRDQMe1HEbk6d5mf+TVm0Lqg==", + "license": "(MIT AND Apache-2.0)", + "dependencies": { + "bn.js": "5.2.1", + "borsh": "^0.7.0", + "bs58": "^4.0.0", + "depd": "^2.0.0", + "error-polyfill": "^0.1.3", + "http-errors": "^1.7.2", + "js-sha256": "^0.9.0", + "mustache": "^4.0.0", + "node-fetch": "^2.6.1", + "text-encoding-utf-8": "^1.0.2", + "tweetnacl": "^1.0.1" + } + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-addon-api": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp-build": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", + "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/noms": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz", + "integrity": "sha512-lNDU9VJaOPxUmXcLb+HQFeUgQQPtMI24Gt6hgfuMHRJgMRHMF/qZ4HJD3GDru4sSw9IQl2jPjAYnQrdIeLbwow==", + "license": "ISC", + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "~1.0.31" + } + }, + "node_modules/noms/node_modules/readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/noms/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/o3": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/o3/-/o3-1.0.3.tgz", + "integrity": "sha512-f+4n+vC6s4ysy7YO7O2gslWZBUu8Qj2i2OUJOvjRxQva7jVjYjB29jrr9NCjmxZQR0gzrOcv1RnqoYOeMs5VRQ==", + "license": "MIT", + "dependencies": { + "capability": "^0.2.5" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optimism": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/optimism/-/optimism-0.16.2.tgz", + "integrity": "sha512-zWNbgWj+3vLEjZNIh/okkY2EUfX+vB9TJopzIZwT1xxaMqC5hRLLraePod4c5n4He08xuXNH+zhKFFCu390wiQ==", + "license": "MIT", + "dependencies": { + "@wry/context": "^0.7.0", + "@wry/trie": "^0.3.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "license": "(MIT AND Zlib)" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "license": "MIT", + "dependencies": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prettier": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz", + "integrity": "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==", + "license": "MIT", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readonly-date": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/readonly-date/-/readonly-date-1.0.0.tgz", + "integrity": "sha512-tMKIV7hlk0h4mO3JTmmVuIlJVXjKk3Sep9Bf5OH0O+758ruuVkUy2J9SttDLm91IEX/WHlXPSpxMGjPj4beMIQ==", + "license": "Apache-2.0" + }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/response-iterator": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/response-iterator/-/response-iterator-0.2.6.tgz", + "integrity": "sha512-pVzEEzrsg23Sh053rmDUvLSkGXluZio0qu8VT6ukrYuvtjVfCbDZH9d6PGXb8HZfzdNZt8feXv/jvUzlhRgLnw==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "license": "MIT", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/ripemd160-min": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/ripemd160-min/-/ripemd160-min-0.0.6.tgz", + "integrity": "sha512-+GcJgQivhs6S9qvLogusiTcS9kQUfgR75whKuy5jIhuiOfQuJ8fjqxV6EGD5duH1Y/FawFUMtMhyeq3Fbnib8A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/rlp": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.2.7.tgz", + "integrity": "sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==", + "license": "MPL-2.0", + "dependencies": { + "bn.js": "^5.2.0" + }, + "bin": { + "rlp": "bin/rlp" + } + }, + "node_modules/rpc-websockets": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-7.5.1.tgz", + "integrity": "sha512-kGFkeTsmd37pHPMaHIgN1LVKXMi0JD782v4Ds9ZKtLlwdTKjn+CxM9A9/gLT2LaOuEcEFGL98h1QWQtlOIdW0w==", + "license": "LGPL-3.0-only", + "dependencies": { + "@babel/runtime": "^7.17.2", + "eventemitter3": "^4.0.7", + "uuid": "^8.3.2", + "ws": "^8.5.0" + }, + "funding": { + "type": "paypal", + "url": "https://paypal.me/kozjak" + }, + "optionalDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + } + }, + "node_modules/rpc-websockets/node_modules/ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/rxjs": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", + "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/scrypt-js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", + "license": "MIT" + }, + "node_modules/secp256k1": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz", + "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "elliptic": "^6.5.4", + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/sha3": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/sha3/-/sha3-2.1.4.tgz", + "integrity": "sha512-S8cNxbyb0UGUM2VhRD4Poe5N58gJnJsLJ5vC7FYWGUmGhcsj4++WaIOBFVDxlG0W3To6xBuiRh+i0Qp2oNCOtg==", + "license": "MIT", + "dependencies": { + "buffer": "6.0.3" + } + }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "license": "BSD-3-Clause", + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/shx": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz", + "integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.3", + "shelljs": "^0.8.5" + }, + "bin": { + "shx": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/snakecase-keys": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/snakecase-keys/-/snakecase-keys-5.4.5.tgz", + "integrity": "sha512-qSQVcgcWk8mQUN1miVGnRMAUye1dbj9+F9PVkR7wZUXNCidQwrl/kOKmoYf+WbH2ju6c9pXnlmbS2he7pb2/9A==", + "license": "MIT", + "dependencies": { + "map-obj": "^4.1.0", + "snake-case": "^3.0.4", + "type-fest": "^2.5.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/store2": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/store2/-/store2-2.14.2.tgz", + "integrity": "sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==", + "license": "(MIT OR GPL-3.0)" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-hex-prefix": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz", + "integrity": "sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A==", + "license": "MIT", + "dependencies": { + "is-hex-prefixed": "1.0.0" + }, + "engines": { + "node": ">=6.5.0", + "npm": ">=3" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/superstruct": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-1.0.3.tgz", + "integrity": "sha512-8iTn3oSS8nRGn+C2pgXSKPI3jmpm6FExNazNpjvqS6ZUJQCej3PUXEKM8NjHBOs54ExM+LPW/FBRhymrdcCiSg==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-observable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", + "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/text-encoding-utf-8": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz", + "integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==" + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "license": "MIT" + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/through2/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/through2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/through2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/through2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/tiny-secp256k1": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz", + "integrity": "sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.3.0", + "bn.js": "^4.11.8", + "create-hmac": "^1.1.7", + "elliptic": "^6.4.0", + "nan": "^2.13.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/tiny-secp256k1/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "license": "MIT" + }, + "node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "license": "MIT", + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/toml": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz", + "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==", + "license": "MIT" + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/ts-invariant": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.10.3.tgz", + "integrity": "sha512-uivwYcQaxAucv1CzRp2n/QdYPo4ILf9VXgH19zEIjFx2EJufV16P0JtJVpYHy89DItG6Kwj2oIUjrcK5au+4tQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ts-mocha": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/ts-mocha/-/ts-mocha-10.0.0.tgz", + "integrity": "sha512-VRfgDO+iiuJFlNB18tzOfypJ21xn2xbuZyDvJvqpTbWgkAgD17ONGr8t+Tl8rcBtOBdjXp5e/Rk+d39f7XBHRw==", + "license": "MIT", + "dependencies": { + "ts-node": "7.0.1" + }, + "bin": { + "ts-mocha": "bin/ts-mocha" + }, + "engines": { + "node": ">= 6.X.X" + }, + "optionalDependencies": { + "tsconfig-paths": "^3.5.0" + }, + "peerDependencies": { + "mocha": "^3.X.X || ^4.X.X || ^5.X.X || ^6.X.X || ^7.X.X || ^8.X.X || ^9.X.X || ^10.X.X" + } + }, + "node_modules/ts-mocha/node_modules/diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ts-mocha/node_modules/ts-node": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz", + "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==", + "dependencies": { + "arrify": "^1.0.0", + "buffer-from": "^1.1.0", + "diff": "^3.1.0", + "make-error": "^1.1.1", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "source-map-support": "^0.5.6", + "yn": "^2.0.0" + }, + "bin": { + "ts-node": "dist/bin.js" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/ts-mocha/node_modules/yn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", + "integrity": "sha512-uTv8J/wiWTgUTg+9vLTi//leUl5vDQS6uii/emeTb2ssY7vl6QWf2fFbIIGjnhjvbdKlU0ed7QPgY1htTC86jQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "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" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", + "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "license": "0BSD" + }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "license": "Unlicense" + }, + "node_modules/tweetnacl-util": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz", + "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==", + "license": "Unlicense" + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typeforce": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", + "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", + "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/u3": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/u3/-/u3-0.1.1.tgz", + "integrity": "sha512-+J5D5ir763y+Am/QY6hXNRlwljIeRMZMGs0cT6qqZVVzzT3X3nFPXVyPOFRMOR4kupB0T8JnCdpWdp6Q/iXn3w==", + "license": "MIT" + }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" + }, + "node_modules/vlq": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-2.0.4.tgz", + "integrity": "sha512-aodjPa2wPQFkra1G8CzJBTHXhgk3EVSwxSWXNPr1fgdFLUb8kvLV1iEb6rFgasIsjP82HWI6dsb5Io26DDnasA==", + "license": "MIT" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wif": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", + "integrity": "sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ==", + "license": "MIT", + "dependencies": { + "bs58check": "<3.0.0" + } + }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xstream": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/xstream/-/xstream-11.14.0.tgz", + "integrity": "sha512-1bLb+kKKtKPbgTK6i/BaoAn03g47PpFstlbe1BA+y3pNS/LfvcaghS5BFf9+EE1J+KwSQsEpfJvFN5GqFtiNmw==", + "license": "MIT", + "dependencies": { + "globalthis": "^1.0.1", + "symbol-observable": "^2.0.3" + } + }, + "node_modules/xstream/node_modules/symbol-observable": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-2.0.3.tgz", + "integrity": "sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "license": "MIT", + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zen-observable": { + "version": "0.8.15", + "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz", + "integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==", + "license": "MIT" + }, + "node_modules/zen-observable-ts": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-1.2.5.tgz", + "integrity": "sha512-QZWQekv6iB72Naeake9hS1KxHlotfRpe+WGNbNx5/ta+R3DNjVO2bswf63gXlWDcs+EMd7XY8HfVQyP1X6T4Zg==", + "license": "MIT", + "dependencies": { + "zen-observable": "0.8.15" + } + } + } +} diff --git a/sui/testing/package.json b/sui/testing/package.json new file mode 100644 index 000000000..f29c59e15 --- /dev/null +++ b/sui/testing/package.json @@ -0,0 +1,22 @@ +{ + "name": "@wormhole-foundation/wormhole-sui-integration-test", + "version": "0.0.1", + "description": "Wormhole Sui Integration Test", + "main": "index.js", + "license": "MIT", + "dependencies": { + "@certusone/wormhole-sdk": "^0.9.12", + "@mysten/sui.js": "^0.32.2", + "chai": "^4.3.7", + "mocha": "^10.2.0", + "prettier": "^2.8.7", + "ts-mocha": "^10.0.0", + "ts-node": "^10.9.1", + "typescript": "^5.0.4" + }, + "devDependencies": { + "@types/chai": "^4.3.4", + "@types/mocha": "^10.0.1", + "@types/node": "^18.15.11" + } +} diff --git a/sui/testing/run_integration_test.sh b/sui/testing/run_integration_test.sh new file mode 100755 index 000000000..abdaeb67e --- /dev/null +++ b/sui/testing/run_integration_test.sh @@ -0,0 +1,35 @@ +#/bin/bash + +pgrep -f sui > /dev/null +if [ $? -eq 0 ]; then + echo "sui local validator already running" + exit 1; +fi + +TEST_DIR=$(dirname $0) +SUI_CONFIG=$TEST_DIR/sui_config + +### Remove databases generated by localnet +rm -rf $SUI_CONFIG/*_db + +### Start local node +echo "$(date) :: starting localnet" +sui start --network.config $SUI_CONFIG/network.yaml > /dev/null 2>&1 & +sleep 1 + +echo "$(date) :: deploying wormhole and token bridge" +cd $TEST_DIR/.. +bash scripts/deploy.sh devnet \ + -k AGA20wtGcwbcNAG4nwapbQ5wIuXwkYQEWFUoSVAxctHb > deploy.out 2>&1 +cd testing + +## run contract tests here +echo "$(date) :: running tests" +npx ts-mocha -t 1000000 $TEST_DIR/js/*.ts + +# nuke +echo "$(date) :: done" +pkill sui + +# remove databases generated by localnet +rm -rf $SUI_CONFIG/*_db diff --git a/sui/testing/scripts/upgrade-token-bridge.ts b/sui/testing/scripts/upgrade-token-bridge.ts new file mode 100644 index 000000000..605128ddd --- /dev/null +++ b/sui/testing/scripts/upgrade-token-bridge.ts @@ -0,0 +1,300 @@ +import * as mock from "@certusone/wormhole-sdk/lib/cjs/mock"; +import { + RawSigner, + SUI_CLOCK_OBJECT_ID, + TransactionBlock, + fromB64, + normalizeSuiObjectId, + JsonRpcProvider, + Ed25519Keypair, + testnetConnection, +} from "@mysten/sui.js"; +import { execSync } from "child_process"; +import { resolve } from "path"; +import * as fs from "fs"; + +const GOVERNANCE_EMITTER = + "0000000000000000000000000000000000000000000000000000000000000004"; + +const TOKEN_BRIDGE_STATE_ID = + "0x32422cb2f929b6a4e3f81b4791ea11ac2af896b310f3d9442aa1fe924ce0bab4"; +const WORMHOLE_STATE_ID = + "0x69ae41bdef4770895eb4e7aaefee5e4673acc08f6917b4856cf55549c4573ca8"; + +async function main() { + const guardianPrivateKey = process.env.TESTNET_GUARDIAN_PRIVATE_KEY; + if (guardianPrivateKey === undefined) { + throw new Error("TESTNET_GUARDIAN_PRIVATE_KEY unset in environment"); + } + + const walletPrivateKey = process.env.TESTNET_WALLET_PRIVATE_KEY; + if (walletPrivateKey === undefined) { + throw new Error("TESTNET_WALLET_PRIVATE_KEY unset in environment"); + } + + const provider = new JsonRpcProvider(testnetConnection); + const wallet = new RawSigner( + Ed25519Keypair.fromSecretKey( + Buffer.from(walletPrivateKey, "base64").subarray(1) + ), + provider + ); + + const dstTokenBridgePath = resolve(`${__dirname}/../../token_bridge`); + + // Build for digest. + const { modules, dependencies, digest } = + buildForBytecodeAndDigest(dstTokenBridgePath); + console.log("dependencies", dependencies); + console.log("digest", digest.toString("hex")); + + // We will use the signed VAA when we execute the upgrade. + const guardians = new mock.MockGuardians(0, [guardianPrivateKey]); + + const timestamp = 12345678; + const governance = new mock.GovernanceEmitter(GOVERNANCE_EMITTER); + const published = governance.publishWormholeUpgradeContract( + timestamp, + 2, + "0x" + digest.toString("hex") + ); + const moduleName = Buffer.alloc(32); + moduleName.write("TokenBridge", 32 - "TokenBridge".length); + published.write(moduleName.toString(), 84 - 33); + published.writeUInt16BE(21, 84); + published.writeUInt8(2, 83); + //message.writeUInt8(1, 83); + published.writeUInt16BE(21, published.length - 34); + + const signedVaa = guardians.addSignatures(published, [0]); + console.log("Upgrade VAA:", signedVaa.toString("hex")); + + // // And execute upgrade with governance VAA. + // const upgradeResults = await upgradeTokenBridge( + // wallet, + // TOKEN_BRIDGE_STATE_ID, + // WORMHOLE_STATE_ID, + // modules, + // dependencies, + // signedVaa + // ); + + // console.log("tx digest", upgradeResults.digest); + // console.log("tx effects", JSON.stringify(upgradeResults.effects!)); + // console.log("tx events", JSON.stringify(upgradeResults.events!)); + + // TODO: grab new package ID from the events above. Do not rely on the RPC + // call because it may give you a stale package ID after the upgrade. + + const migrateResults = await migrateTokenBridge( + wallet, + TOKEN_BRIDGE_STATE_ID, + WORMHOLE_STATE_ID, + signedVaa + ); + console.log("tx digest", migrateResults.digest); + console.log("tx effects", JSON.stringify(migrateResults.effects!)); + console.log("tx events", JSON.stringify(migrateResults.events!)); +} + +main(); + +// Yeah buddy. + +function buildForBytecodeAndDigest(packagePath: string) { + const buildOutput: { + modules: string[]; + dependencies: string[]; + digest: number[]; + } = JSON.parse( + execSync( + `sui move build --dump-bytecode-as-base64 -p ${packagePath} 2> /dev/null`, + { encoding: "utf-8" } + ) + ); + return { + modules: buildOutput.modules.map((m: string) => Array.from(fromB64(m))), + dependencies: buildOutput.dependencies.map((d: string) => + normalizeSuiObjectId(d) + ), + digest: Buffer.from(buildOutput.digest), + }; +} + +async function getPackageId( + provider: JsonRpcProvider, + stateId: string +): Promise { + const state = await provider + .getObject({ + id: stateId, + options: { + showContent: true, + }, + }) + .then((result) => { + if (result.data?.content?.dataType == "moveObject") { + return result.data.content.fields; + } + + throw new Error("not move object"); + }); + + if ("upgrade_cap" in state) { + return state.upgrade_cap.fields.package; + } + + throw new Error("upgrade_cap not found"); +} + +async function upgradeTokenBridge( + signer: RawSigner, + tokenBridgeStateId: string, + wormholeStateId: string, + modules: number[][], + dependencies: string[], + signedVaa: Buffer +) { + const tokenBridgePackage = await getPackageId( + signer.provider, + tokenBridgeStateId + ); + const wormholePackage = await getPackageId(signer.provider, wormholeStateId); + + const tx = new TransactionBlock(); + + const [verifiedVaa] = tx.moveCall({ + target: `${wormholePackage}::vaa::parse_and_verify`, + arguments: [ + tx.object(wormholeStateId), + tx.pure(Array.from(signedVaa)), + tx.object(SUI_CLOCK_OBJECT_ID), + ], + }); + const [decreeTicket] = tx.moveCall({ + target: `${tokenBridgePackage}::upgrade_contract::authorize_governance`, + arguments: [tx.object(tokenBridgeStateId)], + }); + const [decreeReceipt] = tx.moveCall({ + target: `${wormholePackage}::governance_message::verify_vaa`, + arguments: [tx.object(wormholeStateId), verifiedVaa, decreeTicket], + typeArguments: [ + `${tokenBridgePackage}::upgrade_contract::GovernanceWitness`, + ], + }); + + // Authorize upgrade. + const [upgradeTicket] = tx.moveCall({ + target: `${tokenBridgePackage}::upgrade_contract::authorize_upgrade`, + arguments: [tx.object(tokenBridgeStateId), decreeReceipt], + }); + + // Build and generate modules and dependencies for upgrade. + const [upgradeReceipt] = tx.upgrade({ + modules, + dependencies, + packageId: tokenBridgePackage, + ticket: upgradeTicket, + }); + + // Commit upgrade. + tx.moveCall({ + target: `${tokenBridgePackage}::upgrade_contract::commit_upgrade`, + arguments: [tx.object(tokenBridgeStateId), upgradeReceipt], + }); + + // Cannot auto compute gas budget, so we need to configure it manually. + // Gas ~215m. + //tx.setGasBudget(1_000_000_000n); + + return signer.signAndExecuteTransactionBlock({ + transactionBlock: tx, + options: { + showEffects: true, + showEvents: true, + }, + }); +} + +async function migrateTokenBridge( + signer: RawSigner, + tokenBridgeStateId: string, + wormholeStateId: string, + signedUpgradeVaa: Buffer +) { + const tokenBridgePackage = await getPackageId( + signer.provider, + tokenBridgeStateId + ); + const wormholePackage = await getPackageId(signer.provider, wormholeStateId); + + const tx = new TransactionBlock(); + + const [verifiedVaa] = tx.moveCall({ + target: `${wormholePackage}::vaa::parse_and_verify`, + arguments: [ + tx.object(wormholeStateId), + tx.pure(Array.from(signedUpgradeVaa)), + tx.object(SUI_CLOCK_OBJECT_ID), + ], + }); + const [decreeTicket] = tx.moveCall({ + target: `${tokenBridgePackage}::upgrade_contract::authorize_governance`, + arguments: [tx.object(tokenBridgeStateId)], + }); + const [decreeReceipt] = tx.moveCall({ + target: `${wormholePackage}::governance_message::verify_vaa`, + arguments: [tx.object(wormholeStateId), verifiedVaa, decreeTicket], + typeArguments: [ + `${tokenBridgePackage}::upgrade_contract::GovernanceWitness`, + ], + }); + tx.moveCall({ + target: `${tokenBridgePackage}::migrate::migrate`, + arguments: [tx.object(tokenBridgeStateId), decreeReceipt], + }); + + return signer.signAndExecuteTransactionBlock({ + transactionBlock: tx, + options: { + showEffects: true, + showEvents: true, + }, + }); +} + +function setUpWormholeDirectory( + srcWormholePath: string, + dstWormholePath: string +) { + fs.cpSync(srcWormholePath, dstWormholePath, { recursive: true }); + + // Remove irrelevant files. This part is not necessary, but is helpful + // for debugging a clean package directory. + const removeThese = [ + "Move.devnet.toml", + "Move.lock", + "Makefile", + "README.md", + "build", + ]; + for (const basename of removeThese) { + fs.rmSync(`${dstWormholePath}/${basename}`, { + recursive: true, + force: true, + }); + } + + // Fix Move.toml file. + const moveTomlPath = `${dstWormholePath}/Move.toml`; + const moveToml = fs.readFileSync(moveTomlPath, "utf-8"); + fs.writeFileSync( + moveTomlPath, + moveToml.replace(`wormhole = "_"`, `wormhole = "0x0"`), + "utf-8" + ); +} + +function cleanUpPackageDirectory(packagePath: string) { + fs.rmSync(packagePath, { recursive: true, force: true }); +} diff --git a/sui/testing/scripts/upgrade-wormhole.ts b/sui/testing/scripts/upgrade-wormhole.ts new file mode 100644 index 000000000..82d26b0f7 --- /dev/null +++ b/sui/testing/scripts/upgrade-wormhole.ts @@ -0,0 +1,267 @@ +import * as mock from "@certusone/wormhole-sdk/lib/cjs/mock"; +import { + RawSigner, + SUI_CLOCK_OBJECT_ID, + TransactionBlock, + fromB64, + normalizeSuiObjectId, + JsonRpcProvider, + Ed25519Keypair, + testnetConnection, +} from "@mysten/sui.js"; +import { execSync } from "child_process"; +import { resolve } from "path"; +import * as fs from "fs"; + +const GOVERNANCE_EMITTER = + "0000000000000000000000000000000000000000000000000000000000000004"; + +const WORMHOLE_STATE_ID = + "0x69ae41bdef4770895eb4e7aaefee5e4673acc08f6917b4856cf55549c4573ca8"; + +async function main() { + const guardianPrivateKey = process.env.TESTNET_GUARDIAN_PRIVATE_KEY; + if (guardianPrivateKey === undefined) { + throw new Error("TESTNET_GUARDIAN_PRIVATE_KEY unset in environment"); + } + + const walletPrivateKey = process.env.TESTNET_WALLET_PRIVATE_KEY; + if (walletPrivateKey === undefined) { + throw new Error("TESTNET_WALLET_PRIVATE_KEY unset in environment"); + } + + const provider = new JsonRpcProvider(testnetConnection); + const wallet = new RawSigner( + Ed25519Keypair.fromSecretKey( + Buffer.from(walletPrivateKey, "base64").subarray(1) + ), + provider + ); + + const srcWormholePath = resolve(`${__dirname}/../../wormhole`); + const dstWormholePath = resolve(`${__dirname}/wormhole`); + + // Stage build(s). + setUpWormholeDirectory(srcWormholePath, dstWormholePath); + + // Build for digest. + const { modules, dependencies, digest } = + buildForBytecodeAndDigest(dstWormholePath); + + // We will use the signed VAA when we execute the upgrade. + const guardians = new mock.MockGuardians(0, [guardianPrivateKey]); + + const timestamp = 12345678; + const governance = new mock.GovernanceEmitter(GOVERNANCE_EMITTER); + const published = governance.publishWormholeUpgradeContract( + timestamp, + 2, + "0x" + digest.toString("hex") + ); + published.writeUInt16BE(21, published.length - 34); + + const signedVaa = guardians.addSignatures(published, [0]); + console.log("Upgrade VAA:", signedVaa.toString("hex")); + + // And execute upgrade with governance VAA. + const upgradeResults = await buildAndUpgradeWormhole( + wallet, + WORMHOLE_STATE_ID, + modules, + dependencies, + signedVaa + ); + + console.log("tx digest", upgradeResults.digest); + console.log("tx effects", JSON.stringify(upgradeResults.effects!)); + console.log("tx events", JSON.stringify(upgradeResults.events!)); + + // TODO: grab new package ID from the events above. Do not rely on the RPC + // call because it may give you a stale package ID after the upgrade. + + // const migrateResults = await migrateWormhole( + // wallet, + // WORMHOLE_STATE_ID, + // signedVaa + // ); + // console.log("tx digest", migrateResults.digest); + // console.log("tx effects", JSON.stringify(migrateResults.effects!)); + // console.log("tx events", JSON.stringify(migrateResults.events!)); + + // Clean up. + cleanUpPackageDirectory(dstWormholePath); +} + +main(); + +// Yeah buddy. + +function buildForBytecodeAndDigest(packagePath: string) { + const buildOutput: { + modules: string[]; + dependencies: string[]; + digest: number[]; + } = JSON.parse( + execSync( + `sui move build --dump-bytecode-as-base64 -p ${packagePath} 2> /dev/null`, + { encoding: "utf-8" } + ) + ); + return { + modules: buildOutput.modules.map((m: string) => Array.from(fromB64(m))), + dependencies: buildOutput.dependencies.map((d: string) => + normalizeSuiObjectId(d) + ), + digest: Buffer.from(buildOutput.digest), + }; +} + +async function getPackageId( + provider: JsonRpcProvider, + stateId: string +): Promise { + const state = await provider + .getObject({ + id: stateId, + options: { + showContent: true, + }, + }) + .then((result) => { + if (result.data?.content?.dataType == "moveObject") { + return result.data.content.fields; + } + + throw new Error("not move object"); + }); + + if ("upgrade_cap" in state) { + return state.upgrade_cap.fields.package; + } + + throw new Error("upgrade_cap not found"); +} + +async function buildAndUpgradeWormhole( + signer: RawSigner, + wormholeStateId: string, + modules: number[][], + dependencies: string[], + signedVaa: Buffer +) { + const wormholePackage = await getPackageId(signer.provider, wormholeStateId); + + const tx = new TransactionBlock(); + + const [verifiedVaa] = tx.moveCall({ + target: `${wormholePackage}::vaa::parse_and_verify`, + arguments: [ + tx.object(wormholeStateId), + tx.pure(Array.from(signedVaa)), + tx.object(SUI_CLOCK_OBJECT_ID), + ], + }); + const [decreeTicket] = tx.moveCall({ + target: `${wormholePackage}::upgrade_contract::authorize_governance`, + arguments: [tx.object(wormholeStateId)], + }); + const [decreeReceipt] = tx.moveCall({ + target: `${wormholePackage}::governance_message::verify_vaa`, + arguments: [tx.object(wormholeStateId), verifiedVaa, decreeTicket], + typeArguments: [`${wormholePackage}::upgrade_contract::GovernanceWitness`], + }); + + // Authorize upgrade. + const [upgradeTicket] = tx.moveCall({ + target: `${wormholePackage}::upgrade_contract::authorize_upgrade`, + arguments: [tx.object(wormholeStateId), decreeReceipt], + }); + + // Build and generate modules and dependencies for upgrade. + const [upgradeReceipt] = tx.upgrade({ + modules, + dependencies, + packageId: wormholePackage, + ticket: upgradeTicket, + }); + + // Commit upgrade. + tx.moveCall({ + target: `${wormholePackage}::upgrade_contract::commit_upgrade`, + arguments: [tx.object(wormholeStateId), upgradeReceipt], + }); + + // Cannot auto compute gas budget, so we need to configure it manually. + // Gas ~215m. + //tx.setGasBudget(1_000_000_000n); + + return signer.signAndExecuteTransactionBlock({ + transactionBlock: tx, + options: { + showEffects: true, + showEvents: true, + }, + }); +} + +async function migrateWormhole( + signer: RawSigner, + wormholeStateId: string, + signedUpgradeVaa: Buffer +) { + const contractPackage = await getPackageId(signer.provider, wormholeStateId); + + const tx = new TransactionBlock(); + tx.moveCall({ + target: `${contractPackage}::migrate::migrate`, + arguments: [ + tx.object(wormholeStateId), + tx.pure(Array.from(signedUpgradeVaa)), + tx.object(SUI_CLOCK_OBJECT_ID), + ], + }); + + return signer.signAndExecuteTransactionBlock({ + transactionBlock: tx, + options: { + showEffects: true, + showEvents: true, + }, + }); +} + +function setUpWormholeDirectory( + srcWormholePath: string, + dstWormholePath: string +) { + fs.cpSync(srcWormholePath, dstWormholePath, { recursive: true }); + + // Remove irrelevant files. This part is not necessary, but is helpful + // for debugging a clean package directory. + const removeThese = [ + "Move.devnet.toml", + "Move.lock", + "Makefile", + "README.md", + "build", + ]; + for (const basename of removeThese) { + fs.rmSync(`${dstWormholePath}/${basename}`, { + recursive: true, + force: true, + }); + } + + // Fix Move.toml file. + const moveTomlPath = `${dstWormholePath}/Move.toml`; + const moveToml = fs.readFileSync(moveTomlPath, "utf-8"); + fs.writeFileSync( + moveTomlPath, + moveToml.replace(`wormhole = "_"`, `wormhole = "0x0"`), + "utf-8" + ); +} + +function cleanUpPackageDirectory(packagePath: string) { + fs.rmSync(packagePath, { recursive: true, force: true }); +} diff --git a/sui/testing/sui_config/client.yaml b/sui/testing/sui_config/client.yaml new file mode 100644 index 000000000..33371df95 --- /dev/null +++ b/sui/testing/sui_config/client.yaml @@ -0,0 +1,12 @@ +--- +keystore: + File: sui_config/sui.keystore +envs: + - alias: localnet + rpc: "http://0.0.0.0:9000" + ws: ~ + - alias: devnet + rpc: "https://fullnode.devnet.sui.io:443" + ws: ~ +active_env: localnet +active_address: "0xed867315e3f7c83ae82e6d5858b6a6cc57c291fd84f7509646ebc8162169cf96" diff --git a/sui/testing/sui_config/fullnode.yaml b/sui/testing/sui_config/fullnode.yaml new file mode 100644 index 000000000..fc71e910e --- /dev/null +++ b/sui/testing/sui_config/fullnode.yaml @@ -0,0 +1,53 @@ +--- +protocol-key-pair: + value: W+hPTVWhdFgzHs3YuRHV6gLfgFhHA1WG0pisIXiN8E8= +worker-key-pair: + value: AApEvpZE1O+2GMqZ1AbRE3+Kmgr1O5mdsMZ6I/gLpVSy +account-key-pair: + value: AN7ZHgjN8G7Nw7Q8NtY9TisPBjmEYpdUzbczjqR98XLh +network-key-pair: + value: AAnB6/zZooq4xDtB7oM/GeTSCh5tBxKAyJwWOMPlEJ4R +db-path: sui_config/authorities_db/full_node_db +network-address: /ip4/127.0.0.1/tcp/36683/http +json-rpc-address: "0.0.0.0:9000" +metrics-address: "127.0.0.1:35915" +admin-interface-port: 44319 +enable-event-processing: true +enable-index-processing: true +grpc-load-shed: ~ +grpc-concurrency-limit: ~ +p2p-config: + listen-address: "127.0.0.1:38187" + external-address: /ip4/127.0.0.1/udp/38187 + seed-peers: + - peer-id: ce60e3077e02a3683436af450f3a4511b4c40b158956637caf9ccf11391e7e10 + address: /ip4/127.0.0.1/udp/44061 + - peer-id: 5f0f42cb3fb20dd577703388320964f9351d997313c04a032247060d214b2e71 + address: /ip4/127.0.0.1/udp/46335 + - peer-id: 6d9095130b1536c0c9218ea9feb0f36685a6fa0b3b1e67d256cc4fb340a48d69 + address: /ip4/127.0.0.1/udp/32965 + - peer-id: b2915bf787845a55c24e18fdc162a575eb02d23bae3f9e566d7c51ebcfeb4a42 + address: /ip4/127.0.0.1/udp/39889 +genesis: + genesis-file-location: sui_config/genesis.blob +authority-store-pruning-config: + num-latest-epoch-dbs-to-retain: 3 + epoch-db-pruning-period-secs: 3600 + num-epochs-to-retain: 2 + max-checkpoints-in-batch: 200 + max-transactions-in-batch: 1000 + use-range-deletion: true +end-of-epoch-broadcast-channel-capacity: 128 +checkpoint-executor-config: + checkpoint-execution-max-concurrency: 200 + local-execution-timeout-sec: 30 +db-checkpoint-config: + perform-db-checkpoints-at-epoch-end: false +indirect-objects-threshold: 18446744073709551615 +expensive-safety-check-config: + enable-epoch-sui-conservation-check: false + enable-deep-per-tx-sui-conservation-check: false + force-disable-epoch-sui-conservation-check: false + enable-state-consistency-check: false + force-disable-state-consistency-check: false + enable-move-vm-paranoid-checks: false diff --git a/sui/testing/sui_config/genesis.blob b/sui/testing/sui_config/genesis.blob new file mode 100644 index 000000000..0bf9f806b Binary files /dev/null and b/sui/testing/sui_config/genesis.blob differ diff --git a/sui/testing/sui_config/network.yaml b/sui/testing/sui_config/network.yaml new file mode 100644 index 000000000..37122d68f --- /dev/null +++ b/sui/testing/sui_config/network.yaml @@ -0,0 +1,324 @@ +--- +validator_configs: + - protocol-key-pair: + value: VTDx4HjVmRBqdqBWg2zN+zcFE20io3CrBchGy/iV1lo= + worker-key-pair: + value: ABlC9PMmIQHjxila3AEOXDxwCSuodcvJh2Q5O5HIB00K + account-key-pair: + value: AIV4Ng6OYQf6irjVCZly5X7dSpdFpwoWtdAx9u4PANRl + network-key-pair: + value: AOqJl2rHMnroe26vjkkIuWGBD/y6HzQG6MK5bC9njF0s + db-path: sui_config/authorities_db/99f25ef61f80 + network-address: /ip4/127.0.0.1/tcp/36459/http + json-rpc-address: "127.0.0.1:38133" + metrics-address: "127.0.0.1:44135" + admin-interface-port: 33917 + consensus-config: + address: /ip4/127.0.0.1/tcp/41459/http + db-path: sui_config/consensus_db/99f25ef61f80 + internal-worker-address: ~ + max-pending-transactions: ~ + narwhal-config: + header_num_of_batches_threshold: 32 + max_header_num_of_batches: 1000 + max_header_delay: 2000ms + min_header_delay: 500ms + gc_depth: 50 + sync_retry_delay: 5000ms + sync_retry_nodes: 3 + batch_size: 500000 + max_batch_delay: 100ms + block_synchronizer: + range_synchronize_timeout: 30000ms + certificates_synchronize_timeout: 30000ms + payload_synchronize_timeout: 30000ms + payload_availability_timeout: 30000ms + handler_certificate_deliver_timeout: 30000ms + consensus_api_grpc: + socket_addr: /ip4/127.0.0.1/tcp/44689/http + get_collections_timeout: 5000ms + remove_collections_timeout: 5000ms + max_concurrent_requests: 500000 + prometheus_metrics: + socket_addr: /ip4/127.0.0.1/tcp/33219/http + network_admin_server: + primary_network_admin_server_port: 33945 + worker_network_admin_server_base_port: 38081 + anemo: + send_certificate_rate_limit: ~ + get_payload_availability_rate_limit: ~ + get_certificates_rate_limit: ~ + report_batch_rate_limit: ~ + request_batch_rate_limit: ~ + enable-event-processing: false + enable-index-processing: true + grpc-load-shed: ~ + grpc-concurrency-limit: 20000000000 + p2p-config: + listen-address: "127.0.0.1:44061" + external-address: /ip4/127.0.0.1/udp/44061 + genesis: + genesis-file-location: sui_config/genesis.blob + authority-store-pruning-config: + num-latest-epoch-dbs-to-retain: 3 + epoch-db-pruning-period-secs: 3600 + num-epochs-to-retain: 2 + max-checkpoints-in-batch: 200 + max-transactions-in-batch: 1000 + use-range-deletion: true + end-of-epoch-broadcast-channel-capacity: 128 + checkpoint-executor-config: + checkpoint-execution-max-concurrency: 200 + local-execution-timeout-sec: 30 + db-checkpoint-config: + perform-db-checkpoints-at-epoch-end: false + indirect-objects-threshold: 18446744073709551615 + expensive-safety-check-config: + enable-epoch-sui-conservation-check: false + enable-deep-per-tx-sui-conservation-check: false + force-disable-epoch-sui-conservation-check: false + enable-state-consistency-check: false + force-disable-state-consistency-check: false + enable-move-vm-paranoid-checks: false + - protocol-key-pair: + value: avYcyVgYMXTyaUYh9IRwLK0gSzl7YF6ZQDAbrS1Bhvo= + worker-key-pair: + value: AGsxCVxeIZ6fscvGECzV93Hi4JkqM4zMYEA8wBGfXQrz + account-key-pair: + value: AF9cOMxTRAUTOws2M8W5slHf41HITA+M3nqXHT6nlH6S + network-key-pair: + value: ALH/8qz2YlwAuxY/hOvuXiglYq0e4LLU1/lyf5uKgPY8 + db-path: sui_config/authorities_db/8dcff6d15504 + network-address: /ip4/127.0.0.1/tcp/33355/http + json-rpc-address: "127.0.0.1:39573" + metrics-address: "127.0.0.1:45851" + admin-interface-port: 35739 + consensus-config: + address: /ip4/127.0.0.1/tcp/42959/http + db-path: sui_config/consensus_db/8dcff6d15504 + internal-worker-address: ~ + max-pending-transactions: ~ + narwhal-config: + header_num_of_batches_threshold: 32 + max_header_num_of_batches: 1000 + max_header_delay: 2000ms + min_header_delay: 500ms + gc_depth: 50 + sync_retry_delay: 5000ms + sync_retry_nodes: 3 + batch_size: 500000 + max_batch_delay: 100ms + block_synchronizer: + range_synchronize_timeout: 30000ms + certificates_synchronize_timeout: 30000ms + payload_synchronize_timeout: 30000ms + payload_availability_timeout: 30000ms + handler_certificate_deliver_timeout: 30000ms + consensus_api_grpc: + socket_addr: /ip4/127.0.0.1/tcp/37001/http + get_collections_timeout: 5000ms + remove_collections_timeout: 5000ms + max_concurrent_requests: 500000 + prometheus_metrics: + socket_addr: /ip4/127.0.0.1/tcp/39831/http + network_admin_server: + primary_network_admin_server_port: 39853 + worker_network_admin_server_base_port: 36429 + anemo: + send_certificate_rate_limit: ~ + get_payload_availability_rate_limit: ~ + get_certificates_rate_limit: ~ + report_batch_rate_limit: ~ + request_batch_rate_limit: ~ + enable-event-processing: false + enable-index-processing: true + grpc-load-shed: ~ + grpc-concurrency-limit: 20000000000 + p2p-config: + listen-address: "127.0.0.1:46335" + external-address: /ip4/127.0.0.1/udp/46335 + genesis: + genesis-file-location: sui_config/genesis.blob + authority-store-pruning-config: + num-latest-epoch-dbs-to-retain: 3 + epoch-db-pruning-period-secs: 3600 + num-epochs-to-retain: 2 + max-checkpoints-in-batch: 200 + max-transactions-in-batch: 1000 + use-range-deletion: true + end-of-epoch-broadcast-channel-capacity: 128 + checkpoint-executor-config: + checkpoint-execution-max-concurrency: 200 + local-execution-timeout-sec: 30 + db-checkpoint-config: + perform-db-checkpoints-at-epoch-end: false + indirect-objects-threshold: 18446744073709551615 + expensive-safety-check-config: + enable-epoch-sui-conservation-check: false + enable-deep-per-tx-sui-conservation-check: false + force-disable-epoch-sui-conservation-check: false + enable-state-consistency-check: false + force-disable-state-consistency-check: false + enable-move-vm-paranoid-checks: false + - protocol-key-pair: + value: OXnx3yM1C/ppgnDMx/o1d49fJs7E05kq11mXNae/O+I= + worker-key-pair: + value: AHXs8DP7EccyxtxAGq/m33LgvOApXs4JStH3PLAe9vGw + account-key-pair: + value: AC8vF9E3QYf0aTyBZWlSzJJXETvV5vYkOtEJl+DWQMlk + network-key-pair: + value: AOapcKU6mW5SopFM6eBSiXgbuPJTz11CiEqM+SJGIEOF + db-path: sui_config/authorities_db/addeef94d898 + network-address: /ip4/127.0.0.1/tcp/34633/http + json-rpc-address: "127.0.0.1:38025" + metrics-address: "127.0.0.1:43451" + admin-interface-port: 36793 + consensus-config: + address: /ip4/127.0.0.1/tcp/40307/http + db-path: sui_config/consensus_db/addeef94d898 + internal-worker-address: ~ + max-pending-transactions: ~ + narwhal-config: + header_num_of_batches_threshold: 32 + max_header_num_of_batches: 1000 + max_header_delay: 2000ms + min_header_delay: 500ms + gc_depth: 50 + sync_retry_delay: 5000ms + sync_retry_nodes: 3 + batch_size: 500000 + max_batch_delay: 100ms + block_synchronizer: + range_synchronize_timeout: 30000ms + certificates_synchronize_timeout: 30000ms + payload_synchronize_timeout: 30000ms + payload_availability_timeout: 30000ms + handler_certificate_deliver_timeout: 30000ms + consensus_api_grpc: + socket_addr: /ip4/127.0.0.1/tcp/37445/http + get_collections_timeout: 5000ms + remove_collections_timeout: 5000ms + max_concurrent_requests: 500000 + prometheus_metrics: + socket_addr: /ip4/127.0.0.1/tcp/43943/http + network_admin_server: + primary_network_admin_server_port: 39611 + worker_network_admin_server_base_port: 38377 + anemo: + send_certificate_rate_limit: ~ + get_payload_availability_rate_limit: ~ + get_certificates_rate_limit: ~ + report_batch_rate_limit: ~ + request_batch_rate_limit: ~ + enable-event-processing: false + enable-index-processing: true + grpc-load-shed: ~ + grpc-concurrency-limit: 20000000000 + p2p-config: + listen-address: "127.0.0.1:32965" + external-address: /ip4/127.0.0.1/udp/32965 + genesis: + genesis-file-location: sui_config/genesis.blob + authority-store-pruning-config: + num-latest-epoch-dbs-to-retain: 3 + epoch-db-pruning-period-secs: 3600 + num-epochs-to-retain: 2 + max-checkpoints-in-batch: 200 + max-transactions-in-batch: 1000 + use-range-deletion: true + end-of-epoch-broadcast-channel-capacity: 128 + checkpoint-executor-config: + checkpoint-execution-max-concurrency: 200 + local-execution-timeout-sec: 30 + db-checkpoint-config: + perform-db-checkpoints-at-epoch-end: false + indirect-objects-threshold: 18446744073709551615 + expensive-safety-check-config: + enable-epoch-sui-conservation-check: false + enable-deep-per-tx-sui-conservation-check: false + force-disable-epoch-sui-conservation-check: false + enable-state-consistency-check: false + force-disable-state-consistency-check: false + enable-move-vm-paranoid-checks: false + - protocol-key-pair: + value: CyNkjqNVr3HrHTH7f/NLs7u5lUHJzuPAw0PqMTD2y2s= + worker-key-pair: + value: AHd6qvbBv7bTCGGoD1TUR5dOGnwOnYvhHV9ryCUp7rmZ + account-key-pair: + value: ALSCvWwsVryGIwq+n4f9bIPCRqsooGodE/vDaVCSLfjE + network-key-pair: + value: APFCK1pRVxn9PDt+KzWx52+EY5nzaZZU2GF9RZoQY58Y + db-path: sui_config/authorities_db/b3fd5efb5c87 + network-address: /ip4/127.0.0.1/tcp/33953/http + json-rpc-address: "127.0.0.1:35625" + metrics-address: "127.0.0.1:37813" + admin-interface-port: 46405 + consensus-config: + address: /ip4/127.0.0.1/tcp/43213/http + db-path: sui_config/consensus_db/b3fd5efb5c87 + internal-worker-address: ~ + max-pending-transactions: ~ + narwhal-config: + header_num_of_batches_threshold: 32 + max_header_num_of_batches: 1000 + max_header_delay: 2000ms + min_header_delay: 500ms + gc_depth: 50 + sync_retry_delay: 5000ms + sync_retry_nodes: 3 + batch_size: 500000 + max_batch_delay: 100ms + block_synchronizer: + range_synchronize_timeout: 30000ms + certificates_synchronize_timeout: 30000ms + payload_synchronize_timeout: 30000ms + payload_availability_timeout: 30000ms + handler_certificate_deliver_timeout: 30000ms + consensus_api_grpc: + socket_addr: /ip4/127.0.0.1/tcp/46745/http + get_collections_timeout: 5000ms + remove_collections_timeout: 5000ms + max_concurrent_requests: 500000 + prometheus_metrics: + socket_addr: /ip4/127.0.0.1/tcp/38817/http + network_admin_server: + primary_network_admin_server_port: 34929 + worker_network_admin_server_base_port: 37447 + anemo: + send_certificate_rate_limit: ~ + get_payload_availability_rate_limit: ~ + get_certificates_rate_limit: ~ + report_batch_rate_limit: ~ + request_batch_rate_limit: ~ + enable-event-processing: false + enable-index-processing: true + grpc-load-shed: ~ + grpc-concurrency-limit: 20000000000 + p2p-config: + listen-address: "127.0.0.1:39889" + external-address: /ip4/127.0.0.1/udp/39889 + genesis: + genesis-file-location: sui_config/genesis.blob + authority-store-pruning-config: + num-latest-epoch-dbs-to-retain: 3 + epoch-db-pruning-period-secs: 3600 + num-epochs-to-retain: 2 + max-checkpoints-in-batch: 200 + max-transactions-in-batch: 1000 + use-range-deletion: true + end-of-epoch-broadcast-channel-capacity: 128 + checkpoint-executor-config: + checkpoint-execution-max-concurrency: 200 + local-execution-timeout-sec: 30 + db-checkpoint-config: + perform-db-checkpoints-at-epoch-end: false + indirect-objects-threshold: 18446744073709551615 + expensive-safety-check-config: + enable-epoch-sui-conservation-check: false + enable-deep-per-tx-sui-conservation-check: false + force-disable-epoch-sui-conservation-check: false + enable-state-consistency-check: false + force-disable-state-consistency-check: false + enable-move-vm-paranoid-checks: false +account_keys: [] +genesis: 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AQkAAgcLCQEJAAsJAQkAAwgLAwgLAQsPAQkABggLCAwICwgLAwgMBQgMCAsICwMDAwgMCAsICwMDAwMBBgsIAQkAAQsIAQkAAQYLCQEJAAMHCwkBCQADBwgQAQgEAgYIDAkAAQgGAQgFAQYJAQIICwgLAQcJAQdCYWxhbmNlBkJvcnJvdwRDb2luAklEBEl0ZW0KSXRlbUxpc3RlZAVLaW9zaw1LaW9za093bmVyQ2FwB0xpc3RpbmcETG9jawZPcHRpb24LUHVyY2hhc2VDYXADU1VJDlRyYW5zZmVyUG9saWN5D1RyYW5zZmVyUmVxdWVzdAlUeENvbnRleHQDVUlEA2FkZBBhbGxvd19leHRlbnNpb25zB2JhbGFuY2UGYm9ycm93CmJvcnJvd19tdXQKYm9ycm93X3ZhbBJjbG9zZV9hbmRfd2l0aGRyYXcEY29pbgZkZWxldGUMZGVzdHJveV9zb21lDWR5bmFtaWNfZmllbGQUZHluYW1pY19vYmplY3RfZmllbGQEZW1pdAVldmVudAdleGlzdHNfA2Zvcgxmcm9tX2JhbGFuY2UKaGFzX2FjY2VzcwhoYXNfaXRlbQJpZAxpbnRvX2JhbGFuY2UMaXNfZXhjbHVzaXZlCWlzX2xpc3RlZBVpc19saXN0ZWRfZXhjbHVzaXZlbHkJaXNfbG9ja2VkB2lzX3NvbWUKaXRlbV9jb3VudAdpdGVtX2lkBGpvaW4Fa2lvc2sIa2lvc2tfaWQEbGlzdBZsaXN0X3dpdGhfcHVyY2hhc2VfY2FwBGxvY2sJbWluX3ByaWNlA25ldwtuZXdfcmVxdWVzdAZvYmplY3QGb3B0aW9uBW93bmVyBXBsYWNlDnBsYWNlX2FuZF9saXN0BXByaWNlB3Byb2ZpdHMOcHJvZml0c19hbW91bnQLcHJvZml0c19tdXQIcHVyY2hhc2URcHVyY2hhc2VfY2FwX2l0ZW0ScHVyY2hhc2VfY2FwX2tpb3NrFnB1cmNoYXNlX2NhcF9taW5fcHJpY2URcHVyY2hhc2Vfd2l0aF9jYXAGcmVtb3ZlEHJlbW92ZV9pZl9leGlzdHMTcmV0dXJuX3B1cmNoYXNlX2NhcApyZXR1cm5fdmFsBnNlbmRlchRzZXRfYWxsb3dfZXh0ZW5zaW9ucwlzZXRfb3duZXIQc2V0X293bmVy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aXRpdmVfcG9saWN5BWFzY2lpEWF1dGhvcml6ZV91cGdyYWRlDmJ1cm5fcHVibGlzaGVyA2NhcAVjbGFpbQ5jbGFpbV9hbmRfa2VlcA5jb21taXRfdXBncmFkZRFjb21wYXRpYmxlX3BvbGljeQZkZWxldGUPZGVwX29ubHlfcG9saWN5BmRpZ2VzdAtmcm9tX21vZHVsZQxmcm9tX3BhY2thZ2UDZ2V0C2dldF9hZGRyZXNzCmdldF9tb2R1bGUCaWQPaWRfZnJvbV9hZGRyZXNzDWlkX3RvX2FkZHJlc3MTaXNfb25lX3RpbWVfd2l0bmVzcw5tYWtlX2ltbXV0YWJsZQttb2R1bGVfbmFtZQNuZXcGb2JqZWN0Fm9ubHlfYWRkaXRpdmVfdXBncmFkZXMRb25seV9kZXBfdXBncmFkZXMHcGFja2FnZQZwb2xpY3kPcHVibGljX3RyYW5zZmVyEHB1Ymxpc2hlZF9tb2R1bGURcHVibGlzaGVkX3BhY2thZ2ULcmVjZWlwdF9jYXAPcmVjZWlwdF9wYWNrYWdlCHJlc3RyaWN0BnNlbmRlcg10aWNrZXRfZGlnZXN0DnRpY2tldF9wYWNrYWdlDXRpY2tldF9wb2xpY3kIdHJhbnNmZXIKdHhfY29udGV4dAl0eXBlX25hbWUFdHlwZXMPdXBncmFkZV9wYWNrYWdlDnVwZ3JhZGVfcG9saWN5B3ZlcnNpb24AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAwgAAAAAAAAAAAMIAQAAAAAAAAADCAIAAAAAAAAAAwgDAAAAAAAAAAMIBAAAAAAAAAACAQACAYACAcAFIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIDGggHJAgEHwgEAQIEGggHJAgGNgMlAgICBA0IBiQIBiUCFAoCAwICDQgGJAgGAAEAABMSDgA4AAQEBQgLAQEHACc4AQwCCwERHw4CERkOAhEaEgACAQEAAAIICwAKATgCCwEuESE4AwICAQAAAgYLABMAAQERGwIDAQAAEwk4AQwBDgERGQsAEAAUIQIEAQAAHRc4AQwCDgIRGQoAEAAUIQQRDgIRGgsAEAEUIQwBBRULAAEJDAELAQIFAQAA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CwABCQAECQEHCwEBCQAGCwIBCQAJAgACCQEGCwEBCQABBgkCAwkBBwsBAQkACwgBCAwCCQEHCwABCQABBgsBAQkAAQECBwsBAQkABgsCAQkAAQYICgEHCAoBBgsAAQkAAQgJAQMBCAYBCw4BCQABCQAFCAoLDgEIBgsHAQgMCAoICQEGCAsBBwgNAQgKAQsDAQkAAQgMAQsHAQkAAwMDAwELAQEJAAEGCQABBgsFAQkAAQsFAQkAAQYLBwEJAAMHCwcBCQADBwgNAQsIAQkAAgsHAQgMCAoCCwcBCQAHCA0HCggGCAkICQMLDgEIBggGAwEKCQABBgsOAQkAAgYLDgEJAAYJAAIJAAkBAQkBAgsEAQkBCQIDBwgKCQAJAQIHCw4BCQAJAAIGCAoJAAEGCQECBwsHAQkACwgBCQABCwQBCQECBwgKCQAHQmFsYW5jZQRDb2luAklEBk9wdGlvbglQdWJsaXNoZXIHUnVsZUtleQNTVUkOVHJhbnNmZXJQb2xpY3kRVHJhbnNmZXJQb2xpY3lDYXAVVHJhbnNmZXJQb2xpY3lDcmVhdGVkD1RyYW5zZmVyUmVxdWVzdAlUeENvbnRleHQIVHlwZU5hbWUDVUlEBlZlY1NldANhZGQLYWRkX3JlY2VpcHQIYWRkX3J1bGUOYWRkX3RvX2JhbGFuY2UHYmFsYW5jZQZib3Jyb3cEY29pbg9jb25maXJtX3JlcXVlc3QIY29udGFpbnMGZGVsZXRlFGRlc3Ryb3lfYW5kX3dpdGhkcmF3DGRlc3Ryb3lfc29tZQtkdW1teV9maWVsZA1keW5hbWljX2ZpZWxkBGVtaXQFZW1wdHkFZXZlbnQHZXhpc3RzXwRmcm9tDGZyb21fYmFsYW5jZQxmcm9tX3BhY2thZ2UDZ2V0CGdldF9ydWxlCGhhc19ydWxlAmlkBmluc2VydAlpbnRvX2tleXMHaXNfc29tZQRpdGVtA25ldwtuZXdfcmVxdWVzdAZvYmplY3QGb3B0aW9uB3BhY2thZ2UEcGFpZAlwb2xpY3lfaWQDcHV0CHJlY2VpcHRzBnJlbW92ZQtyZW1vdmVfcnVsZQVydWxlcwRzaXplA3N1aQR0YWtlD3RyYW5zZmVyX3BvbGljeQp0eF9jb250ZXh0CXR5cGVfbmFtZQN1aWQQdWlkX211dF9hc19vd25l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ZW5lc2lzVmFsaWRhdG9yTWV0YWRhdGEGT3B0aW9uA1NVSQxTdGFrZVN1YnNpZHkQU3lzdGVtUGFyYW1ldGVycw9Ub2tlbkFsbG9jYXRpb24ZVG9rZW5EaXN0cmlidXRpb25TY2hlZHVsZQlUeENvbnRleHQDVUlECVZhbGlkYXRvcghhY3RpdmF0ZRNhY3RpdmF0ZV92YWxpZGF0b3JzD2FsbG9jYXRlX3Rva2VucwthbGxvY2F0aW9ucwthbW91bnRfbWlzdAdiYWxhbmNlGGNoYWluX3N0YXJ0X3RpbWVzdGFtcF9tcwRjb2luD2NvbW1pc3Npb25fcmF0ZQZjcmVhdGUYY3JlYXRlX3N5c3RlbV9wYXJhbWV0ZXJzC2Rlc2NyaXB0aW9uDGRlc3Ryb3lfc29tZQxkZXN0cm95X3plcm8FZXBvY2gRZXBvY2hfZHVyYXRpb25fbXMMZnJvbV9iYWxhbmNlCWdhc19wcmljZQdnZW5lc2lzEWdldF92YWxpZGF0b3JfbXV0CWltYWdlX3VybBZpc19kdXBsaWNhdGVfdmFsaWRhdG9yCGlzX2VtcHR5B2lzX3NvbWUTbWF4X3ZhbGlkYXRvcl9jb3VudBttaW5fdmFsaWRhdG9yX2pvaW5pbmdfc3Rha2UEbmFtZQ9uZXR3b3JrX2FkZHJlc3MSbmV0d29ya19wdWJsaWNfa2V5A25ldwZvYmplY3QGb3B0aW9uC3AycF9hZGRyZXNzD3ByaW1hcnlfYWRkcmVzcwtwcm9qZWN0X3VybBNwcm9vZl9vZl9wb3NzZXNzaW9uE3Byb3RvY29sX3B1YmxpY19rZXkQcHJvdG9jb2xfdmVyc2lvbhFyZWNpcGllbnRfYWRkcmVzcxxyZXF1ZXN0X2FkZF9zdGFrZV9hdF9nZW5lc2lzBXNwbGl0DXN0YWtlX3N1YnNpZHkbc3Rha2Vfc3Vic2lkeV9kZWNyZWFzZV9yYXRlF3N0YWtlX3N1YnNpZHlfZnVuZF9taXN0KXN0YWtlX3N1YnNpZHlfaW5pdGlhbF9kaXN0cmlidXRpb25fYW1vdW50G3N0YWtlX3N1YnNpZHlfcGVyaW9kX2xlbmd0aBlzdGFrZV9zdWJzaWR5X3N0YXJ0X2Vwb2NoFXN0YWtlZF93aXRoX3ZhbGlkYXRvcgNzdWkLc3VpX2FkZHJl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AwcIAQgOBwgLAwcIAQgQAwIHCAELCAEICgIHCAEHCAsBBggBAQEBBggAAQUBBggGAQYIDAEGCgIBBgsFAQgGAQYLBQEKAgEGCAkBAwIGCAEDAQgNAQgJAgYIAQYIAQIGCwUBCQAGCQACBgsFAQkABgsFAQkAAgcIAQoCAwcIAQoCCgIBCgIECAADAwcICxYFCAYIBggGCAYLBQEKAgsFAQoCCwUBCgILBQEKAgsFAQgGCwUBCAYKAgsFAQgGCwUBCAYIBwoCCgIKAggGCAYIDAgMAQsFAQkAAQgGCAEBAQEBAQEIAAECAQgEAQgMAQcICwEIBwIHCA8DAgMDAQgKAQYLCAEJAAEGCAsFBwgPCwgBCAoFAwcICwEGCA8BBwgPAQgCAQkABAMDAwMBBggOAwcIDwgOBwgLAQgDAQYIEAEGBQIHCA8LCAEICgIHCA8HCAsCBggPAwEIDwEGCQAdAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBBgsFAQkAAgEBAgUHCAsBBwsFAQkAAwgJCA8FA0JhZwdCYWxhbmNlAklEBk9wdGlvbhVQb29sVG9rZW5FeGNoYW5nZVJhdGUDU1VJCVN0YWtlZFN1aQtTdGFraW5nUG9vbBNTdGFraW5nUmVxdWVzdEV2ZW50BlN0cmluZwlUeENvbnRleHQVVW5zdGFraW5nUmVxdWVzdEV2ZW50A1VybAlWYWxpZGF0b3IRVmFsaWRhdG9yTWV0YWRhdGEVVmFsaWRhdG9yT3BlcmF0aW9uQ2FwCGFjdGl2YXRlFWFjdGl2YXRlX3N0YWtpbmdfcG9vbBphZGp1c3Rfc3Rha2VfYW5kX2dhc19wcmljZQZhbW91bnQFYXNjaWkDYmFnB2JhbGFuY2UDYmNzBmJvcnJvdw9jb21taXNzaW9uX3JhdGUKZGVhY3RpdmF0ZRdkZWFjdGl2YXRlX3N0YWtpbmdfcG9vbA9kZXBvc2l0X3Jld2FyZHMVZGVwb3NpdF9zdGFrZV9yZXdhcmRzC2Rlc2NyaXB0aW9uGmVmZmVjdHVhdGVfc3RhZ2VkX21ldGFkYXRhBGVtaXQFZXBvY2gFZXZlbnQMZXh0cmFfZmllbGRzB2V4dHJhY3QKZnJvbV9hc2NpaQlnYXNfcHJpY2UHZ2VuZXNpcwJpZAlpbWFnZV91cmwMaXNfZHVwbGljYXRlDWlzX2VxdWFsX3NvbWUXaXNfZXF1YWxfc29tZV9hbmRfdmFsdWUHaXNfbm9uZQxpc19wcmVhY3RpdmUHaXNfc29tZQhtZXRhZGF0YQRuYW1lC25ldF9hZGRyZXNzD25ldHdvcmtfYWRkcmVzcxRuZXR3b3JrX3B1YmtleV9ieXRlcwNuZXcRbmV3X2Zyb21fbWV0YWRhdGEMbmV3X21ldGFkYXRhFW5ld191bnNhZmVfZnJvbV9ieXRlczNuZXdfdW52ZXJpZmllZF92YWxpZGF0b3Jfb3BlcmF0aW9uX2NhcF9hbmRfdHJhbnNmZXIabmV4dF9lcG9jaF9jb21taXNzaW9uX3JhdGUUbmV4dF9lcG9jaF9nYXNfcHJpY2UWbmV4dF9lcG9jaF9uZXRfYWRkcmVzcxpuZXh0X2Vwb2NoX25ldHdvcmtfYWRkcmVzcx9uZXh0X2Vwb2NoX25ldHdvcmtfcHVia2V5X2J5dGVzFm5leHRfZXBvY2hfcDJwX2FkZHJlc3MabmV4dF9lcG9jaF9wcmltYXJ5X2FkZHJlc3MebmV4dF9lcG9jaF9wcm9vZl9vZl9wb3NzZXNzaW9uIG5leHRfZXBvY2hfcHJvdG9jb2xfcHVia2V5X2J5dGVzEG5leHRfZXBvY2hfc3Rha2UZbmV4dF9lcG9jaF93b3JrZXJfYWRkcmVzcx5uZXh0X2Vwb2NoX3dvcmtlcl9wdWJrZXlfYnl0ZXMEbm9uZQZvYmplY3QQb3BlcmF0aW9uX2NhcF9pZAZvcHRpb24LcDJwX2FkZHJlc3MUcGVuZGluZ19zdGFrZV9hbW91bnQdcGVuZGluZ19zdGFrZV93aXRoZHJhd19hbW91bnQHcG9vbF9pZCFwb29sX3Rva2VuX2V4Y2hhbmdlX3JhdGVfYXRfZXBvY2gPcHJpbWFyeV9hZGRyZXNzEHByaW5jaXBhbF9hbW91bnQVcHJvY2Vzc19wZW5kaW5nX3N0YWtlJHByb2Nlc3NfcGVuZGluZ19zdGFrZXNfYW5kX3dpdGhkcmF3cwtwcm9qZWN0X3VybBNwcm9vZl9vZl9wb3NzZXNzaW9uFXByb3RvY29sX3B1YmtleV9ieXRlcxFyZXF1ZXN0X2FkZF9zdGFrZRxyZXF1ZXN0X2FkZF9zdGFrZV9hdF9nZW5lc2lzG3JlcXVlc3Rfc2V0X2NvbW1pc3Npb25fcmF0ZRVyZXF1ZXN0X3NldF9nYXNfcHJpY2UWcmVxdWVzdF93aXRoZHJhd19zdGFrZQ1yZXdhcmRfYW1vdW50BnNlbmRlch1zZXRfY2FuZGlkYXRlX2NvbW1pc3Npb25fcmF0ZRdzZXRfY2FuZGlkYXRlX2dhc19wcmljZRBzZXRfdm90aW5nX3Bvd2VyBHNvbWUWc3Rha2VfYWN0aXZhdGlvbl9lcG9jaAxzdGFrZV9hbW91bnQRc3Rha2VkX3N1aV9hbW91bnQOc3Rha2VyX2FkZHJlc3MMc3Rha2luZ19wb29sD3N0YWtpbmdfcG9vbF9pZAZzdHJpbmcDc3VpC3N1aV9hZGRyZXNzC3N1aV9iYWxhbmNlFnN1aV9zeXN0ZW1fc3RhdGVfaW5uZXIIdG9fYnl0ZXMLdG90YWxfc3Rha2USdG90YWxfc3Rha2VfYW1vdW50CnR4X2NvbnRleHQPdW5zdGFraW5nX2Vwb2NoIHVwZGF0ZV9jYW5kaWRhdGVfbmV0d29ya19hZGRyZXNzH3VwZGF0ZV9jYW5kaWRhdGVfbmV0d29ya19wdWJrZXkcdXBkYXRlX2NhbmRpZGF0ZV9wMnBfYWRkcmVzcyB1cGRhdGVfY2FuZGlkYXRlX3ByaW1hcnlfYWRkcmVzcyB1cGRhdGVfY2FuZGlkYXRlX3Byb3RvY29sX3B1YmtleR91cGRhdGVfY2FuZGlkYXRlX3dvcmtlcl9hZGRyZXNzHnVwZGF0ZV9jYW5kaWRhdGVfd29ya2VyX3B1YmtleRJ1cGRhdGVfZGVzY3JpcHRpb24QdXBkYXRlX2ltYWdlX3VybAt1cGRhdGVfbmFtZSF1cGRhdGVfbmV4dF9lcG9jaF9uZXR3b3JrX2FkZHJlc3MgdXBkYXRlX25leHRfZXBvY2hfbmV0d29ya19wdWJrZXkddXBkYXRlX25leHRfZXBvY2hfcDJwX2FkZHJlc3MhdXBkYXRlX25leHRfZXBvY2hfcHJpbWFyeV9hZGRyZXNzIXVwZGF0ZV9uZXh0X2Vwb2NoX3Byb3RvY29sX3B1YmtleSB1cGRhdGVfbmV4dF9lcG9jaF93b3JrZXJfYWRkcmVzcx91cGRhdGVfbmV4dF9lcG9jaF93b3JrZXJfcHVia2V5EnVwZGF0ZV9w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cm93X211dARjb2luBmNyZWF0ZQ1keW5hbWljX2ZpZWxkB2dlbmVzaXMcZ2VuZXNpc19zeXN0ZW1fc3RhdGVfdmVyc2lvbgJpZBhsb2FkX2lubmVyX21heWJlX3VwZ3JhZGURbG9hZF9zeXN0ZW1fc3RhdGUVbG9hZF9zeXN0ZW1fc3RhdGVfbXV0Bm9iamVjdAZvcHRpb24GcmVtb3ZlEHJlcG9ydF92YWxpZGF0b3IRcmVxdWVzdF9hZGRfc3Rha2UacmVxdWVzdF9hZGRfc3Rha2VfbXVsX2NvaW4VcmVxdWVzdF9hZGRfdmFsaWRhdG9yH3JlcXVlc3RfYWRkX3ZhbGlkYXRvcl9jYW5kaWRhdGUYcmVxdWVzdF9yZW1vdmVfdmFsaWRhdG9yInJlcXVlc3RfcmVtb3ZlX3ZhbGlkYXRvcl9jYW5kaWRhdGUbcmVxdWVzdF9zZXRfY29tbWlzc2lvbl9yYXRlFXJlcXVlc3Rfc2V0X2dhc19wcmljZRZyZXF1ZXN0X3dpdGhkcmF3X3N0YWtlFHJvdGF0ZV9vcGVyYXRpb25fY2FwBnNlbmRlcidzZXRfY2FuZGlkYXRlX3ZhbGlkYXRvcl9jb21taXNzaW9uX3JhdGUhc2V0X2NhbmRpZGF0ZV92YWxpZGF0b3JfZ2FzX3ByaWNlDHNoYXJlX29iamVjdA1zdGFrZV9zdWJzaWR5DHN0YWtpbmdfcG9vbANzdWkKc3VpX3N5c3RlbRZzdWlfc3lzdGVtX3N0YXRlX2lubmVyFHN5c3RlbV9zdGF0ZV92ZXJzaW9uCHRyYW5zZmVyCnR4X2NvbnRleHQVdW5kb19yZXBvcnRfdmFsaWRhdG9yKnVwZGF0ZV9jYW5kaWRhdGVfdmFsaWRhdG9yX25ldHdvcmtfYWRkcmVzcyl1cGRhdGVfY2FuZGlkYXRlX3ZhbGlkYXRvcl9uZXR3b3JrX3B1YmtleSZ1cGRhdGVfY2FuZGlkYXRlX3ZhbGlkYXRvcl9wMnBfYWRkcmVzcyp1cGRhdGVfY2FuZGlkYXRlX3ZhbGlkYXRvcl9wcmltYXJ5X2FkZHJlc3MqdXBkYXRlX2NhbmRpZGF0ZV92YWxpZGF0b3JfcHJvdG9jb2xfcHVia2V5KXVwZGF0ZV9jYW5kaWRhdGVfdmFsaWRhdG9yX3dvcmtlcl9hZGRyZXNzKHVwZGF0ZV9jYW5kaWRhdGVfdmFsaWRhdG9yX3dvcmtlcl9wdWJrZXkcdXBkYXRlX3ZhbGlkYXRvcl9kZXNjcmlwdGlvbhp1cGRhdGVfdmFsaWRhdG9yX2ltYWdlX3VybBV1cGRhdGVfdmFsaWRhdG9yX25hbWUrdXBkYXRlX3ZhbGlkYXRvcl9uZXh0X2Vwb2NoX25ldHdvcmtfYWRkcmVzcyp1cGRhdGVfdmFsaWRhdG9yX25leHRfZXBvY2hfbmV0d29ya19wdWJrZXkndXBkYXRlX3ZhbGlkYXRvcl9uZXh0X2Vwb2NoX3AycF9hZGRyZXNzK3VwZGF0ZV92YWxpZGF0b3JfbmV4dF9lcG9jaF9wcmltYXJ5X2FkZHJlc3MrdXBkYXRlX3ZhbGlkYXRvcl9uZXh0X2Vwb2NoX3Byb3RvY29sX3B1YmtleSp1cGRhdGVfdmFsaWRhdG9yX25leHRfZXBvY2hfd29ya2VyX2FkZHJlc3MpdXBkYXRlX3ZhbGlkYXRvcl9uZXh0X2Vwb2NoX3dvcmtlcl9wdWJrZXkcdXBkYXRlX3ZhbGlkYXRvcl9wcm9qZWN0X3VybAh2MV90b192Mgl2YWxpZGF0b3INdmFsaWRhdG9yX2NhcAd2ZXJzaW9uAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwgAAAAAAAAAAAMIAQAAAAAAAAAFIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICFwgESwMAAwAAERcLAQsCCwMLBAsFCwYLBxErDAkRLAwKCwAKChIADAgNCA8ACwoLCTgACwg4AQIBAQQAARMLABEjCwELAgsDCwQLBQsGCwcLCAsJCwoLCwsMCw0LDgsPETECAgEEAAEFCwARIwsBETMCAwEEAAEFCwARIwsBETACBAEEAAEFCwARIwsBETICBQEEAAEGCwARIwsBCwIRNQIGAQQAAQYLABEjCwELAhE5AgcBBAABBgsAESMLAQsCETQCCAEEAAEGCwARIwsBCwIROAIJAQQAAQcLABEjCwELAgsDES4CCgEEAAEICwARIwsBCwILAwsEES8CCwEE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c19lcXVhbF9zdGFraW5nX21ldGFkYXRhC2lzX2luYWN0aXZlB2lzX25vbmUMaXNfcHJlYWN0aXZlFWlzX3ByZWFjdGl2ZV9hdF9lcG9jaAdpc19zb21lBGpvaW4Pam9pbl9zdGFrZWRfc3VpBG1hdGgDbWluA25ldwRub25lBm9iamVjdAZvcHRpb24bcGVuZGluZ19wb29sX3Rva2VuX3dpdGhkcmF3DXBlbmRpbmdfc3Rha2UUcGVuZGluZ19zdGFrZV9hbW91bnQdcGVuZGluZ19zdGFrZV93aXRoZHJhd19hbW91bnQacGVuZGluZ190b3RhbF9zdWlfd2l0aGRyYXcHcG9vbF9pZBFwb29sX3Rva2VuX2Ftb3VudBJwb29sX3Rva2VuX2JhbGFuY2UhcG9vbF90b2tlbl9leGNoYW5nZV9yYXRlX2F0X2Vwb2NoCXByaW5jaXBhbBVwcm9jZXNzX3BlbmRpbmdfc3Rha2UecHJvY2Vzc19wZW5kaW5nX3N0YWtlX3dpdGhkcmF3JHByb2Nlc3NfcGVuZGluZ19zdGFrZXNfYW5kX3dpdGhkcmF3cw9wdWJsaWNfdHJhbnNmZXIRcmVxdWVzdF9hZGRfc3Rha2UWcmVxdWVzdF93aXRoZHJhd19zdGFrZSByZXF1ZXN0X3dpdGhkcmF3X3N0YWtlX3ByZWFjdGl2ZQxyZXdhcmRzX3Bvb2wGc2VuZGVyBHNvbWUFc3BsaXQQc3BsaXRfc3Rha2VkX3N1aRZzdGFrZV9hY3RpdmF0aW9uX2Vwb2NoEXN0YWtlZF9zdWlfYW1vdW50DHN0YWtpbmdfcG9vbANzdWkKc3VpX2Ftb3VudAtzdWlfYmFsYW5jZQV0YWJsZQh0cmFuc2Zlcgp0eF9jb250ZXh0EXVud3JhcF9zdGFrZWRfc3VpCXZhbGlkYXRvcg12YWxpZGF0b3Jfc2V0BXZhbHVlF3dpdGhkcmF3X2Zyb21fcHJpbmNpcGFsEHdpdGhkcmF3X3Jld2FyZHMEemVybwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgMIAMqaOwAAAAADCAAAAAAAAAAAAwgBAAAA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CgUEBgoIEwMDAwIKAwoDCQYKCBMDAwoDCgMDCw8CAwMDCw8CAwMGBwoIEwYKAwYKAwcLBwEICwcLBwEICwcIDgYDBgoIEwYKAwYKAwYLDwIFCxABBQYKBQIGCAAICAYDAwsMAggIBQMGCBMIAAIICAUBBwgOAQsMAgkACQEBCBMBBQMHCwwCCQAJAQkACQEBCw0BCQACCAgIFgIFCBYCBQMBCw8CCQAJAQEIBgQGCBMGCBMBBQIGCwwCCQAJAQkAAggTBwgOAQgWAwgICBMFAQYIDgIHCwwCCQAJAQkAAQkBAgcIEwMFBggTBggTAQgTBQIHCw0BCQAJAAMFAwsFAQMBBgsFAQkAAQcLBQEJAAEJAAIGCgkABgkAAQgLAQYLBwEJAAQHCBMLBwEICwUHCA4BBggSAQYJAQMHCBMIEgcIDgEHCQEBBwgWDgsPAgULEAEFBgoFCgMKAwsPAgMDCw8CAwMDCgUDAwMDCgMKAwgDAwcDAwgTCBMFBggTAgYLDwIJAAkBBgkAAgcLDwIJAAkBBgkAAgkACQEDBwsPAgkACQEJAAkBAgcKCQADAgMDCgoLCQEDAwMLCgEDAwMDAwYIEwYKCBMBCwkBAwIDCQABCwkBCQABCgsJAQkAAQsKAQkAAQcLCgEJAAEGCw0BCQADAwMDAgYIEwYIEwIGCw0BCQADAQYJAAELBQEJAAUFAwsFAQMDCgMEAwMLBQEDCwUBAwIHCw0BCQADAQcJAAEGCBUBBgUCAwsFAQMGAQEDAwsFAQMLBQEDBQUGCBMICAUGCBMBBggUAQgUAQYICAIDCBMBBgoJAAMDBQgIAQgEBwYFBgUDAwYFCgUHCxABBQIFCxABBQEGCw8CCQAJAQEKCQACBgsQAQkABgkAAgcLEAEJAAYJAAEGCxABCQABBwsNAQkAAQgDBgMDAwMDAwIHCBMHCA4EAwMDBggTBwsPAgMDCw8CAwMEBAMDAwUKBQYKCBMLEAEFCgUFAQcLDwIJAAkBAQsQAQkABgMDBAoDCgMDEQMDAwoDAwoDAwQDAwMDAwMDAwQIAwMLBwEICwMHCBMFBAsHAQgLAgcLBwEJAAMBCwcBCQACBwsHAQkACwcBCQACBwgTCwcBCAsICgUDAwMDCgUGCBMFAgYIEwMBCBEBCAIDQmFnB0JhbGFuY2UFRW50cnkCSUQGT3B0aW9uFVBvb2xUb2tlbkV4Y2hhbmdlUmF0ZQ1Qcmlvcml0eVF1ZXVlA1NVSQlTdGFrZWRTdWkFVGFibGUIVGFibGVWZWMJVHhDb250ZXh0H1VudmVyaWZpZWRWYWxpZGF0b3JPcGVyYXRpb25DYXAJVmFsaWRhdG9yF1ZhbGlkYXRvckVwb2NoSW5mb0V2ZW50GVZhbGlkYXRvckVwb2NoSW5mb0V2ZW50VjISVmFsaWRhdG9ySm9pbkV2ZW50E1ZhbGlkYXRvckxlYXZlRXZlbnQVVmFsaWRhdG9yT3BlcmF0aW9uQ2FwDFZhbGlkYXRvclNldBBWYWxpZGF0b3JXcmFwcGVyBlZlY01hcAZWZWNTZXQIYWN0aXZhdGURYWN0aXZlX3ZhbGlkYXRvcnMDYWRkGmFkanVzdF9zdGFrZV9hbmRfZ2FzX3ByaWNlDWFkdmFuY2VfZXBvY2gmYXNzZXJ0X25vX3BlbmRpbmdfb3JfYWN0aXZlX2R1cGxpY2F0ZXMSYXRfcmlza192YWxpZGF0b3JzA2JhZwdiYWxhbmNlBmJvcnJvdwpib3Jyb3dfbXV0FmNhbGN1bGF0ZV90b3RhbF9zdGFrZXMmY2xlYW5fcmVwb3J0X3JlY29yZHNfbGVhdmluZ192YWxpZGF0b3IPY29tbWlzc2lvbl9yYXRlJGNvbXB1dGVfYWRqdXN0ZWRfcmV3YXJkX2Rpc3RyaWJ1dGlvbhpjb21wdXRlX3Jld2FyZF9hZGp1c3RtZW50cxpjb21wdXRlX3NsYXNoZWRfdmFsaWRhdG9ycyZjb21wdXRlX3VuYWRqdXN0ZWRfcmV3YXJkX2Rpc3RyaWJ1dGlvbghjb250YWlucxljb3VudF9kdXBsaWNhdGVzX3RhYmxldmVjFGNvdW50X2R1cGxpY2F0ZXNfdmVjCWNyZWF0ZV92MQpkZWFjdGl2YXRlFWRlcG9zaXRfc3Rha2VfcmV3YXJkcxpkZXJpdmVfcmVmZXJlbmNlX2dhc19wcmljZQdkZXN0cm95DGRlc3Ryb3lfc29tZQxkZXN0cm95X3plcm8RZGlzdHJpYnV0ZV9yZXdhcmQaZWZmZWN0dWF0ZV9zdGFnZWRfbWV0YWRhdGEEZW1pdBtlbWl0X3ZhbGlkYXRvcl9lcG9jaF9ldmVudHMFZW1wdHkFZXBvY2gFZXZlbnQMZXh0cmFfZmllbGRzB2V4dHJhY3QOZmluZF92YWxpZGF0b3IdZmluZF92YWxpZGF0b3JfZnJvbV90YWJsZV92ZWMJZ2FzX3ByaWNlB2dlbmVzaXMDZ2V0MGdldF9hY3RpdmVfb3JfcGVuZGluZ19vcl9jYW5kaWRhdGVfdmFsaWRhdG9yX211dDBnZXRfYWN0aXZlX29yX3BlbmRpbmdfb3JfY2FuZGlkYXRlX3ZhbGlkYXRvcl9yZWYYZ2V0X2FjdGl2ZV92YWxpZGF0b3JfcmVmJWdldF9jYW5kaWRhdGVfb3JfYWN0aXZlX3ZhbGlkYXRvcl9tdXQHZ2V0X211dBlnZXRfcGVuZGluZ192YWxpZGF0b3JfcmVmFWdldF92YWxpZGF0b3JfaW5kaWNlcxFnZXRfdmFsaWRhdG9yX211dBpnZXRfdmFsaWRhdG9yX211dF93aXRoX2N0eC9nZXRfdmFsaWRhdG9yX211dF93aXRoX2N0eF9pbmNsdWRpbmdfY2FuZGlkYXRlcyNnZXRfdmFsaWRhdG9yX211dF93aXRoX3ZlcmlmaWVkX2NhcBFnZXRfdmFsaWRhdG9yX3JlZgJpZBNpbmFjdGl2ZV92YWxpZGF0b3JzBmluc2VydAlpbnRvX2tleXMiaXNfYWN0aXZlX3ZhbGlkYXRvcl9ieV9zdWlfYWRkcmVzcwxpc19kdXBsaWNhdGUWaXNfZHVwbGljYXRlX3ZhbGlkYXRvciJpc19kdXBsaWNhdGVfd2l0aF9hY3RpdmVfdmFsaWRhdG9yI2lzX2R1cGxpY2F0ZV93aXRoX3BlbmRpbmdfdmFsaWRhdG9yCGlzX2VtcHR5FWlzX2luYWN0aXZlX3ZhbGlkYXRvcgxpc19wcmVhY3RpdmUHaXNfc29tZRZpc192YWxpZGF0b3JfY2FuZGlkYXRlDGlzX3ZvbHVudGFyeQRqb2luBGtleXMGbGVuZ3RoHGxvYWRfdmFsaWRhdG9yX21heWJlX3VwZ3JhZGUDbmV3CW5ld19lbnRyeRNuZXdfZnJvbV91bnZlcmlmaWVkGm5leHRfZXBvY2hfdmFsaWRhdG9yX2NvdW50BG5vbmUGb2JqZWN0EG9wZXJhdGlvbl9jYXBfaWQGb3B0aW9uGXBlbmRpbmdfYWN0aXZlX3ZhbGlkYXRvcnMQcGVuZGluZ19yZW1vdmFscwdwb29sX2lkE3Bvb2xfc3Rha2luZ19yZXdhcmQYcG9vbF90b2tlbl9leGNoYW5nZV9yYXRlIXBvb2xfdG9rZW5fZXhjaGFuZ2VfcmF0ZV9hdF9lcG9jaANwb3AIcG9wX2JhY2sHcG9wX21heA5wcmlvcml0eV9xdWV1ZRhwcm9jZXNzX3BlbmRpbmdfcmVtb3ZhbHMkcHJvY2Vzc19wZW5kaW5nX3N0YWtlc19hbmRfd2l0aGRyYXdzGnByb2Nlc3NfcGVuZGluZ192YWxpZGF0b3JzG3Byb2Nlc3NfdmFsaWRhdG9yX2RlcGFydHVyZQlwdXNoX2JhY2sQcXVvcnVtX3RocmVzaG9sZBpyZWZlcmVuY2VfZ2FzX3N1cnZleV9xdW90ZQZyZW1vdmURcmVxdWVzdF9hZGRfc3Rha2UVcmVxdWVzdF9hZGRfdmFsaWRhdG9yH3JlcXVlc3RfYWRkX3ZhbGlkYXRvcl9jYW5kaWRhdGUYcmVxdWVzdF9yZW1vdmVfdmFsaWRhdG9yInJlcXVlc3RfcmVtb3ZlX3ZhbGlkYXRvcl9jYW5kaWRhdGUbcmVxdWVzdF9zZXRfY29tbWlzc2lvbl9yYXRlFnJlcXVlc3Rfd2l0aGRyYXdfc3Rha2UGc2VuZGVyEHNldF92b3RpbmdfcG93ZXIEc2l6ZQRzb21lEXNvcnRfcmVtb3ZhbF9saXN0BXNwbGl0BXN0YWtlDHN0YWtlX2Ftb3VudAxzdGFraW5nX3Bvb2wPc3Rha2luZ19wb29sX2lkFXN0YWtpbmdfcG9vbF9tYXBwaW5ncxtzdG9yYWdlX2Z1bmRfc3Rha2luZ19yZXdhcmQDc3VpC3N1aV9hZGRyZXNzFnN1aV9zeXN0ZW1fc3RhdGVfaW5uZXIdc3VtX3ZvdGluZ19wb3dlcl9ieV9hZGRyZXNzZXMFdGFibGUJdGFibGVfdmVjGnRhbGx5aW5nX3J1bGVfZ2xvYmFsX3Njb3JlF3RhbGx5aW5nX3J1bGVfcmVwb3J0ZXJzC3RvdGFsX3N0YWtlEnRvdGFsX3N0YWtlX2Ftb3VudBJ0b3RhbF92b3RpbmdfcG93ZXIKdHhfY29udGV4dCB1bnZlcmlmaWVkX29wZXJhdGlvbl9jYXBfYWRkcmVzcyd1cGRhdGVfYW5kX3Byb2Nlc3NfbG93X3N0YWtl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BwgMAwcIFQMHCAwBBgoIEgEIFAMHCBUGCBMCAwcIFQYIFAEBBwgSAwcIEggUAwMHCBUDBggMAgcIFQYIDAIHCBIDAQsIAQkABAcIFQULBwEICgcIDAEGCBADBwgVCBAHCAwCBggVBQEBBAYFBgUFBwsOAQUBBggUAQYFAgYLDQIJAAkBBgkAAQkAAQsOAQkAAwcLDQIJAAkBCQAJAQIHCw0CCQAJAQYJAAEHCQECBgsOAQkABgkAAgcLDgEJAAkAAgcLDgEJAAYJAAEGCw4BCQACCQAJAQIHCBIHCAwCBwgSCgICBwgSBggSAgYIFQYIEgMHCBIKAgoCLAEDAwMDAwMBAQELBwEICgMDAwMDAwMDBAMDAwsHAQgKAwMDCwcBCAoLBwEICgsHAQgKCwcBCAoDAwMLBwEICgQLBwEICgQDAwMDBAMBBwsHAQkAAgcLBwEJAAsHAQkAAQYIEQEGCwcBCQABBwgPAgcLBwEJAAMJBwgVBwsHAQgKBwsHAQgKBwsNAgULDgEFAwMDAwcIDAYHCBELBwEICgsHAQgKCwcBCAoDAwEIBAEGCQEFCwcBCAoDCwcBCAoLCAEICgsHAQgKAQsIAQgKAgcLCAEJAAoLCAEJAAEGCwUBCQABCwUBCQACCwcBCQAHCAwCCQAFA0JhZwdCYWxhbmNlBENvaW4CSUQGT3B0aW9uA1NVSQxTdGFrZVN1YnNpZHkJU3Rha2VkU3VpC1N0b3JhZ2VGdW5kE1N1aVN5c3RlbVN0YXRlSW5uZXIVU3VpU3lzdGVtU3RhdGVJbm5lclYyFFN5c3RlbUVwb2NoSW5mb0V2ZW50EFN5c3RlbVBhcmFtZXRlcnMSU3lzdGVtUGFyYW1ldGVyc1YyBVRhYmxlCVR4Q29udGV4dB9VbnZlcmlmaWVkVmFsaWRhdG9yT3BlcmF0aW9uQ2FwCVZhbGlkYXRvchVWYWxpZGF0b3JPcGVyYXRpb25DYXAMVmFsaWRhdG9yU2V0BlZlY01hcAZWZWNTZXQRYWN0aXZlX3ZhbGlkYXRvcnMNYWR2YW5jZV9lcG9jaCZhc3NlcnRfbm9fcGVuZGluZ19vcl9hY3RpdmVfZHVwbGljYXRlcwNiYWcHYmFsYW5jZQRjb2luCGNvbnRhaW5zBmNyZWF0ZRhjcmVhdGVfc3lzdGVtX3BhcmFtZXRlcnMaZGVyaXZlX3JlZmVyZW5jZV9nYXNfcHJpY2UMZGVzdHJveV9zb21lDGRlc3Ryb3lfemVybwRlbWl0BWVtcHR5BWVwb2NoEWVwb2NoX2R1cmF0aW9uX21zGGVwb2NoX3N0YXJ0X3RpbWVzdGFtcF9tcwVldmVudAxleHRyYV9maWVsZHMUZXh0cmFjdF9jb2luX2JhbGFuY2UMZnJvbV9iYWxhbmNlB2dlbmVzaXMcZ2VuZXNpc19zeXN0ZW1fc3RhdGVfdmVyc2lvbgNnZXQHZ2V0X211dBBnZXRfcmVwb3J0ZXJzX29mH2dldF9zdG9yYWdlX2Z1bmRfb2JqZWN0X3JlYmF0ZXMeZ2V0X3N0b3JhZ2VfZnVuZF90b3RhbF9iYWxhbmNlGmdldF92YWxpZGF0b3JfbXV0X3dpdGhfY3R4L2dldF92YWxpZGF0b3JfbXV0X3dpdGhfY3R4X2luY2x1ZGluZ19jYW5kaWRhdGVzI2dldF92YWxpZGF0b3JfbXV0X3dpdGhfdmVyaWZpZWRfY2FwBmluc2VydAxpbnRvX2JhbGFuY2UiaXNfYWN0aXZlX3ZhbGlkYXRvcl9ieV9zdWlfYWRkcmVzcwhpc19lbXB0eQdpc19zb21lBGpvaW4Iam9pbl92ZWMcbGVmdG92ZXJfc3RvcmFnZV9mdW5kX2luZmxvdxNtYXhfdmFsaWRhdG9yX2NvdW50E21pbl92YWxpZGF0b3JfY291bnQbbWluX3ZhbGlkYXRvcl9qb2luaW5nX3N0YWtlA25ldzNuZXdfdW52ZXJpZmllZF92YWxpZGF0b3Jfb3BlcmF0aW9uX2NhcF9hbmRfdHJhbnNmZXIabmV4dF9lcG9jaF92YWxpZGF0b3JfY291bnQGb2JqZWN0Bm9wdGlvbgpwYXJhbWV0ZXJzA3BheRBwcm90b2NvbF92ZXJzaW9uD3B1YmxpY190cmFuc2ZlchNyZWZlcmVuY2VfZ2FzX3ByaWNlBnJlbW92ZRByZXBvcnRfdmFsaWRhdG9yFXJlcG9ydF92YWxpZGF0b3JfaW1wbBFyZXF1ZXN0X2FkZF9zdGFrZRpyZXF1ZXN0X2FkZF9zdGFrZV9tdWxfY29pbhVyZXF1ZXN0X2FkZF92YWxpZGF0b3IfcmVxdWVzdF9hZGRfdmFsaWRhdG9yX2NhbmRpZGF0ZRhyZXF1ZXN0X3JlbW92ZV92YWxpZGF0b3IicmVxdWVzdF9yZW1vdmVfdmFsaWRhdG9yX2NhbmRpZGF0ZRtyZXF1ZXN0X3NldF9jb21taXNzaW9uX3JhdGUVcmVxdWVzdF9zZXRfZ2FzX3ByaWNlFnJlcXVlc3Rfd2l0aGRyYXdfc3Rha2UUcm90YXRlX29wZXJhdGlvbl9jYXAJc2FmZV9tb2RlHXNhZmVfbW9kZV9jb21wdXRhdGlvbl9yZXdhcmRzJHNhZmVfbW9kZV9ub25fcmVmdW5kYWJsZV9zdG9yYWdlX2ZlZRlzYWZlX21vZGVfc3RvcmFnZV9yZWJhdGVzGXNhZmVfbW9kZV9zdG9yYWdlX3Jld2FyZHMGc2VuZGVyHXNldF9jYW5kaWRhdGVfY29tbWlzc2lvbl9yYXRlF3NldF9jYW5kaWRhdGVfZ2FzX3ByaWNlJ3NldF9jYW5kaWRhdGVfdmFsaWRhdG9yX2NvbW1pc3Npb25fcmF0ZSFzZXRfY2FuZGlkYXRlX3ZhbGlkYXRvcl9nYXNfcHJpY2UJc2luZ2xldG9uBXNwbGl0FnN0YWtlX2FjdGl2YXRpb25fZXBvY2gNc3Rha2Vfc3Vic2lkeRRzdGFrZV9zdWJzaWR5X2Ftb3VudBlzdGFrZV9zdWJzaWR5X3N0YXJ0X2Vwb2NoDHN0YWtpbmdfcG9vbBVzdGFraW5nX3Bvb2xfbWFwcGluZ3MOc3RvcmFnZV9jaGFyZ2UMc3RvcmFnZV9mdW5kFHN0b3JhZ2VfZnVuZF9iYWxhbmNlGXN0b3JhZ2VfZnVuZF9yZWludmVzdG1lbnQOc3RvcmFnZV9yZWJhdGUDc3VpCnN1aV9zeXN0ZW0Wc3VpX3N5c3RlbV9zdGF0ZV9pbm5lchRzeXN0ZW1fc3RhdGVfdmVyc2lvbgV0YWJsZQ10b3RhbF9iYWxhbmNlDnRvdGFsX2dhc19mZWVzHHRvdGFsX29iamVjdF9zdG9yYWdlX3JlYmF0ZXMLdG90YWxfc3Rha2UfdG90YWxfc3Rha2VfcmV3YXJkc19kaXN0cmlidXRlZAh0cmFuc2Zlcgp0eF9jb250ZXh0FXVuZG9fcmVwb3J0X3ZhbGlkYXRvchp1bmRvX3JlcG9ydF92YWxpZGF0b3JfaW1wbCB1cGRhdGVfY2FuZGlkYXRlX25ldHdvcmtfYWRkcmVzcx91cGRhdGVfY2FuZGlkYXRlX25ldHdvcmtfcHVia2V5HHVwZGF0ZV9jYW5kaWRhdGVfcDJwX2FkZHJlc3MgdXBkYXRlX2NhbmRpZGF0ZV9wcmltYXJ5X2FkZHJlc3MgdXBkYXRlX2NhbmRpZGF0ZV9wcm90b2NvbF9wdWJrZXkqdXBkYXRlX2NhbmRpZGF0ZV92YWxpZGF0b3JfbmV0d29ya19hZGRyZXNzKXVwZGF0ZV9jYW5kaWRhdGVfdmFsaWRhdG9yX25ldHdvcmtfcHVia2V5JnVwZGF0ZV9jYW5kaWRhdGVfdmFsaWRhdG9yX3AycF9hZGRyZXNzKnVwZGF0ZV9jYW5kaWRhdGVfdmFsaWRhdG9yX3ByaW1hcnlfYWRkcmVzcyp1cGRhdGVfY2FuZGlkYXRlX3ZhbGlkYXRvcl9wcm90b2NvbF9wdWJrZXkpdXBkYXRlX2NhbmRpZGF0ZV92YWxpZGF0b3Jfd29ya2VyX2FkZHJlc3ModXBkYXRlX2NhbmRpZGF0ZV92YWxpZGF0b3Jfd29ya2VyX3B1YmtleR91cGRhdGVfY2FuZGlkYXRlX3dvcmtlcl9hZGRyZXNzHnVwZGF0ZV9jYW5kaWRhdGVfd29ya2VyX3B1YmtleRJ1cGRhdGVfZGVzY3JpcHRpb24QdXBkYXRlX2ltYWdlX3VybAt1cGRhdGVfbmFtZSF1cGRhdGVfbmV4dF9lcG9jaF9uZXR3b3JrX2FkZHJlc3MgdXBkYXRlX25leHRfZXBvY2hfbmV0d29ya19wdWJrZXkddXBkYXRlX25leHRfZXBvY2hfcDJwX2FkZHJlc3MhdXBkYXRlX25leHRfZXBvY2hfcHJpbWFyeV9hZGRyZXNzIXVwZGF0ZV9uZXh0X2Vwb2NoX3Byb3RvY29sX3B1YmtleSB1cGRhdGVfbmV4dF9lcG9jaF93b3JrZXJfYWRkcmVzcx91cGRhdGVfbmV4dF9lcG9jaF93b3JrZXJfcHVia2V5EnVwZGF0ZV9wcm9qZWN0X3VybBx1cGRhdGVfdmFsaWRhdG9yX2Rlc2NyaXB0aW9uGnVwZGF0ZV92YWxpZGF0b3JfaW1hZ2VfdXJsFXVwZGF0ZV92YWxpZGF0b3JfbmFtZSt1cGRhdGVfdmFsaWRhdG9yX25leHRfZXBvY2hfbmV0d29ya19hZGRyZXNzKnVwZGF0ZV92YWxpZGF0b3JfbmV4dF9lcG9jaF9uZXR3b3JrX3B1YmtleSd1cGRhdGVfdmFsaWRhdG9yX25leHRfZXBvY2hfcDJwX2FkZHJlc3MrdXBkYXRlX3ZhbGlkYXRvcl9uZXh0X2Vwb2NoX3ByaW1hcnlfYWRkcmVzcyt1cGRhdGVfdmFsaWRhdG9yX25leHRfZXBvY2hfcHJvdG9jb2xfcHVia2V5KnVwZGF0ZV92YWxpZGF0b3JfbmV4dF9lcG9jaF93b3JrZXJfYWRkcmVzcyl1cGRhdGVfdmFsaWRhdG9yX25leHRfZXBvY2hfd29ya2VyX3B1YmtleRx1cGRhdGVfdmFsaWRhdG9yX3Byb2plY3RfdXJsCHYxX3RvX3YyCXZhbGlkYXRvcg12YWxpZGF0b3JfY2FwIHZhbGlkYXRvcl9sb3dfc3Rha2VfZ3JhY2VfcGVyaW9kHXZhbGlkYXRvcl9sb3dfc3Rha2VfdGhyZXNob2xkGHZhbGlkYXRvcl9yZXBvcnRfcmVjb3Jkcw12YWxpZGF0b3Jfc2V0FnZhbGlkYXRvcl9zdGFrZV9hbW91bnQZdmFsaWRhdG9yX3N0YWtpbmdfcG9vbF9pZB92YWxpZGF0b3Jfc3Rha2luZ19wb29sX21hcHBpbmdzHHZhbGlkYXRvcl90b3RhbF9zdGFrZV9hbW91bnQidmFsaWRhdG9yX3ZlcnlfbG93X3N0YWtlX3RocmVzaG9sZAp2YWxpZGF0b3JzBXZhbHVlB3ZlY19tYXAHdmVjX3NldB52ZXJpZmllZF9vcGVyYXRpb25fY2FwX2FkZHJlc3MKdmVyaWZ5X2NhcAx3aXRoZHJhd19hbGwEemVybwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgIBAQIBAgIBAwMIAQAAAAAAAAADCAAAAAAAAAAAAwgCAAAAAAAAAAMIAwAAAAAAAAADCAQAAAAAAAAAAwgFAAAAAAAAAAMIBgAAAAAAAAADCAcAAAAAAAAAAwgIAAAAAAAAAAQQECcAAAAAAAAAAAAAAAAAAAACCCUDZgM9Az8DpAEDqwEDowED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CAcIFAQDCw4BCQALDgEJAQMDCwwBCQALDAEJAQMBBggNAQYLDAEJAAILDAEJAAcIFBoBAwEDAwcLBwEIBQsMAQkAAwMLDAEJAAMGCAQHCAQDAwYLCgEDAwMLDAEJAQEDCwwBCQADAwcIBQMBBgsHAQkAAgMDAgcLBwEJAAMBBwkAAQYLDwIJAAkBAQYLCgEJAAIGCw8CCQAJAQkAAQYJAQMHCwkBCQAIEAMCAQMCBwsMAQkAAwIHCwwBCQALDAEJAAELAwIJAAkBAgcLEwIJAAkBCQABBwkBAgcLDwIJAAkBCQACBgsHAQkAAxoBAwEDAwcLBwEIBQsMAQkAAwMLDAEJAAMGCAQHCAQDBgsKAQMDAwgQCwwBCQEBAwMLDAEJAAMHCAUDGwEDAQMDBwsHAQgFCwwBCQADAwsMAQkBAwYIBAcIBAMDBgsKAQMDAwgQCwwBCQEBAwMLDAEJAQMHCAUDAwsMAQkACwwBCQELDAEJAQIHCw4BCQALDgEJAAcDBwsHAQgFCAQDAwMIEAMHCwkBCQAGCAgDAwcLBwEJAAMJAAMHCw8CCQAJAQkACQEBCwECCQAJAQIGCxMCCQAJAQkAAwcLEwIJAAkBCQAJAQsLDAEJAAsMAQkACwwBCQADAwsMAQkBCwwBCQELDAEJAQMDCBACBgsJAQkACBABCwICCQAJAQsDAwYLBwEIBQcLBwEIBQMBCAQDAwgQBwsPAgMDAwMHCAUIBA0DBwsHAQgFAwMBBwsHAQgFCAQDAwgQAwgQBwsPAgMDEgYLBwEIBQMDBgsHAQgFBwsHAQgFAwMBAwMDCAQDCBADAwgQBwsPAgMDBwYIBQoIBAYIBAYLCgEDAwgQBgsPAgMDBQMDAwMIEAYDCgMDAwMKAwQDBggEBgsKAQMGCw8CAwgEAwYLBwEIBQMIEApBY2NvdW50Q2FwB0JhbGFuY2UFQ2xvY2sEQ29pbgtDcml0Yml0VHJlZQlDdXN0b2RpYW4CSUQLTGlua2VkVGFibGUGT3B0aW9uBU9yZGVyDU9yZGVyQ2FuY2VsZWQLT3JkZXJGaWxsZWQLT3JkZXJQbGFjZWQEUG9vbAtQb29sQ3JlYXRlZANTVUkFVGFibGUJVGlja0xldmVsCVR4Q29udGV4dAhUeXBlTmFtZQNVSUQZYWNjb3VudF9hdmFpbGFibGVfYmFsYW5jZQ9hY2NvdW50X2JhbGFuY2UDYWRkBGFza3MEYmFjawdiYWxhbmNlCmJhc2VfYXNzZXQcYmFzZV9hc3NldF9xdWFudGl0eV9jYW5jZWxlZBpiYXNlX2Fzc2V0X3F1YW50aXR5X2ZpbGxlZBpiYXNlX2Fzc2V0X3F1YW50aXR5X3BsYWNlZB1iYXNlX2Fzc2V0X3F1YW50aXR5X3JlbWFpbmluZxdiYXNlX2Fzc2V0X3RyYWRpbmdfZmVlcw5iYXNlX2N1c3RvZGlhbhJiYXRjaF9jYW5jZWxfb3JkZXIEYmlkcwZib3Jyb3cUYm9ycm93X2xlYWZfYnlfaW5kZXgSYm9ycm93X2xlYWZfYnlfa2V5CmJvcnJvd19tdXQYYm9ycm93X211dF9sZWFmX2J5X2luZGV4EWNhbmNlbF9hbGxfb3JkZXJzDGNhbmNlbF9vcmRlcgRjbG9iBWNsb2NrBGNvaW4IY29udGFpbnMOY3JlYXRlX2FjY291bnQLY3JlYXRlX3Bvb2wMY3JlYXRlX3Bvb2xfDGNyZWF0aW9uX2ZlZQdjcml0Yml0CWN1c3RvZGlhbh9kZWNyZWFzZV91c2VyX2F2YWlsYWJsZV9iYWxhbmNlHGRlY3JlYXNlX3VzZXJfbG9ja2VkX2JhbGFuY2UMZGVwb3NpdF9iYXNlDWRlcG9zaXRfcXVvdGUNZGVzdHJveV9lbXB0eRNkZXN0cm95X2VtcHR5X2xldmVsCWRpdl9yb3VuZARlbWl0E2VtaXRfb3JkZXJfY2FuY2VsZWQFZXZlbnQQZXhwaXJlX3RpbWVzdGFtcBBmaW5kX2Nsb3Nlc3Rfa2V5CWZpbmRfbGVhZgxmcm9tX2JhbGFuY2UFZnJvbnQDZ2V0FmdldF9sZXZlbDJfYm9va19zdGF0dXMfZ2V0X2xldmVsMl9ib29rX3N0YXR1c19hc2tfc2lkZR9nZXRfbGV2ZWwyX2Jvb2tfc3RhdHVzX2JpZF9zaWRlEGdldF9vcmRlcl9zdGF0dXMCaWQfaW5jcmVhc2VfdXNlcl9hdmFpbGFibGVfYmFsYW5jZRJpbmplY3RfbGltaXRfb3JkZXILaW5zZXJ0X2xlYWYMaW50b19iYWxhbmNlBmlzX2JpZAhpc19lbXB0eQdpc19ub25lBGpvaW4MbGlua2VkX3RhYmxlEGxpc3Rfb3Blbl9vcmRlcnMMbG9ja19iYWxhbmNlCGxvdF9zaXplEW1ha2VyX3JlYmF0ZV9yYXRlCW1hdGNoX2FzawltYXRjaF9iaWQdbWF0Y2hfYmlkX3dpdGhfcXVvdGVfcXVhbnRpdHkEbWF0aAhtYXhfbGVhZghtaW5fbGVhZhBtaW50X2FjY291bnRfY2FwA211bAltdWxfcm91bmQDbmV3BG5leHQRbmV4dF9hc2tfb3JkZXJfaWQRbmV4dF9iaWRfb3JkZXJfaWQJbmV4dF9sZWFmBm9iamVjdAtvcGVuX29yZGVycwZvcHRpb24Ib3JkZXJfaWQMb3JkZXJfaXNfYmlkBW93bmVyEXBsYWNlX2xpbWl0X29yZGVyEnBsYWNlX21hcmtldF9vcmRlcgdwb29sX2lkDXByZXZpb3VzX2xlYWYFcHJpY2UJcHVzaF9iYWNrCHF1YW50aXR5C3F1b3RlX2Fzc2V0GHF1b3RlX2Fzc2V0X3RyYWRpbmdfZmVlcw9xdW90ZV9jdXN0b2RpYW4GcmVtb3ZlFHJlbW92ZV9sZWFmX2J5X2luZGV4DHJlbW92ZV9vcmRlcgxzaGFyZV9vYmplY3QFc3BsaXQDc3VpGXN3YXBfZXhhY3RfYmFzZV9mb3JfcXVvdGUZc3dhcF9leGFjdF9xdW90ZV9mb3JfYmFzZQV0YWJsZQ50YWtlcl9mZWVfcmF0ZQl0aWNrX3NpemUMdGltZXN0YW1wX21zDnRvdGFsX3F1YW50aXR5CHRyYW5zZmVyCnR4X2NvbnRleHQJdHlwZV9uYW1lDHVpZF9hc19pbm5lcg51bmxvY2tfYmFsYW5jZQ91c3Jfb3Blbl9vcmRlcnMFdmFsdWUNd2l0aGRyYXdfYmFzZRN3aXRoZHJhd19iYXNlX2Fzc2V0DndpdGhkcmF3X3F1b3RlFHdpdGhkcmF3X3F1b3RlX2Fzc2V0BHplcm8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADe6QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIDCAEAAAAAAAAAAwgCAAAAAAAAAAMIAwAAAAAAAAADCAQAAAAAAAAAAwgFAAAAAAAAAAMIBgAAAAAAAAADCAcAAAAAAAAAAwgIAAAAAAAA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AAgGAwYLAgEJAAcLAQEJAAgEAQYLAQEJAAECAQgGAQgEAQsFAQgLAQkAAQgJAgcLBwEJAAMCBwsHAQkACwcBCQADAwMKCwABCQABBgkAAQEBCwMBCQABCgICBwsIAQkAAwIHCwgBCQALBwEJAAIJAAUBCAsBCwUBCQAHQmFsYW5jZQRDb2luDENvaW5NZXRhZGF0YQ9DdXJyZW5jeUNyZWF0ZWQGT3B0aW9uBlN0cmluZwZTdXBwbHkLVHJlYXN1cnlDYXAJVHhDb250ZXh0A1VJRANVcmwFYXNjaWkHYmFsYW5jZQtiYWxhbmNlX211dARidXJuBGNvaW4PY3JlYXRlX2N1cnJlbmN5DWNyZWF0ZV9zdXBwbHkIZGVjaW1hbHMPZGVjcmVhc2Vfc3VwcGx5BmRlbGV0ZQtkZXNjcmlwdGlvbgxkZXN0cm95X3plcm8NZGl2aWRlX2ludG9fbgRlbWl0BWV2ZW50DGZyb21fYmFsYW5jZQxnZXRfZGVjaW1hbHMPZ2V0X2Rlc2NyaXB0aW9uDGdldF9pY29uX3VybAhnZXRfbmFtZQpnZXRfc3ltYm9sCGljb25fdXJsAmlkD2luY3JlYXNlX3N1cHBseQxpbnRvX2JhbGFuY2UTaXNfb25lX3RpbWVfd2l0bmVzcwRqb2luBG1pbnQRbWludF9hbmRfdHJhbnNmZXIMbWludF9iYWxhbmNlBG5hbWUDbmV3Cm5ld191bnNhZmUGb2JqZWN0Bm9wdGlvbg9wdWJsaWNfdHJhbnNmZXIDcHV0BHNvbWUFc3BsaXQGc3RyaW5nBnN1cHBseQpzdXBwbHlfbXV0DHN1cHBseV92YWx1ZQZzeW1ib2wEdGFrZQx0b3RhbF9zdXBwbHkIdHJhbnNmZXIUdHJlYXN1cnlfaW50b19zdXBwbHkKdHhfY29udGV4dAV0eXBlcxJ1cGRhdGVfZGVzY3JpcHRpb24PdXBkYXRlX2ljb25fdXJsC3VwZGF0ZV9uYW1lDXVwZGF0ZV9zeW1ib2wDdXJsBHV0ZjgFdmFsdWUEemVybwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEDCAAAAAAAAAAAAwgBAAAAAAAAAAMIAgAAAAAAAAAAAgIhCAkMCwcB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YW1pY19vYmplY3RfZmllbGQEZW1pdAVldmVudAdleGlzdHNfA2Zvcgxmcm9tX2JhbGFuY2UKaGFzX2FjY2VzcwhoYXNfaXRlbQJpZAxpbnRvX2JhbGFuY2UMaXNfZXhjbHVzaXZlCWlzX2xpc3RlZBVpc19saXN0ZWRfZXhjbHVzaXZlbHkJaXNfbG9ja2VkB2lzX3NvbWUKaXRlbV9jb3VudAdpdGVtX2lkBGpvaW4Fa2lvc2sIa2lvc2tfaWQEbGlzdBZsaXN0X3dpdGhfcHVyY2hhc2VfY2FwBGxvY2sJbWluX3ByaWNlA25ldwtuZXdfcmVxdWVzdAZvYmplY3QGb3B0aW9uBW93bmVyBXBsYWNlDnBsYWNlX2FuZF9saXN0BXByaWNlB3Byb2ZpdHMOcHJvZml0c19hbW91bnQLcHJvZml0c19tdXQIcHVyY2hhc2URcHVyY2hhc2VfY2FwX2l0ZW0ScHVyY2hhc2VfY2FwX2tpb3NrFnB1cmNoYXNlX2NhcF9taW5fcHJpY2URcHVyY2hhc2Vfd2l0aF9jYXAGcmVtb3ZlEHJlbW92ZV9pZl9leGlzdHMTcmV0dXJuX3B1cmNoYXNlX2NhcApyZXR1cm5fdmFsBnNlbmRlchRzZXRfYWxsb3dfZXh0ZW5zaW9ucwlzZXRfb3duZXIQc2V0X293bmVyX2N1c3RvbQNzdWkEdGFrZQ90cmFuc2Zlcl9wb2xpY3kKdHhfY29udGV4dAd1aWRfbXV0EHVpZF9tdXRfYXNfb3duZXIMdWlkX3RvX2lubmVyBXZhbHVlCHdpdGhkcmF3BHplcm8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAwgAAAAAAAAAAAMIAQAAAAAAAAADCAIAAAAAAAAAAwgDAAAAAAAAAAMIBAAAAAAAAAADCAUAAAAAAAAAAwgGAAAAAAAAAAMIBwAAAAAAAAADCAgAAAAAAAAAAwgJAAAAAAAAAAMICgAAAAAAAAADCAsAAAAAAAAAAAIFJAgMPAsJAQgNOAUrDhIBAQICJAgMIAgLAgIEJAgMLwgLLAgLMwMDAgIvCAssCAsEAgEkCAsFAgIkCAsmAQYCASQICwcCAy4ICyQICzsDBwoCCgABAAAmEwoA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bnNmZXJQb2xpY3kRVHJhbnNmZXJQb2xpY3lDYXAVVHJhbnNmZXJQb2xpY3lDcmVhdGVkD1RyYW5zZmVyUmVxdWVzdAlUeENvbnRleHQIVHlwZU5hbWUDVUlEBlZlY1NldANhZGQLYWRkX3JlY2VpcHQIYWRkX3J1bGUOYWRkX3RvX2JhbGFuY2UHYmFsYW5jZQZib3Jyb3cEY29pbg9jb25maXJtX3JlcXVlc3QIY29udGFpbnMGZGVsZXRlFGRlc3Ryb3lfYW5kX3dpdGhkcmF3DGRlc3Ryb3lfc29tZQtkdW1teV9maWVsZA1keW5hbWljX2ZpZWxkBGVtaXQFZW1wdHkFZXZlbnQHZXhpc3RzXwRmcm9tDGZyb21fYmFsYW5jZQxmcm9tX3BhY2thZ2UDZ2V0CGdldF9ydWxlCGhhc19ydWxlAmlkBmluc2VydAlpbnRvX2tleXMHaXNfc29tZQRpdGVtA25ldwtuZXdfcmVxdWVzdAZvYmplY3QGb3B0aW9uB3BhY2thZ2UEcGFpZAlwb2xpY3lfaWQDcHV0CHJlY2VpcHRzBnJlbW92ZQtyZW1vdmVfcnVsZQVydWxlcwRzaXplA3N1aQR0YWtlD3RyYW5zZmVyX3BvbGljeQp0eF9jb250ZXh0CXR5cGVfbmFtZQN1aWQQdWlkX211dF9hc19vd25lcgx1aWRfdG9faW5uZXIFdmFsdWUHdmVjX3NldAh3aXRoZHJhdwR6ZXJvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQMIAAAAAAAAAAADCAEAAAAAAAAAAwgCAAAAAAAAAAMIAwAAAAAAAAADCAQAAAAAAAAAAwgFAAAAAAAAAAACBCsICTEDIQgJNAsOAQgGAQIDJwgKEwsHAQgMNwsOAQgGAgICJwgKMggJAwIBJwgJBAIBGwEAGAMYARgCGAQvAAEAAAkGCwALAQsCOAA5AAIBAQAAGSALADgBBAQFCAsBAQYAAAAAAAAAACcKAREfDAUOBREgDAYKBjkBOAILBQwCOAAMAzgDDAQLAgsECwM5AgsBER8LBjkDAgIBAAAgMQoALjgECwE3ABQhBAkFDwsAAQsDAQcEJw4COAUEJQsCOAYMBgoGCgA3ATgHJQQcBSIL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cmF0aW9uX21zDGZyb21fYmFsYW5jZQlnYXNfcHJpY2UHZ2VuZXNpcxFnZXRfdmFsaWRhdG9yX211dAlpbWFnZV91cmwWaXNfZHVwbGljYXRlX3ZhbGlkYXRvcghpc19lbXB0eQdpc19zb21lE21heF92YWxpZGF0b3JfY291bnQbbWluX3ZhbGlkYXRvcl9qb2luaW5nX3N0YWtlBG5hbWUPbmV0d29ya19hZGRyZXNzEm5ldHdvcmtfcHVibGljX2tleQNuZXcGb2JqZWN0Bm9wdGlvbgtwMnBfYWRkcmVzcw9wcmltYXJ5X2FkZHJlc3MLcHJvamVjdF91cmwTcHJvb2Zfb2ZfcG9zc2Vzc2lvbhNwcm90b2NvbF9wdWJsaWNfa2V5EHByb3RvY29sX3ZlcnNpb24RcmVjaXBpZW50X2FkZHJlc3MccmVxdWVzdF9hZGRfc3Rha2VfYXRfZ2VuZXNpcwVzcGxpdA1zdGFrZV9zdWJzaWR5G3N0YWtlX3N1YnNpZHlfZGVjcmVhc2VfcmF0ZRdzdGFrZV9zdWJzaWR5X2Z1bmRfbWlzdClzdGFrZV9zdWJzaWR5X2luaXRpYWxfZGlzdHJpYnV0aW9uX2Ftb3VudBtzdGFrZV9zdWJzaWR5X3BlcmlvZF9sZW5ndGgZc3Rha2Vfc3Vic2lkeV9zdGFydF9lcG9jaBVzdGFrZWRfd2l0aF92YWxpZGF0b3IDc3VpC3N1aV9hZGRyZXNzCnN1aV9zeXN0ZW0Wc3VpX3N5c3RlbV9zdGF0ZV9pbm5lcgh0cmFuc2Zlcgp0eF9jb250ZXh0CXZhbGlkYXRvciB2YWxpZGF0b3JfbG93X3N0YWtlX2dyYWNlX3BlcmlvZB12YWxpZGF0b3JfbG93X3N0YWtlX3RocmVzaG9sZA12YWxpZGF0b3Jfc2V0InZhbGlkYXRvcl92ZXJ5X2xvd19zdGFrZV90aHJlc2hvbGQGdmVjdG9yDndvcmtlcl9hZGRyZXNzEXdvcmtlcl9wdWJsaWNfa2V5BHplcm8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIDCAAAAAAAAAAAAwgBAAAAAAAA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AgYIDwMBCA8BBgkAHQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQYLBQEJAAIBAQIFBwgLAQcLBQEJAAMICQgPBQNCYWcHQmFsYW5jZQJJRAZPcHRpb24VUG9vbFRva2VuRXhjaGFuZ2VSYXRlA1NVSQlTdGFrZWRTdWkLU3Rha2luZ1Bvb2wTU3Rha2luZ1JlcXVlc3RFdmVudAZTdHJpbmcJVHhDb250ZXh0FVVuc3Rha2luZ1JlcXVlc3RFdmVudANVcmwJVmFsaWRhdG9yEVZhbGlkYXRvck1ldGFkYXRhFVZhbGlkYXRvck9wZXJhdGlvbkNhcAhhY3RpdmF0ZRVhY3RpdmF0ZV9zdGFraW5nX3Bvb2waYWRqdXN0X3N0YWtlX2FuZF9nYXNfcHJpY2UGYW1vdW50BWFzY2lpA2JhZwdiYWxhbmNlA2JjcwZib3Jyb3cPY29tbWlzc2lvbl9yYXRlCmRlYWN0aXZhdGUXZGVhY3RpdmF0ZV9zdGFraW5nX3Bvb2wPZGVwb3NpdF9yZXdhcmRzFWRlcG9zaXRfc3Rha2VfcmV3YXJkcwtkZXNjcmlwdGlvbhplZmZlY3R1YXRlX3N0YWdlZF9tZXRhZGF0YQRlbWl0BWVwb2NoBWV2ZW50DGV4dHJhX2ZpZWxkcwdleHRyYWN0CmZyb21fYXNjaWkJZ2FzX3ByaWNlB2dlbmVzaXMCaWQJaW1hZ2VfdXJsDGlzX2R1cGxpY2F0ZQ1pc19lcXVhbF9zb21lF2lzX2VxdWFsX3NvbWVfYW5kX3ZhbHVlB2lzX25vbmUMaXNfcHJlYWN0aXZlB2lzX3NvbWUIbWV0YWRhdGEEbmFtZQtuZXRfYWRkcmVzcw9uZXR3b3JrX2FkZHJlc3MUbmV0d29ya19wdWJrZXlfYnl0ZXMDbmV3EW5ld19mcm9tX21ldGFkYXRhDG5ld19tZXRhZGF0YRVuZXdfdW5zYWZlX2Zyb21fYnl0ZXMzbmV3X3VudmVyaWZpZWRfdmFsaWRhdG9yX29wZXJhdGlvbl9jYXBfYW5kX3RyYW5zZmVyGm5leHRfZXBvY2hfY29tbWlzc2lvbl9yYXRlFG5leHRfZXBvY2hfZ2FzX3ByaWNlFm5leHRfZXBvY2hfbmV0X2FkZHJlc3MabmV4dF9lcG9jaF9uZXR3b3JrX2FkZHJlc3MfbmV4dF9lcG9jaF9uZXR3b3JrX3B1YmtleV9ieXRlcxZuZXh0X2Vwb2NoX3AycF9hZGRyZXNzGm5leHRfZXBvY2hfcHJpbWFyeV9hZGRyZXNzHm5leHRfZXBvY2hfcHJvb2Zfb2ZfcG9zc2Vzc2lvbiBuZXh0X2Vwb2NoX3Byb3RvY29sX3B1YmtleV9ieXRlcxBuZXh0X2Vwb2NoX3N0YWtlGW5leHRfZXBvY2hfd29ya2VyX2FkZHJlc3MebmV4dF9lcG9jaF93b3JrZXJfcHVia2V5X2J5dGVzBG5vbmUGb2JqZWN0EG9wZXJhdGlvbl9jYXBfaWQGb3B0aW9uC3AycF9hZGRyZXNzFHBlbmRpbmdfc3Rha2VfYW1vdW50HXBlbmRpbmdfc3Rha2Vfd2l0aGRyYXdfYW1vdW50B3Bvb2xfaWQhcG9vbF90b2tlbl9leGNoYW5nZV9yYXRlX2F0X2Vwb2NoD3ByaW1hcnlfYWRkcmVzcxBwcmluY2lwYWxfYW1vdW50FXByb2Nlc3NfcGVuZGluZ19zdGFrZSRwcm9jZXNzX3BlbmRpbmdfc3Rha2VzX2FuZF93aXRoZHJhd3MLcHJvamVjdF91cmwTcHJvb2Zfb2ZfcG9zc2Vzc2lvbhVwcm90b2NvbF9wdWJrZXlfYnl0ZXMRcmVxdWVzdF9hZGRfc3Rha2UccmVxdWVzdF9hZGRfc3Rha2VfYXRfZ2VuZXNpcxtyZXF1ZXN0X3NldF9jb21taXNzaW9uX3JhdGUVcmVxdWVzdF9zZXRfZ2FzX3ByaWNlFnJlcXVlc3Rfd2l0aGRyYXdfc3Rha2UNcmV3YXJkX2Ftb3VudAZzZW5kZXIdc2V0X2NhbmRpZGF0ZV9jb21taXNzaW9uX3JhdGUXc2V0X2NhbmRpZGF0ZV9nYXNfcHJpY2UQc2V0X3ZvdGluZ19wb3dlcgRzb21lFnN0YWtlX2FjdGl2YXRpb25fZXBvY2gMc3Rha2VfYW1vdW50EXN0YWtlZF9zdWlfYW1vdW50DnN0YWtlcl9hZGRyZXNzDHN0YWtpbmdfcG9vbA9zdGFraW5nX3Bvb2xfaWQGc3RyaW5nA3N1aQtzdWlfYWRkcmVzcwtzdWlfYmFsYW5jZRZzdWlfc3lzdGVtX3N0YXRlX2lubmVyCHRvX2J5dGVzC3RvdGFsX3N0YWtlEnRvdGFsX3N0YWtlX2Ftb3VudAp0eF9jb250ZXh0D3Vuc3Rha2luZ19lcG9jaCB1cGRhdGVfY2FuZGlkYXRlX25ldHdvcmtfYWRkcmVzcx91cGRhdGVfY2FuZGlkYXRlX25ldHdvcmtfcHVia2V5HHVwZGF0ZV9jYW5kaWRhdGVfcDJwX2FkZHJlc3MgdXBkYXRlX2NhbmRpZGF0ZV9wcmltYXJ5X2FkZHJlc3MgdXBkYXRlX2NhbmRpZGF0ZV9wcm90b2NvbF9wdWJrZXkfdXBkYXRlX2NhbmRpZGF0ZV93b3JrZXJfYWRkcmVzcx51cGRhdGVfY2FuZGlkYXRlX3dvcmtlcl9wdWJrZXkSdXBkYXRlX2Rlc2NyaXB0aW9uEHVwZGF0ZV9pbWFnZV91cmwLdXBkYXRlX25hbWUhdXBkYXRlX25leHRfZXBvY2hfbmV0d29ya19hZGRyZXNzIHVwZGF0ZV9uZXh0X2Vwb2NoX25ldHdvcmtfcHVia2V5HXVwZGF0ZV9uZXh0X2Vwb2NoX3AycF9hZGRyZXNzIXVwZGF0ZV9uZXh0X2Vwb2NoX3ByaW1hcnlfYWRkcmVzcyF1cGRhdGVfbmV4dF9lcG9jaF9wcm90b2NvbF9wdWJrZXkgdXBkYXRlX25leHRfZXBvY2hfd29ya2VyX2FkZHJlc3MfdXBkYXRlX25leHRfZXBvY2hfd29ya2VyX3B1YmtleRJ1cGRhdGVfcHJvamVjdF91cmwDdXJsEXZhbGlkYXRlX21ldGFkYXRhFXZhbGlkYXRlX21ldGFkYXRhX2Jjcwl2YWxpZGF0b3IRdmFsaWRhdG9yX2FkZHJlc3MNdmFsaWRhdG9yX2NhcA12YWxpZGF0b3Jfc2V0EXZhbGlkYXRvcl93cmFwcGVyBXZhbHVlHnZlcmlmaWVkX29wZXJhdGlvbl9jYXBfYWRkcmVzcwx2b3RpbmdfcG93ZXIOd29ya2VyX2FkZHJlc3MTd29ya2VyX3B1YmtleV9ieXRlcwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgMIAAAAAAAAAAADCAEAAAAAAAAAAwgCAAAAAAAAAAMIAwAAAAAAAAADCAQAAAAAAAAAAwgFAAAAAAAAAAMIBgAAAAAAAAADCAcAAAAAAAAAAwgIAAAAAAAAAAMICQAAAAAAAAADCAoAAAAAAAAAAwgLAAAAAAAAAAMIDAAAAAAAAAADCGQAAAAAAAAAAwhlAAAAAAAAAAMIECcAAAAAAAADCAABAAAAAAAAAAIWaQVVCgI0CgKPAQoCVAoCMQgGHggGKQgMUwgMMggGSggGTwgGjgEIBkILBQEKAkELBQEKAj4LBQEKAkULBQEKAjwLBQEIBj8LBQEIBkALBQEIBkQLBQEIBiMIBwECCjAIAI0BA0gICSYDZQgPGQNDAzsDOgMjCAcCAgVNCAmHAQVkBSEDEwMDAgdNCAmHAQVkBWEDcANQA1sDAAMAACFECwAMDgsBDBkLAgwdCwMMHgsEDB8LBQwgCwYMIQsHDCILCAwjCwkMDwsKDBALCwwRCwwMEjgADBM4AAwUOAAMFTgADBY4AQwXOAEMGDgBDBo4AQwbCw0MHAsOCxkLHQseCx8LIAshCyILIwsPCxALEQsSCxMLFgsUCxULFwsYCxoLGwscEgACAQMAACSAAQ4JQSUHECUECw4KQSUHECUMEAUNCQwQCxAEFQ4LQSUHECUMEQUXCQwRCxEEHw4MQSUHECUMEgUhCQwSCxIEKQ4FQSUHECUMEwUrCQwTCxMEMw4GQSUHECUMFAU1CQwUCxQEPQ4HQSUHECUMFQU/CQwVCxUERw4IQSUHECUMFgVJCQwWCxYETAVQCw8BBwknCg4HDyUEVQVZCw8BBwgnCwALAQsCCwMLBAsFEUwRVAsGEUwRVAsHEVsLCBFbCwkRTBFUCwoRTBFUCwsRTBFUCwwRTBFUCg8RVREADBcOFxFJCxcLDQsOCw8RSwICAwAABQULAA8ACwERXQIDAwAABQULAA8ACwERXAIEAwAABQ0KABABFAoADwIVCgAQAxQLAA8EFQIFAwAAKzkOATgCDAQKBAYAAAAAAAAAACQECAUOCwABCwMBBwsnCgMuEVkGAQAAAAAAAAAWDAUKAA8ACwEKAgsFCgMRZgoAEAARXwQiCgAPABFkCgAQBRQKBBYKAA8FFQoALhExCwAQBhAHFAsCCwMuEVkLBBICOAMCBgMAABYuCgMuEVkGAAAAAAAAAAAhBAcFDQsAAQsDAQcMJw4BOAIMBAoEBgAAAAAAAAAAJAQVBRsLAAELAwEHCycKAA8ACwELAgYAAAAAAAAAAAsDEWYKAA8AEWQKABAFFAsEFgsADwUVAgcDAAA0Kw4BEWkMAw4BEWgMBQoADwALAQoCEWcMBgoGCgMXDAQKABAFFAsGFwoADwUVCgAuETELABAGEAcUCgIuEVoLBQsCLhFZCwMLBBIDOAQCCAMAAAUTDgERbBQKABAGEAcUIQQKBQ4LAAEHDicLAgsADwEVAgkDAAAFIAoALhEOBAUFCQsAAQcKJw4BEWwUCgAQBhAHFCEEEwUXCwABBw4nCgIKAA8BFQsCCwAPAhUCCgMAAAUOCgEHDyUEBQUJCwABBwgnCwELAA8DFQILAwAABRcKAC4RDgQFBQkLAAEHCicKAQcPJQQOBRILAAEHCCcLAQsADwQVAgwDAAAFDgoAEAUUDgE4AhYKAA8FFQsADwALARFeAg0DAAAFEAoADwALARFlCgAuESgLABAFFCEEDQUPBwsnAg4BAAAFBAsAEAARXwIPAQAABQMLABAGAhABAAAFBQsAEAYQBxQCEQEAAAUECwAQBhAIAhIBAAAFBAsAEAYQCQITAQAABQQLABAGEAoCFAEAAAUECwAQBhALAhUBAAAFBAsAEAYQDAIWAQAABQQLABAGEA0CFwEAAAUECwAQBhAOAhgBAAAFBAsAEAYQDwIZAQAABQQLABAGEBACGgEAAAUECwAQBhARAhsBAAAFBAsAEAYQEgIcAQAABQQLABAGEBMCHQEAAAUECwAQBhAUAh4BAAAFBAsAEAYQFQIfAQAABQQLABAGEBYCIAEAAAUECwAQBhAXAiEBAAAFBAsAEAYQGAIiAQAABQQLABAGEBkCIwEAAAUECwAQBhAaAiQBAAAFBAsAEAYQGwIlAQAABQMLABAcAiYBAAAFBAsAEAEUAicBAAAFBSgLABAAEWoCKAEAAAUECwAQABFqAikBAAAFAwsAESgCKgEAAAUECwAQHRQCKwMAAAUFCwELAA8dFQIsAQAABQQLABAAEWECLQEAAAUECwAQABFiAi4BAAAFBAsAEAIUAi8BAAAFBAsAEAQUAjABAAAFBQsAEAALARFjAjEBAAAFBAsAEAA4BQIyAQAAP5cDCgAQBhAHFAoBEAYQBxQhBA0IDAIFFwoAEAYQCBQKARAGEAgUIQwCCwIEHAgMDQUmCgAQBhAMFAoBEAYQDBQhDA0LDQQrCAwYBTUKABAGEA0UCgEQBhANFCEMGAsYBDoIDBkFRAoAEAYQEBQKARAGEBAUIQwZCxkESQgMGgVTCgAQBhASFAoBEAYQEhQhDBoLGgRYCAwbBWIKABAGEBIUCgEQBhATFCEMGwsbBGcIDBwFcQoAEAYQExQKARAGEBMUIQwcCxwEdggMHQWAAQoAEAYQExQKARAGEBIUIQwdCx0EhQEIDB4FjQEKABAGEBQKARAGEBQ4BgweCx4EkgEIDAMFmgEKABAGEBUKARAGEBU4BgwDCwMEnwEIDAQFpwEKABAGEBgKARAGEBg4BwwECwQErAEIDAUFtAEKABAGEBoKARAGEBo4BwwFCwUEuQEIDAYFwQEKABAGEBoKARAGEBs4BwwGCwYExgEIDAcFzgEKABAGEBsKARAGEBs4BwwHCwcE0wEIDAgF2wEKABAGEBsKARAGEBo4BwwICwgE4AEIDAkF6AEKABAGEBQKARAGEAw4CAwJCwkE7QEIDAoF9QEKABAGEBUKARAGEA04CAwKCwoE+gEIDAsFggIKABAGEBgKARAGEBA4CQwLCwsEhwIIDAwFjwIKABAGEBoKARAGEBI4CQwMCwwElAIIDA4FnAIKABAGEBoKARAGEBM4CQwOCw4EoQIIDA8FqQIKABAGEBsKARAGEBM4CQwPCw8ErgIIDBAFtgIKABAGEBsKARAGEBI4CQwQCxAEuwIIDBEFwwIKARAGEBQKABAGEAw4CAwRCxEEyAIIDBIF0AIKARAGEBUKABAGEA04CAwSCxIE1QIIDBMF3QIKARAGEBgKABAGEBA4CQwTCxME4gIIDBQF6gIKARAGEBoKABAGEBI4CQwUCxQE7wIIDBUF9wIKARAGEBoKABAGEBM4CQwVCxUE/AIIDBYFhAMKARAGEBsKABAGEBM4CQwWCxYEjQMLAAELAQEIDBcFlQMLARAGEBsLABAGEBI4CQwXCxcCMwAAAA0RCgA4CgQKCwEBCwABCQwCBQ8LADgLCwEhDAILAgI0AAAAQRoKADgKBAYIDAIFCQoBOAoMAgsCBBILAQELAAEJDAMFGAsAOAsLATgLIQwDCwMCNQMAAA8ZCgEuEVoMAgoCCgAQBhAHFCEEDAUSCwABCwEBBw0nCwILARFrCwAPHBUCNgMAAAUSDgFBJQcQJQQGBQoLAAEHCScLARFMEVQLAA8GDwgVAjcDAAAFEg4BQSUHECUEBgUKCwABBwknCwERTBFUCwAPBg8JFQI4AwAABREOAUElBxAlBAYFCgsAAQcJJwsBEVsLAA8GDwoVAjkDAAAFEQ4BQSUHECUEBgUKCwABBwknCwERWwsADwYPCxUCOgMAAAUWDgFBJQcQJQQGBQoLAAEHCScLARFMEVQ4DAoADwYPFBULABAGEUkCOwMAAAUeCgAuEQ4EBQUJCwABBwonDgFBJQcQJQQPBRMLAAEHCScLARFMEVQKAA8GDwwVCwAQBhFJAjwDAAAFFg4BQSUHECUEBgUKCwABBwknCwERTBFUOAwKAA8GDxUVCwAQBhFJAj0DAAAFHgoALhEOBAUFCQsAAQcKJw4BQSUHECUEDwUTCwABBwknCwERTBFUCgAPBg8NFQsAEAYRSQI+AwAABRYOAUElBxAlBAYFCgsAAQcJJwsBEUwRVDgMCgAPBg8WFQsAEAYRSQI/AwAABR4KAC4RDgQFBQkLAAEHCicOAUElBxAlBA8FEwsAAQcJJwsBEUwRVAoADwYPDhULABAGEUkCQAMAAAUWDgFBJQcQJQQGBQoLAAEHCScLARFMEVQ4DAoADwYPFxULABAGEUkCQQMAAAUeCgAuEQ4EBQUJCwABBwonDgFBJQcQJQQPBRMLAAEHCScLARFMEVQKAA8GDw8VCwAQBhFJAkIDAAAFEAsBOA0KAA8GDxgVCwI4DQoADwYPGRULABAGEUkCQwMAAAUXCgAuEQ4EBQUJCwABBwonCwEKAA8GDxAVCwIKAA8GDxEVCwAQBhFJAkQDAAAFCgsBOA0KAA8GDxoVCwAQBhFJAkUDAAAFEgoALhEOBAUFCQsAAQcKJwsBCgAPBg8SFQsAEAYRSQJGAwAABQoLATgNCgAPBg8bFQsAEAYRSQJHAwAABRIKAC4RDgQFBQkLAAEHCicLAQoADwYPExULABAGEUkCSAMAAAWPAQoALhEdOA4EEgoADwYPFDgPCgAPBg8MFTgBCgAPBg8UFQoALhEeOA4EJAoADwYPFTgPCgAPBg8NFTgBCgAPBg8VFQoALhEfOA4ENgoADwYPFjgPCgAPBg8OFTgBCgAPBg8WFQoALhEgOA4ESAoADwYPFzgPCgAPBg8PFTgBCgAPBg8XFQoALhEhOBAEZwoADwYPGDgRCgAPBg8QFTgACgAPBg8YFQoADwYPGTgRCgAPBg8RFTgACgAPBg8ZFQoALhEjOBAEeQoADwYPGjgRCgAPBg8SFTgACgAPBg8aFQoALhEkOBAEjAEKAA8GDxs4EQoADwYPExU4AAsADwYPGxUFjgELAAECSQEAAAUECwA4EhFKAkoBAgBLAAAARBgOABAHFAwGCgMRYAwFCwYKAxFrDAQLAAYAAAAAAAAAAAsECgELBQoCBgAAAAAAAAAACwELAgsDEVUSAQIBBAEHAQMBCAEFAQYBAAAAAAUABgAHAAgACQAKAAsADAABAAQAAgADABEAEgATABQADQAOAA8AEAECAQEAJwBrAIkBAIoBAI0BAApzdWlfc3lzdGVt2BuhHOsLBgAAAAwBABwCHD4DWpIDBOwDCgX2A54DB5QHvwwI0xNgBrMUNgrpFAgM8RSmBg2XGwQPmxsCADABHAIQAhICFAIbAi8CMwI0AC0ALgAxAEkASgAGCAABAgcBAAACAAQBAAEDAQwBAAEFCwQABgMCAAgKAgAJBAQACgUIAAsHBAALCAQACwkEAAwNBAANDAwAABMAAQAAIgIBAAAkAwEAACEDAQAAIwMBAAAmBAEAACsEAQAAJQUBAAAqBQEAAB8GAQAAIAcBAAAnCAEAAB4JAQAANQkBAAAoAwEAAD8KAQAAPQoBAAA+CgEAAEcKAQAAQAoBAAA2CgEAAEIKAQAAOAoBAABDCgEAADkKAQAARQoBAAA7CgEAAEQLAQAAOgsBAABGCgEAADwKAQAAQQoBAAA3CgEAAA8MDQAAGQ4PAAAaDhAAABgOEAAEDhYBAgcEBBEnKwIHBAQdJygCBwQHLBgBAQgIKSMkAAsPJQ0ACxMSEwALFgEUAAseIAEACx8dAQALIB4BAAshGgEACyIZAQALIxoBAAskGgEACyUcAQALJhsBAAsnHwEACygaAQALKhwBAAsrGwEACzIPFAALNSABAAs2IQEACzchAQALOCEBAAs5IQEACzoiAQALOyEBAAs8IQEACz0hAQALPiEBAAs/IQEAC0AhAQALQSEBAAtCIQEAC0MhAQALRCIBAAtFIQEAC0YhAQALRyEBAAtIEykAJRUoFycVJSomKggIBAoIDAsCAQgFAwMICwgHBwgGABAHCAAKAgoCCgIKAgoCCgIKAgoCCgIKAgoCCgIDAwcIBgIHCAAHCAYDBwgABggNAwMHCAADBwgGBAcIAAsDAQgFBQcIBgUHCAAKCwMBCAULAQEDBQcIBgMHCAAICAcIBgMHCAAGCA0FAwcIAAoCBggGBAcIAAoCCgIGCAYLCwIBCAULAgEIBQcIAAMDAwMDAwMHCAYBCwIBCAUBBwgAAQYICgEHCAoDCAAICQMHCggMCwIBCAUDAwgLCAcHCAYBCAkBAwIDCAkDBwgECQAJAQEIAAEJABAHCAoKAgoCCgIKAgoCCgIKAgoCCgIKAgoCCgIDAwcIBgIHCAoHCAYDBwgKBggNAwMHCAoDBwgGBAcICgsDAQgFBQcIBgUHCAoKCwMBCAULAQEDBQcIBgMHCAoICAcIBgMHCAoGCA0FAwcICgoCBggGBAcICgoCCgIGCAYBBggGAQULBwgKAwMLAgEIBQsCAQgFAwMDAwMHCAYCBwgKCAoCBwgECQABCQEBCAoCAwgKAQcJAQdCYWxhbmNlBENvaW4GT3B0aW9uA1NVSQxTdGFrZVN1YnNpZHkJU3Rha2VkU3VpDlN1aVN5c3RlbVN0YXRlE1N1aVN5c3RlbVN0YXRlSW5uZXIVU3VpU3lzdGVtU3RhdGVJbm5lclYyEFN5c3RlbVBhcmFtZXRlcnMJVHhDb250ZXh0A1VJRB9VbnZlcmlmaWVkVmFsaWRhdG9yT3BlcmF0aW9uQ2FwCVZhbGlkYXRvcgNhZGQNYWR2YW5jZV9lcG9jaAdiYWxhbmNlCmJvcnJvd19tdXQEY29pbgZjcmVhdGUNZHluYW1pY19maWVsZAdnZW5lc2lzHGdlbmVzaXNfc3lzdGVtX3N0YXRlX3ZlcnNpb24CaWQYbG9hZF9pbm5lcl9tYXliZV91cGdyYWRlEWxvYWRfc3lzdGVtX3N0YXRlFWxvYWRfc3lzdGVtX3N0YXRlX211dAZvYmplY3QGb3B0aW9uBnJlbW92ZRByZXBvcnRfdmFsaWRhdG9yEXJlcXVlc3RfYWRkX3N0YWtlGnJlcXVlc3RfYWRkX3N0YWtlX211bF9jb2luFXJlcXVlc3RfYWRkX3ZhbGlkYXRvch9yZXF1ZXN0X2FkZF92YWxpZGF0b3JfY2FuZGlkYXRlGHJlcXVlc3RfcmVtb3ZlX3ZhbGlkYXRvciJyZXF1ZXN0X3JlbW92ZV92YWxpZGF0b3JfY2FuZGlkYXRlG3JlcXVlc3Rfc2V0X2NvbW1pc3Npb25fcmF0ZRVyZXF1ZXN0X3NldF9nYXNfcHJpY2UWcmVxdWVzdF93aXRoZHJhd19zdGFrZRRyb3RhdGVfb3BlcmF0aW9uX2NhcAZzZW5kZXInc2V0X2NhbmRpZGF0ZV92YWxpZGF0b3JfY29tbWlzc2lvbl9yYXRlIXNldF9jYW5kaWRhdGVfdmFsaWRhdG9yX2dhc19wcmljZQxzaGFyZV9vYmplY3QNc3Rha2Vfc3Vic2lkeQxzdGFraW5nX3Bvb2wDc3VpCnN1aV9zeXN0ZW0Wc3VpX3N5c3RlbV9zdGF0ZV9pbm5lchRzeXN0ZW1fc3RhdGVfdmVyc2lvbgh0cmFuc2Zlcgp0eF9jb250ZXh0FXVuZG9fcmVwb3J0X3ZhbGlkYXRvcip1cGRhdGVfY2FuZGlkYXRlX3ZhbGlkYXRvcl9uZXR3b3JrX2FkZHJlc3MpdXBkYXRlX2NhbmRpZGF0ZV92YWxpZGF0b3JfbmV0d29ya19wdWJrZXkmdXBkYXRlX2NhbmRpZGF0ZV92YWxpZGF0b3JfcDJwX2FkZHJlc3MqdXBkYXRlX2NhbmRpZGF0ZV92YWxpZGF0b3JfcHJpbWFyeV9hZGRyZXNzKnVwZGF0ZV9jYW5kaWRhdGVfdmFsaWRhdG9yX3Byb3RvY29sX3B1YmtleSl1cGRhdGVfY2FuZGlkYXRlX3ZhbGlkYXRvcl93b3JrZXJfYWRkcmVzcyh1cGRhdGVfY2FuZGlkYXRlX3ZhbGlkYXRvcl93b3JrZXJfcHVia2V5HHVwZGF0ZV92YWxpZGF0b3JfZGVzY3JpcHRpb24adXBkYXRlX3ZhbGlkYXRvcl9pbWFnZV91cmwVdXBkYXRlX3ZhbGlkYXRvcl9uYW1lK3VwZGF0ZV92YWxpZGF0b3JfbmV4dF9lcG9jaF9uZXR3b3JrX2FkZHJlc3MqdXBkYXRlX3ZhbGlkYXRvcl9uZXh0X2Vwb2NoX25ldHdvcmtfcHVia2V5J3VwZGF0ZV92YWxpZGF0b3JfbmV4dF9lcG9jaF9wMnBfYWRkcmVzcyt1cGRhdGVfdmFsaWRhdG9yX25leHRfZXBvY2hfcHJpbWFyeV9hZGRyZXNzK3VwZGF0ZV92YWxpZGF0b3JfbmV4dF9lcG9jaF9wcm90b2NvbF9wdWJrZXkq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CwUBCQABBgkAAgkABQYDCwUBCAkDCwUBCAkFAwEGCAsBBQIHCwUBCQALBQEJAAILBQEJAAcICwELBgEJAAELBgEICQMDCAELBQEICQIDAwMHCwoCCQAJAQkACQEFAwMIAQMDAgcLBQEJAAMCBwsDAQkACQADCwUBCAkFAwEJAAEGCwMBCQACBggCCwUBCAkCBgsDAQkACQACBgsKAgkACQEJAAEGCQEDAwgBAwNCYWcHQmFsYW5jZQRDb2luAklEBk9wdGlvbhVQb29sVG9rZW5FeGNoYW5nZVJhdGUDU1VJCVN0YWtlZFN1aQtTdGFraW5nUG9vbAVUYWJsZQlUeENvbnRleHQDVUlEFWFjdGl2YXRlX3N0YWtpbmdfcG9vbBBhY3RpdmF0aW9uX2Vwb2NoA2FkZANiYWcHYmFsYW5jZQZib3Jyb3cYY2hlY2tfYmFsYW5jZV9pbnZhcmlhbnRzBGNvaW4IY29udGFpbnMXZGVhY3RpdmF0ZV9zdGFraW5nX3Bvb2wSZGVhY3RpdmF0aW9uX2Vwb2NoBmRlbGV0ZQ9kZXBvc2l0X3Jld2FyZHMFZXBvY2gOZXhjaGFuZ2VfcmF0ZXMMZXh0cmFfZmllbGRzBGZpbGwMZnJvbV9iYWxhbmNlDmdldF9zdWlfYW1vdW50EGdldF90b2tlbl9hbW91bnQQZ2V0X3dpdGhfZGVmYXVsdAJpZBVpbml0aWFsX2V4Y2hhbmdlX3JhdGUZaXNfZXF1YWxfc3Rha2luZ19tZXRhZGF0YQtpc19pbmFjdGl2ZQdpc19ub25lDGlzX3ByZWFjdGl2ZRVpc19wcmVhY3RpdmVfYXRfZXBvY2gHaXNfc29tZQRqb2luD2pvaW5fc3Rha2VkX3N1aQRtYXRoA21pbgNuZXcEbm9uZQZvYmplY3QGb3B0aW9uG3BlbmRpbmdfcG9vbF90b2tlbl93aXRoZHJhdw1wZW5kaW5nX3N0YWtlFHBlbmRpbmdfc3Rha2VfYW1vdW50HXBlbmRpbmdfc3Rha2Vfd2l0aGRyYXdfYW1vdW50GnBlbmRpbmdfdG90YWxfc3VpX3dpdGhkcmF3B3Bvb2xfaWQRcG9vbF90b2tlbl9hbW91bnQScG9vbF90b2tlbl9iYWxhbmNlIXBvb2xfdG9rZW5fZXhjaGFuZ2VfcmF0ZV9hdF9lcG9j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BQYKBQoDCgMLDwIDAwsPAgMDAwoFAwMDAwoDCgMIAwMHAwMIEwgTBQYIEwIGCw8CCQAJAQYJAAIHCw8CCQAJAQYJAAIJAAkBAwcLDwIJAAkBCQAJAQIHCgkAAwIDAwoKCwkBAwMDCwoBAwMDAwMGCBMGCggTAQsJAQMCAwkAAQsJAQkAAQoLCQEJAAELCgEJAAEHCwoBCQABBgsNAQkAAwMDAwIGCBMGCBMCBgsNAQkAAwEGCQABCwUBCQAFBQMLBQEDAwoDBAMDCwUBAwsFAQMCBwsNAQkAAwEHCQABBggVAQYFAgMLBQEDBgEBAwMLBQEDCwUBAwUFBggTCAgFBggTAQYIFAEIFAEGCAgCAwgTAQYKCQADAwUICAEIBAcGBQYFAwMGBQoFBwsQAQUCBQsQAQUBBgsPAgkACQEBCgkAAgYLEAEJAAYJAAIHCxABCQAGCQABBgsQAQkAAQcLDQEJAAEIAwYDAwMDAwMCBwgTBwgOBAMDAwYIEwcLDwIDAwsPAgMDBAQDAwMFCgUGCggTCxABBQoFBQEHCw8CCQAJAQELEAEJAAYDAwQKAwoDAxEDAwMKAwMKAwMEAwMDAwMDAwMECAMDCwcBCAsDBwgTBQQLBwEICwIHCwcBCQADAQsHAQkAAgcLBwEJAAsHAQkAAgcIEwsHAQgLCAoFAwMDAwoFBggTBQIGCBMDAQgRAQgCA0JhZwdCYWxhbmNlBUVudHJ5AklEBk9wdGlvbhVQb29sVG9rZW5FeGNoYW5nZVJhdGUNUHJpb3JpdHlRdWV1ZQNTVUkJU3Rha2VkU3VpBVRhYmxlCFRhYmxlVmVjCVR4Q29udGV4dB9VbnZlcmlmaWVkVmFsaWRhdG9yT3BlcmF0aW9uQ2FwCVZhbGlkYXRvchdWYWxpZGF0b3JFcG9jaEluZm9FdmVudBlWYWxpZGF0b3JFcG9jaEluZm9FdmVudFYyElZhbGlkYXRvckpvaW5FdmVudBNWYWxpZGF0b3JMZWF2ZUV2ZW50FVZhbGlkYXRvck9wZXJhdGlvbkNhcAxWYWxpZGF0b3JTZXQQVmFsaWRhdG9yV3JhcHBlcgZWZWNNYXAGVmVjU2V0CGFjdGl2YXRlEWFjdGl2ZV92YWxpZGF0b3JzA2FkZBphZGp1c3Rfc3Rha2VfYW5kX2dhc19wcmljZQ1hZHZhbmNlX2Vwb2NoJmFzc2VydF9ub19wZW5kaW5nX29yX2FjdGl2ZV9kdXBsaWNhdGVzEmF0X3Jpc2tfdmFsaWRhdG9ycwNiYWcHYmFsYW5jZQZib3Jyb3cKYm9ycm93X211dBZjYWxjdWxhdGVfdG90YWxfc3Rha2VzJmNsZWFuX3JlcG9ydF9yZWNvcmRzX2xlYXZpbmdfdmFsaWRhdG9yD2NvbW1pc3Npb25fcmF0ZSRjb21wdXRlX2FkanVzdGVkX3Jld2FyZF9kaXN0cmlidXRpb24aY29tcHV0ZV9yZXdhcmRfYWRqdXN0bWVudHMaY29tcHV0ZV9zbGFzaGVkX3ZhbGlkYXRvcnMmY29tcHV0ZV91bmFkanVzdGVkX3Jld2FyZF9kaXN0cmlidXRpb24IY29udGFpbnMZY291bnRfZHVwbGljYXRlc190YWJsZXZlYxRjb3VudF9kdXBsaWNhdGVzX3ZlYwljcmVhdGVfdjEKZGVhY3RpdmF0ZRVkZXBvc2l0X3N0YWtlX3Jld2FyZHMaZGVyaXZlX3JlZmVyZW5jZV9nYXNfcHJpY2UHZGVzdHJveQxkZXN0cm95X3NvbWUMZGVzdHJveV96ZXJvEWRpc3RyaWJ1dGVfcmV3YXJkGmVmZmVjdHVhdGVfc3RhZ2VkX21ldGFkYXRhBGVtaXQbZW1pdF92YWxpZGF0b3JfZXBvY2hfZXZlbnRzBWVtcHR5BWVwb2NoBWV2ZW50DGV4dHJhX2ZpZWxkcwdleHRyYWN0DmZpbmRfdmFsaWRhdG9yHWZpbmRfdmFsaWRhdG9yX2Zyb21fdGFibGVfdmVjCWdhc19wcmljZQdnZW5lc2lzA2dldDBnZXRfYWN0aXZlX29yX3BlbmRpbmdfb3JfY2FuZGlkYXRlX3ZhbGlkYXRvcl9tdXQwZ2V0X2FjdGl2ZV9vcl9wZW5kaW5nX29yX2NhbmRpZGF0ZV92YWxpZGF0b3JfcmVmGGdldF9hY3RpdmVfdmFsaWRhdG9yX3JlZiVnZXRfY2FuZGlkYXRlX29yX2FjdGl2ZV92YWxpZGF0b3JfbXV0B2dldF9tdXQZZ2V0X3BlbmRpbmdfdmFsaWRhdG9yX3JlZhVnZXRfdmFsaWRhdG9yX2luZGljZXMRZ2V0X3ZhbGlkYXRvcl9tdXQaZ2V0X3ZhbGlkYXRvcl9tdXRfd2l0aF9jdHgvZ2V0X3ZhbGlkYXRvcl9tdXRfd2l0aF9jdHhfaW5jbHVkaW5nX2NhbmRpZGF0ZXMjZ2V0X3ZhbGlkYXRvcl9tdXRfd2l0aF92ZXJpZmllZF9jYXARZ2V0X3ZhbGlkYXRvcl9yZWYCaWQTaW5hY3RpdmVfdmFsaWRhdG9ycwZpbnNlcnQJaW50b19rZXlzImlzX2FjdGl2ZV92YWxpZGF0b3JfYnlfc3VpX2FkZHJlc3MMaXNfZHVwbGljYXRlFmlzX2R1cGxpY2F0ZV92YWxpZGF0b3IiaXNfZHVwbGljYXRlX3dpdGhfYWN0aXZlX3ZhbGlkYXRvciNpc19kdXBsaWNhdGVfd2l0aF9wZW5kaW5nX3ZhbGlkYXRvcghpc19lbXB0eRVpc19pbmFjdGl2ZV92YWxpZGF0b3IMaXNfcHJlYWN0aXZlB2lzX3NvbWUWaXNfdmFsaWRhdG9yX2NhbmRpZGF0ZQxpc192b2x1bnRhcnkEam9pbgRrZXlzBmxlbmd0aBxsb2FkX3ZhbGlkYXRvcl9tYXliZV91cGdyYWRlA25ldwluZXdfZW50cnkTbmV3X2Zyb21fdW52ZXJpZmllZBpuZXh0X2Vwb2NoX3ZhbGlkYXRvcl9jb3VudARub25lBm9iamVjdBBvcGVyYXRpb25fY2FwX2lkBm9wdGlvbhlwZW5kaW5nX2FjdGl2ZV92YWxpZGF0b3JzEHBlbmRpbmdfcmVtb3ZhbHMHcG9vbF9pZBNwb29sX3N0YWtpbmdfcmV3YXJkGHBvb2xfdG9rZW5fZXhjaGFuZ2VfcmF0ZSFwb29sX3Rva2VuX2V4Y2hhbmdlX3JhdGVfYXRfZXBvY2gDcG9wCHBvcF9iYWNrB3BvcF9tYXgOcHJpb3JpdHlfcXVldWUYcHJvY2Vzc19wZW5kaW5nX3JlbW92YWxzJHByb2Nlc3NfcGVuZGluZ19zdGFrZXNfYW5kX3dpdGhkcmF3cxpwcm9jZXNzX3BlbmRpbmdfdmFsaWRhdG9ycxtwcm9jZXNzX3ZhbGlkYXRvcl9kZXBhcnR1cmUJcHVzaF9iYWNrEHF1b3J1bV90aHJlc2hvbGQacmVmZXJlbmNlX2dhc19zdXJ2ZXlfcXVvdGUGcmVtb3ZlEXJlcXVlc3RfYWRkX3N0YWtlFXJlcXVlc3RfYWRkX3ZhbGlkYXRvch9yZXF1ZXN0X2FkZF92YWxpZGF0b3JfY2FuZGlkYXRlGHJlcXVlc3RfcmVtb3ZlX3ZhbGlkYXRvciJyZXF1ZXN0X3JlbW92ZV92YWxpZGF0b3JfY2FuZGlkYXRlG3JlcXVlc3Rfc2V0X2NvbW1pc3Npb25fcmF0ZRZyZXF1ZXN0X3dpdGhkcmF3X3N0YWtlBnNlbmRlchBzZXRfdm90aW5nX3Bvd2VyBHNpemUEc29tZRFzb3J0X3JlbW92YWxfbGlzdAVzcGxpdAVzdGFrZQxzdGFrZV9hbW91bnQMc3Rha2luZ19wb29sD3N0YWtpbmdfcG9vbF9pZBVzdGFraW5nX3Bvb2xfbWFwcGluZ3Mbc3RvcmFnZV9mdW5kX3N0YWtpbmdfcmV3YXJkA3N1aQtzdWlfYWRkcmVzcxZzdWlfc3lzdGVtX3N0YXRlX2lubmVyHXN1bV92b3RpbmdfcG93ZXJfYnlfYWRkcmVzc2VzBXRhYmxlCXRhYmxlX3ZlYxp0YWxseWluZ19ydWxlX2dsb2JhbF9zY29yZRd0YWxseWluZ19ydWxlX3JlcG9ydGVycwt0b3RhbF9zdGFrZRJ0b3RhbF9zdGFrZV9hbW91bnQSdG90YWxfdm90aW5nX3Bvd2VyCnR4X2NvbnRleHQgdW52ZXJpZmllZF9vcGVyYXRpb25fY2FwX2FkZHJlc3MndXBkYXRlX2FuZF9wcm9jZXNzX2xvd19zdGFrZV9kZXBhcnR1cmVzCXZhbGlkYXRvchF2YWxpZGF0b3JfYWRkcmVzcxR2YWxpZGF0b3JfY2FuZGlkYXRlcw12YWxpZGF0b3JfY2FwDXZhbGlkYXRvcl9zZXQWdmFsaWRhdG9yX3N0YWtlX2Ftb3VudBl2YWxpZGF0b3Jfc3Rha2luZ19wb29sX2lkHHZhbGlkYXRvcl90b3RhbF9zdGFrZV9hbW91bnQRdmFsaWRhdG9yX3dyYXBwZXIFdmFsdWUHdmVjX21hcAd2ZWNfc2V0BnZlY3Rvch52ZXJpZmllZF9vcGVyYXRpb25fY2FwX2FkZHJlc3MKdmVyaWZ5X2NhcAx2b3RpbmdfcG93ZXIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAA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CQALBwEJAAEGCBEBBgsHAQkAAQcIDwIHCwcBCQADCQcIFQcLBwEICgcLBwEICgcLDQIFCw4BBQMDAwMHCAwGBwgRCwcBCAoLBwEICgsHAQgKAwMBCAQBBgkBBQsHAQgKAwsHAQgKCwgBCAoLBwEICgELCAEICgIHCwgBCQAKCwgBCQABBgsFAQkAAQsFAQkAAgsHAQkABwgMAgkABQNCYWcHQmFsYW5jZQRDb2luAklEBk9wdGlvbgNTVUkMU3Rha2VTdWJzaWR5CVN0YWtlZFN1aQtTdG9yYWdlRnVuZBNTdWlTeXN0ZW1TdGF0ZUlubmVyFVN1aVN5c3RlbVN0YXRlSW5uZXJWMhRTeXN0ZW1FcG9jaEluZm9FdmVudBBTeXN0ZW1QYXJhbWV0ZXJzElN5c3RlbVBhcmFtZXRlcnNWMgVUYWJsZQlUeENvbnRleHQfVW52ZXJpZmllZFZhbGlkYXRvck9wZXJhdGlvbkNhcAlWYWxpZGF0b3IVVmFsaWRhdG9yT3BlcmF0aW9uQ2FwDFZhbGlkYXRvclNldAZWZWNNYXAGVmVjU2V0EWFjdGl2ZV92YWxpZGF0b3JzDWFkdmFuY2VfZXBvY2gmYXNzZXJ0X25vX3BlbmRpbmdfb3JfYWN0aXZlX2R1cGxpY2F0ZXMDYmFnB2JhbGFuY2UEY29pbghjb250YWlucwZjcmVhdGUYY3JlYXRlX3N5c3RlbV9wYXJhbWV0ZXJzGmRlcml2ZV9yZWZlcmVuY2VfZ2FzX3ByaWNlDGRlc3Ryb3lfc29tZQxkZXN0cm95X3plcm8EZW1pdAVlbXB0eQVlcG9jaBFlcG9jaF9kdXJhdGlvbl9tcxhlcG9jaF9zdGFydF90aW1lc3RhbXBfbXMFZXZlbnQMZXh0cmFfZmllbGRzFGV4dHJhY3RfY29pbl9iYWxhbmNlDGZyb21fYmFsYW5jZQdnZW5lc2lzHGdlbmVzaXNfc3lzdGVtX3N0YXRlX3ZlcnNpb24DZ2V0B2dldF9tdXQQZ2V0X3JlcG9ydGVyc19vZh9nZXRfc3RvcmFnZV9mdW5kX29iamVjdF9yZWJhdGVzHmdldF9zdG9yYWdlX2Z1bmRfdG90YWxfYmFsYW5jZRpnZXRfdmFsaWRhdG9yX211dF93aXRoX2N0eC9nZXRfdmFsaWRhdG9yX211dF93aXRoX2N0eF9pbmNsdWRpbmdfY2FuZGlkYXRlcyNnZXRfdmFsaWRhdG9yX211dF93aXRoX3ZlcmlmaWVkX2NhcAZpbnNlcnQMaW50b19iYWxhbmNlImlzX2FjdGl2ZV92YWxpZGF0b3JfYnlfc3VpX2FkZHJlc3MIaXNfZW1wdHkHaXNfc29tZQRqb2luCGpvaW5fdmVjHGxlZnRvdmVyX3N0b3JhZ2VfZnVuZF9pbmZsb3cTbWF4X3ZhbGlkYXRvcl9jb3VudBNtaW5fdmFsaWRhdG9yX2NvdW50G21pbl92YWxpZGF0b3Jfam9pbmluZ19zdGFrZQNuZXczbmV3X3VudmVyaWZpZWRfdmFsaWRhdG9yX29wZXJhdGlvbl9jYXBfYW5kX3RyYW5zZmVyGm5leHRfZXBvY2hfdmFsaWRhdG9yX2NvdW50Bm9iamVjdAZvcHRpb24KcGFyYW1ldGVycwNwYXkQcHJvdG9jb2xfdmVyc2lvbg9wdWJsaWNfdHJhbnNmZXITcmVmZXJlbmNlX2dhc19wcmljZQZyZW1vdmUQcmVwb3J0X3ZhbGlkYXRvchVyZXBvcnRfdmFsaWRhdG9yX2ltcGwRcmVxdWVzdF9hZGRfc3Rha2UacmVxdWVzdF9hZGRfc3Rha2VfbXVsX2NvaW4VcmVxdWVzdF9hZGRfdmFsaWRhdG9yH3JlcXVlc3RfYWRkX3ZhbGlkYXRvcl9jYW5kaWRhdGUYcmVxdWVzdF9yZW1vdmVfdmFsaWRhdG9yInJlcXVlc3RfcmVtb3ZlX3ZhbGlkYXRvcl9jYW5kaWRhdGUbcmVxdWVzdF9zZXRfY29tbWlzc2lvbl9yYXRlFXJlcXVlc3Rfc2V0X2dhc19wcmljZRZyZXF1ZXN0X3dpdGhkcmF3X3N0YWtlFHJvdGF0ZV9vcGVyYXRpb25fY2FwCXNhZmVfbW9kZR1zYWZlX21vZGVfY29tcHV0YXRpb25fcmV3YXJkcyRzYWZlX21vZGVfbm9uX3JlZnVuZGFibGVfc3RvcmFnZV9mZWUZc2FmZV9tb2RlX3N0b3JhZ2VfcmViYXRlcxlzYWZlX21vZGVfc3RvcmFnZV9yZXdhcmRzBnNlbmRlch1zZXRfY2FuZGlkYXRlX2NvbW1pc3Npb25fcmF0ZRdzZXRfY2FuZGlkYXRlX2dhc19wcmljZSdzZXRfY2FuZGlkYXRlX3ZhbGlkYXRvcl9jb21taXNzaW9uX3JhdGUhc2V0X2NhbmRpZGF0ZV92YWxpZGF0b3JfZ2FzX3ByaWNlCXNpbmdsZXRvbgVzcGxpdBZzdGFrZV9hY3RpdmF0aW9uX2Vwb2NoDXN0YWtlX3N1YnNpZHkUc3Rha2Vfc3Vic2lkeV9hbW91bnQZc3Rha2Vfc3Vic2lkeV9zdGFydF9lcG9jaAxzdGFraW5nX3Bvb2wVc3Rha2luZ19wb29sX21hcHBpbmdzDnN0b3JhZ2VfY2hhcmdlDHN0b3JhZ2VfZnVuZBRzdG9yYWdlX2Z1bmRfYmFsYW5jZRlzdG9yYWdlX2Z1bmRfcmVpbnZlc3RtZW50DnN0b3JhZ2VfcmViYXRlA3N1aQpzdWlfc3lzdGVtFnN1aV9zeXN0ZW1fc3RhdGVfaW5uZXIUc3lzdGVtX3N0YXRlX3ZlcnNpb24FdGFibGUNdG90YWxfYmFsYW5jZQ50b3RhbF9nYXNfZmVlcxx0b3RhbF9vYmplY3Rfc3RvcmFnZV9yZWJhdGVzC3RvdGFsX3N0YWtlH3RvdGFsX3N0YWtlX3Jld2FyZHNfZGlzdHJpYnV0ZWQIdHJhbnNmZXIKdHhfY29udGV4dBV1bmRvX3JlcG9ydF92YWxpZGF0b3IadW5kb19yZXBvcnRfdmFsaWRhdG9yX2ltcGwgdXBkYXRlX2NhbmRpZGF0ZV9uZXR3b3JrX2FkZHJlc3MfdXBkYXRlX2NhbmRpZGF0ZV9uZXR3b3JrX3B1YmtleRx1cGRhdGVfY2FuZGlkYXRlX3AycF9hZGRyZXNzIHVwZGF0ZV9jYW5kaWRhdGVfcHJpbWFyeV9hZGRyZXNzIHVwZGF0ZV9jYW5kaWRhdGVfcHJvdG9jb2xfcHVia2V5KnVwZGF0ZV9jYW5kaWRhdGVfdmFsaWRhdG9yX25ldHdvcmtfYWRkcmVzcyl1cGRhdGVfY2FuZGlkYXRlX3ZhbGlkYXRvcl9uZXR3b3JrX3B1YmtleSZ1cGRhdGVfY2FuZGlkYXRlX3ZhbGlkYXRvcl9wMnBfYWRkcmVzcyp1cGRhdGVfY2FuZGlkYXRlX3ZhbGlkYXRvcl9wcmltYXJ5X2FkZHJlc3MqdXBkYXRlX2NhbmRpZGF0ZV92YWxpZGF0b3JfcHJvdG9jb2xfcHVia2V5KXVwZGF0ZV9jYW5kaWRhdGVfdmFsaWRhdG9yX3dvcmtlcl9hZGRyZXNzKHVwZGF0ZV9jYW5kaWRhdGVfdmFsaWRhdG9yX3dvcmtlcl9wdWJrZXkfdXBkYXRlX2NhbmRpZGF0ZV93b3JrZXJfYWRkcmVzcx51cGRhdGVfY2FuZGlkYXRlX3dvcmtlcl9wdWJrZXkSdXBkYXRlX2Rlc2NyaXB0aW9uEHVwZGF0ZV9pbWFnZV91cmwLdXBkYXRlX25hbWUhdXBkYXRlX25leHRfZXBvY2hfbmV0d29ya19hZGRyZXNzIHVwZGF0ZV9uZXh0X2Vwb2NoX25ldHdvcmtfcHVia2V5HXVwZGF0ZV9uZXh0X2Vwb2NoX3AycF9hZGRyZXNzIXVwZGF0ZV9uZXh0X2Vwb2NoX3ByaW1hcnlfYWRkcmVzcyF1cGRhdGVfbmV4dF9lcG9jaF9wcm90b2NvbF9wdWJrZXkgdXBkYXRlX25leHRfZXBvY2hfd29ya2VyX2FkZHJlc3MfdXBkYXRlX25leHRfZXBvY2hfd29ya2VyX3B1YmtleRJ1cGRhdGVfcHJvamVjdF91cmwcdXBkYXRlX3ZhbGlkYXRvcl9kZXNjcmlwdGlvbhp1cGRhdGVfdmFsaWRhdG9yX2ltYWdlX3VybBV1cGRhdGVfdmFsaWRhdG9yX25hbWUrdXBkYXRlX3ZhbGlkYXRvcl9uZXh0X2Vwb2NoX25ldHdvcmtfYWRkcmVzcyp1cGRhdGVfdmFsaWRhdG9yX25leHRfZXBvY2hfbmV0d29ya19wdWJrZXkndXBkYXRlX3ZhbGlkYXRvcl9uZXh0X2Vwb2NoX3AycF9hZGRyZXNzK3VwZGF0ZV92YWxpZGF0b3JfbmV4dF9lcG9jaF9wcmltYXJ5X2FkZHJlc3MrdXBkYXRlX3ZhbGlkYXRvcl9uZXh0X2Vwb2NoX3Byb3RvY29sX3B1YmtleSp1cGRhdGVfdmFsaWRhdG9yX25leHRfZXBvY2hfd29ya2VyX2FkZHJlc3MpdXBkYXRlX3ZhbGlkYXRvcl9uZXh0X2Vwb2NoX3dvcmtlcl9wdWJrZXkcdXBkYXRlX3ZhbGlkYXRvcl9wcm9qZWN0X3VybAh2MV90b192Mgl2YWxpZGF0b3INdmFsaWRhdG9yX2NhcCB2YWxpZGF0b3Jf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Cw8CCQAJAQkAAgYLBwEJAAMaAQMBAwMHCwcBCAULDAEJAAMDCwwBCQADBggEBwgEAwYLCgEDAwMIEAsMAQkBAQMDCwwBCQADBwgFAxsBAwEDAwcLBwEIBQsMAQkAAwMLDAEJAQMGCAQHCAQDAwYLCgEDAwMIEAsMAQkBAQMDCwwBCQEDBwgFAwMLDAEJAAsMAQkBCwwBCQECBwsOAQkACw4BCQAHAwcLBwEIBQgEAwMDCBADBwsJAQkABggIAwMHCwcBCQADCQADBwsPAgkACQEJAAkBAQsBAgkACQECBgsTAgkACQEJAAMHCxMCCQAJAQkACQELCwwBCQALDAEJAAsMAQkAAwMLDAEJAQsMAQkBCwwBCQEDAwgQAgYLCQEJAAgQAQsCAgkACQELAwMGCwcBCAUHCwcBCAUDAQgEAwMIEAcLDwIDAwMDBwgFCAQNAwcLBwEIBQMDAQcLBwEIBQgEAwMIEAMIEAcLDwIDAxIGCwcBCAUDAwYLBwEIBQcLBwEIBQMDAQMDAwgEAwgQAwMIEAcLDwIDAwcGCAUKCAQGCAQGCwoBAwMIEAYLDwIDAwUDAwMDCBAGAwoDAwMDCgMEAwYIBAYLCgEDBgsPAgMIBAMGCwcBCAUDCBAKQWNjb3VudENhcAdCYWxhbmNlBUNsb2NrBENvaW4LQ3JpdGJpdFRyZWUJQ3VzdG9kaWFuAklEC0xpbmtlZFRhYmxlBk9wdGlvbgVPcmRlcg1PcmRlckNhbmNlbGVkC09yZGVyRmlsbGVkC09yZGVyUGxhY2VkBFBvb2wLUG9vbENyZWF0ZWQDU1VJBVRhYmxlCVRpY2tMZXZlbAlUeENvbnRleHQIVHlwZU5hbWUDVUlEGWFjY291bnRfYXZhaWxhYmxlX2JhbGFuY2UPYWNjb3VudF9iYWxhbmNlA2FkZARhc2tzBGJhY2sHYmFsYW5jZQpiYXNlX2Fzc2V0HGJhc2VfYXNzZXRfcXVhbnRpdHlfY2FuY2VsZWQaYmFzZV9hc3NldF9xdWFudGl0eV9maWxsZWQaYmFzZV9hc3NldF9xdWFudGl0eV9wbGFjZWQdYmFzZV9hc3NldF9xdWFudGl0eV9yZW1haW5pbmcXYmFzZV9hc3NldF90cmFkaW5nX2ZlZXMOYmFzZV9jdXN0b2RpYW4SYmF0Y2hfY2FuY2VsX29yZGVyBGJpZHMGYm9ycm93FGJvcnJvd19sZWFmX2J5X2luZGV4EmJvcnJvd19sZWFmX2J5X2tleQpib3Jyb3dfbXV0GGJvcnJvd19tdXRfbGVhZl9ieV9pbmRleBFjYW5jZWxfYWxsX29yZGVycwxjYW5jZWxfb3JkZXIEY2xvYgVjbG9jawRjb2luCGNvbnRhaW5zDmNyZWF0ZV9hY2NvdW50C2NyZWF0ZV9wb29sDGNyZWF0ZV9wb29sXwxjcmVhdGlvbl9mZWUHY3JpdGJpdAljdXN0b2RpYW4fZGVjcmVhc2VfdXNlcl9hdmFpbGFibGVfYmFsYW5jZRxkZWNyZWFzZV91c2VyX2xvY2tlZF9iYWxhbmNlDGRlcG9zaXRfYmFzZQ1kZXBvc2l0X3F1b3RlDWRlc3Ryb3lfZW1wdHkTZGVzdHJveV9lbXB0eV9sZXZlbAlkaXZfcm91bmQEZW1pdBNlbWl0X29yZGVyX2NhbmNlbGVkBWV2ZW50EGV4cGlyZV90aW1lc3RhbXAQZmluZF9jbG9zZXN0X2tleQlmaW5kX2xlYWYMZnJvbV9iYWxhbmNlBWZyb250A2dldBZnZXRfbGV2ZWwyX2Jvb2tfc3RhdHVzH2dldF9sZXZlbDJfYm9va19zdGF0dXNfYXNrX3NpZGUfZ2V0X2xldmVsMl9ib29rX3N0YXR1c19iaWRfc2lkZRBnZXRfb3JkZXJfc3RhdHVzAmlkH2luY3JlYXNlX3VzZXJfYXZhaWxhYmxlX2JhbGFuY2USaW5qZWN0X2xpbWl0X29yZGVyC2luc2VydF9sZWFmDGludG9fYmFsYW5jZQZpc19iaWQIaXNfZW1wdHkHaXNfbm9uZQRqb2luDGxpbmtlZF90YWJsZRBsaXN0X29wZW5fb3JkZXJzDGxvY2tfYmFsYW5jZQhsb3Rfc2l6ZRFtYWtlcl9yZWJhdGVfcmF0ZQltYXRjaF9hc2sJbWF0Y2hfYmlkHW1hdGNoX2JpZF93aXRoX3F1b3RlX3F1YW50aXR5BG1hdGgIbWF4X2xlYWYIbWluX2xlYWYQbWludF9hY2NvdW50X2NhcANtdWwJbXVsX3JvdW5kA25ldwRuZXh0EW5leHRfYXNrX29yZGVyX2lkEW5leHRfYmlkX29yZGVyX2lkCW5leHRfbGVhZgZvYmplY3QLb3Blbl9vcmRlcnMGb3B0aW9uCG9yZGVyX2lkDG9yZGVyX2lzX2JpZAVvd25lchFwbGFjZV9saW1pdF9vcmRlchJwbGFjZV9tYXJrZXRfb3JkZXIHcG9vbF9pZA1wcmV2aW91c19sZWFmBXByaWNlCXB1c2hfYmFjawhxdWFudGl0eQtxdW90ZV9hc3NldBhxdW90ZV9hc3NldF90cmFkaW5nX2ZlZXMPcXVvdGVfY3VzdG9kaWFuBnJlbW92ZRRyZW1vdmVfbGVhZl9ieV9pbmRleAxyZW1vdmVfb3JkZXIMc2hhcmVfb2JqZWN0BXNwbGl0A3N1aRlzd2FwX2V4YWN0X2Jhc2VfZm9yX3F1b3RlGXN3YXBfZXhhY3RfcXVvdGVfZm9yX2Jhc2UFdGFibGUOdGFrZXJfZmVlX3JhdGUJdGlja19zaXplDHRpbWVzdGFtcF9tcw50b3RhbF9xdWFudGl0eQh0cmFuc2Zlcgp0eF9jb250ZXh0CXR5cGVfbmFtZQx1aWRfYXNfaW5uZXIOdW5sb2NrX2JhbGFuY2UPdXNyX29wZW5fb3JkZXJzBXZhbHVlDXdpdGhkcmF3X2Jhc2UTd2l0aGRyYXdfYmFzZV9hc3NldA53aXRoZHJhd19xdW90ZRR3aXRoZHJhd19xdW90ZV9hc3NldAR6ZXJvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3ukAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwgBAAAAAAAAAAMIAgAAAAAAAAADCAMAAAAAAAAAAwgEAAAAAAAAAAMIBQAAAAAAAAADCAYAAAAAAAAAAwgHAAAAAAAAAAMICAAAAAAAAAADCAkAAAAAAAAAAwgKAAAAAAAAAAMICwAAAAAAAAADCAwAAAAAAAAAAwgNAAAAAAAAAAMIDwAAAAAAAAADCBAAAAAAAAAAAwgRAAAAAAAAAAMIEgAAAAAAAAADCBMAAAAAAAAAAwgUAAAAAAAAAAMIFQAAAAAAAAADCBYAAAAAAAAAAgEEAgEAAgEBAgECAgEDAwgAAAAAAAAAAAMIAAAAAAAAAIADCEBLTAAAAAAAAwigJSYAAAAAAAMIAOh2SBcAAAADCADKmjsAAAAAAAIHbQgQGwgLcggL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BggBBAAQBgMBBAAmCQoBBAAKCQsBBAAHBgwBBAAIBgwBBAAPAQ0BBgAOAQ0BBAASBgMBBAAtDg0BBAAWDwQBBAAMEBEAAQUaDQIHBAEGFhcCBwQBCRweAgcEAQ4TDQIHBAEPEw0CBwYBFRUEAgcEARsVAwIHBAEfABMCBwQBJRwdAgcEHRIdFBwUGxQCChcUCgoUChcSCAoHChYUEgoWEhMKBgoFCh4UAQoYEhgUHhIOChoSGhQZFBkSAQcIBAELAgEJAAEGCwIBCQABAwEBAgMDAgYLAgEJAAMDBwsCAQkAAwkAAgEDAgcLAgEJAAMBCQABBwkAAQYJAAAEBwsCAQkAAwMBAwYLAgEJAAMDAQQBAgIDCAEBCwMCCQAJAQIDCwABCQABBgsDAgkACQECBgsDAgkACQEJAAEGCQEEAQMDAxADAwMDAwIGCAEBAQgBAwMLAAEJAAMDAwMHCwMCCQAJAQkACQEQAwMDAwMDAwMDAQMDBggBAwMJAAIHCwMCCQAJAQkAAQkBAQcJAQILAwIDCAELAwIDCwABCQACBggBAwICAgtDcml0Yml0VHJlZQxJbnRlcm5hbE5vZGUETGVhZgVUYWJsZQlUeENvbnRleHQDYWRkBmJvcnJvdxRib3Jyb3dfbGVhZl9ieV9pbmRleBJib3Jyb3dfbGVhZl9ieV9rZXkKYm9ycm93X211dBhib3Jyb3dfbXV0X2xlYWZfYnlfaW5kZXgEY2xvYhNjb3VudF9sZWFkaW5nX3plcm9zB2NyaXRiaXQNZGVzdHJveV9lbXB0eQRkcm9wEGZpbmRfY2xvc2VzdF9rZXkJZmluZF9sZWFmHWdldF9jbG9zZXN0X2xlYWZfaW5kZXhfYnlfa2V5C2luc2VydF9sZWFmDmludGVybmFsX25vZGVzCGlzX2VtcHR5DWlzX2xlZnRfY2hpbGQDa2V5BmxlYXZlcwpsZWZ0X2NoaWxkDmxlZnRfbW9zdF9sZWFmBmxlbmd0aARtYXNrCG1heF9sZWFmCG1pbl9sZWFmA25ldxhuZXh0X2ludGVybmFsX25vZGVfaW5kZXgJbmV4dF9sZWFmD25leHRfbGVhZl9pbmRleAZwYXJlbnQNcHJldmlvdXNfbGVhZgZyZW1vdmUUcmVtb3ZlX2xlYWZfYnlfaW5kZXgLcmlnaHRfY2hpbGQPcmln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 diff --git a/sui/testing/sui_config/sui.keystore b/sui/testing/sui_config/sui.keystore new file mode 100644 index 000000000..47355b939 --- /dev/null +++ b/sui/testing/sui_config/sui.keystore @@ -0,0 +1,7 @@ +[ + "AB522qKKEsXMTFRD2SG3Het/02S/ZBOugmcH3R1CDG6l", + "AOmPq9B16F3W3ijO/4s9hI6v8LdiYCawKAW31PKpg4Qp", + "AOLhc0ryVWnD5LmqH3kCHruBpVV+68EWjEGu2eC9gndK", + "AKCo1FyhQ0zUpnoZLmGJJ+8LttTrt56W87Ho4vBF+R+8", + "AGA20wtGcwbcNAG4nwapbQ5wIuXwkYQEWFUoSVAxctHb" +] \ No newline at end of file diff --git a/sui/testing/sui_config/validator-config-0.yaml b/sui/testing/sui_config/validator-config-0.yaml new file mode 100644 index 000000000..27d370a59 --- /dev/null +++ b/sui/testing/sui_config/validator-config-0.yaml @@ -0,0 +1,81 @@ +--- +protocol-key-pair: + value: VTDx4HjVmRBqdqBWg2zN+zcFE20io3CrBchGy/iV1lo= +worker-key-pair: + value: ABlC9PMmIQHjxila3AEOXDxwCSuodcvJh2Q5O5HIB00K +account-key-pair: + value: AIV4Ng6OYQf6irjVCZly5X7dSpdFpwoWtdAx9u4PANRl +network-key-pair: + value: AOqJl2rHMnroe26vjkkIuWGBD/y6HzQG6MK5bC9njF0s +db-path: sui_config/authorities_db/99f25ef61f80 +network-address: /ip4/127.0.0.1/tcp/36459/http +json-rpc-address: "127.0.0.1:38133" +metrics-address: "127.0.0.1:44135" +admin-interface-port: 33917 +consensus-config: + address: /ip4/127.0.0.1/tcp/41459/http + db-path: sui_config/consensus_db/99f25ef61f80 + internal-worker-address: ~ + max-pending-transactions: ~ + narwhal-config: + header_num_of_batches_threshold: 32 + max_header_num_of_batches: 1000 + max_header_delay: 2000ms + min_header_delay: 500ms + gc_depth: 50 + sync_retry_delay: 5000ms + sync_retry_nodes: 3 + batch_size: 500000 + max_batch_delay: 100ms + block_synchronizer: + range_synchronize_timeout: 30000ms + certificates_synchronize_timeout: 30000ms + payload_synchronize_timeout: 30000ms + payload_availability_timeout: 30000ms + handler_certificate_deliver_timeout: 30000ms + consensus_api_grpc: + socket_addr: /ip4/127.0.0.1/tcp/44689/http + get_collections_timeout: 5000ms + remove_collections_timeout: 5000ms + max_concurrent_requests: 500000 + prometheus_metrics: + socket_addr: /ip4/127.0.0.1/tcp/33219/http + network_admin_server: + primary_network_admin_server_port: 33945 + worker_network_admin_server_base_port: 38081 + anemo: + send_certificate_rate_limit: ~ + get_payload_availability_rate_limit: ~ + get_certificates_rate_limit: ~ + report_batch_rate_limit: ~ + request_batch_rate_limit: ~ +enable-event-processing: false +enable-index-processing: true +grpc-load-shed: ~ +grpc-concurrency-limit: 20000000000 +p2p-config: + listen-address: "127.0.0.1:44061" + external-address: /ip4/127.0.0.1/udp/44061 +genesis: + genesis-file-location: sui_config/genesis.blob +authority-store-pruning-config: + num-latest-epoch-dbs-to-retain: 3 + epoch-db-pruning-period-secs: 3600 + num-epochs-to-retain: 2 + max-checkpoints-in-batch: 200 + max-transactions-in-batch: 1000 + use-range-deletion: true +end-of-epoch-broadcast-channel-capacity: 128 +checkpoint-executor-config: + checkpoint-execution-max-concurrency: 200 + local-execution-timeout-sec: 30 +db-checkpoint-config: + perform-db-checkpoints-at-epoch-end: false +indirect-objects-threshold: 18446744073709551615 +expensive-safety-check-config: + enable-epoch-sui-conservation-check: false + enable-deep-per-tx-sui-conservation-check: false + force-disable-epoch-sui-conservation-check: false + enable-state-consistency-check: false + force-disable-state-consistency-check: false + enable-move-vm-paranoid-checks: false diff --git a/sui/testing/sui_config/validator-config-1.yaml b/sui/testing/sui_config/validator-config-1.yaml new file mode 100644 index 000000000..31920d77c --- /dev/null +++ b/sui/testing/sui_config/validator-config-1.yaml @@ -0,0 +1,81 @@ +--- +protocol-key-pair: + value: avYcyVgYMXTyaUYh9IRwLK0gSzl7YF6ZQDAbrS1Bhvo= +worker-key-pair: + value: AGsxCVxeIZ6fscvGECzV93Hi4JkqM4zMYEA8wBGfXQrz +account-key-pair: + value: AF9cOMxTRAUTOws2M8W5slHf41HITA+M3nqXHT6nlH6S +network-key-pair: + value: ALH/8qz2YlwAuxY/hOvuXiglYq0e4LLU1/lyf5uKgPY8 +db-path: sui_config/authorities_db/8dcff6d15504 +network-address: /ip4/127.0.0.1/tcp/33355/http +json-rpc-address: "127.0.0.1:39573" +metrics-address: "127.0.0.1:45851" +admin-interface-port: 35739 +consensus-config: + address: /ip4/127.0.0.1/tcp/42959/http + db-path: sui_config/consensus_db/8dcff6d15504 + internal-worker-address: ~ + max-pending-transactions: ~ + narwhal-config: + header_num_of_batches_threshold: 32 + max_header_num_of_batches: 1000 + max_header_delay: 2000ms + min_header_delay: 500ms + gc_depth: 50 + sync_retry_delay: 5000ms + sync_retry_nodes: 3 + batch_size: 500000 + max_batch_delay: 100ms + block_synchronizer: + range_synchronize_timeout: 30000ms + certificates_synchronize_timeout: 30000ms + payload_synchronize_timeout: 30000ms + payload_availability_timeout: 30000ms + handler_certificate_deliver_timeout: 30000ms + consensus_api_grpc: + socket_addr: /ip4/127.0.0.1/tcp/37001/http + get_collections_timeout: 5000ms + remove_collections_timeout: 5000ms + max_concurrent_requests: 500000 + prometheus_metrics: + socket_addr: /ip4/127.0.0.1/tcp/39831/http + network_admin_server: + primary_network_admin_server_port: 39853 + worker_network_admin_server_base_port: 36429 + anemo: + send_certificate_rate_limit: ~ + get_payload_availability_rate_limit: ~ + get_certificates_rate_limit: ~ + report_batch_rate_limit: ~ + request_batch_rate_limit: ~ +enable-event-processing: false +enable-index-processing: true +grpc-load-shed: ~ +grpc-concurrency-limit: 20000000000 +p2p-config: + listen-address: "127.0.0.1:46335" + external-address: /ip4/127.0.0.1/udp/46335 +genesis: + genesis-file-location: sui_config/genesis.blob +authority-store-pruning-config: + num-latest-epoch-dbs-to-retain: 3 + epoch-db-pruning-period-secs: 3600 + num-epochs-to-retain: 2 + max-checkpoints-in-batch: 200 + max-transactions-in-batch: 1000 + use-range-deletion: true +end-of-epoch-broadcast-channel-capacity: 128 +checkpoint-executor-config: + checkpoint-execution-max-concurrency: 200 + local-execution-timeout-sec: 30 +db-checkpoint-config: + perform-db-checkpoints-at-epoch-end: false +indirect-objects-threshold: 18446744073709551615 +expensive-safety-check-config: + enable-epoch-sui-conservation-check: false + enable-deep-per-tx-sui-conservation-check: false + force-disable-epoch-sui-conservation-check: false + enable-state-consistency-check: false + force-disable-state-consistency-check: false + enable-move-vm-paranoid-checks: false diff --git a/sui/testing/sui_config/validator-config-2.yaml b/sui/testing/sui_config/validator-config-2.yaml new file mode 100644 index 000000000..caa5501a6 --- /dev/null +++ b/sui/testing/sui_config/validator-config-2.yaml @@ -0,0 +1,81 @@ +--- +protocol-key-pair: + value: OXnx3yM1C/ppgnDMx/o1d49fJs7E05kq11mXNae/O+I= +worker-key-pair: + value: AHXs8DP7EccyxtxAGq/m33LgvOApXs4JStH3PLAe9vGw +account-key-pair: + value: AC8vF9E3QYf0aTyBZWlSzJJXETvV5vYkOtEJl+DWQMlk +network-key-pair: + value: AOapcKU6mW5SopFM6eBSiXgbuPJTz11CiEqM+SJGIEOF +db-path: sui_config/authorities_db/addeef94d898 +network-address: /ip4/127.0.0.1/tcp/34633/http +json-rpc-address: "127.0.0.1:38025" +metrics-address: "127.0.0.1:43451" +admin-interface-port: 36793 +consensus-config: + address: /ip4/127.0.0.1/tcp/40307/http + db-path: sui_config/consensus_db/addeef94d898 + internal-worker-address: ~ + max-pending-transactions: ~ + narwhal-config: + header_num_of_batches_threshold: 32 + max_header_num_of_batches: 1000 + max_header_delay: 2000ms + min_header_delay: 500ms + gc_depth: 50 + sync_retry_delay: 5000ms + sync_retry_nodes: 3 + batch_size: 500000 + max_batch_delay: 100ms + block_synchronizer: + range_synchronize_timeout: 30000ms + certificates_synchronize_timeout: 30000ms + payload_synchronize_timeout: 30000ms + payload_availability_timeout: 30000ms + handler_certificate_deliver_timeout: 30000ms + consensus_api_grpc: + socket_addr: /ip4/127.0.0.1/tcp/37445/http + get_collections_timeout: 5000ms + remove_collections_timeout: 5000ms + max_concurrent_requests: 500000 + prometheus_metrics: + socket_addr: /ip4/127.0.0.1/tcp/43943/http + network_admin_server: + primary_network_admin_server_port: 39611 + worker_network_admin_server_base_port: 38377 + anemo: + send_certificate_rate_limit: ~ + get_payload_availability_rate_limit: ~ + get_certificates_rate_limit: ~ + report_batch_rate_limit: ~ + request_batch_rate_limit: ~ +enable-event-processing: false +enable-index-processing: true +grpc-load-shed: ~ +grpc-concurrency-limit: 20000000000 +p2p-config: + listen-address: "127.0.0.1:32965" + external-address: /ip4/127.0.0.1/udp/32965 +genesis: + genesis-file-location: sui_config/genesis.blob +authority-store-pruning-config: + num-latest-epoch-dbs-to-retain: 3 + epoch-db-pruning-period-secs: 3600 + num-epochs-to-retain: 2 + max-checkpoints-in-batch: 200 + max-transactions-in-batch: 1000 + use-range-deletion: true +end-of-epoch-broadcast-channel-capacity: 128 +checkpoint-executor-config: + checkpoint-execution-max-concurrency: 200 + local-execution-timeout-sec: 30 +db-checkpoint-config: + perform-db-checkpoints-at-epoch-end: false +indirect-objects-threshold: 18446744073709551615 +expensive-safety-check-config: + enable-epoch-sui-conservation-check: false + enable-deep-per-tx-sui-conservation-check: false + force-disable-epoch-sui-conservation-check: false + enable-state-consistency-check: false + force-disable-state-consistency-check: false + enable-move-vm-paranoid-checks: false diff --git a/sui/testing/sui_config/validator-config-3.yaml b/sui/testing/sui_config/validator-config-3.yaml new file mode 100644 index 000000000..0d44fa248 --- /dev/null +++ b/sui/testing/sui_config/validator-config-3.yaml @@ -0,0 +1,81 @@ +--- +protocol-key-pair: + value: CyNkjqNVr3HrHTH7f/NLs7u5lUHJzuPAw0PqMTD2y2s= +worker-key-pair: + value: AHd6qvbBv7bTCGGoD1TUR5dOGnwOnYvhHV9ryCUp7rmZ +account-key-pair: + value: ALSCvWwsVryGIwq+n4f9bIPCRqsooGodE/vDaVCSLfjE +network-key-pair: + value: APFCK1pRVxn9PDt+KzWx52+EY5nzaZZU2GF9RZoQY58Y +db-path: sui_config/authorities_db/b3fd5efb5c87 +network-address: /ip4/127.0.0.1/tcp/33953/http +json-rpc-address: "127.0.0.1:35625" +metrics-address: "127.0.0.1:37813" +admin-interface-port: 46405 +consensus-config: + address: /ip4/127.0.0.1/tcp/43213/http + db-path: sui_config/consensus_db/b3fd5efb5c87 + internal-worker-address: ~ + max-pending-transactions: ~ + narwhal-config: + header_num_of_batches_threshold: 32 + max_header_num_of_batches: 1000 + max_header_delay: 2000ms + min_header_delay: 500ms + gc_depth: 50 + sync_retry_delay: 5000ms + sync_retry_nodes: 3 + batch_size: 500000 + max_batch_delay: 100ms + block_synchronizer: + range_synchronize_timeout: 30000ms + certificates_synchronize_timeout: 30000ms + payload_synchronize_timeout: 30000ms + payload_availability_timeout: 30000ms + handler_certificate_deliver_timeout: 30000ms + consensus_api_grpc: + socket_addr: /ip4/127.0.0.1/tcp/46745/http + get_collections_timeout: 5000ms + remove_collections_timeout: 5000ms + max_concurrent_requests: 500000 + prometheus_metrics: + socket_addr: /ip4/127.0.0.1/tcp/38817/http + network_admin_server: + primary_network_admin_server_port: 34929 + worker_network_admin_server_base_port: 37447 + anemo: + send_certificate_rate_limit: ~ + get_payload_availability_rate_limit: ~ + get_certificates_rate_limit: ~ + report_batch_rate_limit: ~ + request_batch_rate_limit: ~ +enable-event-processing: false +enable-index-processing: true +grpc-load-shed: ~ +grpc-concurrency-limit: 20000000000 +p2p-config: + listen-address: "127.0.0.1:39889" + external-address: /ip4/127.0.0.1/udp/39889 +genesis: + genesis-file-location: sui_config/genesis.blob +authority-store-pruning-config: + num-latest-epoch-dbs-to-retain: 3 + epoch-db-pruning-period-secs: 3600 + num-epochs-to-retain: 2 + max-checkpoints-in-batch: 200 + max-transactions-in-batch: 1000 + use-range-deletion: true +end-of-epoch-broadcast-channel-capacity: 128 +checkpoint-executor-config: + checkpoint-execution-max-concurrency: 200 + local-execution-timeout-sec: 30 +db-checkpoint-config: + perform-db-checkpoints-at-epoch-end: false +indirect-objects-threshold: 18446744073709551615 +expensive-safety-check-config: + enable-epoch-sui-conservation-check: false + enable-deep-per-tx-sui-conservation-check: false + force-disable-epoch-sui-conservation-check: false + enable-state-consistency-check: false + force-disable-state-consistency-check: false + enable-move-vm-paranoid-checks: false diff --git a/sui/testing/tsconfig.json b/sui/testing/tsconfig.json new file mode 100644 index 000000000..d6a4db085 --- /dev/null +++ b/sui/testing/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "types": ["mocha", "chai"], + "typeRoots": ["./node_modules/@types"], + "lib": ["es2020"], + "module": "commonjs", + "target": "es2020", + "strict": true, + "resolveJsonModule": true, + "esModuleInterop": true + } + } diff --git a/sui/tests/go.mod b/sui/tests/go.mod deleted file mode 100644 index 2fdc13cde..000000000 --- a/sui/tests/go.mod +++ /dev/null @@ -1,13 +0,0 @@ -module watcher - -go 1.17 - -require ( - github.com/ethereum/go-ethereum v1.10.26 // indirect - github.com/tidwall/gjson v1.14.3 // indirect - github.com/tidwall/match v1.1.1 // indirect - github.com/tidwall/pretty v1.2.0 // indirect - golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect - golang.org/x/net v0.1.0 // indirect - golang.org/x/sys v0.1.0 // indirect -) diff --git a/sui/tests/go.sum b/sui/tests/go.sum deleted file mode 100644 index fe25f810d..000000000 --- a/sui/tests/go.sum +++ /dev/null @@ -1,14 +0,0 @@ -github.com/ethereum/go-ethereum v1.10.26 h1:i/7d9RBBwiXCEuyduBQzJw/mKmnvzsN14jqBmytw72s= -github.com/ethereum/go-ethereum v1.10.26/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg= -github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw= -github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= -github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= -github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/sui/tests/watcher.go b/sui/tests/watcher.go deleted file mode 100644 index 72ba8bf81..000000000 --- a/sui/tests/watcher.go +++ /dev/null @@ -1,147 +0,0 @@ -package main - -import ( - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "github.com/tidwall/gjson" - "golang.org/x/net/websocket" - "log" - "os" - "time" - - eth_common "github.com/ethereum/go-ethereum/common" -) - -type SuiResult struct { - Timestamp int64 `json:"timestamp"` - TxDigest string `json:"txDigest"` - Event struct { - MoveEvent struct { - PackageID string `json:"packageId"` - TransactionModule string `json:"transactionModule"` - Sender string `json:"sender"` - Type string `json:"type"` - Fields *struct { - ConsistencyLevel uint8 `json:"consistency_level"` - Nonce uint64 `json:"nonce"` - Payload string `json:"payload"` - Sender uint64 `json:"sender"` - Sequence uint64 `json:"sequence"` - } `json:"fields"` - Bcs string `json:"bcs"` - } `json:"moveEvent"` - } `json:"event"` -} - -type SuiEventMsg struct { - Jsonrpc string `json:"jsonrpc"` - Method *string `json:"method"` - ID *int `json:"id"` - result *int `json:"result"` - Params *struct { - Subscription int64 `json:"subscription"` - Result *SuiResult `json:"result"` - } `json:"params"` -} - -type SuiTxnQuery struct { - Jsonrpc string `json:"jsonrpc"` - Result struct { - Data []SuiResult `json:"data"` - NextCursor interface{} `json:"nextCursor"` - } `json:"result"` - ID int `json:"id"` -} - -func inspectBody(body gjson.Result) error { - txDigest := body.Get("txDigest") - timestamp := body.Get("timestamp") - packageId := body.Get("event.moveEvent.packageId") // defense in depth: check this - account := body.Get("event.moveEvent.sender") // defense in depth: check this - consistency_level := body.Get("event.moveEvent.fields.consistency_level") - nonce := body.Get("event.moveEvent.fields.nonce") - payload := body.Get("event.moveEvent.fields.payload") - sender := body.Get("event.moveEvent.fields.sender") - sequence := body.Get("event.moveEvent.fields.sequence") - - if !txDigest.Exists() || !timestamp.Exists() || !packageId.Exists() || !account.Exists() || !consistency_level.Exists() || !nonce.Exists() || !payload.Exists() || !sender.Exists() || !sequence.Exists() { - return errors.New("block parse error") - } - - id, err := base64.StdEncoding.DecodeString(txDigest.String()) - if err != nil { - fmt.Printf("txDigest decode error: %s\n", txDigest.String()) - return err - } - - var txHash = eth_common.BytesToHash(id) // 32 bytes = d3b136a6a182a40554b2fafbc8d12a7a22737c10c81e33b33d1dcb74c532708b - fmt.Printf("\ntxHash: %s\n", txHash) - - pl, err := base64.StdEncoding.DecodeString(payload.String()) - if err != nil { - fmt.Printf("payload decode error\n") - return err - } - fmt.Printf("\npl: %s\n", pl) - - return nil -} - -func main() { - origin := "http://localhost/" - url := "ws://localhost:9001" - ws, err := websocket.Dial(url, "", origin) - if err != nil { - log.Fatal(err) - } - - s := fmt.Sprintf(`{"jsonrpc":"2.0", "id": 1, "method": "sui_subscribeEvent", "params": [{"Package": "%s"}]}`, os.Getenv("WORM_PACKAGE")) - fmt.Printf("Sending: %s.\n", s) - - if _, err := ws.Write([]byte(s)); err != nil { - log.Fatal(err) - } - for { - var msg = make([]byte, 4096) - var n int - ws.SetReadDeadline(time.Now().Local().Add(1_000_000_000)) - if n, err = ws.Read(msg); err != nil { - fmt.Printf("err") - } else { - fmt.Printf("\nReceived: %s.\n", msg[:n]) - parsedMsg := gjson.ParseBytes(msg[:n]) - - var res SuiEventMsg - err = json.Unmarshal(msg[:n], &res) - if err != nil { - fmt.Printf("SuiEventMsg: %s", err.Error()) - } - - if res.Method != nil { - fmt.Printf("%s\n", *res.Method) - } else { - fmt.Printf("Method nil\n") - } - - if res.ID != nil { - fmt.Printf("%d\n", *res.ID) - } else { - fmt.Printf("ID nil\n") - } - - result := parsedMsg.Get("params.result") - if !result.Exists() { - // Other messages come through on the channel.. we can ignore them safely - continue - } - fmt.Printf("inspect body called\n") - - err := inspectBody(result) - if err != nil { - fmt.Printf("inspectBody: %s", err.Error()) - } - } - } -} diff --git a/sui/tests/ws.py b/sui/tests/ws.py deleted file mode 100644 index e1f994de1..000000000 --- a/sui/tests/ws.py +++ /dev/null @@ -1,94 +0,0 @@ -import websocket -import _thread -import time -import rel -import os -import pprint -import json -import base64 - -# https://github.com/MystenLabs/sui/pull/5113 - -# { -# "jsonrpc": "2.0", -# "method": "sui_subscribeEvent", -# "params": { -# "subscription": 1805876586195140, -# "result": { -# "timestamp": 1666704112752, -# "txDigest": "ckB13AaG+OHrO0Ha3I8IK3ERanYHmHAI0jSXnqk9R+I=", -# "event": { -# "moveEvent": { -# "packageId": "0xbd99019f3c8f9d08b5498fedcc97e1c24cddff88", -# "transactionModule": "wormhole", -# "sender": "0xdc2f7334400a353c6a9303235b578477202809c6", -# "type": "0xbd99019f3c8f9d08b5498fedcc97e1c24cddff88::state::WormholeMessage", -# "fields": { -# "consistency_level": 0, -# "nonce": 400, -# "payload": "Ag==", -# "sender": "0xdc2f7334400a353c6a9303235b578477202809c6", -# "sequence": 19, -# "timestamp": 0 -# }, -# "bcs": "3C9zNEAKNTxqkwMjW1eEdyAoCcYTAAAAAAAAAJABAAAAAAAAAQIAAAAAAAAAAAA=" -# } -# } -# } -# } -# } - -# curl -s -X POST -d '{"jsonrpc":"2.0", "id": 1, "method": "sui_getEventsByTransaction", "params": ["KgsiF8pCF61N02zX2oMFYLWQdrbxkOD1ypBxND752No=", 2]}' -H 'Content-Type: application/json' http://127.0.0.1:9000 | jq - -# { -# "jsonrpc": "2.0", -# "result": [ -# { -# "timestamp": 1666704112752, -# "txDigest": "ckB13AaG+OHrO0Ha3I8IK3ERanYHmHAI0jSXnqk9R+I=", -# "event": { -# "moveEvent": { -# "packageId": "0xbd99019f3c8f9d08b5498fedcc97e1c24cddff88", -# "transactionModule": "wormhole", -# "sender": "0xdc2f7334400a353c6a9303235b578477202809c6", -# "type": "0xbd99019f3c8f9d08b5498fedcc97e1c24cddff88::state::WormholeMessage", -# "bcs": "3C9zNEAKNTxqkwMjW1eEdyAoCcYTAAAAAAAAAJABAAAAAAAAAQIAAAAAAAAAAAA=" -# } -# } -# } -# ], -# "id": 1 -# } - -def on_message(ws, message): - v = json.loads(message) - print(json.dumps(v, indent=4)) - if "params" in v: - tx = v["params"]["result"]["txDigest"] - #tx = base64.standard_b64decode(tx) - print(tx + " -> " + base64.standard_b64decode(tx).hex()) - - pl = v["params"]["result"]["event"]["moveEvent"]["fields"]["payload"] - pl = base64.standard_b64decode(pl) - print(pl.hex()) - -def on_error(ws, error): - print(error) - -def on_close(ws, close_status_code, close_msg): - print("### closed ###") - -def on_open(ws): - print("Opened connection") - ws.send("{\"jsonrpc\":\"2.0\", \"id\": 1, \"method\": \"sui_subscribeEvent\", \"params\": [{\"Package\": \"" + os.getenv("WORM_PACKAGE") + "\"}]}") - -if __name__ == "__main__": - ws = websocket.WebSocketApp("ws://localhost:9001", - on_open=on_open, - on_message=on_message, - on_error=on_error, - on_close=on_close) - - ws.run_forever(dispatcher=rel) # Set dispatcher to automatic reconnection - rel.signal(2, rel.abort) # Keyboard Interrupt - rel.dispatch() diff --git a/sui/token_bridge/.gitignore b/sui/token_bridge/.gitignore new file mode 100644 index 000000000..378eac25d --- /dev/null +++ b/sui/token_bridge/.gitignore @@ -0,0 +1 @@ +build diff --git a/sui/token_bridge/Makefile b/sui/token_bridge/Makefile index 8b1641db8..b1c345e0f 100644 --- a/sui/token_bridge/Makefile +++ b/sui/token_bridge/Makefile @@ -1,14 +1,18 @@ -include ../../Makefile.help -.PHONY: artifacts -artifacts: build +VERSION = $(shell grep -Po "version = \"\K[^\"]*" Move.toml | sed "s/\./_/g") -.PHONY: build +.PHONY: clean +clean: + rm -rf build + +.PHONY: check ## Build contract -build: - sui move build +check: + sui move build -d .PHONY: test ## Run tests -test: - sui move test +test: check + grep "public(friend) fun current_version(): V__${VERSION} {" sources/version_control.move + sui move test -t 1 diff --git a/sui/token_bridge/Move.devnet.toml b/sui/token_bridge/Move.devnet.toml new file mode 100644 index 000000000..eb49cdb1f --- /dev/null +++ b/sui/token_bridge/Move.devnet.toml @@ -0,0 +1,14 @@ +[package] +name = "TokenBridge" +version = "0.2.0" + +[dependencies.Sui] +git = "https://github.com/MystenLabs/sui.git" +subdir = "crates/sui-framework/packages/sui-framework" +rev = "09b2081498366df936abae26eea4b2d5cafb2788" + +[dependencies.Wormhole] +local = "../wormhole" + +[addresses] +token_bridge = "_" diff --git a/sui/token_bridge/Move.lock b/sui/token_bridge/Move.lock new file mode 100644 index 000000000..d128783cb --- /dev/null +++ b/sui/token_bridge/Move.lock @@ -0,0 +1,32 @@ +# @generated by Move, please check-in and do not edit manually. + +[move] +version = 0 + +dependencies = [ + { name = "Sui" }, +] + +dev-dependencies = [ + { name = "Wormhole" }, +] + +[[move.package]] +name = "MoveStdlib" +source = { git = "https://github.com/MystenLabs/sui.git", rev = "09b2081498366df936abae26eea4b2d5cafb2788", subdir = "crates/sui-framework/packages/move-stdlib" } + +[[move.package]] +name = "Sui" +source = { git = "https://github.com/MystenLabs/sui.git", rev = "09b2081498366df936abae26eea4b2d5cafb2788", subdir = "crates/sui-framework/packages/sui-framework" } + +dependencies = [ + { name = "MoveStdlib" }, +] + +[[move.package]] +name = "Wormhole" +source = { local = "../wormhole" } + +dependencies = [ + { name = "Sui" }, +] diff --git a/sui/token_bridge/Move.testnet.toml b/sui/token_bridge/Move.testnet.toml new file mode 100644 index 000000000..e3c10215c --- /dev/null +++ b/sui/token_bridge/Move.testnet.toml @@ -0,0 +1,15 @@ +[package] +name = "TokenBridge" +version = "0.1.1" +published-at = "0x4eb7c5bca3759ab3064b46044edb5668c9066be8a543b28b58375f041f876a80" + +[dependencies.Sui] +git = "https://github.com/MystenLabs/sui.git" +subdir = "crates/sui-framework/packages/sui-framework" +rev = "09b2081498366df936abae26eea4b2d5cafb2788" + +[dependencies.Wormhole] +local = "../wormhole" + +[addresses] +token_bridge = "0x92d81f28c167d90f84638c654b412fe7fa8e55bdfac7f638bdcf70306289be86" diff --git a/sui/token_bridge/Move.toml b/sui/token_bridge/Move.toml index 2e6e52891..a65b44fa1 100644 --- a/sui/token_bridge/Move.toml +++ b/sui/token_bridge/Move.toml @@ -1,10 +1,21 @@ [package] name = "TokenBridge" -version = "0.0.1" +version = "0.2.0" -[dependencies] -Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework", rev = "2d709054a08d904b9229a2472af679f210af3827" } -Wormhole = { local = "../wormhole" } +[dependencies.Sui] +git = "https://github.com/MystenLabs/sui.git" +subdir = "crates/sui-framework/packages/sui-framework" +rev = "09b2081498366df936abae26eea4b2d5cafb2788" + +[dependencies.Wormhole] +local = "../wormhole" [addresses] -token_bridge = "0x0" +token_bridge = "_" + +[dev-dependencies.Wormhole] +local = "../wormhole" + +[dev-addresses] +wormhole = "0x100" +token_bridge = "0x200" diff --git a/sui/token_bridge/README.md b/sui/token_bridge/README.md deleted file mode 100644 index 0f2b073e0..000000000 --- a/sui/token_bridge/README.md +++ /dev/null @@ -1,60 +0,0 @@ -# Sui Wormhole Token Bridge Design - -TODO: make sure this is up to date - -The Token Bridge is responsible for storing treasury caps and locked tokens and exposing functions for initiating and completing transfers, which are gated behind VAAs. It also supports token attestations from foreign chains (which must be done once prior to transfer), contract upgrades, and chain registration. - -## Token Attestation - -TODO: up to date implementation notes - -The sui RPC provides a way to get the object id for CoinMetadata objects: -https://github.com/MystenLabs/sui/pull/6281/files#diff-80bf625d87d89549275351d95cfdfab4a6c2a1311804adbc5f1a7fcff225f049R430 - -we should document that this will only work for coins whose metadata object is -either shared or frozen. This seems to be the case at least for all example -coins, so we can probably expect most coins to follow this pattern. Ones that -don't, however, will not be transferrable through the token bridge - -## Creating new Coin Types - -TODO: up to date implementation notes - -Internally, `create_wrapped_coin` calls `coin::create_currency(witness, decimals, ctx)`, obtains a treasury cap, and finally stores -the treasury cap inside of a `TreasuryCapContainer` object, whose usage is restricted by the functions in its defining module (in particular, gated by VAAs). The `TreasuryCapContainer` is mutably shared so users can access it in a permissionless way. The reason that the treasury cap itself -is not mutably shared is that users would be able to use it to mint/burn tokens without limitation. - -## Initiating and Completing Transfers - -The Token Bridge stores both coins transferred by the user for lockup and treasury caps used for minting/burning wrapped assets. To this end, we implement two structs, which are both mutably shared and whose usage is restricted by VAA-gated functions defined in their parent modules. - -```rust -struct TreasuryCapContainer { - t: TreasuryCap, -} -``` - -```rust -struct CoinStore { - coins: coin, -} -``` - -Accordingly, we define the following functions for initiating and completing transfers. There is a version of each for wrapped and native coins, because we can't store info about `CoinType` within `BridgeState`. There does not seem to be a way of introspecting the CoinType to determine whether it represents a native or wrapped asset. In addition, we have to use either a `TreasuryCapStore` or `CoinStore` depending on whether we want to initiate or complete a transfer for a native or wrapped asset, which leads to different function signatures. - -### `complete_transfer_wrapped(treasury_cap_store: &mut TreasuryCapStore)` -- Use treasury cap to mint wrapped assets to recipient - -### `complete_transfer_native(store: &mut CoinStore)` -- Idea is to extract coins from token_bridge and give them to the recipient. We pass in a mutably shared `CoinStore` object, which contains balance or coin objects belonging to token bridge. Coins are extracted from this object and passed to the recipient. - -### `transfer_native(coin: Coin, store: &mut CoinStore)` -- Transfer user-supplied native coins to `CoinStore` -### `transfer_wrapped(treasury_cap_store: &mut TreasuryCapStore)` -- Use the treasury cap to burn some user-supplied wrapped assets - -## Contract Upgrades -Not yet supported in Sui. - -## Bridge State -TODO: up to date implementation notes diff --git a/sui/token_bridge/sources/attest_token.move b/sui/token_bridge/sources/attest_token.move index 0d37b4167..c5e67d167 100644 --- a/sui/token_bridge/sources/attest_token.move +++ b/sui/token_bridge/sources/attest_token.move @@ -1,146 +1,385 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements the method `attest_token` which allows someone +/// to send asset metadata of a coin type native to Sui. Part of this process +/// is registering this asset in the `TokenRegistry`. +/// +/// NOTE: If an asset has not been attested for, it cannot be bridged using +/// `transfer_tokens` or `transfer_tokens_with_payload`. +/// +/// See `asset_meta` module for serialization and deserialization of Wormhole +/// message payload. module token_bridge::attest_token { - use sui::sui::SUI; - use sui::coin::{Coin, CoinMetadata}; - use sui::tx_context::TxContext; + use sui::coin::{CoinMetadata}; + use wormhole::publish_message::{MessageTicket}; - use wormhole::state::{State as WormholeState}; + use token_bridge::asset_meta::{Self}; + use token_bridge::create_wrapped::{Self}; + use token_bridge::state::{Self, State, LatestOnly}; + use token_bridge::token_registry::{Self}; - use token_bridge::bridge_state::{Self as state, BridgeState}; - use token_bridge::asset_meta::{Self, AssetMeta}; + /// Coin type belongs to a wrapped asset. + const E_WRAPPED_ASSET: u64 = 0; + /// Coin type belongs to an untrusted contract from `create_wrapped` which + /// has not completed registration. + const E_FROM_CREATE_WRAPPED: u64 = 1; - public entry fun attest_token( - wormhole_state: &mut WormholeState, - bridge_state: &mut BridgeState, + /// `attest_token` takes `CoinMetadata` of a coin type and generates a + /// `MessageTicket` with encoded asset metadata for a foreign Token Bridge + /// contract to consume and create a wrapped asset reflecting this Sui + /// asset. Asset metadata is encoded using `AssetMeta`. + /// + /// See `token_registry` and `asset_meta` module for more info. + public fun attest_token( + token_bridge_state: &mut State, coin_meta: &CoinMetadata, - fee_coins: Coin, - ctx: &mut TxContext - ) { - let asset_meta = attest_token_internal( - wormhole_state, - bridge_state, - coin_meta, - ctx - ); - let payload = asset_meta::encode(asset_meta); - let nonce = 0; - state::publish_message( - wormhole_state, - bridge_state, + nonce: u32 + ): MessageTicket { + // This capability ensures that the current build version is used. + let latest_only = state::assert_latest_only(token_bridge_state); + + // Encode Wormhole message payload. + let encoded_asset_meta = + serialize_asset_meta(&latest_only, token_bridge_state, coin_meta); + + // Prepare Wormhole message. + state::prepare_wormhole_message( + &latest_only, + token_bridge_state, nonce, - payload, - fee_coins - ); + encoded_asset_meta + ) } - fun attest_token_internal( - wormhole_state: &mut WormholeState, - bridge_state: &mut BridgeState, + fun serialize_asset_meta( + latest_only: &LatestOnly, + token_bridge_state: &mut State, coin_meta: &CoinMetadata, - ctx: &mut TxContext - ): AssetMeta { - let asset_meta = - state::register_native_asset(wormhole_state, bridge_state, coin_meta, ctx); - return asset_meta + ): vector { + let registry = state::borrow_token_registry(token_bridge_state); + + // Register if it is a new asset. + // + // NOTE: We don't want to abort if the asset is already registered + // because we may want to send asset metadata again after registration + // (the owner of a particular `CoinType` can change `CoinMetadata` any + // time after we register the asset). + if (token_registry::has(registry)) { + let asset_info = token_registry::verified_asset(registry); + // If this asset is already registered, there should already + // be canonical info associated with this coin type. + assert!( + !token_registry::is_wrapped(&asset_info), + E_WRAPPED_ASSET + ); + } else { + // Before we consider registering, we should not accidentally + // perform this registration that may be the `CoinMetadata` from + // `create_wrapped::prepare_registration`, which has empty fields. + assert!( + !create_wrapped::incomplete_metadata(coin_meta), + E_FROM_CREATE_WRAPPED + ); + + // Now register it. + token_registry::add_new_native( + state::borrow_mut_token_registry( + latest_only, + token_bridge_state + ), + coin_meta + ); + }; + + asset_meta::serialize(asset_meta::from_metadata(coin_meta)) } #[test_only] - public fun test_attest_token_internal( - wormhole_state: &mut WormholeState, - bridge_state: &mut BridgeState, - coin_meta: &CoinMetadata, - ctx: &mut TxContext - ): AssetMeta { - attest_token_internal( - wormhole_state, - bridge_state, - coin_meta, - ctx - ) + public fun serialize_asset_meta_test_only( + token_bridge_state: &mut State, + coin_metadata: &CoinMetadata, + ): vector { + // This capability ensures that the current build version is used. + let latest_only = state::assert_latest_only(token_bridge_state); + + serialize_asset_meta(&latest_only, token_bridge_state, coin_metadata) } } #[test_only] -module token_bridge::attest_token_test{ - use sui::test_scenario::{Self, Scenario, next_tx, ctx, take_shared, return_shared}; - use sui::coin::{CoinMetadata}; +module token_bridge::attest_token_tests { + use std::ascii::{Self}; + use std::string::{Self}; + use sui::coin::{Self}; + use sui::test_scenario::{Self}; + use wormhole::publish_message::{Self}; + use wormhole::state::{chain_id}; - use wormhole::state::{State}; - - use token_bridge::string32::{Self}; - use token_bridge::bridge_state::{BridgeState}; - use token_bridge::attest_token::{test_attest_token_internal}; - use token_bridge::bridge_state_test::{set_up_wormhole_core_and_token_bridges}; - use token_bridge::native_coin_witness::{Self, NATIVE_COIN_WITNESS}; use token_bridge::asset_meta::{Self}; - - fun scenario(): Scenario { test_scenario::begin(@0x123233) } - fun people(): (address, address, address) { (@0x124323, @0xE05, @0xFACE) } + use token_bridge::attest_token::{Self}; + use token_bridge::coin_native_10::{Self, COIN_NATIVE_10}; + use token_bridge::coin_wrapped_7::{Self, COIN_WRAPPED_7}; + use token_bridge::native_asset::{Self}; + use token_bridge::state::{Self}; + use token_bridge::token_bridge_scenario::{ + person, + return_state, + set_up_wormhole_and_token_bridge, + take_state, + }; + use token_bridge::token_registry::{Self}; #[test] - fun test_attest_token(){ - let test = scenario(); - let (admin, _, _) = people(); + fun test_attest_token() { + use token_bridge::attest_token::{attest_token}; - test = set_up_wormhole_core_and_token_bridges(admin, test); + let user = person(); + let my_scenario = test_scenario::begin(user); + let scenario = &mut my_scenario; - next_tx(&mut test, admin); { - native_coin_witness::test_init(ctx(&mut test)); - }; - next_tx(&mut test, admin); { - let wormhole_state = take_shared(&test); - let bridge_state = take_shared(&test); - let coin_meta = take_shared>(&test); + // Publish coin. + coin_native_10::init_test_only(test_scenario::ctx(scenario)); - let asset_meta = test_attest_token_internal( - &mut wormhole_state, - &mut bridge_state, + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Ignore effects. + test_scenario::next_tx(scenario, user); + + let token_bridge_state = take_state(scenario); + let coin_meta = coin_native_10::take_metadata(scenario); + + // Emit `AssetMeta` payload. + let prepared_msg = + attest_token( + &mut token_bridge_state, &coin_meta, - ctx(&mut test) + 1234, // nonce ); - assert!(asset_meta::get_decimals(&asset_meta)==10, 0); - assert!(asset_meta::get_symbol(&asset_meta)==string32::from_bytes(x"00"), 0); - assert!(asset_meta::get_name(&asset_meta)==string32::from_bytes(x"11"), 0); + // Ignore effects. + test_scenario::next_tx(scenario, user); - return_shared(wormhole_state); - return_shared(bridge_state); - return_shared>(coin_meta); + // Check that asset is registered. + { + let registry = + state::borrow_token_registry(&token_bridge_state); + let verified = + token_registry::verified_asset(registry); + assert!(!token_registry::is_wrapped(&verified), 0); + + let asset = token_registry::borrow_native(registry); + + let expected_token_address = + native_asset::canonical_address(&coin_meta); + assert!( + native_asset::token_address(asset) == expected_token_address, + 0 + ); + assert!(native_asset::decimals(asset) == 10, 0); + + let ( + token_chain, + token_address + ) = native_asset::canonical_info(asset); + assert!(token_chain == chain_id(), 0); + assert!(token_address == expected_token_address, 0); + + assert!(native_asset::custody(asset) == 0, 0); }; - test_scenario::end(test); + + // Clean up for next call. + publish_message::destroy(prepared_msg); + + // Update metadata. + let new_symbol = { + use std::vector::{Self}; + + let symbol = coin::get_symbol(&coin_meta); + let buf = ascii::into_bytes(symbol); + vector::reverse(&mut buf); + + ascii::string(buf) + }; + + let new_name = coin::get_name(&coin_meta); + string::append(&mut new_name, string::utf8(b"??? and profit")); + + let treasury_cap = coin_native_10::take_treasury_cap(scenario); + coin::update_symbol(&treasury_cap, &mut coin_meta, new_symbol); + coin::update_name(&treasury_cap, &mut coin_meta, new_name); + + // We should be able to call `attest_token` any time after. + let prepared_msg = + attest_token( + &mut token_bridge_state, + &coin_meta, + 1234, // nonce + ); + + // Clean up. + publish_message::destroy(prepared_msg); + return_state(token_bridge_state); + coin_native_10::return_globals(treasury_cap, coin_meta); + + // Done. + test_scenario::end(my_scenario); } #[test] - #[expected_failure(abort_code = 0, location=0000000000000000000000000000000000000002::dynamic_field)] - fun test_attest_token_twice_fails(){ - let test = scenario(); - let (admin, _, _) = people(); + fun test_serialize_asset_meta() { + use token_bridge::attest_token::{serialize_asset_meta_test_only}; - test = set_up_wormhole_core_and_token_bridges(admin, test); + let user = person(); + let my_scenario = test_scenario::begin(user); + let scenario = &mut my_scenario; - next_tx(&mut test, admin); { - native_coin_witness::test_init(ctx(&mut test)); - }; - next_tx(&mut test, admin); { - let wormhole_state = take_shared(&test); - let bridge_state = take_shared(&test); - let coin_meta = take_shared>(&test); + // Publish coin. + coin_native_10::init_test_only(test_scenario::ctx(scenario)); - let _asset_meta_1 = test_attest_token_internal( - &mut wormhole_state, - &mut bridge_state, - &coin_meta, - ctx(&mut test) + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Proceed to next operation. + test_scenario::next_tx(scenario, user); + + let token_bridge_state = take_state(scenario); + let coin_meta = coin_native_10::take_metadata(scenario); + + // Emit `AssetMeta` payload. + let serialized = + serialize_asset_meta_test_only(&mut token_bridge_state, &coin_meta); + let expected_serialized = + asset_meta::serialize_test_only( + asset_meta::from_metadata_test_only(&coin_meta) ); - let _asset_meta_2 = test_attest_token_internal( - &mut wormhole_state, - &mut bridge_state, - &coin_meta, - ctx(&mut test) - ); - return_shared(wormhole_state); - return_shared(bridge_state); - return_shared>(coin_meta); + assert!(serialized == expected_serialized, 0); + + // Update metadata. + let new_symbol = { + use std::vector::{Self}; + + let symbol = coin::get_symbol(&coin_meta); + let buf = ascii::into_bytes(symbol); + vector::reverse(&mut buf); + + ascii::string(buf) }; - test_scenario::end(test); + + let new_name = coin::get_name(&coin_meta); + string::append(&mut new_name, string::utf8(b"??? and profit")); + + let treasury_cap = coin_native_10::take_treasury_cap(scenario); + coin::update_symbol(&treasury_cap, &mut coin_meta, new_symbol); + coin::update_name(&treasury_cap, &mut coin_meta, new_name); + + // Check that the new serialization reflects updated metadata. + let expected_serialized = + asset_meta::serialize_test_only( + asset_meta::from_metadata_test_only(&coin_meta) + ); + assert!(serialized != expected_serialized, 0); + let updated_serialized = + serialize_asset_meta_test_only(&mut token_bridge_state, &coin_meta); + assert!(updated_serialized == expected_serialized, 0); + + // Clean up. + return_state(token_bridge_state); + coin_native_10::return_globals(treasury_cap, coin_meta); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + #[expected_failure(abort_code = attest_token::E_FROM_CREATE_WRAPPED)] + fun test_cannot_attest_token_from_create_wrapped() { + use token_bridge::attest_token::{attest_token}; + + let user = person(); + let my_scenario = test_scenario::begin(user); + let scenario = &mut my_scenario; + + // Publish coin. + coin_wrapped_7::init_test_only(test_scenario::ctx(scenario)); + + // Ignore effects. + test_scenario::next_tx(scenario, user); + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Ignore effects. + test_scenario::next_tx(scenario, user); + + let token_bridge_state = take_state(scenario); + let coin_meta = test_scenario::take_shared(scenario); + + // You shall not pass! + let prepared_msg = + attest_token( + &mut token_bridge_state, + &coin_meta, + 1234 // nonce + ); + + // Clean up. + publish_message::destroy(prepared_msg); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = wormhole::package_utils::E_NOT_CURRENT_VERSION)] + fun test_cannot_attest_token_outdated_version() { + use token_bridge::attest_token::{attest_token}; + + let user = person(); + let my_scenario = test_scenario::begin(user); + let scenario = &mut my_scenario; + + // Publish coin. + coin_wrapped_7::init_test_only(test_scenario::ctx(scenario)); + + // Ignore effects. + test_scenario::next_tx(scenario, user); + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Ignore effects. + test_scenario::next_tx(scenario, user); + + let token_bridge_state = take_state(scenario); + let coin_meta = test_scenario::take_shared(scenario); + + // Conveniently roll version back. + state::reverse_migrate_version(&mut token_bridge_state); + + // Simulate executing with an outdated build by upticking the minimum + // required version for `publish_message` to something greater than + // this build. + state::migrate_version_test_only( + &mut token_bridge_state, + token_bridge::version_control::previous_version_test_only(), + token_bridge::version_control::next_version() + ); + + // You shall not pass! + let prepared_msg = + attest_token( + &mut token_bridge_state, + &coin_meta, + 1234 // nonce + ); + + // Clean up. + publish_message::destroy(prepared_msg); + + abort 42 } } diff --git a/sui/token_bridge/sources/bridge_state.move b/sui/token_bridge/sources/bridge_state.move deleted file mode 100644 index 2fdaefeb1..000000000 --- a/sui/token_bridge/sources/bridge_state.move +++ /dev/null @@ -1,510 +0,0 @@ -module token_bridge::bridge_state { - use std::option::{Self, Option}; - use std::ascii::{Self}; - - use sui::object::{Self, UID}; - use sui::vec_map::{Self, VecMap}; - use sui::tx_context::{TxContext}; - use sui::coin::{Self, Coin, TreasuryCap, CoinMetadata}; - use sui::transfer::{Self}; - use sui::tx_context::{Self}; - use sui::sui::SUI; - - use token_bridge::string32; - use token_bridge::dynamic_set; - use token_bridge::asset_meta::{Self, AssetMeta}; - - use wormhole::external_address::{Self, ExternalAddress}; - use wormhole::myu16::{U16}; - use wormhole::wormhole::{Self}; - use wormhole::state::{Self as wormhole_state, State as WormholeState}; - use wormhole::emitter::{EmitterCapability}; - use wormhole::set::{Self, Set}; - - const E_IS_NOT_WRAPPED_ASSET: u64 = 0; - const E_IS_NOT_REGISTERED_NATIVE_ASSET: u64 = 1; - const E_COIN_TYPE_HAS_NO_REGISTERED_INTEGER_ADDRESS: u64 = 2; - const E_COIN_TYPE_HAS_REGISTERED_INTEGER_ADDRESS: u64 = 3; - const E_ORIGIN_CHAIN_MISMATCH: u64 = 4; - const E_ORIGIN_ADDRESS_MISMATCH: u64 = 5; - const E_IS_WRAPPED_ASSET: u64 = 6; - - friend token_bridge::vaa; - friend token_bridge::register_chain; - friend token_bridge::wrapped; - friend token_bridge::complete_transfer; - friend token_bridge::transfer_tokens; - friend token_bridge::attest_token; - #[test_only] - friend token_bridge::bridge_state_test; - #[test_only] - friend token_bridge::complete_transfer_test; - #[test_only] - friend token_bridge::token_bridge_vaa_test; - - /// Capability for creating a bridge state object, granted to sender when this - /// module is deployed - struct DeployerCapability has key, store {id: UID} - - /// WrappedAssetInfo stores all the metadata about a wrapped asset - struct WrappedAssetInfo has key, store { - id: UID, - token_chain: U16, - token_address: ExternalAddress, - treasury_cap: TreasuryCap, - } - - struct NativeAssetInfo has key, store { - id: UID, - // Even though we can look up token_chain at any time from wormhole state, - // it can be more efficient to store it here locally so we don't have to do lookups. - custody: Coin, - asset_meta: AssetMeta, - } - - /// OriginInfo is a non-Sui object that stores info about a tokens native token - /// chain and address - struct OriginInfo has store, copy, drop { - token_chain: U16, - token_address: ExternalAddress, - } - - public fun get_token_chain_from_origin_info(origin_info: &OriginInfo): U16 { - return origin_info.token_chain - } - - public fun get_token_address_from_origin_info(origin_info: &OriginInfo): ExternalAddress { - return origin_info.token_address - } - - public fun get_origin_info_from_wrapped_asset_info(wrapped_asset_info: &WrappedAssetInfo): OriginInfo { - OriginInfo { token_chain: wrapped_asset_info.token_chain, token_address: wrapped_asset_info.token_address } - } - - public fun get_origin_info_from_native_asset_info(native_asset_info: &NativeAssetInfo): OriginInfo { - let asset_meta = &native_asset_info.asset_meta; - let token_chain = asset_meta::get_token_chain(asset_meta); - let token_address = asset_meta::get_token_address(asset_meta); - OriginInfo { token_chain, token_address } - } - - public(friend) fun create_wrapped_asset_info( - token_chain: U16, - token_address: ExternalAddress, - treasury_cap: TreasuryCap, - ctx: &mut TxContext - ): WrappedAssetInfo { - return WrappedAssetInfo { - id: object::new(ctx), - token_chain, - token_address, - treasury_cap - } - } - - // Integer label for coin types registered with Wormhole - - struct NativeIdRegistry has key, store { - id: UID, - index: u64, // next index to use - } - - fun next_native_id(registry: &mut NativeIdRegistry): ExternalAddress { - use wormhole::serialize::serialize_u64; - - let cur_index = registry.index; - registry.index = cur_index + 1; - let bytes = std::vector::empty(); - serialize_u64(&mut bytes, cur_index); - external_address::from_bytes(bytes) - } - - // Treasury caps, token stores, consumed VAAs, registered emitters, etc. - // are stored as dynamic fields of bridge state. - struct BridgeState has key, store { - id: UID, - - /// Set of consumed VAA hashes - consumed_vaas: Set>, - - /// Token bridge owned emitter capability - emitter_cap: EmitterCapability, - - /// Mapping of bridge contracts on other chains - registered_emitters: VecMap, - - native_id_registry: NativeIdRegistry, - } - - fun init(ctx: &mut TxContext) { - transfer::transfer(DeployerCapability{id: object::new(ctx)}, tx_context::sender(ctx)); - } - - #[test_only] - public fun test_init(ctx: &mut TxContext) { - transfer::transfer(DeployerCapability{id: object::new(ctx)}, tx_context::sender(ctx)); - } - - // converts owned state object into a shared object, so that anyone can get a reference to &mut State - // and pass it into various functions - public entry fun init_and_share_state( - deployer: DeployerCapability, - emitter_cap: EmitterCapability, - ctx: &mut TxContext - ) { - let DeployerCapability{ id } = deployer; - object::delete(id); - let state = BridgeState { - id: object::new(ctx), - consumed_vaas: set::new(ctx), - emitter_cap, - registered_emitters: vec_map::empty(), - native_id_registry: NativeIdRegistry { - id: object::new(ctx), - index: 1, - } - }; - - // permanently shares state - transfer::share_object(state); - } - - public(friend) fun deposit( - bridge_state: &mut BridgeState, - coin: Coin, - ) { - // TODO: create custom errors for each dynamic_set::borrow_mut - let native_asset = dynamic_set::borrow_mut>(&mut bridge_state.id); - coin::join(&mut native_asset.custody, coin); - } - - public(friend) fun withdraw( - _verified_coin_witness: VerifiedCoinType, - bridge_state: &mut BridgeState, - value: u64, - ctx: &mut TxContext - ): Coin { - let native_asset = dynamic_set::borrow_mut>(&mut bridge_state.id); - coin::split(&mut native_asset.custody, value, ctx) - } - - public(friend) fun mint( - _verified_coin_witness: VerifiedCoinType, - bridge_state: &mut BridgeState, - value: u64, - ctx: &mut TxContext, - ): Coin { - let wrapped_info = dynamic_set::borrow_mut>(&mut bridge_state.id); - coin::mint(&mut wrapped_info.treasury_cap, value, ctx) - } - - public(friend) fun burn( - bridge_state: &mut BridgeState, - coin: Coin, - ) { - let wrapped_info = dynamic_set::borrow_mut>(&mut bridge_state.id); - coin::burn(&mut wrapped_info.treasury_cap, coin); - } - - public(friend) fun publish_message( - wormhole_state: &mut WormholeState, - bridge_state: &mut BridgeState, - nonce: u64, - payload: vector, - message_fee: Coin, - ): u64 { - wormhole::publish_message( - &mut bridge_state.emitter_cap, - wormhole_state, - nonce, - payload, - message_fee, - ) - } - - /// getters - - public fun vaa_is_consumed(state: &BridgeState, hash: vector): bool { - set::contains(&state.consumed_vaas, hash) - } - - public fun get_registered_emitter(state: &BridgeState, chain_id: &U16): Option { - if (vec_map::contains(&state.registered_emitters, chain_id)) { - option::some(*vec_map::get(&state.registered_emitters, chain_id)) - } else { - option::none() - } - } - - public fun is_wrapped_asset(bridge_state: &BridgeState): bool { - dynamic_set::exists_>(&bridge_state.id) - } - - public fun is_registered_native_asset(bridge_state: &BridgeState): bool { - dynamic_set::exists_>(&bridge_state.id) - } - - /// Returns the origin information for a CoinType - public fun origin_info(bridge_state: &BridgeState): OriginInfo { - if (is_wrapped_asset(bridge_state)) { - get_wrapped_asset_origin_info(bridge_state) - } else { - get_registered_native_asset_origin_info(bridge_state) - } - } - - /// A value of type `VerifiedCoinType` witnesses the fact that the type - /// `T` has been verified to correspond to a particular chain id and token - /// address (may be either a wrapped or native asset). - /// The verification is performed by `verify_coin_type`. - /// - /// This is important because the coin type is an input to several - /// functions, and is thus untrusted. Most coin-related functionality - /// requires passing in a coin type generic argument. - /// When transferring tokens *out*, that type instantiation determines the - /// token bridge's behaviour, and thus we just take whatever was supplied. - /// When transferring tokens *in*, it's the transfer VAA that determines - /// which coin should be used via the origin chain and origin address - /// fields. - /// - /// For technical reasons, the latter case still requires a type argument to - /// be passed in (since Move does not support existential types, so we must - /// rely on old school universal quantification). We must thus verify that - /// the supplied type corresponds to the origin info in the VAA. - /// - /// Accordingly, the `mint` and `withdraw` operations are gated by this - /// witness type, since these two operations require a VAA to supply the - /// token information. This ensures that those two functions can't be called - /// without first verifying the `CoinType`. - struct VerifiedCoinType has copy, drop {} - - /// See the documentation for `VerifiedCoinType` above. - public fun verify_coin_type( - bridge_state: &BridgeState, - token_chain: U16, - token_address: ExternalAddress - ): VerifiedCoinType { - let coin_origin = origin_info(bridge_state); - assert!(coin_origin.token_chain == token_chain, E_ORIGIN_CHAIN_MISMATCH); - assert!(coin_origin.token_address == token_address, E_ORIGIN_ADDRESS_MISMATCH); - VerifiedCoinType {} - } - - public fun get_wrapped_asset_origin_info(bridge_state: &BridgeState): OriginInfo { - assert!(is_wrapped_asset(bridge_state), E_IS_NOT_WRAPPED_ASSET); - let wrapped_asset_info = dynamic_set::borrow>(&bridge_state.id); - get_origin_info_from_wrapped_asset_info(wrapped_asset_info) - } - - public fun get_registered_native_asset_origin_info(bridge_state: &BridgeState): OriginInfo { - let native_asset_info = dynamic_set::borrow>(&bridge_state.id); - get_origin_info_from_native_asset_info(native_asset_info) - } - - /// setters - - public(friend) fun set_registered_emitter(state: &mut BridgeState, chain_id: U16, emitter: ExternalAddress) { - if (vec_map::contains(&mut state.registered_emitters, &chain_id)){ - vec_map::remove(&mut state.registered_emitters, &chain_id); - }; - vec_map::insert(&mut state.registered_emitters, chain_id, emitter); - } - - /// dynamic ops - - public(friend) fun store_consumed_vaa(bridge_state: &mut BridgeState, vaa: vector) { - set::add(&mut bridge_state.consumed_vaas, vaa); - } - - public(friend) fun register_wrapped_asset(bridge_state: &mut BridgeState, wrapped_asset_info: WrappedAssetInfo) { - dynamic_set::add>(&mut bridge_state.id, wrapped_asset_info); - } - - public(friend) fun register_native_asset( - wormhole_state: &WormholeState, - bridge_state: &mut BridgeState, - coin_meta: &CoinMetadata, - ctx: &mut TxContext - ): AssetMeta { - assert!(!is_wrapped_asset(bridge_state), E_IS_WRAPPED_ASSET); // TODO - test - let asset_meta = asset_meta::create( - next_native_id(&mut bridge_state.native_id_registry), - wormhole_state::get_chain_id(wormhole_state), // TODO: should we just hardcode this? - coin::get_decimals(coin_meta), // decimals - string32::from_bytes(ascii::into_bytes(coin::get_symbol(coin_meta))), // symbol - string32::from_string(&coin::get_name(coin_meta)) // name - ); - let native_asset_info = NativeAssetInfo { - id: object::new(ctx), - custody: coin::zero(ctx), - asset_meta, - }; - dynamic_set::add>(&mut bridge_state.id, native_asset_info); - asset_meta - } - -} - -#[test_only] -module token_bridge::bridge_state_test{ - use sui::test_scenario::{Self, Scenario, next_tx, ctx, take_from_address, take_shared, return_shared}; - use sui::coin::{CoinMetadata}; - - use wormhole::state::{State}; - use wormhole::test_state::{init_wormhole_state}; - use wormhole::wormhole::{Self}; - use wormhole::external_address::{Self}; - - use token_bridge::bridge_state::{Self as state, BridgeState, DeployerCapability}; - use token_bridge::native_coin_witness::{Self, NATIVE_COIN_WITNESS}; - use token_bridge::native_coin_witness_v2::{Self, NATIVE_COIN_WITNESS_V2}; - - fun scenario(): Scenario { test_scenario::begin(@0x123233) } - fun people(): (address, address, address) { (@0x124323, @0xE05, @0xFACE) } - - #[test] - fun test_state_setters() { - test_state_setters_(scenario()) - } - - #[test] - fun test_coin_type_addressing(){ - test_coin_type_addressing_(scenario()) - } - - #[test] - #[expected_failure(abort_code = 0, location=0000000000000000000000000000000000000002::dynamic_field)] - fun test_coin_type_addressing_failure_case(){ - test_coin_type_addressing_failure_case_(scenario()) - } - - public fun set_up_wormhole_core_and_token_bridges(admin: address, test: Scenario): Scenario { - // init and share wormhole core bridge - test = init_wormhole_state(test, admin); - - // call init for token bridge to get deployer cap - next_tx(&mut test, admin); { - state::test_init(ctx(&mut test)); - }; - - // register for emitter cap and init_and_share token bridge - next_tx(&mut test, admin); { - let wormhole_state = take_shared(&test); - let my_emitter = wormhole::register_emitter(&mut wormhole_state, ctx(&mut test)); - let deployer = take_from_address(&test, admin); - state::init_and_share_state(deployer, my_emitter, ctx(&mut test)); - return_shared(wormhole_state); - }; - - next_tx(&mut test, admin); { - let bridge_state = take_shared(&test); - return_shared(bridge_state); - }; - - return test - } - - fun test_state_setters_(test: Scenario) { - let (admin, _, _) = people(); - - test = set_up_wormhole_core_and_token_bridges(admin, test); - - //test BridgeState setter and getter functions - next_tx(&mut test, admin); { - let state = take_shared(&test); - - // test store consumed vaa - state::store_consumed_vaa(&mut state, x"1234"); - assert!(state::vaa_is_consumed(&state, x"1234"), 0); - - // TODO - test store coin store - // TODO - test store treasury cap - - return_shared(state); - }; - test_scenario::end(test); - } - - fun test_coin_type_addressing_(test: Scenario) { - let (admin, _, _) = people(); - - test = set_up_wormhole_core_and_token_bridges(admin, test); - - //test coin type addressing - next_tx(&mut test, admin); { - native_coin_witness::test_init(ctx(&mut test)); - native_coin_witness_v2::test_init(ctx(&mut test)); - }; - next_tx(&mut test, admin); { - let wormhole_state = take_shared(&test); - let bridge_state = take_shared(&test); - let coin_meta = take_shared>(&test); - state::register_native_asset( - &mut wormhole_state, - &mut bridge_state, - &coin_meta, - ctx(&mut test) - ); - let origin_info = state::origin_info(&bridge_state); - let address = state::get_token_address_from_origin_info(&origin_info); - assert!(address == external_address::from_bytes(x"01"), 0); - - let coin_meta_v2 = take_shared>(&test); - state::register_native_asset( - &mut wormhole_state, - &mut bridge_state, - &coin_meta_v2, - ctx(&mut test) - ); - let origin_info = state::origin_info(&bridge_state); - let address = state::get_token_address_from_origin_info(&origin_info); - assert!(address == external_address::from_bytes(x"02"), 0); - - return_shared(wormhole_state); - return_shared(bridge_state); - return_shared>(coin_meta_v2); - return_shared>(coin_meta); - }; - test_scenario::end(test); - } - - - fun test_coin_type_addressing_failure_case_(test: Scenario) { - let (admin, _, _) = people(); - - test = set_up_wormhole_core_and_token_bridges(admin, test); - - //test coin type addressing - next_tx(&mut test, admin); { - native_coin_witness::test_init(ctx(&mut test)); - native_coin_witness_v2::test_init(ctx(&mut test)); - }; - next_tx(&mut test, admin); { - let wormhole_state = take_shared(&test); - let bridge_state = take_shared(&test); - let coin_meta = take_shared>(&test); - state::register_native_asset( - &mut wormhole_state, - &mut bridge_state, - &coin_meta, - ctx(&mut test) - ); - let origin_info = state::origin_info(&bridge_state); - let address = state::get_token_address_from_origin_info(&origin_info); - assert!(address == external_address::from_bytes(x"01"), 0); - - // aborts because trying to re-register native coin - state::register_native_asset( - &mut wormhole_state, - &mut bridge_state, - &coin_meta, - ctx(&mut test) - ); - - return_shared(wormhole_state); - return_shared(bridge_state); - return_shared>(coin_meta); - }; - test_scenario::end(test); - } -} diff --git a/sui/token_bridge/sources/complete_transfer.move b/sui/token_bridge/sources/complete_transfer.move index 1528356d7..2d6d92ace 100644 --- a/sui/token_bridge/sources/complete_transfer.move +++ b/sui/token_bridge/sources/complete_transfer.move @@ -1,769 +1,1227 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements two methods: `authorize_transfer` and +/// `redeem_relayer_payout`, which are to be executed in a transaction block in +/// this order. +/// +/// `authorize_transfer` allows a contract to complete a Token Bridge transfer, +/// sending assets to the encoded recipient. The coin payout incentive in +/// redeeming the transfer is packaged in a `RelayerReceipt`. +/// +/// `redeem_relayer_payout` unpacks the `RelayerReceipt` to release the coin +/// containing the relayer fee amount. +/// +/// The purpose of splitting this transfer redemption into two steps is in case +/// Token Bridge needs to be upgraded and there is a breaking change for this +/// module, an integrator would not be left broken. It is discouraged to put +/// `authorize_transfer` in an integrator's package logic. Otherwise, this +/// integrator needs to be prepared to upgrade his contract to handle the latest +/// version of `complete_transfer`. +/// +/// Instead, an integrator is encouraged to execute a transaction block, which +/// executes `authorize_transfer` using the latest Token Bridge package ID and +/// to implement `redeem_relayer_payout` in his contract to consume this receipt. +/// This is similar to how an integrator with Wormhole is not meant to use +/// `vaa::parse_and_verify` in his contract in case the `vaa` module needs to +/// be upgraded due to a breaking change. +/// +/// See `transfer` module for serialization and deserialization of Wormhole +/// message payload. module token_bridge::complete_transfer { - use sui::tx_context::{TxContext}; - use sui::transfer::{Self as transfer_object}; - use sui::coin::{Self, CoinMetadata}; + use sui::balance::{Self, Balance}; + use sui::coin::{Self, Coin}; + use sui::tx_context::{Self, TxContext}; + use wormhole::external_address::{Self, ExternalAddress}; - use wormhole::state::{State as WormholeState}; - use wormhole::external_address::{Self}; + use token_bridge::native_asset::{Self}; + use token_bridge::normalized_amount::{Self, NormalizedAmount}; + use token_bridge::state::{Self, State, LatestOnly}; + use token_bridge::token_registry::{Self, VerifiedAsset}; + use token_bridge::transfer::{Self}; + use token_bridge::vaa::{Self, TokenBridgeMessage}; + use token_bridge::wrapped_asset::{Self}; - use token_bridge::bridge_state::{Self, BridgeState, VerifiedCoinType}; - use token_bridge::vaa::{Self}; - use token_bridge::transfer::{Self, Transfer}; - use token_bridge::normalized_amount::{denormalize}; + // Requires `handle_complete_transfer`. + friend token_bridge::complete_transfer_with_payload; - const E_INVALID_TARGET: u64 = 0; + /// Transfer not intended to be received on Sui. + const E_TARGET_NOT_SUI: u64 = 0; + /// Input token info does not match registered info. + const E_CANONICAL_TOKEN_INFO_MISMATCH: u64 = 1; - public entry fun submit_vaa( - wormhole_state: &mut WormholeState, - bridge_state: &mut BridgeState, - coin_meta: &CoinMetadata, - vaa: vector, - fee_recipient: address, - ctx: &mut TxContext - ) { - - let vaa = vaa::parse_verify_and_replay_protect( - wormhole_state, - bridge_state, - vaa, - ctx - ); - - let transfer = transfer::parse(wormhole::myvaa::destroy(vaa)); - - let token_chain = transfer::get_token_chain(&transfer); - let token_address = transfer::get_token_address(&transfer); - let verified_coin_witness = bridge_state::verify_coin_type( - bridge_state, - token_chain, - token_address - ); - - complete_transfer( - verified_coin_witness, - &transfer, - wormhole_state, - bridge_state, - coin_meta, - fee_recipient, - ctx - ); + /// Event reflecting when a transfer via `complete_transfer` or + /// `complete_transfer_with_payload` is successfully executed. + struct TransferRedeemed has drop, copy { + emitter_chain: u16, + emitter_address: ExternalAddress, + sequence: u64 } - // complete transfer with arbitrary Transfer request and without the VAA - // for native tokens - #[test_only] - public fun test_complete_transfer( - transfer: &Transfer, - wormhole_state: &mut WormholeState, - bridge_state: &mut BridgeState, - coin_meta: &CoinMetadata, - fee_recipient: address, - ctx: &mut TxContext - ) { - let token_chain = transfer::get_token_chain(transfer); - let token_address = transfer::get_token_address(transfer); - let verified_coin_witness = bridge_state::verify_coin_type( - bridge_state, - token_chain, - token_address - ); - complete_transfer( - verified_coin_witness, - transfer, - wormhole_state, - bridge_state, - coin_meta, - fee_recipient, - ctx - ); + /// This type is only generated from `authorize_transfer` and can only be + /// redeemed using `redeem_relayer_payout`. Integrators running relayer + /// contracts are expected to implement `redeem_relayer_payout` within their + /// contracts and call `authorize_transfer` in a transaction block preceding + /// the method that consumes this receipt. + struct RelayerReceipt { + /// Coin of relayer fee payout. + payout: Coin } - fun complete_transfer( - verified_coin_witness: VerifiedCoinType, - transfer: &Transfer, - wormhole_state: &mut WormholeState, - bridge_state: &mut BridgeState, - coin_meta: &CoinMetadata, - fee_recipient: address, + /// `authorize_transfer` deserializes a token transfer VAA payload. Once the + /// transfer is authorized, an event (`TransferRedeemed`) is emitted to + /// reflect which Token Bridge this transfer originated from. The + /// `RelayerReceipt` returned wraps a `Coin` object containing a payout that + /// incentivizes someone to execute a transaction on behalf of the encoded + /// recipient. + /// + /// NOTE: This method is guarded by a minimum build version check. This + /// method could break backward compatibility on an upgrade. + /// + /// It is important for integrators to refrain from calling this method + /// within their contracts. This method is meant to be called in a + /// transaction block, passing the `RelayerReceipt` to a method which calls + /// `redeem_relayer_payout` within a contract. If in a circumstance where + /// this module has a breaking change in an upgrade, `redeem_relayer_payout` + /// will not be affected by this change. + /// + /// See `redeem_relayer_payout` for more details. + public fun authorize_transfer( + token_bridge_state: &mut State, + msg: TokenBridgeMessage, ctx: &mut TxContext + ): RelayerReceipt { + // This capability ensures that the current build version is used. + let latest_only = state::assert_latest_only(token_bridge_state); + + // Emitting the transfer being redeemed (and disregard return value). + emit_transfer_redeemed(&msg); + + // Deserialize transfer message and process. + handle_complete_transfer( + &latest_only, + token_bridge_state, + vaa::take_payload(msg), + ctx + ) + } + + /// After a transfer is authorized, a relayer contract may unpack the + /// `RelayerReceipt` using this method. Coin representing the relaying + /// incentive from this receipt is returned. This method is meant to be + /// simple. It allows for a coordination with calling `authorize_upgrade` + /// before a method that implements `redeem_relayer_payout` in a transaction + /// block to consume this receipt. + /// + /// NOTE: Integrators of Token Bridge collecting relayer fee payouts from + /// these token transfers should be calling only this method from their + /// contracts. This method is not guarded by version control (thus not + /// requiring a reference to the Token Bridge `State` object), so it is + /// intended to work for any package version. + public fun redeem_relayer_payout( + receipt: RelayerReceipt + ): Coin { + let RelayerReceipt { payout } = receipt; + + payout + } + + /// This is a privileged method only used by `complete_transfer` and + /// `complete_transfer_with_payload` modules. This method validates the + /// encoded token info with the passed in coin type via the `TokenRegistry`. + /// The transfer amount is denormalized and either mints balance of + /// wrapped asset or withdraws balance from native asset custody. + /// + /// Depending on whether this coin is a Token Bridge wrapped asset or a + /// natively existing asset on Sui, the coin is either minted or withdrawn + /// from Token Bridge's custody. + public(friend) fun verify_and_bridge_out( + latest_only: &LatestOnly, + token_bridge_state: &mut State, + token_chain: u16, + token_address: ExternalAddress, + target_chain: u16, + amount: NormalizedAmount + ): ( + VerifiedAsset, + Balance ) { - let to_chain = transfer::get_to_chain(transfer); - let this_chain = wormhole::state::get_chain_id(wormhole_state); - assert!(to_chain == this_chain, E_INVALID_TARGET); + // Verify that the intended chain ID for this transfer is for Sui. + assert!( + target_chain == wormhole::state::chain_id(), + E_TARGET_NOT_SUI + ); - let recipient = external_address::to_address(&transfer::get_to(transfer)); + let asset_info = state::verified_asset(token_bridge_state); + assert!( + ( + token_chain == token_registry::token_chain(&asset_info) && + token_address == token_registry::token_address(&asset_info) + ), + E_CANONICAL_TOKEN_INFO_MISMATCH + ); - let decimals = coin::get_decimals(coin_meta); - let amount = denormalize(transfer::get_amount(transfer), decimals); - let fee_amount = denormalize(transfer::get_fee(transfer), decimals); - - let recipient_coins; - if (bridge_state::is_wrapped_asset(bridge_state)) { - recipient_coins = bridge_state::mint( - verified_coin_witness, - bridge_state, + // De-normalize amount in preparation to take `Balance`. + let raw_amount = + normalized_amount::to_raw( amount, - ctx - ); - } else { - recipient_coins = bridge_state::withdraw( - verified_coin_witness, - bridge_state, - amount, - ctx + token_registry::coin_decimals(&asset_info) ); + + // If the token is wrapped by Token Bridge, we will mint these tokens. + // Otherwise, we will withdraw from custody. + let bridged_out = { + let registry = + state::borrow_mut_token_registry( + latest_only, + token_bridge_state + ); + if (token_registry::is_wrapped(&asset_info)) { + wrapped_asset::mint( + token_registry::borrow_mut_wrapped(registry), + raw_amount + ) + } else { + native_asset::withdraw( + token_registry::borrow_mut_native(registry), + raw_amount + ) + } }; - // take out fee from the recipient's coins. `extract` will revert - // if fee > amount - let fee_coins = coin::split(&mut recipient_coins, fee_amount, ctx); - transfer_object::transfer(recipient_coins, recipient); - transfer_object::transfer(fee_coins, fee_recipient); + + (asset_info, bridged_out) + } + + /// This method emits source information of the token transfer. Off-chain + /// processes may want to observe when transfers have been redeemed. + public(friend) fun emit_transfer_redeemed(msg: &TokenBridgeMessage): u16 { + let emitter_chain = vaa::emitter_chain(msg); + + // Emit Sui event with `TransferRedeemed`. + sui::event::emit( + TransferRedeemed { + emitter_chain, + emitter_address: vaa::emitter_address(msg), + sequence: vaa::sequence(msg) + } + ); + + emitter_chain + } + + fun handle_complete_transfer( + latest_only: &LatestOnly, + token_bridge_state: &mut State, + transfer_vaa_payload: vector, + ctx: &mut TxContext + ): RelayerReceipt { + let ( + amount, + token_address, + token_chain, + recipient, + recipient_chain, + relayer_fee + ) = transfer::unpack(transfer::deserialize(transfer_vaa_payload)); + + let ( + asset_info, + bridged_out + ) = + verify_and_bridge_out( + latest_only, + token_bridge_state, + token_chain, + token_address, + recipient_chain, + amount + ); + + let recipient = external_address::to_address(recipient); + + // If the recipient did not redeem his own transfer, Token Bridge will + // split the withdrawn coins and send a portion to the transaction + // relayer. + let payout = if ( + normalized_amount::value(&relayer_fee) == 0 || + recipient == tx_context::sender(ctx) + ) { + balance::zero() + } else { + let payout_amount = + normalized_amount::to_raw( + relayer_fee, + token_registry::coin_decimals(&asset_info) + ); + balance::split(&mut bridged_out, payout_amount) + }; + + // Transfer tokens to the recipient. + sui::transfer::public_transfer( + coin::from_balance(bridged_out, ctx), + recipient + ); + + // Finally produce the receipt that a relayer can consume via + // `redeem_relayer_payout`. + RelayerReceipt { + payout: coin::from_balance(payout, ctx) + } + } + + #[test_only] + public fun burn(receipt: RelayerReceipt) { + coin::burn_for_testing(redeem_relayer_payout(receipt)); } } #[test_only] -module token_bridge::complete_transfer_test { - use std::bcs::{Self}; +module token_bridge::complete_transfer_tests { + use sui::coin::{Self, Coin}; + use sui::test_scenario::{Self}; + use wormhole::state::{chain_id}; + use wormhole::wormhole_scenario::{parse_and_verify_vaa}; - use sui::test_scenario::{Self, Scenario, next_tx, return_shared, take_shared, ctx, take_from_address, return_to_address}; - use sui::coin::{Self, Coin, CoinMetadata}; - - use wormhole::myu16::{Self as u16}; - use wormhole::external_address::{Self}; - - use token_bridge::normalized_amount::{Self}; - use token_bridge::transfer::{Self, Transfer}; - use token_bridge::bridge_state::{Self, BridgeState}; - use token_bridge::coin_witness::{Self, COIN_WITNESS}; - use token_bridge::coin_witness_test::{test_register_wrapped_}; + use token_bridge::coin_wrapped_12::{Self, COIN_WRAPPED_12}; + use token_bridge::coin_wrapped_7::{Self, COIN_WRAPPED_7}; + use token_bridge::coin_native_10::{Self, COIN_NATIVE_10}; + use token_bridge::coin_native_4::{Self, COIN_NATIVE_4}; use token_bridge::complete_transfer::{Self}; - use token_bridge::native_coin_witness::{Self, NATIVE_COIN_WITNESS}; - use token_bridge::native_coin_witness_v2::{Self, NATIVE_COIN_WITNESS_V2}; - use token_bridge::bridge_state_test::{set_up_wormhole_core_and_token_bridges}; - - use wormhole::state::{Self as wormhole_state, State}; - - fun scenario(): Scenario { test_scenario::begin(@0x123233) } - fun people(): (address, address, address) { (@0x124323, @0xE05, @0xFACE) } + use token_bridge::dummy_message::{Self}; + use token_bridge::native_asset::{Self}; + use token_bridge::state::{Self}; + use token_bridge::token_bridge_scenario::{ + set_up_wormhole_and_token_bridge, + register_dummy_emitter, + return_state, + take_state, + three_people, + two_people + }; + use token_bridge::token_registry::{Self}; + use token_bridge::transfer::{Self}; + use token_bridge::vaa::{Self}; + use token_bridge::wrapped_asset::{Self}; struct OTHER_COIN_WITNESS has drop {} #[test] - fun test_complete_native_transfer(){ - let (admin, fee_recipient_person, _) = people(); - let test = scenario(); - test = set_up_wormhole_core_and_token_bridges(admin, test); - next_tx(&mut test, admin);{ - native_coin_witness::test_init(ctx(&mut test)); + /// An end-to-end test for complete transfer native with VAA. + fun test_complete_transfer_native_10_relayer_fee() { + use token_bridge::complete_transfer::{ + authorize_transfer, + redeem_relayer_payout }; - // register native asset type with the token bridge - next_tx(&mut test, admin);{ - let bridge_state = take_shared(&test); - let worm_state = take_shared(&test); - let coin_meta = take_shared>(&test); - bridge_state::register_native_asset( - &mut worm_state, - &mut bridge_state, - &coin_meta, - ctx(&mut test) - ); - return_shared(bridge_state); - return_shared(worm_state); - return_shared>(coin_meta); - }; - // create a treasury cap for the native asset type, mint some tokens, - // and deposit the native tokens into the token bridge - next_tx(&mut test, admin); { - let bridge_state = take_shared(&test); - let worm_state = take_shared(&test); - let t_cap = take_shared>(&test); - let coins = coin::mint(&mut t_cap, 10000000000, ctx(&mut test)); - bridge_state::deposit(&mut bridge_state, coins); - return_shared>(t_cap); - return_shared(bridge_state); - return_shared(worm_state); - }; - // complete transfer, sending native tokens to a recipient address - next_tx(&mut test, admin); { - let bridge_state = take_shared(&test); - let worm_state = take_shared(&test); - let coin_meta = take_shared>(&test); - let to = admin; - let amount = 1000000000; - let fee_amount = 100000000; - let decimals = 10; - let token_address = external_address::from_bytes(x"01"); - let token_chain = wormhole_state::get_chain_id(&worm_state); - let to_chain = wormhole_state::get_chain_id(&worm_state); + let transfer_vaa = + dummy_message::encoded_transfer_vaa_native_with_fee(); - let transfer: Transfer = transfer::create( - normalized_amount::normalize(amount, decimals), - token_address, - token_chain, - external_address::from_bytes(bcs::to_bytes(&to)), - to_chain, - normalized_amount::normalize(fee_amount, decimals), + let (expected_recipient, tx_relayer, coin_deployer) = three_people(); + let my_scenario = test_scenario::begin(tx_relayer); + let scenario = &mut my_scenario; + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register foreign emitter on chain ID == 2. + let expected_source_chain = 2; + register_dummy_emitter(scenario, expected_source_chain); + + let custody_amount = 500000; + coin_native_10::init_register_and_deposit( + scenario, + coin_deployer, + custody_amount + ); + + // Ignore effects. + test_scenario::next_tx(scenario, tx_relayer); + + let token_bridge_state = take_state(scenario); + + // These will be checked later. + let expected_relayer_fee = 100000; + let expected_recipient_amount = 200000; + let expected_amount = expected_relayer_fee + expected_recipient_amount; + + // Scope to allow immutable reference to `TokenRegistry`. + { + let registry = state::borrow_token_registry(&token_bridge_state); + let asset = token_registry::borrow_native(registry); + assert!(native_asset::custody(asset) == custody_amount, 0); + + // Verify transfer parameters. + let parsed = + transfer::deserialize_test_only( + wormhole::vaa::take_payload( + parse_and_verify_vaa(scenario, transfer_vaa) + ) + ); + + let asset_info = + token_registry::verified_asset(registry); + let expected_token_chain = token_registry::token_chain(&asset_info); + let expected_token_address = + token_registry::token_address(&asset_info); + assert!(transfer::token_chain(&parsed) == expected_token_chain, 0); + assert!( + transfer::token_address(&parsed) == expected_token_address, + 0 ); - complete_transfer::test_complete_transfer( - &transfer, - &mut worm_state, - &mut bridge_state, - &coin_meta, - fee_recipient_person, - ctx(&mut test) + let coin_meta = test_scenario::take_shared(scenario); + + let decimals = coin::get_decimals(&coin_meta); + + test_scenario::return_shared(coin_meta); + + assert!( + transfer::raw_amount(&parsed, decimals) == expected_amount, + 0 ); - return_shared(bridge_state); - return_shared(worm_state); - return_shared>(coin_meta); + + assert!( + transfer::raw_relayer_fee(&parsed, decimals) == expected_relayer_fee, + 0 + ); + assert!( + transfer::recipient_as_address(&parsed) == expected_recipient, + 0 + ); + assert!(transfer::recipient_chain(&parsed) == chain_id(), 0); + + // Clean up. + transfer::destroy(parsed); }; - // check balances after - next_tx(&mut test, admin);{ - let coins = take_from_address>(&test, admin); - assert!(coin::value(&coins) == 900000000, 0); - return_to_address>(admin, coins); + let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa); + let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa); - let fee_coins = take_from_address>(&test, fee_recipient_person); - assert!(coin::value(&fee_coins) == 100000000, 0); - return_to_address>(fee_recipient_person, fee_coins); + // Ignore effects. + test_scenario::next_tx(scenario, tx_relayer); + + let receipt = + authorize_transfer( + &mut token_bridge_state, + msg, + test_scenario::ctx(scenario) + ); + let payout = redeem_relayer_payout(receipt); + assert!(coin::value(&payout) == expected_relayer_fee, 0); + + // TODO: Check for one event? `TransferRedeemed`. + let _effects = test_scenario::next_tx(scenario, tx_relayer); + + // Check recipient's `Coin`. + let received = + test_scenario::take_from_address>( + scenario, + expected_recipient + ); + assert!(coin::value(&received) == expected_recipient_amount, 0); + + // And check remaining amount in custody. + let registry = state::borrow_token_registry(&token_bridge_state); + let remaining = custody_amount - expected_amount; + { + let asset = token_registry::borrow_native(registry); + assert!(native_asset::custody(asset) == remaining, 0); }; - test_scenario::end(test); + + // Clean up. + coin::burn_for_testing(payout); + coin::burn_for_testing(received); + return_state(token_bridge_state); + + // Done. + test_scenario::end(my_scenario); } #[test] - fun test_complete_native_transfer_10_decimals(){ - let (admin, fee_recipient_person, _) = people(); - let test = scenario(); - test = set_up_wormhole_core_and_token_bridges(admin, test); - next_tx(&mut test, admin);{ - native_coin_witness::test_init(ctx(&mut test)); + /// An end-to-end test for complete transfer native with VAA. + fun test_complete_transfer_native_4_relayer_fee() { + use token_bridge::complete_transfer::{ + authorize_transfer, + redeem_relayer_payout }; - // register native asset type with the token bridge - next_tx(&mut test, admin);{ - let coin_meta = take_shared>(&test); - let bridge_state = take_shared(&test); - let worm_state = take_shared(&test); - bridge_state::register_native_asset( - &mut worm_state, - &mut bridge_state, - &coin_meta, - ctx(&mut test) - ); - native_coin_witness::test_init(ctx(&mut test)); - return_shared>(coin_meta); - return_shared(bridge_state); - return_shared(worm_state); - }; - // create a treasury cap for the native asset type, mint some tokens, - // and deposit the native tokens into the token bridge - next_tx(&mut test, admin); { - let bridge_state = take_shared(&test); - let worm_state = take_shared(&test); - let t_cap = take_shared>(&test); - let coins = coin::mint(&mut t_cap, 10000000000, ctx(&mut test)); - bridge_state::deposit(&mut bridge_state, coins); - return_shared>(t_cap); - return_shared(bridge_state); - return_shared(worm_state); - }; - // complete transfer, sending native tokens to a recipient address - next_tx(&mut test, admin); { - let bridge_state = take_shared(&test); - let worm_state = take_shared(&test); - let coin_meta = take_shared>(&test); - let to = admin; - // dust at the end gets rounded to nothing, since 10-8=2 digits are lopped off - let amount = 1000000079; - let fee_amount = 100000000; - let decimals = 10; - let token_address = external_address::from_bytes(x"01"); - let token_chain = wormhole_state::get_chain_id(&worm_state); - let to_chain = wormhole_state::get_chain_id(&worm_state); + let transfer_vaa = + dummy_message::encoded_transfer_vaa_native_with_fee(); - let transfer: Transfer = transfer::create( - normalized_amount::normalize(amount, decimals), - token_address, - token_chain, - external_address::from_bytes(bcs::to_bytes(&to)), - to_chain, - normalized_amount::normalize(fee_amount, decimals), + let (expected_recipient, tx_relayer, coin_deployer) = three_people(); + let my_scenario = test_scenario::begin(tx_relayer); + let scenario = &mut my_scenario; + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register foreign emitter on chain ID == 2. + let expected_source_chain = 2; + register_dummy_emitter(scenario, expected_source_chain); + + let custody_amount = 5000; + coin_native_4::init_register_and_deposit( + scenario, + coin_deployer, + custody_amount + ); + + // Ignore effects. + test_scenario::next_tx(scenario, tx_relayer); + + let token_bridge_state = take_state(scenario); + + // These will be checked later. + let expected_relayer_fee = 1000; + let expected_recipient_amount = 2000; + let expected_amount = expected_relayer_fee + expected_recipient_amount; + + // Scope to allow immutable reference to `TokenRegistry`. + { + let registry = state::borrow_token_registry(&token_bridge_state); + let asset = token_registry::borrow_native(registry); + assert!(native_asset::custody(asset) == custody_amount, 0); + + // Verify transfer parameters. + let parsed = + transfer::deserialize_test_only( + wormhole::vaa::take_payload( + parse_and_verify_vaa(scenario, transfer_vaa) + ) + ); + + let asset_info = + token_registry::verified_asset(registry); + let expected_token_chain = token_registry::token_chain(&asset_info); + let expected_token_address = + token_registry::token_address(&asset_info); + assert!(transfer::token_chain(&parsed) == expected_token_chain, 0); + assert!( + transfer::token_address(&parsed) == expected_token_address, + 0 ); - complete_transfer::test_complete_transfer( - &transfer, - &mut worm_state, - &mut bridge_state, - &coin_meta, - fee_recipient_person, - ctx(&mut test) + let coin_meta = test_scenario::take_shared(scenario); + let decimals = coin::get_decimals(&coin_meta); + test_scenario::return_shared(coin_meta); + + assert!( + transfer::raw_amount(&parsed, decimals) == expected_amount, + 0 ); - return_shared(bridge_state); - return_shared(worm_state); - return_shared>(coin_meta); + + assert!( + transfer::raw_relayer_fee(&parsed, decimals) == expected_relayer_fee, + 0 + ); + assert!( + transfer::recipient_as_address(&parsed) == expected_recipient, + 0 + ); + assert!(transfer::recipient_chain(&parsed) == chain_id(), 0); + + // Clean up. + transfer::destroy(parsed); }; - // check balances after - next_tx(&mut test, admin);{ - let coins = take_from_address>(&test, admin); - assert!(coin::value(&coins) == 900000000, 0); - return_to_address>(admin, coins); + let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa); + let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa); - let fee_coins = take_from_address>(&test, fee_recipient_person); - assert!(coin::value(&fee_coins) == 100000000, 0); - return_to_address>(fee_recipient_person, fee_coins); + // Ignore effects. + test_scenario::next_tx(scenario, tx_relayer); + + let receipt = + authorize_transfer( + &mut token_bridge_state, + msg, + test_scenario::ctx(scenario) + ); + let payout = redeem_relayer_payout(receipt); + assert!(coin::value(&payout) == expected_relayer_fee, 0); + + // TODO: Check for one event? `TransferRedeemed`. + let _effects = test_scenario::next_tx(scenario, tx_relayer); + + // Check recipient's `Coin`. + let received = + test_scenario::take_from_address>( + scenario, + expected_recipient + ); + assert!(coin::value(&received) == expected_recipient_amount, 0); + + // And check remaining amount in custody. + let registry = state::borrow_token_registry(&token_bridge_state); + let remaining = custody_amount - expected_amount; + { + let asset = token_registry::borrow_native(registry); + assert!(native_asset::custody(asset) == remaining, 0); }; - test_scenario::end(test); + + // Clean up. + coin::burn_for_testing(payout); + coin::burn_for_testing(received); + return_state(token_bridge_state); + + // Done. + test_scenario::end(my_scenario); } #[test] - fun test_complete_native_transfer_4_decimals(){ - let (admin, fee_recipient_person, _) = people(); - let test = scenario(); - test = set_up_wormhole_core_and_token_bridges(admin, test); - next_tx(&mut test, admin);{ - native_coin_witness_v2::test_init(ctx(&mut test)); + /// An end-to-end test for complete transfer wrapped with VAA. + fun test_complete_transfer_wrapped_7_relayer_fee() { + use token_bridge::complete_transfer::{ + authorize_transfer, + redeem_relayer_payout }; - // register native asset type with the token bridge - next_tx(&mut test, admin);{ - let coin_meta = take_shared>(&test); - let bridge_state = take_shared(&test); - let worm_state = take_shared(&test); - bridge_state::register_native_asset( - &mut worm_state, - &mut bridge_state, - &coin_meta, - ctx(&mut test) - ); - return_shared>(coin_meta); - return_shared(bridge_state); - return_shared(worm_state); - }; - // create a treasury cap for the native asset type, mint some tokens, - // and deposit the native tokens into the token bridge - next_tx(&mut test, admin); { - let bridge_state = take_shared(&test); - let worm_state = take_shared(&test); - let t_cap = take_shared>(&test); - let coins = coin::mint(&mut t_cap, 10000000000, ctx(&mut test)); - bridge_state::deposit(&mut bridge_state, coins); - return_shared>(t_cap); - return_shared(bridge_state); - return_shared(worm_state); - }; - // complete transfer, sending native tokens to a recipient address - next_tx(&mut test, admin); { - let bridge_state = take_shared(&test); - let worm_state = take_shared(&test); - let coin_meta = take_shared>(&test); - let to = admin; - let amount = 100; - let fee_amount = 40; - let decimals = 4; - let token_address = external_address::from_bytes(x"01"); - let token_chain = wormhole_state::get_chain_id(&worm_state); - let to_chain = wormhole_state::get_chain_id(&worm_state); + let transfer_vaa = + dummy_message::encoded_transfer_vaa_wrapped_7_with_fee(); - let transfer: Transfer = transfer::create( - normalized_amount::normalize(amount, decimals), - token_address, - token_chain, - external_address::from_bytes(bcs::to_bytes(&to)), - to_chain, - normalized_amount::normalize(fee_amount, decimals), + let (expected_recipient, tx_relayer, coin_deployer) = three_people(); + let my_scenario = test_scenario::begin(tx_relayer); + let scenario = &mut my_scenario; + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register foreign emitter on chain ID == 2. + let expected_source_chain = 2; + register_dummy_emitter(scenario, expected_source_chain); + + coin_wrapped_7::init_and_register(scenario, coin_deployer); + + // Ignore effects. + test_scenario::next_tx(scenario, tx_relayer); + + let token_bridge_state = take_state(scenario); + + // These will be checked later. + let expected_relayer_fee = 1000; + let expected_recipient_amount = 2000; + let expected_amount = expected_relayer_fee + expected_recipient_amount; + + // Scope to allow immutable reference to `TokenRegistry`. + { + let registry = state::borrow_token_registry(&token_bridge_state); + let asset = + token_registry::borrow_wrapped(registry); + assert!(wrapped_asset::total_supply(asset) == 0, 0); + + // Verify transfer parameters. + let parsed = + transfer::deserialize_test_only( + wormhole::vaa::take_payload( + parse_and_verify_vaa(scenario, transfer_vaa) + ) + ); + + let asset_info = + token_registry::verified_asset(registry); + let expected_token_chain = token_registry::token_chain(&asset_info); + let expected_token_address = + token_registry::token_address(&asset_info); + assert!(transfer::token_chain(&parsed) == expected_token_chain, 0); + assert!( + transfer::token_address(&parsed) == expected_token_address, + 0 ); - complete_transfer::test_complete_transfer( - &transfer, - &mut worm_state, - &mut bridge_state, - &coin_meta, - fee_recipient_person, - ctx(&mut test) + let coin_meta = test_scenario::take_shared(scenario); + let decimals = coin::get_decimals(&coin_meta); + test_scenario::return_shared(coin_meta); + + assert!( + transfer::raw_amount(&parsed, decimals) == expected_amount, + 0 ); - return_shared(bridge_state); - return_shared(worm_state); - return_shared>(coin_meta); + + assert!( + transfer::raw_relayer_fee(&parsed, decimals) == expected_relayer_fee, + 0 + ); + assert!( + transfer::recipient_as_address(&parsed) == expected_recipient, + 0 + ); + assert!(transfer::recipient_chain(&parsed) == chain_id(), 0); + + // Clean up. + transfer::destroy(parsed); }; - // check balances after - next_tx(&mut test, admin);{ - let coins = take_from_address>(&test, admin); - assert!(coin::value(&coins) == 60, 0); - return_to_address>(admin, coins); + let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa); + let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa); - let fee_coins = take_from_address>(&test, fee_recipient_person); - assert!(coin::value(&fee_coins) == 40, 0); - return_to_address>(fee_recipient_person, fee_coins); + // Ignore effects. + test_scenario::next_tx(scenario, tx_relayer); + + let receipt = + authorize_transfer( + &mut token_bridge_state, + msg, + test_scenario::ctx(scenario) + ); + let payout = redeem_relayer_payout(receipt); + assert!(coin::value(&payout) == expected_relayer_fee, 0); + + // TODO: Check for one event? `TransferRedeemed`. + let _effects = test_scenario::next_tx(scenario, tx_relayer); + + // Check recipient's `Coin`. + let received = + test_scenario::take_from_address>( + scenario, + expected_recipient + ); + assert!(coin::value(&received) == expected_recipient_amount, 0); + + // And check that the amount is the total wrapped supply. + let registry = state::borrow_token_registry(&token_bridge_state); + { + let asset = + token_registry::borrow_wrapped(registry); + assert!(wrapped_asset::total_supply(asset) == expected_amount, 0); }; - test_scenario::end(test); + + // Clean up. + coin::burn_for_testing(payout); + coin::burn_for_testing(received); + return_state(token_bridge_state); + + // Done. + test_scenario::end(my_scenario); } #[test] - #[expected_failure(abort_code = 4, location=0000000000000000000000000000000000000000::bridge_state)] // E_ORIGIN_CHAIN_MISMATCH - fun test_complete_native_transfer_wrong_origin_chain(){ - let (admin, fee_recipient_person, _) = people(); - let test = scenario(); - test = set_up_wormhole_core_and_token_bridges(admin, test); - next_tx(&mut test, admin);{ - native_coin_witness::test_init(ctx(&mut test)); + /// An end-to-end test for complete transfer wrapped with VAA. + fun test_complete_transfer_wrapped_12_relayer_fee() { + use token_bridge::complete_transfer::{ + authorize_transfer, + redeem_relayer_payout }; - // register native asset type with the token bridge - next_tx(&mut test, admin);{ - let coin_meta = take_shared>(&test); - let bridge_state = take_shared(&test); - let worm_state = take_shared(&test); - bridge_state::register_native_asset( - &mut worm_state, - &mut bridge_state, - &coin_meta, - ctx(&mut test) - ); - native_coin_witness::test_init(ctx(&mut test)); - return_shared>(coin_meta); - return_shared(bridge_state); - return_shared(worm_state); - }; - // create a treasury cap for the native asset type, mint some tokens, - // and deposit the native tokens into the token bridge - next_tx(&mut test, admin); { - let bridge_state = take_shared(&test); - let worm_state = take_shared(&test); - let t_cap = take_shared>(&test); - let coins = coin::mint(&mut t_cap, 10000000000, ctx(&mut test)); - bridge_state::deposit(&mut bridge_state, coins); - return_shared>(t_cap); - return_shared(bridge_state); - return_shared(worm_state); - }; - // attempt complete transfer - next_tx(&mut test, admin); { - let bridge_state = take_shared(&test); - let worm_state = take_shared(&test); - let coin_meta = take_shared>(&test); - let to = admin; - let amount = 1000000000; - let fee_amount = 100000000; - let decimals = 8; - let token_address = external_address::from_bytes(x"01"); - let token_chain = u16::from_u64(34); // wrong chain! - let to_chain = wormhole_state::get_chain_id(&worm_state); + let transfer_vaa = + dummy_message::encoded_transfer_vaa_wrapped_12_with_fee(); - let transfer: Transfer = transfer::create( - normalized_amount::normalize(amount, decimals), - token_address, - token_chain, - external_address::from_bytes(bcs::to_bytes(&to)), - to_chain, - normalized_amount::normalize(fee_amount, decimals), - ); + let (expected_recipient, tx_relayer, coin_deployer) = three_people(); + let my_scenario = test_scenario::begin(tx_relayer); + let scenario = &mut my_scenario; - complete_transfer::test_complete_transfer( - &transfer, - &mut worm_state, - &mut bridge_state, - &coin_meta, - fee_recipient_person, - ctx(&mut test) + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register foreign emitter on chain ID == 2. + let expected_source_chain = 2; + register_dummy_emitter(scenario, expected_source_chain); + + coin_wrapped_12::init_and_register(scenario, coin_deployer); + + // Ignore effects. + // + // NOTE: `tx_relayer` != `expected_recipient`. + assert!(expected_recipient != tx_relayer, 0); + test_scenario::next_tx(scenario, tx_relayer); + + let token_bridge_state = take_state(scenario); + + // These will be checked later. + let expected_relayer_fee = 1000; + let expected_recipient_amount = 2000; + let expected_amount = expected_relayer_fee + expected_recipient_amount; + + // Scope to allow immutable reference to `TokenRegistry`. + { + let registry = state::borrow_token_registry(&token_bridge_state); + let asset = + token_registry::borrow_wrapped(registry); + assert!(wrapped_asset::total_supply(asset) == 0, 0); + + // Verify transfer parameters. + let parsed = + transfer::deserialize_test_only( + wormhole::vaa::take_payload( + parse_and_verify_vaa(scenario, transfer_vaa) + ) + ); + + let asset_info = + token_registry::verified_asset(registry); + let expected_token_chain = token_registry::token_chain(&asset_info); + let expected_token_address = + token_registry::token_address(&asset_info); + assert!(transfer::token_chain(&parsed) == expected_token_chain, 0); + assert!(transfer::token_address(&parsed) == expected_token_address, 0); + + let coin_meta = test_scenario::take_shared(scenario); + let decimals = coin::get_decimals(&coin_meta); + test_scenario::return_shared(coin_meta); + + assert!(transfer::raw_amount(&parsed, decimals) == expected_amount, 0); + + assert!( + transfer::raw_relayer_fee(&parsed, decimals) == expected_relayer_fee, + 0 ); - return_shared(bridge_state); - return_shared(worm_state); - return_shared>(coin_meta); + assert!( + transfer::recipient_as_address(&parsed) == expected_recipient, + 0 + ); + assert!(transfer::recipient_chain(&parsed) == chain_id(), 0); + + // Clean up. + transfer::destroy(parsed); }; - test_scenario::end(test); + + let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa); + let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa); + + // Ignore effects. + test_scenario::next_tx(scenario, tx_relayer); + + let receipt = + authorize_transfer( + &mut token_bridge_state, + msg, + test_scenario::ctx(scenario) + ); + let payout = redeem_relayer_payout(receipt); + assert!(coin::value(&payout) == expected_relayer_fee, 0); + + // TODO: Check for one event? `TransferRedeemed`. + let _effects = test_scenario::next_tx(scenario, tx_relayer); + + // Check recipient's `Coin`. + let received = + test_scenario::take_from_address>( + scenario, + expected_recipient + ); + assert!(coin::value(&received) == expected_recipient_amount, 0); + + // And check that the amount is the total wrapped supply. + let registry = state::borrow_token_registry(&token_bridge_state); + { + let asset = token_registry::borrow_wrapped(registry); + assert!(wrapped_asset::total_supply(asset) == expected_amount, 0); + }; + + // Clean up. + coin::burn_for_testing(payout); + coin::burn_for_testing(received); + return_state(token_bridge_state); + + // Done. + test_scenario::end(my_scenario); } #[test] - #[expected_failure(abort_code = 5, location=0000000000000000000000000000000000000000::bridge_state)] // E_ORIGIN_ADDRESS_MISMATCH - fun test_complete_native_transfer_wrong_coin_address(){ - let (admin, fee_recipient_person, _) = people(); - let test = scenario(); - test = set_up_wormhole_core_and_token_bridges(admin, test); - next_tx(&mut test, admin);{ - native_coin_witness::test_init(ctx(&mut test)); + /// An end-to-end test for complete transfer native with VAA. The encoded VAA + /// specifies a nonzero fee, however the `recipient` should receive the full + /// amount for self redeeming the transfer. + fun test_complete_transfer_native_10_relayer_fee_self_redemption() { + use token_bridge::complete_transfer::{ + authorize_transfer, + redeem_relayer_payout }; - // register native asset type with the token bridge - next_tx(&mut test, admin);{ - let bridge_state = take_shared(&test); - let worm_state = take_shared(&test); - let coin_meta = take_shared>(&test); - bridge_state::register_native_asset( - &mut worm_state, - &mut bridge_state, - &coin_meta, - ctx(&mut test) + + let transfer_vaa = + dummy_message::encoded_transfer_vaa_native_with_fee(); + + let (expected_recipient, _, coin_deployer) = three_people(); + let my_scenario = test_scenario::begin(expected_recipient); + let scenario = &mut my_scenario; + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register foreign emitter on chain ID == 2. + let expected_source_chain = 2; + register_dummy_emitter(scenario, expected_source_chain); + + let custody_amount = 500000; + coin_native_10::init_register_and_deposit( + scenario, + coin_deployer, + custody_amount + ); + + // Ignore effects. + test_scenario::next_tx(scenario, expected_recipient); + + let token_bridge_state = take_state(scenario); + + // NOTE: Although there is a fee encoded in the VAA, the relayer + // shouldn't receive this fee. The `expected_relayer_fee` should + // go to the recipient. + // + // These values will be used later. + let expected_relayer_fee = 0; + let encoded_relayer_fee = 100000; + let expected_recipient_amount = 300000; + let expected_amount = expected_relayer_fee + expected_recipient_amount; + + // Scope to allow immutable reference to `TokenRegistry`. + { + let registry = state::borrow_token_registry(&token_bridge_state); + let asset = token_registry::borrow_native(registry); + assert!(native_asset::custody(asset) == custody_amount, 0); + + // Verify transfer parameters. + let parsed = + transfer::deserialize_test_only( + wormhole::vaa::take_payload( + parse_and_verify_vaa(scenario, transfer_vaa) + ) + ); + + let asset_info = + token_registry::verified_asset(registry); + let expected_token_chain = token_registry::token_chain(&asset_info); + let expected_token_address = + token_registry::token_address(&asset_info); + assert!(transfer::token_chain(&parsed) == expected_token_chain, 0); + assert!(transfer::token_address(&parsed) == expected_token_address, 0); + + let coin_meta = test_scenario::take_shared(scenario); + + let decimals = coin::get_decimals(&coin_meta); + + test_scenario::return_shared(coin_meta); + + assert!(transfer::raw_amount(&parsed, decimals) == expected_amount, 0); + assert!( + transfer::raw_relayer_fee(&parsed, decimals) == encoded_relayer_fee, + 0 ); - native_coin_witness::test_init(ctx(&mut test)); - return_shared>(coin_meta); - return_shared(bridge_state); - return_shared(worm_state); - }; - // create a treasury cap for the native asset type, mint some tokens, - // and deposit the native tokens into the token bridge - next_tx(&mut test, admin); { - let bridge_state = take_shared(&test); - let worm_state = take_shared(&test); - let t_cap = take_shared>(&test); - let coins = coin::mint(&mut t_cap, 10000000000, ctx(&mut test)); - bridge_state::deposit(&mut bridge_state, coins); - return_shared>(t_cap); - return_shared(bridge_state); - return_shared(worm_state); - }; - // attempt complete transfer - next_tx(&mut test, admin); { - let bridge_state = take_shared(&test); - let worm_state = take_shared(&test); - let coin_meta = take_shared>(&test); - - let to = admin; - let amount = 1000000000; - let fee_amount = 100000000; - let decimals = 8; - let token_address = external_address::from_bytes(x"1111"); // wrong address! - let token_chain = wormhole_state::get_chain_id(&worm_state); - let to_chain = wormhole_state::get_chain_id(&worm_state); - - let transfer: Transfer = transfer::create( - normalized_amount::normalize(amount, decimals), - token_address, - token_chain, - external_address::from_bytes(bcs::to_bytes(&to)), - to_chain, - normalized_amount::normalize(fee_amount, decimals), + assert!( + transfer::recipient_as_address(&parsed) == expected_recipient, + 0 ); + assert!(transfer::recipient_chain(&parsed) == chain_id(), 0); - complete_transfer::test_complete_transfer( - &transfer, - &mut worm_state, - &mut bridge_state, - &coin_meta, - fee_recipient_person, - ctx(&mut test) - ); - - return_shared(bridge_state); - return_shared(worm_state); - return_shared>(coin_meta); + // Clean up. + transfer::destroy(parsed); }; - test_scenario::end(test); + + let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa); + let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa); + + // Ignore effects. + test_scenario::next_tx(scenario, expected_recipient); + + let receipt = + authorize_transfer( + &mut token_bridge_state, + msg, + test_scenario::ctx(scenario) + ); + let payout = redeem_relayer_payout(receipt); + assert!(coin::value(&payout) == expected_relayer_fee, 0); + + // TODO: Check for one event? `TransferRedeemed`. + let _effects = test_scenario::next_tx(scenario, expected_recipient); + + // Check recipient's `Coin`. + let received = + test_scenario::take_from_address>( + scenario, + expected_recipient + ); + assert!(coin::value(&received) == expected_recipient_amount, 0); + + // And check remaining amount in custody. + let registry = state::borrow_token_registry(&token_bridge_state); + let remaining = custody_amount - expected_amount; + { + let asset = token_registry::borrow_native(registry); + assert!(native_asset::custody(asset) == remaining, 0); + }; + + // Clean up. + coin::burn_for_testing(payout); + coin::burn_for_testing(received); + return_state(token_bridge_state); + + // Done. + test_scenario::end(my_scenario); } #[test] - #[expected_failure(abort_code = 2, location=0000000000000000000000000000000000000002::balance)] // E_TOO_MUCH_FEE - fun test_complete_native_transfer_too_much_fee(){ - let (admin, fee_recipient_person, _) = people(); - let test = scenario(); - test = set_up_wormhole_core_and_token_bridges(admin, test); - next_tx(&mut test, admin);{ - native_coin_witness::test_init(ctx(&mut test)); - }; - // register native asset type with the token bridge - next_tx(&mut test, admin);{ - let bridge_state = take_shared(&test); - let worm_state = take_shared(&test); - let coin_meta = take_shared>(&test); - bridge_state::register_native_asset( - &mut worm_state, - &mut bridge_state, - &coin_meta, - ctx(&mut test) - ); - native_coin_witness::test_init(ctx(&mut test)); - return_shared>(coin_meta); - return_shared(bridge_state); - return_shared(worm_state); - }; - // create a treasury cap for the native asset type, mint some tokens, - // and deposit the native tokens into the token bridge - next_tx(&mut test, admin); { - let bridge_state = take_shared(&test); - let worm_state = take_shared(&test); - let t_cap = take_shared>(&test); - let coins = coin::mint(&mut t_cap, 10000000000, ctx(&mut test)); - bridge_state::deposit(&mut bridge_state, coins); - return_shared>(t_cap); - return_shared(bridge_state); - return_shared(worm_state); - }; - // attempt complete transfer - next_tx(&mut test, admin); { - let bridge_state = take_shared(&test); - let worm_state = take_shared(&test); - let coin_meta = take_shared>(&test); + #[expected_failure( + abort_code = complete_transfer::E_CANONICAL_TOKEN_INFO_MISMATCH + )] + /// This test verifies that `authorize_transfer` reverts when called with + /// a native COIN_TYPE that's not encoded in the VAA. + fun test_cannot_authorize_transfer_native_invalid_coin_type() { + use token_bridge::complete_transfer::{authorize_transfer}; - let to = admin; - let amount = 1000000000; - let fee_amount = 1000000001; // Too much fee! Can't be greater than amount - let decimals = 8; - let token_address = external_address::from_bytes(x"01"); - let token_chain = wormhole_state::get_chain_id(&worm_state); - let to_chain = wormhole_state::get_chain_id(&worm_state); + let transfer_vaa = + dummy_message::encoded_transfer_vaa_native_with_fee(); - let transfer: Transfer = transfer::create( - normalized_amount::normalize(amount, decimals), - token_address, - token_chain, - external_address::from_bytes(bcs::to_bytes(&to)), - to_chain, - normalized_amount::normalize(fee_amount, decimals), + let (_, tx_relayer, coin_deployer) = three_people(); + let my_scenario = test_scenario::begin(tx_relayer); + let scenario = &mut my_scenario; + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register foreign emitter on chain ID == 2. + let expected_source_chain = 2; + register_dummy_emitter(scenario, expected_source_chain); + + let custody_amount_coin_10 = 500000; + coin_native_10::init_register_and_deposit( + scenario, + coin_deployer, + custody_amount_coin_10 + ); + + // Register a second native asset. + let custody_amount_coin_4 = 69420; + coin_native_4::init_register_and_deposit( + scenario, + coin_deployer, + custody_amount_coin_4 + ); + + // Ignore effects. + test_scenario::next_tx(scenario, tx_relayer); + + let token_bridge_state = take_state(scenario); + + // Scope to allow immutable reference to `TokenRegistry`. This verifies + // that both coin types have been registered. + { + let registry = state::borrow_token_registry(&token_bridge_state); + + // COIN_10. + let coin_10 = + token_registry::borrow_native(registry); + assert!( + native_asset::custody(coin_10) == custody_amount_coin_10, + 0 ); - complete_transfer::test_complete_transfer( - &transfer, - &mut worm_state, - &mut bridge_state, - &coin_meta, - fee_recipient_person, - ctx(&mut test) - ); - return_shared(bridge_state); - return_shared(worm_state); - return_shared>(coin_meta); + // COIN_4. + let coin_4 = token_registry::borrow_native(registry); + assert!(native_asset::custody(coin_4) == custody_amount_coin_4, 0); }; - test_scenario::end(test); + + let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa); + let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa); + + // Ignore effects. + test_scenario::next_tx(scenario, tx_relayer); + + // NOTE: this call should revert since the transfer VAA is for + // a coin of type COIN_NATIVE_10. However, the `complete_transfer` + // method is called using the COIN_NATIVE_4 type. + let receipt = + authorize_transfer( + &mut token_bridge_state, + msg, + test_scenario::ctx(scenario) + ); + + // Clean up. + complete_transfer::burn(receipt); + + abort 42 } #[test] - #[expected_failure(abort_code = 1, location=0000000000000000000000000000000000000002::dynamic_field)] // E_WRONG_COIN_TYPE - fun test_complete_native_transfer_wrong_coin(){ - let (admin, fee_recipient_person, _) = people(); - let test = scenario(); - test = set_up_wormhole_core_and_token_bridges(admin, test); - next_tx(&mut test, admin);{ - native_coin_witness::test_init(ctx(&mut test)); - }; - next_tx(&mut test, admin);{ - native_coin_witness_v2::test_init(ctx(&mut test)); - }; - // register native asset type with the token bridge - next_tx(&mut test, admin);{ - let bridge_state = take_shared(&test); - let worm_state = take_shared(&test); - let coin_meta = take_shared>(&test); - bridge_state::register_native_asset( - &mut worm_state, - &mut bridge_state, - &coin_meta, - ctx(&mut test) - ); - native_coin_witness::test_init(ctx(&mut test)); - return_shared>(coin_meta); - return_shared(bridge_state); - return_shared(worm_state); - }; - // create a treasury cap for the native asset type, mint some tokens, - // and deposit the native tokens into the token bridge - next_tx(&mut test, admin); { - let bridge_state = take_shared(&test); - let worm_state = take_shared(&test); - let t_cap = take_shared>(&test); - let coins = coin::mint(&mut t_cap, 10000000000, ctx(&mut test)); - bridge_state::deposit(&mut bridge_state, coins); - return_shared>(t_cap); - return_shared(bridge_state); - return_shared(worm_state); - }; - // attempt complete transfer with wrong coin type (NATIVE_COIN_WITNESS_V2) - next_tx(&mut test, admin); { - let bridge_state = take_shared(&test); - let worm_state = take_shared(&test); - let coin_meta = take_shared>(&test); + #[expected_failure( + abort_code = complete_transfer::E_CANONICAL_TOKEN_INFO_MISMATCH + )] + /// This test verifies that `authorize_transfer` reverts when called with + /// a wrapped COIN_TYPE that's not encoded in the VAA. + fun test_cannot_authorize_transfer_wrapped_invalid_coin_type() { + use token_bridge::complete_transfer::{authorize_transfer}; - let to = admin; - let amount = 1000000000; - let fee_amount = 10000000; - let decimals = 8; - let token_address = external_address::from_bytes(x"01"); - let token_chain = wormhole_state::get_chain_id(&worm_state); - let to_chain = wormhole_state::get_chain_id(&worm_state); + let transfer_vaa = dummy_message::encoded_transfer_vaa_wrapped_12_with_fee(); - let transfer: Transfer = transfer::create( - normalized_amount::normalize(amount, decimals), - token_address, - token_chain, - external_address::from_bytes(bcs::to_bytes(&to)), - to_chain, - normalized_amount::normalize(fee_amount, decimals), + let (expected_recipient, tx_relayer, coin_deployer) = three_people(); + let my_scenario = test_scenario::begin(tx_relayer); + let scenario = &mut my_scenario; + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register foreign emitter on chain ID == 2. + let expected_source_chain = 2; + register_dummy_emitter(scenario, expected_source_chain); + + // Register both wrapped coin types (12 and 7). + coin_wrapped_12::init_and_register(scenario, coin_deployer); + coin_wrapped_7::init_and_register(scenario, coin_deployer); + + // Ignore effects. + test_scenario::next_tx(scenario, tx_relayer); + + // NOTE: `tx_relayer` != `expected_recipient`. + assert!(expected_recipient != tx_relayer, 0); + + let token_bridge_state = take_state(scenario); + + // Scope to allow immutable reference to `TokenRegistry`. This verifies + // that both coin types have been registered. + { + let registry = state::borrow_token_registry(&token_bridge_state); + + let coin_12 = + token_registry::borrow_wrapped(registry); + assert!(wrapped_asset::total_supply(coin_12) == 0, 0); + + let coin_7 = + token_registry::borrow_wrapped(registry); + assert!(wrapped_asset::total_supply(coin_7) == 0, 0); + }; + + let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa); + let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa); + + // Ignore effects. + test_scenario::next_tx(scenario, tx_relayer); + + // NOTE: this call should revert since the transfer VAA is for + // a coin of type COIN_WRAPPED_12. However, the `authorize_transfer` + // method is called using the COIN_WRAPPED_7 type. + let receipt = + authorize_transfer( + &mut token_bridge_state, + msg, + test_scenario::ctx(scenario) ); - complete_transfer::test_complete_transfer( - &transfer, - &mut worm_state, - &mut bridge_state, - &coin_meta, - fee_recipient_person, - ctx(&mut test) - ); - return_shared(bridge_state); - return_shared(worm_state); - return_shared>(coin_meta); - }; - test_scenario::end(test); + // Clean up. + complete_transfer::burn(receipt); + + abort 42 } - // the following test is for the "beefface" token from ethereum (chain id = 2), - // which has 8 decimals #[test] - fun complete_wrapped_transfer_test(){ - let (admin, fee_recipient_person, _) = people(); - let scenario = scenario(); - // First register foreign chain, create wrapped asset, register wrapped asset. - let test = test_register_wrapped_(admin, scenario); - next_tx(&mut test, admin);{ - coin_witness::test_init(ctx(&mut test)); - }; - // Complete transfer of wrapped asset from foreign chain to this chain. - next_tx(&mut test, admin); { - let bridge_state = take_shared(&test); - let worm_state = take_shared(&test); - let coin_meta = take_shared>(&test); + #[expected_failure(abort_code = complete_transfer::E_TARGET_NOT_SUI)] + /// This test verifies that `authorize_transfer` reverts when a transfer is + /// sent to the wrong target blockchain (chain ID != 21). + fun test_cannot_authorize_transfer_wrapped_12_invalid_target_chain() { + use token_bridge::complete_transfer::{authorize_transfer}; - let to = admin; - let amount = 1000000000; - let fee_amount = 100000000; - let decimals = 8; - let token_address = external_address::from_bytes(x"beefface"); - let token_chain = u16::from_u64(2); - let to_chain = wormhole_state::get_chain_id(&worm_state); + let transfer_vaa = + dummy_message::encoded_transfer_vaa_wrapped_12_invalid_target_chain(); - let transfer: Transfer = transfer::create( - normalized_amount::normalize(amount, decimals), - token_address, - token_chain, - external_address::from_bytes(bcs::to_bytes(&to)), - to_chain, - normalized_amount::normalize(fee_amount, decimals), + let (expected_recipient, tx_relayer, coin_deployer) = three_people(); + let my_scenario = test_scenario::begin(tx_relayer); + let scenario = &mut my_scenario; + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register foreign emitter on chain ID == 2. + let expected_source_chain = 2; + register_dummy_emitter(scenario, expected_source_chain); + + coin_wrapped_12::init_and_register(scenario, coin_deployer); + + // Ignore effects. + // + // NOTE: `tx_relayer` != `expected_recipient`. + assert!(expected_recipient != tx_relayer, 0); + + // Ignore effects. + test_scenario::next_tx(scenario, tx_relayer); + + let token_bridge_state = take_state(scenario); + + let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa); + let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa); + + // Ignore effects. + test_scenario::next_tx(scenario, tx_relayer); + + // NOTE: this call should revert since the target chain encoded is + // chain 69 instead of chain 21 (Sui). + let receipt = + authorize_transfer( + &mut token_bridge_state, + msg, + test_scenario::ctx(scenario) ); - complete_transfer::test_complete_transfer( - &transfer, - &mut worm_state, - &mut bridge_state, - &coin_meta, - fee_recipient_person, - ctx(&mut test) + // Clean up. + complete_transfer::burn(receipt); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = wormhole::package_utils::E_NOT_CURRENT_VERSION)] + fun test_cannot_complete_transfer_outdated_version() { + use token_bridge::complete_transfer::{authorize_transfer}; + + let transfer_vaa = + dummy_message::encoded_transfer_vaa_native_with_fee(); + + let (tx_relayer, coin_deployer) = two_people(); + let my_scenario = test_scenario::begin(tx_relayer); + let scenario = &mut my_scenario; + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register foreign emitter on chain ID == 2. + let expected_source_chain = 2; + register_dummy_emitter(scenario, expected_source_chain); + + let custody_amount = 500000; + coin_native_10::init_register_and_deposit( + scenario, + coin_deployer, + custody_amount + ); + + // Ignore effects. + test_scenario::next_tx(scenario, tx_relayer); + + let token_bridge_state = take_state(scenario); + + let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa); + let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa); + + // Ignore effects. + test_scenario::next_tx(scenario, tx_relayer); + + // Conveniently roll version back. + state::reverse_migrate_version(&mut token_bridge_state); + + // Simulate executing with an outdated build by upticking the minimum + // required version for `publish_message` to something greater than + // this build. + state::migrate_version_test_only( + &mut token_bridge_state, + token_bridge::version_control::previous_version_test_only(), + token_bridge::version_control::next_version() + ); + + // You shall not pass! + let receipt = + authorize_transfer( + &mut token_bridge_state, + msg, + test_scenario::ctx(scenario) ); - return_shared(bridge_state); - return_shared(worm_state); - return_shared>(coin_meta); - }; - // check balances after - next_tx(&mut test, admin);{ - let coins = take_from_address>(&test, admin); - assert!(coin::value(&coins) == 900000000, 0); - return_to_address>(admin, coins); + // Clean up. + complete_transfer::burn(receipt); - let fee_coins = take_from_address>(&test, fee_recipient_person); - assert!(coin::value(&fee_coins) == 100000000, 0); - return_to_address>(fee_recipient_person, fee_coins); - }; - test_scenario::end(test); + abort 42 } } diff --git a/sui/token_bridge/sources/complete_transfer_with_payload.move b/sui/token_bridge/sources/complete_transfer_with_payload.move new file mode 100644 index 000000000..da4c845c8 --- /dev/null +++ b/sui/token_bridge/sources/complete_transfer_with_payload.move @@ -0,0 +1,775 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements two methods: `authorize_transfer` and `redeem_coin`, +/// which are to be executed in a transaction block in this order. +/// +/// `authorize_transfer` allows a contract to complete a Token Bridge transfer +/// with arbitrary payload. This deserialized `TransferWithPayload` with the +/// bridged balance and source chain ID are packaged in a `RedeemerReceipt`. +/// +/// `redeem_coin` unpacks the `RedeemerReceipt` and checks whether the specified +/// `EmitterCap` is the specified redeemer for this transfer. If he is the +/// correct redeemer, the balance is unpacked and transformed into `Coin` and +/// is returned alongside `TransferWithPayload` and source chain ID. +/// +/// The purpose of splitting this transfer redemption into two steps is in case +/// Token Bridge needs to be upgraded and there is a breaking change for this +/// module, an integrator would not be left broken. It is discouraged to put +/// `authorize_transfer` in an integrator's package logic. Otherwise, this +/// integrator needs to be prepared to upgrade his contract to handle the latest +/// version of `complete_transfer_with_payload`. +/// +/// Instead, an integrator is encouraged to execute a transaction block, which +/// executes `authorize_transfer` using the latest Token Bridge package ID and +/// to implement `redeem_coin` in his contract to consume this receipt. This is +/// similar to how an integrator with Wormhole is not meant to use +/// `vaa::parse_and_verify` in his contract in case the `vaa` module needs to +/// be upgraded due to a breaking change. +/// +/// Like in `complete_transfer`, a VAA with an encoded transfer can be redeemed +/// only once. +/// +/// See `transfer_with_payload` module for serialization and deserialization of +/// Wormhole message payload. +module token_bridge::complete_transfer_with_payload { + use sui::coin::{Self, Coin}; + use sui::object::{Self}; + use sui::tx_context::{TxContext}; + use wormhole::emitter::{EmitterCap}; + + use token_bridge::complete_transfer::{Self}; + use token_bridge::state::{Self, State, LatestOnly}; + use token_bridge::transfer_with_payload::{Self, TransferWithPayload}; + use token_bridge::vaa::{Self, TokenBridgeMessage}; + + /// `EmitterCap` address does not agree with encoded redeemer. + const E_INVALID_REDEEMER: u64 = 0; + + /// This type is only generated from `authorize_transfer` and can only be + /// redeemed using `redeem_coin`. Integrators are expected to implement + /// `redeem_coin` within their contracts and call `authorize_transfer` in a + /// transaction block preceding the method that consumes this receipt. The + /// only way to destroy this receipt is callling `redeem_coin` with an + /// `EmitterCap` generated from the `wormhole::emitter` module, whose ID is + /// the expected redeemer for this token transfer. + struct RedeemerReceipt { + /// Which chain ID this transfer originated from. + source_chain: u16, + /// Deserialized transfer info. + parsed: TransferWithPayload, + /// Coin of bridged asset. + bridged_out: Coin + } + + /// `authorize_transfer` deserializes a token transfer VAA payload, which + /// encodes its own arbitrary payload (which has meaning to the redeemer). + /// Once the transfer is authorized, an event (`TransferRedeemed`) is + /// emitted to reflect which Token Bridge this transfer originated from. + /// The `RedeemerReceipt` returned wraps a balance reflecting the encoded + /// transfer amount along with the source chain and deserialized + /// `TransferWithPayload`. + /// + /// NOTE: This method is guarded by a minimum build version check. This + /// method could break backward compatibility on an upgrade. + /// + /// It is important for integrators to refrain from calling this method + /// within their contracts. This method is meant to be called in a + /// transaction block, passing the `RedeemerReceipt` to a method which calls + /// `redeem_coin` within a contract. If in a circumstance where this module + /// has a breaking change in an upgrade, `redeem_coin` will not be affected + /// by this change. + /// + /// See `redeem_coin` for more details. + public fun authorize_transfer( + token_bridge_state: &mut State, + msg: TokenBridgeMessage, + ctx: &mut TxContext + ): RedeemerReceipt { + // This capability ensures that the current build version is used. + let latest_only = state::assert_latest_only(token_bridge_state); + + // Emitting the transfer being redeemed. + // + // NOTE: We save the emitter chain ID to save the integrator from + // having to `parse_and_verify` the same encoded VAA to get this info. + let source_chain = + complete_transfer::emit_transfer_redeemed(&msg); + + // Finally deserialize the Wormhole message payload and handle bridging + // out token of a given coin type. + handle_authorize_transfer( + &latest_only, + token_bridge_state, + source_chain, + vaa::take_payload(msg), + ctx + ) + } + + /// After a transfer is authorized, only a valid redeemer may unpack the + /// `RedeemerReceipt`. The specified `EmitterCap` is the only authorized + /// redeemer of the transfer. Once the redeemer is validated, coin from + /// this receipt of the specified coin type is returned alongside the + /// deserialized `TransferWithPayload` and source chain ID. + /// + /// NOTE: Integrators of Token Bridge redeeming these token transfers should + /// be calling only this method from their contracts. This method is not + /// guarded by version control (thus not requiring a reference to the + /// Token Bridge `State` object), so it is intended to work for any package + /// version. + public fun redeem_coin( + emitter_cap: &EmitterCap, + receipt: RedeemerReceipt + ): ( + Coin, + TransferWithPayload, + u16 // `wormhole::vaa::emitter_chain` + ) { + let RedeemerReceipt { source_chain, parsed, bridged_out } = receipt; + + // Transfer must be redeemed by the contract's registered Wormhole + // emitter. + let redeemer = transfer_with_payload::redeemer_id(&parsed); + assert!(redeemer == object::id(emitter_cap), E_INVALID_REDEEMER); + + // Create coin from balance and return other unpacked members of receipt. + (bridged_out, parsed, source_chain) + } + + fun handle_authorize_transfer( + latest_only: &LatestOnly, + token_bridge_state: &mut State, + source_chain: u16, + transfer_vaa_payload: vector, + ctx: &mut TxContext + ): RedeemerReceipt { + // Deserialize for processing. + let parsed = transfer_with_payload::deserialize(transfer_vaa_payload); + + // Handle bridging assets out to be returned to method caller. + // + // See `complete_transfer` module for more info. + let ( + _, + bridged_out, + ) = + complete_transfer::verify_and_bridge_out( + latest_only, + token_bridge_state, + transfer_with_payload::token_chain(&parsed), + transfer_with_payload::token_address(&parsed), + transfer_with_payload::redeemer_chain(&parsed), + transfer_with_payload::amount(&parsed) + ); + + RedeemerReceipt { + source_chain, + parsed, + bridged_out: coin::from_balance(bridged_out, ctx) + } + } + + #[test_only] + public fun burn(receipt: RedeemerReceipt) { + let RedeemerReceipt { + source_chain: _, + parsed: _, + bridged_out + } = receipt; + coin::burn_for_testing(bridged_out); + } +} + +#[test_only] +module token_bridge::complete_transfer_with_payload_tests { + use sui::coin::{Self}; + use sui::object::{Self}; + use sui::test_scenario::{Self}; + use wormhole::emitter::{Self}; + use wormhole::state::{chain_id}; + use wormhole::wormhole_scenario::{new_emitter, parse_and_verify_vaa}; + + use token_bridge::coin_wrapped_12::{Self, COIN_WRAPPED_12}; + use token_bridge::complete_transfer_with_payload::{Self}; + use token_bridge::complete_transfer::{Self}; + use token_bridge::coin_native_10::{Self, COIN_NATIVE_10}; + use token_bridge::dummy_message::{Self}; + use token_bridge::native_asset::{Self}; + use token_bridge::state::{Self}; + use token_bridge::token_bridge_scenario::{ + register_dummy_emitter, + return_state, + set_up_wormhole_and_token_bridge, + take_state, + two_people + }; + use token_bridge::token_registry::{Self}; + use token_bridge::transfer_with_payload::{Self}; + use token_bridge::vaa::{Self}; + use token_bridge::wrapped_asset::{Self}; + + #[test] + /// Test the public-facing function authorize_transfer. + /// using a native transfer VAA_ATTESTED_DECIMALS_10. + fun test_complete_transfer_with_payload_native_asset() { + use token_bridge::complete_transfer_with_payload::{ + authorize_transfer, + redeem_coin + }; + + let transfer_vaa = + dummy_message::encoded_transfer_with_payload_vaa_native(); + + let (user, coin_deployer) = two_people(); + let my_scenario = test_scenario::begin(user); + let scenario = &mut my_scenario; + + // Initialize Wormhole and Token Bridge. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register Sui as a foreign emitter. + let expected_source_chain = 2; + register_dummy_emitter(scenario, expected_source_chain); + + // Initialize native token. + let mint_amount = 1000000; + coin_native_10::init_register_and_deposit( + scenario, + coin_deployer, + mint_amount + ); + + // Ignore effects. Begin processing as arbitrary tx executor. + test_scenario::next_tx(scenario, user); + + let token_bridge_state = take_state(scenario); + + { + let asset = token_registry::borrow_native( + state::borrow_token_registry(&token_bridge_state) + ); + assert!(native_asset::custody(asset) == mint_amount, 0); + }; + + // Set up dummy `EmitterCap` as the expected redeemer. + let emitter_cap = emitter::dummy(); + + // Verify that the emitter cap is the expected redeemer. + let expected_transfer = + transfer_with_payload::deserialize( + wormhole::vaa::take_payload( + parse_and_verify_vaa(scenario, transfer_vaa) + ) + ); + assert!( + transfer_with_payload::redeemer_id(&expected_transfer) == object::id(&emitter_cap), + 0 + ); + + let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa); + let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa); + + // Ignore effects. Begin processing as arbitrary tx executor. + test_scenario::next_tx(scenario, user); + + // Execute authorize_transfer. + let receipt = + authorize_transfer( + &mut token_bridge_state, + msg, + test_scenario::ctx(scenario) + ); + let ( + bridged, + parsed_transfer, + source_chain + ) = redeem_coin(&emitter_cap, receipt); + + assert!(source_chain == expected_source_chain, 0); + + // Assert coin value, source chain, and parsed transfer details are correct. + // We expect the coin value to be 300000, because that's in terms of + // 10 decimals. The amount specifed in the VAA_ATTESTED_DECIMALS_12 is 3000, because that's + // in terms of 8 decimals. + let expected_bridged = 300000; + assert!(coin::value(&bridged) == expected_bridged, 0); + + // Amount left on custody should be whatever is left remaining after + // the transfer. + let remaining = mint_amount - expected_bridged; + { + let asset = token_registry::borrow_native( + state::borrow_token_registry(&token_bridge_state) + ); + assert!(native_asset::custody(asset) == remaining, 0); + }; + + // Verify token info. + let registry = state::borrow_token_registry(&token_bridge_state); + let verified = + token_registry::verified_asset(registry); + let expected_token_chain = token_registry::token_chain(&verified); + let expected_token_address = token_registry::token_address(&verified); + assert!(expected_token_chain == chain_id(), 0); + assert!( + transfer_with_payload::token_chain(&parsed_transfer) == expected_token_chain, + 0 + ); + assert!( + transfer_with_payload::token_address(&parsed_transfer) == expected_token_address, + 0 + ); + + // Verify transfer by serializing both parsed and expected. + let serialized = transfer_with_payload::serialize(parsed_transfer); + let expected_serialized = + transfer_with_payload::serialize(expected_transfer); + assert!(serialized == expected_serialized, 0); + + // Clean up. + return_state(token_bridge_state); + coin::burn_for_testing(bridged); + emitter::destroy_test_only(emitter_cap); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + /// Test the public-facing functions `authorize_transfer` and `redeem_coin`. + /// Use an actual devnet Wormhole complete transfer with payload + /// VAA_ATTESTED_DECIMALS_12. + /// + /// This test confirms that: + /// - `authorize_transfer` with `redeem_coin` deserializes the encoded + /// transfer and recovers the source chain, payload, and additional + /// transfer details wrapped in a redeemer receipt. + /// - a wrapped coin with the correct value is minted by the bridge + /// and returned by authorize_transfer + /// + fun test_complete_transfer_with_payload_wrapped_asset() { + use token_bridge::complete_transfer_with_payload::{ + authorize_transfer, + redeem_coin + }; + + let transfer_vaa = + dummy_message::encoded_transfer_with_payload_wrapped_12(); + + let (user, coin_deployer) = two_people(); + let my_scenario = test_scenario::begin(user); + let scenario = &mut my_scenario; + + // Initialize Wormhole and Token Bridge. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register chain ID 2 as a foreign emitter. + let expected_source_chain = 2; + register_dummy_emitter(scenario, expected_source_chain); + + // Register wrapped token. + coin_wrapped_12::init_and_register(scenario, coin_deployer); + + // Ignore effects. Begin processing as arbitrary tx executor. + test_scenario::next_tx(scenario, user); + + let token_bridge_state = take_state(scenario); + + // Set up dummy `EmitterCap` as the expected redeemer. + let emitter_cap = emitter::dummy(); + + // Verify that the emitter cap is the expected redeemer. + let expected_transfer = + transfer_with_payload::deserialize( + wormhole::vaa::take_payload( + parse_and_verify_vaa(scenario, transfer_vaa) + ) + ); + assert!( + transfer_with_payload::redeemer_id(&expected_transfer) == object::id(&emitter_cap), + 0 + ); + + let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa); + let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa); + + // Ignore effects. Begin processing as arbitrary tx executor. + test_scenario::next_tx(scenario, user); + + // Execute authorize_transfer. + let receipt = + authorize_transfer( + &mut token_bridge_state, + msg, + test_scenario::ctx(scenario) + ); + let ( + bridged, + parsed_transfer, + source_chain + ) = redeem_coin(&emitter_cap, receipt); + assert!(source_chain == expected_source_chain, 0); + + // Assert coin value, source chain, and parsed transfer details are correct. + let expected_bridged = 3000; + assert!(coin::value(&bridged) == expected_bridged, 0); + + // Total supply should equal the amount just minted. + let registry = state::borrow_token_registry(&token_bridge_state); + { + let asset = + token_registry::borrow_wrapped(registry); + assert!(wrapped_asset::total_supply(asset) == expected_bridged, 0); + }; + + // Verify token info. + let verified = + token_registry::verified_asset(registry); + let expected_token_chain = token_registry::token_chain(&verified); + let expected_token_address = token_registry::token_address(&verified); + assert!(expected_token_chain != chain_id(), 0); + assert!( + transfer_with_payload::token_chain(&parsed_transfer) == expected_token_chain, + 0 + ); + assert!( + transfer_with_payload::token_address(&parsed_transfer) == expected_token_address, + 0 + ); + + // Verify transfer by serializing both parsed and expected. + let serialized = transfer_with_payload::serialize(parsed_transfer); + let expected_serialized = + transfer_with_payload::serialize(expected_transfer); + assert!(serialized == expected_serialized, 0); + + // Clean up. + return_state(token_bridge_state); + coin::burn_for_testing(bridged); + emitter::destroy_test_only(emitter_cap); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + #[expected_failure( + abort_code = complete_transfer_with_payload::E_INVALID_REDEEMER, + )] + /// Test the public-facing function authorize_transfer. + /// This test fails because the ecmitter_cap (recipient) is incorrect (0x2 instead of 0x3). + /// + fun test_cannot_complete_transfer_with_payload_invalid_redeemer() { + use token_bridge::complete_transfer_with_payload::{ + authorize_transfer, + redeem_coin + }; + + let transfer_vaa = + dummy_message::encoded_transfer_with_payload_wrapped_12(); + + let (user, coin_deployer) = two_people(); + let my_scenario = test_scenario::begin(user); + let scenario = &mut my_scenario; + + // Initialize Wormhole and Token Bridge. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register chain ID 2 as a foreign emitter. + register_dummy_emitter(scenario, 2); + + // Register wrapped asset with 12 decimals. + coin_wrapped_12::init_and_register(scenario, coin_deployer); + + // Ignore effects. Begin processing as arbitrary tx executor. + test_scenario::next_tx(scenario, user); + + let token_bridge_state = take_state(scenario); + + let parsed = + transfer_with_payload::deserialize( + wormhole::vaa::take_payload( + parse_and_verify_vaa(scenario, transfer_vaa) + ) + ); + + // Because the vaa expects the dummy emitter as the redeemer, we need + // to generate another emitter. + let emitter_cap = new_emitter(scenario); + + // Ignore effects. Begin processing as arbitrary tx executor. + test_scenario::next_tx(scenario, user); + + assert!( + transfer_with_payload::redeemer_id(&parsed) != object::id(&emitter_cap), + 0 + ); + + let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa); + let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa); + + // Ignore effects. Begin processing as arbitrary tx executor. + test_scenario::next_tx(scenario, user); + + let receipt = + authorize_transfer( + &mut token_bridge_state, + msg, + test_scenario::ctx(scenario) + ); + // You shall not pass! + let ( + bridged_out, + _, + _ + ) = redeem_coin(&emitter_cap, receipt); + + // Clean up. + coin::burn_for_testing(bridged_out); + + abort 42 + } + + #[test] + #[expected_failure( + abort_code = complete_transfer::E_CANONICAL_TOKEN_INFO_MISMATCH + )] + /// This test demonstrates that the `CoinType` specified for the token + /// redemption must agree with the canonical token info encoded in the VAA_ATTESTED_DECIMALS_12, + /// which is registered with the Token Bridge. + fun test_cannot_complete_transfer_with_payload_wrong_coin_type() { + use token_bridge::complete_transfer_with_payload::{ + authorize_transfer + }; + + let transfer_vaa = + dummy_message::encoded_transfer_with_payload_wrapped_12(); + + let (user, coin_deployer) = two_people(); + let my_scenario = test_scenario::begin(user); + let scenario = &mut my_scenario; + + // Initialize Wormhole and Token Bridge. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register chain ID 2 as a foreign emitter. + let expected_source_chain = 2; + register_dummy_emitter(scenario, expected_source_chain); + + // Register wrapped token. + coin_wrapped_12::init_and_register(scenario, coin_deployer); + + // Also register unexpected token (in this case a native one). + coin_native_10::init_and_register(scenario, coin_deployer); + + // Ignore effects. Begin processing as arbitrary tx executor. + test_scenario::next_tx(scenario, user); + + let token_bridge_state = take_state(scenario); + + let registry = state::borrow_token_registry(&token_bridge_state); + + // Set up dummy `EmitterCap` as the expected redeemer. + let emitter_cap = emitter::dummy(); + + // Verify that the emitter cap is the expected redeemer. + let expected_transfer = + transfer_with_payload::deserialize( + wormhole::vaa::take_payload( + parse_and_verify_vaa(scenario, transfer_vaa) + ) + ); + assert!( + transfer_with_payload::redeemer_id(&expected_transfer) == object::id(&emitter_cap), + 0 + ); + + // Also verify that the encoded token info disagrees with the expected + // token info. + let verified = + token_registry::verified_asset(registry); + let expected_token_chain = token_registry::token_chain(&verified); + let expected_token_address = token_registry::token_address(&verified); + assert!( + transfer_with_payload::token_chain(&expected_transfer) != expected_token_chain, + 0 + ); + assert!( + transfer_with_payload::token_address(&expected_transfer) != expected_token_address, + 0 + ); + + let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa); + let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa); + + // Ignore effects. Begin processing as arbitrary tx executor. + test_scenario::next_tx(scenario, user); + + // You shall not pass! + let receipt = + authorize_transfer( + &mut token_bridge_state, + msg, + test_scenario::ctx(scenario) + ); + + // Clean up. + return_state(token_bridge_state); + complete_transfer_with_payload::burn(receipt); + emitter::destroy_test_only(emitter_cap); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + #[expected_failure(abort_code = complete_transfer::E_TARGET_NOT_SUI)] + /// This test verifies that `complete_transfer` reverts when a transfer is + /// sent to the wrong target blockchain (chain ID != 21). + fun test_cannot_complete_transfer_with_payload_wrapped_asset_invalid_target_chain() { + use token_bridge::complete_transfer_with_payload::{ + authorize_transfer + }; + + let transfer_vaa = + dummy_message::encoded_transfer_with_payload_wrapped_12_invalid_target_chain(); + + let (user, coin_deployer) = two_people(); + let my_scenario = test_scenario::begin(user); + let scenario = &mut my_scenario; + + // Initialize Wormhole and Token Bridge. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register chain ID 2 as a foreign emitter. + let expected_source_chain = 2; + register_dummy_emitter(scenario, expected_source_chain); + + // Register wrapped token. + coin_wrapped_12::init_and_register(scenario, coin_deployer); + + // Ignore effects. Begin processing as arbitrary tx executor. + test_scenario::next_tx(scenario, user); + + let token_bridge_state = take_state(scenario); + + // Set up dummy `EmitterCap` as the expected redeemer. + let emitter_cap = emitter::dummy(); + + // Verify that the emitter cap is the expected redeemer. + let expected_transfer = + transfer_with_payload::deserialize( + wormhole::vaa::take_payload( + parse_and_verify_vaa(scenario, transfer_vaa) + ) + ); + assert!( + transfer_with_payload::redeemer_id(&expected_transfer) == object::id(&emitter_cap), + 0 + ); + + let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa); + let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa); + + // Ignore effects. Begin processing as arbitrary tx executor. + test_scenario::next_tx(scenario, user); + + // You shall not pass! + let receipt = + authorize_transfer( + &mut token_bridge_state, + msg, + test_scenario::ctx(scenario) + ); + + // Clean up. + complete_transfer_with_payload::burn(receipt); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = wormhole::package_utils::E_NOT_CURRENT_VERSION)] + fun test_cannot_complete_transfer_with_payload_outdated_version() { + use token_bridge::complete_transfer_with_payload::{authorize_transfer}; + + let transfer_vaa = + dummy_message::encoded_transfer_with_payload_vaa_native(); + + let (user, coin_deployer) = two_people(); + let my_scenario = test_scenario::begin(user); + let scenario = &mut my_scenario; + + // Initialize Wormhole and Token Bridge. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register Sui as a foreign emitter. + let expected_source_chain = 2; + register_dummy_emitter(scenario, expected_source_chain); + + // Initialize native token. + let mint_amount = 1000000; + coin_native_10::init_register_and_deposit( + scenario, + coin_deployer, + mint_amount + ); + + // Ignore effects. Begin processing as arbitrary tx executor. + test_scenario::next_tx(scenario, user); + + let token_bridge_state = take_state(scenario); + + // Set up dummy `EmitterCap` as the expected redeemer. + let emitter_cap = emitter::dummy(); + + // Verify that the emitter cap is the expected redeemer. + let expected_transfer = + transfer_with_payload::deserialize( + wormhole::vaa::take_payload( + parse_and_verify_vaa(scenario, transfer_vaa) + ) + ); + assert!( + transfer_with_payload::redeemer_id(&expected_transfer) == object::id(&emitter_cap), + 0 + ); + + let verified_vaa = parse_and_verify_vaa(scenario, transfer_vaa); + let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa); + + // Ignore effects. Begin processing as arbitrary tx executor. + test_scenario::next_tx(scenario, user); + + // Conveniently roll version back. + state::reverse_migrate_version(&mut token_bridge_state); + + // Simulate executing with an outdated build by upticking the minimum + // required version for `publish_message` to something greater than + // this build. + state::migrate_version_test_only( + &mut token_bridge_state, + token_bridge::version_control::previous_version_test_only(), + token_bridge::version_control::next_version() + ); + + // You shall not pass! + let receipt = + authorize_transfer( + &mut token_bridge_state, + msg, + test_scenario::ctx(scenario) + ); + + // Clean up. + complete_transfer_with_payload::burn(receipt); + + abort 42 + } +} diff --git a/sui/token_bridge/sources/create_wrapped.move b/sui/token_bridge/sources/create_wrapped.move new file mode 100644 index 000000000..db60fd8da --- /dev/null +++ b/sui/token_bridge/sources/create_wrapped.move @@ -0,0 +1,642 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements methods that create a specific coin type reflecting a +/// wrapped (foreign) asset, whose metadata is encoded in a VAA sent from +/// another network. +/// +/// Wrapped assets are created in two steps. +/// 1. `prepare_registration`: This method creates a new `TreasuryCap` for a +/// given coin type and wraps an encoded asset metadata VAA. We require a +/// one-time witness (OTW) to throw an explicit error (even though it is +/// redundant with what `create_currency` requires). This coin will +/// be published using this method, meaning the `init` method in that +/// untrusted package will have the asset's decimals hard-coded for its +/// coin metadata. A `WrappedAssetSetup` object is transferred to the +/// transaction sender. +/// 2. `complete_registration`: This method destroys the `WrappedAssetSetup` +/// object by unpacking its `TreasuryCap`, which will be warehoused in the +/// `TokenRegistry`. The shared coin metadata object will be updated to +/// reflect the contents of the encoded asset metadata payload. +/// +/// Wrapped asset metadata can also be updated with a new asset metadata VAA. +/// By calling `update_attestation`, Token Bridge verifies that the specific +/// coin type is registered and agrees with the encoded asset metadata's +/// canonical token info. `ForeignInfo` and the coin's metadata will be updated +/// based on the encoded asset metadata payload. +/// +/// See `state` and `wrapped_asset` modules for more details. +/// +/// References: +/// https://examples.sui.io/basics/one-time-witness.html +module token_bridge::create_wrapped { + use std::ascii::{Self}; + use std::option::{Self}; + use std::type_name::{Self}; + use sui::coin::{Self, TreasuryCap, CoinMetadata}; + use sui::object::{Self, UID}; + use sui::package::{UpgradeCap}; + use sui::transfer::{Self}; + use sui::tx_context::{TxContext}; + + use token_bridge::asset_meta::{Self}; + use token_bridge::normalized_amount::{max_decimals}; + use token_bridge::state::{Self, State}; + use token_bridge::token_registry::{Self}; + use token_bridge::vaa::{Self, TokenBridgeMessage}; + use token_bridge::wrapped_asset::{Self}; + + #[test_only] + use token_bridge::version_control::{Self, V__0_2_0 as V__CURRENT}; + + /// Failed one-time witness verification. + const E_BAD_WITNESS: u64 = 0; + /// Coin witness does not equal "COIN". + const E_INVALID_COIN_MODULE_NAME: u64 = 1; + /// Decimals value exceeds `MAX_DECIMALS` from `normalized_amount`. + const E_DECIMALS_EXCEED_WRAPPED_MAX: u64 = 2; + + /// A.K.A. "coin". + const COIN_MODULE_NAME: vector = b"coin"; + + /// Container holding new coin type's `TreasuryCap` and encoded asset metadata + /// VAA, which are required to complete this asset's registration. + struct WrappedAssetSetup has key, store { + id: UID, + treasury_cap: TreasuryCap + } + + /// This method is executed within the `init` method of an untrusted module, + /// which defines a one-time witness (OTW) type (`CoinType`). OTW is + /// required to ensure that only one `TreasuryCap` exists for `CoinType`. This + /// is similar to how a `TreasuryCap` is created in `coin::create_currency`. + /// + /// Because this method is stateless (i.e. no dependency on Token Bridge's + /// `State` object), the contract defers VAA verification to + /// `complete_registration` after this method has been executed. + public fun prepare_registration( + witness: CoinType, + decimals: u8, + ctx: &mut TxContext + ): WrappedAssetSetup { + let setup = prepare_registration_internal(witness, decimals, ctx); + + // Also make sure that this witness module name is literally "coin". + let module_name = type_name::get_module(&type_name::get()); + assert!( + ascii::into_bytes(module_name) == COIN_MODULE_NAME, + E_INVALID_COIN_MODULE_NAME + ); + + setup + } + + /// This function performs the bulk of `prepare_registration`, except + /// checking the module name. This separation is useful for testing. + fun prepare_registration_internal( + witness: CoinType, + decimals: u8, + ctx: &mut TxContext + ): WrappedAssetSetup { + // Make sure there's only one instance of the type `CoinType`. This + // resembles the same check for `coin::create_currency`. + // Technically this check is redundant as it's performed by + // `coin::create_currency` below, but it doesn't hurt. + assert!(sui::types::is_one_time_witness(&witness), E_BAD_WITNESS); + + // Ensure that the decimals passed into this method do not exceed max + // decimals (see `normalized_amount` module). + assert!(decimals <= max_decimals(), E_DECIMALS_EXCEED_WRAPPED_MAX); + + // We initialise the currency with empty metadata. Later on, in the + // `complete_registration` call, when `CoinType` gets associated with a + // VAA, we update these fields. + let no_symbol = b""; + let no_name = b""; + let no_description = b""; + let no_icon_url = option::none(); + + let (treasury_cap, coin_meta) = + coin::create_currency( + witness, + decimals, + no_symbol, + no_name, + no_description, + no_icon_url, + ctx + ); + + // The CoinMetadata is turned into a shared object so that other + // functions (and wallets) can easily grab references to it. This is + // safe to do, as the metadata setters require a `TreasuryCap` for the + // coin too, which is held by the token bridge. + transfer::public_share_object(coin_meta); + + // Create `WrappedAssetSetup` object and transfer to transaction sender. + // The owner of this object will call `complete_registration` to destroy + // it. + WrappedAssetSetup { + id: object::new(ctx), + treasury_cap + } + } + + /// After executing `prepare_registration`, owner of `WrappedAssetSetup` + /// executes this method to complete this wrapped asset's registration. + /// + /// This method destroys `WrappedAssetSetup`, unpacking the `TreasuryCap` and + /// encoded asset metadata VAA. The deserialized asset metadata VAA is used + /// to update the associated `CoinMetadata`. + public fun complete_registration( + token_bridge_state: &mut State, + coin_meta: &mut CoinMetadata, + setup: WrappedAssetSetup, + coin_upgrade_cap: UpgradeCap, + msg: TokenBridgeMessage + ) { + // This capability ensures that the current build version is used. This + // call performs an additional check of whether `WrappedAssetSetup` was + // created using the current package. + let latest_only = + state::assert_latest_only_specified(token_bridge_state); + + let WrappedAssetSetup { + id, + treasury_cap + } = setup; + + // Finally destroy the object. + object::delete(id); + + // Deserialize to `AssetMeta`. + let token_meta = asset_meta::deserialize(vaa::take_payload(msg)); + + // `register_wrapped_asset` uses `token_registry::add_new_wrapped`, + // which will check whether the asset has already been registered and if + // the token chain ID is not Sui's. + // + // If both of these conditions are met, `register_wrapped_asset` will + // succeed and the new wrapped coin will be registered. + token_registry::add_new_wrapped( + state::borrow_mut_token_registry(&latest_only, token_bridge_state), + token_meta, + coin_meta, + treasury_cap, + coin_upgrade_cap + ); + } + + /// For registered wrapped assets, we can update `ForeignInfo` for a + /// given `CoinType` with a new asset meta VAA emitted from another network. + public fun update_attestation( + token_bridge_state: &mut State, + coin_meta: &mut CoinMetadata, + msg: TokenBridgeMessage + ) { + // This capability ensures that the current build version is used. + let latest_only = state::assert_latest_only(token_bridge_state); + + // Deserialize to `AssetMeta`. + let token_meta = asset_meta::deserialize(vaa::take_payload(msg)); + + // This asset must exist in the registry. + let registry = + state::borrow_mut_token_registry(&latest_only, token_bridge_state); + token_registry::assert_has(registry); + + // Now update wrapped. + wrapped_asset::update_metadata( + token_registry::borrow_mut_wrapped(registry), + coin_meta, + token_meta + ); + } + + public fun incomplete_metadata( + coin_meta: &CoinMetadata + ): bool { + use std::string::{bytes}; + use std::vector::{is_empty}; + + ( + is_empty(ascii::as_bytes(&coin::get_symbol(coin_meta))) && + is_empty(bytes(&coin::get_name(coin_meta))) && + is_empty(bytes(&coin::get_description(coin_meta))) && + std::option::is_none(&coin::get_icon_url(coin_meta)) + ) + } + + #[test_only] + public fun new_setup_test_only( + _version: Version, + witness: CoinType, + decimals: u8, + ctx: &mut TxContext + ): (WrappedAssetSetup, UpgradeCap) { + let setup = + prepare_registration_internal( + witness, + decimals, + ctx + ); + + let upgrade_cap = + sui::package::test_publish( + object::id_from_address(@token_bridge), + ctx + ); + + (setup, upgrade_cap) + } + + #[test_only] + public fun new_setup_current( + witness: CoinType, + decimals: u8, + ctx: &mut TxContext + ): (WrappedAssetSetup, UpgradeCap) { + new_setup_test_only( + version_control::current_version_test_only(), + witness, + decimals, + ctx + ) + } + + #[test_only] + public fun take_treasury_cap( + setup: WrappedAssetSetup + ): TreasuryCap { + let WrappedAssetSetup { + id, + treasury_cap + } = setup; + object::delete(id); + + treasury_cap + } +} + +#[test_only] +module token_bridge::create_wrapped_tests { + use sui::coin::{Self}; + use sui::test_scenario::{Self}; + use sui::test_utils::{Self}; + use sui::tx_context::{Self}; + use wormhole::wormhole_scenario::{parse_and_verify_vaa}; + + use token_bridge::asset_meta::{Self}; + use token_bridge::coin_wrapped_12::{Self}; + use token_bridge::coin_wrapped_7::{Self}; + use token_bridge::create_wrapped::{Self}; + use token_bridge::state::{Self}; + use token_bridge::string_utils::{Self}; + use token_bridge::token_bridge_scenario::{ + register_dummy_emitter, + return_state, + set_up_wormhole_and_token_bridge, + take_state, + two_people + }; + use token_bridge::token_registry::{Self}; + use token_bridge::vaa::{Self}; + use token_bridge::version_control::{V__0_2_0 as V__CURRENT}; + use token_bridge::wrapped_asset::{Self}; + + struct NOT_A_WITNESS has drop {} + + struct CREATE_WRAPPED_TESTS has drop {} + + #[test] + #[expected_failure(abort_code = create_wrapped::E_BAD_WITNESS)] + fun test_cannot_prepare_registration_bad_witness() { + let ctx = &mut tx_context::dummy(); + + // You shall not pass! + let wrapped_asset_setup = + create_wrapped::prepare_registration( + NOT_A_WITNESS {}, + 3, + ctx + ); + + // Clean up. + test_utils::destroy(wrapped_asset_setup); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = create_wrapped::E_INVALID_COIN_MODULE_NAME)] + fun test_cannot_prepare_registration_invalid_coin_module_name() { + let ctx = &mut tx_context::dummy(); + + // You shall not pass! + let wrapped_asset_setup = + create_wrapped::prepare_registration< + CREATE_WRAPPED_TESTS, + V__CURRENT + >( + CREATE_WRAPPED_TESTS {}, + 3, + ctx + ); + + // Clean up. + test_utils::destroy(wrapped_asset_setup); + + abort 42 + } + + #[test] + fun test_complete_and_update_attestation() { + let (caller, coin_deployer) = two_people(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register foreign emitter on chain ID == 2. + let expected_source_chain = 2; + register_dummy_emitter(scenario, expected_source_chain); + + // Ignore effects. Make sure `coin_deployer` receives + // `WrappedAssetSetup`. + test_scenario::next_tx(scenario, coin_deployer); + + // Publish coin. + let ( + wrapped_asset_setup, + upgrade_cap + ) = + create_wrapped::new_setup_current( + CREATE_WRAPPED_TESTS {}, + 8, + test_scenario::ctx(scenario) + ); + + let token_bridge_state = take_state(scenario); + + let verified_vaa = + parse_and_verify_vaa(scenario, coin_wrapped_12::encoded_vaa()); + let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa); + + let coin_meta = test_scenario::take_shared(scenario); + + create_wrapped::complete_registration( + &mut token_bridge_state, + &mut coin_meta, + wrapped_asset_setup, + upgrade_cap, + msg + ); + + let ( + token_address, + token_chain, + native_decimals, + symbol, + name + ) = asset_meta::unpack_test_only(coin_wrapped_12::token_meta()); + + // Check registry. + { + let registry = state::borrow_token_registry(&token_bridge_state); + let verified = + token_registry::verified_asset(registry); + assert!(token_registry::is_wrapped(&verified), 0); + + let asset = + token_registry::borrow_wrapped(registry); + assert!(wrapped_asset::total_supply(asset) == 0, 0); + + // Decimals are capped for this wrapped asset. + assert!(coin::get_decimals(&coin_meta) == 8, 0); + + // Check metadata against asset metadata. + let info = wrapped_asset::info(asset); + assert!(wrapped_asset::token_chain(info) == token_chain, 0); + assert!(wrapped_asset::token_address(info) == token_address, 0); + assert!( + wrapped_asset::native_decimals(info) == native_decimals, + 0 + ); + assert!(coin::get_symbol(&coin_meta) == string_utils::to_ascii(&symbol), 0); + assert!(coin::get_name(&coin_meta) == name, 0); + }; + + + // Now update metadata. + let verified_vaa = + parse_and_verify_vaa( + scenario, + coin_wrapped_12::encoded_updated_vaa() + ); + let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa); + create_wrapped::update_attestation( + &mut token_bridge_state, + &mut coin_meta, + msg + ); + + // Check updated name and symbol. + let ( + _, + _, + _, + new_symbol, + new_name + ) = asset_meta::unpack_test_only(coin_wrapped_12::updated_token_meta()); + + assert!(symbol != new_symbol, 0); + + assert!(coin::get_symbol(&coin_meta) == string_utils::to_ascii(&new_symbol), 0); + + assert!(name != new_name, 0); + assert!(coin::get_name(&coin_meta) == new_name, 0); + + test_scenario::return_shared(coin_meta); + + // Clean up. + return_state(token_bridge_state); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + #[expected_failure(abort_code = wrapped_asset::E_ASSET_META_MISMATCH)] + fun test_cannot_update_attestation_wrong_canonical_info() { + let (caller, coin_deployer) = two_people(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register foreign emitter on chain ID == 2. + let expected_source_chain = 2; + register_dummy_emitter(scenario, expected_source_chain); + + // Ignore effects. Make sure `coin_deployer` receives + // `WrappedAssetSetup`. + test_scenario::next_tx(scenario, coin_deployer); + + // Publish coin. + let ( + wrapped_asset_setup, + upgrade_cap + ) = + create_wrapped::new_setup_current( + CREATE_WRAPPED_TESTS {}, + 8, + test_scenario::ctx(scenario) + ); + + let token_bridge_state = take_state(scenario); + + let verified_vaa = + parse_and_verify_vaa(scenario, coin_wrapped_12::encoded_vaa()); + let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa); + + let coin_meta = test_scenario::take_shared(scenario); + + create_wrapped::complete_registration( + &mut token_bridge_state, + &mut coin_meta, + wrapped_asset_setup, + upgrade_cap, + msg + ); + // This VAA is for COIN_WRAPPED_7 metadata, which disagrees with + // COIN_WRAPPED_12. + let invalid_asset_meta_vaa = coin_wrapped_7::encoded_vaa(); + + let verified_vaa = + parse_and_verify_vaa(scenario, invalid_asset_meta_vaa); + let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa); + // You shall not pass! + create_wrapped::update_attestation( + &mut token_bridge_state, + &mut coin_meta, + msg + ); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = state::E_VERSION_MISMATCH)] + fun test_cannot_complete_registration_version_mismatch() { + let (caller, coin_deployer) = two_people(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register foreign emitter on chain ID == 2. + let expected_source_chain = 2; + register_dummy_emitter(scenario, expected_source_chain); + + // Ignore effects. Make sure `coin_deployer` receives + // `WrappedAssetSetup`. + test_scenario::next_tx(scenario, coin_deployer); + + // Publish coin. + let ( + wrapped_asset_setup, + upgrade_cap + ) = + create_wrapped::new_setup_test_only( + token_bridge::version_control::dummy(), + CREATE_WRAPPED_TESTS {}, + 8, + test_scenario::ctx(scenario) + ); + + let token_bridge_state = take_state(scenario); + + let verified_vaa = + parse_and_verify_vaa(scenario, coin_wrapped_12::encoded_vaa()); + let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa); + + let coin_meta = test_scenario::take_shared(scenario); + + create_wrapped::complete_registration( + &mut token_bridge_state, + &mut coin_meta, + wrapped_asset_setup, + upgrade_cap, + msg + ); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = wormhole::package_utils::E_NOT_CURRENT_VERSION)] + fun test_cannot_complete_registration_outdated_version() { + let (caller, coin_deployer) = two_people(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register foreign emitter on chain ID == 2. + let expected_source_chain = 2; + register_dummy_emitter(scenario, expected_source_chain); + + // Ignore effects. Make sure `coin_deployer` receives + // `WrappedAssetSetup`. + test_scenario::next_tx(scenario, coin_deployer); + + // Publish coin. + let ( + wrapped_asset_setup, + upgrade_cap + ) = + create_wrapped::new_setup_current( + CREATE_WRAPPED_TESTS {}, + 8, + test_scenario::ctx(scenario) + ); + + let token_bridge_state = take_state(scenario); + + let verified_vaa = + parse_and_verify_vaa(scenario, coin_wrapped_12::encoded_vaa()); + let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa); + + let coin_meta = test_scenario::take_shared(scenario); + + // Conveniently roll version back. + state::reverse_migrate_version(&mut token_bridge_state); + + // Simulate executing with an outdated build by upticking the minimum + // required version for `publish_message` to something greater than + // this build. + state::migrate_version_test_only( + &mut token_bridge_state, + token_bridge::version_control::previous_version_test_only(), + token_bridge::version_control::next_version() + ); + + // You shall not pass! + create_wrapped::complete_registration( + &mut token_bridge_state, + &mut coin_meta, + wrapped_asset_setup, + upgrade_cap, + msg + ); + + abort 42 + } +} diff --git a/sui/token_bridge/sources/datatypes/normalized_amount.move b/sui/token_bridge/sources/datatypes/normalized_amount.move new file mode 100644 index 000000000..e63d3d296 --- /dev/null +++ b/sui/token_bridge/sources/datatypes/normalized_amount.move @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements a container that stores the token transfer amount +/// encoded in a Token Bridge message. These amounts are capped at 8 decimals. +/// This means that any amount of a coin whose metadata defines its decimals +/// as some value greater than 8, the encoded amount will be normalized to +/// eight decimals (which will lead to some residual amount after the transfer). +/// For inbound transfers, this amount will be denormalized (scaled by the same +/// decimal difference). +module token_bridge::normalized_amount { + use sui::math::{Self}; + use wormhole::bytes32::{Self}; + use wormhole::cursor::{Cursor}; + + /// The amounts in the token bridge payload are truncated to 8 decimals + /// in each of the contracts when sending tokens out, so there's no + /// precision beyond 10^-8. We could preserve the original number of + /// decimals when creating wrapped assets, and "untruncate" the amounts + /// on the way out by scaling back appropriately. This is what most + /// other chains do, but untruncating from 8 decimals to 18 decimals + /// loses log2(10^10) ~ 33 bits of precision, which we cannot afford on + /// Aptos (and Solana), as the coin type only has 64bits to begin with. + /// Contrast with Ethereum, where amounts are 256 bits. + /// So we cap the maximum decimals at 8 when creating a wrapped token. + const MAX_DECIMALS: u8 = 8; + + /// Container holding the value decoded from a Token Bridge transfer. + struct NormalizedAmount has store, copy, drop { + value: u64 + } + + public fun max_decimals(): u8 { + MAX_DECIMALS + } + + /// Utility function to cap decimal amount to 8. + public fun cap_decimals(decimals: u8): u8 { + if (decimals > MAX_DECIMALS) { + MAX_DECIMALS + } else { + decimals + } + } + + /// Create new `NormalizedAmount` of zero. + public fun default(): NormalizedAmount { + new(0) + } + + /// Retrieve underlying value. + public fun value(self: &NormalizedAmount): u64 { + self.value + } + + /// Retrieve underlying value as `u256`. + public fun to_u256(norm: NormalizedAmount): u256 { + (take_value(norm) as u256) + } + + /// Create new `NormalizedAmount` using raw amount and specified decimals. + public fun from_raw(amount: u64, decimals: u8): NormalizedAmount { + if (amount == 0) { + default() + } else if (decimals > MAX_DECIMALS) { + new(amount / math::pow(10, decimals - MAX_DECIMALS)) + } else { + new(amount) + } + } + + /// Denormalize `NormalizedAmount` using specified decimals. + public fun to_raw(norm: NormalizedAmount, decimals: u8): u64 { + let value = take_value(norm); + + if (value > 0 && decimals > MAX_DECIMALS) { + value * math::pow(10, decimals - MAX_DECIMALS) + } else { + value + } + } + + /// Transform `NormalizedAmount` to serialized (big-endian) u256. + public fun to_bytes(norm: NormalizedAmount): vector { + bytes32::to_bytes(bytes32::from_u256_be(to_u256(norm))) + } + + /// Read 32 bytes from `Cursor` and deserialize to u64, ensuring no + /// overflow. + public fun take_bytes(cur: &mut Cursor): NormalizedAmount { + // Amounts are encoded with 32 bytes. + new(bytes32::to_u64_be(bytes32::take_bytes(cur))) + } + + fun new(value: u64): NormalizedAmount { + NormalizedAmount { + value + } + } + + fun take_value(norm: NormalizedAmount): u64 { + let NormalizedAmount { value } = norm; + value + } +} + +#[test_only] +module token_bridge::normalized_amount_test { + use wormhole::bytes::{Self}; + use wormhole::cursor::{Self}; + + use token_bridge::normalized_amount::{Self}; + + #[test] + fun test_from_and_to_raw() { + // Use decimals > 8 to check truncation. + let decimals = 9; + let raw_amount = 12345678910111; + let normalized = normalized_amount::from_raw(raw_amount, decimals); + let denormalized = normalized_amount::to_raw(normalized, decimals); + assert!(denormalized == 10 * (raw_amount / 10), 0); + + // Use decimals <= 8 to check raw amount recovery. + let decimals = 5; + let normalized = normalized_amount::from_raw(raw_amount, decimals); + let denormalized = normalized_amount::to_raw(normalized, decimals); + assert!(denormalized == raw_amount, 0); + } + + #[test] + fun test_take_bytes() { + let cur = + cursor::new( + x"000000000000000000000000000000000000000000000000ffffffffffffffff" + ); + + let norm = normalized_amount::take_bytes(&mut cur); + assert!( + normalized_amount::value(&norm) == ((1u256 << 64) - 1 as u64), + 0 + ); + + // Clean up. + cursor::destroy_empty(cur); + } + + #[test] + #[expected_failure(abort_code = wormhole::bytes32::E_U64_OVERFLOW)] + fun test_cannot_take_bytes_overflow() { + let encoded_overflow = + x"0000000000000000000000000000000000000000000000010000000000000000"; + + let amount = { + let cur = cursor::new(encoded_overflow); + let value = bytes::take_u256_be(&mut cur); + cursor::destroy_empty(cur); + value + }; + assert!(amount == (1 << 64), 0); + + let cur = cursor::new(encoded_overflow); + + // You shall not pass! + normalized_amount::take_bytes(&mut cur); + + abort 42 + } +} diff --git a/sui/token_bridge/sources/dynamic_set.move b/sui/token_bridge/sources/dynamic_set.move deleted file mode 100644 index a7f1a7dd8..000000000 --- a/sui/token_bridge/sources/dynamic_set.move +++ /dev/null @@ -1,45 +0,0 @@ -/// This module recovers a Diem-style storage model where objects are collected -/// into a heterogeneous global storage, identified by their type. -/// -/// Under the hood, it uses dynamic object fields, but set up in a way that the -/// key is derived from the value's type. -module token_bridge::dynamic_set { - use sui::dynamic_object_field as ofield; - use sui::object::{UID}; - - /// Wrap the value type. Avoids key collisions with other uses of dynamic - /// fields. - struct Wrapper has copy, drop, store { - } - - public fun add( - object: &mut UID, - value: Value, - ) { - ofield::add(object, Wrapper{}, value) - } - - public fun borrow( - object: &UID, - ): &Value { - ofield::borrow(object, Wrapper{}) - } - - public fun borrow_mut( - object: &mut UID, - ): &mut Value { - ofield::borrow_mut(object, Wrapper{}) - } - - public fun remove( - object: &mut UID, - ): Value { - ofield::remove(object, Wrapper{}) - } - - public fun exists_( - object: &UID, - ): bool { - ofield::exists_>(object, Wrapper{}) - } -} diff --git a/sui/token_bridge/sources/governance/register_chain.move b/sui/token_bridge/sources/governance/register_chain.move new file mode 100644 index 000000000..90af5c1fb --- /dev/null +++ b/sui/token_bridge/sources/governance/register_chain.move @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements handling a governance VAA to enact registering a +/// foreign Token Bridge for a particular chain ID. +module token_bridge::register_chain { + use sui::table::{Self}; + use wormhole::bytes::{Self}; + use wormhole::cursor::{Self}; + use wormhole::external_address::{Self, ExternalAddress}; + use wormhole::governance_message::{Self, DecreeTicket, DecreeReceipt}; + + use token_bridge::state::{Self, State, LatestOnly}; + + /// Cannot register chain ID == 0. + const E_INVALID_EMITTER_CHAIN: u64 = 0; + /// Emitter already exists for a given chain ID. + const E_EMITTER_ALREADY_REGISTERED: u64 = 1; + + /// Specific governance payload ID (action) for registering foreign Token + /// Bridge contract address. + const ACTION_REGISTER_CHAIN: u8 = 1; + + struct GovernanceWitness has drop {} + + struct RegisterChain { + chain: u16, + contract_address: ExternalAddress, + } + + public fun authorize_governance( + token_bridge_state: &State + ): DecreeTicket { + governance_message::authorize_verify_global( + GovernanceWitness {}, + state::governance_chain(token_bridge_state), + state::governance_contract(token_bridge_state), + state::governance_module(), + ACTION_REGISTER_CHAIN + ) + } + + public fun register_chain( + token_bridge_state: &mut State, + receipt: DecreeReceipt + ): ( + u16, + ExternalAddress + ) { + // This capability ensures that the current build version is used. + let latest_only = state::assert_latest_only(token_bridge_state); + + let payload = + governance_message::take_payload( + state::borrow_mut_consumed_vaas( + &latest_only, + token_bridge_state + ), + receipt + ); + + handle_register_chain(&latest_only, token_bridge_state, payload) + } + + fun handle_register_chain( + latest_only: &LatestOnly, + token_bridge_state: &mut State, + governance_payload: vector + ): ( + u16, + ExternalAddress + ) { + // Deserialize the payload as amount to change the Wormhole fee. + let RegisterChain { + chain, + contract_address + } = deserialize(governance_payload); + + register_new_emitter( + latest_only, + token_bridge_state, + chain, + contract_address + ); + + (chain, contract_address) + } + + fun deserialize(payload: vector): RegisterChain { + let cur = cursor::new(payload); + + // This amount cannot be greater than max u64. + let chain = bytes::take_u16_be(&mut cur); + let contract_address = external_address::take_bytes(&mut cur); + + cursor::destroy_empty(cur); + + RegisterChain { chain, contract_address} + } + + /// Add a new Token Bridge emitter to the registry. This method will abort + /// if an emitter is already registered for a particular chain ID. + /// + /// See `register_chain` module for more info. + fun register_new_emitter( + latest_only: &LatestOnly, + token_bridge_state: &mut State, + chain: u16, + contract_address: ExternalAddress + ) { + assert!(chain != 0, E_INVALID_EMITTER_CHAIN); + + let registry = + state::borrow_mut_emitter_registry(latest_only, token_bridge_state); + assert!( + !table::contains(registry, chain), + E_EMITTER_ALREADY_REGISTERED + ); + table::add(registry, chain, contract_address); + } + + #[test_only] + public fun register_new_emitter_test_only( + token_bridge_state: &mut State, + chain: u16, + contract_address: ExternalAddress + ) { + // This capability ensures that the current build version is used. + let latest_only = state::assert_latest_only(token_bridge_state); + + register_new_emitter( + &latest_only, + token_bridge_state, + chain, + contract_address + ); + } + + #[test_only] + public fun action(): u8 { + ACTION_REGISTER_CHAIN + } +} + +#[test_only] +module token_bridge::register_chain_tests { + use sui::table::{Self}; + use sui::test_scenario::{Self}; + use wormhole::bytes::{Self}; + use wormhole::cursor::{Self}; + use wormhole::external_address::{Self}; + use wormhole::governance_message::{Self}; + use wormhole::wormhole_scenario::{ + parse_and_verify_vaa, + verify_governance_vaa + }; + + use token_bridge::register_chain::{Self}; + use token_bridge::state::{Self}; + use token_bridge::token_bridge_scenario::{ + person, + return_state, + set_up_wormhole_and_token_bridge, + take_state + }; + + const VAA_REGISTER_CHAIN_1: vector = + x"01000000000100dd8cf046ad6dd17b2b5130d236b3545350899ac33b5c9e93e4d8c3e0da718a351c3f76cb9ddb15a0f0d7db7b1dded2b5e79c2f6e76dde6d8ed4bcb9cb461eb480100bc614e0000000000010000000000000000000000000000000000000000000000000000000000000004000000000000000101000000000000000000000000000000000000000000546f6b656e4272696467650100000002000000000000000000000000deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; + const VAA_REGISTER_SAME_CHAIN: vector = + x"01000000000100847ca782db7616135de4a835ed5b12ba7946bbd39f70ecd9912ec55bdc9cb6c6215c98d6ad5c8d7253c2bb0fb0f8df0dc6591408c366cf0c09e58abcfb8c0abe0000bc614e0000000000010000000000000000000000000000000000000000000000000000000000000004000000000000000101000000000000000000000000000000000000000000546f6b656e427269646765010000000200000000000000000000000000000000000000000000000000000000deafbeef"; + + #[test] + fun test_register_chain() { + // Testing this method. + use token_bridge::register_chain::{register_chain}; + + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + // Initialize Wormhole and Token Bridge. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Prepare test to execute `set_fee`. + test_scenario::next_tx(scenario, caller); + + let token_bridge_state = take_state(scenario); + + // Check that the emitter is not registered. + let expected_chain = 2; + { + let registry = state::borrow_emitter_registry(&token_bridge_state); + assert!(!table::contains(registry, expected_chain), 0); + }; + + let verified_vaa = parse_and_verify_vaa(scenario, VAA_REGISTER_CHAIN_1); + let ticket = register_chain::authorize_governance(&token_bridge_state); + let receipt = + verify_governance_vaa(scenario, verified_vaa, ticket); + let ( + chain, + contract_address + ) = register_chain(&mut token_bridge_state, receipt); + assert!(chain == expected_chain, 0); + + let expected_contract = + external_address::from_address( + @0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef + ); + assert!(contract_address == expected_contract, 0); + { + let registry = state::borrow_emitter_registry(&token_bridge_state); + assert!(*table::borrow(registry, expected_chain) == expected_contract, 0); + }; + + // Clean up. + return_state(token_bridge_state); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + #[expected_failure(abort_code = register_chain::E_EMITTER_ALREADY_REGISTERED)] + fun test_cannot_register_chain_already_registered() { + // Testing this method. + use token_bridge::register_chain::{register_chain}; + + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + // Initialize Wormhole and Token Bridge. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Prepare test to execute `set_fee`. + test_scenario::next_tx(scenario, caller); + + let token_bridge_state = take_state(scenario); + + let verified_vaa = parse_and_verify_vaa(scenario, VAA_REGISTER_CHAIN_1); + let ticket = register_chain::authorize_governance(&token_bridge_state); + let receipt = + verify_governance_vaa(scenario, verified_vaa, ticket); + let ( + chain, + _ + ) = register_chain(&mut token_bridge_state, receipt); + + // Check registry. + let expected_contract = + *table::borrow( + state::borrow_emitter_registry(&token_bridge_state), + chain + ); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + let verified_vaa = + parse_and_verify_vaa(scenario, VAA_REGISTER_SAME_CHAIN); + let payload = + governance_message::take_decree( + wormhole::vaa::payload(&verified_vaa) + ); + let cur = cursor::new(payload); + + // Show this payload is attempting to register the same chain ID. + let another_chain = bytes::take_u16_be(&mut cur); + assert!(chain == another_chain, 0); + + let another_contract = external_address::take_bytes(&mut cur); + assert!(another_contract != expected_contract, 0); + + // No more payload to read. + cursor::destroy_empty(cur); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + let ticket = register_chain::authorize_governance(&token_bridge_state); + let receipt = + verify_governance_vaa(scenario, verified_vaa, ticket); + + // You shall not pass! + register_chain(&mut token_bridge_state, receipt); + + abort 42 + } +} + + + + diff --git a/sui/token_bridge/sources/governance/upgrade_contract.move b/sui/token_bridge/sources/governance/upgrade_contract.move new file mode 100644 index 000000000..e03729b02 --- /dev/null +++ b/sui/token_bridge/sources/governance/upgrade_contract.move @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements handling a governance VAA to enact upgrading the +/// Token Bridge contract to a new build. The procedure to upgrade this contract +/// requires a Programmable Transaction, which includes the following procedure: +/// 1. Load new build. +/// 2. Authorize upgrade. +/// 3. Upgrade. +/// 4. Commit upgrade. +module token_bridge::upgrade_contract { + use sui::object::{ID}; + use sui::package::{UpgradeReceipt, UpgradeTicket}; + use wormhole::bytes32::{Self, Bytes32}; + use wormhole::cursor::{Self}; + use wormhole::governance_message::{Self, DecreeTicket, DecreeReceipt}; + + use token_bridge::state::{Self, State}; + + friend token_bridge::migrate; + + /// Digest is all zeros. + const E_DIGEST_ZERO_BYTES: u64 = 0; + + /// Specific governance payload ID (action) to complete upgrading the + /// contract. + const ACTION_UPGRADE_CONTRACT: u8 = 2; + + struct GovernanceWitness has drop {} + + // Event reflecting package upgrade. + struct ContractUpgraded has drop, copy { + old_contract: ID, + new_contract: ID + } + + struct UpgradeContract { + digest: Bytes32 + } + + public fun authorize_governance( + token_bridge_state: &State + ): DecreeTicket { + governance_message::authorize_verify_local( + GovernanceWitness {}, + state::governance_chain(token_bridge_state), + state::governance_contract(token_bridge_state), + state::governance_module(), + ACTION_UPGRADE_CONTRACT + ) + } + + /// Redeem governance VAA to issue an `UpgradeTicket` for the upgrade given + /// a contract upgrade VAA. This governance message is only relevant for Sui + /// because a contract upgrade is only relevant to one particular network + /// (in this case Sui), whose build digest is encoded in this message. + public fun authorize_upgrade( + token_bridge_state: &mut State, + receipt: DecreeReceipt + ): UpgradeTicket { + // current package checking when consuming VAA hashes. This is because + // upgrades are protected by the Sui VM, enforcing the latest package + // is the one performing the upgrade. + let consumed = + state::borrow_mut_consumed_vaas_unchecked(token_bridge_state); + + // And consume. + let payload = governance_message::take_payload(consumed, receipt); + + // Proceed with processing new implementation version. + handle_upgrade_contract(token_bridge_state, payload) + } + + /// Finalize the upgrade that ran to produce the given `receipt`. This + /// method invokes `state::commit_upgrade` which interacts with + /// `sui::package`. + public fun commit_upgrade( + self: &mut State, + receipt: UpgradeReceipt, + ) { + let (old_contract, new_contract) = state::commit_upgrade(self, receipt); + + // Emit an event reflecting package ID change. + sui::event::emit(ContractUpgraded { old_contract, new_contract }); + } + + /// Privileged method only to be used by this module and `migrate` module. + /// + /// During migration, we make sure that the digest equals what we expect by + /// passing in the same VAA used to upgrade the package. + public(friend) fun take_digest(governance_payload: vector): Bytes32 { + // Deserialize the payload as the build digest. + let UpgradeContract { digest } = deserialize(governance_payload); + + digest + } + + fun handle_upgrade_contract( + wormhole_state: &mut State, + payload: vector + ): UpgradeTicket { + state::authorize_upgrade(wormhole_state, take_digest(payload)) + } + + fun deserialize(payload: vector): UpgradeContract { + let cur = cursor::new(payload); + + // This amount cannot be greater than max u64. + let digest = bytes32::take_bytes(&mut cur); + assert!(bytes32::is_nonzero(&digest), E_DIGEST_ZERO_BYTES); + + cursor::destroy_empty(cur); + + UpgradeContract { digest } + } + + #[test_only] + public fun action(): u8 { + ACTION_UPGRADE_CONTRACT + } +} + +#[test_only] +module token_bridge::upgrade_contract_tests { + // TODO +} diff --git a/sui/token_bridge/sources/messages/asset_meta.move b/sui/token_bridge/sources/messages/asset_meta.move new file mode 100644 index 000000000..30f03f2cd --- /dev/null +++ b/sui/token_bridge/sources/messages/asset_meta.move @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements serialization and deserialization for asset metadata, +/// which is a specific Wormhole message payload for Token Bridge. +module token_bridge::asset_meta { + use std::string::{Self, String}; + use std::vector::{Self}; + use sui::coin::{Self, CoinMetadata}; + use wormhole::bytes::{Self}; + use wormhole::bytes32::{Self}; + use wormhole::external_address::{Self, ExternalAddress}; + use wormhole::cursor::{Self}; + use wormhole::state::{chain_id}; + + use token_bridge::native_asset::{Self}; + + friend token_bridge::attest_token; + friend token_bridge::create_wrapped; + friend token_bridge::wrapped_asset; + + /// Message payload is not `AssetMeta`. + const E_INVALID_PAYLOAD: u64 = 0; + + /// Message identifier. + const PAYLOAD_ID: u8 = 2; + + /// Container that warehouses asset metadata information. This struct is + /// used only by `attest_token` and `create_wrapped` modules. + struct AssetMeta { + /// Address of the token. + token_address: ExternalAddress, + /// Chain ID of the token. + token_chain: u16, + /// Number of decimals of the token. + native_decimals: u8, + /// Symbol of the token (UTF-8). + /// TODO(csongor): maybe turn these into String32s? + symbol: String, + /// Name of the token (UTF-8). + name: String, + } + + + public(friend) fun from_metadata(metadata: &CoinMetadata): AssetMeta { + AssetMeta { + token_address: native_asset::canonical_address(metadata), + token_chain: chain_id(), + native_decimals: coin::get_decimals(metadata), + symbol: string::from_ascii(coin::get_symbol(metadata)), + name: coin::get_name(metadata) + } + } + + #[test_only] + public fun from_metadata_test_only(metadata: &CoinMetadata): AssetMeta { + from_metadata(metadata) + } + + public(friend) fun unpack( + meta: AssetMeta + ): ( + ExternalAddress, + u16, + u8, + String, + String + ) { + let AssetMeta { + token_address, + token_chain, + native_decimals, + symbol, + name + } = meta; + + ( + token_address, + token_chain, + native_decimals, + symbol, + name + ) + } + + + #[test_only] + public fun unpack_test_only( + meta: AssetMeta + ): ( + ExternalAddress, + u16, + u8, + String, + String + ) { + unpack(meta) + } + + public fun token_chain(self: &AssetMeta): u16 { + self.token_chain + } + + public fun token_address(self: &AssetMeta): ExternalAddress { + self.token_address + } + + public(friend) fun serialize(meta: AssetMeta): vector { + let ( + token_address, + token_chain, + native_decimals, + symbol, + name + ) = unpack(meta); + + let buf = vector::empty(); + bytes::push_u8(&mut buf, PAYLOAD_ID); + vector::append(&mut buf, external_address::to_bytes(token_address)); + bytes::push_u16_be(&mut buf, token_chain); + bytes::push_u8(&mut buf, native_decimals); + vector::append( + &mut buf, + bytes32::to_bytes(bytes32::from_utf8(symbol)) + ); + vector::append( + &mut buf, + bytes32::to_bytes(bytes32::from_utf8(name)) + ); + + buf + } + + #[test_only] + public fun serialize_test_only(meta: AssetMeta): vector { + serialize(meta) + } + + public(friend) fun deserialize(buf: vector): AssetMeta { + let cur = cursor::new(buf); + assert!(bytes::take_u8(&mut cur) == PAYLOAD_ID, E_INVALID_PAYLOAD); + let token_address = external_address::take_bytes(&mut cur); + let token_chain = bytes::take_u16_be(&mut cur); + let native_decimals = bytes::take_u8(&mut cur); + let symbol = bytes32::to_utf8(bytes32::take_bytes(&mut cur)); + let name = bytes32::to_utf8(bytes32::take_bytes(&mut cur)); + cursor::destroy_empty(cur); + + AssetMeta { + token_address, + token_chain, + native_decimals, + symbol, + name + } + } + + #[test_only] + public fun deserialize_test_only(buf: vector): AssetMeta { + deserialize(buf) + } + + #[test_only] + public fun new( + token_address: ExternalAddress, + token_chain: u16, + native_decimals: u8, + symbol: String, + name: String, + ): AssetMeta { + AssetMeta { + token_address, + token_chain, + native_decimals, + symbol, + name + } + } + + #[test_only] + public fun native_decimals(self: &AssetMeta): u8 { + self.native_decimals + } + + #[test_only] + public fun symbol(self: &AssetMeta): String { + self.symbol + } + + #[test_only] + public fun name(self: &AssetMeta): String { + self.name + } + + #[test_only] + public fun destroy(token_meta: AssetMeta) { + unpack(token_meta); + } + + #[test_only] + public fun payload_id(): u8 { + PAYLOAD_ID + } +} + +#[test_only] +module token_bridge::asset_meta_tests { + use std::string::{Self}; + use wormhole::external_address::{Self}; + use wormhole::vaa::{Self}; + + use token_bridge::asset_meta::{Self}; + + #[test] + fun test_serialize_deserialize() { + let token_address = external_address::from_address(@0x1122); + let symbol = string::utf8(b"a creative symbol"); + let name = string::utf8(b"a creative name"); + let asset_meta = asset_meta::new( + token_address, //token address + 3, // token chain + 4, //native decimals + symbol, // symbol + name, // name + ); + // Serialize and deserialize TransferWithPayload object. + let se = asset_meta::serialize_test_only(asset_meta); + let de = asset_meta::deserialize_test_only(se); + + // Test that the object fields are unchanged. + assert!(asset_meta::token_chain(&de) == 3, 0); + assert!(asset_meta::token_address(&de) == token_address, 0); + assert!(asset_meta::native_decimals(&de) == 4, 0); + assert!(asset_meta::symbol(&de) == symbol, 0); + assert!(asset_meta::name(&de) == name, 0); + + // Clean up. + asset_meta::destroy(de); + } + + #[test] + fun test_create_wrapped_12() { + use token_bridge::dummy_message::{encoded_asset_meta_vaa_foreign_12}; + + let payload = + vaa::peel_payload_from_vaa(&encoded_asset_meta_vaa_foreign_12()); + + let token_meta = asset_meta::deserialize_test_only(payload); + let serialized = asset_meta::serialize_test_only(token_meta); + assert!(payload == serialized, 0); + } +} diff --git a/sui/token_bridge/sources/messages/transfer.move b/sui/token_bridge/sources/messages/transfer.move new file mode 100644 index 000000000..190afad9d --- /dev/null +++ b/sui/token_bridge/sources/messages/transfer.move @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements serialization and deserialization for token transfer +/// with an optional relayer fee. This message is a specific Wormhole message +/// payload for Token Bridge. +/// +/// When this transfer is redeemed, the relayer fee will be subtracted from the +/// transfer amount. If the transaction sender is the same address of the +/// recipient, the recipient will collect the full amount. +/// +/// See `transfer_tokens` and `complete_transfer` modules for more details. +module token_bridge::transfer { + use std::vector::{Self}; + use wormhole::bytes::{Self}; + use wormhole::cursor::{Self}; + use wormhole::external_address::{Self, ExternalAddress}; + + use token_bridge::normalized_amount::{Self, NormalizedAmount}; + + friend token_bridge::complete_transfer; + friend token_bridge::transfer_tokens; + + /// Message payload is not `Transfer`. + const E_INVALID_PAYLOAD: u64 = 0; + + /// Message identifier. + const PAYLOAD_ID: u8 = 1; + + /// Container that warehouses transfer information. This struct is used only + /// by `transfer_tokens` and `complete_transfer` modules. + struct Transfer { + // Amount being transferred. + amount: NormalizedAmount, + // Address of the token. Left-zero-padded if shorter than 32 bytes. + token_address: ExternalAddress, + // Chain ID of the token. + token_chain: u16, + // Address of the recipient. Left-zero-padded if shorter than 32 bytes. + recipient: ExternalAddress, + // Chain ID of the recipient. + recipient_chain: u16, + // Amount of tokens that the user is willing to pay as relayer fee. + // Must be <= amount. + relayer_fee: NormalizedAmount, + } + + /// Create new `Transfer`. + public(friend) fun new( + amount: NormalizedAmount, + token_address: ExternalAddress, + token_chain: u16, + recipient: ExternalAddress, + recipient_chain: u16, + relayer_fee: NormalizedAmount, + ): Transfer { + Transfer { + amount, + token_address, + token_chain, + recipient, + recipient_chain, + relayer_fee, + } + } + + #[test_only] + public fun new_test_only( + amount: NormalizedAmount, + token_address: ExternalAddress, + token_chain: u16, + recipient: ExternalAddress, + recipient_chain: u16, + relayer_fee: NormalizedAmount, + ): Transfer { + new( + amount, + token_address, + token_chain, + recipient, + recipient_chain, + relayer_fee + ) + } + + /// Decompose `Transfer` into its members. + public(friend) fun unpack( + transfer: Transfer + ): ( + NormalizedAmount, + ExternalAddress, + u16, + ExternalAddress, + u16, + NormalizedAmount + ) { + let Transfer { + amount, + token_address, + token_chain, + recipient, + recipient_chain, + relayer_fee, + } = transfer; + + ( + amount, + token_address, + token_chain, + recipient, + recipient_chain, + relayer_fee + ) + } + + #[test_only] + public fun unpack_test_only( + transfer: Transfer + ): ( + NormalizedAmount, + ExternalAddress, + u16, + ExternalAddress, + u16, + NormalizedAmount + ) { + unpack(transfer) + } + + /// Decode Wormhole message payload as `Transfer`. + public(friend) fun deserialize(buf: vector): Transfer { + let cur = cursor::new(buf); + assert!(bytes::take_u8(&mut cur) == PAYLOAD_ID, E_INVALID_PAYLOAD); + + let amount = normalized_amount::take_bytes(&mut cur); + let token_address = external_address::take_bytes(&mut cur); + let token_chain = bytes::take_u16_be(&mut cur); + let recipient = external_address::take_bytes(&mut cur); + let recipient_chain = bytes::take_u16_be(&mut cur); + let relayer_fee = normalized_amount::take_bytes(&mut cur); + cursor::destroy_empty(cur); + + Transfer { + amount, + token_address, + token_chain, + recipient, + recipient_chain, + relayer_fee, + } + } + + #[test_only] + public fun deserialize_test_only(buf: vector): Transfer { + deserialize(buf) + } + + /// Encode `Transfer` for Wormhole message payload. + public(friend) fun serialize(transfer: Transfer): vector { + let ( + amount, + token_address, + token_chain, + recipient, + recipient_chain, + relayer_fee, + ) = unpack(transfer); + + let buf = vector::empty(); + bytes::push_u8(&mut buf, PAYLOAD_ID); + vector::append(&mut buf, normalized_amount::to_bytes(amount)); + vector::append(&mut buf, external_address::to_bytes(token_address)); + bytes::push_u16_be(&mut buf, token_chain); + vector::append(&mut buf, external_address::to_bytes(recipient)); + bytes::push_u16_be(&mut buf, recipient_chain); + vector::append(&mut buf, normalized_amount::to_bytes(relayer_fee)); + + buf + } + + #[test_only] + public fun serialize_test_only(transfer: Transfer): vector { + serialize(transfer) + } + + #[test_only] + public fun amount(self: &Transfer): NormalizedAmount { + self.amount + } + + #[test_only] + public fun raw_amount(self: &Transfer, decimals: u8): u64 { + normalized_amount::to_raw(self.amount, decimals) + } + + #[test_only] + public fun token_address(self: &Transfer): ExternalAddress { + self.token_address + } + + #[test_only] + public fun token_chain(self: &Transfer): u16 { + self.token_chain + } + + #[test_only] + public fun recipient(self: &Transfer): ExternalAddress { + self.recipient + } + + #[test_only] + public fun recipient_as_address(self: &Transfer): address { + external_address::to_address(self.recipient) + } + + #[test_only] + public fun recipient_chain(self: &Transfer): u16 { + self.recipient_chain + } + + #[test_only] + public fun relayer_fee(self: &Transfer): NormalizedAmount { + self.relayer_fee + } + + #[test_only] + public fun raw_relayer_fee(self: &Transfer, decimals: u8): u64 { + normalized_amount::to_raw(self.relayer_fee, decimals) + } + + #[test_only] + public fun destroy(transfer: Transfer) { + unpack(transfer); + } + + #[test_only] + public fun payload_id(): u8 { + PAYLOAD_ID + } +} + +#[test_only] +module token_bridge::transfer_tests { + use std::vector::{Self}; + use wormhole::external_address::{Self}; + + use token_bridge::dummy_message::{Self}; + use token_bridge::transfer::{Self}; + use token_bridge::normalized_amount::{Self}; + + #[test] + fun test_serialize_deserialize() { + let decimals = 8; + let expected_amount = normalized_amount::from_raw(234567890, decimals); + let expected_token_address = external_address::from_address(@0xbeef); + let expected_token_chain = 1; + let expected_recipient = external_address::from_address(@0xcafe); + let expected_recipient_chain = 7; + let expected_relayer_fee = + normalized_amount::from_raw(123456789, decimals); + + let serialized = + transfer::serialize_test_only( + transfer::new_test_only( + expected_amount, + expected_token_address, + expected_token_chain, + expected_recipient, + expected_recipient_chain, + expected_relayer_fee, + ) + ); + assert!(serialized == dummy_message::encoded_transfer(), 0); + + let ( + amount, + token_address, + token_chain, + recipient, + recipient_chain, + relayer_fee + ) = transfer::unpack_test_only( + transfer::deserialize_test_only(serialized) + ); + assert!(amount == expected_amount, 0); + assert!(token_address == expected_token_address, 0); + assert!(token_chain == expected_token_chain, 0); + assert!(recipient == expected_recipient, 0); + assert!(recipient_chain == expected_recipient_chain, 0); + assert!(relayer_fee == expected_relayer_fee, 0); + } + + #[test] + #[expected_failure(abort_code = transfer::E_INVALID_PAYLOAD)] + fun test_cannot_deserialize_invalid_payload() { + let invalid_payload = dummy_message::encoded_transfer_with_payload(); + + // Show that the first byte is not the expected payload ID. + assert!( + *vector::borrow(&invalid_payload, 0) != transfer::payload_id(), + 0 + ); + + // You shall not pass! + let parsed = transfer::deserialize_test_only(invalid_payload); + + // Clean up. + transfer::destroy(parsed); + + abort 42 + } +} diff --git a/sui/token_bridge/sources/messages/transfer_with_payload.move b/sui/token_bridge/sources/messages/transfer_with_payload.move new file mode 100644 index 000000000..318061687 --- /dev/null +++ b/sui/token_bridge/sources/messages/transfer_with_payload.move @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements serialization and deserialization for token transfer +/// with an arbitrary payload. This message is a specific Wormhole message +/// payload for Token Bridge. +/// +/// In order to redeem these types of transfers, one must have an `EmitterCap` +/// and the specified `redeemer` must agree with this capability. +/// +/// See `transfer_tokens_with_payload` and `complete_transfer_with_payload` +/// modules for more details. +module token_bridge::transfer_with_payload { + use std::vector::{Self}; + use sui::object::{Self, ID}; + use wormhole::bytes::{Self}; + use wormhole::cursor::{Self}; + use wormhole::external_address::{Self, ExternalAddress}; + + use token_bridge::normalized_amount::{Self, NormalizedAmount}; + + friend token_bridge::transfer_tokens_with_payload; + + /// Message payload is not `TransferWithPayload`. + const E_INVALID_PAYLOAD: u64 = 0; + + /// Message identifier. + const PAYLOAD_ID: u8 = 3; + + /// Container that warehouses transfer information, including arbitrary + /// payload. + /// + /// NOTE: This struct has `drop` because we do not want to require an + /// integrator receiving transfer information to have to manually destroy. + struct TransferWithPayload has drop { + // Transfer amount. + amount: NormalizedAmount, + // Address of the token. Left-zero-padded if shorter than 32 bytes. + token_address: ExternalAddress, + // Chain ID of the token. + token_chain: u16, + // A.K.A. 32-byte representation of `EmitterCap`. + redeemer: ExternalAddress, + // Chain ID of the redeemer. + redeemer_chain: u16, + // Address of the message sender. + sender: ExternalAddress, + // An arbitrary payload. + payload: vector, + } + + /// Create new `TransferWithPayload` using a Token Bridge integrator's + /// emitter cap ID as the sender. + public(friend) fun new( + sender: ID, + amount: NormalizedAmount, + token_address: ExternalAddress, + token_chain: u16, + redeemer: ExternalAddress, + redeemer_chain: u16, + payload: vector + ): TransferWithPayload { + TransferWithPayload { + amount, + token_address, + token_chain, + redeemer, + redeemer_chain, + sender: external_address::from_id(sender), + payload + } + } + + #[test_only] + public fun new_test_only( + sender: ID, + amount: NormalizedAmount, + token_address: ExternalAddress, + token_chain: u16, + redeemer: ExternalAddress, + redeemer_chain: u16, + payload: vector + ): TransferWithPayload { + new( + sender, + amount, + token_address, + token_chain, + redeemer, + redeemer_chain, + payload + ) + } + + /// Destroy `TransferWithPayload` and take only its payload. + public fun take_payload(transfer: TransferWithPayload): vector { + let TransferWithPayload { + amount: _, + token_address: _, + token_chain: _, + redeemer: _, + redeemer_chain: _, + sender: _, + payload + } = transfer; + + payload + } + + /// Retrieve normalized amount of token transfer. + public fun amount(self: &TransferWithPayload): NormalizedAmount { + self.amount + } + + // Retrieve token's canonical address. + public fun token_address(self: &TransferWithPayload): ExternalAddress { + self.token_address + } + + /// Retrieve token's canonical chain ID. + public fun token_chain(self: &TransferWithPayload): u16 { + self.token_chain + } + + /// Retrieve redeemer. + public fun redeemer(self: &TransferWithPayload): ExternalAddress { + self.redeemer + } + + // Retrieve redeemer as `ID`. + public fun redeemer_id(self: &TransferWithPayload): ID { + object::id_from_bytes(external_address::to_bytes(self.redeemer)) + } + + /// Retrieve target chain for redeemer. + public fun redeemer_chain(self: &TransferWithPayload): u16 { + self.redeemer_chain + } + + /// Retrieve transfer sender. + public fun sender(self: &TransferWithPayload): ExternalAddress { + self.sender + } + + /// Retrieve arbitrary payload. + public fun payload(self: &TransferWithPayload): vector { + self.payload + } + + /// Decode Wormhole message payload as `TransferWithPayload`. + public fun deserialize(transfer: vector): TransferWithPayload { + let cur = cursor::new(transfer); + assert!(bytes::take_u8(&mut cur) == PAYLOAD_ID, E_INVALID_PAYLOAD); + + let amount = normalized_amount::take_bytes(&mut cur); + let token_address = external_address::take_bytes(&mut cur); + let token_chain = bytes::take_u16_be(&mut cur); + let redeemer = external_address::take_bytes(&mut cur); + let redeemer_chain = bytes::take_u16_be(&mut cur); + let sender = external_address::take_bytes(&mut cur); + + TransferWithPayload { + amount, + token_address, + token_chain, + redeemer, + redeemer_chain, + sender, + payload: cursor::take_rest(cur) + } + } + + /// Encode `TransferWithPayload` for Wormhole message payload. + public fun serialize(transfer: TransferWithPayload): vector { + let TransferWithPayload { + amount, + token_address, + token_chain, + redeemer, + redeemer_chain, + sender, + payload + } = transfer; + + let buf = vector::empty(); + bytes::push_u8(&mut buf, PAYLOAD_ID); + bytes::push_u256_be(&mut buf, normalized_amount::to_u256(amount)); + vector::append(&mut buf, external_address::to_bytes(token_address)); + bytes::push_u16_be(&mut buf, token_chain); + vector::append(&mut buf, external_address::to_bytes(redeemer)); + bytes::push_u16_be(&mut buf, redeemer_chain); + vector::append(&mut buf, external_address::to_bytes(sender)); + vector::append(&mut buf, payload); + + buf + } + + #[test_only] + public fun destroy(transfer: TransferWithPayload) { + take_payload(transfer); + } + + #[test_only] + public fun payload_id(): u8 { + PAYLOAD_ID + } +} + +#[test_only] +module token_bridge::transfer_with_payload_tests { + use std::vector::{Self}; + use sui::object::{Self}; + use wormhole::emitter::{Self}; + use wormhole::external_address::{Self}; + + use token_bridge::dummy_message::{Self}; + use token_bridge::normalized_amount::{Self}; + use token_bridge::transfer_with_payload::{Self}; + + #[test] + fun test_serialize() { + let emitter_cap = emitter::dummy(); + let amount = normalized_amount::from_raw(234567890, 8); + let token_address = external_address::from_address(@0xbeef); + let token_chain = 1; + let redeemer = external_address::from_address(@0xcafe); + let redeemer_chain = 7; + let payload = b"All your base are belong to us."; + + let new_transfer = + transfer_with_payload::new_test_only( + object::id(&emitter_cap), + amount, + token_address, + token_chain, + redeemer, + redeemer_chain, + payload + ); + + // Verify getters. + assert!( + transfer_with_payload::amount(&new_transfer) == amount, + 0 + ); + assert!( + transfer_with_payload::token_address(&new_transfer) == token_address, + 0 + ); + assert!( + transfer_with_payload::token_chain(&new_transfer) == token_chain, + 0 + ); + assert!( + transfer_with_payload::redeemer(&new_transfer) == redeemer, + 0 + ); + assert!( + transfer_with_payload::redeemer_chain(&new_transfer) == redeemer_chain, + 0 + ); + let expected_sender = + external_address::from_id(object::id(&emitter_cap)); + assert!( + transfer_with_payload::sender(&new_transfer) == expected_sender, + 0 + ); + assert!( + transfer_with_payload::payload(&new_transfer) == payload, + 0 + ); + + let serialized = transfer_with_payload::serialize(new_transfer); + let expected_serialized = + dummy_message::encoded_transfer_with_payload(); + assert!(serialized == expected_serialized, 0); + + // Clean up. + emitter::destroy_test_only(emitter_cap); + } + + #[test] + fun test_deserialize() { + let expected_amount = normalized_amount::from_raw(234567890, 8); + let expected_token_address = external_address::from_address(@0xbeef); + let expected_token_chain = 1; + let expected_recipient = external_address::from_address(@0xcafe); + let expected_recipient_chain = 7; + let expected_sender = + external_address::from_address( + @0x381dd9078c322a4663c392761a0211b527c127b29583851217f948d62131f409 + ); + let expected_payload = b"All your base are belong to us."; + + let parsed = + transfer_with_payload::deserialize( + dummy_message::encoded_transfer_with_payload() + ); + + // Verify getters. + assert!( + transfer_with_payload::amount(&parsed) == expected_amount, + 0 + ); + assert!( + transfer_with_payload::token_address(&parsed) == expected_token_address, + 0 + ); + assert!( + transfer_with_payload::token_chain(&parsed) == expected_token_chain, + 0 + ); + assert!( + transfer_with_payload::redeemer(&parsed) == expected_recipient, + 0 + ); + assert!( + transfer_with_payload::redeemer_chain(&parsed) == expected_recipient_chain, + 0 + ); + assert!( + transfer_with_payload::sender(&parsed) == expected_sender, + 0 + ); + assert!( + transfer_with_payload::payload(&parsed) == expected_payload, + 0 + ); + + let payload = transfer_with_payload::take_payload(parsed); + assert!(payload == expected_payload, 0); + } + + #[test] + #[expected_failure(abort_code = transfer_with_payload::E_INVALID_PAYLOAD)] + fun test_cannot_deserialize_invalid_payload() { + let invalid_payload = token_bridge::dummy_message::encoded_transfer(); + + // Show that the first byte is not the expected payload ID. + assert!( + *vector::borrow(&invalid_payload, 0) != transfer_with_payload::payload_id(), + 0 + ); + + // You shall not pass! + let parsed = transfer_with_payload::deserialize(invalid_payload); + + // Clean up. + transfer_with_payload::destroy(parsed); + + abort 42 + } +} diff --git a/sui/token_bridge/sources/migrate.move b/sui/token_bridge/sources/migrate.move new file mode 100644 index 000000000..ed9b65781 --- /dev/null +++ b/sui/token_bridge/sources/migrate.move @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements a public method intended to be called after an +/// upgrade has been commited. The purpose is to add one-off migration logic +/// that would alter Token Bridge `State`. +/// +/// Included in migration is the ability to ensure that breaking changes for +/// any of Token Bridge's methods by enforcing the current build version as +/// their required minimum version. +module token_bridge::migrate { + use sui::object::{ID}; + use wormhole::governance_message::{Self, DecreeReceipt}; + + use token_bridge::state::{Self, State}; + use token_bridge::upgrade_contract::{Self}; + + /// Event reflecting when `migrate` is successfully executed. + struct MigrateComplete has drop, copy { + package: ID + } + + /// Execute migration logic. See `token_bridge::migrate` description for + /// more info. + public fun migrate( + token_bridge_state: &mut State, + receipt: DecreeReceipt + ) { + state::migrate__v__0_2_0(token_bridge_state); + + // Perform standard migrate. + handle_migrate(token_bridge_state, receipt); + + //////////////////////////////////////////////////////////////////////// + // + // NOTE: Put any one-off migration logic here. + // + // Most upgrades likely won't need to do anything, in which case the + // rest of this function's body may be empty. Make sure to delete it + // after the migration has gone through successfully. + // + // WARNING: The migration does *not* proceed atomically with the + // upgrade (as they are done in separate transactions). + // If the nature of this migration absolutely requires the migration to + // happen before certain other functionality is available, then guard + // that functionality with the `assert!` from above. + // + //////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////// + } + + fun handle_migrate( + token_bridge_state: &mut State, + receipt: DecreeReceipt + ) { + // Update the version first. + // + // See `version_control` module for hard-coded configuration. + state::migrate_version(token_bridge_state); + + // This capability ensures that the current build version is used. + let latest_only = state::assert_latest_only(token_bridge_state); + + // Check if build digest is the current one. + let digest = + upgrade_contract::take_digest( + governance_message::payload(&receipt) + ); + state::assert_authorized_digest( + &latest_only, + token_bridge_state, + digest + ); + governance_message::destroy(receipt); + + // Finally emit an event reflecting a successful migrate. + let package = state::current_package(&latest_only, token_bridge_state); + sui::event::emit(MigrateComplete { package }); + } + + #[test_only] + public fun set_up_migrate(token_bridge_state: &mut State) { + state::reverse_migrate__v__dummy(token_bridge_state); + } +} + +#[test_only] +module token_bridge::migrate_tests { + use sui::test_scenario::{Self}; + use wormhole::wormhole_scenario::{ + parse_and_verify_vaa, + verify_governance_vaa + }; + + use token_bridge::state::{Self}; + use token_bridge::upgrade_contract::{Self}; + use token_bridge::token_bridge_scenario::{ + person, + return_state, + set_up_wormhole_and_token_bridge, + take_state, + upgrade_token_bridge + }; + + const UPGRADE_VAA: vector = + x"010000000001005b18d7710c442414435162dc2b46a421c3018a7ff03290eff112a828b7927e4a6a624174cb8385210f4684ac2dbde6e01e4046218f7f245af53e85c97a48e21a0100bc614e0000000000010000000000000000000000000000000000000000000000000000000000000004000000000000000101000000000000000000000000000000000000000000546f6b656e42726964676502001500000000000000000000000000000000000000000000006e6577206275696c64"; + + #[test] + fun test_migrate() { + use token_bridge::migrate::{migrate}; + + let user = person(); + let my_scenario = test_scenario::begin(user); + let scenario = &mut my_scenario; + + // Initialize Wormhole. + let wormhole_message_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_message_fee); + + // Next transaction should be conducted as an ordinary user. + test_scenario::next_tx(scenario, user); + + // Upgrade (digest is just b"new build") for testing purposes. + upgrade_token_bridge(scenario); + + // Ignore effects. + test_scenario::next_tx(scenario, user); + + let token_bridge_state = take_state(scenario); + + // Set up migrate (which prepares this package to be the same state as + // a previous release). + token_bridge::migrate::set_up_migrate(&mut token_bridge_state); + + // Conveniently roll version back. + state::reverse_migrate_version(&mut token_bridge_state); + + let verified_vaa = parse_and_verify_vaa(scenario, UPGRADE_VAA); + let ticket = + upgrade_contract::authorize_governance(&token_bridge_state); + let receipt = + verify_governance_vaa(scenario, verified_vaa, ticket); + // Simulate executing with an outdated build by upticking the minimum + // required version for `publish_message` to something greater than + // this build. + migrate(&mut token_bridge_state, receipt); + + // Make sure we emitted an event. + let effects = test_scenario::next_tx(scenario, user); + assert!(test_scenario::num_user_events(&effects) == 1, 0); + + // Clean up. + return_state(token_bridge_state); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + #[expected_failure(abort_code = wormhole::package_utils::E_INCORRECT_OLD_VERSION)] + /// ^ This expected error may change depending on the migration. In most + /// cases, this will abort with `wormhole::package_utils::E_INCORRECT_OLD_VERSION`. + fun test_cannot_migrate_again() { + use token_bridge::migrate::{migrate}; + + let user = person(); + let my_scenario = test_scenario::begin(user); + let scenario = &mut my_scenario; + + // Initialize Wormhole. + let wormhole_message_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_message_fee); + + // Next transaction should be conducted as an ordinary user. + test_scenario::next_tx(scenario, user); + + // Upgrade (digest is just b"new build") for testing purposes. + upgrade_token_bridge(scenario); + + // Ignore effects. + test_scenario::next_tx(scenario, user); + + let token_bridge_state = take_state(scenario); + + // Set up migrate (which prepares this package to be the same state as + // a previous release). + token_bridge::migrate::set_up_migrate(&mut token_bridge_state); + + // Conveniently roll version back. + state::reverse_migrate_version(&mut token_bridge_state); + + let verified_vaa = parse_and_verify_vaa(scenario, UPGRADE_VAA); + let ticket = + upgrade_contract::authorize_governance(&token_bridge_state); + let receipt = + verify_governance_vaa(scenario, verified_vaa, ticket); + // Simulate executing with an outdated build by upticking the minimum + // required version for `publish_message` to something greater than + // this build. + migrate(&mut token_bridge_state, receipt); + + // Make sure we emitted an event. + let effects = test_scenario::next_tx(scenario, user); + assert!(test_scenario::num_user_events(&effects) == 1, 0); + + let verified_vaa = parse_and_verify_vaa(scenario, UPGRADE_VAA); + let ticket = + upgrade_contract::authorize_governance(&token_bridge_state); + let receipt = + verify_governance_vaa(scenario, verified_vaa, ticket); + // You shall not pass! + migrate(&mut token_bridge_state, receipt); + + abort 42 + } +} diff --git a/sui/token_bridge/sources/newtypes/normalized_amount.move b/sui/token_bridge/sources/newtypes/normalized_amount.move deleted file mode 100644 index 4305a51ab..000000000 --- a/sui/token_bridge/sources/newtypes/normalized_amount.move +++ /dev/null @@ -1,74 +0,0 @@ -/// Amounts in represented in token bridge VAAs are capped at 8 decimals. This -/// means that any amount that's given as having more decimals is truncated to 8 -/// decimals. On the way out, these amount have to be scaled back to the -/// original decimal amount. This module defines `NormalizedAmount`, which -/// represents amounts that have been capped at 8 decimals. -/// -/// The functions `normalize` and `denormalize` take care of convertion to/from -/// this type given the original amount's decimals. -module token_bridge::normalized_amount { - use wormhole::cursor::Cursor; - use wormhole::deserialize; - use wormhole::serialize; - - struct NormalizedAmount has store, copy, drop { - amount: u64 - } - - #[test_only] - public fun get_amount(n: NormalizedAmount): u64 { - n.amount - } - - public fun normalize(amount: u64, decimals: u8): NormalizedAmount { - if (decimals > 8) { - let n = decimals - 8; - while (n > 0){ - amount = amount / 10; - n = n - 1; - } - }; - NormalizedAmount { amount } - } - - public fun denormalize(amount: NormalizedAmount, decimals: u8): u64 { - let NormalizedAmount { amount } = amount; - if (decimals > 8) { - let n = decimals - 8; - while (n > 0){ - amount = amount * 10; - n = n - 1; - } - }; - amount - } - - public fun deserialize(cur: &mut Cursor): NormalizedAmount { - // in the VAA wire format, amounts are 32 bytes. - let amount = deserialize::deserialize_u256(cur); - NormalizedAmount { amount: wormhole::myu256::as_u64(amount) } - } - - public fun serialize(buf: &mut vector, e: NormalizedAmount) { - let NormalizedAmount { amount } = e; - serialize::serialize_u256(buf, wormhole::myu256::from_u64(amount)) - } -} - -#[test_only] -module token_bridge::normalized_amount_test { - use token_bridge::normalized_amount; - - #[test] - fun test_normalize_denormalize_amount() { - let a = 12345678910111; - let b = normalized_amount::normalize(a, 9); - let c = normalized_amount::denormalize(b, 9); - assert!(c == 12345678910110, 0); - - let x = 12345678910111; - let y = normalized_amount::normalize(x, 5); - let z = normalized_amount::denormalize(y, 5); - assert!(z == x, 0); - } -} \ No newline at end of file diff --git a/sui/token_bridge/sources/newtypes/string32.move b/sui/token_bridge/sources/newtypes/string32.move deleted file mode 100644 index b0e53832d..000000000 --- a/sui/token_bridge/sources/newtypes/string32.move +++ /dev/null @@ -1,171 +0,0 @@ -/// The `string32` module defines the `String32` type which represents UTF8 -/// encoded strings that are guaranteed to be 32 bytes long, with 0 padding on -/// the right. -module token_bridge::string32 { - - use std::string::{Self, String}; - use std::option; - use std::vector; - - use wormhole::cursor::Cursor; - use wormhole::deserialize; - use wormhole::serialize; - - const E_STRING_TOO_LONG: u64 = 0; - - /// A `String32` holds a ut8 string which is guaranteed to be 32 bytes long. - struct String32 has copy, drop, store { - string: String - } - - spec String32 { - invariant string::length(string) == 32; - } - - /// Right-pads a `String` to a `String32` with 0 bytes. - /// Aborts if the string is longer than 32 bytes. - public fun right_pad(s: &String): String32 { - let length = string::length(s); - assert!(length <= 32, E_STRING_TOO_LONG); - let string = *string::bytes(s); - let zeros = 32 - length; - while ({ - spec { - invariant zeros + vector::length(string) == 32; - }; - zeros > 0 - }) { - vector::push_back(&mut string, 0); - zeros = zeros - 1; - }; - String32 { string: string::utf8(string) } - } - - /// Internal function to take the first 32 bytes of a byte sequence and - /// convert to a utf8 `String`. - /// Takes the longest prefix that's valid utf8 and maximum 32 bytes. - /// - /// Even if the input is valid utf8, the result might be shorter than 32 - /// bytes, because the original string might have a multi-byte utf8 - /// character at the 32 byte boundary, which, when split, results in an - /// invalid code point, so we remove it. - fun take(bytes: vector, n: u64): String { - while (vector::length(&bytes) > n) { - vector::pop_back(&mut bytes); - }; - - let utf8 = string::try_utf8(bytes); - while (option::is_none(&utf8)) { - vector::pop_back(&mut bytes); - utf8 = string::try_utf8(bytes); - }; - option::extract(&mut utf8) - } - - /// Takes the first `n` bytes of a `String`. - /// - /// Even if the input string is longer than `n`, the resulting string might - /// be shorter because the original string might have a multi-byte utf8 - /// character at the byte boundary, which, when split, results in an invalid - /// code point, so we remove it. - public fun take_utf8(str: String, n: u64): String { - take(*string::bytes(&str), n) - } - - /// Truncates or right-pads a `String` to a `String32`. - /// Does not abort. - public fun from_string(s: &String): String32 { - right_pad(&take(*string::bytes(s), 32)) - } - - /// Truncates or right-pads a byte vector to a `String32`. - /// Does not abort. - public fun from_bytes(b: vector): String32 { - right_pad(&take(b, 32)) - } - - /// Converts `String32` to `String`, removing trailing 0s. - public fun to_string(s: &String32): String { - let String32 { string } = s; - let bytes = *string::bytes(string); - // keep dropping the last character while it's 0 - while (!vector::is_empty(&bytes) && - *vector::borrow(&bytes, vector::length(&bytes) - 1) == 0 - ) { - vector::pop_back(&mut bytes); - }; - string::utf8(bytes) - } - - /// Converts `String32` to a byte vector of length 32. - public fun to_bytes(s: &String32): vector { - *string::bytes(&s.string) - } - - public fun deserialize(cur: &mut Cursor): String32 { - let bytes = deserialize::deserialize_vector(cur, 32); - from_bytes(bytes) - } - - public fun serialize(buf: &mut vector, e: String32) { - serialize::serialize_vector(buf, to_bytes(&e)) - } - -} - -#[test_only] -module token_bridge::string32_test { - use std::string; - use std::vector; - use token_bridge::string32; - - #[test] - public fun test_right_pad() { - let result = string32::right_pad(&string::utf8(b"hello")); - assert!(string32::to_string(&result) == string::utf8(b"hello"), 0) - } - - #[test] - #[expected_failure(abort_code = string32::E_STRING_TOO_LONG)] - public fun test_right_pad_fail() { - let too_long = string::utf8(b"this string is very very very very very very very very very very very very very very very long"); - string32::right_pad(&too_long); - } - - #[test] - public fun test_from_string_short() { - let result = string32::from_string(&string::utf8(b"hello")); - assert!(string32::to_string(&result) == string::utf8(b"hello"), 0) - } - - #[test] - public fun test_from_string_long() { - let long = string32::from_string(&string::utf8(b"this string is very very very very very very very very very very very very very very very long")); - assert!(string32::to_string(&long) == string::utf8(b"this string is very very very ve"), 0) - } - - #[test] - public fun test_from_string_weird_utf8() { - let string = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; - assert!(vector::length(&string) == 31, 0); - // append the samaritan letter Alaf, a 3-byte utf8 character the move - // parser only allows ascii characters unfortunately (the character - // looks nice) - vector::append(&mut string, x"e0a080"); - // it's valid utf8 - let string = string::utf8(string); - // string length is bytes, not characters - assert!(string::length(&string) == 34, 0); - let padded = string32::from_string(&string); - // notice that the e0 byte got dropped at the end - assert!(string32::to_string(&padded) == string::utf8(b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), 0) - } - - #[test] - public fun test_from_bytes_invalid_utf8() { - // invalid utf8 - let bytes = x"e0a0"; - let result = string::utf8(b""); - assert!(string32::to_string(&string32::from_bytes(bytes)) == result, 0) - } -} \ No newline at end of file diff --git a/sui/token_bridge/sources/register_chain.move b/sui/token_bridge/sources/register_chain.move deleted file mode 100644 index 94ced0579..000000000 --- a/sui/token_bridge/sources/register_chain.move +++ /dev/null @@ -1,229 +0,0 @@ -module token_bridge::register_chain { - - use sui::tx_context::TxContext; - - use wormhole::myu16::{Self as u16, U16}; - use wormhole::cursor; - use wormhole::deserialize; - use wormhole::myvaa::{Self as corevaa}; - use wormhole::external_address::{Self, ExternalAddress}; - use wormhole::state::{State as WormholeState}; - - use token_bridge::vaa as token_bridge_vaa; - use token_bridge::bridge_state::{Self as bridge_state, BridgeState}; - - /// "TokenBridge" (left padded) - const TOKEN_BRIDGE: vector = x"000000000000000000000000000000000000000000546f6b656e427269646765"; - - const E_INVALID_MODULE: u64 = 0; - const E_INVALID_ACTION: u64 = 1; - const E_INVALID_TARGET: u64 = 2; - - struct RegisterChain has copy, drop { - /// Chain ID - emitter_chain_id: U16, - /// Emitter address. Left-zero-padded if shorter than 32 bytes - emitter_address: ExternalAddress, - } - - #[test_only] - public fun parse_payload_test(payload: vector): RegisterChain { - parse_payload(payload) - } - - fun parse_payload(payload: vector): RegisterChain { - let cur = cursor::cursor_init(payload); - let target_module = deserialize::deserialize_vector(&mut cur, 32); - - assert!(target_module == TOKEN_BRIDGE, E_INVALID_MODULE); - - let action = deserialize::deserialize_u8(&mut cur); - assert!(action == 0x01, E_INVALID_ACTION); - - // TODO(csongor): should we also accept a VAA directly? - // why would a registration VAA target a specific chain? - let target_chain = deserialize::deserialize_u16(&mut cur); - assert!(target_chain == u16::from_u64(0x0), E_INVALID_TARGET); - - let emitter_chain_id = deserialize::deserialize_u16(&mut cur); - - let emitter_address = external_address::deserialize(&mut cur); - - cursor::destroy_empty(cur); - - RegisterChain { emitter_chain_id, emitter_address } - } - - public entry fun submit_vaa(wormhole_state: &mut WormholeState, bridge_state: &mut BridgeState, vaa: vector, ctx: &mut TxContext) { - let vaa = corevaa::parse_and_verify(wormhole_state, vaa, ctx); - corevaa::assert_governance(wormhole_state, &vaa); - token_bridge_vaa::replay_protect(bridge_state, &vaa); - let RegisterChain { emitter_chain_id, emitter_address } = parse_payload(corevaa::destroy(vaa)); - bridge_state::set_registered_emitter(bridge_state, emitter_chain_id, emitter_address); - } - - public fun get_emitter_chain_id(a: &RegisterChain): U16 { - a.emitter_chain_id - } - - public fun get_emitter_address(a: &RegisterChain): ExternalAddress { - a.emitter_address - } -} - -#[test_only] -module token_bridge::register_chain_test { - use std::option::{Self}; - - use sui::test_scenario::{Self, Scenario, next_tx, ctx, take_shared, return_shared}; - - use wormhole::state::{State}; - //use wormhole::test_state::{init_wormhole_state}; - //use wormhole::wormhole::{Self}; - - use wormhole::myu16::{Self as u16}; - use wormhole::external_address::{Self}; - use wormhole::myvaa::{Self as corevaa}; - - use token_bridge::bridge_state::{Self as bridge_state, BridgeState}; - use token_bridge::register_chain::{Self, submit_vaa}; - use token_bridge::bridge_state_test::{set_up_wormhole_core_and_token_bridges}; - - fun scenario(): Scenario { test_scenario::begin(@0x123233) } - fun people(): (address, address, address) { (@0x124323, @0xE05, @0xFACE) } - - struct MyCoinType1 {} - - /// Registration VAA for the etheruem token bridge 0xdeadbeef - const ETHEREUM_TOKEN_REG: vector = x"0100000000010015d405c74be6d93c3c33ed6b48d8db70dfb31e0981f8098b2a6c7583083e0c3343d4a1abeb3fc1559674fa067b0c0e2e9de2fafeaecdfeae132de2c33c9d27cc0100000001000000010001000000000000000000000000000000000000000000000000000000000000000400000000016911ae00000000000000000000000000000000000000000000546f6b656e427269646765010000000200000000000000000000000000000000000000000000000000000000deadbeef"; - - /// Another registration VAA for the ethereum token bridge, 0xbeefface - const ETHEREUM_TOKEN_REG_2:vector = x"01000000000100c2157fa1c14957dff26d891e4ad0d993ad527f1d94f603e3d2bb1e37541e2fbe45855ffda1efc7eb2eb24009a1585fa25a267815db97e4a9d4a5eb31987b5fb40100000001000000010001000000000000000000000000000000000000000000000000000000000000000400000000017ca43300000000000000000000000000000000000000000000546f6b656e427269646765010000000200000000000000000000000000000000000000000000000000000000beefface"; - - /// Registration VAA for the etheruem NFT bridge 0xdeadbeef - const ETHEREUM_NFT_REG: vector = x"0100000000010066cce2cb12d88c97d4975cba858bb3c35d6430003e97fced46a158216f3ca01710fd16cc394441a08fef978108ed80c653437f43bb2ca039226974d9512298b10000000001000000010001000000000000000000000000000000000000000000000000000000000000000400000000018483540000000000000000000000000000000000000000000000004e4654427269646765010000000200000000000000000000000000000000000000000000000000000000deadbeef"; - - const ETH_ID: u64 = 2; - - #[test] - fun test_parse(){ - test_parse_(scenario()) - } - - #[test] - #[expected_failure(abort_code = 0, location=token_bridge::register_chain)] - fun test_parse_fail(){ - test_parse_fail_(scenario()) - } - - #[test] - fun test_register_chain(){ - test_register_chain_(scenario()) - } - - #[test] - #[expected_failure(abort_code = 0, location=0000000000000000000000000000000000000002::dynamic_field)] - fun test_replay_protect(){ - test_replay_protect_(scenario()) - } - - #[test] - fun test_re_registration(){ - test_re_registration_(scenario()) - } - - public fun test_parse_(test: Scenario) { - let (admin, _, _) = people(); - next_tx(&mut test, admin); { - let vaa = corevaa::parse_test(ETHEREUM_TOKEN_REG); - let register_chain = register_chain::parse_payload_test(corevaa::destroy(vaa)); - let chain = register_chain::get_emitter_chain_id(®ister_chain); - let address = register_chain::get_emitter_address(®ister_chain); - - assert!(chain == u16::from_u64(ETH_ID), 0); - assert!(address == external_address::from_bytes(x"deadbeef"), 0); - }; - test_scenario::end(test); - } - - public fun test_parse_fail_(test: Scenario) { - let (admin, _, _) = people(); - next_tx(&mut test, admin); { - let vaa = corevaa::parse_test(ETHEREUM_NFT_REG); - // this should fail because it's an NFT registration - let _register_chain = register_chain::parse_payload_test(corevaa::destroy(vaa)); - }; - test_scenario::end(test); - } - - fun test_register_chain_(test: Scenario) { - let (admin, _, _) = people(); - test = set_up_wormhole_core_and_token_bridges(admin, test); - next_tx(&mut test, admin); { - let wormhole_state = take_shared(&test); - let bridge_state = take_shared(&test); - submit_vaa(&mut wormhole_state, &mut bridge_state, ETHEREUM_TOKEN_REG, ctx(&mut test)); - return_shared(wormhole_state); - return_shared(bridge_state); - }; - next_tx(&mut test, admin); { - let bridge_state = take_shared(&test); - let addr = bridge_state::get_registered_emitter(&bridge_state, &u16::from_u64(ETH_ID)); - assert!(addr == option::some(external_address::from_bytes(x"deadbeef")), 0); - return_shared(bridge_state); - }; - test_scenario::end(test); - } - - public fun test_replay_protect_(test: Scenario) { - let (admin, _, _) = people(); - test = set_up_wormhole_core_and_token_bridges(admin, test); - next_tx(&mut test, admin); { - let wormhole_state = take_shared(&test); - let bridge_state = take_shared(&test); - // submit vaa (register chain) twice - triggering replay protection - submit_vaa(&mut wormhole_state, &mut bridge_state, ETHEREUM_TOKEN_REG, ctx(&mut test)); - submit_vaa(&mut wormhole_state, &mut bridge_state, ETHEREUM_TOKEN_REG, ctx(&mut test)); - return_shared(wormhole_state); - return_shared(bridge_state); - }; - test_scenario::end(test); - } - - public fun test_re_registration_(test: Scenario) { - // first register chain using ETHEREUM_TOKEN_REG_1 - let (admin, _, _) = people(); - test = set_up_wormhole_core_and_token_bridges(admin, test); - next_tx(&mut test, admin); { - let wormhole_state = take_shared(&test); - let bridge_state = take_shared(&test); - submit_vaa(&mut wormhole_state, &mut bridge_state, ETHEREUM_TOKEN_REG, ctx(&mut test)); - return_shared(wormhole_state); - return_shared(bridge_state); - }; - next_tx(&mut test, admin); { - let bridge_state = take_shared(&test); - let addr = bridge_state::get_registered_emitter(&bridge_state, &u16::from_u64(ETH_ID)); - assert!(addr == option::some(external_address::from_bytes(x"deadbeef")), 0); - return_shared(bridge_state); - }; - next_tx(&mut test, admin); { - let wormhole_state = take_shared(&test); - let bridge_state = take_shared(&test); - // TODO(csongor): we register ethereum again, which overrides the - // previous one. This deviates from other chains (where this is - // rejected), but I think this is the right behaviour. - // Easy to change, should be discussed. - submit_vaa(&mut wormhole_state, &mut bridge_state, ETHEREUM_TOKEN_REG_2, ctx(&mut test)); - let address = bridge_state::get_registered_emitter(&bridge_state, &u16::from_u64(ETH_ID)); - assert!(address == option::some(external_address::from_bytes(x"beefface")), 0); - return_shared(wormhole_state); - return_shared(bridge_state); - }; - test_scenario::end(test); - } -} - - - - diff --git a/sui/token_bridge/sources/resources/native_asset.move b/sui/token_bridge/sources/resources/native_asset.move new file mode 100644 index 000000000..dba6b9196 --- /dev/null +++ b/sui/token_bridge/sources/resources/native_asset.move @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements a custom type that keeps track of info relating to +/// assets (coin types) native to Sui. Token Bridge takes custody of these +/// assets when someone invokes a token transfer outbound. Likewise, Token +/// Bridge releases some of its balance from its custody of when someone redeems +/// an inbound token transfer intended for Sui. +/// +/// See `token_registry` module for more details. +module token_bridge::native_asset { + use sui::balance::{Self, Balance}; + use sui::coin::{Self, CoinMetadata}; + use sui::object::{Self}; + use wormhole::external_address::{Self, ExternalAddress}; + use wormhole::state::{chain_id}; + + friend token_bridge::complete_transfer; + friend token_bridge::token_registry; + friend token_bridge::transfer_tokens; + + /// Container for storing canonical token address and custodied `Balance`. + struct NativeAsset has store { + custody: Balance, + token_address: ExternalAddress, + decimals: u8 + } + + /// Token Bridge identifies native assets using `CoinMetadata` object `ID`. + /// This method converts this `ID` to `ExternalAddress`. + public fun canonical_address( + metadata: &CoinMetadata + ): ExternalAddress { + external_address::from_id(object::id(metadata)) + } + + /// Create new `NativeAsset`. + /// + /// NOTE: The canonical token address is determined by the coin metadata's + /// object ID. + public(friend) fun new(metadata: &CoinMetadata): NativeAsset { + NativeAsset { + custody: balance::zero(), + token_address: canonical_address(metadata), + decimals: coin::get_decimals(metadata) + } + } + + #[test_only] + public fun new_test_only(metadata: &CoinMetadata): NativeAsset { + new(metadata) + } + + /// Retrieve canonical token address. + public fun token_address(self: &NativeAsset): ExternalAddress { + self.token_address + } + + /// Retrieve decimals, which originated from `CoinMetadata`. + public fun decimals(self: &NativeAsset): u8 { + self.decimals + } + + /// Retrieve custodied `Balance` value. + public fun custody(self: &NativeAsset): u64 { + balance::value(&self.custody) + } + + /// Retrieve canonical token chain ID (Sui's) and token address. + public fun canonical_info( + self: &NativeAsset + ): (u16, ExternalAddress) { + (chain_id(), self.token_address) + } + + /// Deposit a given `Balance`. `Balance` originates from an outbound token + /// transfer for a native asset. + /// + /// See `transfer_tokens` module for more info. + public(friend) fun deposit( + self: &mut NativeAsset, + deposited: Balance + ) { + balance::join(&mut self.custody, deposited); + } + + #[test_only] + public fun deposit_test_only( + self: &mut NativeAsset, + deposited: Balance + ) { + deposit(self, deposited) + } + + /// Withdraw a given amount from custody. This amount is determiend by an + /// inbound token transfer payload for a native asset. + /// + /// See `complete_transfer` module for more info. + public(friend) fun withdraw( + self: &mut NativeAsset, + amount: u64 + ): Balance { + balance::split(&mut self.custody, amount) + } + + #[test_only] + public fun withdraw_test_only( + self: &mut NativeAsset, + amount: u64 + ): Balance { + withdraw(self, amount) + } + + #[test_only] + public fun destroy(asset: NativeAsset) { + let NativeAsset { + custody, + token_address: _, + decimals: _ + } = asset; + balance::destroy_for_testing(custody); + } +} + +#[test_only] +module token_bridge::native_asset_tests { + use sui::balance::{Self}; + use sui::coin::{Self}; + use sui::object::{Self}; + use sui::test_scenario::{Self}; + use wormhole::external_address::{Self}; + use wormhole::state::{chain_id}; + + use token_bridge::coin_native_10::{Self, COIN_NATIVE_10}; + use token_bridge::native_asset::{Self}; + use token_bridge::token_bridge_scenario::{person}; + + #[test] + /// In this test, we exercise all the functionalities of a native asset + /// object, including new, deposit, withdraw, to_token_info, as well as + /// getting fields token_address, decimals, balance. + fun test_native_asset() { + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + // Publish coin. + coin_native_10::init_test_only(test_scenario::ctx(scenario)); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + let coin_meta = coin_native_10::take_metadata(scenario); + + // Make new. + let asset = native_asset::new_test_only(&coin_meta); + + // Assert token address and decimals are correct. + let expected_token_address = + external_address::from_id(object::id(&coin_meta)); + assert!( + native_asset::token_address(&asset) == expected_token_address, + 0 + ); + assert!( + native_asset::decimals(&asset) == coin::get_decimals(&coin_meta), + 0 + ); + assert!(native_asset::custody(&asset) == 0, 0); + + // deposit some coins into the NativeAsset coin custody + let deposit_amount = 1000; + let (i, n) = (0, 8); + while (i < n) { + native_asset::deposit_test_only( + &mut asset, + balance::create_for_testing( + deposit_amount + ) + ); + i = i + 1; + }; + let total_deposited = n * deposit_amount; + assert!(native_asset::custody(&asset) == total_deposited, 0); + + let withdraw_amount = 690; + let total_withdrawn = balance::zero(); + let i = 0; + while (i < n) { + let withdrawn = native_asset::withdraw_test_only( + &mut asset, + withdraw_amount + ); + assert!(balance::value(&withdrawn) == withdraw_amount, 0); + balance::join(&mut total_withdrawn, withdrawn); + i = i + 1; + }; + + // convert to token info and assert convrsion is correct + let ( + token_chain, + token_address + ) = native_asset::canonical_info(&asset); + + assert!(token_chain == chain_id(), 0); + assert!(token_address == expected_token_address, 0); + + // check that updated balance is correct + let expected_remaining = total_deposited - n * withdraw_amount; + let remaining = native_asset::custody(&asset); + assert!(remaining == expected_remaining, 0); + + // Clean up. + coin_native_10::return_metadata(coin_meta); + balance::destroy_for_testing(total_withdrawn); + native_asset::destroy(asset); + + // Done. + test_scenario::end(my_scenario); + } +} diff --git a/sui/token_bridge/sources/resources/token_registry.move b/sui/token_bridge/sources/resources/token_registry.move new file mode 100644 index 000000000..f03f1d338 --- /dev/null +++ b/sui/token_bridge/sources/resources/token_registry.move @@ -0,0 +1,784 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements a custom type that keeps track of both native and +/// wrapped assets via dynamic fields. These dynamic fields are keyed off using +/// coin types. This registry lives in `State`. +/// +/// See `state` module for more details. +module token_bridge::token_registry { + use std::ascii::{String}; + use std::type_name::{Self}; + use sui::coin::{TreasuryCap, CoinMetadata}; + use sui::dynamic_field::{Self}; + use sui::object::{Self, UID}; + use sui::package::{UpgradeCap}; + use sui::table::{Self, Table}; + use sui::tx_context::{TxContext}; + use wormhole::external_address::{Self, ExternalAddress}; + + use token_bridge::asset_meta::{Self, AssetMeta}; + use token_bridge::native_asset::{Self, NativeAsset}; + use token_bridge::wrapped_asset::{Self, WrappedAsset}; + + friend token_bridge::attest_token; + friend token_bridge::complete_transfer; + friend token_bridge::create_wrapped; + friend token_bridge::state; + friend token_bridge::transfer_tokens; + + /// Asset is not registered yet. + const E_UNREGISTERED: u64 = 0; + /// Cannot register wrapped asset with same canonical token info. + const E_ALREADY_WRAPPED: u64 = 1; + + /// This container is used to store native and wrapped assets of coin type + /// as dynamic fields under its `UID`. It also uses a mechanism to generate + /// arbitrary token addresses for native assets. + struct TokenRegistry has key, store { + id: UID, + num_wrapped: u64, + num_native: u64, + coin_types: Table + } + + /// Container to provide convenient checking of whether an asset is wrapped + /// or native. `VerifiedAsset` can only be created either by passing in a + /// resource with `CoinType` or by verifying input token info against the + /// canonical info that exists in `TokenRegistry`. + /// + /// NOTE: This container can be dropped after it was created. + struct VerifiedAsset has drop { + is_wrapped: bool, + chain: u16, + addr: ExternalAddress, + coin_decimals: u8 + } + + /// Wrapper of coin type to act as dynamic field key. + struct Key has copy, drop, store {} + + /// This struct is not used for anything within the contract. It exists + /// purely for someone with an RPC query to be able to fetch the type name + /// of coin type as a string via `TokenRegistry`. + struct CoinTypeKey has drop, copy, store { + chain: u16, + addr: vector + } + + /// Create new `TokenRegistry`. + /// + /// See `setup` module for more info. + public(friend) fun new(ctx: &mut TxContext): TokenRegistry { + TokenRegistry { + id: object::new(ctx), + num_wrapped: 0, + num_native: 0, + coin_types: table::new(ctx) + } + } + + #[test_only] + public fun new_test_only(ctx: &mut TxContext): TokenRegistry { + new(ctx) + } + + /// Determine whether a particular coin type is registered. + public fun has(self: &TokenRegistry): bool { + dynamic_field::exists_(&self.id, Key {}) + } + + public fun assert_has(self: &TokenRegistry) { + assert!(has(self), E_UNREGISTERED); + } + + public fun verified_asset( + self: &TokenRegistry + ): VerifiedAsset { + // We check specifically whether `CoinType` is associated with a dynamic + // field for `WrappedAsset`. This boolean will be used as the underlying + // value for `VerifiedAsset`. + let is_wrapped = + dynamic_field::exists_with_type, WrappedAsset>( + &self.id, + Key {} + ); + if (is_wrapped) { + let asset = borrow_wrapped(self); + let (chain, addr) = wrapped_asset::canonical_info(asset); + let coin_decimals = wrapped_asset::decimals(asset); + + VerifiedAsset { is_wrapped, chain, addr, coin_decimals } + } else { + let asset = borrow_native(self); + let (chain, addr) = native_asset::canonical_info(asset); + let coin_decimals = native_asset::decimals(asset); + + VerifiedAsset { is_wrapped, chain, addr, coin_decimals } + } + } + + /// Determine whether a given `CoinType` is a wrapped asset. + public fun is_wrapped(verified: &VerifiedAsset): bool { + verified.is_wrapped + } + + /// Retrieve canonical token chain ID from `VerifiedAsset`. + public fun token_chain( + verified: &VerifiedAsset + ): u16 { + verified.chain + } + + /// Retrieve canonical token address from `VerifiedAsset`. + public fun token_address( + verified: &VerifiedAsset + ): ExternalAddress { + verified.addr + } + + /// Retrieve decimals for a `VerifiedAsset`. + public fun coin_decimals( + verified: &VerifiedAsset + ): u8 { + verified.coin_decimals + } + + /// Add a new wrapped asset to the registry and return the canonical token + /// address. + /// + /// See `state` module for more info. + public(friend) fun add_new_wrapped( + self: &mut TokenRegistry, + token_meta: AssetMeta, + coin_meta: &mut CoinMetadata, + treasury_cap: TreasuryCap, + upgrade_cap: UpgradeCap + ): ExternalAddress { + // Grab canonical token info. + let token_chain = asset_meta::token_chain(&token_meta); + let token_addr = asset_meta::token_address(&token_meta); + + let coin_types = &mut self.coin_types; + let key = + CoinTypeKey { + chain: token_chain, + addr: external_address::to_bytes(token_addr) + }; + // We need to make sure that the canonical token info has not been + // created for another coin type. This can happen if asset metadata + // is attested again from a foreign chain and another coin type is + // published using its VAA. + assert!(!table::contains(coin_types, key), E_ALREADY_WRAPPED); + + // Now add the coin type. + table::add( + coin_types, + key, + type_name::into_string(type_name::get()) + ); + + // NOTE: We do not assert that the coin type has not already been + // registered using !has(self) because `wrapped_asset::new` + // consumes `TreasuryCap`. This `TreasuryCap` is only created once for a particuar + // coin type via `create_wrapped::prepare_registration`. Because the + // `TreasuryCap` is globally unique and can only be created once, there is no + // risk that `add_new_wrapped` can be called again on the same coin + // type. + let asset = + wrapped_asset::new( + token_meta, + coin_meta, + treasury_cap, + upgrade_cap + ); + dynamic_field::add(&mut self.id, Key {}, asset); + self.num_wrapped = self.num_wrapped + 1; + + token_addr + } + + #[test_only] + public fun add_new_wrapped_test_only( + self: &mut TokenRegistry, + token_meta: AssetMeta, + coin_meta: &mut CoinMetadata, + treasury_cap: TreasuryCap, + ctx: &mut TxContext + ): ExternalAddress { + add_new_wrapped( + self, + token_meta, + coin_meta, + treasury_cap, + sui::package::test_publish( + object::id_from_address(@token_bridge), + ctx + ) + ) + } + + /// Add a new native asset to the registry and return the canonical token + /// address. + /// + /// NOTE: This method does not verify if `CoinType` is already in the + /// registry because `attest_token` already takes care of this check. If + /// This method were to be called on an already-registered asset, this + /// will throw with an error from `sui::dynamic_field` reflectina duplicate + /// field. + /// + /// See `attest_token` module for more info. + public(friend) fun add_new_native( + self: &mut TokenRegistry, + metadata: &CoinMetadata, + ): ExternalAddress { + // Create new native asset. + let asset = native_asset::new(metadata); + let token_addr = native_asset::token_address(&asset); + + // Add to registry. + dynamic_field::add(&mut self.id, Key {}, asset); + self.num_native = self.num_native + 1; + + // Now add the coin type. + table::add( + &mut self.coin_types, + CoinTypeKey { + chain: wormhole::state::chain_id(), + addr: external_address::to_bytes(token_addr) + }, + type_name::into_string(type_name::get()) + ); + + // Return the token address. + token_addr + } + + #[test_only] + public fun add_new_native_test_only( + self: &mut TokenRegistry, + metadata: &CoinMetadata + ): ExternalAddress { + add_new_native(self, metadata) + } + + public fun borrow_wrapped( + self: &TokenRegistry + ): &WrappedAsset { + dynamic_field::borrow(&self.id, Key {}) + } + + public(friend) fun borrow_mut_wrapped( + self: &mut TokenRegistry + ): &mut WrappedAsset { + dynamic_field::borrow_mut(&mut self.id, Key {}) + } + + #[test_only] + public fun borrow_mut_wrapped_test_only( + self: &mut TokenRegistry + ): &mut WrappedAsset { + borrow_mut_wrapped(self) + } + + public fun borrow_native( + self: &TokenRegistry + ): &NativeAsset { + dynamic_field::borrow(&self.id, Key {}) + } + + public(friend) fun borrow_mut_native( + self: &mut TokenRegistry + ): &mut NativeAsset { + dynamic_field::borrow_mut(&mut self.id, Key {}) + } + + #[test_only] + public fun borrow_mut_native_test_only( + self: &mut TokenRegistry + ): &mut NativeAsset { + borrow_mut_native(self) + } + + #[test_only] + public fun num_native(self: &TokenRegistry): u64 { + self.num_native + } + + #[test_only] + public fun num_wrapped(self: &TokenRegistry): u64 { + self.num_wrapped + } + + #[test_only] + public fun destroy(registry: TokenRegistry) { + let TokenRegistry { + id, + num_wrapped: _, + num_native: _, + coin_types + } = registry; + object::delete(id); + table::drop(coin_types); + } + + #[test_only] + public fun coin_type_for( + self: &TokenRegistry, + chain: u16, + addr: vector + ): String { + *table::borrow(&self.coin_types, CoinTypeKey { chain, addr }) + } +} + +// In this test, we exercise the various functionalities of TokenRegistry, +// including registering native and wrapped coins via add_new_native, and +// add_new_wrapped, minting/burning/depositing/withdrawing said tokens, and also +// storing metadata about the tokens. +#[test_only] +module token_bridge::token_registry_tests { + use std::type_name::{Self}; + use sui::balance::{Self}; + use sui::coin::{CoinMetadata}; + use sui::test_scenario::{Self}; + use wormhole::external_address::{Self}; + use wormhole::state::{chain_id}; + + use token_bridge::asset_meta::{Self}; + use token_bridge::coin_native_10::{Self, COIN_NATIVE_10}; + use token_bridge::coin_wrapped_7::{Self, COIN_WRAPPED_7}; + use token_bridge::native_asset::{Self}; + use token_bridge::token_registry::{Self}; + use token_bridge::token_bridge_scenario::{person}; + use token_bridge::wrapped_asset::{Self}; + + struct SCAM_COIN has drop {} + + #[test] + fun test_registered_tokens_native() { + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + // Initialize new coin. + coin_native_10::init_test_only(test_scenario::ctx(scenario)); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + // Initialize new token registry. + let registry = + token_registry::new_test_only(test_scenario::ctx(scenario)); + + // Check initial state. + assert!(token_registry::num_native(®istry) == 0, 0); + assert!(token_registry::num_wrapped(®istry) == 0, 0); + + // Register native asset. + let coin_meta = coin_native_10::take_metadata(scenario); + let token_address = + token_registry::add_new_native_test_only( + &mut registry, + &coin_meta, + ); + let expected_token_address = + native_asset::canonical_address(&coin_meta); + assert!(token_address == expected_token_address, 0); + + // mint some native coins, then deposit them into the token registry + let deposit_amount = 69; + let (i, n) = (0, 8); + while (i < n) { + native_asset::deposit_test_only( + token_registry::borrow_mut_native_test_only( + &mut registry, + ), + balance::create_for_testing( + deposit_amount + ) + ); + i = i + 1; + }; + let total_deposited = n * deposit_amount; + { + let asset = + token_registry::borrow_native(®istry); + assert!(native_asset::custody(asset) == total_deposited, 0); + }; + + // Withdraw and check balances. + let withdraw_amount = 420; + let withdrawn = + native_asset::withdraw_test_only( + token_registry::borrow_mut_native_test_only( + &mut registry + ), + withdraw_amount + ); + assert!(balance::value(&withdrawn) == withdraw_amount, 0); + balance::destroy_for_testing(withdrawn); + + let expected_remaining = total_deposited - withdraw_amount; + { + let asset = + token_registry::borrow_native(®istry); + assert!(native_asset::custody(asset) == expected_remaining, 0); + }; + + // Verify registry values. + assert!(token_registry::num_native(®istry) == 1, 0); + assert!(token_registry::num_wrapped(®istry) == 0, 0); + + let verified = token_registry::verified_asset(®istry); + assert!(!token_registry::is_wrapped(&verified), 0); + assert!(token_registry::coin_decimals(&verified) == 10, 0); + assert!(token_registry::token_chain(&verified) == chain_id(), 0); + assert!( + token_registry::token_address(&verified) == expected_token_address, + 0 + ); + + // Check coin type. + let coin_type = + token_registry::coin_type_for( + ®istry, + token_registry::token_chain(&verified), + external_address::to_bytes( + token_registry::token_address(&verified) + ) + ); + assert!( + coin_type == type_name::into_string(type_name::get()), + 0 + ); + + // Clean up. + token_registry::destroy(registry); + coin_native_10::return_metadata(coin_meta); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + fun test_registered_tokens_wrapped() { + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + // Initialize new coin. + let treasury_cap = + coin_wrapped_7::init_and_take_treasury_cap( + scenario, + caller + ); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + // Initialize new token registry. + let registry = + token_registry::new_test_only(test_scenario::ctx(scenario)); + + // Check initial state. + assert!(token_registry::num_wrapped(®istry) == 0, 0); + assert!(token_registry::num_native(®istry) == 0, 0); + + let coin_meta = test_scenario::take_shared>(scenario); + + // Register wrapped asset. + let wrapped_token_meta = coin_wrapped_7::token_meta(); + token_registry::add_new_wrapped_test_only( + &mut registry, + wrapped_token_meta, + &mut coin_meta, + treasury_cap, + test_scenario::ctx(scenario) + ); + + test_scenario::return_shared(coin_meta); + + // Mint wrapped coin via `WrappedAsset` several times. + let mint_amount = 420; + let total_minted = balance::zero(); + let (i, n) = (0, 8); + while (i < n) { + let minted = + wrapped_asset::mint_test_only( + token_registry::borrow_mut_wrapped_test_only( + &mut registry, + ), + mint_amount + ); + assert!(balance::value(&minted) == mint_amount, 0); + balance::join(&mut total_minted, minted); + i = i + 1; + }; + + let total_supply = + wrapped_asset::total_supply( + token_registry::borrow_wrapped( + ®istry + ) + ); + assert!(total_supply == balance::value(&total_minted), 0); + + // withdraw, check value, and re-deposit native coins into registry + let burn_amount = 69; + let burned = + wrapped_asset::burn_test_only( + token_registry::borrow_mut_wrapped_test_only(&mut registry), + balance::split(&mut total_minted, burn_amount) + ); + assert!(burned == burn_amount, 0); + + let expected_remaining = total_supply - burn_amount; + let remaining = + wrapped_asset::total_supply( + token_registry::borrow_wrapped( + ®istry + ) + ); + assert!(remaining == expected_remaining, 0); + balance::destroy_for_testing(total_minted); + + // Verify registry values. + assert!(token_registry::num_wrapped(®istry) == 1, 0); + assert!(token_registry::num_native(®istry) == 0, 0); + + + let verified = token_registry::verified_asset(®istry); + assert!(token_registry::is_wrapped(&verified), 0); + assert!(token_registry::coin_decimals(&verified) == 7, 0); + + let wrapped_token_meta = coin_wrapped_7::token_meta(); + assert!( + token_registry::token_chain(&verified) == asset_meta::token_chain(&wrapped_token_meta), + 0 + ); + assert!( + token_registry::token_address(&verified) == asset_meta::token_address(&wrapped_token_meta), + 0 + ); + + // Check coin type. + let coin_type = + token_registry::coin_type_for( + ®istry, + token_registry::token_chain(&verified), + external_address::to_bytes( + token_registry::token_address(&verified) + ) + ); + assert!( + coin_type == type_name::into_string(type_name::get()), + 0 + ); + + + // Clean up. + token_registry::destroy(registry); + asset_meta::destroy(wrapped_token_meta); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + #[expected_failure(abort_code = sui::dynamic_field::EFieldAlreadyExists)] + /// In this negative test case, we try to register a native token twice. + fun test_cannot_add_new_native_again() { + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + // Initialize new coin. + coin_native_10::init_test_only(test_scenario::ctx(scenario)); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + // Initialize new token registry. + let registry = + token_registry::new_test_only(test_scenario::ctx(scenario)); + + let coin_meta = coin_native_10::take_metadata(scenario); + + // Add new native asset. + token_registry::add_new_native_test_only( + &mut registry, + &coin_meta + ); + + // You shall not pass! + // + // NOTE: We don't have a custom error for this. This will trigger a + // `sui::dynamic_field` error. + token_registry::add_new_native_test_only( + &mut registry, + &coin_meta + ); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = sui::dynamic_field::EFieldTypeMismatch)] + // In this negative test case, we attempt to deposit a wrapped token into + // a TokenRegistry object, resulting in failure. A wrapped coin can + // only be minted and burned, not deposited. + fun test_cannot_deposit_wrapped_asset() { + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let treasury_cap = + coin_wrapped_7::init_and_take_treasury_cap( + scenario, + caller + ); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + // Initialize new token registry. + let registry = + token_registry::new_test_only(test_scenario::ctx(scenario)); + + let coin_meta = test_scenario::take_shared>(scenario); + + token_registry::add_new_wrapped_test_only( + &mut registry, + coin_wrapped_7::token_meta(), + &mut coin_meta, + treasury_cap, + test_scenario::ctx(scenario) + ); + + test_scenario::return_shared(coin_meta); + + // Mint some wrapped coins and attempt to deposit balance. + let minted = + wrapped_asset::mint_test_only( + token_registry::borrow_mut_wrapped_test_only( + &mut registry + ), + 420420420 + ); + + let verified = token_registry::verified_asset(®istry); + assert!(token_registry::is_wrapped(&verified), 0); + + // You shall not pass! + // + // NOTE: We don't have a custom error for this. This will trigger a + // `sui::dynamic_field` error. + native_asset::deposit_test_only( + token_registry::borrow_mut_native_test_only( + &mut registry + ), + minted + ); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = sui::dynamic_field::EFieldTypeMismatch)] + // In this negative test case, we attempt to deposit a wrapped token into + // a TokenRegistry object, resulting in failure. A wrapped coin can + // only be minted and burned, not deposited. + fun test_cannot_mint_native_asset() { + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + coin_native_10::init_test_only(test_scenario::ctx(scenario)); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + // Initialize new token registry. + let registry = + token_registry::new_test_only(test_scenario::ctx(scenario)); + + let coin_meta = coin_native_10::take_metadata(scenario); + token_registry::add_new_native_test_only( + &mut registry, + &coin_meta + ); + + // Show that this asset is not wrapped. + let verified = token_registry::verified_asset(®istry); + assert!(!token_registry::is_wrapped(&verified), 0); + + // You shall not pass! + // + // NOTE: We don't have a custom error for this. This will trigger a + // `sui::dynamic_field` error. + let minted = + wrapped_asset::mint_test_only( + token_registry::borrow_mut_wrapped_test_only( + &mut registry + ), + 420 + ); + + // Clean up. + balance::destroy_for_testing(minted); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = token_registry::E_ALREADY_WRAPPED)] + fun test_cannot_add_new_wrapped_with_same_canonical_info() { + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + // Initialize new coin. + let treasury_cap = + coin_wrapped_7::init_and_take_treasury_cap( + scenario, + caller + ); + + // Initialize other coin + coin_native_10::init_test_only(test_scenario::ctx(scenario)); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + // Initialize new token registry. + let registry = + token_registry::new_test_only(test_scenario::ctx(scenario)); + + let coin_meta = test_scenario::take_shared>(scenario); + + // Register wrapped asset. + token_registry::add_new_wrapped_test_only( + &mut registry, + coin_wrapped_7::token_meta(), + &mut coin_meta, + treasury_cap, + test_scenario::ctx(scenario) + ); + + test_scenario::return_shared(coin_meta); + + let coin_meta = coin_native_10::take_metadata(scenario); + let treasury_cap = coin_native_10::take_treasury_cap(scenario); + + // You shall not pass! + token_registry::add_new_wrapped_test_only( + &mut registry, + coin_wrapped_7::token_meta(), + &mut coin_meta, + treasury_cap, + test_scenario::ctx(scenario) + ); + + abort 42 + } +} diff --git a/sui/token_bridge/sources/resources/wrapped_asset.move b/sui/token_bridge/sources/resources/wrapped_asset.move new file mode 100644 index 000000000..fccc6a33b --- /dev/null +++ b/sui/token_bridge/sources/resources/wrapped_asset.move @@ -0,0 +1,806 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements two custom types relating to Token Bridge wrapped +/// assets. These assets have been attested from foreign networks, whose +/// metadata is stored in `ForeignInfo`. The Token Bridge contract is the +/// only authority that can mint and burn these assets via `Supply`. +/// +/// See `create_wrapped` and 'token_registry' modules for more details. +module token_bridge::wrapped_asset { + use std::string::{String}; + use sui::balance::{Self, Balance}; + use sui::coin::{Self, TreasuryCap, CoinMetadata}; + use sui::package::{Self, UpgradeCap}; + use wormhole::external_address::{ExternalAddress}; + use wormhole::state::{chain_id}; + + use token_bridge::string_utils; + use token_bridge::asset_meta::{Self, AssetMeta}; + use token_bridge::normalized_amount::{cap_decimals}; + + friend token_bridge::complete_transfer; + friend token_bridge::create_wrapped; + friend token_bridge::token_registry; + friend token_bridge::transfer_tokens; + + /// Token chain ID matching Sui's are not allowed. + const E_SUI_CHAIN: u64 = 0; + /// Canonical token info does match `AssetMeta` payload. + const E_ASSET_META_MISMATCH: u64 = 1; + /// Coin decimals don't match the VAA. + const E_DECIMALS_MISMATCH: u64 = 2; + + /// Container storing foreign asset info. + struct ForeignInfo has store { + token_chain: u16, + token_address: ExternalAddress, + native_decimals: u8, + symbol: String + } + + /// Container managing `ForeignInfo` and `TreasuryCap` for a wrapped asset + /// coin type. + struct WrappedAsset has store { + info: ForeignInfo, + treasury_cap: TreasuryCap, + decimals: u8, + upgrade_cap: UpgradeCap + } + + /// Create new `WrappedAsset`. + /// + /// See `token_registry` module for more info. + public(friend) fun new( + token_meta: AssetMeta, + coin_meta: &mut CoinMetadata, + treasury_cap: TreasuryCap, + upgrade_cap: UpgradeCap + ): WrappedAsset { + // Verify that the upgrade cap is from the same package as coin type. + // This cap should not have been modified prior to creating this asset + // (i.e. should have the default upgrade policy and build version == 1). + wormhole::package_utils::assert_package_upgrade_cap( + &upgrade_cap, + package::compatible_policy(), + 1 + ); + + let ( + token_address, + token_chain, + native_decimals, + symbol, + name + ) = asset_meta::unpack(token_meta); + + // Protect against adding `AssetMeta` which has Sui's chain ID. + assert!(token_chain != chain_id(), E_SUI_CHAIN); + + // Set metadata. + coin::update_name(&mut treasury_cap, coin_meta, name); + coin::update_symbol(&mut treasury_cap, coin_meta, string_utils::to_ascii(&symbol)); + + let decimals = cap_decimals(native_decimals); + + // Ensure that the `C` type has the right number of decimals. This is + // the only field in the coinmeta that cannot be changed after the fact, + // so we expect to receive one that already has the correct decimals + // set. + assert!(decimals == coin::get_decimals(coin_meta), E_DECIMALS_MISMATCH); + + let info = + ForeignInfo { + token_address, + token_chain, + native_decimals, + symbol + }; + + WrappedAsset { + info, + treasury_cap, + decimals, + upgrade_cap + } + } + + #[test_only] + public fun new_test_only( + token_meta: AssetMeta, + coin_meta: &mut CoinMetadata, + treasury_cap: TreasuryCap, + upgrade_cap: UpgradeCap + ): WrappedAsset { + new(token_meta, coin_meta, treasury_cap, upgrade_cap) + } + + /// Update existing `ForeignInfo` using new `AssetMeta`. + /// + /// See `token_registry` module for more info. + public(friend) fun update_metadata( + self: &mut WrappedAsset, + coin_meta: &mut CoinMetadata, + token_meta: AssetMeta + ) { + // NOTE: We ignore `native_decimals` because we do not enforce that + // an asset's decimals on a foreign network needs to stay the same. + let ( + token_address, + token_chain, + _native_decimals, + symbol, + name + ) = asset_meta::unpack(token_meta); + + // Verify canonical token info. Also check that the native decimals + // have not changed (because changing this info is not desirable, as + // this change means the supply changed on its native network). + // + // NOTE: This implicitly verifies that `token_chain` is not Sui's + // because this was checked already when the asset was first added. + let (expected_chain, expected_address) = canonical_info(self); + assert!( + ( + token_chain == expected_chain && + token_address == expected_address + ), + E_ASSET_META_MISMATCH + ); + + // Finally only update the name and symbol. + self.info.symbol = symbol; + coin::update_name(&mut self.treasury_cap, coin_meta, name); + coin::update_symbol(&mut self.treasury_cap, coin_meta, string_utils::to_ascii(&symbol)); + } + + #[test_only] + public fun update_metadata_test_only( + self: &mut WrappedAsset, + coin_meta: &mut CoinMetadata, + token_meta: AssetMeta + ) { + update_metadata(self, coin_meta, token_meta) + } + + /// Retrieve immutable reference to `ForeignInfo`. + public fun info(self: &WrappedAsset): &ForeignInfo { + &self.info + } + + /// Retrieve canonical token chain ID from `ForeignInfo`. + public fun token_chain(info: &ForeignInfo): u16 { + info.token_chain + } + + /// Retrieve canonical token address from `ForeignInfo`. + public fun token_address(info: &ForeignInfo): ExternalAddress { + info.token_address + } + + /// Retrieve decimal amount from `ForeignInfo`. + /// + /// NOTE: This is for informational purposes. This decimal amount is not + /// used for any calculations. + public fun native_decimals(info: &ForeignInfo): u8 { + info.native_decimals + } + + /// Retrieve asset's symbol (UTF-8) from `ForeignMetadata`. + /// + /// NOTE: This value can be updated. + public fun symbol(info: &ForeignInfo): String { + info.symbol + } + + /// Retrieve total minted supply. + public fun total_supply(self: &WrappedAsset): u64 { + coin::total_supply(&self.treasury_cap) + } + + /// Retrieve decimals for this wrapped asset. For any asset whose native + /// decimals is greater than the cap (8), this will be 8. + /// + /// See `normalized_amount` module for more info. + public fun decimals(self: &WrappedAsset): u8 { + self.decimals + } + + /// Retrieve canonical token chain ID and token address. + public fun canonical_info( + self: &WrappedAsset + ): (u16, ExternalAddress) { + (self.info.token_chain, self.info.token_address) + } + + /// Burn a given `Balance`. `Balance` originates from an outbound token + /// transfer for a wrapped asset. + /// + /// See `transfer_tokens` module for more info. + public(friend) fun burn( + self: &mut WrappedAsset, + burned: Balance + ): u64 { + balance::decrease_supply(coin::supply_mut(&mut self.treasury_cap), burned) + } + + #[test_only] + public fun burn_test_only( + self: &mut WrappedAsset, + burned: Balance + ): u64 { + burn(self, burned) + } + + /// Mint a given amount. This amount is determined by an inbound token + /// transfer payload for a wrapped asset. + /// + /// See `complete_transfer` module for more info. + public(friend) fun mint( + self: &mut WrappedAsset, + amount: u64 + ): Balance { + coin::mint_balance(&mut self.treasury_cap, amount) + } + + #[test_only] + public fun mint_test_only( + self: &mut WrappedAsset, + amount: u64 + ): Balance { + mint(self, amount) + } + + #[test_only] + public fun destroy(asset: WrappedAsset) { + let WrappedAsset { + info, + treasury_cap, + decimals: _, + upgrade_cap + } = asset; + sui::test_utils::destroy(treasury_cap); + + let ForeignInfo { + token_chain: _, + token_address: _, + native_decimals: _, + symbol: _ + } = info; + + sui::package::make_immutable(upgrade_cap); + } +} + +#[test_only] +module token_bridge::wrapped_asset_tests { + use std::string::{Self}; + use sui::balance::{Self}; + use sui::coin::{Self, CoinMetadata}; + use sui::object::{Self}; + use sui::package::{Self}; + use sui::test_scenario::{Self}; + use wormhole::external_address::{Self}; + use wormhole::state::{chain_id}; + + use token_bridge::asset_meta::{Self}; + use token_bridge::string_utils; + use token_bridge::coin_native_10::{COIN_NATIVE_10, Self}; + use token_bridge::coin_wrapped_12::{COIN_WRAPPED_12, Self}; + use token_bridge::coin_wrapped_7::{COIN_WRAPPED_7, Self}; + use token_bridge::token_bridge_scenario::{person}; + use token_bridge::wrapped_asset::{Self}; + + #[test] + fun test_wrapped_asset_7() { + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let parsed_meta = coin_wrapped_7::token_meta(); + let expected_token_chain = asset_meta::token_chain(&parsed_meta); + let expected_token_address = asset_meta::token_address(&parsed_meta); + let expected_native_decimals = + asset_meta::native_decimals(&parsed_meta); + let expected_symbol = asset_meta::symbol(&parsed_meta); + let expected_name = asset_meta::name(&parsed_meta); + + // Publish coin. + let treasury_cap = + coin_wrapped_7::init_and_take_treasury_cap( + scenario, + caller + ); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + // Upgrade cap belonging to coin type. + let upgrade_cap = + package::test_publish( + object::id_from_address(@token_bridge), + test_scenario::ctx(scenario) + ); + + let coin_meta: CoinMetadata = test_scenario::take_shared(scenario); + + // Make new. + let asset = + wrapped_asset::new_test_only( + parsed_meta, + &mut coin_meta, + treasury_cap, + upgrade_cap + ); + + // Verify members. + let info = wrapped_asset::info(&asset); + assert!( + wrapped_asset::token_chain(info) == expected_token_chain, + 0 + ); + assert!( + wrapped_asset::token_address(info) == expected_token_address, + 0 + ); + assert!( + wrapped_asset::native_decimals(info) == expected_native_decimals, + 0 + ); + assert!(coin::get_symbol(&coin_meta) == string_utils::to_ascii(&expected_symbol), 0); + assert!(coin::get_name(&coin_meta) == expected_name, 0); + assert!(wrapped_asset::total_supply(&asset) == 0, 0); + + let (token_chain, token_address) = + wrapped_asset::canonical_info(&asset); + assert!(token_chain == expected_token_chain, 0); + assert!(token_address == expected_token_address, 0); + + // Decimals are read from `CoinMetadata`, but in this case will agree + // with the value encoded in the VAA. + assert!(wrapped_asset::decimals(&asset) == expected_native_decimals, 0); + assert!(coin::get_decimals(&coin_meta) == expected_native_decimals, 0); + + // Change name and symbol for update. + let new_symbol = std::ascii::into_bytes(coin::get_symbol(&coin_meta)); + + std::vector::append(&mut new_symbol, b"??? and profit"); + assert!(new_symbol != *string::bytes(&expected_symbol), 0); + + let new_name = coin::get_name(&coin_meta); + string::append(&mut new_name, string::utf8(b"??? and profit")); + assert!(new_name != expected_name, 0); + + let updated_meta = + asset_meta::new( + expected_token_address, + expected_token_chain, + expected_native_decimals, + string::utf8(new_symbol), + new_name + ); + + // Update metadata now. + wrapped_asset::update_metadata_test_only(&mut asset, &mut coin_meta, updated_meta); + + assert!(coin::get_symbol(&coin_meta) == std::ascii::string(new_symbol), 0); + assert!(coin::get_name(&coin_meta) == new_name, 0); + + // Try to mint. + let mint_amount = 420; + let collected = balance::zero(); + let (i, n) = (0, 8); + while (i < n) { + let minted = + wrapped_asset::mint_test_only(&mut asset, mint_amount); + assert!(balance::value(&minted) == mint_amount, 0); + balance::join(&mut collected, minted); + i = i + 1; + }; + assert!(balance::value(&collected) == n * mint_amount, 0); + assert!( + wrapped_asset::total_supply(&asset) == balance::value(&collected), + 0 + ); + + // Now try to burn. + let burn_amount = 69; + let i = 0; + while (i < n) { + let burned = balance::split(&mut collected, burn_amount); + let check_amount = + wrapped_asset::burn_test_only(&mut asset, burned); + assert!(check_amount == burn_amount, 0); + i = i + 1; + }; + let remaining = n * mint_amount - n * burn_amount; + assert!(wrapped_asset::total_supply(&asset) == remaining, 0); + assert!(balance::value(&collected) == remaining, 0); + + test_scenario::return_shared(coin_meta); + + // Clean up. + balance::destroy_for_testing(collected); + wrapped_asset::destroy(asset); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + fun test_wrapped_asset_12() { + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let parsed_meta = coin_wrapped_12::token_meta(); + let expected_token_chain = asset_meta::token_chain(&parsed_meta); + let expected_token_address = asset_meta::token_address(&parsed_meta); + let expected_native_decimals = + asset_meta::native_decimals(&parsed_meta); + let expected_symbol = asset_meta::symbol(&parsed_meta); + let expected_name = asset_meta::name(&parsed_meta); + + // Publish coin. + let treasury_cap = + coin_wrapped_12::init_and_take_treasury_cap( + scenario, + caller + ); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + // Upgrade cap belonging to coin type. + let upgrade_cap = + package::test_publish( + object::id_from_address(@token_bridge), + test_scenario::ctx(scenario) + ); + + let coin_meta: CoinMetadata = test_scenario::take_shared(scenario); + + // Make new. + let asset = + wrapped_asset::new_test_only( + parsed_meta, + &mut coin_meta, + treasury_cap, + upgrade_cap + ); + + // Verify members. + let info = wrapped_asset::info(&asset); + assert!( + wrapped_asset::token_chain(info) == expected_token_chain, + 0 + ); + assert!( + wrapped_asset::token_address(info) == expected_token_address, + 0 + ); + assert!( + wrapped_asset::native_decimals(info) == expected_native_decimals, + 0 + ); + assert!(coin::get_symbol(&coin_meta) == string_utils::to_ascii(&expected_symbol), 0); + assert!(coin::get_name(&coin_meta) == expected_name, 0); + assert!(wrapped_asset::total_supply(&asset) == 0, 0); + + let (token_chain, token_address) = + wrapped_asset::canonical_info(&asset); + assert!(token_chain == expected_token_chain, 0); + assert!(token_address == expected_token_address, 0); + + // Decimals are read from `CoinMetadata`, but in this case will not + // agree with the value encoded in the VAA. + assert!(wrapped_asset::decimals(&asset) == 8, 0); + assert!( + coin::get_decimals(&coin_meta) == wrapped_asset::decimals(&asset), + 0 + ); + assert!(wrapped_asset::decimals(&asset) != expected_native_decimals, 0); + + // Change name and symbol for update. + let new_symbol = std::ascii::into_bytes(coin::get_symbol(&coin_meta)); + + std::vector::append(&mut new_symbol, b"??? and profit"); + assert!(new_symbol != *string::bytes(&expected_symbol), 0); + + let new_name = coin::get_name(&coin_meta); + string::append(&mut new_name, string::utf8(b"??? and profit")); + assert!(new_name != expected_name, 0); + + let updated_meta = + asset_meta::new( + expected_token_address, + expected_token_chain, + expected_native_decimals, + string::utf8(new_symbol), + new_name + ); + + // Update metadata now. + wrapped_asset::update_metadata_test_only(&mut asset, &mut coin_meta, updated_meta); + + assert!(coin::get_symbol(&coin_meta) == std::ascii::string(new_symbol), 0); + assert!(coin::get_name(&coin_meta) == new_name, 0); + + // Try to mint. + let mint_amount = 420; + let collected = balance::zero(); + let (i, n) = (0, 8); + while (i < n) { + let minted = + wrapped_asset::mint_test_only(&mut asset, mint_amount); + assert!(balance::value(&minted) == mint_amount, 0); + balance::join(&mut collected, minted); + i = i + 1; + }; + assert!(balance::value(&collected) == n * mint_amount, 0); + assert!( + wrapped_asset::total_supply(&asset) == balance::value(&collected), + 0 + ); + + // Now try to burn. + let burn_amount = 69; + let i = 0; + while (i < n) { + let burned = balance::split(&mut collected, burn_amount); + let check_amount = + wrapped_asset::burn_test_only(&mut asset, burned); + assert!(check_amount == burn_amount, 0); + i = i + 1; + }; + let remaining = n * mint_amount - n * burn_amount; + assert!(wrapped_asset::total_supply(&asset) == remaining, 0); + assert!(balance::value(&collected) == remaining, 0); + + // Clean up. + balance::destroy_for_testing(collected); + wrapped_asset::destroy(asset); + test_scenario::return_shared(coin_meta); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + #[expected_failure(abort_code = wrapped_asset::E_SUI_CHAIN)] + // In this negative test case, we attempt to register a native coin as a + // wrapped coin. + fun test_cannot_new_sui_chain() { + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + // Initialize new coin type. + coin_native_10::init_test_only(test_scenario::ctx(scenario)); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + // Sui's chain ID is not allowed. + let invalid_meta = + asset_meta::new( + external_address::default(), + chain_id(), + 10, + string::utf8(b""), + string::utf8(b"") + ); + + // Upgrade cap belonging to coin type. + let upgrade_cap = + package::test_publish( + object::id_from_address(@token_bridge), + test_scenario::ctx(scenario) + ); + + let treasury_cap = test_scenario::take_shared>(scenario); + let coin_meta = test_scenario::take_shared>(scenario); + + // You shall not pass! + let asset = + wrapped_asset::new_test_only( + invalid_meta, + &mut coin_meta, + treasury_cap, + upgrade_cap + ); + + // Clean up. + wrapped_asset::destroy(asset); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = wrapped_asset::E_ASSET_META_MISMATCH)] + /// In this negative test case, we attempt to update with a mismatching + /// chain. + fun test_cannot_update_metadata_asset_meta_mismatch_token_address() { + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let parsed_meta = coin_wrapped_12::token_meta(); + let expected_token_chain = asset_meta::token_chain(&parsed_meta); + let expected_token_address = asset_meta::token_address(&parsed_meta); + let expected_native_decimals = + asset_meta::native_decimals(&parsed_meta); + + // Publish coin. + let treasury_cap = + coin_wrapped_12::init_and_take_treasury_cap( + scenario, + caller + ); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + // Upgrade cap belonging to coin type. + let upgrade_cap = + package::test_publish( + object::id_from_address(@token_bridge), + test_scenario::ctx(scenario) + ); + + let coin_meta = test_scenario::take_shared(scenario); + + // Make new. + let asset = + wrapped_asset::new_test_only( + parsed_meta, + &mut coin_meta, + treasury_cap, + upgrade_cap + ); + + let invalid_meta = + asset_meta::new( + external_address::default(), + expected_token_chain, + expected_native_decimals, + string::utf8(b""), + string::utf8(b""), + ); + assert!( + asset_meta::token_address(&invalid_meta) != expected_token_address, + 0 + ); + assert!( + asset_meta::token_chain(&invalid_meta) == expected_token_chain, + 0 + ); + assert!( + asset_meta::native_decimals(&invalid_meta) == expected_native_decimals, + 0 + ); + + // You shall not pass! + wrapped_asset::update_metadata_test_only(&mut asset, &mut coin_meta, invalid_meta); + + // Clean up. + wrapped_asset::destroy(asset); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = wrapped_asset::E_ASSET_META_MISMATCH)] + /// In this negative test case, we attempt to update with a mismatching + /// chain. + fun test_cannot_update_metadata_asset_meta_mismatch_token_chain() { + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let parsed_meta = coin_wrapped_12::token_meta(); + let expected_token_chain = asset_meta::token_chain(&parsed_meta); + let expected_token_address = asset_meta::token_address(&parsed_meta); + let expected_native_decimals = + asset_meta::native_decimals(&parsed_meta); + + // Publish coin. + let treasury_cap = + coin_wrapped_12::init_and_take_treasury_cap( + scenario, + caller + ); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + // Upgrade cap belonging to coin type. + let upgrade_cap = + package::test_publish( + object::id_from_address(@token_bridge), + test_scenario::ctx(scenario) + ); + + let coin_meta = test_scenario::take_shared(scenario); + + // Make new. + let asset = + wrapped_asset::new_test_only( + parsed_meta, + &mut coin_meta, + treasury_cap, + upgrade_cap + ); + + let invalid_meta = + asset_meta::new( + expected_token_address, + chain_id(), + expected_native_decimals, + string::utf8(b""), + string::utf8(b""), + ); + assert!( + asset_meta::token_address(&invalid_meta) == expected_token_address, + 0 + ); + assert!( + asset_meta::token_chain(&invalid_meta) != expected_token_chain, + 0 + ); + assert!( + asset_meta::native_decimals(&invalid_meta) == expected_native_decimals, + 0 + ); + + // You shall not pass! + wrapped_asset::update_metadata_test_only(&mut asset, &mut coin_meta, invalid_meta); + + // Clean up. + wrapped_asset::destroy(asset); + + abort 42 + } + + #[test] + #[expected_failure( + abort_code = wormhole::package_utils::E_INVALID_UPGRADE_CAP + )] + fun test_cannot_new_upgrade_cap_mismatch() { + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + // Publish coin. + let treasury_cap = + coin_wrapped_12::init_and_take_treasury_cap( + scenario, + caller + ); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + // Upgrade cap belonging to coin type. + let upgrade_cap = + package::test_publish( + object::id_from_address(@0xbadc0de), + test_scenario::ctx(scenario) + ); + + let coin_meta = test_scenario::take_shared(scenario); + + // You shall not pass! + let asset = + wrapped_asset::new_test_only( + coin_wrapped_12::token_meta(), + &mut coin_meta, + treasury_cap, + upgrade_cap + ); + + // Clean up. + wrapped_asset::destroy(asset); + + abort 42 + } +} diff --git a/sui/token_bridge/sources/setup.move b/sui/token_bridge/sources/setup.move new file mode 100644 index 000000000..6aed059c3 --- /dev/null +++ b/sui/token_bridge/sources/setup.move @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements the mechanism to publish the Token Bridge contract +/// and initialize `State` as a shared object. +module token_bridge::setup { + use sui::object::{Self, UID}; + use sui::package::{Self, UpgradeCap}; + use sui::transfer::{Self}; + use sui::tx_context::{Self, TxContext}; + use wormhole::emitter::{EmitterCap}; + + use token_bridge::state::{Self}; + + /// Capability created at `init`, which will be destroyed once + /// `init_and_share_state` is called. This ensures only the deployer can + /// create the shared `State`. + struct DeployerCap has key, store { + id: UID + } + + /// Called automatically when module is first published. Transfers + /// `DeployerCap` to sender. + /// + /// Only `setup::init_and_share_state` requires `DeployerCap`. + fun init(ctx: &mut TxContext) { + let deployer = DeployerCap { id: object::new(ctx) }; + transfer::transfer(deployer, tx_context::sender(ctx)); + } + + #[test_only] + public fun init_test_only(ctx: &mut TxContext) { + // NOTE: This exists to mock up sui::package for proposed upgrades. + use sui::package::{Self}; + + init(ctx); + + // This will be created and sent to the transaction sender + // automatically when the contract is published. + transfer::public_transfer( + package::test_publish(object::id_from_address(@token_bridge), ctx), + tx_context::sender(ctx) + ); + } + + /// Only the owner of the `DeployerCap` can call this method. This + /// method destroys the capability and shares the `State` object. + public fun complete( + deployer: DeployerCap, + upgrade_cap: UpgradeCap, + emitter_cap: EmitterCap, + governance_chain: u16, + governance_contract: vector, + ctx: &mut TxContext + ) { + wormhole::package_utils::assert_package_upgrade_cap( + &upgrade_cap, + package::compatible_policy(), + 1 + ); + + // Destroy deployer cap. + let DeployerCap { id } = deployer; + object::delete(id); + + // Share new state. + transfer::public_share_object( + state::new( + emitter_cap, + upgrade_cap, + governance_chain, + wormhole::external_address::new_nonzero( + wormhole::bytes32::from_bytes(governance_contract) + ), + ctx + )); + } +} diff --git a/sui/token_bridge/sources/state.move b/sui/token_bridge/sources/state.move new file mode 100644 index 000000000..fbe8dab4a --- /dev/null +++ b/sui/token_bridge/sources/state.move @@ -0,0 +1,396 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements the global state variables for Token Bridge as a +/// shared object. The `State` object is used to perform anything that requires +/// access to data that defines the Token Bridge contract. Examples of which are +/// accessing registered assets and verifying `VAA` intended for Token Bridge by +/// checking the emitter against its own registered emitters. +module token_bridge::state { + use sui::object::{Self, ID, UID}; + use sui::package::{UpgradeCap, UpgradeReceipt, UpgradeTicket}; + use sui::table::{Self, Table}; + use sui::tx_context::{TxContext}; + use wormhole::bytes32::{Self, Bytes32}; + use wormhole::consumed_vaas::{Self, ConsumedVAAs}; + use wormhole::emitter::{EmitterCap}; + use wormhole::external_address::{ExternalAddress}; + use wormhole::package_utils::{Self}; + use wormhole::publish_message::{MessageTicket}; + + use token_bridge::token_registry::{Self, TokenRegistry, VerifiedAsset}; + use token_bridge::version_control::{Self}; + + /// Build digest does not agree with current implementation. + const E_INVALID_BUILD_DIGEST: u64 = 0; + /// Specified version does not match this build's version. + const E_VERSION_MISMATCH: u64 = 1; + /// Emitter has already been used to emit Wormhole messages. + const E_USED_EMITTER: u64 = 2; + + friend token_bridge::attest_token; + friend token_bridge::complete_transfer; + friend token_bridge::complete_transfer_with_payload; + friend token_bridge::create_wrapped; + friend token_bridge::migrate; + friend token_bridge::register_chain; + friend token_bridge::setup; + friend token_bridge::transfer_tokens; + friend token_bridge::transfer_tokens_with_payload; + friend token_bridge::upgrade_contract; + friend token_bridge::vaa; + + /// Capability reflecting that the current build version is used to invoke + /// state methods. + struct LatestOnly has drop {} + + /// Container for all state variables for Token Bridge. + struct State has key, store { + id: UID, + + /// Governance chain ID. + governance_chain: u16, + + /// Governance contract address. + governance_contract: ExternalAddress, + + /// Set of consumed VAA hashes. + consumed_vaas: ConsumedVAAs, + + /// Emitter capability required to publish Wormhole messages. + emitter_cap: EmitterCap, + + /// Registry for foreign Token Bridge contracts. + emitter_registry: Table, + + /// Registry for native and wrapped assets. + token_registry: TokenRegistry, + + /// Upgrade capability. + upgrade_cap: UpgradeCap + } + + /// Create new `State`. This is only executed using the `setup` module. + public(friend) fun new( + emitter_cap: EmitterCap, + upgrade_cap: UpgradeCap, + governance_chain: u16, + governance_contract: ExternalAddress, + ctx: &mut TxContext + ): State { + assert!(wormhole::emitter::sequence(&emitter_cap) == 0, E_USED_EMITTER); + + let state = State { + id: object::new(ctx), + governance_chain, + governance_contract, + consumed_vaas: consumed_vaas::new(ctx), + emitter_cap, + emitter_registry: table::new(ctx), + token_registry: token_registry::new(ctx), + upgrade_cap + }; + + // Set first version and initialize package info. This will be used for + // emitting information of successful migrations. + let upgrade_cap = &state.upgrade_cap; + package_utils::init_package_info( + &mut state.id, + version_control::current_version(), + upgrade_cap + ); + + state + } + + //////////////////////////////////////////////////////////////////////////// + // + // Simple Getters + // + // These methods do not require `LatestOnly` for access. Anyone is free to + // access these values. + // + //////////////////////////////////////////////////////////////////////////// + + /// Retrieve governance module name. + public fun governance_module(): Bytes32 { + // A.K.A. "TokenBridge". + bytes32::new( + x"000000000000000000000000000000000000000000546f6b656e427269646765" + ) + } + + /// Retrieve governance chain ID, which is governance's emitter chain ID. + public fun governance_chain(self: &State): u16 { + self.governance_chain + } + + /// Retrieve governance emitter address. + public fun governance_contract(self: &State): ExternalAddress { + self.governance_contract + } + + /// Retrieve immutable reference to `TokenRegistry`. + public fun borrow_token_registry( + self: &State + ): &TokenRegistry { + &self.token_registry + } + + public fun borrow_emitter_registry( + self: &State + ): &Table { + &self.emitter_registry + } + + public fun verified_asset( + self: &State + ): VerifiedAsset { + token_registry::assert_has(&self.token_registry); + token_registry::verified_asset(&self.token_registry) + } + + #[test_only] + public fun borrow_mut_token_registry_test_only( + self: &mut State + ): &mut TokenRegistry { + borrow_mut_token_registry(&assert_latest_only(self), self) + } + + #[test_only] + public fun migrate_version_test_only( + self: &mut State, + old_version: Old, + new_version: New + ) { + wormhole::package_utils::update_version_type_test_only( + &mut self.id, + old_version, + new_version + ); + } + + #[test_only] + public fun test_upgrade(self: &mut State) { + let test_digest = bytes32::from_bytes(b"new build"); + let ticket = authorize_upgrade(self, test_digest); + let receipt = sui::package::test_upgrade(ticket); + commit_upgrade(self, receipt); + } + + #[test_only] + public fun reverse_migrate_version(self: &mut State) { + package_utils::update_version_type_test_only( + &mut self.id, + version_control::current_version(), + version_control::previous_version() + ); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Privileged `State` Access + // + // This section of methods require a `LatestOnly`, which can only be + // created within the Token Bridge package. This capability allows special + // access to the `State` object where we require that the latest build is + // used for these interactions. + // + // NOTE: A lot of these methods are still marked as `(friend)` as a safety + // precaution. When a package is upgraded, friend modifiers can be + // removed. + // + //////////////////////////////////////////////////////////////////////////// + + /// Obtain a capability to interact with `State` methods. This method checks + /// that we are running the current build. + /// + /// NOTE: This method allows caching the current version check so we avoid + /// multiple checks to dynamic fields. + public(friend) fun assert_latest_only(self: &State): LatestOnly { + package_utils::assert_version( + &self.id, + version_control::current_version() + ); + + LatestOnly {} + } + + /// Obtain a capability to interact with `State` methods. This method checks + /// that we are running the current build and that the specified `Version` + /// equals the current version. This method is useful when external modules + /// invoke Token Bridge and we need to check that the external module's + /// version is up-to-date (e.g. `create_wrapped::prepare_registration`). + /// + /// NOTE: This method allows caching the current version check so we avoid + /// multiple checks to dynamic fields. + public(friend) fun assert_latest_only_specified( + self: &State + ): LatestOnly { + use std::type_name::{get}; + + // Explicitly check the type names. + let current_type = + package_utils::type_of_version(version_control::current_version()); + assert!(current_type == get(), E_VERSION_MISMATCH); + + assert_latest_only(self) + } + + /// Store `VAA` hash as a way to claim a VAA. This method prevents a VAA + /// from being replayed. + public(friend) fun borrow_mut_consumed_vaas( + _: &LatestOnly, + self: &mut State + ): &mut ConsumedVAAs { + borrow_mut_consumed_vaas_unchecked(self) + } + + /// Store `VAA` hash as a way to claim a VAA. This method prevents a VAA + /// from being replayed. + /// + /// NOTE: This method does not require `LatestOnly`. Only methods in the + /// `upgrade_contract` module requires this to be unprotected to prevent + /// a corrupted upgraded contract from bricking upgradability. + public(friend) fun borrow_mut_consumed_vaas_unchecked( + self: &mut State + ): &mut ConsumedVAAs { + &mut self.consumed_vaas + } + + /// Publish Wormhole message using Token Bridge's `EmitterCap`. + public(friend) fun prepare_wormhole_message( + _: &LatestOnly, + self: &mut State, + nonce: u32, + payload: vector + ): MessageTicket { + wormhole::publish_message::prepare_message( + &mut self.emitter_cap, + nonce, + payload, + ) + } + + /// Retrieve mutable reference to `TokenRegistry`. + public(friend) fun borrow_mut_token_registry( + _: &LatestOnly, + self: &mut State + ): &mut TokenRegistry { + &mut self.token_registry + } + + public(friend) fun borrow_mut_emitter_registry( + _: &LatestOnly, + self: &mut State + ): &mut Table { + &mut self.emitter_registry + } + + public(friend) fun current_package(_: &LatestOnly, self: &State): ID { + package_utils::current_package(&self.id) + } + + //////////////////////////////////////////////////////////////////////////// + // + // Upgradability + // + // A special space that controls upgrade logic. These methods are invoked + // via the `upgrade_contract` module. + // + // Also in this section is managing contract migrations, which uses the + // `migrate` module to officially roll state access to the latest build. + // Only those methods that require `LatestOnly` will be affected by an + // upgrade. + // + //////////////////////////////////////////////////////////////////////////// + + /// Issue an `UpgradeTicket` for the upgrade. + /// + /// NOTE: The Sui VM performs a check that this method is executed from the + /// latest published package. If someone were to try to execute this using + /// a stale build, the transaction will revert with `PackageUpgradeError`, + /// specifically `PackageIDDoesNotMatch`. + public(friend) fun authorize_upgrade( + self: &mut State, + package_digest: Bytes32 + ): UpgradeTicket { + let cap = &mut self.upgrade_cap; + package_utils::authorize_upgrade(&mut self.id, cap, package_digest) + } + + /// Finalize the upgrade that ran to produce the given `receipt`. + /// + /// NOTE: The Sui VM performs a check that this method is executed from the + /// latest published package. If someone were to try to execute this using + /// a stale build, the transaction will revert with `PackageUpgradeError`, + /// specifically `PackageIDDoesNotMatch`. + public(friend) fun commit_upgrade( + self: &mut State, + receipt: UpgradeReceipt + ): (ID, ID) { + let cap = &mut self.upgrade_cap; + package_utils::commit_upgrade(&mut self.id, cap, receipt) + } + + /// Method executed by the `migrate` module to roll access from one package + /// to another. This method will be called from the upgraded package. + public(friend) fun migrate_version(self: &mut State) { + package_utils::migrate_version( + &mut self.id, + version_control::previous_version(), + version_control::current_version() + ); + } + + /// As a part of the migration, we verify that the upgrade contract VAA's + /// encoded package digest used in `migrate` equals the one used to conduct + /// the upgrade. + public(friend) fun assert_authorized_digest( + _: &LatestOnly, + self: &State, + digest: Bytes32 + ) { + let authorized = package_utils::authorized_digest(&self.id); + assert!(digest == authorized, E_INVALID_BUILD_DIGEST); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Special State Interaction via Migrate + // + // A VERY special space that manipulates `State` via calling `migrate`. + // + // PLEASE KEEP ANY METHODS HERE AS FRIENDS. We want the ability to remove + // these for future builds. + // + //////////////////////////////////////////////////////////////////////////// + + /// This method is used to make modifications to `State` when `migrate` is + /// called. This method name should change reflecting which version this + /// contract is migrating to. + /// + /// NOTE: Please keep this method as public(friend) because we never want + /// to expose this method as a public method. + public(friend) fun migrate__v__0_2_0(_self: &mut State) { + // Intentionally do nothing. + } + + #[test_only] + /// Bloody hack. + /// + /// This method is used to set up tests where we migrate to a new version, + /// which is meant to test that modules protected by version control will + /// break. + public fun reverse_migrate__v__dummy(_self: &mut State) { + // Intentionally do nothing. + } + + //////////////////////////////////////////////////////////////////////////// + // + // Deprecated + // + // Dumping grounds for old structs and methods. These things should not + // be used in future builds. + // + //////////////////////////////////////////////////////////////////////////// +} diff --git a/sui/token_bridge/sources/structs/asset_meta.move b/sui/token_bridge/sources/structs/asset_meta.move deleted file mode 100644 index 7aaaf9433..000000000 --- a/sui/token_bridge/sources/structs/asset_meta.move +++ /dev/null @@ -1,99 +0,0 @@ -module token_bridge::asset_meta { - use std::vector::{Self}; - use wormhole::serialize::{serialize_u8, serialize_u16, serialize_vector}; - use wormhole::deserialize::{deserialize_u8, deserialize_u16, deserialize_vector}; - use wormhole::cursor::{Self}; - - use wormhole::myu16::{U16}; - use wormhole::external_address::{Self, ExternalAddress}; - - use token_bridge::string32::{Self, String32}; - - friend token_bridge::bridge_state; - friend token_bridge::wrapped; - - //#[test_only] - //friend token_bridge::wrapped_test; - - const E_INVALID_ACTION: u64 = 0; - - struct AssetMeta has copy, store, drop { - /// Address of the token. Left-zero-padded if shorter than 32 bytes - token_address: ExternalAddress, - /// Chain ID of the token - token_chain: U16, - /// Number of decimals of the token (big-endian uint256) - decimals: u8, - /// Symbol of the token (UTF-8) - symbol: String32, - /// Name of the token (UTF-8) - name: String32, - } - - public fun get_token_address(a: &AssetMeta): ExternalAddress { - a.token_address - } - - public fun get_token_chain(a: &AssetMeta): U16 { - a.token_chain - } - - public fun get_decimals(a: &AssetMeta): u8 { - a.decimals - } - - public fun get_symbol(a: &AssetMeta): String32 { - a.symbol - } - - public fun get_name(a: &AssetMeta): String32 { - a.name - } - - public(friend) fun create( - token_address: ExternalAddress, - token_chain: U16, - decimals: u8, - symbol: String32, - name: String32, - ): AssetMeta { - AssetMeta { - token_address, - token_chain, - decimals, - symbol, - name - } - } - - public fun encode(meta: AssetMeta): vector { - let encoded = vector::empty(); - serialize_u8(&mut encoded, 2); - serialize_vector(&mut encoded, external_address::get_bytes(&meta.token_address)); - serialize_u16(&mut encoded, meta.token_chain); - serialize_u8(&mut encoded, meta.decimals); - string32::serialize(&mut encoded, meta.symbol); - string32::serialize(&mut encoded, meta.name); - encoded - } - - public fun parse(meta: vector): AssetMeta { - let cur = cursor::cursor_init(meta); - let action = deserialize_u8(&mut cur); - assert!(action == 2, E_INVALID_ACTION); - let token_address = deserialize_vector(&mut cur, 32); - let token_chain = deserialize_u16(&mut cur); - let decimals = deserialize_u8(&mut cur); - let symbol = string32::deserialize(&mut cur); - let name = string32::deserialize(&mut cur); - cursor::destroy_empty(cur); - AssetMeta { - token_address: external_address::from_bytes(token_address), - token_chain, - decimals, - symbol, - name - } - } - -} diff --git a/sui/token_bridge/sources/structs/transfer.move b/sui/token_bridge/sources/structs/transfer.move deleted file mode 100644 index 172092646..000000000 --- a/sui/token_bridge/sources/structs/transfer.move +++ /dev/null @@ -1,149 +0,0 @@ -module token_bridge::transfer { - use std::vector; - use wormhole::serialize::{ - serialize_u8, - serialize_u16, - }; - use wormhole::deserialize::{ - deserialize_u8, - deserialize_u16, - }; - use wormhole::cursor; - use wormhole::external_address::{Self, ExternalAddress}; - use wormhole::myu16::U16; - - use token_bridge::normalized_amount::{Self, NormalizedAmount}; - - friend token_bridge::transfer_tokens; - - #[test_only] - friend token_bridge::complete_transfer_test; - #[test_only] - friend token_bridge::transfer_test; - - const E_INVALID_ACTION: u64 = 0; - - struct Transfer has drop { - /// Amount being transferred - amount: NormalizedAmount, - /// Address of the token. Left-zero-padded if shorter than 32 bytes - token_address: ExternalAddress, - /// Chain ID of the token - token_chain: U16, - /// Address of the recipient. Left-zero-padded if shorter than 32 bytes - to: ExternalAddress, - /// Chain ID of the recipient - to_chain: U16, - /// Amount of tokens that the user is willing to pay as relayer fee. Must be <= Amount. - fee: NormalizedAmount, - } - - public fun get_amount(a: &Transfer): NormalizedAmount { - a.amount - } - - public fun get_token_address(a: &Transfer): ExternalAddress { - a.token_address - } - - public fun get_token_chain(a: &Transfer): U16 { - a.token_chain - } - - public fun get_to(a: &Transfer): ExternalAddress { - a.to - } - - public fun get_to_chain(a: &Transfer): U16 { - a.to_chain - } - - public fun get_fee(a: &Transfer): NormalizedAmount { - a.fee - } - - public(friend) fun create( - amount: NormalizedAmount, - token_address: ExternalAddress, - token_chain: U16, - to: ExternalAddress, - to_chain: U16, - fee: NormalizedAmount, - ): Transfer { - Transfer { - amount, - token_address, - token_chain, - to, - to_chain, - fee, - } - } - - public fun parse(transfer: vector): Transfer { - let cur = cursor::cursor_init(transfer); - let action = deserialize_u8(&mut cur); - assert!(action == 1, E_INVALID_ACTION); - let amount = normalized_amount::deserialize(&mut cur); - let token_address = external_address::deserialize(&mut cur); - let token_chain = deserialize_u16(&mut cur); - let to = external_address::deserialize(&mut cur); - let to_chain = deserialize_u16(&mut cur); - let fee = normalized_amount::deserialize(&mut cur); - cursor::destroy_empty(cur); - Transfer { - amount, - token_address, - token_chain, - to, - to_chain, - fee, - } - } - - public fun encode(transfer: Transfer): vector { - let encoded = vector::empty(); - serialize_u8(&mut encoded, 1); - normalized_amount::serialize(&mut encoded, transfer.amount); - external_address::serialize(&mut encoded, transfer.token_address); - serialize_u16(&mut encoded, transfer.token_chain); - external_address::serialize(&mut encoded, transfer.to); - serialize_u16(&mut encoded, transfer.to_chain); - normalized_amount::serialize(&mut encoded, transfer.fee); - encoded - } - -} - -#[test_only] -module token_bridge::transfer_test { - use token_bridge::transfer; - use token_bridge::normalized_amount; - use wormhole::external_address; - use wormhole::myu16::{Self as u16}; - - #[test] - public fun parse_roundtrip() { - let amount = normalized_amount::normalize(100, 8); - let token_address = external_address::from_bytes(x"beef"); - let token_chain = u16::from_u64(1); - let to = external_address::from_bytes(x"cafe"); - let to_chain = u16::from_u64(7); - let fee = normalized_amount::normalize(50, 8); - let transfer = transfer::create( - amount, - token_address, - token_chain, - to, - to_chain, - fee, - ); - let transfer = transfer::parse(transfer::encode(transfer)); - assert!(transfer::get_amount(&transfer) == amount, 0); - assert!(transfer::get_token_address(&transfer) == token_address, 0); - assert!(transfer::get_token_chain(&transfer) == token_chain, 0); - assert!(transfer::get_to(&transfer) == to, 0); - assert!(transfer::get_to_chain(&transfer) == to_chain, 0); - assert!(transfer::get_fee(&transfer) == fee, 0); - } -} \ No newline at end of file diff --git a/sui/token_bridge/sources/structs/transfer_result.move b/sui/token_bridge/sources/structs/transfer_result.move deleted file mode 100644 index f650e970e..000000000 --- a/sui/token_bridge/sources/structs/transfer_result.move +++ /dev/null @@ -1,44 +0,0 @@ -module token_bridge::transfer_result { - use wormhole::myu16::U16; - use wormhole::external_address::ExternalAddress; - - use token_bridge::normalized_amount::NormalizedAmount; - - friend token_bridge::transfer_tokens; - - struct TransferResult { - /// Chain ID of the token - token_chain: U16, - /// Address of the token. Left-zero-padded if shorter than 32 bytes - token_address: ExternalAddress, - /// Amount being transferred - normalized_amount: NormalizedAmount, - /// Amount of tokens that the user is willing to pay as relayer fee. Must be <= Amount. - normalized_relayer_fee: NormalizedAmount, - } - - public fun destroy(a: TransferResult): (U16, ExternalAddress, NormalizedAmount, NormalizedAmount) { - let TransferResult { - token_chain, - token_address, - normalized_amount, - normalized_relayer_fee - } = a; - (token_chain, token_address, normalized_amount, normalized_relayer_fee) - } - - public(friend) fun create( - token_chain: U16, - token_address: ExternalAddress, - normalized_amount: NormalizedAmount, - normalized_relayer_fee: NormalizedAmount, - ): TransferResult { - TransferResult { - token_chain, - token_address, - normalized_amount, - normalized_relayer_fee, - } - } - -} \ No newline at end of file diff --git a/sui/token_bridge/sources/structs/transfer_with_payload.move b/sui/token_bridge/sources/structs/transfer_with_payload.move deleted file mode 100644 index dd1a6db68..000000000 --- a/sui/token_bridge/sources/structs/transfer_with_payload.move +++ /dev/null @@ -1,122 +0,0 @@ -module token_bridge::transfer_with_payload { - use std::vector; - use wormhole::serialize::{ - serialize_u8, - serialize_u16, - serialize_vector, - }; - use wormhole::deserialize::{ - deserialize_u8, - deserialize_u16, - }; - use wormhole::cursor; - - use wormhole::myu16::U16; - use wormhole::external_address::{Self, ExternalAddress}; - - use token_bridge::normalized_amount::{Self, NormalizedAmount}; - - friend token_bridge::transfer_tokens; - - const E_INVALID_ACTION: u64 = 0; - - struct TransferWithPayload has store, drop { - /// Amount being transferred (big-endian uint256) - amount: NormalizedAmount, - /// Address of the token. Left-zero-padded if shorter than 32 bytes - token_address: ExternalAddress, - /// Chain ID of the token - token_chain: U16, - /// Address of the recipient. Left-zero-padded if shorter than 32 bytes - to: ExternalAddress, - /// Chain ID of the recipient - to_chain: U16, - /// Address of the message sender. Left-zero-padded if shorter than 32 bytes - from_address: ExternalAddress, - /// An arbitrary payload - payload: vector, - } - - public fun get_amount(a: &TransferWithPayload): NormalizedAmount { - a.amount - } - - public fun get_token_address(a: &TransferWithPayload): ExternalAddress { - a.token_address - } - - public fun get_token_chain(a: &TransferWithPayload): U16 { - a.token_chain - } - - public fun get_to(a: &TransferWithPayload): ExternalAddress { - a.to - } - - public fun get_to_chain(a: &TransferWithPayload): U16 { - a.to_chain - } - - public fun get_from_address(a: &TransferWithPayload): ExternalAddress { - a.from_address - } - - public fun get_payload(a: &TransferWithPayload): vector { - a.payload - } - - public(friend) fun create( - amount: NormalizedAmount, - token_address: ExternalAddress, - token_chain: U16, - to: ExternalAddress, - to_chain: U16, - from_address: ExternalAddress, - payload: vector - ): TransferWithPayload { - TransferWithPayload { - amount, - token_address, - token_chain, - to, - to_chain, - from_address, - payload, - } - } - - public fun encode(transfer: TransferWithPayload): vector { - let encoded = vector::empty(); - serialize_u8(&mut encoded, 3); - normalized_amount::serialize(&mut encoded, transfer.amount); - external_address::serialize(&mut encoded, transfer.token_address); - serialize_u16(&mut encoded, transfer.token_chain); - external_address::serialize(&mut encoded, transfer.to); - serialize_u16(&mut encoded, transfer.to_chain); - external_address::serialize(&mut encoded, transfer.from_address); - serialize_vector(&mut encoded, transfer.payload); - encoded - } - - public fun parse(transfer: vector): TransferWithPayload { - let cur = cursor::cursor_init(transfer); - let action = deserialize_u8(&mut cur); - assert!(action == 3, E_INVALID_ACTION); - let amount = normalized_amount::deserialize(&mut cur); - let token_address = external_address::deserialize(&mut cur); - let token_chain = deserialize_u16(&mut cur); - let to = external_address::deserialize(&mut cur); - let to_chain = deserialize_u16(&mut cur); - let from_address = external_address::deserialize(&mut cur); - let payload = cursor::rest(cur); - TransferWithPayload { - amount, - token_address, - token_chain, - to, - to_chain, - from_address, - payload - } - } -} \ No newline at end of file diff --git a/sui/token_bridge/sources/test/coin_native_10.move b/sui/token_bridge/sources/test/coin_native_10.move new file mode 100644 index 000000000..c87282a15 --- /dev/null +++ b/sui/token_bridge/sources/test/coin_native_10.move @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: Apache 2 + +#[test_only] +module token_bridge::coin_native_10 { + use std::option::{Self}; + use sui::balance::{Self, Balance}; + use sui::coin::{Self, CoinMetadata, TreasuryCap}; + use sui::test_scenario::{Self, Scenario}; + use sui::transfer::{Self}; + use sui::tx_context::{TxContext}; + + use token_bridge::native_asset::{Self}; + use token_bridge::state::{Self}; + use token_bridge::token_registry::{Self}; + + struct COIN_NATIVE_10 has drop {} + + // This module creates a Sui-native token for testing purposes, + // for example in complete_transfer, where we create a native coin, + // mint some and deposit in the token bridge, then complete transfer + // and ultimately transfer a portion of those native coins to a recipient. + fun init(coin_witness: COIN_NATIVE_10, ctx: &mut TxContext) { + let ( + treasury_cap, + coin_metadata + ) = + coin::create_currency( + coin_witness, + 10, + b"DEC10", + b"Decimals 10", + b"Coin with 10 decimals for testing purposes.", + option::none(), + ctx + ); + + // Allow us to mutate metadata if we need. + transfer::public_share_object(coin_metadata); + + // Give everyone access to `TrasuryCap`. + transfer::public_share_object(treasury_cap); + } + + #[test_only] + /// For a test scenario, register this native asset. + /// + /// NOTE: Even though this module is `#[test_only]`, this method is tagged + /// with the same macro as a trick to allow another method within this + /// module to call `init` using OTW. + public fun init_and_register(scenario: &mut Scenario, caller: address) { + use token_bridge::token_bridge_scenario::{return_state, take_state}; + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + // Publish coin. + init(COIN_NATIVE_10 {}, test_scenario::ctx(scenario)); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + let token_bridge_state = take_state(scenario); + let coin_meta = take_metadata(scenario); + + // Register asset. + let registry = + state::borrow_mut_token_registry_test_only(&mut token_bridge_state); + token_registry::add_new_native_test_only(registry, &coin_meta); + + // Clean up. + return_state(token_bridge_state); + return_metadata(coin_meta); + } + + #[test_only] + public fun init_register_and_mint( + scenario: &mut Scenario, + caller: address, + amount: u64 + ): Balance { + // First publish and register. + init_and_register(scenario, caller); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + // Mint. + balance::create_for_testing(amount) + } + + #[test_only] + public fun init_register_and_deposit( + scenario: &mut Scenario, + caller: address, + amount: u64 + ) { + use token_bridge::token_bridge_scenario::{return_state, take_state}; + + let minted = init_register_and_mint(scenario, caller, amount); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + let token_bridge_state = take_state(scenario); + native_asset::deposit_test_only( + token_registry::borrow_mut_native_test_only( + state::borrow_mut_token_registry_test_only( + &mut token_bridge_state + ) + ), + minted + ); + + return_state(token_bridge_state); + } + + #[test_only] + public fun init_test_only(ctx: &mut TxContext) { + init(COIN_NATIVE_10 {}, ctx); + } + + public fun take_metadata( + scenario: &Scenario + ): CoinMetadata { + test_scenario::take_shared(scenario) + } + + public fun return_metadata( + metadata: CoinMetadata + ) { + test_scenario::return_shared(metadata); + } + + public fun take_treasury_cap( + scenario: &Scenario + ): TreasuryCap { + test_scenario::take_shared(scenario) + } + + public fun return_treasury_cap( + treasury_cap: TreasuryCap + ) { + test_scenario::return_shared(treasury_cap); + } + + public fun take_globals( + scenario: &Scenario + ): ( + TreasuryCap, + CoinMetadata + ) { + ( + take_treasury_cap(scenario), + take_metadata(scenario) + ) + } + + public fun return_globals( + treasury_cap: TreasuryCap, + metadata: CoinMetadata + ) { + return_treasury_cap(treasury_cap); + return_metadata(metadata); + } +} diff --git a/sui/token_bridge/sources/test/coin_native_4.move b/sui/token_bridge/sources/test/coin_native_4.move new file mode 100644 index 000000000..889d03620 --- /dev/null +++ b/sui/token_bridge/sources/test/coin_native_4.move @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: Apache 2 + +#[test_only] +module token_bridge::coin_native_4 { + use std::option::{Self}; + use sui::balance::{Self, Balance}; + use sui::coin::{Self, CoinMetadata, TreasuryCap}; + use sui::test_scenario::{Self, Scenario}; + use sui::transfer::{Self}; + use sui::tx_context::{TxContext}; + + use token_bridge::native_asset::{Self}; + use token_bridge::state::{Self}; + use token_bridge::token_registry::{Self}; + + struct COIN_NATIVE_4 has drop {} + + // This module creates a Sui-native token for testing purposes, + // for example in complete_transfer, where we create a native coin, + // mint some and deposit in the token bridge, then complete transfer + // and ultimately transfer a portion of those native coins to a recipient. + fun init(coin_witness: COIN_NATIVE_4, ctx: &mut TxContext) { + let ( + treasury_cap, + coin_metadata + ) = + coin::create_currency( + coin_witness, + 4, + b"DEC4", + b"Decimals 4", + b"Coin with 4 decimals for testing purposes.", + option::none(), + ctx + ); + + // Let's make the metadata shared. + transfer::public_share_object(coin_metadata); + + // Give everyone access to `TrasuryCap`. + transfer::public_share_object(treasury_cap); + } + + #[test_only] + /// For a test scenario, register this native asset. + /// + /// NOTE: Even though this module is `#[test_only]`, this method is tagged + /// with the same macro as a trick to allow another method within this + /// module to call `init` using OTW. + public fun init_and_register(scenario: &mut Scenario, caller: address) { + use token_bridge::token_bridge_scenario::{return_state, take_state}; + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + // Publish coin. + init(COIN_NATIVE_4 {}, test_scenario::ctx(scenario)); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + let token_bridge_state = take_state(scenario); + let coin_meta = take_metadata(scenario); + + // Register asset. + let registry = + state::borrow_mut_token_registry_test_only(&mut token_bridge_state); + token_registry::add_new_native_test_only(registry, &coin_meta); + + // Clean up. + return_state(token_bridge_state); + return_metadata(coin_meta); + } + + #[test_only] + public fun init_register_and_mint( + scenario: &mut Scenario, + caller: address, + amount: u64 + ): Balance { + // First publish and register. + init_and_register(scenario, caller); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + // Mint. + balance::create_for_testing(amount) + } + + #[test_only] + public fun init_register_and_deposit( + scenario: &mut Scenario, + caller: address, + amount: u64 + ) { + use token_bridge::token_bridge_scenario::{return_state, take_state}; + + let minted = init_register_and_mint(scenario, caller, amount); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + let token_bridge_state = take_state(scenario); + native_asset::deposit_test_only( + token_registry::borrow_mut_native_test_only( + state::borrow_mut_token_registry_test_only( + &mut token_bridge_state + ) + ), + minted + ); + + return_state(token_bridge_state); + } + + #[test_only] + public fun init_test_only(ctx: &mut TxContext) { + init(COIN_NATIVE_4 {}, ctx); + } + + public fun take_metadata( + scenario: &Scenario + ): CoinMetadata { + test_scenario::take_shared(scenario) + } + + public fun return_metadata( + metadata: CoinMetadata + ) { + test_scenario::return_shared(metadata); + } + + public fun take_treasury_cap( + scenario: &Scenario + ): TreasuryCap { + test_scenario::take_shared(scenario) + } + + public fun return_treasury_cap( + treasury_cap: TreasuryCap + ) { + test_scenario::return_shared(treasury_cap); + } + + public fun take_globals( + scenario: &Scenario + ): ( + TreasuryCap, + CoinMetadata + ) { + ( + take_treasury_cap(scenario), + take_metadata(scenario) + ) + } + + public fun return_globals( + treasury_cap: TreasuryCap, + metadata: CoinMetadata + ) { + return_treasury_cap(treasury_cap); + return_metadata(metadata); + } +} diff --git a/sui/token_bridge/sources/test/coin_wrapped_12.move b/sui/token_bridge/sources/test/coin_wrapped_12.move new file mode 100644 index 000000000..fe4acb742 --- /dev/null +++ b/sui/token_bridge/sources/test/coin_wrapped_12.move @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: Apache 2 + +#[test_only] +module token_bridge::coin_wrapped_12 { + use sui::balance::{Balance}; + use sui::package::{UpgradeCap}; + use sui::coin::{CoinMetadata, TreasuryCap}; + use sui::test_scenario::{Self, Scenario}; + use sui::transfer::{Self}; + use sui::tx_context::{Self, TxContext}; + + use token_bridge::asset_meta::{Self, AssetMeta}; + use token_bridge::create_wrapped::{Self, WrappedAssetSetup}; + use token_bridge::state::{Self}; + use token_bridge::token_registry::{Self}; + use token_bridge::wrapped_asset::{Self}; + + use token_bridge::version_control::{V__0_2_0 as V__CURRENT}; + + struct COIN_WRAPPED_12 has drop {} + + const VAA: vector = + x"0100000000010080366065746148420220f25a6275097370e8db40984529a6676b7a5fc9feb11755ec49ca626b858ddfde88d15601f85ab7683c5f161413b0412143241c700aff010000000100000001000200000000000000000000000000000000000000000000000000000000deadbeef000000000150eb23000200000000000000000000000000000000000000000000000000000000beefface00020c424545460000000000000000000000000000000000000000000000000000000042656566206661636520546f6b656e0000000000000000000000000000000000"; + + const UPDATED_VAA: vector = + x"0100000000010062f4dcd21bbbc4af8b8baaa2da3a0b168efc4c975de5b828c7a3c710b67a0a0d476d10a74aba7a7867866daf97d1372d8e6ee62ccc5ae522e3e603c67fa23787000000000000000045000200000000000000000000000000000000000000000000000000000000deadbeef00000000000000010f0200000000000000000000000000000000000000000000000000000000beefface00020c424545463f3f3f20616e642070726f666974000000000000000000000000000042656566206661636520546f6b656e3f3f3f20616e642070726f666974000000"; + + fun init(witness: COIN_WRAPPED_12, ctx: &mut TxContext) { + let ( + setup, + upgrade_cap + ) = + create_wrapped::new_setup_current( + witness, + 8, // capped to 8 + ctx + ); + transfer::public_transfer(setup, tx_context::sender(ctx)); + transfer::public_transfer(upgrade_cap, tx_context::sender(ctx)); + } + + #[test_only] + public fun init_test_only(ctx: &mut TxContext) { + init(COIN_WRAPPED_12 {}, ctx); + } + + + public fun encoded_vaa(): vector { + VAA + } + + public fun encoded_updated_vaa(): vector { + UPDATED_VAA + } + + public fun token_meta(): AssetMeta { + asset_meta::deserialize_test_only( + wormhole::vaa::peel_payload_from_vaa(&VAA) + ) + } + + public fun updated_token_meta(): AssetMeta { + asset_meta::deserialize_test_only( + wormhole::vaa::peel_payload_from_vaa(&UPDATED_VAA) + ) + } + + #[test_only] + /// for a test scenario, simply deploy the coin and expose `Supply`. + public fun init_and_take_treasury_cap( + scenario: &mut Scenario, + caller: address + ): TreasuryCap { + use token_bridge::create_wrapped; + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + // Publish coin. + init(COIN_WRAPPED_12 {}, test_scenario::ctx(scenario)); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + create_wrapped::take_treasury_cap( + test_scenario::take_from_sender(scenario) + ) + } + + #[test_only] + /// For a test scenario, register this wrapped asset. + /// + /// NOTE: Even though this module is `#[test_only]`, this method is tagged + /// with the same macro as a trick to allow another method within this + /// module to call `init` using OTW. + public fun init_and_register( + scenario: &mut Scenario, + caller: address + ) { + use token_bridge::token_bridge_scenario::{return_state, take_state}; + use wormhole::wormhole_scenario::{parse_and_verify_vaa}; + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + // Publish coin. + init(COIN_WRAPPED_12 {}, test_scenario::ctx(scenario)); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + let token_bridge_state = take_state(scenario); + + let verified_vaa = parse_and_verify_vaa(scenario, VAA); + let msg = + token_bridge::vaa::verify_only_once( + &mut token_bridge_state, + verified_vaa + ); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + let coin_meta = + test_scenario::take_shared>(scenario); + + // Register the attested asset. + create_wrapped::complete_registration( + &mut token_bridge_state, + &mut coin_meta, + test_scenario::take_from_sender< + WrappedAssetSetup + >( + scenario + ), + test_scenario::take_from_sender(scenario), + msg + ); + + test_scenario::return_shared(coin_meta); + + // Clean up. + return_state(token_bridge_state); + } + + #[test_only] + /// NOTE: Even though this module is `#[test_only]`, this method is tagged + /// with the same macro as a trick to allow another method within this + /// module to call `init` using OTW. + public fun init_register_and_mint( + scenario: &mut Scenario, + caller: address, + amount: u64 + ): Balance { + use token_bridge::token_bridge_scenario::{return_state, take_state}; + + // First publish and register. + init_and_register(scenario, caller); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + let token_bridge_state = take_state(scenario); + let minted = + wrapped_asset::mint_test_only( + token_registry::borrow_mut_wrapped_test_only( + state::borrow_mut_token_registry_test_only( + &mut token_bridge_state + ) + ), + amount + ); + + return_state(token_bridge_state); + + minted + } +} + +#[test_only] +module token_bridge::coin_wrapped_12_tests { + use token_bridge::asset_meta::{Self}; + use token_bridge::coin_wrapped_12::{token_meta}; + + #[test] + fun test_native_decimals() { + let meta = token_meta(); + assert!(asset_meta::native_decimals(&meta) == 12, 0); + asset_meta::destroy(meta); + } +} diff --git a/sui/token_bridge/sources/test/coin_wrapped_7.move b/sui/token_bridge/sources/test/coin_wrapped_7.move new file mode 100644 index 000000000..5b0717ffa --- /dev/null +++ b/sui/token_bridge/sources/test/coin_wrapped_7.move @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: Apache 2 + +#[test_only] +module token_bridge::coin_wrapped_7 { + use sui::balance::{Balance}; + use sui::coin::{CoinMetadata, TreasuryCap}; + use sui::package::{UpgradeCap}; + use sui::test_scenario::{Self, Scenario}; + use sui::transfer::{Self}; + use sui::tx_context::{Self, TxContext}; + + use token_bridge::asset_meta::{Self, AssetMeta}; + use token_bridge::create_wrapped::{Self, WrappedAssetSetup}; + use token_bridge::state::{Self}; + use token_bridge::token_registry::{Self}; + use token_bridge::wrapped_asset::{Self}; + + use token_bridge::version_control::{V__0_2_0 as V__CURRENT}; + + struct COIN_WRAPPED_7 has drop {} + + // TODO: need to fix the emitter address + // +------------------------------------------------------------------------------+ + // | Wormhole VAA v1 | nonce: 69 | time: 0 | + // | guardian set #0 | #1 | consistency: 15 | + // |------------------------------------------------------------------------------| + // | Signature: | + // | #0: 3d8fd671611d84801dc9d14a07835e8729d217b1aac77b054175d0f91294... | + // |------------------------------------------------------------------------------| + // | Emitter: 0x00000000000000000000000000000000deadbeef (Ethereum) | + // |------------------------------------------------------------------------------| + // | Token attestation | + // | decimals: 7 | + // | Token: 0x00000000000000000000000000000000deafface (Ethereum) | + // | Symbol: DEC7 | + // | Name: DECIMALS 7 | + // +------------------------------------------------------------------------------+ + const VAA: vector = + x"010000000001003d8fd671611d84801dc9d14a07835e8729d217b1aac77b054175d0f91294040742a1ed6f3e732b2fbf208e64422816accf89dd0cd3ead20d2e0fb3d372ce221c010000000000000045000200000000000000000000000000000000000000000000000000000000deadbeef00000000000000010f0200000000000000000000000000000000000000000000000000000000deafface000207000000000000000000000000000000000000000000000000000000004445433700000000000000000000000000000000000000000000444543494d414c532037"; + + fun init(witness: COIN_WRAPPED_7, ctx: &mut TxContext) { + let ( + setup, + upgrade_cap + ) = + create_wrapped::new_setup_current( + witness, + 7, + ctx + ); + transfer::public_transfer(setup, tx_context::sender(ctx)); + transfer::public_transfer(upgrade_cap, tx_context::sender(ctx)); + } + + #[test_only] + public fun init_test_only(ctx: &mut TxContext) { + init(COIN_WRAPPED_7 {}, ctx); + } + + public fun encoded_vaa(): vector { + VAA + } + + public fun token_meta(): AssetMeta { + asset_meta::deserialize_test_only( + wormhole::vaa::peel_payload_from_vaa(&VAA) + ) + } + + #[test_only] + /// for a test scenario, simply deploy the coin and expose `TreasuryCap`. + public fun init_and_take_treasury_cap( + scenario: &mut Scenario, + caller: address + ): TreasuryCap { + use token_bridge::create_wrapped; + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + // Publish coin. + init(COIN_WRAPPED_7 {}, test_scenario::ctx(scenario)); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + create_wrapped::take_treasury_cap( + test_scenario::take_from_sender(scenario) + ) + } + + #[test_only] + /// For a test scenario, register this wrapped asset. + /// + /// NOTE: Even though this module is `#[test_only]`, this method is tagged + /// with the same macro as a trick to allow another method within this + /// module to call `init` using OTW. + public fun init_and_register( + scenario: &mut Scenario, + caller: address + ) { + use token_bridge::token_bridge_scenario::{return_state, take_state}; + use wormhole::wormhole_scenario::{parse_and_verify_vaa}; + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + // Publish coin. + init(COIN_WRAPPED_7 {}, test_scenario::ctx(scenario)); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + let token_bridge_state = take_state(scenario); + + let verified_vaa = parse_and_verify_vaa(scenario, VAA); + let msg = + token_bridge::vaa::verify_only_once( + &mut token_bridge_state, + verified_vaa + ); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + let coin_meta = + test_scenario::take_shared>(scenario); + + // Register the attested asset. + create_wrapped::complete_registration( + &mut token_bridge_state, + &mut coin_meta, + test_scenario::take_from_sender< + WrappedAssetSetup + >( + scenario + ), + test_scenario::take_from_sender(scenario), + msg + ); + + test_scenario::return_shared(coin_meta); + + // Clean up. + return_state(token_bridge_state); + } + + #[test_only] + /// NOTE: Even though this module is `#[test_only]`, this method is tagged + /// with the same macro as a trick to allow another method within this + /// module to call `init` using OTW. + public fun init_register_and_mint( + scenario: &mut Scenario, + caller: address, + amount: u64 + ): Balance { + use token_bridge::token_bridge_scenario::{return_state, take_state}; + + // First publish and register. + init_and_register(scenario, caller); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + let token_bridge_state = take_state(scenario); + let minted = + wrapped_asset::mint_test_only( + token_registry::borrow_mut_wrapped_test_only( + state::borrow_mut_token_registry_test_only( + &mut token_bridge_state + ) + ), + amount + ); + + return_state(token_bridge_state); + + minted + } +} + +#[test_only] +module token_bridge::coin_wrapped_7_tests { + use token_bridge::asset_meta::{Self}; + use token_bridge::coin_wrapped_7::{token_meta}; + + #[test] + fun test_native_decimals() { + let meta = token_meta(); + assert!(asset_meta::native_decimals(&meta) == 7, 0); + asset_meta::destroy(meta); + } +} diff --git a/sui/token_bridge/sources/test/dummy_message.move b/sui/token_bridge/sources/test/dummy_message.move new file mode 100644 index 000000000..d3784e5a9 --- /dev/null +++ b/sui/token_bridge/sources/test/dummy_message.move @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: Apache 2 + +#[test_only] +module token_bridge::dummy_message { + public fun encoded_transfer(): vector { + // let decimals = 8; + // let expected_amount = normalized_amount::from_raw(234567890, decimals); + // let expected_token_address = external_address::from_address(@0xbeef); + // let expected_token_chain = 1; + // let expected_recipient = external_address::from_address(@0xcafe); + // let expected_recipient_chain = 7; + // let expected_relayer_fee = + // normalized_amount::from_raw(123456789, decimals); + x"01000000000000000000000000000000000000000000000000000000000dfb38d2000000000000000000000000000000000000000000000000000000000000beef0001000000000000000000000000000000000000000000000000000000000000cafe000700000000000000000000000000000000000000000000000000000000075bcd15" + } + + public fun encoded_transfer_with_payload(): vector { + // let expected_amount = normalized_amount::from_raw(234567890, 8); + // let expected_token_address = external_address::from_address(@0xbeef); + // let expected_token_chain = 1; + // let expected_recipient = external_address::from_address(@0xcafe); + // let expected_recipient_chain = 7; + // let expected_sender = external_address::from_address(@0xdeadbeef); + // let expected_payload = b"All your base are belong to us."; + x"03000000000000000000000000000000000000000000000000000000000dfb38d2000000000000000000000000000000000000000000000000000000000000beef0001000000000000000000000000000000000000000000000000000000000000cafe0007381dd9078c322a4663c392761a0211b527c127b29583851217f948d62131f409416c6c20796f75722062617365206172652062656c6f6e6720746f2075732e" + } + + public fun encoded_transfer_vaa_native_with_fee(): vector { + // emitterChain: 2, + // emitterAddress: '0x00000000000000000000000000000000000000000000000000000000deadbeef', + // amount: 3000n, + // tokenAddress: '0x0000000000000000000000000000000000000000000000000000000000000001', + // tokenChain: 21, + // toAddress: '0x000000000000000000000000000000000000000000000000000000000000b0b1', + // chain: 21, + // fee: 1000n + x"01000000000100bce07d9dce4e16f564788b0885fa31fa6c5c1bb7ee1f7d0948b8f2c2ae9e87ea4eccfc86affb8b7cf8bfcc774effe0fa7a54066d8a4310a4bb0350fd3097ab25000000000000000000000200000000000000000000000000000000000000000000000000000000deadbeef00000000000000010f010000000000000000000000000000000000000000000000000000000000000bb80bc9c77af025eb7f73940ad00c9d6f06d45253339a110b0f9ff03b822e5877d30015000000000000000000000000000000000000000000000000000000000000b0b1001500000000000000000000000000000000000000000000000000000000000003e8" + } + + public fun encoded_transfer_with_payload_vaa_native(): vector { + // emitterChain: 2, + // emitterAddress: '0x00000000000000000000000000000000000000000000000000000000deadbeef', + // amount: 3000n, + // tokenAddress: '0x0000000000000000000000000000000000000000000000000000000000000001', + // tokenChain: 21, + // toAddress: '0x381dd9078c322a4663c392761a0211b527c127b29583851217f948d62131f409', + // chain: 21, + // fromAddress: '0x000000000000000000000000000000000000000000000000000000000badc0de', + // payload: 'All your base are belong to us.' + x"010000000001003aced6a481653aa534b2f679122e0179de056dbef47442b8c3a1a810dbdfa71049f53cab6e82362800c1558d44993fa6e958a75bd6e6a3472dd278e900041e29010000000000000000000200000000000000000000000000000000000000000000000000000000deadbeef00000000000000010f030000000000000000000000000000000000000000000000000000000000000bb80bc9c77af025eb7f73940ad00c9d6f06d45253339a110b0f9ff03b822e5877d30015381dd9078c322a4663c392761a0211b527c127b29583851217f948d62131f4090015000000000000000000000000000000000000000000000000000000000badc0de416c6c20796f75722062617365206172652062656c6f6e6720746f2075732e" + } + + public fun encoded_transfer_vaa_wrapped_12_with_fee(): vector { + // emitterChain: 2, + // emitterAddress: '0x00000000000000000000000000000000000000000000000000000000deadbeef', + // amount: 3000n, + // tokenAddress: '0x00000000000000000000000000000000000000000000000000000000beefface', + // tokenChain: 2, + // toAddress: '0x000000000000000000000000000000000000000000000000000000000000b0b1', + // chain: 21, + // fee: 1000n + x"010000000001005537ca9a981a62823f57a706f3ceab648391fd99a11631296f798aa394ba6aff73540afefad8634ed573c73c5aa9a16e68906321fa6a4c8a488611b933b1f5b1000000000000000000000200000000000000000000000000000000000000000000000000000000deadbeef00000000000000010f010000000000000000000000000000000000000000000000000000000000000bb800000000000000000000000000000000000000000000000000000000beefface0002000000000000000000000000000000000000000000000000000000000000b0b1001500000000000000000000000000000000000000000000000000000000000003e8" + } + + public fun encoded_transfer_vaa_wrapped_12_without_fee(): vector { + // emitterChain: 2, + // emitterAddress: '0x00000000000000000000000000000000000000000000000000000000deadbeef', + // amount: 3000n, + // tokenAddress: '0x00000000000000000000000000000000000000000000000000000000beefface', + // tokenChain: 2, + // toAddress: '0x000000000000000000000000000000000000000000000000000000000000b0b1', + // chain: 21, + // fee: 0n + x"01000000000100e5558a2955f94fdb174d7868c9f643700174949ac72b90f803bdbea00453ed4c426c055b956060c905189cb710b97916af6a77cd3168f83eca9c66b6366c85c4000000000000000000000200000000000000000000000000000000000000000000000000000000deadbeef00000000000000010f010000000000000000000000000000000000000000000000000000000000000bb800000000000000000000000000000000000000000000000000000000beefface0002000000000000000000000000000000000000000000000000000000000000b0b100150000000000000000000000000000000000000000000000000000000000000000" + } + + public fun encoded_transfer_with_payload_wrapped_12(): vector { + // emitterChain: 2, + // emitterAddress: '0x00000000000000000000000000000000000000000000000000000000deadbeef', + // amount: 3000n, + // tokenAddress: '0x00000000000000000000000000000000000000000000000000000000beefface', + // tokenChain: 2, + // toAddress: '0x381dd9078c322a4663c392761a0211b527c127b29583851217f948d62131f409', + // chain: 21, + // fromAddress: '0x000000000000000000000000000000000000000000000000000000000badc0de', + // payload: 'All your base are belong to us.' + x"0100000000010054968c9be4059d7dc373fff8e80dfc9083c485663517534807d61d11abec64896c4185a2bdd71e3caa713d082c78f5d8b1586c56bd5042dfaba1de0ca0d978a0010000000000000000000200000000000000000000000000000000000000000000000000000000deadbeef00000000000000010f030000000000000000000000000000000000000000000000000000000000000bb800000000000000000000000000000000000000000000000000000000beefface0002381dd9078c322a4663c392761a0211b527c127b29583851217f948d62131f4090015000000000000000000000000000000000000000000000000000000000badc0de416c6c20796f75722062617365206172652062656c6f6e6720746f2075732e" + } + + public fun encoded_transfer_vaa_wrapped_7_with_fee(): vector { + // emitterChain: 2, + // emitterAddress: '0x00000000000000000000000000000000000000000000000000000000deadbeef', + // amount: 3000n, + // tokenAddress: '0x00000000000000000000000000000000000000000000000000000000deafface', + // tokenChain: 2, + // toAddress: '0x000000000000000000000000000000000000000000000000000000000000b0b1', + // chain: 21, + // fee: 1000n + x"01000000000100b9dc34e110e4268ac1e0ef729513083d45b59e0c2cbee8f9fd7d7d2ed900c8ad2a5ca55310fb3741bf3ff8c611e37a2fee2852e09feb491261edf53fcc956edf010000000000000000000200000000000000000000000000000000000000000000000000000000deadbeef00000000000000010f010000000000000000000000000000000000000000000000000000000000000bb800000000000000000000000000000000000000000000000000000000deafface0002000000000000000000000000000000000000000000000000000000000000b0b1001500000000000000000000000000000000000000000000000000000000000003e8" + } + + public fun encoded_transfer_vaa_wrapped_7_without_fee(): vector { + // emitterChain: 2, + // emitterAddress: '0x00000000000000000000000000000000000000000000000000000000deadbeef', + // amount: 3000n, + // tokenAddress: '0x00000000000000000000000000000000000000000000000000000000deafface', + // tokenChain: 2, + // toAddress: '0x000000000000000000000000000000000000000000000000000000000000b0b1', + // chain: 21, + // fee: 0n + x"01000000000100389f0544dc2d3f7095d4e9543ae9f6cb5c9dd6a561e95ed896c870907fe85a94373a455acac8d2ad66154df1cb19ba4ae6c583a1c2839971e6760ecaa1d9fca7000000000000000000000200000000000000000000000000000000000000000000000000000000deadbeef00000000000000010f010000000000000000000000000000000000000000000000000000000000000bb800000000000000000000000000000000000000000000000000000000deafface0002000000000000000000000000000000000000000000000000000000000000b0b100150000000000000000000000000000000000000000000000000000000000000000" + } + + public fun encoded_transfer_vaa_wrapped_12_invalid_target_chain(): vector { + // emitterChain: 2, + // emitterAddress: '0x00000000000000000000000000000000000000000000000000000000deadbeef', + // amount: 3000n, + // tokenAddress: '0x00000000000000000000000000000000000000000000000000000000beefface', + // tokenChain: 2, + // toAddress: '0x000000000000000000000000000000000000000000000000000000000000b0b1', + // chain: 69, + // fee: 0n + x"010000000001009c0b89b21622bde003f8e775daffe343e65d6a537719bc977c85b0b18c26751c7bff61077e74711dfe865d935fa840a7352d7a1ccbcec4be77bfc591cd265a48000000000000000000000200000000000000000000000000000000000000000000000000000000deadbeef00000000000000010f010000000000000000000000000000000000000000000000000000000000000bb800000000000000000000000000000000000000000000000000000000beefface0002000000000000000000000000000000000000000000000000000000000000b0b1004500000000000000000000000000000000000000000000000000000000000003e8" + } + + public fun encoded_transfer_with_payload_wrapped_12_invalid_target_chain(): vector { + // emitterChain: 2, + // emitterAddress: '0x00000000000000000000000000000000000000000000000000000000deadbeef', + // amount: 3000n, + // tokenAddress: '0x00000000000000000000000000000000000000000000000000000000beefface', + // tokenChain: 2, + // toAddress: '0x381dd9078c322a4663c392761a0211b527c127b29583851217f948d62131f409', + // chain: 21, + // fromAddress: '0x000000000000000000000000000000000000000000000000000000000badc0de', + // payload: 'All your base are belong to us.' + x"01000000000100b139a7dbb747b04509ae4f511080a9cb080e423d8db086d5c7553baed2d6151e3fbdd00e691d82662b8d1ed49ec374dba5f82e82df20921151da4b948ddce41e000000000000000000000200000000000000000000000000000000000000000000000000000000deadbeef00000000000000010f030000000000000000000000000000000000000000000000000000000000000bb800000000000000000000000000000000000000000000000000000000beefface0002381dd9078c322a4663c392761a0211b527c127b29583851217f948d62131f4090045000000000000000000000000000000000000000000000000000000000badc0de416c6c20796f75722062617365206172652062656c6f6e6720746f2075732e" + } + + public fun encoded_register_chain_2(): vector { + x"0100000000010015d405c74be6d93c3c33ed6b48d8db70dfb31e0981f8098b2a6c7583083e0c3343d4a1abeb3fc1559674fa067b0c0e2e9de2fafeaecdfeae132de2c33c9d27cc0100000001000000010001000000000000000000000000000000000000000000000000000000000000000400000000016911ae00000000000000000000000000000000000000000000546f6b656e427269646765010000000200000000000000000000000000000000000000000000000000000000deadbeef" + } + + public fun encoded_asset_meta_vaa_foreign_12(): vector { + x"0100000000010080366065746148420220f25a6275097370e8db40984529a6676b7a5fc9feb11755ec49ca626b858ddfde88d15601f85ab7683c5f161413b0412143241c700aff010000000100000001000200000000000000000000000000000000000000000000000000000000deadbeef000000000150eb23000200000000000000000000000000000000000000000000000000000000beefface00020c424545460000000000000000000000000000000000000000000000000000000042656566206661636520546f6b656e0000000000000000000000000000000000" + } +} diff --git a/sui/token_bridge/sources/test/token_bridge_scenario.move b/sui/token_bridge/sources/test/token_bridge_scenario.move new file mode 100644 index 000000000..d7921ee80 --- /dev/null +++ b/sui/token_bridge/sources/test/token_bridge_scenario.move @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: Apache 2 + +#[test_only] +module token_bridge::token_bridge_scenario { + use std::vector::{Self}; + use sui::balance::{Self}; + use sui::package::{UpgradeCap}; + use sui::test_scenario::{Self, Scenario}; + use wormhole::external_address::{Self}; + use wormhole::wormhole_scenario::{ + deployer, + return_state as return_wormhole_state, + set_up_wormhole, + take_state as take_wormhole_state + }; + + use token_bridge::native_asset::{Self}; + use token_bridge::setup::{Self, DeployerCap}; + use token_bridge::state::{Self, State}; + use token_bridge::token_registry::{Self}; + + public fun set_up_wormhole_and_token_bridge( + scenario: &mut Scenario, + wormhole_fee: u64 + ) { + // init and share wormhole core bridge + set_up_wormhole(scenario, wormhole_fee); + + // Ignore effects. + test_scenario::next_tx(scenario, deployer()); + + // Publish Token Bridge. + setup::init_test_only(test_scenario::ctx(scenario)); + + // Ignore effects. + test_scenario::next_tx(scenario, deployer()); + + let wormhole_state = take_wormhole_state(scenario); + + let upgrade_cap = + test_scenario::take_from_sender(scenario); + let emitter_cap = + wormhole::emitter::new( + &wormhole_state, + test_scenario::ctx(scenario) + ); + let governance_chain = 1; + let governance_contract = + x"0000000000000000000000000000000000000000000000000000000000000004"; + + // Finally share `State`. + setup::complete( + test_scenario::take_from_sender(scenario), + upgrade_cap, + emitter_cap, + governance_chain, + governance_contract, + test_scenario::ctx(scenario) + ); + + // Clean up. + return_wormhole_state(wormhole_state); + } + + /// Perform an upgrade (which just upticks the current version of what the + /// `State` believes is true). + public fun upgrade_token_bridge(scenario: &mut Scenario) { + // Clean up from activity prior. + test_scenario::next_tx(scenario, person()); + + let token_bridge_state = take_state(scenario); + state::test_upgrade(&mut token_bridge_state); + + // Clean up. + return_state(token_bridge_state); + } + + /// Register arbitrary chain ID with the same emitter address (0xdeadbeef). + public fun register_dummy_emitter(scenario: &mut Scenario, chain: u16) { + // Ignore effects. + test_scenario::next_tx(scenario, person()); + + let token_bridge_state = take_state(scenario); + token_bridge::register_chain::register_new_emitter_test_only( + &mut token_bridge_state, + chain, + external_address::from_address(@0xdeadbeef) + ); + + // Clean up. + return_state(token_bridge_state); + } + + /// Register 0xdeadbeef for multiple chains. + public fun register_dummy_emitters( + scenario: &mut Scenario, + chains: vector + ) { + while (!vector::is_empty(&chains)) { + register_dummy_emitter(scenario, vector::pop_back(&mut chains)); + }; + vector::destroy_empty(chains); + } + + public fun deposit_native( + token_bridge_state: &mut State, + deposit_amount: u64 + ) { + native_asset::deposit_test_only( + token_registry::borrow_mut_native_test_only( + state::borrow_mut_token_registry_test_only(token_bridge_state) + ), + balance::create_for_testing(deposit_amount) + ) + } + + public fun person(): address { + wormhole::wormhole_scenario::person() + } + + public fun two_people(): (address, address) { + wormhole::wormhole_scenario::two_people() + } + + public fun three_people(): (address, address, address) { + wormhole::wormhole_scenario::three_people() + } + + public fun take_state(scenario: &Scenario): State { + test_scenario::take_shared(scenario) + } + + public fun return_state(token_bridge_state: State) { + test_scenario::return_shared(token_bridge_state); + } +} diff --git a/sui/token_bridge/sources/testing/native_coin.move b/sui/token_bridge/sources/testing/native_coin.move deleted file mode 100644 index b8f4a48ba..000000000 --- a/sui/token_bridge/sources/testing/native_coin.move +++ /dev/null @@ -1,32 +0,0 @@ -#[test_only] -module token_bridge::native_coin_witness { - use std::option::{Self}; - use sui::tx_context::{TxContext}; - use sui::coin::{Self}; - use sui::transfer::{Self}; - - struct NATIVE_COIN_WITNESS has drop {} - - // This module creates a Sui-native token for testing purposes, - // for example in complete_transfer, where we create a native coin, - // mint some and deposit in the token bridge, then complete transfer - // and ultimately transfer a portion of those native coins to a recipient. - fun init(coin_witness: NATIVE_COIN_WITNESS, ctx: &mut TxContext) { - let (treasury_cap, coin_metadata) = coin::create_currency( - coin_witness, - 10, - x"00", - x"11", - x"22", - option::none(), - ctx - ); - transfer::share_object(coin_metadata); - transfer::share_object(treasury_cap); - } - - #[test_only] - public fun test_init(ctx: &mut TxContext) { - init(NATIVE_COIN_WITNESS {}, ctx) - } -} diff --git a/sui/token_bridge/sources/testing/native_coin_v2.move b/sui/token_bridge/sources/testing/native_coin_v2.move deleted file mode 100644 index 8cb6ae217..000000000 --- a/sui/token_bridge/sources/testing/native_coin_v2.move +++ /dev/null @@ -1,32 +0,0 @@ -#[test_only] -module token_bridge::native_coin_witness_v2 { - use std::option::{Self}; - use sui::tx_context::{TxContext}; - use sui::coin::{Self}; - use sui::transfer::{Self}; - - struct NATIVE_COIN_WITNESS_V2 has drop {} - - // This module creates a Sui-native token for testing purposes, - // for example in complete_transfer, where we create a native coin, - // mint some and deposit in the token bridge, then complete transfer - // and ultimately transfer a portion of those native coins to a recipient. - fun init(coin_witness: NATIVE_COIN_WITNESS_V2, ctx: &mut TxContext) { - let (treasury_cap, coin_metadata) = coin::create_currency( - coin_witness, - 4, - x"33", - x"44", - x"55", - option::none(), - ctx - ); - transfer::share_object(coin_metadata); - transfer::share_object(treasury_cap); - } - - #[test_only] - public fun test_init(ctx: &mut TxContext) { - init(NATIVE_COIN_WITNESS_V2 {}, ctx) - } -} diff --git a/sui/token_bridge/sources/testing/wrapped_coin.move b/sui/token_bridge/sources/testing/wrapped_coin.move deleted file mode 100644 index 5c74c02ea..000000000 --- a/sui/token_bridge/sources/testing/wrapped_coin.move +++ /dev/null @@ -1,112 +0,0 @@ -#[test_only] -module token_bridge::coin_witness { - use sui::transfer; - use sui::tx_context::{Self, TxContext}; - - use token_bridge::wrapped; - - struct COIN_WITNESS has drop {} - - fun init(coin_witness: COIN_WITNESS, ctx: &mut TxContext) { - // Step 1. Paste token attestation VAA below. This example is ethereum beefface token. - let vaa_bytes = x"0100000000010080366065746148420220f25a6275097370e8db40984529a6676b7a5fc9feb11755ec49ca626b858ddfde88d15601f85ab7683c5f161413b0412143241c700aff010000000100000001000200000000000000000000000000000000000000000000000000000000deadbeef000000000150eb23000200000000000000000000000000000000000000000000000000000000beefface00020c424545460000000000000000000000000000000000000000000000000000000042656566206661636520546f6b656e0000000000000000000000000000000000"; - - let new_wrapped_coin = wrapped::create_wrapped_coin(vaa_bytes, coin_witness, ctx); - transfer::transfer( - new_wrapped_coin, - tx_context::sender(ctx) - ); - } - - #[test_only] - public fun test_init(ctx: &mut TxContext) { - init(COIN_WITNESS {}, ctx) - } -} - -#[test_only] -module token_bridge::coin_witness_test { - use sui::test_scenario::{Self, Scenario, ctx, next_tx, take_from_address, return_shared, take_shared}; - - use wormhole::state::{State}; - use wormhole::myu16::{Self as u16}; - use wormhole::external_address::{Self}; - - use token_bridge::bridge_state::{BridgeState, is_wrapped_asset, is_registered_native_asset, origin_info, get_token_chain_from_origin_info, get_token_address_from_origin_info}; - use token_bridge::bridge_state_test::{set_up_wormhole_core_and_token_bridges}; - use token_bridge::wrapped::{NewWrappedCoin, register_wrapped_coin}; - use token_bridge::register_chain::{submit_vaa}; - - use token_bridge::coin_witness::{test_init, COIN_WITNESS}; - - fun scenario(): Scenario { test_scenario::begin(@0x123233) } - fun people(): (address, address, address) { (@0x124323, @0xE05, @0xFACE) } - - /// Registration VAA for the etheruem token bridge 0xdeadbeef - const ETHEREUM_TOKEN_REG: vector = x"0100000000010015d405c74be6d93c3c33ed6b48d8db70dfb31e0981f8098b2a6c7583083e0c3343d4a1abeb3fc1559674fa067b0c0e2e9de2fafeaecdfeae132de2c33c9d27cc0100000001000000010001000000000000000000000000000000000000000000000000000000000000000400000000016911ae00000000000000000000000000000000000000000000546f6b656e427269646765010000000200000000000000000000000000000000000000000000000000000000deadbeef"; - - // call coin init to create wrapped coin and traasfer to sender - #[test] - fun test_create_wrapped() { - let test = scenario(); - let (admin, _, _) = people(); - next_tx(&mut test, admin); { - test_init(ctx(&mut test)) - }; - test_scenario::end(test); - } - - // call token bridge register wrapped coin - #[test] - fun test_register_wrapped() { - let (admin, _, _) = people(); - let scenario = scenario(); - let test = test_register_wrapped_(admin, scenario); - test_scenario::end(test); - } - - public fun test_register_wrapped_(admin: address, test: Scenario): Scenario { - test = set_up_wormhole_core_and_token_bridges(admin, test); - // create and transfer new wrapped coin to sender - next_tx(&mut test, admin); { - test_init(ctx(&mut test)) - }; - // register chain - next_tx(&mut test, admin); { - let wormhole_state = take_shared(&test); - let bridge_state = take_shared(&test); - submit_vaa(&mut wormhole_state, &mut bridge_state, ETHEREUM_TOKEN_REG, ctx(&mut test)); - return_shared(wormhole_state); - return_shared(bridge_state); - }; - // register wrapped coin with token bridge, handing it the treasury cap and storing metadata - next_tx(&mut test, admin);{ - let bridge_state = take_shared(&test); - let worm_state = take_shared(&test); - let wrapped_coin = take_from_address>(&test, admin); - register_wrapped_coin( - &mut worm_state, - &mut bridge_state, - wrapped_coin, - ctx(&mut test) - ); - // assert that wrapped asset is indeed recognized by token bridge - let is_wrapped = is_wrapped_asset(&bridge_state); - assert!(is_wrapped, 0); - - // assert that wrapped asset is not recognized as a native asset by token bridge - let is_native = is_registered_native_asset(&bridge_state); - assert!(!is_native, 0); - - // assert origin info is correct - let origin_info = origin_info(&bridge_state); - let chain = get_token_chain_from_origin_info(&origin_info); - let address = get_token_address_from_origin_info(&origin_info); - assert!(chain == u16::from_u64(2), 0); - assert!(address == external_address::from_bytes(x"beefface"), 0); - return_shared(bridge_state); - return_shared(worm_state); - }; - return test - } -} diff --git a/sui/token_bridge/sources/transfer_tokens.move b/sui/token_bridge/sources/transfer_tokens.move index ab7f255f0..1071344ae 100644 --- a/sui/token_bridge/sources/transfer_tokens.move +++ b/sui/token_bridge/sources/transfer_tokens.move @@ -1,129 +1,1053 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements three methods: `prepare_transfer` and +/// `transfer_tokens`, which are meant to work together. +/// +/// `prepare_transfer` allows a contract to pack token transfer parameters in +/// preparation to bridge these assets to another network. Anyone can call this +/// method to create `TransferTicket`. +/// +/// `transfer_tokens` unpacks the `TransferTicket` and constructs a +/// `MessageTicket`, which will be used by Wormhole's `publish_message` +/// module. +/// +/// The purpose of splitting this token transferring into two steps is in case +/// Token Bridge needs to be upgraded and there is a breaking change for this +/// module, an integrator would not be left broken. It is discouraged to put +/// `transfer_tokens` in an integrator's package logic. Otherwise, this +/// integrator needs to be prepared to upgrade his contract to handle the latest +/// version of `transfer_tokens`. +/// +/// Instead, an integrator is encouraged to execute a transaction block, which +/// executes `transfer_tokens` using the latest Token Bridge package ID and to +/// implement `prepare_transfer` in his contract to produce `PrepareTransfer`. +/// +/// NOTE: Only assets that exist in the `TokenRegistry` can be bridged out, +/// which are native Sui assets that have been attested for via `attest_token` +/// and wrapped foreign assets that have been created using foreign asset +/// metadata via the `create_wrapped` module. +/// +/// See `transfer` module for serialization and deserialization of Wormhole +/// message payload. module token_bridge::transfer_tokens { - use sui::sui::SUI; - use sui::coin::{Self, Coin, CoinMetadata}; - - use wormhole::state::{State as WormholeState}; + use sui::balance::{Self, Balance}; + use sui::coin::{Self, Coin}; + use wormhole::bytes32::{Self}; use wormhole::external_address::{Self, ExternalAddress}; - use wormhole::myu16::{Self as u16, U16}; - use wormhole::emitter::{Self, EmitterCapability}; + use wormhole::publish_message::{MessageTicket}; - use token_bridge::bridge_state::{Self, BridgeState}; - use token_bridge::transfer_result::{Self, TransferResult}; + use token_bridge::native_asset::{Self}; + use token_bridge::normalized_amount::{Self, NormalizedAmount}; + use token_bridge::state::{Self, State, LatestOnly}; + use token_bridge::token_registry::{Self, VerifiedAsset}; use token_bridge::transfer::{Self}; - use token_bridge::normalized_amount::{Self}; - use token_bridge::transfer_with_payload::{Self}; + use token_bridge::wrapped_asset::{Self}; - const E_TOO_MUCH_RELAYER_FEE: u64 = 0; + friend token_bridge::transfer_tokens_with_payload; - public entry fun transfer_tokens( - wormhole_state: &mut WormholeState, - bridge_state: &mut BridgeState, - coins: Coin, - coin_metadata: &CoinMetadata, - wormhole_fee_coins: Coin, - recipient_chain: u64, + /// Relayer fee exceeds `Coin` object's value. + const E_RELAYER_FEE_EXCEEDS_AMOUNT: u64 = 0; + + /// This type represents transfer data for a recipient on a foreign chain. + /// The only way to destroy this type is calling `transfer_tokens`. + /// + /// NOTE: An integrator that expects to bridge assets between his contracts + /// should probably use the `transfer_tokens_with_payload` module, which + /// expects a specific redeemer to complete the transfer (transfers sent + /// using `transfer_tokens` can be redeemed by anyone on behalf of the + /// encoded recipient). + struct TransferTicket { + asset_info: VerifiedAsset, + bridged_in: Balance, + norm_amount: NormalizedAmount, + recipient_chain: u16, recipient: vector, relayer_fee: u64, - nonce: u64, - ) { - let result = transfer_tokens_internal( - bridge_state, - coins, - coin_metadata, - relayer_fee, - ); - let (token_chain, token_address, normalized_amount, normalized_relayer_fee) - = transfer_result::destroy(result); - let transfer = transfer::create( - normalized_amount, - token_address, - token_chain, - external_address::from_bytes(recipient), - u16::from_u64(recipient_chain), - normalized_relayer_fee, - ); - bridge_state::publish_message( - wormhole_state, - bridge_state, - nonce, - transfer::encode(transfer), - wormhole_fee_coins, - ); + nonce: u32 } - public fun transfer_tokens_with_payload( - emitter_cap: &EmitterCapability, - wormhole_state: &mut WormholeState, - bridge_state: &mut BridgeState, - coins: Coin, - coin_metadata: &CoinMetadata, - wormhole_fee_coins: Coin, - recipient_chain: U16, - recipient: ExternalAddress, + /// `prepare_transfer` constructs token transfer parameters. Any remaining + /// amount (A.K.A. dust) from the funds provided will be returned along with + /// the `TransferTicket` type. The returned coin object is the same object + /// moved into this method. + /// + /// NOTE: Integrators of Token Bridge should be calling only this method + /// from their contracts. This method is not guarded by version control + /// (thus not requiring a reference to the Token Bridge `State` object), so + /// it is intended to work for any package version. + public fun prepare_transfer( + asset_info: VerifiedAsset, + funded: Coin, + recipient_chain: u16, + recipient: vector, relayer_fee: u64, - nonce: u64, - payload: vector, - ): u64 { - let result = transfer_tokens_internal( - bridge_state, - coins, - coin_metadata, - relayer_fee, - ); - let (token_chain, token_address, normalized_amount, _) - = transfer_result::destroy(result); + nonce: u32 + ): ( + TransferTicket, + Coin + ) { + let ( + bridged_in, + norm_amount + ) = take_truncated_amount(&asset_info, &mut funded); - let transfer = transfer_with_payload::create( - normalized_amount, - token_address, - token_chain, - recipient, - recipient_chain, - emitter::get_external_address(emitter_cap), - payload - ); - let payload = transfer_with_payload::encode(transfer); - bridge_state::publish_message( - wormhole_state, - bridge_state, + let ticket = + TransferTicket { + asset_info, + bridged_in, + norm_amount, + relayer_fee, + recipient_chain, + recipient, + nonce + }; + + // The remaining amount of funded may have dust depending on the + // decimals of this asset. + (ticket, funded) + } + + /// `transfer_tokens` is the only method that can unpack the members of + /// `TransferTicket`. This method takes the balance from this type and + /// bridges this asset out of Sui by either joining its balance in the Token + /// Bridge's custody for native assets or burning its balance for wrapped + /// assets. + /// + /// A `relayer_fee` of some value less than or equal to the bridged balance + /// can be specified to incentivize someone to redeem this transfer on + /// behalf of the `recipient`. + /// + /// This method returns the prepared Wormhole message (which should be + /// consumed by calling `publish_message` in a transaction block). + /// + /// NOTE: This method is guarded by a minimum build version check. This + /// method could break backward compatibility on an upgrade. + /// + /// It is important for integrators to refrain from calling this method + /// within their contracts. This method is meant to be called in a + /// tranasction block after receiving a `TransferTicket` from calling + /// `prepare_transfer` within a contract. If in a circumstance where this + /// module has a breaking change in an upgrade, `prepare_transfer` will not + /// be affected by this change. + public fun transfer_tokens( + token_bridge_state: &mut State, + ticket: TransferTicket + ): MessageTicket { + // This capability ensures that the current build version is used. + let latest_only = state::assert_latest_only(token_bridge_state); + + let ( nonce, - payload, - wormhole_fee_coins + encoded_transfer + ) = + bridge_in_and_serialize_transfer( + &latest_only, + token_bridge_state, + ticket + ); + + // Prepare Wormhole message with encoded `Transfer`. + state::prepare_wormhole_message( + &latest_only, + token_bridge_state, + nonce, + encoded_transfer ) } - fun transfer_tokens_internal( - bridge_state: &mut BridgeState, - coins: Coin, - coin_metadata: &CoinMetadata, - relayer_fee: u64, - ): TransferResult { - let amount = coin::value(&coins); - assert!(relayer_fee <= amount, E_TOO_MUCH_RELAYER_FEE); + /// Modify coin based on the decimals of a given coin type, which may + /// leave some amount if the decimals lead to truncating the coin's balance. + /// This method returns the extracted balance (which will be bridged out of + /// Sui) and the normalized amount, which will be encoded in the token + /// transfer payload. + /// + /// NOTE: This is a privileged method, which only this and the + /// `transfer_tokens_with_payload` modules can use. + public(friend) fun take_truncated_amount( + asset_info: &VerifiedAsset, + funded: &mut Coin + ): ( + Balance, + NormalizedAmount + ) { + // Calculate dust. If there is any, `bridged_in` will have remaining + // value after split. `norm_amount` is copied since it is denormalized + // at this step. + let decimals = token_registry::coin_decimals(asset_info); + let norm_amount = + normalized_amount::from_raw(coin::value(funded), decimals); - if (bridge_state::is_wrapped_asset(bridge_state)) { - // now we burn the wrapped coins to remove them from circulation - bridge_state::burn(bridge_state, coins); + // Split the `bridged_in` coin object to return any dust remaining on + // that object. Only bridge in the adjusted amount after de-normalizing + // the normalized amount. + let truncated = + balance::split( + coin::balance_mut(funded), + normalized_amount::to_raw(norm_amount, decimals) + ); + + (truncated, norm_amount) + } + + /// For a given coin type, either burn Token Bridge wrapped assets or + /// deposit coin into Token Bridge's custody. This method returns the + /// canonical token info (chain ID and address), which will be encoded in + /// the token transfer. + /// + /// NOTE: This is a privileged method, which only this and the + /// `transfer_tokens_with_payload` modules can use. + public(friend) fun burn_or_deposit_funds( + latest_only: &LatestOnly, + token_bridge_state: &mut State, + asset_info: &VerifiedAsset, + bridged_in: Balance + ): ( + u16, + ExternalAddress + ) { + // Either burn or deposit depending on `CoinType`. + let registry = + state::borrow_mut_token_registry(latest_only, token_bridge_state); + if (token_registry::is_wrapped(asset_info)) { + wrapped_asset::burn( + token_registry::borrow_mut_wrapped(registry), + bridged_in + ); } else { - // deposit native assets. this call to deposit requires the native - // asset to have been attested - bridge_state::deposit(bridge_state, coins); + native_asset::deposit( + token_registry::borrow_mut_native(registry), + bridged_in + ); }; - let origin_info = bridge_state::origin_info(bridge_state); - let token_chain = bridge_state::get_token_chain_from_origin_info(&origin_info); - let token_address = bridge_state::get_token_address_from_origin_info(&origin_info); + // Return canonical token info. + ( + token_registry::token_chain(asset_info), + token_registry::token_address(asset_info) + ) + } - let decimals = coin::get_decimals(coin_metadata); - let normalized_amount = normalized_amount::normalize(amount, decimals); - let normalized_relayer_fee = normalized_amount::normalize(relayer_fee, decimals); + fun bridge_in_and_serialize_transfer( + latest_only: &LatestOnly, + token_bridge_state: &mut State, + ticket: TransferTicket + ): ( + u32, + vector + ) { + let TransferTicket { + asset_info, + bridged_in, + norm_amount, + recipient_chain, + recipient, + relayer_fee, + nonce + } = ticket; - let transfer_result: TransferResult = transfer_result::create( + // Disallow `relayer_fee` to be greater than the `Coin` object's value. + // Keep in mind that the relayer fee is evaluated against the truncated + // amount. + let amount = sui::balance::value(&bridged_in); + assert!(relayer_fee <= amount, E_RELAYER_FEE_EXCEEDS_AMOUNT); + + // Handle funds and get canonical token info for encoded transfer. + let ( token_chain, - token_address, - normalized_amount, - normalized_relayer_fee, + token_address + ) = burn_or_deposit_funds( + latest_only, + token_bridge_state, + &asset_info, bridged_in ); - transfer_result + + // Ensure that the recipient is a 32-byte address. + let recipient = external_address::new(bytes32::from_bytes(recipient)); + + // Finally encode `Transfer`. + let encoded = + transfer::serialize( + transfer::new( + norm_amount, + token_address, + token_chain, + recipient, + recipient_chain, + normalized_amount::from_raw( + relayer_fee, + token_registry::coin_decimals(&asset_info) + ) + ) + ); + + (nonce, encoded) + } + + #[test_only] + public fun bridge_in_and_serialize_transfer_test_only( + token_bridge_state: &mut State, + ticket: TransferTicket + ): ( + u32, + vector + ) { + // This capability ensures that the current build version is used. + let latest_only = state::assert_latest_only(token_bridge_state); + + bridge_in_and_serialize_transfer( + &latest_only, + token_bridge_state, + ticket + ) + } +} + +#[test_only] +module token_bridge::transfer_token_tests { + use sui::coin::{Self}; + use sui::test_scenario::{Self}; + use wormhole::bytes32::{Self}; + use wormhole::external_address::{Self}; + use wormhole::publish_message::{Self}; + use wormhole::state::{chain_id}; + + use token_bridge::coin_native_10::{Self, COIN_NATIVE_10}; + use token_bridge::coin_wrapped_7::{Self, COIN_WRAPPED_7}; + use token_bridge::native_asset::{Self}; + use token_bridge::normalized_amount::{Self}; + use token_bridge::state::{Self}; + use token_bridge::token_bridge_scenario::{ + set_up_wormhole_and_token_bridge, + register_dummy_emitter, + return_state, + take_state, + person + }; + use token_bridge::token_registry::{Self}; + use token_bridge::transfer::{Self}; + use token_bridge::transfer_tokens::{Self}; + use token_bridge::wrapped_asset::{Self}; + + /// Test consts. + const TEST_TARGET_RECIPIENT: vector = x"beef4269"; + const TEST_TARGET_CHAIN: u16 = 2; + const TEST_NONCE: u32 = 0; + const TEST_COIN_NATIVE_10_DECIMALS: u8 = 10; + const TEST_COIN_WRAPPED_7_DECIMALS: u8 = 7; + + #[test] + fun test_transfer_tokens_native_10() { + use token_bridge::transfer_tokens::{prepare_transfer, transfer_tokens}; + + let sender = person(); + let my_scenario = test_scenario::begin(sender); + let scenario = &mut my_scenario; + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register foreign emitter on chain ID == 2. + register_dummy_emitter(scenario, TEST_TARGET_CHAIN); + + // Register and mint coins. + let transfer_amount = 6942000; + let coin_10_balance = + coin_native_10::init_register_and_mint( + scenario, + sender, + transfer_amount + ); + + // Ignore effects. + test_scenario::next_tx(scenario, sender); + + // Fetch objects necessary for sending the transfer. + let token_bridge_state = take_state(scenario); + + // Define the relayer fee. + let relayer_fee = 100000; + + // Balance check the Token Bridge before executing the transfer. The + // initial balance should be zero for COIN_NATIVE_10. + { + let registry = state::borrow_token_registry(&token_bridge_state); + let asset = token_registry::borrow_native(registry); + assert!(native_asset::custody(asset) == 0, 0); + }; + + let asset_info = state::verified_asset(&token_bridge_state); + let ( + ticket, + dust + ) = + prepare_transfer( + asset_info, + coin::from_balance( + coin_10_balance, + test_scenario::ctx(scenario) + ), + TEST_TARGET_CHAIN, + TEST_TARGET_RECIPIENT, + relayer_fee, + TEST_NONCE, + ); + coin::destroy_zero(dust); + + // Call `transfer_tokens`. + let prepared_msg = + transfer_tokens(&mut token_bridge_state, ticket); + + // Balance check the Token Bridge after executing the transfer. The + // balance should now reflect the `transfer_amount` defined in this + // test. + { + let registry = state::borrow_token_registry(&token_bridge_state); + let asset = token_registry::borrow_native(registry); + assert!(native_asset::custody(asset) == transfer_amount, 0); + }; + + // Clean up. + publish_message::destroy(prepared_msg); + return_state(token_bridge_state); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + fun test_transfer_tokens_native_10_with_dust_refund() { + use token_bridge::transfer_tokens::{prepare_transfer, transfer_tokens}; + + let sender = person(); + let my_scenario = test_scenario::begin(sender); + let scenario = &mut my_scenario; + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register foreign emitter on chain ID == 2. + register_dummy_emitter(scenario, TEST_TARGET_CHAIN); + + // Register and mint coins. + let transfer_amount = 1000069; + let coin_10_balance = + coin_native_10::init_register_and_mint( + scenario, + sender, + transfer_amount + ); + + // This value will be used later. The contract should return dust + // to the caller since COIN_NATIVE_10 has 10 decimals. + let expected_dust = 69; + + // Ignore effects. + test_scenario::next_tx(scenario, sender); + + // Fetch objects necessary for sending the transfer. + let token_bridge_state = take_state(scenario); + + // Define the relayer fee. + let relayer_fee = 100000; + + // Balance check the Token Bridge before executing the transfer. The + // initial balance should be zero for COIN_NATIVE_10. + { + let registry = state::borrow_token_registry(&token_bridge_state); + let asset = token_registry::borrow_native(registry); + assert!(native_asset::custody(asset) == 0, 0); + }; + + let asset_info = state::verified_asset(&token_bridge_state); + let ( + ticket, + dust + ) = + prepare_transfer( + asset_info, + coin::from_balance( + coin_10_balance, + test_scenario::ctx(scenario) + ), + TEST_TARGET_CHAIN, + TEST_TARGET_RECIPIENT, + relayer_fee, + TEST_NONCE, + ); + assert!(coin::value(&dust) == expected_dust, 0); + + // Call `transfer_tokens`. + let prepared_msg = + transfer_tokens(&mut token_bridge_state, ticket); + + // Balance check the Token Bridge after executing the transfer. The + // balance should now reflect the `transfer_amount` less `expected_dust` + // defined in this test. + { + let registry = state::borrow_token_registry(&token_bridge_state); + let asset = token_registry::borrow_native(registry); + assert!( + native_asset::custody(asset) == transfer_amount - expected_dust, + 0 + ); + }; + + // Clean up. + publish_message::destroy(prepared_msg); + coin::burn_for_testing(dust); + return_state(token_bridge_state); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + fun test_serialize_transfer_tokens_native_10() { + use token_bridge::transfer_tokens::{ + bridge_in_and_serialize_transfer_test_only, + prepare_transfer + }; + + let sender = person(); + let my_scenario = test_scenario::begin(sender); + let scenario = &mut my_scenario; + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register foreign emitter on chain ID == 2. + register_dummy_emitter(scenario, TEST_TARGET_CHAIN); + + // Register and mint coins. + let transfer_amount = 6942000; + let bridged_coin_10 = + coin::from_balance( + coin_native_10::init_register_and_mint( + scenario, + sender, + transfer_amount + ), + test_scenario::ctx(scenario) + ); + + // Ignore effects. + test_scenario::next_tx(scenario, sender); + + // Fetch objects necessary for sending the transfer. + let token_bridge_state = take_state(scenario); + + // Define the relayer fee. + let relayer_fee = 100000; + + let asset_info = state::verified_asset(&token_bridge_state); + let expected_token_address = token_registry::token_address(&asset_info); + + let ( + ticket, + dust + ) = + prepare_transfer( + asset_info, + bridged_coin_10, + TEST_TARGET_CHAIN, + TEST_TARGET_RECIPIENT, + relayer_fee, + TEST_NONCE, + ); + coin::destroy_zero(dust); + + // Call `transfer_tokens`. + let ( + nonce, + payload + ) = + bridge_in_and_serialize_transfer_test_only( + &mut token_bridge_state, + ticket + ); + assert!(nonce == TEST_NONCE, 0); + + // Construct expected payload from scratch and confirm that the + // `transfer_tokens` call produces the same payload. + let expected_amount = + normalized_amount::from_raw( + transfer_amount, + TEST_COIN_NATIVE_10_DECIMALS + ); + let expected_relayer_fee = + normalized_amount::from_raw( + relayer_fee, + TEST_COIN_NATIVE_10_DECIMALS + ); + + let expected_payload = + transfer::new_test_only( + expected_amount, + expected_token_address, + chain_id(), + external_address::new( + bytes32::from_bytes(TEST_TARGET_RECIPIENT) + ), + TEST_TARGET_CHAIN, + expected_relayer_fee + ); + assert!(transfer::serialize_test_only(expected_payload) == payload, 0); + + // Clean up. + return_state(token_bridge_state); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + fun test_transfer_tokens_wrapped_7() { + use token_bridge::transfer_tokens::{prepare_transfer, transfer_tokens}; + + let sender = person(); + let my_scenario = test_scenario::begin(sender); + let scenario = &mut my_scenario; + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register foreign emitter on chain ID == 2. + register_dummy_emitter(scenario, TEST_TARGET_CHAIN); + + // Register and mint coins. + let transfer_amount = 42069000; + let coin_7_balance = + coin_wrapped_7::init_register_and_mint( + scenario, + sender, + transfer_amount + ); + + // Ignore effects. + test_scenario::next_tx(scenario, sender); + + // Fetch objects necessary for sending the transfer. + let token_bridge_state = take_state(scenario); + + // Define the relayer fee. + let relayer_fee = 100000; + + // Balance check the Token Bridge before executing the transfer. The + // initial balance should be the `transfer_amount` for COIN_WRAPPED_7. + { + let registry = state::borrow_token_registry(&token_bridge_state); + let asset = + token_registry::borrow_wrapped(registry); + assert!(wrapped_asset::total_supply(asset) == transfer_amount, 0); + }; + + let asset_info = state::verified_asset(&token_bridge_state); + let ( + ticket, + dust + ) = + prepare_transfer( + asset_info, + coin::from_balance( + coin_7_balance, + test_scenario::ctx(scenario) + ), + TEST_TARGET_CHAIN, + TEST_TARGET_RECIPIENT, + relayer_fee, + TEST_NONCE, + ); + coin::destroy_zero(dust); + + // Call `transfer_tokens`. + let prepared_msg = + transfer_tokens(&mut token_bridge_state, ticket); + + // Balance check the Token Bridge after executing the transfer. The + // balance should be zero, since tokens are burned when an outbound + // wrapped token transfer occurs. + { + let registry = state::borrow_token_registry(&token_bridge_state); + let asset = token_registry::borrow_wrapped(registry); + assert!(wrapped_asset::total_supply(asset) == 0, 0); + }; + + // Clean up. + publish_message::destroy(prepared_msg); + return_state(token_bridge_state); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + fun test_serialize_transfer_tokens_wrapped_7() { + use token_bridge::transfer_tokens::{ + bridge_in_and_serialize_transfer_test_only, + prepare_transfer + }; + + let sender = person(); + let my_scenario = test_scenario::begin(sender); + let scenario = &mut my_scenario; + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register foreign emitter on chain ID == 2. + register_dummy_emitter(scenario, TEST_TARGET_CHAIN); + + // Register and mint coins. + let transfer_amount = 6942000; + let bridged_coin_7 = + coin::from_balance( + coin_wrapped_7::init_register_and_mint( + scenario, + sender, + transfer_amount + ), + test_scenario::ctx(scenario) + ); + + // Ignore effects. + test_scenario::next_tx(scenario, sender); + + // Fetch objects necessary for sending the transfer. + let token_bridge_state = take_state(scenario); + + // Define the relayer fee. + let relayer_fee = 100000; + + let asset_info = state::verified_asset(&token_bridge_state); + let expected_token_address = token_registry::token_address(&asset_info); + let expected_token_chain = token_registry::token_chain(&asset_info); + + let ( + ticket, + dust + ) = + prepare_transfer( + asset_info, + bridged_coin_7, + TEST_TARGET_CHAIN, + TEST_TARGET_RECIPIENT, + relayer_fee, + TEST_NONCE, + ); + coin::destroy_zero(dust); + + // Call `transfer_tokens`. + let ( + nonce, + payload + ) = + bridge_in_and_serialize_transfer_test_only( + &mut token_bridge_state, + ticket + ); + assert!(nonce == TEST_NONCE, 0); + + // Construct expected payload from scratch and confirm that the + // `transfer_tokens` call produces the same payload. + let expected_amount = + normalized_amount::from_raw( + transfer_amount, + TEST_COIN_WRAPPED_7_DECIMALS + ); + let expected_relayer_fee = + normalized_amount::from_raw( + relayer_fee, + TEST_COIN_WRAPPED_7_DECIMALS + ); + + let expected_payload = + transfer::new_test_only( + expected_amount, + expected_token_address, + expected_token_chain, + external_address::new( + bytes32::from_bytes(TEST_TARGET_RECIPIENT) + ), + TEST_TARGET_CHAIN, + expected_relayer_fee + ); + assert!(transfer::serialize_test_only(expected_payload) == payload, 0); + + // Clean up. + return_state(token_bridge_state); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + #[expected_failure(abort_code = token_registry::E_UNREGISTERED)] + fun test_cannot_transfer_tokens_native_not_registered() { + use token_bridge::transfer_tokens::{prepare_transfer, transfer_tokens}; + + let sender = person(); + let my_scenario = test_scenario::begin(sender); + let scenario = &mut my_scenario; + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register foreign emitter on chain ID == 2. + register_dummy_emitter(scenario, TEST_TARGET_CHAIN); + + // Initialize COIN_NATIVE_10 (but don't register it). + coin_native_10::init_test_only(test_scenario::ctx(scenario)); + + // NOTE: This test purposely doesn't `attest` COIN_NATIVE_10. + let transfer_amount = 6942000; + let test_coins = + coin::mint_for_testing( + transfer_amount, + test_scenario::ctx(scenario) + ); + + // Ignore effects. + test_scenario::next_tx(scenario, sender); + + // Fetch objects necessary for sending the transfer. + let token_bridge_state = take_state(scenario); + + // Define the relayer fee. + let relayer_fee = 100000; + + let asset_info = state::verified_asset(&token_bridge_state); + let ( + ticket, + dust + ) = + prepare_transfer( + asset_info, + test_coins, + TEST_TARGET_CHAIN, + TEST_TARGET_RECIPIENT, + relayer_fee, + TEST_NONCE, + ); + coin::destroy_zero(dust); + + // You shall not pass! + let prepared_msg = + transfer_tokens(&mut token_bridge_state, ticket); + + // Clean up. + publish_message::destroy(prepared_msg); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = token_registry::E_UNREGISTERED)] + fun test_cannot_transfer_tokens_wrapped_not_registered() { + use token_bridge::transfer_tokens::{prepare_transfer, transfer_tokens}; + + let sender = person(); + let my_scenario = test_scenario::begin(sender); + let scenario = &mut my_scenario; + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register foreign emitter on chain ID == 2. + register_dummy_emitter(scenario, TEST_TARGET_CHAIN); + + // Initialize COIN_WRAPPED_7 (but don't register it). + coin_native_10::init_test_only(test_scenario::ctx(scenario)); + + let treasury_cap = + coin_wrapped_7::init_and_take_treasury_cap( + scenario, + sender + ); + sui::test_utils::destroy(treasury_cap); + + // NOTE: This test purposely doesn't `attest` COIN_WRAPPED_7. + let transfer_amount = 42069; + let test_coins = + coin::mint_for_testing( + transfer_amount, + test_scenario::ctx(scenario) + ); + + // Ignore effects. + test_scenario::next_tx(scenario, sender); + + // Fetch objects necessary for sending the transfer. + let token_bridge_state = take_state(scenario); + + // Define the relayer fee. + let relayer_fee = 1000; + + let asset_info = state::verified_asset(&token_bridge_state); + let ( + ticket, + dust + ) = + prepare_transfer( + asset_info, + test_coins, + TEST_TARGET_CHAIN, + TEST_TARGET_RECIPIENT, + relayer_fee, + TEST_NONCE, + ); + coin::destroy_zero(dust); + + // You shall not pass! + let prepared_msg = + transfer_tokens(&mut token_bridge_state, ticket); + + // Clean up. + publish_message::destroy(prepared_msg); + + abort 42 + } + + #[test] + #[expected_failure( + abort_code = transfer_tokens::E_RELAYER_FEE_EXCEEDS_AMOUNT + )] + fun test_cannot_transfer_tokens_fee_exceeds_amount() { + use token_bridge::transfer_tokens::{prepare_transfer, transfer_tokens}; + + let sender = person(); + let my_scenario = test_scenario::begin(sender); + let scenario = &mut my_scenario; + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register foreign emitter on chain ID == 2. + register_dummy_emitter(scenario, TEST_TARGET_CHAIN); + + // NOTE: The `relayer_fee` is intentionally set to a higher number + // than the `transfer_amount`. + let relayer_fee = 100001; + let transfer_amount = 100000; + let coin_10_balance = + coin_native_10::init_register_and_mint( + scenario, + sender, + transfer_amount + ); + + // Ignore effects. + test_scenario::next_tx(scenario, sender); + + // Fetch objects necessary for sending the transfer. + let token_bridge_state = take_state(scenario); + + let asset_info = state::verified_asset(&token_bridge_state); + let ( + ticket, + dust + ) = + prepare_transfer( + asset_info, + coin::from_balance( + coin_10_balance, + test_scenario::ctx(scenario) + ), + TEST_TARGET_CHAIN, + TEST_TARGET_RECIPIENT, + relayer_fee, + TEST_NONCE, + ); + coin::destroy_zero(dust); + + // You shall not pass! + let prepared_msg = + transfer_tokens(&mut token_bridge_state, ticket); + + // Done. + publish_message::destroy(prepared_msg); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = wormhole::package_utils::E_NOT_CURRENT_VERSION)] + fun test_cannot_transfer_tokens_outdated_version() { + use token_bridge::transfer_tokens::{prepare_transfer, transfer_tokens}; + + let sender = person(); + let my_scenario = test_scenario::begin(sender); + let scenario = &mut my_scenario; + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register foreign emitter on chain ID == 2. + register_dummy_emitter(scenario, TEST_TARGET_CHAIN); + + // Register and mint coins. + let transfer_amount = 6942000; + let coin_10_balance = + coin_native_10::init_register_and_mint( + scenario, + sender, + transfer_amount + ); + + // Ignore effects. + test_scenario::next_tx(scenario, sender); + + // Fetch objects necessary for sending the transfer. + let token_bridge_state = take_state(scenario); + + let asset_info = state::verified_asset(&token_bridge_state); + + let relayer_fee = 0; + + let ( + ticket, + dust + ) = + prepare_transfer( + asset_info, + coin::from_balance( + coin_10_balance, + test_scenario::ctx(scenario) + ), + TEST_TARGET_CHAIN, + TEST_TARGET_RECIPIENT, + relayer_fee, + TEST_NONCE, + ); + coin::destroy_zero(dust); + + // Conveniently roll version back. + state::reverse_migrate_version(&mut token_bridge_state); + + // Simulate executing with an outdated build by upticking the minimum + // required version for `publish_message` to something greater than + // this build. + state::migrate_version_test_only( + &mut token_bridge_state, + token_bridge::version_control::previous_version_test_only(), + token_bridge::version_control::next_version() + ); + + // You shall not pass! + let prepared_msg = + transfer_tokens(&mut token_bridge_state, ticket); + + // Clean up. + publish_message::destroy(prepared_msg); + + abort 42 } } diff --git a/sui/token_bridge/sources/transfer_tokens_with_payload.move b/sui/token_bridge/sources/transfer_tokens_with_payload.move new file mode 100644 index 000000000..650cc6a0b --- /dev/null +++ b/sui/token_bridge/sources/transfer_tokens_with_payload.move @@ -0,0 +1,812 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements three methods: `prepare_transfer` and +/// `transfer_tokens_with_payload`, which are meant to work together. +/// +/// `prepare_transfer` allows a contract to pack token transfer parameters with +/// an arbitrary payload in preparation to bridge these assets to another +/// network. Only an `EmitterCap` has the capability to create +/// `TransferTicket`. The `EmitterCap` object ID is encoded as the +/// sender. +/// +/// `transfer_tokens_with_payload` unpacks the `TransferTicket` and +/// constructs a `MessageTicket`, which will be used by Wormhole's +/// `publish_message` module. +/// +/// The purpose of splitting this token transferring into two steps is in case +/// Token Bridge needs to be upgraded and there is a breaking change for this +/// module, an integrator would not be left broken. It is discouraged to put +/// `transfer_tokens_with_payload` in an integrator's package logic. Otherwise, +/// this integrator needs to be prepared to upgrade his contract to handle the +/// latest version of `transfer_tokens_with_payload`. +/// +/// Instead, an integrator is encouraged to execute a transaction block, which +/// executes `transfer_tokens_with_payload` using the latest Token Bridge +/// package ID and to implement `prepare_transfer` in his contract to produce +/// `PrepareTransferWithPayload`. +/// +/// NOTE: Only assets that exist in the `TokenRegistry` can be bridged out, +/// which are native Sui assets that have been attested for via `attest_token` +/// and wrapped foreign assets that have been created using foreign asset +/// metadata via the `create_wrapped` module. +/// +/// See `transfer_with_payload` module for serialization and deserialization of +/// Wormhole message payload. +module token_bridge::transfer_tokens_with_payload { + use sui::balance::{Balance}; + use sui::coin::{Coin}; + use sui::object::{Self, ID}; + use wormhole::bytes32::{Self}; + use wormhole::emitter::{EmitterCap}; + use wormhole::external_address::{Self}; + use wormhole::publish_message::{MessageTicket}; + + use token_bridge::normalized_amount::{NormalizedAmount}; + use token_bridge::state::{Self, State, LatestOnly}; + use token_bridge::token_registry::{VerifiedAsset}; + use token_bridge::transfer_with_payload::{Self}; + + /// This type represents transfer data for a specific redeemer contract on a + /// foreign chain. The only way to destroy this type is calling + /// `transfer_tokens_with_payload`. Only the owner of an `EmitterCap` has + /// the capability of generating `TransferTicket`. This emitter + /// cap will usually live in an integrator's contract storage object. + struct TransferTicket { + asset_info: VerifiedAsset, + bridged_in: Balance, + norm_amount: NormalizedAmount, + sender: ID, + redeemer_chain: u16, + redeemer: vector, + payload: vector, + nonce: u32 + } + + /// `prepare_transfer` constructs token transfer parameters. Any remaining + /// amount (A.K.A. dust) from the funds provided will be returned along with + /// the `TransferTicket` type. The returned coin object is the + /// same object moved into this method. + /// + /// NOTE: Integrators of Token Bridge should be calling only this method + /// from their contracts. This method is not guarded by version control + /// (thus not requiring a reference to the Token Bridge `State` object), so + /// it is intended to work for any package version. + public fun prepare_transfer( + emitter_cap: &EmitterCap, + asset_info: VerifiedAsset, + funded: Coin, + redeemer_chain: u16, + redeemer: vector, + payload: vector, + nonce: u32 + ): ( + TransferTicket, + Coin + ) { + use token_bridge::transfer_tokens::{take_truncated_amount}; + + let ( + bridged_in, + norm_amount + ) = take_truncated_amount(&asset_info, &mut funded); + + let prepared_transfer = + TransferTicket { + asset_info, + bridged_in, + norm_amount, + sender: object::id(emitter_cap), + redeemer_chain, + redeemer, + payload, + nonce + }; + + // The remaining amount of funded may have dust depending on the + // decimals of this asset. + (prepared_transfer, funded) + } + + /// `transfer_tokens_with_payload` is the only method that can unpack the + /// members of `TransferTicket`. This method takes the balance + /// from this type and bridges this asset out of Sui by either joining its + /// balance in the Token Bridge's custody for native assets or burning its + /// balance for wrapped assets. + /// + /// The unpacked sender ID comes from an `EmitterCap`. It is encoded as the + /// sender of these assets. And associated with this transfer is an + /// arbitrary payload, which can be consumed by the specified redeemer and + /// used as instructions for a contract composing with Token Bridge. + /// + /// This method returns the prepared Wormhole message (which should be + /// consumed by calling `publish_message` in a transaction block). + /// + /// NOTE: This method is guarded by a minimum build version check. This + /// method could break backward compatibility on an upgrade. + /// + /// It is important for integrators to refrain from calling this method + /// within their contracts. This method is meant to be called in a + /// tranasction block after receiving a `TransferTicket` from calling + /// `prepare_transfer` within a contract. If in a circumstance where this + /// module has a breaking change in an upgrade, `prepare_transfer` will not + /// be affected by this change. + public fun transfer_tokens_with_payload( + token_bridge_state: &mut State, + prepared_transfer: TransferTicket + ): MessageTicket { + // This capability ensures that the current build version is used. + let latest_only = state::assert_latest_only(token_bridge_state); + + // Encode Wormhole message payload. + let ( + nonce, + encoded_transfer_with_payload + ) = + bridge_in_and_serialize_transfer( + &latest_only, + token_bridge_state, + prepared_transfer + ); + + // Prepare Wormhole message with encoded `TransferWithPayload`. + state::prepare_wormhole_message( + &latest_only, + token_bridge_state, + nonce, + encoded_transfer_with_payload + ) + } + + fun bridge_in_and_serialize_transfer( + latest_only: &LatestOnly, + token_bridge_state: &mut State, + prepared_transfer: TransferTicket + ): ( + u32, + vector + ) { + use token_bridge::transfer_tokens::{burn_or_deposit_funds}; + + let TransferTicket { + asset_info, + bridged_in, + norm_amount, + sender, + redeemer_chain, + redeemer, + payload, + nonce + } = prepared_transfer; + + let ( + token_chain, + token_address + ) = + burn_or_deposit_funds( + latest_only, + token_bridge_state, + &asset_info, + bridged_in + ); + + let redeemer = external_address::new(bytes32::from_bytes(redeemer)); + + let encoded = + transfer_with_payload::serialize( + transfer_with_payload::new( + sender, + norm_amount, + token_address, + token_chain, + redeemer, + redeemer_chain, + payload + ) + ); + + (nonce, encoded) + } + + #[test_only] + public fun bridge_in_and_serialize_transfer_test_only( + token_bridge_state: &mut State, + prepared_transfer: TransferTicket + ): ( + u32, + vector + ) { + // This capability ensures that the current build version is used. + let latest_only = state::assert_latest_only(token_bridge_state); + + bridge_in_and_serialize_transfer( + &latest_only, + token_bridge_state, + prepared_transfer + ) + } +} + +#[test_only] +module token_bridge::transfer_tokens_with_payload_tests { + use sui::coin::{Self}; + use sui::object::{Self}; + use sui::test_scenario::{Self}; + use wormhole::bytes32::{Self}; + use wormhole::emitter::{Self}; + use wormhole::external_address::{Self}; + use wormhole::publish_message::{Self}; + use wormhole::state::{chain_id}; + + use token_bridge::coin_wrapped_7::{Self, COIN_WRAPPED_7}; + use token_bridge::coin_native_10::{Self, COIN_NATIVE_10}; + use token_bridge::native_asset::{Self}; + use token_bridge::normalized_amount::{Self}; + use token_bridge::state::{Self}; + use token_bridge::token_bridge_scenario::{ + set_up_wormhole_and_token_bridge, + register_dummy_emitter, + return_state, + take_state, + person + }; + use token_bridge::token_registry::{Self}; + use token_bridge::transfer_with_payload::{Self}; + use token_bridge::wrapped_asset::{Self}; + + /// Test consts. + const TEST_TARGET_RECIPIENT: vector = x"beef4269"; + const TEST_TARGET_CHAIN: u16 = 2; + const TEST_NONCE: u32 = 0; + const TEST_COIN_NATIVE_10_DECIMALS: u8 = 10; + const TEST_COIN_WRAPPED_7_DECIMALS: u8 = 7; + const TEST_MESSAGE_PAYLOAD: vector = x"deadbeefdeadbeef"; + + #[test] + fun test_transfer_tokens_with_payload_native_10() { + use token_bridge::transfer_tokens_with_payload::{ + prepare_transfer, + transfer_tokens_with_payload + }; + + let sender = person(); + let my_scenario = test_scenario::begin(sender); + let scenario = &mut my_scenario; + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register foreign emitter on chain ID == 2. + register_dummy_emitter(scenario, TEST_TARGET_CHAIN); + + // Register and mint coins. + let transfer_amount = 6942000; + let coin_10_balance = + coin_native_10::init_register_and_mint( + scenario, + sender, + transfer_amount + ); + + // Ignore effects. + test_scenario::next_tx(scenario, sender); + + // Fetch objects necessary for sending the transfer. + let token_bridge_state = take_state(scenario); + + // Balance check the Token Bridge before executing the transfer. The + // initial balance should be zero for COIN_NATIVE_10. + { + let registry = state::borrow_token_registry(&token_bridge_state); + let asset = token_registry::borrow_native(registry); + assert!(native_asset::custody(asset) == 0, 0); + }; + + // Register and obtain a new wormhole emitter cap. + let emitter_cap = emitter::dummy(); + + let asset_info = state::verified_asset(&token_bridge_state); + let ( + prepared_transfer, + dust + ) = + prepare_transfer( + &emitter_cap, + asset_info, + coin::from_balance( + coin_10_balance, + test_scenario::ctx(scenario) + ), + TEST_TARGET_CHAIN, + TEST_TARGET_RECIPIENT, + TEST_MESSAGE_PAYLOAD, + TEST_NONCE, + ); + coin::destroy_zero(dust); + + // Call `transfer_tokens_with_payload`. + let prepared_msg = + transfer_tokens_with_payload( + &mut token_bridge_state, + prepared_transfer + ); + + // Balance check the Token Bridge after executing the transfer. The + // balance should now reflect the `transfer_amount` defined in this + // test. + { + let registry = state::borrow_token_registry(&token_bridge_state); + let asset = token_registry::borrow_native(registry); + assert!(native_asset::custody(asset) == transfer_amount, 0); + }; + + // Clean up. + publish_message::destroy(prepared_msg); + return_state(token_bridge_state); + emitter::destroy_test_only(emitter_cap); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + fun test_transfer_tokens_native_10_with_dust_refund() { + use token_bridge::transfer_tokens_with_payload::{ + prepare_transfer, + transfer_tokens_with_payload + }; + + let sender = person(); + let my_scenario = test_scenario::begin(sender); + let scenario = &mut my_scenario; + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register foreign emitter on chain ID == 2. + register_dummy_emitter(scenario, TEST_TARGET_CHAIN); + + // Register and mint coins. + let transfer_amount = 1000069; + let coin_10_balance = coin_native_10::init_register_and_mint( + scenario, + sender, + transfer_amount + ); + + // This value will be used later. The contract should return dust + // to the caller since COIN_NATIVE_10 has 10 decimals. + let expected_dust = 69; + + // Ignore effects. + test_scenario::next_tx(scenario, sender); + + // Fetch objects necessary for sending the transfer. + let token_bridge_state = take_state(scenario); + + // Balance check the Token Bridge before executing the transfer. The + // initial balance should be zero for COIN_NATIVE_10. + { + let registry = state::borrow_token_registry(&token_bridge_state); + let asset = token_registry::borrow_native(registry); + assert!(native_asset::custody(asset) == 0, 0); + }; + + // Register and obtain a new wormhole emitter cap. + let emitter_cap = emitter::dummy(); + + let asset_info = state::verified_asset(&token_bridge_state); + let ( + prepared_transfer, + dust + ) = + prepare_transfer( + &emitter_cap, + asset_info, + coin::from_balance( + coin_10_balance, + test_scenario::ctx(scenario) + ), + TEST_TARGET_CHAIN, + TEST_TARGET_RECIPIENT, + TEST_MESSAGE_PAYLOAD, + TEST_NONCE, + ); + assert!(coin::value(&dust) == expected_dust, 0); + + // Call `transfer_tokens`. + let prepared_msg = + transfer_tokens_with_payload( + &mut token_bridge_state, + prepared_transfer + ); + + // Balance check the Token Bridge after executing the transfer. The + // balance should now reflect the `transfer_amount` less `expected_dust` + // defined in this test. + { + let registry = state::borrow_token_registry(&token_bridge_state); + let asset = token_registry::borrow_native(registry); + assert!( + native_asset::custody(asset) == transfer_amount - expected_dust, + 0 + ); + }; + + // Clean up. + publish_message::destroy(prepared_msg); + coin::burn_for_testing(dust); + emitter::destroy_test_only(emitter_cap); + return_state(token_bridge_state); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + fun test_serialize_transfer_tokens_native_10() { + use token_bridge::transfer_tokens_with_payload::{ + bridge_in_and_serialize_transfer_test_only, + prepare_transfer + }; + + let sender = person(); + let my_scenario = test_scenario::begin(sender); + let scenario = &mut my_scenario; + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register foreign emitter on chain ID == 2. + register_dummy_emitter(scenario, TEST_TARGET_CHAIN); + + // Register and mint coins. + let transfer_amount = 6942000; + let bridge_coin_10 = + coin::from_balance( + coin_native_10::init_register_and_mint( + scenario, + sender, + transfer_amount + ), + test_scenario::ctx(scenario) + ); + + // Ignore effects. + test_scenario::next_tx(scenario, sender); + + // Fetch objects necessary for sending the transfer. + let token_bridge_state = take_state(scenario); + + // Register and obtain a new wormhole emitter cap. + let emitter_cap = emitter::dummy(); + + let asset_info = state::verified_asset(&token_bridge_state); + let expected_token_address = token_registry::token_address(&asset_info); + + let ( + prepared_transfer, + dust + ) = + prepare_transfer( + &emitter_cap, + asset_info, + bridge_coin_10, + TEST_TARGET_CHAIN, + TEST_TARGET_RECIPIENT, + TEST_MESSAGE_PAYLOAD, + TEST_NONCE, + ); + coin::destroy_zero(dust); + + // Serialize the payload. + let ( + nonce, + payload + ) = + bridge_in_and_serialize_transfer_test_only( + &mut token_bridge_state, + prepared_transfer + ); + assert!(nonce == TEST_NONCE, 0); + + // Construct expected payload from scratch and confirm that the + // `transfer_tokens` call produces the same payload. + let expected_amount = normalized_amount::from_raw( + transfer_amount, + TEST_COIN_NATIVE_10_DECIMALS + ); + + let expected_payload = + transfer_with_payload::new_test_only( + object::id(&emitter_cap), + expected_amount, + expected_token_address, + chain_id(), + external_address::new(bytes32::from_bytes(TEST_TARGET_RECIPIENT)), + TEST_TARGET_CHAIN, + TEST_MESSAGE_PAYLOAD + ); + assert!( + transfer_with_payload::serialize(expected_payload) == payload, + 0 + ); + + // Clean up. + return_state(token_bridge_state); + emitter::destroy_test_only(emitter_cap); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + fun test_transfer_tokens_with_payload_wrapped_7() { + use token_bridge::transfer_tokens_with_payload::{ + prepare_transfer, + transfer_tokens_with_payload + }; + + let sender = person(); + let my_scenario = test_scenario::begin(sender); + let scenario = &mut my_scenario; + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register foreign emitter on chain ID == 2. + register_dummy_emitter(scenario, TEST_TARGET_CHAIN); + + // Register and mint coins. + let transfer_amount = 6942000; + let coin_7_balance = + coin_wrapped_7::init_register_and_mint( + scenario, + sender, + transfer_amount + ); + + // Ignore effects. + test_scenario::next_tx(scenario, sender); + + // Fetch objects necessary for sending the transfer. + let token_bridge_state = take_state(scenario); + + // Balance check the Token Bridge before executing the transfer. The + // initial balance should be the `transfer_amount` for COIN_WRAPPED_7. + { + let registry = state::borrow_token_registry(&token_bridge_state); + let asset = token_registry::borrow_wrapped(registry); + assert!(wrapped_asset::total_supply(asset) == transfer_amount, 0); + }; + + // Register and obtain a new wormhole emitter cap. + let emitter_cap = emitter::dummy(); + + let asset_info = state::verified_asset(&token_bridge_state); + let ( + prepared_transfer, + dust + ) = + prepare_transfer( + &emitter_cap, + asset_info, + coin::from_balance( + coin_7_balance, + test_scenario::ctx(scenario) + ), + TEST_TARGET_CHAIN, + TEST_TARGET_RECIPIENT, + TEST_MESSAGE_PAYLOAD, + TEST_NONCE, + ); + coin::destroy_zero(dust); + + // Call `transfer_tokens_with_payload`. + let prepared_msg = + transfer_tokens_with_payload( + &mut token_bridge_state, + prepared_transfer + ); + + // Balance check the Token Bridge after executing the transfer. The + // balance should be zero, since tokens are burned when an outbound + // wrapped token transfer occurs. + { + let registry = state::borrow_token_registry(&token_bridge_state); + let asset = token_registry::borrow_wrapped(registry); + assert!(wrapped_asset::total_supply(asset) == 0, 0); + }; + + // Clean up. + publish_message::destroy(prepared_msg); + emitter::destroy_test_only(emitter_cap); + return_state(token_bridge_state); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + fun test_serialize_transfer_tokens_wrapped_7() { + use token_bridge::transfer_tokens_with_payload::{ + bridge_in_and_serialize_transfer_test_only, + prepare_transfer + }; + + let sender = person(); + let my_scenario = test_scenario::begin(sender); + let scenario = &mut my_scenario; + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register foreign emitter on chain ID == 2. + register_dummy_emitter(scenario, TEST_TARGET_CHAIN); + + // Register and mint coins. + let transfer_amount = 6942000; + let bridged_coin_7 = + coin::from_balance( + coin_wrapped_7::init_register_and_mint( + scenario, + sender, + transfer_amount + ), + test_scenario::ctx(scenario) + ); + + // Ignore effects. + test_scenario::next_tx(scenario, sender); + + // Fetch objects necessary for sending the transfer. + let token_bridge_state = take_state(scenario); + + // Register and obtain a new wormhole emitter cap. + let emitter_cap = emitter::dummy(); + + let asset_info = state::verified_asset(&token_bridge_state); + let expected_token_address = token_registry::token_address(&asset_info); + let expected_token_chain = token_registry::token_chain(&asset_info); + + let ( + prepared_transfer, + dust + ) = + prepare_transfer( + &emitter_cap, + asset_info, + bridged_coin_7, + TEST_TARGET_CHAIN, + TEST_TARGET_RECIPIENT, + TEST_MESSAGE_PAYLOAD, + TEST_NONCE, + ); + coin::destroy_zero(dust); + + // Serialize the payload. + let ( + nonce, + payload + ) = + bridge_in_and_serialize_transfer_test_only( + &mut token_bridge_state, + prepared_transfer + ); + assert!(nonce == TEST_NONCE, 0); + + // Construct expected payload from scratch and confirm that the + // `transfer_tokens` call produces the same payload. + let expected_amount = normalized_amount::from_raw( + transfer_amount, + TEST_COIN_WRAPPED_7_DECIMALS + ); + + let expected_payload = + transfer_with_payload::new_test_only( + object::id(&emitter_cap), + expected_amount, + expected_token_address, + expected_token_chain, + external_address::new(bytes32::from_bytes(TEST_TARGET_RECIPIENT)), + TEST_TARGET_CHAIN, + TEST_MESSAGE_PAYLOAD + ); + assert!( + transfer_with_payload::serialize(expected_payload) == payload, + 0 + ); + + // Clean up. + emitter::destroy_test_only(emitter_cap); + return_state(token_bridge_state); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + #[expected_failure(abort_code = wormhole::package_utils::E_NOT_CURRENT_VERSION)] + fun test_cannot_transfer_tokens_with_payload_outdated_version() { + use token_bridge::transfer_tokens_with_payload::{ + prepare_transfer, + transfer_tokens_with_payload + }; + + let sender = person(); + let my_scenario = test_scenario::begin(sender); + let scenario = &mut my_scenario; + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register foreign emitter on chain ID == 2. + register_dummy_emitter(scenario, TEST_TARGET_CHAIN); + + // Register and mint coins. + let transfer_amount = 6942000; + let coin_10_balance = + coin_native_10::init_register_and_mint( + scenario, + sender, + transfer_amount + ); + + // Ignore effects. + test_scenario::next_tx(scenario, sender); + + // Fetch objects necessary for sending the transfer. + let token_bridge_state = take_state(scenario); + + // Register and obtain a new wormhole emitter cap. + let emitter_cap = emitter::dummy(); + + let asset_info = state::verified_asset(&token_bridge_state); + let ( + prepared_transfer, + dust + ) = + prepare_transfer( + &emitter_cap, + asset_info, + coin::from_balance( + coin_10_balance, + test_scenario::ctx(scenario) + ), + TEST_TARGET_CHAIN, + TEST_TARGET_RECIPIENT, + TEST_MESSAGE_PAYLOAD, + TEST_NONCE, + ); + coin::destroy_zero(dust); + + // Conveniently roll version back. + state::reverse_migrate_version(&mut token_bridge_state); + + // Simulate executing with an outdated build by upticking the minimum + // required version for `publish_message` to something greater than + // this build. + state::migrate_version_test_only( + &mut token_bridge_state, + token_bridge::version_control::previous_version_test_only(), + token_bridge::version_control::next_version() + ); + + // You shall not pass! + let prepared_msg = + transfer_tokens_with_payload( + &mut token_bridge_state, + prepared_transfer + ); + + // Clean up. + publish_message::destroy(prepared_msg); + + abort 42 + } +} diff --git a/sui/token_bridge/sources/utils/coin_utils.move b/sui/token_bridge/sources/utils/coin_utils.move new file mode 100644 index 000000000..80d351f93 --- /dev/null +++ b/sui/token_bridge/sources/utils/coin_utils.move @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements utilities helpful for outbound token transfers. These +/// utility methods should also help avoid having to work around conversions +/// between `Coin` and `Balance` avoiding unnecessary object creation and +/// destruction. +module token_bridge::coin_utils { + use sui::balance::{Self, Balance}; + use sui::coin::{Self, Coin}; + use sui::tx_context::{TxContext}; + + /// Method similar to `coin::take` where an amount is split from a `Coin` + /// object's inner balance. + public fun take_balance( + coin_mut: &mut Coin, + amount: u64 + ): Balance { + balance::split(coin::balance_mut(coin_mut), amount) + } + + /// Method out of convenience to take the full balance value out of a `Coin` + /// object while preserving that object. This method is used to avoid + /// calling `coin::into_balance` which destroys the object. + public fun take_full_balance(coin_mut: &mut Coin): Balance { + let amount = coin::value(coin_mut); + take_balance(coin_mut, amount) + } + + /// Method similar to `coin::put` where an outside balance is joined with + /// an existing `Coin` object. + public fun put_balance( + coin_mut: &mut Coin, + the_balance: Balance + ): u64 { + balance::join(coin::balance_mut(coin_mut), the_balance) + } + + /// Method for those integrators that use `Coin` objects, where `the_coin` + /// will be destroyed if the value is zero. Otherwise it will be returned + /// back to the transaction sender. + public fun return_nonzero(the_coin: Coin, ctx: &TxContext) { + if (coin::value(&the_coin) == 0) { + coin::destroy_zero(the_coin); + } else { + sui::pay::keep(the_coin, ctx) + } + } +} diff --git a/sui/token_bridge/sources/utils/string_utils.move b/sui/token_bridge/sources/utils/string_utils.move new file mode 100644 index 000000000..868fa7492 --- /dev/null +++ b/sui/token_bridge/sources/utils/string_utils.move @@ -0,0 +1,97 @@ +module token_bridge::string_utils { + use std::ascii::{Self}; + use std::string::{Self, String}; + use std::vector::{Self}; + + const QUESTION_MARK: u8 = 63; + // Recall that UTF-8 characters have variable-length encoding and can have + // 1, 2, 3, or 4 bytes. + // The first byte of the 2, 3, and 4-byte UTF-8 characters have a special + // form indicating how many more bytes follow in the same character + // representation. Specifically, it can have the forms + // - 110xxxxx // 11000000 is 192 (base 10) + // - 1110xxxx // 11100000 is 224 (base 10) + // - or 11110xxx // 11110000 is 240 (base 10) + // + // We can tell the length the a hex UTF-8 character in bytes by looking + // at the first byte and counting the leading 1's, or alternatively + // seeing whether it falls in the range + // [11000000, 11100000) or [11100000, 11110000) or [11110000, 11111111], + // + // The following constants demarcate those ranges and are used in the + // string32::to_ascii function. + const UTF8_LENGTH_2_FIRST_BYTE_LOWER_BOUND: u8 = 192; + const UTF8_LENGTH_3_FIRST_BYTE_LOWER_BOUND: u8 = 224; + const UTF8_LENGTH_4_FIRST_BYTE_LOWER_BOUND: u8 = 240; + + /// Converts a String32 to an ascii string if possible, otherwise errors + /// out at `ascii::string(bytes)`. For input strings that contain non-ascii + /// characters, we will swap the non-ascii character with `?`. + /// + /// Note that while the Sui spec limits symbols to only use ascii + /// characters, the token bridge spec does allow utf8 symbols. + public fun to_ascii(s: &String): ascii::String { + let buf = *string::bytes(s); + // keep dropping the last character while it's 0 + while ( + !vector::is_empty(&buf) && + *vector::borrow(&buf, vector::length(&buf) - 1) == 0 + ) { + vector::pop_back(&mut buf); + }; + + // Run through `buf` to convert any non-ascii character to `?`. + let asciified = vector::empty(); + let (i, n) = (0, vector::length(&buf)); + while (i < n) { + let b = *vector::borrow(&buf, i); + // If it is a valid ascii character, keep it. + if (ascii::is_valid_char(b)) { + vector::push_back(&mut asciified, b); + i = i + 1; + } else { + // Since UTF-8 characters have variable-length encoding (they are + // represented using 1-4 bytes, unlike ASCII characters, which + // are represented using 1 byte), we don't want to transform + // every byte in a UTF-8 string that does not represent an ASCII + // character to the question mark symbol "?". This would result + // in having too many "?" symbols. + // + // Instead, we want a single "?" for each character. Note that + // the 1-byte UTF-8 characters correspond to valid ASCII + // characters and have the form 0xxxxxxx. + // The 2, 3, and 4-byte UTF-8 characters have first byte equal + // to: + // - 110xxxxx // 192 + // - 1110xxxx // 224 + // - or 11110xxx // 240 + // + // and remaining bytes of the form: + // - 10xxxxxx + // + // To ensure a one-to-one mapping of a multi-byte UTF-8 character + // to a "?", we detect the first byte of a new UTF-8 character + // in a multi-byte representation by checking if it is + // >= 11000000 (base 2) or 192 (base 10) and convert it to a "?" + // and skip the remaining bytes in the same representation. + // + // + // Reference: https://en.wikipedia.org/wiki/UTF-8 + if (b >= UTF8_LENGTH_2_FIRST_BYTE_LOWER_BOUND){ + vector::push_back(&mut asciified, QUESTION_MARK); + if (b >= UTF8_LENGTH_4_FIRST_BYTE_LOWER_BOUND){ + // The UTF-8 char has a 4-byte hex representation. + i = i + 4; + } else if (b >= UTF8_LENGTH_3_FIRST_BYTE_LOWER_BOUND){ + // The UTF-8 char has a 3-byte hex representation. + i = i + 3; + } else { + // The UTF-8 char has a 2-byte hex representation. + i = i + 2; + } + } + }; + }; + ascii::string(asciified) + } +} diff --git a/sui/token_bridge/sources/vaa.move b/sui/token_bridge/sources/vaa.move index cb7b04557..c2e9a2609 100644 --- a/sui/token_bridge/sources/vaa.move +++ b/sui/token_bridge/sources/vaa.move @@ -1,221 +1,351 @@ -/// Token Bridge VAA utilities +// SPDX-License-Identifier: Apache 2 + +/// This module builds on Wormhole's `vaa::parse_and_verify` method by adding +/// emitter verification and replay protection. +/// +/// Token Bridge only cares about other Token Bridge messages, so the emitter +/// address must be a registered Token Bridge emitter according to the VAA's +/// emitter chain ID. +/// +/// Token Bridge does not allow replaying any of its VAAs, so its hash is stored +/// in its `State`. If the encoded VAA passes through `parse_and_verify` again, +/// it will abort. module token_bridge::vaa { - use std::option; - use sui::tx_context::{TxContext}; - - use wormhole::myvaa::{Self as corevaa, VAA}; - use wormhole::state::{State as WormholeState}; + use sui::table::{Self}; use wormhole::external_address::{ExternalAddress}; + use wormhole::vaa::{Self, VAA}; - use token_bridge::bridge_state::{Self as bridge_state, BridgeState}; + use token_bridge::state::{Self, State}; - //friend token_bridge::contract_upgrade; - friend token_bridge::register_chain; - friend token_bridge::wrapped; + friend token_bridge::create_wrapped; friend token_bridge::complete_transfer; + friend token_bridge::complete_transfer_with_payload; + + /// For a given chain ID, Token Bridge is non-existent. + const E_UNREGISTERED_EMITTER: u64 = 0; + /// Encoded emitter address does not match registered Token Bridge. + const E_EMITTER_ADDRESS_MISMATCH: u64 = 1; + + /// This type represents VAA data whose emitter is a registered Token Bridge + /// emitter. This message is also representative of a VAA that cannot be + /// replayed. + struct TokenBridgeMessage { + /// Wormhole chain ID from which network the message originated from. + emitter_chain: u16, + /// Address of Token Bridge (standardized to 32 bytes) that produced + /// this message. + emitter_address: ExternalAddress, + /// Sequence number of Token Bridge's Wormhole message. + sequence: u64, + /// Token Bridge payload. + payload: vector + } + + /// Parses and verifies encoded VAA. Because Token Bridge does not allow + /// VAAs to be replayed, the VAA hash is stored in a set, which is checked + /// against the next time the same VAA is used to make sure it cannot be + /// used again. + /// + /// In its verification, this method checks whether the emitter is a + /// registered Token Bridge contract on another network. + /// + /// NOTE: It is important for integrators to refrain from calling this + /// method within their contracts. This method is meant to be called within + /// a transaction block, passing the `TokenBridgeMessage` to one of the + /// Token Bridge methods that consumes this type. If in a circumstance where + /// this module has a breaking change in an upgrade, another method (e.g. + /// `complete_transfer_with_payload`) will not be affected by this change. + public fun verify_only_once( + token_bridge_state: &mut State, + verified_vaa: VAA + ): TokenBridgeMessage { + // This capability ensures that the current build version is used. + let latest_only = state::assert_latest_only(token_bridge_state); + + // First parse and verify VAA using Wormhole. This also consumes the VAA + // hash to prevent replay. + vaa::consume( + state::borrow_mut_consumed_vaas(&latest_only, token_bridge_state), + &verified_vaa + ); + + // Does the emitter agree with a registered Token Bridge? + assert_registered_emitter(token_bridge_state, &verified_vaa); + + // Take emitter info, sequence and payload. + let sequence = vaa::sequence(&verified_vaa); + let ( + emitter_chain, + emitter_address, + payload + ) = vaa::take_emitter_info_and_payload(verified_vaa); + + TokenBridgeMessage { + emitter_chain, + emitter_address, + sequence, + payload + } + } + + public fun emitter_chain(self: &TokenBridgeMessage): u16 { + self.emitter_chain + } + + public fun emitter_address(self: &TokenBridgeMessage): ExternalAddress { + self.emitter_address + } + + public fun sequence(self: &TokenBridgeMessage): u64 { + self.sequence + } + + /// Destroy `TokenBridgeMessage` and extract payload, which is the same + /// payload in the `VAA`. + /// + /// NOTE: This is a privileged method, which only friends within the Token + /// Bridge package can use. This guarantees that no other package can redeem + /// a VAA intended for Token Bridge as a denial-of-service by calling + /// `verify_only_once` and then destroying it by calling it this method. + public(friend) fun take_payload(msg: TokenBridgeMessage): vector { + let TokenBridgeMessage { + emitter_chain: _, + emitter_address: _, + sequence: _, + payload + } = msg; + + payload + } + + /// Assert that a given emitter equals one that is registered as a foreign + /// Token Bridge. + fun assert_registered_emitter( + token_bridge_state: &State, + verified_vaa: &VAA + ) { + let chain = vaa::emitter_chain(verified_vaa); + let registry = state::borrow_emitter_registry(token_bridge_state); + assert!(table::contains(registry, chain), E_UNREGISTERED_EMITTER); + + let registered = table::borrow(registry, chain); + let emitter_addr = vaa::emitter_address(verified_vaa); + assert!(*registered == emitter_addr, E_EMITTER_ADDRESS_MISMATCH); + } #[test_only] - friend token_bridge::token_bridge_vaa_test; - - /// We have no registration for this chain - const E_UNKNOWN_CHAIN: u64 = 0; - /// We have a registration, but it's different from what's given - const E_UNKNOWN_EMITTER: u64 = 1; - - /// Aborts if the VAA has already been consumed. Marks the VAA as consumed - /// the first time around. - public(friend) fun replay_protect(bridge_state: &mut BridgeState, vaa: &VAA) { - // this calls set::add which aborts if the element already exists - bridge_state::store_consumed_vaa(bridge_state, corevaa::get_hash(vaa)); - } - - /// Asserts that the VAA is from a known token bridge. - public fun assert_known_emitter(state: &BridgeState, vm: &VAA) { - let maybe_emitter = bridge_state::get_registered_emitter(state, &corevaa::get_emitter_chain(vm)); - assert!(option::is_some(&maybe_emitter), E_UNKNOWN_CHAIN); - - let emitter = option::extract(&mut maybe_emitter); - assert!(emitter == corevaa::get_emitter_address(vm), E_UNKNOWN_EMITTER); - } - - /// Parses, verifies, and replay protects a token bridge VAA. - /// Aborts if the VAA is not from a known token bridge emitter. - /// - /// Has a 'friend' visibility so that it's only callable by the token bridge - /// (otherwise the replay protection could be abused to DoS the bridge) - public(friend) fun parse_verify_and_replay_protect( - wormhole_state: &mut WormholeState, - bridge_state: &mut BridgeState, - vaa: vector, - ctx: &mut TxContext - ): VAA { - let vaa = parse_and_verify(wormhole_state, bridge_state, vaa, ctx); - replay_protect(bridge_state, &vaa); - vaa - } - - /// Parses, and verifies a token bridge VAA. - /// Aborts if the VAA is not from a known token bridge emitter. - public fun parse_and_verify(wormhole_state: &mut WormholeState, bridge_state: &BridgeState, vaa: vector, ctx:&mut TxContext): VAA { - let vaa = corevaa::parse_and_verify(wormhole_state, vaa, ctx); - assert_known_emitter(bridge_state, &vaa); - vaa + public fun destroy(msg: TokenBridgeMessage) { + take_payload(msg); } } #[test_only] -module token_bridge::token_bridge_vaa_test{ - use sui::test_scenario::{Self, Scenario, next_tx, ctx, take_shared, return_shared}; - - use wormhole::state::{State}; - use wormhole::myvaa::{Self as corevaa}; - use wormhole::myu16::{Self as u16}; +module token_bridge::vaa_tests { + use sui::test_scenario::{Self}; use wormhole::external_address::{Self}; + use wormhole::wormhole_scenario::{parse_and_verify_vaa}; - use token_bridge::bridge_state::{Self, BridgeState}; + use token_bridge::state::{Self}; + use token_bridge::token_bridge_scenario::{ + person, + register_dummy_emitter, + return_state, + set_up_wormhole_and_token_bridge, + take_state + }; use token_bridge::vaa::{Self}; - use token_bridge::bridge_state_test::{set_up_wormhole_core_and_token_bridges}; - fun scenario(): Scenario { test_scenario::begin(@0x123233) } - fun people(): (address, address, address) { (@0x124323, @0xE05, @0xFACE) } - - /// VAA sent from the ethereum token bridge 0xdeadbeef - const VAA: vector = x"01000000000100102d399190fa61daccb11c2ea4f7a3db3a9365e5936bcda4cded87c1b9eeb095173514f226256d5579af71d4089eb89496befb998075ba94cd1d4460c5c57b84000000000100000001000200000000000000000000000000000000000000000000000000000000deadbeef0000000002634973000200000000000000000000000000000000000000000000000000000000beefface00020c0000000000000000000000000000000000000000000000000000000042454546000000000000000000000000000000000042656566206661636520546f6b656e"; + /// VAA sent from the ethereum token bridge 0xdeadbeef. + const VAA: vector = + x"01000000000100102d399190fa61daccb11c2ea4f7a3db3a9365e5936bcda4cded87c1b9eeb095173514f226256d5579af71d4089eb89496befb998075ba94cd1d4460c5c57b84000000000100000001000200000000000000000000000000000000000000000000000000000000deadbeef0000000002634973000200000000000000000000000000000000000000000000000000000000beefface00020c0000000000000000000000000000000000000000000000000000000042454546000000000000000000000000000000000042656566206661636520546f6b656e"; #[test] - #[expected_failure(abort_code = vaa::E_UNKNOWN_CHAIN)] - fun test_unknown_chain() { - let (admin, _, _) = people(); - let test = scenario(); - test = set_up_wormhole_core_and_token_bridges(admin, test); - next_tx(&mut test, admin); { - let state = take_shared(&test); - let w_state = take_shared(&test); - let vaa = vaa::parse_verify_and_replay_protect(&mut w_state, &mut state, VAA, ctx(&mut test)); - corevaa::destroy(vaa); - return_shared(state); - return_shared(w_state); - }; - test_scenario::end(test); - } + #[expected_failure(abort_code = vaa::E_UNREGISTERED_EMITTER)] + fun test_cannot_verify_only_once_unregistered_chain() { + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); - #[test] - #[expected_failure(abort_code = vaa::E_UNKNOWN_EMITTER)] - fun test_unknown_emitter() { - let (admin, _, _) = people(); - let test = scenario(); - test = set_up_wormhole_core_and_token_bridges(admin, test); + // Ignore effects. + test_scenario::next_tx(scenario, caller); - next_tx(&mut test, admin); { - let state = take_shared(&test); - bridge_state::set_registered_emitter( - &mut state, - u16::from_u64(2), - external_address::from_bytes(x"deadbeed"), // not deadbeef - ); - return_shared(state); - }; + let token_bridge_state = take_state(scenario); - next_tx(&mut test, admin); { - let state = take_shared(&test); - let w_state = take_shared(&test); - let vaa = vaa::parse_verify_and_replay_protect(&mut w_state, &mut state, VAA, ctx(&mut test)); - corevaa::destroy(vaa); - return_shared(state); - return_shared(w_state); - }; - test_scenario::end(test); + let verified_vaa = parse_and_verify_vaa(scenario, VAA); + // You shall not pass! + let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa); + + // Clean up. + vaa::destroy(msg); + + abort 42 } #[test] - fun test_known_emitter() { - let (admin, _, _) = people(); - let test = scenario(); - test = set_up_wormhole_core_and_token_bridges(admin, test); + #[expected_failure(abort_code = vaa::E_EMITTER_ADDRESS_MISMATCH)] + fun test_cannot_verify_only_once_emitter_address_mismatch() { + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; - next_tx(&mut test, admin); { - let state = take_shared(&test); - bridge_state::set_registered_emitter( - &mut state, - u16::from_u64(2), - external_address::from_bytes(x"deadbeef"), - ); - return_shared(state); - }; + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); - next_tx(&mut test, admin); { - let state = take_shared(&test); - let w_state = take_shared(&test); - let vaa = vaa::parse_verify_and_replay_protect(&mut w_state, &mut state, VAA, ctx(&mut test)); - corevaa::destroy(vaa); - return_shared(state); - return_shared(w_state); - }; - test_scenario::end(test); + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + let token_bridge_state = take_state(scenario); + + // First register emitter. + let emitter_chain = 2; + let emitter_addr = external_address::from_address(@0xdeafbeef); + token_bridge::register_chain::register_new_emitter_test_only( + &mut token_bridge_state, + emitter_chain, + emitter_addr + ); + + // Confirm that encoded emitter disagrees with registered emitter. + let verified_vaa = parse_and_verify_vaa(scenario, VAA); + assert!( + wormhole::vaa::emitter_address(&verified_vaa) != emitter_addr, + 0 + ); + + // You shall not pass! + let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa); + + // Clean up. + vaa::destroy(msg); + + abort 42 } #[test] - #[expected_failure(abort_code = 0, location=0000000000000000000000000000000000000002::dynamic_field)] - fun test_replay_protection_works() { - let (admin, _, _) = people(); - let test = scenario(); - test = set_up_wormhole_core_and_token_bridges(admin, test); + fun test_verify_only_once() { + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; - next_tx(&mut test, admin); { - let state = take_shared(&test); - bridge_state::set_registered_emitter( - &mut state, - u16::from_u64(2), - external_address::from_bytes(x"deadbeef"), - ); - return_shared(state); - }; + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); - next_tx(&mut test, admin); { - let state = take_shared(&test); - let w_state = take_shared(&test); + // Register foreign emitter. + let expected_source_chain = 2; + register_dummy_emitter(scenario, expected_source_chain); - // try to use the VAA twice - let vaa = vaa::parse_verify_and_replay_protect(&mut w_state, &mut state, VAA, ctx(&mut test)); - corevaa::destroy(vaa); - let vaa = vaa::parse_verify_and_replay_protect(&mut w_state, &mut state, VAA, ctx(&mut test)); - corevaa::destroy(vaa); - return_shared(state); - return_shared(w_state); - }; - test_scenario::end(test); + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + let token_bridge_state = take_state(scenario); + + // Confirm VAA originated from where we expect. + let verified_vaa = parse_and_verify_vaa(scenario, VAA); + assert!( + wormhole::vaa::emitter_chain(&verified_vaa) == expected_source_chain, + 0 + ); + + // Finally verify. + let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa); + + // Clean up. + vaa::destroy(msg); + return_state(token_bridge_state); + + // Done. + test_scenario::end(my_scenario); } #[test] - fun test_can_verify_after_replay_protect() { - let (admin, _, _) = people(); - let test = scenario(); - test = set_up_wormhole_core_and_token_bridges(admin, test); + #[expected_failure(abort_code = wormhole::set::E_KEY_ALREADY_EXISTS)] + fun test_cannot_verify_only_once_again() { + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; - next_tx(&mut test, admin); { - let state = take_shared(&test); - bridge_state::set_registered_emitter( - &mut state, - u16::from_u64(2), - external_address::from_bytes(x"deadbeef"), - ); - return_shared(state); - }; + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); - next_tx(&mut test, admin); { - let state = take_shared(&test); - let w_state = take_shared(&test); + // Register foreign emitter. + let expected_source_chain = 2; + register_dummy_emitter(scenario, expected_source_chain); - // parse and verify and replay protect VAA the first time, don't replay protect the second time - let vaa = vaa::parse_verify_and_replay_protect(&mut w_state, &mut state, VAA, ctx(&mut test)); - corevaa::destroy(vaa); - let vaa = vaa::parse_and_verify(&mut w_state, &mut state, VAA, ctx(&mut test)); - corevaa::destroy(vaa); - return_shared(state); - return_shared(w_state); - }; - test_scenario::end(test); + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + let token_bridge_state = take_state(scenario); + + // Confirm VAA originated from where we expect. + let verified_vaa = parse_and_verify_vaa(scenario, VAA); + assert!( + wormhole::vaa::emitter_chain(&verified_vaa) == expected_source_chain, + 0 + ); + + // Finally verify. + let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa); + vaa::destroy(msg); + + let verified_vaa = parse_and_verify_vaa(scenario, VAA); + // You shall not pass! + let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa); + + // Clean up. + vaa::destroy(msg); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = wormhole::package_utils::E_NOT_CURRENT_VERSION)] + fun test_cannot_verify_only_once_outdated_version() { + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + // Set up contracts. + let wormhole_fee = 350; + set_up_wormhole_and_token_bridge(scenario, wormhole_fee); + + // Register foreign emitter. + let expected_source_chain = 2; + register_dummy_emitter(scenario, expected_source_chain); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + let token_bridge_state = take_state(scenario); + + // Verify VAA. + let verified_vaa = parse_and_verify_vaa(scenario, VAA); + + // Conveniently roll version back. + state::reverse_migrate_version(&mut token_bridge_state); + + // Simulate executing with an outdated build by upticking the minimum + // required version for `publish_message` to something greater than + // this build. + state::migrate_version_test_only( + &mut token_bridge_state, + token_bridge::version_control::previous_version_test_only(), + token_bridge::version_control::next_version() + ); + + // You shall not pass! + let msg = vaa::verify_only_once(&mut token_bridge_state, verified_vaa); + + // Clean up. + vaa::destroy(msg); + + abort 42 } } diff --git a/sui/token_bridge/sources/version_control.move b/sui/token_bridge/sources/version_control.move new file mode 100644 index 000000000..caee75794 --- /dev/null +++ b/sui/token_bridge/sources/version_control.move @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements dynamic field keys as empty structs. These keys are +/// used to determine the latest version for this build. If the current version +/// is not this build's, then paths through the `state` module will abort. +/// +/// See `token_bridge::state` and `wormhole::package_utils` for more info. +module token_bridge::version_control { + //////////////////////////////////////////////////////////////////////////// + // + // Hard-coded Version Control + // + // Before upgrading, please set the types for `current_version` and + // `previous_version` to match the correct types (current being the latest + // version reflecting this build). + // + //////////////////////////////////////////////////////////////////////////// + + public(friend) fun current_version(): V__0_2_0 { + V__0_2_0 {} + } + + #[test_only] + public fun current_version_test_only(): V__0_2_0 { + current_version() + } + + public(friend) fun previous_version(): V__DUMMY { + V__DUMMY {} + } + + #[test_only] + public fun previous_version_test_only(): V__DUMMY { + previous_version() + } + + //////////////////////////////////////////////////////////////////////////// + // + // Change Log + // + // Please write release notes as doc strings for each version struct. These + // notes will be our attempt at tracking upgrades. Wish us luck. + // + //////////////////////////////////////////////////////////////////////////// + + /// First published package on Sui mainnet. + struct V__0_2_0 has store, drop, copy {} + + // Dummy. + struct V__DUMMY has store, drop, copy {} + + //////////////////////////////////////////////////////////////////////////// + // + // Implementation and Test-Only Methods + // + //////////////////////////////////////////////////////////////////////////// + + friend token_bridge::state; + + #[test_only] + public fun dummy(): V__DUMMY { + V__DUMMY {} + } + + #[test_only] + struct V__MIGRATED has store, drop, copy {} + + #[test_only] + public fun next_version(): V__MIGRATED { + V__MIGRATED {} + } +} diff --git a/sui/token_bridge/sources/wrapped.move b/sui/token_bridge/sources/wrapped.move deleted file mode 100644 index e728393ac..000000000 --- a/sui/token_bridge/sources/wrapped.move +++ /dev/null @@ -1,132 +0,0 @@ -/// This module uses the one-time witness (OTW) -/// Sui one-time witness pattern reference: https://examples.sui.io/basics/one-time-witness.html -module token_bridge::wrapped { - use std::option::{Self}; - - use sui::tx_context::{TxContext}; - use sui::coin::{TreasuryCap}; - use sui::object::{Self, UID}; - use sui::coin::{Self}; - use sui::url::{Url}; - use sui::transfer::{Self}; - - use token_bridge::bridge_state::{Self, BridgeState}; - use token_bridge::asset_meta::{Self, AssetMeta}; - use token_bridge::vaa; - use token_bridge::string32::{Self}; - - use wormhole::state::{Self as state, State as WormholeState}; - use wormhole::myvaa as core_vaa; - - const E_WRAPPING_NATIVE_COIN: u64 = 0; - const E_WRAPPING_REGISTERED_NATIVE_COIN: u64 = 1; - const E_WRAPPED_COIN_ALREADY_INITIALIZED: u64 = 2; - - /// Wrapped assets are created in two steps. - /// 1) The coin is initialised by calling `create_wrapped_coin` in the - /// `init` function of a OTW module. - /// 2) The coin is registered in the token bridge in - /// `register_wrapped_coin`. - /// - /// Since Step 1. takes places in an untrusted context, we want to remove - /// all degrees of freedom. To this end, `create_wrapped_coin` just takes a - /// VAA, and returns a `NewWrappedCoin` object. That's the only way to - /// create a `NewWrappedCoin` object. Then this object can be passed to - /// `register_wrapped_coin` in Step 2. - /// - /// This setup ensures that we don't have to trust (or verify) that the OTW - /// initialiser did the right thing. - /// - /// TODO: it would be nice if we could also enforce that the OTW struct's - /// name matches the token symbol being registered. Currently there's no way - /// to do this in the sui framework. - struct NewWrappedCoin has key, store { - id: UID, - vaa_bytes: vector, - treasury_cap: TreasuryCap, - } - - /// This function will be called from the `init` function of a module that - /// defines a OTW type. Due to the nature of `init` functions, this function - /// must be stateless. - /// This means that it performs no verification of the VAA beyond parsing - /// it. It is the responsbility of `register_wrapped_coin` to perform the - /// validation. - /// This function guarantees that if the VAA is valid, then a new currency - /// `CoinType` will be created such that: - /// 1) the asset metadata matches the VAA - /// 2) the treasury total supply will be 0 - /// - /// Thanks to the above properties, `register_wrapped_coin` does not need to - /// do any checks other than the VAA in `NewWrappedCoin` is valid. - public fun create_wrapped_coin( - vaa_bytes: vector, - coin_witness: CoinType, - ctx: &mut TxContext - ): NewWrappedCoin { - let payload = core_vaa::parse_and_get_payload(vaa_bytes); - let asset_meta: AssetMeta = asset_meta::parse(payload); - - // The amounts in the token bridge payload are truncated to 8 decimals - // in each of the contracts when sending tokens out, so there's no - // precision beyond 10^-8. We could preserve the original number of - // decimals when creating wrapped assets, and "untruncate" the amounts - // on the way out by scaling back appropriately. This is what most other - // chains do, but untruncating from 8 decimals to 18 decimals loses - // log2(10^10) ~ 33 bits of precision, which we cannot afford on Aptos - // (and Solana), as the coin type only has 64bits to begin with. - // Contrast with Ethereum, where amounts are 256 bits. - // So we cap the maximum decimals at 8 when creating a wrapped token. - let max_decimals: u8 = 8; - - let parsed_decimals = asset_meta::get_decimals(&asset_meta); - let symbol = asset_meta::get_symbol(&asset_meta); - let name = asset_meta::get_name(&asset_meta); - - let decimals = if (max_decimals < parsed_decimals) max_decimals else parsed_decimals; - let (treasury_cap, coin_metadata) = coin::create_currency( - coin_witness, - decimals, - string32::to_bytes(&symbol), - string32::to_bytes(&name), - x"", //empty description - option::none(), //empty url - ctx - ); - transfer::share_object(coin_metadata); - NewWrappedCoin { id: object::new(ctx), vaa_bytes, treasury_cap } - } - - public entry fun register_wrapped_coin( - state: &mut WormholeState, - bridge_state: &mut BridgeState, - new_wrapped_coin: NewWrappedCoin, - ctx: &mut TxContext, - ) { - let NewWrappedCoin { id, vaa_bytes, treasury_cap } = new_wrapped_coin; - object::delete(id); - - let vaa = vaa::parse_verify_and_replay_protect( - state, - bridge_state, - vaa_bytes, - ctx - ); - let payload = core_vaa::destroy(vaa); - - let metadata = asset_meta::parse(payload); - let origin_chain = asset_meta::get_token_chain(&metadata); - let external_address = asset_meta::get_token_address(&metadata); - let wrapped_asset_info = - bridge_state::create_wrapped_asset_info( - origin_chain, - external_address, - treasury_cap, - ctx - ); - assert!(origin_chain != state::get_chain_id(state), E_WRAPPING_NATIVE_COIN); - assert!(!bridge_state::is_registered_native_asset(bridge_state), E_WRAPPING_REGISTERED_NATIVE_COIN); - assert!(!bridge_state::is_wrapped_asset(bridge_state), E_WRAPPED_COIN_ALREADY_INITIALIZED); - bridge_state::register_wrapped_asset(bridge_state, wrapped_asset_info); - } -} diff --git a/sui/wormhole/.gitignore b/sui/wormhole/.gitignore new file mode 100644 index 000000000..378eac25d --- /dev/null +++ b/sui/wormhole/.gitignore @@ -0,0 +1 @@ +build diff --git a/sui/wormhole/Makefile b/sui/wormhole/Makefile index 8b1641db8..1f9d45f8a 100644 --- a/sui/wormhole/Makefile +++ b/sui/wormhole/Makefile @@ -1,14 +1,18 @@ -include ../../Makefile.help -.PHONY: artifacts -artifacts: build +VERSION = $(shell grep -Po "version = \"\K[^\"]*" Move.toml | sed "s/\./_/g") -.PHONY: build +.PHONY: clean +clean: + rm -rf build + +.PHONY: check ## Build contract -build: - sui move build +check: + sui move build -d .PHONY: test ## Run tests -test: - sui move test +test: check + grep "public(friend) fun current_version(): V__${VERSION} {" sources/version_control.move + sui move test -d -t 1 diff --git a/sui/wormhole/Move.devnet.toml b/sui/wormhole/Move.devnet.toml new file mode 100644 index 000000000..3aa230215 --- /dev/null +++ b/sui/wormhole/Move.devnet.toml @@ -0,0 +1,11 @@ +[package] +name = "Wormhole" +version = "0.2.0" + +[dependencies.Sui] +git = "https://github.com/MystenLabs/sui.git" +subdir = "crates/sui-framework/packages/sui-framework" +rev = "09b2081498366df936abae26eea4b2d5cafb2788" + +[addresses] +wormhole = "_" diff --git a/sui/wormhole/Move.lock b/sui/wormhole/Move.lock new file mode 100644 index 000000000..a48679e4e --- /dev/null +++ b/sui/wormhole/Move.lock @@ -0,0 +1,20 @@ +# @generated by Move, please check-in and do not edit manually. + +[move] +version = 0 + +dependencies = [ + { name = "Sui" }, +] + +[[move.package]] +name = "MoveStdlib" +source = { git = "https://github.com/MystenLabs/sui.git", rev = "09b2081498366df936abae26eea4b2d5cafb2788", subdir = "crates/sui-framework/packages/move-stdlib" } + +[[move.package]] +name = "Sui" +source = { git = "https://github.com/MystenLabs/sui.git", rev = "09b2081498366df936abae26eea4b2d5cafb2788", subdir = "crates/sui-framework/packages/sui-framework" } + +dependencies = [ + { name = "MoveStdlib" }, +] diff --git a/sui/wormhole/Move.testnet.toml b/sui/wormhole/Move.testnet.toml new file mode 100644 index 000000000..5f06fcf6b --- /dev/null +++ b/sui/wormhole/Move.testnet.toml @@ -0,0 +1,12 @@ +[package] +name = "Wormhole" +version = "0.1.2" +published-at = "0x3542d705ec6a7e05045288ec99a6c4b4e3ded999b6feab720fab535b08fa51f8" + +[dependencies.Sui] +git = "https://github.com/MystenLabs/sui.git" +subdir = "crates/sui-framework/packages/sui-framework" +rev = "09b2081498366df936abae26eea4b2d5cafb2788" + +[addresses] +wormhole = "0x15e1e51cb59fe1f987b037da12745a278855c8ac73050f4f194466096a0ca05b" diff --git a/sui/wormhole/Move.toml b/sui/wormhole/Move.toml index 93da3123e..0465fdaae 100644 --- a/sui/wormhole/Move.toml +++ b/sui/wormhole/Move.toml @@ -1,9 +1,14 @@ [package] name = "Wormhole" -version = "0.0.1" +version = "0.2.0" -[dependencies] -Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework", rev = "2d709054a08d904b9229a2472af679f210af3827" } +[dependencies.Sui] +git = "https://github.com/MystenLabs/sui.git" +subdir = "crates/sui-framework/packages/sui-framework" +rev = "09b2081498366df936abae26eea4b2d5cafb2788" [addresses] -wormhole = "0x0" +wormhole = "_" + +[dev-addresses] +wormhole = "0x100" diff --git a/sui/wormhole/README.md b/sui/wormhole/README.md index 2c8814371..62a62a67b 100644 --- a/sui/wormhole/README.md +++ b/sui/wormhole/README.md @@ -10,7 +10,7 @@ ease deployment to different environments without recompiling the contract). To allow configuring the state with arguments, it's initialised in the `init_and_share_state` function, which also shares the state object. To ensure -this function can only be called once, it consumes a `DeployerCapability` object +this function can only be called once, it consumes a `DeployerCap` object which in turn is created and transferred to the deployer in the `init` function. Since `init_and_share_state` consumes this object, it won't be possible to call it again. diff --git a/sui/wormhole/sources/cursor.move b/sui/wormhole/sources/cursor.move deleted file mode 100644 index 9d3c7232e..000000000 --- a/sui/wormhole/sources/cursor.move +++ /dev/null @@ -1,47 +0,0 @@ -module wormhole::cursor { - use std::vector::{Self}; - - /// A cursor allows consuming a vector incrementally for parsing operations. - /// It has no drop ability, and the only way to deallocate it is by calling the - /// `destroy_empty` method, which will fail if the whole input hasn't been consumed. - /// - /// This setup statically guarantees that the parsing methods consume the - /// full input. - struct Cursor { - data: vector, - } - - /// Initialises a cursor from a vector. - public fun cursor_init(data: vector): Cursor { - // reverse the array so we have access to the first element easily - vector::reverse(&mut data); - Cursor { - data, - } - } - - /// Destroys an empty cursor. - /// Aborts if the cursor is not empty. - public fun destroy_empty(cur: Cursor) { - let Cursor { data } = cur; - vector::destroy_empty(data); - } - - /// Consumes the rest of the cursor (thus destroying it) and returns the - /// remaining bytes. - /// NOTE: Only use this function if you intend to consume the rest of the - /// bytes. Since the result is a vector, which can be dropped, it is not - /// possible to statically guarantee that the rest will be used. - public fun rest(cur: Cursor): vector { - let Cursor { data } = cur; - // re-reverse the data so it is in the same order as the original input - vector::reverse(&mut data); - data - } - - /// Returns the first element of the cursor and advances it. - public fun poke(cur: &mut Cursor): T { - vector::pop_back(&mut cur.data) - } - -} \ No newline at end of file diff --git a/sui/wormhole/sources/datatypes/bytes20.move b/sui/wormhole/sources/datatypes/bytes20.move new file mode 100644 index 000000000..3c097dec6 --- /dev/null +++ b/sui/wormhole/sources/datatypes/bytes20.move @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements a custom type representing a fixed-size array of +/// length 20. +module wormhole::bytes20 { + use std::vector::{Self}; + + use wormhole::bytes::{Self}; + use wormhole::cursor::{Cursor}; + + /// Invalid vector length to create `Bytes20`. + const E_INVALID_BYTES20: u64 = 0; + /// Found non-zero bytes when attempting to trim `vector`. + const E_CANNOT_TRIM_NONZERO: u64 = 1; + + /// 20. + const LEN: u64 = 20; + + /// Container for `vector`, which has length == 20. + struct Bytes20 has copy, drop, store { + data: vector + } + + public fun length(): u64 { + LEN + } + + /// Create new `Bytes20`, which checks the length of input `data`. + public fun new(data: vector): Bytes20 { + assert!(is_valid(&data), E_INVALID_BYTES20); + Bytes20 { data } + } + + /// Create new `Bytes20` of all zeros. + public fun default(): Bytes20 { + let data = vector::empty(); + let i = 0; + while (i < LEN) { + vector::push_back(&mut data, 0); + i = i + 1; + }; + new(data) + } + + /// Retrieve underlying `data`. + public fun data(self: &Bytes20): vector { + self.data + } + + /// Either trim or pad (depending on length of the input `vector`) to 20 + /// bytes. + public fun from_bytes(buf: vector): Bytes20 { + let len = vector::length(&buf); + if (len > LEN) { + trim_nonzero_left(&mut buf); + new(buf) + } else { + new(pad_left(&buf, false)) + } + } + + /// Destroy `Bytes20` for its underlying data. + public fun to_bytes(value: Bytes20): vector { + let Bytes20 { data } = value; + data + } + + /// Drain 20 elements of `Cursor` to create `Bytes20`. + public fun take(cur: &mut Cursor): Bytes20 { + new(bytes::take_bytes(cur, LEN)) + } + + /// Validate that any of the bytes in underlying data is non-zero. + public fun is_nonzero(self: &Bytes20): bool { + let i = 0; + while (i < LEN) { + if (*vector::borrow(&self.data, i) > 0) { + return true + }; + i = i + 1; + }; + + false + } + + /// Check that the input data is correct length. + fun is_valid(data: &vector): bool { + vector::length(data) == LEN + } + + /// For vector size less than 20, add zeros to the left. + fun pad_left(data: &vector, data_reversed: bool): vector { + let out = vector::empty(); + let len = vector::length(data); + let i = len; + while (i < LEN) { + vector::push_back(&mut out, 0); + i = i + 1; + }; + if (data_reversed) { + let i = 0; + while (i < len) { + vector::push_back( + &mut out, + *vector::borrow(data, len - i - 1) + ); + i = i + 1; + }; + } else { + vector::append(&mut out, *data); + }; + + out + } + + /// Trim bytes from the left if they are zero. If any of these bytes + /// are non-zero, abort. + fun trim_nonzero_left(data: &mut vector) { + vector::reverse(data); + let (i, n) = (0, vector::length(data) - LEN); + while (i < n) { + assert!(vector::pop_back(data) == 0, E_CANNOT_TRIM_NONZERO); + i = i + 1; + }; + vector::reverse(data); + } +} + +#[test_only] +module wormhole::bytes20_tests { + use std::vector::{Self}; + + use wormhole::bytes20::{Self}; + + #[test] + public fun new() { + let data = x"deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; + assert!(vector::length(&data) == 20, 0); + let actual = bytes20::new(data); + + assert!(bytes20::data(&actual) == data, 0); + } + + #[test] + public fun default() { + let actual = bytes20::default(); + let expected = x"0000000000000000000000000000000000000000"; + assert!(bytes20::data(&actual) == expected, 0); + } + + #[test] + public fun from_bytes() { + let actual = bytes20::from_bytes(x"deadbeef"); + let expected = x"00000000000000000000000000000000deadbeef"; + assert!(bytes20::data(&actual) == expected, 0); + } + + #[test] + public fun is_nonzero() { + let data = x"deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; + let actual = bytes20::new(data); + assert!(bytes20::is_nonzero(&actual), 0); + + let zeros = bytes20::default(); + assert!(!bytes20::is_nonzero(&zeros), 0); + } + + #[test] + #[expected_failure(abort_code = bytes20::E_INVALID_BYTES20)] + public fun cannot_new_non_20_byte_vector() { + let data = + x"deadbeefdeadbeefdeadbeefdeadbeefdeadbe"; + assert!(vector::length(&data) != 20, 0); + bytes20::new(data); + } +} diff --git a/sui/wormhole/sources/datatypes/bytes32.move b/sui/wormhole/sources/datatypes/bytes32.move new file mode 100644 index 000000000..ab713f6f2 --- /dev/null +++ b/sui/wormhole/sources/datatypes/bytes32.move @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements a custom type representing a fixed-size array of +/// length 32. +module wormhole::bytes32 { + use std::option::{Self}; + use std::string::{Self, String}; + use std::vector::{Self}; + use sui::bcs::{Self}; + + use wormhole::bytes::{Self}; + use wormhole::cursor::{Self, Cursor}; + + /// Invalid vector length to create `Bytes32`. + const E_INVALID_BYTES32: u64 = 0; + /// Found non-zero bytes when attempting to trim `vector`. + const E_CANNOT_TRIM_NONZERO: u64 = 1; + /// Value of deserialized 32-byte array data overflows u64 max. + const E_U64_OVERFLOW: u64 = 2; + + /// 32. + const LEN: u64 = 32; + + /// Container for `vector`, which has length == 32. + struct Bytes32 has copy, drop, store { + data: vector, + } + + public fun length(): u64 { + LEN + } + + /// Create new `Bytes32`, which checks the length of input `data`. + public fun new(data: vector): Bytes32 { + assert!(is_valid(&data), E_INVALID_BYTES32); + Bytes32 { data } + } + + /// Create new `Bytes20` of all zeros. + public fun default(): Bytes32 { + let data = vector::empty(); + let i = 0; + while (i < LEN) { + vector::push_back(&mut data, 0); + i = i + 1; + }; + new(data) + } + + /// Retrieve underlying `data`. + public fun data(self: &Bytes32): vector { + self.data + } + + /// Serialize `u256` as big-endian format in zero-padded `Bytes32`. + public fun from_u256_be(value: u256): Bytes32 { + let buf = bcs::to_bytes(&value); + vector::reverse(&mut buf); + new(buf) + } + + /// Deserialize from big-endian `u256`. + public fun to_u256_be(value: Bytes32): u256 { + let cur = cursor::new(to_bytes(value)); + let out = bytes::take_u256_be(&mut cur); + cursor::destroy_empty(cur); + + out + } + + /// Serialize `u64` as big-endian format in zero-padded `Bytes32`. + public fun from_u64_be(value: u64): Bytes32 { + from_u256_be((value as u256)) + } + + /// Deserialize from big-endian `u64` as long as the data does not + /// overflow. + public fun to_u64_be(value: Bytes32): u64 { + let num = to_u256_be(value); + assert!(num < (1u256 << 64), E_U64_OVERFLOW); + (num as u64) + } + + /// Either trim or pad (depending on length of the input `vector`) to 32 + /// bytes. + public fun from_bytes(buf: vector): Bytes32 { + let len = vector::length(&buf); + if (len > LEN) { + trim_nonzero_left(&mut buf); + new(buf) + } else { + new(pad_left(&buf, false)) + } + } + + /// Destroy `Bytes32` for its underlying data. + public fun to_bytes(value: Bytes32): vector { + let Bytes32 { data } = value; + data + } + + /// Drain 32 elements of `Cursor` to create `Bytes32`. + public fun take_bytes(cur: &mut Cursor): Bytes32 { + new(bytes::take_bytes(cur, LEN)) + } + + /// Destroy `Bytes32` to represent its underlying data as `address`. + public fun to_address(value: Bytes32): address { + sui::address::from_bytes(to_bytes(value)) + } + + /// Create `Bytes32` from `address`. + public fun from_address(addr: address): Bytes32 { + new(sui::address::to_bytes(addr)) + } + + public fun from_utf8(str: String): Bytes32 { + let data = *string::bytes(&str); + let len = vector::length(&data); + if (len > LEN) { + // Trim from end. + let i = len; + while (i > LEN) { + vector::pop_back(&mut data); + i = i - 1; + } + } else { + // Pad right to `LEN`. + let i = len; + while (i < LEN) { + vector::push_back(&mut data, 0); + i = i + 1; + } + }; + + new(data) + } + + /// Even if the input is valid utf8, the result might be shorter than 32 + /// bytes, because the original string might have a multi-byte utf8 + /// character at the 32 byte boundary, which, when split, results in an + /// invalid code point, so we remove it. + public fun to_utf8(value: Bytes32): String { + let data = to_bytes(value); + + let utf8 = string::try_utf8(data); + while (option::is_none(&utf8)) { + vector::pop_back(&mut data); + utf8 = string::try_utf8(data); + }; + + let buf = *string::bytes(&option::extract(&mut utf8)); + + // Now trim zeros from the right. + while ( + *vector::borrow(&buf, vector::length(&buf) - 1) == 0 + ) { + vector::pop_back(&mut buf); + }; + + string::utf8(buf) + } + + /// Validate that any of the bytes in underlying data is non-zero. + public fun is_nonzero(self: &Bytes32): bool { + let i = 0; + while (i < LEN) { + if (*vector::borrow(&self.data, i) > 0) { + return true + }; + i = i + 1; + }; + + false + } + + /// Check that the input data is correct length. + fun is_valid(data: &vector): bool { + vector::length(data) == LEN + } + + /// For vector size less than 32, add zeros to the left. + fun pad_left(data: &vector, data_reversed: bool): vector { + let out = vector::empty(); + let len = vector::length(data); + let i = len; + while (i < LEN) { + vector::push_back(&mut out, 0); + i = i + 1; + }; + if (data_reversed) { + let i = 0; + while (i < len) { + vector::push_back( + &mut out, + *vector::borrow(data, len - i - 1) + ); + i = i + 1; + }; + } else { + vector::append(&mut out, *data); + }; + + out + } + + /// Trim bytes from the left if they are zero. If any of these bytes + /// are non-zero, abort. + fun trim_nonzero_left(data: &mut vector) { + vector::reverse(data); + let (i, n) = (0, vector::length(data) - LEN); + while (i < n) { + assert!(vector::pop_back(data) == 0, E_CANNOT_TRIM_NONZERO); + i = i + 1; + }; + vector::reverse(data); + } +} + +#[test_only] +module wormhole::bytes32_tests { + use std::vector::{Self}; + + use wormhole::bytes32::{Self}; + + #[test] + public fun new() { + let data = + x"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; + assert!(vector::length(&data) == 32, 0); + let actual = bytes32::new(data); + + assert!(bytes32::data(&actual) == data, 0); + } + + #[test] + public fun default() { + let actual = bytes32::default(); + let expected = + x"0000000000000000000000000000000000000000000000000000000000000000"; + assert!(bytes32::data(&actual) == expected, 0); + } + + #[test] + public fun from_u256_be() { + let actual = bytes32::from_u256_be(1 << 32); + let expected = + x"0000000000000000000000000000000000000000000000000000000100000000"; + assert!(bytes32::data(&actual) == expected, 0); + } + + #[test] + public fun to_u256_be() { + let actual = bytes32::new( + x"0000000000000000000000000000000000000000000000000000000100000000" + ); + assert!(bytes32::to_u256_be(actual) == (1 << 32), 0); + } + + #[test] + public fun from_bytes() { + let actual = bytes32::from_bytes(x"deadbeef"); + let expected = + x"00000000000000000000000000000000000000000000000000000000deadbeef"; + assert!(bytes32::data(&actual) == expected, 0); + } + + #[test] + public fun is_nonzero() { + let data = + x"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; + let actual = bytes32::new(data); + assert!(bytes32::is_nonzero(&actual), 0); + + let zeros = bytes32::default(); + assert!(!bytes32::is_nonzero(&zeros), 0); + } + + #[test] + #[expected_failure(abort_code = bytes32::E_INVALID_BYTES32)] + public fun cannot_new_non_32_byte_vector() { + let data = + x"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbe"; + assert!(vector::length(&data) != 32, 0); + bytes32::new(data); + } +} diff --git a/sui/wormhole/sources/datatypes/external_address.move b/sui/wormhole/sources/datatypes/external_address.move new file mode 100644 index 000000000..b2134caf3 --- /dev/null +++ b/sui/wormhole/sources/datatypes/external_address.move @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements a custom type for a 32-byte standardized address, +/// which is meant to represent an address from any other network. +module wormhole::external_address { + use sui::object::{Self, ID}; + use wormhole::bytes32::{Self, Bytes32}; + use wormhole::cursor::{Cursor}; + + /// Underlying data is all zeros. + const E_ZERO_ADDRESS: u64 = 0; + + /// Container for `Bytes32`. + struct ExternalAddress has copy, drop, store { + value: Bytes32, + } + + /// Create `ExternalAddress`. + public fun new(value: Bytes32): ExternalAddress { + ExternalAddress { value } + } + + /// Create `ExternalAddress` of all zeros.` + public fun default(): ExternalAddress { + new(bytes32::default()) + } + + /// Create `ExternalAddress` ensuring that not all bytes are zero. + public fun new_nonzero(value: Bytes32): ExternalAddress { + assert!(bytes32::is_nonzero(&value), E_ZERO_ADDRESS); + new(value) + } + + /// Destroy `ExternalAddress` for underlying bytes as `vector`. + public fun to_bytes(ext: ExternalAddress): vector { + bytes32::to_bytes(to_bytes32(ext)) + } + + /// Destroy 'ExternalAddress` for underlying data. + public fun to_bytes32(ext: ExternalAddress): Bytes32 { + let ExternalAddress { value } = ext; + value + } + + /// Drain 32 elements of `Cursor` to create `ExternalAddress`. + public fun take_bytes(cur: &mut Cursor): ExternalAddress { + new(bytes32::take_bytes(cur)) + } + + /// Drain 32 elements of `Cursor` to create `ExternalAddress` ensuring + /// that not all bytes are zero. + public fun take_nonzero(cur: &mut Cursor): ExternalAddress { + new_nonzero(bytes32::take_bytes(cur)) + } + + /// Destroy `ExternalAddress` to represent its underlying data as `address`. + public fun to_address(ext: ExternalAddress): address { + sui::address::from_bytes(to_bytes(ext)) + } + + /// Create `ExternalAddress` from `address`. + public fun from_address(addr: address): ExternalAddress { + new(bytes32::from_address(addr)) + } + + /// Create `ExternalAddress` from `ID`. + public fun from_id(id: ID): ExternalAddress { + new(bytes32::from_bytes(object::id_to_bytes(&id))) + } + + /// Check whether underlying data is not all zeros. + public fun is_nonzero(self: &ExternalAddress): bool { + bytes32::is_nonzero(&self.value) + } +} + +#[test_only] +module wormhole::external_address_tests { + use wormhole::bytes32::{Self}; + use wormhole::external_address::{Self}; + + #[test] + public fun test_bytes() { + let data = + bytes32::new( + x"1234567891234567891234567891234512345678912345678912345678912345" + ); + let addr = external_address::new(data); + assert!(external_address::to_bytes(addr) == bytes32::to_bytes(data), 0); + } + + #[test] + public fun test_address() { + let data = + bytes32::new( + x"0000000000000000000000000000000000000000000000000000000000001234" + ); + let addr = external_address::new(data); + assert!(external_address::to_address(addr) == @0x1234, 0); + assert!(addr == external_address::from_address(@0x1234), 0); + } +} diff --git a/sui/wormhole/sources/datatypes/guardian_signature.move b/sui/wormhole/sources/datatypes/guardian_signature.move new file mode 100644 index 000000000..58698d51a --- /dev/null +++ b/sui/wormhole/sources/datatypes/guardian_signature.move @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements a custom type representing a Guardian's signature +/// with recovery ID of a particular hashed VAA message body. The components of +/// `GuardianSignature` are used to perform public key recovery using ECDSA. +module wormhole::guardian_signature { + use std::vector::{Self}; + + use wormhole::bytes32::{Self, Bytes32}; + + /// Container for elliptic curve signature parameters and Guardian index. + struct GuardianSignature has store, drop { + r: Bytes32, + s: Bytes32, + recovery_id: u8, + index: u8, + } + + /// Create new `GuardianSignature`. + public fun new( + r: Bytes32, + s: Bytes32, + recovery_id: u8, + index: u8 + ): GuardianSignature { + GuardianSignature { r, s, recovery_id, index } + } + + /// 32-byte signature parameter R. + public fun r(self: &GuardianSignature): Bytes32 { + self.r + } + + /// 32-byte signature parameter S. + public fun s(self: &GuardianSignature): Bytes32 { + self.s + } + + /// Signature recovery ID. + public fun recovery_id(self: &GuardianSignature): u8 { + self.recovery_id + } + + /// Guardian index. + public fun index(self: &GuardianSignature): u8 { + self.index + } + + /// Guardian index as u64. + public fun index_as_u64(self: &GuardianSignature): u64 { + (self.index as u64) + } + + /// Serialize elliptic curve paramters as `vector` of length == 65 to be + /// consumed by `ecdsa_k1` for public key recovery. + public fun to_rsv(gs: GuardianSignature): vector { + let GuardianSignature { r, s, recovery_id, index: _ } = gs; + let out = vector::empty(); + vector::append(&mut out, bytes32::to_bytes(r)); + vector::append(&mut out, bytes32::to_bytes(s)); + vector::push_back(&mut out, recovery_id); + out + } +} diff --git a/sui/wormhole/sources/deserialize.move b/sui/wormhole/sources/deserialize.move deleted file mode 100644 index c5bcac432..000000000 --- a/sui/wormhole/sources/deserialize.move +++ /dev/null @@ -1,138 +0,0 @@ -module wormhole::deserialize { - use std::vector::{Self}; - use wormhole::cursor::{Self, Cursor}; - use wormhole::myu16::{Self as u16, U16}; - use wormhole::myu32::{Self as u32, U32}; - use wormhole::myu256::{Self as u256, U256}; - - public fun deserialize_u8(cur: &mut Cursor): u8 { - cursor::poke(cur) - } - - public fun deserialize_u16(cur: &mut Cursor): U16 { - let res: u64 = 0; - let i = 0; - while (i < 2) { - let b = cursor::poke(cur); - res = (res << 8) + (b as u64); - i = i + 1; - }; - u16::from_u64(res) - } - - public fun deserialize_u32(cur: &mut Cursor): U32 { - let res: u64 = 0; - let i = 0; - while (i < 4) { - let b = cursor::poke(cur); - res = (res << 8) + (b as u64); - i = i + 1; - }; - u32::from_u64(res) - } - - public fun deserialize_u64(cur: &mut Cursor): u64 { - let res: u64 = 0; - let i = 0; - while (i < 8) { - let b = cursor::poke(cur); - res = (res << 8) + (b as u64); - i = i + 1; - }; - res - } - - public fun deserialize_u128(cur: &mut Cursor): u128 { - let res: u128 = 0; - let i = 0; - while (i < 16) { - let b = cursor::poke(cur); - res = (res << 8) + (b as u128); - i = i + 1; - }; - res - } - - public fun deserialize_u256(cur: &mut Cursor): U256 { - let v0 = deserialize_u128(cur); - let v1 = deserialize_u128(cur); - u256::add(u256::shl(u256::from_u128(v0), 128), u256::from_u128(v1)) - } - - public fun deserialize_vector(cur: &mut Cursor, len: u64): vector { - let result = vector::empty(); - while (len > 0) { - vector::push_back(&mut result, cursor::poke(cur)); - len = len - 1; - }; - result - } - -} - -#[test_only] -module wormhole::deserialize_test { - use wormhole::cursor; - use wormhole::myu16::{Self as u16}; - use wormhole::myu32::{Self as u32}; - use wormhole::deserialize::{ - deserialize_u8, - deserialize_u16, - deserialize_u32, - deserialize_u64, - deserialize_u128, - deserialize_vector, - }; - - #[test] - fun test_deserialize_u8() { - let cur = cursor::cursor_init(x"99"); - let byte = deserialize_u8(&mut cur); - assert!(byte==0x99, 0); - cursor::destroy_empty(cur); - } - - #[test] - fun test_deserialize_u16() { - let cur = cursor::cursor_init(x"9987"); - let u = deserialize_u16(&mut cur); - assert!(u == u16::from_u64(0x9987), 0); - cursor::destroy_empty(cur); - } - - #[test] - fun test_deserialize_u32() { - let cur = cursor::cursor_init(x"99876543"); - let u = deserialize_u32(&mut cur); - assert!(u == u32::from_u64(0x99876543), 0); - cursor::destroy_empty(cur); - } - - #[test] - fun test_deserialize_u64() { - let cur = cursor::cursor_init(x"1300000025000001"); - let u = deserialize_u64(&mut cur); - assert!(u==0x1300000025000001, 0); - cursor::destroy_empty(cur); - } - - #[test] - fun test_deserialize_u128() { - let cur = cursor::cursor_init(x"130209AB2500FA0113CD00AE25000001"); - let u = deserialize_u128(&mut cur); - assert!(u==0x130209AB2500FA0113CD00AE25000001, 0); - cursor::destroy_empty(cur); - } - - #[test] - fun test_deserialize_vector() { - let cur = cursor::cursor_init(b"hello world"); - let hello = deserialize_vector(&mut cur, 5); - deserialize_u8(&mut cur); - let world = deserialize_vector(&mut cur, 5); - assert!(hello == b"hello", 0); - assert!(world == b"world", 0); - cursor::destroy_empty(cur); - } - -} \ No newline at end of file diff --git a/sui/wormhole/sources/emitter.move b/sui/wormhole/sources/emitter.move index 6f3f7855a..5c5f2dbe7 100644 --- a/sui/wormhole/sources/emitter.move +++ b/sui/wormhole/sources/emitter.move @@ -1,99 +1,183 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements a capability (`EmitterCap`), which allows one to send +/// Wormhole messages. Its external address is determined by the capability's +/// `id`, which is a 32-byte vector. module wormhole::emitter { - use sui::object::{Self, UID}; + use sui::object::{Self, ID, UID}; use sui::tx_context::{TxContext}; - use wormhole::serialize; - use wormhole::external_address::{Self, ExternalAddress}; + use wormhole::state::{Self, State}; - friend wormhole::state; - friend wormhole::wormhole; + friend wormhole::publish_message; - #[test_only] - friend wormhole::emitter_test; - - struct EmitterRegistry has store { - next_id: u64 + /// Event reflecting when `new` is called. + struct EmitterCreated has drop, copy { + emitter_cap: ID } - // TODO(csongor): document that this has to be globally unique. - // The friend modifier is very important here. - public(friend) fun init_emitter_registry(): EmitterRegistry { - EmitterRegistry { next_id: 1 } + /// Event reflecting when `destroy` is called. + struct EmitterDestroyed has drop, copy { + emitter_cap: ID } - #[test_only] - public fun destroy_emitter_registry(registry: EmitterRegistry) { - let EmitterRegistry { next_id: _ } = registry; - } - - public(friend) fun new_emitter( - registry: &mut EmitterRegistry, - ctx: &mut TxContext - ): EmitterCapability { - let emitter = registry.next_id; - registry.next_id = emitter + 1; - EmitterCapability { - id: object::new(ctx), - emitter: emitter, - sequence: 0 - } - } - - struct EmitterCapability has key, store { + /// `EmitterCap` is a Sui object that gives a user or smart contract the + /// capability to send Wormhole messages. For every Wormhole message + /// emitted, a unique `sequence` is used. + struct EmitterCap has key, store { id: UID, - /// Unique identifier of the emitter - emitter: u64, - /// Sequence number of the next wormhole message + + /// Sequence number of the next wormhole message. sequence: u64 } - /// Destroys an emitter capability. + /// Generate a new `EmitterCap`. + public fun new(wormhole_state: &State, ctx: &mut TxContext): EmitterCap { + state::assert_latest_only(wormhole_state); + + let cap = + EmitterCap { + id: object::new(ctx), + sequence: 0 + }; + + sui::event::emit( + EmitterCreated { emitter_cap: object::id(&cap)} + ); + + cap + } + + /// Returns current sequence (which will be used in the next Wormhole + /// message emitted). + public fun sequence(self: &EmitterCap): u64 { + self.sequence + } + + /// Once a Wormhole message is emitted, an `EmitterCap` upticks its + /// internal `sequence` for the next message. + public(friend) fun use_sequence(self: &mut EmitterCap): u64 { + let sequence = self.sequence; + self.sequence = sequence + 1; + sequence + } + + /// Destroys an `EmitterCap`. /// /// Note that this operation removes the ability to send messages using the /// emitter id, and is irreversible. - public fun destroy_emitter_cap(emitter_cap: EmitterCapability) { - let EmitterCapability {id: id, emitter: _, sequence: _ } = emitter_cap; + public fun destroy(wormhole_state: &State, cap: EmitterCap) { + state::assert_latest_only(wormhole_state); + + sui::event::emit( + EmitterDestroyed { emitter_cap: object::id(&cap) } + ); + + let EmitterCap { id, sequence: _ } = cap; object::delete(id); } - public fun get_emitter(emitter_cap: &EmitterCapability): u64 { - emitter_cap.emitter + #[test_only] + public fun destroy_test_only(cap: EmitterCap) { + let EmitterCap { id, sequence: _ } = cap; + object::delete(id); } - /// Returns the external address of the emitter. - /// - /// The 16 byte (u128) emitter id left-padded to u256 - public fun get_external_address(emitter_cap: &EmitterCapability): ExternalAddress { - let emitter_bytes = vector[]; - serialize::serialize_u64(&mut emitter_bytes, emitter_cap.emitter); - external_address::from_bytes(emitter_bytes) - } - - public(friend) fun use_sequence(emitter_cap: &mut EmitterCapability): u64 { - let sequence = emitter_cap.sequence; - emitter_cap.sequence = sequence + 1; - sequence + #[test_only] + public fun dummy(): EmitterCap { + EmitterCap { + id: object::new(&mut sui::tx_context::dummy()), + sequence: 0 + } } } #[test_only] -module wormhole::emitter_test { - use wormhole::emitter; - use sui::tx_context; +module wormhole::emitter_tests { + use sui::object::{Self}; + use sui::test_scenario::{Self}; + + use wormhole::emitter::{Self}; + use wormhole::state::{Self}; + use wormhole::version_control::{Self}; + use wormhole::wormhole_scenario::{ + person, + return_state, + set_up_wormhole, + take_state + }; #[test] - public fun test_increasing_emitters() { - let ctx = tx_context::dummy(); + fun test_emitter() { + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; - let registry = emitter::init_emitter_registry(); - let emitter1 = emitter::new_emitter(&mut registry, &mut ctx); - let emitter2 = emitter::new_emitter(&mut registry, &mut ctx); + let wormhole_fee = 350; + set_up_wormhole(scenario, wormhole_fee); - assert!(emitter::get_emitter(&emitter1) == 1, 0); - assert!(emitter::get_emitter(&emitter2) == 2, 0); + // Ignore effects. + test_scenario::next_tx(scenario, caller); - emitter::destroy_emitter_cap(emitter1); - emitter::destroy_emitter_cap(emitter2); - emitter::destroy_emitter_registry(registry); + let worm_state = take_state(scenario); + + let dummy_cap = emitter::dummy(); + let expected = + @0x381dd9078c322a4663c392761a0211b527c127b29583851217f948d62131f409; + assert!(object::id_to_address(&object::id(&dummy_cap)) == expected, 0); + + // Generate new emitter. + let cap = emitter::new(&worm_state, test_scenario::ctx(scenario)); + + // And check emitter cap's address. + let expected = + @0x75c3360eb19fd2c20fbba5e2da8cf1a39cdb1ee913af3802ba330b852e459e05; + assert!(object::id_to_address(&object::id(&cap)) == expected, 0); + + // Clean up. + emitter::destroy(&worm_state, dummy_cap); + emitter::destroy(&worm_state, cap); + return_state(worm_state); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + #[expected_failure(abort_code = wormhole::package_utils::E_NOT_CURRENT_VERSION)] + fun test_cannot_new_emitter_outdated_version() { + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let wormhole_fee = 350; + set_up_wormhole(scenario, wormhole_fee); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + + // Conveniently roll version back. + state::reverse_migrate_version(&mut worm_state); + + // Simulate executing with an outdated build by upticking the minimum + // required version for `publish_message` to something greater than + // this build. + state::migrate_version_test_only( + &mut worm_state, + version_control::previous_version_test_only(), + version_control::next_version() + ); + + // You shall not pass! + let cap = emitter::new(&worm_state, test_scenario::ctx(scenario)); + + // Clean up. + emitter::destroy(&worm_state, cap); + + abort 42 } } diff --git a/sui/wormhole/sources/external_address.move b/sui/wormhole/sources/external_address.move deleted file mode 100644 index b48716d3c..000000000 --- a/sui/wormhole/sources/external_address.move +++ /dev/null @@ -1,166 +0,0 @@ -/// 32 byte, left-padded address representing an arbitrary address, to be used in VAAs to -/// refer to addresses. -module wormhole::external_address { - use std::vector; - - use sui::address; - - use wormhole::cursor::Cursor; - use wormhole::deserialize; - use wormhole::serialize; - - const E_VECTOR_TOO_LONG: u64 = 0; - const E_INVALID_EXTERNAL_ADDRESS: u64 = 1; - - struct ExternalAddress has drop, copy, store { - external_address: vector, - } - - public fun get_bytes(e: &ExternalAddress): vector { - e.external_address - } - - public fun pad_left_32(input: &vector): vector{ - let len = vector::length(input); - assert!(len <= 32, E_VECTOR_TOO_LONG); - let ret = vector::empty(); - let zeros_remaining = 32 - len; - while (zeros_remaining > 0){ - vector::push_back(&mut ret, 0); - zeros_remaining = zeros_remaining - 1; - }; - vector::append(&mut ret, *input); - ret - } - - public fun left_pad(s: &vector): ExternalAddress { - let padded_vector = pad_left_32(s); - ExternalAddress { external_address: padded_vector} - } - - public fun from_bytes(bytes: vector): ExternalAddress { - left_pad(&bytes) - } - - public fun deserialize(cur: &mut Cursor): ExternalAddress { - let bytes = deserialize::deserialize_vector(cur, 32); - from_bytes(bytes) - } - - public fun serialize(buf: &mut vector, e: ExternalAddress) { - serialize::serialize_vector(buf, e.external_address) - } - - /// Convert an `ExternalAddress` to a native Sui address. - /// - /// Sui addresses are 20 bytes, while external addresses are represented as - /// 32 bytes, left-padded with 0s. This function thus takes the last 20 - /// bytes of an external address, and reverts if the first 12 bytes contain - /// non-0 bytes. - public fun to_address(e: &ExternalAddress): address { - let vec = e.external_address; - // we reverse the vector and drop the last 12 bytes - vector::reverse(&mut vec); - let bytes_to_drop = 12; - while (bytes_to_drop > 0) { - let last_byte = vector::pop_back(&mut vec); - // ensure no junk in the first 12 bytes - assert!(last_byte == 0, E_INVALID_EXTERNAL_ADDRESS); - bytes_to_drop = bytes_to_drop - 1; - }; - // reverse back to original order - vector::reverse(&mut vec); - address::from_bytes(vec) - } - -} - -#[test_only] -module wormhole::external_address_test { - use wormhole::external_address; - use std::vector::{Self}; - - // test get_bytes and left_pad - #[test] - public fun test_left_pad() { - let v = x"123456789123456789123456789123451234567891234567891234"; // less than 32 bytes - let res = external_address::left_pad(&v); - let bytes = external_address::get_bytes(&res); - let m = x"0000000000"; - vector::append(&mut m, v); - assert!(bytes == m, 0); - } - - #[test] - public fun test_left_pad_length_32_vector() { - let v = x"1234567891234567891234567891234512345678912345678912345678912345"; //32 bytes - let res = external_address::left_pad(&v); - let bytes = external_address::get_bytes(&res); - assert!(bytes == v, 0); - } - - #[test] - #[expected_failure(abort_code = 0, location=wormhole::external_address)] - public fun test_left_pad_vector_too_long() { - let v = x"123456789123456789123456789123451234567891234567891234567891234500"; //33 bytes - let res = external_address::left_pad(&v); - let bytes = external_address::get_bytes(&res); - assert!(bytes == v, 0); - } - - #[test] - public fun test_from_bytes() { - let v = x"1234"; - let ea = external_address::from_bytes(v); - let bytes = external_address::get_bytes(&ea); - let w = x"000000000000000000000000000000000000000000000000000000000000"; - vector::append(&mut w, v); - assert!(bytes == w, 0); - } - - #[test] - #[expected_failure(abort_code = 0, location=wormhole::external_address)] - public fun test_from_bytes_over_32_bytes() { - let v = x"00000000000000000000000000000000000000000000000000000000000000001234"; - let ea = external_address::from_bytes(v); - let _bytes = external_address::get_bytes(&ea); - } - - #[test] - fun test_pad_left_short() { - let v = x"11"; - let pad_left_v = external_address::pad_left_32(&v); - assert!(pad_left_v == x"0000000000000000000000000000000000000000000000000000000000000011", 0); - } - - #[test] - fun test_pad_left_exact() { - let v = x"5555555555555555555555555555555555555555555555555555555555555555"; - let pad_left_v = external_address::pad_left_32(&v); - assert!(pad_left_v == x"5555555555555555555555555555555555555555555555555555555555555555", 0); - } - - #[test] - #[expected_failure(abort_code = 0, location=wormhole::external_address)] - fun test_pad_left_long() { - let v = x"665555555555555555555555555555555555555555555555555555555555555555"; - external_address::pad_left_32(&v); - } - - #[test] - #[expected_failure(abort_code = 1, location=wormhole::external_address)] - public fun test_to_address_too_long() { - // non-0 bytes in first 12 bytes - let v = x"0000010000000000000000000000000000000000000000000000000000001234"; - let res = external_address::from_bytes(v); - let _address = external_address::to_address(&res); - } - - #[test] - public fun test_to_address() { - let v = x"0000000000000000000000000000000000000000000000000000000000001234"; - let res = external_address::from_bytes(v); - let address = external_address::to_address(&res); - assert!(address == @0x1234, 0); - } -} diff --git a/sui/wormhole/sources/governance/set_fee.move b/sui/wormhole/sources/governance/set_fee.move new file mode 100644 index 000000000..ab3431b7c --- /dev/null +++ b/sui/wormhole/sources/governance/set_fee.move @@ -0,0 +1,343 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements handling a governance VAA to enact setting the +/// Wormhole message fee to another amount. +module wormhole::set_fee { + use wormhole::bytes32::{Self}; + use wormhole::cursor::{Self}; + use wormhole::governance_message::{Self, DecreeTicket, DecreeReceipt}; + use wormhole::state::{Self, State}; + + /// Specific governance payload ID (action) for setting Wormhole fee. + const ACTION_SET_FEE: u8 = 3; + + struct GovernanceWitness has drop {} + + struct SetFee { + amount: u64 + } + + public fun authorize_governance( + wormhole_state: &State + ): DecreeTicket { + governance_message::authorize_verify_local( + GovernanceWitness {}, + state::governance_chain(wormhole_state), + state::governance_contract(wormhole_state), + state::governance_module(), + ACTION_SET_FEE + ) + } + + /// Redeem governance VAA to configure Wormhole message fee amount in SUI + /// denomination. This governance message is only relevant for Sui because + /// fee administration is only relevant to one particular network (in this + /// case Sui). + /// + /// NOTE: This method is guarded by a minimum build version check. This + /// method could break backward compatibility on an upgrade. + public fun set_fee( + wormhole_state: &mut State, + receipt: DecreeReceipt + ): u64 { + // This capability ensures that the current build version is used. + let latest_only = state::assert_latest_only(wormhole_state); + + let payload = + governance_message::take_payload( + state::borrow_mut_consumed_vaas(&latest_only, wormhole_state), + receipt + ); + + // Deserialize the payload as amount to change the Wormhole fee. + let SetFee { amount } = deserialize(payload); + + state::set_message_fee(&latest_only, wormhole_state, amount); + + amount + } + + fun deserialize(payload: vector): SetFee { + let cur = cursor::new(payload); + + // This amount cannot be greater than max u64. + let amount = bytes32::to_u64_be(bytes32::take_bytes(&mut cur)); + + cursor::destroy_empty(cur); + + SetFee { amount: (amount as u64) } + } + + #[test_only] + public fun action(): u8 { + ACTION_SET_FEE + } +} + +#[test_only] +module wormhole::set_fee_tests { + use sui::balance::{Self}; + use sui::test_scenario::{Self}; + + use wormhole::bytes::{Self}; + use wormhole::cursor::{Self}; + use wormhole::governance_message::{Self}; + use wormhole::set_fee::{Self}; + use wormhole::state::{Self}; + use wormhole::vaa::{Self}; + use wormhole::version_control::{Self}; + use wormhole::wormhole_scenario::{ + person, + return_clock, + return_state, + set_up_wormhole, + take_clock, + take_state, + upgrade_wormhole + }; + + const VAA_SET_FEE_1: vector = + x"01000000000100181aa27fd44f3060fad0ae72895d42f97c45f7a5d34aa294102911370695e91e17ae82caa59f779edde2356d95cd46c2c381cdeba7a8165901a562374f212d750000bc614e000000000001000000000000000000000000000000000000000000000000000000000000000400000000000000010100000000000000000000000000000000000000000000000000000000436f7265030015000000000000000000000000000000000000000000000000000000000000015e"; + const VAA_SET_FEE_MAX: vector = + x"01000000000100b0697fd31572e11b2256cf46d5934f38fbb90e6265e999bee50950846bf9f94d5b86f247cce20e3cc158163be7b5ae21ebaaf67e20d597229ca04d505fd4bc1c0000bc614e000000000001000000000000000000000000000000000000000000000000000000000000000400000000000000010100000000000000000000000000000000000000000000000000000000436f7265030015000000000000000000000000000000000000000000000000ffffffffffffffff"; + const VAA_SET_FEE_OVERFLOW: vector = + x"01000000000100950a509a797c9b40a678a5d6297f5b74e1ce1794b3c012dad5774c395e65e8b0773cf160113f571f1452ee98d10aa61273b6bc8aefa74a3c8f7e2c9c89fb25fa0000bc614e000000000001000000000000000000000000000000000000000000000000000000000000000400000000000000010100000000000000000000000000000000000000000000000000000000436f72650300150000000000000000000000000000000000000000000000010000000000000000"; + + #[test] + fun test_set_fee() { + // Testing this method. + use wormhole::set_fee::{set_fee}; + + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let wormhole_fee = 420; + set_up_wormhole(scenario, wormhole_fee); + + // Prepare test to execute `set_fee`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + // Double-check current fee (from setup). + assert!(state::message_fee(&worm_state) == wormhole_fee, 0); + + let verified_vaa = + vaa::parse_and_verify(&worm_state, VAA_SET_FEE_1, &the_clock); + let ticket = set_fee::authorize_governance(&worm_state); + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + let fee_amount = set_fee(&mut worm_state, receipt); + assert!(wormhole_fee != fee_amount, 0); + + // Confirm the fee changed. + assert!(state::message_fee(&worm_state) == fee_amount, 0); + + // And confirm that we can deposit the new fee amount. + state::deposit_fee_test_only( + &mut worm_state, + balance::create_for_testing(fee_amount) + ); + + // Finally set the fee again to max u64 (this will effectively pause + // Wormhole message publishing until the fee gets adjusted back to a + // reasonable level again). + let verified_vaa = + vaa::parse_and_verify(&worm_state, VAA_SET_FEE_MAX, &the_clock); + let ticket = set_fee::authorize_governance(&worm_state); + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + let fee_amount = set_fee(&mut worm_state, receipt); + + // Confirm. + assert!(state::message_fee(&worm_state) == fee_amount, 0); + + // Clean up. + return_state(worm_state); + return_clock(the_clock); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + fun test_set_fee_after_upgrade() { + // Testing this method. + use wormhole::set_fee::{set_fee}; + + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let wormhole_fee = 420; + set_up_wormhole(scenario, wormhole_fee); + + // Upgrade. + upgrade_wormhole(scenario); + + // Prepare test to execute `set_fee`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + // Double-check current fee (from setup). + assert!(state::message_fee(&worm_state) == wormhole_fee, 0); + + let verified_vaa = + vaa::parse_and_verify(&worm_state, VAA_SET_FEE_1, &the_clock); + let ticket = set_fee::authorize_governance(&worm_state); + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + let fee_amount = set_fee(&mut worm_state, receipt); + + // Confirm the fee changed. + assert!(state::message_fee(&worm_state) == fee_amount, 0); + + // Clean up. + return_state(worm_state); + return_clock(the_clock); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + #[expected_failure(abort_code = wormhole::set::E_KEY_ALREADY_EXISTS)] + fun test_cannot_set_fee_with_same_vaa() { + // Testing this method. + use wormhole::set_fee::{set_fee}; + + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let wormhole_fee = 420; + set_up_wormhole(scenario, wormhole_fee); + + // Prepare test to execute `set_fee`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + // Set once. + let verified_vaa = + vaa::parse_and_verify(&worm_state, VAA_SET_FEE_1, &the_clock); + let ticket = set_fee::authorize_governance(&worm_state); + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + set_fee(&mut worm_state, receipt); + + let verified_vaa = + vaa::parse_and_verify(&worm_state, VAA_SET_FEE_1, &the_clock); + let ticket = set_fee::authorize_governance(&worm_state); + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + + // You shall not pass! + set_fee(&mut worm_state, receipt); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = wormhole::bytes32::E_U64_OVERFLOW)] + fun test_cannot_set_fee_with_overflow() { + // Testing this method. + use wormhole::set_fee::{set_fee}; + + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let wormhole_fee = 420; + set_up_wormhole(scenario, wormhole_fee); + + // Prepare test to execute `set_fee`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + // Show that the encoded fee is greater than u64 max. + let verified_vaa = + vaa::parse_and_verify( + &worm_state, + VAA_SET_FEE_OVERFLOW, + &the_clock + ); + let payload = + governance_message::take_decree(vaa::payload(&verified_vaa)); + let cur = cursor::new(payload); + + let fee_amount = bytes::take_u256_be(&mut cur); + assert!(fee_amount > 0xffffffffffffffff, 0); + + cursor::destroy_empty(cur); + + let ticket = set_fee::authorize_governance(&worm_state); + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + + // You shall not pass! + set_fee(&mut worm_state, receipt); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = wormhole::package_utils::E_NOT_CURRENT_VERSION)] + fun test_cannot_set_fee_outdated_version() { + // Testing this method. + use wormhole::set_fee::{set_fee}; + + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let wormhole_fee = 420; + set_up_wormhole(scenario, wormhole_fee); + + // Prepare test to execute `set_fee`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + // Conveniently roll version back. + state::reverse_migrate_version(&mut worm_state); + + // Simulate executing with an outdated build by upticking the minimum + // required version for `publish_message` to something greater than + // this build. + state::migrate_version_test_only( + &mut worm_state, + version_control::previous_version_test_only(), + version_control::next_version() + ); + + let verified_vaa = + vaa::parse_and_verify( + &worm_state, + VAA_SET_FEE_1, + &the_clock + ); + + let ticket = set_fee::authorize_governance(&worm_state); + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + + // You shall not pass! + set_fee(&mut worm_state, receipt); + + abort 42 + } +} diff --git a/sui/wormhole/sources/governance/transfer_fee.move b/sui/wormhole/sources/governance/transfer_fee.move new file mode 100644 index 000000000..f31274b8e --- /dev/null +++ b/sui/wormhole/sources/governance/transfer_fee.move @@ -0,0 +1,529 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements handling a governance VAA to enact transferring some +/// amount of collected fees to an intended recipient. +module wormhole::transfer_fee { + use sui::coin::{Self}; + use sui::transfer::{Self}; + use sui::tx_context::{TxContext}; + + use wormhole::bytes32::{Self}; + use wormhole::cursor::{Self}; + use wormhole::external_address::{Self}; + use wormhole::governance_message::{Self, DecreeTicket, DecreeReceipt}; + use wormhole::state::{Self, State, LatestOnly}; + + /// Specific governance payload ID (action) for setting Wormhole fee. + const ACTION_TRANSFER_FEE: u8 = 4; + + struct GovernanceWitness has drop {} + + struct TransferFee { + amount: u64, + recipient: address + } + + public fun authorize_governance( + wormhole_state: &State + ): DecreeTicket { + governance_message::authorize_verify_local( + GovernanceWitness {}, + state::governance_chain(wormhole_state), + state::governance_contract(wormhole_state), + state::governance_module(), + ACTION_TRANSFER_FEE + ) + } + + /// Redeem governance VAA to transfer collected Wormhole fees to the + /// recipient encoded in its Wormhole governance message. This governance + /// message is only relevant for Sui because fee administration is only + /// relevant to one particular network (in this case Sui). + /// + /// NOTE: This method is guarded by a minimum build version check. This + /// method could break backward compatibility on an upgrade. + public fun transfer_fee( + wormhole_state: &mut State, + receipt: DecreeReceipt, + ctx: &mut TxContext + ): u64 { + // This capability ensures that the current build version is used. + let latest_only = state::assert_latest_only(wormhole_state); + + let payload = + governance_message::take_payload( + state::borrow_mut_consumed_vaas(&latest_only, wormhole_state), + receipt + ); + + // Proceed with setting the new message fee. + handle_transfer_fee(&latest_only, wormhole_state, payload, ctx) + } + + fun handle_transfer_fee( + latest_only: &LatestOnly, + wormhole_state: &mut State, + governance_payload: vector, + ctx: &mut TxContext + ): u64 { + // Deserialize the payload as amount to withdraw and to whom SUI should + // be sent. + let TransferFee { amount, recipient } = deserialize(governance_payload); + + transfer::public_transfer( + coin::from_balance( + state::withdraw_fee(latest_only, wormhole_state, amount), + ctx + ), + recipient + ); + + amount + } + + fun deserialize(payload: vector): TransferFee { + let cur = cursor::new(payload); + + // This amount cannot be greater than max u64. + let amount = bytes32::to_u64_be(bytes32::take_bytes(&mut cur)); + + // Recipient must be non-zero address. + let recipient = external_address::take_nonzero(&mut cur); + + cursor::destroy_empty(cur); + + TransferFee { + amount: (amount as u64), + recipient: external_address::to_address(recipient) + } + } + + #[test_only] + public fun action(): u8 { + ACTION_TRANSFER_FEE + } +} + +#[test_only] +module wormhole::transfer_fee_tests { + use sui::balance::{Self}; + use sui::coin::{Self, Coin}; + use sui::sui::{SUI}; + use sui::test_scenario::{Self}; + + use wormhole::bytes::{Self}; + use wormhole::bytes32::{Self}; + use wormhole::cursor::{Self}; + use wormhole::external_address::{Self}; + use wormhole::governance_message::{Self}; + use wormhole::state::{Self}; + use wormhole::transfer_fee::{Self}; + use wormhole::vaa::{Self}; + use wormhole::version_control::{Self}; + use wormhole::wormhole_scenario::{ + person, + return_clock, + return_state, + set_up_wormhole, + take_clock, + take_state, + two_people, + upgrade_wormhole + }; + + const VAA_TRANSFER_FEE_1: vector = + x"01000000000100a96aee105d7683266d98c9b274eddb20391378adddcefbc7a5266b4be78bc6eb582797741b65617d796c6c613ae7a4dad52a8b4aa4659842dcc4c9b3891549820100bc614e000000000001000000000000000000000000000000000000000000000000000000000000000400000000000000010100000000000000000000000000000000000000000000000000000000436f726504001500000000000000000000000000000000000000000000000000000000000004b0000000000000000000000000000000000000000000000000000000000000b0b2"; + const VAA_TRANSFER_FEE_OVERFLOW: vector = + x"01000000000100529b407a673f8917ccb9bb6f8d46d0f729c1ff845b0068ef5e0a3de464670b2e379a8994b15362785e52d73e01c880dbcdf432ef3702782d17d352fb07ed86830100bc614e000000000001000000000000000000000000000000000000000000000000000000000000000400000000000000010100000000000000000000000000000000000000000000000000000000436f72650400150000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000b0b2"; + const VAA_TRANSFER_FEE_ZERO_ADDRESS: vector = + x"0100000000010032b2ab65a690ae4af8c85903d7b22239fc272183eefdd5a4fa784664f82aa64b381380cc03859156e88623949ce4da4435199aaac1cb09e52a09d6915725a5e70100bc614e000000000001000000000000000000000000000000000000000000000000000000000000000400000000000000010100000000000000000000000000000000000000000000000000000000436f726504001500000000000000000000000000000000000000000000000000000000000004b00000000000000000000000000000000000000000000000000000000000000000"; + + #[test] + fun test_transfer_fee() { + // Testing this method. + use wormhole::transfer_fee::{transfer_fee}; + + // Set up. + let (caller, recipient) = two_people(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let wormhole_fee = 350; + set_up_wormhole(scenario, wormhole_fee); + + // Prepare test to execute `transfer_fee`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + // Double-check current fee (from setup). + assert!(state::message_fee(&worm_state) == wormhole_fee, 0); + + // Deposit fee several times. + let (i, n) = (0, 8); + while (i < n) { + state::deposit_fee_test_only( + &mut worm_state, + balance::create_for_testing(wormhole_fee) + ); + i = i + 1; + }; + + // Double-check balance. + let total_deposited = n * wormhole_fee; + assert!(state::fees_collected(&worm_state) == total_deposited, 0); + + let verified_vaa = + vaa::parse_and_verify(&worm_state, VAA_TRANSFER_FEE_1, &the_clock); + let ticket = transfer_fee::authorize_governance(&worm_state); + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + let withdrawn = + transfer_fee( + &mut worm_state, + receipt, + test_scenario::ctx(scenario) + ); + assert!(withdrawn == 1200, 0); + + // Ignore effects. + test_scenario::next_tx(scenario, caller); + + // Verify that the recipient received the withdrawal. + let withdrawn_coin = + test_scenario::take_from_address>(scenario, recipient); + assert!(coin::value(&withdrawn_coin) == withdrawn, 0); + + // And there is still a balance on Wormhole's fee collector. + let remaining = total_deposited - withdrawn; + assert!(state::fees_collected(&worm_state) == remaining, 0); + + // Clean up. + coin::burn_for_testing(withdrawn_coin); + return_state(worm_state); + return_clock(the_clock); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + fun test_transfer_fee_after_upgrade() { + // Testing this method. + use wormhole::transfer_fee::{transfer_fee}; + + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let wormhole_fee = 350; + set_up_wormhole(scenario, wormhole_fee); + + // Upgrade. + upgrade_wormhole(scenario); + + // Prepare test to execute `transfer_fee`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + // Double-check current fee (from setup). + assert!(state::message_fee(&worm_state) == wormhole_fee, 0); + + // Deposit fee several times. + let (i, n) = (0, 8); + while (i < n) { + state::deposit_fee_test_only( + &mut worm_state, + balance::create_for_testing(wormhole_fee) + ); + i = i + 1; + }; + + // Double-check balance. + let total_deposited = n * wormhole_fee; + assert!(state::fees_collected(&worm_state) == total_deposited, 0); + + let verified_vaa = + vaa::parse_and_verify(&worm_state, VAA_TRANSFER_FEE_1, &the_clock); + let ticket = transfer_fee::authorize_governance(&worm_state); + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + let withdrawn = + transfer_fee( + &mut worm_state, + receipt, + test_scenario::ctx(scenario) + ); + assert!(withdrawn == 1200, 0); + + // Clean up. + return_state(worm_state); + return_clock(the_clock); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + #[expected_failure(abort_code = wormhole::set::E_KEY_ALREADY_EXISTS)] + fun test_cannot_transfer_fee_with_same_vaa() { + // Testing this method. + use wormhole::transfer_fee::{transfer_fee}; + + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let wormhole_fee = 350; + set_up_wormhole(scenario, wormhole_fee); + + // Prepare test to execute `transfer_fee`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + // Double-check current fee (from setup). + assert!(state::message_fee(&worm_state) == wormhole_fee, 0); + + // Deposit fee several times. + let (i, n) = (0, 8); + while (i < n) { + state::deposit_fee_test_only( + &mut worm_state, + balance::create_for_testing(wormhole_fee) + ); + i = i + 1; + }; + + // Transfer once. + let verified_vaa = + vaa::parse_and_verify(&worm_state, VAA_TRANSFER_FEE_1, &the_clock); + let ticket = transfer_fee::authorize_governance(&worm_state); + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + transfer_fee(&mut worm_state, receipt, test_scenario::ctx(scenario)); + + let verified_vaa = + vaa::parse_and_verify(&worm_state, VAA_TRANSFER_FEE_1, &the_clock); + let ticket = transfer_fee::authorize_governance(&worm_state); + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + // You shall not pass! + transfer_fee(&mut worm_state, receipt, test_scenario::ctx(scenario)); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = sui::balance::ENotEnough)] + fun test_cannot_transfer_fee_insufficient_balance() { + // Testing this method. + use wormhole::transfer_fee::{transfer_fee}; + + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let wormhole_fee = 350; + set_up_wormhole(scenario, wormhole_fee); + + // Prepare test to execute `transfer_fee`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + // Show balance is zero. + assert!(state::fees_collected(&worm_state) == 0, 0); + + // Show that the encoded fee is greater than zero. + let verified_vaa = + vaa::parse_and_verify(&worm_state, VAA_TRANSFER_FEE_1, &the_clock); + let payload = + governance_message::take_decree(vaa::payload(&verified_vaa)); + let cur = cursor::new(payload); + + let amount = bytes::take_u256_be(&mut cur); + assert!(amount > 0, 0); + cursor::take_rest(cur); + + let ticket = transfer_fee::authorize_governance(&worm_state); + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + // You shall not pass! + transfer_fee(&mut worm_state, receipt, test_scenario::ctx(scenario)); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = external_address::E_ZERO_ADDRESS)] + fun test_cannot_transfer_fee_recipient_zero_address() { + // Testing this method. + use wormhole::transfer_fee::{transfer_fee}; + + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let wormhole_fee = 350; + set_up_wormhole(scenario, wormhole_fee); + + // Prepare test to execute `transfer_fee`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + // Show balance is zero. + assert!(state::fees_collected(&worm_state) == 0, 0); + + // Show that the encoded fee is greater than zero. + let verified_vaa = + vaa::parse_and_verify( + &worm_state, + VAA_TRANSFER_FEE_ZERO_ADDRESS, + &the_clock + ); + let payload = + governance_message::take_decree(vaa::payload(&verified_vaa)); + let cur = cursor::new(payload); + + bytes::take_u256_be(&mut cur); + + // Confirm recipient is zero address. + let addr = bytes32::take_bytes(&mut cur); + assert!(!bytes32::is_nonzero(&addr), 0); + cursor::destroy_empty(cur); + + let ticket = transfer_fee::authorize_governance(&worm_state); + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + // You shall not pass! + transfer_fee(&mut worm_state, receipt, test_scenario::ctx(scenario)); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = wormhole::bytes32::E_U64_OVERFLOW)] + fun test_cannot_transfer_fee_withdraw_amount_overflow() { + // Testing this method. + use wormhole::transfer_fee::{transfer_fee}; + + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let wormhole_fee = 350; + set_up_wormhole(scenario, wormhole_fee); + + // Prepare test to execute `transfer_fee`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + // Show balance is zero. + assert!(state::fees_collected(&worm_state) == 0, 0); + + // Show that the encoded fee is greater than zero. + let verified_vaa = + vaa::parse_and_verify( + &worm_state, + VAA_TRANSFER_FEE_OVERFLOW, + &the_clock + ); + let payload = + governance_message::take_decree(vaa::payload(&verified_vaa)); + let cur = cursor::new(payload); + + let amount = bytes::take_u256_be(&mut cur); + assert!(amount > 0xffffffffffffffff, 0); + cursor::take_rest(cur); + + let ticket = transfer_fee::authorize_governance(&worm_state); + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + // You shall not pass! + transfer_fee(&mut worm_state, receipt, test_scenario::ctx(scenario)); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = wormhole::package_utils::E_NOT_CURRENT_VERSION)] + fun test_cannot_set_fee_outdated_version() { + // Testing this method. + use wormhole::transfer_fee::{transfer_fee}; + + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let wormhole_fee = 350; + set_up_wormhole(scenario, wormhole_fee); + + // Prepare test to execute `transfer_fee`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + // Double-check current fee (from setup). + assert!(state::message_fee(&worm_state) == wormhole_fee, 0); + + // Deposit fee several times. + let (i, n) = (0, 8); + while (i < n) { + state::deposit_fee_test_only( + &mut worm_state, + balance::create_for_testing(wormhole_fee) + ); + i = i + 1; + }; + + // Double-check balance. + let total_deposited = n * wormhole_fee; + assert!(state::fees_collected(&worm_state) == total_deposited, 0); + + // Prepare test to execute `transfer_fee`. + test_scenario::next_tx(scenario, caller); + + // Conveniently roll version back. + state::reverse_migrate_version(&mut worm_state); + + // Simulate executing with an outdated build by upticking the minimum + // required version for `publish_message` to something greater than + // this build. + state::migrate_version_test_only( + &mut worm_state, + version_control::previous_version_test_only(), + version_control::next_version() + ); + + let verified_vaa = + vaa::parse_and_verify( + &worm_state, + VAA_TRANSFER_FEE_1, + &the_clock + ); + let ticket = transfer_fee::authorize_governance(&worm_state); + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + // You shall not pass! + transfer_fee(&mut worm_state, receipt, test_scenario::ctx(scenario)); + + abort 42 + } +} diff --git a/sui/wormhole/sources/governance/update_guardian_set.move b/sui/wormhole/sources/governance/update_guardian_set.move new file mode 100644 index 000000000..15bfb1953 --- /dev/null +++ b/sui/wormhole/sources/governance/update_guardian_set.move @@ -0,0 +1,471 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements handling a governance VAA to enact updating the +/// current guardian set to be a new set of guardian public keys. As a part of +/// this process, the previous guardian set's expiration time is set. Keep in +/// mind that the current guardian set has no expiration. +module wormhole::update_guardian_set { + use std::vector::{Self}; + use sui::clock::{Clock}; + + use wormhole::bytes::{Self}; + use wormhole::cursor::{Self}; + use wormhole::governance_message::{Self, DecreeTicket, DecreeReceipt}; + use wormhole::guardian::{Self, Guardian}; + use wormhole::guardian_set::{Self}; + use wormhole::state::{Self, State, LatestOnly}; + + /// No guardians public keys found in VAA. + const E_NO_GUARDIANS: u64 = 0; + /// Guardian set index is not incremented from last known guardian set. + const E_NON_INCREMENTAL_GUARDIAN_SETS: u64 = 1; + + /// Specific governance payload ID (action) for updating the guardian set. + const ACTION_UPDATE_GUARDIAN_SET: u8 = 2; + + struct GovernanceWitness has drop {} + + /// Event reflecting a Guardian Set update. + struct GuardianSetAdded has drop, copy { + new_index: u32 + } + + struct UpdateGuardianSet { + new_index: u32, + guardians: vector, + } + + public fun authorize_governance( + wormhole_state: &State + ): DecreeTicket { + governance_message::authorize_verify_global( + GovernanceWitness {}, + state::governance_chain(wormhole_state), + state::governance_contract(wormhole_state), + state::governance_module(), + ACTION_UPDATE_GUARDIAN_SET + ) + } + + /// Redeem governance VAA to update the current Guardian set with a new + /// set of Guardian public keys. This governance action is applied globally + /// across all networks. + /// + /// NOTE: This method is guarded by a minimum build version check. This + /// method could break backward compatibility on an upgrade. + public fun update_guardian_set( + wormhole_state: &mut State, + receipt: DecreeReceipt, + the_clock: &Clock + ): u32 { + // This capability ensures that the current build version is used. + let latest_only = state::assert_latest_only(wormhole_state); + + // Even though this disallows the VAA to be replayed, it may be + // impossible to redeem the same VAA again because `governance_message` + // requires new governance VAAs being signed by the most recent guardian + // set). + let payload = + governance_message::take_payload( + state::borrow_mut_consumed_vaas(&latest_only, wormhole_state), + receipt + ); + + // Proceed with the update. + handle_update_guardian_set(&latest_only, wormhole_state, payload, the_clock) + } + + fun handle_update_guardian_set( + latest_only: &LatestOnly, + wormhole_state: &mut State, + governance_payload: vector, + the_clock: &Clock + ): u32 { + // Deserialize the payload as the updated guardian set. + let UpdateGuardianSet { + new_index, + guardians + } = deserialize(governance_payload); + + // Every new guardian set index must be incremental from the last known + // guardian set. + assert!( + new_index == state::guardian_set_index(wormhole_state) + 1, + E_NON_INCREMENTAL_GUARDIAN_SETS + ); + + // Expire the existing guardian set. + state::expire_guardian_set(latest_only, wormhole_state, the_clock); + + // And store the new one. + state::add_new_guardian_set( + latest_only, + wormhole_state, + guardian_set::new(new_index, guardians) + ); + + sui::event::emit(GuardianSetAdded { new_index }); + + new_index + } + + fun deserialize(payload: vector): UpdateGuardianSet { + let cur = cursor::new(payload); + let new_index = bytes::take_u32_be(&mut cur); + let num_guardians = bytes::take_u8(&mut cur); + assert!(num_guardians > 0, E_NO_GUARDIANS); + + let guardians = vector::empty(); + let i = 0; + while (i < num_guardians) { + let key = bytes::take_bytes(&mut cur, 20); + vector::push_back(&mut guardians, guardian::new(key)); + i = i + 1; + }; + cursor::destroy_empty(cur); + + UpdateGuardianSet { new_index, guardians } + } + + #[test_only] + public fun action(): u8 { + ACTION_UPDATE_GUARDIAN_SET + } +} + +#[test_only] +module wormhole::update_guardian_set_tests { + use std::vector::{Self}; + use sui::clock::{Self}; + use sui::test_scenario::{Self}; + + use wormhole::bytes::{Self}; + use wormhole::cursor::{Self}; + use wormhole::governance_message::{Self}; + use wormhole::guardian::{Self}; + use wormhole::guardian_set::{Self}; + use wormhole::state::{Self}; + use wormhole::update_guardian_set::{Self}; + use wormhole::vaa::{Self}; + use wormhole::version_control::{Self}; + use wormhole::wormhole_scenario::{ + person, + return_clock, + return_state, + set_up_wormhole, + take_clock, + take_state, + upgrade_wormhole + }; + + const VAA_UPDATE_GUARDIAN_SET_1: vector = + x"010000000001004f74e9596bd8246ef456918594ae16e81365b52c0cf4490b2a029fb101b058311f4a5592baeac014dc58215faad36453467a85a4c3e1c6cf5166e80f6e4dc50b0100bc614e000000000001000000000000000000000000000000000000000000000000000000000000000400000000000000010100000000000000000000000000000000000000000000000000000000436f72650200000000000113befa429d57cd18b7f8a4d91a2da9ab4af05d0fbe88d7d8b32a9105d228100e72dffe2fae0705d31c58076f561cc62a47087b567c86f986426dfcd000bd6e9833490f8fa87c733a183cd076a6cbd29074b853fcf0a5c78c1b56d15fce7a154e6ebe9ed7a2af3503dbd2e37518ab04d7ce78b630f98b15b78a785632dea5609064803b1c8ea8bb2c77a6004bd109a281a698c0f5ba31f158585b41f4f33659e54d3178443ab76a60e21690dbfb17f7f59f09ae3ea1647ec26ae49b14060660504f4da1c2059e1c5ab6810ac3d8e1258bd2f004a94ca0cd4c68fc1c061180610e96d645b12f47ae5cf4546b18538739e90f2edb0d8530e31a218e72b9480202acbaeb06178da78858e5e5c4705cdd4b668ffe3be5bae4867c9d5efe3a05efc62d60e1d19faeb56a80223cdd3472d791b7d32c05abb1cc00b6381fa0c4928f0c56fc14bc029b8809069093d712a3fd4dfab31963597e246ab29fc6ebedf2d392a51ab2dc5c59d0902a03132a84dfd920b35a3d0ba5f7a0635df298f9033e"; + const VAA_UPDATE_GUARDIAN_SET_2A: vector = + x"010000000001005fb17d5e0e736e3014756bf7e7335722c4fe3ad18b5b1b566e8e61e562cc44555f30b298bc6a21ea4b192a6f1877a5e638ecf90a77b0b028f297a3a70d93614d0100bc614e000000000001000000000000000000000000000000000000000000000000000000000000000400000000000000010100000000000000000000000000000000000000000000000000000000436f72650200000000000101befa429d57cd18b7f8a4d91a2da9ab4af05d0fbe"; + const VAA_UPDATE_GUARDIAN_SET_2B: vector = + x"01000000010100195f37abd29438c74db6e57bf527646b36fa96e36392221e869debe0e911f2f319abc0fd5c5a454da76fc0ffdd23a71a60bca40aa4289a841ad07f2964cde9290000bc614e000000000001000000000000000000000000000000000000000000000000000000000000000400000000000000020100000000000000000000000000000000000000000000000000000000436f72650200000000000201befa429d57cd18b7f8a4d91a2da9ab4af05d0fbe"; + const VAA_UPDATE_GUARDIAN_SET_EMPTY: vector = + x"0100000000010098f9e45f836661d2932def9c74c587168f4f75d0282201ee6f5a98557e6212ff19b0f8881c2750646250f60dd5d565530779ecbf9442aa5ffc2d6afd7303aaa40000bc614e000000000001000000000000000000000000000000000000000000000000000000000000000400000000000000010100000000000000000000000000000000000000000000000000000000436f72650200000000000100"; + + #[test] + fun test_update_guardian_set() { + // Testing this method. + use wormhole::update_guardian_set::{update_guardian_set}; + + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let wormhole_fee = 350; + set_up_wormhole(scenario, wormhole_fee); + + // Prepare test to execute `update_guardian_set`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + let verified_vaa = + vaa::parse_and_verify( + &worm_state, + VAA_UPDATE_GUARDIAN_SET_1, + &the_clock + ); + let ticket = update_guardian_set::authorize_governance(&worm_state); + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + let new_index = + update_guardian_set(&mut worm_state, receipt, &the_clock); + assert!(new_index == 1, 0); + + let new_guardian_set = + state::guardian_set_at(&worm_state, new_index); + + // Verify new guardian set index. + assert!(state::guardian_set_index(&worm_state) == new_index, 0); + assert!( + guardian_set::index(new_guardian_set) == state::guardian_set_index(&worm_state), + 0 + ); + + // Check that the guardians agree with what we expect. + let guardians = guardian_set::guardians(new_guardian_set); + let expected = vector[ + guardian::new(x"befa429d57cd18b7f8a4d91a2da9ab4af05d0fbe"), + guardian::new(x"88d7d8b32a9105d228100e72dffe2fae0705d31c"), + guardian::new(x"58076f561cc62a47087b567c86f986426dfcd000"), + guardian::new(x"bd6e9833490f8fa87c733a183cd076a6cbd29074"), + guardian::new(x"b853fcf0a5c78c1b56d15fce7a154e6ebe9ed7a2"), + guardian::new(x"af3503dbd2e37518ab04d7ce78b630f98b15b78a"), + guardian::new(x"785632dea5609064803b1c8ea8bb2c77a6004bd1"), + guardian::new(x"09a281a698c0f5ba31f158585b41f4f33659e54d"), + guardian::new(x"3178443ab76a60e21690dbfb17f7f59f09ae3ea1"), + guardian::new(x"647ec26ae49b14060660504f4da1c2059e1c5ab6"), + guardian::new(x"810ac3d8e1258bd2f004a94ca0cd4c68fc1c0611"), + guardian::new(x"80610e96d645b12f47ae5cf4546b18538739e90f"), + guardian::new(x"2edb0d8530e31a218e72b9480202acbaeb06178d"), + guardian::new(x"a78858e5e5c4705cdd4b668ffe3be5bae4867c9d"), + guardian::new(x"5efe3a05efc62d60e1d19faeb56a80223cdd3472"), + guardian::new(x"d791b7d32c05abb1cc00b6381fa0c4928f0c56fc"), + guardian::new(x"14bc029b8809069093d712a3fd4dfab31963597e"), + guardian::new(x"246ab29fc6ebedf2d392a51ab2dc5c59d0902a03"), + guardian::new(x"132a84dfd920b35a3d0ba5f7a0635df298f9033e"), + ]; + assert!(vector::length(&expected) == vector::length(guardians), 0); + + let cur = cursor::new(expected); + let i = 0; + while (!cursor::is_empty(&cur)) { + let left = guardian::as_bytes(vector::borrow(guardians, i)); + let right = guardian::to_bytes(cursor::poke(&mut cur)); + assert!(left == right, 0); + i = i + 1; + }; + cursor::destroy_empty(cur); + + // Make sure old guardian set is still active. + let old_guardian_set = + state::guardian_set_at(&worm_state, new_index - 1); + assert!(guardian_set::is_active(old_guardian_set, &the_clock), 0); + + // Fast forward time beyond expiration by + // `guardian_set_seconds_to_live`. + let tick_ms = + (state::guardian_set_seconds_to_live(&worm_state) as u64) * 1000; + clock::increment_for_testing(&mut the_clock, tick_ms + 1); + + // Now the old guardian set should be expired (because in the test setup + // time to live is set to 2 epochs). + assert!(!guardian_set::is_active(old_guardian_set, &the_clock), 0); + + // Clean up. + return_state(worm_state); + return_clock(the_clock); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + fun test_update_guardian_set_after_upgrade() { + // Testing this method. + use wormhole::update_guardian_set::{update_guardian_set}; + + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let wormhole_fee = 350; + set_up_wormhole(scenario, wormhole_fee); + + // Upgrade. + upgrade_wormhole(scenario); + + // Prepare test to execute `update_guardian_set`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + let verified_vaa = + vaa::parse_and_verify( + &worm_state, + VAA_UPDATE_GUARDIAN_SET_1, + &the_clock + ); + let ticket = update_guardian_set::authorize_governance(&worm_state); + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + let new_index = + update_guardian_set(&mut worm_state, receipt, &the_clock); + assert!(new_index == 1, 0); + + // Clean up. + return_state(worm_state); + return_clock(the_clock); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + #[expected_failure( + abort_code = governance_message::E_OLD_GUARDIAN_SET_GOVERNANCE + )] + fun test_cannot_update_guardian_set_again_with_same_vaa() { + // Testing this method. + use wormhole::update_guardian_set::{update_guardian_set}; + + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let wormhole_fee = 350; + set_up_wormhole(scenario, wormhole_fee); + + // Prepare test to execute `update_guardian_set`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + let verified_vaa = + vaa::parse_and_verify( + &worm_state, + VAA_UPDATE_GUARDIAN_SET_2A, + &the_clock + ); + let ticket = update_guardian_set::authorize_governance(&worm_state); + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + update_guardian_set(&mut worm_state, receipt, &the_clock); + + // Update guardian set again with new VAA. + let verified_vaa = + vaa::parse_and_verify( + &worm_state, + VAA_UPDATE_GUARDIAN_SET_2B, + &the_clock + ); + let ticket = update_guardian_set::authorize_governance(&worm_state); + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + let new_index = + update_guardian_set(&mut worm_state, receipt, &the_clock); + assert!(new_index == 2, 0); + assert!(state::guardian_set_index(&worm_state) == 2, 0); + + let verified_vaa = + vaa::parse_and_verify( + &worm_state, + VAA_UPDATE_GUARDIAN_SET_2A, + &the_clock + ); + let ticket = update_guardian_set::authorize_governance(&worm_state); + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + // You shall not pass! + update_guardian_set(&mut worm_state, receipt, &the_clock); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = update_guardian_set::E_NO_GUARDIANS)] + fun test_cannot_update_guardian_set_with_no_guardians() { + // Testing this method. + use wormhole::update_guardian_set::{update_guardian_set}; + + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let wormhole_fee = 350; + set_up_wormhole(scenario, wormhole_fee); + + // Prepare test to execute `update_guardian_set`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + + // Show that the encoded number of guardians is zero. + let verified_vaa = + vaa::parse_and_verify( + &worm_state, + VAA_UPDATE_GUARDIAN_SET_EMPTY, + &the_clock + ); + let payload = + governance_message::take_decree(vaa::payload(&verified_vaa)); + let cur = cursor::new(payload); + + let new_guardian_set_index = bytes::take_u32_be(&mut cur); + assert!(new_guardian_set_index == 1, 0); + + let num_guardians = bytes::take_u8(&mut cur); + assert!(num_guardians == 0, 0); + + cursor::destroy_empty(cur); + + let ticket = update_guardian_set::authorize_governance(&worm_state); + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + // You shall not pass! + update_guardian_set(&mut worm_state, receipt, &the_clock); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = wormhole::package_utils::E_NOT_CURRENT_VERSION)] + fun test_cannot_set_fee_outdated_version() { + // Testing this method. + use wormhole::update_guardian_set::{update_guardian_set}; + + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let wormhole_fee = 350; + set_up_wormhole(scenario, wormhole_fee); + + // Prepare test to execute `update_guardian_set`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + // Conveniently roll version back. + state::reverse_migrate_version(&mut worm_state); + + // Simulate executing with an outdated build by upticking the minimum + // required version for `publish_message` to something greater than + // this build. + state::migrate_version_test_only( + &mut worm_state, + version_control::previous_version_test_only(), + version_control::next_version() + ); + + let verified_vaa = + vaa::parse_and_verify( + &worm_state, + VAA_UPDATE_GUARDIAN_SET_1, + &the_clock + ); + let ticket = update_guardian_set::authorize_governance(&worm_state); + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + // You shall not pass! + update_guardian_set(&mut worm_state, receipt, &the_clock); + + abort 42 + } +} diff --git a/sui/wormhole/sources/governance/upgrade_contract.move b/sui/wormhole/sources/governance/upgrade_contract.move new file mode 100644 index 000000000..018511e78 --- /dev/null +++ b/sui/wormhole/sources/governance/upgrade_contract.move @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements handling a governance VAA to enact upgrading the +/// Wormhole contract to a new build. The procedure to upgrade this contract +/// requires a Programmable Transaction, which includes the following procedure: +/// 1. Load new build. +/// 2. Authorize upgrade. +/// 3. Upgrade. +/// 4. Commit upgrade. +module wormhole::upgrade_contract { + use sui::object::{ID}; + use sui::package::{UpgradeReceipt, UpgradeTicket}; + + use wormhole::bytes32::{Self, Bytes32}; + use wormhole::cursor::{Self}; + use wormhole::governance_message::{Self, DecreeTicket, DecreeReceipt}; + use wormhole::state::{Self, State}; + + friend wormhole::migrate; + + /// Digest is all zeros. + const E_DIGEST_ZERO_BYTES: u64 = 0; + + /// Specific governance payload ID (action) to complete upgrading the + /// contract. + const ACTION_UPGRADE_CONTRACT: u8 = 1; + + struct GovernanceWitness has drop {} + + // Event reflecting package upgrade. + struct ContractUpgraded has drop, copy { + old_contract: ID, + new_contract: ID + } + + struct UpgradeContract { + digest: Bytes32 + } + + public fun authorize_governance( + wormhole_state: &State + ): DecreeTicket { + governance_message::authorize_verify_local( + GovernanceWitness {}, + state::governance_chain(wormhole_state), + state::governance_contract(wormhole_state), + state::governance_module(), + ACTION_UPGRADE_CONTRACT + ) + } + + /// Redeem governance VAA to issue an `UpgradeTicket` for the upgrade given + /// a contract upgrade VAA. This governance message is only relevant for Sui + /// because a contract upgrade is only relevant to one particular network + /// (in this case Sui), whose build digest is encoded in this message. + public fun authorize_upgrade( + wormhole_state: &mut State, + receipt: DecreeReceipt + ): UpgradeTicket { + // NOTE: This is the only governance method that does not enforce + // current package checking when consuming VAA hashes. This is because + // upgrades are protected by the Sui VM, enforcing the latest package + // is the one performing the upgrade. + let consumed = + state::borrow_mut_consumed_vaas_unchecked(wormhole_state); + + // And consume. + let payload = governance_message::take_payload(consumed, receipt); + + // Proceed with processing new implementation version. + handle_upgrade_contract(wormhole_state, payload) + } + + /// Finalize the upgrade that ran to produce the given `receipt`. This + /// method invokes `state::commit_upgrade` which interacts with + /// `sui::package`. + public fun commit_upgrade( + self: &mut State, + receipt: UpgradeReceipt, + ) { + let (old_contract, new_contract) = state::commit_upgrade(self, receipt); + + // Emit an event reflecting package ID change. + sui::event::emit(ContractUpgraded { old_contract, new_contract }); + } + + /// Privileged method only to be used by this module and `migrate` module. + /// + /// During migration, we make sure that the digest equals what we expect by + /// passing in the same VAA used to upgrade the package. + public(friend) fun take_digest(governance_payload: vector): Bytes32 { + // Deserialize the payload as the build digest. + let UpgradeContract { digest } = deserialize(governance_payload); + + digest + } + + fun handle_upgrade_contract( + wormhole_state: &mut State, + payload: vector + ): UpgradeTicket { + state::authorize_upgrade(wormhole_state, take_digest(payload)) + } + + fun deserialize(payload: vector): UpgradeContract { + let cur = cursor::new(payload); + + // This amount cannot be greater than max u64. + let digest = bytes32::take_bytes(&mut cur); + assert!(bytes32::is_nonzero(&digest), E_DIGEST_ZERO_BYTES); + + cursor::destroy_empty(cur); + + UpgradeContract { digest } + } + + #[test_only] + public fun action(): u8 { + ACTION_UPGRADE_CONTRACT + } +} + +#[test_only] +module wormhole::upgrade_contract_tests { + // TODO +} diff --git a/sui/wormhole/sources/governance_message.move b/sui/wormhole/sources/governance_message.move new file mode 100644 index 000000000..f05fd8ed1 --- /dev/null +++ b/sui/wormhole/sources/governance_message.move @@ -0,0 +1,694 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements a custom type representing a Guardian governance +/// action. Each governance action has an associated module name, relevant chain +/// and payload encoding instructions/data used to perform an adminstrative +/// change on a contract. +module wormhole::governance_message { + use wormhole::bytes::{Self}; + use wormhole::bytes32::{Self, Bytes32}; + use wormhole::consumed_vaas::{Self, ConsumedVAAs}; + use wormhole::cursor::{Self}; + use wormhole::external_address::{ExternalAddress}; + use wormhole::state::{Self, State, chain_id}; + use wormhole::vaa::{Self, VAA}; + + /// Guardian set used to sign VAA did not use current Guardian set. + const E_OLD_GUARDIAN_SET_GOVERNANCE: u64 = 0; + /// Governance chain disagrees does not match. + const E_INVALID_GOVERNANCE_CHAIN: u64 = 1; + /// Governance emitter address does not match. + const E_INVALID_GOVERNANCE_EMITTER: u64 = 2; + /// Governance module name does not match. + const E_INVALID_GOVERNANCE_MODULE: u64 = 4; + /// Governance action does not match. + const E_INVALID_GOVERNANCE_ACTION: u64 = 5; + /// Governance target chain not indicative of global action. + const E_GOVERNANCE_TARGET_CHAIN_NONZERO: u64 = 6; + /// Governance target chain not indicative of actino specifically for Sui + /// Wormhole contract. + const E_GOVERNANCE_TARGET_CHAIN_NOT_SUI: u64 = 7; + + /// The public constructors for `DecreeTicket` (`authorize_verify_global` + /// and `authorize_verify_local`) require a witness of type `T`. This is to + /// ensure that `DecreeTicket`s cannot be mixed up between modules + /// maliciously. + struct DecreeTicket { + governance_chain: u16, + governance_contract: ExternalAddress, + module_name: Bytes32, + action: u8, + global: bool + } + + struct DecreeReceipt { + payload: vector, + digest: Bytes32, + sequence: u64 + } + + /// This method prepares `DecreeTicket` for global governance action. This + /// means the VAA encodes target chain ID == 0. + public fun authorize_verify_global( + _witness: T, + governance_chain: u16, + governance_contract: ExternalAddress, + module_name: Bytes32, + action: u8 + ): DecreeTicket { + DecreeTicket { + governance_chain, + governance_contract, + module_name, + action, + global: true + } + } + + /// This method prepares `DecreeTicket` for local governance action. This + /// means the VAA encodes target chain ID == 21 (Sui's). + public fun authorize_verify_local( + _witness: T, + governance_chain: u16, + governance_contract: ExternalAddress, + module_name: Bytes32, + action: u8 + ): DecreeTicket { + DecreeTicket { + governance_chain, + governance_contract, + module_name, + action, + global: false + } + } + + public fun sequence(receipt: &DecreeReceipt): u64 { + receipt.sequence + } + + /// This method unpacks `DecreeReceipt` and puts the VAA digest into a + /// `ConsumedVAAs` container. Then it returns the governance payload. + public fun take_payload( + consumed: &mut ConsumedVAAs, + receipt: DecreeReceipt + ): vector { + let DecreeReceipt { payload, digest, sequence: _ } = receipt; + + consumed_vaas::consume(consumed, digest); + + payload + } + + /// Method to peek into the payload in `DecreeReceipt`. + public fun payload(receipt: &DecreeReceipt): vector { + receipt.payload + } + + /// Destroy the receipt. + public fun destroy(receipt: DecreeReceipt) { + let DecreeReceipt { payload: _, digest: _, sequence: _ } = receipt; + } + + /// This method unpacks a `DecreeTicket` to validate its members to make + /// sure that the parameters match what was encoded in the VAA. + public fun verify_vaa( + wormhole_state: &State, + verified_vaa: VAA, + ticket: DecreeTicket + ): DecreeReceipt { + state::assert_latest_only(wormhole_state); + + let DecreeTicket { + governance_chain, + governance_contract, + module_name, + action, + global + } = ticket; + + // Protect against governance actions enacted using an old guardian set. + // This is not a protection found in the other Wormhole contracts. + assert!( + vaa::guardian_set_index(&verified_vaa) == state::guardian_set_index(wormhole_state), + E_OLD_GUARDIAN_SET_GOVERNANCE + ); + + // Both the emitter chain and address must equal. + assert!( + vaa::emitter_chain(&verified_vaa) == governance_chain, + E_INVALID_GOVERNANCE_CHAIN + ); + assert!( + vaa::emitter_address(&verified_vaa) == governance_contract, + E_INVALID_GOVERNANCE_EMITTER + ); + + // Cache VAA digest. + let digest = vaa::digest(&verified_vaa); + + // Get the VAA sequence number. + let sequence = vaa::sequence(&verified_vaa); + + // Finally deserialize Wormhole payload as governance message. + let ( + parsed_module_name, + parsed_action, + chain, + payload + ) = deserialize(vaa::take_payload(verified_vaa)); + + assert!(module_name == parsed_module_name, E_INVALID_GOVERNANCE_MODULE); + assert!(action == parsed_action, E_INVALID_GOVERNANCE_ACTION); + + // Target chain, which determines whether the governance VAA applies to + // all chains or Sui. + if (global) { + assert!(chain == 0, E_GOVERNANCE_TARGET_CHAIN_NONZERO); + } else { + assert!(chain == chain_id(), E_GOVERNANCE_TARGET_CHAIN_NOT_SUI); + }; + + DecreeReceipt { payload, digest, sequence } + } + + fun deserialize(buf: vector): (Bytes32, u8, u16, vector) { + let cur = cursor::new(buf); + + ( + bytes32::take_bytes(&mut cur), + bytes::take_u8(&mut cur), + bytes::take_u16_be(&mut cur), + cursor::take_rest(cur) + ) + } + + #[test_only] + public fun deserialize_test_only( + buf: vector + ): ( + Bytes32, + u8, + u16, + vector + ) { + deserialize(buf) + } + + #[test_only] + public fun take_decree(buf: vector): vector { + let (_, _, _, payload) = deserialize(buf); + payload + } +} + +#[test_only] +module wormhole::governance_message_tests { + use sui::test_scenario::{Self}; + use sui::tx_context::{Self}; + + use wormhole::bytes32::{Self}; + use wormhole::consumed_vaas::{Self}; + use wormhole::external_address::{Self}; + use wormhole::governance_message::{Self}; + use wormhole::state::{Self}; + use wormhole::vaa::{Self}; + use wormhole::version_control::{Self}; + use wormhole::wormhole_scenario::{ + set_up_wormhole, + person, + return_clock, + return_state, + take_clock, + take_state + }; + + struct GovernanceWitness has drop {} + + const VAA_UPDATE_GUARDIAN_SET_1: vector = + x"010000000001004f74e9596bd8246ef456918594ae16e81365b52c0cf4490b2a029fb101b058311f4a5592baeac014dc58215faad36453467a85a4c3e1c6cf5166e80f6e4dc50b0100bc614e000000000001000000000000000000000000000000000000000000000000000000000000000400000000000000010100000000000000000000000000000000000000000000000000000000436f72650200000000000113befa429d57cd18b7f8a4d91a2da9ab4af05d0fbe88d7d8b32a9105d228100e72dffe2fae0705d31c58076f561cc62a47087b567c86f986426dfcd000bd6e9833490f8fa87c733a183cd076a6cbd29074b853fcf0a5c78c1b56d15fce7a154e6ebe9ed7a2af3503dbd2e37518ab04d7ce78b630f98b15b78a785632dea5609064803b1c8ea8bb2c77a6004bd109a281a698c0f5ba31f158585b41f4f33659e54d3178443ab76a60e21690dbfb17f7f59f09ae3ea1647ec26ae49b14060660504f4da1c2059e1c5ab6810ac3d8e1258bd2f004a94ca0cd4c68fc1c061180610e96d645b12f47ae5cf4546b18538739e90f2edb0d8530e31a218e72b9480202acbaeb06178da78858e5e5c4705cdd4b668ffe3be5bae4867c9d5efe3a05efc62d60e1d19faeb56a80223cdd3472d791b7d32c05abb1cc00b6381fa0c4928f0c56fc14bc029b8809069093d712a3fd4dfab31963597e246ab29fc6ebedf2d392a51ab2dc5c59d0902a03132a84dfd920b35a3d0ba5f7a0635df298f9033e"; + const VAA_SET_FEE_1: vector = + x"01000000000100181aa27fd44f3060fad0ae72895d42f97c45f7a5d34aa294102911370695e91e17ae82caa59f779edde2356d95cd46c2c381cdeba7a8165901a562374f212d750000bc614e000000000001000000000000000000000000000000000000000000000000000000000000000400000000000000010100000000000000000000000000000000000000000000000000000000436f7265030015000000000000000000000000000000000000000000000000000000000000015e"; + + #[test] + fun test_global_action() { + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let wormhole_fee = 350; + set_up_wormhole(scenario, wormhole_fee); + + // Prepare test setting sender to `caller`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + let verified_vaa = + vaa::parse_and_verify( + &worm_state, + VAA_UPDATE_GUARDIAN_SET_1, + &the_clock + ); + let ( + _, + _, + _, + expected_payload + ) = governance_message::deserialize_test_only( + vaa::payload(&verified_vaa) + ); + + let ticket = + governance_message::authorize_verify_global( + GovernanceWitness {}, + state::governance_chain(&worm_state), + state::governance_contract(&worm_state), + state::governance_module(), + 2 // update guadian set + ); + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + + let consumed = consumed_vaas::new(&mut tx_context::dummy()); + let payload = governance_message::take_payload(&mut consumed, receipt); + assert!(payload == expected_payload, 0); + + // Clean up. + consumed_vaas::destroy(consumed); + return_state(worm_state); + return_clock(the_clock); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + fun test_local_action() { + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let wormhole_fee = 350; + set_up_wormhole(scenario, wormhole_fee); + + // Prepare test setting sender to `caller`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + let verified_vaa = + vaa::parse_and_verify(&worm_state, VAA_SET_FEE_1, &the_clock); + let ( + _, + _, + _, + expected_payload + ) = governance_message::deserialize_test_only( + vaa::payload(&verified_vaa) + ); + + let ticket = + governance_message::authorize_verify_local( + GovernanceWitness {}, + state::governance_chain(&worm_state), + state::governance_contract(&worm_state), + state::governance_module(), + 3 // set fee + ); + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + + let consumed = consumed_vaas::new(&mut tx_context::dummy()); + let payload = governance_message::take_payload(&mut consumed, receipt); + assert!(payload == expected_payload, 0); + + // Clean up. + consumed_vaas::destroy(consumed); + return_state(worm_state); + return_clock(the_clock); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + #[expected_failure( + abort_code = governance_message::E_INVALID_GOVERNANCE_CHAIN + )] + fun test_cannot_verify_vaa_invalid_governance_chain() { + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let wormhole_fee = 350; + set_up_wormhole(scenario, wormhole_fee); + + // Prepare test setting sender to `caller`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + let verified_vaa = + vaa::parse_and_verify(&worm_state, VAA_SET_FEE_1, &the_clock); + + // Show that this emitter chain ID does not equal the encoded one. + let invalid_chain = 0xffff; + assert!(invalid_chain != vaa::emitter_chain(&verified_vaa), 0); + + let ticket = + governance_message::authorize_verify_local( + GovernanceWitness {}, + invalid_chain, + state::governance_contract(&worm_state), + state::governance_module(), + 3 // set fee + ); + + // You shall not pass! + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + + // Clean up. + governance_message::destroy(receipt); + + abort 42 + } + + #[test] + #[expected_failure( + abort_code = governance_message::E_INVALID_GOVERNANCE_EMITTER + )] + fun test_cannot_verify_vaa_invalid_governance_emitter() { + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let wormhole_fee = 350; + set_up_wormhole(scenario, wormhole_fee); + + // Prepare test setting sender to `caller`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + let verified_vaa = + vaa::parse_and_verify(&worm_state, VAA_SET_FEE_1, &the_clock); + + // Show that this emitter address does not equal the encoded one. + let invalid_emitter = external_address::new(bytes32::default()); + assert!(invalid_emitter != vaa::emitter_address(&verified_vaa), 0); + + let ticket = + governance_message::authorize_verify_global( + GovernanceWitness {}, + state::governance_chain(&worm_state), + invalid_emitter, + state::governance_module(), + 3 // set fee + ); + + // You shall not pass! + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + + // Clean up. + governance_message::destroy(receipt); + + abort 42 + } + + #[test] + #[expected_failure( + abort_code = governance_message::E_INVALID_GOVERNANCE_MODULE + )] + fun test_cannot_verify_vaa_invalid_governance_module() { + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let wormhole_fee = 350; + set_up_wormhole(scenario, wormhole_fee); + + // Prepare test setting sender to `caller`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + let verified_vaa = + vaa::parse_and_verify(&worm_state, VAA_SET_FEE_1, &the_clock); + let ( + expected_module, + _, + _, + _ + ) = governance_message::deserialize_test_only( + vaa::payload(&verified_vaa) + ); + + // Show that this module does not equal the encoded one. + let invalid_module = bytes32::from_bytes(b"Not Wormhole"); + assert!(invalid_module != expected_module, 0); + + let ticket = + governance_message::authorize_verify_local( + GovernanceWitness {}, + state::governance_chain(&worm_state), + state::governance_contract(&worm_state), + invalid_module, + 3 // set fee + ); + + // You shall not pass! + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + + // Clean up. + governance_message::destroy(receipt); + + abort 42 + } + + #[test] + #[expected_failure( + abort_code = governance_message::E_INVALID_GOVERNANCE_ACTION + )] + fun test_cannot_verify_vaa_invalid_governance_action() { + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let wormhole_fee = 350; + set_up_wormhole(scenario, wormhole_fee); + + // Prepare test setting sender to `caller`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + let verified_vaa = + vaa::parse_and_verify(&worm_state, VAA_SET_FEE_1, &the_clock); + let ( + _, + expected_action, + _, + _ + ) = governance_message::deserialize_test_only( + vaa::payload(&verified_vaa) + ); + + // Show that this action does not equal the encoded one. + let invalid_action = 0xff; + assert!(invalid_action != expected_action, 0); + + let ticket = + governance_message::authorize_verify_local( + GovernanceWitness {}, + state::governance_chain(&worm_state), + state::governance_contract(&worm_state), + state::governance_module(), + invalid_action + ); + + // You shall not pass! + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + + // Clean up. + governance_message::destroy(receipt); + + abort 42 + } + + #[test] + #[expected_failure( + abort_code = governance_message::E_GOVERNANCE_TARGET_CHAIN_NONZERO + )] + fun test_cannot_verify_vaa_governance_target_chain_nonzero() { + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let wormhole_fee = 350; + set_up_wormhole(scenario, wormhole_fee); + + // Prepare test setting sender to `caller`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + let verified_vaa = + vaa::parse_and_verify(&worm_state, VAA_SET_FEE_1, &the_clock); + let ( + _, + _, + expected_target_chain, + _ + ) = governance_message::deserialize_test_only( + vaa::payload(&verified_vaa) + ); + + // Show that this target chain ID does reflect a global action. + let not_global = expected_target_chain != 0; + assert!(not_global, 0); + + let ticket = + governance_message::authorize_verify_global( + GovernanceWitness {}, + state::governance_chain(&worm_state), + state::governance_contract(&worm_state), + state::governance_module(), + 3 // set fee + ); + + // You shall not pass! + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + + // Clean up. + governance_message::destroy(receipt); + + abort 42 + } + + #[test] + #[expected_failure( + abort_code = governance_message::E_GOVERNANCE_TARGET_CHAIN_NOT_SUI + )] + fun test_cannot_verify_vaa_governance_target_chain_not_sui() { + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let wormhole_fee = 350; + set_up_wormhole(scenario, wormhole_fee); + + // Prepare test setting sender to `caller`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + let verified_vaa = + vaa::parse_and_verify( + &worm_state, + VAA_UPDATE_GUARDIAN_SET_1, + &the_clock + ); + let ( + _, + _, + expected_target_chain, + _ + ) = governance_message::deserialize_test_only( + vaa::payload(&verified_vaa) + ); + + // Show that this target chain ID does reflect a global action. + let global = expected_target_chain == 0; + assert!(global, 0); + + let ticket = + governance_message::authorize_verify_local( + GovernanceWitness {}, + state::governance_chain(&worm_state), + state::governance_contract(&worm_state), + state::governance_module(), + 2 // update guardian set + ); + + // You shall not pass! + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + + // Clean up. + governance_message::destroy(receipt); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = wormhole::package_utils::E_NOT_CURRENT_VERSION)] + fun test_cannot_verify_vaa_outdated_version() { + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + let wormhole_fee = 350; + set_up_wormhole(scenario, wormhole_fee); + + // Prepare test setting sender to `caller`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + let verified_vaa = + vaa::parse_and_verify(&worm_state, VAA_SET_FEE_1, &the_clock); + let ticket = + governance_message::authorize_verify_local( + GovernanceWitness {}, + state::governance_chain(&worm_state), + state::governance_contract(&worm_state), + state::governance_module(), + 3 // set fee + ); + + // Conveniently roll version back. + state::reverse_migrate_version(&mut worm_state); + + // Simulate executing with an outdated build by upticking the minimum + // required version for `publish_message` to something greater than + // this build. + state::migrate_version_test_only( + &mut worm_state, + version_control::previous_version_test_only(), + version_control::next_version() + ); + + // You shall not pass! + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + + // Clean up. + governance_message::destroy(receipt); + + abort 42 + } +} diff --git a/sui/wormhole/sources/guardian_pubkey.move b/sui/wormhole/sources/guardian_pubkey.move deleted file mode 100644 index 6f775523d..000000000 --- a/sui/wormhole/sources/guardian_pubkey.move +++ /dev/null @@ -1,82 +0,0 @@ -/// Guardian keys are EVM-style 20 byte addresses -/// That is, they are computed by taking the last 20 bytes of the keccak256 -/// hash of their 64 byte secp256k1 public key. -module wormhole::guardian_pubkey { - use sui::ecdsa_k1::{Self as ecdsa}; - use std::vector; - use wormhole::keccak256::keccak256; - - /// An error occurred while deserializing, for example due to wrong input size. - const E_DESERIALIZE: u64 = 1; - - struct Address has store, drop, copy { - bytes: vector - } - - /// Deserializes a raw byte sequence into an address. - /// Aborts if the input is not 20 bytes long. - public fun from_bytes(bytes: vector): Address { - assert!(std::vector::length(&bytes) == 20, E_DESERIALIZE); - Address { bytes } - } - - /// Computes the address from a 64 byte public key. - public fun from_pubkey(pubkey: vector): Address { - assert!(std::vector::length(&pubkey) == 64, E_DESERIALIZE); - let hash = keccak256(pubkey); - let address = vector::empty(); - let i = 0; - while (i < 20) { - vector::push_back(&mut address, vector::pop_back(&mut hash)); - i = i + 1; - }; - vector::reverse(&mut address); - Address { bytes: address } - } - - /// Recovers the address from a signature and message. - /// This is known as 'ecrecover' in EVM. - public fun from_signature( - message: vector, - recovery_id: u8, - sig: vector, - ): Address { - // sui's ecrecover function takes a 65 byte array (signature + recovery byte) - vector::push_back(&mut sig, recovery_id); - - let pubkey = ecdsa::ecrecover(&sig, &message); - let pubkey = ecdsa::decompress_pubkey(&pubkey); - - // decompress_pubkey returns 65 bytes, the first byte is not relevant to - // us, so we remove it - vector::remove(&mut pubkey, 0); - - from_pubkey(pubkey) - } -} - -#[test_only] -module wormhole::guardian_pubkey_test { - use wormhole::guardian_pubkey; - - #[test] - public fun from_pubkey_test() { - // devnet guardian public key - let pubkey = x"d4a4629979f0c9fa0f0bb54edf33f87c8c5a1f42c0350a30d68f7e967023e34e495a8ebf5101036d0fd66e3b0a8c7c61b65fceeaf487ab3cd1b5b7b50beb7970"; - let expected_address = guardian_pubkey::from_bytes(x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"); - - let address = guardian_pubkey::from_pubkey(pubkey); - - assert!(address == expected_address, 0); - } - - #[test] - public fun from_signature() { - let sig = x"38535089d6eec412a00066f84084212316ee3451145a75591dbd4a1c2a2bff442223f81e58821bfa4e8ffb80a881daf7a37500b04dfa5719fff25ed4cec8dda3"; - let msg = x"43f3693ccdcb4400e1d1c5c8cec200153bd4b3d167e5b9fe5400508cf8717880"; - let addr = guardian_pubkey::from_signature(msg, 0x01, sig); - let expected_addr = guardian_pubkey::from_bytes(x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"); - assert!(addr == expected_addr, 0); - } - -} diff --git a/sui/wormhole/sources/guardian_set_upgrade.move b/sui/wormhole/sources/guardian_set_upgrade.move deleted file mode 100644 index 9dff8cf90..000000000 --- a/sui/wormhole/sources/guardian_set_upgrade.move +++ /dev/null @@ -1,188 +0,0 @@ -module wormhole::guardian_set_upgrade { - use std::vector::{Self}; - use sui::tx_context::{TxContext}; - - use wormhole::deserialize; - use wormhole::cursor::{Self}; - use wormhole::myvaa::{Self as vaa}; - //use wormhole::myvaa::{Self as vaa}; - use wormhole::state::{Self, State}; - use wormhole::structs::{ - Guardian, - create_guardian, - create_guardian_set - }; - use wormhole::myu32::{Self as u32,U32}; - use wormhole::myu16::{Self as u16}; - - const E_WRONG_GUARDIAN_LEN: u64 = 0x0; - const E_NO_GUARDIAN_SET: u64 = 0x1; - const E_INVALID_MODULE: u64 = 0x2; - const E_INVALID_ACTION: u64 = 0x3; - const E_INVALID_TARGET: u64 = 0x4; - const E_NON_INCREMENTAL_GUARDIAN_SETS: u64 = 0x5; - - struct GuardianSetUpgrade { - new_index: U32, - guardians: vector, - } - - public entry fun submit_vaa(state: &mut State, vaa: vector, ctx: &mut TxContext) { - let vaa = vaa::parse_and_verify(state, vaa, ctx); - vaa::assert_governance(state, &vaa); - vaa::replay_protect(state, &vaa); - - do_upgrade(state, parse_payload(vaa::destroy(vaa)), ctx) - } - - fun do_upgrade(state: &mut State, upgrade: GuardianSetUpgrade, ctx: &TxContext) { - let current_index = state::get_current_guardian_set_index(state); - - let GuardianSetUpgrade { - new_index, - guardians, - } = upgrade; - - assert!( - u32::to_u64(new_index) == u32::to_u64(current_index) + 1, - E_NON_INCREMENTAL_GUARDIAN_SETS - ); - - state::update_guardian_set_index(state, new_index); - state::store_guardian_set(state, new_index, create_guardian_set(new_index, guardians)); - state::expire_guardian_set(state, current_index, ctx); - } - - #[test_only] - public fun do_upgrade_test(s: &mut State, new_index: U32, guardians: vector, ctx: &mut TxContext) { - do_upgrade(s, GuardianSetUpgrade { new_index, guardians }, ctx) - } - - public fun parse_payload(bytes: vector): GuardianSetUpgrade { - let cur = cursor::cursor_init(bytes); - let guardians = vector::empty(); - - let target_module = deserialize::deserialize_vector(&mut cur, 32); - let expected_module = x"00000000000000000000000000000000000000000000000000000000436f7265"; // Core - assert!(target_module == expected_module, E_INVALID_MODULE); - - let action = deserialize::deserialize_u8(&mut cur); - assert!(action == 0x02, E_INVALID_ACTION); - - let chain = deserialize::deserialize_u16(&mut cur); - assert!(chain == u16::from_u64(0x00), E_INVALID_TARGET); - - let new_index = deserialize::deserialize_u32(&mut cur); - let guardian_len = deserialize::deserialize_u8(&mut cur); - - while (guardian_len > 0) { - let key = deserialize::deserialize_vector(&mut cur, 20); - vector::push_back(&mut guardians, create_guardian(key)); - guardian_len = guardian_len - 1; - }; - - cursor::destroy_empty(cur); - - GuardianSetUpgrade { - new_index: new_index, - guardians: guardians, - } - } - - #[test_only] - public fun split(upgrade: GuardianSetUpgrade): (U32, vector) { - let GuardianSetUpgrade { new_index, guardians } = upgrade; - (new_index, guardians) - } -} - -#[test_only] -module wormhole::guardian_set_upgrade_test { - use std::vector; - - use wormhole::structs::{create_guardian}; - use wormhole::guardian_set_upgrade; - use wormhole::myu32::{Self as u32}; - - use sui::test_scenario::{Self, Scenario, next_tx, take_shared, return_shared, ctx}; - use sui::tx_context::{increment_epoch_number}; - - fun scenario(): Scenario { test_scenario::begin(@0x123233) } - fun people(): (address, address, address) { (@0x124323, @0xE05, @0xFACE) } - - #[test] - public fun test_parse_guardian_set_upgrade() { - use wormhole::myu32::{Self as u32}; - - let b = x"00000000000000000000000000000000000000000000000000000000436f7265020000000000011358cc3ae5c097b213ce3c81979e1b9f9570746aa5ff6cb952589bde862c25ef4392132fb9d4a42157114de8460193bdf3a2fcf81f86a09765f4762fd1107a0086b32d7a0977926a205131d8731d39cbeb8c82b2fd82faed2711d59af0f2499d16e726f6b211b39756c042441be6d8650b69b54ebe715e234354ce5b4d348fb74b958e8966e2ec3dbd4958a7cdeb5f7389fa26941519f0863349c223b73a6ddee774a3bf913953d695260d88bc1aa25a4eee363ef0000ac0076727b35fbea2dac28fee5ccb0fea768eaf45ced136b9d9e24903464ae889f5c8a723fc14f93124b7c738843cbb89e864c862c38cddcccf95d2cc37a4dc036a8d232b48f62cdd4731412f4890da798f6896a3331f64b48c12d1d57fd9cbe7081171aa1be1d36cafe3867910f99c09e347899c19c38192b6e7387ccd768277c17dab1b7a5027c0b3cf178e21ad2e77ae06711549cfbb1f9c7a9d8096e85e1487f35515d02a92753504a8d75471b9f49edb6fbebc898f403e4773e95feb15e80c9a99c8348d"; - let (new_index, guardians) = guardian_set_upgrade::split(guardian_set_upgrade::parse_payload(b)); - assert!(new_index == u32::from_u64(1), 0); - assert!(vector::length(&guardians) == 19, 0); - let expected = vector[ - create_guardian(x"58cc3ae5c097b213ce3c81979e1b9f9570746aa5"), - create_guardian(x"ff6cb952589bde862c25ef4392132fb9d4a42157"), - create_guardian(x"114de8460193bdf3a2fcf81f86a09765f4762fd1"), - create_guardian(x"107a0086b32d7a0977926a205131d8731d39cbeb"), - create_guardian(x"8c82b2fd82faed2711d59af0f2499d16e726f6b2"), - create_guardian(x"11b39756c042441be6d8650b69b54ebe715e2343"), - create_guardian(x"54ce5b4d348fb74b958e8966e2ec3dbd4958a7cd"), - create_guardian(x"eb5f7389fa26941519f0863349c223b73a6ddee7"), - create_guardian(x"74a3bf913953d695260d88bc1aa25a4eee363ef0"), - create_guardian(x"000ac0076727b35fbea2dac28fee5ccb0fea768e"), - create_guardian(x"af45ced136b9d9e24903464ae889f5c8a723fc14"), - create_guardian(x"f93124b7c738843cbb89e864c862c38cddcccf95"), - create_guardian(x"d2cc37a4dc036a8d232b48f62cdd4731412f4890"), - create_guardian(x"da798f6896a3331f64b48c12d1d57fd9cbe70811"), - create_guardian(x"71aa1be1d36cafe3867910f99c09e347899c19c3"), - create_guardian(x"8192b6e7387ccd768277c17dab1b7a5027c0b3cf"), - create_guardian(x"178e21ad2e77ae06711549cfbb1f9c7a9d8096e8"), - create_guardian(x"5e1487f35515d02a92753504a8d75471b9f49edb"), - create_guardian(x"6fbebc898f403e4773e95feb15e80c9a99c8348d"), - ]; - assert!(expected == guardians, 0); - } - - #[test] - public fun test_guardian_set_expiry() { - use wormhole::state::{State, Self as worm_state}; - use wormhole::test_state::{init_wormhole_state}; - - let (admin, _, _) = people(); - let test = init_wormhole_state(scenario(), admin); - - next_tx(&mut test, admin);{ - let state = take_shared(&test); - let first_index = worm_state::get_current_guardian_set_index(&state); - let guardian_set = worm_state::get_guardian_set(&state, first_index); - // make sure guardian set is active - assert!(worm_state::guardian_set_is_active(&state, &guardian_set, ctx(&mut test)), 0); - - // do an upgrade - guardian_set_upgrade::do_upgrade_test( - &mut state, - u32::from_u64(1), - vector[create_guardian(x"71aa1be1d36cafe3867910f99c09e347899c19c3")], // new guardian set - ctx(&mut test), - ); - - // make sure old guardian set is still active - guardian_set = worm_state::get_guardian_set(&state, first_index); - assert!(worm_state::guardian_set_is_active(&state, &guardian_set, ctx(&mut test)), 0); - - // fast forward time beyond expiration - - // increment by 3 epochs - increment_epoch_number(ctx(&mut test)); - increment_epoch_number(ctx(&mut test)); - increment_epoch_number(ctx(&mut test)); - - // make sure old guardian set is no longer active - assert!(!worm_state::guardian_set_is_active(&state, &guardian_set, ctx(&mut test)), 0); - - return_shared(state); - }; - - test_scenario::end(test); - } - -} diff --git a/sui/wormhole/sources/keccack256.move b/sui/wormhole/sources/keccack256.move deleted file mode 100644 index 2842fadce..000000000 --- a/sui/wormhole/sources/keccack256.move +++ /dev/null @@ -1,16 +0,0 @@ -module wormhole::keccak256 { - use sui::ecdsa_k1::{Self as ecdsa}; - - spec module { - pragma verify=false; - } - - public fun keccak256(bytes: vector): vector { - ecdsa::keccak256(&bytes) - } - - spec keccak256 { - pragma opaque; - } - -} diff --git a/sui/wormhole/sources/migrate.move b/sui/wormhole/sources/migrate.move new file mode 100644 index 000000000..a7f59eeb4 --- /dev/null +++ b/sui/wormhole/sources/migrate.move @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements a public method intended to be called after an +/// upgrade has been commited. The purpose is to add one-off migration logic +/// that would alter Wormhole `State`. +/// +/// Included in migration is the ability to ensure that breaking changes for +/// any of Wormhole's methods by enforcing the current build version as their +/// required minimum version. +module wormhole::migrate { + use sui::clock::{Clock}; + use sui::object::{ID}; + + use wormhole::governance_message::{Self}; + use wormhole::state::{Self, State}; + use wormhole::upgrade_contract::{Self}; + use wormhole::vaa::{Self}; + + /// Event reflecting when `migrate` is successfully executed. + struct MigrateComplete has drop, copy { + package: ID + } + + /// Execute migration logic. See `wormhole::migrate` description for more + /// info. + public fun migrate( + wormhole_state: &mut State, + upgrade_vaa_buf: vector, + the_clock: &Clock + ) { + state::migrate__v__0_2_0(wormhole_state); + + // Perform standard migrate. + handle_migrate(wormhole_state, upgrade_vaa_buf, the_clock); + + //////////////////////////////////////////////////////////////////////// + // + // NOTE: Put any one-off migration logic here. + // + // Most upgrades likely won't need to do anything, in which case the + // rest of this function's body may be empty. Make sure to delete it + // after the migration has gone through successfully. + // + // WARNING: The migration does *not* proceed atomically with the + // upgrade (as they are done in separate transactions). + // If the nature of this migration absolutely requires the migration to + // happen before certain other functionality is available, then guard + // that functionality with the `assert!` from above. + // + //////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////// + } + + fun handle_migrate( + wormhole_state: &mut State, + upgrade_vaa_buf: vector, + the_clock: &Clock + ) { + // Update the version first. + // + // See `version_control` module for hard-coded configuration. + state::migrate_version(wormhole_state); + + // This VAA needs to have been used for upgrading this package. + // + // NOTE: All of the following methods have protections to make sure that + // the current build is used. Given that we officially migrated the + // version as the first call of `migrate`, these should be successful. + + // First we need to check that `parse_and_verify` still works. + let verified_vaa = + vaa::parse_and_verify(wormhole_state, upgrade_vaa_buf, the_clock); + + // And governance methods. + let ticket = upgrade_contract::authorize_governance(wormhole_state); + let receipt = + governance_message::verify_vaa( + wormhole_state, + verified_vaa, + ticket + ); + + // This capability ensures that the current build version is used. + let latest_only = state::assert_latest_only(wormhole_state); + + // Check if build digest is the current one. + let digest = + upgrade_contract::take_digest( + governance_message::payload(&receipt) + ); + state::assert_authorized_digest(&latest_only, wormhole_state, digest); + governance_message::destroy(receipt); + + // Finally emit an event reflecting a successful migrate. + let package = state::current_package(&latest_only, wormhole_state); + sui::event::emit(MigrateComplete { package }); + } + + #[test_only] + public fun set_up_migrate(wormhole_state: &mut State) { + state::reverse_migrate__v__dummy(wormhole_state); + } +} + +#[test_only] +module wormhole::migrate_tests { + use sui::test_scenario::{Self}; + + use wormhole::state::{Self}; + use wormhole::wormhole_scenario::{ + person, + return_clock, + return_state, + set_up_wormhole, + take_clock, + take_state, + upgrade_wormhole + }; + + const UPGRADE_VAA: vector = + x"01000000000100db695668c0c91f4df6e4106dcb912d9062898fd976d631ff1c1b4109ccd203b43cd2419c7d9a191f8d42a780419e63307aacc93080d8629c6c03061c52becf1d0100bc614e000000000001000000000000000000000000000000000000000000000000000000000000000400000000000000010100000000000000000000000000000000000000000000000000000000436f726501001500000000000000000000000000000000000000000000006e6577206275696c64"; + + #[test] + fun test_migrate() { + use wormhole::migrate::{migrate}; + + let user = person(); + let my_scenario = test_scenario::begin(user); + let scenario = &mut my_scenario; + + // Initialize Wormhole. + let wormhole_message_fee = 350; + set_up_wormhole(scenario, wormhole_message_fee); + + // Next transaction should be conducted as an ordinary user. + test_scenario::next_tx(scenario, user); + + // Upgrade (digest is just b"new build") for testing purposes. + upgrade_wormhole(scenario); + + // Ignore effects. + test_scenario::next_tx(scenario, user); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + // Set up migrate (which prepares this package to be the same state as + // a previous release). + wormhole::migrate::set_up_migrate(&mut worm_state); + + // Conveniently roll version back. + state::reverse_migrate_version(&mut worm_state); + + // Simulate executing with an outdated build by upticking the minimum + // required version for `publish_message` to something greater than + // this build. + migrate(&mut worm_state, UPGRADE_VAA, &the_clock); + + // Make sure we emitted an event. + let effects = test_scenario::next_tx(scenario, user); + assert!(test_scenario::num_user_events(&effects) == 1, 0); + + // Clean up. + return_state(worm_state); + return_clock(the_clock); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + #[expected_failure(abort_code = wormhole::package_utils::E_INCORRECT_OLD_VERSION)] + /// ^ This expected error may change depending on the migration. In most + /// cases, this will abort with `wormhole::package_utils::E_INCORRECT_OLD_VERSION`. + fun test_cannot_migrate_again() { + use wormhole::migrate::{migrate}; + + let user = person(); + let my_scenario = test_scenario::begin(user); + let scenario = &mut my_scenario; + + // Initialize Wormhole. + let wormhole_message_fee = 350; + set_up_wormhole(scenario, wormhole_message_fee); + + // Next transaction should be conducted as an ordinary user. + test_scenario::next_tx(scenario, user); + + // Upgrade (digest is just b"new build") for testing purposes. + upgrade_wormhole(scenario); + + // Ignore effects. + test_scenario::next_tx(scenario, user); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + // Set up migrate (which prepares this package to be the same state as + // a previous release). + wormhole::migrate::set_up_migrate(&mut worm_state); + + // Conveniently roll version back. + state::reverse_migrate_version(&mut worm_state); + + // Simulate executing with an outdated build by upticking the minimum + // required version for `publish_message` to something greater than + // this build. + migrate(&mut worm_state, UPGRADE_VAA, &the_clock); + + // Make sure we emitted an event. + let effects = test_scenario::next_tx(scenario, user); + assert!(test_scenario::num_user_events(&effects) == 1, 0); + + // You shall not pass! + migrate(&mut worm_state, UPGRADE_VAA, &the_clock); + + abort 42 + } +} diff --git a/sui/wormhole/sources/myu16.move b/sui/wormhole/sources/myu16.move deleted file mode 100644 index e8ee6f263..000000000 --- a/sui/wormhole/sources/myu16.move +++ /dev/null @@ -1,40 +0,0 @@ -module wormhole::myu16 { - - const MAX_U16: u64 = (1 << 16) - 1; - - const E_OVERFLOW: u64 = 0x0; - - struct U16 has store, copy, drop { - number: u64 - } - - fun check_overflow(u: &U16) { - assert!(u.number <= MAX_U16, E_OVERFLOW) - } - - public fun from_u64(number: u64): U16 { - let u = U16 { number }; - check_overflow(&u); - u - } - - public fun to_u64(u: U16): u64 { - u.number - } - - public fun split_u8(number: U16): (u8, u8) { - let U16 { number } = number; - let v0: u8 = ((number >> 8) % (0xFF + 1) as u8); - let v1: u8 = (number % (0xFF + 1) as u8); - (v0, v1) - } - - #[test] - public fun test_split_u8() { - let u = from_u64(0x1234); - let (v0, v1) = split_u8(u); - assert!(v0 == 0x12, 0); - assert!(v1 == 0x34, 0); - } - -} \ No newline at end of file diff --git a/sui/wormhole/sources/myu256.move b/sui/wormhole/sources/myu256.move deleted file mode 100644 index 6629875ef..000000000 --- a/sui/wormhole/sources/myu256.move +++ /dev/null @@ -1,888 +0,0 @@ -/// The implementation of large numbers written in Move language. -/// Code derived from original work by Andrew Poelstra -/// -/// Rust Bitcoin Library -/// Written in 2014 by -/// Andrew Poelstra -/// -/// To the extent possible under law, the author(s) have dedicated all -/// copyright and related and neighboring rights to this software to -/// the public domain worldwide. This software is distributed without -/// any warranty. -/// -/// Simplified impl by Parity Team - https://github.com/paritytech/parity-common/blob/master/uint/src/uint.rs -/// -/// Features: -/// * mul -/// * div -/// * add -/// * sub -/// * shift left -/// * shift right -/// * compare -/// * if math overflows the contract crashes. -/// -/// Would be nice to help with the following TODO list: -/// * pow() , sqrt(). -/// * math funcs that don't abort on overflows, but just returns reminders. -/// * Export of low_u128 (see original implementation). -/// * Export of low_u64 (see original implementation). -/// * Gas Optimisation: -/// * We can optimize by replacing bytecode, as far as we know Move VM itself support slices, so probably -/// we can try to replace parts works with (`v0`,`v1`,`v2`,`v3` etc) works. -/// * More? -/// * More tests (see current tests and TODOs i left): -/// * u256_arithmetic_test - https://github.com/paritytech/bigint/blob/master/src/uint.rs#L1338 -/// * More from - https://github.com/paritytech/bigint/blob/master/src/uint.rs -/// * Division: -/// * Could be improved with div_mod_small (current version probably would took a lot of resources for small numbers). -/// * Also could be improved with Knuth, TAOCP, Volume 2, section 4.3.1, Algorithm D (see link to Parity above). -module wormhole::myu256 { - // Errors. - /// When can't cast `U256` to `u128` (e.g. number too large). - const ECAST_OVERFLOW: u64 = 0; - - /// When trying to get or put word into U256 but it's out of index. - const EWORDS_OVERFLOW: u64 = 1; - - /// When math overflows. - const EOVERFLOW: u64 = 2; - - /// When attempted to divide by zero. - const EDIV_BY_ZERO: u64 = 3; - - /// TODO: removed some functionality that the prover was breaking on. - /// In order to keep the functions backwards compatible, we keep the - /// signatures but revert immediately. - /// A better solution would be figuring out a way to skip checking them - /// in the prover, and just restore the original functionality. - const EUNSUPPORTED: u64 = 4; - - // Constants. - - /// Max `u64` value. - const U64_MAX: u128 = 18446744073709551615; - - /// Max `u128` value. - const U128_MAX: u128 = 340282366920938463463374607431768211455; - - /// Total words in `U256` (64 * 4 = 256). - const WORDS: u64 = 4; - - /// When both `U256` equal. - const EQUAL: u8 = 0; - - /// When `a` is less than `b`. - const LESS_THAN: u8 = 1; - - /// When `b` is greater than `b`. - const GREATER_THAN: u8 = 2; - - // Data structs. - - /// The `U256` resource. - /// Contains 4 u64 numbers. - struct U256 has copy, drop, store { - v0: u64, - v1: u64, - v2: u64, - v3: u64, - } - - /// Double `U256` used for multiple (to store overflow). - struct DU256 has copy, drop, store { - v0: u64, - v1: u64, - v2: u64, - v3: u64, - v4: u64, - v5: u64, - v6: u64, - v7: u64, - } - - // Public functions. - /// Adds two `U256` and returns sum. - public fun add(a: U256, b: U256): U256 { - let ret = zero(); - let carry = 0u64; - - let i = 0; - while (i < WORDS) { - let a1 = get(&a, i); - let b1 = get(&b, i); - - if (carry != 0) { - let (res1, is_overflow1) = overflowing_add(a1, b1); - let (res2, is_overflow2) = overflowing_add(res1, carry); - put(&mut ret, i, res2); - - carry = 0; - if (is_overflow1) { - carry = carry + 1; - }; - - if (is_overflow2) { - carry = carry + 1; - } - } else { - let (res, is_overflow) = overflowing_add(a1, b1); - put(&mut ret, i, res); - - carry = 0; - if (is_overflow) { - carry = 1; - }; - }; - - i = i + 1; - }; - - assert!(carry == 0, EOVERFLOW); - - ret - } - - /// Convert `U256` to `u128` value if possible (otherwise it aborts). - public fun as_u128(a: U256): u128 { - assert!(a.v2 == 0 && a.v3 == 0, ECAST_OVERFLOW); - ((a.v1 as u128) << 64) + (a.v0 as u128) - } - - /// Convert `U256` to `u64` value if possible (otherwise it aborts). - public fun as_u64(a: U256): u64 { - assert!(a.v1 == 0 && a.v2 == 0 && a.v3 == 0, ECAST_OVERFLOW); - a.v0 - } - - /// Compares two `U256` numbers. - public fun compare(a: &U256, b: &U256): u8 { - let i = WORDS; - while (i > 0) { - i = i - 1; - let a1 = get(a, i); - let b1 = get(b, i); - - if (a1 != b1) { - if (a1 < b1) { - return LESS_THAN - } else { - return GREATER_THAN - } - } - }; - - EQUAL - } - - /// Returns a `U256` from `u64` value. - public fun from_u64(val: u64): U256 { - from_u128((val as u128)) - } - - /// Returns a `U256` from `u128` value. - public fun from_u128(val: u128): U256 { - let (a2, a1) = split_u128(val); - - U256 { - v0: a1, - v1: a2, - v2: 0, - v3: 0, - } - } - - /// Multiples two `U256`. - public fun mul(a: U256, b: U256): U256 { - let ret = DU256 { - v0: 0, - v1: 0, - v2: 0, - v3: 0, - v4: 0, - v5: 0, - v6: 0, - v7: 0, - }; - - let i = 0; - while (i < WORDS) { - let carry = 0u64; - let b1 = get(&b, i); - - let j = 0; - while (j < WORDS) { - let a1 = get(&a, j); - - if (a1 != 0 || carry != 0) { - let (hi, low) = split_u128((a1 as u128) * (b1 as u128)); - - let overflow = { - let existing_low = get_d(&ret, i + j); - let (low, o) = overflowing_add(low, existing_low); - put_d(&mut ret, i + j, low); - if (o) { - 1 - } else { - 0 - } - }; - - carry = { - let existing_hi = get_d(&ret, i + j + 1); - let hi = hi + overflow; - let (hi, o0) = overflowing_add(hi, carry); - let (hi, o1) = overflowing_add(hi, existing_hi); - put_d(&mut ret, i + j + 1, hi); - - if (o0 || o1) { - 1 - } else { - 0 - } - }; - }; - - j = j + 1; - }; - - i = i + 1; - }; - - let (r, overflow) = du256_to_u256(ret); - assert!(!overflow, EOVERFLOW); - r - } - - /// Subtracts two `U256`, returns result. - public fun sub(a: U256, b: U256): U256 { - let ret = zero(); - - let carry = 0u64; - - let i = 0; - while (i < WORDS) { - let a1 = get(&a, i); - let b1 = get(&b, i); - - if (carry != 0) { - let (res1, is_overflow1) = overflowing_sub(a1, b1); - let (res2, is_overflow2) = overflowing_sub(res1, carry); - put(&mut ret, i, res2); - - carry = 0; - if (is_overflow1) { - carry = carry + 1; - }; - - if (is_overflow2) { - carry = carry + 1; - } - } else { - let (res, is_overflow) = overflowing_sub(a1, b1); - put(&mut ret, i, res); - - carry = 0; - if (is_overflow) { - carry = 1; - }; - }; - - i = i + 1; - }; - - assert!(carry == 0, EOVERFLOW); - ret - } - - public fun div(_a: U256, _b: U256): U256 { - abort EUNSUPPORTED - } - - /// Shift right `a` by `shift`. - public fun shr(a: U256, shift: u8): U256 { - let ret = zero(); - - let word_shift = (shift as u64) / 64; - let bit_shift = (shift as u64) % 64; - - let i = word_shift; - while (i < WORDS) { - let m = get(&a, i) >> (bit_shift as u8); - put(&mut ret, i - word_shift, m); - i = i + 1; - }; - - if (bit_shift > 0) { - let j = word_shift + 1; - while (j < WORDS) { - let m = get(&ret, j - word_shift - 1) + (get(&a, j) << (64 - (bit_shift as u8))); - put(&mut ret, j - word_shift - 1, m); - j = j + 1; - }; - }; - - ret - } - - /// Shift left `a` by `shift`. - public fun shl(a: U256, shift: u8): U256 { - let ret = zero(); - - let word_shift = (shift as u64) / 64; - let bit_shift = (shift as u64) % 64; - - let i = word_shift; - while (i < WORDS) { - let m = get(&a, i - word_shift) << (bit_shift as u8); - put(&mut ret, i, m); - i = i + 1; - }; - - if (bit_shift > 0) { - let j = word_shift + 1; - - while (j < WORDS) { - let m = get(&ret, j) + (get(&a, j - 1 - word_shift) >> (64 - (bit_shift as u8))); - put(&mut ret, j, m); - j = j + 1; - }; - }; - - ret - } - - /// Returns `U256` equals to zero. - public fun zero(): U256 { - U256 { - v0: 0, - v1: 0, - v2: 0, - v3: 0, - } - } - - // Private functions. - - /// Similar to Rust `overflowing_add`. - /// Returns a tuple of the addition along with a boolean indicating whether an arithmetic overflow would occur. - /// If an overflow would have occurred then the wrapped value is returned. - fun overflowing_add(a: u64, b: u64): (u64, bool) { - let a128 = (a as u128); - let b128 = (b as u128); - - let r = a128 + b128; - if (r > U64_MAX) { - // overflow - let overflow = r - U64_MAX - 1; - ((overflow as u64), true) - } else { - (((a128 + b128) as u64), false) - } - } - - /// Similar to Rust `overflowing_sub`. - /// Returns a tuple of the addition along with a boolean indicating whether an arithmetic overflow would occur. - /// If an overflow would have occurred then the wrapped value is returned. - fun overflowing_sub(a: u64, b: u64): (u64, bool) { - if (a < b) { - let r = b - a; - ((U64_MAX as u64) - r + 1, true) - } else { - (a - b, false) - } - } - - /// Extracts two `u64` from `a` `u128`. - fun split_u128(a: u128): (u64, u64) { - let a1 = ((a >> 64) as u64); - let a2 = ((a % (0xFFFFFFFFFFFFFFFF + 1)) as u64); - - (a1, a2) - } - - /// Get word from `a` by index `i`. - public fun get(a: &U256, i: u64): u64 { - if (i == 0) { - a.v0 - } else if (i == 1) { - a.v1 - } else if (i == 2) { - a.v2 - } else if (i == 3) { - a.v3 - } else { - abort EWORDS_OVERFLOW - } - } - - /// Get word from `DU256` by index. - fun get_d(a: &DU256, i: u64): u64 { - if (i == 0) { - a.v0 - } else if (i == 1) { - a.v1 - } else if (i == 2) { - a.v2 - } else if (i == 3) { - a.v3 - } else if (i == 4) { - a.v4 - } else if (i == 5) { - a.v5 - } else if (i == 6) { - a.v6 - } else if (i == 7) { - a.v7 - } else { - abort EWORDS_OVERFLOW - } - } - - /// Put new word `val` into `U256` by index `i`. - fun put(a: &mut U256, i: u64, val: u64) { - if (i == 0) { - a.v0 = val; - } else if (i == 1) { - a.v1 = val; - } else if (i == 2) { - a.v2 = val; - } else if (i == 3) { - a.v3 = val; - } else { - abort EWORDS_OVERFLOW - } - } - - /// Put new word into `DU256` by index `i`. - fun put_d(a: &mut DU256, i: u64, val: u64) { - if (i == 0) { - a.v0 = val; - } else if (i == 1) { - a.v1 = val; - } else if (i == 2) { - a.v2 = val; - } else if (i == 3) { - a.v3 = val; - } else if (i == 4) { - a.v4 = val; - } else if (i == 5) { - a.v5 = val; - } else if (i == 6) { - a.v6 = val; - } else if (i == 7) { - a.v7 = val; - } else { - abort EWORDS_OVERFLOW - } - } - - /// Convert `DU256` to `U256`. - fun du256_to_u256(a: DU256): (U256, bool) { - let b = U256 { - v0: a.v0, - v1: a.v1, - v2: a.v2, - v3: a.v3, - }; - - let overflow = false; - if (a.v4 != 0 || a.v5 != 0 || a.v6 != 0 || a.v7 != 0) { - overflow = true; - }; - - (b, overflow) - } - - // Tests. - #[test] - fun test_get_d() { - let a = DU256 { - v0: 1, - v1: 2, - v2: 3, - v3: 4, - v4: 5, - v5: 6, - v6: 7, - v7: 8, - }; - - assert!(get_d(&a, 0) == 1, 0); - assert!(get_d(&a, 1) == 2, 1); - assert!(get_d(&a, 2) == 3, 2); - assert!(get_d(&a, 3) == 4, 3); - assert!(get_d(&a, 4) == 5, 4); - assert!(get_d(&a, 5) == 6, 5); - assert!(get_d(&a, 6) == 7, 6); - assert!(get_d(&a, 7) == 8, 7); - } - - #[test] - #[expected_failure(abort_code = 1, location=wormhole::myu256)] - fun test_get_d_overflow() { - let a = DU256 { - v0: 1, - v1: 2, - v2: 3, - v3: 4, - v4: 5, - v5: 6, - v6: 7, - v7: 8, - }; - - get_d(&a, 8); - } - - #[test] - fun test_put_d() { - let a = DU256 { - v0: 1, - v1: 2, - v2: 3, - v3: 4, - v4: 5, - v5: 6, - v6: 7, - v7: 8, - }; - - put_d(&mut a, 0, 10); - put_d(&mut a, 1, 20); - put_d(&mut a, 2, 30); - put_d(&mut a, 3, 40); - put_d(&mut a, 4, 50); - put_d(&mut a, 5, 60); - put_d(&mut a, 6, 70); - put_d(&mut a, 7, 80); - - assert!(get_d(&a, 0) == 10, 0); - assert!(get_d(&a, 1) == 20, 1); - assert!(get_d(&a, 2) == 30, 2); - assert!(get_d(&a, 3) == 40, 3); - assert!(get_d(&a, 4) == 50, 4); - assert!(get_d(&a, 5) == 60, 5); - assert!(get_d(&a, 6) == 70, 6); - assert!(get_d(&a, 7) == 80, 7); - } - - #[test] - #[expected_failure(abort_code = 1, location=wormhole::myu256)] - fun test_put_d_overflow() { - let a = DU256 { - v0: 1, - v1: 2, - v2: 3, - v3: 4, - v4: 5, - v5: 6, - v6: 7, - v7: 8, - }; - - put_d(&mut a, 8, 0); - } - - #[test] - fun test_du256_to_u256() { - let a = DU256 { - v0: 255, - v1: 100, - v2: 50, - v3: 300, - v4: 0, - v5: 0, - v6: 0, - v7: 0, - }; - - let (m, overflow) = du256_to_u256(a); - assert!(!overflow, 0); - assert!(m.v0 == a.v0, 1); - assert!(m.v1 == a.v1, 2); - assert!(m.v2 == a.v2, 3); - assert!(m.v3 == a.v3, 4); - - a.v4 = 100; - a.v5 = 5; - - let (m, overflow) = du256_to_u256(a); - assert!(overflow, 5); - assert!(m.v0 == a.v0, 6); - assert!(m.v1 == a.v1, 7); - assert!(m.v2 == a.v2, 8); - assert!(m.v3 == a.v3, 9); - } - - #[test] - fun test_get() { - let a = U256 { - v0: 1, - v1: 2, - v2: 3, - v3: 4, - }; - - assert!(get(&a, 0) == 1, 0); - assert!(get(&a, 1) == 2, 1); - assert!(get(&a, 2) == 3, 2); - assert!(get(&a, 3) == 4, 3); - } - - #[test] - #[expected_failure(abort_code = 1, location=wormhole::myu256)] - fun test_get_aborts() { - let _ = get(&zero(), 4); - } - - #[test] - fun test_put() { - let a = zero(); - put(&mut a, 0, 255); - assert!(get(&a, 0) == 255, 0); - - put(&mut a, 1, (U64_MAX as u64)); - assert!(get(&a, 1) == (U64_MAX as u64), 1); - - put(&mut a, 2, 100); - assert!(get(&a, 2) == 100, 2); - - put(&mut a, 3, 3); - assert!(get(&a, 3) == 3, 3); - - put(&mut a, 2, 0); - assert!(get(&a, 2) == 0, 4); - } - - #[test] - #[expected_failure(abort_code = 1, location=wormhole::myu256)] - fun test_put_overflow() { - let a = zero(); - put(&mut a, 6, 255); - } - - #[test] - fun test_from_u128() { - let i = 0; - while (i < 1024) { - let big = from_u128(i); - assert!(as_u128(big) == i, 0); - i = i + 1; - }; - } - - #[test] - fun test_add() { - let a = from_u128(1000); - let b = from_u128(500); - - let s = as_u128(add(a, b)); - assert!(s == 1500, 0); - - a = from_u128(U64_MAX); - b = from_u128(U64_MAX); - - s = as_u128(add(a, b)); - assert!(s == (U64_MAX + U64_MAX), 1); - } - - #[test] - #[expected_failure(abort_code = 2, location=wormhole::myu256)] - fun test_add_overflow() { - let max = (U64_MAX as u64); - - let a = U256 { - v0: max, - v1: max, - v2: max, - v3: max - }; - - let _ = add(a, from_u128(1)); - } - - #[test] - fun test_sub() { - let a = from_u128(1000); - let b = from_u128(500); - - let s = as_u128(sub(a, b)); - assert!(s == 500, 0); - } - - #[test] - #[expected_failure(abort_code = 2, location=wormhole::myu256)] - fun test_sub_overflow() { - let a = from_u128(0); - let b = from_u128(1); - - let _ = sub(a, b); - } - - #[test] - #[expected_failure(abort_code = 0, location=wormhole::myu256)] - fun test_too_big_to_cast_to_u128() { - let a = from_u128(U128_MAX); - let b = from_u128(U128_MAX); - - let _ = as_u128(add(a, b)); - } - - #[test] - fun test_overflowing_add() { - let (n, z) = overflowing_add(10, 10); - assert!(n == 20, 0); - assert!(!z, 1); - - (n, z) = overflowing_add((U64_MAX as u64), 1); - assert!(n == 0, 2); - assert!(z, 3); - - (n, z) = overflowing_add((U64_MAX as u64), 10); - assert!(n == 9, 4); - assert!(z, 5); - - (n, z) = overflowing_add(5, 8); - assert!(n == 13, 6); - assert!(!z, 7); - } - - #[test] - fun test_overflowing_sub() { - let (n, z) = overflowing_sub(10, 5); - assert!(n == 5, 0); - assert!(!z, 1); - - (n, z) = overflowing_sub(0, 1); - assert!(n == (U64_MAX as u64), 2); - assert!(z, 3); - - (n, z) = overflowing_sub(10, 10); - assert!(n == 0, 4); - assert!(!z, 5); - } - - #[test] - fun test_split_u128() { - let (a1, a2) = split_u128(100); - assert!(a1 == 0, 0); - assert!(a2 == 100, 1); - - (a1, a2) = split_u128(U64_MAX + 1); - assert!(a1 == 1, 2); - assert!(a2 == 0, 3); - } - - #[test] - fun test_mul() { - let a = from_u128(285); - let b = from_u128(375); - - let c = as_u128(mul(a, b)); - assert!(c == 106875, 0); - - a = from_u128(0); - b = from_u128(1); - - c = as_u128(mul(a, b)); - - assert!(c == 0, 1); - - a = from_u128(U64_MAX); - b = from_u128(2); - - c = as_u128(mul(a, b)); - - assert!(c == 36893488147419103230, 2); - } - - #[test] - #[expected_failure(abort_code = 2, location=wormhole::myu256)] - fun test_mul_overflow() { - let max = (U64_MAX as u64); - - let a = U256 { - v0: max, - v1: max, - v2: max, - v3: max, - }; - - let _ = mul(a, from_u128(2)); - } - - #[test] - fun test_zero() { - let a = as_u128(zero()); - assert!(a == 0, 0); - - let a = zero(); - assert!(a.v0 == 0, 1); - assert!(a.v1 == 0, 2); - assert!(a.v2 == 0, 3); - assert!(a.v3 == 0, 4); - } - - #[test] - fun test_from_u64() { - let a = as_u128(from_u64(100)); - assert!(a == 100, 0); - - // TODO: more tests. - } - - #[test] - fun test_compare() { - let a = from_u128(1000); - let b = from_u128(50); - - let cmp = compare(&a, &b); - assert!(cmp == 2, 0); - - a = from_u128(100); - b = from_u128(100); - cmp = compare(&a, &b); - - assert!(cmp == 0, 1); - - a = from_u128(50); - b = from_u128(75); - - cmp = compare(&a, &b); - assert!(cmp == 1, 2); - } - - #[test] - fun test_shift_left() { - let a = from_u128(100); - let b = shl(a, 2); - - assert!(as_u128(b) == 400, 0); - - // TODO: more shift left tests. - } - - #[test] - fun test_shift_right() { - let a = from_u128(100); - let b = shr(a, 2); - - assert!(as_u128(b) == 25, 0); - - // TODO: more shift right tests. - } - - #[test] - fun test_as_u64() { - let _ = as_u64(from_u64((U64_MAX as u64))); - let _ = as_u64(from_u128(1)); - } - - #[test] - #[expected_failure(abort_code=0, location=wormhole::myu256)] - fun test_as_u64_overflow() { - let _ = as_u64(from_u128(U128_MAX)); - } - -} \ No newline at end of file diff --git a/sui/wormhole/sources/myu32.move b/sui/wormhole/sources/myu32.move deleted file mode 100644 index dfda10ccf..000000000 --- a/sui/wormhole/sources/myu32.move +++ /dev/null @@ -1,44 +0,0 @@ -module wormhole::myu32 { - - const MAX_U32: u64 = (1 << 32) - 1; - - const E_OVERFLOW: u64 = 0x0; - - struct U32 has store, copy, drop { - number: u64 - } - - fun check_overflow(u: &U32) { - assert!(u.number <= MAX_U32, E_OVERFLOW) - } - - public fun from_u64(number: u64): U32 { - let u = U32 { number }; - check_overflow(&u); - u - } - - public fun to_u64(u: U32): u64 { - u.number - } - - public fun split_u8(number: U32): (u8, u8, u8, u8) { - let U32 { number } = number; - let v0: u8 = ((number >> 24) % (0xFF + 1) as u8); - let v1: u8 = ((number >> 16) % (0xFF + 1) as u8); - let v2: u8 = ((number >> 8) % (0xFF + 1) as u8); - let v3: u8 = (number % (0xFF + 1) as u8); - (v0, v1, v2, v3) - } - - #[test] - public fun test_split_u8() { - let u = from_u64(0x12345678); - let (v0, v1, v2, v3) = split_u8(u); - assert!(v0 == 0x12, 0); - assert!(v1 == 0x34, 0); - assert!(v2 == 0x56, 0); - assert!(v3 == 0x78, 0); - } - -} \ No newline at end of file diff --git a/sui/wormhole/sources/myvaa.move b/sui/wormhole/sources/myvaa.move deleted file mode 100644 index cfb56e40b..000000000 --- a/sui/wormhole/sources/myvaa.move +++ /dev/null @@ -1,548 +0,0 @@ -module wormhole::myvaa { - use std::vector; - use sui::tx_context::TxContext; - //use 0x1::secp256k1; - - use wormhole::myu16::{U16}; - use wormhole::myu32::{U32}; - use wormhole::deserialize; - use wormhole::cursor; - use wormhole::guardian_pubkey; - use wormhole::structs::{ - Guardian, - GuardianSet, - Signature, - create_signature, - get_guardians, - unpack_signature, - get_address, - }; - use wormhole::state::{Self, State}; - use wormhole::external_address::{Self, ExternalAddress}; - use wormhole::keccak256::keccak256; - - friend wormhole::guardian_set_upgrade; - //friend wormhole::contract_upgrade; - - const E_NO_QUORUM: u64 = 0x0; - const E_TOO_MANY_SIGNATURES: u64 = 0x1; - const E_INVALID_SIGNATURE: u64 = 0x2; - const E_GUARDIAN_SET_EXPIRED: u64 = 0x3; - const E_INVALID_GOVERNANCE_CHAIN: u64 = 0x4; - const E_INVALID_GOVERNANCE_EMITTER: u64 = 0x5; - const E_WRONG_VERSION: u64 = 0x6; - const E_NON_INCREASING_SIGNERS: u64 = 0x7; - const E_OLD_GUARDIAN_SET_GOVERNANCE: u64 = 0x8; - - struct VAA { - /// Header - guardian_set_index: U32, - signatures: vector, - - /// Body - timestamp: U32, - nonce: U32, - emitter_chain: U16, - emitter_address: ExternalAddress, - sequence: u64, - consistency_level: u8, - hash: vector, // 32 bytes - payload: vector, // variable bytes - } - - //break - - #[test_only] - public fun parse_test(bytes: vector): VAA { - parse(bytes) - } - - /// Parses a VAA. - /// Does not do any verification, and is thus private. - /// This ensures the invariant that if an external module receives a `VAA` - /// object, its signatures must have been verified, because the only public - /// function that returns a VAA is `parse_and_verify` - fun parse(bytes: vector): VAA { - let cur = cursor::cursor_init(bytes); - let version = deserialize::deserialize_u8(&mut cur); - assert!(version == 1, E_WRONG_VERSION); - let guardian_set_index = deserialize::deserialize_u32(&mut cur); - - let signatures_len = deserialize::deserialize_u8(&mut cur); - let signatures = vector::empty(); - - while (signatures_len > 0) { - let guardian_index = deserialize::deserialize_u8(&mut cur); - let sig = deserialize::deserialize_vector(&mut cur, 64); - let recovery_id = deserialize::deserialize_u8(&mut cur); - vector::push_back(&mut signatures, create_signature(sig, recovery_id, guardian_index)); - signatures_len = signatures_len - 1; - }; - - let body = cursor::rest(cur); - let hash = keccak256(keccak256(body)); - - let cur = cursor::cursor_init(body); - - let timestamp = deserialize::deserialize_u32(&mut cur); - let nonce = deserialize::deserialize_u32(&mut cur); - let emitter_chain = deserialize::deserialize_u16(&mut cur); - let emitter_address = external_address::deserialize(&mut cur); - let sequence = deserialize::deserialize_u64(&mut cur); - let consistency_level = deserialize::deserialize_u8(&mut cur); - - let payload = cursor::rest(cur); - - VAA { - guardian_set_index, - signatures, - timestamp, - nonce, - emitter_chain, - emitter_address, - sequence, - consistency_level, - hash, - payload, - } - } - - public fun get_guardian_set_index(vaa: &VAA): U32 { - vaa.guardian_set_index - } - - public fun get_timestamp(vaa: &VAA): U32 { - vaa.timestamp - } - - public fun get_payload(vaa: &VAA): vector { - vaa.payload - } - - public fun get_hash(vaa: &VAA): vector { - vaa.hash - } - - public fun get_emitter_chain(vaa: &VAA): U16 { - vaa.emitter_chain - } - - public fun get_emitter_address(vaa: &VAA): ExternalAddress { - vaa.emitter_address - } - - public fun get_sequence(vaa: &VAA): u64 { - vaa.sequence - } - - public fun get_consistency_level(vaa: &VAA): u8 { - vaa.consistency_level - } - - // break - - public fun destroy(vaa: VAA): vector { - let VAA { - guardian_set_index: _, - signatures: _, - timestamp: _, - nonce: _, - emitter_chain: _, - emitter_address: _, - sequence: _, - consistency_level: _, - hash: _, - payload, - } = vaa; - payload - } - - /// Verifies the signatures of a VAA. - /// It's private, because there's no point calling it externally, since VAAs - /// external to this module have already been verified (by construction). - fun verify(vaa: &VAA, state: &State, guardian_set: &GuardianSet, ctx: &TxContext) { - assert!(state::guardian_set_is_active(state, guardian_set, ctx), E_GUARDIAN_SET_EXPIRED); - - let guardians = get_guardians(guardian_set); - let hash = vaa.hash; - let sigs_len = vector::length(&vaa.signatures); - let guardians_len = vector::length(&guardians); - - assert!(sigs_len >= quorum(guardians_len), E_NO_QUORUM); - - let sig_i = 0; - let last_index = 0; - while (sig_i < sigs_len) { - let (sig, recovery_id, guardian_index) = unpack_signature(vector::borrow(&vaa.signatures, sig_i)); - - // Ensure that the provided signatures are strictly increasing. - // This check makes sure that no duplicate signers occur. The - // increasing order is guaranteed by the guardians, or can always be - // reordered by the client. - assert!(sig_i == 0 || guardian_index > last_index, E_NON_INCREASING_SIGNERS); - last_index = guardian_index; - - let address = guardian_pubkey::from_signature(hash, recovery_id, sig); - - let cur_guardian = vector::borrow(&guardians, (guardian_index as u64)); - let cur_address = get_address(cur_guardian); - - assert!(address == cur_address, E_INVALID_SIGNATURE); - - sig_i = sig_i + 1; - }; - } - - /// Parses and verifies the signatures of a VAA. - /// NOTE: this is the only public function that returns a VAA, and it should - /// be kept that way. This ensures that if an external module receives a - /// `VAA`, it has been verified. - public fun parse_and_verify(state: &mut State, bytes: vector, ctx: &TxContext): VAA { - let vaa = parse(bytes); - let guardian_set = state::get_guardian_set(state, vaa.guardian_set_index); - verify(&vaa, state, &guardian_set, ctx); - vaa - } - - /// Gets a VAA payload without doing verififcation on the VAA. This method is - /// used for convenience in the Coin package, for example, for creating new tokens - /// with asset metadata in a token attestation VAA payload. - public fun parse_and_get_payload(bytes: vector): vector { - let vaa = parse(bytes); - let payload = destroy(vaa); - return payload - } - - /// Aborts if the VAA is not governance (i.e. sent from the governance - /// emitter on the governance chain) - public fun assert_governance(state: &State, vaa: &VAA) { - let latest_guardian_set_index = state::get_current_guardian_set_index(state); - assert!(vaa.guardian_set_index == latest_guardian_set_index, E_OLD_GUARDIAN_SET_GOVERNANCE); - assert!(vaa.emitter_chain == state::get_governance_chain(state), E_INVALID_GOVERNANCE_CHAIN); - assert!(vaa.emitter_address == state::get_governance_contract(state), E_INVALID_GOVERNANCE_EMITTER); - } - - /// Aborts if the VAA has already been consumed. Marks the VAA as consumed - /// the first time around. - /// Only to be used for core bridge messages. Protocols should implement - /// their own replay protection. - public(friend) fun replay_protect(state: &mut State, vaa: &VAA) { - // this calls table::add which aborts if the key already exists - state::set_governance_action_consumed(state, vaa.hash); - } - - /// Returns the minimum number of signatures required for a VAA to be valid. - public fun quorum(num_guardians: u64): u64 { - (num_guardians * 2) / 3 + 1 - } - -} - -// tests -// - do_upgrade (upgrade active guardian set to new set) - -// TODO: fast forward test, check that previous guardian set gets expired -// TODO: adapt the tests from the aptos contracts test suite -#[test_only] -module wormhole::vaa_test { - use sui::test_scenario::{Self, Scenario, next_tx, ctx, take_shared, return_shared}; - use sui::tx_context::{increment_epoch_number}; - - fun scenario(): Scenario { test_scenario::begin(@0x123233) } - fun people(): (address, address, address) { (@0x124323, @0xE05, @0xFACE) } - - use wormhole::guardian_set_upgrade::{Self, do_upgrade_test}; - use wormhole::state::{Self, State}; - use wormhole::test_state::{init_wormhole_state}; - use wormhole::structs::{Self, create_guardian}; - use wormhole::myu32::{Self as u32}; - use wormhole::myvaa::{Self as vaa}; - - /// A test VAA signed by the first guardian set (index 0) containing guardian a single - /// guardian beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe - /// It's a governance VAA (contract upgrade), so we can test all sorts of - /// properties - const GOV_VAA: vector = x"010000000001000da16466429ee8ffb09b90ca90db8326d20cfeeae0542da9dcaaad641a5aca2d6c1fe33a5970ca84fd0ff5e6d29ef9e40404eb1a8892b509f085fc725b9e23a30100000001000000010001000000000000000000000000000000000000000000000000000000000000000400000000020b10360000000000000000000000000000000000000000000000000000000000436f7265010016d8f30e4a345ea0fa5df11daac4e1866ee368d253209cf9eda012d915a2db09e6"; - - /// Identical VAA except it's signed by guardian set 1, and double signed by - /// beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe - /// Used to test that a single guardian can't supply multiple signatures - const GOV_VAA_DOUBLE_SIGNED: vector = x"010000000102000da16466429ee8ffb09b90ca90db8326d20cfeeae0542da9dcaaad641a5aca2d6c1fe33a5970ca84fd0ff5e6d29ef9e40404eb1a8892b509f085fc725b9e23a301000da16466429ee8ffb09b90ca90db8326d20cfeeae0542da9dcaaad641a5aca2d6c1fe33a5970ca84fd0ff5e6d29ef9e40404eb1a8892b509f085fc725b9e23a30100000001000000010001000000000000000000000000000000000000000000000000000000000000000400000000020b10360000000000000000000000000000000000000000000000000000000000436f7265010016d8f30e4a345ea0fa5df11daac4e1866ee368d253209cf9eda012d915a2db09e6"; - - /// A test VAA signed by the second guardian set (index 1) with the following two guardians: - /// 0: beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe - /// 1: 90F8bf6A479f320ead074411a4B0e7944Ea8c9C1 - const GOV_VAA_2: vector = x"0100000001020052da07c7ba7d58661e22922a1130e75732f454e81086330f9a5337797ee7ee9d703fd55aabc257c4d53d8ab1e471e4eb1f2767bf37cc6d3d6774e2ca3ab429eb00018c9859f14027c2a62563028a2a9bbb30464ce5b86d13728b02fb85b34761d258154bb59bad87908c9b09342efa9045d4420d289bb0144729eb368ec50c45e719010000000100000001000100000000000000000000000000000000000000000000000000000000000000040000000004cdedc90000000000000000000000000000000000000000000000000000000000436f72650100167759324e86f870265b8648ef8d5ef505b2ae99840a616081eb7adc13995204a4"; - - #[test] - fun test_upgrade_guardian() { - test_upgrade_guardian_(scenario()) - } - - fun test_upgrade_guardian_(test: Scenario) { - let (admin, _, _) = people(); - test = init_wormhole_state(test, admin); - next_tx(&mut test, admin);{ - let state = take_shared(&mut test); - let new_guardians = vector[structs::create_guardian(x"71aa1be1d36cafe3867910f99c09e347899c19c4")]; - // upgrade guardian set - do_upgrade_test(&mut state, u32::from_u64(1), new_guardians, ctx(&mut test)); - assert!(state::get_current_guardian_set_index(&state)==u32::from_u64(1), 0); - return_shared(state); - }; - test_scenario::end(test); - } - - #[test] - /// Ensures that the GOV_VAA can still be verified after the guardian set - /// upgrade before expiry - public fun test_guardian_set_not_expired() { - let (admin, _, _) = people(); - let test = init_wormhole_state(scenario(), admin); - - next_tx(&mut test, admin);{ - let state = take_shared(&test); - - // do an upgrade - guardian_set_upgrade::do_upgrade_test( - &mut state, - u32::from_u64(1), - vector[create_guardian(x"71aa1be1d36cafe3867910f99c09e347899c19c3")], - ctx(&mut test) - ); - - // fast forward time before expiration - increment_epoch_number(ctx(&mut test)); - - // we still expect this to verify - vaa::destroy(vaa::parse_and_verify(&mut state, GOV_VAA, ctx(&mut test))); - return_shared(state); - }; - test_scenario::end(test); - } - - #[test] - #[expected_failure(abort_code = vaa::E_GUARDIAN_SET_EXPIRED)] - /// Ensures that the GOV_VAA can no longer be verified after the guardian set - /// upgrade after expiry - public fun test_guardian_set_expired() { - let (admin, _, _) = people(); - let test = init_wormhole_state(scenario(), admin); - - next_tx(&mut test, admin);{ - let state = take_shared(&test); - - // do an upgrade - guardian_set_upgrade::do_upgrade_test( - &mut state, - u32::from_u64(1), - vector[create_guardian(x"71aa1be1d36cafe3867910f99c09e347899c19c3")], - ctx(&mut test) - ); - - // fast forward time beyond expiration - increment_epoch_number(ctx(&mut test)); - increment_epoch_number(ctx(&mut test)); - increment_epoch_number(ctx(&mut test)); - - // we expect this to fail because guardian set has expired - vaa::destroy(vaa::parse_and_verify(&mut state, GOV_VAA, ctx(&mut test))); - return_shared(state); - }; - test_scenario::end(test); - } - - #[test] - #[expected_failure(abort_code = vaa::E_OLD_GUARDIAN_SET_GOVERNANCE)] - /// Ensures that governance GOV_VAAs can only be verified by the latest guardian - /// set, even if the signer hasn't expired yet - public fun test_governance_guardian_set_latest() { - let (admin, _, _) = people(); - let test = init_wormhole_state(scenario(), admin); - - next_tx(&mut test, admin);{ - let state = take_shared(&test); - - // do an upgrade - guardian_set_upgrade::do_upgrade_test( - &mut state, - u32::from_u64(1), - vector[create_guardian(x"71aa1be1d36cafe3867910f99c09e347899c19c3")], - ctx(&mut test) - ); - - // fast forward time before expiration - increment_epoch_number(ctx(&mut test)); - - //still expect this to verify - let vaa = vaa::parse_and_verify(&mut state, GOV_VAA, ctx(&mut test)); - - // expect this to fail - vaa::assert_governance(&mut state, &vaa); - - vaa::destroy(vaa); - - return_shared(state); - }; - test_scenario::end(test); - } - - #[test] - #[expected_failure(abort_code = vaa::E_INVALID_GOVERNANCE_EMITTER)] - /// Ensures that governance GOV_VAAs can only be sent from the correct governance emitter - public fun test_invalid_governance_emitter() { - let (admin, _, _) = people(); - let test = init_wormhole_state(scenario(), admin); - - next_tx(&mut test, admin);{ - let state = take_shared(&test); - state::set_governance_contract(&mut state, x"0000000000000000000000000000000000000000000000000000000000000005"); // set emitter contract to wrong contract - - // expect this to succeed - let vaa = vaa::parse_and_verify(&mut state, GOV_VAA, ctx(&mut test)); - - // expect this to fail - vaa::assert_governance(&mut state, &vaa); - - vaa::destroy(vaa); - - return_shared(state); - }; - test_scenario::end(test); - } - - #[test] - #[expected_failure(abort_code = vaa::E_INVALID_GOVERNANCE_CHAIN)] - /// Ensures that governance GOV_VAAs can only be sent from the correct governance chain - public fun test_invalid_governance_chain() { - let (admin, _, _) = people(); - let test = init_wormhole_state(scenario(), admin); - - next_tx(&mut test, admin);{ - let state = take_shared(&test); - state::set_governance_chain_id(&mut state, 200); // set governance chain to wrong chain - - // expect this to succeed - let vaa = vaa::parse_and_verify(&mut state, GOV_VAA, ctx(&mut test)); - - // expect this to fail - vaa::assert_governance(&mut state, &vaa); - - vaa::destroy(vaa); - - return_shared(state); - }; - test_scenario::end(test); - } - - #[test] - public fun test_quorum() { - let (admin, _, _) = people(); - let test = init_wormhole_state(scenario(), admin); - - next_tx(&mut test, admin);{ - let state = take_shared(&test); - - // do an upgrade - guardian_set_upgrade::do_upgrade_test( - &mut state, - u32::from_u64(1), - vector[ - create_guardian(x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"), - create_guardian(x"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1") - ], - ctx(&mut test), - ); - - // we expect this to succeed because both guardians signed in the correct order - vaa::destroy(vaa::parse_and_verify(&mut state, GOV_VAA_2, ctx(&mut test))); - return_shared(state); - }; - test_scenario::end(test); - } - - #[test] - #[expected_failure(abort_code = vaa::E_NO_QUORUM)] - public fun test_no_quorum() { - let (admin, _, _) = people(); - let test = init_wormhole_state(scenario(), admin); - - next_tx(&mut test, admin);{ - let state = take_shared(&test); - - // do an upgrade - guardian_set_upgrade::do_upgrade_test( - &mut state, - u32::from_u64(1), - vector[ - create_guardian(x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"), - create_guardian(x"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"), - create_guardian(x"5e1487f35515d02a92753504a8d75471b9f49edb") - ], - ctx(&mut test), - ); - - // we expect this to fail because not enough signatures - vaa::destroy(vaa::parse_and_verify(&mut state, GOV_VAA_2, ctx(&mut test))); - return_shared(state); - }; - test_scenario::end(test); - } - - #[test] - #[expected_failure(abort_code = vaa::E_NON_INCREASING_SIGNERS)] - public fun test_double_signed() { - let (admin, _, _) = people(); - let test = init_wormhole_state(scenario(), admin); - - next_tx(&mut test, admin);{ - let state = take_shared(&test); - - // do an upgrade - guardian_set_upgrade::do_upgrade_test( - &mut state, - u32::from_u64(1), - vector[ - create_guardian(x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"), - create_guardian(x"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"), - ], - ctx(&mut test), - ); - - // we expect this to fail because - // beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe signed this twice - vaa::destroy(vaa::parse_and_verify(&mut state, GOV_VAA_DOUBLE_SIGNED, ctx(&mut test))); - return_shared(state); - }; - test_scenario::end(test); - } - - #[test] - #[expected_failure(abort_code = vaa::E_INVALID_SIGNATURE)] - public fun test_out_of_order_signers() { - let (admin, _, _) = people(); - let test = init_wormhole_state(scenario(), admin); - - next_tx(&mut test, admin);{ - let state = take_shared(&test); - - // do an upgrade - guardian_set_upgrade::do_upgrade_test( - &mut state, - u32::from_u64(1), - vector[ - // guardians are set up in opposite order - create_guardian(x"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"), - create_guardian(x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"), - ], - ctx(&mut test), - ); - - // we expect this to fail because signatures are out of order - vaa::destroy(vaa::parse_and_verify(&mut state, GOV_VAA_2, ctx(&mut test))); - return_shared(state); - }; - test_scenario::end(test); - } - -} diff --git a/sui/wormhole/sources/publish_message.move b/sui/wormhole/sources/publish_message.move new file mode 100644 index 000000000..1a394c464 --- /dev/null +++ b/sui/wormhole/sources/publish_message.move @@ -0,0 +1,426 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements two methods: `prepare_message` and `publish_message`, +/// which are to be executed in a transaction block in this order. +/// +/// `prepare_message` allows a contract to pack Wormhole message info (payload +/// that has meaning to an integrator plus nonce) in preparation to publish a +/// `WormholeMessage` event via `publish_message`. Only the owner of an +/// `EmitterCap` has the capability of creating this `MessageTicket`. +/// +/// `publish_message` unpacks the `MessageTicket` and emits a +/// `WormholeMessage` with this message info and timestamp. This event is +/// observed by the Guardian network. +/// +/// The purpose of splitting this message publishing into two steps is in case +/// Wormhole needs to be upgraded and there is a breaking change for this +/// module, an integrator would not be left broken. It is discouraged to put +/// `publish_message` in an integrator's package logic. Otherwise, this +/// integrator needs to be prepared to upgrade his contract to handle the latest +/// version of `publish_message`. +/// +/// Instead, an integtrator is encouraged to execute a transaction block, which +/// executes `publish_message` using the latest Wormhole package ID and to +/// implement `prepare_message` in his contract to produce `MessageTicket`, +/// which `publish_message` consumes. +module wormhole::publish_message { + use sui::coin::{Self, Coin}; + use sui::clock::{Self, Clock}; + use sui::object::{Self, ID}; + use sui::sui::{SUI}; + + use wormhole::emitter::{Self, EmitterCap}; + use wormhole::state::{Self, State}; + + /// This type is emitted via `sui::event` module. Guardians pick up this + /// observation and attest to its existence. + struct WormholeMessage has drop, copy { + /// `EmitterCap` object ID. + sender: ID, + /// From `EmitterCap`. + sequence: u64, + /// A.K.A. Batch ID. + nonce: u32, + /// Arbitrary message data relevant to integrator. + payload: vector, + /// This will always be `0`. + consistency_level: u8, + /// `Clock` timestamp. + timestamp: u64 + } + + /// This type represents Wormhole message data. The sender is the object ID + /// of an `EmitterCap`, who acts as the capability of creating this type. + /// The only way to destroy this type is calling `publish_message` with + /// a fee to emit a `WormholeMessage` with the unpacked members of this + /// struct. + struct MessageTicket { + /// `EmitterCap` object ID. + sender: ID, + /// From `EmitterCap`. + sequence: u64, + /// A.K.A. Batch ID. + nonce: u32, + /// Arbitrary message data relevant to integrator. + payload: vector + } + + /// `prepare_message` constructs Wormhole message parameters. An + /// `EmitterCap` provides the capability to send an arbitrary payload. + /// + /// NOTE: Integrators of Wormhole should be calling only this method from + /// their contracts. This method is not guarded by version control (thus not + /// requiring a reference to the Wormhole `State` object), so it is intended + /// to work for any package version. + public fun prepare_message( + emitter_cap: &mut EmitterCap, + nonce: u32, + payload: vector + ): MessageTicket { + // Produce sequence number for this message. This will also be the + // return value for this method. + let sequence = emitter::use_sequence(emitter_cap); + + MessageTicket { + sender: object::id(emitter_cap), + sequence, + nonce, + payload + } + } + + /// `publish_message` emits a message as a Sui event. This method uses the + /// input `EmitterCap` as the registered sender of the + /// `WormholeMessage`. It also produces a new sequence for this emitter. + /// + /// NOTE: This method is guarded by a minimum build version check. This + /// method could break backward compatibility on an upgrade. + /// + /// It is important for integrators to refrain from calling this method + /// within their contracts. This method is meant to be called in a + /// tranasction block after receiving a `MessageTicket` from calling + /// `prepare_message` within a contract. If in a circumstance where this + /// module has a breaking change in an upgrade, `prepare_message` will not + /// be affected by this change. + /// + /// See `prepare_message` for more details. + public fun publish_message( + wormhole_state: &mut State, + message_fee: Coin, + prepared_msg: MessageTicket, + the_clock: &Clock + ): u64 { + // This capability ensures that the current build version is used. + let latest_only = state::assert_latest_only(wormhole_state); + + // Deposit `message_fee`. This method interacts with the `FeeCollector`, + // which will abort if `message_fee` does not equal the collector's + // expected fee amount. + state::deposit_fee( + &latest_only, + wormhole_state, + coin::into_balance(message_fee) + ); + + let MessageTicket { + sender, + sequence, + nonce, + payload + } = prepared_msg; + + // Truncate to seconds. + let timestamp = clock::timestamp_ms(the_clock) / 1000; + + // Sui is an instant finality chain, so we don't need confirmations. + let consistency_level = 0; + + // Emit Sui event with `WormholeMessage`. + sui::event::emit( + WormholeMessage { + sender, + sequence, + nonce, + payload, + consistency_level, + timestamp + } + ); + + // Done. + sequence + } + + #[test_only] + public fun destroy(prepared_msg: MessageTicket) { + let MessageTicket { + sender: _, + sequence: _, + nonce: _, + payload: _ + } = prepared_msg; + } +} + +#[test_only] +module wormhole::publish_message_tests { + use sui::coin::{Self}; + use sui::test_scenario::{Self}; + + use wormhole::emitter::{Self, EmitterCap}; + use wormhole::fee_collector::{Self}; + use wormhole::state::{Self}; + use wormhole::version_control::{Self}; + use wormhole::wormhole_scenario::{ + person, + return_clock, + return_state, + set_up_wormhole, + take_clock, + take_state, + upgrade_wormhole + }; + + #[test] + /// This test verifies that `publish_message` is successfully called when + /// the specified message fee is used. + fun test_publish_message() { + use wormhole::publish_message::{prepare_message, publish_message}; + + let user = person(); + let my_scenario = test_scenario::begin(user); + let scenario = &mut my_scenario; + + let wormhole_message_fee = 100000000; + + // Initialize Wormhole. + set_up_wormhole(scenario, wormhole_message_fee); + + // Next transaction should be conducted as an ordinary user. + test_scenario::next_tx(scenario, user); + + { + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + // User needs an `EmitterCap` so he can send a message. + let emitter_cap = + wormhole::emitter::new( + &worm_state, + test_scenario::ctx(scenario) + ); + + // Check for event corresponding to new emitter. + let effects = test_scenario::next_tx(scenario, user); + assert!(test_scenario::num_user_events(&effects) == 1, 0); + + // Prepare message. + let msg = + prepare_message( + &mut emitter_cap, + 0, // nonce + b"Hello World" + ); + + // Finally publish Wormhole message. + let sequence = + publish_message( + &mut worm_state, + coin::mint_for_testing( + wormhole_message_fee, + test_scenario::ctx(scenario) + ), + msg, + &the_clock + ); + assert!(sequence == 0, 0); + + // Prepare another message. + let msg = + prepare_message( + &mut emitter_cap, + 0, // nonce + b"Hello World... again" + ); + + // Publish again to check sequence uptick. + let another_sequence = + publish_message( + &mut worm_state, + coin::mint_for_testing( + wormhole_message_fee, + test_scenario::ctx(scenario) + ), + msg, + &the_clock + ); + assert!(another_sequence == 1, 0); + + // Clean up. + return_state(worm_state); + return_clock(the_clock); + sui::transfer::public_transfer(emitter_cap, user); + }; + + // Grab the `TransactionEffects` of the previous transaction. + let effects = test_scenario::next_tx(scenario, user); + + // We expect two events (the Wormhole messages). `test_scenario` does + // not give us an in-depth view of the event specifically. But we can + // check that there was an event associated with the previous + // transaction. + assert!(test_scenario::num_user_events(&effects) == 2, 0); + + // Simulate upgrade and confirm that publish message still works. + { + upgrade_wormhole(scenario); + + // Ignore effects from upgrade. + test_scenario::next_tx(scenario, user); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + let emitter_cap = + test_scenario::take_from_sender(scenario); + + let msg = + prepare_message( + &mut emitter_cap, + 0, // nonce + b"Hello?" + ); + + let sequence = + publish_message( + &mut worm_state, + coin::mint_for_testing( + wormhole_message_fee, + test_scenario::ctx(scenario) + ), + msg, + &the_clock + ); + assert!(sequence == 2, 0); + + // Clean up. + test_scenario::return_to_sender(scenario, emitter_cap); + return_state(worm_state); + return_clock(the_clock); + }; + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + #[expected_failure(abort_code = fee_collector::E_INCORRECT_FEE)] + /// This test verifies that `publish_message` fails when the fee is not the + /// correct amount. `FeeCollector` will be the reason for this abort. + fun test_cannot_publish_message_with_incorrect_fee() { + use wormhole::publish_message::{prepare_message, publish_message}; + + let user = person(); + let my_scenario = test_scenario::begin(user); + let scenario = &mut my_scenario; + + let wormhole_message_fee = 100000000; + let wrong_fee_amount = wormhole_message_fee - 1; + + // Initialize Wormhole. + set_up_wormhole(scenario, wormhole_message_fee); + + // Next transaction should be conducted as an ordinary user. + test_scenario::next_tx(scenario, user); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + // User needs an `EmitterCap` so he can send a message. + let emitter_cap = + emitter::new(&worm_state, test_scenario::ctx(scenario)); + + let msg = + prepare_message( + &mut emitter_cap, + 0, // nonce + b"Hello World" + ); + // You shall not pass! + publish_message( + &mut worm_state, + coin::mint_for_testing( + wrong_fee_amount, + test_scenario::ctx(scenario) + ), + msg, + &the_clock + ); + + // Clean up. + emitter::destroy_test_only(emitter_cap); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = wormhole::package_utils::E_NOT_CURRENT_VERSION)] + /// This test verifies that `publish_message` will fail if the minimum + /// required version is greater than the current build's. + fun test_cannot_publish_message_outdated_version() { + use wormhole::publish_message::{prepare_message, publish_message}; + + let user = person(); + let my_scenario = test_scenario::begin(user); + let scenario = &mut my_scenario; + + let wormhole_message_fee = 100000000; + + // Initialize Wormhole. + set_up_wormhole(scenario, wormhole_message_fee); + + // Next transaction should be conducted as an ordinary user. + test_scenario::next_tx(scenario, user); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + // User needs an `EmitterCap` so he can send a message. + let emitter_cap = + emitter::new(&worm_state, test_scenario::ctx(scenario)); + + // Conveniently roll version back. + state::reverse_migrate_version(&mut worm_state); + + // Simulate executing with an outdated build by upticking the minimum + // required version for `publish_message` to something greater than + // this build. + state::migrate_version_test_only( + &mut worm_state, + version_control::previous_version_test_only(), + version_control::next_version() + ); + + let msg = + prepare_message( + &mut emitter_cap, + 0, // nonce + b"Hello World", + ); + + // You shall not pass! + publish_message( + &mut worm_state, + coin::mint_for_testing( + wormhole_message_fee, + test_scenario::ctx(scenario) + ), + msg, + &the_clock + ); + + // Clean up. + emitter::destroy_test_only(emitter_cap); + + abort 42 + } +} diff --git a/sui/wormhole/sources/resources/consumed_vaas.move b/sui/wormhole/sources/resources/consumed_vaas.move new file mode 100644 index 000000000..a09327cf7 --- /dev/null +++ b/sui/wormhole/sources/resources/consumed_vaas.move @@ -0,0 +1,29 @@ +module wormhole::consumed_vaas { + use sui::tx_context::{TxContext}; + + use wormhole::bytes32::{Bytes32}; + use wormhole::set::{Self, Set}; + + /// Container storing VAA hashes (digests). This will be checked against in + /// `parse_verify_and_consume` so a particular VAA cannot be replayed. It + /// is up to the integrator to have this container live in his contract + /// in order to take advantage of this no-replay protection. Or an + /// integrator can implement his own method to prevent replay. + struct ConsumedVAAs has store { + hashes: Set + } + + public fun new(ctx: &mut TxContext): ConsumedVAAs { + ConsumedVAAs { hashes: set::new(ctx) } + } + + public fun consume(self: &mut ConsumedVAAs, digest: Bytes32) { + set::add(&mut self.hashes, digest); + } + + #[test_only] + public fun destroy(consumed: ConsumedVAAs) { + let ConsumedVAAs { hashes } = consumed; + set::destroy(hashes); + } +} diff --git a/sui/wormhole/sources/resources/fee_collector.move b/sui/wormhole/sources/resources/fee_collector.move new file mode 100644 index 000000000..f75ab4971 --- /dev/null +++ b/sui/wormhole/sources/resources/fee_collector.move @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements a container that collects fees in SUI denomination. +/// The `FeeCollector` requires that the fee deposited is exactly equal to the +/// `fee_amount` configured. +module wormhole::fee_collector { + use sui::balance::{Self, Balance}; + use sui::coin::{Self, Coin}; + use sui::sui::{SUI}; + use sui::tx_context::{TxContext}; + + /// Amount deposited is not exactly the amount configured. + const E_INCORRECT_FEE: u64 = 0; + + /// Container for configured `fee_amount` and `balance` of SUI collected. + struct FeeCollector has store { + fee_amount: u64, + balance: Balance + } + + /// Create new `FeeCollector` with specified amount to collect. + public fun new(fee_amount: u64): FeeCollector { + FeeCollector { fee_amount, balance: balance::zero() } + } + + /// Retrieve configured amount to collect. + public fun fee_amount(self: &FeeCollector): u64 { + self.fee_amount + } + + /// Retrieve current SUI balance. + public fun balance_value(self: &FeeCollector): u64 { + balance::value(&self.balance) + } + + /// Take `Balance` and add it to current collected balance. + public fun deposit_balance(self: &mut FeeCollector, fee: Balance) { + assert!(balance::value(&fee) == self.fee_amount, E_INCORRECT_FEE); + balance::join(&mut self.balance, fee); + } + + /// Take `Coin` and add it to current collected balance. + public fun deposit(self: &mut FeeCollector, fee: Coin) { + deposit_balance(self, coin::into_balance(fee)) + } + + /// Create `Balance` of some `amount` by taking from collected balance. + public fun withdraw_balance( + self: &mut FeeCollector, + amount: u64 + ): Balance { + // This will trigger `sui::balance::ENotEnough` if amount > balance. + balance::split(&mut self.balance, amount) + } + + /// Create `Coin` of some `amount` by taking from collected balance. + public fun withdraw( + self: &mut FeeCollector, + amount: u64, + ctx: &mut TxContext + ): Coin { + coin::from_balance(withdraw_balance(self, amount), ctx) + } + + /// Re-configure current `fee_amount`. + public fun change_fee(self: &mut FeeCollector, new_amount: u64) { + self.fee_amount = new_amount; + } + + #[test_only] + public fun destroy(collector: FeeCollector) { + let FeeCollector { fee_amount: _, balance: bal } = collector; + balance::destroy_for_testing(bal); + } +} + +#[test_only] +module wormhole::fee_collector_tests { + use sui::coin::{Self}; + use sui::tx_context::{Self}; + + use wormhole::fee_collector::{Self}; + + #[test] + public fun test_fee_collector() { + let ctx = &mut tx_context::dummy(); + + let fee_amount = 350; + let collector = fee_collector::new(fee_amount); + + // We expect the fee_amount to be the same as what we specified and + // no balance on `FeeCollector` yet. + assert!(fee_collector::fee_amount(&collector) == fee_amount, 0); + assert!(fee_collector::balance_value(&collector) == 0, 0); + + // Deposit fee once. + let fee = coin::mint_for_testing(fee_amount, ctx); + fee_collector::deposit(&mut collector, fee); + assert!(fee_collector::balance_value(&collector) == fee_amount, 0); + + // Now deposit nine more times and check the aggregate balance. + let i = 0; + while (i < 9) { + let fee = coin::mint_for_testing(fee_amount, ctx); + fee_collector::deposit(&mut collector, fee); + i = i + 1; + }; + let total = fee_collector::balance_value(&collector); + assert!(total == 10 * fee_amount, 0); + + // Withdraw a fifth. + let withdraw_amount = total / 5; + let withdrawn = + fee_collector::withdraw(&mut collector, withdraw_amount, ctx); + assert!(coin::value(&withdrawn) == withdraw_amount, 0); + coin::burn_for_testing(withdrawn); + + let remaining = fee_collector::balance_value(&collector); + assert!(remaining == total - withdraw_amount, 0); + + // Withdraw remaining. + let withdrawn = fee_collector::withdraw(&mut collector, remaining, ctx); + assert!(coin::value(&withdrawn) == remaining, 0); + coin::burn_for_testing(withdrawn); + + // There shouldn't be anything left in `FeeCollector`. + assert!(fee_collector::balance_value(&collector) == 0, 0); + + // Done. + fee_collector::destroy(collector); + } + + #[test] + #[expected_failure(abort_code = fee_collector::E_INCORRECT_FEE)] + public fun test_cannot_deposit_incorrect_fee() { + let ctx = &mut tx_context::dummy(); + + let fee_amount = 350; + let collector = fee_collector::new(fee_amount); + + // You shall not pass! + let fee = coin::mint_for_testing(fee_amount + 1, ctx); + fee_collector::deposit(&mut collector, fee); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = sui::balance::ENotEnough)] + public fun test_cannot_withdraw_more_than_balance() { + let ctx = &mut tx_context::dummy(); + + let fee_amount = 350; + let collector = fee_collector::new(fee_amount); + + // Deposit once. + let fee = coin::mint_for_testing(fee_amount, ctx); + fee_collector::deposit(&mut collector, fee); + + // Attempt to withdraw more than the balance. + let bal = fee_collector::balance_value(&collector); + let withdrawn = + fee_collector::withdraw(&mut collector, bal + 1, ctx); + + // Shouldn't get here. But we need to clean up anyway. + coin::burn_for_testing(withdrawn); + + abort 42 + } +} diff --git a/sui/wormhole/sources/resources/guardian.move b/sui/wormhole/sources/resources/guardian.move new file mode 100644 index 000000000..84c6a48eb --- /dev/null +++ b/sui/wormhole/sources/resources/guardian.move @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements a `Guardian` that warehouses a 20-byte public key. +module wormhole::guardian { + use std::vector::{Self}; + use sui::hash::{Self}; + use sui::ecdsa_k1::{Self}; + + use wormhole::bytes20::{Self, Bytes20}; + use wormhole::guardian_signature::{Self, GuardianSignature}; + + /// Guardian public key is all zeros. + const E_ZERO_ADDRESS: u64 = 1; + + /// Container for 20-byte Guardian public key. + struct Guardian has store { + pubkey: Bytes20 + } + + /// Create new `Guardian` ensuring that the input is not all zeros. + public fun new(pubkey: vector): Guardian { + let data = bytes20::new(pubkey); + assert!(bytes20::is_nonzero(&data), E_ZERO_ADDRESS); + Guardian { pubkey: data } + } + + /// Retrieve underlying 20-byte public key. + public fun pubkey(self: &Guardian): Bytes20 { + self.pubkey + } + + /// Retrieve underlying 20-byte public key as `vector`. + public fun as_bytes(self: &Guardian): vector { + bytes20::data(&self.pubkey) + } + + /// Verify that the recovered public key (using `ecrecover`) equals the one + /// that exists for this Guardian with an elliptic curve signature and raw + /// message that was signed. + public fun verify( + self: &Guardian, + signature: GuardianSignature, + message_hash: vector + ): bool { + let sig = guardian_signature::to_rsv(signature); + as_bytes(self) == ecrecover(message_hash, sig) + } + + /// Same as 'ecrecover' in EVM. + fun ecrecover(message: vector, sig: vector): vector { + let pubkey = + ecdsa_k1::decompress_pubkey(&ecdsa_k1::secp256k1_ecrecover(&sig, &message, 0)); + + // `decompress_pubkey` returns 65 bytes. The last 64 bytes are what we + // need to compute the Guardian's public key. + vector::remove(&mut pubkey, 0); + + let hash = hash::keccak256(&pubkey); + let guardian_pubkey = vector::empty(); + let (i, n) = (0, bytes20::length()); + while (i < n) { + vector::push_back( + &mut guardian_pubkey, + vector::pop_back(&mut hash) + ); + i = i + 1; + }; + vector::reverse(&mut guardian_pubkey); + + guardian_pubkey + } + + #[test_only] + public fun destroy(g: Guardian) { + let Guardian { pubkey: _ } = g; + } + + #[test_only] + public fun to_bytes(value: Guardian): vector { + let Guardian { pubkey } = value; + bytes20::to_bytes(pubkey) + } +} diff --git a/sui/wormhole/sources/resources/guardian_set.move b/sui/wormhole/sources/resources/guardian_set.move new file mode 100644 index 000000000..e55ccd375 --- /dev/null +++ b/sui/wormhole/sources/resources/guardian_set.move @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements a container that keeps track of a list of Guardian +/// public keys and which Guardian set index this list of Guardians represents. +/// Each guardian set is unique and there should be no two sets that have the +/// same Guardian set index (which requirement is handled in `wormhole::state`). +/// +/// If the current Guardian set is not the latest one, its `expiration_time` is +/// configured, which defines how long the past Guardian set can be active. +module wormhole::guardian_set { + use std::vector::{Self}; + use sui::clock::{Self, Clock}; + + use wormhole::guardian::{Self, Guardian}; + + // Needs `set_expiration`. + friend wormhole::state; + + /// Found duplicate public key. + const E_DUPLICATE_GUARDIAN: u64 = 0; + + /// Container for the list of Guardian public keys, its index value and at + /// what point in time the Guardian set is configured to expire. + struct GuardianSet has store { + /// A.K.A. Guardian set index. + index: u32, + + /// List of Guardians. This order should not change. + guardians: vector, + + /// At what point in time the Guardian set is no longer active (in ms). + expiration_timestamp_ms: u64, + } + + /// Create new `GuardianSet`. + public fun new(index: u32, guardians: vector): GuardianSet { + // Ensure that there are no duplicate guardians. + let (i, n) = (0, vector::length(&guardians)); + while (i < n - 1) { + let left = guardian::pubkey(vector::borrow(&guardians, i)); + let j = i + 1; + while (j < n) { + let right = guardian::pubkey(vector::borrow(&guardians, j)); + assert!(left != right, E_DUPLICATE_GUARDIAN); + j = j + 1; + }; + i = i + 1; + }; + + GuardianSet { index, guardians, expiration_timestamp_ms: 0 } + } + + /// Retrieve the Guardian set index. + public fun index(self: &GuardianSet): u32 { + self.index + } + + /// Retrieve the Guardian set index as `u64` (for convenience when used to + /// compare to indices for iterations, which are natively `u64`). + public fun index_as_u64(self: &GuardianSet): u64 { + (self.index as u64) + } + + /// Retrieve list of Guardians. + public fun guardians(self: &GuardianSet): &vector { + &self.guardians + } + + /// Retrieve specific Guardian by index (in the array representing the set). + public fun guardian_at(self: &GuardianSet, index: u64): &Guardian { + vector::borrow(&self.guardians, index) + } + + /// Retrieve when the Guardian set is no longer active. + public fun expiration_timestamp_ms(self: &GuardianSet): u64 { + self.expiration_timestamp_ms + } + + /// Retrieve whether this Guardian set is still active by checking the + /// current time. + public fun is_active(self: &GuardianSet, clock: &Clock): bool { + ( + self.expiration_timestamp_ms == 0 || + self.expiration_timestamp_ms > clock::timestamp_ms(clock) + ) + } + + /// Retrieve how many guardians exist in the Guardian set. + public fun num_guardians(self: &GuardianSet): u64 { + vector::length(&self.guardians) + } + + /// Returns the minimum number of signatures required for a VAA to be valid. + public fun quorum(self: &GuardianSet): u64 { + (num_guardians(self) * 2) / 3 + 1 + } + + /// Configure this Guardian set to expire from some amount of time based on + /// what time it is right now. + /// + /// NOTE: `time_to_live` is in units of seconds while `Clock` uses + /// milliseconds. + public(friend) fun set_expiration( + self: &mut GuardianSet, + seconds_to_live: u32, + the_clock: &Clock + ) { + let ttl_ms = (seconds_to_live as u64) * 1000; + self.expiration_timestamp_ms = clock::timestamp_ms(the_clock) + ttl_ms; + } + + #[test_only] + public fun destroy(set: GuardianSet) { + use wormhole::guardian::{Self}; + + let GuardianSet { + index: _, + guardians, + expiration_timestamp_ms: _ + } = set; + while (!vector::is_empty(&guardians)) { + guardian::destroy(vector::pop_back(&mut guardians)); + }; + + vector::destroy_empty(guardians); + } +} + +#[test_only] +module wormhole::guardian_set_tests { + use std::vector::{Self}; + + use wormhole::guardian::{Self}; + use wormhole::guardian_set::{Self}; + + #[test] + fun test_new() { + let guardians = vector::empty(); + + let pubkeys = vector[ + x"8888888888888888888888888888888888888888", + x"9999999999999999999999999999999999999999", + x"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + x"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + x"cccccccccccccccccccccccccccccccccccccccc", + x"dddddddddddddddddddddddddddddddddddddddd", + x"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + x"ffffffffffffffffffffffffffffffffffffffff" + ]; + while (!vector::is_empty(&pubkeys)) { + vector::push_back( + &mut guardians, + guardian::new(vector::pop_back(&mut pubkeys)) + ); + }; + + let set = guardian_set::new(69, guardians); + + // Clean up. + guardian_set::destroy(set); + } + + #[test] + #[expected_failure(abort_code = guardian_set::E_DUPLICATE_GUARDIAN)] + fun test_cannot_new_duplicate_guardian() { + let guardians = vector::empty(); + + let pubkeys = vector[ + x"8888888888888888888888888888888888888888", + x"9999999999999999999999999999999999999999", + x"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + x"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + x"cccccccccccccccccccccccccccccccccccccccc", + x"dddddddddddddddddddddddddddddddddddddddd", + x"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + x"ffffffffffffffffffffffffffffffffffffffff", + x"cccccccccccccccccccccccccccccccccccccccc", + ]; + while (!vector::is_empty(&pubkeys)) { + vector::push_back( + &mut guardians, + guardian::new(vector::pop_back(&mut pubkeys)) + ); + }; + + let set = guardian_set::new(69, guardians); + + // Clean up. + guardian_set::destroy(set); + + abort 42 + } +} diff --git a/sui/wormhole/sources/resources/set.move b/sui/wormhole/sources/resources/set.move new file mode 100644 index 000000000..bb5232648 --- /dev/null +++ b/sui/wormhole/sources/resources/set.move @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements a custom type that resembles the set data structure. +/// `Set` leverages `sui::table` to store unique keys of the same type. +/// +/// NOTE: Items added to this data structure cannot be removed. +module wormhole::set { + use sui::table::{Self, Table}; + use sui::tx_context::{TxContext}; + + /// Explicit error if key already exists in `Set`. + const E_KEY_ALREADY_EXISTS: u64 = 0; + /// Explicit error if key does not exist in `Set`. + const E_KEY_NONEXISTENT: u64 = 1; + + /// Empty struct. Used as the value type in mappings to encode a set + struct Empty has store, drop {} + + /// A set containing elements of type `T` with support for membership + /// checking. + struct Set has store { + items: Table + } + + /// Create a new Set. + public fun new(ctx: &mut TxContext): Set { + Set { items: table::new(ctx) } + } + + /// Add a new element to the set. + /// Aborts if the element already exists + public fun add(self: &mut Set, key: T) { + assert!(!contains(self, key), E_KEY_ALREADY_EXISTS); + table::add(&mut self.items, key, Empty {}) + } + + /// Returns true iff `set` contains an entry for `key`. + public fun contains(self: &Set, key: T): bool { + table::contains(&self.items, key) + } + + public fun remove(self: &mut Set, key: T) { + assert!(contains(self, key), E_KEY_NONEXISTENT); + table::remove(&mut self.items, key); + } + + #[test_only] + public fun destroy(set: Set) { + let Set { items } = set; + table::drop(items); + } + +} + +#[test_only] +module wormhole::set_tests { + use sui::tx_context::{Self}; + + use wormhole::set::{Self}; + + #[test] + public fun test_add_and_contains() { + let ctx = &mut tx_context::dummy(); + + let my_set = set::new(ctx); + + let (i, n) = (0, 256); + while (i < n) { + set::add(&mut my_set, i); + i = i + 1; + }; + + // Check that the set has the values just added. + let i = 0; + while (i < n) { + assert!(set::contains(&my_set, i), 0); + i = i + 1; + }; + + // Check that these values that were not added are not in the set. + while (i < 2 * n) { + assert!(!set::contains(&my_set, i), 0); + i = i + 1; + }; + + set::destroy(my_set); + } +} diff --git a/sui/wormhole/sources/serialize.move b/sui/wormhole/sources/serialize.move deleted file mode 100644 index 04828b425..000000000 --- a/sui/wormhole/sources/serialize.move +++ /dev/null @@ -1,137 +0,0 @@ -module wormhole::serialize { - use std::vector; - use wormhole::myu16::{Self as u16, U16}; - use wormhole::myu32::{Self as u32, U32}; - use wormhole::myu256::U256; - - // we reuse the native bcs serialiser -- it uses little-endian encoding, and - // we need big-endian, so the results are reversed - use std::bcs; - - public fun serialize_u8(buf: &mut vector, v: u8) { - vector::push_back(buf, v); - } - - public fun serialize_u16(buf: &mut vector, v: U16) { - let (v0, v1) = u16::split_u8(v); - serialize_u8(buf, v0); - serialize_u8(buf, v1); - } - - public fun serialize_u32(buf: &mut vector, v: U32) { - let (v0, v1, v2, v3) = u32::split_u8(v); - serialize_u8(buf, v0); - serialize_u8(buf, v1); - serialize_u8(buf, v2); - serialize_u8(buf, v3); - } - - public fun serialize_u64(buf: &mut vector, v: u64) { - let v = bcs::to_bytes(&v); - vector::reverse(&mut v); - vector::append(buf, v); - } - - public fun serialize_u128(buf: &mut vector, v: u128) { - let v = bcs::to_bytes(&v); - vector::reverse(&mut v); - vector::append(buf, v); - } - - public fun serialize_u256(buf: &mut vector, v: U256) { - let v = bcs::to_bytes(&v); - vector::reverse(&mut v); - vector::append(buf, v); - } - - public fun serialize_vector(buf: &mut vector, v: vector){ - vector::append(buf, v) - } -} - -#[test_only] -module wormhole::test_serialize { - use wormhole::serialize; - use wormhole::deserialize; - use wormhole::cursor; - use wormhole::myu32::{Self as u32}; - use wormhole::myu16::{Self as u16}; - use wormhole::myu256::{Self as u256}; - use 0x1::vector; - - #[test] - fun test_serialize_u8(){ - let u = 0x12; - let s = vector::empty(); - serialize::serialize_u8(&mut s, u); - let cur = cursor::cursor_init(s); - let p = deserialize::deserialize_u8(&mut cur); - cursor::destroy_empty(cur); - assert!(p==u, 0); - } - - #[test] - fun test_serialize_u16(){ - let u = u16::from_u64((0x1234 as u64)); - let s = vector::empty(); - serialize::serialize_u16(&mut s, u); - let cur = cursor::cursor_init(s); - let p = deserialize::deserialize_u16(&mut cur); - cursor::destroy_empty(cur); - assert!(p==u, 0); - } - - #[test] - fun test_serialize_u32(){ - let u = u32::from_u64((0x12345678 as u64)); - let s = vector::empty(); - serialize::serialize_u32(&mut s, u); - let cur = cursor::cursor_init(s); - let p = deserialize::deserialize_u32(&mut cur); - cursor::destroy_empty(cur); - assert!(p==u, 0); - } - - #[test] - fun test_serialize_u64(){ - let u = 0x1234567812345678; - let s = vector::empty(); - serialize::serialize_u64(&mut s, u); - let cur = cursor::cursor_init(s); - let p = deserialize::deserialize_u64(&mut cur); - cursor::destroy_empty(cur); - assert!(p==u, 0); - } - - #[test] - fun test_serialize_u128(){ - let u = 0x12345678123456781234567812345678; - let s = vector::empty(); - serialize::serialize_u128(&mut s, u); - let cur = cursor::cursor_init(s); - let p = deserialize::deserialize_u128(&mut cur); - cursor::destroy_empty(cur); - assert!(p==u, 0); - } - - #[test] - fun test_serialize_u256(){ - let u = u256::add(u256::shl(u256::from_u128(0x47386917590997937461700473756125), 128), u256::from_u128(0x9876)); - let s = vector::empty(); - serialize::serialize_u256(&mut s, u); - let exp = x"4738691759099793746170047375612500000000000000000000000000009876"; - assert!(s == exp, 0); - } - - #[test] - fun test_serialize_vector(){ - let x = vector::empty(); - let y = vector::empty(); - vector::push_back(&mut x, 0x12); - vector::push_back(&mut x, 0x34); - vector::push_back(&mut x, 0x56); - serialize::serialize_vector(&mut y, x); - assert!(y == x"123456", 0); - } - -} \ No newline at end of file diff --git a/sui/wormhole/sources/set.move b/sui/wormhole/sources/set.move deleted file mode 100644 index 2841b0860..000000000 --- a/sui/wormhole/sources/set.move +++ /dev/null @@ -1,33 +0,0 @@ -/// A set data structure. -module wormhole::set { - use sui::table::{Self, Table}; - use sui::tx_context::TxContext; - - /// Empty struct. Used as the value type in mappings to encode a set - struct Unit has store, copy, drop {} - - /// A set containing elements of type `A` with support for membership - /// checking. - struct Set has store { - elems: Table - } - - /// Create a new Set. - public fun new(ctx: &mut TxContext): Set { - Set { - elems: table::new(ctx) - } - } - - /// Add a new element to the set. - /// Aborts if the element already exists - public fun add(set: &mut Set, key: A) { - table::add(&mut set.elems, key, Unit {}) - } - - /// Returns true iff `set` contains an entry for `key`. - public fun contains(set: &Set, key: A): bool { - table::contains(&set.elems, key) - } - -} diff --git a/sui/wormhole/sources/setup.move b/sui/wormhole/sources/setup.move new file mode 100644 index 000000000..8c4ceb0cc --- /dev/null +++ b/sui/wormhole/sources/setup.move @@ -0,0 +1,326 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements the mechanism to publish the Wormhole contract and +/// initialize `State` as a shared object. +module wormhole::setup { + use std::vector::{Self}; + use sui::object::{Self, UID}; + use sui::package::{Self, UpgradeCap}; + use sui::transfer::{Self}; + use sui::tx_context::{Self, TxContext}; + + use wormhole::cursor::{Self}; + use wormhole::state::{Self}; + + /// Capability created at `init`, which will be destroyed once + /// `init_and_share_state` is called. This ensures only the deployer can + /// create the shared `State`. + struct DeployerCap has key, store { + id: UID + } + + /// Called automatically when module is first published. Transfers + /// `DeployerCap` to sender. + /// + /// Only `setup::init_and_share_state` requires `DeployerCap`. + fun init(ctx: &mut TxContext) { + let deployer = DeployerCap { id: object::new(ctx) }; + transfer::transfer(deployer, tx_context::sender(ctx)); + } + + #[test_only] + public fun init_test_only(ctx: &mut TxContext) { + init(ctx); + + // This will be created and sent to the transaction sender + // automatically when the contract is published. + transfer::public_transfer( + sui::package::test_publish(object::id_from_address(@wormhole), ctx), + tx_context::sender(ctx) + ); + } + + /// Only the owner of the `DeployerCap` can call this method. This + /// method destroys the capability and shares the `State` object. + public fun complete( + deployer: DeployerCap, + upgrade_cap: UpgradeCap, + governance_chain: u16, + governance_contract: vector, + guardian_set_index: u32, + initial_guardians: vector>, + guardian_set_seconds_to_live: u32, + message_fee: u64, + ctx: &mut TxContext + ) { + wormhole::package_utils::assert_package_upgrade_cap( + &upgrade_cap, + package::compatible_policy(), + 1 + ); + + // Destroy deployer cap. + let DeployerCap { id } = deployer; + object::delete(id); + + let guardians = { + let out = vector::empty(); + let cur = cursor::new(initial_guardians); + while (!cursor::is_empty(&cur)) { + vector::push_back( + &mut out, + wormhole::guardian::new(cursor::poke(&mut cur)) + ); + }; + cursor::destroy_empty(cur); + out + }; + + // Share new state. + transfer::public_share_object( + state::new( + upgrade_cap, + governance_chain, + wormhole::external_address::new_nonzero( + wormhole::bytes32::from_bytes(governance_contract) + ), + guardian_set_index, + guardians, + guardian_set_seconds_to_live, + message_fee, + ctx + ) + ); + } +} + +#[test_only] +module wormhole::setup_tests { + use std::option::{Self}; + use std::vector::{Self}; + use sui::package::{Self}; + use sui::object::{Self}; + use sui::test_scenario::{Self}; + + use wormhole::bytes32::{Self}; + use wormhole::cursor::{Self}; + use wormhole::external_address::{Self}; + use wormhole::guardian::{Self}; + use wormhole::guardian_set::{Self}; + use wormhole::setup::{Self, DeployerCap}; + use wormhole::state::{Self, State}; + use wormhole::wormhole_scenario::{person}; + + #[test] + fun test_init() { + let deployer = person(); + let my_scenario = test_scenario::begin(deployer); + let scenario = &mut my_scenario; + + // Initialize Wormhole smart contract. + setup::init_test_only(test_scenario::ctx(scenario)); + + // Process effects of `init`. + let effects = test_scenario::next_tx(scenario, deployer); + + // We expect two objects to be created: `DeployerCap` and `UpgradeCap`. + assert!(vector::length(&test_scenario::created(&effects)) == 2, 0); + + // We should be able to take the `DeployerCap` from the sender + // of the transaction. + let cap = + test_scenario::take_from_address( + scenario, + deployer + ); + + // The above should succeed, so we will return to `deployer`. + test_scenario::return_to_address(deployer, cap); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + fun test_complete() { + let deployer = person(); + let my_scenario = test_scenario::begin(deployer); + let scenario = &mut my_scenario; + + // Initialize Wormhole smart contract. + setup::init_test_only(test_scenario::ctx(scenario)); + + // Ignore effects. + test_scenario::next_tx(scenario, deployer); + + let governance_chain = 1234; + let governance_contract = + x"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; + let guardian_set_index = 0; + let initial_guardians = + vector[ + x"1337133713371337133713371337133713371337", + x"c0dec0dec0dec0dec0dec0dec0dec0dec0dec0de", + x"ba5edba5edba5edba5edba5edba5edba5edba5ed" + ]; + let guardian_set_seconds_to_live = 5678; + let message_fee = 350; + + // Take the `DeployerCap` and move it to `init_and_share_state`. + let deployer_cap = + test_scenario::take_from_address( + scenario, + deployer + ); + let deployer_cap_id = object::id(&deployer_cap); + + // This will be created and sent to the transaction sender automatically + // when the contract is published. This exists in place of grabbing + // it from the sender. + let upgrade_cap = + package::test_publish( + object::id_from_address(@wormhole), + test_scenario::ctx(scenario) + ); + + setup::complete( + deployer_cap, + upgrade_cap, + governance_chain, + governance_contract, + guardian_set_index, + initial_guardians, + guardian_set_seconds_to_live, + message_fee, + test_scenario::ctx(scenario) + ); + + // Process effects. + let effects = test_scenario::next_tx(scenario, deployer); + + // We expect one object to be created: `State`. And it is shared. + let created = test_scenario::created(&effects); + let shared = test_scenario::shared(&effects); + assert!(vector::length(&created) == 1, 0); + assert!(vector::length(&shared) == 1, 0); + assert!( + vector::borrow(&created, 0) == vector::borrow(&shared, 0), + 0 + ); + + // Verify `State`. Ideally we compare structs, but we will check each + // element. + let worm_state = test_scenario::take_shared(scenario); + + assert!(state::governance_chain(&worm_state) == governance_chain, 0); + + let expected_governance_contract = + external_address::new_nonzero( + bytes32::from_bytes(governance_contract) + ); + assert!( + state::governance_contract(&worm_state) == expected_governance_contract, + 0 + ); + + assert!(state::guardian_set_index(&worm_state) == 0, 0); + assert!( + state::guardian_set_seconds_to_live(&worm_state) == guardian_set_seconds_to_live, + 0 + ); + + let guardians = + guardian_set::guardians( + state::guardian_set_at(&worm_state, 0) + ); + let num_guardians = vector::length(guardians); + assert!(num_guardians == vector::length(&initial_guardians), 0); + + let i = 0; + while (i < num_guardians) { + let left = guardian::as_bytes(vector::borrow(guardians, i)); + let right = *vector::borrow(&initial_guardians, i); + assert!(left == right, 0); + i = i + 1; + }; + + assert!(state::message_fee(&worm_state) == message_fee, 0); + + // Clean up. + test_scenario::return_shared(worm_state); + + // We expect `DeployerCap` to be destroyed. There are other + // objects deleted, but we only care about the deployer cap for this + // test. + let deleted = cursor::new(test_scenario::deleted(&effects)); + let found = option::none(); + while (!cursor::is_empty(&deleted)) { + let id = cursor::poke(&mut deleted); + if (id == deployer_cap_id) { + found = option::some(id); + } + }; + cursor::destroy_empty(deleted); + + // If we found the deployer cap, `found` will have the ID. + assert!(!option::is_none(&found), 0); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + #[expected_failure( + abort_code = wormhole::package_utils::E_INVALID_UPGRADE_CAP + )] + fun test_cannot_complete_invalid_upgrade_cap() { + let deployer = person(); + let my_scenario = test_scenario::begin(deployer); + let scenario = &mut my_scenario; + + // Initialize Wormhole smart contract. + setup::init_test_only(test_scenario::ctx(scenario)); + + // Ignore effects. + test_scenario::next_tx(scenario, deployer); + + let governance_chain = 1234; + let governance_contract = + x"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; + let guardian_set_index = 0; + let initial_guardians = + vector[x"1337133713371337133713371337133713371337"]; + let guardian_set_seconds_to_live = 5678; + let message_fee = 350; + + // Take the `DeployerCap` and move it to `init_and_share_state`. + let deployer_cap = + test_scenario::take_from_address( + scenario, + deployer + ); + + // This will be created and sent to the transaction sender automatically + // when the contract is published. This exists in place of grabbing + // it from the sender. + let upgrade_cap = + package::test_publish( + object::id_from_address(@0xbadc0de), + test_scenario::ctx(scenario) + ); + + setup::complete( + deployer_cap, + upgrade_cap, + governance_chain, + governance_contract, + guardian_set_index, + initial_guardians, + guardian_set_seconds_to_live, + message_fee, + test_scenario::ctx(scenario) + ); + + abort 42 + } +} diff --git a/sui/wormhole/sources/state.move b/sui/wormhole/sources/state.move index 757b1e84f..8b19cf194 100644 --- a/sui/wormhole/sources/state.move +++ b/sui/wormhole/sources/state.move @@ -1,267 +1,468 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements the global state variables for Wormhole as a shared +/// object. The `State` object is used to perform anything that requires access +/// to data that defines the Wormhole contract. Examples of which are publishing +/// Wormhole messages (requires depositing a message fee), verifying `VAA` by +/// checking signatures versus an existing Guardian set, and generating new +/// emitters for Wormhole integrators. module wormhole::state { use std::vector::{Self}; + use sui::balance::{Balance}; + use sui::clock::{Clock}; + use sui::object::{Self, ID, UID}; + use sui::package::{UpgradeCap, UpgradeReceipt, UpgradeTicket}; + use sui::sui::{SUI}; + use sui::table::{Self, Table}; + use sui::tx_context::{TxContext}; - use sui::object::{Self, UID}; - use sui::tx_context::{Self, TxContext}; - use sui::transfer::{Self}; - use sui::vec_map::{Self, VecMap}; - use sui::event::{Self}; + use wormhole::bytes32::{Self, Bytes32}; + use wormhole::consumed_vaas::{Self, ConsumedVAAs}; + use wormhole::external_address::{ExternalAddress}; + use wormhole::fee_collector::{Self, FeeCollector}; + use wormhole::guardian::{Guardian}; + use wormhole::guardian_set::{Self, GuardianSet}; + use wormhole::package_utils::{Self}; + use wormhole::version_control::{Self}; - use wormhole::myu16::{Self as u16, U16}; - use wormhole::myu32::{Self as u32, U32}; - use wormhole::set::{Self, Set}; - use wormhole::structs::{Self, create_guardian, Guardian, GuardianSet}; - use wormhole::external_address::{Self, ExternalAddress}; - use wormhole::emitter::{Self}; + friend wormhole::emitter; + friend wormhole::governance_message; + friend wormhole::migrate; + friend wormhole::publish_message; + friend wormhole::set_fee; + friend wormhole::setup; + friend wormhole::transfer_fee; + friend wormhole::update_guardian_set; + friend wormhole::upgrade_contract; + friend wormhole::vaa; - friend wormhole::guardian_set_upgrade; - //friend wormhole::contract_upgrade; - friend wormhole::wormhole; - friend wormhole::myvaa; - #[test_only] - friend wormhole::vaa_test; + /// Cannot initialize state with zero guardians. + const E_ZERO_GUARDIANS: u64 = 0; + /// Build digest does not agree with current implementation. + const E_INVALID_BUILD_DIGEST: u64 = 1; - struct DeployerCapability has key, store {id: UID} + /// Sui's chain ID is hard-coded to one value. + const CHAIN_ID: u16 = 21; - struct WormholeMessage has store, copy, drop { - sender: u64, - sequence: u64, - nonce: u64, - payload: vector, - consistency_level: u8 - } + /// Capability reflecting that the current build version is used to invoke + /// state methods. + struct LatestOnly has drop {} + /// Container for all state variables for Wormhole. struct State has key, store { id: UID, - /// chain id - chain_id: U16, + /// Governance chain ID. + governance_chain: u16, - /// guardian chain ID - governance_chain_id: U16, - - /// Address of governance contract on governance chain + /// Governance contract address. governance_contract: ExternalAddress, - /// Current active guardian set index - guardian_set_index: U32, + /// Current active guardian set index. + guardian_set_index: u32, - /// guardian sets - guardian_sets: VecMap, + /// All guardian sets (including expired ones). + guardian_sets: Table, - /// Period for which a guardian set stays active after it has been replaced - guardian_set_expiry: U32, + /// Period for which a guardian set stays active after it has been + /// replaced. + /// + /// NOTE: `Clock` timestamp is in units of ms while this value is in + /// terms of seconds. See `guardian_set` module for more info. + guardian_set_seconds_to_live: u32, - /// Consumed governance actions - consumed_governance_actions: Set>, + /// Consumed VAA hashes to protect against replay. VAAs relevant to + /// Wormhole are just governance VAAs. + consumed_vaas: ConsumedVAAs, - /// Capability for creating new emitters - emitter_registry: emitter::EmitterRegistry, + /// Wormhole fee collector. + fee_collector: FeeCollector, - /// wormhole message fee + /// Upgrade capability. + upgrade_cap: UpgradeCap + } + + /// Create new `State`. This is only executed using the `setup` module. + public(friend) fun new( + upgrade_cap: UpgradeCap, + governance_chain: u16, + governance_contract: ExternalAddress, + guardian_set_index: u32, + initial_guardians: vector, + guardian_set_seconds_to_live: u32, message_fee: u64, - } - - /// Called automatically when module is first published. Transfers a deployer cap to sender. - fun init(ctx: &mut TxContext) { - transfer::transfer(DeployerCapability{id: object::new(ctx)}, tx_context::sender(ctx)); - } - - // creates a shared state object, so that anyone can get a reference to &mut State - // and pass it into various functions - public entry fun init_and_share_state( - deployer: DeployerCapability, - chain_id: u64, - governance_chain_id: u64, - governance_contract: vector, - initial_guardians: vector>, ctx: &mut TxContext - ) { - let DeployerCapability{id} = deployer; - object::delete(id); + ): State { + // We need at least one guardian. + assert!(vector::length(&initial_guardians) > 0, E_ZERO_GUARDIANS); + let state = State { id: object::new(ctx), - chain_id: u16::from_u64(chain_id), - governance_chain_id: u16::from_u64(governance_chain_id), - governance_contract: external_address::from_bytes(governance_contract), - guardian_set_index: u32::from_u64(0), - guardian_sets: vec_map::empty(), - guardian_set_expiry: u32::from_u64(2), // TODO - what is the right #epochs to set this to? - consumed_governance_actions: set::new(ctx), - emitter_registry: emitter::init_emitter_registry(), - message_fee: 0, + governance_chain, + governance_contract, + guardian_set_index, + guardian_sets: table::new(ctx), + guardian_set_seconds_to_live, + consumed_vaas: consumed_vaas::new(ctx), + fee_collector: fee_collector::new(message_fee), + upgrade_cap }; - let guardians = vector::empty(); - vector::reverse(&mut initial_guardians); - while (!vector::is_empty(&initial_guardians)) { - vector::push_back(&mut guardians, create_guardian(vector::pop_back(&mut initial_guardians))); - }; + // Set first version and initialize package info. This will be used for + // emitting information of successful migrations. + let upgrade_cap = &state.upgrade_cap; + package_utils::init_package_info( + &mut state.id, + version_control::current_version(), + upgrade_cap + ); - // the initial guardian set with index 0 - let initial_index = u32::from_u64(0); - store_guardian_set(&mut state, initial_index, structs::create_guardian_set(initial_index, guardians)); + // Store the initial guardian set. + add_new_guardian_set( + &assert_latest_only(&state), + &mut state, + guardian_set::new(guardian_set_index, initial_guardians) + ); - // permanently shares state - transfer::share_object(state); + state + } + + //////////////////////////////////////////////////////////////////////////// + // + // Simple Getters + // + // These methods do not require `LatestOnly` for access. Anyone is free to + // access these values. + // + //////////////////////////////////////////////////////////////////////////// + + /// Convenience method to get hard-coded Wormhole chain ID (recognized by + /// the Wormhole network). + public fun chain_id(): u16 { + CHAIN_ID + } + + /// Retrieve governance module name. + public fun governance_module(): Bytes32 { + // A.K.A. "Core". + bytes32::new( + x"00000000000000000000000000000000000000000000000000000000436f7265" + ) + } + + /// Retrieve governance chain ID, which is governance's emitter chain ID. + public fun governance_chain(self: &State): u16 { + self.governance_chain + } + + /// Retrieve governance emitter address. + public fun governance_contract(self: &State): ExternalAddress { + self.governance_contract + } + + /// Retrieve current Guardian set index. This value is important for + /// verifying VAA signatures and especially important for governance VAAs. + public fun guardian_set_index(self: &State): u32 { + self.guardian_set_index + } + + /// Retrieve how long after a Guardian set can live for in terms of Sui + /// timestamp (in seconds). + public fun guardian_set_seconds_to_live(self: &State): u32 { + self.guardian_set_seconds_to_live + } + + /// Retrieve a particular Guardian set by its Guardian set index. This + /// method is used when verifying a VAA. + /// + /// See `wormhole::vaa` for more info. + public fun guardian_set_at( + self: &State, + index: u32 + ): &GuardianSet { + table::borrow(&self.guardian_sets, index) + } + + /// Retrieve current fee to send Wormhole message. + public fun message_fee(self: &State): u64 { + fee_collector::fee_amount(&self.fee_collector) } #[test_only] - public fun test_init(ctx: &mut TxContext) { - transfer::transfer(DeployerCapability{id: object::new(ctx)}, tx_context::sender(ctx)); + public fun fees_collected(self: &State): u64 { + fee_collector::balance_value(&self.fee_collector) } - public(friend) entry fun publish_event( - sender: u64, - sequence: u64, - nonce: u64, - payload: vector - ) { - event::emit( - WormholeMessage { - sender: sender, - sequence: sequence, - nonce: nonce, - payload: payload, - // Sui is an instant finality chain, so we don't need - // confirmations - consistency_level: 0, - } + #[test_only] + public fun cache_latest_only_test_only(self: &State): LatestOnly { + assert_latest_only(self) + } + + #[test_only] + public fun deposit_fee_test_only(self: &mut State, fee: Balance) { + deposit_fee(&assert_latest_only(self), self, fee) + } + + #[test_only] + public fun migrate_version_test_only( + self: &mut State, + old_version: Old, + new_version: New + ) { + package_utils::update_version_type_test_only( + &mut self.id, + old_version, + new_version ); } - // setters - - public(friend) fun set_chain_id(state: &mut State, id: u64){ - state.chain_id = u16::from_u64(id); + #[test_only] + public fun test_upgrade(self: &mut State) { + let test_digest = bytes32::from_bytes(b"new build"); + let ticket = authorize_upgrade(self, test_digest); + let receipt = sui::package::test_upgrade(ticket); + commit_upgrade(self, receipt); } #[test_only] - public fun test_set_chain_id(state: &mut State, id: u64) { - set_chain_id(state, id); + public fun reverse_migrate_version(self: &mut State) { + package_utils::update_version_type_test_only( + &mut self.id, + version_control::current_version(), + version_control::previous_version() + ); } - public(friend) fun set_governance_chain_id(state: &mut State, id: u64){ - state.governance_chain_id = u16::from_u64(id); + //////////////////////////////////////////////////////////////////////////// + // + // Privileged `State` Access + // + // This section of methods require a `LatestOnly`, which can only be created + // within the Wormhole package. This capability allows special access to + // the `State` object. + // + // NOTE: A lot of these methods are still marked as `(friend)` as a safety + // precaution. When a package is upgraded, friend modifiers can be + // removed. + // + //////////////////////////////////////////////////////////////////////////// + + /// Obtain a capability to interact with `State` methods. This method checks + /// that we are running the current build. + /// + /// NOTE: This method allows caching the current version check so we avoid + /// multiple checks to dynamic fields. + public(friend) fun assert_latest_only(self: &State): LatestOnly { + package_utils::assert_version( + &self.id, + version_control::current_version() + ); + + LatestOnly {} + } + + /// Deposit fee when sending Wormhole message. This method does not + /// necessarily have to be a `friend` to `wormhole::publish_message`. But + /// we also do not want an integrator to mistakenly deposit fees outside + /// of calling `publish_message`. + /// + /// See `wormhole::publish_message` for more info. + public(friend) fun deposit_fee( + _: &LatestOnly, + self: &mut State, + fee: Balance + ) { + fee_collector::deposit_balance(&mut self.fee_collector, fee); + } + + /// Withdraw collected fees when governance action to transfer fees to a + /// particular recipient. + /// + /// See `wormhole::transfer_fee` for more info. + public(friend) fun withdraw_fee( + _: &LatestOnly, + self: &mut State, + amount: u64 + ): Balance { + fee_collector::withdraw_balance(&mut self.fee_collector, amount) + } + + /// Store `VAA` hash as a way to claim a VAA. This method prevents a VAA + /// from being replayed. For Wormhole, the only VAAs that it cares about + /// being replayed are its governance actions. + public(friend) fun borrow_mut_consumed_vaas( + _: &LatestOnly, + self: &mut State + ): &mut ConsumedVAAs { + borrow_mut_consumed_vaas_unchecked(self) + } + + /// Store `VAA` hash as a way to claim a VAA. This method prevents a VAA + /// from being replayed. For Wormhole, the only VAAs that it cares about + /// being replayed are its governance actions. + /// + /// NOTE: This method does not require `LatestOnly`. Only methods in the + /// `upgrade_contract` module requires this to be unprotected to prevent + /// a corrupted upgraded contract from bricking upgradability. + public(friend) fun borrow_mut_consumed_vaas_unchecked( + self: &mut State + ): &mut ConsumedVAAs { + &mut self.consumed_vaas + } + + /// When a new guardian set is added to `State`, part of the process + /// involves setting the last known Guardian set's expiration time based + /// on how long a Guardian set can live for. + /// + /// See `guardian_set_epochs_to_live` for the parameter that determines how + /// long a Guardian set can live for. + /// + /// See `wormhole::update_guardian_set` for more info. + public(friend) fun expire_guardian_set( + _: &LatestOnly, + self: &mut State, + the_clock: &Clock + ) { + guardian_set::set_expiration( + table::borrow_mut(&mut self.guardian_sets, self.guardian_set_index), + self.guardian_set_seconds_to_live, + the_clock + ); + } + + /// Add the latest Guardian set from the governance action to update the + /// current guardian set. + /// + /// See `wormhole::update_guardian_set` for more info. + public(friend) fun add_new_guardian_set( + _: &LatestOnly, + self: &mut State, + new_guardian_set: GuardianSet + ) { + self.guardian_set_index = guardian_set::index(&new_guardian_set); + table::add( + &mut self.guardian_sets, + self.guardian_set_index, + new_guardian_set + ); + } + + /// Modify the cost to send a Wormhole message via governance. + /// + /// See `wormhole::set_fee` for more info. + public(friend) fun set_message_fee( + _: &LatestOnly, + self: &mut State, + amount: u64 + ) { + fee_collector::change_fee(&mut self.fee_collector, amount); + } + + public(friend) fun current_package(_: &LatestOnly, self: &State): ID { + package_utils::current_package(&self.id) + } + + //////////////////////////////////////////////////////////////////////////// + // + // Upgradability + // + // A special space that controls upgrade logic. These methods are invoked + // via the `upgrade_contract` module. + // + // Also in this section is managing contract migrations, which uses the + // `migrate` module to officially roll state access to the latest build. + // Only those methods that require `LatestOnly` will be affected by an + // upgrade. + // + //////////////////////////////////////////////////////////////////////////// + + /// Issue an `UpgradeTicket` for the upgrade. + /// + /// NOTE: The Sui VM performs a check that this method is executed from the + /// latest published package. If someone were to try to execute this using + /// a stale build, the transaction will revert with `PackageUpgradeError`, + /// specifically `PackageIDDoesNotMatch`. + public(friend) fun authorize_upgrade( + self: &mut State, + package_digest: Bytes32 + ): UpgradeTicket { + let cap = &mut self.upgrade_cap; + package_utils::authorize_upgrade(&mut self.id, cap, package_digest) + } + + /// Finalize the upgrade that ran to produce the given `receipt`. + /// + /// NOTE: The Sui VM performs a check that this method is executed from the + /// latest published package. If someone were to try to execute this using + /// a stale build, the transaction will revert with `PackageUpgradeError`, + /// specifically `PackageIDDoesNotMatch`. + public(friend) fun commit_upgrade( + self: &mut State, + receipt: UpgradeReceipt + ): (ID, ID) { + let cap = &mut self.upgrade_cap; + package_utils::commit_upgrade(&mut self.id, cap, receipt) + } + + /// Method executed by the `migrate` module to roll access from one package + /// to another. This method will be called from the upgraded package. + public(friend) fun migrate_version(self: &mut State) { + package_utils::migrate_version( + &mut self.id, + version_control::previous_version(), + version_control::current_version() + ); + } + + /// As a part of the migration, we verify that the upgrade contract VAA's + /// encoded package digest used in `migrate` equals the one used to conduct + /// the upgrade. + public(friend) fun assert_authorized_digest( + _: &LatestOnly, + self: &State, + digest: Bytes32 + ) { + let authorized = package_utils::authorized_digest(&self.id); + assert!(digest == authorized, E_INVALID_BUILD_DIGEST); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Special State Interaction via Migrate + // + // A VERY special space that manipulates `State` via calling `migrate`. + // + // PLEASE KEEP ANY METHODS HERE AS FRIENDS. We want the ability to remove + // these for future builds. + // + //////////////////////////////////////////////////////////////////////////// + + /// This method is used to make modifications to `State` when `migrate` is + /// called. This method name should change reflecting which version this + /// contract is migrating to. + /// + /// NOTE: Please keep this method as public(friend) because we never want + /// to expose this method as a public method. + public(friend) fun migrate__v__0_2_0(_self: &mut State) { + // Intentionally do nothing. } #[test_only] - public fun test_set_governance_chain_id(state: &mut State, id: u64) { - set_governance_chain_id(state, id); - } - - public(friend) fun set_governance_action_consumed(state: &mut State, hash: vector){ - set::add>(&mut state.consumed_governance_actions, hash); - } - - public(friend) fun set_governance_contract(state: &mut State, contract: vector) { - state.governance_contract = external_address::from_bytes(contract); - } - - public(friend) fun update_guardian_set_index(state: &mut State, new_index: U32) { - state.guardian_set_index = new_index; - } - - public(friend) fun expire_guardian_set(state: &mut State, index: U32, ctx: &TxContext) { - let expiry = state.guardian_set_expiry; - let guardian_set = vec_map::get_mut(&mut state.guardian_sets, &index); - structs::expire_guardian_set(guardian_set, expiry, ctx); - } - - public(friend) fun store_guardian_set(state: &mut State, index: U32, set: GuardianSet) { - vec_map::insert(&mut state.guardian_sets, index, set); - } - - // getters - - public fun get_current_guardian_set_index(state: &State): U32 { - return state.guardian_set_index - } - - public fun get_guardian_set(state: &State, index: U32): GuardianSet { - return *vec_map::get(&state.guardian_sets, &index) - } - - public fun guardian_set_is_active(state: &State, guardian_set: &GuardianSet, ctx: &TxContext): bool { - let cur_epoch = tx_context::epoch(ctx); - let index = structs::get_guardian_set_index(guardian_set); - let current_index = get_current_guardian_set_index(state); - index == current_index || - u32::to_u64(structs::get_guardian_set_expiry(guardian_set)) > cur_epoch - } - - public fun get_governance_chain(state: &State): U16 { - return state.governance_chain_id - } - - public fun get_governance_contract(state: &State): ExternalAddress { - return state.governance_contract - } - - public fun get_chain_id(state: &State): U16 { - return state.chain_id - } - - public fun get_message_fee(state: &State): u64 { - return state.message_fee - } - - public(friend) fun new_emitter(state: &mut State, ctx: &mut TxContext): emitter::EmitterCapability{ - emitter::new_emitter(&mut state.emitter_registry, ctx) + /// Bloody hack. + /// + /// This method is used to set up tests where we migrate to a new version, + /// which is meant to test that modules protected by version control will + /// break. + public fun reverse_migrate__v__dummy(_self: &mut State) { + // Intentionally do nothing. } -} - -#[test_only] -module wormhole::test_state{ - use sui::test_scenario::{Self, Scenario, next_tx, ctx, take_from_address, take_shared, return_shared}; - - use wormhole::state::{Self, test_init, State, DeployerCapability}; - use wormhole::myu16::{Self as u16}; - - fun scenario(): Scenario { test_scenario::begin(@0x123233) } - fun people(): (address, address, address) { (@0x124323, @0xE05, @0xFACE) } - - public fun init_wormhole_state(test: Scenario, admin: address): Scenario { - next_tx(&mut test, admin); { - test_init(ctx(&mut test)); - }; - next_tx(&mut test, admin); { - let deployer = take_from_address(&test, admin); - state::init_and_share_state( - deployer, - 21, - 1, // governance chain - x"0000000000000000000000000000000000000000000000000000000000000004", // governance_contract - vector[x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"], // initial_guardian(s) - ctx(&mut test)); - }; - return test - } - - #[test] - fun test_state_setters() { - test_state_setters_(scenario()) - } - - fun test_state_setters_(test: Scenario) { - let (admin, _, _) = people(); - test = init_wormhole_state(test, admin); - - // test setters - next_tx(&mut test, admin); { - let state = take_shared(&test); - - // test set chain id - state::test_set_chain_id(&mut state, 5); - assert!(state::get_chain_id(&state) == u16::from_u64(5), 0); - - // test set governance chain id - state::test_set_governance_chain_id(&mut state, 100); - assert!(state::get_governance_chain(&state) == u16::from_u64(100), 0); - - return_shared(state); - }; - test_scenario::end(test); - } + //////////////////////////////////////////////////////////////////////////// + // + // Deprecated + // + // Dumping grounds for old structs and methods. These things should not + // be used in future builds. + // + //////////////////////////////////////////////////////////////////////////// } diff --git a/sui/wormhole/sources/structs.move b/sui/wormhole/sources/structs.move deleted file mode 100644 index 2786ef393..000000000 --- a/sui/wormhole/sources/structs.move +++ /dev/null @@ -1,69 +0,0 @@ -module wormhole::structs { - use wormhole::myu32::{Self as u32, U32}; - use sui::tx_context::{Self, TxContext}; - - friend wormhole::state; - use wormhole::guardian_pubkey::{Self}; - - struct Signature has store, copy, drop { - sig: vector, - recovery_id: u8, - guardian_index: u8, - } - - struct Guardian has store, drop, copy { - address: guardian_pubkey::Address - } - - struct GuardianSet has store, copy, drop { - index: U32, - guardians: vector, - expiration_time: U32, - } - - public fun create_guardian(address: vector): Guardian { - Guardian { - address: guardian_pubkey::from_bytes(address) - } - } - - public fun create_guardian_set(index: U32, guardians: vector): GuardianSet { - GuardianSet { - index: index, - guardians: guardians, - expiration_time: u32::from_u64(0), - } - } - - public(friend) fun expire_guardian_set(guardian_set: &mut GuardianSet, delta: U32, ctx: &TxContext) { - guardian_set.expiration_time = u32::from_u64(tx_context::epoch(ctx) + u32::to_u64(delta)); - } - - public fun unpack_signature(s: &Signature): (vector, u8, u8) { - (s.sig, s.recovery_id, s.guardian_index) - } - - public fun create_signature( - sig: vector, - recovery_id: u8, - guardian_index: u8 - ): Signature { - Signature{ sig, recovery_id, guardian_index } - } - - public fun get_address(guardian: &Guardian): guardian_pubkey::Address { - guardian.address - } - - public fun get_guardian_set_index(guardian_set: &GuardianSet): U32 { - guardian_set.index - } - - public fun get_guardians(guardian_set: &GuardianSet): vector { - guardian_set.guardians - } - - public fun get_guardian_set_expiry(guardian_set: &GuardianSet): U32 { - guardian_set.expiration_time - } -} diff --git a/sui/wormhole/sources/test/wormhole_scenario.move b/sui/wormhole/sources/test/wormhole_scenario.move new file mode 100644 index 000000000..f587778e4 --- /dev/null +++ b/sui/wormhole/sources/test/wormhole_scenario.move @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: Apache 2 + +#[test_only] +/// This module implements ways to initialize Wormhole in a test scenario. This +/// module includes a default method (`set_up_wormhole`) with only one of the +/// devnet (Tilt) Guardians. The private key for this Guardian is known (see the +/// main Wormhole repository at https://github.com/wormhole-foundation/wormhole +/// for the key), which allows an integrator to generate his own VAAs and +/// validate them with this test-only Wormhole instance. +module wormhole::wormhole_scenario { + use std::vector::{Self}; + use sui::clock::{Self, Clock}; + use sui::package::{UpgradeCap}; + use sui::test_scenario::{Self, Scenario}; + + use wormhole::emitter::{EmitterCap}; + use wormhole::governance_message::{Self, DecreeTicket, DecreeReceipt}; + use wormhole::setup::{Self, DeployerCap}; + use wormhole::state::{Self, State}; + use wormhole::vaa::{Self, VAA}; + + const DEPLOYER: address = @0xDEADBEEF; + const WALLET_1: address = @0xB0B1; + const WALLET_2: address = @0xB0B2; + const WALLET_3: address = @0xB0B3; + const VAA_VERIFIER: address = @0xD00D; + const EMITTER_MAKER: address = @0xFEED; + + /// Set up Wormhole with any guardian pubkeys. For most testing purposes, + /// please use `set_up_wormhole` which only uses one guardian. + /// + /// NOTE: This also creates `Clock` for testing. + public fun set_up_wormhole_with_guardians( + scenario: &mut Scenario, + message_fee: u64, + initial_guardians: vector>, + ) { + // Process effects prior. `init_test_only` will be executed as the + // Wormhole contract deployer. + test_scenario::next_tx(scenario, DEPLOYER); + + // `init` Wormhole contract as if it were published. + wormhole::setup::init_test_only(test_scenario::ctx(scenario)); + + // `init_and_share_state` will also be executed as the Wormhole deployer + // to destroy the `DeployerCap` to create a sharable `State`. + test_scenario::next_tx(scenario, DEPLOYER); + + // Parameters for Wormhole's `State` are common in the Wormhole testing + // environment aside from the `guardian_set_epochs_to_live`, which at + // the moment needs to be discussed on how to configure. As of now, + // there is no clock with unix timestamp to expire guardian sets in + // terms of human-interpretable time. + { + // This will be created and sent to the transaction sender + // automatically when the contract is published. This exists in + // place of grabbing it from the sender. + let upgrade_cap = + test_scenario::take_from_sender(scenario); + + let governance_chain = 1; + let governance_contract = + x"0000000000000000000000000000000000000000000000000000000000000004"; + let guardian_set_index = 0; + let guardian_set_seconds_to_live = 420; + + // Share `State`. + setup::complete( + test_scenario::take_from_address( + scenario, DEPLOYER + ), + upgrade_cap, + governance_chain, + governance_contract, + guardian_set_index, + initial_guardians, + guardian_set_seconds_to_live, + message_fee, + test_scenario::ctx(scenario) + ); + }; + + // Done. + } + + /// Set up Wormhole with only the first devnet guardian. + public fun set_up_wormhole(scenario: &mut Scenario, message_fee: u64) { + let initial_guardians = vector::empty(); + vector::push_back( + &mut initial_guardians, + *vector::borrow(&guardians(), 0) + ); + + set_up_wormhole_with_guardians(scenario, message_fee, initial_guardians) + } + + /// Perform an upgrade (which just upticks the current version of what the + /// `State` believes is true). + public fun upgrade_wormhole(scenario: &mut Scenario) { + // Clean up from activity prior. + test_scenario::next_tx(scenario, person()); + + let worm_state = take_state(scenario); + state::test_upgrade(&mut worm_state); + + // Clean up. + return_state(worm_state); + } + + /// Address of wallet that published Wormhole contract. + public fun deployer(): address { + DEPLOYER + } + + public fun person(): address { + WALLET_1 + } + + public fun two_people(): (address, address) { + (WALLET_1, WALLET_2) + } + + public fun three_people(): (address, address, address) { + (WALLET_1, WALLET_2, WALLET_3) + } + + /// All guardians that exist in devnet (Tilt) environment. + public fun guardians(): vector> { + vector[ + x"befa429d57cd18b7f8a4d91a2da9ab4af05d0fbe", + x"88d7d8b32a9105d228100e72dffe2fae0705d31c", + x"58076f561cc62a47087b567c86f986426dfcd000", + x"bd6e9833490f8fa87c733a183cd076a6cbd29074", + x"b853fcf0a5c78c1b56d15fce7a154e6ebe9ed7a2", + x"af3503dbd2e37518ab04d7ce78b630f98b15b78a", + x"785632dea5609064803b1c8ea8bb2c77a6004bd1", + x"09a281a698c0f5ba31f158585b41f4f33659e54d", + x"3178443ab76a60e21690dbfb17f7f59f09ae3ea1", + x"647ec26ae49b14060660504f4da1c2059e1c5ab6", + x"810ac3d8e1258bd2f004a94ca0cd4c68fc1c0611", + x"80610e96d645b12f47ae5cf4546b18538739e90f", + x"2edb0d8530e31a218e72b9480202acbaeb06178d", + x"a78858e5e5c4705cdd4b668ffe3be5bae4867c9d", + x"5efe3a05efc62d60e1d19faeb56a80223cdd3472", + x"d791b7d32c05abb1cc00b6381fa0c4928f0c56fc", + x"14bc029b8809069093d712a3fd4dfab31963597e", + x"246ab29fc6ebedf2d392a51ab2dc5c59d0902a03", + x"132a84dfd920b35a3d0ba5f7a0635df298f9033e", + ] + } + + public fun take_state(scenario: &Scenario): State { + test_scenario::take_shared(scenario) + } + + public fun return_state(wormhole_state: State) { + test_scenario::return_shared(wormhole_state); + } + + public fun parse_and_verify_vaa( + scenario: &mut Scenario, + vaa_buf: vector + ): VAA { + test_scenario::next_tx(scenario, VAA_VERIFIER); + + let the_clock = take_clock(scenario); + let worm_state = take_state(scenario); + + let out = + vaa::parse_and_verify( + &worm_state, + vaa_buf, + &the_clock + ); + + // Clean up. + return_state(worm_state); + return_clock(the_clock); + + out + } + + public fun verify_governance_vaa( + scenario: &mut Scenario, + verified_vaa: VAA, + ticket: DecreeTicket + ): DecreeReceipt { + test_scenario::next_tx(scenario, VAA_VERIFIER); + + let worm_state = take_state(scenario); + + let receipt = + governance_message::verify_vaa(&worm_state, verified_vaa, ticket); + + // Clean up. + return_state(worm_state); + + receipt + } + + public fun new_emitter( + scenario: &mut Scenario + ): EmitterCap { + test_scenario::next_tx(scenario, EMITTER_MAKER); + + let worm_state = take_state(scenario); + + let emitter = + wormhole::emitter::new(&worm_state, test_scenario::ctx(scenario)); + + // Clean up. + return_state(worm_state); + + emitter + } + + public fun take_clock(scenario: &mut Scenario): Clock { + clock::create_for_testing(test_scenario::ctx(scenario)) + } + + public fun return_clock(the_clock: Clock) { + clock::destroy_for_testing(the_clock) + } +} diff --git a/sui/wormhole/sources/utils/bytes.move b/sui/wormhole/sources/utils/bytes.move new file mode 100644 index 000000000..0d181f677 --- /dev/null +++ b/sui/wormhole/sources/utils/bytes.move @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements a library that serializes and deserializes specific +/// types into a buffer (i.e. `vector`). For serialization, the first +/// argument will be of `&mut vector`. For deserialization, the first +/// argument will be of `&mut Cursor` (see `wormhole::cursor` for more +/// details). +module wormhole::bytes { + use std::vector::{Self}; + use std::bcs::{Self}; + use wormhole::cursor::{Self, Cursor}; + + public fun push_u8(buf: &mut vector, v: u8) { + vector::push_back(buf, v); + } + + public fun push_u16_be(buf: &mut vector, value: u16) { + push_reverse(buf, value); + } + + public fun push_u32_be(buf: &mut vector, value: u32) { + push_reverse(buf, value); + } + + public fun push_u64_be(buf: &mut vector, value: u64) { + push_reverse(buf, value); + } + + public fun push_u128_be(buf: &mut vector, value: u128) { + push_reverse(buf, value); + } + + public fun push_u256_be(buf: &mut vector, value: u256) { + push_reverse(buf, value); + } + + public fun take_u8(cur: &mut Cursor): u8 { + cursor::poke(cur) + } + + public fun take_u16_be(cur: &mut Cursor): u16 { + let out = 0; + let i = 0; + while (i < 2) { + out = (out << 8) + (cursor::poke(cur) as u16); + i = i + 1; + }; + out + } + + public fun take_u32_be(cur: &mut Cursor): u32 { + let out = 0; + let i = 0; + while (i < 4) { + out = (out << 8) + (cursor::poke(cur) as u32); + i = i + 1; + }; + out + } + + public fun take_u64_be(cur: &mut Cursor): u64 { + let out = 0; + let i = 0; + while (i < 8) { + out = (out << 8) + (cursor::poke(cur) as u64); + i = i + 1; + }; + out + } + + public fun take_u128_be(cur: &mut Cursor): u128 { + let out = 0; + let i = 0; + while (i < 16) { + out = (out << 8) + (cursor::poke(cur) as u128); + i = i + 1; + }; + out + } + + public fun take_u256_be(cur: &mut Cursor): u256 { + let out = 0; + let i = 0; + while (i < 32) { + out = (out << 8) + (cursor::poke(cur) as u256); + i = i + 1; + }; + out + } + + public fun take_bytes(cur: &mut Cursor, num_bytes: u64): vector { + let out = vector::empty(); + let i = 0; + while (i < num_bytes) { + vector::push_back(&mut out, cursor::poke(cur)); + i = i + 1; + }; + out + } + + fun push_reverse(buf: &mut vector, v: T) { + let data = bcs::to_bytes(&v); + vector::reverse(&mut data); + vector::append(buf, data); + } +} + +#[test_only] +module wormhole::bytes_tests { + use std::vector::{Self}; + use wormhole::bytes::{Self}; + use wormhole::cursor::{Self}; + + #[test] + fun test_push_u8(){ + let u = 0x12; + let s = vector::empty(); + bytes::push_u8(&mut s, u); + let cur = cursor::new(s); + let p = bytes::take_u8(&mut cur); + cursor::destroy_empty(cur); + assert!(p==u, 0); + } + + #[test] + fun test_push_u16_be(){ + let u = 0x1234; + let s = vector::empty(); + bytes::push_u16_be(&mut s, u); + let cur = cursor::new(s); + let p = bytes::take_u16_be(&mut cur); + cursor::destroy_empty(cur); + assert!(p==u, 0); + } + + #[test] + fun test_push_u32_be(){ + let u = 0x12345678; + let s = vector::empty(); + bytes::push_u32_be(&mut s, u); + let cur = cursor::new(s); + let p = bytes::take_u32_be(&mut cur); + cursor::destroy_empty(cur); + assert!(p==u, 0); + } + + #[test] + fun test_push_u64_be(){ + let u = 0x1234567812345678; + let s = vector::empty(); + bytes::push_u64_be(&mut s, u); + let cur = cursor::new(s); + let p = bytes::take_u64_be(&mut cur); + cursor::destroy_empty(cur); + assert!(p==u, 0); + } + + #[test] + fun test_push_u128_be(){ + let u = 0x12345678123456781234567812345678; + let s = vector::empty(); + bytes::push_u128_be(&mut s, u); + let cur = cursor::new(s); + let p = bytes::take_u128_be(&mut cur); + cursor::destroy_empty(cur); + assert!(p==u, 0); + } + + #[test] + fun test_push_u256_be(){ + let u = + 0x4738691759099793746170047375612500000000000000000000000000009876; + let s = vector::empty(); + bytes::push_u256_be(&mut s, u); + assert!( + s == x"4738691759099793746170047375612500000000000000000000000000009876", + 0 + ); + } + + #[test] + fun test_take_u8() { + let cursor = cursor::new(x"99"); + let byte = bytes::take_u8(&mut cursor); + assert!(byte==0x99, 0); + cursor::destroy_empty(cursor); + } + + #[test] + fun test_take_u16_be() { + let cursor = cursor::new(x"9987"); + let u = bytes::take_u16_be(&mut cursor); + assert!(u == 0x9987, 0); + cursor::destroy_empty(cursor); + } + + #[test] + fun test_take_u32_be() { + let cursor = cursor::new(x"99876543"); + let u = bytes::take_u32_be(&mut cursor); + assert!(u == 0x99876543, 0); + cursor::destroy_empty(cursor); + } + + #[test] + fun test_take_u64_be() { + let cursor = cursor::new(x"1300000025000001"); + let u = bytes::take_u64_be(&mut cursor); + assert!(u == 0x1300000025000001, 0); + cursor::destroy_empty(cursor); + } + + #[test] + fun test_take_u128_be() { + let cursor = cursor::new(x"130209AB2500FA0113CD00AE25000001"); + let u = bytes::take_u128_be(&mut cursor); + assert!(u == 0x130209AB2500FA0113CD00AE25000001, 0); + cursor::destroy_empty(cursor); + } + + #[test] + fun test_to_bytes() { + let cursor = cursor::new(b"hello world"); + let hello = bytes::take_bytes(&mut cursor, 5); + bytes::take_u8(&mut cursor); + let world = bytes::take_bytes(&mut cursor, 5); + assert!(hello == b"hello", 0); + assert!(world == b"world", 0); + cursor::destroy_empty(cursor); + } + +} diff --git a/sui/wormhole/sources/utils/cursor.move b/sui/wormhole/sources/utils/cursor.move new file mode 100644 index 000000000..73a96ebea --- /dev/null +++ b/sui/wormhole/sources/utils/cursor.move @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements a custom type that allows consuming a vector +/// incrementally for parsing operations. It has no drop ability, and the only +/// way to deallocate it is by calling the `destroy_empty` method, which will +/// fail if the whole input hasn't been consumed. +/// +/// This setup statically guarantees that the parsing methods consume the full +/// input. +module wormhole::cursor { + use std::vector::{Self}; + + /// Container for the underlying `vector` data to be consumed. + struct Cursor { + data: vector, + } + + /// Initialises a cursor from a vector. + public fun new(data: vector): Cursor { + // reverse the array so we have access to the first element easily + vector::reverse(&mut data); + Cursor { data } + } + + /// Retrieve underlying data. + public fun data(self: &Cursor): &vector { + &self.data + } + + /// Check whether the underlying data is empty. This method is useful for + /// iterating over a `Cursor` to exhaust its contents. + public fun is_empty(self: &Cursor): bool { + vector::is_empty(&self.data) + } + + /// Destroys an empty cursor. This method aborts if the cursor is not empty. + public fun destroy_empty(cursor: Cursor) { + let Cursor { data } = cursor; + vector::destroy_empty(data); + } + + /// Consumes the rest of the cursor (thus destroying it) and returns the + /// remaining bytes. + /// + /// NOTE: Only use this function if you intend to consume the rest of the + /// bytes. Since the result is a vector, which can be dropped, it is not + /// possible to statically guarantee that the rest will be used. + public fun take_rest(cursor: Cursor): vector { + let Cursor { data } = cursor; + // Because the data was reversed in initialization, we need to reverse + // again so it is in the same order as the original input. + vector::reverse(&mut data); + data + } + + /// Retrieve the first element of the cursor and advances it. + public fun poke(self: &mut Cursor): T { + vector::pop_back(&mut self.data) + } +} diff --git a/sui/wormhole/sources/utils/package_utils.move b/sui/wormhole/sources/utils/package_utils.move new file mode 100644 index 000000000..12dcfde91 --- /dev/null +++ b/sui/wormhole/sources/utils/package_utils.move @@ -0,0 +1,422 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements utilities that supplement those methods implemented +/// in `sui::package`. +module wormhole::package_utils { + use std::type_name::{Self, TypeName}; + use sui::dynamic_field::{Self as field}; + use sui::object::{Self, ID, UID}; + use sui::package::{Self, UpgradeCap, UpgradeTicket, UpgradeReceipt}; + + use wormhole::bytes32::{Self, Bytes32}; + + /// `UpgradeCap` is not from the same package as `T`. + const E_INVALID_UPGRADE_CAP: u64 = 0; + /// Build is not current. + const E_NOT_CURRENT_VERSION: u64 = 1; + /// Old version to update from is wrong. + const E_INCORRECT_OLD_VERSION: u64 = 2; + /// Old and new are the same version. + const E_SAME_VERSION: u64 = 3; + /// Version types must come from this module. + const E_TYPE_NOT_ALLOWED: u64 = 4; + + /// Key for version dynamic fields. + struct CurrentVersion has store, drop, copy {} + + /// Key for dynamic field reflecting current package info. Its value is + /// `PackageInfo`. + struct CurrentPackage has store, drop, copy {} + struct PendingPackage has store, drop, copy {} + + struct PackageInfo has store, drop, copy { + package: ID, + digest: Bytes32 + } + + /// Retrieve current package ID, which should be the only one that anyone is + /// allowed to interact with. + public fun current_package(id: &UID): ID { + let info: &PackageInfo = field::borrow(id, CurrentPackage {}); + info.package + } + + /// Retrieve the build digest reflecting the current build. + public fun current_digest(id: &UID): Bytes32 { + let info: &PackageInfo = field::borrow(id, CurrentPackage {}); + info.digest + } + + /// Retrieve the upgraded package ID, which was taken from `UpgradeCap` + /// during `commit_upgrade`. + public fun committed_package(id: &UID): ID { + let info: &PackageInfo = field::borrow(id, PendingPackage {}); + info.package + } + + /// Retrieve the build digest of the latest upgrade, which was the same + /// digest used when `authorize_upgrade` is called. + public fun authorized_digest(id: &UID): Bytes32 { + let info: &PackageInfo = field::borrow(id, PendingPackage {}); + info.digest + } + + /// Convenience method that can be used with any package that requires + /// `UpgradeCap` to have certain preconditions before it is considered + /// belonging to `T` object's package. + public fun assert_package_upgrade_cap( + cap: &UpgradeCap, + expected_policy: u8, + expected_version: u64 + ) { + let expected_package = + sui::address::from_bytes( + sui::hex::decode( + std::ascii::into_bytes( + std::type_name::get_address( + &std::type_name::get() + ) + ) + ) + ); + let cap_package = + object::id_to_address(&package::upgrade_package(cap)); + assert!( + ( + cap_package == expected_package && + package::upgrade_policy(cap) == expected_policy && + package::version(cap) == expected_version + ), + E_INVALID_UPGRADE_CAP + ); + } + + /// Assert that the version type passed into this method is what exists + /// as the current version. + public fun assert_version( + id: &UID, + _version: Version + ) { + assert!( + field::exists_with_type( + id, + CurrentVersion {} + ), + E_NOT_CURRENT_VERSION + ) + } + + // Retrieve the `TypeName` of a given version. + public fun type_of_version(_version: Version): TypeName { + type_name::get() + } + + /// Initialize package info and set the initial version. This should be done + /// when a contract's state/storage shared object is created. + public fun init_package_info( + id: &mut UID, + version: InitialVersion, + upgrade_cap: &UpgradeCap + ) { + let package = package::upgrade_package(upgrade_cap); + field::add( + id, + CurrentPackage {}, + PackageInfo { package, digest: bytes32::default() } + ); + + // Set placeholders for pending package. We don't ever plan on removing + // this field. + field::add( + id, + PendingPackage {}, + PackageInfo { package, digest: bytes32::default() } + ); + + // Set the initial version. + field::add(id, CurrentVersion {}, version); + } + + /// Perform the version switchover and copy package info from pending to + /// current. This method should be executed after an upgrade (via a migrate + /// method) from the upgraded package. + /// + /// NOTE: This method can only be called once with the same version type + /// arguments. + public fun migrate_version< + Old: store + drop, + New: store + drop + >( + id: &mut UID, + old_version: Old, + new_version: New + ) { + update_version_type(id, old_version, new_version); + + update_package_info_from_pending(id); + } + + /// Helper for `sui::package::authorize_upgrade` to modify pending package + /// info by updating its digest. + /// + /// NOTE: This digest will be copied over when `migrate_version` is called. + public fun authorize_upgrade( + id: &mut UID, + upgrade_cap: &mut UpgradeCap, + package_digest: Bytes32 + ): UpgradeTicket { + let policy = package::upgrade_policy(upgrade_cap); + + // Manage saving the current digest. + set_authorized_digest(id, package_digest); + + // Finally authorize upgrade. + package::authorize_upgrade( + upgrade_cap, + policy, + bytes32::to_bytes(package_digest), + ) + } + + /// Helper for `sui::package::commit_upgrade` to modify pending package info + /// by updating its package ID with from what exists in the `UpgradeCap`. + /// This method returns the last package and the upgraded package IDs. + /// + /// NOTE: This package ID (second return value) will be copied over when + /// `migrate_version` is called. + public fun commit_upgrade( + id: &mut UID, + upgrade_cap: &mut UpgradeCap, + receipt: UpgradeReceipt + ): (ID, ID) { + // Uptick the upgrade cap version number using this receipt. + package::commit_upgrade(upgrade_cap, receipt); + + // Take the last pending package and replace it with the one now in + // the upgrade cap. + let previous_package = committed_package(id); + set_commited_package(id, upgrade_cap); + + // Return the package IDs. + (previous_package, committed_package(id)) + } + + fun set_commited_package(id: &mut UID, upgrade_cap: &UpgradeCap) { + let info: &mut PackageInfo = field::borrow_mut(id, PendingPackage {}); + info.package = package::upgrade_package(upgrade_cap); + } + + fun set_authorized_digest(id: &mut UID, digest: Bytes32) { + let info: &mut PackageInfo = field::borrow_mut(id, PendingPackage {}); + info.digest = digest; + } + + fun update_package_info_from_pending(id: &mut UID) { + let pending: PackageInfo = *field::borrow(id, PendingPackage {}); + *field::borrow_mut(id, CurrentPackage {}) = pending; + } + + /// Update from version n to n+1. We enforce that the versions be kept in + /// a module called "version_control". + fun update_version_type< + Old: store + drop, + New: store + drop + >( + id: &mut UID, + _old_version: Old, + new_version: New + ) { + use std::ascii::{into_bytes}; + + assert!( + field::exists_with_type(id, CurrentVersion {}), + E_INCORRECT_OLD_VERSION + ); + let _: Old = field::remove(id, CurrentVersion {}); + + let new_type = type_name::get(); + // Make sure the new type does not equal the old type, which means there + // is no protection against either build. + assert!(new_type != type_name::get(), E_SAME_VERSION); + + // Also make sure `New` originates from this module. + let module_name = into_bytes(type_name::get_module(&new_type)); + assert!(module_name == b"version_control", E_TYPE_NOT_ALLOWED); + + // Finally add the new version. + field::add(id, CurrentVersion {}, new_version); + } + + #[test_only] + public fun remove_package_info(id: &mut UID) { + let _: PackageInfo = field::remove(id, CurrentPackage {}); + let _: PackageInfo = field::remove(id, PendingPackage {}); + } + + #[test_only] + public fun init_version( + id: &mut UID, + version: Version + ) { + field::add(id, CurrentVersion {}, version); + } + + #[test_only] + public fun update_version_type_test_only< + Old: store + drop, + New: store + drop + >( + id: &mut UID, + old_version: Old, + new_version: New + ) { + update_version_type(id, old_version, new_version) + } +} + +#[test_only] +module wormhole::package_utils_tests { + use sui::object::{Self, UID}; + use sui::tx_context::{Self}; + + use wormhole::package_utils::{Self}; + use wormhole::version_control::{Self}; + + struct State has key { + id: UID + } + + struct V_DUMMY has store, drop, copy {} + + #[test] + fun test_assert_current() { + // Create dummy state. + let state = State { id: object::new(&mut tx_context::dummy()) }; + package_utils::init_version( + &mut state.id, + version_control::current_version() + ); + + package_utils::assert_version( + &state.id, + version_control::current_version() + ); + + // Clean up. + let State { id } = state; + object::delete(id); + } + + #[test] + #[expected_failure(abort_code = package_utils::E_INCORRECT_OLD_VERSION)] + fun test_cannot_update_incorrect_old_version() { + // Create dummy state. + let state = State { id: object::new(&mut tx_context::dummy()) }; + package_utils::init_version( + &mut state.id, + version_control::current_version() + ); + + package_utils::assert_version( + &state.id, + version_control::current_version() + ); + + // You shall not pass! + package_utils::update_version_type_test_only( + &mut state.id, + version_control::next_version(), + version_control::next_version() + ); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = package_utils::E_SAME_VERSION)] + fun test_cannot_update_same_version() { + // Create dummy state. + let state = State { id: object::new(&mut tx_context::dummy()) }; + package_utils::init_version( + &mut state.id, + version_control::current_version() + ); + + package_utils::assert_version( + &state.id, + version_control::current_version() + ); + + // You shall not pass! + package_utils::update_version_type_test_only( + &mut state.id, + version_control::current_version(), + version_control::current_version() + ); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = package_utils::E_NOT_CURRENT_VERSION)] + fun test_cannot_assert_current_outdated_version() { + // Create dummy state. + let state = State { id: object::new(&mut tx_context::dummy()) }; + package_utils::init_version( + &mut state.id, + version_control::current_version() + ); + + package_utils::assert_version( + &state.id, + version_control::current_version() + ); + + // Valid update. + package_utils::update_version_type_test_only( + &mut state.id, + version_control::current_version(), + version_control::next_version() + ); + + // You shall not pass! + package_utils::assert_version( + &state.id, + version_control::current_version() + ); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = package_utils::E_TYPE_NOT_ALLOWED)] + fun test_cannot_update_type_not_allowed() { + // Create dummy state. + let state = State { id: object::new(&mut tx_context::dummy()) }; + package_utils::init_version( + &mut state.id, + version_control::current_version() + ); + + package_utils::assert_version( + &state.id, + version_control::current_version() + ); + + // You shall not pass! + package_utils::update_version_type_test_only( + &mut state.id, + version_control::current_version(), + V_DUMMY {} + ); + + abort 42 + } + + #[test] + fun test_latest_version_different_from_previous() { + let prev = version_control::previous_version(); + let curr = version_control::current_version(); + assert!(package_utils::type_of_version(prev) != package_utils::type_of_version(curr), 0); + } +} diff --git a/sui/wormhole/sources/vaa.move b/sui/wormhole/sources/vaa.move new file mode 100644 index 000000000..03528351d --- /dev/null +++ b/sui/wormhole/sources/vaa.move @@ -0,0 +1,782 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements a mechanism to parse and verify VAAs, which are +/// verified Wormhole messages (messages with Guardian signatures attesting to +/// its observation). Signatures on VAA are checked against an existing Guardian +/// set that exists in the `State` (see `wormhole::state`). +/// +/// A Wormhole integrator is discouraged from integrating `parse_and_verify` in +/// his contract. If there is a breaking change to the `vaa` module, Wormhole +/// will be upgraded to prevent previous build versions of this module to work. +/// If an integrator happened to use `parse_and_verify` in his contract, he will +/// need to be prepared to upgrade his contract to take the change (by building +/// with the latest package implementation). +/// +/// Instead, an integrator is encouraged to execute a transaction block, which +/// executes `parse_and_verify` from the latest Wormhole package ID and to +/// implement his methods that require redeeming a VAA to take `VAA` as an +/// argument. +/// +/// A good example of how this methodology is implemented is how the Token +/// Bridge contract redeems its VAAs. +module wormhole::vaa { + use std::option::{Self}; + use std::vector::{Self}; + use sui::clock::{Clock}; + use sui::hash::{keccak256}; + + use wormhole::bytes::{Self}; + use wormhole::bytes32::{Self, Bytes32}; + use wormhole::consumed_vaas::{Self, ConsumedVAAs}; + use wormhole::cursor::{Self}; + use wormhole::external_address::{Self, ExternalAddress}; + use wormhole::guardian::{Self}; + use wormhole::guardian_set::{Self, GuardianSet}; + use wormhole::guardian_signature::{Self, GuardianSignature}; + use wormhole::state::{Self, State}; + + /// Incorrect VAA version. + const E_WRONG_VERSION: u64 = 0; + /// Not enough guardians attested to this Wormhole observation. + const E_NO_QUORUM: u64 = 1; + /// Signature does not match expected Guardian public key. + const E_INVALID_SIGNATURE: u64 = 2; + /// Prior guardian set is no longer valid. + const E_GUARDIAN_SET_EXPIRED: u64 = 3; + /// Guardian signature is encoded out of sequence. + const E_NON_INCREASING_SIGNERS: u64 = 4; + + const VERSION_VAA: u8 = 1; + + /// Container storing verified Wormhole message info. This struct also + /// caches the digest, which is a double Keccak256 hash of the message body. + struct VAA { + /// Guardian set index of Guardians that attested to observing the + /// Wormhole message. + guardian_set_index: u32, + /// Time when Wormhole message was emitted or observed. + timestamp: u32, + /// A.K.A. Batch ID. + nonce: u32, + /// Wormhole chain ID from which network the message originated from. + emitter_chain: u16, + /// Address of contract (standardized to 32 bytes) that produced the + /// message. + emitter_address: ExternalAddress, + /// Sequence number of emitter's Wormhole message. + sequence: u64, + /// A.K.A. Finality. + consistency_level: u8, + /// Arbitrary payload encoding data relevant to receiver. + payload: vector, + + /// Double Keccak256 hash of message body. + digest: Bytes32 + } + + public fun guardian_set_index(self: &VAA): u32 { + self.guardian_set_index + } + + public fun timestamp(self: &VAA): u32 { + self.timestamp + } + + public fun nonce(self: &VAA): u32 { + self.nonce + } + + public fun batch_id(self: &VAA): u32 { + nonce(self) + } + + public fun payload(self: &VAA): vector { + self.payload + } + + public fun digest(self: &VAA): Bytes32 { + self.digest + } + + public fun emitter_chain(self: &VAA): u16 { + self.emitter_chain + } + + public fun emitter_address(self: &VAA): ExternalAddress { + self.emitter_address + } + + public fun emitter_info(self: &VAA): (u16, ExternalAddress, u64) { + (self.emitter_chain, self.emitter_address, self.sequence) + } + + public fun sequence(self: &VAA): u64 { + self.sequence + } + + public fun consistency_level(self: &VAA): u8 { + self.consistency_level + } + + public fun finality(self: &VAA): u8 { + consistency_level(self) + } + + /// Destroy the `VAA` and take the Wormhole message payload. + public fun take_payload(vaa: VAA): vector { + let (_, _, payload) = take_emitter_info_and_payload(vaa); + + payload + } + + /// Destroy the `VAA` and take emitter info (chain and address) and Wormhole + /// message payload. + public fun take_emitter_info_and_payload( + vaa: VAA + ): (u16, ExternalAddress, vector) { + let VAA { + guardian_set_index: _, + timestamp: _, + nonce: _, + emitter_chain, + emitter_address, + sequence: _, + consistency_level: _, + digest: _, + payload, + } = vaa; + (emitter_chain, emitter_address, payload) + } + + /// Parses and verifies the signatures of a VAA. + /// + /// NOTE: This is the only public function that returns a VAA, and it should + /// be kept that way. This ensures that if an external module receives a + /// `VAA`, it has been verified. + public fun parse_and_verify( + wormhole_state: &State, + buf: vector, + the_clock: &Clock + ): VAA { + state::assert_latest_only(wormhole_state); + + // Deserialize VAA buffer (and return `VAA` after verifying signatures). + let (signatures, vaa) = parse(buf); + + // Fetch the guardian set which this VAA was supposedly signed with and + // verify signatures using guardian set. + verify_signatures( + state::guardian_set_at( + wormhole_state, + vaa.guardian_set_index + ), + signatures, + bytes32::to_bytes(compute_message_hash(&vaa)), + the_clock + ); + + // Done. + vaa + } + + public fun consume(consumed: &mut ConsumedVAAs, parsed: &VAA) { + consumed_vaas::consume(consumed, digest(parsed)) + } + + public fun compute_message_hash(parsed: &VAA): Bytes32 { + let buf = vector::empty(); + + bytes::push_u32_be(&mut buf, parsed.timestamp); + bytes::push_u32_be(&mut buf, parsed.nonce); + bytes::push_u16_be(&mut buf, parsed.emitter_chain); + vector::append( + &mut buf, + external_address::to_bytes(parsed.emitter_address) + ); + bytes::push_u64_be(&mut buf, parsed.sequence); + bytes::push_u8(&mut buf, parsed.consistency_level); + vector::append(&mut buf, parsed.payload); + + // Return hash. + bytes32::new(keccak256(&buf)) + } + + /// Parses a VAA. + /// + /// NOTE: This method does NOT perform any verification. This ensures the + /// invariant that if an external module receives a `VAA` object, its + /// signatures must have been verified, because the only public function + /// that returns a `VAA` is `parse_and_verify`. + fun parse(buf: vector): (vector, VAA) { + let cur = cursor::new(buf); + + // Check VAA version. + assert!( + bytes::take_u8(&mut cur) == VERSION_VAA, + E_WRONG_VERSION + ); + + let guardian_set_index = bytes::take_u32_be(&mut cur); + + // Deserialize guardian signatures. + let num_signatures = bytes::take_u8(&mut cur); + let signatures = vector::empty(); + let i = 0; + while (i < num_signatures) { + let guardian_index = bytes::take_u8(&mut cur); + let r = bytes32::take_bytes(&mut cur); + let s = bytes32::take_bytes(&mut cur); + let recovery_id = bytes::take_u8(&mut cur); + vector::push_back( + &mut signatures, + guardian_signature::new(r, s, recovery_id, guardian_index) + ); + i = i + 1; + }; + + // Deserialize message body. + let body_buf = cursor::take_rest(cur); + + let cur = cursor::new(body_buf); + let timestamp = bytes::take_u32_be(&mut cur); + let nonce = bytes::take_u32_be(&mut cur); + let emitter_chain = bytes::take_u16_be(&mut cur); + let emitter_address = external_address::take_bytes(&mut cur); + let sequence = bytes::take_u64_be(&mut cur); + let consistency_level = bytes::take_u8(&mut cur); + let payload = cursor::take_rest(cur); + + let parsed = VAA { + guardian_set_index, + timestamp, + nonce, + emitter_chain, + emitter_address, + sequence, + consistency_level, + digest: double_keccak256(body_buf), + payload, + }; + + (signatures, parsed) + } + + fun double_keccak256(buf: vector): Bytes32 { + use sui::hash::{keccak256}; + + bytes32::new(keccak256(&keccak256(&buf))) + } + + /// Using the Guardian signatures deserialized from VAA, verify that all of + /// the Guardian public keys are recovered using these signatures and the + /// VAA message body as the message used to produce these signatures. + /// + /// We are careful to only allow `wormhole:vaa` to control the hash that + /// gets used in the `ecdsa_k1` module by computing the hash after + /// deserializing the VAA message body. Even though `ecdsa_k1` hashes a + /// raw message (as of version 0.28), the "raw message" in this case is a + /// single keccak256 hash of the VAA message body. + fun verify_signatures( + set: &GuardianSet, + signatures: vector, + message_hash: vector, + the_clock: &Clock + ) { + // Guardian set must be active (not expired). + assert!( + guardian_set::is_active(set, the_clock), + E_GUARDIAN_SET_EXPIRED + ); + + // Number of signatures must be at least quorum. + assert!( + vector::length(&signatures) >= guardian_set::quorum(set), + E_NO_QUORUM + ); + + // Drain `Cursor` by checking each signature. + let cur = cursor::new(signatures); + let last_guardian_index = option::none(); + while (!cursor::is_empty(&cur)) { + let signature = cursor::poke(&mut cur); + let guardian_index = guardian_signature::index_as_u64(&signature); + + // Ensure that the provided signatures are strictly increasing. + // This check makes sure that no duplicate signers occur. The + // increasing order is guaranteed by the guardians, or can always be + // reordered by the client. + assert!( + ( + option::is_none(&last_guardian_index) || + guardian_index > *option::borrow(&last_guardian_index) + ), + E_NON_INCREASING_SIGNERS + ); + + // If the guardian pubkey cannot be recovered using the signature + // and message hash, revert. + assert!( + guardian::verify( + guardian_set::guardian_at(set, guardian_index), + signature, + message_hash + ), + E_INVALID_SIGNATURE + ); + + // Continue. + option::swap_or_fill(&mut last_guardian_index, guardian_index); + }; + + // Done. + cursor::destroy_empty(cur); + } + + #[test_only] + public fun parse_test_only( + buf: vector + ): (vector, VAA) { + parse(buf) + } + + #[test_only] + public fun destroy(vaa: VAA) { + take_payload(vaa); + } + + #[test_only] + public fun peel_payload_from_vaa(buf: &vector): vector { + // Just make sure that we are passing version 1 VAAs to this method. + assert!(*vector::borrow(buf, 0) == VERSION_VAA, E_WRONG_VERSION); + + // Find the location of the payload. + let num_signatures = (*vector::borrow(buf, 5) as u64); + let i = 57 + num_signatures * 66; + + // Push the payload bytes to `out` and return. + let out = vector::empty(); + let len = vector::length(buf); + while (i < len) { + vector::push_back(&mut out, *vector::borrow(buf, i)); + i = i + 1; + }; + + // Return the payload. + out + } +} + +#[test_only] +module wormhole::vaa_tests { + use std::vector::{Self}; + use sui::test_scenario::{Self}; + + use wormhole::bytes32::{Self}; + use wormhole::cursor::{Self}; + use wormhole::external_address::{Self}; + use wormhole::guardian_signature::{Self}; + use wormhole::state::{Self}; + use wormhole::vaa::{Self}; + use wormhole::version_control::{Self}; + use wormhole::wormhole_scenario::{ + guardians, + person, + return_clock, + return_state, + set_up_wormhole_with_guardians, + take_clock, + take_state + //upgrade_wormhole + }; + + const VAA_1: vector = + x"01000000000d009bafff633087a9587d9afb6d29bd74a3483b7a8d5619323a416fe9ca43b482cd5526fabe953157cfd42eea9ffa544babc0f3a025a8a6159217b96fc9ff586d560002c9367884940a43a1a4d86531ea33ccb41e52bd7d1679c106fdff756f3da2ca743f7c181fcf40d19151d0a8397335c1b71709279b6e4fa97b6e3de90824e841c801035a493b65bf12ab9b98aa4db3bfcb73df20ab854d8e5998a1552f3b3e57ea7cd3546187c62cd450d12d430cae0fb48124ae68034dae602fa3e2232b55257961f90104758e265101353923661f6df67cec3c38528ed1b68825099b5bb2ce3fb2e735c5073d90223bebd00cc10406a60413a6089b5fb9acee0a1b04a63a8d7db24c0bbc000587777306dd174e266c313f711e881086355b6ce66cf2bf1f5da58273a10be77813b5ffcafc1ba6b83645e326a7c1a3751496f279ba307a6cd554f2709c2f1eda0108ed23ba8264146c3e3cc0601c93260c25058bcdd25213a7834e51679afdc4b50104e3f3a3079ba45115e703096c7e0700354cd48348bbf686dcbc58be896c35a20009c2352cb46ef1d2ef9e185764650403aee87a1be071555b31cdcee0c346991da858defb8d5e164a293ce4377b54fc74b65e3acbdedcbb53c2bcc2688a0b5bd1c9010ae470b1573989f387f7c54a86325cc05978bbcbc13267e90e2fa2efb0e18bccb772252bd6d13ebf908f7f3f2caf20a45c17dec7168122a2535ea93d300fae7063000ba0e8770298d4e3567488f455455a33f1e723e1e629ba4f87928016aeaa5875561ec38bde5d934389dc657d80a927cd9d06a9d9c7ce910c98d77a576e3f31735c000eeeedc956cff4489ac55b52ca38233cdc11e88767e5cc82f664bd1d7c28dfb5a12d7d17620725aae08e499b021200919f42c50c05916cf425dcd6e59f24b4b233000f18d447c9608a076c066b30ee770910e3c133087d33e329ad0101f08f88d88e142623df87aa3842edcf34e10fd36271b49f7af73ff2a7bcf4a65a4306d59586f20111905fc99dc650d9b1b33c313e9b31dfdbc85ce57e9f31abc4841d5791a239f20e5f28e4e612db96aee2f49ae712f724466007aaf27309d0385005fe0264d33dd100127b46f2fbbbf12efb10c2e662b4449de404f6a408ad7f38c7ea40a46300930e9a3b1e02ce00b97e33fa8a87221c1fd9064ce966dc4772658b98f2ec1e28d13e7400000023280000000c002adeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef000000000000092a20416c6c20796f75722062617365206172652062656c6f6e6720746f207573"; + const VAA_DOUBLE_SIGNED: vector = + x"01000000000d009bafff633087a9587d9afb6d29bd74a3483b7a8d5619323a416fe9ca43b482cd5526fabe953157cfd42eea9ffa544babc0f3a025a8a6159217b96fc9ff586d560002c9367884940a43a1a4d86531ea33ccb41e52bd7d1679c106fdff756f3da2ca743f7c181fcf40d19151d0a8397335c1b71709279b6e4fa97b6e3de90824e841c80102c9367884940a43a1a4d86531ea33ccb41e52bd7d1679c106fdff756f3da2ca743f7c181fcf40d19151d0a8397335c1b71709279b6e4fa97b6e3de90824e841c801035a493b65bf12ab9b98aa4db3bfcb73df20ab854d8e5998a1552f3b3e57ea7cd3546187c62cd450d12d430cae0fb48124ae68034dae602fa3e2232b55257961f90104758e265101353923661f6df67cec3c38528ed1b68825099b5bb2ce3fb2e735c5073d90223bebd00cc10406a60413a6089b5fb9acee0a1b04a63a8d7db24c0bbc000587777306dd174e266c313f711e881086355b6ce66cf2bf1f5da58273a10be77813b5ffcafc1ba6b83645e326a7c1a3751496f279ba307a6cd554f2709c2f1eda0108ed23ba8264146c3e3cc0601c93260c25058bcdd25213a7834e51679afdc4b50104e3f3a3079ba45115e703096c7e0700354cd48348bbf686dcbc58be896c35a20009c2352cb46ef1d2ef9e185764650403aee87a1be071555b31cdcee0c346991da858defb8d5e164a293ce4377b54fc74b65e3acbdedcbb53c2bcc2688a0b5bd1c9010ae470b1573989f387f7c54a86325cc05978bbcbc13267e90e2fa2efb0e18bccb772252bd6d13ebf908f7f3f2caf20a45c17dec7168122a2535ea93d300fae7063000ba0e8770298d4e3567488f455455a33f1e723e1e629ba4f87928016aeaa5875561ec38bde5d934389dc657d80a927cd9d06a9d9c7ce910c98d77a576e3f31735c000f18d447c9608a076c066b30ee770910e3c133087d33e329ad0101f08f88d88e142623df87aa3842edcf34e10fd36271b49f7af73ff2a7bcf4a65a4306d59586f20111905fc99dc650d9b1b33c313e9b31dfdbc85ce57e9f31abc4841d5791a239f20e5f28e4e612db96aee2f49ae712f724466007aaf27309d0385005fe0264d33dd100127b46f2fbbbf12efb10c2e662b4449de404f6a408ad7f38c7ea40a46300930e9a3b1e02ce00b97e33fa8a87221c1fd9064ce966dc4772658b98f2ec1e28d13e7400000023280000000c002adeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef000000000000092a20416c6c20796f75722062617365206172652062656c6f6e6720746f207573"; + const VAA_NO_QUORUM: vector = + x"01000000000c009bafff633087a9587d9afb6d29bd74a3483b7a8d5619323a416fe9ca43b482cd5526fabe953157cfd42eea9ffa544babc0f3a025a8a6159217b96fc9ff586d560002c9367884940a43a1a4d86531ea33ccb41e52bd7d1679c106fdff756f3da2ca743f7c181fcf40d19151d0a8397335c1b71709279b6e4fa97b6e3de90824e841c801035a493b65bf12ab9b98aa4db3bfcb73df20ab854d8e5998a1552f3b3e57ea7cd3546187c62cd450d12d430cae0fb48124ae68034dae602fa3e2232b55257961f90104758e265101353923661f6df67cec3c38528ed1b68825099b5bb2ce3fb2e735c5073d90223bebd00cc10406a60413a6089b5fb9acee0a1b04a63a8d7db24c0bbc000587777306dd174e266c313f711e881086355b6ce66cf2bf1f5da58273a10be77813b5ffcafc1ba6b83645e326a7c1a3751496f279ba307a6cd554f2709c2f1eda0108ed23ba8264146c3e3cc0601c93260c25058bcdd25213a7834e51679afdc4b50104e3f3a3079ba45115e703096c7e0700354cd48348bbf686dcbc58be896c35a20009c2352cb46ef1d2ef9e185764650403aee87a1be071555b31cdcee0c346991da858defb8d5e164a293ce4377b54fc74b65e3acbdedcbb53c2bcc2688a0b5bd1c9010ae470b1573989f387f7c54a86325cc05978bbcbc13267e90e2fa2efb0e18bccb772252bd6d13ebf908f7f3f2caf20a45c17dec7168122a2535ea93d300fae7063000ba0e8770298d4e3567488f455455a33f1e723e1e629ba4f87928016aeaa5875561ec38bde5d934389dc657d80a927cd9d06a9d9c7ce910c98d77a576e3f31735c000f18d447c9608a076c066b30ee770910e3c133087d33e329ad0101f08f88d88e142623df87aa3842edcf34e10fd36271b49f7af73ff2a7bcf4a65a4306d59586f20111905fc99dc650d9b1b33c313e9b31dfdbc85ce57e9f31abc4841d5791a239f20e5f28e4e612db96aee2f49ae712f724466007aaf27309d0385005fe0264d33dd100127b46f2fbbbf12efb10c2e662b4449de404f6a408ad7f38c7ea40a46300930e9a3b1e02ce00b97e33fa8a87221c1fd9064ce966dc4772658b98f2ec1e28d13e7400000023280000000c002adeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef000000000000092a20416c6c20796f75722062617365206172652062656c6f6e6720746f207573"; + + #[test] + fun test_parse() { + let (signatures, parsed) = vaa::parse_test_only(VAA_1); + + let expected_signatures = + vector[ + guardian_signature::new( + bytes32::new( + x"9bafff633087a9587d9afb6d29bd74a3483b7a8d5619323a416fe9ca43b482cd" + ), // r + bytes32::new( + x"5526fabe953157cfd42eea9ffa544babc0f3a025a8a6159217b96fc9ff586d56" + ), // s + 0, // recovery_id + 0 // index + ), + guardian_signature::new( + bytes32::new( + x"c9367884940a43a1a4d86531ea33ccb41e52bd7d1679c106fdff756f3da2ca74" + ), // r + bytes32::new( + x"3f7c181fcf40d19151d0a8397335c1b71709279b6e4fa97b6e3de90824e841c8" + ), // s + 1, // recovery_id + 2 // index + ), + guardian_signature::new( + bytes32::new( + x"5a493b65bf12ab9b98aa4db3bfcb73df20ab854d8e5998a1552f3b3e57ea7cd3" + ), // r + bytes32::new( + x"546187c62cd450d12d430cae0fb48124ae68034dae602fa3e2232b55257961f9" + ), // s + 1, // recovery_id + 3 // index + ), + guardian_signature::new( + bytes32::new( + x"758e265101353923661f6df67cec3c38528ed1b68825099b5bb2ce3fb2e735c5" + ), // r + bytes32::new( + x"073d90223bebd00cc10406a60413a6089b5fb9acee0a1b04a63a8d7db24c0bbc" + ), // s + 0, // recovery_id + 4 // index + ), + guardian_signature::new( + bytes32::new( + x"87777306dd174e266c313f711e881086355b6ce66cf2bf1f5da58273a10be778" + ), // r + bytes32::new( + x"13b5ffcafc1ba6b83645e326a7c1a3751496f279ba307a6cd554f2709c2f1eda" + ), // s + 1, // recovery_id + 5 // index + ), + guardian_signature::new( + bytes32::new( + x"ed23ba8264146c3e3cc0601c93260c25058bcdd25213a7834e51679afdc4b501" + ), // r + bytes32::new( + x"04e3f3a3079ba45115e703096c7e0700354cd48348bbf686dcbc58be896c35a2" + ), // s + 0, // recovery_id + 8 // index + ), + guardian_signature::new( + bytes32::new( + x"c2352cb46ef1d2ef9e185764650403aee87a1be071555b31cdcee0c346991da8" + ), // r + bytes32::new( + x"58defb8d5e164a293ce4377b54fc74b65e3acbdedcbb53c2bcc2688a0b5bd1c9" + ), // s + 1, // recovery_id + 9 // index + ), + guardian_signature::new( + bytes32::new( + x"e470b1573989f387f7c54a86325cc05978bbcbc13267e90e2fa2efb0e18bccb7" + ), // r + bytes32::new( + x"72252bd6d13ebf908f7f3f2caf20a45c17dec7168122a2535ea93d300fae7063" + ), // s + 0, // recovery_id + 10 // index + ), + guardian_signature::new( + bytes32::new( + x"a0e8770298d4e3567488f455455a33f1e723e1e629ba4f87928016aeaa587556" + ), // r + bytes32::new( + x"1ec38bde5d934389dc657d80a927cd9d06a9d9c7ce910c98d77a576e3f31735c" + ), // s + 0, // recovery_id + 11 // index + ), + guardian_signature::new( + bytes32::new( + x"eeedc956cff4489ac55b52ca38233cdc11e88767e5cc82f664bd1d7c28dfb5a1" + ), // r + bytes32::new( + x"2d7d17620725aae08e499b021200919f42c50c05916cf425dcd6e59f24b4b233" + ), // s + 0, // recovery_id + 14 // index + ), + guardian_signature::new( + bytes32::new( + x"18d447c9608a076c066b30ee770910e3c133087d33e329ad0101f08f88d88e14" + ), // r + bytes32::new( + x"2623df87aa3842edcf34e10fd36271b49f7af73ff2a7bcf4a65a4306d59586f2" + ), // s + 1, // recovery_id + 15 // index + ), + guardian_signature::new( + bytes32::new( + x"905fc99dc650d9b1b33c313e9b31dfdbc85ce57e9f31abc4841d5791a239f20e" + ), // r + bytes32::new( + x"5f28e4e612db96aee2f49ae712f724466007aaf27309d0385005fe0264d33dd1" + ), // s + 0, // recovery_id + 17 // index + ), + guardian_signature::new( + bytes32::new( + x"7b46f2fbbbf12efb10c2e662b4449de404f6a408ad7f38c7ea40a46300930e9a" + ), // r + bytes32::new( + x"3b1e02ce00b97e33fa8a87221c1fd9064ce966dc4772658b98f2ec1e28d13e74" + ), // s + 0, // recovery_id + 18 // index + ) + ]; + assert!( + vector::length(&signatures) == vector::length(&expected_signatures), + 0 + ); + let left = cursor::new(signatures); + let right = cursor::new(expected_signatures); + while (!cursor::is_empty(&left)) { + assert!(cursor::poke(&mut left) == cursor::poke(&mut right), 0); + }; + cursor::destroy_empty(left); + cursor::destroy_empty(right); + + assert!(vaa::guardian_set_index(&parsed) == 0, 0); + assert!(vaa::timestamp(&parsed) == 9000, 0); + + let expected_batch_id = 12; + assert!(vaa::batch_id(&parsed) == expected_batch_id, 0); + assert!(vaa::nonce(&parsed) == expected_batch_id, 0); + + assert!(vaa::emitter_chain(&parsed) == 42, 0); + + let expected_emitter_address = + external_address::from_address( + @0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef + ); + assert!(vaa::emitter_address(&parsed) == expected_emitter_address, 0); + assert!(vaa::sequence(&parsed) == 2346, 0); + + let expected_finality = 32; + assert!(vaa::finality(&parsed) == expected_finality, 0); + assert!(vaa::consistency_level(&parsed) == expected_finality, 0); + + // The message Wormhole guardians sign is a hash of the actual message + // body. So the hash we need to check against is keccak256 of this + // message. + let body_buf = { + use wormhole::bytes::{Self}; + + let buf = vector::empty(); + bytes::push_u32_be(&mut buf, vaa::timestamp(&parsed)); + bytes::push_u32_be(&mut buf, vaa::nonce(&parsed)); + bytes::push_u16_be(&mut buf, vaa::emitter_chain(&parsed)); + vector::append( + &mut buf, + external_address::to_bytes(vaa::emitter_address(&parsed)) + ); + bytes::push_u64_be(&mut buf, vaa::sequence(&parsed)); + bytes::push_u8(&mut buf, vaa::consistency_level(&parsed)); + vector::append(&mut buf, vaa::payload(&parsed)); + + buf + }; + + let expected_message_hash = + bytes32::new(sui::hash::keccak256(&body_buf)); + assert!(vaa::compute_message_hash(&parsed) == expected_message_hash, 0); + + let expected_digest = + bytes32::new( + sui::hash::keccak256(&sui::hash::keccak256(&body_buf)) + ); + assert!(vaa::digest(&parsed) == expected_digest, 0); + + assert!( + vaa::take_payload(parsed) == b"All your base are belong to us", + 0 + ); + } + + #[test] + fun test_parse_and_verify() { + // Testing this method. + use wormhole::vaa::{parse_and_verify}; + + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + // Initialize Wormhole with 19 guardians. + let wormhole_fee = 350; + set_up_wormhole_with_guardians(scenario, wormhole_fee, guardians()); + + // Prepare test to execute `parse_and_verify`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + let verified_vaa = parse_and_verify(&worm_state, VAA_1, &the_clock); + + // We verified all parsed output in `test_parse`. But in destroying the + // parsed VAA, we will check the payload for the heck of it. + assert!( + vaa::take_payload(verified_vaa) == b"All your base are belong to us", + 0 + ); + + // Clean up. + return_state(worm_state); + return_clock(the_clock); + + // Done. + test_scenario::end(my_scenario); + } + + #[test] + #[expected_failure(abort_code = vaa::E_NO_QUORUM)] + fun test_cannot_parse_and_verify_without_quorum() { + // Testing this method. + use wormhole::vaa::{parse_and_verify}; + + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + // Initialize Wormhole with 19 guardians. + let wormhole_fee = 350; + set_up_wormhole_with_guardians(scenario, wormhole_fee, guardians()); + + // Prepare test to execute `parse_and_verify`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + // You shall not pass! + let verified_vaa = parse_and_verify(&worm_state, VAA_NO_QUORUM, &the_clock); + + // Clean up. + vaa::destroy(verified_vaa); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = vaa::E_NON_INCREASING_SIGNERS)] + fun test_cannot_parse_and_verify_non_increasing() { + // Testing this method. + use wormhole::vaa::{parse_and_verify}; + + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + // Initialize Wormhole with 19 guardians. + let wormhole_fee = 350; + set_up_wormhole_with_guardians(scenario, wormhole_fee, guardians()); + + // Prepare test to execute `parse_and_verify`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + // You shall not pass! + let verified_vaa = + parse_and_verify(&worm_state, VAA_DOUBLE_SIGNED, &the_clock); + + // Clean up. + vaa::destroy(verified_vaa); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = vaa::E_INVALID_SIGNATURE)] + fun test_cannot_parse_and_verify_invalid_signature() { + // Testing this method. + use wormhole::vaa::{parse_and_verify}; + + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + // Initialize Wormhole with 19 guardians. But reverse the order so the + // signatures will not match. + let initial_guardians = guardians(); + std::vector::reverse(&mut initial_guardians); + + let wormhole_fee = 350; + set_up_wormhole_with_guardians( + scenario, + wormhole_fee, + initial_guardians + ); + + // Prepare test to execute `parse_and_verify`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + // You shall not pass! + let verified_vaa = parse_and_verify(&worm_state, VAA_1, &the_clock); + + // Clean up. + vaa::destroy(verified_vaa); + + abort 42 + } + + #[test] + #[expected_failure(abort_code = wormhole::package_utils::E_NOT_CURRENT_VERSION)] + fun test_cannot_parse_and_verify_outdated_version() { + // Testing this method. + use wormhole::vaa::{parse_and_verify}; + + // Set up. + let caller = person(); + let my_scenario = test_scenario::begin(caller); + let scenario = &mut my_scenario; + + // Initialize Wormhole with 19 guardians. + let wormhole_fee = 350; + set_up_wormhole_with_guardians(scenario, wormhole_fee, guardians()); + + // Prepare test to execute `parse_and_verify`. + test_scenario::next_tx(scenario, caller); + + let worm_state = take_state(scenario); + let the_clock = take_clock(scenario); + + // Conveniently roll version back. + state::reverse_migrate_version(&mut worm_state); + + // Simulate executing with an outdated build by upticking the minimum + // required version for `publish_message` to something greater than + // this build. + state::migrate_version_test_only( + &mut worm_state, + version_control::previous_version_test_only(), + version_control::next_version() + ); + + // You shall not pass! + let verified_vaa = parse_and_verify(&worm_state, VAA_1, &the_clock); + + // Clean up. + vaa::destroy(verified_vaa); + + abort 42 + } +} diff --git a/sui/wormhole/sources/version_control.move b/sui/wormhole/sources/version_control.move new file mode 100644 index 000000000..13ee6106d --- /dev/null +++ b/sui/wormhole/sources/version_control.move @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: Apache 2 + +/// This module implements dynamic field keys as empty structs. These keys are +/// used to determine the latest version for this build. If the current version +/// is not this build's, then paths through the `state` module will abort. +/// +/// See `wormhole::state` and `wormhole::package_utils` for more info. +module wormhole::version_control { + //////////////////////////////////////////////////////////////////////////// + // + // Hard-coded Version Control + // + // Before upgrading, please set the types for `current_version` and + // `previous_version` to match the correct types (current being the latest + // version reflecting this build). + // + //////////////////////////////////////////////////////////////////////////// + + public(friend) fun current_version(): V__0_2_0 { + V__0_2_0 {} + } + + public(friend) fun previous_version(): V__DUMMY { + V__DUMMY {} + } + + #[test_only] + public fun previous_version_test_only(): V__DUMMY { + previous_version() + } + + //////////////////////////////////////////////////////////////////////////// + // + // Change Log + // + // Please write release notes as doc strings for each version struct. These + // notes will be our attempt at tracking upgrades. Wish us luck. + // + //////////////////////////////////////////////////////////////////////////// + + /// First published package on Sui mainnet. + struct V__0_2_0 has store, drop, copy {} + + // Dummy. + struct V__DUMMY has store, drop, copy {} + + //////////////////////////////////////////////////////////////////////////// + // + // Implementation and Test-Only Methods + // + //////////////////////////////////////////////////////////////////////////// + + friend wormhole::state; + + #[test_only] + friend wormhole::package_utils_tests; + + #[test_only] + public fun dummy(): V__DUMMY { + V__DUMMY {} + } + + #[test_only] + struct V__MIGRATED has store, drop, copy {} + + #[test_only] + public fun next_version(): V__MIGRATED { + V__MIGRATED {} + } +} diff --git a/sui/wormhole/sources/wormhole.move b/sui/wormhole/sources/wormhole.move deleted file mode 100644 index eebbd7934..000000000 --- a/sui/wormhole/sources/wormhole.move +++ /dev/null @@ -1,94 +0,0 @@ -module wormhole::wormhole { - use sui::sui::{SUI}; - use sui::coin::{Self, Coin}; - use sui::tx_context::{Self, TxContext}; - use sui::transfer::{Self}; - - //use wormhole::structs::{create_guardian, create_guardian_set}; - use wormhole::state::{Self, State}; - use wormhole::emitter::{Self}; - - const E_INSUFFICIENT_FEE: u64 = 0; - -// ----------------------------------------------------------------------------- -// Sending messages - public fun publish_message( - emitter_cap: &mut emitter::EmitterCapability, - state: &State, - nonce: u64, - payload: vector, - message_fee: Coin, - ): u64 { - // ensure that provided fee is sufficient to cover message fees - let expected_fee = state::get_message_fee(state); - assert!(expected_fee <= coin::value(&message_fee), E_INSUFFICIENT_FEE); - - // deposit the fees into the wormhole account - transfer::transfer(message_fee, @wormhole); - - // get sequence number - let sequence = emitter::use_sequence(emitter_cap); - - // emit event - state::publish_event( - emitter::get_emitter(emitter_cap), - sequence, - nonce, - payload, - ); - return sequence - } - - public entry fun publish_message_entry( - emitter_cap: &mut emitter::EmitterCapability, - state: &State, - nonce: u64, - payload: vector, - message_fee: Coin, - ) { - publish_message(emitter_cap, state, nonce, payload, message_fee); - } - - public entry fun publish_message_free( - emitter_cap: &mut emitter::EmitterCapability, - state: &mut State, - nonce: u64, - payload: vector, - ) { - // ensure that provided fee is sufficient to cover message fees - let expected_fee = state::get_message_fee(state); - assert!(expected_fee == 0, E_INSUFFICIENT_FEE); - - // get sender and sequence number - let sequence = emitter::use_sequence(emitter_cap); - - // emit event - state::publish_event( - emitter::get_emitter(emitter_cap), - sequence, - nonce, - payload, - ); - } - - // ----------------------------------------------------------------------------- - // Emitter registration - - public fun register_emitter(state: &mut State, ctx: &mut TxContext): emitter::EmitterCapability { - state::new_emitter(state, ctx) - } - - // ----------------------------------------------------------------------------- - // get_new_emitter - // - // Honestly, unsure if this should survive once we get into code review but it - // sure makes writing my test script work quite well - // - // This creates a new emitter object and stores it away into the senders context. - // - // You can then use this to call publish_message_free and generate a vaa - - public entry fun get_new_emitter(state: &mut State, ctx: &mut TxContext) { - transfer::transfer(state::new_emitter(state, ctx), tx_context::sender(ctx)); - } -}