From 25abafc753b0af6cd42ece13a6c177905a8b4d5c Mon Sep 17 00:00:00 2001 From: Chirantan Ekbote Date: Thu, 15 Dec 2022 02:06:45 +0900 Subject: [PATCH] cosmwasm: Add wormchain-accounting contract (#1920) * sdk/rust: Move profile settings to workspace * sdk/rust: Add serde_wormhole crate The serde_wormhole crate implements the wormhole wire format as a serde data format. This will let us replace all the hand-rolled serialization with auto-generated code, which is less error-prone and easier to review. * sdk/rust: Add serde-based struct defintions Refactor the core crate to add serde-based struct definitions for the various messages used by the different wormhole smart contracts. This will also make it easier to use alternate data formats (like json) for client-side tooling. Co-authored-by: Reisen * sdk/rust: Drop references to `de::Unexpected` The `de::Unexpected` enum from serde has a `Float(f64)` variant. Referencing this enum anywhere in the code will cause the compiler to emit its `fmt::Display` impl, which includes an `f64.load` instruction on wasm targets. Even if this instruction is never executed, its mere existence will cause cosmos chains to reject any cosmwasm contract that has it. Fix this by removing all references to `de::Unexpected`. * cosmwasm: Use cargo resolver version "2" Enable the new feature resolver for the entire workspace. This prevents features that are enabled only for dev builds from also being enabled in normal builds. * Move cosmwasm Dockerfile to root directory The cosmwasm contracts now also depend on the rust sdk so the docker build context needs to be set to the root directory rather than the cosmwasm/ directory. * cosmwasm: Add wormchain-accounting contract This contract implements tokenbridge accounting specifically for the wormchain environment. Fixes #1880. * cosmwasm/accounting: Drop references to `de::Unexpected` The `de::Unexpected` enum from serde has a `Float(f64)` variant. Referencing this enum anywhere in the code will cause the compiler to emit its `fmt::Display` impl, which includes an `f64.load` instruction on wasm targets. Even if this instruction is never executed, its mere existence will cause cosmos chains to reject any cosmwasm contracts that contain it. Fix this by removing references to `de::Unexpected`. Co-authored-by: Reisen --- cosmwasm/Dockerfile => Dockerfile.cosmwasm | 16 +- Tiltfile | 4 +- cosmwasm/Cargo.lock | 747 +++++++----- cosmwasm/Cargo.toml | 8 + cosmwasm/Makefile | 2 +- .../contracts/wormchain-accounting/Cargo.toml | 37 + .../examples/wormchain_accounting_schema.rs | 11 + .../schema/wormchain-accounting.json | 1031 +++++++++++++++++ .../wormchain-accounting/src/contract.rs | 461 ++++++++ .../wormchain-accounting/src/error.rs | 59 + .../contracts/wormchain-accounting/src/lib.rs | 6 + .../contracts/wormchain-accounting/src/msg.rs | 145 +++ .../wormchain-accounting/src/state.rs | 69 ++ .../tests/helpers/fake_tokenbridge.rs | 23 + .../wormchain-accounting/tests/helpers/mod.rs | 310 +++++ .../wormchain-accounting/tests/instantiate.rs | 29 + .../tests/modify_balance.rs | 243 ++++ .../wormchain-accounting/tests/query.rs | 276 +++++ .../tests/submit_observations.rs | 755 ++++++++++++ .../tests/upgrade_contract.rs | 60 + .../packages/accounting/src/state/addr.rs | 2 +- .../packages/wormhole-bindings/Cargo.toml | 5 + .../packages/wormhole-bindings/src/fake.rs | 234 ++++ .../packages/wormhole-bindings/src/lib.rs | 2 + cosmwasm/tools/deploy.js | 1 + 25 files changed, 4234 insertions(+), 302 deletions(-) rename cosmwasm/Dockerfile => Dockerfile.cosmwasm (78%) create mode 100644 cosmwasm/contracts/wormchain-accounting/Cargo.toml create mode 100644 cosmwasm/contracts/wormchain-accounting/examples/wormchain_accounting_schema.rs create mode 100644 cosmwasm/contracts/wormchain-accounting/schema/wormchain-accounting.json create mode 100644 cosmwasm/contracts/wormchain-accounting/src/contract.rs create mode 100644 cosmwasm/contracts/wormchain-accounting/src/error.rs create mode 100644 cosmwasm/contracts/wormchain-accounting/src/lib.rs create mode 100644 cosmwasm/contracts/wormchain-accounting/src/msg.rs create mode 100644 cosmwasm/contracts/wormchain-accounting/src/state.rs create mode 100644 cosmwasm/contracts/wormchain-accounting/tests/helpers/fake_tokenbridge.rs create mode 100644 cosmwasm/contracts/wormchain-accounting/tests/helpers/mod.rs create mode 100644 cosmwasm/contracts/wormchain-accounting/tests/instantiate.rs create mode 100644 cosmwasm/contracts/wormchain-accounting/tests/modify_balance.rs create mode 100644 cosmwasm/contracts/wormchain-accounting/tests/query.rs create mode 100644 cosmwasm/contracts/wormchain-accounting/tests/submit_observations.rs create mode 100644 cosmwasm/contracts/wormchain-accounting/tests/upgrade_contract.rs create mode 100644 cosmwasm/packages/wormhole-bindings/src/fake.rs diff --git a/cosmwasm/Dockerfile b/Dockerfile.cosmwasm similarity index 78% rename from cosmwasm/Dockerfile rename to Dockerfile.cosmwasm index 440a91c57..cfc974815 100644 --- a/cosmwasm/Dockerfile +++ b/Dockerfile.cosmwasm @@ -3,10 +3,12 @@ # 2. The second is an empty image with only the wasm files (useful for exporting) # 3. The third creates a node.js environment to deploy the contracts to devnet FROM cosmwasm/workspace-optimizer:0.12.6@sha256:e6565a5e87c830ef3e8775a9035006b38ad0aaf0a96319158c802457b1dd1d08 AS builder -COPY Cargo.lock /code/ -COPY Cargo.toml /code/ -COPY contracts /code/contracts -COPY packages /code/packages + +COPY cosmwasm/Cargo.lock /code/ +COPY cosmwasm/Cargo.toml /code/ +COPY cosmwasm/contracts /code/contracts +COPY cosmwasm/packages /code/packages +COPY sdk/rust /sdk/rust # Support additional root CAs COPY README.md cert.pem* /certs/ @@ -34,9 +36,9 @@ RUN apt update && apt install netcat curl jq -y WORKDIR /app/tools COPY --from=artifacts / /app/artifacts -COPY ./artifacts/cw20_base.wasm /app/artifacts/ +COPY ./cosmwasm/artifacts/cw20_base.wasm /app/artifacts/ -COPY ./tools/package.json ./tools/package-lock.json /app/tools/ +COPY ./cosmwasm/tools/package.json ./cosmwasm/tools/package-lock.json /app/tools/ RUN --mount=type=cache,uid=1000,gid=1000,target=/home/node/.npm \ npm ci -COPY ./tools /app/tools +COPY ./cosmwasm/tools /app/tools diff --git a/Tiltfile b/Tiltfile index 14a79b973..051b466c5 100644 --- a/Tiltfile +++ b/Tiltfile @@ -599,8 +599,8 @@ if terra2: docker_build( ref = "terra2-contracts", - context = "./cosmwasm", - dockerfile = "./cosmwasm/Dockerfile", + context = ".", + dockerfile = "./Dockerfile.cosmwasm", ) k8s_yaml_with_ns("devnet/terra2-devnet.yaml") diff --git a/cosmwasm/Cargo.lock b/cosmwasm/Cargo.lock index fe4297a57..0460cd65e 100644 --- a/cosmwasm/Cargo.lock +++ b/cosmwasm/Cargo.lock @@ -23,7 +23,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" dependencies = [ - "gimli 0.26.1", + "gimli 0.26.2", ] [[package]] @@ -38,7 +38,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.3", + "getrandom", "once_cell", "version_check", ] @@ -54,22 +54,22 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.63" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "321629d8ba6513061f26707241fa9bc89524ff1cd7a915a97ef0c62c666ce1b6" +checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" dependencies = [ "addr2line", "cc", "cfg-if", "libc", "miniz_oxide", - "object 0.27.1", + "object 0.29.0", "rustc-demangle", ] @@ -81,15 +81,15 @@ checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" [[package]] name = "base64" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64ct" -version = "1.5.0" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea908e7347a8c64e378c17e30ef880ad73e3b4498346b055c2c00ea342f3179" +checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" [[package]] name = "bigint" @@ -117,6 +117,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + [[package]] name = "block-padding" version = "0.2.1" @@ -124,16 +133,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] -name = "bumpalo" -version = "3.10.0" +name = "bstr" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" +checksum = "fca0852af221f458706eb0725c03e4ed6c46af9ac98e6a689d5e634215d594dd" +dependencies = [ + "memchr", + "once_cell", + "regex-automata", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "bytecheck" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a31f923c2db9513e4298b72df143e6e655a759b3d6a0966df18f81223fff54f" +checksum = "d11cac2c12b5adc6570dad2ee1b87eff4955dac476fe12d81e5fdd352e52406f" dependencies = [ "bytecheck_derive", "ptr_meta", @@ -141,9 +162,9 @@ dependencies = [ [[package]] name = "bytecheck_derive" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb17c862a905d912174daa27ae002326fff56dc8b8ada50a0a5f0976cb174f0" +checksum = "13e576ebe98e605500b3c8041bb888e966653577172df6dd97398714eb30b9bf" dependencies = [ "proc-macro2", "quote", @@ -158,15 +179,15 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" [[package]] name = "cc" -version = "1.0.72" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" +checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" [[package]] name = "cfg-if" @@ -180,6 +201,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "591ff76ca0691bd91c1b0b5b987e5cf93b21ec810ad96665c5a569c60846dd93" +[[package]] +name = "const-oid" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b" + [[package]] name = "const-oid" version = "0.7.1" @@ -192,27 +219,27 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5eb0afef2325df81aadbf9be1233f522ed8f6e91df870c764bc44cca2b1415bd" dependencies = [ - "digest", + "digest 0.9.0", "ed25519-zebra", "k256 0.10.4", - "rand_core 0.6.3", + "rand_core 0.6.4", "thiserror", ] [[package]] name = "cosmwasm-derive" -version = "1.0.0" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b36e527620a2a3e00e46b6e731ab6c9b68d11069c986f7d7be8eba79ef081a4" +checksum = "a552716cf87ad173cd6b593fd72bf24fab490e0e5420aa75a227d6817d06b5df" dependencies = [ "syn", ] [[package]] name = "cosmwasm-schema" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a227cfeb9a7152b26a354b1c990e930e962f75fd68f57ab5ae2ef888c8524292" +checksum = "98c025d629589ca5d43fb8ff06decc387a4d01ead14f6b505300777fc6f01e1d" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -223,9 +250,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3626cb42eef870de67f791e873711255325224d86f281bf628c42abd295f3a14" +checksum = "6f09a65d22861a24a1b30eab6aa6759333723629bc2cb568cd22569745e1dcb2" dependencies = [ "proc-macro2", "quote", @@ -282,9 +309,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.1" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" dependencies = [ "libc", ] @@ -351,18 +378,18 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-channel" -version = "0.5.1" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" dependencies = [ "cfg-if", "crossbeam-utils", @@ -370,9 +397,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ "cfg-if", "crossbeam-epoch", @@ -381,25 +408,24 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.5" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" +checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" dependencies = [ + "autocfg", "cfg-if", "crossbeam-utils", - "lazy_static", "memoffset", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.5" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" dependencies = [ "cfg-if", - "lazy_static", ] [[package]] @@ -421,7 +447,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f83bd3bb4314701c568e340cd8cf78c975aa0ca79e03d3f6d1677d5b0c9c0c03" dependencies = [ "generic-array", - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle", "zeroize", ] @@ -433,11 +459,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" dependencies = [ "generic-array", - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle", "zeroize", ] +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "crypto-mac" version = "0.11.1" @@ -455,12 +491,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" dependencies = [ "byteorder", - "digest", + "digest 0.9.0", "rand_core 0.5.1", "subtle", "zeroize", ] +[[package]] +name = "cw-multi-test" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f9a8ab7c3c29ec93cb7a39ce4b14a05e053153b4a17ef7cf2246af1b7c087e" +dependencies = [ + "anyhow", + "cosmwasm-std", + "cosmwasm-storage", + "cw-storage-plus", + "cw-utils", + "derivative", + "itertools", + "prost", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "cw-storage-plus" version = "0.13.4" @@ -541,9 +596,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.13.0" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "757c0ded2af11d8e739c4daea1ac623dd1624b06c844cf3f5a39f1bdbd99bb12" +checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa" dependencies = [ "darling_core", "darling_macro", @@ -551,23 +606,22 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.13.0" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c34d8efb62d0c2d7f60ece80f75e5c63c1588ba68032740494b0b9a996466e3" +checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "strsim", "syn", ] [[package]] name = "darling_macro" -version = "0.13.0" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade7bff147130fe5e6d39f089c6bd49ec0250f35d70b2eebf72afdfc919f15cc" +checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e" dependencies = [ "darling_core", "quote", @@ -576,9 +630,12 @@ dependencies = [ [[package]] name = "der" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28e98c534e9c8a0483aa01d6f6913bc063de254311bd267c9cf535e9b70e15b2" +checksum = "79b71cca7d95d7681a4b3b9cdf63c8dbc3730d0584c2c74e31416d64a90493f4" +dependencies = [ + "const-oid 0.6.2", +] [[package]] name = "der" @@ -586,7 +643,18 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" dependencies = [ - "const-oid", + "const-oid 0.7.1", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -599,10 +667,20 @@ dependencies = [ ] [[package]] -name = "dyn-clone" -version = "1.0.4" +name = "digest" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer 0.10.3", + "crypto-common", +] + +[[package]] +name = "dyn-clone" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" [[package]] name = "dynasm" @@ -636,7 +714,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43ee23aa5b4f68c7a092b5c3beb25f50c406adc75e2363634f242f28ab255372" dependencies = [ - "der 0.4.4", + "der 0.4.5", "elliptic-curve 0.10.6", "hmac", "signature", @@ -656,24 +734,24 @@ dependencies = [ [[package]] name = "ed25519-zebra" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403ef3e961ab98f0ba902771d29f842058578bb1ce7e3c59dad5a6a93e784c69" +checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" dependencies = [ "curve25519-dalek", + "hashbrown 0.12.3", "hex", - "rand_core 0.6.3", + "rand_core 0.6.4", "serde", "sha2", - "thiserror", "zeroize", ] [[package]] name = "either" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "elliptic-curve" @@ -685,7 +763,8 @@ dependencies = [ "ff 0.10.1", "generic-array", "group 0.10.0", - "rand_core 0.6.3", + "pkcs8 0.7.6", + "rand_core 0.6.4", "subtle", "zeroize", ] @@ -702,7 +781,7 @@ dependencies = [ "ff 0.11.1", "generic-array", "group 0.11.0", - "rand_core 0.6.3", + "rand_core 0.6.4", "sec1", "subtle", "zeroize", @@ -730,18 +809,18 @@ dependencies = [ [[package]] name = "enumset" -version = "1.0.8" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6216d2c19a6fb5f29d1ada1dc7bc4367a8cbf0fa4af5cf12e07b5bbdde6b5b2c" +checksum = "19be8061a06ab6f3a6cf21106c873578bf01bd42ad15e0311a9c76161cb1c753" dependencies = [ "enumset_derive", ] [[package]] name = "enumset_derive" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6451128aa6655d880755345d085494cf7561a6bee7c8dc821e5d77e6d267ecd4" +checksum = "03e7b551eba279bf0fa88b83a46330168c1560a52a94f5126f892f0b364ab3e0" dependencies = [ "darling", "proc-macro2", @@ -755,13 +834,22 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + [[package]] name = "ff" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f" dependencies = [ - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle", ] @@ -771,7 +859,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle", ] @@ -789,9 +877,9 @@ checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" [[package]] name = "generic-array" -version = "0.14.4" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", @@ -799,24 +887,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.1.16" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.10.2+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -832,9 +909,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" [[package]] name = "group" @@ -843,7 +920,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912" dependencies = [ "ff 0.10.1", - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle", ] @@ -854,7 +931,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" dependencies = [ "ff 0.11.1", - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle", ] @@ -869,9 +946,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ "ahash", ] @@ -898,7 +975,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" dependencies = [ "crypto-mac", - "digest", + "digest 0.9.0", ] [[package]] @@ -909,26 +986,44 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" -version = "1.7.0" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", - "hashbrown 0.11.2", + "hashbrown 0.12.3", "serde", ] [[package]] -name = "itoa" -version = "0.4.8" +name = "instant" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" [[package]] name = "js-sys" -version = "0.3.57" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" dependencies = [ "wasm-bindgen", ] @@ -942,6 +1037,7 @@ dependencies = [ "cfg-if", "ecdsa 0.12.4", "elliptic-curve 0.10.6", + "sha2", ] [[package]] @@ -959,9 +1055,12 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.0" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" +checksum = "3afef3b6eff9ce9d8ff9b3601125eec7f0c8cbac7abd14f355d053fa56c98768" +dependencies = [ + "cpufeatures", +] [[package]] name = "lazy_static" @@ -977,15 +1076,15 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.108" +version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" [[package]] name = "libloading" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afe203d669ec979b7128619bae5a63b7b42e9203c1b29146079ee05e2f604b52" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" dependencies = [ "cfg-if", "winapi", @@ -993,9 +1092,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.14" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", ] @@ -1032,36 +1131,35 @@ dependencies = [ [[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" -version = "0.5.0" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4647a11b578fead29cdbb34d4adef8dd3dc35b876c9c6d5240d83f205abfe96e" +checksum = "4b182332558b18d807c4ce1ca8ca983b34c3ee32765e47b3f0f69b90355cc1dc" dependencies = [ "libc", ] [[package]] name = "memoffset" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ "autocfg", ] [[package]] name = "miniz_oxide" -version = "0.4.4" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" dependencies = [ "adler", - "autocfg", ] [[package]] @@ -1082,7 +1180,7 @@ dependencies = [ "schemars", "serde", "serde_json", - "sha3", + "sha3 0.9.1", "terraswap", "thiserror", "token-bridge-terra-2", @@ -1097,23 +1195,14 @@ checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" [[package]] name = "num_cpus" -version = "1.13.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" dependencies = [ "hermit-abi", "libc", ] -[[package]] -name = "object" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" -dependencies = [ - "memchr", -] - [[package]] name = "object" version = "0.28.4" @@ -1127,10 +1216,19 @@ dependencies = [ ] [[package]] -name = "once_cell" -version = "1.12.0" +name = "object" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "opaque-debug" @@ -1146,9 +1244,19 @@ checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" [[package]] name = "pin-project-lite" -version = "0.2.7" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pkcs8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447" +dependencies = [ + "der 0.4.5", + "spki 0.4.1", +] [[package]] name = "pkcs8" @@ -1157,16 +1265,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" dependencies = [ "der 0.5.1", - "spki", + "spki 0.5.4", "zeroize", ] -[[package]] -name = "ppv-lite86" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" - [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1193,18 +1295,41 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.39" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" dependencies = [ "unicode-ident", ] [[package]] -name = "protobuf" -version = "2.27.1" +name = "prost" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf7e6d18738ecd0902d30d1ad232c9125985a3422929b16c65517b38adc14f96" +checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "protobuf" +version = "2.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" dependencies = [ "bytes", ] @@ -1231,67 +1356,33 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.10" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" -dependencies = [ - "libc", - "rand_chacha", - "rand_core 0.6.3", - "rand_hc", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.3", -] - [[package]] name = "rand_core" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.3", -] - -[[package]] -name = "rand_hc" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" -dependencies = [ - "rand_core 0.6.3", + "getrandom", ] [[package]] name = "rayon" -version = "1.5.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" dependencies = [ "autocfg", "crossbeam-deque", @@ -1301,22 +1392,21 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.9.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" dependencies = [ "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "lazy_static", "num_cpus", ] [[package]] name = "redox_syscall" -version = "0.2.10" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] @@ -1332,6 +1422,12 @@ dependencies = [ "smallvec", ] +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" + [[package]] name = "region" version = "3.0.0" @@ -1375,12 +1471,12 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.38" +version = "0.7.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "517a3034eb2b1499714e9d1e49b2367ad567e07639b69776d35e259d9c27cca6" +checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15" dependencies = [ "bytecheck", - "hashbrown 0.12.1", + "hashbrown 0.12.3", "ptr_meta", "rend", "rkyv_derive", @@ -1389,9 +1485,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.38" +version = "0.7.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "505c209ee04111a006431abf39696e640838364d67a107c559ababaf6fd8c9dd" +checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" dependencies = [ "proc-macro2", "quote", @@ -1412,21 +1508,21 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustversion" -version = "1.0.5" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" +checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" [[package]] name = "ryu" -version = "1.0.5" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" [[package]] name = "schemars" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1847b767a3d62d95cbf3d8a9f0e421cf57a0d8aa4f411d4b16525afb0284d4ed" +checksum = "2a5fb6c61f29e723026dc8e923d94c694313212abbecbbe5f55a7748eec5b307" dependencies = [ "dyn-clone", "schemars_derive", @@ -1436,9 +1532,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af4d7e1b012cb3d9129567661a63755ea4b8a7386d339dc945ae187e403c6743" +checksum = "f188d036977451159430f3b8dc82ec76364a42b7e289c2b18a9a18f4470058e9" dependencies = [ "proc-macro2", "quote", @@ -1466,16 +1562,16 @@ checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" dependencies = [ "der 0.5.1", "generic-array", - "pkcs8", + "pkcs8 0.8.0", "subtle", "zeroize", ] [[package]] name = "serde" -version = "1.0.137" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" dependencies = [ "serde_derive", ] @@ -1491,18 +1587,18 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.5" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" +checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.137" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" dependencies = [ "proc-macro2", "quote", @@ -1522,9 +1618,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.71" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063bf466a64011ac24040a49009724ee60a57da1b437617ceb32e53ad61bfb19" +checksum = "8e8b3801309262e8184d9687fb697586833e939767aea0dda89f5a8e650e8bd7" dependencies = [ "itoa", "ryu", @@ -1532,15 +1628,24 @@ dependencies = [ ] [[package]] -name = "sha2" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" +name = "serde_wormhole" +version = "0.1.0" dependencies = [ - "block-buffer", + "itoa", + "serde", + "thiserror", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", "cfg-if", "cpufeatures", - "digest", + "digest 0.9.0", "opaque-debug", ] @@ -1550,12 +1655,22 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" dependencies = [ - "block-buffer", - "digest", + "block-buffer 0.9.0", + "digest 0.9.0", "keccak", "opaque-debug", ] +[[package]] +name = "sha3" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" +dependencies = [ + "digest 0.10.6", + "keccak", +] + [[package]] name = "shutdown-core-bridge-cosmwasm" version = "0.1.0" @@ -1576,15 +1691,24 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2807892cfa58e081aa1f1111391c7a0649d4fa127a4ffbe34bcbfb35a1171a4" dependencies = [ - "digest", - "rand_core 0.6.3", + "digest 0.9.0", + "rand_core 0.6.4", ] [[package]] name = "smallvec" -version = "1.7.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "spki" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32" +dependencies = [ + "der 0.4.5", +] [[package]] name = "spki" @@ -1608,12 +1732,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "subtle" version = "2.4.1" @@ -1622,9 +1740,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.96" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" dependencies = [ "proc-macro2", "quote", @@ -1633,19 +1751,19 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.2" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bffcddbc2458fa3e6058414599e3c838a022abae82e5c67b4f7f80298d5bff" +checksum = "9410d0f6853b1d94f0e519fb95df60f29d2c1eff2d921ffdf01a4c8a3b54f12d" [[package]] name = "tempfile" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ "cfg-if", + "fastrand", "libc", - "rand", "redox_syscall", "remove_dir_all", "winapi", @@ -1653,9 +1771,9 @@ dependencies = [ [[package]] name = "terraswap" -version = "2.6.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02458cd8271acd0fc98d097ca6d296f5f7abe6359ca841554db90de67c376500" +checksum = "b9540f8489ec6e098de380c9fa8fa81fa95e502f87d63705aa6fba56817ad1a7" dependencies = [ "cosmwasm-std", "cosmwasm-storage", @@ -1667,24 +1785,40 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.31" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.31" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "serde", + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + [[package]] name = "token-bridge-terra-2" version = "0.1.0" @@ -1702,7 +1836,7 @@ dependencies = [ "schemars", "serde", "serde_json", - "sha3", + "sha3 0.9.1", "terraswap", "thiserror", "wormhole-bridge-terra-2", @@ -1710,9 +1844,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.29" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", "log", @@ -1723,9 +1857,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.18" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", @@ -1734,24 +1868,24 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.21" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ - "lazy_static", + "once_cell", ] [[package]] name = "typenum" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "uint" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" +checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601" dependencies = [ "byteorder", "crunchy 0.2.2", @@ -1761,33 +1895,27 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.0" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" [[package]] name = "version_check" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.80" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1795,13 +1923,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.80" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", "syn", @@ -1810,9 +1938,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.80" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1820,9 +1948,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.80" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", @@ -1833,9 +1961,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.80" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "wasmer" @@ -2069,13 +2197,13 @@ checksum = "52144d4c78e5cf8b055ceab8e5fa22814ce4315d6002ad32cfd914f37c12fd65" [[package]] name = "which" -version = "4.2.2" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea187a8ef279bc014ec368c27a920da2024d2a711109bfbe3440585d5cf27ad9" +checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" dependencies = [ "either", - "lazy_static", "libc", + "once_cell", ] [[package]] @@ -2100,6 +2228,30 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "wormchain-accounting" +version = "0.1.0" +dependencies = [ + "accounting", + "anyhow", + "base64", + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-multi-test", + "cw-storage-plus", + "cw2", + "hex", + "schemars", + "serde", + "serde_wormhole", + "thiserror", + "tinyvec", + "token-bridge-terra-2", + "wormhole-bindings", + "wormhole-core", +] + [[package]] name = "wormhole-bindings" version = "0.1.0" @@ -2107,6 +2259,8 @@ dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", + "cw-multi-test", + "k256 0.9.6", "schemars", "serde", ] @@ -2121,17 +2275,28 @@ dependencies = [ "cw20-base", "cw20-wrapped-2", "generic-array", - "getrandom 0.2.3", + "getrandom", "hex", "k256 0.9.6", "lazy_static", "schemars", "serde", "serde_json", - "sha3", + "sha3 0.9.1", "thiserror", ] +[[package]] +name = "wormhole-core" +version = "0.1.0" +dependencies = [ + "anyhow", + "bstr", + "serde", + "serde_wormhole", + "sha3 0.10.6", +] + [[package]] name = "zeroize" version = "1.4.3" diff --git a/cosmwasm/Cargo.toml b/cosmwasm/Cargo.toml index c82bede3b..32edc160f 100644 --- a/cosmwasm/Cargo.toml +++ b/cosmwasm/Cargo.toml @@ -7,9 +7,14 @@ members = [ "contracts/shutdown-token-bridge", "contracts/mock-bridge-integration", "packages/accounting", + "contracts/wormchain-accounting", "packages/wormhole-bindings", ] +# Needed to prevent unwanted feature unification between normal builds and dev builds. See +# https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions for more details. +resolver = "2" + [profile.release] opt-level = 3 debug = false @@ -24,6 +29,9 @@ overflow-checks = true [patch.crates-io] accounting = { path = "packages/accounting" } cw20-wrapped-2 = { path = "contracts/cw20-wrapped" } +serde_wormhole = { path = "../sdk/rust/serde_wormhole" } token-bridge-terra-2 = { path = "contracts/token-bridge" } +wormchain-accounting = { path = "contracts/wormchain-accounting" } wormhole-bindings = { path = "packages/wormhole-bindings" } wormhole-bridge-terra-2 = { path = "contracts/wormhole" } +wormhole-core = { path = "../sdk/rust/core" } diff --git a/cosmwasm/Makefile b/cosmwasm/Makefile index c72b6241e..8aa944eda 100644 --- a/cosmwasm/Makefile +++ b/cosmwasm/Makefile @@ -22,7 +22,7 @@ ifndef VALID_$(NETWORK) endif $(WASMS) artifacts/checksums.txt: $(SOURCE_FILES) - DOCKER_BUILDKIT=1 docker build --target artifacts -o artifacts . + DOCKER_BUILDKIT=1 docker build --target artifacts -o artifacts -f ../Dockerfile.cosmwasm ../ payer-$(NETWORK).json: $(error Missing private key in payer-$(NETWORK).json) diff --git a/cosmwasm/contracts/wormchain-accounting/Cargo.toml b/cosmwasm/contracts/wormchain-accounting/Cargo.toml new file mode 100644 index 000000000..b6f3c265c --- /dev/null +++ b/cosmwasm/contracts/wormchain-accounting/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "wormchain-accounting" +version = "0.1.0" +authors = ["Wormhole Project Contributors"] +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +accounting = "0.1.0" +anyhow = "1" +base64 = "0.13" +cosmwasm-schema = "1" +cosmwasm-std = "1" +cosmwasm-storage = "1" +cw-storage-plus = "0.13.2" +cw2 = "0.13.2" +hex = "0.4.3" +schemars = "0.8.8" +serde = { version = "1.0.137", default-features = false, features = ["derive"] } +serde_wormhole = "0.1.0" +thiserror = { version = "1.0.31" } +tinyvec = { version = "1.6", default-features = false, features = ["alloc", "serde"]} +tokenbridge = { package = "token-bridge-terra-2", version = "0.1.0", features = ["library"] } +wormhole-bindings = "0.1.0" +wormhole-core = "0.1.0" + +[dev-dependencies] +anyhow = { version = "1", features = ["backtrace"] } +cw-multi-test = "0.13.2" +wormhole-bindings = { version = "0.1", features = ["fake"] } diff --git a/cosmwasm/contracts/wormchain-accounting/examples/wormchain_accounting_schema.rs b/cosmwasm/contracts/wormchain-accounting/examples/wormchain_accounting_schema.rs new file mode 100644 index 000000000..755f4d03f --- /dev/null +++ b/cosmwasm/contracts/wormchain-accounting/examples/wormchain_accounting_schema.rs @@ -0,0 +1,11 @@ +use cosmwasm_schema::write_api; + +use wormchain_accounting::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + } +} diff --git a/cosmwasm/contracts/wormchain-accounting/schema/wormchain-accounting.json b/cosmwasm/contracts/wormchain-accounting/schema/wormchain-accounting.json new file mode 100644 index 000000000..8b0a514bd --- /dev/null +++ b/cosmwasm/contracts/wormchain-accounting/schema/wormchain-accounting.json @@ -0,0 +1,1031 @@ +{ + "contract_name": "wormchain-accounting", + "contract_version": "0.1.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "guardian_set_index", + "instantiate", + "signatures" + ], + "properties": { + "guardian_set_index": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "instantiate": { + "$ref": "#/definitions/Binary" + }, + "signatures": { + "type": "array", + "items": { + "$ref": "#/definitions/Signature" + } + } + }, + "additionalProperties": false, + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", + "type": "string" + }, + "Signature": { + "type": "object", + "required": [ + "index", + "signature" + ], + "properties": { + "index": { + "description": "The index of the guardian in the guardian set.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "signature": { + "description": "The signature, which should be exactly 65 bytes with the following layout:\n\n```markdown 0 .. 64: Signature (ECDSA) 64 .. 65: Recovery ID (ECDSA) ```", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + }, + "additionalProperties": false + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "submit_observations" + ], + "properties": { + "submit_observations": { + "type": "object", + "required": [ + "guardian_set_index", + "observations", + "signature" + ], + "properties": { + "guardian_set_index": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "observations": { + "$ref": "#/definitions/Binary" + }, + "signature": { + "$ref": "#/definitions/Signature" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "modify_balance" + ], + "properties": { + "modify_balance": { + "type": "object", + "required": [ + "guardian_set_index", + "modification", + "signatures" + ], + "properties": { + "guardian_set_index": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "modification": { + "$ref": "#/definitions/Binary" + }, + "signatures": { + "type": "array", + "items": { + "$ref": "#/definitions/Signature" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "upgrade_contract" + ], + "properties": { + "upgrade_contract": { + "type": "object", + "required": [ + "guardian_set_index", + "signatures", + "upgrade" + ], + "properties": { + "guardian_set_index": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "signatures": { + "type": "array", + "items": { + "$ref": "#/definitions/Signature" + } + }, + "upgrade": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", + "type": "string" + }, + "Signature": { + "type": "object", + "required": [ + "index", + "signature" + ], + "properties": { + "index": { + "description": "The index of the guardian in the guardian set.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "signature": { + "description": "The signature, which should be exactly 65 bytes with the following layout:\n\n```markdown 0 .. 64: Signature (ECDSA) 64 .. 65: Recovery ID (ECDSA) ```", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + }, + "additionalProperties": false + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "balance" + ], + "properties": { + "balance": { + "$ref": "#/definitions/Key" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "all_accounts" + ], + "properties": { + "all_accounts": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "anyOf": [ + { + "$ref": "#/definitions/Key" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "$ref": "#/definitions/Key" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "all_transfers" + ], + "properties": { + "all_transfers": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "anyOf": [ + { + "$ref": "#/definitions/Key" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "pending_transfer" + ], + "properties": { + "pending_transfer": { + "$ref": "#/definitions/Key" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "all_pending_transfers" + ], + "properties": { + "all_pending_transfers": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "anyOf": [ + { + "$ref": "#/definitions/Key" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "modification" + ], + "properties": { + "modification": { + "type": "object", + "required": [ + "sequence" + ], + "properties": { + "sequence": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "all_modifications" + ], + "properties": { + "all_modifications": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Key": { + "type": "object", + "required": [ + "chain_id", + "token_address", + "token_chain" + ], + "properties": { + "chain_id": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "token_address": { + "$ref": "#/definitions/TokenAddress" + }, + "token_chain": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "TokenAddress": { + "type": "string" + } + } + }, + "migrate": null, + "sudo": null, + "responses": { + "all_accounts": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AllAccountsResponse", + "type": "object", + "required": [ + "accounts" + ], + "properties": { + "accounts": { + "type": "array", + "items": { + "$ref": "#/definitions/Account" + } + } + }, + "additionalProperties": false, + "definitions": { + "Account": { + "type": "object", + "required": [ + "balance", + "key" + ], + "properties": { + "balance": { + "$ref": "#/definitions/Balance" + }, + "key": { + "$ref": "#/definitions/Key" + } + }, + "additionalProperties": false + }, + "Balance": { + "$ref": "#/definitions/Uint256" + }, + "Key": { + "type": "object", + "required": [ + "chain_id", + "token_address", + "token_chain" + ], + "properties": { + "chain_id": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "token_address": { + "$ref": "#/definitions/TokenAddress" + }, + "token_chain": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "TokenAddress": { + "type": "string" + }, + "Uint256": { + "description": "An implementation of u256 that is using strings for JSON encoding/decoding, such that the full u256 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances out of primitive uint types or `new` to provide big endian bytes:\n\n``` # use cosmwasm_std::Uint256; let a = Uint256::from(258u128); let b = Uint256::new([ 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 1u8, 2u8, ]); assert_eq!(a, b); ```", + "type": "string" + } + } + }, + "all_modifications": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AllModificationsResponse", + "type": "object", + "required": [ + "modifications" + ], + "properties": { + "modifications": { + "type": "array", + "items": { + "$ref": "#/definitions/Modification" + } + } + }, + "additionalProperties": false, + "definitions": { + "Kind": { + "type": "string", + "enum": [ + "add", + "sub" + ] + }, + "Modification": { + "type": "object", + "required": [ + "amount", + "chain_id", + "kind", + "reason", + "sequence", + "token_address", + "token_chain" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint256" + }, + "chain_id": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "kind": { + "$ref": "#/definitions/Kind" + }, + "reason": { + "type": "string" + }, + "sequence": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "token_address": { + "$ref": "#/definitions/TokenAddress" + }, + "token_chain": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "TokenAddress": { + "type": "string" + }, + "Uint256": { + "description": "An implementation of u256 that is using strings for JSON encoding/decoding, such that the full u256 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances out of primitive uint types or `new` to provide big endian bytes:\n\n``` # use cosmwasm_std::Uint256; let a = Uint256::from(258u128); let b = Uint256::new([ 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 1u8, 2u8, ]); assert_eq!(a, b); ```", + "type": "string" + } + } + }, + "all_pending_transfers": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AllPendingTransfersResponse", + "type": "object", + "required": [ + "pending" + ], + "properties": { + "pending": { + "type": "array", + "items": { + "$ref": "#/definitions/PendingTransfer" + } + } + }, + "additionalProperties": false, + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", + "type": "string" + }, + "Data": { + "type": "object", + "required": [ + "guardian_set_index", + "observation", + "signatures" + ], + "properties": { + "guardian_set_index": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "observation": { + "$ref": "#/definitions/Observation" + }, + "signatures": { + "type": "array", + "items": { + "$ref": "#/definitions/Signature" + } + } + }, + "additionalProperties": false + }, + "Key": { + "type": "object", + "required": [ + "emitter_address", + "emitter_chain", + "sequence" + ], + "properties": { + "emitter_address": { + "$ref": "#/definitions/TokenAddress" + }, + "emitter_chain": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sequence": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "Observation": { + "type": "object", + "required": [ + "key", + "nonce", + "payload", + "tx_hash" + ], + "properties": { + "key": { + "$ref": "#/definitions/Key" + }, + "nonce": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "payload": { + "$ref": "#/definitions/Binary" + }, + "tx_hash": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + }, + "PendingTransfer": { + "type": "object", + "required": [ + "data", + "key" + ], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/Data" + } + }, + "key": { + "$ref": "#/definitions/Key" + } + }, + "additionalProperties": false + }, + "Signature": { + "type": "object", + "required": [ + "index", + "signature" + ], + "properties": { + "index": { + "description": "The index of the guardian in the guardian set.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "signature": { + "description": "The signature, which should be exactly 65 bytes with the following layout:\n\n```markdown 0 .. 64: Signature (ECDSA) 64 .. 65: Recovery ID (ECDSA) ```", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + }, + "additionalProperties": false + }, + "TokenAddress": { + "type": "string" + } + } + }, + "all_transfers": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AllTransfersResponse", + "type": "object", + "required": [ + "transfers" + ], + "properties": { + "transfers": { + "type": "array", + "items": { + "$ref": "#/definitions/Transfer" + } + } + }, + "additionalProperties": false, + "definitions": { + "Data": { + "type": "object", + "required": [ + "amount", + "recipient_chain", + "token_address", + "token_chain" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint256" + }, + "recipient_chain": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "token_address": { + "$ref": "#/definitions/TokenAddress" + }, + "token_chain": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "Key": { + "type": "object", + "required": [ + "emitter_address", + "emitter_chain", + "sequence" + ], + "properties": { + "emitter_address": { + "$ref": "#/definitions/TokenAddress" + }, + "emitter_chain": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sequence": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "TokenAddress": { + "type": "string" + }, + "Transfer": { + "type": "object", + "required": [ + "data", + "key" + ], + "properties": { + "data": { + "$ref": "#/definitions/Data" + }, + "key": { + "$ref": "#/definitions/Key" + } + }, + "additionalProperties": false + }, + "Uint256": { + "description": "An implementation of u256 that is using strings for JSON encoding/decoding, such that the full u256 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances out of primitive uint types or `new` to provide big endian bytes:\n\n``` # use cosmwasm_std::Uint256; let a = Uint256::from(258u128); let b = Uint256::new([ 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 1u8, 2u8, ]); assert_eq!(a, b); ```", + "type": "string" + } + } + }, + "balance": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Balance", + "allOf": [ + { + "$ref": "#/definitions/Uint256" + } + ], + "definitions": { + "Uint256": { + "description": "An implementation of u256 that is using strings for JSON encoding/decoding, such that the full u256 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances out of primitive uint types or `new` to provide big endian bytes:\n\n``` # use cosmwasm_std::Uint256; let a = Uint256::from(258u128); let b = Uint256::new([ 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 1u8, 2u8, ]); assert_eq!(a, b); ```", + "type": "string" + } + } + }, + "modification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Modification", + "type": "object", + "required": [ + "amount", + "chain_id", + "kind", + "reason", + "sequence", + "token_address", + "token_chain" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint256" + }, + "chain_id": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "kind": { + "$ref": "#/definitions/Kind" + }, + "reason": { + "type": "string" + }, + "sequence": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "token_address": { + "$ref": "#/definitions/TokenAddress" + }, + "token_chain": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Kind": { + "type": "string", + "enum": [ + "add", + "sub" + ] + }, + "TokenAddress": { + "type": "string" + }, + "Uint256": { + "description": "An implementation of u256 that is using strings for JSON encoding/decoding, such that the full u256 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances out of primitive uint types or `new` to provide big endian bytes:\n\n``` # use cosmwasm_std::Uint256; let a = Uint256::from(258u128); let b = Uint256::new([ 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 1u8, 2u8, ]); assert_eq!(a, b); ```", + "type": "string" + } + } + }, + "pending_transfer": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Data", + "type": "object", + "required": [ + "guardian_set_index", + "observation", + "signatures" + ], + "properties": { + "guardian_set_index": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "observation": { + "$ref": "#/definitions/Observation" + }, + "signatures": { + "type": "array", + "items": { + "$ref": "#/definitions/Signature" + } + } + }, + "additionalProperties": false, + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", + "type": "string" + }, + "Key": { + "type": "object", + "required": [ + "emitter_address", + "emitter_chain", + "sequence" + ], + "properties": { + "emitter_address": { + "$ref": "#/definitions/TokenAddress" + }, + "emitter_chain": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sequence": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "Observation": { + "type": "object", + "required": [ + "key", + "nonce", + "payload", + "tx_hash" + ], + "properties": { + "key": { + "$ref": "#/definitions/Key" + }, + "nonce": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "payload": { + "$ref": "#/definitions/Binary" + }, + "tx_hash": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + }, + "Signature": { + "type": "object", + "required": [ + "index", + "signature" + ], + "properties": { + "index": { + "description": "The index of the guardian in the guardian set.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "signature": { + "description": "The signature, which should be exactly 65 bytes with the following layout:\n\n```markdown 0 .. 64: Signature (ECDSA) 64 .. 65: Recovery ID (ECDSA) ```", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + }, + "additionalProperties": false + }, + "TokenAddress": { + "type": "string" + } + } + }, + "transfer": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Data", + "type": "object", + "required": [ + "amount", + "recipient_chain", + "token_address", + "token_chain" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint256" + }, + "recipient_chain": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "token_address": { + "$ref": "#/definitions/TokenAddress" + }, + "token_chain": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "TokenAddress": { + "type": "string" + }, + "Uint256": { + "description": "An implementation of u256 that is using strings for JSON encoding/decoding, such that the full u256 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances out of primitive uint types or `new` to provide big endian bytes:\n\n``` # use cosmwasm_std::Uint256; let a = Uint256::from(258u128); let b = Uint256::new([ 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 1u8, 2u8, ]); assert_eq!(a, b); ```", + "type": "string" + } + } + } + } +} diff --git a/cosmwasm/contracts/wormchain-accounting/src/contract.rs b/cosmwasm/contracts/wormchain-accounting/src/contract.rs new file mode 100644 index 000000000..3e6fe6de1 --- /dev/null +++ b/cosmwasm/contracts/wormchain-accounting/src/contract.rs @@ -0,0 +1,461 @@ +use std::marker::PhantomData; + +use accounting::{ + query_balance, query_modification, query_transfer, + state::{account, transfer, Modification, TokenAddress, Transfer}, +}; +use anyhow::{ensure, Context}; +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + from_binary, to_binary, Binary, ConversionOverflowError, CosmosMsg, Deps, DepsMut, Empty, Env, + Event, MessageInfo, Order, Response, StdError, StdResult, Uint256, WasmMsg, +}; +use cw2::set_contract_version; +use cw_storage_plus::Bound; +use tinyvec::{Array, TinyVec}; +use wormhole::token::Message; +use wormhole_bindings::{Signature, WormholeQuery}; + +use crate::{ + bail, + error::{AnyError, ContractError}, + msg::{ + AllAccountsResponse, AllModificationsResponse, AllPendingTransfersResponse, + AllTransfersResponse, ExecuteMsg, Instantiate, InstantiateMsg, MigrateMsg, Observation, + QueryMsg, Upgrade, + }, + state::{self, Data, PendingTransfer, PENDING_TRANSFERS, TOKENBRIDGE_ADDR}, +}; + +// version info for migration info +const CONTRACT_NAME: &str = "crates.io:wormchain-accounting"; +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION) + .context("failed to set contract version")?; + + let _: Empty = deps + .querier + .query( + &WormholeQuery::VerifyQuorum { + data: msg.instantiate.clone(), + guardian_set_index: msg.guardian_set_index, + signatures: msg.signatures, + } + .into(), + ) + .context(ContractError::VerifyQuorum)?; + + let init: Instantiate = + from_binary(&msg.instantiate).context("failed to parse `Instantiate` message")?; + + let tokenbridge_addr = deps + .api + .addr_validate(&init.tokenbridge_addr) + .context("failed to validate tokenbridge address")?; + + TOKENBRIDGE_ADDR + .save(deps.storage, &tokenbridge_addr) + .context("failed to save tokenbridge address")?; + + let event = + accounting::instantiate(deps, init.into()).context("failed to instantiate accounting")?; + + Ok(Response::new() + .add_attribute("action", "instantiate") + .add_attribute("owner", info.sender) + .add_event(event)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult { + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::SubmitObservations { + observations, + guardian_set_index, + signature, + } => submit_observations(deps, info, observations, guardian_set_index, signature), + ExecuteMsg::ModifyBalance { + modification, + guardian_set_index, + signatures, + } => modify_balance(deps, info, modification, guardian_set_index, signatures), + ExecuteMsg::UpgradeContract { + upgrade, + guardian_set_index, + signatures, + } => upgrade_contract(deps, env, info, upgrade, guardian_set_index, signatures), + } +} + +fn submit_observations( + mut deps: DepsMut, + info: MessageInfo, + observations: Binary, + guardian_set_index: u32, + signature: Signature, +) -> Result { + deps.querier + .query::( + &WormholeQuery::VerifySignature { + data: observations.clone(), + guardian_set_index, + signature: signature.clone(), + } + .into(), + ) + .context("failed to verify signature")?; + + let quorum = deps + .querier + .query::(&WormholeQuery::CalculateQuorum { guardian_set_index }.into()) + .and_then(|q| { + usize::try_from(q).map_err(|_| StdError::ConversionOverflow { + source: ConversionOverflowError::new("u32", "usize", q.to_string()), + }) + }) + .context("failed to calculate quorum")?; + + let observations: Vec = + from_binary(&observations).context("failed to parse `Observations`")?; + + let events = observations + .into_iter() + .map(|o| { + handle_observation( + deps.branch(), + o, + guardian_set_index, + quorum, + signature.clone(), + ) + }) + .filter_map(Result::transpose) + .collect::>>() + .context("failed to handle `Observation`")?; + + Ok(Response::new() + .add_attribute("action", "submit_observations") + .add_attribute("owner", info.sender) + .add_events(events)) +} + +fn handle_observation( + mut deps: DepsMut, + o: Observation, + guardian_set_index: u32, + quorum: usize, + sig: Signature, +) -> anyhow::Result> { + if accounting::has_transfer(deps.as_ref(), o.key.clone()) { + bail!("transfer for key \"{}\" already committed", o.key); + } + + let key = PENDING_TRANSFERS.key(o.key.clone()); + let mut pending = key + .may_load(deps.storage) + .map(Option::unwrap_or_default) + .context("failed to load `PendingTransfer`")?; + let data = match pending + .iter_mut() + .find(|d| d.guardian_set_index() == guardian_set_index && d.observation() == &o) + { + Some(d) => d, + None => { + pending.push(Data::new(o.clone(), guardian_set_index)); + let back = pending.len() - 1; + &mut pending[back] + } + }; + + data.add_signature(sig)?; + + if data.signatures().len() < quorum { + // Still need more signatures so just save the pending transfer data and exit. + key.save(deps.storage, &pending) + .context("failed to save pending transfers")?; + + return Ok(None); + } + + let (msg, _) = serde_wormhole::from_slice_with_payload(&o.payload) + .context("failed to parse observation payload")?; + let tx_data = match msg { + Message::Transfer { + amount, + token_address, + token_chain, + recipient_chain, + .. + } + | Message::TransferWithPayload { + amount, + token_address, + token_chain, + recipient_chain, + .. + } => transfer::Data { + amount: Uint256::from_be_bytes(amount.0), + token_address: TokenAddress::new(token_address.0), + token_chain: token_chain.into(), + recipient_chain: recipient_chain.into(), + }, + _ => bail!("Unknown tokenbridge payload"), + }; + + let emitter_chain = o.key.emitter_chain(); + + let tokenbridge_addr = TOKENBRIDGE_ADDR + .load(deps.storage) + .context("failed to load tokenbridge addr")?; + + let registered_emitter: Vec = deps + .querier + .query_wasm_smart( + tokenbridge_addr, + &tokenbridge::msg::QueryMsg::ChainRegistration { + chain: emitter_chain, + }, + ) + .context("failed to query chain registration")?; + ensure!( + *registered_emitter == **o.key.emitter_address(), + "unknown emitter address" + ); + + accounting::commit_transfer( + deps.branch(), + Transfer { + key: o.key.clone(), + data: tx_data, + }, + ) + .context("failed to commit transfer")?; + + // Now that the transfer has been committed, we don't need to keep it in the pending list. + key.remove(deps.storage); + + Ok(Some( + Event::new("Transfer") + .add_attribute("emitter_chain", o.key.emitter_chain().to_string()) + .add_attribute("emitter_address", o.key.emitter_address().to_string()) + .add_attribute("sequence", o.key.sequence().to_string()) + .add_attribute("nonce", o.nonce.to_string()) + .add_attribute("tx_hash", o.tx_hash.to_base64()) + .add_attribute("payload", o.payload.to_base64()), + )) +} + +fn modify_balance( + deps: DepsMut, + info: MessageInfo, + modification: Binary, + guardian_set_index: u32, + signatures: Vec, +) -> Result { + deps.querier + .query::( + &WormholeQuery::VerifyQuorum { + data: modification.clone(), + guardian_set_index, + signatures: signatures.into_iter().map(From::from).collect(), + } + .into(), + ) + .context(ContractError::VerifyQuorum)?; + + let msg: Modification = from_binary(&modification).context("failed to parse `Modification`")?; + + let event = + accounting::modify_balance(deps, msg).context("failed to modify account balance")?; + + Ok(Response::new() + .add_attribute("action", "modify_balance") + .add_attribute("owner", info.sender) + .add_event(event)) +} + +fn upgrade_contract( + deps: DepsMut, + env: Env, + info: MessageInfo, + upgrade: Binary, + guardian_set_index: u32, + signatures: Vec, +) -> Result { + deps.querier + .query::( + &WormholeQuery::VerifyQuorum { + data: upgrade.clone(), + guardian_set_index, + signatures: signatures.into_iter().map(From::from).collect(), + } + .into(), + ) + .context(ContractError::VerifyQuorum)?; + + let Upgrade { new_addr } = from_binary(&upgrade).context("failed to parse `Upgrade`")?; + + let mut buf = 0u64.to_ne_bytes(); + buf.copy_from_slice(&new_addr[24..]); + let new_contract = u64::from_be_bytes(buf); + + Ok(Response::new() + .add_message(CosmosMsg::Wasm(WasmMsg::Migrate { + contract_addr: env.contract.address.to_string(), + new_code_id: new_contract, + msg: to_binary(&MigrateMsg {})?, + })) + .add_attribute("action", "contract_upgrade") + .add_attribute("owner", info.sender)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Balance(key) => query_balance(deps, key).and_then(|resp| to_binary(&resp)), + QueryMsg::AllAccounts { start_after, limit } => { + query_all_accounts(deps, start_after, limit).and_then(|resp| to_binary(&resp)) + } + QueryMsg::Transfer(req) => query_transfer(deps, req).and_then(|resp| to_binary(&resp)), + QueryMsg::AllTransfers { start_after, limit } => { + query_all_transfers(deps, start_after, limit).and_then(|resp| to_binary(&resp)) + } + QueryMsg::PendingTransfer(req) => { + query_pending_transfer(deps, req).and_then(|resp| to_binary(&resp)) + } + QueryMsg::AllPendingTransfers { start_after, limit } => { + query_all_pending_transfers(deps, start_after, limit).and_then(|resp| to_binary(&resp)) + } + QueryMsg::Modification { sequence } => { + query_modification(deps, sequence).and_then(|resp| to_binary(&resp)) + } + QueryMsg::AllModifications { start_after, limit } => { + query_all_modifications(deps, start_after, limit).and_then(|resp| to_binary(&resp)) + } + } +} + +fn query_all_accounts( + deps: Deps, + start_after: Option, + limit: Option, +) -> StdResult { + if let Some(lim) = limit { + let l = lim + .try_into() + .map_err(|_| ConversionOverflowError::new("u32", "usize", lim.to_string()))?; + accounting::query_all_accounts(deps, start_after) + .take(l) + .collect::>>() + .map(|accounts| AllAccountsResponse { accounts }) + } else { + accounting::query_all_accounts(deps, start_after) + .collect::>>() + .map(|accounts| AllAccountsResponse { accounts }) + } +} + +fn query_all_transfers( + deps: Deps, + start_after: Option, + limit: Option, +) -> StdResult { + if let Some(lim) = limit { + let l = lim + .try_into() + .map_err(|_| ConversionOverflowError::new("u32", "usize", lim.to_string()))?; + accounting::query_all_transfers(deps, start_after) + .take(l) + .collect::>>() + .map(|transfers| AllTransfersResponse { transfers }) + } else { + accounting::query_all_transfers(deps, start_after) + .collect::>>() + .map(|transfers| AllTransfersResponse { transfers }) + } +} + +#[inline] +fn tinyvec_to_vec(tv: TinyVec) -> Vec { + match tv { + TinyVec::Inline(mut arr) => arr.drain_to_vec(), + TinyVec::Heap(v) => v, + } +} + +fn query_pending_transfer( + deps: Deps, + key: transfer::Key, +) -> StdResult> { + PENDING_TRANSFERS + .load(deps.storage, key) + .map(tinyvec_to_vec) +} + +fn query_all_pending_transfers( + deps: Deps, + start_after: Option, + limit: Option, +) -> StdResult { + let start = start_after.map(|key| Bound::Exclusive((key, PhantomData))); + + let iter = PENDING_TRANSFERS + .range(deps.storage, start, None, Order::Ascending) + .map(|item| { + item.map(|(key, tv)| PendingTransfer { + key, + data: tinyvec_to_vec(tv), + }) + }); + + if let Some(lim) = limit { + let l = lim + .try_into() + .map_err(|_| ConversionOverflowError::new("u32", "usize", lim.to_string()))?; + iter.take(l) + .collect::>>() + .map(|pending| AllPendingTransfersResponse { pending }) + } else { + iter.collect::>>() + .map(|pending| AllPendingTransfersResponse { pending }) + } +} + +fn query_all_modifications( + deps: Deps, + start_after: Option, + limit: Option, +) -> StdResult { + if let Some(lim) = limit { + let l = lim + .try_into() + .map_err(|_| ConversionOverflowError::new("u32", "usize", lim.to_string()))?; + accounting::query_all_modifications(deps, start_after) + .take(l) + .collect::>>() + .map(|modifications| AllModificationsResponse { modifications }) + } else { + accounting::query_all_modifications(deps, start_after) + .collect::>>() + .map(|modifications| AllModificationsResponse { modifications }) + } +} diff --git a/cosmwasm/contracts/wormchain-accounting/src/error.rs b/cosmwasm/contracts/wormchain-accounting/src/error.rs new file mode 100644 index 000000000..9b817b1e6 --- /dev/null +++ b/cosmwasm/contracts/wormchain-accounting/src/error.rs @@ -0,0 +1,59 @@ +use std::ops::{Deref, DerefMut}; + +use anyhow::anyhow; +use cosmwasm_std::StdError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("failed to verify quorum")] + VerifyQuorum, +} + +// This is a workaround for the fact that `cw_multi_test::ContractWrapper` doesn't support contract +// functions returning `anyhow::Error` directly. +#[derive(Error, Debug)] +#[repr(transparent)] +#[error("{0:#}")] +pub struct AnyError(#[from] anyhow::Error); + +impl Deref for AnyError { + type Target = anyhow::Error; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for AnyError { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl From for AnyError { + fn from(e: StdError) -> AnyError { + anyhow!(e).into() + } +} + +impl From for AnyError { + fn from(e: ContractError) -> AnyError { + anyhow!(e).into() + } +} + +// Workaround for not being able to use the `bail!` macro directly. +#[doc(hidden)] +#[macro_export] +macro_rules! bail { + ($msg:literal $(,)?) => { + return ::core::result::Result::Err(::anyhow::anyhow!($msg).into()) + }; + ($err:expr $(,)?) => { + return ::core::result::Result::Err(::anyhow::anyhow!($err).into()) + }; + ($fmt:expr, $($arg:tt)*) => { + return ::core::result::Result::Err(::anyhow::anyhow!($fmt, $($arg)*).into()) + }; +} diff --git a/cosmwasm/contracts/wormchain-accounting/src/lib.rs b/cosmwasm/contracts/wormchain-accounting/src/lib.rs new file mode 100644 index 000000000..dfedc9dc6 --- /dev/null +++ b/cosmwasm/contracts/wormchain-accounting/src/lib.rs @@ -0,0 +1,6 @@ +pub mod contract; +mod error; +pub mod msg; +pub mod state; + +pub use crate::error::ContractError; diff --git a/cosmwasm/contracts/wormchain-accounting/src/msg.rs b/cosmwasm/contracts/wormchain-accounting/src/msg.rs new file mode 100644 index 000000000..67ad5bed2 --- /dev/null +++ b/cosmwasm/contracts/wormchain-accounting/src/msg.rs @@ -0,0 +1,145 @@ +use accounting::state::{account, transfer, Account, Modification, Transfer}; +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::Binary; +use wormhole_bindings::Signature; + +use crate::state::{self, PendingTransfer}; + +#[cw_serde] +pub struct Instantiate { + pub tokenbridge_addr: String, + pub accounts: Vec, + pub transfers: Vec, + pub modifications: Vec, +} + +impl From for accounting::msg::Instantiate { + fn from(i: Instantiate) -> Self { + Self { + accounts: i.accounts, + transfers: i.transfers, + modifications: i.modifications, + } + } +} + +#[cw_serde] +pub struct InstantiateMsg { + // A serialized `Instantiate` message. + pub instantiate: Binary, + // The index of the guardian set used to sign this message. + pub guardian_set_index: u32, + // A quorum of signatures for `instantiate`. + pub signatures: Vec, +} + +#[cw_serde] +#[derive(Default)] +pub struct Observation { + // The key that uniquely identifies the observation. + pub key: transfer::Key, + + // The nonce for the transfer. + pub nonce: u32, + + // The hash of the transaction on the emitter chain in which the transfer + // was performed. + pub tx_hash: Binary, + + // The serialized tokenbridge payload. + pub payload: Binary, +} + +#[cw_serde] +pub struct Upgrade { + pub new_addr: [u8; 32], +} + +#[cw_serde] +pub enum ExecuteMsg { + SubmitObservations { + // A serialized `Vec`. Multiple observations can be submitted together to reduce + // transaction overhead. + observations: Binary, + // The index of the guardian set used to sign the observations. + guardian_set_index: u32, + // A signature for `observations`. + signature: Signature, + }, + ModifyBalance { + // A serialized `Modification` message. + modification: Binary, + + // The index of the guardian set used to sign this modification. + guardian_set_index: u32, + + // A quorum of signatures for `modification`. + signatures: Vec, + }, + UpgradeContract { + // A serialized `Upgrade` message. + upgrade: Binary, + + // The index of the guardian set used to sign this request. + guardian_set_index: u32, + + // A quorum of signatures for `key`. + signatures: Vec, + }, +} + +#[cw_serde] +pub struct MigrateMsg {} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(account::Balance)] + Balance(account::Key), + #[returns(AllAccountsResponse)] + AllAccounts { + start_after: Option, + limit: Option, + }, + #[returns(transfer::Data)] + Transfer(transfer::Key), + #[returns(AllTransfersResponse)] + AllTransfers { + start_after: Option, + limit: Option, + }, + #[returns(state::Data)] + PendingTransfer(transfer::Key), + #[returns(AllPendingTransfersResponse)] + AllPendingTransfers { + start_after: Option, + limit: Option, + }, + #[returns(Modification)] + Modification { sequence: u64 }, + #[returns(AllModificationsResponse)] + AllModifications { + start_after: Option, + limit: Option, + }, +} + +#[cw_serde] +pub struct AllAccountsResponse { + pub accounts: Vec, +} + +#[cw_serde] +pub struct AllTransfersResponse { + pub transfers: Vec, +} + +#[cw_serde] +pub struct AllPendingTransfersResponse { + pub pending: Vec, +} + +#[cw_serde] +pub struct AllModificationsResponse { + pub modifications: Vec, +} diff --git a/cosmwasm/contracts/wormchain-accounting/src/state.rs b/cosmwasm/contracts/wormchain-accounting/src/state.rs new file mode 100644 index 000000000..b20df1cad --- /dev/null +++ b/cosmwasm/contracts/wormchain-accounting/src/state.rs @@ -0,0 +1,69 @@ +use accounting::state::transfer; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Addr; +use cw_storage_plus::{Item, Map}; +use thiserror::Error; +use tinyvec::TinyVec; +use wormhole_bindings::Signature; + +use crate::msg::Observation; + +pub const TOKENBRIDGE_ADDR: Item = Item::new("tokenbride_addr"); +pub const PENDING_TRANSFERS: Map> = Map::new("pending_transfers"); + +#[cw_serde] +pub struct PendingTransfer { + pub key: transfer::Key, + pub data: Vec, +} + +#[derive(Error, Debug)] +#[error("cannot submit duplicate signatures for the same observation")] +pub struct DuplicateSignatureError; + +#[cw_serde] +#[derive(Default)] +pub struct Data { + observation: Observation, + + guardian_set_index: u32, + + signatures: Vec, +} + +impl Data { + pub const fn new(observation: Observation, guardian_set_index: u32) -> Self { + Self { + observation, + guardian_set_index, + signatures: Vec::new(), + } + } + + pub fn observation(&self) -> &Observation { + &self.observation + } + + pub fn guardian_set_index(&self) -> u32 { + self.guardian_set_index + } + + pub fn signatures(&self) -> &[Signature] { + &self.signatures + } + + /// Adds `sig` to the list of signatures for this transfer data. Returns true if `sig` + /// was successfully added or false if `sig` was already in the signature list. + pub fn add_signature(&mut self, sig: Signature) -> Result<(), DuplicateSignatureError> { + match self + .signatures + .binary_search_by_key(&sig.index, |s| s.index) + { + Ok(_) => Err(DuplicateSignatureError), + Err(idx) => { + self.signatures.insert(idx, sig); + Ok(()) + } + } + } +} diff --git a/cosmwasm/contracts/wormchain-accounting/tests/helpers/fake_tokenbridge.rs b/cosmwasm/contracts/wormchain-accounting/tests/helpers/fake_tokenbridge.rs new file mode 100644 index 000000000..fb4aed2a2 --- /dev/null +++ b/cosmwasm/contracts/wormchain-accounting/tests/helpers/fake_tokenbridge.rs @@ -0,0 +1,23 @@ +use cosmwasm_std::{ + to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdError, StdResult, +}; +use tokenbridge::msg::QueryMsg; + +pub fn instantiate(_: DepsMut, _: Env, _: MessageInfo, _: Empty) -> StdResult { + Ok(Response::new()) +} + +pub fn execute(_: DepsMut, _: Env, _: MessageInfo, _: Empty) -> StdResult { + Err(StdError::GenericErr { + msg: "execute not implemented".into(), + }) +} + +pub fn query(_: Deps, _: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::ChainRegistration { chain } => to_binary(&vec![chain as u8; 32]), + _ => Err(StdError::GenericErr { + msg: "unimplemented query message".into(), + }), + } +} diff --git a/cosmwasm/contracts/wormchain-accounting/tests/helpers/mod.rs b/cosmwasm/contracts/wormchain-accounting/tests/helpers/mod.rs new file mode 100644 index 000000000..d0d71ac2d --- /dev/null +++ b/cosmwasm/contracts/wormchain-accounting/tests/helpers/mod.rs @@ -0,0 +1,310 @@ +#![allow(dead_code)] + +use accounting::state::{account, transfer, Account, Kind, Modification, Transfer}; +use cosmwasm_std::{ + testing::{MockApi, MockStorage}, + to_binary, Addr, Binary, Coin, Empty, StdResult, Uint128, Uint256, +}; +use cw_multi_test::{ + App, AppBuilder, AppResponse, BankKeeper, ContractWrapper, Executor, WasmKeeper, +}; +use wormchain_accounting::{ + msg::{ + AllAccountsResponse, AllModificationsResponse, AllPendingTransfersResponse, + AllTransfersResponse, ExecuteMsg, Instantiate, InstantiateMsg, QueryMsg, + }, + state, +}; +use wormhole_bindings::{fake, WormholeQuery}; + +mod fake_tokenbridge; + +pub struct Contract { + addr: Addr, + app: FakeApp, +} + +impl Contract { + pub fn addr(&self) -> Addr { + self.addr.clone() + } + + pub fn app(&self) -> &FakeApp { + &self.app + } + + pub fn app_mut(&mut self) -> &mut FakeApp { + &mut self.app + } + + pub fn submit_observations( + &mut self, + observations: Binary, + guardian_set_index: u32, + signature: wormhole_bindings::Signature, + ) -> anyhow::Result { + self.app.execute_contract( + Addr::unchecked(USER), + self.addr(), + &ExecuteMsg::SubmitObservations { + observations, + guardian_set_index, + signature, + }, + &[], + ) + } + + pub fn modify_balance( + &mut self, + modification: Binary, + guardian_set_index: u32, + signatures: Vec, + ) -> anyhow::Result { + self.app.execute_contract( + Addr::unchecked(USER), + self.addr(), + &ExecuteMsg::ModifyBalance { + modification, + guardian_set_index, + signatures, + }, + &[], + ) + } + + pub fn upgrade_contract( + &mut self, + upgrade: Binary, + guardian_set_index: u32, + signatures: Vec, + ) -> anyhow::Result { + self.app.execute_contract( + Addr::unchecked(ADMIN), + self.addr(), + &ExecuteMsg::UpgradeContract { + upgrade, + guardian_set_index, + signatures, + }, + &[], + ) + } + + pub fn query_balance(&self, key: account::Key) -> StdResult { + self.app + .wrap() + .query_wasm_smart(self.addr(), &QueryMsg::Balance(key)) + } + + pub fn query_all_accounts( + &self, + start_after: Option, + limit: Option, + ) -> StdResult { + self.app + .wrap() + .query_wasm_smart(self.addr(), &QueryMsg::AllAccounts { start_after, limit }) + } + + pub fn query_transfer(&self, key: transfer::Key) -> StdResult { + self.app + .wrap() + .query_wasm_smart(self.addr(), &QueryMsg::Transfer(key)) + } + + pub fn query_all_transfers( + &self, + start_after: Option, + limit: Option, + ) -> StdResult { + self.app + .wrap() + .query_wasm_smart(self.addr(), &QueryMsg::AllTransfers { start_after, limit }) + } + + pub fn query_pending_transfer(&self, key: transfer::Key) -> StdResult> { + self.app + .wrap() + .query_wasm_smart(self.addr(), &QueryMsg::PendingTransfer(key)) + } + + pub fn query_all_pending_transfers( + &self, + start_after: Option, + limit: Option, + ) -> StdResult { + self.app.wrap().query_wasm_smart( + self.addr(), + &QueryMsg::AllPendingTransfers { start_after, limit }, + ) + } + + pub fn query_modification(&self, sequence: u64) -> StdResult { + self.app + .wrap() + .query_wasm_smart(self.addr(), &QueryMsg::Modification { sequence }) + } + + pub fn query_all_modifications( + &self, + start_after: Option, + limit: Option, + ) -> StdResult { + self.app.wrap().query_wasm_smart( + self.addr(), + &QueryMsg::AllModifications { start_after, limit }, + ) + } +} + +const USER: &str = "USER"; +const ADMIN: &str = "ADMIN"; +const NATIVE_DENOM: &str = "denom"; + +pub type FakeApp = + App>; + +fn fake_app(wh: fake::WormholeKeeper) -> FakeApp { + AppBuilder::new_custom() + .with_custom(wh) + .build(|router, _, storage| { + router + .bank + .init_balance( + storage, + &Addr::unchecked(USER), + vec![Coin { + denom: NATIVE_DENOM.to_string(), + amount: Uint128::new(1), + }], + ) + .unwrap(); + }) +} + +pub fn create_accounts(count: usize) -> Vec { + let mut out = Vec::with_capacity(count * count); + for i in 0..count { + for j in 0..count { + let key = account::Key::new(i as u16, j as u16, [i as u8; 32].into()); + let balance = Uint256::from(j as u128).into(); + out.push(Account { key, balance }); + } + } + + out +} + +pub fn create_transfers(count: usize) -> Vec { + let mut out = Vec::with_capacity(count); + for i in 0..count { + let key = transfer::Key::new(i as u16, [i as u8; 32].into(), i as u64); + let data = transfer::Data { + amount: Uint256::from(i as u128), + token_chain: i as u16, + token_address: [i as u8; 32].into(), + recipient_chain: i as u16, + }; + + out.push(Transfer { key, data }); + } + + out +} + +pub fn create_modifications(count: usize) -> Vec { + let mut out = Vec::with_capacity(count); + for i in 0..count { + let m = Modification { + sequence: i as u64, + chain_id: i as u16, + token_chain: i as u16, + token_address: [i as u8; 32].into(), + kind: if i % 2 == 0 { Kind::Add } else { Kind::Sub }, + amount: Uint256::from(i as u128), + reason: format!("{i}"), + }; + out.push(m); + } + + out +} + +pub fn proper_instantiate( + accounts: Vec, + transfers: Vec, + modifications: Vec, +) -> (fake::WormholeKeeper, Contract) { + let wh = fake::WormholeKeeper::new(); + let mut app = fake_app(wh.clone()); + + let tokenbridge_id = app.store_code(Box::new(ContractWrapper::new_with_empty( + fake_tokenbridge::execute, + fake_tokenbridge::instantiate, + fake_tokenbridge::query, + ))); + + let accounting_id = app.store_code(Box::new(ContractWrapper::new( + wormchain_accounting::contract::execute, + wormchain_accounting::contract::instantiate, + wormchain_accounting::contract::query, + ))); + + let tokenbridge_addr = app + .instantiate_contract( + tokenbridge_id, + Addr::unchecked(ADMIN), + &Empty {}, + &[], + "tokenbridge", + None, + ) + .unwrap() + .into(); + + let instantiate = to_binary(&Instantiate { + tokenbridge_addr, + accounts, + transfers, + modifications, + }) + .unwrap(); + + let signatures = wh.sign(&instantiate); + let msg = InstantiateMsg { + instantiate, + guardian_set_index: wh.guardian_set_index(), + signatures, + }; + + // We want the contract to be able to upgrade itself, which means we have to set the contract + // as its own admin. So we have a bit of a catch-22 where we need to know the contract + // address to register it but we need to register it to get its address. The hacky solution + // here is to rely on the internal details of the test framework to figure out what the + // address of the contract is going to be and then use that. + // + // TODO: Figure out a better way to do this. One option is to do something like: + // + // ``` + // let mut data = app.contract_data(&addr).unwrap(); + // data.admin = Some(addr.clone()); + // app.init_modules(|router, _, storage| router.wasm.save_contract(storage, &addr, &data)) + // .unwrap(); + // ``` + // + // Unfortunately, the `wasm` field of `router` is private to the `cw-multi-test` crate so we + // can't use it here. Maybe something to bring up with upstream. + let addr = app + .instantiate_contract( + accounting_id, + Addr::unchecked(ADMIN), + &msg, + &[], + "accounting", + Some("contract1".into()), + ) + .unwrap(); + + (wh, Contract { addr, app }) +} diff --git a/cosmwasm/contracts/wormchain-accounting/tests/instantiate.rs b/cosmwasm/contracts/wormchain-accounting/tests/instantiate.rs new file mode 100644 index 000000000..e3e055b5e --- /dev/null +++ b/cosmwasm/contracts/wormchain-accounting/tests/instantiate.rs @@ -0,0 +1,29 @@ +mod helpers; + +use helpers::*; + +#[test] +fn instantiate_contract() { + const COUNT: usize = 5; + let accounts = create_accounts(COUNT); + let transfers = create_transfers(COUNT); + let modifications = create_modifications(COUNT); + + let (_, contract) = + proper_instantiate(accounts.clone(), transfers.clone(), modifications.clone()); + + for a in accounts { + let balance = contract.query_balance(a.key).unwrap(); + assert_eq!(a.balance, balance); + } + + for t in transfers { + let data = contract.query_transfer(t.key).unwrap(); + assert_eq!(t.data, data); + } + + for m in modifications { + let data = contract.query_modification(m.sequence).unwrap(); + assert_eq!(m, data); + } +} diff --git a/cosmwasm/contracts/wormchain-accounting/tests/modify_balance.rs b/cosmwasm/contracts/wormchain-accounting/tests/modify_balance.rs new file mode 100644 index 000000000..fcf969816 --- /dev/null +++ b/cosmwasm/contracts/wormchain-accounting/tests/modify_balance.rs @@ -0,0 +1,243 @@ +mod helpers; + +use accounting::state::{account, Kind, Modification}; +use cosmwasm_std::{to_binary, Event, Uint256}; +use helpers::*; + +#[test] +fn simple_modify() { + let (wh, mut contract) = proper_instantiate(Vec::new(), Vec::new(), Vec::new()); + + let index = wh.guardian_set_index(); + let m = Modification { + sequence: 0, + chain_id: 1, + token_chain: 1, + token_address: [0x7c; 32].into(), + kind: Kind::Add, + amount: Uint256::from(300u128), + reason: "test".into(), + }; + let modification = to_binary(&m).unwrap(); + + let signatures = wh.sign(&modification); + let resp = contract + .modify_balance(modification, index, signatures) + .unwrap(); + + let evt = Event::new("wasm-ModifyBalance") + .add_attribute("sequence", m.sequence.to_string()) + .add_attribute("chain_id", m.chain_id.to_string()) + .add_attribute("token_chain", m.token_chain.to_string()) + .add_attribute("token_address", m.token_address.to_string()) + .add_attribute("kind", m.kind.to_string()) + .add_attribute("amount", m.amount) + .add_attribute("reason", m.reason.clone()); + + resp.assert_event(&evt); + + let actual = contract.query_modification(m.sequence).unwrap(); + assert_eq!(m, actual); + + let balance = contract + .query_balance(account::Key::new( + m.chain_id, + m.token_chain, + m.token_address, + )) + .unwrap(); + assert_eq!(m.amount, *balance); +} + +#[test] +fn duplicate_modify() { + let (wh, mut contract) = proper_instantiate(Vec::new(), Vec::new(), Vec::new()); + + let index = wh.guardian_set_index(); + let m = Modification { + sequence: 0, + chain_id: 1, + token_chain: 1, + token_address: [0x7c; 32].into(), + kind: Kind::Add, + amount: Uint256::from(300u128), + reason: "test".into(), + }; + let modification = to_binary(&m).unwrap(); + + let signatures = wh.sign(&modification); + contract + .modify_balance(modification.clone(), index, signatures.clone()) + .unwrap(); + + contract + .modify_balance(modification, index, signatures) + .expect_err("successfully submitted duplicate modification"); +} + +#[test] +fn round_trip() { + let (wh, mut contract) = proper_instantiate(Vec::new(), Vec::new(), Vec::new()); + + let index = wh.guardian_set_index(); + let mut m = Modification { + sequence: 0, + chain_id: 1, + token_chain: 1, + token_address: [0x7c; 32].into(), + kind: Kind::Add, + amount: Uint256::from(300u128), + reason: "test".into(), + }; + let modification = to_binary(&m).unwrap(); + + let signatures = wh.sign(&modification); + contract + .modify_balance(modification, index, signatures) + .unwrap(); + + let actual = contract.query_modification(m.sequence).unwrap(); + assert_eq!(m, actual); + + // Now reverse the modification. + m.sequence += 1; + m.kind = Kind::Sub; + m.reason = "reverse".into(); + + let modification = to_binary(&m).unwrap(); + + let signatures = wh.sign(&modification); + contract + .modify_balance(modification, index, signatures) + .unwrap(); + + let actual = contract.query_modification(m.sequence).unwrap(); + assert_eq!(m, actual); + + let balance = contract + .query_balance(account::Key::new( + m.chain_id, + m.token_chain, + m.token_address, + )) + .unwrap(); + assert_eq!(Uint256::zero(), *balance); +} + +#[test] +fn missing_guardian_set() { + let (wh, mut contract) = proper_instantiate(Vec::new(), Vec::new(), Vec::new()); + + let index = wh.guardian_set_index(); + let m = Modification { + sequence: 0, + chain_id: 1, + token_chain: 1, + token_address: [0x7c; 32].into(), + kind: Kind::Add, + amount: Uint256::from(300u128), + reason: "test".into(), + }; + let modification = to_binary(&m).unwrap(); + + let signatures = wh.sign(&modification); + contract + .modify_balance(modification, index + 1, signatures) + .expect_err("successfully modified balance with invalid guardian set"); +} + +#[test] +fn expired_guardian_set() { + let (wh, mut contract) = proper_instantiate(Vec::new(), Vec::new(), Vec::new()); + + let index = wh.guardian_set_index(); + let mut block = contract.app().block_info(); + wh.set_expiration(block.height); + block.height += 1; + contract.app_mut().set_block(block); + + let m = Modification { + sequence: 0, + chain_id: 1, + token_chain: 1, + token_address: [0x7c; 32].into(), + kind: Kind::Add, + amount: Uint256::from(300u128), + reason: "test".into(), + }; + let modification = to_binary(&m).unwrap(); + + let signatures = wh.sign(&modification); + contract + .modify_balance(modification, index, signatures) + .expect_err("successfully modified balance with expired guardian set"); +} + +#[test] +fn no_quorum() { + let (wh, mut contract) = proper_instantiate(Vec::new(), Vec::new(), Vec::new()); + + let index = wh.guardian_set_index(); + let m = Modification { + sequence: 0, + chain_id: 1, + token_chain: 1, + token_address: [0x7c; 32].into(), + kind: Kind::Add, + amount: Uint256::from(300u128), + reason: "test".into(), + }; + let modification = to_binary(&m).unwrap(); + + let mut signatures = wh.sign(&modification); + let newlen = wh + .calculate_quorum(0, contract.app().block_info().height) + .map(|q| (q - 1) as usize) + .unwrap(); + signatures.truncate(newlen); + + contract + .modify_balance(modification, index, signatures) + .expect_err("successfully submitted modification without quorum"); +} + +#[test] +fn repeat() { + const ITERATIONS: usize = 10; + + let (wh, mut contract) = proper_instantiate(Vec::new(), Vec::new(), Vec::new()); + + let index = wh.guardian_set_index(); + let mut m = Modification { + sequence: 0, + chain_id: 1, + token_chain: 1, + token_address: [0x7c; 32].into(), + kind: Kind::Add, + amount: Uint256::from(300u128), + reason: "test".into(), + }; + + for _ in 0..ITERATIONS { + m.sequence += 1; + + let modification = to_binary(&m).unwrap(); + + let signatures = wh.sign(&modification); + contract + .modify_balance(modification, index, signatures) + .unwrap(); + + let actual = contract.query_modification(m.sequence).unwrap(); + assert_eq!(m, actual); + } + + let balance = contract + .query_balance(account::Key::new( + m.chain_id, + m.token_chain, + m.token_address, + )) + .unwrap(); + assert_eq!(m.amount * Uint256::from(ITERATIONS as u128), *balance); +} diff --git a/cosmwasm/contracts/wormchain-accounting/tests/query.rs b/cosmwasm/contracts/wormchain-accounting/tests/query.rs new file mode 100644 index 000000000..aeb136c04 --- /dev/null +++ b/cosmwasm/contracts/wormchain-accounting/tests/query.rs @@ -0,0 +1,276 @@ +mod helpers; + +use std::collections::BTreeMap; + +use accounting::state::{ + account::{self, Balance}, + transfer, Kind, Modification, +}; +use cosmwasm_std::Uint256; +use helpers::*; +use wormhole_bindings::fake; + +fn set_up(count: usize) -> (fake::WormholeKeeper, Contract) { + let accounts = create_accounts(count); + let transfers = create_transfers(count); + let modifications = create_modifications(count); + + proper_instantiate(accounts, transfers, modifications) +} + +#[test] +fn account_balance() { + let count = 2; + let (_, contract) = set_up(count); + + for i in 0..count { + for j in 0..count { + let key = account::Key::new(i as u16, j as u16, [i as u8; 32].into()); + let balance = contract.query_balance(key).unwrap(); + assert_eq!(balance, Balance::new(Uint256::from(j as u128))) + } + } +} + +#[test] +fn missing_account() { + let count = 2; + let (_, contract) = set_up(count); + + let missing = account::Key::new( + (count + 1) as u16, + (count + 2) as u16, + [(count + 3) as u8; 32].into(), + ); + + contract + .query_balance(missing) + .expect_err("successfully queried missing account key"); +} + +#[test] +fn all_balances() { + let count = 3; + let (_, contract) = set_up(count); + + let resp = contract.query_all_accounts(None, None).unwrap(); + let found = resp + .accounts + .into_iter() + .map(|acc| (acc.key, acc.balance)) + .collect::>(); + assert_eq!(found.len(), count * count); + + for i in 0..count { + for j in 0..count { + let key = account::Key::new(i as u16, j as u16, [i as u8; 32].into()); + assert!(found.contains_key(&key)); + } + } +} + +#[test] +fn all_balances_sub_range() { + let count = 3; + let (_, contract) = set_up(count); + + for i in 0..count { + for j in 0..count { + let max_limit = (count - i - 1) * count + (count - j - 1); + for l in 1..=max_limit { + let start_after = Some(account::Key::new(i as u16, j as u16, [i as u8; 32].into())); + let limit = Some(l as u32); + let resp = contract.query_all_accounts(start_after, limit).unwrap(); + let found = resp + .accounts + .into_iter() + .map(|acc| (acc.key, acc.balance)) + .collect::>(); + assert_eq!(found.len(), l); + + let mut checked = 0; + for y in j + 1..count { + if checked >= l { + break; + } + + let key = account::Key::new(i as u16, y as u16, [i as u8; 32].into()); + assert!(found.contains_key(&key)); + checked += 1; + } + + 'outer: for x in i + 1..count { + for y in 0..count { + if checked >= l { + break 'outer; + } + let key = account::Key::new(x as u16, y as u16, [x as u8; 32].into()); + assert!(found.contains_key(&key)); + checked += 1; + } + } + } + } + } +} + +#[test] +fn transfer_data() { + let count = 2; + let (_, contract) = set_up(count); + + for i in 0..count { + let expected = transfer::Data { + amount: Uint256::from(i as u128), + token_chain: i as u16, + token_address: [i as u8; 32].into(), + recipient_chain: i as u16, + }; + + let key = transfer::Key::new(i as u16, [i as u8; 32].into(), i as u64); + let actual = contract.query_transfer(key).unwrap(); + + assert_eq!(expected, actual); + } +} + +#[test] +fn missing_transfer() { + let count = 2; + let (_, contract) = set_up(count); + + let missing = transfer::Key::new( + (count + 1) as u16, + [(count + 2) as u8; 32].into(), + (count + 3) as u64, + ); + + contract + .query_transfer(missing) + .expect_err("successfully queried missing transfer key"); +} + +#[test] +fn all_transfer_data() { + let count = 3; + let (_, contract) = set_up(count); + + let resp = contract.query_all_transfers(None, None).unwrap(); + let found = resp + .transfers + .into_iter() + .map(|acc| (acc.key, acc.data)) + .collect::>(); + assert_eq!(found.len(), count); + + for i in 0..count { + let key = transfer::Key::new(i as u16, [i as u8; 32].into(), i as u64); + assert!(found.contains_key(&key)); + } +} + +#[test] +fn all_transfer_data_sub_range() { + let count = 5; + let (_, contract) = set_up(count); + + for i in 0..count { + for l in 1..count - i { + let start_after = Some(transfer::Key::new(i as u16, [i as u8; 32].into(), i as u64)); + let limit = Some(l as u32); + let resp = contract.query_all_transfers(start_after, limit).unwrap(); + let found = resp + .transfers + .into_iter() + .map(|acc| (acc.key, acc.data)) + .collect::>(); + assert_eq!(found.len(), l); + + for x in i + 1..=i + l { + let key = transfer::Key::new(x as u16, [x as u8; 32].into(), x as u64); + assert!(found.contains_key(&key)); + } + } + } +} + +#[test] +fn modification_data() { + let count = 2; + let (_, contract) = set_up(count); + + for i in 0..count { + let expected = Modification { + sequence: i as u64, + chain_id: i as u16, + token_chain: i as u16, + token_address: [i as u8; 32].into(), + kind: if i % 2 == 0 { Kind::Add } else { Kind::Sub }, + amount: Uint256::from(i as u128), + reason: format!("{i}"), + }; + + let key = i as u64; + let actual = contract.query_modification(key).unwrap(); + + assert_eq!(expected, actual); + } +} + +#[test] +fn missing_modification() { + let count = 2; + let (_, contract) = set_up(count); + + let missing = (count + 1) as u64; + + contract + .query_modification(missing) + .expect_err("successfully queried missing modification key"); +} + +#[test] +fn all_modification_data() { + let count = 3; + let (_, contract) = set_up(count); + + let resp = contract.query_all_modifications(None, None).unwrap(); + let found = resp + .modifications + .into_iter() + .map(|m| (m.sequence, m)) + .collect::>(); + assert_eq!(found.len(), count); + + for i in 0..count { + let key = i as u64; + assert!(found.contains_key(&key)); + } +} + +#[test] +fn all_modification_data_sub_range() { + let count = 5; + let (_, contract) = set_up(count); + + for i in 0..count { + for l in 1..count - i { + let start_after = Some(i as u64); + let limit = Some(l as u32); + let resp = contract + .query_all_modifications(start_after, limit) + .unwrap(); + let found = resp + .modifications + .into_iter() + .map(|m| (m.sequence, m)) + .collect::>(); + assert_eq!(found.len(), l); + + for x in i + 1..=i + l { + let key = x as u64; + assert!(found.contains_key(&key)); + } + } + } +} diff --git a/cosmwasm/contracts/wormchain-accounting/tests/submit_observations.rs b/cosmwasm/contracts/wormchain-accounting/tests/submit_observations.rs new file mode 100644 index 000000000..02da10549 --- /dev/null +++ b/cosmwasm/contracts/wormchain-accounting/tests/submit_observations.rs @@ -0,0 +1,755 @@ +mod helpers; + +use accounting::state::{ + account::{self, Balance}, + transfer, Account, TokenAddress, +}; +use cosmwasm_std::{to_binary, Binary, Event, Uint256}; +use cw_multi_test::AppResponse; +use helpers::*; +use wormchain_accounting::msg::Observation; +use wormhole::{token::Message, Address, Amount}; +use wormhole_bindings::fake; + +fn set_up(count: usize) -> (Vec, Vec) { + let mut txs = Vec::with_capacity(count); + let mut observations = Vec::with_capacity(count); + for i in 0..count { + let key = transfer::Key::new(i as u16, [i as u8; 32].into(), i as u64); + let tx = Message::Transfer { + amount: Amount(Uint256::from(500u128).to_be_bytes()), + token_address: Address([(i + 1) as u8; 32]), + token_chain: (i as u16).into(), + recipient: Address([(i + 2) as u8; 32]), + recipient_chain: ((i + 3) as u16).into(), + fee: Amount([0u8; 32]), + }; + let payload = serde_wormhole::to_vec(&tx).map(Binary::from).unwrap(); + txs.push(tx); + observations.push(Observation { + key, + nonce: i as u32, + tx_hash: vec![(i + 4) as u8; 20].into(), + payload, + }); + } + + (txs, observations) +} + +#[test] +fn batch() { + const COUNT: usize = 5; + + let (txs, observations) = set_up(COUNT); + let (wh, mut contract) = proper_instantiate(Vec::new(), Vec::new(), Vec::new()); + + let index = wh.guardian_set_index(); + + let obs = to_binary(&observations).unwrap(); + let signatures = wh.sign(&obs); + let quorum = wh + .calculate_quorum(index, contract.app().block_info().height) + .unwrap() as usize; + + for (i, s) in signatures.into_iter().enumerate() { + if i < quorum { + contract.submit_observations(obs.clone(), index, s).unwrap(); + + // Once there is a quorum the pending transfers are removed. + if i < quorum - 1 { + for o in &observations { + let data = contract.query_pending_transfer(o.key.clone()).unwrap(); + assert_eq!(o, data[0].observation()); + + // Make sure the transfer hasn't yet been committed. + contract + .query_transfer(o.key.clone()) + .expect_err("transfer committed without quorum"); + } + } else { + for o in &observations { + contract + .query_pending_transfer(o.key.clone()) + .expect_err("found pending transfer for observation with quorum"); + } + } + } else { + contract + .submit_observations(obs.clone(), index, s) + .expect_err("successfully submitted observation for committed transfer"); + } + } + + for (tx, o) in txs.into_iter().zip(observations) { + let expected = if let Message::Transfer { + amount, + token_address, + token_chain, + recipient_chain, + .. + } = tx + { + transfer::Data { + amount: Uint256::new(amount.0), + token_chain: token_chain.into(), + token_address: TokenAddress::new(token_address.0), + recipient_chain: recipient_chain.into(), + } + } else { + panic!("unexpected tokenbridge payload"); + }; + + let emitter_chain = o.key.emitter_chain(); + let actual = contract.query_transfer(o.key).unwrap(); + assert_eq!(expected, actual); + + let src = contract + .query_balance(account::Key::new( + emitter_chain, + expected.token_chain, + expected.token_address, + )) + .unwrap(); + + assert_eq!(expected.amount, *src); + + let dst = contract + .query_balance(account::Key::new( + expected.recipient_chain, + expected.token_chain, + expected.token_address, + )) + .unwrap(); + + assert_eq!(expected.amount, *dst); + } +} + +#[test] +fn duplicates() { + const COUNT: usize = 5; + + let (txs, observations) = set_up(COUNT); + let (wh, mut contract) = proper_instantiate(Vec::new(), Vec::new(), Vec::new()); + let index = wh.guardian_set_index(); + + let obs = to_binary(&observations).unwrap(); + let signatures = wh.sign(&obs); + let quorum = wh + .calculate_quorum(index, contract.app().block_info().height) + .unwrap() as usize; + + for (i, s) in signatures.iter().take(quorum).cloned().enumerate() { + contract + .submit_observations(obs.clone(), index, s.clone()) + .unwrap(); + let err = contract + .submit_observations(obs.clone(), index, s) + .expect_err("successfully submitted duplicate observations"); + if i < quorum - 1 { + // Sadly we can't match on the exact error type in an integration test because the + // test frameworks converts it into a string before it reaches this point. + assert!(format!("{err:#}").contains("duplicate signatures")); + } + } + + for (tx, o) in txs.into_iter().zip(observations) { + let expected = if let Message::Transfer { + amount, + token_address, + token_chain, + recipient_chain, + .. + } = tx + { + transfer::Data { + amount: Uint256::new(amount.0), + token_chain: token_chain.into(), + token_address: TokenAddress::new(token_address.0), + recipient_chain: recipient_chain.into(), + } + } else { + panic!("unexpected tokenbridge payload"); + }; + + let emitter_chain = o.key.emitter_chain(); + let actual = contract.query_transfer(o.key).unwrap(); + assert_eq!(expected, actual); + + let src = contract + .query_balance(account::Key::new( + emitter_chain, + expected.token_chain, + expected.token_address, + )) + .unwrap(); + + assert_eq!(expected.amount, *src); + + let dst = contract + .query_balance(account::Key::new( + expected.recipient_chain, + expected.token_chain, + expected.token_address, + )) + .unwrap(); + + assert_eq!(expected.amount, *dst); + } + + for s in signatures { + contract + .submit_observations(obs.clone(), index, s) + .expect_err("successfully submitted observation for committed transfer"); + } +} + +fn transfer_tokens( + wh: &fake::WormholeKeeper, + contract: &mut Contract, + key: transfer::Key, + msg: Message, + index: u32, + quorum: usize, +) -> anyhow::Result<(Observation, Vec)> { + let payload = serde_wormhole::to_vec(&msg).map(Binary::from).unwrap(); + let o = Observation { + key, + nonce: 0x4343b191, + tx_hash: vec![0xd8u8; 20].into(), + payload, + }; + + let obs = to_binary(&vec![o.clone()]).unwrap(); + let signatures = wh.sign(&obs); + + let responses = signatures + .into_iter() + .take(quorum) + .map(|s| contract.submit_observations(obs.clone(), index, s)) + .collect::>>()?; + + Ok((o, responses)) +} + +#[test] +fn round_trip() { + let (wh, mut contract) = proper_instantiate(Vec::new(), Vec::new(), Vec::new()); + let index = wh.guardian_set_index(); + let quorum = wh + .calculate_quorum(index, contract.app().block_info().height) + .unwrap() as usize; + + let emitter_chain = 2; + let amount = Amount(Uint256::from(500u128).to_be_bytes()); + let token_address = Address([0xccu8; 32]); + let token_chain = 2u16.into(); + let recipient_chain = 14u16.into(); + + let key = transfer::Key::new(emitter_chain, [emitter_chain as u8; 32].into(), 37); + let msg = Message::Transfer { + amount, + token_address, + token_chain, + recipient: Address([0xb9u8; 32]), + recipient_chain, + fee: Amount([0u8; 32]), + }; + + transfer_tokens(&wh, &mut contract, key.clone(), msg, index, quorum).unwrap(); + + let expected = transfer::Data { + amount: Uint256::new(amount.0), + token_chain: token_chain.into(), + token_address: TokenAddress::new(token_address.0), + recipient_chain: recipient_chain.into(), + }; + let actual = contract.query_transfer(key).unwrap(); + assert_eq!(expected, actual); + + // Now send the tokens back. + let key = transfer::Key::new( + recipient_chain.into(), + [u16::from(recipient_chain) as u8; 32].into(), + 91156748, + ); + let msg = Message::Transfer { + amount, + token_address, + token_chain, + recipient: Address([0xe4u8; 32]), + recipient_chain: emitter_chain.into(), + fee: Amount([0u8; 32]), + }; + transfer_tokens(&wh, &mut contract, key.clone(), msg, index, quorum).unwrap(); + + let expected = transfer::Data { + amount: Uint256::new(amount.0), + token_chain: token_chain.into(), + token_address: TokenAddress::new(token_address.0), + recipient_chain: emitter_chain, + }; + let actual = contract.query_transfer(key).unwrap(); + assert_eq!(expected, actual); + + // Now both balances should be zero. + let src = contract + .query_balance(account::Key::new( + emitter_chain, + token_chain.into(), + expected.token_address, + )) + .unwrap(); + + assert_eq!(Uint256::zero(), *src); + + let dst = contract + .query_balance(account::Key::new( + recipient_chain.into(), + token_chain.into(), + expected.token_address, + )) + .unwrap(); + + assert_eq!(Uint256::zero(), *dst); +} + +#[test] +fn missing_guardian_set() { + let (wh, mut contract) = proper_instantiate(Vec::new(), Vec::new(), Vec::new()); + let index = wh.guardian_set_index(); + let quorum = wh + .calculate_quorum(index, contract.app().block_info().height) + .unwrap() as usize; + + let emitter_chain = 2; + let amount = Amount(Uint256::from(500u128).to_be_bytes()); + let token_address = Address([0xccu8; 32]); + let token_chain = 2.into(); + let recipient_chain = 14.into(); + + let key = transfer::Key::new(emitter_chain, [emitter_chain as u8; 32].into(), 37); + let msg = Message::Transfer { + amount, + token_address, + token_chain, + recipient: Address([0xb9u8; 32]), + recipient_chain, + fee: Amount([0u8; 32]), + }; + + transfer_tokens(&wh, &mut contract, key, msg, index + 1, quorum) + .expect_err("successfully submitted observations with invalid guardian set"); +} + +#[test] +fn expired_guardian_set() { + let (wh, mut contract) = proper_instantiate(Vec::new(), Vec::new(), Vec::new()); + let index = wh.guardian_set_index(); + let mut block = contract.app().block_info(); + + let quorum = wh.calculate_quorum(index, block.height).unwrap() as usize; + + let emitter_chain = 2; + let amount = Amount(Uint256::from(500u128).to_be_bytes()); + let token_address = Address([0xccu8; 32]); + let token_chain = 2.into(); + let recipient_chain = 14.into(); + + let key = transfer::Key::new(emitter_chain, [emitter_chain as u8; 32].into(), 37); + let msg = Message::Transfer { + amount, + token_address, + token_chain, + recipient: Address([0xb9u8; 32]), + recipient_chain, + fee: Amount([0u8; 32]), + }; + + // Mark the guardian set expired. + wh.set_expiration(block.height); + block.height += 1; + contract.app_mut().set_block(block); + + transfer_tokens(&wh, &mut contract, key, msg, index, quorum) + .expect_err("successfully submitted observations with expired guardian set"); +} + +#[test] +fn no_quorum() { + let (wh, mut contract) = proper_instantiate(Vec::new(), Vec::new(), Vec::new()); + let index = wh.guardian_set_index(); + let quorum = wh + .calculate_quorum(index, contract.app().block_info().height) + .unwrap() as usize; + + let emitter_chain = 2; + let amount = Amount(Uint256::from(500u128).to_be_bytes()); + let token_address = Address([0xccu8; 32]); + let token_chain = 2.into(); + let recipient_chain = 14.into(); + + let key = transfer::Key::new(emitter_chain, [emitter_chain as u8; 32].into(), 37); + let msg = Message::Transfer { + amount, + token_address, + token_chain, + recipient: Address([0xb9u8; 32]), + recipient_chain, + fee: Amount([0u8; 32]), + }; + + transfer_tokens( + &wh, + &mut contract, + key.clone(), + msg.clone(), + index, + quorum - 1, + ) + .unwrap(); + + let data = contract.query_pending_transfer(key.clone()).unwrap(); + assert_eq!(key, data[0].observation().key); + + let actual = serde_wormhole::from_slice(&data[0].observation().payload).unwrap(); + assert_eq!(msg, actual); + + // Make sure the transfer hasn't yet been committed. + contract + .query_transfer(key) + .expect_err("transfer committed without quorum"); +} + +#[test] +fn missing_wrapped_account() { + let (wh, mut contract) = proper_instantiate(Vec::new(), Vec::new(), Vec::new()); + let index = wh.guardian_set_index(); + let quorum = wh + .calculate_quorum(index, contract.app().block_info().height) + .unwrap() as usize; + + let emitter_chain = 14; + let amount = Amount(Uint256::from(500u128).to_be_bytes()); + let token_address = Address([0xccu8; 32]); + let token_chain = 2.into(); + let recipient_chain = 2.into(); + + let key = transfer::Key::new(emitter_chain, [emitter_chain as u8; 32].into(), 37); + let msg = Message::Transfer { + amount, + token_address, + token_chain, + recipient: Address([0xb9u8; 32]), + recipient_chain, + fee: Amount([0u8; 32]), + }; + + transfer_tokens(&wh, &mut contract, key, msg, index, quorum) + .expect_err("successfully burned wrapped tokens without a wrapped amount"); +} + +#[test] +fn missing_native_account() { + let emitter_chain = 14; + let recipient_chain = 2; + let amount = Amount(Uint256::from(500u128).to_be_bytes()); + let token_address = [0xccu8; 32]; + let token_chain = 2; + + // We need to set up a fake wrapped account so that the initial check succeeds. + let (wh, mut contract) = proper_instantiate( + vec![Account { + key: account::Key::new(emitter_chain, token_chain, token_address.into()), + balance: Balance::new(Uint256::new(amount.0)), + }], + Vec::new(), + Vec::new(), + ); + let index = wh.guardian_set_index(); + let quorum = wh + .calculate_quorum(index, contract.app().block_info().height) + .unwrap() as usize; + + let key = transfer::Key::new(emitter_chain, [emitter_chain as u8; 32].into(), 37); + let msg = Message::Transfer { + amount, + token_address: Address(token_address), + token_chain: token_chain.into(), + recipient: Address([0xb9u8; 32]), + recipient_chain: recipient_chain.into(), + fee: Amount([0u8; 32]), + }; + + transfer_tokens(&wh, &mut contract, key, msg, index, quorum) + .expect_err("successfully unlocked native tokens without a native account"); +} + +#[test] +fn repeated() { + const ITERATIONS: usize = 10; + + let (wh, mut contract) = proper_instantiate(Vec::new(), Vec::new(), Vec::new()); + let index = wh.guardian_set_index(); + let quorum = wh + .calculate_quorum(index, contract.app().block_info().height) + .unwrap() as usize; + + let emitter_chain = 2; + let recipient_chain = 14; + let amount = Amount(Uint256::from(500u128).to_be_bytes()); + let token_address = [0xccu8; 32]; + let token_chain = 2; + + let msg = Message::Transfer { + amount, + token_address: Address(token_address), + token_chain: token_chain.into(), + recipient: Address([0xb9u8; 32]), + recipient_chain: recipient_chain.into(), + fee: Amount([0u8; 32]), + }; + + for i in 0..ITERATIONS { + let key = transfer::Key::new(emitter_chain, [emitter_chain as u8; 32].into(), i as u64); + transfer_tokens(&wh, &mut contract, key.clone(), msg.clone(), index, quorum).unwrap(); + } + + let expected = Uint256::new(amount.0) * Uint256::from(ITERATIONS as u128); + let src = contract + .query_balance(account::Key::new( + emitter_chain, + token_chain, + token_address.into(), + )) + .unwrap(); + + assert_eq!(expected, *src); + + let dst = contract + .query_balance(account::Key::new( + recipient_chain, + token_chain, + token_address.into(), + )) + .unwrap(); + + assert_eq!(expected, *dst); +} + +#[test] +fn wrapped_to_wrapped() { + let emitter_chain = 14; + let recipient_chain = 2; + let amount = Amount(Uint256::from(500u128).to_be_bytes()); + let token_address = [0xccu8; 32]; + let token_chain = 5; + + // We need an initial fake wrapped account. + let (wh, mut contract) = proper_instantiate( + vec![Account { + key: account::Key::new(emitter_chain, token_chain, token_address.into()), + balance: Balance::new(Uint256::new(amount.0)), + }], + Vec::new(), + Vec::new(), + ); + let index = wh.guardian_set_index(); + let quorum = wh + .calculate_quorum(index, contract.app().block_info().height) + .unwrap() as usize; + + let key = transfer::Key::new(emitter_chain, [emitter_chain as u8; 32].into(), 37); + let msg = Message::Transfer { + amount, + token_address: Address(token_address), + token_chain: token_chain.into(), + recipient: Address([0xb9u8; 32]), + recipient_chain: recipient_chain.into(), + fee: Amount([0u8; 32]), + }; + + transfer_tokens(&wh, &mut contract, key.clone(), msg, index, quorum).unwrap(); + + let expected = transfer::Data { + amount: Uint256::new(amount.0), + token_chain, + token_address: TokenAddress::new(token_address), + recipient_chain, + }; + let actual = contract.query_transfer(key).unwrap(); + assert_eq!(expected, actual); + + let src = contract + .query_balance(account::Key::new( + emitter_chain, + token_chain, + token_address.into(), + )) + .unwrap(); + + assert_eq!(Uint256::zero(), *src); + + let dst = contract + .query_balance(account::Key::new( + recipient_chain, + token_chain, + token_address.into(), + )) + .unwrap(); + + assert_eq!(Uint256::new(amount.0), *dst); +} + +#[test] +fn unknown_emitter() { + let (wh, mut contract) = proper_instantiate(Vec::new(), Vec::new(), Vec::new()); + let index = wh.guardian_set_index(); + let quorum = wh + .calculate_quorum(index, contract.app().block_info().height) + .unwrap() as usize; + + let emitter_chain = 14; + let amount = Amount(Uint256::from(500u128).to_be_bytes()); + let token_address = Address([0xccu8; 32]); + let token_chain = 2.into(); + let recipient_chain = 2.into(); + + let key = transfer::Key::new(emitter_chain, [0xde; 32].into(), 37); + let msg = Message::Transfer { + amount, + token_address, + token_chain, + recipient: Address([0xb9u8; 32]), + recipient_chain, + fee: Amount([0u8; 32]), + }; + + transfer_tokens(&wh, &mut contract, key, msg, index, quorum) + .expect_err("successfully transfered tokens with an invalid emitter address"); +} + +#[test] +fn different_observations() { + let (wh, mut contract) = proper_instantiate(Vec::new(), Vec::new(), Vec::new()); + let index = wh.guardian_set_index(); + let quorum = wh + .calculate_quorum(index, contract.app().block_info().height) + .unwrap() as usize; + + // First submit some observations without enough signatures for quorum. + let emitter_chain = 2; + let fake_amount = Amount(Uint256::from(500u128).to_be_bytes()); + let token_address = Address([0xccu8; 32]); + let token_chain = 2.into(); + let fake_recipient_chain = 14.into(); + + let key = transfer::Key::new(emitter_chain, [emitter_chain as u8; 32].into(), 37); + let fake = Message::Transfer { + amount: fake_amount, + token_address, + token_chain, + recipient: Address([0xb9u8; 32]), + recipient_chain: fake_recipient_chain, + fee: Amount([0u8; 32]), + }; + + transfer_tokens(&wh, &mut contract, key.clone(), fake, index, quorum - 1).unwrap(); + + // Make sure there is no committed transfer yet. + contract + .query_transfer(key.clone()) + .expect_err("committed transfer without quorum"); + + // Now change the details of the transfer and resubmit with the same key. + let real_amount = Amount(Uint256::from(200u128).to_be_bytes()); + let real_recipient_chain = 9.into(); + let real = Message::Transfer { + amount: real_amount, + token_address, + token_chain, + recipient: Address([0xb9u8; 32]), + recipient_chain: real_recipient_chain, + fee: Amount([0u8; 32]), + }; + + transfer_tokens(&wh, &mut contract, key.clone(), real, index, quorum).unwrap(); + + contract + .query_pending_transfer(key.clone()) + .expect_err("found pending transfer for observation with quorum"); + + let expected = transfer::Data { + amount: Uint256::new(real_amount.0), + token_chain: token_chain.into(), + token_address: TokenAddress::new(token_address.0), + recipient_chain: real_recipient_chain.into(), + }; + let actual = contract.query_transfer(key).unwrap(); + assert_eq!(expected, actual); + + let src = contract + .query_balance(account::Key::new( + emitter_chain, + token_chain.into(), + expected.token_address, + )) + .unwrap(); + + assert_eq!(Uint256::new(real_amount.0), *src); + + let dst = contract + .query_balance(account::Key::new( + real_recipient_chain.into(), + token_chain.into(), + expected.token_address, + )) + .unwrap(); + + assert_eq!(Uint256::new(real_amount.0), *dst); +} + +#[test] +fn emit_event_with_quorum() { + let (wh, mut contract) = proper_instantiate(Vec::new(), Vec::new(), Vec::new()); + let index = wh.guardian_set_index(); + let quorum = wh + .calculate_quorum(index, contract.app().block_info().height) + .unwrap() as usize; + + let emitter_chain = 2; + let amount = Amount(Uint256::from(500u128).to_be_bytes()); + let token_address = Address([0xccu8; 32]); + let token_chain = 2.into(); + let recipient_chain = 14.into(); + + let key = transfer::Key::new(emitter_chain, [emitter_chain as u8; 32].into(), 37); + let msg = Message::Transfer { + amount, + token_address, + token_chain, + recipient: Address([0xb9u8; 32]), + recipient_chain, + fee: Amount([0u8; 32]), + }; + + let (o, responses) = transfer_tokens(&wh, &mut contract, key, msg, index, quorum).unwrap(); + + let expected = Event::new("wasm-Transfer") + .add_attribute("emitter_chain", o.key.emitter_chain().to_string()) + .add_attribute("emitter_address", o.key.emitter_address().to_string()) + .add_attribute("sequence", o.key.sequence().to_string()) + .add_attribute("nonce", o.nonce.to_string()) + .add_attribute("tx_hash", o.tx_hash.to_base64()) + .add_attribute("payload", o.payload.to_base64()); + + assert_eq!(responses.len(), quorum); + for (i, r) in responses.into_iter().enumerate() { + if i < quorum - 1 { + assert!(!r.has_event(&expected)); + } else { + r.assert_event(&expected); + } + } +} diff --git a/cosmwasm/contracts/wormchain-accounting/tests/upgrade_contract.rs b/cosmwasm/contracts/wormchain-accounting/tests/upgrade_contract.rs new file mode 100644 index 000000000..36f7c551e --- /dev/null +++ b/cosmwasm/contracts/wormchain-accounting/tests/upgrade_contract.rs @@ -0,0 +1,60 @@ +mod helpers; + +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{ + to_binary, Binary, Deps, DepsMut, Empty, Env, Event, MessageInfo, Response, StdResult, +}; +use cw_multi_test::ContractWrapper; +use helpers::*; +use wormchain_accounting::msg::Upgrade; +use wormhole_bindings::WormholeQuery; + +pub fn instantiate( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: Empty, +) -> StdResult { + Ok(Response::default()) +} + +pub fn migrate(_deps: DepsMut, _env: Env, _msg: Empty) -> StdResult { + Ok(Response::default().add_event(Event::new("migrate-success"))) +} + +pub fn execute(_deps: DepsMut, _env: Env, _info: MessageInfo, _msg: Empty) -> StdResult { + Ok(Response::default()) +} + +#[cw_serde] +struct NewContract; + +pub fn query(_deps: Deps, _env: Env, _msg: Empty) -> StdResult { + to_binary(&NewContract) +} + +#[test] +fn upgrade() { + let (wh, mut contract) = proper_instantiate(Vec::new(), Vec::new(), Vec::new()); + + let new_code_id = contract.app_mut().store_code(Box::new( + ContractWrapper::new_with_empty(execute, instantiate, query).with_migrate_empty(migrate), + )); + + let mut new_addr = [0u8; 32]; + new_addr[24..].copy_from_slice(&new_code_id.to_be_bytes()); + + let upgrade = to_binary(&Upgrade { new_addr }).unwrap(); + let signatures = wh.sign(&upgrade); + + let resp = contract + .upgrade_contract(upgrade, wh.guardian_set_index(), signatures) + .unwrap(); + resp.assert_event(&Event::new("wasm-migrate-success")); + + contract + .app() + .wrap() + .query_wasm_smart::(contract.addr(), &Empty {}) + .unwrap(); +} diff --git a/cosmwasm/packages/accounting/src/state/addr.rs b/cosmwasm/packages/accounting/src/state/addr.rs index c64843691..85c657182 100644 --- a/cosmwasm/packages/accounting/src/state/addr.rs +++ b/cosmwasm/packages/accounting/src/state/addr.rs @@ -125,7 +125,7 @@ impl<'de> de::Visitor<'de> for Base64Visitor { E: de::Error, { base64::decode(v) - .map_err(|_| E::invalid_value(de::Unexpected::Str(v), &self)) + .map_err(E::custom) .and_then(|b| { b.try_into() .map_err(|b: Vec| E::invalid_length(b.len(), &self)) diff --git a/cosmwasm/packages/wormhole-bindings/Cargo.toml b/cosmwasm/packages/wormhole-bindings/Cargo.toml index 0fe0256b9..dad2eb461 100644 --- a/cosmwasm/packages/wormhole-bindings/Cargo.toml +++ b/cosmwasm/packages/wormhole-bindings/Cargo.toml @@ -4,9 +4,14 @@ version = "0.1.0" authors = ["Wormhole Project Contributors"] edition = "2021" +[features] +fake = ["dep:cw-multi-test", "dep:k256"] + [dependencies] anyhow = "1" cosmwasm-schema = "1" cosmwasm-std = "1" schemars = "0.8.8" serde = { version = "1.0.137", default-features = false, features = ["derive"] } +cw-multi-test = { version = "0.13.2", optional = true } +k256 = { version = "0.9.4", optional = true } diff --git a/cosmwasm/packages/wormhole-bindings/src/fake.rs b/cosmwasm/packages/wormhole-bindings/src/fake.rs new file mode 100644 index 000000000..675f81fa3 --- /dev/null +++ b/cosmwasm/packages/wormhole-bindings/src/fake.rs @@ -0,0 +1,234 @@ +use std::{cell::RefCell, collections::BTreeSet, fmt::Debug, rc::Rc}; + +use anyhow::{anyhow, bail, ensure}; +use cosmwasm_std::{to_binary, Addr, Api, Binary, BlockInfo, CustomQuery, Empty, Querier, Storage}; +use cw_multi_test::{AppResponse, CosmosRouter, Module}; +use k256::ecdsa::{ + self, + signature::{Signature as SigT, Signer, Verifier}, + SigningKey, +}; +use schemars::JsonSchema; +use serde::de::DeserializeOwned; + +use crate::{Signature, WormholeQuery}; + +#[derive(Debug)] +struct Inner { + index: u32, + expiration: u64, + guardians: [SigningKey; 7], +} + +#[derive(Clone, Debug)] +pub struct WormholeKeeper(Rc>); + +impl WormholeKeeper { + pub fn new() -> WormholeKeeper { + let guardians = [ + SigningKey::from_bytes(&[ + 93, 217, 189, 224, 168, 81, 157, 93, 238, 38, 143, 8, 182, 94, 69, 77, 232, 199, + 238, 206, 15, 135, 221, 58, 43, 74, 0, 129, 54, 198, 62, 226, + ]) + .unwrap(), + SigningKey::from_bytes(&[ + 150, 48, 135, 223, 194, 186, 243, 139, 177, 8, 126, 32, 210, 57, 42, 28, 29, 102, + 196, 201, 106, 136, 40, 149, 218, 150, 240, 213, 192, 128, 161, 245, + ]) + .unwrap(), + SigningKey::from_bytes(&[ + 121, 51, 199, 93, 237, 227, 62, 220, 128, 129, 195, 4, 190, 163, 254, 12, 212, 224, + 188, 76, 141, 242, 229, 121, 192, 5, 161, 176, 136, 99, 83, 53, + ]) + .unwrap(), + SigningKey::from_bytes(&[ + 224, 180, 4, 114, 215, 161, 184, 12, 218, 96, 20, 141, 154, 242, 46, 230, 167, 165, + 54, 141, 108, 64, 146, 27, 193, 89, 251, 139, 234, 132, 124, 30, + ]) + .unwrap(), + SigningKey::from_bytes(&[ + 69, 1, 17, 179, 19, 47, 56, 47, 255, 219, 143, 89, 115, 54, 242, 209, 163, 131, + 225, 30, 59, 195, 217, 141, 167, 253, 6, 95, 252, 52, 7, 223, + ]) + .unwrap(), + SigningKey::from_bytes(&[ + 181, 3, 165, 125, 15, 200, 155, 56, 157, 204, 105, 221, 203, 149, 215, 175, 220, + 228, 200, 37, 169, 39, 68, 127, 132, 196, 203, 232, 155, 55, 67, 253, + ]) + .unwrap(), + SigningKey::from_bytes(&[ + 72, 81, 175, 107, 23, 108, 178, 66, 32, 53, 14, 117, 233, 33, 114, 102, 68, 89, 83, + 201, 129, 57, 56, 130, 214, 212, 172, 16, 23, 22, 234, 160, + ]) + .unwrap(), + ]; + WormholeKeeper(Rc::new(RefCell::new(Inner { + index: 0, + expiration: 0, + guardians, + }))) + } + + pub fn sign(&self, msg: &[u8]) -> Vec { + self.0 + .borrow() + .guardians + .iter() + .map(|g| { + >::sign(g, msg) + .as_bytes() + .to_vec() + .into() + }) + .enumerate() + .map(|(idx, sig)| Signature { + index: idx as u8, + signature: sig, + }) + .collect() + } + + pub fn verify_quorum( + &self, + data: &[u8], + index: u32, + signatures: &[Signature], + block_time: u64, + ) -> anyhow::Result { + let mut signers = BTreeSet::new(); + for s in signatures { + self.verify_signature(data, index, s, block_time)?; + signers.insert(s.index); + } + + if signers.len() as u32 >= self.calculate_quorum(index, block_time)? { + Ok(Empty {}) + } else { + Err(anyhow!("no quorum")) + } + } + + pub fn verify_signature( + &self, + data: &[u8], + index: u32, + sig: &Signature, + block_time: u64, + ) -> anyhow::Result { + let this = self.0.borrow(); + ensure!(this.index == index, "invalid guardian set"); + ensure!( + this.expiration == 0 || block_time < this.expiration, + "guardian set expired" + ); + + if let Some(g) = this.guardians.get(sig.index as usize) { + let s = ecdsa::Signature::try_from(&*sig.signature).unwrap(); + g.verifying_key() + .verify(data, &s) + .map(|()| Empty {}) + .map_err(From::from) + } else { + Err(anyhow!("invalid guardian index")) + } + } + + pub fn calculate_quorum(&self, index: u32, block_time: u64) -> anyhow::Result { + let this = self.0.borrow(); + ensure!(this.index == index, "invalid guardian set"); + ensure!( + this.expiration == 0 || block_time < this.expiration, + "guardian set expired" + ); + + Ok(((this.guardians.len() as u32 * 10 / 3) * 2) / 10 + 1) + } + + pub fn query(&self, request: WormholeQuery, block: &BlockInfo) -> anyhow::Result { + match request { + WormholeQuery::VerifyQuorum { + data, + guardian_set_index, + signatures, + } => self + .verify_quorum(&data, guardian_set_index, &signatures, block.height) + .and_then(|e| to_binary(&e).map_err(From::from)), + WormholeQuery::VerifySignature { + data, + guardian_set_index, + signature, + } => self + .verify_signature(&data, guardian_set_index, &signature, block.height) + .and_then(|e| to_binary(&e).map_err(From::from)), + WormholeQuery::CalculateQuorum { guardian_set_index } => self + .calculate_quorum(guardian_set_index, block.height) + .and_then(|q| to_binary(&q).map_err(From::from)), + } + } + + pub fn expiration(&self) -> u64 { + self.0.borrow().expiration + } + + pub fn set_expiration(&self, expiration: u64) { + self.0.borrow_mut().expiration = expiration; + } + + pub fn guardian_set_index(&self) -> u32 { + self.0.borrow().index + } + + pub fn set_index(&self, index: u32) { + self.0.borrow_mut().index = index; + } +} + +impl Default for WormholeKeeper { + fn default() -> Self { + Self::new() + } +} + +impl Module for WormholeKeeper { + type ExecT = Empty; + type QueryT = WormholeQuery; + type SudoT = Empty; + + fn execute( + &self, + _api: &dyn Api, + _storage: &mut dyn Storage, + _router: &dyn CosmosRouter, + _block: &BlockInfo, + sender: Addr, + msg: Self::ExecT, + ) -> anyhow::Result + where + ExecC: Debug + Clone + PartialEq + JsonSchema + DeserializeOwned + 'static, + QueryC: CustomQuery + DeserializeOwned + 'static, + { + bail!("Unexpected exec msg {msg:?} from {sender}") + } + + fn sudo( + &self, + _api: &dyn Api, + _storage: &mut dyn Storage, + _router: &dyn CosmosRouter, + _block: &BlockInfo, + msg: Self::SudoT, + ) -> anyhow::Result { + bail!("Unexpected sudo msg {msg:?}") + } + + fn query( + &self, + _api: &dyn Api, + _storage: &dyn Storage, + _querier: &dyn Querier, + block: &BlockInfo, + request: Self::QueryT, + ) -> anyhow::Result { + self.query(request, block) + } +} diff --git a/cosmwasm/packages/wormhole-bindings/src/lib.rs b/cosmwasm/packages/wormhole-bindings/src/lib.rs index 68ef083c6..52cf7af7e 100644 --- a/cosmwasm/packages/wormhole-bindings/src/lib.rs +++ b/cosmwasm/packages/wormhole-bindings/src/lib.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "fake")] +pub mod fake; mod query; pub use query::*; diff --git a/cosmwasm/tools/deploy.js b/cosmwasm/tools/deploy.js index 111edd1ac..5a57d7839 100644 --- a/cosmwasm/tools/deploy.js +++ b/cosmwasm/tools/deploy.js @@ -22,6 +22,7 @@ const artifacts = [ "mock_bridge_integration_2.wasm", "shutdown_core_bridge_cosmwasm.wasm", "shutdown_token_bridge_cosmwasm.wasm", + "wormchain_accounting.wasm", ]; /* Check that the artifact folder contains all the wasm files we expect and nothing else */