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 */